]> sigrok.org Git - libsigrok.git/blob - src/serial_tcpraw.c
output/csv: use intermediate time_t var, silence compiler warning
[libsigrok.git] / src / serial_tcpraw.c
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  */
76 static 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. */
133 SR_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
152 static 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
187 static 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
200 static 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
210 static 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
219 static 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
255 static 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
315 static 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 };
326 SR_PRIV struct ser_lib_functions *ser_lib_funcs_tcpraw = &serlib_tcpraw;
327
328 /** @} */