From 8a776e490d689174cb91d89f43972300d37d4091 Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Thu, 13 May 2010 22:13:30 +0200 Subject: Implement mobilesync API --- include/libimobiledevice/mobilesync.h | 43 +++ src/mobilesync.c | 585 ++++++++++++++++++++++++++++++++++ src/mobilesync.h | 8 + 3 files changed, 636 insertions(+) diff --git a/include/libimobiledevice/mobilesync.h b/include/libimobiledevice/mobilesync.h index 7af3aef..4943f66 100644 --- a/include/libimobiledevice/mobilesync.h +++ b/include/libimobiledevice/mobilesync.h @@ -3,6 +3,7 @@ * @brief MobileSync Implementation * \internal * + * Copyright (c) 2010 Bryan Forbes All Rights Reserved. * Copyright (c) 2009 Jonathan Beck All Rights Reserved. * * This library is free software; you can redistribute it and/or @@ -28,6 +29,7 @@ extern "C" { #endif #include +#include /** @name Error Codes */ /*@{*/ @@ -36,21 +38,62 @@ extern "C" { #define MOBILESYNC_E_PLIST_ERROR -2 #define MOBILESYNC_E_MUX_ERROR -3 #define MOBILESYNC_E_BAD_VERSION -4 +#define MOBILESYNC_E_SYNC_REFUSED -5 +#define MOBILESYNC_E_CANCELLED -6 +#define MOBILESYNC_E_WRONG_DIRECTION -7 +#define MOBILESYNC_E_NOT_READY -8 #define MOBILESYNC_E_UNKNOWN_ERROR -256 /*@}*/ +typedef enum { + MOBILESYNC_SYNC_TYPE_FAST, + MOBILESYNC_SYNC_TYPE_SLOW, + MOBILESYNC_SYNC_TYPE_RESET +} mobilesync_sync_type_t; + /** Represents an error code. */ typedef int16_t mobilesync_error_t; typedef struct mobilesync_client_private mobilesync_client_private; typedef mobilesync_client_private *mobilesync_client_t; /**< The client handle */ +typedef struct { + char *device_anchor; + char *computer_anchor; +} mobilesync_anchors; +typedef mobilesync_anchors *mobilesync_anchors_t; + +/* Interface */ mobilesync_error_t mobilesync_client_new(idevice_t device, uint16_t port, mobilesync_client_t * client); mobilesync_error_t mobilesync_client_free(mobilesync_client_t client); + mobilesync_error_t mobilesync_receive(mobilesync_client_t client, plist_t *plist); mobilesync_error_t mobilesync_send(mobilesync_client_t client, plist_t plist); +mobilesync_error_t mobilesync_start(mobilesync_client_t client, const char *data_class, mobilesync_anchors_t anchors, mobilesync_sync_type_t *sync_type, uint64_t *data_class_version); +mobilesync_error_t mobilesync_cancel(mobilesync_client_t client, const char* reason); +mobilesync_error_t mobilesync_finish(mobilesync_client_t client); + +mobilesync_error_t mobilesync_get_all_records_from_device(mobilesync_client_t client); +mobilesync_error_t mobilesync_get_changes_from_device(mobilesync_client_t client); + +mobilesync_error_t mobilesync_receive_changes(mobilesync_client_t client, plist_t *entities, uint8_t *is_last_record, plist_t *actions); +mobilesync_error_t mobilesync_acknowledge_changes_from_device(mobilesync_client_t client); + +mobilesync_error_t mobilesync_ready_to_send_changes_from_computer(mobilesync_client_t client); + +mobilesync_error_t mobilesync_send_changes(mobilesync_client_t client, plist_t entities, uint8_t is_last_record, plist_t actions); +mobilesync_error_t mobilesync_remap_identifiers(mobilesync_client_t client, plist_t *mapping); + +/* Helper */ +mobilesync_anchors_t mobilesync_anchors_new(const char *device_anchor, const char *computer_anchor); +void mobilesync_anchors_free(mobilesync_anchors_t anchors); + +plist_t mobilesync_actions_new(); +void mobilesync_actions_add(plist_t actions, ...) G_GNUC_NULL_TERMINATED; +void mobilesync_actions_free(plist_t actions); + #ifdef __cplusplus } #endif diff --git a/src/mobilesync.c b/src/mobilesync.c index b5b9453..db437c4 100644 --- a/src/mobilesync.c +++ b/src/mobilesync.c @@ -2,6 +2,7 @@ * mobilesync.c * Contains functions for the built-in MobileSync client. * + * Copyright (c) 2010 Bryan Forbes All Rights Reserved. * Copyright (c) 2009 Jonathan Beck All Rights Reserved. * * This library is free software; you can redistribute it and/or @@ -19,9 +20,14 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#define _GNU_SOURCE 1 +#define __USE_GNU 1 + #include #include #include +#include +#include #include "mobilesync.h" #include "device_link_service.h" @@ -30,6 +36,8 @@ #define MSYNC_VERSION_INT1 100 #define MSYNC_VERSION_INT2 100 +#define EMPTY_PARAMETER_STRING "___EmptyParameterString___" + /** * Convert an device_link_service_error_t value to an mobilesync_error_t value. * Used internally to get correct error codes when using device_link_service stuff. @@ -84,6 +92,8 @@ mobilesync_error_t mobilesync_client_new(idevice_t device, uint16_t port, mobilesync_client_t client_loc = (mobilesync_client_t) malloc(sizeof(struct mobilesync_client_private)); client_loc->parent = dlclient; + client_loc->direction = MOBILESYNC_SYNC_DIR_DEVICE_TO_COMPUTER; + client_loc->data_class = NULL; /* perform handshake */ ret = mobilesync_error(device_link_service_version_exchange(dlclient, MSYNC_VERSION_INT1, MSYNC_VERSION_INT2)); @@ -150,3 +160,578 @@ mobilesync_error_t mobilesync_send(mobilesync_client_t client, plist_t plist) return MOBILESYNC_E_INVALID_ARG; return mobilesync_error(device_link_service_send(client->parent, plist)); } + +mobilesync_error_t mobilesync_start(mobilesync_client_t client, const char *data_class, mobilesync_anchors_t anchors, mobilesync_sync_type_t *sync_type, uint64_t *data_class_version) +{ + if (!client || client->data_class || !data_class || + !anchors || !anchors->computer_anchor) { + return MOBILESYNC_E_INVALID_ARG; + } + + mobilesync_error_t err = MOBILESYNC_E_UNKNOWN_ERROR; + char *response_type = NULL; + char *sync_type_str = NULL; + plist_t msg = NULL; + plist_t response_type_node = NULL; + + msg = plist_new_array(); + plist_array_append_item(msg, plist_new_string("SDMessageSyncDataClassWithDevice")); + plist_array_append_item(msg, plist_new_string(data_class)); + if (anchors->device_anchor) { + plist_array_append_item(msg, plist_new_string(anchors->device_anchor)); + } else { + plist_array_append_item(msg, plist_new_string("---")); + } + plist_array_append_item(msg, plist_new_string(anchors->computer_anchor)); + plist_array_append_item(msg, plist_new_uint(*data_class_version)); + plist_array_append_item(msg, plist_new_string(EMPTY_PARAMETER_STRING)); + + err = mobilesync_send(client, msg); + + if (err != MOBILESYNC_E_SUCCESS) { + goto out; + } + + plist_free(msg); + msg = NULL; + + err = mobilesync_receive(client, &msg); + + if (err != MOBILESYNC_E_SUCCESS) { + goto out; + } + + response_type_node = plist_array_get_item(msg, 0); + if (!response_type_node) { + err = MOBILESYNC_E_PLIST_ERROR; + goto out; + } + + plist_get_string_val(response_type_node, &response_type); + if (!response_type) { + err = MOBILESYNC_E_PLIST_ERROR; + goto out; + } + + if (!strcmp(response_type, "SDMessageRefuseToSyncDataClassWithComputer")) { + char *reason = NULL; + err = MOBILESYNC_E_SYNC_REFUSED; + plist_get_string_val(plist_array_get_item(msg, 2), &reason); + debug_info("Device refused sync: %s", reason); + free(reason); + reason = NULL; + goto out; + } + + if (!strcmp(response_type, "SDMessageCancelSession")) { + char *reason = NULL; + err = MOBILESYNC_E_CANCELLED; + plist_get_string_val(plist_array_get_item(msg, 2), &reason); + debug_info("Device cancelled: %s", reason); + free(reason); + reason = NULL; + goto out; + } + + if (sync_type != NULL) { + plist_t sync_type_node = plist_array_get_item(msg, 4); + if (!sync_type_node) { + err = MOBILESYNC_E_PLIST_ERROR; + goto out; + } + + plist_get_string_val(sync_type_node, &sync_type_str); + if (!sync_type_str) { + err = MOBILESYNC_E_PLIST_ERROR; + goto out; + } + + if (!strcmp(sync_type_str, "SDSyncTypeFast")) { + *sync_type = MOBILESYNC_SYNC_TYPE_FAST; + } else if (!strcmp(sync_type_str, "SDSyncTypeSlow")) { + *sync_type = MOBILESYNC_SYNC_TYPE_SLOW; + } else if (!strcmp(sync_type_str, "SDSyncTypeReset")) { + *sync_type = MOBILESYNC_SYNC_TYPE_RESET; + } else { + err = MOBILESYNC_E_PLIST_ERROR; + goto out; + } + } + + if (data_class_version != NULL) { + plist_t data_class_version_node = plist_array_get_item(msg, 5); + if (!data_class_version_node) { + err = MOBILESYNC_E_PLIST_ERROR; + goto out; + } + + plist_get_uint_val(data_class_version_node, data_class_version); + } + + err = MOBILESYNC_E_SUCCESS; + + out: + if (sync_type_str) { + free(sync_type_str); + sync_type_str = NULL; + } + if (response_type) { + free(response_type); + response_type = NULL; + } + if (msg) { + plist_free(msg); + msg = NULL; + } + + client->data_class = strdup(data_class); + client->direction = MOBILESYNC_SYNC_DIR_DEVICE_TO_COMPUTER; + return err; +} + +mobilesync_error_t mobilesync_finish(mobilesync_client_t client) +{ + if (!client || !client->data_class) { + return MOBILESYNC_E_INVALID_ARG; + } + + mobilesync_error_t err = MOBILESYNC_E_UNKNOWN_ERROR; + + plist_t msg = NULL; + plist_t response_type_node = NULL; + char *response_type = NULL; + + msg = plist_new_array(); + plist_array_append_item(msg, plist_new_string("SDMessageFinishSessionOnDevice")); + plist_array_append_item(msg, plist_new_string(client->data_class)); + + err = mobilesync_send(client, msg); + + if (err != MOBILESYNC_E_SUCCESS) { + goto out; + } + + plist_free(msg); + msg = NULL; + + err = mobilesync_receive(client, &msg); + + if (err != MOBILESYNC_E_SUCCESS) { + goto out; + } + + response_type_node = plist_array_get_item(msg, 0); + if (!response_type_node) { + err = MOBILESYNC_E_PLIST_ERROR; + goto out; + } + + plist_get_string_val(response_type_node, &response_type); + if (!response_type) { + err = MOBILESYNC_E_PLIST_ERROR; + goto out; + } + + if (!strcmp(response_type, "SDMessageDeviceFinishedSession")) { + err = MOBILESYNC_E_SUCCESS; + } + + out: + if (response_type) { + free(response_type); + response_type = NULL; + } + if (msg) { + plist_free(msg); + msg = NULL; + } + + free(client->data_class); + client->data_class = NULL; + client->direction = MOBILESYNC_SYNC_DIR_DEVICE_TO_COMPUTER; + return err; +} + +static mobilesync_error_t mobilesync_get_records(mobilesync_client_t client, const char *operation) +{ + if (!client || !client->data_class || !operation) { + return MOBILESYNC_E_INVALID_ARG; + } + + mobilesync_error_t err = MOBILESYNC_E_UNKNOWN_ERROR; + plist_t msg = NULL; + + msg = plist_new_array(); + plist_array_append_item(msg, plist_new_string(operation)); + plist_array_append_item(msg, plist_new_string(client->data_class)); + + err = mobilesync_send(client, msg); + + if (msg) { + plist_free(msg); + msg = NULL; + } + return err; +} + +mobilesync_error_t mobilesync_get_all_records_from_device(mobilesync_client_t client) +{ + return mobilesync_get_records(client, "SDMessageGetAllRecordsFromDevice"); +} + +mobilesync_error_t mobilesync_get_changes_from_device(mobilesync_client_t client) +{ + return mobilesync_get_records(client, "SDMessageGetChangesFromDevice"); +} + +mobilesync_error_t mobilesync_receive_changes(mobilesync_client_t client, plist_t *entities, uint8_t *is_last_record, plist_t *actions) +{ + if (!client || !client->data_class) { + return MOBILESYNC_E_INVALID_ARG; + } + + plist_t msg = NULL; + plist_t response_type_node = NULL; + plist_t actions_node = NULL; + char *response_type = NULL; + uint8_t has_more_changes = 0; + + mobilesync_error_t err = mobilesync_receive(client, &msg); + if (err != MOBILESYNC_E_SUCCESS) { + goto out; + } + + response_type_node = plist_array_get_item(msg, 0); + if (!response_type_node) { + err = MOBILESYNC_E_PLIST_ERROR; + goto out; + } + + plist_get_string_val(response_type_node, &response_type); + if (!response_type) { + err = MOBILESYNC_E_PLIST_ERROR; + goto out; + } + + if (!strcmp(response_type, "SDMessageCancelSession")) { + char *reason = NULL; + err = MOBILESYNC_E_CANCELLED; + plist_get_string_val(plist_array_get_item(msg, 2), &reason); + debug_info("Device cancelled: %s", reason); + free(reason); + goto out; + } + + *entities = plist_copy(plist_array_get_item(msg, 2)); + + plist_get_bool_val(plist_array_get_item(msg, 3), &has_more_changes); + *is_last_record = (has_more_changes > 0 ? 0 : 1); + + actions_node = plist_array_get_item(msg, 4); + if (plist_get_node_type(actions) == PLIST_DICT) + *actions = plist_copy(actions_node); + else + *actions = NULL; + + out: + if (response_type) { + free(response_type); + response_type = NULL; + } + if (msg) { + plist_free(msg); + msg = NULL; + } + return err; +} + +mobilesync_error_t mobilesync_acknowledge_changes_from_device(mobilesync_client_t client) +{ + if (!client || !client->data_class) { + return MOBILESYNC_E_INVALID_ARG; + } + + plist_t msg = NULL; + mobilesync_error_t err = MOBILESYNC_E_UNKNOWN_ERROR; + + msg = plist_new_array(); + plist_array_append_item(msg, plist_new_string("SDMessageAcknowledgeChangesFromDevice")); + plist_array_append_item(msg, plist_new_string(client->data_class)); + + err = mobilesync_send(client, msg); + plist_free(msg); + return err; +} + +static plist_t create_process_changes_message(const char *data_class, plist_t entities, uint8_t more_changes, plist_t actions) +{ + plist_t msg = plist_new_array(); + plist_array_append_item(msg, plist_new_string("SDMessageProcessChanges")); + plist_array_append_item(msg, plist_copy(entities)); + plist_array_append_item(msg, plist_new_bool(more_changes)); + plist_array_append_item(msg, plist_copy(actions)); + + return msg; +} + +mobilesync_error_t mobilesync_ready_to_send_changes_from_computer(mobilesync_client_t client) +{ + if (!client || !client->data_class) { + return MOBILESYNC_E_INVALID_ARG; + } + + if (client->direction != MOBILESYNC_SYNC_DIR_DEVICE_TO_COMPUTER) { + return MOBILESYNC_E_WRONG_DIRECTION; + } + + plist_t msg = NULL; + plist_t response_type_node = NULL; + char *response_type = NULL; + mobilesync_error_t err = MOBILESYNC_E_UNKNOWN_ERROR; + + err = mobilesync_receive(client, &msg); + if (err != MOBILESYNC_E_SUCCESS) { + goto out; + } + + response_type_node = plist_array_get_item(msg, 0); + if (!response_type_node) { + err = MOBILESYNC_E_PLIST_ERROR; + goto out; + } + + plist_get_string_val(response_type_node, &response_type); + if (!response_type) { + err = MOBILESYNC_E_PLIST_ERROR; + goto out; + } + + if (!strcmp(response_type, "SDMessageCancelSession")) { + char *reason = NULL; + err = MOBILESYNC_E_CANCELLED; + plist_get_string_val(plist_array_get_item(msg, 2), &reason); + debug_info("Device cancelled: %s", reason); + free(reason); + goto out; + } + + if (strcmp(response_type, "SDMessageDeviceReadyToReceiveChanges") != 0) { + err = MOBILESYNC_E_NOT_READY; + goto out; + } + + err = mobilesync_error(device_link_service_send_ping(client->parent, "Preparing to get changes for device")); + if (err != MOBILESYNC_E_SUCCESS) { + goto out; + } + + client->direction = MOBILESYNC_SYNC_DIR_COMPUTER_TO_DEVICE; + err = MOBILESYNC_E_SUCCESS; + + out: + if (msg) { + plist_free(msg); + msg = NULL; + } + + return err; +} + +mobilesync_error_t mobilesync_send_changes(mobilesync_client_t client, plist_t entities, uint8_t is_last_record, plist_t actions) +{ + if (!client || !client->data_class || !entities) { + return MOBILESYNC_E_INVALID_ARG; + } + + if (plist_get_node_type(entities) != PLIST_DICT) { + return MOBILESYNC_E_INVALID_ARG; + } + + if (client->direction != MOBILESYNC_SYNC_DIR_COMPUTER_TO_DEVICE) { + return MOBILESYNC_E_WRONG_DIRECTION; + } + + mobilesync_error_t err = MOBILESYNC_E_UNKNOWN_ERROR; + plist_t msg = NULL; + + msg = create_process_changes_message(client->data_class, entities, (is_last_record > 0 ? 0 : 1), actions); + err = mobilesync_send(client, msg); + + if (msg) { + plist_free(msg); + msg = NULL; + } + + return err; +} + +mobilesync_error_t mobilesync_remap_identifiers(mobilesync_client_t client, plist_t *mapping) +{ + if (!client || !client->data_class) { + return MOBILESYNC_E_INVALID_ARG; + } + + if (client->direction == MOBILESYNC_SYNC_DIR_DEVICE_TO_COMPUTER) { + return MOBILESYNC_E_WRONG_DIRECTION; + } + + plist_t msg = NULL; + plist_t response_type_node = NULL; + char *response_type = NULL; + + mobilesync_error_t err = mobilesync_receive(client, &msg); + if (err != MOBILESYNC_E_SUCCESS) { + goto out; + } + + response_type_node = plist_array_get_item(msg, 0); + if (!response_type_node) { + err = MOBILESYNC_E_PLIST_ERROR; + goto out; + } + + plist_get_string_val(response_type_node, &response_type); + if (!response_type) { + err = MOBILESYNC_E_PLIST_ERROR; + goto out; + } + + if (!strcmp(response_type, "SDMessageCancelSession")) { + char *reason = NULL; + err = MOBILESYNC_E_CANCELLED; + plist_get_string_val(plist_array_get_item(msg, 2), &reason); + debug_info("Device cancelled: %s", reason); + free(reason); + goto out; + } + + if (strcmp(response_type, "SDMessageRemapRecordIdentifiers") != 0) { + err = MOBILESYNC_E_PLIST_ERROR; + goto out; + } + + if (mapping != NULL) { + plist_t map = plist_array_get_item(msg, 2); + if (plist_get_node_type(map) == PLIST_DICT) { + *mapping = plist_copy(map); + } else { + *mapping = NULL; + } + } + + err = MOBILESYNC_E_SUCCESS; + + out: + if (response_type) { + free(response_type); + response_type = NULL; + } + if (msg) { + plist_free(msg); + msg = NULL; + } + + return err; +} + +mobilesync_error_t mobilesync_cancel(mobilesync_client_t client, const char* reason) +{ + if (!client || !client->data_class || !reason) { + return MOBILESYNC_E_INVALID_ARG; + } + + mobilesync_error_t err = MOBILESYNC_E_UNKNOWN_ERROR; + plist_t msg = NULL; + + msg = plist_new_array(); + plist_array_append_item(msg, plist_new_string("SDMessageCancelSession")); + plist_array_append_item(msg, plist_new_string(client->data_class)); + plist_array_append_item(msg, plist_new_string(reason)); + + err = mobilesync_send(client, msg); + + plist_free(msg); + msg = NULL; + + free(client->data_class); + client->data_class = NULL; + client->direction = MOBILESYNC_SYNC_DIR_DEVICE_TO_COMPUTER; + + return err; +} + +mobilesync_anchors_t mobilesync_anchors_new(const char *device_anchor, const char *computer_anchor) +{ + mobilesync_anchors_t anchors = (mobilesync_anchors_t) malloc(sizeof(mobilesync_anchors)); + if (device_anchor != NULL) { + anchors->device_anchor = strdup(device_anchor); + } else { + anchors->device_anchor = NULL; + } + if (computer_anchor != NULL) { + anchors->computer_anchor = strdup(computer_anchor); + } else { + anchors->computer_anchor = NULL; + } + + return anchors; +} + +void mobilesync_anchors_free(mobilesync_anchors_t anchors) +{ + if (anchors->device_anchor != NULL) { + free(anchors->device_anchor); + anchors->device_anchor = NULL; + } + if (anchors->computer_anchor != NULL) { + free(anchors->computer_anchor); + anchors->computer_anchor = NULL; + } + free(anchors); + anchors = NULL; +} + +plist_t mobilesync_actions_new() +{ + return plist_new_dict(); +} + +void mobilesync_actions_add(plist_t actions, ...) +{ + if (!actions) + return; + va_list args; + va_start(args, actions); + char *arg = va_arg(args, char*); + while (arg) { + char *key = strdup(arg); + if (!strcmp(key, "SyncDeviceLinkEntityNamesKey")) { + char **entity_names = va_arg(args, char**); + int entity_names_length = va_arg(args, int); + int i = 0; + + plist_t array = plist_new_array(); + + for (i = 0; i < entity_names_length; i++) { + plist_array_append_item(array, plist_new_string(entity_names[i])); + } + + plist_dict_insert_item(actions, key, array); + } else if (!strcmp(key, "SyncDeviceLinkAllRecordsOfPulledEntityTypeSentKey")) { + int link_records = va_arg(args, int); + plist_dict_insert_item(actions, key, plist_new_bool(link_records)); + } + free(key); + key = NULL; + arg = va_arg(args, char*); + } + va_end(args); +} + +void mobilesync_actions_free(plist_t actions) +{ + if (actions) { + plist_free(actions); + actions = NULL; + } +} diff --git a/src/mobilesync.h b/src/mobilesync.h index 8317c69..24e61af 100644 --- a/src/mobilesync.h +++ b/src/mobilesync.h @@ -2,6 +2,7 @@ * mobilesync.h * Definitions for the built-in MobileSync client * + * Copyright (c) 2010 Bryan Forbes All Rights Reserved. * Copyright (c) 2009 Jonathan Beck All Rights Reserved. * * This library is free software; you can redistribute it and/or @@ -24,8 +25,15 @@ #include "libimobiledevice/mobilesync.h" #include "device_link_service.h" +typedef enum { + MOBILESYNC_SYNC_DIR_DEVICE_TO_COMPUTER, + MOBILESYNC_SYNC_DIR_COMPUTER_TO_DEVICE +} mobilesync_sync_direction_t; + struct mobilesync_client_private { device_link_service_client_t parent; + mobilesync_sync_direction_t direction; + char *data_class; }; #endif -- cgit v1.1-32-gdbae