diff options
-rw-r--r-- | libdleyna/server/device.c | 333 |
1 files changed, 324 insertions, 9 deletions
diff --git a/libdleyna/server/device.c b/libdleyna/server/device.c index d5d5bf8..cc842ee 100644 --- a/libdleyna/server/device.c +++ b/libdleyna/server/device.c @@ -52,6 +52,8 @@ #define DLS_UPLOAD_STATUS_ERROR "ERROR" #define DLS_UPLOAD_STATUS_COMPLETED "COMPLETED" +#define DLS_DEFAULT_WAKE_PORT 9 + typedef gboolean(*dls_device_count_cb_t)(dls_async_task_t *cb_data, gint count); @@ -94,6 +96,16 @@ struct dls_device_download_t_ { dls_async_task_t *task; }; +typedef struct dls_tcp_wake_t_ dls_tcp_wake_t; +struct dls_tcp_wake_t_ { + GOutputStream *output_stream; + GSocketConnection *socket_connection; + guint8 *buffer; + gssize to_send; + gssize sent; + dls_async_task_t *task; +}; + /* Private structure used in chain task */ typedef struct prv_new_device_ct_t_ prv_new_device_ct_t; struct prv_new_device_ct_t_ { @@ -5399,24 +5411,270 @@ end: (void) g_idle_add(dls_async_task_complete, cb_data); } +static void prv_free_tcp_data(dls_tcp_wake_t *tcp_data) +{ + if (tcp_data != NULL) { + g_free(tcp_data->buffer); + + g_object_unref(tcp_data->socket_connection); + + g_object_unref(tcp_data->output_stream); + + g_free(tcp_data); + } +} + +static void tcp_wake_cb(GObject *source, GAsyncResult *result, + gpointer user_data) +{ + dls_tcp_wake_t *tcp_data = (dls_tcp_wake_t *)user_data; + dls_async_task_t *cb_data = (dls_async_task_t *)tcp_data->task; + GError *tcp_error = NULL; + gssize written; + + DLEYNA_LOG_DEBUG("Enter"); + + if (tcp_data->socket_connection == NULL) { + tcp_data->socket_connection = + g_socket_client_connect_to_host_finish( + G_SOCKET_CLIENT(source), + result, &tcp_error); + + g_object_unref(source); + + g_object_unref(result); + + if (tcp_data->socket_connection == NULL) { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_IO, + "Failed to connect"); + if (tcp_error) { + DLEYNA_LOG_WARNING("Failed to connect: %s", + tcp_error->message); + g_error_free(tcp_error); + } + + goto on_complete; + } + + tcp_data->output_stream = + g_io_stream_get_output_stream( + G_IO_STREAM(tcp_data->socket_connection)); + + goto on_write; + } + + written = g_output_stream_write_finish(G_OUTPUT_STREAM(source), result, + &tcp_error); + + if (written < 0) { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_IO, + "Failed to write"); + if (tcp_error) { + DLEYNA_LOG_WARNING("Failed to write: %s", + tcp_error->message); + g_error_free(tcp_error); + } + + goto on_complete; + } + + tcp_data->sent += written; + + if (tcp_data->sent == tcp_data->to_send) + goto on_complete; + +on_write: + g_output_stream_write_async(tcp_data->output_stream, + tcp_data->buffer + tcp_data->sent, + tcp_data->to_send - tcp_data->sent, + G_PRIORITY_DEFAULT, + cb_data->cancellable, + tcp_wake_cb, tcp_data); + + goto on_exit; + +on_complete: + prv_free_tcp_data(tcp_data); + + if (!g_cancellable_is_cancelled(cb_data->cancellable)) + (void) g_idle_add(dls_async_task_complete, cb_data); + + g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); + +on_exit: + DLEYNA_LOG_DEBUG("Exit"); + + return; +} + +static gboolean prv_hex_char_to_byte(const gchar hex_char, uint8_t *byte) +{ + gchar ch; + gboolean result = TRUE; + + if (!g_ascii_isxdigit(hex_char)) + goto on_exit; + + ch = g_ascii_toupper(hex_char); + + if (ch >= '0' && ch <= '9') + *byte = ch - '0'; + else if (ch >= 'A' && ch <= 'F') + *byte = 10 + ch - 'A'; + +on_exit: + return result; +} + +static uint8_t *prv_hex_str_to_bin(const gchar *hex_str, gsize *out_len) +{ + gsize i; + gsize j; + uint8_t *buffer = NULL; + uint8_t byte; + gsize len; + + len = strlen(hex_str); + + if (len % 2 != 0) { + DLEYNA_LOG_WARNING("Invalid Hex String"); + goto on_exit; + } + + buffer = g_malloc(len / 2); + + for (i = 0, j = 0; i < len; i += 2, j++) { + if (!prv_hex_char_to_byte(hex_str[i], &buffer[j])) + goto on_error; + + if (!prv_hex_char_to_byte(hex_str[i+1], &byte)) + goto on_error; + + buffer[j] = (buffer[j] << 4) + byte; + } + + *out_len = j; + + goto on_exit; + +on_error: + g_free(buffer); + buffer = NULL; + +on_exit: + return buffer; +} + +static void prv_device_wake_tcp(guint8 *packet, gsize packet_len, + const gchar *host, + dls_async_task_t *cb_data) +{ + GSocketClient *socket_client; + dls_tcp_wake_t *tcp_data; + + socket_client = g_socket_client_new(); + + tcp_data = g_new0(dls_tcp_wake_t, 1); + tcp_data->task = cb_data; + tcp_data->buffer = packet; + tcp_data->to_send = packet_len; + + cb_data->cancel_id = + g_cancellable_connect(cb_data->cancellable, + G_CALLBACK(dls_async_task_cancelled_cb), + cb_data, NULL); + + g_socket_client_connect_to_host_async(socket_client, + host, DLS_DEFAULT_WAKE_PORT, + cb_data->cancellable, + tcp_wake_cb, tcp_data); +} + +static GError *prv_device_wake_udp(guint8 *packet, gsize packet_len, + GInetAddress *host_inet_address, + GSocketFamily socket_family, + gboolean broadcast) +{ + GSocket *socket; + GError *send_error; + GError *error = NULL; + gssize bytes_sent; + GSocketAddress *host_address = NULL; + + socket = g_socket_new(socket_family, + G_SOCKET_TYPE_DATAGRAM, + G_SOCKET_PROTOCOL_UDP, NULL); + + if (socket == NULL) { + error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_IO, + "Cannot create UDP socket"); + goto on_complete; + } + + host_address = g_inet_socket_address_new(host_inet_address, + DLS_DEFAULT_WAKE_PORT); + + g_socket_set_blocking(socket, FALSE); + + if (broadcast) + g_socket_set_broadcast(socket, broadcast); + + bytes_sent = g_socket_send_to(socket, host_address, + (const gchar *)packet, packet_len, + NULL, &send_error); + + if (bytes_sent == -1) { + error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_IO, + "Failed to send UDP packet"); + + DLEYNA_LOG_WARNING("Failed to send UDP packet: %s", + send_error->message); + + g_error_free(send_error); + } + +on_complete: + if (socket) { + g_socket_close(socket, NULL); + + g_object_unref(socket); + } + + if (host_address) + g_object_unref(host_address); + + return error; +} + + void dls_device_wake(dls_client_t *client, dls_task_t *task) { dls_device_context_t *context; dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_device_t *device = task->target.device; dls_network_if_info_t *info; - GList *next; + GSocketFamily socket_family; + GSocketProtocol socket_protocol; + GInetAddress *host_inet_address = NULL; + gboolean broadcast = FALSE; + gsize packet_len; + guint8 *packet = NULL; DLEYNA_LOG_DEBUG("Enter"); context = dls_device_get_context(device, client); + if (!device->sleeping) + goto on_complete; + if ((context->ems.proxy == NULL) || (context->network_if_info == NULL)) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_NOT_SUPPORTED, - "WOL is not supported"); - goto end; + "Wake is not supported"); + goto on_complete; } info = context->network_if_info; @@ -5427,17 +5685,74 @@ void dls_device_wake(dls_client_t *client, dls_task_t *task) DLEYNA_LOG_DEBUG("WakeOnPattern = %s", info->wake_on_pattern); DLEYNA_LOG_DEBUG("WakeSupportedTransport = %s", info->wake_transport); - next = info->ip_addresses; - while (next != NULL) { - DLEYNA_LOG_DEBUG("IpAddress = %s", (gchar *)next->data); + if (!strcmp(info->wake_transport, "UDP-Broadcast")) { + socket_protocol = G_SOCKET_PROTOCOL_UDP; + broadcast = TRUE; + } else if (!strcmp(info->wake_transport, "UDP-Unicast")) { + socket_protocol = G_SOCKET_PROTOCOL_UDP; + } else if (!strcmp(info->wake_transport, "TCP-Unicast")) { + socket_protocol = G_SOCKET_PROTOCOL_TCP; + } else { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_NOT_SUPPORTED, + "Unsupported wake transport"); + goto on_complete; + } - next = g_list_next(next); + DLEYNA_LOG_DEBUG("Sending WakeOn to IpAddress = %s", + context->ip_address); + + host_inet_address = g_inet_address_new_from_string(context->ip_address); + + if (host_inet_address == NULL) { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_HOST_FAILED, + "Invalid host address: %s", + context->ip_address); + goto on_complete; } - DLEYNA_LOG_DEBUG("context IpAddress = %s", context->ip_address); + socket_family = g_inet_address_get_family(host_inet_address); + + if ((socket_family != G_SOCKET_FAMILY_IPV4) && + (socket_family != G_SOCKET_FAMILY_IPV6)) { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_HOST_FAILED, + "Invalid host address family: %s", + context->ip_address); + goto on_complete; + } + + packet = prv_hex_str_to_bin(info->wake_on_pattern, &packet_len); + + if (packet == NULL) { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_HOST_FAILED, + "Invalid wake on pattern"); + goto on_complete; + } + + if (socket_protocol == G_SOCKET_PROTOCOL_UDP) { + cb_data->error = prv_device_wake_udp(packet, packet_len, + host_inet_address, + socket_family, broadcast); + } else { + prv_device_wake_tcp(packet, packet_len, context->ip_address, + cb_data); + + goto on_exit; + } + +on_complete: + if (host_inet_address != NULL) + g_object_unref(host_inet_address); + + g_free(packet); -end: (void) g_idle_add(dls_async_task_complete, cb_data); +on_exit: DLEYNA_LOG_DEBUG("Exit"); + + return; } |