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/>.
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>
34 #include "libjaylink.h"
35 #include "libjaylink-internal.h"
40 * Device discovery (TCP/IP).
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
57 static bool compare_devices(const void *a
, const void *b
)
59 const struct jaylink_device
*dev
;
60 const struct jaylink_device
*new_dev
;
65 if (dev
->iface
!= JAYLINK_HIF_TCP
)
68 if (memcmp(dev
->ipv4_address
, new_dev
->ipv4_address
,
69 sizeof(dev
->ipv4_address
)) != 0)
72 if (dev
->serial_number
!= new_dev
->serial_number
)
75 if (memcmp(dev
->mac_address
, new_dev
->mac_address
,
76 sizeof(dev
->mac_address
)) != 0)
79 if (strcmp(dev
->product_name
, new_dev
->product_name
) != 0)
82 if (strcmp(dev
->nickname
, new_dev
->nickname
) != 0)
85 if (dev
->hw_version
.type
!= new_dev
->hw_version
.type
)
88 if (dev
->hw_version
.major
!= new_dev
->hw_version
.major
)
91 if (dev
->hw_version
.minor
!= new_dev
->hw_version
.minor
)
94 if (dev
->hw_version
.revision
!= new_dev
->hw_version
.revision
)
100 static struct jaylink_device
*find_device(struct list
*list
,
101 const struct jaylink_device
*dev
)
105 item
= list_find_custom(list
, &compare_devices
, dev
);
113 static bool parse_adv_message(struct jaylink_device
*dev
,
114 const uint8_t *buffer
)
119 if (memcmp(buffer
, "Found", 5) != 0)
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]);
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");
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
);
186 log_dbg(ctx
, "Ignoring already discovered device");
190 dev
= find_device(ctx
->devs
, &tmp
);
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
);
202 log_warn(ctx
, "Device instance malloc failed");
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
;
229 JAYLINK_PRIV
int discovery_tcp_scan(struct jaylink_context
*ctx
)
235 struct sockaddr_in addr
;
237 struct timeval timeout
;
238 uint8_t buf
[ADV_MESSAGE_SIZE
];
239 struct jaylink_device
*dev
;
243 sock
= socket(AF_INET
, SOCK_DGRAM
, IPPROTO_UDP
);
246 log_err(ctx
, "Failed to create discovery socket");
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 "
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");
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");
287 return JAYLINK_ERR_IO
;
290 if (length
< DISC_MESSAGE_SIZE
) {
291 log_err(ctx
, "Only sent %zu bytes of discovery message",
294 return JAYLINK_ERR_IO
;
297 timeout
.tv_sec
= DISC_TIMEOUT
/ 1000;
298 timeout
.tv_usec
= (DISC_TIMEOUT
% 1000) * 1000;
306 ret
= select(sock
+ 1, &rfds
, NULL
, NULL
, &timeout
);
311 if (!FD_ISSET(sock
, &rfds
))
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 "
325 * Filter out messages with an invalid size. This includes the
326 * broadcast message we sent before.
328 if (length
!= ADV_MESSAGE_SIZE
)
331 dev
= probe_device(ctx
, &addr
, buf
);
334 ctx
->discovered_devs
= list_prepend(
335 ctx
->discovered_devs
, dev
);
343 log_err(ctx
, "select() failed");
347 log_dbg(ctx
, "Found %zu TCP/IP device(s)", num_devs
);