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/types.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
33 #include "libjaylink.h"
34 #include "libjaylink-internal.h"
39 * Device discovery (TCP/IP).
43 /** Size of the advertisement message in bytes. */
44 #define ADV_MESSAGE_SIZE 128
46 /** Device discovery port number. */
47 #define DISC_PORT 19020
49 /** Size of the discovery message in bytes. */
50 #define DISC_MESSAGE_SIZE 64
52 /** Discovery timeout in milliseconds. */
53 #define DISC_TIMEOUT 20
56 static bool compare_devices(const void *a
, const void *b
)
58 const struct jaylink_device
*dev
;
59 const struct jaylink_device
*new_dev
;
64 if (dev
->iface
!= JAYLINK_HIF_TCP
)
67 if (memcmp(dev
->ipv4_address
, new_dev
->ipv4_address
,
68 sizeof(dev
->ipv4_address
)) != 0)
71 if (dev
->serial_number
!= new_dev
->serial_number
)
74 if (memcmp(dev
->mac_address
, new_dev
->mac_address
,
75 sizeof(dev
->mac_address
)) != 0)
78 if (strcmp(dev
->product_name
, new_dev
->product_name
) != 0)
81 if (strcmp(dev
->nickname
, new_dev
->nickname
) != 0)
84 if (dev
->hw_version
.type
!= new_dev
->hw_version
.type
)
87 if (dev
->hw_version
.major
!= new_dev
->hw_version
.major
)
90 if (dev
->hw_version
.minor
!= new_dev
->hw_version
.minor
)
93 if (dev
->hw_version
.revision
!= new_dev
->hw_version
.revision
)
99 static struct jaylink_device
*find_device(struct list
*list
,
100 const struct jaylink_device
*dev
)
104 item
= list_find_custom(list
, &compare_devices
, dev
);
112 static bool parse_adv_message(struct jaylink_device
*dev
,
113 const uint8_t *buffer
)
118 if (memcmp(buffer
, "Found", 5) != 0)
122 * Use inet_ntoa() instead of inet_ntop() because the latter requires
123 * at least Windows Vista.
125 memcpy(&in
, buffer
+ 16, 4);
126 memcpy(dev
->ipv4_address
, inet_ntoa(in
), sizeof(dev
->ipv4_address
));
128 memcpy(dev
->mac_address
, buffer
+ 32, sizeof(dev
->mac_address
));
129 dev
->has_mac_address
= true;
131 dev
->serial_number
= buffer_get_u32(buffer
, 48);
132 dev
->valid_serial_number
= true;
134 tmp
= buffer_get_u32(buffer
, 52);
135 dev
->hw_version
.type
= (tmp
/ 1000000) % 100;
136 dev
->hw_version
.major
= (tmp
/ 10000) % 100;
137 dev
->hw_version
.minor
= (tmp
/ 100) % 100;
138 dev
->hw_version
.revision
= tmp
% 100;
139 dev
->has_hw_version
= true;
141 memcpy(dev
->product_name
, buffer
+ 64, sizeof(dev
->product_name
));
142 dev
->product_name
[JAYLINK_PRODUCT_NAME_MAX_LENGTH
- 1] = '\0';
143 dev
->has_product_name
= isprint((unsigned char)dev
->product_name
[0]);
145 memcpy(dev
->nickname
, buffer
+ 96, sizeof(dev
->nickname
));
146 dev
->nickname
[JAYLINK_NICKNAME_MAX_LENGTH
- 1] = '\0';
147 dev
->has_nickname
= isprint((unsigned char)dev
->nickname
[0]);
152 static struct jaylink_device
*probe_device(struct jaylink_context
*ctx
,
153 struct sockaddr_in
*addr
, const uint8_t *buffer
)
155 struct jaylink_device tmp
;
156 struct jaylink_device
*dev
;
159 * Use inet_ntoa() instead of inet_ntop() because the latter requires
160 * at least Windows Vista.
162 log_dbg(ctx
, "Received advertisement message (IPv4 address = %s).",
163 inet_ntoa(addr
->sin_addr
));
165 if (!parse_adv_message(&tmp
, buffer
)) {
166 log_dbg(ctx
, "Received invalid advertisement message.");
170 log_dbg(ctx
, "Found device (IPv4 address = %s).", tmp
.ipv4_address
);
171 log_dbg(ctx
, "Device: MAC address = %02x:%02x:%02x:%02x:%02x:%02x.",
172 tmp
.mac_address
[0], tmp
.mac_address
[1], tmp
.mac_address
[2],
173 tmp
.mac_address
[3], tmp
.mac_address
[4], tmp
.mac_address
[5]);
174 log_dbg(ctx
, "Device: Serial number = %u.", tmp
.serial_number
);
176 if (tmp
.has_product_name
)
177 log_dbg(ctx
, "Device: Product = %s.", tmp
.product_name
);
179 if (tmp
.has_nickname
)
180 log_dbg(ctx
, "Device: Nickname = %s.", tmp
.nickname
);
182 dev
= find_device(ctx
->discovered_devs
, &tmp
);
185 log_dbg(ctx
, "Ignoring already discovered device.");
189 dev
= find_device(ctx
->devs
, &tmp
);
192 log_dbg(ctx
, "Using existing device instance.");
193 return jaylink_ref_device(dev
);
196 log_dbg(ctx
, "Allocating new device instance.");
198 dev
= device_allocate(ctx
);
201 log_warn(ctx
, "Device instance malloc failed.");
205 dev
->iface
= JAYLINK_HIF_TCP
;
207 dev
->serial_number
= tmp
.serial_number
;
208 dev
->valid_serial_number
= tmp
.valid_serial_number
;
210 memcpy(dev
->ipv4_address
, tmp
.ipv4_address
, sizeof(dev
->ipv4_address
));
212 memcpy(dev
->mac_address
, tmp
.mac_address
, sizeof(dev
->mac_address
));
213 dev
->has_mac_address
= tmp
.has_mac_address
;
215 memcpy(dev
->product_name
, tmp
.product_name
, sizeof(dev
->product_name
));
216 dev
->has_product_name
= tmp
.has_product_name
;
218 memcpy(dev
->nickname
, tmp
.nickname
, sizeof(dev
->nickname
));
219 dev
->has_nickname
= tmp
.has_nickname
;
221 dev
->hw_version
= tmp
.hw_version
;
222 dev
->has_hw_version
= tmp
.has_hw_version
;
228 JAYLINK_PRIV
int discovery_tcp_scan(struct jaylink_context
*ctx
)
234 struct sockaddr_in addr
;
236 struct timeval timeout
;
237 uint8_t buf
[ADV_MESSAGE_SIZE
];
238 struct jaylink_device
*dev
;
242 sock
= socket(AF_INET
, SOCK_DGRAM
, IPPROTO_UDP
);
245 log_err(ctx
, "Failed to create discovery socket.");
251 if (!socket_set_option(sock
, SOL_SOCKET
, SO_BROADCAST
, &opt_value
,
252 sizeof(opt_value
))) {
253 log_err(ctx
, "Failed to enable broadcast option for discovery "
259 memset(&addr
, 0, sizeof(struct sockaddr_in
));
260 addr
.sin_family
= AF_INET
;
261 addr
.sin_port
= htons(DISC_PORT
);
262 addr
.sin_addr
.s_addr
= INADDR_ANY
;
264 if (!socket_bind(sock
, (struct sockaddr
*)&addr
,
265 sizeof(struct sockaddr_in
))) {
266 log_err(ctx
, "Failed to bind discovery socket.");
271 addr
.sin_family
= AF_INET
;
272 addr
.sin_port
= htons(DISC_PORT
);
273 addr
.sin_addr
.s_addr
= INADDR_BROADCAST
;
275 memset(buf
, 0, DISC_MESSAGE_SIZE
);
276 memcpy(buf
, "Discover", 8);
278 log_dbg(ctx
, "Sending discovery message.");
280 length
= DISC_MESSAGE_SIZE
;
282 if (!socket_sendto(sock
, (char *)buf
, &length
, 0,
283 (const struct sockaddr
*)&addr
, sizeof(addr
))) {
284 log_err(ctx
, "Failed to send discovery message.");
286 return JAYLINK_ERR_IO
;
289 if (length
< DISC_MESSAGE_SIZE
) {
290 log_err(ctx
, "Only sent %zu bytes of discovery message.",
293 return JAYLINK_ERR_IO
;
296 timeout
.tv_sec
= DISC_TIMEOUT
/ 1000;
297 timeout
.tv_usec
= (DISC_TIMEOUT
% 1000) * 1000;
305 ret
= select(sock
+ 1, &rfds
, NULL
, NULL
, &timeout
);
310 if (!FD_ISSET(sock
, &rfds
))
313 length
= ADV_MESSAGE_SIZE
;
314 addr_length
= sizeof(struct sockaddr_in
);
316 if (!socket_recvfrom(sock
, buf
, &length
, 0,
317 (struct sockaddr
*)&addr
, &addr_length
)) {
318 log_warn(ctx
, "Failed to receive advertisement "
324 * Filter out messages with an invalid size. This includes the
325 * broadcast message we sent before.
327 if (length
!= ADV_MESSAGE_SIZE
)
330 dev
= probe_device(ctx
, &addr
, buf
);
333 ctx
->discovered_devs
= list_prepend(
334 ctx
->discovered_devs
, dev
);
342 log_err(ctx
, "select() failed.");
346 log_dbg(ctx
, "Found %zu TCP/IP device(s).", num_devs
);