diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile.am | 31 | ||||
| -rw-r--r-- | src/libusbmuxd.c | 202 | ||||
| -rw-r--r-- | src/main.c | 1351 | ||||
| -rw-r--r-- | src/sock_stuff.c | 298 | ||||
| -rw-r--r-- | src/sock_stuff.h | 28 | ||||
| -rw-r--r-- | src/usbmux.c | 1259 | ||||
| -rw-r--r-- | src/usbmux.h | 51 | ||||
| -rw-r--r-- | src/usbmuxd-proto.h | 52 | ||||
| -rw-r--r-- | src/usbmuxd.h | 45 | 
9 files changed, 3317 insertions, 0 deletions
| diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..547870e --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,31 @@ +AM_CFLAGS = $(GLOBAL_CFLAGS) $(libusb_CFLAGS) +AM_LDFLAGS = $(libusb_LIBS) -lpthread -lrt + +# Libraries + +noinst_LTLIBRARIES = libusbmux.la libsock_stuff.la +libsock_stuff_la_SOURCES = sock_stuff.c \ +			   sock_stuff.h + +libusbmux_la_SOURCES = usbmux.c \ +		       usbmux.h +libusbmux_la_CFLAGS = $(AM_CFLAGS) +libusbmux_la_LDFLAGS = $(AM_LDFLAGS) + +lib_LTLIBRARIES = libusbmuxd.la +libusbmuxd_la_SOURCES = libusbmuxd.c \ +		        usbmuxd.h \ +		        usbmuxd-proto.h +libusbmuxd_la_LIBADD = libsock_stuff.la + +include_HEADERS = usbmuxd.h \ +		  usbmuxd-proto.h + +# Programs + +sbin_PROGRAMS = usbmuxd + +usbmuxd_SOURCES = main.c +usbmuxd_LDADD = libusbmux.la \ +	        libsock_stuff.la + diff --git a/src/libusbmuxd.c b/src/libusbmuxd.c new file mode 100644 index 0000000..c8acbf8 --- /dev/null +++ b/src/libusbmuxd.c @@ -0,0 +1,202 @@ +#include <stdint.h> +#include <stdlib.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <unistd.h> + +// usbmuxd public interface +#include <usbmuxd.h> +// usbmuxd protocol  +#include <usbmuxd-proto.h> +// socket utility functions +#include "sock_stuff.h" + +static int usbmuxd_get_result(int sfd, uint32_t tag, uint32_t * result) +{ +	struct usbmuxd_result res; +	int recv_len; + +	if (!result) { +		return -EINVAL; +	} + +	if ((recv_len = recv_buf(sfd, &res, sizeof(res))) <= 0) { +		perror("recv"); +		return -errno; +	} else { +		if ((recv_len == sizeof(res)) +			&& (res.header.length == (uint32_t) recv_len) +			&& (res.header.reserved == 0) +			&& (res.header.type == USBMUXD_RESULT) +			) { +			*result = res.result; +			if (res.header.tag == tag) { +				return 1; +			} else { +				return 0; +			} +		} +	} + +	return -1; +} + +int usbmuxd_scan(usbmuxd_scan_result ** available_devices) +{ +	struct usbmuxd_scan_request s_req; +	int sfd; +	int scan_success = 0; +	uint32_t res; +	uint32_t pktlen; +	int recv_len; +	usbmuxd_scan_result *newlist = NULL; +	struct usbmuxd_device_info_record dev_info_pkt; +	int dev_cnt = 0; + +	sfd = connect_unix_socket(USBMUXD_SOCKET_FILE); +	if (sfd < 0) { +		fprintf(stderr, "%s: error opening socket!\n", __func__); +		return sfd; +	} + +	s_req.header.length = sizeof(struct usbmuxd_scan_request); +	s_req.header.reserved = 0; +	s_req.header.type = USBMUXD_SCAN; +	s_req.header.tag = 2; + +	// send scan request packet +	if (send_buf(sfd, &s_req, s_req.header.length) == +		(int) s_req.header.length) { +		res = -1; +		// get response +		if (usbmuxd_get_result(sfd, s_req.header.tag, &res) && (res == 0)) { +			scan_success = 1; +		} else { +			fprintf(stderr, +					"%s: Did not get response to scan request (with result=0)...\n", +					__func__); +			close(sfd); +			return res; +		} +	} + +	if (!scan_success) { +		fprintf(stderr, "%s: Could not send scan request!\n", __func__); +		return -1; +	} + +	*available_devices = NULL; +	// receive device list +	while (1) { +		if (recv_buf_timeout(sfd, &pktlen, 4, MSG_PEEK, 1000) == 4) { +			if (pktlen != sizeof(dev_info_pkt)) { +				// invalid packet size received! +				fprintf(stderr, +						"%s: Invalid packet size (%d) received when expecting a device info record.\n", +						__func__, pktlen); +				break; +			} + +			recv_len = recv_buf(sfd, &dev_info_pkt, pktlen); +			if (recv_len <= 0) { +				fprintf(stderr, +						"%s: Error when receiving device info record\n", +						__func__); +				break; +			} else if ((uint32_t) recv_len < pktlen) { +				fprintf(stderr, +						"%s: received less data than specified in header!\n", +						__func__); +			} else { +				//fprintf(stderr, "%s: got device record with id %d, UUID=%s\n", __func__, dev_info_pkt.device_info.device_id, dev_info_pkt.device_info.serial_number); +				newlist = +					(usbmuxd_scan_result *) realloc(*available_devices, +													sizeof +													(usbmuxd_scan_result) * +													(dev_cnt + 1)); +				if (newlist) { +					newlist[dev_cnt].handle = +						(int) dev_info_pkt.device.device_id; +					newlist[dev_cnt].product_id = +						dev_info_pkt.device.product_id; +					memset(newlist[dev_cnt].serial_number, '\0', +						   sizeof(newlist[dev_cnt].serial_number)); +					memcpy(newlist[dev_cnt].serial_number, +						   dev_info_pkt.device.serial_number, +						   sizeof(dev_info_pkt.device.serial_number)); +					*available_devices = newlist; +					dev_cnt++; +				} else { +					fprintf(stderr, +							"%s: ERROR: out of memory when trying to realloc!\n", +							__func__); +					break; +				} +			} +		} else { +			// we _should_ have all of them now. +			// or perhaps an error occured. +			break; +		} +	} + +	// terminating zero record +	newlist = +		(usbmuxd_scan_result *) realloc(*available_devices, +										sizeof(usbmuxd_scan_result) * +										(dev_cnt + 1)); +	memset(newlist + dev_cnt, 0, sizeof(usbmuxd_scan_result)); +	*available_devices = newlist; + +	return dev_cnt; +} + +int usbmuxd_connect(const int handle, const unsigned short tcp_port) +{ +	int sfd; +	struct usbmuxd_connect_request c_req; +	int connected = 0; +	uint32_t res = -1; + +	sfd = connect_unix_socket(USBMUXD_SOCKET_FILE); +	if (sfd < 0) { +		fprintf(stderr, "%s: Error: Connection to usbmuxd failed: %s\n", +				__func__, strerror(errno)); +		return sfd; +	} + +	c_req.header.length = sizeof(c_req); +	c_req.header.reserved = 0; +	c_req.header.type = USBMUXD_CONNECT; +	c_req.header.tag = 3; +	c_req.device_id = (uint32_t) handle; +	c_req.tcp_dport = htons(tcp_port); +	c_req.reserved = 0; + +	if (send_buf(sfd, &c_req, sizeof(c_req)) < 0) { +		perror("send"); +	} else { +		// read ACK +		//fprintf(stderr, "%s: Reading connect result...\n", __func__); +		if (usbmuxd_get_result(sfd, c_req.header.tag, &res)) { +			if (res == 0) { +				//fprintf(stderr, "%s: Connect success!\n", __func__); +				connected = 1; +			} else { +				fprintf(stderr, "%s: Connect failed, Error code=%d\n", +						__func__, res); +			} +		} +	} + +	if (connected) { +		return sfd; +	} + +	close(sfd); + +	return -1; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..e7292cc --- /dev/null +++ b/src/main.c @@ -0,0 +1,1351 @@ +/* + * usbmuxd -- daemon for communication with iPhone/iPod via USB + *  + * Copyright (c) 2009 Nikias Bassen. All Rights Reserved. + * Based upon iTunnel source code, Copyright (c) 2008 Jing Su. + *  http://www.cs.toronto.edu/~jingsu/itunnel/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA  + */ +#include <stddef.h> +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <getopt.h> +#include <stdarg.h> +#include <syslog.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/stat.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <signal.h> +#include <pthread.h> +#include <stdint.h> +#include <usb.h> +#include <pwd.h> + +#include "usbmuxd-proto.h" +#include "sock_stuff.h" + +#include "usbmux.h" + +#define DEFAULT_TIMEOUT 4000 +#define DEFAULT_CHILDREN_CAPACITY 10 +#define DEBUG_LEVEL 0 + +#define LOCKFILE "/var/run/usbmuxd.lock" + +#define THREAD (unsigned int)pthread_self() + +static int quit_flag = 0; +static int fsock = -1; +static int verbose = DEBUG_LEVEL; +static int foreground = 0; +static int exit_on_no_devices = 0; + +struct device_info { +	uint32_t device_id; +	usbmux_device_t phone; +	int use_count; +	pthread_t bulk_reader; +	pthread_mutex_t mutex; +	/* mutex for mutual exclusion of calling the usbmux_send function +	 * TODO: I don't know if we need really need this? */ +	pthread_mutex_t writer_mutex; +}; + +struct client_data { +	volatile int dead; +	int socket; +	int tag; +	pthread_t thread; +	pthread_t handler; +	pthread_t reader; +	int reader_quit; +	int reader_dead; +	int handler_dead; +	int connected; +	usbmux_client_t muxclient; +	struct device_info *dev; +}; + +static struct device_info **devices = NULL; +static int device_count = 0; +static pthread_mutex_t usbmux_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t usb_mutex = PTHREAD_MUTEX_INITIALIZER; + +/** + * logs a message to syslog when running as daemon or to stdout/stderr when + * running in foreground. + * @param prio The logging priority. + * @param format The message to be printed. + */ +static void logmsg(int prio, const char *format, ...) +{ +	va_list args; +	va_start(args, format); + +	if (!foreground) { +		// daemon. log using syslog. +		vsyslog(prio, format, args); +	} else { +		// running in foreground. log to stdout/stderr. +		char msgbuf[256]; +		FILE *lfp = stdout; +		switch (prio) { +		case LOG_EMERG: +		case LOG_ALERT: +		case LOG_CRIT: +		case LOG_ERR: +		case LOG_WARNING: +			lfp = stderr; +			break; +		default: +			lfp = stdout; +		} +		strcpy(msgbuf, "usbmuxd: "); +		vsnprintf(msgbuf + 9, 244, format, args); +		strcat(msgbuf, "\n"); +		fputs(msgbuf, lfp); +	} + +	va_end(args); +} + +#ifdef DEBUG +/** + * for debugging purposes. + */ +static void print_buffer(FILE * fp, const char *data, const int length) +{ +	int i; +	int j; +	unsigned char c; + +	for (i = 0; i < length; i += 16) { +		if (verbose >= 4) +			fprintf(fp, "%04x: ", i); +		for (j = 0; j < 16; j++) { +			if (i + j >= length) { +				if (verbose >= 4) +					fprintf(fp, "   "); +				continue; +			} +			if (verbose >= 4) +				fprintf(fp, "%02hhx ", *(data + i + j)); +		} +		if (verbose >= 4) +			fprintf(fp, "  | "); +		for (j = 0; j < 16; j++) { +			if (i + j >= length) +				break; +			c = *(data + i + j); +			if ((c < 32) || (c > 127)) { +				if (verbose >= 4) +					fprintf(fp, "."); +				continue; +			} +			if (verbose >= 4) +				fprintf(fp, "%c", c); +		} +		if (verbose >= 4) +			fprintf(fp, "\n"); +	} +	if (verbose >= 4) +		fprintf(fp, "\n"); +} +#endif + +/** + * Read incoming usbmuxd packet. If the packet is larger than + * the size specified by len, the data will be truncated. + * + * @param fd the file descriptor to read from. + * @param data pointer to a buffer to store the read data to. + * @param len the length of the data to be read. The buffer + *        pointed to by data should be at least len bytes in size. + * + * @return  + */ +static int usbmuxd_get_request(int fd, void **data, size_t len) +{ +	uint32_t pktlen; +	int recv_len; + +	if (peek_buf(fd, &pktlen, sizeof(pktlen)) < (int) sizeof(pktlen)) { +		return -errno; +	} + +	if (len == 0) { +		// allocate buffer space +		*data = malloc(pktlen); +	} else if (len < pktlen) { +		// target buffer is to small to hold this packet! fix it! +		if (verbose >= 2) +			logmsg(LOG_WARNING, +				   "%s: WARNING -- packet (%d) is larger than target buffer (%d)! Truncating.", +				   __func__, pktlen, len); +		pktlen = len; +	} + +	recv_len = recv_buf(fd, *data, pktlen); +	if ((recv_len > 0) && ((uint32_t) recv_len < pktlen)) { +		if (verbose >= 2) +			logmsg(LOG_WARNING, +				   "%s: Uh-oh, we got less than the packet's size, %d instead of %d...", +				   __func__, recv_len, pktlen); +	} +#ifdef DEBUG +	if (*data && (recv_len > 0) && verbose >= 4) { +		fprintf(stderr, "%s: received:\n", __func__); +		print_buffer(stderr, *data, recv_len); +	} +#endif + +	return recv_len; +} + +/** + * Send a usbmuxd result packet with given tag and result_code. + * + * @param fd the file descriptor to write to. + * @param tag the tag value that identifies where this message belongs to. + * @param result_code the error value (0 = Success, most likely errno values otherwise) + * + * @return the return value returned by send_buf (normally the number of bytes sent) + */ +static int usbmuxd_send_result(int fd, uint32_t tag, uint32_t result_code) +{ +	struct usbmuxd_result res; +	int ret; + +	res.header.length = sizeof(res); +	res.header.reserved = 0; +	res.header.type = USBMUXD_RESULT; +	res.header.tag = tag; +	res.result = result_code; + +	if (verbose >= 4) +		logmsg(LOG_NOTICE, "%s: tag=%d result=%d", __func__, +			   res.header.tag, res.result); + +	ret = send_buf(fd, &res, sizeof(res)); +	fsync(fd);					// let's get it sent +	return ret; +} + +/** + * this thread reads from the usb connection and writes the  + * data to the connected client. + * + * @param arg pointer to a client_data structure. + * + * @return NULL in any case + */ +static void *usbmuxd_client_reader_thread(void *arg) +{ +	struct client_data *cdata; + +	char rbuffer[512]; +	uint32_t rbuffersize = 512; +	uint32_t rlen; +	int err; +	char *cursor; +	ssize_t len; +	int result; + +	if (!arg) { +		if (verbose >= 2) +			logmsg(LOG_ERR, "%s: invalid client_data supplied!", __func__); +		cdata->reader_dead = 1; +		return NULL; +	} + +	cdata = (struct client_data *) arg; + +	cdata->reader_dead = 0; + +	if (verbose >= 3) +		logmsg(LOG_NOTICE, "%s[%d:%d]: started", __func__, +			   cdata->dev->device_id, cdata->dev->use_count); + +	while (!quit_flag && !cdata->reader_quit) { +		result = check_fd(cdata->socket, FD_WRITE, DEFAULT_TIMEOUT); +		if (result <= 0) { +			if (result < 0) { +				if (verbose >= 2) +					logmsg(LOG_ERR, "%s: select error: %s", __func__, +						   strerror(errno)); +			} +			continue; +		} + +		rlen = 0; +		err = +			usbmux_recv_timeout(cdata->muxclient, rbuffer, rbuffersize, +								&rlen, DEFAULT_TIMEOUT); +		if (err != 0) { +			if (verbose >= 2) +				logmsg(LOG_ERR, +					   "%s[%d:%d]: encountered USB read error: %d", +					   __func__, cdata->dev->device_id, +					   cdata->dev->use_count, err); +			break; +		} + +		cursor = rbuffer; +		while (rlen > 0) { +			len = send_buf(cdata->socket, cursor, rlen); +			if (len <= 0) { +				logmsg(LOG_ERR, "%s: Error: send returned %d", __func__, +					   len); +				err = 1; +				break; +			} +			// calculate remainder +			rlen -= len; +			// advance cursor +			cursor += len; +		} +		if (err != 0) { +			logmsg(LOG_ERR, "%s: Error when writing to client...", +				   __func__); +			break; +		} +		fsync(cdata->socket); +	} + +	if (verbose >= 3) +		logmsg(LOG_NOTICE, "%s[%d:%d]: terminated", __func__, +			   cdata->dev->device_id, cdata->dev->use_count); + +	cdata->reader_dead = 1; + +	return NULL; +} + +/** + * This function handles the connecting procedure to a previously + * set up usbmux client. + * Sends a usbmuxd result packet denoting success or failure. + * A successful result is mandatory for later communication. + * + * @param cdata pointer to a previously initialized client_data structure + * + * @return + */ +static int usbmuxd_handleConnectResult(struct client_data *cdata) +{ +	int result; +	char buffer[512]; +	char err_type[64]; +	int err_code; +	ssize_t maxlen = 512; +	uint32_t rlen; +	int err; + +	if (!cdata) { +		if (verbose >= 2) +			logmsg(LOG_ERR, "%s: Invalid client_data provided!", __func__); +		return -EINVAL; +	} + +	result = check_fd(cdata->socket, FD_WRITE, DEFAULT_TIMEOUT); +	if (result <= 0) { +		if (result < 0) { +			if (verbose >= 2) +				logmsg(LOG_ERR, "%s: select error: %s", __func__, +					   strerror(errno)); +			return result; +		} +	} else { +		result = 0; +		err = +			usbmux_recv_timeout(cdata->muxclient, buffer, maxlen, &rlen, +								100); +		if (err < 0) { +			if (verbose >= 2) +				logmsg(LOG_ERR, "%s: encountered USB read error: %d", +					   __func__, err); +			usbmuxd_send_result(cdata->socket, cdata->tag, -err); +			return err; +		} else { +			if (rlen > 0) { +				if ((buffer[0] == 1) && (rlen > 20) +					&& !memcmp(buffer + 1, "handleConnectResult:", 20)) { +					// hm... we got an error message! +					buffer[rlen] = 0; +					if (verbose >= 1) +						logmsg(LOG_ERR, "%s: %s\n", __func__, buffer + 22); + +					if (sscanf +						(buffer + 22, "%s - %d\n", err_type, &err_code) +						== 2) { +						usbmuxd_send_result(cdata->socket, cdata->tag, +											err_code); +						return -err_code; +					} else { +						usbmuxd_send_result(cdata->socket, cdata->tag, +											ENODATA); +						return -ENODATA; +					} +				} else { +					// send success result +					usbmuxd_send_result(cdata->socket, cdata->tag, 0); +					// and the server greeting message +					send_buf(cdata->socket, buffer, rlen); +				} +			} else { +				// no server greeting? this seems to be ok. send success. +				usbmuxd_send_result(cdata->socket, cdata->tag, 0); +			} +		} +		//fsync(cdata->socket); +	} +	return result; +} + +/** + * This thread handles the communication between the connected iPhone/iPod + * and the client that created the connection. + */ +static void *usbmuxd_client_handler_thread(void *arg) +{ +	struct client_data *cdata; +	int result; +	char *cursor; +	char buffer[65536]; +	ssize_t len; +	ssize_t maxlen = sizeof(buffer); +	uint32_t wlen; +	int err; + +	if (!arg) { +		if (verbose >= 2) +			logmsg(LOG_ERR, "%s: invalid client_data provided!", __func__); +		return NULL; +	} + +	cdata = (struct client_data *) arg; + +	if (verbose >= 3) +		logmsg(LOG_NOTICE, "%s[%d:%d]: started", __func__, +			   cdata->dev->device_id, cdata->dev->use_count); + +	if (usbmuxd_handleConnectResult(cdata)) { +		if (verbose >= 3) +			logmsg(LOG_ERR, "handleConnectResult: Error"); +		goto leave; +	} else { +		if (verbose >= 3) +			logmsg(LOG_NOTICE, "handleConnectResult: Success"); +	} + +	// starting mux reader thread +	cdata->reader_quit = 0; +	cdata->reader_dead = 0; +	if (pthread_create +		(&cdata->reader, NULL, usbmuxd_client_reader_thread, cdata) != 0) { +		if (verbose >= 2) +			logmsg(LOG_ERR, "%s: could not start client_reader thread", +				   __func__); +		cdata->reader = 0; +	} + +	while (!quit_flag && !cdata->reader_dead) { +		result = check_fd(cdata->socket, FD_READ, DEFAULT_TIMEOUT); +		if (result <= 0) { +			if (result < 0) { +				if (verbose >= 3) +					logmsg(LOG_ERR, "%s: Error: checkfd: %s", __func__, +						   strerror(errno)); +			} +			continue; +		} +		// check_fd told us there's data available, so read from client +		// and push to USB device. +		len = recv(cdata->socket, buffer, maxlen, 0); +		if (len == 0) { +			break; +		} +		if (len < 0) { +			if (verbose >= 2) +				logmsg(LOG_ERR, "%s[%d:%d]: Error: recv: %s", __func__, +					   cdata->dev->device_id, cdata->dev->use_count, +					   strerror(errno)); +			break; +		} + +		cursor = buffer; + +		pthread_mutex_lock(&cdata->dev->writer_mutex); +		do { +			wlen = 0; +			err = usbmux_send(cdata->muxclient, cursor, len, &wlen); +			if (err == -ETIMEDOUT) { +				// some kind of timeout... just be patient and retry. +			} else if (err < 0) { +				if (verbose >= 2) +					logmsg(LOG_ERR, "%s[%d:%d]: USB write error: %d", +						   __func__, cdata->dev->device_id, +						   cdata->dev->use_count, err); +				len = -1; +				break; +			} +			// calculate remainder. +			len -= wlen; +			// advance cursor appropiately. +			cursor += wlen; +		} +		while ((len > 0) && !quit_flag); +		pthread_mutex_unlock(&cdata->dev->writer_mutex); +		if (len < 0) { +			break; +		} +	} + +  leave: +	// cleanup +	if (verbose >= 3) +		logmsg(LOG_NOTICE, "%s[%d:%d]: terminating", __func__, +			   cdata->dev->device_id, cdata->dev->use_count); +	if (cdata->reader != 0) { +		cdata->reader_quit = 1; +		pthread_join(cdata->reader, NULL); +	} + +	cdata->handler_dead = 1; + +	if (verbose >= 3) +		logmsg(LOG_NOTICE, "%s[%d:%d]: terminated", __func__, +			   cdata->dev->device_id, cdata->dev->use_count); +	return NULL; +} + +/** + * Thread performing usb_bulk_read from the connected device. + * One thread per device. Lives as long as the device is in use. + */ +static void *usbmuxd_bulk_reader_thread(void *arg) +{ +	struct device_info *cur_dev; +	int err; + +	if (!arg) { +		if (verbose >= 2) +			logmsg(LOG_ERR, "%s: Invalid client_data provided", __func__); +		return NULL; +	} + +	cur_dev = (struct device_info *) arg; + +	if (verbose >= 3) +		logmsg(LOG_NOTICE, "%s: started", __func__); + +	while (!quit_flag && cur_dev) { + +		pthread_mutex_lock(&cur_dev->mutex); +		if (cur_dev->use_count <= 0) { +			pthread_mutex_unlock(&cur_dev->mutex); +			break; +		} +		pthread_mutex_unlock(&cur_dev->mutex); + +		if ((err = usbmux_pullbulk(cur_dev->phone)) < 0) { +			if (verbose >= 1) +				logmsg(LOG_ERR, "%s: error %d when reading from device", +					   __func__, err); +			break; +		} +	} + +	if (verbose >= 3) +		logmsg(LOG_NOTICE, "%s: terminated", __func__); + +	return NULL; +} + +/** + * This thread is started when a new connection is accepted. + * It performs the handshake, then waits for the connect packet and + * on success it starts the usbmuxd_client_handler thread. + */ +static void *usbmuxd_client_init_thread(void *arg) +{ +	struct client_data *cdata; +	struct usbmuxd_scan_request *s_req = NULL; +	struct usbmuxd_device_info_record dev_info_rec; +	struct usbmuxd_connect_request *c_req = NULL; + +	struct usb_bus *bus; +	struct usb_device *dev; + +	int recv_len; +	int found = 0; +	int res; +	int i; + +	usbmux_device_t phone = NULL; +	struct device_info *cur_dev = NULL; + +	if (!arg) { +		if (verbose >= 1) +			logmsg(LOG_ERR, "%s[%x]: invalid client_data provided!", +				   __func__, THREAD); +		return NULL; +	} + +	cdata = (struct client_data *) arg; +	cdata->dead = 0; + +	if (verbose >= 3) +		logmsg(LOG_NOTICE, "%s[%x]: started (fd=%d)", __func__, THREAD, +			   cdata->socket); + +	if ((recv_len = +		 usbmuxd_get_request(cdata->socket, (void **) &s_req, 0)) <= 0) { +		if (verbose >= 2) +			logmsg(LOG_ERR, "%s[%x]: No scan packet received, error %s", +				   __func__, THREAD, strerror(errno)); +		goto leave; +	} + +	if ((recv_len == sizeof(struct usbmuxd_scan_request)) +		&& (s_req->header.length == sizeof(struct usbmuxd_scan_request)) +		&& (s_req->header.reserved == 0) +		&& (s_req->header.type == USBMUXD_SCAN)) { +		// send success response +		if (verbose >= 3) +			logmsg(LOG_NOTICE, "%s[%x]: Got scan packet!", __func__, +				   THREAD); +		usbmuxd_send_result(cdata->socket, s_req->header.tag, 0); +	} else if ((recv_len == sizeof(struct usbmuxd_connect_request)) +			   && (s_req->header.type == USBMUXD_CONNECT)) { +		c_req = (struct usbmuxd_connect_request *) s_req; +		s_req = NULL; +		goto connect; +	} else { +		// send error response and exit +		if (verbose >= 2) +			logmsg(LOG_ERR, "%s[%x]: Invalid scan packet received.", +				   __func__, THREAD); +		// TODO is this required?! +		usbmuxd_send_result(cdata->socket, s_req->header.tag, EINVAL); +		goto leave; +	} + +	pthread_mutex_lock(&usb_mutex); +	// gather data about all iPhones/iPods attached + +	if (verbose >= 5) +		logmsg(LOG_DEBUG, "%s[%x]: usb init", __func__, THREAD); +	usb_init(); +	if (verbose >= 5) +		logmsg(LOG_DEBUG, "%s[%x]: usb find busses", __func__, THREAD); +	usb_find_busses(); +	if (verbose >= 5) +		logmsg(LOG_DEBUG, "%s[%x]: usb find devices", __func__, THREAD); +	usb_find_devices(); + +	if (verbose >= 2) +		logmsg(LOG_NOTICE, "%s[%x]: Looking for attached devices...", +			   __func__, THREAD); + +	for (bus = usb_get_busses(); bus; bus = bus->next) { +		for (dev = bus->devices; dev; dev = dev->next) { +			if (dev->descriptor.idVendor == 0x05ac +				&& dev->descriptor.idProduct >= 0x1290 +				&& dev->descriptor.idProduct <= 0x1293) { +				if (verbose >= 1) +					logmsg(LOG_NOTICE, +						   "%s[%x]: Found device on bus %d, id %d", +						   __func__, THREAD, bus->location, dev->devnum); +				found++; + +				// construct packet +				memset(&dev_info_rec, 0, sizeof(dev_info_rec)); +				dev_info_rec.header.length = sizeof(dev_info_rec); +				dev_info_rec.header.type = USBMUXD_DEVICE_INFO; +				dev_info_rec.device.device_id = dev->devnum; +				dev_info_rec.device.product_id = dev->descriptor.idProduct; +				if (dev->descriptor.iSerialNumber) { +					usb_dev_handle *udev; +					//pthread_mutex_lock(&usbmux_mutex); +					udev = usb_open(dev); +					if (udev) { +						usb_get_string_simple(udev, +											  dev->descriptor. +											  iSerialNumber, +											  dev_info_rec.device. +											  serial_number, +											  sizeof(dev_info_rec.device. +													 serial_number) + 1); +						usb_close(udev); +					} +					//pthread_mutex_unlock(&usbmux_mutex); +				} +#ifdef DEBUG +				if (verbose >= 4) +					print_buffer(stderr, (char *) &dev_info_rec, +								 sizeof(dev_info_rec)); +#endif + +				// send it +				if (send_buf +					(cdata->socket, &dev_info_rec, +					 sizeof(dev_info_rec)) <= 0) { +					if (verbose >= 3) +						logmsg(LOG_ERR, +							   "%s[%x]: Error: Could not send device info: %s", +							   __func__, THREAD, strerror(errno)); +					found--; +				} +			} +		} +	} +	pthread_mutex_unlock(&usb_mutex); + +	if (found <= 0) { +		if (verbose >= 1) +			logmsg(LOG_NOTICE, +				   "%s[%x]: No attached iPhone/iPod devices found.", +				   __func__, THREAD); +		goto leave; +	} + +	if (verbose >= 3) +		logmsg(LOG_NOTICE, "%s[%x]: Waiting for connect request", __func__, +			   THREAD); + +	// now wait for connect request +	//memset(&c_req, 0, sizeof(c_req)); +	if ((recv_len = +		 usbmuxd_get_request(cdata->socket, (void **) &c_req, 0)) <= 0) { +		if (verbose >= 3) +			logmsg(LOG_NOTICE, +				   "%s[%x]: Did not receive any connect request.", +				   __func__, THREAD); +		goto leave; +	} + +  connect: + +	if (c_req->header.type != USBMUXD_CONNECT) { +		if (verbose >= 2) +			logmsg(LOG_ERR, +				   "%s[%x]: Unexpected packet of type %d received.", +				   __func__, THREAD, c_req->header.type); +		goto leave; +	} + +	if (verbose >= 3) +		logmsg(LOG_NOTICE, +			   "%s[%x]: Setting up connection to usb device #%d on port %d", +			   __func__, THREAD, c_req->device_id, +			   ntohs(c_req->tcp_dport)); + +	// find the device, and open usb connection +	pthread_mutex_lock(&usbmux_mutex); +	phone = NULL; +	cur_dev = NULL; +	// first check if we already have an open connection +	if (devices) { +		for (i = 0; i < device_count; i++) { +			if (devices[i]) { +				if (devices[i]->device_id == c_req->device_id) { +					devices[i]->use_count++; +					cur_dev = devices[i]; +					phone = cur_dev->phone; +					break; +				} +			} +		} +	} +	if (!phone) { +		// if not found, make a new connection +		if (verbose >= 2) +			logmsg(LOG_NOTICE, +				   "%s[%x]: creating new usb connection, device_id=%d", +				   __func__, THREAD, c_req->device_id); + +		pthread_mutex_lock(&usb_mutex); +		if (usbmux_get_specific_device(0, c_req->device_id, &phone) < 0) { +			pthread_mutex_unlock(&usb_mutex); +			pthread_mutex_unlock(&usbmux_mutex); +			if (verbose >= 1) +				logmsg(LOG_ERR, "%s[%x]: device_id %d could not be opened", +					   __func__, THREAD, c_req->device_id); +			usbmuxd_send_result(cdata->socket, c_req->header.tag, ENODEV); +			goto leave; +		} +		pthread_mutex_unlock(&usb_mutex); + +		// create device object +		if (verbose >= 3) +			logmsg(LOG_DEBUG, "%s[%x]: add to device list", __func__, +				   THREAD); +		cur_dev = +			(struct device_info *) malloc(sizeof(struct device_info)); +		memset(cur_dev, 0, sizeof(struct device_info)); +		cur_dev->use_count = 1; +		cur_dev->device_id = c_req->device_id; +		cur_dev->phone = phone; +		cur_dev->bulk_reader = 0; +		pthread_mutex_init(&cur_dev->mutex, NULL); +		pthread_mutex_init(&cur_dev->writer_mutex, NULL); + +		if (verbose >= 3) +			logmsg(LOG_DEBUG, "%s[%x]: device_count = %d", __func__, +				   THREAD, device_count); + +		// add to list of devices +		devices = +			(struct device_info **) realloc(devices, +											sizeof(struct device_info *) * +											(device_count + 1)); +		if (devices) { +			devices[device_count] = cur_dev; +			device_count++; +		} +	} else { +		if (verbose >= 2) +			logmsg(LOG_NOTICE, +				   "%s[%x]: reusing usb connection, device_id=%d", +				   __func__, THREAD, c_req->device_id); +	} +	pthread_mutex_unlock(&usbmux_mutex); + +	// setup connection to iPhone/iPod +//    pthread_mutex_lock(&usbmux_mutex); +	res = +		usbmux_new_client(cur_dev->phone, 0, ntohs(c_req->tcp_dport), +						  &(cdata->muxclient)); +//    pthread_mutex_unlock(&usbmux_mutex); + +	if (res != 0) { +		usbmuxd_send_result(cdata->socket, c_req->header.tag, res); +		if (verbose >= 1) +			logmsg(LOG_ERR, +				   "%s[%x]: mux_new_client returned %d, aborting.", +				   __func__, THREAD, res); +		goto leave; +	} +	// start bulk reader thread (once per device) +	pthread_mutex_lock(&cur_dev->mutex); +	if (cur_dev->bulk_reader == 0) { +		pthread_create(&cur_dev->bulk_reader, NULL, +					   usbmuxd_bulk_reader_thread, cur_dev); +	} +	pthread_mutex_unlock(&cur_dev->mutex); + +	// start connection handler thread +	cdata->handler_dead = 0; +	cdata->tag = c_req->header.tag; +	cdata->dev = cur_dev; +	if (pthread_create +		(&cdata->handler, NULL, usbmuxd_client_handler_thread, cdata) != 0) +	{ +		if (verbose >= 1) +			logmsg(LOG_ERR, +				   "%s[%x]: could not create usbmuxd_client_handler_thread!", +				   __func__, THREAD); +		cdata->handler = 0; +		goto leave; +	} +	// wait for handler thread to finish its work +	if (cdata->handler != 0) { +		pthread_join(cdata->handler, NULL); +	} + +	if (verbose >= 2) +		logmsg(LOG_NOTICE, "%s[%x]: closing connection", __func__, THREAD); + +	// time to clean up +	if (cdata && cdata->muxclient) {	// should be non-NULL +		usbmux_free_client(cdata->muxclient); +	} + +  leave: +	if (verbose >= 3) +		logmsg(LOG_NOTICE, "%s[%x]: terminating", __func__, THREAD); + +	if (s_req) { +		free(s_req); +	} +	if (c_req) { +		free(c_req); +	} +	// this has to be freed only if it's not in use anymore as it closes +	// the USB connection +	pthread_mutex_lock(&usbmux_mutex); +	if (cur_dev) { +		pthread_mutex_lock(&cur_dev->mutex); +		if (cur_dev->use_count > 1) { +			if (verbose >= 2) +				logmsg(LOG_NOTICE, +					   "%s[%x]: decreasing device use count (from %d to %d)", +					   __func__, THREAD, cur_dev->use_count, +					   cur_dev->use_count - 1); +			cur_dev->use_count--; +			pthread_mutex_unlock(&cur_dev->mutex); +		} else { +			if (verbose >= 2) +				logmsg(LOG_NOTICE, +					   "%s[%x]: last client disconnected, cleaning up", +					   __func__, THREAD); +			cur_dev->use_count = 0; +			pthread_mutex_unlock(&cur_dev->mutex); +			if (cur_dev->bulk_reader != 0) { +				if (verbose >= 3) +					logmsg(LOG_NOTICE, "%s[%x]: joining bulk_reader...", +						   __func__, THREAD); +				pthread_join(cur_dev->bulk_reader, NULL); +			} +			pthread_mutex_lock(&usb_mutex); +			usbmux_free_device(cur_dev->phone); +			pthread_mutex_unlock(&usb_mutex); +			pthread_mutex_destroy(&cur_dev->writer_mutex); +			pthread_mutex_destroy(&cur_dev->mutex); +			free(cur_dev); +			cur_dev = NULL; +			if (device_count > 1) { +				struct device_info **newlist; +				int j; + +				newlist = +					(struct device_info **) +					malloc(sizeof(struct device_info *) +						   * device_count - 1); +				for (i = 0; i < device_count; i++) { +					if (devices[i] != NULL) { +						newlist[j++] = devices[i]; +					} +				} +				free(devices); +				devices = newlist; +				device_count--; +			} else { +				free(devices); +				devices = NULL; +				device_count = 0; +			} +		} +	} +	pthread_mutex_unlock(&usbmux_mutex); + +	cdata->dead = 1; +	close(cdata->socket); + +	if (verbose >= 3) +		logmsg(LOG_NOTICE, "%s[%x]: terminated", __func__, THREAD); + +	return NULL; +} + +/** + * make this program run detached from the current console + */ +static int daemonize() +{ +	pid_t pid; +	pid_t sid; + +	// already a daemon +	if (getppid() == 1) +		return 0; + +	pid = fork(); +	if (pid < 0) { +		exit(EXIT_FAILURE); +	} + +	if (pid > 0) { +		// exit parent process +		exit(EXIT_SUCCESS); +	} +	// At this point we are executing as the child process + +	// Change the file mode mask +	umask(0); + +	// Create a new SID for the child process +	sid = setsid(); +	if (sid < 0) { +		return -1; +	} +	// Change the current working directory. +	if ((chdir("/")) < 0) { +		return -2; +	} +	// Redirect standard files to /dev/null +	freopen("/dev/null", "r", stdin); +	freopen("/dev/null", "w", stdout); +	freopen("/dev/null", "w", stderr); + +	return 0; +} + +/** + * signal handler function for cleaning up properly + */ +static void clean_exit(int sig) +{ +	if (sig == SIGINT) { +		if (verbose >= 1) +			fprintf(stderr, "CTRL+C pressed\n"); +	} +	quit_flag = 1; +} + +static void usage() +{ +	printf("usage: usbmuxd [options]\n"); +	printf("\t-h|--help        print this message.\n"); +	printf("\t-v|--verbose     be verbose\n"); +	printf("\t-f|--foreground  do not daemonize\n"); +	printf("\n"); +} + +static void parse_opts(int argc, char **argv) +{ +	static struct option longopts[] = { +		{"help", 0, NULL, 'h'}, +		{"foreground", 0, NULL, 'f'}, +		{"verbose", 0, NULL, 'v'}, +		{"exit-on-no-devices", 0, NULL, 'e'}, +		{NULL, 0, NULL, 0} +	}; +	int c; + +	while (1) { +		c = getopt_long(argc, argv, "hfve", longopts, (int *) 0); +		if (c == -1) { +			break; +		} + +		switch (c) { +		case 'h': +			usage(); +			exit(0); +		case 'f': +			foreground = 1; +			break; +		case 'v': +			sock_stuff_set_verbose(++verbose); +			break; +		case 'e': +			exit_on_no_devices = 1; +			break; +		default: +			usage(); +			exit(2); +		} +	} +} + +/** + * checks for attached devices + * + * @return number of devices found + */ +static int devices_attached() +{ +	struct usb_bus *bus; +	struct usb_device *dev; +	int res = 0; + +	usb_init(); +	usb_find_busses(); +	usb_find_devices(); + +	for (bus = usb_get_busses(); bus; bus = bus->next) { +		for (dev = bus->devices; dev; dev = dev->next) { +			if (dev->descriptor.idVendor == 0x05ac +				&& dev->descriptor.idProduct >= 0x1290 +				&& dev->descriptor.idProduct <= 0x1293) { +				res++; +			} +		} +	} + +	return res; +} + +/** + * main function. Initializes all stuff and then loops waiting in accept. + */ +int main(int argc, char **argv) +{ +	struct sockaddr_un c_addr; +	socklen_t len = sizeof(struct sockaddr_un); +	struct client_data *cdata = NULL; +	struct client_data **children = NULL; +	int children_capacity = DEFAULT_CHILDREN_CAPACITY; +	int i; +	int result = 0; +	int cnt = 0; +	FILE *lfd = NULL; +	struct flock lock; + +	parse_opts(argc, argv); + +	argc -= optind; +	argv += optind; + +	if (!foreground) { +		openlog("usbmuxd", LOG_PID, 0); +	} + +	if (verbose >= 2) +		logmsg(LOG_NOTICE, "starting"); + +	// signal(SIGHUP, reload_conf); // none yet +	signal(SIGINT, clean_exit); +	signal(SIGQUIT, clean_exit); +	signal(SIGTERM, clean_exit); +	signal(SIGPIPE, SIG_IGN); + +	// check for other running instance +	lfd = fopen(LOCKFILE, "r"); +	if (lfd) { +		lock.l_type = 0; +		lock.l_whence = SEEK_SET; +		lock.l_start = 0; +		lock.l_len = 0; +		fcntl(fileno(lfd), F_GETLK, &lock); +		fclose(lfd); +		if (lock.l_type != F_UNLCK) { +			logmsg(LOG_NOTICE, +				   "another instance is already running. exiting."); +			return -1; +		} +	} + +	if (exit_on_no_devices) { +		if (devices_attached() <= 0) { +			logmsg(LOG_NOTICE, "no devices attached. exiting."); +			return 0; +		} +	} + +	fsock = create_unix_socket(USBMUXD_SOCKET_FILE); +	if (fsock < 0) { +		logmsg(LOG_ERR, "Could not create socket, exiting"); +		if (!foreground) { +			closelog(); +		} +		return -1; +	} + +	chmod(USBMUXD_SOCKET_FILE, 0666); + +	if (verbose >= 3) +		usbmux_set_debug(1); + +	if (!foreground) { +		if (daemonize() < 0) { +			fprintf(stderr, "usbmuxd: FATAL: Could not daemonize!\n"); +			syslog(LOG_ERR, "FATAL: Could not daemonize!"); +			closelog(); +			exit(EXIT_FAILURE); +		} +	} +	// now open the lockfile and place the lock +	lfd = fopen(LOCKFILE, "w"); +	if (lfd) { +		lock.l_type = F_WRLCK; +		lock.l_whence = SEEK_SET; +		lock.l_start = 0; +		lock.l_len = 0; +		if (fcntl(fileno(lfd), F_SETLK, &lock) == -1) { +			logmsg(LOG_ERR, "ERROR: lockfile locking failed!"); +		} +	} +	// drop elevated privileges +	if (getuid() == 0 || geteuid() == 0) { +		struct passwd *pw = getpwnam("nobody"); +		if (pw) { +			setuid(pw->pw_uid); +		} else { +			logmsg(LOG_ERR, +				   "ERROR: Dropping privileges failed, check if user 'nobody' exists! Will now terminate."); +			exit(EXIT_FAILURE); +		} + +		// security check +		if (setuid(0) != -1) { +			logmsg(LOG_ERR, "ERROR: Failed to drop privileges properly!"); +			exit(EXIT_FAILURE); +		} +		if (verbose >= 2) +			logmsg(LOG_NOTICE, "Successfully dropped privileges"); +	} +	// Reserve space for 10 clients which should be enough. If not, the +	// buffer gets enlarged later. +	children = +		(struct client_data **) malloc(sizeof(struct client_data *) * +									   children_capacity); +	if (!children) { +		logmsg(LOG_ERR, +			   "Out of memory when allocating memory for child threads. Terminating."); +		if (!foreground) { +			closelog(); +		} +		exit(EXIT_FAILURE); +	} +	memset(children, 0, sizeof(struct client_data *) * children_capacity); + +	if (verbose >= 2) +		logmsg(LOG_NOTICE, "waiting for connection"); +	while (!quit_flag) { +		// Check the file descriptor before accepting a connection. +		// If no connection attempt is made, just repeat... +		result = check_fd(fsock, FD_READ, 1000); +		if (result <= 0) { +			if (result == 0) { +				// cleanup +				for (i = 0; i < children_capacity; i++) { +					if (children[i]) { +						if (children[i]->dead != 0) { +							pthread_join(children[i]->thread, NULL); +							if (verbose >= 3) +								logmsg(LOG_NOTICE, +									   "reclaimed client thread (fd=%d)", +									   children[i]->socket); +							free(children[i]); +							children[i] = NULL; +							cnt++; +						} else { +							cnt = 0; +						} +					} else { +						cnt++; +					} +				} + +				if ((children_capacity > DEFAULT_CHILDREN_CAPACITY) +					&& ((children_capacity - cnt) <= +						DEFAULT_CHILDREN_CAPACITY)) { +					children_capacity = DEFAULT_CHILDREN_CAPACITY; +					children = +						realloc(children, +								sizeof(struct client_data *) * +								children_capacity); +				} +				continue; +			} else { +				if (verbose >= 3) +					logmsg(LOG_ERR, "usbmuxd: select error: %s", +						   strerror(errno)); +				continue; +			} +		} + +		cdata = (struct client_data *) malloc(sizeof(struct client_data)); +		memset(cdata, 0, sizeof(struct client_data)); +		if (!cdata) { +			quit_flag = 1; +			logmsg(LOG_ERR, "Error: Out of memory! Terminating."); +			break; +		} + +		cdata->socket = accept(fsock, (struct sockaddr *) &c_addr, &len); +		if (cdata->socket < 0) { +			free(cdata); +			if (errno == EINTR) { +				continue; +			} else { +				if (verbose >= 3) +					logmsg(LOG_ERR, "Error in accept: %s", +						   strerror(errno)); +				continue; +			} +		} + +		if (verbose >= 1) +			logmsg(LOG_NOTICE, "new client connected (fd=%d)", +				   cdata->socket); + +		// create client thread: +		if (pthread_create +			(&cdata->thread, NULL, usbmuxd_client_init_thread, cdata) == 0) +		{ +			for (i = 0; i < children_capacity; i++) { +				if (children[i] == NULL) +					break; +			} +			if (i == children_capacity) { +				// enlarge buffer +				children_capacity++; +				children = +					realloc(children, +							sizeof(struct client_data *) * +							children_capacity); +				if (!children) { +					logmsg(LOG_ERR, +						   "Out of memory when enlarging child thread buffer"); +				} +			} +			children[i] = cdata; +		} else { +			logmsg(LOG_ERR, "Failed to create client_init_thread."); +			close(cdata->socket); +			free(cdata); +			cdata = NULL; +		} +	} + +	if (verbose >= 3) +		logmsg(LOG_NOTICE, "terminating"); + +	// preparing for shutdown: wait for child threads to terminate (if any) +	if (verbose >= 2) +		logmsg(LOG_NOTICE, "waiting for child threads to terminate..."); +	for (i = 0; i < children_capacity; i++) { +		if (children[i] != NULL) { +			pthread_join(children[i]->thread, NULL); +			free(children[i]); +		} +	} + +	// delete the children set. +	free(children); +	children = NULL; + + +	if (fsock >= 0) { +		close(fsock); +	} + +	unlink(USBMUXD_SOCKET_FILE); + +	// unlock lock file and close it. +	if (lfd) { +		lock.l_type = F_UNLCK; +		fcntl(fileno(lfd), F_SETLK, &lock); +		fclose(lfd); +	} + +	if (verbose >= 1) +		logmsg(LOG_NOTICE, "usbmuxd: terminated"); +	if (!foreground) { +		closelog(); +	} + +	return 0; +} diff --git a/src/sock_stuff.c b/src/sock_stuff.c new file mode 100644 index 0000000..b51d6ba --- /dev/null +++ b/src/sock_stuff.c @@ -0,0 +1,298 @@ +#include <stdio.h> +#include <stddef.h> +#include <unistd.h> +#include <errno.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <netdb.h> +#include <arpa/inet.h> +#include "sock_stuff.h" + +#define RECV_TIMEOUT 20000 + +static int verbose = 0; + +void sock_stuff_set_verbose(int level) +{ +	verbose = level; +} + +int create_unix_socket(const char *filename) +{ +	struct sockaddr_un name; +	int sock; +	size_t size; + +	// remove if still present +	unlink(filename); + +	/* Create the socket. */ +	sock = socket(PF_LOCAL, SOCK_STREAM, 0); +	if (sock < 0) { +		perror("socket"); +		return -1; +	} + +	/* Bind a name to the socket. */ +	name.sun_family = AF_LOCAL; +	strncpy(name.sun_path, filename, sizeof(name.sun_path)); +	name.sun_path[sizeof(name.sun_path) - 1] = '\0'; + +	/* The size of the address is +	   the offset of the start of the filename, +	   plus its length, +	   plus one for the terminating null byte. +	   Alternatively you can just do: +	   size = SUN_LEN (&name); +	 */ +	size = (offsetof(struct sockaddr_un, sun_path) +			+ strlen(name.sun_path) + 1); + +	if (bind(sock, (struct sockaddr *) &name, size) < 0) { +		perror("bind"); +		close(sock); +		return -1; +	} + +	if (listen(sock, 10) < 0) { +		perror("listen"); +		close(sock); +		return -1; +	} + +	return sock; +} + +int connect_unix_socket(const char *filename) +{ +	struct sockaddr_un name; +	int sfd = -1; +	size_t size; +	struct stat fst; + +	// check if socket file exists... +	if (stat(filename, &fst) != 0) { +		if (verbose >= 2) +			fprintf(stderr, "%s: stat '%s': %s\n", __func__, filename, +					strerror(errno)); +		return -1; +	} +	// ... and if it is a unix domain socket +	if (!S_ISSOCK(fst.st_mode)) { +		if (verbose >= 2) +			fprintf(stderr, "%s: File '%s' is not a socket!\n", __func__, +					filename); +		return -1; +	} +	// make a new socket +	if ((sfd = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) { +		if (verbose >= 2) +			fprintf(stderr, "%s: socket: %s\n", __func__, strerror(errno)); +		return -1; +	} +	// and connect to 'filename' +	name.sun_family = AF_LOCAL; +	strncpy(name.sun_path, filename, sizeof(name.sun_path)); +	name.sun_path[sizeof(name.sun_path) - 1] = 0; + +	size = (offsetof(struct sockaddr_un, sun_path) +			+ strlen(name.sun_path) + 1); + +	if (connect(sfd, (struct sockaddr *) &name, size) < 0) { +		close(sfd); +		if (verbose >= 2) +			fprintf(stderr, "%s: connect: %s\n", __func__, +					strerror(errno)); +		return -1; +	} + +	return sfd; +} + +int create_socket(uint16_t port) +{ +	int sfd = -1; +	int yes = 1; +	struct sockaddr_in saddr; + +	if (0 > (sfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))) { +		perror("socket()"); +		return -1; +	} + +	if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { +		perror("setsockopt()"); +		close(sfd); +		return -1; +	} + +	memset((void *) &saddr, 0, sizeof(saddr)); +	saddr.sin_family = AF_INET; +	saddr.sin_addr.s_addr = htonl(INADDR_ANY); +	saddr.sin_port = htons(port); + +	if (0 > bind(sfd, (struct sockaddr *) &saddr, sizeof(saddr))) { +		perror("bind()"); +		close(sfd); +		return -1; +	} + +	if (listen(sfd, 1) == -1) { +		perror("listen()"); +		close(sfd); +		return -1; +	} + +	return sfd; +} + +int connect_socket(const char *addr, uint16_t port) +{ +	int sfd = -1; +	int yes = 1; +	struct hostent *hp; +	struct sockaddr_in saddr; + +	if (!addr) { +		errno = EINVAL; +		return -1; +	} + +	if ((hp = gethostbyname(addr)) == NULL) { +		if (verbose >= 2) +			fprintf(stderr, "%s: unknown host '%s'\n", __func__, addr); +		return -1; +	} + +	if (!hp->h_addr) { +		if (verbose >= 2) +			fprintf(stderr, "%s: gethostbyname returned NULL address!\n", +					__func__); +		return -1; +	} + +	if (0 > (sfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))) { +		perror("socket()"); +		return -1; +	} + +	if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { +		perror("setsockopt()"); +		close(sfd); +		return -1; +	} + +	memset((void *) &saddr, 0, sizeof(saddr)); +	saddr.sin_family = AF_INET; +	saddr.sin_addr.s_addr = *(uint32_t *) hp->h_addr; +	saddr.sin_port = htons(port); + +	if (connect(sfd, (struct sockaddr *) &saddr, sizeof(saddr)) < 0) { +		perror("connect"); +		close(sfd); +		return -2; +	} + +	return sfd; +} + +int check_fd(int fd, fd_mode fdm, unsigned int timeout) +{ +	fd_set fds; +	int sret; +	int eagain; +	struct timeval to; + +	if (fd <= 0) { +		if (verbose >= 2) +			fprintf(stderr, "ERROR: invalid fd in check_fd %d\n", fd); +		return -1; +	} + +	FD_ZERO(&fds); +	FD_SET(fd, &fds); + +	to.tv_sec = (time_t) (timeout / 1000); +	to.tv_usec = (time_t) ((timeout - (to.tv_sec * 1000)) * 1000); + +	sret = -1; + +	do { +		eagain = 0; +		switch (fdm) { +		case FD_READ: +			sret = select(fd + 1, &fds, NULL, NULL, &to); +			break; +		case FD_WRITE: +			sret = select(fd + 1, NULL, &fds, NULL, &to); +			break; +		case FD_EXCEPT: +			sret = select(fd + 1, NULL, NULL, &fds, &to); +			break; +		default: +			return -1; +		} + +		if (sret < 0) { +			switch (errno) { +			case EINTR: +				// interrupt signal in select +				if (verbose >= 2) +					fprintf(stderr, "%s: EINTR\n", __func__); +				eagain = 1; +				break; +			case EAGAIN: +				if (verbose >= 2) +					fprintf(stderr, "%s: EAGAIN\n", __func__); +				break; +			default: +				if (verbose >= 2) +					fprintf(stderr, "%s: select failed: %s\n", __func__, +							strerror(errno)); +				return -1; +			} +		} +	} while (eagain); + +	return sret; +} + +int recv_buf(int fd, void *data, size_t length) +{ +	return recv_buf_timeout(fd, data, length, 0, RECV_TIMEOUT); +} + +int peek_buf(int fd, void *data, size_t length) +{ +	return recv_buf_timeout(fd, data, length, MSG_PEEK, RECV_TIMEOUT); +} + +int recv_buf_timeout(int fd, void *data, size_t length, int flags, +					 unsigned int timeout) +{ +	int res; +	int result; + +	// check if data is available +	res = check_fd(fd, FD_READ, timeout); +	if (res <= 0) { +		return res; +	} +	// if we get here, there _is_ data available +	result = recv(fd, data, length, flags); +	if (res > 0 && result == 0) { +		// but this is an error condition +		if (verbose >= 3) +			fprintf(stderr, "%s: fd=%d recv returned 0\n", __func__, fd); +		return -1; +	} +	return result; +} + +int send_buf(int fd, void *data, size_t length) +{ +	return send(fd, data, length, 0); +} diff --git a/src/sock_stuff.h b/src/sock_stuff.h new file mode 100644 index 0000000..190f7e1 --- /dev/null +++ b/src/sock_stuff.h @@ -0,0 +1,28 @@ +#ifndef __SOCK_STUFF_H +#define __SOCK_STUFF_H + +#include <stdint.h> + +enum fd_mode { +	FD_READ, +	FD_WRITE, +	FD_EXCEPT +}; +typedef enum fd_mode fd_mode; + +int create_unix_socket(const char *filename); +int connect_unix_socket(const char *filename); +int create_socket(uint16_t port); +int connect_socket(const char *addr, uint16_t port); +int check_fd(int fd, fd_mode fdm, unsigned int timeout); + +int recv_buf(int fd, void *data, size_t size); +int peek_buf(int fd, void *data, size_t size); +int recv_buf_timeout(int fd, void *data, size_t size, int flags, +					 unsigned int timeout); + +int send_buf(int fd, void *data, size_t size); + +void sock_stuff_set_verbose(int level); + +#endif							/* __SOCK_STUFF_H */ diff --git a/src/usbmux.c b/src/usbmux.c new file mode 100644 index 0000000..e86e3bc --- /dev/null +++ b/src/usbmux.c @@ -0,0 +1,1259 @@ +/* + * Copyright (c) 2008 Jing Su. 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  + */ +#include <stdint.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <usb.h> +#include <stdio.h> +#include <arpa/inet.h> +#include <errno.h> +#include <pthread.h> +#include "usbmux.h" + +#define BULKIN 0x85 +#define BULKOUT 0x04 +#define HEADERLEN 28 + +static const uint8_t TCP_FIN = 1; +static const uint8_t TCP_SYN = 1 << 1; +static const uint8_t TCP_RST = 1 << 2; +static const uint8_t TCP_PSH = 1 << 3; +static const uint8_t TCP_ACK = 1 << 4; +static const uint8_t TCP_URG = 1 << 5; + +// I have trouble figuring out how to properly manage the windowing to +// the device. It keeps sending back 512 and seems to drop off a cliff +// when the device gets overwhelmed. In addition, the device likes to +// panic and send out RESETS before the window hits zero. Also, waiting +// for responses seems to not be a winning strategy. +// +// Since I'm not sure how in the hell to interpret the window sizes that +// the device is sending back to us, I've figured out some magic number +// constants which seem to work okay. +static const uint32_t WINDOW_MAX = 5 * 1024; +static const uint32_t WINDOW_INCREMENT = 512; + +typedef struct { +	char *buffer; +	int leftover; +	int capacity; +} receivebuf_t; + +struct usbmux_device_int { +	struct usb_dev_handle *usbdev; +	struct usb_device *__device; +	receivebuf_t usbReceive; +}; + +typedef struct { +	uint32_t type, length, major, minor, allnull; +} usbmux_version_header; + +typedef struct { +	uint32_t type, length; +	uint16_t sport, dport; +	uint32_t scnt, ocnt; +	uint8_t offset, tcp_flags; +	uint16_t window, nullnull, length16; +} usbmux_tcp_header; + +struct usbmux_client_int { +	usbmux_tcp_header *header; +	usbmux_device_t device; + +	char *recv_buffer; +	int r_len; +	pthread_cond_t wait; + +	// this contains a conditional variable which usb-writers can wait +	// on while waiting for window updates from the device. +	pthread_cond_t wr_wait; +	// I'm going to do something really cheesy here. We are going to  +	// just record the most recent scnt that we are expecting to hear +	// back on. We will actually halt progress by limiting the number +	// of outstanding un-acked bulk sends that we have beamed out. +	uint32_t wr_pending_scnt; +	long wr_window; + +	pthread_mutex_t mutex; + +	// this variable is not protected by the mutex. This will always +	// be E_SUCCESS, unless an error of some kind breaks this stream. +	// this will then be set to the error that caused the broken stream. +	// no further operations other than free_client will be allowed. +	int error; + +	int cleanup; +}; + + +static pthread_mutex_t usbmuxmutex = PTHREAD_MUTEX_INITIALIZER; +static usbmux_client_t *connlist = NULL; +static int clients = 0; + + +/** + */ +int toto_debug = 0; + +void usbmux_set_debug(int e) +{ +	toto_debug = e; +} + +void log_debug_msg(const char *format, ...) +{ +#ifndef STRIP_DEBUG_CODE +	va_list args; +	/* run the real fprintf */ +	va_start(args, format); + +	if (toto_debug) +		vfprintf(stderr, format, args); + +	va_end(args); +#endif +} + +#ifdef DEBUG +/** + * for debugging purposes. + */ +static void print_buffer(const char *data, const int length) +{ +	if (toto_debug <= 0) { +		return; +	} +	int i; +	int j; +	unsigned char c; + +	for (i = 0; i < length; i += 16) { +		printf("%04x: ", i); +		for (j = 0; j < 16; j++) { +			if (i + j >= length) { +				printf("   "); +				continue; +			} +			printf("%02hhx ", *(data + i + j)); +		} +		printf("  | "); +		for (j = 0; j < 16; j++) { +			if (i + j >= length) +				break; +			c = *(data + i + j); +			if ((c < 32) || (c > 127)) { +				printf("."); +				continue; +			} +			printf("%c", c); +		} +		printf("\n"); +	} +	printf("\n"); +} +#endif + +void hton_header(usbmux_tcp_header * hdr) +{ +	if (hdr) { +		hdr->length = htonl(hdr->length); +		hdr->scnt = htonl(hdr->scnt); +		hdr->ocnt = htonl(hdr->ocnt); +		hdr->length16 = htons(hdr->length16); +	} +} + +void ntoh_header(usbmux_tcp_header * hdr) +{ +	if (hdr) { +		hdr->length = ntohl(hdr->length); +		hdr->scnt = ntohl(hdr->scnt); +		hdr->ocnt = ntohl(hdr->ocnt); +		hdr->length16 = ntohs(hdr->length16); +	} +} + +/** Creates a USBMux header containing version information + *  + * @return A USBMux header + */ +usbmux_version_header *version_header() +{ +	usbmux_version_header *version = +		(usbmux_version_header *) malloc(sizeof(usbmux_version_header)); +	version->type = 0; +	version->length = htonl(20); +	version->major = htonl(1); +	version->minor = 0; +	version->allnull = 0; +	return version; +} + +/** + * This function sets the configuration of the given device to 3 + * and claims the interface 1. If usb_set_configuration fails, it detaches + * the kernel driver that blocks the device, and retries configuration. + * + * @param device which device to configure + */ +static int usbmux_config_usb_device(usbmux_device_t device) +{ +	int ret; +	int bytes; +	char buf[512]; + +#if 0 +	log_debug_msg("checking configuration...\n"); +	if (device->__device->config->bConfigurationValue != 3) { +		log_debug_msg +			("WARNING: usb device configuration is not 3 as expected!\n"); +	} + +	log_debug_msg("setting configuration...\n"); +	ret = usb_set_configuration(device->device, 3); +	if (ret != 0) { +		log_debug_msg("Hm, usb_set_configuration returned %d: %s\n", ret, +					  strerror(-ret)); +#if LIBUSB_HAS_GET_DRIVER_NP +		log_debug_msg("trying to fix:\n"); +		log_debug_msg("-> detaching kernel driver... "); +		ret = +			usb_detach_kernel_driver_np(device->device, +										device->__device->config-> +										interface->altsetting-> +										bInterfaceNumber); +		if (ret != 0) { +			log_debug_msg("usb_detach_kernel_driver_np returned %d: %s\n", +						  ret, strerror(-ret)); +		} else { +			log_debug_msg("done.\n"); +			log_debug_msg("setting configuration again... "); +			ret = usb_set_configuration(device->device, 3); +			if (ret != 0) { +				log_debug_msg +					("Error: usb_set_configuration returned %d: %s\n", ret, +					 strerror(-ret)); +				log_debug_msg("--> trying to continue anyway...\n"); +			} else { +				log_debug_msg("done.\n"); +			} +		} +#else +		log_debug_msg("--> trying to continue anyway...\n"); +#endif +	} else { +		log_debug_msg("done.\n"); +	} +#endif + +	log_debug_msg("claiming interface... "); +	ret = usb_claim_interface(device->usbdev, 1); +	if (ret != 0) { +		log_debug_msg("Error: usb_claim_interface returned %d: %s\n", ret, +					  strerror(-ret)); +		return -ENODEV; +	} else { +		log_debug_msg("done.\n"); +	} + +	do { +		bytes = usb_bulk_read(device->usbdev, BULKIN, buf, 512, 800); +	} while (bytes > 0); + +	return 0; +} + +/** + * Given a USB bus and device number, returns a device handle to the device on + * that bus. To aid compatibility with future devices, this function does not + * check the vendor and device IDs! To do that, you should use + * usbmux_get_device() or a system-specific API (e.g. HAL). + * + * @param bus_n The USB bus number. + * @param dev_n The USB device number. + * @param device A pointer to a usbmux_device_t, which must be set to NULL upon + *      calling usbmux_get_specific_device, which will be filled with a device + *      descriptor on return.  + * @return 0 if ok, otherwise a negative errno value. + */ +int usbmux_get_specific_device(int bus_n, int dev_n, +							   usbmux_device_t * device) +{ +	struct usb_bus *bus; +	struct usb_device *dev; +	usbmux_version_header *version; +	int bytes = 0; + +	//check we can actually write in device +	if (!device || (device && *device)) +		return -EINVAL; + +	usbmux_device_t newdevice = +		(usbmux_device_t) malloc(sizeof(struct usbmux_device_int)); + +	// Initialize the struct +	newdevice->usbdev = NULL; +	newdevice->__device = NULL; + +	// don't forget these: +	newdevice->usbReceive.buffer = NULL; +	newdevice->usbReceive.leftover = 0; +	newdevice->usbReceive.capacity = 0; + +	// Initialize libusb +	usb_init(); +	usb_find_busses(); +	usb_find_devices(); + +	// Set the device configuration +	for (bus = usb_get_busses(); bus; bus = bus->next) +		//if (bus->location == bus_n) +		for (dev = bus->devices; dev != NULL; dev = dev->next) +			if (dev->devnum == dev_n) { +				newdevice->__device = dev; +				newdevice->usbdev = usb_open(newdevice->__device); +				if (usbmux_config_usb_device(newdevice) == 0) { +					goto found; +				} +			} + +	usbmux_free_device(newdevice); + +	log_debug_msg("usbmux_get_specific_device: device not found\n"); +	return -ENODEV; + +  found: +	// Send the version command to the device +	version = version_header(); +	bytes = +		usb_bulk_write(newdevice->usbdev, BULKOUT, (char *) version, +					   sizeof(*version), 800); +	if (bytes < 20) { +		log_debug_msg("%s: libusb did NOT send enough!\n", __func__); +		if (bytes < 0) { +			log_debug_msg("%s: libusb gave me the error %d: %s (%s)\n", +						  __func__, bytes, usb_strerror(), +						  strerror(-bytes)); +		} +	} +	// Read the device's response +	bytes = +		usb_bulk_read(newdevice->usbdev, BULKIN, (char *) version, +					  sizeof(*version), 800); + +	// Check for bad response +	if (bytes < 20) { +		free(version); +		usbmux_free_device(newdevice); +		log_debug_msg("%s: Invalid version message -- header too short.\n", +					  __func__); +		if (bytes < 0) { +			log_debug_msg("%s: libusb error message %d: %s (%s)\n", +						  __func__, bytes, usb_strerror(), +						  strerror(-bytes)); +			return bytes; +		} +		return -EBADMSG; +	} +	// Check for correct version +	if (ntohl(version->major) == 1 && ntohl(version->minor) == 0) { +		// We're all ready to roll. +		log_debug_msg("%s: success\n", __func__); +		free(version); +		*device = newdevice; +		return 0; +	} else { +		// Bad header +		usbmux_free_device(newdevice); +		free(version); +		log_debug_msg("%s: Received a bad header/invalid version number.", +					  __func__); +		return -EBADMSG; +	} + +	// If it got to this point it's gotta be bad +	log_debug_msg("%s: Unknown error.\n", __func__); +	usbmux_free_device(newdevice); +	free(version); +	return -EBADMSG;			// if it got to this point it's gotta be bad +} + +/** Cleans up an usbmux_device_t structure, then frees the structure itself. + * This is a library-level function; deals directly with the device to tear + *  down relations, but otherwise is mostly internal. + *  + * @param device A pointer to an usbmux_device_t structure. + */ +int usbmux_free_device(usbmux_device_t device) +{ +	char buf[512]; +	int bytes; + +	if (!device) +		return -EINVAL; +	int ret = 0; + +	if (device->usbdev) { +		do { +			bytes = usb_bulk_read(device->usbdev, BULKIN, buf, 512, 800); +		} while (bytes > 0); +	} + +	if (bytes < 0) { +		ret = bytes; +	} + +	if (device->usbReceive.buffer) { +		free(device->usbReceive.buffer); +	} +	if (device->usbdev) { +		usb_release_interface(device->usbdev, 1); +		usb_close(device->usbdev); +		ret = 0; +	} +	free(device); + +	return ret; +} + + + +/** Sends data to the device + * This is a low-level (i.e. directly to device) function. + *  + * @param device The device to send data to + * @param data The data to send + * @param datalen The length of the data + * @return The number of bytes sent, or -ERRNO on error + */ +int send_to_device(usbmux_device_t device, char *data, int datalen) +{ +	if (!device) +		return -EINVAL; + +	int timeout = 1000; +	int retrycount = 0; +	int bytes = 0; + +#ifdef DEBUG +#ifdef DEBUG_MORE +	printf("===============================\n%s: trying to send\n", +		   __func__); +	print_buffer(data, datalen); +	printf("===============================\n"); +#endif +#endif +	do { +		if (retrycount > 3) { +			log_debug_msg +				("EPIC FAIL! aborting on retry count overload.\n"); +			return -ECOMM; +		} + +		bytes = +			usb_bulk_write(device->usbdev, BULKOUT, data, datalen, +						   timeout); +		if (bytes == -ETIMEDOUT) { +			// timed out waiting for write. +			log_debug_msg("usb_bulk_write timeout error.\n"); +			return bytes; +		} else if (bytes < 0) { +			log_debug_msg +				("usb_bulk_write failed with error. err:%d (%s)(%s)\n", +				 bytes, usb_strerror(), strerror(-bytes)); +			return bytes; +		} else if (bytes == 0) { +			log_debug_msg("usb_bulk_write sent nothing. retrying.\n"); +			timeout = timeout * 4; +			retrycount++; +			continue; +		} else if (bytes < datalen) { +			log_debug_msg +				("usb_bulk_write failed to send full dataload. %d of %d\n", +				 bytes, datalen); +			timeout = timeout * 4; +			retrycount++; +			data += bytes; +			datalen -= bytes; +			continue; +		} +	} while (0);				// fall out + +#ifdef DEBUG +	if (bytes > 0) { +		if (toto_debug > 0) { +			printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); +			printf("%s: sent to device\n", __func__); +			print_buffer(data, bytes); +			printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); +		} +	} +#endif +	return bytes; +} + +/** Receives data from the device + * This function is a low-level (i.e. direct from device) function. + *  + * @param device The device 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_device_timeout(usbmux_device_t device, char *data, +							 int datalen, int timeoutmillis) +{ +	if (!device) +		return -EINVAL; +	//log_debug_msg("%s: attempting to receive %i bytes\n", __func__, datalen); + +	int bytes = +		usb_bulk_read(device->usbdev, BULKIN, data, datalen, +					  timeoutmillis); +	// There are some things which are errors, others which are no problem. +	// It's not documented in libUSB, but it seems that the error values +	// returned are just negated ERRNO values. +	if (bytes < 0) { +		if (bytes == -ETIMEDOUT) { +			// ignore this. it just means timeout reached before we +			//  picked up any data. no problem. +			return 0; +		} else { +			fprintf(stderr, "%s: libusb gave me the error %d: %s (%s)\n", +					__func__, bytes, usb_strerror(), strerror(-bytes)); +			log_debug_msg("%s: libusb gave me the error %d: %s (%s)\n", +						  __func__, bytes, usb_strerror(), +						  strerror(-bytes)); +		} +		return bytes; +	} +#ifdef DEBUG +	if (bytes > 0) { +		if (toto_debug > 0) { +			printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); +			printf("%s: received from device:\n", __func__); +			print_buffer(data, bytes); +			printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); +		} +	} +#endif + +	return bytes; +} + +/** Creates a USBMux packet for the given set of ports. + *  + * @param s_port The source port for the connection. + * @param d_port The destination port for the connection. + * + * @return A USBMux packet + */ +usbmux_tcp_header *new_mux_packet(uint16_t s_port, uint16_t d_port) +{ +	usbmux_tcp_header *conn = +		(usbmux_tcp_header *) malloc(sizeof(usbmux_tcp_header)); +	conn->type = htonl(6); +	conn->length = HEADERLEN; +	conn->sport = htons(s_port); +	conn->dport = htons(d_port); +	conn->scnt = 0; +	conn->ocnt = 0; +	conn->offset = 0x50; +	conn->window = htons(0x0200); +	conn->nullnull = 0x0000; +	conn->length16 = HEADERLEN; +	return conn; +} + + +/** Removes a connection from the list of connections made. + * The list of connections is necessary for buffering. + *  + * @param connection The connection to delete from the tracking list. + */ +static void delete_connection(usbmux_client_t connection) +{ +	usbmux_client_t *newlist = NULL; + +	pthread_mutex_lock(&usbmuxmutex); + +	// update the global list of connections +	if (clients > 1) { +		newlist = +			(usbmux_client_t *) malloc(sizeof(usbmux_client_t) * +									   (clients - 1)); +		int i = 0, j = 0; +		for (i = 0; i < clients; i++) { +			if (connlist[i] == connection) +				continue; +			else { +				newlist[j] = connlist[i]; +				j++; +			} +		} +	} +	if (connlist) { +		free(connlist); +	} +	connlist = newlist; +	clients--; + +	// free up this connection +	pthread_mutex_lock(&connection->mutex); +	if (connection->recv_buffer) { +		free(connection->recv_buffer); +		connection->recv_buffer = NULL; +	} +	if (connection->header) { +		free(connection->header); +		connection->header = NULL; +	} +	connection->r_len = 0; +	pthread_mutex_unlock(&connection->mutex); +	pthread_mutex_destroy(&connection->mutex); +	free(connection); + +	pthread_mutex_unlock(&usbmuxmutex); +} + +/** Adds a connection to the list of connections made. + * The connection list is necessary for buffering. + * + * @param connection The connection to add to the global list of connections. + */ + +static void add_connection(usbmux_client_t connection) +{ +	pthread_mutex_lock(&usbmuxmutex); +	usbmux_client_t *newlist = +		(usbmux_client_t *) realloc(connlist, +									sizeof(usbmux_client_t) * (clients + +															   1)); +	newlist[clients] = connection; +	connlist = newlist; +	clients++; +	pthread_mutex_unlock(&usbmuxmutex); +} + +/** + * Get a source port number that is not used by one of our connections + * This is needed for us to make sure we are not sending on another + * connection. + */ +static uint16_t get_free_port() +{ +	int i; +	uint16_t newport = 30000; +	int cnt = 0; + +	pthread_mutex_lock(&usbmuxmutex); +	while (1) { +		cnt = 0; +		for (i = 0; i < clients; i++) { +			if (ntohs(connlist[i]->header->sport) == newport) { +				cnt++; +			} +		} +		if (cnt == 0) { +			// newport is not used in our list of connections! +			break; +		} else { +			newport++; +			if (newport < 30000) { +				// if all ports from 30000 to 65535 are in use, +				// the value wraps (16-bit overflow) +				// return 0, no port is available. +				// This should not happen, but just in case ;) +				newport = 0; +				break; +			} +		} +	} +	pthread_mutex_unlock(&usbmuxmutex); + +	return newport; +} + +/** Initializes a connection to 'device' with source port s_port and destination port d_port + * + * @param device The device to initialize a connection on. + * @param src_port The source port + * @param dst_port The destination port -- 0xf27e for lockdownd.  + * @param client A mux TCP header for the connection which is used for tracking and data transfer. + * @return 0 on success, a negative errno value otherwise. + */ +int usbmux_new_client(usbmux_device_t device, uint16_t src_port, +					  uint16_t dst_port, usbmux_client_t * client) +{ +	if (!device || !dst_port) +		return -EINVAL; + +	src_port = get_free_port(); + +	if (!src_port) { +		// this is a special case, if we get 0, this is not good, so +		return -EISCONN;		// TODO: error code suitable? +	} +	// Initialize connection stuff +	usbmux_client_t new_connection = +		(usbmux_client_t) malloc(sizeof(struct usbmux_client_int)); +	new_connection->header = new_mux_packet(src_port, dst_port); + +	// send TCP syn +	if (new_connection && new_connection->header) { +		int err = 0; +		new_connection->header->tcp_flags = TCP_SYN; +		new_connection->header->length = new_connection->header->length; +		new_connection->header->length16 = +			new_connection->header->length16; +		new_connection->header->scnt = 0; +		new_connection->header->ocnt = 0; +		new_connection->device = device; +		new_connection->recv_buffer = NULL; +		new_connection->r_len = 0; +		pthread_cond_init(&new_connection->wait, NULL); +		pthread_mutex_init(&new_connection->mutex, NULL); +		pthread_cond_init(&new_connection->wr_wait, NULL); +		new_connection->wr_pending_scnt = 0; +		new_connection->wr_window = 0; +		add_connection(new_connection); +		new_connection->error = 0; +		new_connection->cleanup = 0; +		hton_header(new_connection->header); +		log_debug_msg("%s: send_to_device (%d --> %d)\n", __func__, +					  ntohs(new_connection->header->sport), +					  ntohs(new_connection->header->dport)); +		err = +			send_to_device(device, (char *) new_connection->header, +						   sizeof(usbmux_tcp_header)); +		if (err >= 0) { +			*client = new_connection; +			return 0; +		} else { +			delete_connection(new_connection); +			return err; +		} +	} +	// if we get to this point it's probably bad +	return -ENOMEM; +} + +/** Cleans up the given USBMux connection. + * @note Once a connection is closed it may not be used again. + *  + * @param connection The connection to close. + * + * @return 0 on success or a negative errno value on error. + */ +int usbmux_free_client(usbmux_client_t client) +{ +	if (!client || !client->device) +		return -EINVAL; + +	int err = 0; +	int result = 0; +	pthread_mutex_lock(&client->mutex); +	client->header->tcp_flags = TCP_FIN; +	client->header->length = 0x1C; +	client->header->window = 0; +	client->header->length16 = 0x1C; +	hton_header(client->header); + +	err = +		send_to_device(client->device, (char *) client->header, +					   sizeof(usbmux_tcp_header)); +	if (err < 0) { +		log_debug_msg("%s: error sending TCP_FIN\n", __func__); +		result = err; +	} + +	client->cleanup = 1; + +	// make sure we don't have any last-minute laggards waiting on this. +	// I put it after the mutex unlock because we have cases where the +	// conditional wait is dependent on re-grabbing that mutex. +	pthread_cond_broadcast(&client->wait); +	pthread_cond_destroy(&client->wait); +	pthread_cond_broadcast(&client->wr_wait); +	pthread_cond_destroy(&client->wr_wait); + +	pthread_mutex_unlock(&client->mutex); + +	return result; +} + +/** Sends the given data over the selected connection. + * + * @param client The client we're sending data on. + * @param data A pointer to the data to send. + * @param datalen How much data we're sending. + * @param sent_bytes The number of bytes sent, minus the header (28) + * + * @return 0 on success or a negative errno value on error. + */ +int usbmux_send(usbmux_client_t client, const char *data, uint32_t datalen, +				uint32_t * sent_bytes) +{ +	if (!client->device || !client || !sent_bytes) +		return -EINVAL; + +	if (client->error < 0) { +		return client->error; +	} + +	*sent_bytes = 0; +	pthread_mutex_lock(&client->mutex); + +	int sendresult = 0; +	uint32_t blocksize = 0; +	if (client->wr_window <= 0) { +		struct timespec ts; +		clock_gettime(CLOCK_REALTIME, &ts); +		//ts.tv_sec += 1; +		ts.tv_nsec += 750 * 1000; +		if (pthread_cond_timedwait(&client->wait, &client->mutex, &ts) == +			ETIMEDOUT) { +			// timed out. optimistically grow the window and try to make progress +			client->wr_window += WINDOW_INCREMENT; +		} +	} + +	blocksize = sizeof(usbmux_tcp_header) + datalen; + +	// client->scnt and client->ocnt should already be in host notation... +	// we don't need to change them juuuust yet.  +	char *buffer = (char *) malloc(blocksize + 2);	// allow 2 bytes of safety padding +	// Set the length +	client->header->length = blocksize; +	client->header->length16 = blocksize; + +	// Put header into big-endian notation +	hton_header(client->header); +	// Concatenation of stuff in the buffer. +	memcpy(buffer, client->header, sizeof(usbmux_tcp_header)); +	memcpy(buffer + sizeof(usbmux_tcp_header), data, datalen); + +	log_debug_msg("%s: send_to_device(%d --> %d)\n", __func__, +				  ntohs(client->header->sport), +				  ntohs(client->header->dport)); +	sendresult = send_to_device(client->device, buffer, blocksize); +	// Now that we've sent it off, we can clean up after our sloppy selves. +	if (buffer) +		free(buffer); + +	// revert header fields that have been swapped before trying to send +	ntoh_header(client->header); + +	// update counts ONLY if the send succeeded. +	if ((uint32_t) sendresult == blocksize) { +		// Re-calculate scnt +		client->header->scnt += datalen; +		client->wr_window -= blocksize; +	} + +	pthread_mutex_unlock(&client->mutex); + +	if (sendresult == -ETIMEDOUT || sendresult == 0) { +		// no problem for now... +		*sent_bytes = 0; +		return -ETIMEDOUT; +	} else if (sendresult < 0) { +		return sendresult; +	} else if ((uint32_t) sendresult == blocksize) { +		// actual number of data bytes sent. +		*sent_bytes = sendresult - HEADERLEN; +		return 0; +	} else { +		fprintf(stderr, +				"usbsend managed to dump a packet that is not full size. %d of %d\n", +				sendresult, blocksize); +		return -EBADMSG; +	} +} + +/** append the packet's DATA to the receive buffer for the client. + * + *  this has a few other corner-case functions: + *  1. this will properly handle the handshake syn+ack. + *  2. for all receives, this will appropriately update the ocnt. + *  + * @return number of bytes consumed (header + data) + */ +uint32_t append_receive_buffer(usbmux_client_t client, char *packet) +{ +	if (client == NULL || packet == NULL) +		return 0; + +	usbmux_tcp_header *header = (usbmux_tcp_header *) packet; +	char *data = &packet[HEADERLEN]; +	uint32_t packetlen = ntohl(header->length); +	uint32_t datalen = packetlen - HEADERLEN; + +	int dobroadcast = 0; + +	pthread_mutex_lock(&client->mutex); + +	// we need to handle a few corner case tasks and book-keeping which +	// falls on our responsibility because we are the ones reading in +	// feedback. +	if (client->header->scnt == 0 && client->header->ocnt == 0) { +		log_debug_msg("client is still waiting for handshake.\n"); +		if (header->tcp_flags == (TCP_SYN | TCP_ACK)) { +			log_debug_msg("yes, got syn+ack ; replying with ack.\n"); +			client->header->tcp_flags = TCP_ACK; +			client->header->length = sizeof(usbmux_tcp_header); +			client->header->length16 = sizeof(usbmux_tcp_header); +			client->header->scnt += 1; +			client->header->ocnt = header->ocnt; +			hton_header(client->header); +			// push it to USB +			// TODO: need to check for error in the send here.... :( +			log_debug_msg("%s: send_to_device (%d --> %d)\n", __func__, +						  ntohs(client->header->sport), +						  ntohs(client->header->dport)); +			if (send_to_device +				(client->device, (char *) client->header, +				 sizeof(usbmux_tcp_header)) <= 0) { +				log_debug_msg("%s: error when pushing to usb...\n", +							  __func__); +			} +			// need to revert some of the fields back to host notation. +			ntoh_header(client->header); +		} else { +			client->error = -ECONNABORTED; +			// woah... this connection failed us. +			// TODO: somehow signal that this stream is a no-go. +			log_debug_msg("WOAH! client failed to get proper syn+ack.\n"); +		} +	} +	// update TCP counters and windows. +	// +	// save the window that we're getting from the USB device. +	// apparently the window is bigger than just the 512 that's typically +	// advertised. iTunes apparently shifts this value by 8 to get a much +	// larger number. +	if (header->tcp_flags & TCP_RST) { +		client->error = -ECONNRESET; + +		if (datalen > 0) { +			char e_msg[128]; +			e_msg[0] = 0; +			if (datalen > 1) { +				memcpy(e_msg, data + 1, datalen - 1); +				e_msg[datalen - 1] = 0; +			} +			// fetch the message +			switch (data[0]) { +			case 0: +				// this is not an error, it's just a status message. +				log_debug_msg("received status message: %s\n", e_msg); +				datalen = 0; +				break; +			case 1: +				log_debug_msg("received error message: %s\n", e_msg); +				datalen = 0; +				break; +			default: +				log_debug_msg +					("received unknown message (type 0x%02x): %s\n", +					 data[0], e_msg); +				//datalen = 0; // <-- we let this commented out for testing +				break; +			} +		} else { +			log_debug_msg +				("peer sent connection reset. setting error: %d\n", +				 client->error); +		} +	} +	// the packet's ocnt tells us how much of our data the device has received. +	if (header->tcp_flags & TCP_ACK) { +		// this is a hacky magic number condition. it seems that once +		// the window reported by the device starts to drop below this +		// number, we quickly fall into connection reset problems. +		// Once we see the reported window size start falling off, +		// ut off and wait for solid acks to come back. +		if (ntohs(header->window) < 256) +			client->wr_window = 0; + +		// check what just got acked. +		if (ntohl(header->ocnt) < client->header->scnt) { +			// we got some kind of ack, but it hasn't caught up +			// with the pending that have been sent. +			pthread_cond_broadcast(&client->wr_wait); +		} else if (ntohl(header->ocnt) > +				   /*client->wr_pending_scnt */ client->header->scnt) { +			fprintf(stderr, +					"WTF?! acks overtook pending outstanding.  %u,%u\n", +					ntohl(header->ocnt), client->wr_pending_scnt); +		} else { +			// reset the window +			client->wr_window = WINDOW_MAX; +			pthread_cond_broadcast(&client->wr_wait); +		} +	} +	// the packet's scnt will be our new ocnt. +	client->header->ocnt = ntohl(header->scnt); + +	// ensure there is enough space, either by first malloc or realloc +	if (datalen > 0) { +		log_debug_msg("%s: putting %d bytes into client's recv_buffer\n", +					  __func__, datalen); +		if (client->r_len == 0) +			dobroadcast = 1; + +		if (client->recv_buffer == NULL) { +			client->recv_buffer = malloc(datalen); +			client->r_len = 0; +		} else { +			client->recv_buffer = +				realloc(client->recv_buffer, client->r_len + datalen); +		} + +		memcpy(&client->recv_buffer[client->r_len], data, datalen); +		client->r_len += datalen; +	} + +	pthread_mutex_unlock(&client->mutex); + +	// I put this outside the mutex unlock just so that when the threads +	// wake, we don't have to do another round of unlock+try to grab. +	if (dobroadcast) +		pthread_cond_broadcast(&client->wait); + +	return packetlen; +} + +/** + * @note THERE IS NO MUTEX LOCK IN THIS FUNCTION! + * because we're only called from one location, pullbulk, where the lock + * is already held. + */ +usbmux_client_t find_client(usbmux_tcp_header * recv_header) +{ +	// remember, as we're looking for the client, the receive header is +	// coming from the USB into our client. This means that when we check +	// the src/dst ports, we need to reverse them. +	usbmux_client_t retval = NULL; + +	// just for debugging check, I'm going to convert the numbers to host-endian. +	uint16_t hsport = ntohs(recv_header->sport); +	uint16_t hdport = ntohs(recv_header->dport); + +	pthread_mutex_lock(&usbmuxmutex); +	int i; +	for (i = 0; i < clients; i++) { +		uint16_t csport = ntohs(connlist[i]->header->sport); +		uint16_t cdport = ntohs(connlist[i]->header->dport); + +		if (hsport == cdport && hdport == csport) { +			retval = connlist[i]; +			break; +		} +	} +	pthread_mutex_unlock(&usbmuxmutex); + +	return retval; +} + +/** pull in a big USB bulk packet and distribute it to queues appropriately. + */ +int usbmux_pullbulk(usbmux_device_t device) +{ +	if (!device) +		return -EINVAL; + +	int res = 0; +	static const int DEFAULT_CAPACITY = 128 * 1024; +	if (device->usbReceive.buffer == NULL) { +		device->usbReceive.capacity = DEFAULT_CAPACITY; +		device->usbReceive.buffer = malloc(device->usbReceive.capacity); +		device->usbReceive.leftover = 0; +	} +	// start the cursor off just ahead of the leftover. +	char *cursor = &device->usbReceive.buffer[device->usbReceive.leftover]; +	// pull in content, note that the amount we can pull is capacity minus leftover +	int readlen = +		recv_from_device_timeout(device, cursor, +								 device->usbReceive.capacity - +								 device->usbReceive.leftover, 3000); +	if (readlen < 0) { +		res = readlen; +		//fprintf(stderr, "recv_from_device_timeout gave us an error.\n"); +		readlen = 0; +	} +	if (readlen > 0) { +		//fprintf(stdout, "recv_from_device_timeout pulled an extra %d bytes\n", readlen); +	} +	// the amount of content we have to work with is the remainder plus +	// what we managed to read +	device->usbReceive.leftover += readlen; + +	// reset the cursor to the front of that buffer and work through +	// trying to decode packets out of them. +	cursor = device->usbReceive.buffer; +	while (1) { +		// check if there's even sufficient data to decode a header +		if (device->usbReceive.leftover < HEADERLEN) +			break; +		usbmux_tcp_header *header = (usbmux_tcp_header *) cursor; + +		log_debug_msg("%s: recv_from_device_timeout (%d --> %d)\n", +					  __func__, ntohs(header->sport), +					  ntohs(header->dport)); + +		// now that we have a header, check if there is sufficient data +		// to construct a full packet, including its data +		uint32_t packetlen = ntohl(header->length); +		if ((uint32_t) device->usbReceive.leftover < packetlen) { +			fprintf(stderr, +					"%s: not enough data to construct a full packet\n", +					__func__); +			break; +		} +		// ok... find the client this packet will get stuffed to. +		usbmux_client_t client = find_client(header); +		if (client == NULL) { +			log_debug_msg +				("WARNING: client for packet cannot be found. dropping packet.\n"); +		} else { +			// stuff the data +			log_debug_msg +				("%s: found client, calling append_receive_buffer\n", +				 __func__); +			append_receive_buffer(client, cursor); + +			// perhaps this is too general, == -ECONNRESET +			//  might be a better check here +			if (client->error < 0) { +				pthread_mutex_lock(&client->mutex); +				if (client->cleanup) { +					pthread_mutex_unlock(&client->mutex); +					log_debug_msg("freeing up connection (%d->%d)\n", +								  ntohs(client->header->sport), +								  ntohs(client->header->dport)); +					delete_connection(client); +				} else { +					pthread_mutex_unlock(&client->mutex); +				} +			} +		} + +		// move the cursor and account for the consumption +		cursor += packetlen; +		device->usbReceive.leftover -= packetlen; +	} + +	// now, we need to manage any leftovers. +	// I'm going to manage the leftovers by alloc'ing a new block and +	// copyingthe leftovers to it. This is just to prevent problems with +	// memory moves where there may be overlap. Besides, the leftovers +	// should be small enough that this copy is minimal in overhead. +	// +	// if there are no leftovers, we just leave the datastructure as is, +	// and re-use the block next time. +	if (device->usbReceive.leftover > 0 +		&& cursor != device->usbReceive.buffer) { +		log_debug_msg("%s: we got a leftover, so handle it\n", __func__); +		char *newbuff = malloc(DEFAULT_CAPACITY); +		memcpy(newbuff, cursor, device->usbReceive.leftover); +		free(device->usbReceive.buffer); +		device->usbReceive.buffer = newbuff; +		device->usbReceive.capacity = DEFAULT_CAPACITY; +	} + +	return res; +} + +/** + * return the error code stored in usbmux_client_t structure, + * e.g. non-zero when an usb read error occurs. + * + * @param client the usbmux client + * + * @return 0 or a negative errno value. + */ +int usbmux_get_error(usbmux_client_t client) +{ +	if (!client) { +		return 0; +	} +	return client->error; +} + +/** This function reads from the client's recv_buffer. + * + * @param client The client to receive data from. + * @param data Where to put the data we receive.  + * @param datalen How much data to read. + * @param timeout How many milliseconds to wait for data + * + * @return 0 on success or a negative errno value on failure. + */ +int usbmux_recv_timeout(usbmux_client_t client, char *data, +						uint32_t datalen, uint32_t * recv_bytes, +						int timeout) +{ + +	if (!client || !data || datalen == 0 || !recv_bytes) +		return -EINVAL; + +	if (client->error < 0) +		return client->error; + +	pthread_mutex_lock(&client->mutex); + +	if (timeout > 0 && (client->recv_buffer == NULL || client->r_len == 0)) { +		struct timespec ts; +		clock_gettime(CLOCK_REALTIME, &ts); +		ts.tv_sec += timeout / 1000; +		ts.tv_nsec += (timeout - ((int) (timeout / 1000)) * 1000) * 1000; +		pthread_cond_timedwait(&client->wait, &client->mutex, &ts); +	} + +	*recv_bytes = 0; +	if (client->recv_buffer != NULL && client->r_len > 0) { +		uint32_t foolen = datalen; +		if ((int) foolen > client->r_len) +			foolen = client->r_len; +		memcpy(data, client->recv_buffer, foolen); +		*recv_bytes = foolen; + +		// preserve any left-over unread amounts. +		int remainder = client->r_len - foolen; +		if (remainder > 0) { +			char *newbuf = malloc(remainder); +			memcpy(newbuf, client->recv_buffer + foolen, remainder); +			client->r_len = remainder; +			free(client->recv_buffer); +			client->recv_buffer = newbuf; +		} else { +			free(client->recv_buffer); +			client->recv_buffer = NULL; +			client->r_len = 0; +		} +	} + +	pthread_mutex_unlock(&client->mutex); + +	return 0; +} diff --git a/src/usbmux.h b/src/usbmux.h new file mode 100644 index 0000000..2bcdb15 --- /dev/null +++ b/src/usbmux.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2008 Jing Su. 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  + */ + +#ifndef __USBMUX_H__ +#define __USBMUX_H__ + +#include <stdint.h> +#include <sys/types.h> +//#include <sys/stat.h> + + +void usbmux_set_debug(int e); + +struct usbmux_device_int; +typedef struct usbmux_device_int *usbmux_device_t; + +struct usbmux_client_int; +typedef struct usbmux_client_int *usbmux_client_t; + +int usbmux_get_device ( usbmux_device_t *device ); +int usbmux_get_specific_device(int bus_n, int dev_n, usbmux_device_t * device); +int usbmux_free_device ( usbmux_device_t device ); + + +int usbmux_new_client ( usbmux_device_t device, uint16_t src_port, uint16_t dst_port, usbmux_client_t *client ); +int usbmux_free_client ( usbmux_client_t client ); + +int usbmux_send(usbmux_client_t client, const char *data, uint32_t datalen, uint32_t * sent_bytes); + +int usbmux_recv_timeout(usbmux_client_t client, char *data, uint32_t datalen, uint32_t * recv_bytes, int timeout); + +int usbmux_pullbulk(usbmux_device_t device); + +int usbmux_get_error(usbmux_client_t client); + +#endif diff --git a/src/usbmuxd-proto.h b/src/usbmuxd-proto.h new file mode 100644 index 0000000..7f8c2d6 --- /dev/null +++ b/src/usbmuxd-proto.h @@ -0,0 +1,52 @@ +/* Protocol defintion for usbmuxd proxy protocol */ + +#ifndef __USBMUXD_PROTO_H +#define __USBMUXD_PROTO_H + +#include <stdint.h> + +#define USBMUXD_SOCKET_FILE "/var/run/usbmuxd" + +struct usbmuxd_header { +	uint32_t length;    // length of message, including header +	uint32_t reserved;  // always zero +	uint32_t type;      // message type +	uint32_t tag;       // responses to this query will echo back this tag +} __attribute__((__packed__)); + +struct usbmuxd_result { +	struct usbmuxd_header header; +	uint32_t result; +} __attribute__((__packed__)); + +struct	usbmuxd_connect_request { +	struct usbmuxd_header header; +	uint32_t device_id; +	uint16_t tcp_dport;   // TCP port number +	uint16_t reserved;   // set to zero +} __attribute__((__packed__)); + +struct usbmuxd_device { +	uint32_t device_id; +	uint16_t product_id; +	char serial_number[40]; +} __attribute__((__packed__)); + +struct usbmuxd_device_info_record { +	struct usbmuxd_header header; +	struct usbmuxd_device device; +	char padding[222]; +} __attribute__((__packed__)); + +struct usbmuxd_scan_request { +	struct usbmuxd_header header; +} __attribute__((__packed__)); + +enum { +	USBMUXD_RESULT  = 1, +	USBMUXD_CONNECT = 2, +	USBMUXD_SCAN = 3, +	USBMUXD_DEVICE_INFO = 4, +}; + +#endif /* __USBMUXD_PROTO_H */ diff --git a/src/usbmuxd.h b/src/usbmuxd.h new file mode 100644 index 0000000..15e97ee --- /dev/null +++ b/src/usbmuxd.h @@ -0,0 +1,45 @@ +#ifndef __USBMUXD_H +#define __USBMUXD_H + +/** + * Array entry returned by 'usbmuxd_scan()' scanning. + * + * If more than one device is available, 'product_id' and + * 'serial_number' and be analysed to help make a selection. + * The relevant 'handle' should be passed to 'usbmuxd_connect()', to + * start a proxy connection.  The value 'handle' should be considered + * opaque and no presumption made about the meaning of its value. + */ +typedef struct { +	int handle; +	int product_id; +	char serial_number[41]; +} usbmuxd_scan_result; + +/** + * Contacts usbmuxd and performs a scan for connected devices. + * + * @param available_devices pointer to array of usbmuxd_scan_result. + * 	Array of available devices.  The required 'handle' + *	should be passed to 'usbmuxd_connect()'.  The returned array + *	is zero-terminated for convenience; the final (unused) + *	entry containing handle == 0.  The returned array pointer + *	should be freed by passing to 'free()' after use. + * + * @return number of available devices, zero on no devices, or negative on error + */ +int usbmuxd_scan(usbmuxd_scan_result **available_devices); + +/** + * Request proxy connect to  + * + * @param handle returned by 'usbmuxd_scan()' + * + * @param tcp_port TCP port number on device, in range 0-65535. + *	common values are 62078 for lockdown, and 22 for SSH. + * + * @return file descriptor socket of the connection, or -1 on error + */ +int usbmuxd_connect(const int handle, const unsigned short tcp_port); + +#endif /* __USBMUXD_H */ | 
