aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Schink <jaylink-dev@marcschink.de>2015-04-08 19:23:02 +0200
committerMarc Schink <jaylink-dev@marcschink.de>2017-07-12 14:26:49 +0200
commitaee9aedaf6759bf9f90760d8a4ad5d14ba04010c (patch)
tree3d411c905f015255fbce7735b326b96c57ffcd39
parentf8022ab9e76da186889fcbb98e1675a80c90b258 (diff)
downloadlibjaylink-aee9aedaf6759bf9f90760d8a4ad5d14ba04010c.zip
libjaylink-aee9aedaf6759bf9f90760d8a4ad5d14ba04010c.tar.gz
libjaylink-aee9aedaf6759bf9f90760d8a4ad5d14ba04010c.tar.bz2
discovery: Add initial TCP/IP support
The current implementation uses 'limited broadcasts' for device discovery. The disadvantages are that they are not routed and that device discovery may not work reliable if multiple network interfaces are available. Furthermore, they do not work on FreeBSD. If required, support for device discovery with 'directed broadcasts' will be added in the future. Signed-off-by: Marc Schink <jaylink-dev@marcschink.de>
-rw-r--r--Doxyfile.in9
-rw-r--r--libjaylink/Makefile.am3
-rw-r--r--libjaylink/device.c24
-rw-r--r--libjaylink/discovery.c270
-rw-r--r--libjaylink/discovery_tcp.c342
-rw-r--r--libjaylink/discovery_usb.c280
-rw-r--r--libjaylink/libjaylink-internal.h91
-rw-r--r--libjaylink/libjaylink.h16
-rw-r--r--libjaylink/socket.c192
9 files changed, 953 insertions, 274 deletions
diff --git a/Doxyfile.in b/Doxyfile.in
index 52510b0..0b4d530 100644
--- a/Doxyfile.in
+++ b/Doxyfile.in
@@ -779,10 +779,13 @@ RECURSIVE = NO
# Note that relative paths are relative to the directory from which doxygen is
# run.
-EXCLUDE = @top_srcdir@/libjaylink/libjaylink-internal.h \
+EXCLUDE = @top_srcdir@/libjaylink/buffer.c \
+ @top_srcdir@/libjaylink/discovery_tcp.c \
+ @top_srcdir@/libjaylink/discovery_usb.c \
+ @top_srcdir@/libjaylink/libjaylink-internal.h \
@top_srcdir@/libjaylink/list.c \
- @top_srcdir@/libjaylink/transport.c \
- @top_srcdir@/libjaylink/buffer.c
+ @top_srcdir@/libjaylink/socket.c \
+ @top_srcdir@/libjaylink/transport.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 dd709fb..e32645c 100644
--- a/libjaylink/Makefile.am
+++ b/libjaylink/Makefile.am
@@ -32,12 +32,15 @@ libjaylink_la_SOURCES = \
core.c \
device.c \
discovery.c \
+ discovery_tcp.c \
+ discovery_usb.c \
emucom.c \
error.c \
fileio.c \
jtag.c \
list.c \
log.c \
+ socket.c \
strutil.c \
swd.c \
swo.c \
diff --git a/libjaylink/device.c b/libjaylink/device.c
index e14a304..010c0a0 100644
--- a/libjaylink/device.c
+++ b/libjaylink/device.c
@@ -87,7 +87,6 @@ JAYLINK_PRIV struct jaylink_device *device_allocate(
dev->ctx = ctx;
dev->ref_count = 1;
- dev->usb_dev = NULL;
return dev;
}
@@ -313,15 +312,22 @@ JAYLINK_API void jaylink_unref_device(struct jaylink_device *dev)
if (!dev->ref_count) {
ctx = dev->ctx;
-
- log_dbg(ctx, "Device destroyed (bus:address = %03u:%03u).",
- libusb_get_bus_number(dev->usb_dev),
- libusb_get_device_address(dev->usb_dev));
-
ctx->devs = list_remove(dev->ctx->devs, dev);
- if (dev->usb_dev)
+ if (dev->interface == JAYLINK_HIF_USB) {
+ log_dbg(ctx, "Device destroyed (bus:address = "
+ "%03u:%03u).",
+ libusb_get_bus_number(dev->usb_dev),
+ libusb_get_device_address(dev->usb_dev));
+
libusb_unref_device(dev->usb_dev);
+ } else if (dev->interface == JAYLINK_HIF_TCP) {
+ log_dbg(ctx, "Device destroyed (IPv4 address = %s).",
+ dev->ipv4_address);
+ } else {
+ log_err(ctx, "BUG: Invalid host interface: %u.",
+ dev->interface);
+ }
free(dev);
}
@@ -359,6 +365,7 @@ 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.
*
@@ -373,6 +380,9 @@ 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/discovery.c b/libjaylink/discovery.c
index 81272af..7b4808b 100644
--- a/libjaylink/discovery.c
+++ b/libjaylink/discovery.c
@@ -18,264 +18,16 @@
*/
#include <stdlib.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <string.h>
-#include <sys/types.h>
#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
*
* Device discovery.
*/
-/** @cond PRIVATE */
-/** USB Vendor ID (VID) of SEGGER products. */
-#define USB_VENDOR_ID 0x1366
-
-/* USB Product IDs (PID) and their corresponding USB addresses. */
-static const uint16_t pids[][2] = {
- {0x0101, 0},
- {0x0102, 1},
- {0x0103, 2},
- {0x0104, 3},
- {0x0105, 0},
- {0x0107, 0},
- {0x0108, 0},
- {0x1010, 0},
- {0x1011, 0},
- {0x1012, 0},
- {0x1013, 0},
- {0x1014, 0},
- {0x1015, 0},
- {0x1016, 0},
- {0x1017, 0},
- {0x1018, 0}
-};
-
-/** Maximum length of the USB string descriptor for the serial number. */
-#define USB_SERIAL_NUMBER_LENGTH 12
-
-/**
- * Maximum number of digits in a serial number
- *
- * The serial number of a device consists of at most 9 digits but user defined
- * serial numbers are allowed with up to 10 digits.
- */
-#define MAX_SERIAL_NUMBER_DIGITS 10
-/** @endcond */
-
-static bool parse_serial_number(const char *str, uint32_t *serial_number)
-{
- size_t length;
-
- length = strlen(str);
-
- /*
- * Skip the first digits which are not part of a valid serial number.
- * This is necessary because some devices erroneously use random digits
- * instead of zeros for padding.
- */
- if (length > MAX_SERIAL_NUMBER_DIGITS)
- str = str + (length - MAX_SERIAL_NUMBER_DIGITS);
-
- if (jaylink_parse_serial_number(str, serial_number) != JAYLINK_OK)
- return false;
-
- return true;
-}
-
-static bool compare_devices(const void *data, const void *user_data)
-{
- const struct jaylink_device *dev;
- const struct libusb_device *usb_dev;
-
- dev = data;
- usb_dev = user_data;
-
- if (dev->usb_dev == usb_dev)
- return true;
-
- return false;
-}
-
-static struct jaylink_device *find_device(const struct jaylink_context *ctx,
- const struct libusb_device *usb_dev)
-{
- struct list *item;
-
- item = list_find_custom(ctx->devs, &compare_devices, usb_dev);
-
- if (item)
- return item->data;
-
- return NULL;
-}
-
-static struct jaylink_device *probe_device(struct jaylink_context *ctx,
- struct libusb_device *usb_dev)
-{
- int ret;
- struct libusb_device_descriptor desc;
- struct libusb_device_handle *usb_devh;
- struct jaylink_device *dev;
- char buf[USB_SERIAL_NUMBER_LENGTH + 1];
- uint8_t usb_address;
- uint32_t serial_number;
- bool valid_serial_number;
- bool found_device;
- size_t i;
-
- ret = libusb_get_device_descriptor(usb_dev, &desc);
-
- if (ret != LIBUSB_SUCCESS) {
- log_warn(ctx, "Failed to get device descriptor: %s.",
- libusb_error_name(ret));
- return NULL;
- }
-
- if (desc.idVendor != USB_VENDOR_ID)
- return NULL;
-
- found_device = false;
-
- for (i = 0; i < sizeof(pids) / sizeof(pids[0]); i++) {
- if (pids[i][0] == desc.idProduct) {
- found_device = true;
- usb_address = pids[i][1];
- break;
- }
- }
-
- if (!found_device)
- return NULL;
-
- log_dbg(ctx, "Found device (VID:PID = %04x:%04x, bus:address = "
- "%03u:%03u).", desc.idVendor, desc.idProduct,
- libusb_get_bus_number(usb_dev),
- libusb_get_device_address(usb_dev));
-
- /*
- * Search for an already allocated device instance for this device and
- * if found return a reference to it.
- */
- dev = find_device(ctx, usb_dev);
-
- if (dev) {
- log_dbg(ctx, "Device: USB address = %u.", dev->usb_address);
-
- if (dev->valid_serial_number)
- log_dbg(ctx, "Device: Serial number = %u.",
- dev->serial_number);
- else
- log_dbg(ctx, "Device: Serial number = N/A.");
-
- log_dbg(ctx, "Using existing device instance.");
- return jaylink_ref_device(dev);
- }
-
- /* Open the device to be able to retrieve its serial number. */
- ret = libusb_open(usb_dev, &usb_devh);
-
- if (ret != LIBUSB_SUCCESS) {
- log_warn(ctx, "Failed to open device: %s.",
- libusb_error_name(ret));
- return NULL;
- }
-
- serial_number = 0;
- valid_serial_number = true;
-
- ret = libusb_get_string_descriptor_ascii(usb_devh, desc.iSerialNumber,
- (unsigned char *)buf, USB_SERIAL_NUMBER_LENGTH + 1);
-
- libusb_close(usb_devh);
-
- if (ret < 0) {
- log_warn(ctx, "Failed to retrieve serial number: %s.",
- libusb_error_name(ret));
- valid_serial_number = false;
- }
-
- if (valid_serial_number) {
- if (!parse_serial_number(buf, &serial_number)) {
- log_warn(ctx, "Failed to parse serial number.");
- return NULL;
- }
- }
-
- log_dbg(ctx, "Device: USB address = %u.", usb_address);
-
- if (valid_serial_number)
- log_dbg(ctx, "Device: Serial number = %u.", serial_number);
- else
- log_dbg(ctx, "Device: Serial number = N/A.");
-
- log_dbg(ctx, "Allocating new device instance.");
-
- dev = device_allocate(ctx);
-
- if (!dev) {
- log_warn(ctx, "Device instance malloc failed.");
- return NULL;
- }
-
- dev->interface = JAYLINK_HIF_USB;
- dev->usb_dev = libusb_ref_device(usb_dev);
- dev->usb_address = usb_address;
- dev->serial_number = serial_number;
- dev->valid_serial_number = valid_serial_number;
-
- return dev;
-}
-
-static int discovery_usb_scan(struct jaylink_context *ctx)
-{
- ssize_t ret;
- struct libusb_device **devs;
- struct jaylink_device *dev;
- size_t num;
- size_t i;
-
- ret = libusb_get_device_list(ctx->usb_ctx, &devs);
-
- if (ret == LIBUSB_ERROR_IO) {
- log_err(ctx, "Failed to retrieve device list: input/output "
- "error.");
- return JAYLINK_ERR_IO;
- } else if (ret < 0) {
- log_err(ctx, "Failed to retrieve device list: %s.",
- libusb_error_name(ret));
- return JAYLINK_ERR;
- }
-
- num = 0;
-
- for (i = 0; devs[i]; i++) {
- dev = probe_device(ctx, devs[i]);
-
- if (!dev)
- continue;
-
- ctx->discovered_devs = list_prepend(ctx->discovered_devs, dev);
- num++;
- }
-
- libusb_free_device_list(devs, true);
- log_dbg(ctx, "Found %zu USB device(s).", num);
-
- return JAYLINK_OK;
-}
-
static void clear_discovery_list(struct jaylink_context *ctx)
{
struct list *item;
@@ -322,15 +74,27 @@ JAYLINK_API int jaylink_discovery_scan(struct jaylink_context *ctx,
if (!ctx)
return JAYLINK_ERR_ARG;
- (void)ifaces;
+ if (!ifaces)
+ ifaces = JAYLINK_HIF_USB | JAYLINK_HIF_TCP;
clear_discovery_list(ctx);
- ret = discovery_usb_scan(ctx);
+ if (ifaces & JAYLINK_HIF_USB) {
+ ret = discovery_usb_scan(ctx);
- if (ret != JAYLINK_OK) {
- log_err(ctx, "USB device discovery failed.");
- return ret;
+ if (ret != JAYLINK_OK) {
+ log_err(ctx, "USB device discovery failed.");
+ return ret;
+ }
+ }
+
+ if (ifaces & JAYLINK_HIF_TCP) {
+ ret = discovery_tcp_scan(ctx);
+
+ if (ret != JAYLINK_OK) {
+ log_err(ctx, "TCP/IP device discovery failed.");
+ return ret;
+ }
}
return JAYLINK_OK;
diff --git a/libjaylink/discovery_tcp.c b/libjaylink/discovery_tcp.c
new file mode 100644
index 0000000..25e7732
--- /dev/null
+++ b/libjaylink/discovery_tcp.c
@@ -0,0 +1,342 @@
+/*
+ * 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 <ctype.h>
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+
+#include "libjaylink.h"
+#include "libjaylink-internal.h"
+
+/**
+ * @file
+ *
+ * Device discovery (TCP/IP).
+ */
+
+/** @cond PRIVATE */
+/** Size of the advertisement message in bytes. */
+#define ADV_MESSAGE_SIZE 128
+
+/** Device discovery port number. */
+#define DISC_PORT 19020
+
+/** Size of the discovery message in bytes. */
+#define DISC_MESSAGE_SIZE 64
+
+/** Discovery timeout in milliseconds. */
+#define DISC_TIMEOUT 20
+/** @endcond */
+
+static bool compare_devices(const void *a, const void *b)
+{
+ const struct jaylink_device *dev;
+ const struct jaylink_device *new_dev;
+
+ dev = a;
+ new_dev = b;
+
+ if (dev->interface != JAYLINK_HIF_TCP)
+ return false;
+
+ if (memcmp(dev->ipv4_address, new_dev->ipv4_address,
+ sizeof(dev->ipv4_address)) != 0)
+ return false;
+
+ if (dev->serial_number != new_dev->serial_number)
+ return false;
+
+ if (memcmp(dev->mac_address, new_dev->mac_address,
+ sizeof(dev->mac_address)) != 0)
+ return false;
+
+ if (strcmp(dev->product_name, new_dev->product_name) != 0)
+ return false;
+
+ if (strcmp(dev->nickname, new_dev->nickname) != 0)
+ return false;
+
+ if (dev->hw_version.type != new_dev->hw_version.type)
+ return false;
+
+ if (dev->hw_version.major != new_dev->hw_version.major)
+ return false;
+
+ if (dev->hw_version.minor != new_dev->hw_version.minor)
+ return false;
+
+ if (dev->hw_version.revision != new_dev->hw_version.revision)
+ return false;
+
+ return true;
+}
+
+static struct jaylink_device *find_device(const struct jaylink_context *ctx,
+ const struct jaylink_device *dev)
+{
+ struct list *item;
+
+ item = list_find_custom(ctx->devs, &compare_devices, dev);
+
+ if (item)
+ return item->data;
+
+ return NULL;
+}
+
+static bool parse_adv_message(struct jaylink_device *dev,
+ const uint8_t *buffer)
+{
+ struct in_addr in;
+ uint32_t tmp;
+
+ if (memcmp(buffer, "Found", 5) != 0)
+ return false;
+
+ /*
+ * Use inet_ntoa() instead of inet_ntop() because the latter requires
+ * at least Windows Vista.
+ */
+ memcpy(&in, buffer + 16, 4);
+ memcpy(dev->ipv4_address, inet_ntoa(in), sizeof(dev->ipv4_address));
+
+ memcpy(dev->mac_address, buffer + 32, sizeof(dev->mac_address));
+ dev->has_mac_address = true;
+
+ dev->serial_number = buffer_get_u32(buffer, 48);
+ dev->valid_serial_number = true;
+
+ tmp = buffer_get_u32(buffer, 52);
+ dev->hw_version.type = (tmp / 1000000) % 100;
+ dev->hw_version.major = (tmp / 10000) % 100;
+ dev->hw_version.minor = (tmp / 100) % 100;
+ dev->hw_version.revision = tmp % 100;
+ dev->has_hw_version = true;
+
+ memcpy(dev->product_name, buffer + 64, sizeof(dev->product_name));
+ dev->product_name[JAYLINK_PRODUCT_NAME_MAX_LENGTH - 1] = '\0';
+ dev->has_product_name = isprint((unsigned char)dev->product_name[0]);
+
+ memcpy(dev->nickname, buffer + 96, sizeof(dev->nickname));
+ dev->nickname[JAYLINK_NICKNAME_MAX_LENGTH - 1] = '\0';
+ dev->has_nickname = isprint((unsigned char)dev->nickname[0]);
+
+ return true;
+}
+
+static struct jaylink_device *probe_device(struct jaylink_context *ctx,
+ struct sockaddr_in *addr, const uint8_t *buffer)
+{
+ struct jaylink_device tmp;
+ struct jaylink_device *dev;
+
+ /*
+ * Use inet_ntoa() instead of inet_ntop() because the latter requires
+ * at least Windows Vista.
+ */
+ log_dbg(ctx, "Received advertisement message (IPv4 address = %s).",
+ inet_ntoa(addr->sin_addr));
+
+ if (!parse_adv_message(&tmp, buffer)) {
+ log_dbg(ctx, "Received invalid advertisement message.");
+ return NULL;
+ }
+
+ log_dbg(ctx, "Found device (IPv4 address = %s).", tmp.ipv4_address);
+ log_dbg(ctx, "Device: MAC address = %02x:%02x:%02x:%02x:%02x:%02x.",
+ tmp.mac_address[0], tmp.mac_address[1], tmp.mac_address[2],
+ tmp.mac_address[3], tmp.mac_address[4], tmp.mac_address[5]);
+ log_dbg(ctx, "Device: Serial number = %u.", tmp.serial_number);
+
+ if (tmp.has_product_name)
+ log_dbg(ctx, "Device: Product = %s.", tmp.product_name);
+
+ if (tmp.has_nickname)
+ log_dbg(ctx, "Device: Nickname = %s.", tmp.nickname);
+
+ dev = find_device(ctx, &tmp);
+
+ if (dev) {
+ log_dbg(ctx, "Using existing device instance.");
+ return jaylink_ref_device(dev);
+ }
+
+ log_dbg(ctx, "Allocating new device instance.");
+
+ dev = device_allocate(ctx);
+
+ if (!dev) {
+ log_warn(ctx, "Device instance malloc failed.");
+ return NULL;
+ }
+
+ dev->interface = JAYLINK_HIF_TCP;
+
+ dev->serial_number = tmp.serial_number;
+ dev->valid_serial_number = tmp.valid_serial_number;
+
+ memcpy(dev->ipv4_address, tmp.ipv4_address, sizeof(dev->ipv4_address));
+
+ memcpy(dev->mac_address, tmp.mac_address, sizeof(dev->mac_address));
+ dev->has_mac_address = tmp.has_mac_address;
+
+ memcpy(dev->product_name, tmp.product_name, sizeof(dev->product_name));
+ dev->has_product_name = tmp.has_product_name;
+
+ memcpy(dev->nickname, tmp.nickname, sizeof(dev->nickname));
+ dev->has_nickname = tmp.has_nickname;
+
+ dev->hw_version = tmp.hw_version;
+ dev->has_hw_version = tmp.has_hw_version;
+
+ return dev;
+}
+
+/** @private */
+JAYLINK_PRIV int discovery_tcp_scan(struct jaylink_context *ctx)
+{
+ int ret;
+ int sock;
+ int opt_value;
+ fd_set rfds;
+ struct sockaddr_in addr;
+ size_t addr_length;
+ struct timeval timeout;
+ uint8_t buf[ADV_MESSAGE_SIZE];
+ struct jaylink_device *dev;
+ size_t length;
+ size_t num_devs;
+
+ sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+ if (sock < 0) {
+ log_err(ctx, "Failed to create discovery socket.");
+ return JAYLINK_ERR;
+ }
+
+ opt_value = true;
+
+ if (!socket_set_option(sock, SOL_SOCKET, SO_BROADCAST, &opt_value,
+ sizeof(opt_value))) {
+ log_err(ctx, "Failed to enable broadcast option for discovery "
+ "socket.");
+ socket_close(sock);
+ return JAYLINK_ERR;
+ }
+
+ memset(&addr, 0, sizeof(struct sockaddr_in));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(DISC_PORT);
+ addr.sin_addr.s_addr = INADDR_ANY;
+
+ if (!socket_bind(sock, (struct sockaddr *)&addr,
+ sizeof(struct sockaddr_in))) {
+ log_err(ctx, "Failed to bind discovery socket.");
+ socket_close(sock);
+ return JAYLINK_ERR;
+ }
+
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(DISC_PORT);
+ addr.sin_addr.s_addr = INADDR_BROADCAST;
+
+ memset(buf, 0, DISC_MESSAGE_SIZE);
+ memcpy(buf, "Discover", 8);
+
+ log_dbg(ctx, "Sending discovery message.");
+
+ length = DISC_MESSAGE_SIZE;
+
+ if (!socket_sendto(sock, (char *)buf, &length, 0,
+ (const struct sockaddr *)&addr, sizeof(addr))) {
+ log_err(ctx, "Failed to send discovery message.");
+ socket_close(sock);
+ return JAYLINK_ERR_IO;
+ }
+
+ if (length < DISC_MESSAGE_SIZE) {
+ log_err(ctx, "Only sent %zu bytes of discovery message.",
+ length);
+ socket_close(sock);
+ return JAYLINK_ERR_IO;
+ }
+
+ timeout.tv_sec = DISC_TIMEOUT / 1000;
+ timeout.tv_usec = (DISC_TIMEOUT % 1000) * 1000;
+
+ num_devs = 0;
+
+ while (true) {
+ FD_ZERO(&rfds);
+ FD_SET(sock, &rfds);
+
+ ret = select(sock + 1, &rfds, NULL, NULL, &timeout);
+
+ if (ret <= 0)
+ break;
+
+ if (!FD_ISSET(sock, &rfds))
+ continue;
+
+ length = ADV_MESSAGE_SIZE;
+ addr_length = sizeof(struct sockaddr_in);
+
+ if (!socket_recvfrom(sock, buf, &length, 0,
+ (struct sockaddr *)&addr, &addr_length)) {
+ log_warn(ctx, "Failed to receive advertisement "
+ "message.");
+ continue;
+ }
+
+ /*
+ * Filter out messages with an invalid size. This includes the
+ * broadcast message we sent before.
+ */
+ if (length != ADV_MESSAGE_SIZE)
+ continue;
+
+ dev = probe_device(ctx, &addr, buf);
+
+ if (dev) {
+ ctx->discovered_devs = list_prepend(
+ ctx->discovered_devs, dev);
+ num_devs++;
+ }
+ }
+
+ socket_close(sock);
+
+ if (ret < 0) {
+ log_err(ctx, "select() failed.");
+ return JAYLINK_ERR;
+ }
+
+ log_dbg(ctx, "Found %zu TCP/IP device(s).", num_devs);
+
+ return JAYLINK_OK;
+}
diff --git a/libjaylink/discovery_usb.c b/libjaylink/discovery_usb.c
new file mode 100644
index 0000000..409ca75
--- /dev/null
+++ b/libjaylink/discovery_usb.c
@@ -0,0 +1,280 @@
+/*
+ * 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 <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/types.h>
+
+#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
+ *
+ * Device discovery (USB).
+ */
+
+/** @cond PRIVATE */
+/** USB Vendor ID (VID) of SEGGER products. */
+#define USB_VENDOR_ID 0x1366
+
+/* USB Product IDs (PID) and their corresponding USB addresses. */
+static const uint16_t pids[][2] = {
+ {0x0101, 0},
+ {0x0102, 1},
+ {0x0103, 2},
+ {0x0104, 3},
+ {0x0105, 0},
+ {0x0107, 0},
+ {0x0108, 0},
+ {0x1010, 0},
+ {0x1011, 0},
+ {0x1012, 0},
+ {0x1013, 0},
+ {0x1014, 0},
+ {0x1015, 0},
+ {0x1016, 0},
+ {0x1017, 0},
+ {0x1018, 0}
+};
+
+/** Maximum length of the USB string descriptor for the serial number. */
+#define USB_SERIAL_NUMBER_LENGTH 12
+
+/**
+ * Maximum number of digits in a serial number
+ *
+ * The serial number of a device consists of at most 9 digits but user defined
+ * serial numbers are allowed with up to 10 digits.
+ */
+#define MAX_SERIAL_NUMBER_DIGITS 10
+/** @endcond */
+
+static bool parse_serial_number(const char *str, uint32_t *serial_number)
+{
+ size_t length;
+
+ length = strlen(str);
+
+ /*
+ * Skip the first digits which are not part of a valid serial number.
+ * This is necessary because some devices erroneously use random digits
+ * instead of zeros for padding.
+ */
+ if (length > MAX_SERIAL_NUMBER_DIGITS)
+ str = str + (length - MAX_SERIAL_NUMBER_DIGITS);
+
+ if (jaylink_parse_serial_number(str, serial_number) != JAYLINK_OK)
+ return false;
+
+ return true;
+}
+
+static bool compare_devices(const void *a, const void *b)
+{
+ const struct jaylink_device *dev;
+ const struct libusb_device *usb_dev;
+
+ dev = a;
+ usb_dev = b;
+
+ if (dev->interface != JAYLINK_HIF_USB)
+ return false;
+
+ if (dev->usb_dev == usb_dev)
+ return true;
+
+ return false;
+}
+
+static struct jaylink_device *find_device(const struct jaylink_context *ctx,
+ const struct libusb_device *usb_dev)
+{
+ struct list *item;
+
+ item = list_find_custom(ctx->devs, &compare_devices, usb_dev);
+
+ if (item)
+ return item->data;
+
+ return NULL;
+}
+
+static struct jaylink_device *probe_device(struct jaylink_context *ctx,
+ struct libusb_device *usb_dev)
+{
+ int ret;
+ struct libusb_device_descriptor desc;
+ struct libusb_device_handle *usb_devh;
+ struct jaylink_device *dev;
+ char buf[USB_SERIAL_NUMBER_LENGTH + 1];
+ uint8_t usb_address;
+ uint32_t serial_number;
+ bool valid_serial_number;
+ bool found_device;
+ size_t i;
+
+ ret = libusb_get_device_descriptor(usb_dev, &desc);
+
+ if (ret != LIBUSB_SUCCESS) {
+ log_warn(ctx, "Failed to get device descriptor: %s.",
+ libusb_error_name(ret));
+ return NULL;
+ }
+
+ if (desc.idVendor != USB_VENDOR_ID)
+ return NULL;
+
+ found_device = false;
+
+ for (i = 0; i < sizeof(pids) / sizeof(pids[0]); i++) {
+ if (pids[i][0] == desc.idProduct) {
+ found_device = true;
+ usb_address = pids[i][1];
+ break;
+ }
+ }
+
+ if (!found_device)
+ return NULL;
+
+ log_dbg(ctx, "Found device (VID:PID = %04x:%04x, bus:address = "
+ "%03u:%03u).", desc.idVendor, desc.idProduct,
+ libusb_get_bus_number(usb_dev),
+ libusb_get_device_address(usb_dev));
+
+ /*
+ * Search for an already allocated device instance for this device and
+ * if found return a reference to it.
+ */
+ dev = find_device(ctx, usb_dev);
+
+ if (dev) {
+ log_dbg(ctx, "Device: USB address = %u.", dev->usb_address);
+
+ if (dev->valid_serial_number)
+ log_dbg(ctx, "Device: Serial number = %u.",
+ dev->serial_number);
+ else
+ log_dbg(ctx, "Device: Serial number = N/A.");
+
+ log_dbg(ctx, "Using existing device instance.");
+ return jaylink_ref_device(dev);
+ }
+
+ /* Open the device to be able to retrieve its serial number. */
+ ret = libusb_open(usb_dev, &usb_devh);
+
+ if (ret != LIBUSB_SUCCESS) {
+ log_warn(ctx, "Failed to open device: %s.",
+ libusb_error_name(ret));
+ return NULL;
+ }
+
+ serial_number = 0;
+ valid_serial_number = true;
+
+ ret = libusb_get_string_descriptor_ascii(usb_devh, desc.iSerialNumber,
+ (unsigned char *)buf, USB_SERIAL_NUMBER_LENGTH + 1);
+
+ libusb_close(usb_devh);
+
+ if (ret < 0) {
+ log_warn(ctx, "Failed to retrieve serial number: %s.",
+ libusb_error_name(ret));
+ valid_serial_number = false;
+ }
+
+ if (valid_serial_number) {
+ if (!parse_serial_number(buf, &serial_number)) {
+ log_warn(ctx, "Failed to parse serial number.");
+ return NULL;
+ }
+ }
+
+ log_dbg(ctx, "Device: USB address = %u.", usb_address);
+
+ if (valid_serial_number)
+ log_dbg(ctx, "Device: Serial number = %u.", serial_number);
+ else
+ log_dbg(ctx, "Device: Serial number = N/A.");
+
+ log_dbg(ctx, "Allocating new device instance.");
+
+ dev = device_allocate(ctx);
+
+ if (!dev) {
+ log_warn(ctx, "Device instance malloc failed.");
+ return NULL;
+ }
+
+ dev->interface = JAYLINK_HIF_USB;
+ dev->usb_dev = libusb_ref_device(usb_dev);
+ dev->usb_address = usb_address;
+ dev->serial_number = serial_number;
+ dev->valid_serial_number = valid_serial_number;
+
+ return dev;
+}
+
+JAYLINK_PRIV int discovery_usb_scan(struct jaylink_context *ctx)
+{
+ ssize_t ret;
+ struct libusb_device **devs;
+ struct jaylink_device *dev;
+ size_t num;
+ size_t i;
+
+ ret = libusb_get_device_list(ctx->usb_ctx, &devs);
+
+ if (ret == LIBUSB_ERROR_IO) {
+ log_err(ctx, "Failed to retrieve device list: input/output "
+ "error.");
+ return JAYLINK_ERR_IO;
+ } else if (ret < 0) {
+ log_err(ctx, "Failed to retrieve device list: %s.",
+ libusb_error_name(ret));
+ return JAYLINK_ERR;
+ }
+
+ num = 0;
+
+ for (i = 0; devs[i]; i++) {
+ dev = probe_device(ctx, devs[i]);
+
+ if (!dev)
+ continue;
+
+ ctx->discovered_devs = list_prepend(ctx->discovered_devs, dev);
+ num++;
+ }
+
+ libusb_free_device_list(devs, true);
+ log_dbg(ctx, "Found %zu USB device(s).", num);
+
+ return JAYLINK_OK;
+}
diff --git a/libjaylink/libjaylink-internal.h b/libjaylink/libjaylink-internal.h
index 8ce28d7..5da37dc 100644
--- a/libjaylink/libjaylink-internal.h
+++ b/libjaylink/libjaylink-internal.h
@@ -24,15 +24,17 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
+#include <sys/types.h>
+#ifdef _WIN32
+#include <ws2tcpip.h>
+#else
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#endif
+#include <libusb.h>
#include "libjaylink.h"
-/*
- * libusb.h includes windows.h and therefore must be included after anything
- * that includes winsock2.h.
- */
-#include <libusb.h>
-
/**
* @file
*
@@ -49,6 +51,9 @@
/** Calculate the minimum of two numeric values. */
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+/** Media Access Control (MAC) address length in bytes. */
+#define MAC_ADDRESS_LENGTH 6
+
struct jaylink_context {
/** libusb context. */
struct libusb_context *usb_ctx;
@@ -77,10 +82,6 @@ struct jaylink_device {
size_t ref_count;
/** Host interface. */
enum jaylink_host_interface interface;
- /** libusb device instance. */
- struct libusb_device *usb_dev;
- /** USB address of the device. */
- uint8_t usb_address;
/**
* Serial number of the device.
*
@@ -90,6 +91,55 @@ struct jaylink_device {
uint32_t serial_number;
/** Indicates whether the serial number is valid. */
bool valid_serial_number;
+ /** libusb device instance. */
+ struct libusb_device *usb_dev;
+ /** USB address of the device. */
+ uint8_t usb_address;
+ /**
+ * IPv4 address.
+ *
+ * The address is encoded as string in quad-dotted decimal format.
+ *
+ * This field is used for devices with host interface #JAYLINK_HIF_TCP
+ * only.
+ */
+ char ipv4_address[INET_ADDRSTRLEN];
+ /**
+ * Media Access Control (MAC) address.
+ *
+ * This field is used for devices with host interface #JAYLINK_HIF_TCP
+ * only.
+ */
+ uint8_t mac_address[MAC_ADDRESS_LENGTH];
+ /** Indicates whether the MAC address is available. */
+ bool has_mac_address;
+ /**
+ * Product name.
+ *
+ * This field is used for devices with host interface #JAYLINK_HIF_TCP
+ * only.
+ */
+ char product_name[JAYLINK_PRODUCT_NAME_MAX_LENGTH];
+ /** Indicates whether the product name is available. */
+ bool has_product_name;
+ /**
+ * Nickname.
+ *
+ * This field is used for devices with host interface #JAYLINK_HIF_TCP
+ * only.
+ */
+ char nickname[JAYLINK_NICKNAME_MAX_LENGTH];
+ /** Indicates whether the nickname is available. */
+ bool has_nickname;
+ /**
+ * Hardware version.
+ *
+ * This field is used for devices with host interface #JAYLINK_HIF_TCP
+ * only.
+ */
+ struct jaylink_hardware_version hw_version;
+ /** Indicates whether the hardware version is available. */
+ bool has_hw_version;
};
struct jaylink_device_handle {
@@ -153,6 +203,14 @@ JAYLINK_PRIV uint32_t buffer_get_u32(const uint8_t *buffer, size_t offset);
JAYLINK_PRIV struct jaylink_device *device_allocate(
struct jaylink_context *ctx);
+/*--- discovery_tcp.c -------------------------------------------------------*/
+
+JAYLINK_PRIV int discovery_tcp_scan(struct jaylink_context *ctx);
+
+/*--- discovery_usb.c -------------------------------------------------------*/
+
+JAYLINK_PRIV int discovery_usb_scan(struct jaylink_context *ctx);
+
/*--- list.c ----------------------------------------------------------------*/
JAYLINK_PRIV struct list *list_prepend(struct list *list, void *data);
@@ -176,6 +234,19 @@ JAYLINK_PRIV void log_info(const struct jaylink_context *ctx,
JAYLINK_PRIV void log_dbg(const struct jaylink_context *ctx,
const char *format, ...);
+/*--- socket.c --------------------------------------------------------------*/
+
+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_sendto(int sock, const void *buffer, size_t *length,
+ int flags, const struct sockaddr *address,
+ size_t address_length);
+JAYLINK_PRIV bool socket_recvfrom(int sock, void *buffer, size_t *length,
+ int flags, struct sockaddr *address, size_t *address_length);
+JAYLINK_PRIV bool socket_set_option(int sock, int level, int option,
+ const void *value, size_t length);
+
/*--- transport.c -----------------------------------------------------------*/
JAYLINK_PRIV int transport_open(struct jaylink_device_handle *devh);
diff --git a/libjaylink/libjaylink.h b/libjaylink/libjaylink.h
index f3bb959..20ecbb6 100644
--- a/libjaylink/libjaylink.h
+++ b/libjaylink/libjaylink.h
@@ -95,7 +95,9 @@ enum jaylink_capability {
/** Host interfaces. */
enum jaylink_host_interface {
/** Universal Serial Bus (USB). */
- JAYLINK_HIF_USB = (1 << 0)
+ JAYLINK_HIF_USB = (1 << 0),
+ /** Transmission Control Protocol (TCP). */
+ JAYLINK_HIF_TCP = (1 << 1)
};
/**
@@ -325,6 +327,18 @@ struct jaylink_connection {
/** Maximum number of connections that can be registered on a device. */
#define JAYLINK_MAX_CONNECTIONS 16
+/**
+ * Maximum length of a device's nickname including trailing null-terminator in
+ * bytes.
+ */
+#define JAYLINK_NICKNAME_MAX_LENGTH 32
+
+/**
+ * Maximum length of a device's product name including trailing null-terminator
+ * in bytes.
+ */
+#define JAYLINK_PRODUCT_NAME_MAX_LENGTH 32
+
/** Maximum length of a filename in bytes. */
#define JAYLINK_FILE_NAME_MAX_LENGTH 255
diff --git a/libjaylink/socket.c b/libjaylink/socket.c
new file mode 100644
index 0000000..965ba9e
--- /dev/null
+++ b/libjaylink/socket.c
@@ -0,0 +1,192 @@
+/*
+ * This file is part of the libjaylink project.
+ *
+ * Copyright (C) 2016-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/>.
+ */
+
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#endif
+
+#include "libjaylink.h"
+#include "libjaylink-internal.h"
+
+/**
+ * @file
+ *
+ * Socket abstraction layer.
+ */
+
+/**
+ * Close a socket.
+ *
+ * @param[in] sock Socket descriptor.
+ *
+ * @return Whether the socket was successfully closed.
+ */
+JAYLINK_PRIV bool socket_close(int sock)
+{
+ int ret;
+
+#ifdef _WIN32
+ ret = closesocket(sock);
+#else
+ ret = close(sock);
+#endif
+
+ if (!ret)
+ return true;
+
+ return false;
+}
+
+/**
+ * Bind an address to a socket.
+ *
+ * @param[in] sock Socket descriptor.
+ * @param[in] address Address to be bound to the socket.
+ * @param[in] length Length of the structure pointed to by @p address in bytes.
+ *
+ * @return Whether the address was successfully assigned to the socket.
+ */
+JAYLINK_PRIV bool socket_bind(int sock, const struct sockaddr *address,
+ size_t length)
+{
+ int ret;
+
+ ret = bind(sock, address, length);
+
+#ifdef _WIN32
+ if (ret == SOCKET_ERROR)
+ return false;
+#else
+ if (ret < 0)
+ return false;
+#endif
+
+ return true;
+}
+
+/**
+ * Send a message on a socket.
+ *
+ * @param[in] sock Socket descriptor.
+ * @param[in] buffer Buffer of the message to be sent.
+ * @param[in,out] length Length of the message in bytes. 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.
+ *
+ * @return Whether the message was successfully sent.
+ */
+JAYLINK_PRIV bool socket_sendto(int sock, const void *buffer, size_t *length,
+ int flags, const struct sockaddr *address,
+ size_t address_length)
+{
+ ssize_t ret;
+
+ ret = sendto(sock, buffer, *length, flags, address, address_length);
+
+#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.
+ * @param[out] address Structure to store the source address of the message on
+ * success. Its content is undefined on failure.
+ * Can be NULL.
+ * @param[in,out] address_length Length of the structure pointed to by
+ * @p address in bytes. On success, the value
+ * gets updated with the actual length of the
+ * structure. The value is undefined on failure.
+ * Should be NULL if @p address is NULL.
+ *
+ * @return Whether a message was successfully received.
+ */
+JAYLINK_PRIV bool socket_recvfrom(int sock, void *buffer, size_t *length,
+ int flags, struct sockaddr *address, size_t *address_length)
+{
+ ssize_t ret;
+#ifdef _WIN32
+ int tmp;
+
+ tmp = *address_length;
+ ret = recvfrom(sock, buffer, *length, flags, address, &tmp);
+
+ if (ret == SOCKET_ERROR)
+ return false;
+#else
+ socklen_t tmp;
+
+ tmp = *address_length;
+ ret = recvfrom(sock, buffer, *length, flags, address, &tmp);
+
+ if (ret < 0)
+ return false;
+#endif
+
+ *address_length = tmp;
+ *length = ret;
+
+ return true;
+}
+
+/**
+ * Set an option on a socket.
+ *
+ * @param[in] sock Socket descriptor.
+ * @param[in] level Level at which the option is defined.
+ * @param[in] option Option to set the value for.
+ * @param[in] value Buffer of the value to be set.
+ * @param[in] length Length of the value buffer in bytes.
+ *
+ * @return Whether the option was set successfully.
+ */
+JAYLINK_PRIV bool socket_set_option(int sock, int level, int option,
+ const void *value, size_t length)
+{
+ if (!setsockopt(sock, level, option, value, length))
+ return true;
+
+ return false;
+}