]> sigrok.org Git - libsigrok.git/blame - src/tcp.c
tcp: introduce common support code for TCP socket communication
[libsigrok.git] / src / tcp.c
CommitLineData
fcab496c
GS
1/*
2 * This file is part of the libsigrok project.
3 *
4 * Copyright (C) 2023 Gerhard Sittig <gerhard.sittig@gmx.net>
5 *
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 3 of the License, or
9 * (at your option) any later version.
10 *
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.
15 *
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/>.
18 */
19
20#include <config.h>
21
22/* TODO
23 * Can we sort these include directives? Or do the platform specific
24 * headers depend on a specific order? Experience from VXI maintenance
25 * suggests that some systems can be picky and it's hard to notice ...
26 * For now the include statements follow the scpi_tcp.c template.
27 */
28#if defined _WIN32
29#define _WIN32_WINNT 0x0501
30#include <winsock2.h>
31#include <ws2tcpip.h>
32#endif
33
34#include <errno.h>
35#include <glib.h>
36#include <poll.h>
37#include <string.h>
38#include <unistd.h>
39
40#if !defined _WIN32
41#include <arpa/inet.h>
42#include <netdb.h>
43#include <netinet/in.h>
44#include <sys/socket.h>
45#include <sys/types.h>
46#endif
47
48#include <libsigrok/libsigrok.h>
49#include "libsigrok-internal.h"
50
51#define LOG_PREFIX "tcp"
52
53/**
54 * Check whether a file descriptor is readable (without blocking).
55 *
56 * @param[in] fd The file descriptor to check for readability.
57 *
58 * @return TRUE when readable, FALSE when read would block or when
59 * readability could not get determined.
60 *
61 * @since 6.0
62 *
63 * TODO Move to common code, applies to non-sockets as well.
64 */
65SR_PRIV gboolean sr_fd_is_readable(int fd)
66{
67 struct pollfd fds[1];
68 int ret;
69
70 memset(fds, 0, sizeof(fds));
71 fds[0].fd = fd;
72 fds[0].events = POLLIN;
73 ret = poll(fds, ARRAY_SIZE(fds), -1);
74 if (ret < 0)
75 return FALSE;
76 if (!ret)
77 return FALSE;
78 if (!(fds[0].revents & POLLIN))
79 return FALSE;
80
81 return TRUE;
82}
83
84/**
85 * Create a TCP communication instance.
86 *
87 * @param[in] host_addr The host name or IP address (a string).
88 * @param[in] tcp_port The TCP port number.
89 *
90 * @return A @ref sr_tcp_dev_inst structure on success. #NULL otherwise.
91 *
92 * @since 6.0
93 */
94SR_PRIV struct sr_tcp_dev_inst *sr_tcp_dev_inst_new(
95 const char *host_addr, const char *tcp_port)
96{
97 char *host, *port;
98 struct sr_tcp_dev_inst *tcp;
99
100 host = NULL;
101 if (host_addr && *host_addr)
102 host = g_strdup(host_addr);
103 port = NULL;
104 if (tcp_port && *tcp_port)
105 port = g_strdup(tcp_port);
106
107 tcp = g_malloc0(sizeof(*tcp));
108 if (!tcp)
109 return NULL;
110 tcp->host_addr = host;
111 tcp->tcp_port = port;
112 tcp->sock_fd = -1;
113 return tcp;
114}
115
116/**
117 * Release a TCP communication instance.
118 *
119 * @param[in] host_addr The host name or IP address (a string).
120 * @param[in] tcp_port The TCP port number.
121 *
122 * @return A @ref sr_tcp_dev_inst structure on success. #NULL otherwise.
123 *
124 * @since 6.0
125 */
126SR_PRIV void sr_tcp_dev_inst_free(struct sr_tcp_dev_inst *tcp)
127{
128
129 if (!tcp)
130 return;
131
132 (void)sr_tcp_disconnect(tcp);
133 g_free(tcp->host_addr);
134 g_free(tcp);
135}
136
137/**
138 * Construct display name for a TCP communication instance.
139 *
140 * @param[in] tcp The TCP communication instance to print the name of.
141 * @param[in] prefix An optional prefix text, or #NULL.
142 * @param[in] separator An optional separator character, or NUL.
143 * @param[out] path The caller provided buffer to fill in.
144 * @param[in] path_len The buffer's maximum length to fill in.
145 *
146 * @return SR_OK on success, SR_ERR_* otherwise.
147 *
148 * @since 6.0
149 */
150SR_PRIV int sr_tcp_get_port_path(struct sr_tcp_dev_inst *tcp,
151 const char *prefix, char separator, char *path, size_t path_len)
152{
153 char sep_text[2];
154
155 /* Only construct connection name for full parameter sets. */
156 if (!tcp || !tcp->host_addr || !tcp->tcp_port)
157 return SR_ERR_ARG;
158
159 /* Normalize input. Apply defaults. */
160 if (!prefix)
161 prefix = "";
162 if (!*prefix && !separator)
163 separator = ':';
164
165 /* Turn everything into strings. Simplifies the printf() call. */
166 sep_text[0] = separator;
167 sep_text[1] = '\0';
168
169 /* Construct the resulting connection name. */
170 snprintf(path, path_len, "%s%s%s%s%s",
171 prefix, *prefix ? sep_text : "",
172 tcp->host_addr, sep_text, tcp->tcp_port);
173 return SR_OK;
174}
175
176/**
177 * Connect to a remote TCP communication peer.
178 *
179 * @param[in] tcp The TCP communication instance to connect.
180 *
181 * @return SR_OK on success, SR_ERR_* otherwise.
182 *
183 * @since 6.0
184 */
185SR_PRIV int sr_tcp_connect(struct sr_tcp_dev_inst *tcp)
186{
187 struct addrinfo hints;
188 struct addrinfo *results, *r;
189 int ret;
190 int fd;
191
192 if (!tcp)
193 return SR_ERR_ARG;
194 if (!tcp->host_addr || !tcp->tcp_port)
195 return SR_ERR_ARG;
196
197 /* Lookup address information for the caller's spec. */
198 memset(&hints, 0, sizeof(hints));
199 hints.ai_family = AF_UNSPEC;
200 hints.ai_socktype = SOCK_STREAM;
201 hints.ai_protocol = IPPROTO_TCP;
202 ret = getaddrinfo(tcp->host_addr, tcp->tcp_port, &hints, &results);
203 if (ret != 0) {
204 sr_err("Address lookup failed: %s:%s: %s.",
205 tcp->host_addr, tcp->tcp_port, gai_strerror(ret));
206 return SR_ERR_DATA;
207 }
208
209 /* Try to connect using the resulting address details. */
210 fd = -1;
211 for (r = results; r; r = r->ai_next) {
212 fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
213 if (fd < 0)
214 continue;
215 ret = connect(fd, r->ai_addr, r->ai_addrlen);
216 if (ret != 0) {
217 close(fd);
218 fd = -1;
219 continue;
220 }
221 break;
222 }
223 freeaddrinfo(results);
224 if (fd < 0) {
225 sr_err("Failed to connect to %s:%s: %s.",
226 tcp->host_addr, tcp->tcp_port, g_strerror(errno));
227 return SR_ERR_IO;
228 }
229
230 tcp->sock_fd = fd;
231 return SR_OK;
232}
233
234/**
235 * Disconnect from a remote TCP communication peer.
236 *
237 * @param[in] tcp The TCP communication instance to disconnect.
238 *
239 * @return SR_OK on success, SR_ERR_* otherwise.
240 *
241 * @since 6.0
242 */
243SR_PRIV int sr_tcp_disconnect(struct sr_tcp_dev_inst *tcp)
244{
245
246 if (!tcp)
247 return SR_ERR_ARG;
248
249 if (tcp->sock_fd < 0)
250 return SR_OK;
251
252 shutdown(tcp->sock_fd, SHUT_RDWR);
253 close(tcp->sock_fd);
254 tcp->sock_fd = -1;
255 return SR_OK;
256}
257
258/**
259 * Send transmit data to a TCP connection.
260 * Does a single operating system call, can return with short
261 * transmit byte counts. Will not continue after short writes,
262 * callers need to handle the condition.
263 *
264 * @param[in] tcp The TCP communication instance to send to.
265 * @param[in] data The data bytes to send.
266 * @param[in] dlen The number of bytes to send.
267 *
268 * @return Number of transmitted bytes on success, SR_ERR_* otherwise.
269 *
270 * @since 6.0
271 */
272SR_PRIV int sr_tcp_write_bytes(struct sr_tcp_dev_inst *tcp,
273 const uint8_t *data, size_t dlen)
274{
275 ssize_t rc;
276 size_t written;
277
278 if (!tcp)
279 return SR_ERR_ARG;
280 if (!dlen)
281 return 0;
282 if (!data)
283 return SR_ERR_ARG;
284
285 if (tcp->sock_fd < 0)
286 return SR_ERR_IO;
287
288 rc = send(tcp->sock_fd, data, dlen, 0);
289 if (rc < 0)
290 return SR_ERR_IO;
291 written = (size_t)rc;
292 return written;
293}
294
295/**
296 * Fetch receive data from a TCP connection.
297 * Does a single operating system call, can return with short
298 * receive byte counts. Will not continue after short reads,
299 * callers need to handle the condition.
300 *
301 * @param[in] tcp The TCP communication instance to read from.
302 * @param[in] data Caller provided buffer for receive data.
303 * @param[in] dlen The maximum number of bytes to receive.
304 * @param[in] nonblocking Whether to block for receive data.
305 *
306 * @return Number of received bytes on success, SR_ERR_* otherwise.
307 *
308 * @since 6.0
309 */
310SR_PRIV int sr_tcp_read_bytes(struct sr_tcp_dev_inst *tcp,
311 uint8_t *data, size_t dlen, gboolean nonblocking)
312{
313 ssize_t rc;
314 size_t got;
315
316 if (!tcp)
317 return SR_ERR_ARG;
318 if (!dlen)
319 return 0;
320 if (!data)
321 return SR_ERR_ARG;
322
323 if (tcp->sock_fd < 0)
324 return SR_ERR_IO;
325
326 if (nonblocking && !sr_fd_is_readable(tcp->sock_fd))
327 return 0;
328
329 rc = recv(tcp->sock_fd, data, dlen, 0);
330 if (rc < 0)
331 return SR_ERR_IO;
332 got = (size_t)rc;
333 return got;
334}
335
336/**
337 * Register receive callback for a TCP connection.
338 * The connection must have been established before. The callback
339 * gets invoked when receive data is available. Or when a timeout
340 * has expired.
341 *
342 * This is a simple wrapper around @ref sr_session_source_add().
343 *
344 * @param[in] session See @ref sr_session_source_add().
345 * @param[in] tcp The TCP communication instance to read from.
346 * @param[in] events See @ref sr_session_source_add().
347 * @param[in] timeout See @ref sr_session_source_add().
348 * @param[in] cb See @ref sr_session_source_add().
349 * @param[in] cb_data See @ref sr_session_source_add().
350 *
351 * @return SR_OK on success, SR_ERR* otherwise.
352 *
353 * @since 6.0
354 */
355SR_PRIV int sr_tcp_source_add(struct sr_session *session,
356 struct sr_tcp_dev_inst *tcp, int events, int timeout,
357 sr_receive_data_callback cb, void *cb_data)
358{
359 if (!tcp || tcp->sock_fd < 0)
360 return SR_ERR_ARG;
361 return sr_session_source_add(session, tcp->sock_fd,
362 events, timeout, cb, cb_data);
363}
364
365/**
366 * Unregister receive callback for a TCP connection.
367 *
368 * This is a simple wrapper around @ref sr_session_source_remove().
369 *
370 * @param[in] session See @ref sr_session_source_remove().
371 * @param[in] tcp The TCP communication instance to unregister.
372 *
373 * @return SR_OK on success, SR_ERR* otherwise.
374 *
375 * @since 6.0
376 */
377SR_PRIV int sr_tcp_source_remove(struct sr_session *session,
378 struct sr_tcp_dev_inst *tcp)
379{
380 if (!tcp || tcp->sock_fd < 0)
381 return SR_ERR_ARG;
382 return sr_session_source_remove(session, tcp->sock_fd);
383}