2 * This file is part of the libsigrok project.
4 * Copyright (C) 2023 Gerhard Sittig <gerhard.sittig@gmx.net>
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.
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/>.
22 #include <libsigrok/libsigrok.h>
25 #include "libsigrok-internal.h"
27 #define LOG_PREFIX "serial-tcpraw"
29 #define SER_TCPRAW_CONN_PREFIX "tcp-raw"
34 * Serial port handling, raw TCP support.
38 * @defgroup grp_serial_tcpraw Serial port handling, raw TCP group
40 * Disguise raw byte sequences over TCP sockets as a serial transport.
45 /* {{{ TCP specific helper routines */
48 * Parse conn= specs for serial over TCP communication.
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).
55 * @return 0 upon success, non-zero upon failure. Fills the *_ref output
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
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.
73 * Supported format resulting from these rules:
74 * tcp-raw/<ipaddr>/<port>
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)
92 if (!serial || !spec || !*spec)
95 fields = g_strsplit(spec, "/", 0);
98 count = g_strv_length(fields);
103 if (valid && strcmp(fields[0], SER_TCPRAW_CONN_PREFIX) != 0)
115 /* Silently ignore trailing fields. Could be future options. */
117 sr_warn("Ignoring excess parameters in %s.", spec);
120 if (host_ref && host)
121 *host_ref = g_strdup(host);
122 if (port_ref && port)
123 *port_ref = g_strdup(port);
126 return valid ? SR_OK : SR_ERR_ARG;
130 /* {{{ transport methods called by the common serial.c code */
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)
139 if (!serial->port || !*serial->port)
143 if (!g_str_has_prefix(p, SER_TCPRAW_CONN_PREFIX))
145 p += strlen(SER_TCPRAW_CONN_PREFIX);
152 static int ser_tcpraw_open(struct sr_serial_dev_inst *serial, int flags)
159 ret = ser_tcpraw_parse_conn_spec(serial, serial->port,
166 serial->tcp_dev = sr_tcp_dev_inst_new(host, port);
169 if (!serial->tcp_dev)
170 return SR_ERR_MALLOC;
173 * Open the TCP socket. Only keep caller's parameters (and the
174 * resulting socket fd) when open completes successfully.
176 ret = sr_tcp_connect(serial->tcp_dev);
178 sr_err("Failed to establish TCP connection.");
179 sr_tcp_dev_inst_free(serial->tcp_dev);
180 serial->tcp_dev = NULL;
187 static int ser_tcpraw_close(struct sr_serial_dev_inst *serial)
193 if (!serial->tcp_dev)
196 (void)sr_tcp_disconnect(serial->tcp_dev);
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)
204 if (!serial || !serial->tcp_dev)
206 return sr_tcp_source_add(session, serial->tcp_dev,
207 events, timeout, cb, cb_data);
210 static int ser_tcpraw_setup_source_remove(struct sr_session *session,
211 struct sr_serial_dev_inst *serial)
213 if (!serial || !serial->tcp_dev)
215 (void)sr_tcp_source_remove(session, serial->tcp_dev);
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)
223 size_t total, written;
226 /* Non-blocking writes, and write timeouts, are not supported. */
230 if (!serial || !serial->tcp_dev)
235 ret = sr_tcp_write_bytes(serial->tcp_dev, buf, count);
236 if (ret < 0 && !total) {
237 sr_err("Error sending TCP transmit data.");
242 sr_warn("Short transmission of TCP data (%zu/%zu).",
246 written = (size_t)ret;
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)
259 guint64 deadline_us, now_us;
263 if (!serial || !serial->tcp_dev)
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'.
275 deadline_us = now_us = 0;
277 now_us = g_get_monotonic_time();
278 deadline_us = now_us + timeout_ms * 1000;
282 * Keep reading until the caller's requested length is reached,
283 * or fatal errors are seen, or specified timeouts have expired.
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.");
294 /* Short read, not worth warning about. */
297 if (ret == 0 && nonblocking)
299 if (ret == 0 && deadline_us) {
300 now_us = g_get_monotonic_time();
301 if (now_us >= deadline_us)
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,
326 SR_PRIV struct ser_lib_functions *ser_lib_funcs_tcpraw = &serlib_tcpraw;