aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Schink <jaylink-dev@marcschink.de>2015-10-18 11:56:10 +0200
committerMarc Schink <jaylink-dev@marcschink.de>2017-07-12 14:27:19 +0200
commit062a600200a6806febf2367116e262ae43f2eb67 (patch)
tree18a70ae58840fa7e32328efee8ddb860d15ac1e7
parent0f10b0a3b432fccc39c1585cf3c1f2cf0fc240b0 (diff)
transport: Add initial TCP/IP support
Signed-off-by: Marc Schink <jaylink-dev@marcschink.de>
-rw-r--r--Doxyfile.in4
-rw-r--r--libjaylink/Makefile.am2
-rw-r--r--libjaylink/device.c4
-rw-r--r--libjaylink/libjaylink-internal.h59
-rw-r--r--libjaylink/socket.c65
-rw-r--r--libjaylink/transport.c639
-rw-r--r--libjaylink/transport_tcp.c568
-rw-r--r--libjaylink/transport_usb.c619
8 files changed, 1396 insertions, 564 deletions
diff --git a/Doxyfile.in b/Doxyfile.in
index 0b4d530..86c5460 100644
--- a/Doxyfile.in
+++ b/Doxyfile.in
@@ -785,7 +785,9 @@ EXCLUDE = @top_srcdir@/libjaylink/buffer.c \
@top_srcdir@/libjaylink/libjaylink-internal.h \
@top_srcdir@/libjaylink/list.c \
@top_srcdir@/libjaylink/socket.c \
- @top_srcdir@/libjaylink/transport.c
+ @top_srcdir@/libjaylink/transport.c \
+ @top_srcdir@/libjaylink/transport_tcp.c \
+ @top_srcdir@/libjaylink/transport_usb.c
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
# directories that are symbolic links (a Unix file system feature) are excluded
diff --git a/libjaylink/Makefile.am b/libjaylink/Makefile.am
index e32645c..3ed3c74 100644
--- a/libjaylink/Makefile.am
+++ b/libjaylink/Makefile.am
@@ -46,6 +46,8 @@ libjaylink_la_SOURCES = \
swo.c \
target.c \
transport.c \
+ transport_tcp.c \
+ transport_usb.c \
util.c \
version.c
diff --git a/libjaylink/device.c b/libjaylink/device.c
index a02585c..d954563 100644
--- a/libjaylink/device.c
+++ b/libjaylink/device.c
@@ -528,7 +528,6 @@ static void free_device_handle(struct jaylink_device_handle *devh)
* @retval JAYLINK_ERR_ARG Invalid arguments.
* @retval JAYLINK_ERR_TIMEOUT A timeout occurred.
* @retval JAYLINK_ERR_MALLOC Memory allocation error.
- * @retval JAYLINK_ERR_NOT_SUPPORTED Operation not supported.
* @retval JAYLINK_ERR_IO Input/output error.
* @retval JAYLINK_ERR Other error conditions.
*
@@ -543,9 +542,6 @@ JAYLINK_API int jaylink_open(struct jaylink_device *dev,
if (!dev || !devh)
return JAYLINK_ERR_ARG;
- if (dev->interface != JAYLINK_HIF_USB)
- return JAYLINK_ERR_NOT_SUPPORTED;
-
handle = allocate_device_handle(dev);
if (!handle) {
diff --git a/libjaylink/libjaylink-internal.h b/libjaylink/libjaylink-internal.h
index 96a9a2a..e48d192 100644
--- a/libjaylink/libjaylink-internal.h
+++ b/libjaylink/libjaylink-internal.h
@@ -142,14 +142,6 @@ struct jaylink_device {
struct jaylink_device_handle {
/** Device instance. */
struct jaylink_device *dev;
- /** libusb device handle. */
- struct libusb_device_handle *usb_devh;
- /** USB interface number of the device. */
- uint8_t interface_number;
- /** USB interface IN endpoint of the device. */
- uint8_t endpoint_in;
- /** USB interface OUT endpoint of the device. */
- uint8_t endpoint_out;
/**
* Buffer for write and read operations.
*
@@ -177,6 +169,21 @@ struct jaylink_device_handle {
* write operations only.
*/
size_t write_pos;
+ /** libusb device handle. */
+ struct libusb_device_handle *usb_devh;
+ /** USB interface number of the device. */
+ uint8_t interface_number;
+ /** USB interface IN endpoint of the device. */
+ uint8_t endpoint_in;
+ /** USB interface OUT endpoint of the device. */
+ uint8_t endpoint_out;
+ /**
+ * Socket descriptor.
+ *
+ * This field is used for devices with host interface #JAYLINK_HIF_TCP
+ * only.
+ */
+ int sock;
};
struct list {
@@ -236,6 +243,10 @@ JAYLINK_PRIV void log_dbg(const struct jaylink_context *ctx,
JAYLINK_PRIV bool socket_close(int sock);
JAYLINK_PRIV bool socket_bind(int sock, const struct sockaddr *address,
size_t length);
+JAYLINK_PRIV bool socket_send(int sock, const void *buffer, size_t *length,
+ int flags);
+JAYLINK_PRIV bool socket_recv(int sock, void *buffer, size_t *length,
+ int flags);
JAYLINK_PRIV bool socket_sendto(int sock, const void *buffer, size_t *length,
int flags, const struct sockaddr *address,
size_t address_length);
@@ -259,4 +270,36 @@ JAYLINK_PRIV int transport_write(struct jaylink_device_handle *devh,
JAYLINK_PRIV int transport_read(struct jaylink_device_handle *devh,
uint8_t *buffer, size_t length);
+/*--- transport_usb.c -------------------------------------------------------*/
+
+JAYLINK_PRIV int transport_usb_open(struct jaylink_device_handle *devh);
+JAYLINK_PRIV int transport_usb_close(struct jaylink_device_handle *devh);
+JAYLINK_PRIV int transport_usb_start_write_read(
+ struct jaylink_device_handle *devh, size_t write_length,
+ size_t read_length, bool has_command);
+JAYLINK_PRIV int transport_usb_start_write(struct jaylink_device_handle *devh,
+ size_t length, bool has_command);
+JAYLINK_PRIV int transport_usb_start_read(struct jaylink_device_handle *devh,
+ size_t length);
+JAYLINK_PRIV int transport_usb_write(struct jaylink_device_handle *devh,
+ const uint8_t *buffer, size_t length);
+JAYLINK_PRIV int transport_usb_read(struct jaylink_device_handle *devh,
+ uint8_t *buffer, size_t length);
+
+/*--- transport_tcp.c -------------------------------------------------------*/
+
+JAYLINK_PRIV int transport_tcp_open(struct jaylink_device_handle *devh);
+JAYLINK_PRIV int transport_tcp_close(struct jaylink_device_handle *devh);
+JAYLINK_PRIV int transport_tcp_start_write_read(
+ struct jaylink_device_handle *devh, size_t write_length,
+ size_t read_length, bool has_command);
+JAYLINK_PRIV int transport_tcp_start_write(struct jaylink_device_handle *devh,
+ size_t length, bool has_command);
+JAYLINK_PRIV int transport_tcp_start_read(struct jaylink_device_handle *devh,
+ size_t length);
+JAYLINK_PRIV int transport_tcp_write(struct jaylink_device_handle *devh,
+ const uint8_t *buffer, size_t length);
+JAYLINK_PRIV int transport_tcp_read(struct jaylink_device_handle *devh,
+ uint8_t *buffer, size_t length);
+
#endif /* LIBJAYLINK_LIBJAYLINK_INTERNAL_H */
diff --git a/libjaylink/socket.c b/libjaylink/socket.c
index 965ba9e..f2a6588 100644
--- a/libjaylink/socket.c
+++ b/libjaylink/socket.c
@@ -94,6 +94,71 @@ JAYLINK_PRIV bool socket_bind(int sock, const struct sockaddr *address,
* value is undefined on failure.
* @param[in] flags Flags to modify the function behaviour. Use bitwise OR to
* specify multiple flags.
+ *
+ * @return Whether the message was sent successfully.
+ */
+JAYLINK_PRIV bool socket_send(int sock, const void *buffer, size_t *length,
+ int flags)
+{
+ ssize_t ret;
+
+ ret = send(sock, buffer, *length, flags);
+#ifdef _WIN32
+ if (ret == SOCKET_ERROR)
+ return false;
+#else
+ if (ret < 0)
+ return false;
+#endif
+ *length = ret;
+
+ return true;
+}
+
+/**
+ * Receive a message from a socket.
+ *
+ * @param[in] sock Socket descriptor.
+ * @param[out] buffer Buffer to store the received message on success. Its
+ * content is undefined on failure.
+ * @param[in,out] length Maximum length of the message in bytes. On success,
+ * the value gets updated with the actual number of
+ * received bytes. The value is undefined on failure.
+ * @param[in] flags Flags to modify the function behaviour. Use bitwise OR to
+ * specify multiple flags.
+ *
+ * @return Whether a message was successfully received.
+ */
+JAYLINK_PRIV bool socket_recv(int sock, void *buffer, size_t *length,
+ int flags)
+{
+ ssize_t ret;
+
+ ret = recv(sock, buffer, *length, flags);
+
+#ifdef _WIN32
+ if (ret == SOCKET_ERROR)
+ return false;
+#else
+ if (ret < 0)
+ return false;
+#endif
+
+ *length = ret;
+
+ return true;
+}
+
+/**
+ * Send a message on a socket.
+ *
+ * @param[in] sock Socket descriptor.
+ * @param[in] buffer Buffer to send message from.
+ * @param[in,out] length Number of bytes to send. On success, the value gets
+ * updated with the actual number of bytes sent. The
+ * value is undefined on failure.
+ * @param[in] flags Flags to modify the function behaviour. Use bitwise OR to
+ * specify multiple flags.
* @param[in] address Destination address of the message.
* @param[in] address_length Length of the structure pointed to by @p address
* in bytes.
diff --git a/libjaylink/transport.c b/libjaylink/transport.c
index 5000a20..11837a4 100644
--- a/libjaylink/transport.c
+++ b/libjaylink/transport.c
@@ -25,142 +25,12 @@
#include "libjaylink.h"
#include "libjaylink-internal.h"
-/*
- * libusb.h includes windows.h and therefore must be included after anything
- * that includes winsock2.h.
- */
-#include <libusb.h>
-
/**
* @file
*
* Transport abstraction layer.
*/
-/** Timeout of an USB transfer in milliseconds. */
-#define USB_TIMEOUT 1000
-
-/**
- * Number of consecutive timeouts before an USB transfer will be treated as
- * timed out.
- */
-#define NUM_TIMEOUTS 2
-
-/** Chunk size in bytes in which data is transferred. */
-#define CHUNK_SIZE 2048
-
-static int initialize_handle(struct jaylink_device_handle *devh)
-{
- int ret;
- struct jaylink_context *ctx;
- struct libusb_config_descriptor *config;
- const struct libusb_interface *interface;
- const struct libusb_interface_descriptor *desc;
- const struct libusb_endpoint_descriptor *ep_desc;
- bool found_interface;
- bool found_endpoint_in;
- bool found_endpoint_out;
- uint8_t i;
-
- ctx = devh->dev->ctx;
- devh->interface_number = 0;
-
- /*
- * Retrieve active configuration descriptor to determine the endpoints
- * for the interface number of the device.
- */
- ret = libusb_get_active_config_descriptor(devh->dev->usb_dev, &config);
-
- if (ret == LIBUSB_ERROR_IO) {
- log_err(ctx, "Failed to get configuration descriptor: "
- "input/output error.");
- return JAYLINK_ERR_IO;
- } else if (ret != LIBUSB_SUCCESS) {
- log_err(ctx, "Failed to get configuration descriptor: %s.",
- libusb_error_name(ret));
- return JAYLINK_ERR;
- }
-
- found_interface = false;
-
- for (i = 0; i < config->bNumInterfaces; i++) {
- interface = &config->interface[i];
- desc = &interface->altsetting[0];
-
- if (desc->bInterfaceClass != LIBUSB_CLASS_VENDOR_SPEC)
- continue;
-
- if (desc->bInterfaceSubClass != LIBUSB_CLASS_VENDOR_SPEC)
- continue;
-
- if (desc->bNumEndpoints < 2)
- continue;
-
- found_interface = true;
- devh->interface_number = i;
- break;
- }
-
- if (!found_interface) {
- log_err(ctx, "No suitable interface found.");
- libusb_free_config_descriptor(config);
- return JAYLINK_ERR;
- }
-
- found_endpoint_in = false;
- found_endpoint_out = false;
-
- for (i = 0; i < desc->bNumEndpoints; i++) {
- ep_desc = &desc->endpoint[i];
-
- if (ep_desc->bEndpointAddress & LIBUSB_ENDPOINT_IN) {
- devh->endpoint_in = ep_desc->bEndpointAddress;
- found_endpoint_in = true;
- } else {
- devh->endpoint_out = ep_desc->bEndpointAddress;
- found_endpoint_out = true;
- }
- }
-
- libusb_free_config_descriptor(config);
-
- if (!found_endpoint_in) {
- log_err(ctx, "Interface IN endpoint not found.");
- return JAYLINK_ERR;
- }
-
- if (!found_endpoint_out) {
- log_err(ctx, "Interface OUT endpoint not found.");
- return JAYLINK_ERR;
- }
-
- log_dbg(ctx, "Using endpoint %02x (IN) and %02x (OUT).",
- devh->endpoint_in, devh->endpoint_out);
-
- /* Buffer size must be a multiple of CHUNK_SIZE bytes. */
- devh->buffer_size = CHUNK_SIZE;
- devh->buffer = malloc(devh->buffer_size);
-
- if (!devh->buffer) {
- log_err(ctx, "Transport buffer malloc failed.");
- return JAYLINK_ERR_MALLOC;
- }
-
- devh->read_length = 0;
- devh->bytes_available = 0;
- devh->read_pos = 0;
-
- devh->write_length = 0;
- devh->write_pos = 0;
-
- return JAYLINK_OK;
-}
-
-static void cleanup_handle(struct jaylink_device_handle *devh)
-{
- free(devh->buffer);
-}
-
/**
* Open a device.
*
@@ -176,55 +46,21 @@ static void cleanup_handle(struct jaylink_device_handle *devh)
JAYLINK_PRIV int transport_open(struct jaylink_device_handle *devh)
{
int ret;
- struct jaylink_device *dev;
- struct jaylink_context *ctx;
- struct libusb_device_handle *usb_devh;
-
- dev = devh->dev;
- ctx = dev->ctx;
-
- log_dbg(ctx, "Trying to open device (bus:address = %03u:%03u).",
- libusb_get_bus_number(dev->usb_dev),
- libusb_get_device_address(dev->usb_dev));
-
- ret = initialize_handle(devh);
-
- if (ret != JAYLINK_OK) {
- log_err(ctx, "Failed to initialize device handle.");
- return ret;
- }
-
- ret = libusb_open(dev->usb_dev, &usb_devh);
-
- if (ret == LIBUSB_ERROR_IO) {
- log_err(ctx, "Failed to open device: input/output error.");
- cleanup_handle(devh);
- return JAYLINK_ERR_IO;
- } else if (ret != LIBUSB_SUCCESS) {
- log_err(ctx, "Failed to open device: %s.",
- libusb_error_name(ret));
- cleanup_handle(devh);
- return JAYLINK_ERR;
- }
- ret = libusb_claim_interface(usb_devh, devh->interface_number);
-
- if (ret == LIBUSB_ERROR_IO) {
- log_err(ctx, "Failed to claim interface: input/output error.");
- return JAYLINK_ERR_IO;
- } else if (ret != LIBUSB_SUCCESS) {
- log_err(ctx, "Failed to claim interface: %s.",
- libusb_error_name(ret));
- cleanup_handle(devh);
- libusb_close(usb_devh);
+ switch (devh->dev->interface) {
+ case JAYLINK_HIF_USB:
+ ret = transport_usb_open(devh);
+ break;
+ case JAYLINK_HIF_TCP:
+ ret = transport_tcp_open(devh);
+ break;
+ default:
+ log_err(devh->dev->ctx, "BUG: Invalid host interface: %u.",
+ devh->dev->interface);
return JAYLINK_ERR;
}
- log_dbg(ctx, "Device opened successfully.");
-
- devh->usb_devh = usb_devh;
-
- return JAYLINK_OK;
+ return ret;
}
/**
@@ -241,30 +77,21 @@ JAYLINK_PRIV int transport_open(struct jaylink_device_handle *devh)
JAYLINK_PRIV int transport_close(struct jaylink_device_handle *devh)
{
int ret;
- struct jaylink_device *dev;
- struct jaylink_context *ctx;
-
- dev = devh->dev;
- ctx = dev->ctx;
- log_dbg(ctx, "Closing device (bus:address = %03u:%03u).",
- libusb_get_bus_number(dev->usb_dev),
- libusb_get_device_address(dev->usb_dev));
-
- ret = libusb_release_interface(devh->usb_devh, devh->interface_number);
-
- libusb_close(devh->usb_devh);
- cleanup_handle(devh);
-
- if (ret != LIBUSB_SUCCESS) {
- log_err(ctx, "Failed to release interface: %s.",
- libusb_error_name(ret));
+ switch (devh->dev->interface) {
+ case JAYLINK_HIF_USB:
+ ret = transport_usb_close(devh);
+ break;
+ case JAYLINK_HIF_TCP:
+ ret = transport_tcp_close(devh);
+ break;
+ default:
+ log_err(devh->dev->ctx, "BUG: Invalid host interface: %u.",
+ devh->dev->interface);
return JAYLINK_ERR;
}
- log_dbg(ctx, "Device closed successfully.");
-
- return JAYLINK_OK;
+ return ret;
}
/**
@@ -285,28 +112,22 @@ JAYLINK_PRIV int transport_close(struct jaylink_device_handle *devh)
JAYLINK_PRIV int transport_start_write(struct jaylink_device_handle *devh,
size_t length, bool has_command)
{
- struct jaylink_context *ctx;
-
- (void)has_command;
-
- if (!length)
- return JAYLINK_ERR_ARG;
-
- ctx = devh->dev->ctx;
-
- log_dbg(ctx, "Starting write operation (length = %zu bytes).", length);
-
- if (devh->write_pos > 0)
- log_warn(ctx, "Last write operation left %zu bytes in the "
- "buffer.", devh->write_pos);
-
- if (devh->write_length > 0)
- log_warn(ctx, "Last write operation was not performed.");
+ int ret;
- devh->write_length = length;
- devh->write_pos = 0;
+ switch (devh->dev->interface) {
+ case JAYLINK_HIF_USB:
+ ret = transport_usb_start_write(devh, length, has_command);
+ break;
+ case JAYLINK_HIF_TCP:
+ ret = transport_tcp_start_write(devh, length, has_command);
+ break;
+ default:
+ log_err(devh->dev->ctx, "BUG: Invalid host interface: %u.",
+ devh->dev->interface);
+ return JAYLINK_ERR;
+ }
- return JAYLINK_OK;
+ return ret;
}
/**
@@ -325,26 +146,22 @@ JAYLINK_PRIV int transport_start_write(struct jaylink_device_handle *devh,
JAYLINK_PRIV int transport_start_read(struct jaylink_device_handle *devh,
size_t length)
{
- struct jaylink_context *ctx;
-
- if (!length)
- return JAYLINK_ERR_ARG;
-
- ctx = devh->dev->ctx;
-
- log_dbg(ctx, "Starting read operation (length = %zu bytes).", length);
-
- if (devh->bytes_available > 0)
- log_dbg(ctx, "Last read operation left %zu bytes in the "
- "buffer.", devh->bytes_available);
-
- if (devh->read_length > 0)
- log_warn(ctx, "Last read operation left %zu bytes.",
- devh->read_length);
+ int ret;
- devh->read_length = length;
+ switch (devh->dev->interface) {
+ case JAYLINK_HIF_USB:
+ ret = transport_usb_start_read(devh, length);
+ break;
+ case JAYLINK_HIF_TCP:
+ ret = transport_tcp_start_read(devh, length);
+ break;
+ default:
+ log_err(devh->dev->ctx, "BUG: Invalid host interface: %u.",
+ devh->dev->interface);
+ return JAYLINK_ERR;
+ }
- return JAYLINK_OK;
+ return ret;
}
/**
@@ -370,119 +187,24 @@ JAYLINK_PRIV int transport_start_read(struct jaylink_device_handle *devh,
JAYLINK_PRIV int transport_start_write_read(struct jaylink_device_handle *devh,
size_t write_length, size_t read_length, bool has_command)
{
- struct jaylink_context *ctx;
-
- (void)has_command;
-
- if (!read_length || !write_length)
- return JAYLINK_ERR_ARG;
-
- ctx = devh->dev->ctx;
-
- log_dbg(ctx, "Starting write / read operation (length = "
- "%zu / %zu bytes).", write_length, read_length);
-
- if (devh->write_pos > 0)
- log_warn(ctx, "Last write operation left %zu bytes in the "
- "buffer.", devh->write_pos);
-
- if (devh->write_length > 0)
- log_warn(ctx, "Last write operation was not performed.");
-
- if (devh->bytes_available > 0)
- log_warn(ctx, "Last read operation left %zu bytes in the "
- "buffer.", devh->bytes_available);
-
- if (devh->read_length > 0)
- log_warn(ctx, "Last read operation left %zu bytes.",
- devh->read_length);
-
- devh->write_length = write_length;
- devh->write_pos = 0;
-
- devh->read_length = read_length;
- devh->bytes_available = 0;
- devh->read_pos = 0;
-
- return JAYLINK_OK;
-}
-
-static bool adjust_buffer(struct jaylink_device_handle *devh, size_t size)
-{
- struct jaylink_context *ctx;
- size_t num_chunks;
- uint8_t *buffer;
-
- ctx = devh->dev->ctx;
-
- /* Adjust buffer size to a multiple of CHUNK_SIZE bytes. */
- num_chunks = size / CHUNK_SIZE;
-
- if (size % CHUNK_SIZE > 0)
- num_chunks++;
-
- size = num_chunks * CHUNK_SIZE;
- buffer = realloc(devh->buffer, size);
-
- if (!buffer) {
- log_err(ctx, "Failed to adjust buffer size to %zu bytes.",
- size);
- return false;
- }
-
- devh->buffer = buffer;
- devh->buffer_size = size;
-
- log_dbg(ctx, "Adjusted buffer size to %zu bytes.", size);
-
- return true;
-}
-
-static int usb_send(struct jaylink_device_handle *devh, const uint8_t *buffer,
- size_t length)
-{
int ret;
- struct jaylink_context *ctx;
- unsigned int tries;
- int transferred;
-
- ctx = devh->dev->ctx;
- tries = NUM_TIMEOUTS;
-
- while (tries > 0 && length > 0) {
- /* Send data in chunks of CHUNK_SIZE bytes to the device. */
- ret = libusb_bulk_transfer(devh->usb_devh, devh->endpoint_out,
- (unsigned char *)buffer, MIN(CHUNK_SIZE, length),
- &transferred, USB_TIMEOUT);
-
- if (ret == LIBUSB_SUCCESS) {
- tries = NUM_TIMEOUTS;
- } else if (ret == LIBUSB_ERROR_TIMEOUT) {
- log_warn(ctx, "Sending data to device timed out, "
- "retrying.");
- tries--;
- } else if (ret == LIBUSB_ERROR_IO) {
- log_err(ctx, "Failed to send data to device: "
- "input/output error.");
- return JAYLINK_ERR_IO;
- } else {
- log_err(ctx, "Failed to send data to device: %s.",
- libusb_error_name(ret));
- return JAYLINK_ERR;
- }
- buffer += transferred;
- length -= transferred;
-
- log_dbg(ctx, "Sent %i bytes to device.", transferred);
+ switch (devh->dev->interface) {
+ case JAYLINK_HIF_USB:
+ ret = transport_usb_start_write_read(devh, write_length,
+ read_length, has_command);
+ break;
+ case JAYLINK_HIF_TCP:
+ ret = transport_tcp_start_write_read(devh, write_length,
+ read_length, has_command);
+ break;
+ default:
+ log_err(devh->dev->ctx, "BUG: Invalid host interface: %u.",
+ devh->dev->interface);
+ return JAYLINK_ERR;
}
- if (!length)
- return JAYLINK_OK;
-
- log_err(ctx, "Sending data to device timed out.");
-
- return JAYLINK_ERR_TIMEOUT;
+ return ret;
}
/**
@@ -512,132 +234,21 @@ JAYLINK_PRIV int transport_write(struct jaylink_device_handle *devh,
const uint8_t *buffer, size_t length)
{
int ret;
- struct jaylink_context *ctx;
- size_t num_chunks;
- size_t fill_bytes;
- size_t tmp;
-
- ctx = devh->dev->ctx;
-
- if (length > devh->write_length) {
- log_err(ctx, "Requested to write %zu bytes but only %zu bytes "
- "are expected for the write operation.", length,
- devh->write_length);
- return JAYLINK_ERR_ARG;
- }
-
- /*
- * Store data in the buffer if the expected number of bytes for the
- * write operation is not reached.
- */
- if (length < devh->write_length) {
- if (devh->write_pos + length > devh->buffer_size) {
- if (!adjust_buffer(devh, devh->write_pos + length))
- return JAYLINK_ERR_MALLOC;
- }
-
- memcpy(devh->buffer + devh->write_pos, buffer, length);
-
- devh->write_length -= length;
- devh->write_pos += length;
-
- log_dbg(ctx, "Wrote %zu bytes into buffer.", length);
- return JAYLINK_OK;
- }
-
- /*
- * Expected number of bytes for this write operation is reached and
- * therefore the write operation will be performed.
- */
- devh->write_length = 0;
-
- /* Send data directly to the device if the buffer is empty. */
- if (!devh->write_pos)
- return usb_send(devh, buffer, length);
-
- /*
- * Calculate the number of bytes to fill up the buffer to reach a
- * multiple of CHUNK_SIZE bytes. This ensures that the data from the
- * buffer will be sent to the device in chunks of CHUNK_SIZE bytes.
- * Note that this is why the buffer size must be a multiple of
- * CHUNK_SIZE bytes.
- */
- num_chunks = devh->write_pos / CHUNK_SIZE;
-
- if (devh->write_pos % CHUNK_SIZE)
- num_chunks++;
-
- fill_bytes = (num_chunks * CHUNK_SIZE) - devh->write_pos;
- tmp = MIN(length, fill_bytes);
-
- if (tmp > 0) {
- memcpy(devh->buffer + devh->write_pos, buffer, tmp);
-
- length -= tmp;
- buffer += tmp;
-
- log_dbg(ctx, "Buffer filled up with %zu bytes.", tmp);
- }
-
- /* Send buffered data to the device. */
- ret = usb_send(devh, devh->buffer, devh->write_pos + tmp);
- devh->write_pos = 0;
-
- if (ret != JAYLINK_OK)
- return ret;
-
- if (!length)
- return JAYLINK_OK;
-
- /* Send remaining data to the device. */
- return usb_send(devh, buffer, length);
-}
-
-static int usb_recv(struct jaylink_device_handle *devh, uint8_t *buffer,
- size_t *length)
-{
- int ret;
- struct jaylink_context *ctx;
- unsigned int tries;
- int transferred;
-
- ctx = devh->dev->ctx;
- tries = NUM_TIMEOUTS;
- transferred = 0;
- while (tries > 0 && !transferred) {
- /* Always request CHUNK_SIZE bytes from the device. */
- ret = libusb_bulk_transfer(devh->usb_devh, devh->endpoint_in,
- (unsigned char *)buffer, CHUNK_SIZE, &transferred,
- USB_TIMEOUT);
-
- if (ret == LIBUSB_ERROR_TIMEOUT) {
- log_warn(ctx, "Receiving data from device timed out, "
- "retrying.");
- tries--;
- continue;
- } else if (ret == LIBUSB_ERROR_IO) {
- log_err(ctx, "Failed to receive data from device: "
- "input/output error.");
- return JAYLINK_ERR_IO;
- } else if (ret != LIBUSB_SUCCESS) {
- log_err(ctx, "Failed to receive data from device: %s.",
- libusb_error_name(ret));
- return JAYLINK_ERR;
- }
-
- log_dbg(ctx, "Received %i bytes from device.", transferred);
- }
-
- /* Ignore a possible timeout if at least one byte was received. */
- if (transferred > 0) {
- *length = transferred;
- return JAYLINK_OK;
+ switch (devh->dev->interface) {
+ case JAYLINK_HIF_USB:
+ ret = transport_usb_write(devh, buffer, length);
+ break;
+ case JAYLINK_HIF_TCP:
+ ret = transport_tcp_write(devh, buffer, length);
+ break;
+ default:
+ log_err(devh->dev->ctx, "BUG: Invalid host interface: %u.",
+ devh->dev->interface);
+ return JAYLINK_ERR;
}
- log_err(ctx, "Receiving data from device timed out.");
-
- return JAYLINK_ERR_TIMEOUT;
+ return ret;
}
/**
@@ -663,93 +274,19 @@ JAYLINK_PRIV int transport_read(struct jaylink_device_handle *devh,
uint8_t *buffer, size_t length)
{
int ret;
- struct jaylink_context *ctx;
- size_t bytes_received;
- size_t tmp;
- ctx = devh->dev->ctx;
-
- if (length > devh->read_length) {
- log_err(ctx, "Requested to read %zu bytes but only %zu bytes "
- "are expected for the read operation.", length,
- devh->read_length);
- return JAYLINK_ERR_ARG;
- }
-
- if (length <= devh->bytes_available) {
- memcpy(buffer, devh->buffer + devh->read_pos, length);
-
- devh->read_length -= length;
- devh->bytes_available -= length;
- devh->read_pos += length;
-
- log_dbg(ctx, "Read %zu bytes from buffer.", length);
- return JAYLINK_OK;
- }
-
- if (devh->bytes_available > 0) {
- memcpy(buffer, devh->buffer + devh->read_pos,
- devh->bytes_available);
-
- buffer += devh->bytes_available;
- length -= devh->bytes_available;
- devh->read_length -= devh->bytes_available;
-
- log_dbg(ctx, "Read %zu bytes from buffer to flush it.",
- devh->bytes_available);
-
- devh->bytes_available = 0;
- devh->read_pos = 0;
- }
-
- while (length > 0) {
- /*
- * If less than CHUNK_SIZE bytes are requested from the device,
- * store the received data into the internal buffer instead of
- * directly into the user provided buffer. This is necessary to
- * prevent a possible buffer overflow because the number of
- * requested bytes from the device is always CHUNK_SIZE and
- * therefore up to CHUNK_SIZE bytes may be received.
- * Note that this is why the internal buffer size must be at
- * least CHUNK_SIZE bytes.
- */
- if (length < CHUNK_SIZE) {
- ret = usb_recv(devh, devh->buffer, &bytes_received);
-
- if (ret != JAYLINK_OK)
- return ret;
-
- tmp = MIN(bytes_received, length);
- memcpy(buffer, devh->buffer, tmp);
-
- /*
- * Setup the buffer for the remaining data if more data
- * was received from the device than was requested.
- */
- if (bytes_received > length) {
- devh->bytes_available = bytes_received - tmp;
- devh->read_pos = tmp;
- }
-
- buffer += tmp;
- length -= tmp;
- devh->read_length -= tmp;
-
- log_dbg(ctx, "Read %zu bytes from buffer.", tmp);
- } else {
- ret = usb_recv(devh, buffer, &bytes_received);
-
- if (ret != JAYLINK_OK)
- return ret;
-
- buffer += bytes_received;
- length -= bytes_received;
- devh->read_length -= bytes_received;
-
- log_dbg(ctx, "Read %zu bytes from device.",
- bytes_received);
- }
+ switch (devh->dev->interface) {
+ case JAYLINK_HIF_USB:
+ ret = transport_usb_read(devh, buffer, length);
+ break;
+ case JAYLINK_HIF_TCP:
+ ret = transport_tcp_read(devh, buffer, length);
+ break;
+ default:
+ log_err(devh->dev->ctx, "BUG: Invalid host interface: %u.",
+ devh->dev->interface);
+ return JAYLINK_ERR;
}
- return JAYLINK_OK;
+ return ret;
}
diff --git a/libjaylink/transport_tcp.c b/libjaylink/transport_tcp.c
new file mode 100644
index 0000000..c89540b
--- /dev/null
+++ b/libjaylink/transport_tcp.c
@@ -0,0 +1,568 @@
+/*
+ * This file is part of the libjaylink project.
+ *
+ * Copyright (C) 2015-2017 Marc Schink <jaylink-dev@marcschink.de>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+
+#ifdef _WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#endif
+
+#include "libjaylink.h"
+#include "libjaylink-internal.h"
+
+/**
+ * @file
+ *
+ * Transport abstraction layer (TCP/IP).
+ */
+
+/** @cond PRIVATE */
+#define CMD_SERVER 0x00
+#define CMD_CLIENT 0x07
+
+/**
+ * Response status code indicating that the maximum number of simultaneous
+ * connections on the device has been reached.
+ */
+#define RESP_MAX_CONNECTIONS 0xfe
+
+/** Buffer size in bytes. */
+#define BUFFER_SIZE 2048
+
+/** Timeout of a receive operation in milliseconds. */
+#define RECV_TIMEOUT 5000
+/** Timeout of a send operation in milliseconds. */
+#define SEND_TIMEOUT 5000
+
+/** String of the port number for the J-Link TCP/IP protocol. */
+#define PORT_STRING "19020"
+
+/** Size of the server's hello message in bytes. */
+#define SERVER_HELLO_SIZE 4
+/**
+ * Maximum length of the server name including trailing null-terminator in
+ * bytes.
+ */
+#define SERVER_NAME_MAX_LENGTH 256
+/** @endcond */
+
+static int initialize_handle(struct jaylink_device_handle *devh)
+{
+ struct jaylink_context *ctx;
+
+ ctx = devh->dev->ctx;
+
+ devh->buffer_size = BUFFER_SIZE;
+ devh->buffer = malloc(devh->buffer_size);
+
+ if (!devh->buffer) {
+ log_err(ctx, "Transport buffer malloc failed.");
+ return JAYLINK_ERR_MALLOC;
+ }
+
+ devh->read_length = 0;
+ devh->bytes_available = 0;
+ devh->read_pos = 0;
+
+ devh->write_length = 0;
+ devh->write_pos = 0;
+
+ return JAYLINK_OK;
+}
+
+static void cleanup_handle(struct jaylink_device_handle *devh)
+{
+ free(devh->buffer);
+}
+
+static int _recv(struct jaylink_device_handle *devh, uint8_t *buffer,
+ size_t length)
+{
+ struct jaylink_context *ctx;
+ size_t tmp;
+
+ ctx = devh->dev->ctx;
+
+ while (length > 0) {
+ tmp = length;
+
+ if (!socket_recv(devh->sock, buffer, &tmp, 0)) {
+ log_err(ctx, "Failed to receive data from device.");
+ return JAYLINK_ERR_IO;
+ } else if (!tmp) {
+ log_err(ctx, "Failed to receive data from device: "
+ "remote connection closed.");
+ return JAYLINK_ERR_IO;
+ }
+
+ buffer += tmp;
+ length -= tmp;
+
+ log_dbg(ctx, "Received %zu bytes from device.", tmp);
+ }
+
+ return JAYLINK_OK;
+}
+
+static int handle_server_hello(struct jaylink_device_handle *devh)
+{
+ int ret;
+ struct jaylink_context *ctx;
+ uint8_t buf[SERVER_HELLO_SIZE];
+ char name[SERVER_NAME_MAX_LENGTH];
+ uint16_t proto_version;
+ size_t length;
+
+ ctx = devh->dev->ctx;
+
+ ret = _recv(devh, buf, sizeof(buf));
+
+ if (ret != JAYLINK_OK) {
+ log_err(ctx, "Failed to receive hello message.");
+ return ret;
+ }
+
+ if (buf[0] == RESP_MAX_CONNECTIONS) {
+ log_err(ctx, "Maximum number of connections reached.");
+ return JAYLINK_ERR;
+ }
+
+ if (buf[0] != CMD_SERVER) {
+ log_err(ctx, "Invalid hello message received.");
+ return JAYLINK_ERR_PROTO;
+ }
+
+ proto_version = buffer_get_u16(buf, 1);
+
+ log_dbg(ctx, "Protocol version: 0x%04x.", proto_version);
+
+ length = buf[3];
+ ret = _recv(devh, (uint8_t *)name, length);
+
+ if (ret != JAYLINK_OK) {
+ log_err(ctx, "Failed to receive server name.");
+ return ret;
+ }
+
+ name[length] = '\0';
+
+ log_dbg(ctx, "Server name: %s.", name);
+
+ return JAYLINK_OK;
+}
+
+JAYLINK_PRIV int transport_tcp_open(struct jaylink_device_handle *devh)
+{
+ int ret;
+ struct jaylink_context *ctx;
+ struct jaylink_device *dev;
+ struct addrinfo hints;
+ struct addrinfo *info;
+ struct addrinfo *rp;
+ struct timeval timeout;
+ int sock;
+
+ dev = devh->dev;
+ ctx = dev->ctx;
+
+ log_dbg(ctx, "Trying to open device (IPv4 address = %s).",
+ dev->ipv4_address);
+
+ ret = initialize_handle(devh);
+
+ if (ret != JAYLINK_OK) {
+ log_err(ctx, "Initialize device handle failed.");
+ return ret;
+ }
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ ret = getaddrinfo(dev->ipv4_address, PORT_STRING, &hints, &info);
+
+ if (ret != 0) {
+ log_err(ctx, "Address lookup failed.");
+ cleanup_handle(devh);
+ return JAYLINK_ERR;
+ }
+
+ sock = -1;
+
+ for (rp = info; rp != NULL; rp = rp->ai_next) {
+ sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+
+ if (sock < 0)
+ continue;
+
+ if (!connect(sock, info->ai_addr, info->ai_addrlen))
+ break;
+
+ socket_close(sock);
+ sock = -1;
+ }
+
+ freeaddrinfo(info);
+
+ if (sock < 0) {
+ log_err(ctx, "Failed to open device.");
+ cleanup_handle(devh);
+ return JAYLINK_ERR;
+ }
+
+ log_dbg(ctx, "Device opened successfully.");
+
+ timeout.tv_sec = RECV_TIMEOUT / 1000;
+ timeout.tv_usec = (RECV_TIMEOUT % 1000) * 1000;
+
+ if (!socket_set_option(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout,
+ sizeof(struct timeval))) {
+ log_err(ctx, "Failed to set socket receive timeout.");
+ socket_close(sock);
+ cleanup_handle(devh);
+ return JAYLINK_ERR;
+ }
+
+ timeout.tv_sec = SEND_TIMEOUT / 1000;
+ timeout.tv_usec = (SEND_TIMEOUT % 1000) * 1000;
+
+ if (!socket_set_option(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout,
+ sizeof(struct timeval))) {
+ log_err(ctx, "Failed to set socket send timeout.");
+ socket_close(sock);
+ cleanup_handle(devh);
+ return JAYLINK_ERR;
+ }
+
+ devh->sock = sock;
+
+ ret = handle_server_hello(devh);
+
+ if (ret != JAYLINK_OK) {
+ socket_close(sock);
+ cleanup_handle(devh);
+ return ret;
+ }
+
+ return JAYLINK_OK;
+}
+
+JAYLINK_PRIV int transport_tcp_close(struct jaylink_device_handle *devh)
+{
+ struct jaylink_context *ctx;
+
+ ctx = devh->dev->ctx;
+
+ log_dbg(ctx, "Closing device (IPv4 address = %s).",
+ devh->dev->ipv4_address);
+
+ cleanup_handle(devh);
+
+ log_dbg(ctx, "Device closed successfully.");
+
+ return JAYLINK_OK;
+}
+
+JAYLINK_PRIV int transport_tcp_start_write(struct jaylink_device_handle *devh,
+ size_t length, bool has_command)
+{
+ struct jaylink_context *ctx;
+
+ if (!length)
+ return JAYLINK_ERR_ARG;
+
+ ctx = devh->dev->ctx;
+
+ log_dbg(ctx, "Starting write operation (length = %zu bytes).", length);
+
+ if (devh->write_pos > 0)
+ log_warn(ctx, "Last write operation left %zu bytes in the "
+ "buffer.", devh->write_pos);
+
+ if (devh->write_length > 0)
+ log_warn(ctx, "Last write operation was not performed.");
+
+ devh->write_length = length;
+ devh->write_pos = 0;
+
+ if (has_command) {
+ devh->buffer[0] = CMD_CLIENT;
+ devh->write_pos++;
+ }
+
+ return JAYLINK_OK;
+}
+
+JAYLINK_PRIV int transport_tcp_start_read(struct jaylink_device_handle *devh,
+ size_t length)
+{
+ struct jaylink_context *ctx;
+
+ if (!length)
+ return JAYLINK_ERR_ARG;
+
+ ctx = devh->dev->ctx;
+
+ log_dbg(ctx, "Starting read operation (length = %zu bytes).", length);
+
+ if (devh->bytes_available > 0)
+ log_dbg(ctx, "Last read operation left %zu bytes in the "
+ "buffer.", devh->bytes_available);
+
+ if (devh->read_length > 0)
+ log_warn(ctx, "Last read operation left %zu bytes.",
+ devh->read_length);
+
+ devh->read_length = length;
+
+ return JAYLINK_OK;
+}
+
+JAYLINK_PRIV int transport_tcp_start_write_read(
+ struct jaylink_device_handle *devh, size_t write_length,
+ size_t read_length, bool has_command)
+{
+ struct jaylink_context *ctx;
+
+ if (!read_length || !write_length)
+ return JAYLINK_ERR_ARG;
+
+ ctx = devh->dev->ctx;
+
+ log_dbg(ctx, "Starting write / read operation (length = "
+ "%zu / %zu bytes).", write_length, read_length);
+
+ if (devh->write_pos > 0)
+ log_warn(ctx, "Last write operation left %zu bytes in the "
+ "buffer.", devh->write_pos);
+
+ if (devh->write_length > 0)
+ log_warn(ctx, "Last write operation was not performed.");
+
+ if (devh->bytes_available > 0)
+ log_warn(ctx, "Last read operation left %zu bytes in the "
+ "buffer.", devh->bytes_available);
+
+ if (devh->read_length > 0)
+ log_warn(ctx, "Last read operation left %zu bytes.",
+ devh->read_length);
+
+ devh->write_length = write_length;
+ devh->write_pos = 0;
+
+ if (has_command) {
+ devh->buffer[0] = CMD_CLIENT;
+ devh->write_pos++;
+ }
+
+ devh->read_length = read_length;
+ devh->bytes_available = 0;
+ devh->read_pos = 0;
+
+ return JAYLINK_OK;
+}
+
+static int _send(struct jaylink_device_handle *devh, const uint8_t *buffer,
+ size_t length)
+{
+ struct jaylink_context *ctx;
+ size_t tmp;
+
+ ctx = devh->dev->ctx;
+
+ while (length > 0) {
+ tmp = length;
+
+ if (!socket_send(devh->sock, buffer, &tmp, 0)) {
+ log_err(ctx, "Failed to send data to device.");
+ return JAYLINK_ERR_IO;
+ }
+
+ buffer += tmp;
+ length -= tmp;
+
+ log_dbg(ctx, "Sent %zu bytes to device.", tmp);
+ }
+
+ return JAYLINK_OK;
+}
+
+static bool adjust_buffer(struct jaylink_device_handle *devh, size_t size)
+{
+ struct jaylink_context *ctx;
+ uint8_t *buffer;
+ size_t num;
+
+ ctx = devh->dev->ctx;
+
+ /* Adjust buffer size to a multiple of BUFFER_SIZE bytes. */
+ num = size / BUFFER_SIZE;
+
+ if (size % BUFFER_SIZE > 0)
+ num++;
+
+ size = num * BUFFER_SIZE;
+ buffer = realloc(devh->buffer, size);
+
+ if (!buffer) {
+ log_err(ctx, "Failed to adjust buffer size to %zu bytes.",
+ size);
+ return false;
+ }
+
+ devh->buffer = buffer;
+ devh->buffer_size = size;
+
+ log_dbg(ctx, "Adjusted buffer size to %zu bytes.", size);
+
+ return true;
+}
+
+JAYLINK_PRIV int transport_tcp_write(struct jaylink_device_handle *devh,
+ const uint8_t *buffer, size_t length)
+{
+ int ret;
+ struct jaylink_context *ctx;
+ size_t tmp;
+
+ ctx = devh->dev->ctx;
+
+ if (length > devh->write_length) {
+ log_err(ctx, "Requested to write %zu bytes but only %zu bytes "
+ "are expected for the write operation.", length,
+ devh->write_length);
+ return JAYLINK_ERR_ARG;
+ }
+
+ /*
+ * Store data in the buffer if the expected number of bytes for the
+ * write operation is not reached.
+ */
+ if (length < devh->write_length) {
+ if (devh->write_pos + length > devh->buffer_size) {
+ if (!adjust_buffer(devh, devh->write_pos + length))
+ return JAYLINK_ERR_MALLOC;
+ }
+
+ memcpy(devh->buffer + devh->write_pos, buffer, length);
+
+ devh->write_length -= length;
+ devh->write_pos += length;
+
+ log_dbg(ctx, "Wrote %zu bytes into buffer.", length);
+ return JAYLINK_OK;
+ }
+
+ /*
+ * Expected number of bytes for this write operation is reached and
+ * therefore the write operation will be performed.
+ */
+ devh->write_length = 0;
+
+ /* Send data directly to the device if the buffer is empty. */
+ if (!devh->write_pos)
+ return _send(devh, buffer, length);
+
+ tmp = MIN(length, devh->buffer_size - devh->write_pos);
+
+ /*
+ * Fill up the internal buffer in order to reduce the number of
+ * messages sent to the device for performance reasons.
+ */
+ memcpy(devh->buffer + devh->write_pos, buffer, tmp);
+
+ length -= tmp;
+ buffer += tmp;
+
+ log_dbg(ctx, "Buffer filled up with %zu bytes.", tmp);
+
+ ret = _send(devh, devh->buffer, devh->write_pos + tmp);
+
+ devh->write_pos = 0;
+
+ if (ret != JAYLINK_OK)
+ return ret;
+
+ if (!length)
+ return JAYLINK_OK;
+
+ return _send(devh, buffer, length);
+}
+
+JAYLINK_PRIV int transport_tcp_read(struct jaylink_device_handle *devh,
+ uint8_t *buffer, size_t length)
+{
+ int ret;
+ struct jaylink_context *ctx;
+
+ ctx = devh->dev->ctx;
+
+ if (length > devh->read_length) {
+ log_err(ctx, "Requested to read %zu bytes but only %zu bytes "
+ "are expected for the read operation.", length,
+ devh->read_length);
+ return JAYLINK_ERR_ARG;
+ }
+
+ if (length <= devh->bytes_available) {
+ memcpy(buffer, devh->buffer + devh->read_pos, length);
+
+ devh->read_length -= length;
+ devh->bytes_available -= length;
+ devh->read_pos += length;
+
+ log_dbg(ctx, "Read %zu bytes from buffer.", length);
+ return JAYLINK_OK;
+ }
+
+ if (devh->bytes_available) {
+ memcpy(buffer, devh->buffer + devh->read_pos,
+ devh->bytes_available);
+
+ buffer += devh->bytes_available;
+ length -= devh->bytes_available;
+ devh->read_length -= devh->bytes_available;
+
+ log_dbg(ctx, "Read %zu bytes from buffer to flush it.",
+ devh->bytes_available);
+
+ devh->bytes_available = 0;
+ devh->read_pos = 0;
+ }
+
+ ret = _recv(devh, buffer, length);
+
+ if (ret != JAYLINK_OK)
+ return ret;
+
+ devh->read_length -= length;
+
+ return JAYLINK_OK;
+}
diff --git a/libjaylink/transport_usb.c b/libjaylink/transport_usb.c
new file mode 100644
index 0000000..057e63d
--- /dev/null
+++ b/libjaylink/transport_usb.c
@@ -0,0 +1,619 @@
+/*
+ * This file is part of the libjaylink project.
+ *
+ * Copyright (C) 2014-2016 Marc Schink <jaylink-dev@marcschink.de>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "libjaylink.h"
+#include "libjaylink-internal.h"
+
+/**
+ * @file
+ *
+ * Transport abstraction layer (USB).
+ */
+
+/** Timeout of an USB transfer in milliseconds. */
+#define USB_TIMEOUT 1000
+
+/**
+ * Number of consecutive timeouts before an USB transfer will be treated as
+ * timed out.
+ */
+#define NUM_TIMEOUTS 2
+
+/** Chunk size in bytes in which data is transferred. */
+#define CHUNK_SIZE 2048
+
+static int initialize_handle(struct jaylink_device_handle *devh)
+{
+ int ret;
+ struct jaylink_context *ctx;
+ struct libusb_config_descriptor *config;
+ const struct libusb_interface *interface;
+ const struct libusb_interface_descriptor *desc;
+ const struct libusb_endpoint_descriptor *epdesc;
+ bool found_interface;
+ bool found_endpoint_in;
+ bool found_endpoint_out;
+ uint8_t i;
+
+ ctx = devh->dev->ctx;
+ devh->interface_number = 0;
+
+ /*
+ * Retrieve active configuration descriptor to determine the endpoints
+ * for the interface number of the device.
+ */
+ ret = libusb_get_active_config_descriptor(devh->dev->usb_dev, &config);
+
+ if (ret != LIBUSB_SUCCESS) {
+ log_err(ctx, "Failed to get configuration descriptor: %s.",
+ libusb_error_name(ret));
+ return JAYLINK_ERR;
+ }
+
+ found_interface = false;
+
+ for (i = 0; i < config->bNumInterfaces; i++) {
+ interface = &config->interface[i];
+ desc = &interface->altsetting[0];
+
+ if (desc->bInterfaceClass != LIBUSB_CLASS_VENDOR_SPEC)
+ continue;
+
+ if (desc->bInterfaceSubClass != LIBUSB_CLASS_VENDOR_SPEC)
+ continue;
+
+ if (desc->bNumEndpoints < 2)
+ continue;
+
+ found_interface = true;
+ devh->interface_number = i;
+ break;
+ }
+
+ if (!found_interface) {
+ log_err(ctx, "No suitable interface found.");
+ libusb_free_config_descriptor(config);
+ return JAYLINK_ERR;
+ }
+
+ found_endpoint_in = false;
+ found_endpoint_out = false;
+
+ for (i = 0; i < desc->bNumEndpoints; i++) {
+ epdesc = &desc->endpoint[i];
+
+ if (epdesc->bEndpointAddress & LIBUSB_ENDPOINT_IN) {
+ devh->endpoint_in = epdesc->bEndpointAddress;
+ found_endpoint_in = true;
+ } else {
+ devh->endpoint_out = epdesc->bEndpointAddress;
+ found_endpoint_out = true;
+ }
+ }
+
+ libusb_free_config_descriptor(config);
+
+ if (!found_endpoint_in) {
+ log_err(ctx, "Interface IN endpoint not found.");
+ return JAYLINK_ERR;
+ }
+
+ if (!found_endpoint_out) {
+ log_err(ctx, "Interface OUT endpoint not found.");
+ return JAYLINK_ERR;
+ }
+
+ log_dbg(ctx, "Using endpoint %02x (IN) and %02x (OUT).",
+ devh->endpoint_in, devh->endpoint_out);
+
+ /* Buffer size must be a multiple of CHUNK_SIZE bytes. */
+ devh->buffer_size = CHUNK_SIZE;
+ devh->buffer = malloc(devh->buffer_size);
+
+ if (!devh->buffer) {
+ log_err(ctx, "Transport buffer malloc failed.");
+ return JAYLINK_ERR_MALLOC;
+ }
+
+ devh->read_length = 0;
+ devh->bytes_available = 0;
+ devh->read_pos = 0;
+
+ devh->write_length = 0;
+ devh->write_pos = 0;
+
+ return JAYLINK_OK;
+}
+
+static void cleanup_handle(struct jaylink_device_handle *devh)
+{
+ free(devh->buffer);
+}
+
+JAYLINK_PRIV int transport_usb_open(struct jaylink_device_handle *devh)
+{
+ int ret;
+ struct jaylink_device *dev;
+ struct jaylink_context *ctx;
+ struct libusb_device_handle *usb_devh;
+
+ dev = devh->dev;
+ ctx = dev->ctx;
+
+ log_dbg(ctx, "Trying to open device (bus:address = %03u:%03u).",
+ libusb_get_bus_number(dev->usb_dev),
+ libusb_get_device_address(dev->usb_dev));
+
+ ret = initialize_handle(devh);
+
+ if (ret != JAYLINK_OK) {
+ log_err(ctx, "Initialize device handle failed.");
+ return ret;
+ }
+
+ ret = libusb_open(dev->usb_dev, &usb_devh);
+
+ if (ret != LIBUSB_SUCCESS) {
+ log_err(ctx, "Failed to open device: %s.",
+ libusb_error_name(ret));
+ cleanup_handle(devh);
+ return JAYLINK_ERR;
+ }
+
+ ret = libusb_claim_interface(usb_devh, devh->interface_number);
+
+ if (ret != LIBUSB_SUCCESS) {
+ log_err(ctx, "Failed to claim interface: %s.",
+ libusb_error_name(ret));
+ cleanup_handle(devh);
+ libusb_close(usb_devh);
+ return JAYLINK_ERR;
+ }
+
+ log_dbg(ctx, "Device opened successfully.");
+
+ devh->usb_devh = usb_devh;
+
+ return JAYLINK_OK;
+}
+
+JAYLINK_PRIV int transport_usb_close(struct jaylink_device_handle *devh)
+{
+ int ret;
+ struct jaylink_device *dev;
+ struct jaylink_context *ctx;
+
+ dev = devh->dev;
+ ctx = dev->ctx;
+
+ log_dbg(ctx, "Closing device (bus:address = %03u:%03u).",
+ libusb_get_bus_number(dev->usb_dev),
+ libusb_get_device_address(dev->usb_dev));
+
+ ret = libusb_release_interface(devh->usb_devh, devh->interface_number);
+
+ libusb_close(devh->usb_devh);
+ cleanup_handle(devh);
+
+ if (ret != LIBUSB_SUCCESS) {
+ log_err(ctx, "Failed to release interface: %s.",
+ libusb_error_name(ret));
+ return JAYLINK_ERR;
+ }
+
+ log_dbg(ctx, "Device closed successfully.");
+
+ return JAYLINK_OK;
+}
+
+JAYLINK_PRIV int transport_usb_start_write(struct jaylink_device_handle *devh,
+ size_t length, bool has_command)
+{
+ struct jaylink_context *ctx;
+
+ (void)has_command;
+
+ if (!length)
+ return JAYLINK_ERR_ARG;
+
+ ctx = devh->dev->ctx;
+
+ log_dbg(ctx, "Starting write operation (length = %zu bytes).", length);
+
+ if (devh->write_pos > 0)
+ log_warn(ctx, "Last write operation left %zu bytes in the "
+ "buffer.", devh->write_pos);
+
+ if (devh->write_length > 0)
+ log_warn(ctx, "Last write operation was not performed.");
+
+ devh->write_length = length;
+ devh->write_pos = 0;
+
+ return JAYLINK_OK;
+}
+
+JAYLINK_PRIV int transport_usb_start_read(struct jaylink_device_handle *devh,
+ size_t length)
+{
+ struct jaylink_context *ctx;
+
+ if (!length)
+ return JAYLINK_ERR_ARG;
+
+ ctx = devh->dev->ctx;
+
+ log_dbg(ctx, "Starting read operation (length = %zu bytes).", length);
+
+ if (devh->bytes_available > 0)
+ log_dbg(ctx, "Last read operation left %zu bytes in the "
+ "buffer.", devh->bytes_available);
+
+ if (devh->read_length > 0)
+ log_warn(ctx, "Last read operation left %zu bytes.",
+ devh->read_length);
+
+ devh->read_length = length;
+
+ return JAYLINK_OK;
+}
+
+JAYLINK_PRIV int transport_usb_start_write_read(
+ struct jaylink_device_handle *devh, size_t write_length,
+ size_t read_length, bool has_command)
+{
+ struct jaylink_context *ctx;
+
+ (void)has_command;
+
+ if (!read_length || !write_length)
+ return JAYLINK_ERR_ARG;
+
+ ctx = devh->dev->ctx;
+
+ log_dbg(ctx, "Starting write / read operation (length = "
+ "%zu / %zu bytes).", write_length, read_length);
+
+ if (devh->write_pos > 0)
+ log_warn(ctx, "Last write operation left %zu bytes in the "
+ "buffer.", devh->write_pos);
+
+ if (devh->write_length > 0)
+ log_warn(ctx, "Last write operation was not performed.");
+
+ if (devh->bytes_available > 0)
+ log_warn(ctx, "Last read operation left %zu bytes in the "
+ "buffer.", devh->bytes_available);
+
+ if (devh->read_length > 0)
+ log_warn(ctx, "Last read operation left %zu bytes.",
+ devh->read_length);
+
+ devh->write_length = write_length;
+ devh->write_pos = 0;
+
+ devh->read_length = read_length;
+ devh->bytes_available = 0;
+ devh->read_pos = 0;
+
+ return JAYLINK_OK;
+}
+
+static int usb_recv(struct jaylink_device_handle *devh, uint8_t *buffer,
+ size_t *length)
+{
+ int ret;
+ struct jaylink_context *ctx;
+ unsigned int tries;
+ int transferred;
+
+ ctx = devh->dev->ctx;
+
+ tries = NUM_TIMEOUTS;
+ transferred = 0;
+
+ while (tries > 0 && !transferred) {
+ /* Always request CHUNK_SIZE bytes from the device. */
+ ret = libusb_bulk_transfer(devh->usb_devh, devh->endpoint_in,
+ (unsigned char *)buffer, CHUNK_SIZE, &transferred,
+ USB_TIMEOUT);
+
+ if (ret == LIBUSB_ERROR_TIMEOUT) {
+ log_warn(ctx, "Failed to receive data from "
+ "device: %s.", libusb_error_name(ret));
+ tries--;
+ continue;
+ } else if (ret != LIBUSB_SUCCESS) {
+ log_err(ctx, "Failed to receive data from "
+ "device: %s.", libusb_error_name(ret));
+ return JAYLINK_ERR;
+ }
+
+ log_dbg(ctx, "Received %i bytes from device.", transferred);
+ }
+
+ /* Ignore a possible timeout if at least one byte was received. */
+ if (transferred > 0) {
+ *length = transferred;
+ return JAYLINK_OK;
+ }
+
+ log_err(ctx, "Receiving data from device timed out.");
+
+ return JAYLINK_ERR_TIMEOUT;
+}
+
+static bool adjust_buffer(struct jaylink_device_handle *devh, size_t size)
+{
+ struct jaylink_context *ctx;
+ size_t num_chunks;
+ uint8_t *buffer;
+
+ ctx = devh->dev->ctx;
+
+ /* Adjust buffer size to a multiple of CHUNK_SIZE bytes. */
+ num_chunks = size / CHUNK_SIZE;
+
+ if (size % CHUNK_SIZE > 0)
+ num_chunks++;
+
+ size = num_chunks * CHUNK_SIZE;
+ buffer = realloc(devh->buffer, size);
+
+ if (!buffer) {
+ log_err(ctx, "Failed to adjust buffer size to %zu bytes.",
+ size);
+ return false;
+ }
+
+ devh->buffer = buffer;
+ devh->buffer_size = size;
+
+ log_dbg(ctx, "Adjusted buffer size to %zu bytes.", size);
+
+ return true;
+}
+
+static int usb_send(struct jaylink_device_handle *devh, const uint8_t *buffer,
+ size_t length)
+{
+ int ret;
+ struct jaylink_context *ctx;
+ unsigned int tries;
+ int transferred;
+
+ ctx = devh->dev->ctx;
+ tries = NUM_TIMEOUTS;
+
+ while (tries > 0 && length > 0) {
+ /* Send data in chunks of CHUNK_SIZE bytes to the device. */
+ ret = libusb_bulk_transfer(devh->usb_devh, devh->endpoint_out,
+ (unsigned char *)buffer, MIN(CHUNK_SIZE, length),
+ &transferred, USB_TIMEOUT);
+
+ if (ret == LIBUSB_SUCCESS) {
+ tries = NUM_TIMEOUTS;
+ } else if (ret == LIBUSB_ERROR_TIMEOUT) {
+ log_warn(ctx, "Failed to send data to device: %s.",
+ libusb_error_name(ret));
+ tries--;
+ } else {
+ log_err(ctx, "Failed to send data to device: %s.",
+ libusb_error_name(ret));
+ return JAYLINK_ERR;
+ }
+
+ buffer += transferred;
+ length -= transferred;
+
+ log_dbg(ctx, "Sent %i bytes to device.", transferred);
+ }
+
+ if (!length)
+ return JAYLINK_OK;
+
+ log_err(ctx, "Sending data to device timed out.");
+
+ return JAYLINK_ERR_TIMEOUT;
+}
+
+JAYLINK_PRIV int transport_usb_write(struct jaylink_device_handle *devh,
+ const uint8_t *buffer, size_t length)
+{
+ int ret;
+ struct jaylink_context *ctx;
+ size_t num_chunks;
+ size_t fill_bytes;
+ size_t tmp;
+
+ ctx = devh->dev->ctx;
+
+ if (length > devh->write_length) {
+ log_err(ctx, "Requested to write %zu bytes but only %zu bytes "
+ "are expected for the write operation.", length,
+ devh->write_length);
+ return JAYLINK_ERR_ARG;
+ }
+
+ /*
+ * Store data in the buffer if the expected number of bytes for the
+ * write operation is not reached.
+ */
+ if (length < devh->write_length) {
+ if (devh->write_pos + length > devh->buffer_size) {
+ if (!adjust_buffer(devh, devh->write_pos + length))
+ return JAYLINK_ERR_MALLOC;
+ }
+
+ memcpy(devh->buffer + devh->write_pos, buffer, length);
+
+ devh->write_length -= length;
+ devh->write_pos += length;
+
+ log_dbg(ctx, "Wrote %zu bytes into buffer.", length);
+ return JAYLINK_OK;
+ }
+
+ /*
+ * Expected number of bytes for this write operation is reached and
+ * therefore the write operation will be performed.
+ */
+ devh->write_length = 0;
+
+ /* Send data directly to the device if the buffer is empty. */
+ if (!devh->write_pos)
+ return usb_send(devh, buffer, length);
+
+ /*
+ * Calculate the number of bytes to fill up the buffer to reach a
+ * multiple of CHUNK_SIZE bytes. This ensures that the data from the
+ * buffer will be sent to the device in chunks of CHUNK_SIZE bytes.
+ * Note that this is why the buffer size must be a multiple of
+ * CHUNK_SIZE bytes.
+ */
+ num_chunks = devh->write_pos / CHUNK_SIZE;
+
+ if (devh->write_pos % CHUNK_SIZE)
+ num_chunks++;
+
+ fill_bytes = (num_chunks * CHUNK_SIZE) - devh->write_pos;
+ tmp = MIN(length, fill_bytes);
+
+ if (tmp > 0) {
+ memcpy(devh->buffer + devh->write_pos, buffer, tmp);
+
+ length -= tmp;
+ buffer += tmp;
+
+ log_dbg(ctx, "Buffer filled up with %zu bytes.", tmp);
+ }
+
+ /* Send buffered data to the device. */
+ ret = usb_send(devh, devh->buffer, devh->write_pos + tmp);
+ devh->write_pos = 0;
+
+ if (ret != JAYLINK_OK)
+ return ret;
+
+ if (!length)
+ return JAYLINK_OK;
+
+ /* Send remaining data to the device. */
+ return usb_send(devh, buffer, length);
+}
+
+JAYLINK_PRIV int transport_usb_read(struct jaylink_device_handle *devh,
+ uint8_t *buffer, size_t length)
+{
+ int ret;
+ struct jaylink_context *ctx;
+ size_t bytes_received;
+ size_t tmp;
+
+ ctx = devh->dev->ctx;
+
+ if (length > devh->read_length) {
+ log_err(ctx, "Requested to read %zu bytes but only %zu bytes "
+ "are expected for the read operation.", length,
+ devh->read_length);
+ return JAYLINK_ERR_ARG;
+ }
+
+ if (length <= devh->bytes_available) {
+ memcpy(buffer, devh->buffer + devh->read_pos, length);
+
+ devh->read_length -= length;
+ devh->bytes_available -= length;
+ devh->read_pos += length;
+
+ log_dbg(ctx, "Read %zu bytes from buffer.", length);
+ return JAYLINK_OK;
+ }
+
+ if (devh->bytes_available) {
+ memcpy(buffer, devh->buffer + devh->read_pos,
+ devh->bytes_available);
+
+ buffer += devh->bytes_available;
+ length -= devh->bytes_available;
+ devh->read_length -= devh->bytes_available;
+
+ log_dbg(ctx, "Read %zu bytes from buffer to flush it.",
+ devh->bytes_available);
+
+ devh->bytes_available = 0;
+ devh->read_pos = 0;
+ }
+
+ while (length > 0) {
+ /*
+ * If less than CHUNK_SIZE bytes are requested from the device,
+ * store the received data into the internal buffer instead of
+ * directly into the user provided buffer. This is necessary to
+ * prevent a possible buffer overflow because the number of
+ * requested bytes from the device is always CHUNK_SIZE and
+ * therefore up to CHUNK_SIZE bytes may be received.
+ * Note that this is why the internal buffer size must be at
+ * least CHUNK_SIZE bytes.
+ */
+ if (length < CHUNK_SIZE) {
+ ret = usb_recv(devh, devh->buffer, &bytes_received);
+
+ if (ret != JAYLINK_OK)
+ return ret;
+
+ tmp = MIN(bytes_received, length);
+ memcpy(buffer, devh->buffer, tmp);
+
+ /*
+ * Setup the buffer for the remaining data if more data
+ * was received from the device than was requested.
+ */
+ if (bytes_received > length) {
+ devh->bytes_available = bytes_received - tmp;
+ devh->read_pos = tmp;
+ }
+
+ buffer += tmp;
+ length -= tmp;
+ devh->read_length -= tmp;
+
+ log_dbg(ctx, "Read %zu bytes from buffer.", tmp);
+ } else {
+ ret = usb_recv(devh, buffer, &bytes_received);
+
+ if (ret != JAYLINK_OK)
+ return ret;
+
+ buffer += bytes_received;
+ length -= bytes_received;
+ devh->read_length -= bytes_received;
+
+ log_dbg(ctx, "Read %zu bytes from device.",
+ bytes_received);
+ }
+ }
+
+ return JAYLINK_OK;
+}