changeset 10:3c5734b88c20

sipout-test-v22 put together
author Mychaela Falconia <falcon@freecalypso.org>
date Thu, 07 Mar 2024 02:33:49 -0800
parents ff535725e01f
children 2cdbd574bba6
files .hgignore Makefile test-v22/Makefile test-v22/bye_in.c test-v22/disc_cmd.c test-v22/main.c test-v22/modem_func.c test-v22/readconf.c test-v22/reinvite.c test-v22/rtp_rx.c test-v22/rtp_tx.c test-v22/sdp_in.c test-v22/sip_log.c test-v22/sip_udp.c test-v22/uac.c test-v22/uas.c test-v22/user_cmd.c
diffstat 17 files changed, 1416 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu Mar 07 00:05:44 2024 -0800
+++ b/.hgignore	Thu Mar 07 02:33:49 2024 -0800
@@ -3,5 +3,6 @@
 \.[oa]$
 
 ^test-fsk/sipout-test-fsk$
+^test-v22/sipout-test-v22$
 ^test-voice/sipout-test-voice$
 ^tone-detect/g711-tone-detect$
--- a/Makefile	Thu Mar 07 00:05:44 2024 -0800
+++ b/Makefile	Thu Mar 07 02:33:49 2024 -0800
@@ -1,13 +1,14 @@
 CC=	gcc
 CFLAGS=	-O2
 
-PROGDIR=test-fsk test-voice tone-detect
+PROGDIR=test-fsk test-v22 test-voice tone-detect
 LIBDIR=	librtpalloc libsip libutil
 SUBDIR=	${PROGDIR} ${LIBDIR}
 
 all:	${SUBDIR}
 
 test-fsk:	librtpalloc libsip libutil
+test-v22:	librtpalloc libsip libutil
 test-voice:	librtpalloc libsip libutil
 
 ${SUBDIR}: FRC
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-v22/Makefile	Thu Mar 07 02:33:49 2024 -0800
@@ -0,0 +1,18 @@
+CC=	gcc
+CFLAGS=	-O2
+PROG=	sipout-test-v22
+OBJS=	bye_in.o disc_cmd.o main.o modem_func.o readconf.o reinvite.o rtp_rx.o \
+	rtp_tx.o sdp_in.o sip_log.o sip_udp.o uac.o uas.o user_cmd.o
+LIBS=	../libsip/libsip.a ../librtpalloc/librtpalloc.a ../libutil/libutil.a
+INSTBIN=/opt/themwi/bin
+
+all:	${PROG}
+
+${PROG}: ${OBJS} ${LIBS}
+	${CC} ${CFLAGS} -o $@ ${OBJS} ${LIBS} -lspandsp -lm
+
+install:
+	install -c -m 755 ${PROG} ${INSTBIN}
+
+clean:
+	rm -f *.o ${PROG} errs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-v22/bye_in.c	Thu Mar 07 02:33:49 2024 -0800
@@ -0,0 +1,70 @@
+/*
+ * Here we handle incoming BYE requests in the UAS role.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "../libsip/parse.h"
+#include "../libsip/uas_basic.h"
+#include "../libsip/out_msg.h"
+
+extern char call_id[];
+extern int rtp_out_enable;
+
+static void
+bye_correct_call(req, ess, sin)
+	struct sip_pkt_rx *req;
+	struct uas_parse_hdrs *ess;
+	struct sockaddr_in *sin;
+{
+	struct sip_msg_out resp;
+	int rc;
+
+	printf("Received BYE for our call, responding with 200\n");
+	rtp_out_enable = 0;
+	start_response_out_msg(&resp, "200 OK");
+	rc = add_resp_basic_headers(&resp, ess, req->req_method);
+	if (rc < 0) {
+		fprintf(stderr, "sending 200 response: msg length exceeded\n");
+		return;
+	}
+	out_msg_finish(&resp);
+	sip_tx_packet(&resp, sin);
+}
+
+static void
+bye_unknown_call(req, ess, sin)
+	struct sip_pkt_rx *req;
+	struct uas_parse_hdrs *ess;
+	struct sockaddr_in *sin;
+{
+	struct sip_msg_out resp;
+	int rc;
+
+	printf("Received BYE for unknown call, responding with 481\n");
+	start_response_out_msg(&resp, "481 Call-ID not found");
+	rc = add_resp_basic_headers(&resp, ess, req->req_method);
+	if (rc < 0) {
+		fprintf(stderr, "sending 481 response: msg length exceeded\n");
+		return;
+	}
+	out_msg_finish(&resp);
+	sip_tx_packet(&resp, sin);
+}
+
+void
+handle_bye_req(req, ess, sin)
+	struct sip_pkt_rx *req;
+	struct uas_parse_hdrs *ess;
+	struct sockaddr_in *sin;
+{
+	if (!strcmp(ess->call_id, call_id))
+		bye_correct_call(req, ess, sin);
+	else
+		bye_unknown_call(req, ess, sin);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-v22/disc_cmd.c	Thu Mar 07 02:33:49 2024 -0800
@@ -0,0 +1,55 @@
+/*
+ * In this module we implement user-driven sending of CANCEL and BYE
+ * disconnection requests.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "../libsip/out_msg.h"
+
+extern struct sockaddr_in sip_dest_sin;
+extern char to_uri[];
+extern int rtp_out_enable;
+
+send_cancel_req()
+{
+	struct sip_msg_out msg;
+	int rc;
+
+	rtp_out_enable = 0;
+	rc = start_request_out_msg(&msg, "CANCEL", to_uri);
+	if (rc < 0) {
+msg_size_err:	fprintf(stderr, "composing CANCEL message: size error\n");
+		return(-1);
+	}
+	rc = add_req_boilerplate(&msg, "1 CANCEL", 0);
+	if (rc < 0)
+		goto msg_size_err;
+	out_msg_finish(&msg);
+	sip_tx_packet(&msg, &sip_dest_sin);
+	return(0);
+}
+
+send_bye_req()
+{
+	struct sip_msg_out msg;
+	int rc;
+
+	rtp_out_enable = 0;
+	rc = start_request_out_msg(&msg, "BYE", to_uri);
+	if (rc < 0) {
+msg_size_err:	fprintf(stderr, "composing BYE message: size error\n");
+		return(-1);
+	}
+	rc = add_req_boilerplate(&msg, "2 BYE", 1);
+	if (rc < 0)
+		goto msg_size_err;
+	out_msg_finish(&msg);
+	sip_tx_packet(&msg, &sip_dest_sin);
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-v22/main.c	Thu Mar 07 02:33:49 2024 -0800
@@ -0,0 +1,207 @@
+/*
+ * This is the main module for sip-manual-out test program.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/errno.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include "../libsip/out_msg.h"
+#include "../libsip/sdp.h"
+
+extern int sip_socket;
+extern struct in_addr sip_bind_ip, sip_dest_ip;
+extern unsigned sip_bind_port, sip_dest_port;
+extern char sip_dest_domain[];
+extern struct sockaddr_in rtp_local_addr;
+extern int rtp_udp_fd, rtcp_udp_fd;
+extern int rtp_out_enable;
+
+struct sockaddr_in sip_dest_sin;
+char from_uri[128], to_uri[128], call_id[128];
+struct timeval cur_event_time;
+unsigned max_forwards = 70;
+int declare_100rel_supp;
+int pcma_codec_pref, pcma_codec_force;
+int v22_bitrate;
+
+send_invite_req()
+{
+	struct sip_msg_out msg;
+	struct sdp_gen sdp;
+	int rc;
+
+	rc = start_request_out_msg(&msg, "INVITE", to_uri);
+	if (rc < 0) {
+msg_size_err:	fprintf(stderr, "composing INVITE req: msg size error\n");
+		exit(1);
+	}
+	rc = add_req_boilerplate(&msg, "1 INVITE", 0);
+	if (rc < 0)
+		goto msg_size_err;
+	rc = add_contact_header(&msg);
+	if (rc < 0)
+		goto msg_size_err;
+	if (declare_100rel_supp) {
+		rc = out_msg_add_header(&msg, "Supported", "100rel");
+		if (rc < 0)
+			goto msg_size_err;
+	}
+	rc = out_msg_add_header(&msg, "Content-Type", "application/sdp");
+	if (rc < 0)
+		goto msg_size_err;
+	bzero(&sdp, sizeof sdp);
+	sdp.conn_ip = rtp_local_addr.sin_addr;
+	sdp.conn_port = ntohs(rtp_local_addr.sin_port);
+	if (pcma_codec_force)
+		sdp.codec_mask = SDP_CODEC_MASK_PCMA;
+	else {
+		sdp.codec_mask = SDP_CODEC_MASK_BOTH;
+		if (pcma_codec_pref)
+			sdp.codec_mask |= SDP_CODEC_MASK_PCMA_PREF;
+	}
+	sdp.session_id = sdp.conn_port << 16;
+	sdp.owner_ip = sip_bind_ip;
+	rc = out_msg_finish_sdp(&msg, &sdp);
+	if (rc < 0)
+		goto msg_size_err;
+	sip_tx_packet(&msg, &sip_dest_sin);
+}
+
+static void
+preliminary_proc(argc, argv)
+	char **argv;
+{
+	extern int optind;
+	extern char *optarg;
+	char *logfile;
+	int opt, rc;
+
+	logfile = 0;
+	while ((opt = getopt(argc, argv, "aAl:m:r")) != EOF) {
+		switch (opt) {
+		case 'a':
+			pcma_codec_pref = 1;
+			continue;
+		case 'A':
+			pcma_codec_force = 1;
+			continue;
+		case 'l':
+			logfile = optarg;
+			continue;
+		case 'm':
+			max_forwards = atoi(optarg);
+			continue;
+		case 'r':
+			declare_100rel_supp = 1;
+			continue;
+		default:
+		usage:
+			fprintf(stderr,
+			"usage: %s [options] dest-conf from-num to-num bps\n",
+				argv[0]);
+			exit(1);
+		}
+	}
+	if (argc != optind + 4)
+		goto usage;
+	read_config_file(argv[optind]);
+	open_sip_udp_socket();
+	obtain_rtp_endp();
+	sip_dest_sin.sin_family = AF_INET;
+	sip_dest_sin.sin_addr = sip_dest_ip;
+	sip_dest_sin.sin_port = htons(sip_dest_port);
+	sprintf(from_uri, "<sip:%s@%s>;tag=out%u", argv[optind+1],
+		inet_ntoa(sip_bind_ip), ntohs(rtp_local_addr.sin_port));
+	sprintf(to_uri, "sip:%s@%s", argv[optind+2], sip_dest_domain);
+	v22_bitrate = atoi(argv[optind+3]);
+	if (v22_bitrate != 1200 && v22_bitrate != 2400) {
+		fprintf(stderr, "error: bps argument must be 1200 or 2400\n");
+		exit(1);
+	}
+	init_modem_func();
+	if (logfile) {
+		rc = open_sip_log_file(logfile);
+		if (rc < 0)
+			exit(1);	/* error msg already printed */
+	}
+}
+
+main(argc, argv)
+	char **argv;
+{
+	fd_set fds;
+	struct timeval next_rtp_out, timeout;
+	int rc, max_fd, rtp_out_running;
+
+	preliminary_proc(argc, argv);
+	gettimeofday(&cur_event_time, 0);
+	sprintf(call_id, "%08u_%u@%s",
+		(unsigned)(cur_event_time.tv_sec % 100000000),
+		ntohs(rtp_local_addr.sin_port), inet_ntoa(sip_bind_ip));
+	send_invite_req();
+	/* main select loop */
+	max_fd = sip_socket;
+	if (rtp_udp_fd > max_fd)
+		max_fd = rtp_udp_fd;
+	if (rtcp_udp_fd > max_fd)
+		max_fd = rtcp_udp_fd;
+	rtp_out_running = 0;
+	for (;;) {
+		FD_ZERO(&fds);
+		FD_SET(0, &fds);
+		FD_SET(sip_socket, &fds);
+		FD_SET(rtp_udp_fd, &fds);
+		FD_SET(rtcp_udp_fd, &fds);
+		if (rtp_out_enable) {
+			if (!rtp_out_running) {
+				printf("Starting RTP output\n");
+				bcopy(&cur_event_time, &next_rtp_out,
+					sizeof(struct timeval));
+				rtp_out_running = 1;
+			}
+			if (timercmp(&cur_event_time, &next_rtp_out, <))
+				timersub(&next_rtp_out, &cur_event_time,
+					 &timeout);
+			else
+				timerclear(&timeout);
+			rc = select(max_fd+1, &fds, 0, 0, &timeout);
+		} else {
+			if (rtp_out_running) {
+				printf("Stopping RTP output\n");
+				rtp_out_running = 0;
+			}
+			rc = select(max_fd+1, &fds, 0, 0, 0);
+		}
+		if (rc < 0) {
+			if (errno == EINTR)
+				continue;
+			perror("select");
+			exit(1);
+		}
+		gettimeofday(&cur_event_time, 0);
+		if (FD_ISSET(0, &fds))
+			select_stdin();
+		if (FD_ISSET(sip_socket, &fds))
+			sip_socket_select();
+		if (FD_ISSET(rtp_udp_fd, &fds))
+			rtp_rx_select();
+		if (FD_ISSET(rtcp_udp_fd, &fds))
+			rtcp_rx_select();
+		if (rtp_out_running && (rc == 0)) {
+			generate_rtp_packet();
+			next_rtp_out.tv_usec += 20000;
+			if (next_rtp_out.tv_usec >= 1000000) {
+				next_rtp_out.tv_sec++;
+				next_rtp_out.tv_usec -= 1000000;
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-v22/modem_func.c	Thu Mar 07 02:33:49 2024 -0800
@@ -0,0 +1,122 @@
+/*
+ * In this module we implement our interface to SpanDSP V.22 engine.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <spandsp.h>
+#include "../include/pstn_defs.h"
+
+extern int v22_bitrate;
+
+#define	MAX_TEXT_LINE	80
+
+static v22bis_state_t *modem_state;
+static async_rx_state_t *async_state;
+static u_char rx_line_buf[MAX_TEXT_LINE];
+static unsigned rx_buf_fill;
+
+static void
+safe_print_char(c)
+{
+	if (c >= ' ' && c <= '~') {
+		putchar(c);
+		return;
+	}
+	switch (c) {
+	case '\t':
+		putchar(c);
+		return;
+	case '\n':
+		return;
+	case '\r':
+		putchar('\\');
+		putchar('r');
+		return;
+	case '\b':
+		putchar('\\');
+		putchar('b');
+		return;
+	case '\f':
+		putchar('\\');
+		putchar('f');
+		return;
+	}
+	printf("\\x%02X", c);
+}
+
+static void
+print_rx_line()
+{
+	u_char *dp, *endp;
+	int c;
+
+	fputs("MRx:\t", stdout);
+	dp = rx_line_buf;
+	endp = rx_line_buf + rx_buf_fill;
+	while (dp < endp) {
+		c = *dp++;
+		safe_print_char(c);
+	}
+	if (c != '\n')
+		putchar('\\');
+	putchar('\n');
+}
+
+static void
+byte_rx_func(user_data, byte)
+	void *user_data;
+	int byte;
+{
+	if (byte < 0) {
+		printf("Modem state change: %s\n", signal_status_to_str(byte));
+		return;
+	}
+	rx_line_buf[rx_buf_fill++] = byte;
+	if (byte == '\n' || rx_buf_fill >= MAX_TEXT_LINE) {
+		print_rx_line();
+		rx_buf_fill = 0;
+	}
+}
+
+static int
+supply_bit()
+{
+	return 1;
+}
+
+void
+init_modem_func()
+{
+	async_state = async_rx_init(NULL, 8, ASYNC_PARITY_NONE, 1, true,
+				    byte_rx_func, NULL);
+	if (!async_state) {
+		fprintf(stderr, "error: async_rx_init() failed!\n");
+		exit(1);
+	}
+	modem_state = v22bis_init(NULL, v22_bitrate, V22BIS_GUARD_TONE_NONE,
+				  true, supply_bit, NULL, async_rx_put_bit,
+				  async_state);
+	if (!modem_state) {
+		fprintf(stderr, "error: v22bis_init() failed!\n");
+		exit(1);
+	}
+}
+
+void
+process_rx_frame(samples)
+	int16_t *samples;
+{
+	v22bis_rx(modem_state, samples, FRAME_20MS);
+}
+
+void
+fill_tx_frame(samples)
+	int16_t *samples;
+{
+	v22bis_tx(modem_state, samples, FRAME_20MS);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-v22/readconf.c	Thu Mar 07 02:33:49 2024 -0800
@@ -0,0 +1,159 @@
+/*
+ * In this module we implement the reading of destination configuration
+ * for sip-manual-out.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+struct in_addr sip_bind_ip, sip_dest_ip;
+unsigned sip_bind_port, sip_dest_port = 5060;
+char sip_dest_domain[64];
+
+struct parse_state {
+	char *filename;
+	int lineno;
+	int set_mask;
+};
+
+static void
+handle_ip(st, kw, var, arg)
+	struct parse_state *st;
+	char *kw, *arg;
+	struct in_addr *var;
+{
+	var->s_addr = inet_addr(arg);
+	if (var->s_addr == INADDR_NONE) {
+		fprintf(stderr,
+			"%s line %d: invalid IP address argument \"%s\"\n",
+			st->filename, st->lineno, arg);
+		exit(1);
+	}
+}
+
+static void
+handle_num(st, kw, var, arg)
+	struct parse_state *st;
+	char *kw, *arg;
+	unsigned *var;
+{
+	char *endp;
+
+	*var = strtoul(arg, &endp, 10);
+	if (*endp) {
+		fprintf(stderr, "%s line %d: invalid numeric argument \"%s\"\n",
+			st->filename, st->lineno, arg);
+		exit(1);
+	}
+}
+
+static void
+handle_str(st, kw, var, arg)
+	struct parse_state *st;
+	char *kw, *arg, *var;
+{
+	strcpy(var, arg);
+}
+
+static void
+process_line(st, line)
+	struct parse_state *st;
+	char *line;
+{
+	char *cp, *np, *arg;
+	void (*handler)(), *var;
+	int set_id;
+
+	if (!index(line, '\n')) {
+		fprintf(stderr, "%s line %d: too long or missing newline\n",
+			st->filename, st->lineno);
+		exit(1);
+	}
+	for (cp = line; isspace(*cp); cp++)
+		;
+	if (*cp == '\0' || *cp == '#')
+		return;
+	for (np = cp; *cp && !isspace(*cp); cp++)
+		;
+	if (*cp)
+		*cp++ = '\0';
+	if (!strcmp(np, "bind-ip")) {
+		handler = handle_ip;
+		var = &sip_bind_ip;
+		set_id = 1;
+	} else if (!strcmp(np, "bind-port")) {
+		handler = handle_num;
+		var = &sip_bind_port;
+		set_id = 2;
+	} else if (!strcmp(np, "dest-ip")) {
+		handler = handle_ip;
+		var = &sip_dest_ip;
+		set_id = 4;
+	} else if (!strcmp(np, "dest-port")) {
+		handler = handle_num;
+		var = &sip_dest_port;
+		set_id = 0;
+	} else if (!strcmp(np, "dest-domain")) {
+		handler = handle_str;
+		var = sip_dest_domain;
+		set_id = 8;
+	} else {
+		fprintf(stderr, "%s line %d: non-understood keyword \"%s\"\n",
+			st->filename, st->lineno, np);
+		exit(1);
+	}
+	if (st->set_mask & set_id) {
+		fprintf(stderr, "%s line %d: duplicate %s setting\n",
+			st->filename, st->lineno, np);
+		exit(1);
+	}
+	while (isspace(*cp))
+		cp++;
+	if (*cp == '\0' || *cp == '#') {
+inv_syntax:	fprintf(stderr,
+			"%s line %d: %s setting requires one argument\n",
+			st->filename, st->lineno, np);
+		exit(1);
+	}
+	for (arg = cp; *cp && !isspace(*cp); cp++)
+		;
+	if (*cp)
+		*cp++ = '\0';
+	while (isspace(*cp))
+		cp++;
+	if (*cp != '\0' && *cp != '#')
+		goto inv_syntax;
+	handler(st, np, var, arg);
+	st->set_mask |= set_id;
+}
+
+read_config_file(filename)
+	char *filename;
+{
+	FILE *inf;
+	struct parse_state pst;
+	char linebuf[256];
+
+	inf = fopen(filename, "r");
+	if (!inf) {
+		perror(filename);
+		exit(1);
+	}
+	pst.set_mask = 0;
+	pst.filename = filename;
+	for (pst.lineno = 1; fgets(linebuf, sizeof linebuf, inf); pst.lineno++)
+		process_line(&pst, linebuf);
+	fclose(inf);
+	if (pst.set_mask != 15) {
+		fprintf(stderr, "error: %s did not set all required settings\n",
+			filename);
+		exit(1);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-v22/reinvite.c	Thu Mar 07 02:33:49 2024 -0800
@@ -0,0 +1,70 @@
+/*
+ * Here we handle incoming INVITE requests in the UAS role: even though
+ * we are strictly outbound, BulkVS servers will send us periodic
+ * re-INVITEs as keepalives, and we have to play along.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "../libsip/parse.h"
+#include "../libsip/uas_basic.h"
+#include "../libsip/out_msg.h"
+
+extern char call_id[];
+
+static void
+invite_correct_call(req, ess, sin)
+	struct sip_pkt_rx *req;
+	struct uas_parse_hdrs *ess;
+	struct sockaddr_in *sin;
+{
+	struct sip_msg_out resp;
+	int rc;
+
+	printf("Received re-INVITE for our call, responding with 200\n");
+	start_response_out_msg(&resp, "200 OK");
+	rc = add_resp_basic_headers(&resp, ess, req->req_method);
+	if (rc < 0) {
+		fprintf(stderr, "sending 200 response: msg length exceeded\n");
+		return;
+	}
+	out_msg_finish(&resp);
+	sip_tx_packet(&resp, sin);
+}
+
+static void
+invite_unknown_call(req, ess, sin)
+	struct sip_pkt_rx *req;
+	struct uas_parse_hdrs *ess;
+	struct sockaddr_in *sin;
+{
+	struct sip_msg_out resp;
+	int rc;
+
+	printf("Received INVITE for unknown call, responding with 405\n");
+	start_response_out_msg(&resp, "405 This gateway is outbound only");
+	rc = add_resp_basic_headers(&resp, ess, req->req_method);
+	if (rc < 0) {
+		fprintf(stderr, "sending 405 response: msg length exceeded\n");
+		return;
+	}
+	out_msg_finish(&resp);
+	sip_tx_packet(&resp, sin);
+}
+
+void
+handle_invite_req(req, ess, sin)
+	struct sip_pkt_rx *req;
+	struct uas_parse_hdrs *ess;
+	struct sockaddr_in *sin;
+{
+	if (!strcmp(ess->call_id, call_id))
+		invite_correct_call(req, ess, sin);
+	else
+		invite_unknown_call(req, ess, sin);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-v22/rtp_rx.c	Thu Mar 07 02:33:49 2024 -0800
@@ -0,0 +1,148 @@
+/*
+ * In this module we implement our RTP handling: obtaining a PSTN-side
+ * RTP endpoint from themwi-rtp-mgr, then handling read select on RTP
+ * and RTCP UDP sockets.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "../include/tmgw_const.h"
+#include "../include/rtp_defs.h"
+#include "../include/pstn_defs.h"
+#include "../librtpalloc/rtp_alloc_simple.h"
+
+extern const uint16_t pcmu_decode_table[256];
+extern const uint16_t pcma_decode_table[256];
+
+extern int rtp_out_enable;	/* misusing the flag :-( */
+
+struct sockaddr_in rtp_local_addr;
+int rtp_udp_fd, rtcp_udp_fd;
+
+static int rtp_start_flag, rtp_bad_flag, rtp_ssrc_chg_flag;
+static int rtp_seq_brk_flag, rtp_seq_zero_flag, rtp_seq_neg_flag;
+static int rtp_ts_brk_flag;
+static uint32_t rtp_ssrc;
+static uint32_t rtp_last_ts;
+static uint16_t rtp_last_seq;
+static int got_some_rtcp;
+
+void
+obtain_rtp_endp()
+{
+	int rc;
+	struct rtp_alloc_simple res;
+
+	rc = rtp_alloc_simple(TMGW_EP_TYPE_PSTN_ONLY, &res);
+	if (rc < 0)
+		exit(1);	/* error msg already printed */
+	bcopy(&res.pstn_addr, &rtp_local_addr, sizeof(struct sockaddr_in));
+	rtp_udp_fd = res.pstn_rtp_fd;
+	rtcp_udp_fd = res.pstn_rtcp_fd;
+}
+
+void
+rtp_rx_select()
+{
+	struct rtp_packet pkt;
+	struct sockaddr_in sin_from;
+	socklen_t addrlen;
+	int16_t seq_delta;
+	int32_t ts_delta;
+	const uint16_t *pcm_dec_table;
+	int16_t pcm_samples[FRAME_20MS];
+	unsigned n;
+	int rc;
+
+	addrlen = sizeof(struct sockaddr_in);
+	rc = recvfrom(rtp_udp_fd, &pkt, sizeof pkt, 0,
+			(struct sockaddr *) &sin_from, &addrlen);
+	if (rc < 0)
+		return;
+	if (rc != RTP_PACKET_SIZE_PSTN) {
+bad_rtp_pkt:	if (!rtp_bad_flag) {
+			printf("Got a bad RTP packet\n");
+			rtp_bad_flag = 1;
+		}
+		return;
+	}
+	if (pkt.v_p_x_cc != 0x80)
+		goto bad_rtp_pkt;
+	switch (pkt.m_pt & 0x7F) {
+	case PSTN_CODEC_PCMU:
+		pcm_dec_table = pcmu_decode_table;
+		break;
+	case PSTN_CODEC_PCMA:
+		pcm_dec_table = pcma_decode_table;
+		break;
+	default:
+		goto bad_rtp_pkt;
+	}
+	if (rtp_start_flag && pkt.ssrc != rtp_ssrc) {
+		if (!rtp_ssrc_chg_flag) {
+			printf("Rx RTP stream changed SSRC\n");
+			rtp_ssrc_chg_flag = 1;
+		}
+	} else if (rtp_start_flag) {
+		seq_delta = ntohs(pkt.seq) - rtp_last_seq;
+		ts_delta = ntohl(pkt.tstamp) - rtp_last_ts;
+		if (seq_delta == 0) {
+			if (!rtp_seq_zero_flag) {
+				printf("Rx RTP seq zero increment\n");
+				rtp_seq_zero_flag = 1;
+			}
+			return;
+		}
+		if (seq_delta < 0) {
+			if (!rtp_seq_neg_flag) {
+				printf("Rx RTP seq negative increment\n");
+				rtp_seq_neg_flag = 1;
+			}
+			return;
+		}
+		if (seq_delta != 1) {
+			if (!rtp_seq_brk_flag) {
+				printf("Rx RTP stream seq break\n");
+				rtp_seq_brk_flag = 1;
+			}
+		} else if (ts_delta != 160) {
+			if (!rtp_ts_brk_flag) {
+				printf("Rx RTP stream tstamp break\n");
+				rtp_ts_brk_flag = 1;
+			}
+		}
+	}
+	rtp_ssrc = pkt.ssrc;
+	rtp_last_ts = ntohl(pkt.tstamp);
+	rtp_last_seq = ntohs(pkt.seq);
+	if (!rtp_start_flag) {
+		printf("Rx RTP stream begins with seq=%u ts=%u\n",
+			rtp_last_seq, rtp_last_ts);
+		rtp_start_flag = 1;
+	}
+	/* ignore early RTP during ringing, before answer supervision */
+	if (!rtp_out_enable)
+		return;
+	/* feed samples to modem Rx */
+	for (n = 0; n < FRAME_20MS; n++)
+		pcm_samples[n] = pcm_dec_table[pkt.payload[n]];
+	process_rx_frame(pcm_samples);
+}
+
+void
+rtcp_rx_select()
+{
+	u_char buf[512];
+
+	recv(rtcp_udp_fd, buf, sizeof buf, 0);
+	if (!got_some_rtcp) {
+		printf("Got some RTCP\n");
+		got_some_rtcp = 1;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-v22/rtp_tx.c	Thu Mar 07 02:33:49 2024 -0800
@@ -0,0 +1,65 @@
+/*
+ * In this module we implement outgoing RTP stream generation.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <spandsp.h>
+#include "../include/tmgw_const.h"
+#include "../include/rtp_defs.h"
+#include "../include/pstn_defs.h"
+#include "../libutil/osmo_bits.h"
+
+extern struct sockaddr_in rtp_local_addr, rtp_remote_addr;
+extern int rtp_udp_fd, rtcp_udp_fd, pcma_selected;
+extern struct timeval cur_event_time;
+
+static uint32_t rtp_ssrc;
+static uint32_t rtp_out_ts;
+static uint16_t rtp_out_seq;
+
+static g711_state_t *g711_enc_state;
+
+void
+assign_rtpout_ssrc()
+{
+	rtp_ssrc = cur_event_time.tv_sec ^ cur_event_time.tv_usec ^ getpid();
+}
+
+void
+init_pcm_tx()
+{
+	g711_enc_state = g711_init(NULL, pcma_selected ? G711_ALAW : G711_ULAW);
+	if (!g711_enc_state) {
+		fprintf(stderr, "error: g711_init() failed!\n");
+		exit(1);
+	}
+}
+
+void
+generate_rtp_packet()
+{
+	struct rtp_packet pkt;
+	socklen_t addrlen;
+	int16_t linear[FRAME_20MS];
+
+	pkt.v_p_x_cc = 0x80;
+	pkt.m_pt = pcma_selected ? PSTN_CODEC_PCMA : PSTN_CODEC_PCMU;
+	pkt.seq = htons(rtp_out_seq++);
+	pkt.tstamp = htonl(rtp_out_ts);
+	rtp_out_ts += FRAME_20MS;
+	pkt.ssrc = rtp_ssrc;
+	fill_tx_frame(linear);
+	g711_encode(g711_enc_state, pkt.payload, linear, FRAME_20MS);
+	addrlen = sizeof(struct sockaddr_in);
+	sendto(rtp_udp_fd, &pkt, RTP_PACKET_SIZE_PSTN, 0,
+		(struct sockaddr *) &rtp_remote_addr, addrlen);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-v22/sdp_in.c	Thu Mar 07 02:33:49 2024 -0800
@@ -0,0 +1,85 @@
+/*
+ * In this module we handle SDP responses to our INVITE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "../libsip/parse.h"
+#include "../libsip/sdp.h"
+
+extern char *get_single_header();
+extern char *extract_to_tag();
+
+struct sockaddr_in rtp_remote_addr;
+int got_sdp_answer, pcma_selected, rtp_out_enable;
+
+static
+check_sdp_present(msg)
+	struct sip_pkt_rx *msg;
+{
+	char *hval;
+
+	if (!msg->msg_body_len)
+		return 0;
+	hval = get_single_header(msg, "Content-Type", "c", (int *) 0);
+	if (!hval)
+		return 0;
+	if (!strcasecmp(hval, "application/sdp"))
+		return 1;
+	else
+		return 0;
+}
+
+void
+extract_resp_sdp(msg)
+	struct sip_pkt_rx *msg;
+{
+	struct sdp_parse sdp_parse;
+	int rc;
+
+	if (!check_sdp_present(msg))
+		return;
+	rc = parse_incoming_sdp(msg->msg_body, msg->msg_body_len, &sdp_parse);
+	if (rc < 0) {
+		printf("SDP parse error: %d\n", rc);
+		return;
+	}
+	switch (sdp_parse.codec_mask) {
+	case SDP_CODEC_MASK_PCMU:
+	case SDP_CODEC_MASK_BOTH:
+		pcma_selected = 0;
+		break;
+	case SDP_CODEC_MASK_PCMA:
+	case SDP_CODEC_MASK_BOTH | SDP_CODEC_MASK_PCMA_PREF:
+		pcma_selected = 1;
+		break;
+	default:
+		printf("SDP error: no supported codec\n");
+		return;
+	}
+	printf("SDP response: IP %s port %u codec %s\n",
+		inet_ntoa(sdp_parse.ip_addr), sdp_parse.audio_port,
+		pcma_selected ? "PCMA" : "PCMU");
+	rtp_remote_addr.sin_family = AF_INET;
+	rtp_remote_addr.sin_addr = sdp_parse.ip_addr;
+	rtp_remote_addr.sin_port = htons(sdp_parse.audio_port);
+	got_sdp_answer = 1;
+}
+
+void
+invite_200_rtpout()
+{
+	if (!got_sdp_answer) {
+		printf("INVITE response has no SDP!\n");
+		return;
+	}
+	rtp_out_enable = 1;
+	assign_rtpout_ssrc();
+	init_pcm_tx();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-v22/sip_log.c	Thu Mar 07 02:33:49 2024 -0800
@@ -0,0 +1,68 @@
+/*
+ * In this module we implement debug logging of SIP Rx & Tx messages.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+extern struct timeval cur_event_time;
+
+static FILE *logfile;
+
+open_sip_log_file(filename)
+	char *filename;
+{
+	logfile = fopen(filename, "a");
+	if (!logfile) {
+		perror(filename);
+		return(-1);
+	}
+	return(0);
+}
+
+static void
+log_common(msg, msglen, sin, dir)
+	char *msg, *dir;
+	unsigned msglen;
+	struct sockaddr_in *sin;
+{
+	unsigned sec, ms;
+
+	sec = cur_event_time.tv_sec % 86400;
+	ms = cur_event_time.tv_usec / 1000;
+	fprintf(logfile, "Msg %s %s:%u %u bytes %02u:%02u:%02u.%03u\n", dir,
+		inet_ntoa(sin->sin_addr), ntohs(sin->sin_port), msglen,
+		sec / 3600, (sec / 60) % 60, sec % 60, ms);
+	fwrite(msg, 1, msglen, logfile);
+	putc('\n', logfile);
+	fflush(logfile);
+}
+
+void
+log_sip_msg_rx(msg, msglen, sin)
+	char *msg;
+	unsigned msglen;
+	struct sockaddr_in *sin;
+{
+	if (!logfile)
+		return;
+	log_common(msg, msglen, sin, "from");
+}
+
+void
+log_sip_msg_tx(msg, msglen, sin)
+	char *msg;
+	unsigned msglen;
+	struct sockaddr_in *sin;
+{
+	if (!logfile)
+		return;
+	log_common(msg, msglen, sin, "to");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-v22/sip_udp.c	Thu Mar 07 02:33:49 2024 -0800
@@ -0,0 +1,83 @@
+/*
+ * In this module we implement our UDP socket for SIP,
+ * and the associated lowest-level protocol handling.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include "../libsip/parse.h"
+#include "../libsip/out_msg.h"
+
+extern struct in_addr sip_bind_ip;
+extern unsigned sip_bind_port;
+
+int sip_socket;
+
+open_sip_udp_socket()
+{
+	struct sockaddr_in sin;
+	int rc;
+
+	sip_socket = socket(AF_INET, SOCK_DGRAM, 0);
+	if (sip_socket < 0) {
+		perror("socket(AF_INET, SOCK_DGRAM, 0)");
+		exit(1);
+	}
+	sin.sin_family = AF_INET;
+	sin.sin_addr = sip_bind_ip;
+	sin.sin_port = htons(sip_bind_port);
+	rc = bind(sip_socket, (struct sockaddr *) &sin, sizeof sin);
+	if (rc < 0) {
+		perror("bind of SIP UDP socket");
+		exit(1);
+	}
+	return(0);
+}
+
+void
+sip_socket_select()
+{
+	struct sip_pkt_rx pkt;
+	struct sockaddr_in sin;
+	socklen_t addrlen;
+	int rc;
+
+	addrlen = sizeof sin;
+	rc = recvfrom(sip_socket, pkt.pkt_buffer, MAX_SIP_RX_PACKET, 0,
+			(struct sockaddr *) &sin, &addrlen);
+	if (rc <= 0) {
+		perror("recvfrom");
+		return;
+	}
+	pkt.pkt_length = rc;
+	log_sip_msg_rx(pkt.pkt_buffer, pkt.pkt_length, &sin);
+	rc = parse_incoming_sip_msg(&pkt);
+	if (rc < 0) {
+		printf("Incoming SIP UDP message parse error %d\n", rc);
+		return;
+	}
+	/* dispatch good-so-far SIP message */
+	if (pkt.parse_msgtype == SIP_MSG_TYPE_REQ)
+		process_sip_request(&pkt, &sin);
+	else if (pkt.parse_msgtype == SIP_MSG_TYPE_RESP)
+		process_sip_response(&pkt, &sin);
+}
+
+void
+sip_tx_packet(msg, sin)
+	struct sip_msg_out *msg;
+	struct sockaddr_in *sin;
+{
+	socklen_t addrlen;
+
+	addrlen = sizeof(struct sockaddr_in);
+	sendto(sip_socket, msg->buf, msg->msg_len, 0, (struct sockaddr *) sin,
+		addrlen);
+	log_sip_msg_tx(msg->buf, msg->msg_len, sin);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-v22/uac.c	Thu Mar 07 02:33:49 2024 -0800
@@ -0,0 +1,142 @@
+/*
+ * Here we implement processing of SIP responses to the requests we sent out.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "../libsip/parse.h"
+#include "../libsip/resp_ident.h"
+#include "../libsip/out_msg.h"
+
+#define	MAX_TO_TAG	63
+
+extern char *get_single_header();
+extern char *extract_to_tag();
+
+extern struct in_addr sip_bind_ip;
+extern unsigned sip_bind_port;
+extern char call_id[], from_uri[], to_uri[];
+extern unsigned max_forwards;
+
+char to_tag[MAX_TO_TAG+1];
+
+add_req_boilerplate(msg, cseq, add_to_tag)
+	struct sip_msg_out *msg;
+	char *cseq;
+{
+	char strbuf[256];
+	int rc;
+
+	sprintf(strbuf, "SIP/2.0/UDP %s:%u",
+		inet_ntoa(sip_bind_ip), sip_bind_port);
+	rc = out_msg_add_header(msg, "Via", strbuf);
+	if (rc < 0)
+		return rc;
+	rc = out_msg_add_header(msg, "From", from_uri);
+	if (rc < 0)
+		return rc;
+	if (add_to_tag && to_tag[0]) {
+		sprintf(strbuf, "<%s>;tag=%s", to_uri, to_tag);
+		rc = out_msg_add_header(msg, "To", strbuf);
+	} else
+		rc = out_msg_add_header(msg, "To", to_uri);
+	if (rc < 0)
+		return rc;
+	rc = out_msg_add_header(msg, "Call-ID", call_id);
+	if (rc < 0)
+		return rc;
+	rc = out_msg_add_header(msg, "CSeq", cseq);
+	if (rc < 0)
+		return rc;
+	sprintf(strbuf, "%u", max_forwards);
+	return out_msg_add_header(msg, "Max-Forwards", strbuf);
+}
+
+add_contact_header(msg)
+	struct sip_msg_out *msg;
+{
+	char strbuf[80];
+
+	sprintf(strbuf, "<sip:%s:%u;transport=udp>",
+		inet_ntoa(sip_bind_ip), sip_bind_port);
+	return out_msg_add_header(msg, "Contact", strbuf);
+}
+
+static void
+send_ack(sin)
+	struct sockaddr_in *sin;
+{
+	struct sip_msg_out msg;
+	int rc;
+
+	rc = start_request_out_msg(&msg, "ACK", to_uri);
+	if (rc < 0) {
+msg_size_err:	fprintf(stderr, "composing ACK message: size error\n");
+		return;
+	}
+	rc = add_req_boilerplate(&msg, "1 ACK", 1);
+	if (rc < 0)
+		goto msg_size_err;
+	out_msg_finish(&msg);
+	sip_tx_packet(&msg, sin);
+}
+
+static void
+handle_invite_response(msg, sin)
+	struct sip_pkt_rx *msg;
+	struct sockaddr_in *sin;
+{
+	char *tag;
+
+	printf("Response to INVITE: %s\n", msg->status_str);
+	tag = extract_to_tag(msg, to_uri);
+	if (tag) {
+		printf("To tag: %s\n", tag);
+		if (strlen(tag) <= MAX_TO_TAG)
+			strcpy(to_tag, tag);
+		else
+			printf("To tag exceeds length limit!\n");
+	}
+	extract_resp_sdp(msg);
+	if (msg->status_code >= 200) {
+		printf("Sending ACK\n");
+		send_ack(sin);
+		if (msg->status_code <= 299)
+			invite_200_rtpout();
+	}
+}
+
+void
+process_sip_response(msg, sin)
+	struct sip_pkt_rx *msg;
+	struct sockaddr_in *sin;
+{
+	struct sip_resp_ident rid;
+	int rc;
+
+	rc = sip_resp_extract_ident(msg, &rid);
+	if (rc < 0) {
+		printf("SIP %03u response: bad or missing %s header\n",
+			msg->status_code, rid.error_field);
+		return;
+	}
+	if (strcmp(rid.call_id, call_id)) {
+		printf("Got SIP response with wrong Call-ID\n");
+		return;
+	}
+	if (rid.cseq_num == 1 && !strcmp(rid.cseq_method, "INVITE"))
+		handle_invite_response(msg, sin);
+	else if (rid.cseq_num == 1 && !strcmp(rid.cseq_method, "CANCEL"))
+		printf("Response to CANCEL: %s\n", msg->status_str);
+	else if (rid.cseq_num == 2 && !strcmp(rid.cseq_method, "BYE"))
+		printf("Response to BYE: %s\n", msg->status_str);
+	else
+		printf("Got SIP resp for our Call-ID with unknown CSeq %u %s\n",
+			rid.cseq_num, rid.cseq_method);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-v22/uas.c	Thu Mar 07 02:33:49 2024 -0800
@@ -0,0 +1,63 @@
+/*
+ * UAS for sip-manual-out.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "../libsip/parse.h"
+#include "../libsip/uas_basic.h"
+#include "../libsip/out_msg.h"
+
+static void
+unsupported_method(req, ess, sin)
+	struct sip_pkt_rx *req;
+	struct uas_parse_hdrs *ess;
+	struct sockaddr_in *sin;
+{
+	struct sip_msg_out resp;
+	int rc;
+
+	printf("SIP %.16s request: unsupported method\n", req->req_method);
+	start_response_out_msg(&resp, "501 Method not supported");
+	rc = add_resp_basic_headers(&resp, ess, req->req_method);
+	if (rc < 0) {
+too_long:	fprintf(stderr,
+			"sending 501 error: response length exceeded\n");
+		return;
+	}
+	rc = out_msg_add_header(&resp, "Allow", "INVITE,ACK,BYE");
+	if (rc < 0)
+		goto too_long;
+	out_msg_finish(&resp);
+	sip_tx_packet(&resp, sin);
+}
+
+void
+process_sip_request(msg, sin)
+	struct sip_pkt_rx *msg;
+	struct sockaddr_in *sin;
+{
+	struct uas_parse_hdrs ess;
+	int rc;
+
+	rc = uas_get_basic_headers(msg, &ess);
+	if (rc < 0) {
+		printf("SIP %.16s request: bad or missing %s header\n",
+			msg->req_method, ess.error_field);
+		return;
+	}
+	/* dispatch by method */
+	if (!strcmp(msg->req_method, "INVITE"))
+		handle_invite_req(msg, &ess, sin);
+	else if (!strcmp(msg->req_method, "ACK"))
+		printf("Received ACK request, swallowing it\n");
+	else if (!strcmp(msg->req_method, "BYE"))
+		handle_bye_req(msg, &ess, sin);
+	else
+		unsupported_method(msg, &ess, sin);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-v22/user_cmd.c	Thu Mar 07 02:33:49 2024 -0800
@@ -0,0 +1,58 @@
+/*
+ * In this module we implement stdin command handling, supporting
+ * user-initiated CANCEL and BYE.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+static void
+pcm_fill_cmd(arg)
+	char *arg;
+{
+	char *cp;
+	unsigned octet;
+
+	for (cp = arg; isspace(*cp); cp++)
+		;
+	if (!isxdigit(*cp)) {
+inv_syntax:	fprintf(stderr, "error: pcm-fill command invalid syntax\n");
+		return;
+	}
+	octet = strtoul(cp, &cp, 16);
+	if (*cp)
+		goto inv_syntax;
+	if (octet > 0xFF) {
+		fprintf(stderr,
+			"error: pcm-fill octet argument out of range\n");
+		return;
+	}
+	set_pcm_fill_octet(octet);
+}
+
+void
+select_stdin()
+{
+	char buf[256], *cp;
+
+	fgets(buf, sizeof buf, stdin);
+	cp = index(buf, '\n');
+	if (cp) {
+		while (cp > buf && isspace(cp[-1]))
+			cp--;
+		*cp = '\0';
+	}
+	for (cp = buf; isspace(*cp); cp++)
+		;
+	if (!*cp)
+		return;
+	if (!strcmp(cp, "b") || !strcasecmp(cp, "bye"))
+		send_bye_req();
+	else if (!strcmp(cp, "c") || !strcasecmp(cp, "cancel"))
+		send_cancel_req();
+	else
+		fprintf(stderr, "error: non-understood stdin command\n");
+}