diff options
author | Nikias Bassen | 2009-05-10 08:27:53 -0700 |
---|---|---|
committer | Matt Colyer | 2009-05-10 08:28:04 -0700 |
commit | 1351b686d450112ae55d26d81d9d59c5f542f12e (patch) | |
tree | 442c7e48eb83f96a22f7c6d889e8fe26151d6cba /src | |
parent | e91caeb1c9929f9dc8af747bb3a2e52ec06b03af (diff) | |
download | libimobiledevice-1351b686d450112ae55d26d81d9d59c5f542f12e.tar.gz libimobiledevice-1351b686d450112ae55d26d81d9d59c5f542f12e.tar.bz2 |
NotificationProxy support added.0.9.0
[#27 state:resolved]
Signed-off-by: Matt Colyer <matt@colyer.name>
Diffstat (limited to 'src')
-rw-r--r-- | src/NotificationProxy.c | 404 | ||||
-rw-r--r-- | src/NotificationProxy.h | 16 | ||||
-rw-r--r-- | src/iphone.c | 5 | ||||
-rw-r--r-- | src/iphone.h | 2 | ||||
-rw-r--r-- | src/lockdown.c | 13 | ||||
-rw-r--r-- | src/usbmux.c | 31 |
6 files changed, 359 insertions, 112 deletions
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 <string.h> #include <stdio.h> +#include <arpa/inet.h> #include <plist/plist.h> #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; } diff --git a/src/NotificationProxy.h b/src/NotificationProxy.h index 7b4b48d..3552b79 100644 --- a/src/NotificationProxy.h +++ b/src/NotificationProxy.h @@ -27,4 +27,20 @@ struct iphone_np_client_int { iphone_umux_client_t connection; GMutex *mutex; + GThread *notifier; }; + +static const char *np_default_notifications[10] = { + NP_SYNC_SUSPEND_REQUEST, + NP_SYNC_RESUME_REQUEST, + NP_PHONE_NUMBER_CHANGED, + NP_SYNC_CANCEL_REQUEST, + NP_DEVICE_NAME_CHANGED, + NP_ATTEMPTACTIVATION, + NP_DS_DOMAIN_CHANGED, + NP_APP_INSTALLED, + NP_APP_UNINSTALLED, + NULL +}; + +gpointer iphone_np_notifier( gpointer arg ); diff --git a/src/iphone.c b/src/iphone.c index 3c3034e..9dd3c07 100644 --- a/src/iphone.c +++ b/src/iphone.c @@ -288,10 +288,11 @@ int send_to_phone(iphone_device_t phone, char *data, int datalen) * @param phone The iPhone to receive data from * @param data Where to put data read * @param datalen How much data to read in + * @param timeout How many milliseconds to wait for data * * @return How many bytes were read in, or -1 on error. */ -int recv_from_phone(iphone_device_t phone, char *data, int datalen) +int recv_from_phone(iphone_device_t phone, char *data, int datalen, int timeout) { if (!phone) return -1; @@ -301,7 +302,7 @@ int recv_from_phone(iphone_device_t phone, char *data, int datalen) return -1; log_debug_msg("recv_from_phone(): attempting to receive %i bytes\n", datalen); - bytes = usb_bulk_read(phone->device, BULKIN, data, datalen, 3500); + bytes = usb_bulk_read(phone->device, BULKIN, data, datalen, timeout); if (bytes < 0) { log_debug_msg("recv_from_phone(): libusb gave me the error %d: %s (%s)\n", bytes, usb_strerror(), strerror(-bytes)); diff --git a/src/iphone.h b/src/iphone.h index 222a1be..15515e3 100644 --- a/src/iphone.h +++ b/src/iphone.h @@ -41,5 +41,5 @@ struct iphone_device_int { // Function definitions int send_to_phone(iphone_device_t phone, char *data, int datalen); -int recv_from_phone(iphone_device_t phone, char *data, int datalen); +int recv_from_phone(iphone_device_t phone, char *data, int datalen, int timeout); #endif diff --git a/src/lockdown.c b/src/lockdown.c index c017cdf..5ade79a 100644 --- a/src/lockdown.c +++ b/src/lockdown.c @@ -472,6 +472,19 @@ iphone_error_t lockdownd_get_device_public_key(iphone_lckd_client_t control, gnu return lockdownd_generic_get_value(control, "Key", "DevicePublicKey", public_key); } +/** Askes for the device's name. + * + * @return IPHONE_E_SUCCESS on succes or an error value < 0 on failure. + */ +iphone_error_t lockdownd_get_device_name(iphone_lckd_client_t control, char **device_name) +{ + gnutls_datum_t temp = { NULL, 0 }; + iphone_error_t res = lockdownd_generic_get_value(control, "Key", "DeviceName", &temp); + log_debug_msg("%s: %s\n", __func__, temp.data); + *device_name = (char*)temp.data; + return res; +} + /** Completes the entire lockdownd handshake. * * @param phone The iPhone diff --git a/src/usbmux.c b/src/usbmux.c index 22ce588..7d74b4b 100644 --- a/src/usbmux.c +++ b/src/usbmux.c @@ -143,7 +143,7 @@ iphone_error_t iphone_mux_new_client(iphone_device_t device, uint16_t src_port, if (send_to_phone(device, (char *) new_connection->header, sizeof(usbmux_tcp_header)) >= 0) { usbmux_tcp_header *response; response = (usbmux_tcp_header *) malloc(sizeof(usbmux_tcp_header)); - bytes = recv_from_phone(device, (char *) response, sizeof(*response)); + bytes = recv_from_phone(device, (char *) response, sizeof(*response), 3500); if (response->tcp_flags != 0x12) { free(response); return IPHONE_E_UNKNOWN_ERROR; @@ -268,10 +268,13 @@ iphone_error_t iphone_mux_send(iphone_umux_client_t client, const char *data, ui * @param connection The connection to receive data on. * @param data Where to put the data we receive. * @param datalen How much data to read. + * @param recv_bytes Pointer to a uint32_t that will be set + * to the number of bytes received. + * @param timeout How many milliseconds to wait for data. * - * @return How many bytes were read, or -1 if something bad happens. + * @return IPHONE_E_SUCCESS on success, or and error value. */ -iphone_error_t iphone_mux_recv(iphone_umux_client_t client, char *data, uint32_t datalen, uint32_t * recv_bytes) +iphone_error_t iphone_mux_recv_timeout(iphone_umux_client_t client, char *data, uint32_t datalen, uint32_t * recv_bytes, int timeout) { if (!client || !data || datalen == 0 || !recv_bytes) @@ -323,7 +326,7 @@ iphone_error_t iphone_mux_recv(iphone_umux_client_t client, char *data, uint32_t buffer = (char *) malloc(sizeof(char) * 131072); // make sure we get enough ;) // See #3. - bytes = recv_from_phone(client->phone, buffer, 131072); + bytes = recv_from_phone(client->phone, buffer, 131072, timeout); if (bytes < 28) { free(buffer); log_debug_msg("mux_recv: Did not even get the header.\n"); @@ -385,3 +388,23 @@ iphone_error_t iphone_mux_recv(iphone_umux_client_t client, char *data, uint32_t log_debug_msg("mux_recv: Heisenbug: bytes and datalen not matching up\n"); return IPHONE_E_UNKNOWN_ERROR; } + +/** + * This function is just like 'iphone_mux_recv_timeout' but you do not need + * to specify a timeout. It simply calls iphone_mux_recv_timeout with a + * timeout value of 3500 milliseconds. + * + * @param connection The connection to receive data on. + * @param data Where to put the data we receive. + * @param datalen How much data to read. + * @param recv_bytes Pointer to a uint32_t that will be set + * to the number of bytes received. + * + * @return The return value of iphone_mux_recv_timeout. + * + * @see iphone_mux_recv_timeout + */ +iphone_error_t iphone_mux_recv(iphone_umux_client_t client, char *data, uint32_t datalen, uint32_t * recv_bytes) +{ + return iphone_mux_recv_timeout(client, data, datalen, recv_bytes, 3500); +} |