From 1351b686d450112ae55d26d81d9d59c5f542f12e Mon Sep 17 00:00:00 2001 From: Nikias Bassen Date: Sun, 10 May 2009 08:27:53 -0700 Subject: NotificationProxy support added. [#27 state:resolved] Signed-off-by: Matt Colyer --- src/NotificationProxy.c | 404 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 299 insertions(+), 105 deletions(-) (limited to 'src/NotificationProxy.c') diff --git a/src/NotificationProxy.c b/src/NotificationProxy.c index bf837bf..d8bcc34 100644 --- a/src/NotificationProxy.c +++ b/src/NotificationProxy.c @@ -21,10 +21,16 @@ #include #include +#include #include #include "NotificationProxy.h" #include "utils.h" +struct np_thread { + iphone_np_client_t client; + iphone_np_notify_cb_t cbfunc; +}; + /** Locks an NP client, done for thread safety stuff. * * @param client The NP @@ -45,6 +51,54 @@ static void np_unlock(iphone_np_client_t client) g_mutex_unlock(client->mutex); } +/** + * Sends an xml plist to the device using the connection specified in client. + * This function is only used internally. + * + * @param client NP to send data to + * @param dict plist to send + * + * @return IPHONE_E_SUCCESS or an error code. + */ +static iphone_error_t np_plist_send(iphone_np_client_t client, plist_t dict) +{ + char *XML_content = NULL; + uint32_t length = 0; + uint32_t nlen = 0; + int bytes = 0; + iphone_error_t res = IPHONE_E_UNKNOWN_ERROR; + + if (!client || !dict) { + return IPHONE_E_INVALID_ARG; + } + + plist_to_xml(dict, &XML_content, &length); + + if (!XML_content || length == 0) { + return IPHONE_E_PLIST_ERROR; + } + + nlen = htonl(length); + iphone_mux_send(client->connection, (const char*)&nlen, sizeof(nlen), (uint32_t*)&bytes); + if (bytes == sizeof(nlen)) { + iphone_mux_send(client->connection, XML_content, length, (uint32_t*)&bytes); + if (bytes > 0) { + if ((uint32_t)bytes == length) { + res = IPHONE_E_SUCCESS; + } else { + log_debug_msg("%s: ERROR: Could not send all data (%d of %d)!\n", __func__, bytes, length); + } + } + } + if (bytes <= 0) { + log_debug_msg("%s: ERROR: sending to device failed.\n", __func__); + } + + free(XML_content); + + return res; +} + /** Makes a connection to the NP service on the phone. * * @param phone The iPhone to connect on. @@ -53,7 +107,7 @@ static void np_unlock(iphone_np_client_t client) * * @return A handle to the newly-connected client or NULL upon error. */ -iphone_error_t iphone_np_new_client(iphone_device_t device, int src_port, int dst_port, iphone_np_client_t * client) +iphone_error_t iphone_np_new_client ( iphone_device_t device, int src_port, int dst_port, iphone_np_client_t *client ) { int ret = IPHONE_E_SUCCESS; @@ -75,6 +129,8 @@ iphone_error_t iphone_np_new_client(iphone_device_t device, int src_port, int ds client_loc->mutex = g_mutex_new(); + client_loc->notifier = NULL; + *client = client_loc; return IPHONE_E_SUCCESS; } @@ -83,91 +139,97 @@ iphone_error_t iphone_np_new_client(iphone_device_t device, int src_port, int ds * * @param client The client to disconnect. */ -iphone_error_t iphone_np_free_client(iphone_np_client_t client) +iphone_error_t iphone_np_free_client ( iphone_np_client_t client ) { - if (!client || !client->connection) + if (!client) return IPHONE_E_INVALID_ARG; - iphone_mux_free_client(client->connection); + if (client->connection) { + iphone_mux_free_client(client->connection); + client->connection = NULL; + if (client->notifier) { + log_debug_msg("joining np callback\n"); + g_thread_join(client->notifier); + } + } + if (client->mutex) { + g_mutex_free(client->mutex); + } free(client); + return IPHONE_E_SUCCESS; } -/** Sends a notification to the NP client. +/** Sends a notification to the device's Notification Proxy. * * notification messages seen so far: * com.apple.itunes-mobdev.syncWillStart * com.apple.itunes-mobdev.syncDidStart * * @param client The client to send to - * @param notification The notification Message + * @param notification The notification message to send */ -iphone_error_t iphone_np_post_notification(iphone_np_client_t client, const char *notification) +iphone_error_t iphone_np_post_notification( iphone_np_client_t client, const char *notification ) { - char *XML_content = NULL; - uint32_t length = 0; - int bytes = 0; - iphone_error_t ret; - unsigned char sndbuf[4096]; - int sndlen = 0; - int nlen = 0; - plist_t dict = NULL; - if (!client || !notification) { return IPHONE_E_INVALID_ARG; } np_lock(client); - dict = plist_new_dict(); + plist_t dict = plist_new_dict(); plist_add_sub_key_el(dict, "Command"); plist_add_sub_string_el(dict, "PostNotification"); plist_add_sub_key_el(dict, "Name"); plist_add_sub_string_el(dict, notification); - plist_to_xml(dict, &XML_content, &length); - - nlen = htonl(length); - - memcpy(sndbuf + sndlen, &nlen, 4); - sndlen += 4; - memcpy(sndbuf + sndlen, XML_content, length); - sndlen += length; + iphone_error_t res = np_plist_send(client, dict); plist_free(dict); - dict = NULL; - free(XML_content); - XML_content = NULL; dict = plist_new_dict(); plist_add_sub_key_el(dict, "Command"); plist_add_sub_string_el(dict, "Shutdown"); - plist_to_xml(dict, &XML_content, &length); - nlen = htonl(length); + res = np_plist_send(client, dict); + plist_free(dict); - memcpy(sndbuf + sndlen, &nlen, 4); - sndlen += 4; + if (res != IPHONE_E_SUCCESS) { + log_debug_msg("%s: Error sending XML plist to device!\n", __func__); + } - memcpy(sndbuf + sndlen, XML_content, length); - sndlen += length; + np_unlock(client); + return res; +} - plist_free(dict); - dict = NULL; - free(XML_content); - XML_content = NULL; +/** Notifies the iphone to send a notification on the specified event. + * + * @param client The client to send to + * @param notification The notifications that should be observed. + */ +iphone_error_t iphone_np_observe_notification( iphone_np_client_t client, const char *notification ) +{ + if (!client || !notification) { + return IPHONE_E_INVALID_ARG; + } + np_lock(client); - log_debug_buffer(sndbuf, sndlen); + plist_t dict = plist_new_dict(); + plist_add_sub_key_el(dict, "Command"); + plist_add_sub_string_el(dict, "ObserveNotification"); + plist_add_sub_key_el(dict, "Name"); + plist_add_sub_string_el(dict, notification); - iphone_mux_send(client->connection, sndbuf, sndlen, &bytes); - if (bytes <= 0) { - np_unlock(client); - return bytes; + iphone_error_t res = np_plist_send(client, dict); + if (res != IPHONE_E_SUCCESS) { + log_debug_msg("%s: Error sending XML plist to device!\n", __func__); } + plist_free(dict); np_unlock(client); - return bytes; + return res; } -/** Notifies the iphone to send a notification on certain events. + +/** Notifies the iphone to send a notification on specified events. * * observation messages seen so far: * com.apple.itunes-client.syncCancelRequest @@ -181,85 +243,217 @@ iphone_error_t iphone_np_post_notification(iphone_np_client_t client, const char * com.apple.mobile.application_uninstalled * * @param client The client to send to + * @param notification_spec Specification of the notifications that should be + * observed. This is expected to be an array of const char* that MUST have a + * terminating NULL entry. However this parameter can be NULL; in this case, + * the default set of notifications will be used. */ -iphone_error_t iphone_np_observe_notification(iphone_np_client_t client) +iphone_error_t iphone_np_observe_notifications( iphone_np_client_t client, const char **notification_spec ) { - plist_t dict = NULL; - char *XML_content = NULL; - uint32_t length = 0; - int bytes = 0; - iphone_error_t ret; - unsigned char sndbuf[4096]; - int sndlen = 0; - int nlen = 0; int i = 0; - const char *notifications[10] = { - "com.apple.itunes-client.syncCancelRequest", - "com.apple.itunes-client.syncSuspendRequest", - "com.apple.itunes-client.syncResumeRequest", - "com.apple.mobile.lockdown.phone_number_changed", - "com.apple.mobile.lockdown.device_name_changed", - "com.apple.springboard.attemptactivation", - "com.apple.mobile.data_sync.domain_changed", - "com.apple.mobile.application_installed", - "com.apple.mobile.application_uninstalled", - NULL - }; - - sndlen = 0; + iphone_error_t res = IPHONE_E_UNKNOWN_ERROR; + const char **notifications = notification_spec; if (!client) { return IPHONE_E_INVALID_ARG; } - np_lock(client); + + if (!notifications) { + notifications = np_default_notifications; + } while (notifications[i]) { + res = iphone_np_observe_notification(client, notifications[i]); + if (res != IPHONE_E_SUCCESS) { + break; + } + i++; + } + + return res; +} - dict = plist_new_dict(); - plist_add_sub_key_el(dict, "Command"); - plist_add_sub_string_el(dict, "ObserveNotification"); - plist_add_sub_key_el(dict, "Name"); - plist_add_sub_string_el(dict, notifications[i++]); - plist_to_xml(dict, &XML_content, &length); - - nlen = htonl(length); - memcpy(sndbuf + sndlen, &nlen, 4); - sndlen += 4; - memcpy(sndbuf + sndlen, XML_content, length); - sndlen += length; - - plist_free(dict); - dict = NULL; - free(XML_content); - XML_content = NULL; +/** + * Checks if a notification has been sent. + * + * @param client NP to get a notification from + * @param notification Pointer to a buffer that will be allocated and filled + * with the notification that has been received. + * + * @return IPHONE_E_SUCCESS if a notification has been received, + * IPHONE_E_TIMEOUT if nothing has been received, + * or an error value if an error occured. + * + * @note You probably want to check out iphone_np_set_notify_callback + * @see iphone_np_set_notify_callback + */ +iphone_error_t iphone_np_get_notification( iphone_np_client_t client, char **notification ) +{ + uint32_t bytes = 0; + iphone_error_t res; + uint32_t pktlen = 0; + char *XML_content = NULL; + plist_t dict = NULL; + + if (!client || !client->connection || *notification) { + return IPHONE_E_INVALID_ARG; } - dict = plist_new_dict(); - plist_add_sub_key_el(dict, "Command"); - plist_add_sub_string_el(dict, "Shutdown"); - plist_to_xml(dict, &XML_content, &length); + np_lock(client); - nlen = htonl(length); + iphone_mux_recv_timeout(client->connection, (char*)&pktlen, sizeof(pktlen), &bytes, 500); + log_debug_msg("NotificationProxy: initial read=%i\n", bytes); + if (bytes < 4) { + log_debug_msg("NotificationProxy: no notification received!\n"); + res = IPHONE_E_TIMEOUT; + } else { + if ((char)pktlen == 0) { + pktlen = ntohl(pktlen); + log_debug_msg("NotificationProxy: %d bytes following\n", pktlen); + XML_content = (char*)malloc(pktlen); + log_debug_msg("pointer %p\n", XML_content); + + iphone_mux_recv_timeout(client->connection, XML_content, pktlen, &bytes, 1000); + if (bytes <= 0) { + res = IPHONE_E_UNKNOWN_ERROR; + } else { + log_debug_msg("NotificationProxy: received data:\n"); + log_debug_buffer(XML_content, pktlen); + + plist_from_xml(XML_content, bytes, &dict); + if (!dict) { + np_unlock(client); + return IPHONE_E_PLIST_ERROR; + } + + plist_t cmd_key_node = plist_find_node_by_key(dict, "Command"); + plist_t cmd_value_node = plist_get_next_sibling(cmd_key_node); + char *cmd_value = NULL; + + if (plist_get_node_type(cmd_value_node) == PLIST_STRING) { + plist_get_string_val(cmd_value_node, &cmd_value); + } + + if (cmd_value && !strcmp(cmd_value, "RelayNotification")) { + plist_t name_key_node = plist_get_next_sibling(cmd_value_node); + plist_t name_value_node = plist_get_next_sibling(name_key_node); + + char *name_key = NULL; + char *name_value = NULL; + + if (plist_get_node_type(name_key_node) == PLIST_KEY) { + plist_get_key_val(name_key_node, &name_key); + } + if (plist_get_node_type(name_value_node) == PLIST_STRING) { + plist_get_string_val(name_value_node, &name_value); + } + + res = IPHONE_E_PLIST_ERROR; + if (name_key && name_value && !strcmp(name_key, "Name")) { + *notification = name_value; + log_debug_msg("%s: got notification %s\n", __func__, name_value); + res = IPHONE_E_SUCCESS; + } + free(name_key); + } else if (cmd_value && !strcmp(cmd_value, "ProxyDeath")) { + log_debug_msg("%s: ERROR: NotificationProxy died!\n", __func__); + res = IPHONE_E_UNKNOWN_ERROR; + } else if (cmd_value) { + log_debug_msg("%d: unknown NotificationProxy command '%s' received!\n", __func__); + res = IPHONE_E_UNKNOWN_ERROR; + } else { + res = IPHONE_E_PLIST_ERROR; + } + if (cmd_value) { + free(cmd_value); + } + plist_free(dict); + dict = NULL; + free(XML_content); + XML_content = NULL; + } + } else { + res = IPHONE_E_UNKNOWN_ERROR; + } + } - memcpy(sndbuf + sndlen, &nlen, 4); - sndlen += 4; + np_unlock(client); - memcpy(sndbuf + sndlen, XML_content, length); - sndlen += length; + return res; +} - plist_free(dict); - dict = NULL; - free(XML_content); - XML_content = NULL; +/** + * Internally used thread function. + */ +gpointer iphone_np_notifier( gpointer arg ) +{ + char *notification = NULL; + struct np_thread *npt = (struct np_thread*)arg; + + if (!npt) return NULL; + + log_debug_msg("%s: starting callback.\n", __func__); + while (npt->client->connection) { + iphone_np_get_notification(npt->client, ¬ification); + if (notification) { + npt->cbfunc(notification); + free(notification); + notification = NULL; + } + sleep(1); + } + if (npt) { + free(npt); + } - log_debug_buffer(sndbuf, sndlen); + return NULL; +} - iphone_mux_send(client->connection, sndbuf, sndlen, &bytes); - if (bytes <= 0) { - np_unlock(client); - return bytes; +/** + * This function allows an application to define a callback function that will + * be called when a notification has been received. + * It will start a thread that polls for notifications and calls the callback + * function if a notification has been received. + * + * @param client the NP client + * @param notify_cb pointer to a callback function or NULL to de-register a + * previously set callback function + * + * @return IPHONE_E_SUCCESS when the callback was successfully registered, + * or an error value when an error occured. + */ +iphone_error_t iphone_np_set_notify_callback( iphone_np_client_t client, iphone_np_notify_cb_t notify_cb ) +{ + if (!client) { + return IPHONE_E_INVALID_ARG; + } + iphone_error_t res = IPHONE_E_UNKNOWN_ERROR; + + np_lock(client); + if (client->notifier) { + log_debug_msg("%s: callback already set, removing\n"); + iphone_umux_client_t conn = client->connection; + client->connection = NULL; + g_thread_join(client->notifier); + client->notifier = NULL; + client->connection = conn; } + if (notify_cb) { + struct np_thread *npt = (struct np_thread*)malloc(sizeof(struct np_thread)); + if (npt) { + npt->client = client; + npt->cbfunc = notify_cb; + + client->notifier = g_thread_create(iphone_np_notifier, npt, TRUE, NULL); + if (client->notifier) { + res = IPHONE_E_SUCCESS; + } + } + } else { + log_debug_msg("%s: no callback set\n", __func__); + } np_unlock(client); - return bytes; + + return res; } -- cgit v1.1-32-gdbae