blob: 66c2461cee651508bd518a117ee056daf3f5eb4b [file] [log] [blame]
/*
* device_link_service.c
* DeviceLink service implementation.
*
* Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <stdlib.h>
#include "device_link_service.h"
#include "property_list_service.h"
#include "common/debug.h"
static device_link_service_error_t device_link_error(property_list_service_error_t err)
{
switch (err) {
case PROPERTY_LIST_SERVICE_E_SUCCESS:
return DEVICE_LINK_SERVICE_E_SUCCESS;
case PROPERTY_LIST_SERVICE_E_INVALID_ARG:
return DEVICE_LINK_SERVICE_E_INVALID_ARG;
case PROPERTY_LIST_SERVICE_E_PLIST_ERROR:
return DEVICE_LINK_SERVICE_E_PLIST_ERROR;
case PROPERTY_LIST_SERVICE_E_MUX_ERROR:
return DEVICE_LINK_SERVICE_E_MUX_ERROR;
case PROPERTY_LIST_SERVICE_E_SSL_ERROR:
return DEVICE_LINK_SERVICE_E_SSL_ERROR;
case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT:
return DEVICE_LINK_SERVICE_E_RECEIVE_TIMEOUT;
default:
break;
}
return DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR;
}
/**
* Internally used function to extract the message string from a DL* message
* plist.
*
* @param dl_msg The DeviceLink property list to parse.
* @param message A pointer that will be set to a newly allocated char*
* containing the DLMessage* string from the given plist. It is up to
* the caller to free the allocated memory. If this parameter is NULL
* it will be ignored.
*
* @return 1 if the given plist is a DL* message, or 0 if the plist does not
* contain any DL* message.
*/
static int device_link_service_get_message(plist_t dl_msg, char **message)
{
plist_t cmd = NULL;
char *cmd_str = NULL;
/* sanity check */
if ((plist_get_node_type(dl_msg) != PLIST_ARRAY) || (plist_array_get_size(dl_msg) < 1)) {
return 0;
}
/* get dl command */
cmd = plist_array_get_item(dl_msg, 0);
if (!cmd || (plist_get_node_type(cmd) != PLIST_STRING)) {
return 0;
}
plist_get_string_val(cmd, &cmd_str);
if (!cmd_str) {
return 0;
}
if ((strlen(cmd_str) < 9) || (strncmp(cmd_str, "DL", 2) != 0)) {
free(cmd_str);
return 0;
}
if (message)
*message = cmd_str;
/* we got a DL* message */
return 1;
}
/**
* Creates a new device link service client.
*
* @param device The device to connect to.
* @param service The service descriptor returned by lockdownd_start_service.
* @param client Reference that will point to a newly allocated
* device_link_service_client_t upon successful return.
*
* @return DEVICE_LINK_SERVICE_E_SUCCESS on success,
* DEVICE_LINK_SERVICE_E_INVALID_ARG when one of the parameters is invalid,
* or DEVICE_LINK_SERVICE_E_MUX_ERROR when the connection failed.
*/
device_link_service_error_t device_link_service_client_new(idevice_t device, lockdownd_service_descriptor_t service, device_link_service_client_t *client)
{
if (!device || !service || service->port == 0 || !client || *client) {
return DEVICE_LINK_SERVICE_E_INVALID_ARG;
}
property_list_service_client_t plistclient = NULL;
device_link_service_error_t err = device_link_error(property_list_service_client_new(device, service, &plistclient));
if (err != DEVICE_LINK_SERVICE_E_SUCCESS) {
return err;
}
/* create client object */
device_link_service_client_t client_loc = (device_link_service_client_t) malloc(sizeof(struct device_link_service_client_private));
client_loc->parent = plistclient;
/* all done, return success */
*client = client_loc;
return DEVICE_LINK_SERVICE_E_SUCCESS;
}
/**
* Frees a device link service client.
*
* @param client The device_link_service_client_t to free.
*
* @return DEVICE_LINK_SERVICE_E_SUCCESS on success,
* DEVICE_LINK_SERVICE_E_INVALID_ARG when one of client or client->parent
* is invalid, or DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR when the was an error
* freeing the parent property_list_service client.
*/
device_link_service_error_t device_link_service_client_free(device_link_service_client_t client)
{
if (!client)
return DEVICE_LINK_SERVICE_E_INVALID_ARG;
device_link_service_error_t err = device_link_error(property_list_service_client_free(client->parent));
free(client);
return err;
}
/**
* Performs the DLMessageVersionExchange with the connected device.
* This should be the first operation to be executed by an implemented
* device link service client.
*
* @param client The device_link_service client to use.
* @param version_major The major version number to check.
* @param version_minor The minor version number to check.
*
* @return DEVICE_LINK_SERVICE_E_SUCCESS on success,
* DEVICE_LINK_SERVICE_E_INVALID_ARG when client is NULL,
* DEVICE_LINK_SERVICE_E_MUX_ERROR when a communication error occurs,
* DEVICE_LINK_SERVICE_E_PLIST_ERROR when the received plist has not the
* expected contents, DEVICE_LINK_SERVICE_E_BAD_VERSION when the version
* given by the device is larger than the given version,
* or DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR otherwise.
*/
device_link_service_error_t device_link_service_version_exchange(device_link_service_client_t client, uint64_t version_major, uint64_t version_minor)
{
if (!client)
return DEVICE_LINK_SERVICE_E_INVALID_ARG;
device_link_service_error_t err = DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR;
/* perform version exchange */
plist_t array = NULL;
char *msg = NULL;
/* receive DLMessageVersionExchange from device */
err = device_link_error(property_list_service_receive_plist(client->parent, &array));
if (err != DEVICE_LINK_SERVICE_E_SUCCESS) {
debug_info("Did not receive initial message from device!");
goto leave;
}
device_link_service_get_message(array, &msg);
if (!msg || strcmp(msg, "DLMessageVersionExchange") != 0) {
debug_info("Did not receive DLMessageVersionExchange from device!");
err = DEVICE_LINK_SERVICE_E_PLIST_ERROR;
goto leave;
}
free(msg);
msg = NULL;
/* get major and minor version number */
if (plist_array_get_size(array) < 3) {
debug_info("DLMessageVersionExchange has unexpected format!");
err = DEVICE_LINK_SERVICE_E_PLIST_ERROR;
goto leave;
}
plist_t maj = plist_array_get_item(array, 1);
plist_t min = plist_array_get_item(array, 2);
uint64_t vmajor = 0;
uint64_t vminor = 0;
if (maj) {
plist_get_uint_val(maj, &vmajor);
}
if (min) {
plist_get_uint_val(min, &vminor);
}
if (vmajor > version_major) {
debug_info("Version mismatch: device=(%lld,%lld) > expected=(%lld,%lld)", vmajor, vminor, version_major, version_minor);
err = DEVICE_LINK_SERVICE_E_BAD_VERSION;
goto leave;
} else if ((vmajor == version_major) && (vminor > version_minor)) {
debug_info("WARNING: Version mismatch: device=(%lld,%lld) > expected=(%lld,%lld)", vmajor, vminor, version_major, version_minor);
err = DEVICE_LINK_SERVICE_E_BAD_VERSION;
goto leave;
}
plist_free(array);
/* version is ok, send reply */
array = plist_new_array();
plist_array_append_item(array, plist_new_string("DLMessageVersionExchange"));
plist_array_append_item(array, plist_new_string("DLVersionsOk"));
plist_array_append_item(array, plist_new_uint(version_major));
err = device_link_error(property_list_service_send_binary_plist(client->parent, array));
if (err != DEVICE_LINK_SERVICE_E_SUCCESS) {
debug_info("Error when sending DLVersionsOk");
goto leave;
}
plist_free(array);
/* receive DeviceReady message */
array = NULL;
err = device_link_error(property_list_service_receive_plist(client->parent, &array));
if (err != DEVICE_LINK_SERVICE_E_SUCCESS) {
debug_info("Error when receiving DLMessageDeviceReady!");
goto leave;
}
device_link_service_get_message(array, &msg);
if (!msg || strcmp(msg, "DLMessageDeviceReady") != 0) {
debug_info("Did not get DLMessageDeviceReady!");
err = DEVICE_LINK_SERVICE_E_PLIST_ERROR;
goto leave;
}
err = DEVICE_LINK_SERVICE_E_SUCCESS;
leave:
if (msg) {
free(msg);
}
if (array) {
plist_free(array);
}
return err;
}
/**
* Performs a disconnect with the connected device link service client.
*
* @param client The device link service client to disconnect.
* @param message Optional message to send send to the device or NULL.
*
* @return DEVICE_LINK_SERVICE_E_SUCCESS on success,
* DEVICE_LINK_SERVICE_E_INVALID_ARG if client is NULL,
* or DEVICE_LINK_SERVICE_E_MUX_ERROR when there's an error when sending
* the the disconnect message.
*/
device_link_service_error_t device_link_service_disconnect(device_link_service_client_t client, const char *message)
{
if (!client)
return DEVICE_LINK_SERVICE_E_INVALID_ARG;
plist_t array = plist_new_array();
plist_array_append_item(array, plist_new_string("DLMessageDisconnect"));
if (message)
plist_array_append_item(array, plist_new_string(message));
else
plist_array_append_item(array, plist_new_string("___EmptyParameterString___"));
device_link_service_error_t err = device_link_error(property_list_service_send_binary_plist(client->parent, array));
plist_free(array);
return err;
}
/**
* Sends a DLMessagePing plist.
*
* @param client The device link service client to use.
* @param message String to send as ping message.
*
* @return DEVICE_LINK_SERVICE_E_SUCCESS on success,
* DEVICE_LINK_SERVICE_E_INVALID_ARG if client or message is invalid,
* or DEVICE_LINK_SERVICE_E_MUX_ERROR if the DLMessagePing plist could
* not be sent.
*/
device_link_service_error_t device_link_service_send_ping(device_link_service_client_t client, const char *message)
{
if (!client || !client->parent || !message)
return DEVICE_LINK_SERVICE_E_INVALID_ARG;
plist_t array = plist_new_array();
plist_array_append_item(array, plist_new_string("DLMessagePing"));
plist_array_append_item(array, plist_new_string(message));
device_link_service_error_t err = device_link_error(property_list_service_send_binary_plist(client->parent, array));
plist_free(array);
return err;
}
/**
* Sends a DLMessageProcessMessage plist.
*
* @param client The device link service client to use.
* @param message PLIST_DICT to send.
*
* @return DEVICE_LINK_SERVICE_E_SUCCESS on success,
* DEVICE_LINK_SERVICE_E_INVALID_ARG if client or message is invalid or
* message is not a PLIST_DICT, or DEVICE_LINK_SERVICE_E_MUX_ERROR if
* the DLMessageProcessMessage plist could not be sent.
*/
device_link_service_error_t device_link_service_send_process_message(device_link_service_client_t client, plist_t message)
{
if (!client || !client->parent || !message)
return DEVICE_LINK_SERVICE_E_INVALID_ARG;
if (plist_get_node_type(message) != PLIST_DICT)
return DEVICE_LINK_SERVICE_E_INVALID_ARG;
plist_t array = plist_new_array();
plist_array_append_item(array, plist_new_string("DLMessageProcessMessage"));
plist_array_append_item(array, plist_copy(message));
device_link_service_error_t err = device_link_error(property_list_service_send_binary_plist(client->parent, array));
plist_free(array);
return err;
}
/**
* Receives a DL* message plist
*
* @param client The connected device link service client used for receiving.
* @param msg_plist Pointer to a plist that will be set to the contents of the
* message plist upon successful return.
* @param dlmessage A pointer that will be set to a newly allocated char*
* containing the DL* string from the given plist. It is up to the caller
* to free the allocated memory. If this parameter is NULL
* it will be ignored.
*
* @return DEVICE_LINK_SERVICE_E_SUCCESS if a DL* message was received,
* DEVICE_LINK_SERVICE_E_INVALID_ARG if client or message is invalid,
* DEVICE_LINK_SERVICE_E_PLIST_ERROR if the received plist is invalid
* or is not a DL* message plist, or DEVICE_LINK_SERVICE_E_MUX_ERROR if
* receiving from the device failed.
*/
device_link_service_error_t device_link_service_receive_message(device_link_service_client_t client, plist_t *msg_plist, char **dlmessage)
{
if (!client || !client->parent || !msg_plist)
return DEVICE_LINK_SERVICE_E_INVALID_ARG;
*msg_plist = NULL;
device_link_service_error_t err = device_link_error(property_list_service_receive_plist(client->parent, msg_plist));
if (err != DEVICE_LINK_SERVICE_E_SUCCESS) {
return err;
}
if (!device_link_service_get_message(*msg_plist, dlmessage)) {
debug_info("Did not receive a DL* message as expected!");
return DEVICE_LINK_SERVICE_E_PLIST_ERROR;
}
return DEVICE_LINK_SERVICE_E_SUCCESS;
}
/**
* Receives a DLMessageProcessMessage plist.
*
* @param client The connected device link service client used for receiving.
* @param message Pointer to a plist that will be set to the contents of the
* message contents upon successful return.
*
* @return DEVICE_LINK_SERVICE_E_SUCCESS when a DLMessageProcessMessage was
* received, DEVICE_LINK_SERVICE_E_INVALID_ARG when client or message is
* invalid, DEVICE_LINK_SERVICE_E_PLIST_ERROR if the received plist is
* invalid or is not a DLMessageProcessMessage,
* or DEVICE_LINK_SERVICE_E_MUX_ERROR if receiving from device fails.
*/
device_link_service_error_t device_link_service_receive_process_message(device_link_service_client_t client, plist_t *message)
{
if (!client || !client->parent || !message)
return DEVICE_LINK_SERVICE_E_INVALID_ARG;
plist_t pmsg = NULL;
device_link_service_error_t err = device_link_error(property_list_service_receive_plist(client->parent, &pmsg));
if (err != DEVICE_LINK_SERVICE_E_SUCCESS) {
return err;
}
err = DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR;
char *msg = NULL;
device_link_service_get_message(pmsg, &msg);
if (!msg || strcmp(msg, "DLMessageProcessMessage") != 0) {
debug_info("Did not receive DLMessageProcessMessage as expected!");
err = DEVICE_LINK_SERVICE_E_PLIST_ERROR;
goto leave;
}
if (plist_array_get_size(pmsg) != 2) {
debug_info("Malformed plist received for DLMessageProcessMessage");
err = DEVICE_LINK_SERVICE_E_PLIST_ERROR;
goto leave;
}
plist_t msg_loc = plist_array_get_item(pmsg, 1);
if (msg_loc) {
*message = plist_copy(msg_loc);
err = DEVICE_LINK_SERVICE_E_SUCCESS;
} else {
*message = NULL;
err = DEVICE_LINK_SERVICE_E_PLIST_ERROR;
}
leave:
if (msg)
free(msg);
if (pmsg)
plist_free(pmsg);
return err;
}
/**
* Generic device link service send function.
*
* @param client The device link service client to use for sending
* @param plist The property list to send
*
* @return DEVICE_LINK_SERVICE_E_SUCCESS on success,
* DEVICE_LINK_SERVICE_E_INVALID_ARG when client or plist is NULL,
* or DEVICE_LINK_SERVICE_E_MUX_ERROR when the given property list could
* not be sent.
*/
device_link_service_error_t device_link_service_send(device_link_service_client_t client, plist_t plist)
{
if (!client || !plist) {
return DEVICE_LINK_SERVICE_E_INVALID_ARG;
}
return device_link_error(property_list_service_send_binary_plist(client->parent, plist));
}
/* Generic device link service receive function.
*
* @param client The device link service client to use for sending
* @param plist Pointer that will point to the property list received upon
* successful return.
*
* @return DEVICE_LINK_SERVICE_E_SUCCESS on success,
* DEVICE_LINK_SERVICE_E_INVALID_ARG when client or plist is NULL,
* or DEVICE_LINK_SERVICE_E_MUX_ERROR when no property list could be
* received.
*/
device_link_service_error_t device_link_service_receive(device_link_service_client_t client, plist_t *plist)
{
if (!client || !plist || (plist && *plist)) {
return DEVICE_LINK_SERVICE_E_INVALID_ARG;
}
return device_link_error(property_list_service_receive_plist(client->parent, plist));
}