Bump package version to 0.4.0
[libjaylink.git] / libjaylink / discovery_tcp.c
blob40791690ca9833fbd11a2da10efa77b8e0133756
1 /*
2 * This file is part of the libjaylink project.
4 * Copyright (C) 2015-2017 Marc Schink <jaylink-dev@marcschink.de>
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include <stdlib.h>
21 #include <stdint.h>
22 #include <string.h>
23 #include <ctype.h>
24 #ifdef _WIN32
25 #include <winsock2.h>
26 #else
27 #include <sys/select.h>
28 #include <sys/types.h>
29 #include <sys/socket.h>
30 #include <netinet/in.h>
31 #include <arpa/inet.h>
32 #endif
34 #include "libjaylink.h"
35 #include "libjaylink-internal.h"
37 /**
38 * @file
40 * Device discovery (TCP/IP).
43 /** @cond PRIVATE */
44 /** Size of the advertisement message in bytes. */
45 #define ADV_MESSAGE_SIZE 128
47 /** Device discovery port number. */
48 #define DISC_PORT 19020
50 /** Size of the discovery message in bytes. */
51 #define DISC_MESSAGE_SIZE 64
53 /** Discovery timeout in milliseconds. */
54 #define DISC_TIMEOUT 20
55 /** @endcond */
57 static bool compare_devices(const void *a, const void *b)
59 const struct jaylink_device *dev;
60 const struct jaylink_device *new_dev;
62 dev = a;
63 new_dev = b;
65 if (dev->iface != JAYLINK_HIF_TCP)
66 return false;
68 if (memcmp(dev->ipv4_address, new_dev->ipv4_address,
69 sizeof(dev->ipv4_address)) != 0)
70 return false;
72 if (dev->serial_number != new_dev->serial_number)
73 return false;
75 if (memcmp(dev->mac_address, new_dev->mac_address,
76 sizeof(dev->mac_address)) != 0)
77 return false;
79 if (strcmp(dev->product_name, new_dev->product_name) != 0)
80 return false;
82 if (strcmp(dev->nickname, new_dev->nickname) != 0)
83 return false;
85 if (dev->hw_version.type != new_dev->hw_version.type)
86 return false;
88 if (dev->hw_version.major != new_dev->hw_version.major)
89 return false;
91 if (dev->hw_version.minor != new_dev->hw_version.minor)
92 return false;
94 if (dev->hw_version.revision != new_dev->hw_version.revision)
95 return false;
97 return true;
100 static struct jaylink_device *find_device(struct list *list,
101 const struct jaylink_device *dev)
103 struct list *item;
105 item = list_find_custom(list, &compare_devices, dev);
107 if (item)
108 return item->data;
110 return NULL;
113 static bool parse_adv_message(struct jaylink_device *dev,
114 const uint8_t *buffer)
116 struct in_addr in;
117 uint32_t tmp;
119 if (memcmp(buffer, "Found", 5) != 0)
120 return false;
123 * Use inet_ntoa() instead of inet_ntop() because the latter requires
124 * at least Windows Vista.
126 memcpy(&in, buffer + 16, 4);
127 memcpy(dev->ipv4_address, inet_ntoa(in), sizeof(dev->ipv4_address));
129 memcpy(dev->mac_address, buffer + 32, sizeof(dev->mac_address));
130 dev->has_mac_address = true;
132 dev->serial_number = buffer_get_u32(buffer, 48);
133 dev->has_serial_number = true;
135 tmp = buffer_get_u32(buffer, 52);
136 dev->hw_version.type = (tmp / 1000000) % 100;
137 dev->hw_version.major = (tmp / 10000) % 100;
138 dev->hw_version.minor = (tmp / 100) % 100;
139 dev->hw_version.revision = tmp % 100;
140 dev->has_hw_version = true;
142 memcpy(dev->product_name, buffer + 64, sizeof(dev->product_name));
143 dev->product_name[JAYLINK_PRODUCT_NAME_MAX_LENGTH - 1] = '\0';
144 dev->has_product_name = isprint((unsigned char)dev->product_name[0]);
146 memcpy(dev->nickname, buffer + 96, sizeof(dev->nickname));
147 dev->nickname[JAYLINK_NICKNAME_MAX_LENGTH - 1] = '\0';
148 dev->has_nickname = isprint((unsigned char)dev->nickname[0]);
150 return true;
153 static struct jaylink_device *probe_device(struct jaylink_context *ctx,
154 struct sockaddr_in *addr, const uint8_t *buffer)
156 struct jaylink_device tmp;
157 struct jaylink_device *dev;
160 * Use inet_ntoa() instead of inet_ntop() because the latter requires
161 * at least Windows Vista.
163 log_dbg(ctx, "Received advertisement message (IPv4 address = %s)",
164 inet_ntoa(addr->sin_addr));
166 if (!parse_adv_message(&tmp, buffer)) {
167 log_dbg(ctx, "Received invalid advertisement message");
168 return NULL;
171 log_dbg(ctx, "Found device (IPv4 address = %s)", tmp.ipv4_address);
172 log_dbg(ctx, "Device: MAC address = %02x:%02x:%02x:%02x:%02x:%02x",
173 tmp.mac_address[0], tmp.mac_address[1], tmp.mac_address[2],
174 tmp.mac_address[3], tmp.mac_address[4], tmp.mac_address[5]);
175 log_dbg(ctx, "Device: Serial number = %u", tmp.serial_number);
177 if (tmp.has_product_name)
178 log_dbg(ctx, "Device: Product = %s", tmp.product_name);
180 if (tmp.has_nickname)
181 log_dbg(ctx, "Device: Nickname = %s", tmp.nickname);
183 dev = find_device(ctx->discovered_devs, &tmp);
185 if (dev) {
186 log_dbg(ctx, "Ignoring already discovered device");
187 return NULL;
190 dev = find_device(ctx->devs, &tmp);
192 if (dev) {
193 log_dbg(ctx, "Using existing device instance");
194 return jaylink_ref_device(dev);
197 log_dbg(ctx, "Allocating new device instance");
199 dev = device_allocate(ctx);
201 if (!dev) {
202 log_warn(ctx, "Device instance malloc failed");
203 return NULL;
206 dev->iface = JAYLINK_HIF_TCP;
208 dev->serial_number = tmp.serial_number;
209 dev->has_serial_number = tmp.has_serial_number;
211 memcpy(dev->ipv4_address, tmp.ipv4_address, sizeof(dev->ipv4_address));
213 memcpy(dev->mac_address, tmp.mac_address, sizeof(dev->mac_address));
214 dev->has_mac_address = tmp.has_mac_address;
216 memcpy(dev->product_name, tmp.product_name, sizeof(dev->product_name));
217 dev->has_product_name = tmp.has_product_name;
219 memcpy(dev->nickname, tmp.nickname, sizeof(dev->nickname));
220 dev->has_nickname = tmp.has_nickname;
222 dev->hw_version = tmp.hw_version;
223 dev->has_hw_version = tmp.has_hw_version;
225 return dev;
228 /** @private */
229 JAYLINK_PRIV int discovery_tcp_scan(struct jaylink_context *ctx)
231 int ret;
232 int sock;
233 int opt_value;
234 fd_set rfds;
235 struct sockaddr_in addr;
236 size_t addr_length;
237 struct timeval timeout;
238 uint8_t buf[ADV_MESSAGE_SIZE];
239 struct jaylink_device *dev;
240 size_t length;
241 size_t num_devs;
243 sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
245 if (sock < 0) {
246 log_err(ctx, "Failed to create discovery socket");
247 return JAYLINK_ERR;
250 opt_value = true;
252 if (!socket_set_option(sock, SOL_SOCKET, SO_BROADCAST, &opt_value,
253 sizeof(opt_value))) {
254 log_err(ctx, "Failed to enable broadcast option for discovery "
255 "socket");
256 socket_close(sock);
257 return JAYLINK_ERR;
260 memset(&addr, 0, sizeof(struct sockaddr_in));
261 addr.sin_family = AF_INET;
262 addr.sin_port = htons(DISC_PORT);
263 addr.sin_addr.s_addr = INADDR_ANY;
265 if (!socket_bind(sock, (struct sockaddr *)&addr,
266 sizeof(struct sockaddr_in))) {
267 log_err(ctx, "Failed to bind discovery socket");
268 socket_close(sock);
269 return JAYLINK_ERR;
272 addr.sin_family = AF_INET;
273 addr.sin_port = htons(DISC_PORT);
274 addr.sin_addr.s_addr = INADDR_BROADCAST;
276 memset(buf, 0, DISC_MESSAGE_SIZE);
277 memcpy(buf, "Discover", 8);
279 log_dbg(ctx, "Sending discovery message");
281 length = DISC_MESSAGE_SIZE;
283 if (!socket_sendto(sock, (char *)buf, &length, 0,
284 (const struct sockaddr *)&addr, sizeof(addr))) {
285 log_err(ctx, "Failed to send discovery message");
286 socket_close(sock);
287 return JAYLINK_ERR_IO;
290 if (length < DISC_MESSAGE_SIZE) {
291 log_err(ctx, "Only sent %zu bytes of discovery message",
292 length);
293 socket_close(sock);
294 return JAYLINK_ERR_IO;
297 timeout.tv_sec = DISC_TIMEOUT / 1000;
298 timeout.tv_usec = (DISC_TIMEOUT % 1000) * 1000;
300 num_devs = 0;
302 while (true) {
303 FD_ZERO(&rfds);
304 FD_SET(sock, &rfds);
306 ret = select(sock + 1, &rfds, NULL, NULL, &timeout);
308 if (ret <= 0)
309 break;
311 if (!FD_ISSET(sock, &rfds))
312 continue;
314 length = ADV_MESSAGE_SIZE;
315 addr_length = sizeof(struct sockaddr_in);
317 if (!socket_recvfrom(sock, buf, &length, 0,
318 (struct sockaddr *)&addr, &addr_length)) {
319 log_warn(ctx, "Failed to receive advertisement "
320 "message");
321 continue;
325 * Filter out messages with an invalid size. This includes the
326 * broadcast message we sent before.
328 if (length != ADV_MESSAGE_SIZE)
329 continue;
331 dev = probe_device(ctx, &addr, buf);
333 if (dev) {
334 ctx->discovered_devs = list_prepend(
335 ctx->discovered_devs, dev);
336 num_devs++;
340 socket_close(sock);
342 if (ret < 0) {
343 log_err(ctx, "select() failed");
344 return JAYLINK_ERR;
347 log_dbg(ctx, "Found %zu TCP/IP device(s)", num_devs);
349 return JAYLINK_OK;