summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Guiraud <christophe.guiraud@intel.com>2013-07-03 10:26:51 +0200
committerrmerlino <regis.merlino@intel.com>2013-12-12 14:50:33 +0100
commitdb713517e91add01c278ec89ba2d77cfb558b7de (patch)
treeed071c3711439031c202bf547a17cd41676555e3
parent13951f33b7a854364a05b7b38893261822cd0e83 (diff)
downloaddleyna-server-db713517e91add01c278ec89ba2d77cfb558b7de.tar.gz
[Device] Add wake packet sending support
- Add the wake method implementation A bytes buffer is computed from the wake on pattern hex string. The buffer is sent according to the device wake transport method. Wake transport methods TCP/UDP unicast and UDP broadcast are supported. If the device is not sleeping, the function returns with no error. The TCP data sending is implemented with GSocketClient. It is done asynchronously and so can be cancelled. UDP data sending uses GSocket. The default wake packet port 9 is used. Signed-off-by: Christophe Guiraud <christophe.guiraud@intel.com>
-rw-r--r--libdleyna/server/device.c333
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;
}