--- a/src/libirecovery.c +++ b/src/libirecovery.c @@ -31,6 +31,12 @@ #include #include #include +#ifndef _WIN32 +#include +#include +#include +#include +#endif #include #include @@ -119,6 +125,11 @@ int usb_alt_interface; unsigned int mode; int isKIS; + int qemu_transport; + int qemu_fd; + uint32_t qemu_request_id; + uint32_t qemu_generation; + uint32_t qemu_poll_timeout_ms; struct irecv_device_info device_info; #ifndef USE_DUMMY #ifndef _WIN32 @@ -163,6 +173,214 @@ #define debug(...) if (libirecovery_debug) fprintf(stderr, __VA_ARGS__) static int libirecovery_debug = 0; + +#ifndef _WIN32 +#define QA6_USB_MAGIC 0x55364151u +#define QA6_USB_PROTOCOL_VERSION 1u +#define QA6_USB_MESSAGE_REQUEST 1u +#define QA6_USB_MESSAGE_RESPONSE 2u +#define QA6_USB_MESSAGE_INFO_REQUEST 3u +#define QA6_USB_MESSAGE_INFO_RESPONSE 4u +#define QA6_USB_MESSAGE_BULK_REQUEST 5u +#define QA6_USB_MESSAGE_BULK_RESPONSE 6u +#define QA6_USB_MESSAGE_RESET_REQUEST 7u +#define QA6_USB_MESSAGE_RESET_RESPONSE 8u +#define QA6_USB_STATUS_SUCCESS 0 +#define QA6_USB_STATUS_STALL -1 +#define QA6_USB_STATUS_DISCONNECTED -2 +#define QA6_USB_STATUS_PROTOCOL -3 +#define QA6_USB_STATUS_UNSUPPORTED -4 +#define QA6_USB_MAX_PAYLOAD 65535u +#define QA6_USB_SERIAL_MAX 256u +#define QA6_USB_DFU_STATE_ERROR 10u +#define QA6_USB_DFU_STATE_WAIT_RESET 8u + +#pragma pack(push, 1) +typedef struct qemu_a6_usb_frame_header { + uint32_t magic; + uint16_t version; + uint16_t type; + uint32_t request_id; + int32_t status; + uint32_t payload_length; + uint32_t transfer_length; + uint8_t setup[8]; +} qemu_a6_usb_frame_header; + +typedef struct qemu_a6_usb_device_info { + uint32_t generation; + uint16_t vendor_id; + uint16_t product_id; + uint8_t device_class; + uint8_t device_subclass; + uint8_t device_protocol; + uint8_t is_dfu; + char serial[QA6_USB_SERIAL_MAX]; +} qemu_a6_usb_device_info; +#pragma pack(pop) + +static const char *qemu_a6_endpoint(void) +{ + const char *endpoint = getenv("LIBIRECOVERY_QEMU"); + + return endpoint && endpoint[0] ? endpoint : NULL; +} + +static int qemu_a6_send_all(int fd, const void *data, size_t length) +{ + const uint8_t *cursor = data; + + while (length) { + ssize_t sent = send(fd, cursor, length, 0); + if (sent < 0 && errno == EINTR) { + continue; + } + if (sent <= 0) { + return -1; + } + cursor += sent; + length -= sent; + } + return 0; +} + +static int qemu_a6_recv_all(int fd, void *data, size_t length) +{ + uint8_t *cursor = data; + + while (length) { + ssize_t received = recv(fd, cursor, length, 0); + if (received < 0 && errno == EINTR) { + continue; + } + if (received < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + return -2; + } + if (received <= 0) { + return -1; + } + cursor += received; + length -= received; + } + return 0; +} + +static int qemu_a6_connect(void) +{ + const char *endpoint = qemu_a6_endpoint(); + char host[256]; + char port[16]; + const char *separator; + struct addrinfo hints; + struct addrinfo *addresses = NULL; + struct addrinfo *address; + struct timeval timeout = { 1, 0 }; + int fd = -1; + + if (!endpoint || !(separator = strrchr(endpoint, ':')) || + separator == endpoint || !separator[1] || + (size_t)(separator - endpoint) >= sizeof(host) || + strlen(separator + 1) >= sizeof(port)) { + return -1; + } + memcpy(host, endpoint, separator - endpoint); + host[separator - endpoint] = '\0'; + strcpy(port, separator + 1); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(host, port, &hints, &addresses) != 0) { + return -1; + } + for (address = addresses; address; address = address->ai_next) { + fd = socket(address->ai_family, address->ai_socktype, + address->ai_protocol); + if (fd < 0) { + continue; + } + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); + if (connect(fd, address->ai_addr, address->ai_addrlen) == 0) { + break; + } + close(fd); + fd = -1; + } + freeaddrinfo(addresses); + return fd; +} + +static int qemu_a6_exchange(int fd, qemu_a6_usb_frame_header *request, + const void *out_data, + qemu_a6_usb_frame_header *response, + void *in_data, size_t in_capacity) +{ + int receive_result; + + if (qemu_a6_send_all(fd, request, sizeof(*request)) < 0 || + (request->payload_length && + qemu_a6_send_all(fd, out_data, request->payload_length) < 0)) { + return IRECV_E_NO_DEVICE; + } + receive_result = qemu_a6_recv_all(fd, response, sizeof(*response)); + if (receive_result == -2) { + return IRECV_E_TIMEOUT; + } + if (receive_result < 0) { + return IRECV_E_NO_DEVICE; + } + if (response->magic != QA6_USB_MAGIC || + response->version != QA6_USB_PROTOCOL_VERSION || + response->request_id != request->request_id || + response->payload_length > QA6_USB_MAX_PAYLOAD || + response->payload_length > in_capacity) { + return IRECV_E_UNKNOWN_ERROR; + } + if (response->payload_length && + qemu_a6_recv_all(fd, in_data, response->payload_length) < 0) { + return IRECV_E_NO_DEVICE; + } + switch (response->status) { + case QA6_USB_STATUS_SUCCESS: + return IRECV_E_SUCCESS; + case QA6_USB_STATUS_STALL: + return IRECV_E_PIPE; + case QA6_USB_STATUS_DISCONNECTED: + return IRECV_E_NO_DEVICE; + case QA6_USB_STATUS_UNSUPPORTED: + return IRECV_E_UNSUPPORTED; + case QA6_USB_STATUS_PROTOCOL: + default: + return IRECV_E_UNKNOWN_ERROR; + } +} + +static int qemu_a6_read_info_fd(int fd, qemu_a6_usb_device_info *info) +{ + qemu_a6_usb_frame_header request; + qemu_a6_usb_frame_header response; + int error; + + memset(&request, 0, sizeof(request)); + request.magic = QA6_USB_MAGIC; + request.version = QA6_USB_PROTOCOL_VERSION; + request.type = QA6_USB_MESSAGE_INFO_REQUEST; + request.request_id = 1; + error = qemu_a6_exchange(fd, &request, NULL, &response, + info, sizeof(*info)); + if (error != IRECV_E_SUCCESS) { + return error; + } + if (response.type != QA6_USB_MESSAGE_INFO_RESPONSE || + response.payload_length != sizeof(*info) || + response.transfer_length != sizeof(*info)) { + return IRECV_E_UNKNOWN_ERROR; + } + info->serial[sizeof(info->serial) - 1] = '\0'; + return IRECV_E_SUCCESS; +} +#endif #ifndef USE_DUMMY #ifndef _WIN32 #ifndef HAVE_IOKIT @@ -930,6 +1149,199 @@ } } +#ifndef _WIN32 +static irecv_error_t qemu_a6_open_with_ecid(irecv_client_t *pclient, + uint64_t ecid) +{ + qemu_a6_usb_device_info info; + irecv_client_t client; + int fd; + int error; + + *pclient = NULL; + fd = qemu_a6_connect(); + if (fd < 0) { + return IRECV_E_UNABLE_TO_CONNECT; + } + memset(&info, 0, sizeof(info)); + error = qemu_a6_read_info_fd(fd, &info); + if (error != IRECV_E_SUCCESS) { + close(fd); + return error; + } + if (info.vendor_id != APPLE_VENDOR_ID || + (info.product_id != IRECV_K_DFU_MODE && + info.product_id != IRECV_K_WTF_MODE && + info.product_id != IRECV_K_PORT_DFU_MODE && + (info.product_id < IRECV_K_RECOVERY_MODE_1 || + info.product_id > IRECV_K_RECOVERY_MODE_4))) { + close(fd); + return IRECV_E_NO_DEVICE; + } + + client = calloc(1, sizeof(*client)); + if (!client) { + close(fd); + return IRECV_E_OUT_OF_MEMORY; + } + client->qemu_transport = 1; + client->qemu_fd = fd; + client->qemu_request_id = 1; + client->qemu_generation = info.generation; + client->mode = info.product_id; + client->usb_interface = 0; + irecv_load_device_info_from_iboot_string(client, info.serial); + if (ecid && ecid != IRECV_K_WTF_MODE && + client->device_info.ecid != ecid) { + irecv_close(client); + return IRECV_E_NO_DEVICE; + } + debug("opening QEMU A6 device %04x:%04x generation %u...\n", + info.vendor_id, info.product_id, info.generation); + *pclient = client; + return IRECV_E_SUCCESS; +} + +static int qemu_a6_control_transfer(irecv_client_t client, + uint8_t bm_request_type, + uint8_t b_request, uint16_t w_value, + uint16_t w_index, unsigned char *data, + uint16_t w_length) +{ + qemu_a6_usb_frame_header request; + qemu_a6_usb_frame_header response; + int direction_in = (bm_request_type & 0x80) != 0; + int error; + + memset(&request, 0, sizeof(request)); + request.magic = QA6_USB_MAGIC; + request.version = QA6_USB_PROTOCOL_VERSION; + request.type = QA6_USB_MESSAGE_REQUEST; + request.request_id = ++client->qemu_request_id; + request.payload_length = direction_in ? 0 : w_length; + request.transfer_length = w_length; + request.setup[0] = bm_request_type; + request.setup[1] = b_request; + request.setup[2] = w_value & 0xff; + request.setup[3] = w_value >> 8; + request.setup[4] = w_index & 0xff; + request.setup[5] = w_index >> 8; + request.setup[6] = w_length & 0xff; + request.setup[7] = w_length >> 8; + error = qemu_a6_exchange(client->qemu_fd, &request, + direction_in ? NULL : data, &response, + direction_in ? data : NULL, + direction_in ? w_length : 0); + if (error != IRECV_E_SUCCESS) { + return error; + } + if (response.type != QA6_USB_MESSAGE_RESPONSE || + response.transfer_length > w_length) { + return IRECV_E_UNKNOWN_ERROR; + } + if (bm_request_type == 0xa1 && b_request == 3 && data && + response.transfer_length >= 4) { + client->qemu_poll_timeout_ms = data[1] | + (data[2] << 8) | (data[3] << 16); + } + return response.transfer_length; +} + +static int qemu_a6_bulk_transfer(irecv_client_t client, + unsigned char endpoint, + unsigned char *data, int length, + int *transferred, unsigned int timeout) +{ + qemu_a6_usb_frame_header request; + qemu_a6_usb_frame_header response; + int direction_in = (endpoint & 0x80) != 0; + int error; + + if (length < 0 || (unsigned int)length > QA6_USB_MAX_PAYLOAD) { + return IRECV_E_INVALID_INPUT; + } + memset(&request, 0, sizeof(request)); + request.magic = QA6_USB_MAGIC; + request.version = QA6_USB_PROTOCOL_VERSION; + request.type = QA6_USB_MESSAGE_BULK_REQUEST; + request.request_id = ++client->qemu_request_id; + request.payload_length = direction_in ? 0 : length; + request.transfer_length = length; + request.setup[0] = endpoint; + request.setup[1] = timeout & 0xff; + request.setup[2] = (timeout >> 8) & 0xff; + request.setup[3] = (timeout >> 16) & 0xff; + request.setup[4] = (timeout >> 24) & 0xff; + error = qemu_a6_exchange(client->qemu_fd, &request, + direction_in ? NULL : data, &response, + direction_in ? data : NULL, + direction_in ? length : 0); + if (error != IRECV_E_SUCCESS) { + return error; + } + if (response.type != QA6_USB_MESSAGE_BULK_RESPONSE || + response.transfer_length > (unsigned int)length) { + return IRECV_E_UNKNOWN_ERROR; + } + *transferred = response.transfer_length; + return IRECV_E_SUCCESS; +} + +static irecv_error_t qemu_a6_reset(irecv_client_t client) +{ + qemu_a6_usb_frame_header request; + qemu_a6_usb_frame_header response; + uint8_t dfu_status[6]; + int retry = 0; + int error; + while (client->qemu_poll_timeout_ms && retry++ < 5) { + uint32_t delay_ms = client->qemu_poll_timeout_ms > 10000 ? + 10000 : client->qemu_poll_timeout_ms; + debug("honoring QEMU DFU poll timeout: %u ms\n", delay_ms); + usleep((useconds_t)delay_ms * 1000); + client->qemu_poll_timeout_ms = 0; + memset(dfu_status, 0, sizeof(dfu_status)); + error = qemu_a6_control_transfer(client, 0xa1, 3, 0, 0, + dfu_status, sizeof(dfu_status)); + if (error != sizeof(dfu_status)) { + return error < 0 ? error : IRECV_E_USB_STATUS; + } + debug("QEMU DFU manifestation state=%u status=%u\n", + dfu_status[4], dfu_status[0]); + if (dfu_status[0] || dfu_status[4] == QA6_USB_DFU_STATE_ERROR) { + return IRECV_E_USB_UPLOAD; + } + if (dfu_status[4] == QA6_USB_DFU_STATE_WAIT_RESET) { + client->qemu_poll_timeout_ms = 0; + break; + } + } + if (client->qemu_poll_timeout_ms) { + return IRECV_E_TIMEOUT; + } + if (getenv("LIBIRECOVERY_QEMU_NO_RESET")) { + debug("leaving QEMU in DFU-WAIT-RESET for debugging\n"); + return IRECV_E_SUCCESS; + } + + memset(&request, 0, sizeof(request)); + request.magic = QA6_USB_MAGIC; + request.version = QA6_USB_PROTOCOL_VERSION; + request.type = QA6_USB_MESSAGE_RESET_REQUEST; + request.request_id = ++client->qemu_request_id; + error = qemu_a6_exchange(client->qemu_fd, &request, NULL, &response, + NULL, 0); + if (error != IRECV_E_SUCCESS) { + return error; + } + if (response.type != QA6_USB_MESSAGE_RESET_RESPONSE || + response.payload_length || response.transfer_length) { + return IRECV_E_UNKNOWN_ERROR; + } + return IRECV_E_SUCCESS; +} +#endif + static void irecv_copy_nonce_with_tag_from_buffer(const char* tag, unsigned char** nonce, unsigned int* nonce_size, const char *buf) { int taglen = strlen(tag); @@ -1006,6 +1350,11 @@ *nonce = NULL; *nonce_size = 0; + if (client->qemu_transport) { + irecv_copy_nonce_with_tag_from_buffer( + tag, nonce, nonce_size, client->device_info.serial_string); + return; + } memset(buf, 0, sizeof(buf)); len = irecv_get_string_descriptor_ascii(client, 1, (unsigned char*)buf, sizeof(buf)-1); @@ -1406,7 +1755,8 @@ static int check_context(irecv_client_t client) { - if (client == NULL || client->handle == NULL) { + if (client == NULL || + (!client->qemu_transport && client->handle == NULL)) { return IRECV_E_NO_DEVICE; } @@ -1456,6 +1806,12 @@ return IRECV_E_UNSUPPORTED; #else #ifndef _WIN32 + if (client->qemu_transport) { + return qemu_a6_control_transfer(client, bm_request_type, b_request, + w_value, w_index, data, w_length); + } +#endif +#ifndef _WIN32 #ifdef HAVE_IOKIT return iokit_usb_control_transfer(client, bm_request_type, b_request, w_value, w_index, data, w_length, timeout); #else @@ -1813,6 +2169,10 @@ int ret; #ifndef _WIN32 + if (client->qemu_transport) { + return qemu_a6_bulk_transfer(client, endpoint, data, length, + transferred, timeout); + } #ifdef HAVE_IOKIT return iokit_usb_bulk_transfer(client, endpoint, data, length, transferred, timeout); #else @@ -2144,11 +2504,15 @@ irecv_set_debug_level(libirecovery_debug); } #ifndef _WIN32 + if (qemu_a6_endpoint()) { + error = qemu_a6_open_with_ecid(pclient, ecid); + } else { #ifdef HAVE_IOKIT - error = iokit_open_with_ecid(pclient, ecid); + error = iokit_open_with_ecid(pclient, ecid); #else - error = libusb_open_with_ecid(pclient, ecid); + error = libusb_open_with_ecid(pclient, ecid); #endif + } #else error = win32_open_with_ecid(pclient, ecid); #endif @@ -2166,12 +2530,14 @@ } #ifdef HAVE_IOKIT - error = (*client->handle)->CreateDeviceAsyncEventSource(client->handle, &client->async_event_source); - if (error != IRECV_E_SUCCESS) { - free(client); - return error; + if (!client->qemu_transport) { + error = (*client->handle)->CreateDeviceAsyncEventSource(client->handle, &client->async_event_source); + if (error != IRECV_E_SUCCESS) { + free(client); + return error; + } + CFRunLoopAddSource(CFRunLoopGetCurrent(), client->async_event_source, kCFRunLoopDefaultMode); } - CFRunLoopAddSource(CFRunLoopGetCurrent(), client->async_event_source, kCFRunLoopDefaultMode); #endif if (client->mode == IRECV_K_DFU_MODE || client->mode == IRECV_K_PORT_DFU_MODE || client->mode == IRECV_K_WTF_MODE || client->mode == KIS_PRODUCT_ID) { @@ -2237,6 +2603,10 @@ #ifndef _WIN32 debug("Setting to configuration %d\n", configuration); + if (client->qemu_transport) { + client->usb_config = configuration; + return IRECV_E_SUCCESS; + } #ifdef HAVE_IOKIT IOReturn result; @@ -2355,6 +2725,17 @@ debug("Setting to interface %d:%d\n", usb_interface, usb_alt_interface); #ifndef _WIN32 + if (client->qemu_transport) { + if (usb_interface == 1 && + qemu_a6_control_transfer(client, 0x01, 0x0B, + usb_alt_interface, usb_interface, + NULL, 0) < 0) { + return IRECV_E_USB_INTERFACE; + } + client->usb_interface = usb_interface; + client->usb_alt_interface = usb_alt_interface; + return IRECV_E_SUCCESS; + } #ifdef HAVE_IOKIT if (iokit_usb_set_interface(client, usb_interface, usb_alt_interface) < 0) { return IRECV_E_USB_INTERFACE; @@ -2393,6 +2768,9 @@ return IRECV_E_NO_DEVICE; #ifndef _WIN32 + if (client->qemu_transport) { + return qemu_a6_reset(client); + } #ifdef HAVE_IOKIT IOReturn result; @@ -2875,6 +3253,44 @@ } #ifndef _WIN32 +static struct irecv_usb_device_info *qemu_a6_handle_device_add( + const qemu_a6_usb_device_info *info) +{ + struct irecv_client_private client_loc; + struct irecv_usb_device_info *usb_dev_info; + irecv_device_event_t dev_event; + + memset(&client_loc, 0, sizeof(client_loc)); + client_loc.mode = info->product_id; + irecv_load_device_info_from_iboot_string(&client_loc, info->serial); + usb_dev_info = calloc(1, sizeof(*usb_dev_info)); + if (!usb_dev_info) { + free(client_loc.device_info.srnm); + free(client_loc.device_info.imei); + free(client_loc.device_info.srtg); + free(client_loc.device_info.serial_string); + return NULL; + } + memcpy(&usb_dev_info->device_info, &client_loc.device_info, + sizeof(client_loc.device_info)); + usb_dev_info->location = info->generation; + usb_dev_info->alive = 1; + usb_dev_info->mode = client_loc.mode; + collection_add(&devices, usb_dev_info); + + dev_event.type = IRECV_DEVICE_ADD; + dev_event.mode = client_loc.mode; + dev_event.device_info = &usb_dev_info->device_info; + mutex_lock(&listener_mutex); + FOREACH(struct irecv_device_event_context* context, &listeners) { + context->callback(&dev_event, context->user_data); + } ENDFOREACH + mutex_unlock(&listener_mutex); + return usb_dev_info; +} +#endif + +#ifndef _WIN32 #ifdef HAVE_IOKIT static void iokit_device_added(void *refcon, io_iterator_t iterator) { @@ -2977,10 +3393,70 @@ cond_t startup_cond; mutex_t startup_mutex; }; + +#ifndef _WIN32 +static void *qemu_a6_event_handler(struct _irecv_event_handler_info *startup) +{ + struct irecv_usb_device_info *current = NULL; + + mutex_lock(&startup->startup_mutex); + cond_signal(&startup->startup_cond); + mutex_unlock(&startup->startup_mutex); + + for (;;) { + qemu_a6_usb_device_info info; + int listeners_active; + int fd; + int error = IRECV_E_UNABLE_TO_CONNECT; + + mutex_lock(&listener_mutex); + listeners_active = collection_count(&listeners) != 0; + mutex_unlock(&listener_mutex); + if (!listeners_active) { + break; + } + + fd = qemu_a6_connect(); + if (fd >= 0) { + memset(&info, 0, sizeof(info)); + error = qemu_a6_read_info_fd(fd, &info); + close(fd); + } + if (error == IRECV_E_SUCCESS) { + if (current && + (current->location != info.generation || + current->mode != info.product_id)) { + /* + * Publish removal and arrival on separate polling turns. + * idevicerestore deliberately waits for MODE_UNKNOWN before + * it accepts the next DFU/recovery attachment. + */ + _irecv_handle_device_remove(current); + current = NULL; + } else if (!current) { + current = qemu_a6_handle_device_add(&info); + } + } else if (error == IRECV_E_NO_DEVICE && current) { + _irecv_handle_device_remove(current); + current = NULL; + } + usleep(250000); + } + if (current) { + _irecv_handle_device_remove(current); + } + return NULL; +} +#endif static void *_irecv_event_handler(void* data) { struct _irecv_event_handler_info* info = (struct _irecv_event_handler_info*)data; +#ifndef _WIN32 + if (qemu_a6_endpoint()) { + return qemu_a6_event_handler(info); + } +#endif #ifdef _WIN32 struct collection newDevices; const GUID *guids[] = { &GUID_DEVINTERFACE_KIS, &GUID_DEVINTERFACE_PORTDFU, &GUID_DEVINTERFACE_DFU, &GUID_DEVINTERFACE_IBOOT, NULL }; @@ -3381,6 +3858,10 @@ client->disconnected_callback(client, &event); } #ifndef _WIN32 + if (client->qemu_transport) { + close(client->qemu_fd); + client->qemu_fd = -1; + } else { #ifdef HAVE_IOKIT if (client->usbInterface) { (*client->usbInterface)->USBInterfaceClose(client->usbInterface); @@ -3405,6 +3886,7 @@ client->handle = NULL; } #endif + } #else CloseHandle(client->handle); #endif