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(const struct jaylink_context
*ctx
,
100 const struct jaylink_device
*dev
)
104 item
= list_find_custom(ctx
->devs
, &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
, &tmp
);
185 log_dbg(ctx
, "Using existing device instance.");
186 return jaylink_ref_device(dev
);
189 log_dbg(ctx
, "Allocating new device instance.");
191 dev
= device_allocate(ctx
);
194 log_warn(ctx
, "Device instance malloc failed.");
198 dev
->iface
= JAYLINK_HIF_TCP
;
200 dev
->serial_number
= tmp
.serial_number
;
201 dev
->valid_serial_number
= tmp
.valid_serial_number
;
203 memcpy(dev
->ipv4_address
, tmp
.ipv4_address
, sizeof(dev
->ipv4_address
));
205 memcpy(dev
->mac_address
, tmp
.mac_address
, sizeof(dev
->mac_address
));
206 dev
->has_mac_address
= tmp
.has_mac_address
;
208 memcpy(dev
->product_name
, tmp
.product_name
, sizeof(dev
->product_name
));
209 dev
->has_product_name
= tmp
.has_product_name
;
211 memcpy(dev
->nickname
, tmp
.nickname
, sizeof(dev
->nickname
));
212 dev
->has_nickname
= tmp
.has_nickname
;
214 dev
->hw_version
= tmp
.hw_version
;
215 dev
->has_hw_version
= tmp
.has_hw_version
;
221 JAYLINK_PRIV
int discovery_tcp_scan(struct jaylink_context
*ctx
)
227 struct sockaddr_in addr
;
229 struct timeval timeout
;
230 uint8_t buf
[ADV_MESSAGE_SIZE
];
231 struct jaylink_device
*dev
;
235 sock
= socket(AF_INET
, SOCK_DGRAM
, IPPROTO_UDP
);
238 log_err(ctx
, "Failed to create discovery socket.");
244 if (!socket_set_option(sock
, SOL_SOCKET
, SO_BROADCAST
, &opt_value
,
245 sizeof(opt_value
))) {
246 log_err(ctx
, "Failed to enable broadcast option for discovery "
252 memset(&addr
, 0, sizeof(struct sockaddr_in
));
253 addr
.sin_family
= AF_INET
;
254 addr
.sin_port
= htons(DISC_PORT
);
255 addr
.sin_addr
.s_addr
= INADDR_ANY
;
257 if (!socket_bind(sock
, (struct sockaddr
*)&addr
,
258 sizeof(struct sockaddr_in
))) {
259 log_err(ctx
, "Failed to bind discovery socket.");
264 addr
.sin_family
= AF_INET
;
265 addr
.sin_port
= htons(DISC_PORT
);
266 addr
.sin_addr
.s_addr
= INADDR_BROADCAST
;
268 memset(buf
, 0, DISC_MESSAGE_SIZE
);
269 memcpy(buf
, "Discover", 8);
271 log_dbg(ctx
, "Sending discovery message.");
273 length
= DISC_MESSAGE_SIZE
;
275 if (!socket_sendto(sock
, (char *)buf
, &length
, 0,
276 (const struct sockaddr
*)&addr
, sizeof(addr
))) {
277 log_err(ctx
, "Failed to send discovery message.");
279 return JAYLINK_ERR_IO
;
282 if (length
< DISC_MESSAGE_SIZE
) {
283 log_err(ctx
, "Only sent %zu bytes of discovery message.",
286 return JAYLINK_ERR_IO
;
289 timeout
.tv_sec
= DISC_TIMEOUT
/ 1000;
290 timeout
.tv_usec
= (DISC_TIMEOUT
% 1000) * 1000;
298 ret
= select(sock
+ 1, &rfds
, NULL
, NULL
, &timeout
);
303 if (!FD_ISSET(sock
, &rfds
))
306 length
= ADV_MESSAGE_SIZE
;
307 addr_length
= sizeof(struct sockaddr_in
);
309 if (!socket_recvfrom(sock
, buf
, &length
, 0,
310 (struct sockaddr
*)&addr
, &addr_length
)) {
311 log_warn(ctx
, "Failed to receive advertisement "
317 * Filter out messages with an invalid size. This includes the
318 * broadcast message we sent before.
320 if (length
!= ADV_MESSAGE_SIZE
)
323 dev
= probe_device(ctx
, &addr
, buf
);
326 ctx
->discovered_devs
= list_prepend(
327 ctx
->discovered_devs
, dev
);
335 log_err(ctx
, "select() failed.");
339 log_dbg(ctx
, "Found %zu TCP/IP device(s).", num_devs
);