]> sigrok.org Git - libsigrok.git/blame - src/serial_tcpraw.c
output/csv: use intermediate time_t var, silence compiler warning
[libsigrok.git] / src / serial_tcpraw.c
CommitLineData
c4f0fdab
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#include <libsigrok/libsigrok.h>
23#include <string.h>
24
25#include "libsigrok-internal.h"
26
27#define LOG_PREFIX "serial-tcpraw"
28
29#define SER_TCPRAW_CONN_PREFIX "tcp-raw"
30
31/**
32 * @file
33 *
34 * Serial port handling, raw TCP support.
35 */
36
37/**
38 * @defgroup grp_serial_tcpraw Serial port handling, raw TCP group
39 *
40 * Disguise raw byte sequences over TCP sockets as a serial transport.
41 *
42 * @{
43 */
44
45/* {{{ TCP specific helper routines */
46
47/**
48 * Parse conn= specs for serial over TCP communication.
49 *
50 * @param[in] serial The serial port that is about to get opened.
51 * @param[in] spec The caller provided conn= specification.
52 * @param[out] host_ref Pointer to host name or IP addr (text string).
53 * @param[out] port_ref Pointer to a TCP port (text string).
54 *
55 * @return 0 upon success, non-zero upon failure. Fills the *_ref output
56 * values.
57 *
58 * Summary of parsing rules as they are implemented:
59 * - The 'spec' MUST start with "tcp-raw" followed by a separator. The
60 * prefix alone is not sufficient, host address and port number are
61 * mandatory.
62 * - Host name follows. It's a DNS name or an IP address.
63 * - TCP port follows. Can be a number or a "service" name.
64 * - More than three fields are accepted, but currently don't take any
65 * effect. It's yet to be seen whether "options" or "variants" are
66 * needed or desired. For now any trailing fields are ignored. Cisco
67 * style serial-over-TCP as seen in ser2net(1) comes to mind (which
68 * includes configuration and control beyond data transmission). But
69 * its spec is rather involved, and ser2net can already derive COM
70 * port configurations from TCP port numbers, so it's not a blocker.
71 * That variant probably should go under a different name anyway.
72 *
73 * Supported format resulting from these rules:
74 * tcp-raw/<ipaddr>/<port>
75 */
76static int ser_tcpraw_parse_conn_spec(
77 struct sr_serial_dev_inst *serial, const char *spec,
78 char **host_ref, char **port_ref)
79{
80 char **fields;
81 size_t count;
82 gboolean valid;
83 char *host, *port;
84
85 if (host_ref)
86 *host_ref = NULL;
87 if (port_ref)
88 *port_ref = NULL;
89
90 host = NULL;
91 port = NULL;
92 if (!serial || !spec || !*spec)
93 return SR_ERR_ARG;
94
95 fields = g_strsplit(spec, "/", 0);
96 if (!fields)
97 return SR_ERR_ARG;
98 count = g_strv_length(fields);
99
100 valid = TRUE;
101 if (count < 3)
102 valid = FALSE;
103 if (valid && strcmp(fields[0], SER_TCPRAW_CONN_PREFIX) != 0)
104 valid = FALSE;
105 if (valid) {
106 host = fields[1];
107 if (!host || !*host)
108 valid = FALSE;
109 }
110 if (valid) {
111 port = fields[2];
112 if (!port || !*port)
113 valid = FALSE;
114 }
115 /* Silently ignore trailing fields. Could be future options. */
116 if (count > 3)
117 sr_warn("Ignoring excess parameters in %s.", spec);
118
119 if (valid) {
120 if (host_ref && host)
121 *host_ref = g_strdup(host);
122 if (port_ref && port)
123 *port_ref = g_strdup(port);
124 }
125 g_strfreev(fields);
126 return valid ? SR_OK : SR_ERR_ARG;
127}
128
129/* }}} */
130/* {{{ transport methods called by the common serial.c code */
131
132/* See if a serial port's name refers to a raw TCP connection. */
133SR_PRIV int ser_name_is_tcpraw(struct sr_serial_dev_inst *serial)
134{
135 char *p;
136
137 if (!serial)
138 return 0;
139 if (!serial->port || !*serial->port)
140 return 0;
141
142 p = serial->port;
143 if (!g_str_has_prefix(p, SER_TCPRAW_CONN_PREFIX))
144 return 0;
145 p += strlen(SER_TCPRAW_CONN_PREFIX);
146 if (*p != '/')
147 return 0;
148
149 return 1;
150}
151
152static int ser_tcpraw_open(struct sr_serial_dev_inst *serial, int flags)
153{
154 char *host, *port;
155 int ret;
156
157 (void)flags;
158
159 ret = ser_tcpraw_parse_conn_spec(serial, serial->port,
160 &host, &port);
161 if (ret != SR_OK) {
162 g_free(host);
163 g_free(port);
164 return SR_ERR_ARG;
165 }
166 serial->tcp_dev = sr_tcp_dev_inst_new(host, port);
167 g_free(host);
168 g_free(port);
169 if (!serial->tcp_dev)
170 return SR_ERR_MALLOC;
171
172 /*
173 * Open the TCP socket. Only keep caller's parameters (and the
174 * resulting socket fd) when open completes successfully.
175 */
176 ret = sr_tcp_connect(serial->tcp_dev);
177 if (ret != SR_OK) {
178 sr_err("Failed to establish TCP connection.");
179 sr_tcp_dev_inst_free(serial->tcp_dev);
180 serial->tcp_dev = NULL;
181 return SR_ERR_IO;
182 }
183
184 return SR_OK;
185}
186
187static int ser_tcpraw_close(struct sr_serial_dev_inst *serial)
188{
189
190 if (!serial)
191 return SR_ERR_ARG;
192
193 if (!serial->tcp_dev)
194 return SR_OK;
195
196 (void)sr_tcp_disconnect(serial->tcp_dev);
197 return SR_OK;
198}
199
200static int ser_tcpraw_setup_source_add(struct sr_session *session,
201 struct sr_serial_dev_inst *serial, int events, int timeout,
202 sr_receive_data_callback cb, void *cb_data)
203{
204 if (!serial || !serial->tcp_dev)
205 return SR_ERR_ARG;
206 return sr_tcp_source_add(session, serial->tcp_dev,
207 events, timeout, cb, cb_data);
208}
209
210static int ser_tcpraw_setup_source_remove(struct sr_session *session,
211 struct sr_serial_dev_inst *serial)
212{
213 if (!serial || !serial->tcp_dev)
214 return SR_ERR_ARG;
215 (void)sr_tcp_source_remove(session, serial->tcp_dev);
216 return SR_OK;
217}
218
219static int ser_tcpraw_write(struct sr_serial_dev_inst *serial,
220 const void *buf, size_t count,
221 int nonblocking, unsigned int timeout_ms)
222{
223 size_t total, written;
224 ssize_t ret;
225
226 /* Non-blocking writes, and write timeouts, are not supported. */
227 (void)nonblocking;
228 (void)timeout_ms;
229
230 if (!serial || !serial->tcp_dev)
231 return SR_ERR_ARG;
232
233 total = 0;
234 while (count) {
235 ret = sr_tcp_write_bytes(serial->tcp_dev, buf, count);
236 if (ret < 0 && !total) {
237 sr_err("Error sending TCP transmit data.");
238 return total;
239 }
240 if (ret <= 0) {
241 count += total;
242 sr_warn("Short transmission of TCP data (%zu/%zu).",
243 total, count);
244 return total;
245 }
246 written = (size_t)ret;
247 buf += written;
248 count -= written;
249 total += written;
250 }
251
252 return total;
253}
254
255static int ser_tcpraw_read(struct sr_serial_dev_inst *serial,
256 void *buf, size_t count,
257 int nonblocking, unsigned int timeout_ms)
258{
259 guint64 deadline_us, now_us;
260 size_t total, chunk;
261 ssize_t ret;
262
263 if (!serial || !serial->tcp_dev)
264 return SR_ERR_ARG;
265 if (!count)
266 return 0;
267
268 /*
269 * Timeouts are only useful in blocking mode, non-blocking read
270 * will return as soon as an iteration sees no more data.
271 * Silence a (false) compiler warning, always assign to 'now_us'.
272 */
273 if (nonblocking)
274 timeout_ms = 0;
275 deadline_us = now_us = 0;
276 if (timeout_ms) {
277 now_us = g_get_monotonic_time();
278 deadline_us = now_us + timeout_ms * 1000;
279 }
280
281 /*
282 * Keep reading until the caller's requested length is reached,
283 * or fatal errors are seen, or specified timeouts have expired.
284 */
285 total = 0;
286 while (count) {
287 ret = sr_tcp_read_bytes(serial->tcp_dev,
288 buf, count, nonblocking);
289 if (ret < 0 && !total) {
290 sr_err("Failed to receive TCP data.");
291 break;
292 }
293 if (ret < 0) {
294 /* Short read, not worth warning about. */
295 break;
296 }
297 if (ret == 0 && nonblocking)
298 break;
299 if (ret == 0 && deadline_us) {
300 now_us = g_get_monotonic_time();
301 if (now_us >= deadline_us)
302 break;
303 g_usleep(10 * 1000);
304 continue;
305 }
306 chunk = (size_t)ret;
307 buf += chunk;
308 count -= chunk;
309 total += chunk;
310 }
311
312 return total;
313}
314
315static struct ser_lib_functions serlib_tcpraw = {
316 .open = ser_tcpraw_open,
317 .close = ser_tcpraw_close,
318 .write = ser_tcpraw_write,
319 .read = ser_tcpraw_read,
320 .set_params = std_dummy_set_params,
321 .set_handshake = std_dummy_set_handshake,
322 .setup_source_add = ser_tcpraw_setup_source_add,
323 .setup_source_remove = ser_tcpraw_setup_source_remove,
324 .get_frame_format = NULL,
325};
326SR_PRIV struct ser_lib_functions *ser_lib_funcs_tcpraw = &serlib_tcpraw;
327
328/** @} */