lists.osmocom.org
Sign In
Sign Up
Sign In
Sign Up
Manage this list
×
Keyboard Shortcuts
Thread View
j
: Next unread message
k
: Previous unread message
j a
: Jump to all threads
j l
: Jump to MailingList overview
2026
September
August
July
June
May
April
March
February
January
2025
December
November
October
September
August
July
June
May
April
March
February
January
2024
December
November
October
September
August
July
June
May
April
March
February
January
2023
December
November
October
September
August
July
June
May
April
March
February
January
2022
December
November
October
September
August
July
June
May
April
March
February
January
List overview
Download
gerrit-log
September 2026
----- 2026 -----
September 2026
August 2026
July 2026
June 2026
May 2026
April 2026
March 2026
February 2026
January 2026
----- 2025 -----
December 2025
November 2025
October 2025
September 2025
August 2025
July 2025
June 2025
May 2025
April 2025
March 2025
February 2025
January 2025
----- 2024 -----
December 2024
November 2024
October 2024
September 2024
August 2024
July 2024
June 2024
May 2024
April 2024
March 2024
February 2024
January 2024
----- 2023 -----
December 2023
November 2023
October 2023
September 2023
August 2023
July 2023
June 2023
May 2023
April 2023
March 2023
February 2023
January 2023
----- 2022 -----
December 2022
November 2022
October 2022
September 2022
August 2022
July 2022
June 2022
May 2022
April 2022
March 2022
February 2022
January 2022
gerrit-log@lists.osmocom.org
1 participants
1617 discussions
Start a n
N
ew thread
[M] Change in osmo-trx[master]: libosmo-trx/ep: make TRXDv2+ PDU batching configurable
by fixeria
17 Sep '26
17 Sep '26
fixeria has submitted this change. (
https://gerrit.osmocom.org/c/osmo-trx/+/43627?usp=email
) Change subject: libosmo-trx/ep: make TRXDv2+ PDU batching configurable ...................................................................... libosmo-trx/ep: make TRXDv2+ PDU batching configurable Change-Id: I60b8337db4ec8e70d37b2d9c0b75da338386ec47 Related: OS#6672 --- M libosmo-trx/include/osmocom/trx/ep.h M libosmo-trx/src/trx_ep.c M tests/libosmo-trx/trx_ep_test.c M tests/libosmo-trx/trx_ep_test.ok 4 files changed, 57 insertions(+), 4 deletions(-) Approvals: laforge: Looks good to me, approved pespin: Looks good to me, but someone else must approve Jenkins Builder: Verified diff --git a/libosmo-trx/include/osmocom/trx/ep.h b/libosmo-trx/include/osmocom/trx/ep.h index 0ffacde..18ec7a8 100644 --- a/libosmo-trx/include/osmocom/trx/ep.h +++ b/libosmo-trx/include/osmocom/trx/ep.h @@ -110,6 +110,9 @@ int osmo_trx_ep_set_ctrl_promisc(struct osmo_trx_ep *ep, bool enable); bool osmo_trx_ep_get_ctrl_promisc(const struct osmo_trx_ep *ep); +void osmo_trx_ep_set_pdu_batch(struct osmo_trx_ep *ep, bool enable); +bool osmo_trx_ep_get_pdu_batch(const struct osmo_trx_ep *ep); + /*! Per-channel TRXD PDU version in use (set after SETFORMAT negotiation) */ int osmo_trx_ep_set_pdu_ver(struct osmo_trx_ep *ep, unsigned int chan, uint8_t ver); int osmo_trx_ep_get_pdu_ver(const struct osmo_trx_ep *ep, unsigned int chan); diff --git a/libosmo-trx/src/trx_ep.c b/libosmo-trx/src/trx_ep.c index 1a478a8..0d78603 100644 --- a/libosmo-trx/src/trx_ep.c +++ b/libosmo-trx/src/trx_ep.c @@ -82,6 +82,9 @@ #define OSMO_TRX_EP_F_PENDING_FREE (1 << 2) /*! Leave the ctrl socket unconnected, accepting/replying to any peer */ #define OSMO_TRX_EP_F_CTRL_PROMISC (1 << 3) +/*! Send one datagram per PDU instead of batching a TDMA frame's worth of + * BURST.ind/req PDUs into one, even if TRXDv2+ is negotiated */ +#define OSMO_TRX_EP_F_NO_PDU_BATCH (1 << 4) struct osmo_trx_ep { uint32_t flags; /* see OSMO_TRX_EP_F_* */ @@ -800,6 +803,26 @@ return ep->flags & OSMO_TRX_EP_F_CTRL_PROMISC; } +/*! Enable/disable batching BURST.ind/req PDUs on the data sockets (default: + * true): when enabled and TRXDv2 (or higher) is negotiated on a channel, a + * TDMA frame's worth of PDUs is accumulated and sent in a single datagram + * (see osmo_trx_ep_send_burst_ind()/_req()); when disabled, every PDU is + * sent in its own datagram regardless of the negotiated TRXD PDU version, + * trading datagram count for latency. May be changed at any time. */ +void osmo_trx_ep_set_pdu_batch(struct osmo_trx_ep *ep, bool enable) +{ + if (enable) + ep->flags &= ~OSMO_TRX_EP_F_NO_PDU_BATCH; + else + ep->flags |= OSMO_TRX_EP_F_NO_PDU_BATCH; +} + +/*! Whether batching BURST.ind/req PDUs on the data sockets is enabled */ +bool osmo_trx_ep_get_pdu_batch(const struct osmo_trx_ep *ep) +{ + return (ep->flags & OSMO_TRX_EP_F_NO_PDU_BATCH) == 0; +} + /*! Set the name (log prefix) of the given instance, e.g. "phy0" */ int osmo_trx_ep_set_name(struct osmo_trx_ep *ep, const char *fmt, ...) { @@ -963,8 +986,8 @@ return rc; } - /* TRXDv2 and higher: wait for osmo_trx_ep_send_burst_fin() */ - if (c->pdu_ver >= 2) { + /* TRXDv2 and higher: wait for osmo_trx_ep_send_burst_fin() (unless disabled) */ + if (c->pdu_ver >= 2 && (~ep->flags & OSMO_TRX_EP_F_NO_PDU_BATCH)) { c->tx_msg = msg; return 0; } @@ -1007,8 +1030,8 @@ return rc; } - /* TRXDv2 and higher: wait for osmo_trx_ep_send_burst_fin() */ - if (c->pdu_ver >= 2) { + /* TRXDv2 and higher: wait for osmo_trx_ep_send_burst_fin() (unless disabled) */ + if (c->pdu_ver >= 2 && (~ep->flags & OSMO_TRX_EP_F_NO_PDU_BATCH)) { c->tx_msg = msg; return 0; } diff --git a/tests/libosmo-trx/trx_ep_test.c b/tests/libosmo-trx/trx_ep_test.c index 7b7a123..26267c3 100644 --- a/tests/libosmo-trx/trx_ep_test.c +++ b/tests/libosmo-trx/trx_ep_test.c @@ -279,6 +279,18 @@ printf("BURST.req batch flush\n"); flush_io(); + printf("=== %s(): BURST.req, TRXDv2 with batching disabled (BTS -> TRX) ===\n", __func__); + OSMO_ASSERT(osmo_trx_ep_get_pdu_batch(ep_bts) == true); + osmo_trx_ep_set_pdu_batch(ep_bts, false); + OSMO_ASSERT(osmo_trx_ep_get_pdu_batch(ep_bts) == false); + fill_burst_req(&br, 300000); + br.tn = 1; + OSMO_ASSERT(osmo_trx_ep_send_burst_req(ep_bts, 0, &br) == 0); + /* nothing was accumulated: there is nothing to flush */ + OSMO_ASSERT(osmo_trx_ep_send_burst_fin(ep_bts, 0) == -ENOMSG); + flush_io(); + osmo_trx_ep_set_pdu_batch(ep_bts, true); /* restore default */ + printf("=== %s(): BURST.ind batch, TRXDv2 with NOPE (TRX -> BTS) ===\n", __func__); /* an empty batch cannot be flushed */ OSMO_ASSERT(osmo_trx_ep_send_burst_fin(ep_trx, 0) == -ENOMSG); @@ -295,6 +307,17 @@ printf("BURST.ind batch flush\n"); flush_io(); + printf("=== %s(): BURST.ind, TRXDv2 with batching disabled (TRX -> BTS) ===\n", __func__); + OSMO_ASSERT(osmo_trx_ep_get_pdu_batch(ep_trx) == true); + osmo_trx_ep_set_pdu_batch(ep_trx, false); + OSMO_ASSERT(osmo_trx_ep_get_pdu_batch(ep_trx) == false); + fill_burst_ind(&bi, 300005); + OSMO_ASSERT(osmo_trx_ep_send_burst_ind(ep_trx, 0, &bi) == 0); + /* nothing was accumulated: there is nothing to flush */ + OSMO_ASSERT(osmo_trx_ep_send_burst_fin(ep_trx, 0) == -ENOMSG); + flush_io(); + osmo_trx_ep_set_pdu_batch(ep_trx, true); /* restore default */ + ep_close_free(ep_trx); ep_close_free(ep_bts); } diff --git a/tests/libosmo-trx/trx_ep_test.ok b/tests/libosmo-trx/trx_ep_test.ok index ad597dd..a633ada 100644 --- a/tests/libosmo-trx/trx_ep_test.ok +++ b/tests/libosmo-trx/trx_ep_test.ok @@ -22,10 +22,14 @@ BURST.req batch flush trx: rx_burst_req(chan=0): BURST.req tn=1 fn=200000 att=10 trx_num=0 mod=GMSK set=0 tsc=7 burst_len=148 trx: rx_burst_req(chan=0): BURST.req tn=2 fn=200000 att=10 trx_num=0 mod=GMSK set=0 tsc=7 burst_len=148 +=== test_burst_req_ind(): BURST.req, TRXDv2 with batching disabled (BTS -> TRX) === +trx: rx_burst_req(chan=0): BURST.req tn=1 fn=300000 att=10 trx_num=0 mod=GMSK set=0 tsc=7 burst_len=148 === test_burst_req_ind(): BURST.ind batch, TRXDv2 with NOPE (TRX -> BTS) === BURST.ind batch flush bts: rx_burst_ind(chan=0): NOPE.ind tn=5 fn=200005 trx_num=0 rssi=-63 toa256=-512 C/I=-150 cB bts: rx_burst_ind(chan=0): BURST.ind tn=6 fn=200005 trx_num=0 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=0 tsc=7 burst_len=148 +=== test_burst_req_ind(): BURST.ind, TRXDv2 with batching disabled (TRX -> BTS) === +bts: rx_burst_ind(chan=0): BURST.ind tn=5 fn=300005 trx_num=0 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=0 tsc=7 burst_len=148 === test_ctrl_close_flush(do_free=0): starting testcase === === test_ctrl_close_flush(): TRXC CMDs sent right before osmo_trx_ep_close() (BTS -> TRX) === bts: closed_cb() -- To view, visit
https://gerrit.osmocom.org/c/osmo-trx/+/43627?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged Gerrit-Project: osmo-trx Gerrit-Branch: master Gerrit-Change-Id: I60b8337db4ec8e70d37b2d9c0b75da338386ec47 Gerrit-Change-Number: 43627 Gerrit-PatchSet: 3 Gerrit-Owner: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: laforge <laforge(a)osmocom.org> Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
1
0
0
0
[M] Change in osmo-trx[master]: libosmo-trx/ep: allow ctrl socket to accept/reply to any peer
by fixeria
17 Sep '26
17 Sep '26
fixeria has submitted this change. (
https://gerrit.osmocom.org/c/osmo-trx/+/43601?usp=email
) ( 2 is the latest approved patch-set. No files were changed between the latest approved patch-set and the submitted one. )Change subject: libosmo-trx/ep: allow ctrl socket to accept/reply to any peer ...................................................................... libosmo-trx/ep: allow ctrl socket to accept/reply to any peer By default, the socket stays connect()ed to the configured peer, so the kernel silently drops datagrams from anyone else. This is fine for the normal osmo-bts/osmo-trx/trxcon use case, where both ends of the link are fixed and known ahead of time. Add an optional promiscuous mode that enables ttcn3-bts-test to inject path simulation TRXC commands from its own source port: when enabled, leave the ctrl socket unconnected and always reply to the actual sender of the last received CMD (tracked in chan->ctrl_peer), rather than only the configured peer. This mode will be used by the upcoming osmo-trx-proxy. Change-Id: I97075eb350e4270f4e909d493ba692e6b65be750 Related: OS#6672 --- M libosmo-trx/include/osmocom/trx/ep.h M libosmo-trx/src/trx_ep.c M tests/libosmo-trx/trx_ep_test.c M tests/libosmo-trx/trx_ep_test.err M tests/libosmo-trx/trx_ep_test.ok 5 files changed, 171 insertions(+), 25 deletions(-) Approvals: pespin: Looks good to me, but someone else must approve fixeria: Looks good to me, approved Jenkins Builder: Verified diff --git a/libosmo-trx/include/osmocom/trx/ep.h b/libosmo-trx/include/osmocom/trx/ep.h index af2c7be..0ffacde 100644 --- a/libosmo-trx/include/osmocom/trx/ep.h +++ b/libosmo-trx/include/osmocom/trx/ep.h @@ -106,6 +106,10 @@ int osmo_trx_ep_set_clock_socket(struct osmo_trx_ep *ep, bool enable); bool osmo_trx_ep_get_clock_socket(const struct osmo_trx_ep *ep); +/*! Unconnected ctrl socket accepting/replying to any peer; default: false */ +int osmo_trx_ep_set_ctrl_promisc(struct osmo_trx_ep *ep, bool enable); +bool osmo_trx_ep_get_ctrl_promisc(const struct osmo_trx_ep *ep); + /*! Per-channel TRXD PDU version in use (set after SETFORMAT negotiation) */ int osmo_trx_ep_set_pdu_ver(struct osmo_trx_ep *ep, unsigned int chan, uint8_t ver); int osmo_trx_ep_get_pdu_ver(const struct osmo_trx_ep *ep, unsigned int chan); diff --git a/libosmo-trx/src/trx_ep.c b/libosmo-trx/src/trx_ep.c index 35cabbd..1a478a8 100644 --- a/libosmo-trx/src/trx_ep.c +++ b/libosmo-trx/src/trx_ep.c @@ -33,6 +33,7 @@ #include <stdarg.h> #include <unistd.h> +#include <sys/socket.h> #include <netinet/in.h> #include <osmocom/core/talloc.h> @@ -59,12 +60,16 @@ struct osmo_io_fd *data_iofd; uint8_t pdu_ver; /* TRXD PDU version in use */ struct msgb *tx_msg; /* pending TRXDv2 Tx batch */ - /* Bytes handed to osmo_iofd_write_msgb() on ctrl_iofd but not yet - * completed (per trx_ep_ctrl_write_cb()), used by trx_ep_ctrl_close() + /* Bytes handed to osmo_iofd_sendto_msgb() on ctrl_iofd but not yet + * completed (per trx_ep_ctrl_sendto_cb()), used by trx_ep_ctrl_close() * to tell whether a flush is needed on teardown. Non-zero here also * means ctrl_iofd is currently being flushed asynchronously after * osmo_trx_ep_close(); see osmo_trx_ep_is_closing(). */ size_t ctrl_wr_pending; + /* Destination for the next ctrl message sent on ctrl_iofd. + * Initialized to the configured peer at open, updated in + * trx_ep_ctrl_recvfrom_cb(). */ + struct osmo_sockaddr ctrl_peer; }; /*! Sockets currently bound */ @@ -72,9 +77,11 @@ /*! Enable the clock socket (base_port + 0) */ #define OSMO_TRX_EP_F_CLOCK_SOCKET (1 << 1) /*! osmo_trx_ep_free() was called while a chan's ctrl socket was still - * flushing: the actual free is deferred to trx_ep_ctrl_close_write_cb(), + * flushing: the actual free is deferred to trx_ep_ctrl_close_sendto_cb(), * once the last one completes. */ #define OSMO_TRX_EP_F_PENDING_FREE (1 << 2) +/*! Leave the ctrl socket unconnected, accepting/replying to any peer */ +#define OSMO_TRX_EP_F_CTRL_PROMISC (1 << 3) struct osmo_trx_ep { uint32_t flags; /* see OSMO_TRX_EP_F_* */ @@ -173,7 +180,8 @@ msgb_free(msg); } -static void trx_ep_ctrl_read_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg) +static void trx_ep_ctrl_recvfrom_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg, + const struct osmo_sockaddr *saddr) { struct osmo_trx_ep_chan *chan = osmo_iofd_get_data(iofd); struct osmo_trx_ep *ep = chan->ep; @@ -183,6 +191,10 @@ if (res <= 0) goto ret_free_msg; + /* Remember the sender of every datagram received on ctrl_iofd, + * so the next reply goes back to whoever actually sent it. */ + chan->ctrl_peer = *saddr; + rc = osmo_trxc_msg_parse(&tmsg, (const char *)msgb_data(msg), msgb_length(msg)); if (rc < 0) { LOGEPCH(ep, chan->num, LOGL_NOTICE, "Rx malformed TRXC message (rc=%d)\n", rc); @@ -250,7 +262,8 @@ /* Track bytes still pending on a channel's ctrl_iofd, so trx_ep_ctrl_close() * can tell whether anything is still in flight at teardown time. */ -static void trx_ep_ctrl_write_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg) +static void trx_ep_ctrl_sendto_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg, + const struct osmo_sockaddr *daddr) { struct osmo_trx_ep_chan *chan = osmo_iofd_get_data(iofd); @@ -282,7 +295,8 @@ * the iofd and, if osmo_trx_ep_free() was called on the (kept alive) * endpoint in the meantime and no other channel is still flushing, * finally frees the endpoint too. */ -static void trx_ep_ctrl_close_write_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg) +static void trx_ep_ctrl_close_sendto_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg, + const struct osmo_sockaddr *daddr) { struct osmo_trx_ep_chan *chan = osmo_iofd_get_data(iofd); struct osmo_trx_ep *ep = chan->ep; @@ -324,7 +338,7 @@ } static const struct osmo_io_ops trx_ep_ctrl_close_ioops = { - .write_cb = &trx_ep_ctrl_close_write_cb, + .sendto_cb = &trx_ep_ctrl_close_sendto_cb, }; /* Close a channel's ctrl_iofd, flushing (best-effort) any still-in-flight @@ -357,8 +371,8 @@ }; static const struct osmo_io_ops trx_ep_ctrl_ioops = { - .read_cb = &trx_ep_ctrl_read_cb, - .write_cb = &trx_ep_ctrl_write_cb, + .recvfrom_cb = &trx_ep_ctrl_recvfrom_cb, + .sendto_cb = &trx_ep_ctrl_sendto_cb, }; static const struct osmo_io_ops trx_ep_data_ioops = { @@ -366,26 +380,34 @@ .write_cb = &trx_ep_write_cb, }; -/* Open a single UDP socket (base port + ofs) and set up osmo_io for it */ +/* Open a single UDP socket (base port + ofs) and set up osmo_io for it. + * connect_socket == false leaves the socket unconnected (bind-only), + * accepting datagrams from and replying to any peer. */ static struct osmo_io_fd *trx_ep_open_iofd(struct osmo_trx_ep *ep, uint16_t ofs, const struct osmo_io_ops *ioops, - unsigned int buf_size, void *data) + enum osmo_io_fd_mode mode, + unsigned int buf_size, void *data, + bool connect_socket) { + unsigned int flags = OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK; char sock_name[OSMO_SOCK_NAME_MAXLEN]; struct osmo_io_fd *iofd; int fd; + if (connect_socket) + flags |= OSMO_SOCK_F_CONNECT; + fd = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, ep->laddr, trx_ep_port(ep, true, ofs), ep->raddr, trx_ep_port(ep, false, ofs), - OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_NONBLOCK); + flags); if (fd < 0) { LOGEP(ep, LOGL_ERROR, "Failed to open a socket (ofs=%u): %d\n", ofs, fd); return NULL; } osmo_sock_get_name_buf(sock_name, sizeof(sock_name), fd); - iofd = osmo_iofd_setup(ep, fd, sock_name, OSMO_IO_FD_MODE_READ_WRITE, ioops, data); + iofd = osmo_iofd_setup(ep, fd, sock_name, mode, ioops, data); if (iofd == NULL) { close(fd); return NULL; @@ -415,16 +437,36 @@ static int trx_ep_chan_open(struct osmo_trx_ep_chan *chan) { struct osmo_trx_ep *ep = chan->ep; + const bool connect_ctrl = !(ep->flags & OSMO_TRX_EP_F_CTRL_PROMISC); chan->ctrl_iofd = trx_ep_open_iofd(ep, 2 * chan->num + 1, &trx_ep_ctrl_ioops, - OSMO_TRXC_MSG_BUF_SIZE, chan); + OSMO_IO_FD_MODE_RECVFROM_SENDTO, + OSMO_TRXC_MSG_BUF_SIZE, chan, connect_ctrl); if (chan->ctrl_iofd == NULL) { LOGEPCH(ep, chan->num, LOGL_ERROR, "Failed to open TRXC socket\n"); return -EIO; } + /* connect()ed: seed ctrl_peer with the actual peer so a CMD sent + * from the L1 side (osmo_trx_ep mode L1) before ever receiving + * anything has somewhere to go; trx_ep_ctrl_recvfrom_cb() keeps it + * up to date afterwards. */ + if (connect_ctrl) { + struct osmo_sockaddr *peer = &chan->ctrl_peer; + socklen_t peer_len = sizeof(peer->u); + + if (getpeername(osmo_iofd_get_fd(chan->ctrl_iofd), &peer->u.sa, &peer_len) < 0) { + /* not fatal: ctrl_peer just stays unset until the first inbound + * datagram updates it via trx_ep_ctrl_recvfrom_cb() */ + LOGEPCH(ep, chan->num, LOGL_ERROR, + "getpeername() failed on TRXC socket: %s\n", + strerror(errno)); + } + } + chan->data_iofd = trx_ep_open_iofd(ep, 2 * chan->num + 2, &trx_ep_data_ioops, - TRX_EP_DATA_BUF_SIZE, chan); + OSMO_IO_FD_MODE_READ_WRITE, + TRX_EP_DATA_BUF_SIZE, chan, true); if (chan->data_iofd == NULL) { LOGEPCH(ep, chan->num, LOGL_ERROR, "Failed to open TRXD socket\n"); return -EIO; @@ -438,7 +480,7 @@ { /* trx_ep_ctrl_close() clears chan->ctrl_iofd itself, but only once * it's actually safe to: immediately if nothing was pending, or - * later from trx_ep_ctrl_close_write_cb() if a flush is needed. */ + * later from trx_ep_ctrl_close_sendto_cb() if a flush is needed. */ trx_ep_ctrl_close(chan); osmo_iofd_free(chan->data_iofd); chan->data_iofd = NULL; @@ -504,7 +546,8 @@ if (ep->flags & OSMO_TRX_EP_F_CLOCK_SOCKET) { ep->clck_iofd = trx_ep_open_iofd(ep, 0, &trx_ep_clck_ioops, - OSMO_TRXC_MSG_BUF_SIZE, ep); + OSMO_IO_FD_MODE_READ_WRITE, + OSMO_TRXC_MSG_BUF_SIZE, ep, true); if (ep->clck_iofd == NULL) goto ret_error; } @@ -551,7 +594,7 @@ /*! Free the given endpoint instance (closes all sockets). If a ctrl socket * is still flushing (see osmo_trx_ep_is_closing()), the endpoint itself is - * kept alive until the flush completes (see trx_ep_ctrl_close_write_cb()), + * kept alive until the flush completes (see trx_ep_ctrl_close_sendto_cb()), * which then finishes this deferred free. */ void osmo_trx_ep_free(struct osmo_trx_ep *ep) { @@ -734,6 +777,29 @@ return ep->flags & OSMO_TRX_EP_F_CLOCK_SOCKET; } +/*! Enable/disable promiscuous ctrl socket mode (default: false): leaves the + * ctrl socket unconnected, accepting TRXC PDUs from any peer instead of only + * the configured one, e.g. a test tool injecting extra TRXC commands from + * its own socket. The RSP always goes back to whoever actually sent the + * CMD, promiscuous or not (see trx_ep_ctrl_recvfrom_cb() and chan->ctrl_peer). */ +int osmo_trx_ep_set_ctrl_promisc(struct osmo_trx_ep *ep, bool enable) +{ + if (ep->flags & OSMO_TRX_EP_F_OPEN) + return -EBUSY; + + if (enable) + ep->flags |= OSMO_TRX_EP_F_CTRL_PROMISC; + else + ep->flags &= ~OSMO_TRX_EP_F_CTRL_PROMISC; + return 0; +} + +/*! Whether promiscuous ctrl socket mode is enabled */ +bool osmo_trx_ep_get_ctrl_promisc(const struct osmo_trx_ep *ep) +{ + return ep->flags & OSMO_TRX_EP_F_CTRL_PROMISC; +} + /*! Set the name (log prefix) of the given instance, e.g. "phy0" */ int osmo_trx_ep_set_name(struct osmo_trx_ep *ep, const char *fmt, ...) { @@ -820,11 +886,13 @@ int osmo_trx_ep_send_ctrl_msg(struct osmo_trx_ep *ep, unsigned int chan, const struct osmo_trxc_msg *tmsg) { + struct osmo_trx_ep_chan *c; struct msgb *msg; size_t len; int rc; OSMO_ASSERT(chan < ep->num_chans); + c = &ep->chans[chan]; msg = msgb_alloc_c(ep, OSMO_TRXC_MSG_BUF_SIZE, "trx_ep_ctrl_tx"); rc = osmo_trxc_msg_build((char *)msgb_data(msg), msgb_tailroom(msg), tmsg); @@ -835,11 +903,11 @@ msgb_put(msg, rc); len = msgb_length(msg); - rc = osmo_iofd_write_msgb(ep->chans[chan].ctrl_iofd, msg); + rc = osmo_iofd_sendto_msgb(c->ctrl_iofd, msg, 0, &c->ctrl_peer); if (rc < 0) msgb_free(msg); else - ep->chans[chan].ctrl_wr_pending += len; + c->ctrl_wr_pending += len; return rc; } diff --git a/tests/libosmo-trx/trx_ep_test.c b/tests/libosmo-trx/trx_ep_test.c index 7f96540..7b7a123 100644 --- a/tests/libosmo-trx/trx_ep_test.c +++ b/tests/libosmo-trx/trx_ep_test.c @@ -27,6 +27,11 @@ #include <errno.h> #include <stdio.h> #include <string.h> +#include <unistd.h> + +#include <arpa/inet.h> +#include <netinet/in.h> +#include <sys/socket.h> #include <osmocom/core/application.h> #include <osmocom/core/logging.h> @@ -37,6 +42,7 @@ #include <osmocom/trx/ep.h> #define TEST_BASE_PORT 16700 +#define TEST_PROMISC_BASE_PORT 16720 static void *test_ctx = NULL; @@ -348,6 +354,66 @@ ep_close_free(ep_trx); } +/* osmo_trx_ep_{get,set}_ctrl_promisc(): with promisc enabled, a channel's + * ctrl socket is left unconnected (bind-only), so it accepts a CMD from any + * peer, not just the configured raddr, and replies to whoever actually sent + * it - e.g. a test tool injecting extra TRXC commands from its own socket. */ +static void test_ctrl_promisc(void) +{ + struct sockaddr_in dst = { .sin_family = AF_INET }; + struct sockaddr_in from; + socklen_t from_len; + struct osmo_trx_ep *ep; + int fd, rc, len; + + printf("=== %s(): starting testcase ===\n", __func__); + + ep = ep_alloc("promisc", OSMO_TRX_EP_MODE_TRX); + osmo_trx_ep_set_base_port(ep, TEST_PROMISC_BASE_PORT); + + OSMO_ASSERT(osmo_trx_ep_get_ctrl_promisc(ep) == false); + OSMO_ASSERT(osmo_trx_ep_set_ctrl_promisc(ep, true) == 0); + OSMO_ASSERT(osmo_trx_ep_get_ctrl_promisc(ep) == true); + + ep_open(ep); + + /* config setters, including this one, are expected to fail once open */ + OSMO_ASSERT(osmo_trx_ep_set_ctrl_promisc(ep, false) == -EBUSY); + + /* chan 0 ctrl; source port of the foreign peer is left to the kernel + * (no bind() before sendto() below) */ + dst.sin_port = htons(TEST_PROMISC_BASE_PORT + 1); + OSMO_ASSERT(inet_pton(AF_INET, "127.0.0.1", &dst.sin_addr) == 1); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + OSMO_ASSERT(fd >= 0); + + char buf[OSMO_TRXC_MSG_BUF_SIZE]; + static const struct osmo_trxc_msg cmd_poweron = { + .type = OSMO_TRXC_MT_CMD, + .cmd = OSMO_TRXC_CMD_POWERON, + }; + + len = osmo_trxc_msg_build(buf, sizeof(buf), &cmd_poweron); + OSMO_ASSERT(len > 0); + rc = sendto(fd, buf, len, 0, (const struct sockaddr *)&dst, sizeof(dst)); + OSMO_ASSERT(rc == len); + + flush_io(); + + /* the RSP must come back to us (the foreign sender), not to raddr */ + from_len = sizeof(from); + rc = recvfrom(fd, buf, sizeof(buf) - 1, MSG_DONTWAIT, + (struct sockaddr *)&from, &from_len); + OSMO_ASSERT(rc > 0); + buf[rc] = '\0'; + OSMO_ASSERT(from.sin_addr.s_addr == dst.sin_addr.s_addr); + printf("foreign rx: '%s'\n", buf); + + close(fd); + ep_close_free(ep); +} + int main(int argc, char **argv) { test_ctx = talloc_named_const(NULL, 0, "trx_ep_test"); @@ -365,6 +431,8 @@ test_ctrl_close_flush(false); test_ctrl_close_flush(true); + test_ctrl_promisc(); + printf("Done\n"); return 0; } diff --git a/tests/libosmo-trx/trx_ep_test.err b/tests/libosmo-trx/trx_ep_test.err index 4f7e5e9..f786f6b 100644 --- a/tests/libosmo-trx/trx_ep_test.err +++ b/tests/libosmo-trx/trx_ep_test.err @@ -22,8 +22,8 @@ DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close(): 12 byte(s) still pending, flushing asynchronously DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close(): 12 byte(s) still pending, flushing asynchronously DLGLOBAL DEBUG (ep=ep_bts, chan=2) trx_ep_ctrl_close(): nothing pending, closing immediately -DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close_write_cb(): wrote 12 byte(s), flush completed -DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close_write_cb(): wrote 12 byte(s), flush completed +DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close_sendto_cb(): wrote 12 byte(s), flush completed +DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close_sendto_cb(): wrote 12 byte(s), flush completed DLGLOBAL INFO (ep=ep_trx) Closing TRXC/TRXD connections l=127.0.0.1:16700<->r=127.0.0.1:16800 DLGLOBAL DEBUG (ep=ep_trx, chan=0) trx_ep_ctrl_close(): nothing pending, closing immediately DLGLOBAL DEBUG (ep=ep_trx, chan=1) trx_ep_ctrl_close(): nothing pending, closing immediately @@ -34,10 +34,13 @@ DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close(): 12 byte(s) still pending, flushing asynchronously DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close(): 12 byte(s) still pending, flushing asynchronously DLGLOBAL DEBUG (ep=ep_bts, chan=2) trx_ep_ctrl_close(): nothing pending, closing immediately -DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close_write_cb(): wrote 12 byte(s), flush completed -DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close_write_cb(): wrote 12 byte(s), flush completed -DLGLOBAL DEBUG (ep=ep_bts) trx_ep_ctrl_close_write_cb(): last flush completed, free()ing +DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close_sendto_cb(): wrote 12 byte(s), flush completed +DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close_sendto_cb(): wrote 12 byte(s), flush completed +DLGLOBAL DEBUG (ep=ep_bts) trx_ep_ctrl_close_sendto_cb(): last flush completed, free()ing DLGLOBAL INFO (ep=ep_trx) Closing TRXC/TRXD connections l=127.0.0.1:16700<->r=127.0.0.1:16800 DLGLOBAL DEBUG (ep=ep_trx, chan=0) trx_ep_ctrl_close(): nothing pending, closing immediately DLGLOBAL DEBUG (ep=ep_trx, chan=1) trx_ep_ctrl_close(): nothing pending, closing immediately DLGLOBAL DEBUG (ep=ep_trx, chan=2) trx_ep_ctrl_close(): nothing pending, closing immediately +DLGLOBAL INFO (ep=ep_promisc) Opening TRXC/TRXD connections l=127.0.0.1:16720<->r=127.0.0.1:16820 +DLGLOBAL INFO (ep=ep_promisc) Closing TRXC/TRXD connections l=127.0.0.1:16720<->r=127.0.0.1:16820 +DLGLOBAL DEBUG (ep=ep_promisc, chan=0) trx_ep_ctrl_close(): nothing pending, closing immediately diff --git a/tests/libosmo-trx/trx_ep_test.ok b/tests/libosmo-trx/trx_ep_test.ok index 194e5d5..ad597dd 100644 --- a/tests/libosmo-trx/trx_ep_test.ok +++ b/tests/libosmo-trx/trx_ep_test.ok @@ -36,4 +36,7 @@ bts: closed_cb() trx: rx_ctrl_msg(chan=0): 'CMD POWEROFF' trx: rx_ctrl_msg(chan=1): 'CMD RFMUTE 1' +=== test_ctrl_promisc(): starting testcase === +promisc: rx_ctrl_msg(chan=0): 'CMD POWERON' +foreign rx: 'RSP POWERON 0' Done -- To view, visit
https://gerrit.osmocom.org/c/osmo-trx/+/43601?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged Gerrit-Project: osmo-trx Gerrit-Branch: master Gerrit-Change-Id: I97075eb350e4270f4e909d493ba692e6b65be750 Gerrit-Change-Number: 43601 Gerrit-PatchSet: 4 Gerrit-Owner: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: laforge <laforge(a)osmocom.org> Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
1
0
0
0
[L] Change in osmo-trx[master]: libosmo-trx/ep: flush pending TRXC messages on endpoint teardown
by fixeria
17 Sep '26
17 Sep '26
fixeria has submitted this change. (
https://gerrit.osmocom.org/c/osmo-trx/+/43112?usp=email
) Change subject: libosmo-trx/ep: flush pending TRXC messages on endpoint teardown ...................................................................... libosmo-trx/ep: flush pending TRXC messages on endpoint teardown osmo_trx_ep_close() used to drop any still-queued ctrl socket Tx data via osmo_iofd_free(), including a 'goodbye' TRXC message (i.e. "CMD POWEROFF") that may have just been enqueued right before teardown. Track bytes still pending on each channel's ctrl_iofd (ctrl_wr_pending) and, if any remain at close() time, swap the iofd's write_cb instead of freeing it right away: keep waiting until every enqueued byte has actually completed (or a write fails), then finally free it. Data sockets and pending Tx data batches (TRXDv2 batching) are still dropped immediately, as before. Expose the "still flushing" state via the new osmo_trx_ep_is_closing(), so a caller can tell it apart from fully closed: re-opening the same ports while a flush is still in flight would otherwise risk a confusing EADDRINUSE, so osmo_trx_ep_open() and osmo_trx_ep_set_num_chans() now return -EBUSY in that case. osmo_trx_ep_free() called while still closing reparents the whole endpoint to OTC_GLOBAL and defers the actual free until the last flush completes, instead of dropping the still-in-flight state. This follows the async-flush pattern used by osmo-pcap's osmo_pcap_wr_file_flush()/_is_flushing(), adapted to the osmo_io API (there is no osmo_iofd_flush()). Change-Id: I69e6a3bcf49afc3cfca4a72afdf459625cb91e56 --- M libosmo-trx/include/osmocom/trx/ep.h M libosmo-trx/src/trx_ep.c M tests/libosmo-trx/trx_ep_test.c M tests/libosmo-trx/trx_ep_test.err M tests/libosmo-trx/trx_ep_test.ok 5 files changed, 297 insertions(+), 10 deletions(-) Approvals: Jenkins Builder: Verified laforge: Looks good to me, approved pespin: Looks good to me, but someone else must approve diff --git a/libosmo-trx/include/osmocom/trx/ep.h b/libosmo-trx/include/osmocom/trx/ep.h index e3c855c..af2c7be 100644 --- a/libosmo-trx/include/osmocom/trx/ep.h +++ b/libosmo-trx/include/osmocom/trx/ep.h @@ -69,6 +69,13 @@ void osmo_trx_ep_close(struct osmo_trx_ep *ep); void osmo_trx_ep_free(struct osmo_trx_ep *ep); bool osmo_trx_ep_is_open(const struct osmo_trx_ep *ep); +bool osmo_trx_ep_is_closing(const struct osmo_trx_ep *ep); + +/*! Called once osmo_trx_ep_close() has fully completed (see + * osmo_trx_ep_set_closed_cb()) */ +typedef void (*osmo_trx_ep_closed_cb_t)(struct osmo_trx_ep *ep); +void osmo_trx_ep_set_closed_cb(struct osmo_trx_ep *ep, osmo_trx_ep_closed_cb_t closed_cb); + void osmo_trx_ep_set_priv(struct osmo_trx_ep *ep, void *priv); void *osmo_trx_ep_get_priv(const struct osmo_trx_ep *ep); int osmo_trx_ep_set_name(struct osmo_trx_ep *ep, const char *fmt, ...); diff --git a/libosmo-trx/src/trx_ep.c b/libosmo-trx/src/trx_ep.c index e12fff9..35cabbd 100644 --- a/libosmo-trx/src/trx_ep.c +++ b/libosmo-trx/src/trx_ep.c @@ -59,12 +59,22 @@ struct osmo_io_fd *data_iofd; uint8_t pdu_ver; /* TRXD PDU version in use */ struct msgb *tx_msg; /* pending TRXDv2 Tx batch */ + /* Bytes handed to osmo_iofd_write_msgb() on ctrl_iofd but not yet + * completed (per trx_ep_ctrl_write_cb()), used by trx_ep_ctrl_close() + * to tell whether a flush is needed on teardown. Non-zero here also + * means ctrl_iofd is currently being flushed asynchronously after + * osmo_trx_ep_close(); see osmo_trx_ep_is_closing(). */ + size_t ctrl_wr_pending; }; /*! Sockets currently bound */ #define OSMO_TRX_EP_F_OPEN (1 << 0) /*! Enable the clock socket (base_port + 0) */ #define OSMO_TRX_EP_F_CLOCK_SOCKET (1 << 1) +/*! osmo_trx_ep_free() was called while a chan's ctrl socket was still + * flushing: the actual free is deferred to trx_ep_ctrl_close_write_cb(), + * once the last one completes. */ +#define OSMO_TRX_EP_F_PENDING_FREE (1 << 2) struct osmo_trx_ep { uint32_t flags; /* see OSMO_TRX_EP_F_* */ @@ -78,6 +88,10 @@ struct osmo_io_fd *clck_iofd; struct osmo_trx_ep_chan *chans; /* array of num_chans channels */ unsigned int num_chans; + /* called once osmo_trx_ep_close() has fully completed, i.e. every + * ctrl chan is closed (immediately if nothing needed flushing, or + * once the last async flush finishes); see osmo_trx_ep_set_closed_cb() */ + osmo_trx_ep_closed_cb_t closed_cb; }; /*! Default base UDP port, see osmo_trx_ep_set_base_port() */ @@ -234,10 +248,109 @@ /* nothing to do, but osmo_io requires a write call-back */ } +/* Track bytes still pending on a channel's ctrl_iofd, so trx_ep_ctrl_close() + * can tell whether anything is still in flight at teardown time. */ +static void trx_ep_ctrl_write_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg) +{ + struct osmo_trx_ep_chan *chan = osmo_iofd_get_data(iofd); + + if (res > 0) { + OSMO_ASSERT((size_t)res <= chan->ctrl_wr_pending); + chan->ctrl_wr_pending -= res; + } else { + /* discard: nothing will complete this write again, so a + * stale non-zero count here would wedge trx_ep_ctrl_close() + * into (uselessly) waiting for it forever on teardown */ + LOGEPCH(chan->ep, chan->num, LOGL_ERROR, + "%s(): write failed (res=%d), discarding %zu pending byte(s)\n", + __func__, res, chan->ctrl_wr_pending); + chan->ctrl_wr_pending = 0; + } +} + /*********************************************************************** * open/close ***********************************************************************/ +/*! Write call-back for a channel's ctrl_iofd while it is being flushed + * asynchronously after osmo_trx_ep_close(): a 'goodbye' TRXC message + * (e.g. "CMD POWEROFF") may have been enqueued via + * osmo_trx_ep_send_ctrl_msg() right before tearing down the endpoint; + * osmo_iofd_free() would otherwise drop it together with the (now + * pointless) rest of the Tx queue. Keeps waiting until every byte + * enqueued on it has actually completed (or a write fails), then frees + * the iofd and, if osmo_trx_ep_free() was called on the (kept alive) + * endpoint in the meantime and no other channel is still flushing, + * finally frees the endpoint too. */ +static void trx_ep_ctrl_close_write_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg) +{ + struct osmo_trx_ep_chan *chan = osmo_iofd_get_data(iofd); + struct osmo_trx_ep *ep = chan->ep; + + if (res > 0) { + OSMO_ASSERT((size_t)res <= chan->ctrl_wr_pending); + chan->ctrl_wr_pending -= res; + /* keep waiting until every enqueued byte has actually completed */ + if (chan->ctrl_wr_pending > 0) { + LOGEPCH(ep, chan->num, LOGL_DEBUG, + "%s(): wrote %d byte(s), %zu still pending\n", + __func__, res, chan->ctrl_wr_pending); + return; + } + LOGEPCH(ep, chan->num, LOGL_DEBUG, + "%s(): wrote %d byte(s), flush completed\n", + __func__, res); + } else { + LOGEPCH(ep, chan->num, LOGL_ERROR, + "%s(): flush aborted (res=%d), discarding %zu pending byte(s)\n", + __func__, res, chan->ctrl_wr_pending); + chan->ctrl_wr_pending = 0; + } + + osmo_iofd_free(chan->ctrl_iofd); + chan->ctrl_iofd = NULL; + + if (osmo_trx_ep_is_closing(ep)) + return; /* other ctrl chans are still closing */ + + if (ep->closed_cb != NULL) + ep->closed_cb(ep); + + if (~ep->flags & OSMO_TRX_EP_F_PENDING_FREE) + return; /* deferred free() is not pending */ + + LOGEP(ep, LOGL_DEBUG, "%s(): last flush completed, free()ing\n", __func__); + talloc_free(ep); +} + +static const struct osmo_io_ops trx_ep_ctrl_close_ioops = { + .write_cb = &trx_ep_ctrl_close_write_cb, +}; + +/* Close a channel's ctrl_iofd, flushing (best-effort) any still-in-flight + * Tx data first instead of dropping it immediately. */ +static void trx_ep_ctrl_close(struct osmo_trx_ep_chan *chan) +{ + struct osmo_io_fd *iofd = chan->ctrl_iofd; + + if (iofd == NULL) + return; + + if (chan->ctrl_wr_pending == 0) { + LOGEPCH(chan->ep, chan->num, LOGL_DEBUG, + "%s(): nothing pending, closing immediately\n", __func__); + osmo_iofd_free(iofd); + chan->ctrl_iofd = NULL; + return; + } + + LOGEPCH(chan->ep, chan->num, LOGL_DEBUG, + "%s(): %zu byte(s) still pending, flushing asynchronously\n", + __func__, chan->ctrl_wr_pending); + + osmo_iofd_set_ioops(iofd, &trx_ep_ctrl_close_ioops); +} + static const struct osmo_io_ops trx_ep_clck_ioops = { .read_cb = &trx_ep_clck_read_cb, .write_cb = &trx_ep_write_cb, @@ -245,7 +358,7 @@ static const struct osmo_io_ops trx_ep_ctrl_ioops = { .read_cb = &trx_ep_ctrl_read_cb, - .write_cb = &trx_ep_write_cb, + .write_cb = &trx_ep_ctrl_write_cb, }; static const struct osmo_io_ops trx_ep_data_ioops = { @@ -323,8 +436,10 @@ /* Close a channel's ctrl+data sockets and drop its pending Tx batch */ static void trx_ep_chan_close(struct osmo_trx_ep_chan *chan) { - osmo_iofd_free(chan->ctrl_iofd); - chan->ctrl_iofd = NULL; + /* trx_ep_ctrl_close() clears chan->ctrl_iofd itself, but only once + * it's actually safe to: immediately if nothing was pending, or + * later from trx_ep_ctrl_close_write_cb() if a flush is needed. */ + trx_ep_ctrl_close(chan); osmo_iofd_free(chan->data_iofd); chan->data_iofd = NULL; msgb_free(chan->tx_msg); @@ -362,13 +477,18 @@ } /*! Open the clock/ctrl/data sockets of the given endpoint. - * \returns 0 on success; -EALREADY if already open; other negative - * values on error (all sockets closed) */ + * \returns 0 on success; -EALREADY if already open; -EBUSY if a previous + * osmo_trx_ep_close() is still flushing a ctrl socket (see + * osmo_trx_ep_is_closing()); other negative values on error (all sockets + * closed) */ int osmo_trx_ep_open(struct osmo_trx_ep *ep) { if (ep->flags & OSMO_TRX_EP_F_OPEN) return -EALREADY; + if (osmo_trx_ep_is_closing(ep)) + return -EBUSY; + if (ep->laddr == NULL || ep->raddr == NULL) return -EINVAL; @@ -401,8 +521,11 @@ return -EIO; } -/*! Close all sockets of the given endpoint (drops pending Tx batches). - * No-op if not opened. */ +/*! Close all sockets of the given endpoint. No-op if not opened. + * Any TRXC message still queued on a ctrl socket (e.g. a 'goodbye' + * "CMD POWEROFF" sent right before teardown) is flushed asynchronously + * (best-effort) instead of being dropped; pending Tx data batches are + * dropped. */ void osmo_trx_ep_close(struct osmo_trx_ep *ep) { if (~ep->flags & OSMO_TRX_EP_F_OPEN) @@ -419,14 +542,34 @@ trx_ep_chan_close(&ep->chans[i]); ep->flags &= ~OSMO_TRX_EP_F_OPEN; + + /* if any chan started an async flush, trx_ep_ctrl_close_sendto_cb() + * calls closed_cb() once the last one completes instead */ + if (ep->closed_cb != NULL && !osmo_trx_ep_is_closing(ep)) + ep->closed_cb(ep); } -/*! Free the given endpoint instance (closes all sockets) */ +/*! Free the given endpoint instance (closes all sockets). If a ctrl socket + * is still flushing (see osmo_trx_ep_is_closing()), the endpoint itself is + * kept alive until the flush completes (see trx_ep_ctrl_close_write_cb()), + * which then finishes this deferred free. */ void osmo_trx_ep_free(struct osmo_trx_ep *ep) { if (ep == NULL) return; + osmo_trx_ep_close(ep); + + if (osmo_trx_ep_is_closing(ep)) { + /* Detach from the caller's (possibly about-to-be-freed) + * talloc parent: ep must outlive it until the flush + * completes, since the still-flushing iofd is a talloc + * child of ep. */ + talloc_steal(OTC_GLOBAL, ep); + ep->flags |= OSMO_TRX_EP_F_PENDING_FREE; + return; + } + talloc_free(ep); } @@ -436,6 +579,25 @@ return ep->flags & OSMO_TRX_EP_F_OPEN; } +/*! Whether a ctrl socket from a previous osmo_trx_ep_close() is still + * flushing a queued TRXC message in the background (see trx_ep_ctrl_close()). */ +bool osmo_trx_ep_is_closing(const struct osmo_trx_ep *ep) +{ + for (unsigned int i = 0; i < ep->num_chans; i++) { + if (ep->chans[i].ctrl_wr_pending > 0) + return true; + } + return false; +} + +/*! Set the call-back invoked once osmo_trx_ep_close() has fully completed: + * immediately if no ctrl chan needed flushing, or once the last async + * flush finishes (see osmo_trx_ep_is_closing()) otherwise. */ +void osmo_trx_ep_set_closed_cb(struct osmo_trx_ep *ep, osmo_trx_ep_closed_cb_t closed_cb) +{ + ep->closed_cb = closed_cb; +} + /*! Set the application-private data */ void osmo_trx_ep_set_priv(struct osmo_trx_ep *ep, void *priv) { @@ -455,13 +617,21 @@ } /*! Change the number of channels; only valid before osmo_trx_ep_open(). - * \returns 0 on success; -EBUSY if the endpoint is already open; -EINVAL - * if num_chans is 0; -ENOMEM on allocation failure */ + * \returns 0 on success; -EBUSY if the endpoint is already open or a ctrl + * socket from a previous osmo_trx_ep_close() is still flushing (see + * osmo_trx_ep_is_closing()); -EINVAL if num_chans is 0; -ENOMEM on + * allocation failure */ int osmo_trx_ep_set_num_chans(struct osmo_trx_ep *ep, unsigned int num_chans) { if (ep->flags & OSMO_TRX_EP_F_OPEN) return -EBUSY; + /* re-allocating ep->chans below would leave a still-flushing + * channel's ctrl_iofd pointing at freed memory (its write_cb looks + * up its struct osmo_trx_ep_chan via osmo_iofd_get_data()) */ + if (osmo_trx_ep_is_closing(ep)) + return -EBUSY; + if (num_chans == 0) return -EINVAL; if (num_chans == ep->num_chans) @@ -651,6 +821,7 @@ const struct osmo_trxc_msg *tmsg) { struct msgb *msg; + size_t len; int rc; OSMO_ASSERT(chan < ep->num_chans); @@ -662,10 +833,13 @@ return rc; } msgb_put(msg, rc); + len = msgb_length(msg); rc = osmo_iofd_write_msgb(ep->chans[chan].ctrl_iofd, msg); if (rc < 0) msgb_free(msg); + else + ep->chans[chan].ctrl_wr_pending += len; return rc; } diff --git a/tests/libosmo-trx/trx_ep_test.c b/tests/libosmo-trx/trx_ep_test.c index a6f3d9c..7f96540 100644 --- a/tests/libosmo-trx/trx_ep_test.c +++ b/tests/libosmo-trx/trx_ep_test.c @@ -91,6 +91,11 @@ ep_label(ep), chan, osmo_trxd_burst_req_name(br)); } +static void ep_closed_cb(struct osmo_trx_ep *ep) +{ + printf("%s: closed_cb()\n", ep_label(ep)); +} + static void fill_burst_req(struct osmo_trxd_burst_req *br, uint32_t fn) { *br = (struct osmo_trxd_burst_req){ @@ -171,6 +176,7 @@ { osmo_trx_ep_close(ep); OSMO_ASSERT(osmo_trx_ep_is_open(ep) == false); + OSMO_ASSERT(osmo_trx_ep_is_closing(ep) == false); osmo_trx_ep_free(ep); } @@ -287,6 +293,61 @@ ep_close_free(ep_bts); } +static void test_ctrl_close_flush(bool do_free) +{ + struct osmo_trx_ep *ep_trx = ep_alloc("trx", OSMO_TRX_EP_MODE_TRX); + struct osmo_trx_ep *ep_bts = ep_alloc("bts", OSMO_TRX_EP_MODE_L1); + + printf("=== %s(do_free=%d): starting testcase ===\n", __func__, (int)do_free); + + ep_set_num_chans(ep_trx, 3); + ep_set_num_chans(ep_bts, 3); + + ep_open(ep_trx); + ep_open(ep_bts); + + static const struct osmo_trxc_msg cmd_rfmute = { + .type = OSMO_TRXC_MT_CMD, + .cmd = OSMO_TRXC_CMD_RFMUTE, + .params = "1", + }; + + static const struct osmo_trxc_msg cmd_poweroff = { + .type = OSMO_TRXC_MT_CMD, + .cmd = OSMO_TRXC_CMD_POWEROFF, + }; + + printf("=== %s(): TRXC CMDs sent right before osmo_trx_ep_close() (BTS -> TRX) ===\n", __func__); + osmo_trx_ep_send_ctrl_msg(ep_bts, 1, &cmd_rfmute); + osmo_trx_ep_send_ctrl_msg(ep_bts, 0, &cmd_poweroff); + + osmo_trx_ep_set_closed_cb(ep_bts, ep_closed_cb); + osmo_trx_ep_close(ep_bts); + OSMO_ASSERT(osmo_trx_ep_is_open(ep_bts) == false); + OSMO_ASSERT(osmo_trx_ep_is_closing(ep_bts) == true); + + /* osmo_trx_ep_open() is expected to fail while closing */ + OSMO_ASSERT(osmo_trx_ep_open(ep_bts) == -EBUSY); + /* osmo_trx_ep_set_num_chans() is expected to fail too */ + OSMO_ASSERT(osmo_trx_ep_set_num_chans(ep_bts, 16) == -EBUSY); + + if (do_free) { + OSMO_ASSERT(talloc_parent(ep_bts) == test_ctx); + osmo_trx_ep_free(ep_bts); /* osmo_trx_ep_free() postpones the actual free() */ + OSMO_ASSERT(talloc_parent(ep_bts) == OTC_GLOBAL); + OSMO_ASSERT(osmo_trx_ep_is_closing(ep_bts) == true); + OSMO_ASSERT(osmo_trx_ep_is_open(ep_bts) == false); + flush_io(); /* after flushing, the ep is finally free()ed! */ + } else { + flush_io(); + OSMO_ASSERT(osmo_trx_ep_is_closing(ep_bts) == false); + OSMO_ASSERT(osmo_trx_ep_is_open(ep_bts) == false); + ep_close_free(ep_bts); + } + + ep_close_free(ep_trx); +} + int main(int argc, char **argv) { test_ctx = talloc_named_const(NULL, 0, "trx_ep_test"); @@ -301,6 +362,8 @@ test_clck_ctrl(); test_burst_req_ind(); + test_ctrl_close_flush(false); + test_ctrl_close_flush(true); printf("Done\n"); return 0; diff --git a/tests/libosmo-trx/trx_ep_test.err b/tests/libosmo-trx/trx_ep_test.err index f8e32d3..4f7e5e9 100644 --- a/tests/libosmo-trx/trx_ep_test.err +++ b/tests/libosmo-trx/trx_ep_test.err @@ -1,10 +1,43 @@ DLGLOBAL INFO (ep=ep_trx) Opening TRXC/TRXD connections l=127.0.0.1:16700<->r=127.0.0.1:16800 DLGLOBAL INFO (ep=ep_bts) Opening TRXC/TRXD connections l=127.0.0.1:16800<->r=127.0.0.1:16700 DLGLOBAL INFO (ep=ep_trx) Closing TRXC/TRXD connections l=127.0.0.1:16700<->r=127.0.0.1:16800 +DLGLOBAL DEBUG (ep=ep_trx, chan=0) trx_ep_ctrl_close(): nothing pending, closing immediately +DLGLOBAL DEBUG (ep=ep_trx, chan=1) trx_ep_ctrl_close(): nothing pending, closing immediately DLGLOBAL INFO (ep=ep_bts) Closing TRXC/TRXD connections l=127.0.0.1:16800<->r=127.0.0.1:16700 +DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close(): nothing pending, closing immediately +DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close(): nothing pending, closing immediately DLGLOBAL INFO (ep=ep_trx) Opening TRXC/TRXD connections l=127.0.0.1:16700<->r=127.0.0.1:16800 DLGLOBAL INFO (ep=ep_bts) Opening TRXC/TRXD connections l=127.0.0.1:16800<->r=127.0.0.1:16700 DLGLOBAL INFO (ep=ep_bts, chan=0) Using TRXD PDU version 2 DLGLOBAL INFO (ep=ep_trx, chan=0) Using TRXD PDU version 2 DLGLOBAL INFO (ep=ep_trx) Closing TRXC/TRXD connections l=127.0.0.1:16700<->r=127.0.0.1:16800 +DLGLOBAL DEBUG (ep=ep_trx, chan=0) trx_ep_ctrl_close(): nothing pending, closing immediately +DLGLOBAL DEBUG (ep=ep_trx, chan=1) trx_ep_ctrl_close(): nothing pending, closing immediately DLGLOBAL INFO (ep=ep_bts) Closing TRXC/TRXD connections l=127.0.0.1:16800<->r=127.0.0.1:16700 +DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close(): nothing pending, closing immediately +DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close(): nothing pending, closing immediately +DLGLOBAL INFO (ep=ep_trx) Opening TRXC/TRXD connections l=127.0.0.1:16700<->r=127.0.0.1:16800 +DLGLOBAL INFO (ep=ep_bts) Opening TRXC/TRXD connections l=127.0.0.1:16800<->r=127.0.0.1:16700 +DLGLOBAL INFO (ep=ep_bts) Closing TRXC/TRXD connections l=127.0.0.1:16800<->r=127.0.0.1:16700 +DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close(): 12 byte(s) still pending, flushing asynchronously +DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close(): 12 byte(s) still pending, flushing asynchronously +DLGLOBAL DEBUG (ep=ep_bts, chan=2) trx_ep_ctrl_close(): nothing pending, closing immediately +DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close_write_cb(): wrote 12 byte(s), flush completed +DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close_write_cb(): wrote 12 byte(s), flush completed +DLGLOBAL INFO (ep=ep_trx) Closing TRXC/TRXD connections l=127.0.0.1:16700<->r=127.0.0.1:16800 +DLGLOBAL DEBUG (ep=ep_trx, chan=0) trx_ep_ctrl_close(): nothing pending, closing immediately +DLGLOBAL DEBUG (ep=ep_trx, chan=1) trx_ep_ctrl_close(): nothing pending, closing immediately +DLGLOBAL DEBUG (ep=ep_trx, chan=2) trx_ep_ctrl_close(): nothing pending, closing immediately +DLGLOBAL INFO (ep=ep_trx) Opening TRXC/TRXD connections l=127.0.0.1:16700<->r=127.0.0.1:16800 +DLGLOBAL INFO (ep=ep_bts) Opening TRXC/TRXD connections l=127.0.0.1:16800<->r=127.0.0.1:16700 +DLGLOBAL INFO (ep=ep_bts) Closing TRXC/TRXD connections l=127.0.0.1:16800<->r=127.0.0.1:16700 +DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close(): 12 byte(s) still pending, flushing asynchronously +DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close(): 12 byte(s) still pending, flushing asynchronously +DLGLOBAL DEBUG (ep=ep_bts, chan=2) trx_ep_ctrl_close(): nothing pending, closing immediately +DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close_write_cb(): wrote 12 byte(s), flush completed +DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close_write_cb(): wrote 12 byte(s), flush completed +DLGLOBAL DEBUG (ep=ep_bts) trx_ep_ctrl_close_write_cb(): last flush completed, free()ing +DLGLOBAL INFO (ep=ep_trx) Closing TRXC/TRXD connections l=127.0.0.1:16700<->r=127.0.0.1:16800 +DLGLOBAL DEBUG (ep=ep_trx, chan=0) trx_ep_ctrl_close(): nothing pending, closing immediately +DLGLOBAL DEBUG (ep=ep_trx, chan=1) trx_ep_ctrl_close(): nothing pending, closing immediately +DLGLOBAL DEBUG (ep=ep_trx, chan=2) trx_ep_ctrl_close(): nothing pending, closing immediately diff --git a/tests/libosmo-trx/trx_ep_test.ok b/tests/libosmo-trx/trx_ep_test.ok index 6b9c322..194e5d5 100644 --- a/tests/libosmo-trx/trx_ep_test.ok +++ b/tests/libosmo-trx/trx_ep_test.ok @@ -26,4 +26,14 @@ BURST.ind batch flush bts: rx_burst_ind(chan=0): NOPE.ind tn=5 fn=200005 trx_num=0 rssi=-63 toa256=-512 C/I=-150 cB bts: rx_burst_ind(chan=0): BURST.ind tn=6 fn=200005 trx_num=0 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=0 tsc=7 burst_len=148 +=== test_ctrl_close_flush(do_free=0): starting testcase === +=== test_ctrl_close_flush(): TRXC CMDs sent right before osmo_trx_ep_close() (BTS -> TRX) === +bts: closed_cb() +trx: rx_ctrl_msg(chan=0): 'CMD POWEROFF' +trx: rx_ctrl_msg(chan=1): 'CMD RFMUTE 1' +=== test_ctrl_close_flush(do_free=1): starting testcase === +=== test_ctrl_close_flush(): TRXC CMDs sent right before osmo_trx_ep_close() (BTS -> TRX) === +bts: closed_cb() +trx: rx_ctrl_msg(chan=0): 'CMD POWEROFF' +trx: rx_ctrl_msg(chan=1): 'CMD RFMUTE 1' Done -- To view, visit
https://gerrit.osmocom.org/c/osmo-trx/+/43112?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged Gerrit-Project: osmo-trx Gerrit-Branch: master Gerrit-Change-Id: I69e6a3bcf49afc3cfca4a72afdf459625cb91e56 Gerrit-Change-Number: 43112 Gerrit-PatchSet: 9 Gerrit-Owner: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: laforge <laforge(a)osmocom.org> Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
1
0
0
0
[M] Change in osmo-trx[master]: libosmo-trx/trxd: support NOPE.{ind,req} in TRXDv0/v1 PDUs
by fixeria
17 Sep '26
17 Sep '26
fixeria has submitted this change. (
https://gerrit.osmocom.org/c/osmo-trx/+/43105?usp=email
) Change subject: libosmo-trx/trxd: support NOPE.{ind,req} in TRXDv0/v1 PDUs ...................................................................... libosmo-trx/trxd: support NOPE.{ind,req} in TRXDv0/v1 PDUs TRXDv0 (and TRXDv1 in the downlink direction) has no MTS field, but NOPE indications/requests do exist there in practice: the burst payload is simply omitted, i.e. a header-only PDU. This is how osmocom-bb's trxcon transmits NOPE.req (see trx_if_handle_phyif_burst_req()). Change-Id: I1a59f31d0f00c8509a016dc2bac71b6aa467f3a4 --- M libosmo-trx/include/osmocom/trx/trxd.h M libosmo-trx/src/trxd.c M tests/libosmo-trx/trxd_test.c M tests/libosmo-trx/trxd_test.ok 4 files changed, 71 insertions(+), 18 deletions(-) Approvals: Jenkins Builder: Verified fixeria: Looks good to me, approved pespin: Looks good to me, but someone else must approve laforge: Looks good to me, but someone else must approve diff --git a/libosmo-trx/include/osmocom/trx/trxd.h b/libosmo-trx/include/osmocom/trx/trxd.h index b415c47..bf9baeb 100644 --- a/libosmo-trx/include/osmocom/trx/trxd.h +++ b/libosmo-trx/include/osmocom/trx/trxd.h @@ -39,6 +39,8 @@ /* Presence/meta flags for osmo_trxd_burst_{ind,req} */ #define OSMO_TRXD_F_NOPE_IND (1 << 0) /*!< no burst detected / idle indication */ +/*! alias of OSMO_TRXD_F_NOPE_IND, reading cleaner in BURST.req context */ +#define OSMO_TRXD_F_NOPE_REQ OSMO_TRXD_F_NOPE_IND #define OSMO_TRXD_F_MOD_TYPE (1 << 1) /*!< 'mod' is valid */ #define OSMO_TRXD_F_TS_INFO (1 << 2) /*!< 'tsc_set'/'tsc' are valid */ #define OSMO_TRXD_F_CI_CB (1 << 3) /*!< 'ci_cb' is valid */ diff --git a/libosmo-trx/src/trxd.c b/libosmo-trx/src/trxd.c index 9b8cf39..6e68542 100644 --- a/libosmo-trx/src/trxd.c +++ b/libosmo-trx/src/trxd.c @@ -210,6 +210,12 @@ trxd_burst_ind_parse_hdr_v0(bi, buf); + /* NOPE.ind: TRXDv0 has no MTS, the burst payload is simply omitted */ + if (burst_len == 0) { + bi->flags |= OSMO_TRXD_F_NOPE_IND; + return buf_len; + } + switch (burst_len) { case OSMO_TRXD_BURST_LEN_GMSK: case OSMO_TRXD_BURST_LEN_GMSK + 2: @@ -382,9 +388,9 @@ * \param[inout] msg destination message buffer * \param[in] pdu_ver TRXD PDU version to encode * \param[in] bi burst indication to be encoded - * \returns 0 on success; negative on error. Note that TRXDv0 cannot - * carry NOPE.ind PDUs: -ENOTSUP is returned and the caller - * shall skip (not send) them. */ + * \returns 0 on success; negative on error. Note that TRXDv0 has no + * MTS field, so a NOPE.ind is encoded as a header-only PDU + * with the burst payload omitted. */ int osmo_trxd_burst_ind_build(struct msgb *msg, uint8_t pdu_ver, const struct osmo_trxd_burst_ind *bi) { @@ -394,9 +400,6 @@ switch (pdu_ver) { case 0: - /* v0 doesn't support NOPE.ind, the caller shall skip it */ - if (bi->flags & OSMO_TRXD_F_NOPE_IND) - return -ENOTSUP; buf = msgb_put(msg, TRXD_IND_V0HDR_LEN); buf[0] = ((pdu_ver & 0x0f) << 4) | (bi->tn & 0x07); osmo_store32be(bi->fn, buf + 1); @@ -467,6 +470,10 @@ case OSMO_TRXD_BURST_LEN_GMSK: br->mod = OSMO_TRXD_MOD_T_GMSK; break; + case 0: /* NOPE.req: TRXDv0/v1 have no MTS, the burst payload is simply omitted */ + br->flags |= OSMO_TRXD_F_NOPE_REQ; + br->burst_len = 0; + return buf_len; default: return -EINVAL; } @@ -513,6 +520,12 @@ br->fn = st->fn; } + /* NOPE.req contains no burst */ + if (br->flags & OSMO_TRXD_F_NOPE_REQ) { + br->burst_len = 0; + return hdr_len; + } + burst_len = burst_len_by_mod(br->mod); if (burst_len < 0) return burst_len; @@ -629,9 +642,11 @@ return -ENOTSUP; } - /* copy hard-bits {0,1} */ - memcpy(msgb_put(msg, br->burst_len), - &br->burst[0], br->burst_len); + if (~br->flags & OSMO_TRXD_F_NOPE_REQ) { + /* copy hard-bits {0,1} */ + memcpy(msgb_put(msg, br->burst_len), + &br->burst[0], br->burst_len); + } return 0; } @@ -711,9 +726,13 @@ { struct osmo_strbuf sb = { .buf = buf, .len = buf_len }; - OSMO_STRBUF_PRINTF(sb, "BURST.req tn=%u fn=%u att=%u", br->tn, br->fn, br->att); + OSMO_STRBUF_PRINTF(sb, "%s tn=%u fn=%u att=%u", + (br->flags & OSMO_TRXD_F_NOPE_REQ) ? "NOPE.req" : "BURST.req", + br->tn, br->fn, br->att); if (br->flags & OSMO_TRXD_F_TRX_NUM) OSMO_STRBUF_PRINTF(sb, " trx_num=%u", br->trx_num); + if (br->flags & OSMO_TRXD_F_NOPE_REQ) + return buf; if (br->flags & OSMO_TRXD_F_MOD_TYPE) OSMO_STRBUF_PRINTF(sb, " mod=%s", osmo_trxd_mod_type_name(br->mod)); if (br->flags & OSMO_TRXD_F_TS_INFO) diff --git a/tests/libosmo-trx/trxd_test.c b/tests/libosmo-trx/trxd_test.c index 454de86..88b159b 100644 --- a/tests/libosmo-trx/trxd_test.c +++ b/tests/libosmo-trx/trxd_test.c @@ -129,13 +129,6 @@ bi.flags = OSMO_TRXD_F_NOPE_IND | OSMO_TRXD_F_CI_CB; rc = osmo_trxd_burst_ind_build(msg, pdu_ver, &bi); - if (pdu_ver == 0) { - /* TRXDv0 cannot carry NOPE.ind */ - printf("build: rc=%d (expected -ENOTSUP)\n", rc); - OSMO_ASSERT(rc == -ENOTSUP); - msgb_free(msg); - return; - } OSMO_ASSERT(rc == 0); osmo_trxd_build_fin(msg, pdu_ver); printf("build: %s\n", osmo_trxd_burst_ind_name(&bi)); @@ -264,6 +257,34 @@ msgb_free(msg); } +static void test_burst_req_nope(uint8_t pdu_ver) +{ + struct osmo_trxd_parse_state st; + struct osmo_trxd_burst_req br, br2; + struct msgb *msg = msgb_alloc(4096, "nope"); + int rc; + + printf("=== %s(v%u) ===\n", __func__, pdu_ver); + + fill_burst_req(&br, 0); + br.flags = OSMO_TRXD_F_NOPE_REQ; + + rc = osmo_trxd_burst_req_build(msg, pdu_ver, &br); + OSMO_ASSERT(rc == 0); + osmo_trxd_build_fin(msg, pdu_ver); + printf("build: %s\n", osmo_trxd_burst_req_name(&br)); + + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_req_parse(&st, &br2, msgb_data(msg), msgb_length(msg)); + OSMO_ASSERT(rc == (int)msgb_length(msg)); + printf("parse: %s\n", osmo_trxd_burst_req_name(&br2)); + + OSMO_ASSERT(br2.flags & OSMO_TRXD_F_NOPE_REQ); + OSMO_ASSERT(br2.burst_len == 0); + + msgb_free(msg); +} + static void test_burst_req_batch(uint8_t pdu_ver) { struct osmo_trxd_parse_state st; @@ -394,6 +415,7 @@ for (uint8_t pdu_ver = 0; pdu_ver <= OSMO_TRXD_PDU_VER_MAX; pdu_ver++) { test_burst_req(pdu_ver, OSMO_TRXD_BURST_LEN_GMSK); test_burst_req(pdu_ver, OSMO_TRXD_BURST_LEN_8PSK); + test_burst_req_nope(pdu_ver); test_burst_req_batch(pdu_ver); test_burst_ind(pdu_ver, OSMO_TRXD_BURST_LEN_GMSK); diff --git a/tests/libosmo-trx/trxd_test.ok b/tests/libosmo-trx/trxd_test.ok index e4f22da..963322b 100644 --- a/tests/libosmo-trx/trxd_test.ok +++ b/tests/libosmo-trx/trxd_test.ok @@ -6,6 +6,9 @@ build: BURST.req tn=2 fn=2654321 att=10 mod=8-PSK set=0 tsc=3 burst_len=444 datagram (450 bytes): 02002880710a00010001000100010001... parse: BURST.req tn=2 fn=2654321 att=10 mod=8-PSK burst_len=444 +=== test_burst_req_nope(v0) === +build: NOPE.req tn=2 fn=2654321 att=10 +parse: NOPE.req tn=2 fn=2654321 att=10 === test_burst_ind(v0, burst_len=148) === build: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=1 tsc=7 burst_len=148 datagram (156 bytes): 050012d6873ffe001be31be31be31be3... @@ -15,7 +18,8 @@ datagram (452 bytes): 050012d6873ffe001be31be31be31be3... parse: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 mod=8-PSK burst_len=444 === test_burst_ind_nope(v0) === -build: rc=-95 (expected -ENOTSUP) +build: NOPE.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB +parse: NOPE.ind tn=5 fn=1234567 rssi=-63 toa256=-512 === test_burst_req(v1, burst_len=148) === build: BURST.req tn=2 fn=2654321 att=10 mod=GMSK set=0 tsc=3 burst_len=148 datagram (154 bytes): 12002880710a00010001000100010001... @@ -24,6 +28,9 @@ build: BURST.req tn=2 fn=2654321 att=10 mod=8-PSK set=0 tsc=3 burst_len=444 datagram (450 bytes): 12002880710a00010001000100010001... parse: BURST.req tn=2 fn=2654321 att=10 mod=8-PSK burst_len=444 +=== test_burst_req_nope(v1) === +build: NOPE.req tn=2 fn=2654321 att=10 +parse: NOPE.req tn=2 fn=2654321 att=10 === test_burst_ind(v1, burst_len=148) === build: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=1 tsc=7 burst_len=148 datagram (159 bytes): 150012d6873ffe000fff6a1be31be31b... @@ -43,6 +50,9 @@ build: BURST.req tn=2 fn=2654321 att=10 mod=8-PSK set=0 tsc=3 burst_len=444 datagram (456 bytes): 2200230a000000000028807100010001... parse: BURST.req tn=2 fn=2654321 att=10 trx_num=0 mod=8-PSK set=0 tsc=3 burst_len=444 +=== test_burst_req_nope(v2) === +build: NOPE.req tn=2 fn=2654321 att=10 +parse: NOPE.req tn=2 fn=2654321 att=10 trx_num=0 === test_burst_req_batch(v2) === datagram (472 bytes) parse[0]: BURST.req tn=0 fn=2654321 att=10 trx_num=0 mod=GMSK set=0 tsc=3 burst_len=148 -- To view, visit
https://gerrit.osmocom.org/c/osmo-trx/+/43105?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged Gerrit-Project: osmo-trx Gerrit-Branch: master Gerrit-Change-Id: I1a59f31d0f00c8509a016dc2bac71b6aa467f3a4 Gerrit-Change-Number: 43105 Gerrit-PatchSet: 5 Gerrit-Owner: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: laforge <laforge(a)osmocom.org> Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
1
0
0
0
[M] Change in osmo-trx[master]: libosmo-trx/trxc: enlarge the params buffer
by fixeria
17 Sep '26
17 Sep '26
fixeria has submitted this change. (
https://gerrit.osmocom.org/c/osmo-trx/+/43106?usp=email
) Change subject: libosmo-trx/trxc: enlarge the params buffer ...................................................................... libosmo-trx/trxc: enlarge the params buffer The SETFH command (implemented by trxcon and fake_trx) carries the whole Mobile Allocation as pairs of Rx/Tx frequencies in kHz: CMD SETFH <HSN> <MAIO> <RXF1> <TXF1> [... <RXFN> <TXFN>] With up to 64 ARFCNs in the Mobile Allocation, the parameters string alone can exceed 1000 characters, far beyond the old 128 byte limit. Derive OSMO_TRXC_PARAMS_LEN_MAX from OSMO_TRXC_MSG_BUF_SIZE, so that any message ("RSP " + verb + status + params) still fits the recommended socket buffer size. Also add the OSMO_TRXC_CMD_SETFH verb constant and a regression test doing a round-trip of a maximum size SETFH command (64 ARFCNs). Change-Id: I2f65b213f5ada499eea4abae87d3727057e03e22 --- M libosmo-trx/include/osmocom/trx/trxc.h M tests/libosmo-trx/trxc_test.c M tests/libosmo-trx/trxc_test.ok 3 files changed, 57 insertions(+), 2 deletions(-) Approvals: pespin: Looks good to me, but someone else must approve fixeria: Looks good to me, approved laforge: Looks good to me, but someone else must approve Jenkins Builder: Verified diff --git a/libosmo-trx/include/osmocom/trx/trxc.h b/libosmo-trx/include/osmocom/trx/trxc.h index ea84b81..f3a64b6 100644 --- a/libosmo-trx/include/osmocom/trx/trxc.h +++ b/libosmo-trx/include/osmocom/trx/trxc.h @@ -7,10 +7,14 @@ /*! Maximum length of a command verb (incl. '\0') */ #define OSMO_TRXC_CMD_LEN_MAX 32 -/*! Maximum length of the parameters string (incl. '\0') */ -#define OSMO_TRXC_PARAMS_LEN_MAX 128 /*! Recommended TRXC socket read/send buffer size */ #define OSMO_TRXC_MSG_BUF_SIZE 1500 +/*! Maximum length of the parameters string (incl. '\0'). Must be large + * enough for SETFH, which carries the whole Mobile Allocation as pairs of + * Rx/Tx frequencies in kHz (over 1000 characters for 64 ARFCNs). Sized so + * that any message ("RSP " + verb + status) still fits the buffer above; + * the extra -32 is a rounded-up safety margin for that fixed overhead. */ +#define OSMO_TRXC_PARAMS_LEN_MAX (OSMO_TRXC_MSG_BUF_SIZE - OSMO_TRXC_CMD_LEN_MAX - 32) enum osmo_trxc_msg_type { OSMO_TRXC_MT_CMD, /*!< "CMD <verb> [<params>]" (L1 -> TRX) */ @@ -55,6 +59,7 @@ #define OSMO_TRXC_CMD_HANDOVER "HANDOVER" #define OSMO_TRXC_CMD_NOHANDOVER "NOHANDOVER" #define OSMO_TRXC_CMD_RFMUTE "RFMUTE" +#define OSMO_TRXC_CMD_SETFH "SETFH" #define OSMO_TRXC_CMD_ERR "ERR" /*!< verb of a reject response */ /* Clock socket: "IND CLOCK <fn>" */ diff --git a/tests/libosmo-trx/trxc_test.c b/tests/libosmo-trx/trxc_test.c index 82676a7..93eea83 100644 --- a/tests/libosmo-trx/trxc_test.c +++ b/tests/libosmo-trx/trxc_test.c @@ -114,6 +114,50 @@ OSMO_ASSERT(rc == 2 && tn == 3 && ts_type == 7); } +/* SETFH (trxcon dialect) carries the whole Mobile Allocation as pairs of + * Rx/Tx frequencies in kHz, so its parameters can be over 1000 characters + * long (up to 64 ARFCNs). Ensure that such messages survive a round-trip. */ +static void test_long_params(void) +{ + char buf[OSMO_TRXC_MSG_BUF_SIZE]; + struct osmo_trxc_msg msg = { + .type = OSMO_TRXC_MT_CMD, + .cmd = OSMO_TRXC_CMD_SETFH, + }; + struct osmo_trxc_msg parsed; + size_t len; + int rc; + + printf("=== %s ===\n", __func__); + + /* HSN=32 MAIO=5, then 64 pairs of DCS1800 Rx/Tx frequencies */ + len = snprintf(msg.params, sizeof(msg.params), "32 5"); + for (unsigned int i = 0; i < 64; i++) { + len += snprintf(msg.params + len, sizeof(msg.params) - len, + " %u %u", 1805200 + i * 200, 1710200 + i * 200); + } + printf("SETFH params_len=%zu\n", len); + OSMO_ASSERT(len < sizeof(msg.params)); + + rc = osmo_trxc_msg_build(buf, sizeof(buf), &msg); + printf("build: rc=%d\n", rc); + OSMO_ASSERT(rc > 0); + + rc = osmo_trxc_msg_parse(&parsed, buf, rc); + printf("parse: rc=%d cmd='%s' params_len=%zu\n", + rc, parsed.cmd, strlen(parsed.params)); + OSMO_ASSERT(rc == 0); + OSMO_ASSERT(strcmp(parsed.params, msg.params) == 0); + + /* parameters longer than OSMO_TRXC_PARAMS_LEN_MAX shall be rejected */ + len = strlen(buf); + memset(buf + len, '6', sizeof(buf) - len - 1); + buf[sizeof(buf) - 1] = '\0'; + rc = osmo_trxc_msg_parse(&parsed, buf, strlen(buf)); + printf("parse oversized params: rc=%d\n", rc); + OSMO_ASSERT(rc < 0); +} + static void test_clk_ind(void) { static const char * const messages[] = { @@ -155,6 +199,7 @@ test_msg_parse(); test_msg_build(); test_params_scan(); + test_long_params(); test_clk_ind(); printf("Done\n"); diff --git a/tests/libosmo-trx/trxc_test.ok b/tests/libosmo-trx/trxc_test.ok index 9422d19..845d4da 100644 --- a/tests/libosmo-trx/trxc_test.ok +++ b/tests/libosmo-trx/trxc_test.ok @@ -38,6 +38,11 @@ build into a too small buffer: rc=-90 === test_params_scan === '3 7' -> rc=2 tn=3 ts_type=7 +=== test_long_params === +SETFH params_len=1028 +build: rc=1038 +parse: rc=0 cmd='SETFH' params_len=1028 +parse oversized params: rc=-90 === test_clk_ind === 'IND CLOCK 402312' -> fn=402312 'IND CLOCK 0' -> fn=0 -- To view, visit
https://gerrit.osmocom.org/c/osmo-trx/+/43106?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged Gerrit-Project: osmo-trx Gerrit-Branch: master Gerrit-Change-Id: I2f65b213f5ada499eea4abae87d3727057e03e22 Gerrit-Change-Number: 43106 Gerrit-PatchSet: 5 Gerrit-Owner: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: laforge <laforge(a)osmocom.org> Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
1
0
0
0
[XL] Change in osmo-trx[master]: libosmo-trx: add TRXC/TRXD message codec
by fixeria
17 Sep '26
17 Sep '26
fixeria has submitted this change. (
https://gerrit.osmocom.org/c/osmo-trx/+/43104?usp=email
) Change subject: libosmo-trx: add TRXC/TRXD message codec ...................................................................... libosmo-trx: add TRXC/TRXD message codec Introduce libosmo-trx: a shared implementation of the TRXC/TRXD (OpenBTS-style TRX) protocol, to be used by osmo-trx, osmo-bts, trxcon, and the upcoming C rewrite of fake_trx. This initial version provides two I/O-free codec modules: * trxd: TRXD PDU codec for BURST.ind and BURST.req * trxc: verb-agnostic TRXC message codec (CMD/RSP/IND) The codec logic is based on the existing implementations in osmo-bts (f0ee51997470e1c3020ddb85082385ed43ba0c68). Change-Id: I933fc417a67d0043f74b04626b7643c79e381492 --- M .gitignore M Makefile.am M configure.ac A libosmo-trx/Makefile.am A libosmo-trx/include/Makefile.am A libosmo-trx/include/osmocom/trx/trxc.h A libosmo-trx/include/osmocom/trx/trxd.h A libosmo-trx/libosmo-trx.pc.in A libosmo-trx/src/Makefile.am A libosmo-trx/src/trxc.c A libosmo-trx/src/trxd.c M tests/Makefile.am A tests/libosmo-trx/Makefile.am A tests/libosmo-trx/trxc_test.c A tests/libosmo-trx/trxc_test.ok A tests/libosmo-trx/trxd_test.c A tests/libosmo-trx/trxd_test.ok M tests/testsuite.at 18 files changed, 1,948 insertions(+), 1 deletion(-) Approvals: Jenkins Builder: Verified laforge: Looks good to me, but someone else must approve pespin: Looks good to me, but someone else must approve fixeria: Looks good to me, approved diff --git a/.gitignore b/.gitignore index 21ff141..e33145b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.o *.lo *.la +*.pc Transceiver52M/osmo-trx-uhd Transceiver52M/osmo-trx-usrp1 Transceiver52M/osmo-trx-lms @@ -19,6 +20,8 @@ .clang-format # tests +tests/libosmo-trx/trxc_test +tests/libosmo-trx/trxd_test tests/CommonLibs/BitVectorTest tests/CommonLibs/F16Test tests/CommonLibs/InterthreadTest diff --git a/Makefile.am b/Makefile.am index d4c3c93..844d6e7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -34,6 +34,7 @@ # Order must be preserved SUBDIRS += \ + libosmo-trx \ CommonLibs \ GSM \ Transceiver52M \ diff --git a/configure.ac b/configure.ac index 50bdd64..356ce9d 100644 --- a/configure.ac +++ b/configure.ac @@ -66,7 +66,11 @@ PKG_PROG_PKG_CONFIG([0.20]) AC_LIBTOOL_WIN32_DLL -AC_DISABLE_SHARED dnl don't build shared libraries +dnl Most libraries in this tree (CommonLibs, GSM, Transceiver52M/*) are +dnl noinst convenience libraries and never installed, so static vs. shared +dnl makes no difference for them. libosmo-trx/src/libosmo-trx.la, however, +dnl is installed and meant to be linked by other packages (osmo-bts, +dnl osmocom-bb/trxcon), so shared library support must stay enabled. AC_ENABLE_STATIC dnl do build static libraries LT_INIT @@ -357,6 +361,10 @@ dnl Output files AC_CONFIG_FILES([\ Makefile \ + libosmo-trx/Makefile \ + libosmo-trx/include/Makefile \ + libosmo-trx/src/Makefile \ + libosmo-trx/libosmo-trx.pc \ CommonLibs/Makefile \ GSM/Makefile \ Transceiver52M/Makefile \ @@ -372,6 +380,7 @@ Transceiver52M/device/ipc/Makefile \ Transceiver52M/device/bladerf/Makefile \ tests/Makefile \ + tests/libosmo-trx/Makefile \ tests/CommonLibs/Makefile \ tests/Transceiver52M/Makefile \ utils/Makefile \ diff --git a/libosmo-trx/Makefile.am b/libosmo-trx/Makefile.am new file mode 100644 index 0000000..e8ae7eb --- /dev/null +++ b/libosmo-trx/Makefile.am @@ -0,0 +1,7 @@ +SUBDIRS = \ + include \ + src \ + $(NULL) + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libosmo-trx.pc diff --git a/libosmo-trx/include/Makefile.am b/libosmo-trx/include/Makefile.am new file mode 100644 index 0000000..3c5f908 --- /dev/null +++ b/libosmo-trx/include/Makefile.am @@ -0,0 +1,4 @@ +nobase_include_HEADERS = \ + osmocom/trx/trxc.h \ + osmocom/trx/trxd.h \ + $(NULL) diff --git a/libosmo-trx/include/osmocom/trx/trxc.h b/libosmo-trx/include/osmocom/trx/trxc.h new file mode 100644 index 0000000..ea84b81 --- /dev/null +++ b/libosmo-trx/include/osmocom/trx/trxc.h @@ -0,0 +1,62 @@ +/*! \file osmocom/trx/trxc.h + * TRXC (control) protocol and clock indications: I/O-free message codec. */ +#pragma once + +#include <stdint.h> +#include <stddef.h> + +/*! Maximum length of a command verb (incl. '\0') */ +#define OSMO_TRXC_CMD_LEN_MAX 32 +/*! Maximum length of the parameters string (incl. '\0') */ +#define OSMO_TRXC_PARAMS_LEN_MAX 128 +/*! Recommended TRXC socket read/send buffer size */ +#define OSMO_TRXC_MSG_BUF_SIZE 1500 + +enum osmo_trxc_msg_type { + OSMO_TRXC_MT_CMD, /*!< "CMD <verb> [<params>]" (L1 -> TRX) */ + OSMO_TRXC_MT_RSP, /*!< "RSP <verb> <status> [<params>]" (TRX -> L1) */ + OSMO_TRXC_MT_IND, /*!< "IND <verb> <params>" (TRX -> L1) */ +}; + +/*! A single de-/serialized TRXC message. + * The command verb set is deliberately open: the codec is verb-agnostic, + * so dialect specific (e.g. trxcon's ECHO/MEASURE/SETTA) and custom + * (e.g. fake_trx's FAKE_*) commands need no library changes. */ +struct osmo_trxc_msg { + enum osmo_trxc_msg_type type; + char cmd[OSMO_TRXC_CMD_LEN_MAX]; /*!< verb, e.g. "POWERON" */ + int status; /*!< RSP only */ + char params[OSMO_TRXC_PARAMS_LEN_MAX]; /*!< raw parameters, may be "" */ +}; + +int osmo_trxc_msg_parse(struct osmo_trxc_msg *msg, const char *buf, size_t len); +int osmo_trxc_msg_build(char *buf, size_t buf_size, const struct osmo_trxc_msg *msg); +int osmo_trxc_msg_params_scan(const struct osmo_trxc_msg *msg, const char *fmt, ...); + +const char *osmo_trxc_msg_name(const struct osmo_trxc_msg *msg); +char *osmo_trxc_msg_name_buf(char *buf, size_t buf_size, + const struct osmo_trxc_msg *msg); + +/* Well-known command verbs (the codec itself is verb-agnostic) */ +#define OSMO_TRXC_CMD_POWERON "POWERON" +#define OSMO_TRXC_CMD_POWEROFF "POWEROFF" +#define OSMO_TRXC_CMD_RXTUNE "RXTUNE" +#define OSMO_TRXC_CMD_TXTUNE "TXTUNE" +#define OSMO_TRXC_CMD_SETSLOT "SETSLOT" +#define OSMO_TRXC_CMD_SETTSC "SETTSC" +#define OSMO_TRXC_CMD_SETBSIC "SETBSIC" +#define OSMO_TRXC_CMD_SETPOWER "SETPOWER" +#define OSMO_TRXC_CMD_ADJPOWER "ADJPOWER" +#define OSMO_TRXC_CMD_NOMTXPOWER "NOMTXPOWER" +#define OSMO_TRXC_CMD_SETRXGAIN "SETRXGAIN" +#define OSMO_TRXC_CMD_SETMAXDLY "SETMAXDLY" +#define OSMO_TRXC_CMD_SETMAXDLYNB "SETMAXDLYNB" +#define OSMO_TRXC_CMD_SETFORMAT "SETFORMAT" +#define OSMO_TRXC_CMD_HANDOVER "HANDOVER" +#define OSMO_TRXC_CMD_NOHANDOVER "NOHANDOVER" +#define OSMO_TRXC_CMD_RFMUTE "RFMUTE" +#define OSMO_TRXC_CMD_ERR "ERR" /*!< verb of a reject response */ + +/* Clock socket: "IND CLOCK <fn>" */ +int osmo_trxc_clock_ind_parse(uint32_t *fn, const char *buf, size_t len); +int osmo_trxc_clock_ind_build(char *buf, size_t buf_size, uint32_t fn); diff --git a/libosmo-trx/include/osmocom/trx/trxd.h b/libosmo-trx/include/osmocom/trx/trxd.h new file mode 100644 index 0000000..b415c47 --- /dev/null +++ b/libosmo-trx/include/osmocom/trx/trxd.h @@ -0,0 +1,128 @@ +/*! \file osmocom/trx/trxd.h + * TRXD (burst data) protocol: I/O-free PDU codec. */ +#pragma once + +#include <stdint.h> +#include <stddef.h> + +#include <osmocom/core/bits.h> +#include <osmocom/core/utils.h> + +struct msgb; + +/*! The highest TRXD PDU version supported by this library */ +#define OSMO_TRXD_PDU_VER_MAX 2 + +/*! GMSK modulated burst: 1 bit per symbol */ +#define OSMO_TRXD_BURST_LEN_GMSK 148 +/*! 8-PSK modulated burst: 3 bits per symbol */ +#define OSMO_TRXD_BURST_LEN_8PSK 444 +/*! Maximum burst length in (soft-)bits */ +#define OSMO_TRXD_BURST_LEN_MAX OSMO_TRXD_BURST_LEN_8PSK + +/*! Maximum single-PDU header size, in bytes: the TRXDv2 header (largest of + * all versions, either direction) plus the TDMA FN present on the first + * PDU of a datagram. */ +#define OSMO_TRXD_HDR_LEN_MAX 12 +/*! Recommended Rx/Tx socket buffer size, in bytes, for a single + * (non-batched) TRXD datagram: largest possible header plus the largest + * possible burst payload. */ +#define OSMO_TRXD_MSG_BUF_SIZE (OSMO_TRXD_HDR_LEN_MAX + OSMO_TRXD_BURST_LEN_MAX) + +/*! Modulation types (defined by the TRXDv2 MTS encoding, 3GPP TS 45.002) */ +enum osmo_trxd_mod_type { + OSMO_TRXD_MOD_T_GMSK, + OSMO_TRXD_MOD_T_8PSK, + OSMO_TRXD_MOD_T_AQPSK, + /* room for 16QAM/32QAM */ +}; + +/* Presence/meta flags for osmo_trxd_burst_{ind,req} */ +#define OSMO_TRXD_F_NOPE_IND (1 << 0) /*!< no burst detected / idle indication */ +#define OSMO_TRXD_F_MOD_TYPE (1 << 1) /*!< 'mod' is valid */ +#define OSMO_TRXD_F_TS_INFO (1 << 2) /*!< 'tsc_set'/'tsc' are valid */ +#define OSMO_TRXD_F_CI_CB (1 << 3) /*!< 'ci_cb' is valid */ +#define OSMO_TRXD_F_TRX_NUM (1 << 4) /*!< 'trx_num' is valid */ +#define OSMO_TRXD_F_BATCH_IND (1 << 5) /*!< more PDUs follow in this datagram */ +#define OSMO_TRXD_F_SHADOW_IND (1 << 6) /*!< burst received on a shadow (VAMOS) channel */ +#define OSMO_TRXD_F_ACCESS_BURST (1 << 7) /*!< an Access Burst (RACH) */ + +/*! TRX -> L1: received burst indication (soft bits + measurements). + * + * ABI stability rule: mandatory fields come first, optional (flag-gated) + * fields after them; fields introduced by future TRXD revisions shall be + * appended at the end of the struct. */ +struct osmo_trxd_burst_ind { + uint32_t flags; /*!< see OSMO_TRXD_F_* */ + + /* Mandatory fields (all TRXD versions) */ + uint32_t fn; /*!< TDMA frame number */ + uint8_t tn; /*!< TDMA timeslot number */ + int16_t toa256; /*!< Timing of Arrival in units of 1/256 of symbol */ + int8_t rssi; /*!< Received Signal Strength Indication (dBm) */ + sbit_t burst[OSMO_TRXD_BURST_LEN_MAX]; /*!< soft-bits buffer */ + size_t burst_len; /*!< number of soft-bits (0 for NOPE.ind) */ + + /* Optional fields (presence indicated by flags) */ + enum osmo_trxd_mod_type mod; /*!< Modulation type */ + uint8_t tsc_set; /*!< Training Sequence Set */ + uint8_t tsc; /*!< Training Sequence Code */ + int16_t ci_cb; /*!< Carrier-to-Interference ratio (centiBels) */ + uint8_t trx_num; /*!< TRX (RF channel) number */ +}; + +/*! L1 -> TRX: burst transmit request (hard bits). + * Same ABI stability rule as for osmo_trxd_burst_ind. */ +struct osmo_trxd_burst_req { + uint32_t flags; /*!< see OSMO_TRXD_F_* */ + + /* Mandatory fields (all TRXD versions) */ + uint32_t fn; /*!< TDMA frame number */ + uint8_t tn; /*!< TDMA timeslot number */ + uint8_t att; /*!< Tx power attenuation (BTS side) */ + ubit_t burst[OSMO_TRXD_BURST_LEN_MAX]; /*!< hard-bits buffer */ + size_t burst_len; /*!< number of hard-bits */ + + /* Optional fields (presence indicated by flags) */ + enum osmo_trxd_mod_type mod; /*!< Modulation type */ + uint8_t tsc_set; /*!< Training Sequence Set */ + uint8_t tsc; /*!< Training Sequence Code */ + int8_t scpir; /*!< SCPIR (AQPSK only, TRXDv2) */ + uint8_t trx_num; /*!< TRX (RF channel) number */ +}; + +/*! Parser state, keeping cross-PDU context for TRXDv2 batches. + * Initialize with osmo_trxd_parse_state_init() before parsing each datagram. */ +struct osmo_trxd_parse_state { + uint8_t pdu_ver; /*!< PDU version (parsed from the first PDU) */ + unsigned int num_pdus; /*!< number of PDUs parsed so far */ + uint32_t fn; /*!< TDMA fn of the first PDU (v2 batches omit it later) */ +}; + +void osmo_trxd_parse_state_init(struct osmo_trxd_parse_state *st); + +int osmo_trxd_burst_ind_parse(struct osmo_trxd_parse_state *st, + struct osmo_trxd_burst_ind *bi, + const uint8_t *buf, size_t buf_len); +int osmo_trxd_burst_req_parse(struct osmo_trxd_parse_state *st, + struct osmo_trxd_burst_req *br, + const uint8_t *buf, size_t buf_len); + +int osmo_trxd_burst_ind_build(struct msgb *msg, uint8_t pdu_ver, + const struct osmo_trxd_burst_ind *bi); +int osmo_trxd_burst_req_build(struct msgb *msg, uint8_t pdu_ver, + const struct osmo_trxd_burst_req *br); +void osmo_trxd_build_fin(struct msgb *msg, uint8_t pdu_ver); + +const char *osmo_trxd_burst_ind_name(const struct osmo_trxd_burst_ind *bi); +char *osmo_trxd_burst_ind_name_buf(char *buf, size_t buf_len, + const struct osmo_trxd_burst_ind *bi); +const char *osmo_trxd_burst_req_name(const struct osmo_trxd_burst_req *br); +char *osmo_trxd_burst_req_name_buf(char *buf, size_t buf_len, + const struct osmo_trxd_burst_req *br); + +extern const struct value_string osmo_trxd_mod_type_names[]; +static inline const char *osmo_trxd_mod_type_name(enum osmo_trxd_mod_type mod) +{ + return get_value_string(osmo_trxd_mod_type_names, mod); +} diff --git a/libosmo-trx/libosmo-trx.pc.in b/libosmo-trx/libosmo-trx.pc.in new file mode 100644 index 0000000..7e0de46 --- /dev/null +++ b/libosmo-trx/libosmo-trx.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Osmocom TRX protocol library +Description: C Utility Library for the TRXC/TRXD (OpenBTS-style TRX) protocol +Version: @VERSION@ +Requires: libosmocore libosmogsm +Libs: -L${libdir} -losmo-trx +Cflags: -I${includedir}/ diff --git a/libosmo-trx/src/Makefile.am b/libosmo-trx/src/Makefile.am new file mode 100644 index 0000000..cec05ec --- /dev/null +++ b/libosmo-trx/src/Makefile.am @@ -0,0 +1,33 @@ +# This is _NOT_ the library release version, it's an API version. +# Please read chapter "Library interface versions" of the libtool +# documentation before making any modification +LIBVERSION = 0:0:0 + +AM_CPPFLAGS = \ + -I$(top_srcdir)/libosmo-trx/include \ + -I$(top_builddir)/libosmo-trx/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(NULL) + +lib_LTLIBRARIES = libosmo-trx.la + +libosmo_trx_la_SOURCES = \ + trxc.c \ + trxd.c \ + $(NULL) + +libosmo_trx_la_LDFLAGS = \ + -version-info $(LIBVERSION) \ + -no-undefined \ + -export-symbols-regex '^osmo_' \ + $(NULL) + +libosmo_trx_la_LIBADD = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(NULL) diff --git a/libosmo-trx/src/trxc.c b/libosmo-trx/src/trxc.c new file mode 100644 index 0000000..70d8c16 --- /dev/null +++ b/libosmo-trx/src/trxc.c @@ -0,0 +1,212 @@ +/*! \file src/trxc.c + * TRXC (control) protocol and clock indications: I/O-free message codec. + * Based on the TRXC implementation in osmo-bts-trx and osmo-trx. */ + +/* + * (C) 2013 Andreas Eversberg <jolly(a)eversberg.eu> + * (C) 2016-2017 Harald Welte <laforge(a)gnumonks.org> + * (C) 2019 Vadim Yanitskiy <axilirator(a)gmail.com> + * (C) 2021-2026 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <
http://www.gnu.org/licenses/
>. + */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> + +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm0502.h> + +#include <osmocom/trx/trxc.h> + +static const struct value_string trxc_msg_type_names[] = { + { OSMO_TRXC_MT_CMD, "CMD" }, + { OSMO_TRXC_MT_RSP, "RSP" }, + { OSMO_TRXC_MT_IND, "IND" }, + { 0, NULL } +}; + +/*! Parse a TRXC message ("CMD <verb> [<params>]", "RSP <verb> <status> + * [<params>]" or "IND <verb> <params>") from a zero-terminated buffer. + * \param[out] msg parsed message + * \param[in] buf message buffer (not necessarily zero-terminated) + * \param[in] len length of the message in the buffer + * \returns 0 on success; negative on error */ +int osmo_trxc_msg_parse(struct osmo_trxc_msg *msg, const char *buf, size_t len) +{ + char tmp[OSMO_TRXC_MSG_BUF_SIZE]; + const char *p, *verb; + size_t vlen; + + memset(msg, 0, sizeof(*msg)); + + if (len >= sizeof(tmp)) + return -EMSGSIZE; + /* work on a zero-terminated copy */ + memcpy(tmp, buf, len); + tmp[len] = '\0'; + + if (strncmp(tmp, "CMD ", 4) == 0) + msg->type = OSMO_TRXC_MT_CMD; + else if (strncmp(tmp, "RSP ", 4) == 0) + msg->type = OSMO_TRXC_MT_RSP; + else if (strncmp(tmp, "IND ", 4) == 0) + msg->type = OSMO_TRXC_MT_IND; + else + return -EINVAL; + + /* the verb ends at the next space (or end of string) */ + verb = tmp + 4; + p = strchr(verb, ' '); + vlen = (p != NULL) ? (size_t)(p - verb) : strlen(verb); + if (vlen == 0 || vlen >= sizeof(msg->cmd)) + return -EINVAL; + memcpy(msg->cmd, verb, vlen); + msg->cmd[vlen] = '\0'; + + if (p == NULL) { /* no parameters at all */ + if (msg->type == OSMO_TRXC_MT_RSP) + return -EINVAL; /* RSP requires a status code */ + return 0; + } + p++; + + /* RSP carries a status code before the parameters */ + if (msg->type == OSMO_TRXC_MT_RSP) { + if (sscanf(p, "%d", &msg->status) != 1) + return -EINVAL; + p = strchr(p, ' '); + if (p == NULL) /* no parameters after the status */ + return 0; + p++; + } + + if (strlen(p) >= sizeof(msg->params)) + return -EMSGSIZE; + OSMO_STRLCPY_ARRAY(msg->params, p); + + return 0; +} + +/*! Serialize a TRXC message into the given buffer (zero-terminated). + * \returns length of the message (excl. '\0') on success; negative on error */ +int osmo_trxc_msg_build(char *buf, size_t buf_size, const struct osmo_trxc_msg *msg) +{ + int rc; + + switch (msg->type) { + case OSMO_TRXC_MT_CMD: + case OSMO_TRXC_MT_IND: + rc = snprintf(buf, buf_size, "%s %s%s%s", + get_value_string(trxc_msg_type_names, msg->type), + msg->cmd, + msg->params[0] != '\0' ? " " : "", msg->params); + break; + case OSMO_TRXC_MT_RSP: + rc = snprintf(buf, buf_size, "RSP %s %d%s%s", + msg->cmd, msg->status, + msg->params[0] != '\0' ? " " : "", msg->params); + break; + default: + return -EINVAL; + } + + if (rc < 0 || (size_t)rc >= buf_size) + return -EMSGSIZE; + return rc; +} + +/*! Parse the parameters string of a TRXC message, sscanf() style. + * \returns number of successfully matched items (see sscanf) */ +int osmo_trxc_msg_params_scan(const struct osmo_trxc_msg *msg, const char *fmt, ...) +{ + va_list ap; + int rc; + + va_start(ap, fmt); + rc = vsscanf(msg->params, fmt, ap); + va_end(ap); + + return rc; +} + +/*! Compose a human-readable representation of the given message + * (for logging), store into a thread-local static buffer. + * \param[in] msg parsed TRXC message to get a human-readable representation of + * \returns pointer to a thread-local static buffer; NULL on error */ +const char *osmo_trxc_msg_name(const struct osmo_trxc_msg *msg) +{ + static __thread char buf[OSMO_TRXC_MSG_BUF_SIZE]; + return osmo_trxc_msg_name_buf(&buf[0], sizeof(buf), msg); +} + +/*! Compose a human-readable representation of the given message + * (for logging), store into the given buffer. + * \param[out] buf output buffer to store the result + * \param[in] buf_size size of the output buffer + * \param[in] msg parsed TRXC message to get a human-readable representation of + * \returns pointer to the given buffer; NULL on error */ +char *osmo_trxc_msg_name_buf(char *buf, size_t buf_size, + const struct osmo_trxc_msg *msg) +{ + return osmo_trxc_msg_build(buf, buf_size, msg) < 0 ? NULL : buf; +} + +/*! Parse a clock indication ("IND CLOCK <fn>"). + * \param[out] fn TDMA frame number (validated to be < GSM_TDMA_HYPERFRAME) + * \param[in] buf message buffer (not necessarily zero-terminated) + * \param[in] len length of the message in the buffer + * \returns 0 on success; negative on error */ +int osmo_trxc_clock_ind_parse(uint32_t *fn, const char *buf, size_t len) +{ + struct osmo_trxc_msg msg; + int rc; + + rc = osmo_trxc_msg_parse(&msg, buf, len); + if (rc < 0) + return rc; + if (msg.type != OSMO_TRXC_MT_IND || strcmp(msg.cmd, "CLOCK") != 0) + return -EINVAL; + if (osmo_trxc_msg_params_scan(&msg, "%u", fn) != 1) + return -EINVAL; + if (*fn >= GSM_TDMA_HYPERFRAME) + return -ERANGE; + + return 0; +} + +/*! Serialize a clock indication ("IND CLOCK <fn>") into the given buffer. + * \param[out] buf message buffer to store the result + * \param[in] buf_size size of the message buffer + * \param[in] fn TDMA frame number (validated to be < GSM_TDMA_HYPERFRAME) + * \returns length of the message (excl. '\0') on success; negative on error */ +int osmo_trxc_clock_ind_build(char *buf, size_t buf_size, uint32_t fn) +{ + int rc; + + if (fn >= GSM_TDMA_HYPERFRAME) + return -ERANGE; + + rc = snprintf(buf, buf_size, "IND CLOCK %u", fn); + if (rc < 0 || (size_t)rc >= buf_size) + return -EMSGSIZE; + return rc; +} diff --git a/libosmo-trx/src/trxd.c b/libosmo-trx/src/trxd.c new file mode 100644 index 0000000..9b8cf39 --- /dev/null +++ b/libosmo-trx/src/trxd.c @@ -0,0 +1,724 @@ +/*! \file src/trxd.c + * TRXD (burst data) protocol: I/O-free PDU codec. + * Based on the TRXD implementation in osmo-bts-trx and osmo-trx. */ + +/* + * (C) 2013 Andreas Eversberg <jolly(a)eversberg.eu> + * (C) 2016-2017 Harald Welte <laforge(a)gnumonks.org> + * (C) 2019 Vadim Yanitskiy <axilirator(a)gmail.com> + * (C) 2021-2026 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <
http://www.gnu.org/licenses/
>. + */ + +#include <errno.h> +#include <string.h> +#include <stdint.h> + +#include <osmocom/core/bits.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm0502.h> + +#include <osmocom/trx/trxd.h> + +/* Uplink TRXDv0 header length: TDMA TN + FN + RSSI + ToA256 */ +#define TRXD_IND_V0HDR_LEN (1 + 4 + 1 + 2) +/* Uplink TRXDv1 header length: additional MTS + C/I */ +#define TRXD_IND_V1HDR_LEN (TRXD_IND_V0HDR_LEN + 1 + 2) +/* Uplink TRXDv2 header length: TDMA TN + TRXN + MTS + RSSI + ToA256 + C/I */ +#define TRXD_IND_V2HDR_LEN (1 + 1 + 1 + 1 + 2 + 2) + +/* Downlink TRXDv0/v1 header length: TDMA TN + FN + Att */ +#define TRXD_REQ_V01HDR_LEN (1 + 4 + 1) +/* Downlink TRXDv2 header length: TDMA TN + TRXN + MTS + Att + SCPIR + spare3 */ +#define TRXD_REQ_V2HDR_LEN (1 + 1 + 1 + 1 + 1 + 3) + +const struct value_string osmo_trxd_mod_type_names[] = { + { OSMO_TRXD_MOD_T_GMSK, "GMSK" }, + { OSMO_TRXD_MOD_T_8PSK, "8-PSK" }, + { OSMO_TRXD_MOD_T_AQPSK, "AQPSK" }, + { 0, NULL } +}; + +/*! Initialize a parser state; call before parsing each datagram. + * \param[out] st parser state to be initialized */ +void osmo_trxd_parse_state_init(struct osmo_trxd_parse_state *st) +{ + memset(st, 0, sizeof(*st)); +} + +/* Expected burst length (in bits on the wire) for a given modulation */ +static int burst_len_by_mod(enum osmo_trxd_mod_type mod) +{ + switch (mod) { + case OSMO_TRXD_MOD_T_GMSK: + case OSMO_TRXD_MOD_T_AQPSK: + return OSMO_TRXD_BURST_LEN_GMSK; + case OSMO_TRXD_MOD_T_8PSK: + return OSMO_TRXD_BURST_LEN_8PSK; + default: + return -ENOTSUP; + } +} + +static int mts_parse(uint32_t *flags, enum osmo_trxd_mod_type *mod, + uint8_t *tsc_set, uint8_t *tsc, uint8_t mts) +{ + if (mts & (1 << 7)) { + *flags |= OSMO_TRXD_F_NOPE_IND; + return 0; + } + + /* | 7 6 5 4 3 2 1 0 | Bitmask / description + * | . 0 0 X X . . . | GMSK, 4 TSC sets (0..3) + * | . 0 1 0 X . . . | 8-PSK, 2 TSC sets (0..1) + * | . 0 1 1 0 . . . | GMSK, Access Burst + * | . 1 1 X X . . . | AQPSK, 2 TSC sets + SCPIR */ + if ((mts >> 5) == 0x00) { + *mod = OSMO_TRXD_MOD_T_GMSK; + *tsc_set = (mts >> 3) & 0x03; + } else if ((mts >> 4) == 0x02) { + *mod = OSMO_TRXD_MOD_T_8PSK; + *tsc_set = (mts >> 3) & 0x01; + } else if ((mts >> 3) == 0x06) { + *flags |= OSMO_TRXD_F_ACCESS_BURST; + *mod = OSMO_TRXD_MOD_T_GMSK; + *tsc_set = 0; + } else if ((mts >> 5) == 0x03) { + *mod = OSMO_TRXD_MOD_T_AQPSK; + *tsc_set = (mts >> 3) & 0x01; + } else { + return -ENOTSUP; + } + + *tsc = mts & 0x07; + *flags |= (OSMO_TRXD_F_MOD_TYPE | OSMO_TRXD_F_TS_INFO); + + return 0; +} + +int trxd_mts_parse_ind(struct osmo_trxd_burst_ind *bi, uint8_t mts) +{ + return mts_parse(&bi->flags, &bi->mod, &bi->tsc_set, &bi->tsc, mts); +} + +int trxd_mts_parse_req(struct osmo_trxd_burst_req *br, uint8_t mts) +{ + return mts_parse(&br->flags, &br->mod, &br->tsc_set, &br->tsc, mts); +} + +static int mts_build(uint8_t *mts, uint32_t flags, + enum osmo_trxd_mod_type mod, + uint8_t tsc_set, uint8_t tsc) +{ + if (flags & OSMO_TRXD_F_NOPE_IND) { + *mts = (1 << 7); + return 0; + } + + if (flags & OSMO_TRXD_F_ACCESS_BURST) { + *mts = (0x06 << 3) | (tsc & 0x07); + return 0; + } + + switch (mod) { + case OSMO_TRXD_MOD_T_GMSK: + *mts = ((tsc_set & 0x03) << 3); + break; + case OSMO_TRXD_MOD_T_8PSK: + *mts = (0x02 << 4) | ((tsc_set & 0x01) << 3); + break; + case OSMO_TRXD_MOD_T_AQPSK: + *mts = (0x03 << 5) | ((tsc_set & 0x01) << 3); + break; + default: + return -ENOTSUP; + } + + *mts |= (tsc & 0x07); + return 0; +} + +int trxd_mts_build_ind(uint8_t *mts, const struct osmo_trxd_burst_ind *bi) +{ + return mts_build(mts, bi->flags, bi->mod, bi->tsc_set, bi->tsc); +} + +int trxd_mts_build_req(uint8_t *mts, const struct osmo_trxd_burst_req *br) +{ + return mts_build(mts, br->flags, br->mod, br->tsc_set, br->tsc); +} + +/*********************************************************************** + * burst indication (TRX -> L1): soft bits + measurements + ***********************************************************************/ + +/* Convert unsigned soft-bits [254..0] to soft-bits [-127..127] */ +static void soft_bits_parse(sbit_t *out, const uint8_t *in, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) { + if (in[i] == 255) + out[i] = -127; + else + out[i] = 127 - in[i]; + } +} + +/* Convert soft-bits [-127..127] to unsigned soft-bits [254..0] */ +static void soft_bits_build(uint8_t *out, const sbit_t *in, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) + out[i] = 127 - in[i]; +} + +/* Common header part shared by TRXDv0 and TRXDv1 */ +static void trxd_burst_ind_parse_hdr_v0(struct osmo_trxd_burst_ind *bi, + const uint8_t *buf) +{ + bi->tn = buf[0] & 0x07; + bi->fn = osmo_load32be(buf + 1); + bi->rssi = -(int8_t)buf[5]; + bi->toa256 = (int16_t)osmo_load16be(buf + 6); +} + +/* TRXDv0: modulation is guessed by the burst length; a legacy transceiver + * may append two garbage bytes, which we tolerate (and consume). */ +static int trxd_burst_ind_parse_v0(struct osmo_trxd_burst_ind *bi, + const uint8_t *buf, size_t buf_len) +{ + size_t burst_len = buf_len - TRXD_IND_V0HDR_LEN; + + trxd_burst_ind_parse_hdr_v0(bi, buf); + + switch (burst_len) { + case OSMO_TRXD_BURST_LEN_GMSK: + case OSMO_TRXD_BURST_LEN_GMSK + 2: + bi->mod = OSMO_TRXD_MOD_T_GMSK; + bi->burst_len = OSMO_TRXD_BURST_LEN_GMSK; + break; + case OSMO_TRXD_BURST_LEN_8PSK: + case OSMO_TRXD_BURST_LEN_8PSK + 2: + bi->mod = OSMO_TRXD_MOD_T_8PSK; + bi->burst_len = OSMO_TRXD_BURST_LEN_8PSK; + break; + default: + return -EINVAL; + } + + bi->flags |= OSMO_TRXD_F_MOD_TYPE; + soft_bits_parse(&bi->burst[0], buf + TRXD_IND_V0HDR_LEN, bi->burst_len); + + /* consume everything, including the optional legacy padding */ + return buf_len; +} + +static int trxd_burst_ind_parse_v1(struct osmo_trxd_burst_ind *bi, + const uint8_t *buf, size_t buf_len) +{ + int rc; + + trxd_burst_ind_parse_hdr_v0(bi, buf); + + /* MTS (Modulation and Training Sequence) */ + rc = trxd_mts_parse_ind(bi, buf[TRXD_IND_V0HDR_LEN + 0]); + if (rc < 0) + return rc; + + /* C/I: Carrier-to-Interference ratio (in centiBels) */ + bi->ci_cb = (int16_t)osmo_load16be(buf + TRXD_IND_V0HDR_LEN + 1); + bi->flags |= OSMO_TRXD_F_CI_CB; + + return TRXD_IND_V1HDR_LEN; +} + +static int trxd_burst_ind_parse_v2(struct osmo_trxd_parse_state *st, + struct osmo_trxd_burst_ind *bi, + const uint8_t *buf, size_t buf_len) +{ + int rc; + + /* TDMA timeslot number (other bits are RFU) */ + bi->tn = buf[0] & 0x07; + + if (buf[1] & (1 << 7)) /* BATCH.ind */ + bi->flags |= OSMO_TRXD_F_BATCH_IND; + if (buf[1] & (1 << 6)) /* VAMOS.ind */ + bi->flags |= OSMO_TRXD_F_SHADOW_IND; + + /* TRX (RF channel) number */ + bi->trx_num = buf[1] & 0x3f; + bi->flags |= OSMO_TRXD_F_TRX_NUM; + + /* MTS (Modulation and Training Sequence) */ + rc = trxd_mts_parse_ind(bi, buf[2]); + if (rc < 0) + return rc; + + bi->rssi = -(int8_t)buf[3]; + bi->toa256 = (int16_t)osmo_load16be(buf + 4); + bi->ci_cb = (int16_t)osmo_load16be(buf + 6); + bi->flags |= OSMO_TRXD_F_CI_CB; + + /* TDMA frame number is absent in batched PDUs */ + if (st->num_pdus == 0) { + if (buf_len < TRXD_IND_V2HDR_LEN + sizeof(uint32_t)) + return -EINVAL; + bi->fn = osmo_load32be(buf + TRXD_IND_V2HDR_LEN); + st->fn = bi->fn; + return TRXD_IND_V2HDR_LEN + sizeof(uint32_t); + } + + bi->fn = st->fn; + return TRXD_IND_V2HDR_LEN; +} + +/*! Parse one burst indication (TRX -> L1) PDU from the given buffer. + * + * The PDU version is parsed from the first PDU of a datagram and kept + * in \a st. Starting from TRXDv2, a datagram may batch multiple PDUs: + * call this function repeatedly on the remaining buffer as long as the + * parsed PDU has OSMO_TRXD_F_BATCH_IND set in bi->flags. + * + * \param[inout] st parser state (see osmo_trxd_parse_state_init()) + * \param[out] bi parsed burst indication + * \param[in] buf buffer positioned at the beginning of a PDU + * \param[in] buf_len remaining length of the buffer + * \returns number of consumed bytes on success; negative on error */ +int osmo_trxd_burst_ind_parse(struct osmo_trxd_parse_state *st, + struct osmo_trxd_burst_ind *bi, + const uint8_t *buf, size_t buf_len) +{ + int hdr_len; + int burst_len; + + if (buf_len == 0) + return -EINVAL; + + /* PDU version is parsed from the first PDU of a datagram */ + if (st->num_pdus == 0) { + st->pdu_ver = buf[0] >> 4; + if (st->pdu_ver > OSMO_TRXD_PDU_VER_MAX) + return -ENOTSUP; + } + + memset(bi, 0, sizeof(*bi)); + + switch (st->pdu_ver) { + case 0: + if (buf_len < TRXD_IND_V0HDR_LEN) + return -EINVAL; + hdr_len = trxd_burst_ind_parse_v0(bi, buf, buf_len); + break; + case 1: + if (buf_len < TRXD_IND_V1HDR_LEN) + return -EINVAL; + hdr_len = trxd_burst_ind_parse_v1(bi, buf, buf_len); + break; + case 2: + if (buf_len < TRXD_IND_V2HDR_LEN) + return -EINVAL; + hdr_len = trxd_burst_ind_parse_v2(st, bi, buf, buf_len); + break; + default: + return -ENOTSUP; + } + + if (hdr_len < 0) + return hdr_len; + + if (bi->fn >= GSM_TDMA_HYPERFRAME) + return -EINVAL; + + st->num_pdus++; + + /* TRXDv0 consumes the whole datagram, incl. the burst bits */ + if (st->pdu_ver == 0) + return hdr_len; + + /* NOPE.ind contains no burst */ + if (bi->flags & OSMO_TRXD_F_NOPE_IND) { + bi->burst_len = 0; + return hdr_len; + } + + burst_len = burst_len_by_mod(bi->mod); + if (burst_len < 0) + return burst_len; + if (buf_len < (size_t)(hdr_len + burst_len)) + return -EINVAL; + + bi->burst_len = burst_len; + soft_bits_parse(&bi->burst[0], buf + hdr_len, bi->burst_len); + + return hdr_len + burst_len; +} + +/*! Append one encoded burst indication (TRX -> L1) PDU to the given msgb. + * + * For TRXDv2, PDU batching works by calling this function repeatedly on + * the same msgb; call osmo_trxd_build_fin() once the datagram is complete + * (it clears the BATCH.ind bit of the last PDU). + * + * \param[inout] msg destination message buffer + * \param[in] pdu_ver TRXD PDU version to encode + * \param[in] bi burst indication to be encoded + * \returns 0 on success; negative on error. Note that TRXDv0 cannot + * carry NOPE.ind PDUs: -ENOTSUP is returned and the caller + * shall skip (not send) them. */ +int osmo_trxd_burst_ind_build(struct msgb *msg, uint8_t pdu_ver, + const struct osmo_trxd_burst_ind *bi) +{ + bool first = (msgb_length(msg) == 0); + uint8_t *buf; + int rc; + + switch (pdu_ver) { + case 0: + /* v0 doesn't support NOPE.ind, the caller shall skip it */ + if (bi->flags & OSMO_TRXD_F_NOPE_IND) + return -ENOTSUP; + buf = msgb_put(msg, TRXD_IND_V0HDR_LEN); + buf[0] = ((pdu_ver & 0x0f) << 4) | (bi->tn & 0x07); + osmo_store32be(bi->fn, buf + 1); + buf[5] = (uint8_t)(-bi->rssi); + osmo_store16be(bi->toa256, buf + 6); + break; + case 1: + buf = msgb_put(msg, TRXD_IND_V1HDR_LEN); + buf[0] = ((pdu_ver & 0x0f) << 4) | (bi->tn & 0x07); + osmo_store32be(bi->fn, buf + 1); + buf[5] = (uint8_t)(-bi->rssi); + osmo_store16be(bi->toa256, buf + 6); + rc = trxd_mts_build_ind(&buf[8], bi); + if (rc < 0) + return rc; + osmo_store16be(bi->ci_cb, buf + 9); + break; + case 2: + /* l2h points to the last encoded PDU (for osmo_trxd_build_fin) */ + msg->l2h = msg->tail; + buf = msgb_put(msg, TRXD_IND_V2HDR_LEN); + buf[0] = bi->tn & 0x07; + /* BATCH.ind; unset in the last PDU by osmo_trxd_build_fin() */ + buf[1] = (bi->trx_num & 0x3f) | (1 << 7); + if (bi->flags & OSMO_TRXD_F_SHADOW_IND) + buf[1] |= (1 << 6); + rc = trxd_mts_build_ind(&buf[2], bi); + if (rc < 0) + return rc; + buf[3] = (uint8_t)(-bi->rssi); + osmo_store16be(bi->toa256, buf + 4); + osmo_store16be(bi->ci_cb, buf + 6); + /* Some fields are not present in batched PDUs */ + if (first) { + buf[0] |= (pdu_ver & 0x0f) << 4; + msgb_put_u32(msg, bi->fn); + } + break; + default: + return -ENOTSUP; + } + + if (~bi->flags & OSMO_TRXD_F_NOPE_IND) { + soft_bits_build(msgb_put(msg, bi->burst_len), + &bi->burst[0], bi->burst_len); + } + + return 0; +} + +/*********************************************************************** + * burst transmit request (L1 -> TRX): hard bits + ***********************************************************************/ + +static int trxd_burst_req_parse_v01(struct osmo_trxd_burst_req *br, + const uint8_t *buf, size_t buf_len) +{ + size_t burst_len = buf_len - TRXD_REQ_V01HDR_LEN; + + br->tn = buf[0] & 0x07; + br->fn = osmo_load32be(&buf[1]); + br->att = buf[5]; + + switch (burst_len) { + case OSMO_TRXD_BURST_LEN_8PSK: + br->mod = OSMO_TRXD_MOD_T_8PSK; + break; + case OSMO_TRXD_BURST_LEN_GMSK: + br->mod = OSMO_TRXD_MOD_T_GMSK; + break; + default: + return -EINVAL; + } + + br->flags |= OSMO_TRXD_F_MOD_TYPE; + br->burst_len = burst_len; + memcpy(&br->burst[0], buf + TRXD_REQ_V01HDR_LEN, burst_len); + + return buf_len; +} + +static int trxd_burst_req_parse_v2(struct osmo_trxd_parse_state *st, + struct osmo_trxd_burst_req *br, + const uint8_t *buf, size_t buf_len) +{ + size_t hdr_len = TRXD_REQ_V2HDR_LEN; + int burst_len; + int rc; + + br->tn = buf[0] & 0x07; + + if (buf[1] & (1 << 7)) /* BATCH.ind */ + br->flags |= OSMO_TRXD_F_BATCH_IND; + + br->trx_num = buf[1] & 0x3f; + br->flags |= OSMO_TRXD_F_TRX_NUM; + + rc = trxd_mts_parse_req(br, buf[2]); + if (rc < 0) + return rc; + + br->att = buf[3]; + br->scpir = (int8_t)buf[4]; + /* buf[5..7] is spare */ + + /* TDMA frame number is absent in batched PDUs */ + if (st->num_pdus == 0) { + if (buf_len < hdr_len + sizeof(uint32_t)) + return -EINVAL; + br->fn = osmo_load32be(buf + hdr_len); + st->fn = br->fn; + hdr_len += sizeof(uint32_t); + } else { + br->fn = st->fn; + } + + burst_len = burst_len_by_mod(br->mod); + if (burst_len < 0) + return burst_len; + if (buf_len < hdr_len + burst_len) + return -EINVAL; + + br->burst_len = burst_len; + memcpy(&br->burst[0], buf + hdr_len, burst_len); + + return hdr_len + burst_len; +} + +/*! Parse one burst transmit request (L1 -> TRX) PDU from the given buffer. + * + * The PDU version is parsed from the first PDU of a datagram and kept + * in \a st. Starting from TRXDv2, a datagram may batch multiple PDUs: + * call this function repeatedly on the remaining buffer as long as the + * parsed PDU has OSMO_TRXD_F_BATCH_IND set in br->flags. + * + * \param[inout] st parser state (see osmo_trxd_parse_state_init()) + * \param[out] br parsed burst transmit request + * \param[in] buf buffer positioned at the beginning of a PDU + * \param[in] buf_len remaining length of the buffer + * \returns number of consumed bytes on success; negative on error */ +int osmo_trxd_burst_req_parse(struct osmo_trxd_parse_state *st, + struct osmo_trxd_burst_req *br, + const uint8_t *buf, size_t buf_len) +{ + int pdu_len; + + if (buf_len == 0) + return -EINVAL; + + if (st->num_pdus == 0) { + st->pdu_ver = buf[0] >> 4; + if (st->pdu_ver > OSMO_TRXD_PDU_VER_MAX) + return -ENOTSUP; + } + + memset(br, 0, sizeof(*br)); + + switch (st->pdu_ver) { + case 0: + case 1: + if (buf_len < TRXD_REQ_V01HDR_LEN) + return -EINVAL; + pdu_len = trxd_burst_req_parse_v01(br, buf, buf_len); + break; + case 2: + if (buf_len < TRXD_REQ_V2HDR_LEN) + return -EINVAL; + pdu_len = trxd_burst_req_parse_v2(st, br, buf, buf_len); + break; + default: + return -ENOTSUP; + } + + if (pdu_len < 0) + return pdu_len; + + if (br->fn >= GSM_TDMA_HYPERFRAME) + return -EINVAL; + + st->num_pdus++; + return pdu_len; +} + +/*! Append one encoded burst transmit request (L1 -> TRX) PDU to the given msgb. + * + * For TRXDv2, PDU batching works by calling this function repeatedly on + * the same msgb; call osmo_trxd_build_fin() once the datagram is complete + * (it clears the BATCH.ind bit of the last PDU). + * + * \param[inout] msg destination message buffer + * \param[in] pdu_ver TRXD PDU version to encode + * \param[in] br burst transmit request to be encoded + * \returns 0 on success; negative on error */ +int osmo_trxd_burst_req_build(struct msgb *msg, uint8_t pdu_ver, + const struct osmo_trxd_burst_req *br) +{ + bool first = (msgb_length(msg) == 0); + uint8_t *buf; + int rc; + + switch (pdu_ver) { + /* Both versions have the same PDU format */ + case 0: + case 1: + buf = msgb_put(msg, TRXD_REQ_V01HDR_LEN); + buf[0] = ((pdu_ver & 0x0f) << 4) | (br->tn & 0x07); + osmo_store32be(br->fn, buf + 1); + buf[5] = br->att; + break; + case 2: + /* l2h points to the last encoded PDU (for osmo_trxd_build_fin) */ + msg->l2h = msg->tail; + buf = msgb_put(msg, TRXD_REQ_V2HDR_LEN); + buf[0] = br->tn & 0x07; + /* BATCH.ind; unset in the last PDU by osmo_trxd_build_fin() */ + buf[1] = (br->trx_num & 0x3f) | (1 << 7); + rc = trxd_mts_build_req(&buf[2], br); + if (rc < 0) + return rc; + buf[3] = br->att; + buf[4] = (uint8_t)br->scpir; + buf[5] = buf[6] = buf[7] = 0x00; /* Spare */ + /* Some fields are not present in batched PDUs */ + if (first) { + buf[0] |= (pdu_ver & 0x0f) << 4; + msgb_put_u32(msg, br->fn); + } + break; + default: + return -ENOTSUP; + } + + /* copy hard-bits {0,1} */ + memcpy(msgb_put(msg, br->burst_len), + &br->burst[0], br->burst_len); + + return 0; +} + +/*! Finalize a datagram built by osmo_trxd_burst_{ind,req}_build(). + * \param[inout] msg message buffer holding the encoded PDU(s) + * \param[in] pdu_ver TRXD PDU version in use */ +void osmo_trxd_build_fin(struct msgb *msg, uint8_t pdu_ver) +{ + /* TRXDv2: unset BATCH.ind in the last PDU */ + if (pdu_ver >= 2 && msg->l2h != NULL) + msg->l2h[1] &= ~(1 << 7); +} + +/*********************************************************************** + * logging helpers + ***********************************************************************/ + +/*! Compose a human-readable representation of the given burst indication + * (for logging), store into a thread-local static buffer. + * \param[in] bi BURST.ind to get a human-readable representation of + * \returns pointer to a thread-local static buffer */ +const char *osmo_trxd_burst_ind_name(const struct osmo_trxd_burst_ind *bi) +{ + static __thread char buf[256]; + return osmo_trxd_burst_ind_name_buf(&buf[0], sizeof(buf), bi); +} + +/*! Compose a human-readable representation of the given burst indication + * (for logging), store into the given buffer. + * \param[out] buf output buffer to store the result + * \param[in] buf_len size of the output buffer + * \param[in] bi BURST.ind to get a human-readable representation of + * \returns pointer to the given buffer */ +char *osmo_trxd_burst_ind_name_buf(char *buf, size_t buf_len, + const struct osmo_trxd_burst_ind *bi) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buf_len }; + + OSMO_STRBUF_PRINTF(sb, "%s tn=%u fn=%u", + (bi->flags & OSMO_TRXD_F_NOPE_IND) ? "NOPE.ind" : "BURST.ind", + bi->tn, bi->fn); + if (bi->flags & OSMO_TRXD_F_TRX_NUM) + OSMO_STRBUF_PRINTF(sb, " trx_num=%u", bi->trx_num); + OSMO_STRBUF_PRINTF(sb, " rssi=%d toa256=%d", bi->rssi, bi->toa256); + if (bi->flags & OSMO_TRXD_F_CI_CB) + OSMO_STRBUF_PRINTF(sb, " C/I=%d cB", bi->ci_cb); + if (bi->flags & OSMO_TRXD_F_NOPE_IND) + return buf; + if (bi->flags & OSMO_TRXD_F_MOD_TYPE) + OSMO_STRBUF_PRINTF(sb, " mod=%s", osmo_trxd_mod_type_name(bi->mod)); + if (bi->flags & OSMO_TRXD_F_TS_INFO) + OSMO_STRBUF_PRINTF(sb, " set=%u tsc=%u", bi->tsc_set, bi->tsc); + OSMO_STRBUF_PRINTF(sb, " burst_len=%zu", bi->burst_len); + + return buf; +} + +/*! Compose a human-readable representation of the given burst transmit + * request (for logging), store into a thread-local static buffer. + * \param[in] br BURST.req to get a human-readable representation of + * \returns pointer to a thread-local static buffer */ +const char *osmo_trxd_burst_req_name(const struct osmo_trxd_burst_req *br) +{ + static __thread char buf[256]; + return osmo_trxd_burst_req_name_buf(&buf[0], sizeof(buf), br); +} + +/*! Compose a human-readable representation of the given burst transmit + * request (for logging), store into the given buffer. + * \param[out] buf output buffer to store the result + * \param[in] buf_len size of the output buffer + * \param[in] br BURST.req to get a human-readable representation of + * \returns pointer to the given buffer */ +char *osmo_trxd_burst_req_name_buf(char *buf, size_t buf_len, + const struct osmo_trxd_burst_req *br) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buf_len }; + + OSMO_STRBUF_PRINTF(sb, "BURST.req tn=%u fn=%u att=%u", br->tn, br->fn, br->att); + if (br->flags & OSMO_TRXD_F_TRX_NUM) + OSMO_STRBUF_PRINTF(sb, " trx_num=%u", br->trx_num); + if (br->flags & OSMO_TRXD_F_MOD_TYPE) + OSMO_STRBUF_PRINTF(sb, " mod=%s", osmo_trxd_mod_type_name(br->mod)); + if (br->flags & OSMO_TRXD_F_TS_INFO) + OSMO_STRBUF_PRINTF(sb, " set=%u tsc=%u", br->tsc_set, br->tsc); + OSMO_STRBUF_PRINTF(sb, " burst_len=%zu", br->burst_len); + + return buf; +} diff --git a/tests/Makefile.am b/tests/Makefile.am index d4589a4..60fd0b0 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,4 +1,5 @@ SUBDIRS = \ + libosmo-trx \ CommonLibs \ Transceiver52M \ $(NULL) diff --git a/tests/libosmo-trx/Makefile.am b/tests/libosmo-trx/Makefile.am new file mode 100644 index 0000000..162cbac --- /dev/null +++ b/tests/libosmo-trx/Makefile.am @@ -0,0 +1,34 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/libosmo-trx/include \ + -I$(top_builddir)/libosmo-trx/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall -g \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(NULL) + +# Link statically, so that the tests can also call symbols hidden +# from the public library ABI (-export-symbols-regex). +AM_LDFLAGS = -static + +LDADD = \ + $(top_builddir)/libosmo-trx/src/libosmo-trx.la \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(NULL) + +EXTRA_DIST = \ + trxc_test.ok \ + trxd_test.ok \ + $(NULL) + +check_PROGRAMS = \ + trxc_test \ + trxd_test \ + $(NULL) + +trxc_test_SOURCES = trxc_test.c + +trxd_test_SOURCES = trxd_test.c diff --git a/tests/libosmo-trx/trxc_test.c b/tests/libosmo-trx/trxc_test.c new file mode 100644 index 0000000..82676a7 --- /dev/null +++ b/tests/libosmo-trx/trxc_test.c @@ -0,0 +1,162 @@ +/*! \file tests/trxc_test.c + * Regression test for the TRXC message codec. */ + +/* + * (C) 2026 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de> + * Author: Vadim Yanitskiy <vyanitskiy(a)sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <
http://www.gnu.org/licenses/
>. + */ + +#include <stdio.h> +#include <string.h> + +#include <osmocom/core/utils.h> + +#include <osmocom/trx/trxc.h> + +static void test_msg_parse(void) +{ + static const char * const messages[] = { + "CMD POWERON", + "CMD POWEROFF", + "CMD RXTUNE 890000", + "CMD SETSLOT 0 7 C5/S1", + "CMD ECHO", /* MS dialect */ + "CMD FAKE_TOA 128 0", /* fake_trx custom command */ + "RSP POWERON 0", + "RSP SETSLOT 0 0 7", + "RSP SETFORMAT 1 2", + "RSP NOMTXPOWER 0 23", + "RSP ERR 1", + "RSP MEASURE 0 890000 -85", + "IND CLOCK 402312", + /* malformed messages */ + "NDI KCOLC 123456", + "RSP NOSTATUS", + "CMD ", + "", + }; + + printf("=== %s ===\n", __func__); + + for (unsigned int i = 0; i < ARRAY_SIZE(messages); i++) { + struct osmo_trxc_msg msg; + int rc; + + rc = osmo_trxc_msg_parse(&msg, messages[i], strlen(messages[i])); + if (rc < 0) { + printf("'%s' -> rc=%d\n", messages[i], rc); + continue; + } + printf("'%s' -> type=%d cmd='%s' status=%d params='%s'\n", + messages[i], msg.type, msg.cmd, msg.status, msg.params); + /* re-encode and compare against the original */ + printf("\tre-encoded: '%s'\n", osmo_trxc_msg_name(&msg)); + OSMO_ASSERT(strcmp(osmo_trxc_msg_name(&msg), messages[i]) == 0); + } +} + +static void test_msg_build(void) +{ + static const struct osmo_trxc_msg messages[] = { + { .type = OSMO_TRXC_MT_CMD, .cmd = "POWERON" }, + { .type = OSMO_TRXC_MT_CMD, .cmd = "SETSLOT", .params = "0 7" }, + { .type = OSMO_TRXC_MT_RSP, .cmd = "POWERON", .status = 0 }, + { .type = OSMO_TRXC_MT_RSP, .cmd = "SETSLOT", .status = 1, .params = "0 7" }, + { .type = OSMO_TRXC_MT_IND, .cmd = "CLOCK", .params = "402312" }, + }; + char buf[OSMO_TRXC_MSG_BUF_SIZE]; + int rc; + + printf("=== %s ===\n", __func__); + + for (unsigned int i = 0; i < ARRAY_SIZE(messages); i++) { + rc = osmo_trxc_msg_build(buf, sizeof(buf), &messages[i]); + OSMO_ASSERT(rc > 0 && rc == (int)strlen(buf)); + printf("'%s' (rc=%d)\n", buf, rc); + } + + /* buffer too small */ + rc = osmo_trxc_msg_build(buf, 8, &messages[1]); + printf("build into a too small buffer: rc=%d\n", rc); + OSMO_ASSERT(rc < 0); +} + +static void test_params_scan(void) +{ + struct osmo_trxc_msg msg; + unsigned int tn, ts_type; + int rc; + + printf("=== %s ===\n", __func__); + + rc = osmo_trxc_msg_parse(&msg, "RSP SETSLOT 0 3 7", 17); + OSMO_ASSERT(rc == 0); + + rc = osmo_trxc_msg_params_scan(&msg, "%u %u", &tn, &ts_type); + printf("'%s' -> rc=%d tn=%u ts_type=%u\n", msg.params, rc, tn, ts_type); + OSMO_ASSERT(rc == 2 && tn == 3 && ts_type == 7); +} + +static void test_clk_ind(void) +{ + static const char * const messages[] = { + "IND CLOCK 402312", + "IND CLOCK 0", + "IND CLOCK 2715647", /* GSM_TDMA_HYPERFRAME - 1 */ + "IND CLOCK 2715648", /* GSM_TDMA_HYPERFRAME */ + "IND CLOCK", + "IND KCOLC 123", + "CMD CLOCK 123", + }; + char buf[OSMO_TRXC_MSG_BUF_SIZE]; + uint32_t fn; + int rc; + + printf("=== %s ===\n", __func__); + + for (unsigned int i = 0; i < ARRAY_SIZE(messages); i++) { + rc = osmo_trxc_clock_ind_parse(&fn, messages[i], strlen(messages[i])); + if (rc < 0) { + printf("'%s' -> rc=%d\n", messages[i], rc); + continue; + } + printf("'%s' -> fn=%u\n", messages[i], fn); + /* re-encode and compare against the original */ + rc = osmo_trxc_clock_ind_build(buf, sizeof(buf), fn); + OSMO_ASSERT(rc > 0); + OSMO_ASSERT(strcmp(buf, messages[i]) == 0); + } + + /* out of range TDMA fn */ + rc = osmo_trxc_clock_ind_build(buf, sizeof(buf), 2715648); + printf("build with out of range fn: rc=%d\n", rc); + OSMO_ASSERT(rc < 0); +} + +int main(int argc, char **argv) +{ + test_msg_parse(); + test_msg_build(); + test_params_scan(); + test_clk_ind(); + + printf("Done\n"); + return 0; +} diff --git a/tests/libosmo-trx/trxc_test.ok b/tests/libosmo-trx/trxc_test.ok new file mode 100644 index 0000000..9422d19 --- /dev/null +++ b/tests/libosmo-trx/trxc_test.ok @@ -0,0 +1,50 @@ +=== test_msg_parse === +'CMD POWERON' -> type=0 cmd='POWERON' status=0 params='' + re-encoded: 'CMD POWERON' +'CMD POWEROFF' -> type=0 cmd='POWEROFF' status=0 params='' + re-encoded: 'CMD POWEROFF' +'CMD RXTUNE 890000' -> type=0 cmd='RXTUNE' status=0 params='890000' + re-encoded: 'CMD RXTUNE 890000' +'CMD SETSLOT 0 7 C5/S1' -> type=0 cmd='SETSLOT' status=0 params='0 7 C5/S1' + re-encoded: 'CMD SETSLOT 0 7 C5/S1' +'CMD ECHO' -> type=0 cmd='ECHO' status=0 params='' + re-encoded: 'CMD ECHO' +'CMD FAKE_TOA 128 0' -> type=0 cmd='FAKE_TOA' status=0 params='128 0' + re-encoded: 'CMD FAKE_TOA 128 0' +'RSP POWERON 0' -> type=1 cmd='POWERON' status=0 params='' + re-encoded: 'RSP POWERON 0' +'RSP SETSLOT 0 0 7' -> type=1 cmd='SETSLOT' status=0 params='0 7' + re-encoded: 'RSP SETSLOT 0 0 7' +'RSP SETFORMAT 1 2' -> type=1 cmd='SETFORMAT' status=1 params='2' + re-encoded: 'RSP SETFORMAT 1 2' +'RSP NOMTXPOWER 0 23' -> type=1 cmd='NOMTXPOWER' status=0 params='23' + re-encoded: 'RSP NOMTXPOWER 0 23' +'RSP ERR 1' -> type=1 cmd='ERR' status=1 params='' + re-encoded: 'RSP ERR 1' +'RSP MEASURE 0 890000 -85' -> type=1 cmd='MEASURE' status=0 params='890000 -85' + re-encoded: 'RSP MEASURE 0 890000 -85' +'IND CLOCK 402312' -> type=2 cmd='CLOCK' status=0 params='402312' + re-encoded: 'IND CLOCK 402312' +'NDI KCOLC 123456' -> rc=-22 +'RSP NOSTATUS' -> rc=-22 +'CMD ' -> rc=-22 +'' -> rc=-22 +=== test_msg_build === +'CMD POWERON' (rc=11) +'CMD SETSLOT 0 7' (rc=15) +'RSP POWERON 0' (rc=13) +'RSP SETSLOT 1 0 7' (rc=17) +'IND CLOCK 402312' (rc=16) +build into a too small buffer: rc=-90 +=== test_params_scan === +'3 7' -> rc=2 tn=3 ts_type=7 +=== test_clk_ind === +'IND CLOCK 402312' -> fn=402312 +'IND CLOCK 0' -> fn=0 +'IND CLOCK 2715647' -> fn=2715647 +'IND CLOCK 2715648' -> rc=-34 +'IND CLOCK' -> rc=-22 +'IND KCOLC 123' -> rc=-22 +'CMD CLOCK 123' -> rc=-22 +build with out of range fn: rc=-34 +Done diff --git a/tests/libosmo-trx/trxd_test.c b/tests/libosmo-trx/trxd_test.c new file mode 100644 index 0000000..454de86 --- /dev/null +++ b/tests/libosmo-trx/trxd_test.c @@ -0,0 +1,412 @@ +/*! \file tests/trxd_test.c + * Regression test for the TRXD PDU codec. */ + +/* + * (C) 2026 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de> + * Author: Vadim Yanitskiy <vyanitskiy(a)sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <
http://www.gnu.org/licenses/
>. + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> + +#include <osmocom/trx/trxd.h> + +/* MTS (Modulation and Training Sequence) field, for TRXDv1 and higher */ +int trxd_mts_parse_ind(struct osmo_trxd_burst_ind *bi, uint8_t mts); +int trxd_mts_parse_req(struct osmo_trxd_burst_req *br, uint8_t mts); +int trxd_mts_build_ind(uint8_t *mts, const struct osmo_trxd_burst_ind *bi); +int trxd_mts_build_req(uint8_t *mts, const struct osmo_trxd_burst_req *br); + +static void fill_burst_ind(struct osmo_trxd_burst_ind *bi, size_t burst_len) +{ + *bi = (struct osmo_trxd_burst_ind){ + .flags = OSMO_TRXD_F_MOD_TYPE + | OSMO_TRXD_F_TS_INFO + | OSMO_TRXD_F_CI_CB, + .fn = 1234567, + .tn = 5, + .toa256 = -512, + .rssi = -63, + .mod = (burst_len == OSMO_TRXD_BURST_LEN_8PSK) ? + OSMO_TRXD_MOD_T_8PSK : OSMO_TRXD_MOD_T_GMSK, + .tsc_set = 1, + .tsc = 7, + .ci_cb = -150, + .burst_len = burst_len, + }; + + for (size_t i = 0; i < burst_len; i++) + bi->burst[i] = (i & 1) ? -100 : 100; +} + +static void fill_burst_req(struct osmo_trxd_burst_req *br, size_t burst_len) +{ + *br = (struct osmo_trxd_burst_req){ + .flags = OSMO_TRXD_F_MOD_TYPE + | OSMO_TRXD_F_TS_INFO, + .fn = 2654321, + .tn = 2, + .att = 10, + .mod = (burst_len == OSMO_TRXD_BURST_LEN_8PSK) ? + OSMO_TRXD_MOD_T_8PSK : OSMO_TRXD_MOD_T_GMSK, + .tsc_set = 0, + .tsc = 3, + .burst_len = burst_len, + }; + + for (size_t i = 0; i < burst_len; i++) + br->burst[i] = (i & 1); +} + +static void test_burst_ind(uint8_t pdu_ver, size_t burst_len) +{ + struct osmo_trxd_parse_state st; + struct osmo_trxd_burst_ind bi, bi2; + struct msgb *msg = msgb_alloc(4096, "ind"); + int rc; + + printf("=== %s(v%u, burst_len=%zu) ===\n", + __func__, pdu_ver, burst_len); + + fill_burst_ind(&bi, burst_len); + rc = osmo_trxd_burst_ind_build(msg, pdu_ver, &bi); + OSMO_ASSERT(rc == 0); + osmo_trxd_build_fin(msg, pdu_ver); + + printf("build: %s\n", osmo_trxd_burst_ind_name(&bi)); + printf("datagram (%u bytes): %s...\n", msgb_length(msg), + osmo_hexdump_nospc(msgb_data(msg), OSMO_MIN(16, msgb_length(msg)))); + + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_ind_parse(&st, &bi2, msgb_data(msg), msgb_length(msg)); + OSMO_ASSERT(rc == (int)msgb_length(msg)); + printf("parse: %s\n", osmo_trxd_burst_ind_name(&bi2)); + + OSMO_ASSERT(bi2.fn == bi.fn && bi2.tn == bi.tn); + OSMO_ASSERT(bi2.rssi == bi.rssi && bi2.toa256 == bi.toa256); + OSMO_ASSERT(bi2.burst_len == bi.burst_len); + OSMO_ASSERT(memcmp(bi2.burst, bi.burst, bi.burst_len) == 0); + if (pdu_ver >= 1) { + OSMO_ASSERT(bi2.mod == bi.mod); + OSMO_ASSERT(bi2.tsc_set == bi.tsc_set && bi2.tsc == bi.tsc); + OSMO_ASSERT(bi2.ci_cb == bi.ci_cb); + } + + msgb_free(msg); +} + +static void test_burst_ind_nope(uint8_t pdu_ver) +{ + struct osmo_trxd_parse_state st; + struct osmo_trxd_burst_ind bi, bi2; + struct msgb *msg = msgb_alloc(4096, "nope"); + int rc; + + printf("=== %s(v%u) ===\n", __func__, pdu_ver); + + fill_burst_ind(&bi, 0); + bi.flags = OSMO_TRXD_F_NOPE_IND | OSMO_TRXD_F_CI_CB; + + rc = osmo_trxd_burst_ind_build(msg, pdu_ver, &bi); + if (pdu_ver == 0) { + /* TRXDv0 cannot carry NOPE.ind */ + printf("build: rc=%d (expected -ENOTSUP)\n", rc); + OSMO_ASSERT(rc == -ENOTSUP); + msgb_free(msg); + return; + } + OSMO_ASSERT(rc == 0); + osmo_trxd_build_fin(msg, pdu_ver); + printf("build: %s\n", osmo_trxd_burst_ind_name(&bi)); + + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_ind_parse(&st, &bi2, msgb_data(msg), msgb_length(msg)); + OSMO_ASSERT(rc == (int)msgb_length(msg)); + printf("parse: %s\n", osmo_trxd_burst_ind_name(&bi2)); + + OSMO_ASSERT(bi2.flags & OSMO_TRXD_F_NOPE_IND); + OSMO_ASSERT(bi2.burst_len == 0); + + msgb_free(msg); +} + +static void test_burst_ind_v0_legacy_padding(void) +{ + struct osmo_trxd_parse_state st; + struct osmo_trxd_burst_ind bi, bi2; + struct msgb *msg = msgb_alloc(4096, "legacy"); + int rc; + + printf("=== %s ===\n", __func__); + + fill_burst_ind(&bi, OSMO_TRXD_BURST_LEN_GMSK); + rc = osmo_trxd_burst_ind_build(msg, 0, &bi); + OSMO_ASSERT(rc == 0); + + /* a legacy transceiver may append two garbage bytes */ + msgb_put_u16(msg, 0xdead); + + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_ind_parse(&st, &bi2, msgb_data(msg), msgb_length(msg)); + printf("parse: rc=%d (%u bytes incl. padding)\n", rc, msgb_length(msg)); + OSMO_ASSERT(rc == (int)msgb_length(msg)); + OSMO_ASSERT(bi2.burst_len == OSMO_TRXD_BURST_LEN_GMSK); + + msgb_free(msg); +} + +static void test_burst_ind_batch(uint8_t pdu_ver) +{ + struct osmo_trxd_parse_state st; + struct osmo_trxd_burst_ind bi, bi2; + struct msgb *msg; + const uint8_t *buf; + size_t buf_len; + unsigned int i; + int rc; + + /* PDU batching is only supported by TRXDv2 and higher */ + if (pdu_ver < 2) + return; + + printf("=== %s(v%u) ===\n", __func__, pdu_ver); + + msg = msgb_alloc(4096, "batch"); + + /* batch a normal burst + a NOPE.ind for another timeslot */ + fill_burst_ind(&bi, OSMO_TRXD_BURST_LEN_GMSK); + bi.trx_num = 1; + bi.flags |= OSMO_TRXD_F_TRX_NUM; + rc = osmo_trxd_burst_ind_build(msg, pdu_ver, &bi); + OSMO_ASSERT(rc == 0); + + bi.tn = 6; + bi.flags = OSMO_TRXD_F_NOPE_IND | OSMO_TRXD_F_TRX_NUM; + bi.burst_len = 0; + rc = osmo_trxd_burst_ind_build(msg, pdu_ver, &bi); + OSMO_ASSERT(rc == 0); + + osmo_trxd_build_fin(msg, pdu_ver); + printf("datagram (%u bytes)\n", msgb_length(msg)); + + osmo_trxd_parse_state_init(&st); + buf = msgb_data(msg); + buf_len = msgb_length(msg); + for (i = 0; buf_len > 0; i++) { + rc = osmo_trxd_burst_ind_parse(&st, &bi2, buf, buf_len); + OSMO_ASSERT(rc > 0); + printf("parse[%u]: %s\n", i, osmo_trxd_burst_ind_name(&bi2)); + /* the batched PDU inherits the TDMA fn of the first one */ + OSMO_ASSERT(bi2.fn == 1234567); + buf += rc; + buf_len -= rc; + /* BATCH.ind shall be set on all PDUs but the last one */ + OSMO_ASSERT(!!(bi2.flags & OSMO_TRXD_F_BATCH_IND) == (buf_len > 0)); + } + OSMO_ASSERT(i == 2); + + msgb_free(msg); +} + +static void test_burst_req(uint8_t pdu_ver, size_t burst_len) +{ + struct osmo_trxd_parse_state st; + struct osmo_trxd_burst_req br, br2; + struct msgb *msg = msgb_alloc(4096, "req"); + int rc; + + printf("=== %s(v%u, burst_len=%zu) ===\n", __func__, pdu_ver, burst_len); + + fill_burst_req(&br, burst_len); + rc = osmo_trxd_burst_req_build(msg, pdu_ver, &br); + OSMO_ASSERT(rc == 0); + osmo_trxd_build_fin(msg, pdu_ver); + + printf("build: %s\n", osmo_trxd_burst_req_name(&br)); + printf("datagram (%u bytes): %s...\n", msgb_length(msg), + osmo_hexdump_nospc(msgb_data(msg), OSMO_MIN(16, msgb_length(msg)))); + + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_req_parse(&st, &br2, msgb_data(msg), msgb_length(msg)); + OSMO_ASSERT(rc == (int)msgb_length(msg)); + printf("parse: %s\n", osmo_trxd_burst_req_name(&br2)); + + OSMO_ASSERT(br2.fn == br.fn && br2.tn == br.tn); + OSMO_ASSERT(br2.att == br.att); + OSMO_ASSERT(br2.burst_len == br.burst_len); + OSMO_ASSERT(memcmp(br2.burst, br.burst, br.burst_len) == 0); + if (pdu_ver >= 2) { + OSMO_ASSERT(br2.mod == br.mod); + OSMO_ASSERT(br2.tsc_set == br.tsc_set && br2.tsc == br.tsc); + } + + msgb_free(msg); +} + +static void test_burst_req_batch(uint8_t pdu_ver) +{ + struct osmo_trxd_parse_state st; + struct osmo_trxd_burst_req br, br2; + struct msgb *msg; + const uint8_t *buf; + size_t buf_len; + unsigned int i; + int rc; + + /* PDU batching is only supported by TRXDv2 and higher */ + if (pdu_ver < 2) + return; + + printf("=== %s(v%u) ===\n", __func__, pdu_ver); + + msg = msgb_alloc(4096, "batch"); + + for (i = 0; i < 3; i++) { + fill_burst_req(&br, OSMO_TRXD_BURST_LEN_GMSK); + br.tn = i; + rc = osmo_trxd_burst_req_build(msg, pdu_ver, &br); + OSMO_ASSERT(rc == 0); + } + osmo_trxd_build_fin(msg, pdu_ver); + printf("datagram (%u bytes)\n", msgb_length(msg)); + + osmo_trxd_parse_state_init(&st); + buf = msgb_data(msg); + buf_len = msgb_length(msg); + for (i = 0; buf_len > 0; i++) { + rc = osmo_trxd_burst_req_parse(&st, &br2, buf, buf_len); + OSMO_ASSERT(rc > 0); + printf("parse[%u]: %s\n", i, osmo_trxd_burst_req_name(&br2)); + OSMO_ASSERT(br2.tn == i && br2.fn == 2654321); + buf += rc; + buf_len -= rc; + OSMO_ASSERT(!!(br2.flags & OSMO_TRXD_F_BATCH_IND) == (buf_len > 0)); + } + OSMO_ASSERT(i == 3); + + msgb_free(msg); +} + +static void test_mts(void) +{ + static const uint8_t mts_bytes[] = { + 0x80, /* NOPE.ind */ + 0x07, /* GMSK, set 0, tsc 7 */ + 0x1d, /* GMSK, set 3, tsc 5 */ + 0x2b, /* 8-PSK, set 1, tsc 3 */ + 0x31, /* GMSK, Access Burst, tsc 1 */ + 0x66, /* AQPSK, set 0, tsc 6 */ + 0x51, /* invalid */ + }; + + printf("=== %s ===\n", __func__); + + for (unsigned int i = 0; i < ARRAY_SIZE(mts_bytes); i++) { + struct osmo_trxd_burst_ind bi = { }; + uint8_t mts = mts_bytes[i]; + + if (trxd_mts_parse_ind(&bi, mts) < 0) { + printf("trxd_mts_parse_ind(0x%02x) failed\n", mts); + continue; + } + printf("trxd_mts_parse_ind(0x%02x): flags=0x%02x mod=%s set=%u tsc=%u", + mts, bi.flags, osmo_trxd_mod_type_name(bi.mod), + bi.tsc_set, bi.tsc); + + /* re-encode and compare */ + OSMO_ASSERT(trxd_mts_build_ind(&mts, &bi) == 0); + printf(" (re-encoded: 0x%02x)\n", mts); + OSMO_ASSERT(mts == mts_bytes[i]); + } +} + +static void test_parse_errors(void) +{ + struct osmo_trxd_parse_state st; + struct osmo_trxd_burst_ind bi; + struct osmo_trxd_burst_req br; + uint8_t buf[512]; + int rc; + + printf("=== %s ===\n", __func__); + + /* empty buffer */ + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_ind_parse(&st, &bi, buf, 0); + printf("ind, empty buffer: rc=%d\n", rc); + OSMO_ASSERT(rc < 0); + + /* unknown PDU version */ + memset(buf, 0, sizeof(buf)); + buf[0] = (0x0f << 4); + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_ind_parse(&st, &bi, buf, sizeof(buf)); + printf("ind, PDU version 15: rc=%d\n", rc); + OSMO_ASSERT(rc == -ENOTSUP); + + /* TRXDv0 with odd burst length */ + memset(buf, 0, sizeof(buf)); + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_ind_parse(&st, &bi, buf, 8 + 100); + printf("ind, v0 with odd burst length: rc=%d\n", rc); + OSMO_ASSERT(rc == -EINVAL); + + /* TRXDv1 header, but no burst bits */ + memset(buf, 0, sizeof(buf)); + buf[0] = (1 << 4); + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_ind_parse(&st, &bi, buf, 11); + printf("ind, v1 without burst bits: rc=%d\n", rc); + OSMO_ASSERT(rc == -EINVAL); + + /* illegal TDMA fn */ + memset(buf, 0, sizeof(buf)); + osmo_store32be(0xffffffff, buf + 1); + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_req_parse(&st, &br, buf, 6 + 148); + printf("req, illegal TDMA fn: rc=%d\n", rc); + OSMO_ASSERT(rc == -EINVAL); +} + +int main(int argc, char **argv) +{ + for (uint8_t pdu_ver = 0; pdu_ver <= OSMO_TRXD_PDU_VER_MAX; pdu_ver++) { + test_burst_req(pdu_ver, OSMO_TRXD_BURST_LEN_GMSK); + test_burst_req(pdu_ver, OSMO_TRXD_BURST_LEN_8PSK); + test_burst_req_batch(pdu_ver); + + test_burst_ind(pdu_ver, OSMO_TRXD_BURST_LEN_GMSK); + test_burst_ind(pdu_ver, OSMO_TRXD_BURST_LEN_8PSK); + test_burst_ind_batch(pdu_ver); + test_burst_ind_nope(pdu_ver); + } + + test_burst_ind_v0_legacy_padding(); + + test_mts(); + test_parse_errors(); + + printf("Done\n"); + return 0; +} diff --git a/tests/libosmo-trx/trxd_test.ok b/tests/libosmo-trx/trxd_test.ok new file mode 100644 index 0000000..e4f22da --- /dev/null +++ b/tests/libosmo-trx/trxd_test.ok @@ -0,0 +1,82 @@ +=== test_burst_req(v0, burst_len=148) === +build: BURST.req tn=2 fn=2654321 att=10 mod=GMSK set=0 tsc=3 burst_len=148 +datagram (154 bytes): 02002880710a00010001000100010001... +parse: BURST.req tn=2 fn=2654321 att=10 mod=GMSK burst_len=148 +=== test_burst_req(v0, burst_len=444) === +build: BURST.req tn=2 fn=2654321 att=10 mod=8-PSK set=0 tsc=3 burst_len=444 +datagram (450 bytes): 02002880710a00010001000100010001... +parse: BURST.req tn=2 fn=2654321 att=10 mod=8-PSK burst_len=444 +=== test_burst_ind(v0, burst_len=148) === +build: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=1 tsc=7 burst_len=148 +datagram (156 bytes): 050012d6873ffe001be31be31be31be3... +parse: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 mod=GMSK burst_len=148 +=== test_burst_ind(v0, burst_len=444) === +build: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=8-PSK set=1 tsc=7 burst_len=444 +datagram (452 bytes): 050012d6873ffe001be31be31be31be3... +parse: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 mod=8-PSK burst_len=444 +=== test_burst_ind_nope(v0) === +build: rc=-95 (expected -ENOTSUP) +=== test_burst_req(v1, burst_len=148) === +build: BURST.req tn=2 fn=2654321 att=10 mod=GMSK set=0 tsc=3 burst_len=148 +datagram (154 bytes): 12002880710a00010001000100010001... +parse: BURST.req tn=2 fn=2654321 att=10 mod=GMSK burst_len=148 +=== test_burst_req(v1, burst_len=444) === +build: BURST.req tn=2 fn=2654321 att=10 mod=8-PSK set=0 tsc=3 burst_len=444 +datagram (450 bytes): 12002880710a00010001000100010001... +parse: BURST.req tn=2 fn=2654321 att=10 mod=8-PSK burst_len=444 +=== test_burst_ind(v1, burst_len=148) === +build: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=1 tsc=7 burst_len=148 +datagram (159 bytes): 150012d6873ffe000fff6a1be31be31b... +parse: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=1 tsc=7 burst_len=148 +=== test_burst_ind(v1, burst_len=444) === +build: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=8-PSK set=1 tsc=7 burst_len=444 +datagram (455 bytes): 150012d6873ffe002fff6a1be31be31b... +parse: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=8-PSK set=1 tsc=7 burst_len=444 +=== test_burst_ind_nope(v1) === +build: NOPE.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB +parse: NOPE.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB +=== test_burst_req(v2, burst_len=148) === +build: BURST.req tn=2 fn=2654321 att=10 mod=GMSK set=0 tsc=3 burst_len=148 +datagram (160 bytes): 2200030a000000000028807100010001... +parse: BURST.req tn=2 fn=2654321 att=10 trx_num=0 mod=GMSK set=0 tsc=3 burst_len=148 +=== test_burst_req(v2, burst_len=444) === +build: BURST.req tn=2 fn=2654321 att=10 mod=8-PSK set=0 tsc=3 burst_len=444 +datagram (456 bytes): 2200230a000000000028807100010001... +parse: BURST.req tn=2 fn=2654321 att=10 trx_num=0 mod=8-PSK set=0 tsc=3 burst_len=444 +=== test_burst_req_batch(v2) === +datagram (472 bytes) +parse[0]: BURST.req tn=0 fn=2654321 att=10 trx_num=0 mod=GMSK set=0 tsc=3 burst_len=148 +parse[1]: BURST.req tn=1 fn=2654321 att=10 trx_num=0 mod=GMSK set=0 tsc=3 burst_len=148 +parse[2]: BURST.req tn=2 fn=2654321 att=10 trx_num=0 mod=GMSK set=0 tsc=3 burst_len=148 +=== test_burst_ind(v2, burst_len=148) === +build: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=1 tsc=7 burst_len=148 +datagram (160 bytes): 25000f3ffe00ff6a0012d6871be31be3... +parse: BURST.ind tn=5 fn=1234567 trx_num=0 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=1 tsc=7 burst_len=148 +=== test_burst_ind(v2, burst_len=444) === +build: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=8-PSK set=1 tsc=7 burst_len=444 +datagram (456 bytes): 25002f3ffe00ff6a0012d6871be31be3... +parse: BURST.ind tn=5 fn=1234567 trx_num=0 rssi=-63 toa256=-512 C/I=-150 cB mod=8-PSK set=1 tsc=7 burst_len=444 +=== test_burst_ind_batch(v2) === +datagram (168 bytes) +parse[0]: BURST.ind tn=5 fn=1234567 trx_num=1 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=1 tsc=7 burst_len=148 +parse[1]: NOPE.ind tn=6 fn=1234567 trx_num=1 rssi=-63 toa256=-512 C/I=-150 cB +=== test_burst_ind_nope(v2) === +build: NOPE.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB +parse: NOPE.ind tn=5 fn=1234567 trx_num=0 rssi=-63 toa256=-512 C/I=-150 cB +=== test_burst_ind_v0_legacy_padding === +parse: rc=158 (158 bytes incl. padding) +=== test_mts === +trxd_mts_parse_ind(0x80): flags=0x01 mod=GMSK set=0 tsc=0 (re-encoded: 0x80) +trxd_mts_parse_ind(0x07): flags=0x06 mod=GMSK set=0 tsc=7 (re-encoded: 0x07) +trxd_mts_parse_ind(0x1d): flags=0x06 mod=GMSK set=3 tsc=5 (re-encoded: 0x1d) +trxd_mts_parse_ind(0x2b): flags=0x06 mod=8-PSK set=1 tsc=3 (re-encoded: 0x2b) +trxd_mts_parse_ind(0x31): flags=0x86 mod=GMSK set=0 tsc=1 (re-encoded: 0x31) +trxd_mts_parse_ind(0x66): flags=0x06 mod=AQPSK set=0 tsc=6 (re-encoded: 0x66) +trxd_mts_parse_ind(0x51) failed +=== test_parse_errors === +ind, empty buffer: rc=-22 +ind, PDU version 15: rc=-95 +ind, v0 with odd burst length: rc=-22 +ind, v1 without burst bits: rc=-22 +req, illegal TDMA fn: rc=-22 +Done diff --git a/tests/testsuite.at b/tests/testsuite.at index 0ac870d..6e72c81 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -49,3 +49,15 @@ cat $abs_srcdir/Transceiver52M/convolve_test.ok > expout AT_CHECK([$abs_top_builddir/tests/Transceiver52M/convolve_test], [], [expout], []) AT_CLEANUP + +AT_SETUP([trxc_test]) +AT_KEYWORDS([trxc_test]) +cat $abs_srcdir/libosmo-trx/trxc_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/libosmo-trx/trxc_test], [], [expout], []) +AT_CLEANUP + +AT_SETUP([trxd_test]) +AT_KEYWORDS([trxd_test]) +cat $abs_srcdir/libosmo-trx/trxd_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/libosmo-trx/trxd_test], [], [expout], []) +AT_CLEANUP -- To view, visit
https://gerrit.osmocom.org/c/osmo-trx/+/43104?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged Gerrit-Project: osmo-trx Gerrit-Branch: master Gerrit-Change-Id: I933fc417a67d0043f74b04626b7643c79e381492 Gerrit-Change-Number: 43104 Gerrit-PatchSet: 5 Gerrit-Owner: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: laforge <laforge(a)osmocom.org> Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
1
0
0
0
[L] Change in osmo-trx[master]: libosmo-trx/trxc: add SETSLOT parameter parser/builder
by fixeria
17 Sep '26
17 Sep '26
fixeria has submitted this change. (
https://gerrit.osmocom.org/c/osmo-trx/+/43107?usp=email
) Change subject: libosmo-trx/trxc: add SETSLOT parameter parser/builder ...................................................................... libosmo-trx/trxc: add SETSLOT parameter parser/builder Add struct osmo_trxc_setslot plus osmo_trxc_setslot_parse()/_build() for the "<tn> <chan_comb> [C<tsc>/S<tsc_set> ...]" SETSLOT parameters, including VAMOS combinations (VFF/VHH/VFH/HVHH) with per-sub-channel TSC overrides. Change-Id: I17a1176b1418edd0caf750d50acda0482d4ad04a --- M libosmo-trx/include/osmocom/trx/trxc.h M libosmo-trx/src/trxc.c M tests/libosmo-trx/trxc_test.c M tests/libosmo-trx/trxc_test.ok 4 files changed, 253 insertions(+), 0 deletions(-) Approvals: laforge: Looks good to me, but someone else must approve pespin: Looks good to me, but someone else must approve fixeria: Looks good to me, approved Jenkins Builder: Verified diff --git a/libosmo-trx/include/osmocom/trx/trxc.h b/libosmo-trx/include/osmocom/trx/trxc.h index f3a64b6..3844357 100644 --- a/libosmo-trx/include/osmocom/trx/trxc.h +++ b/libosmo-trx/include/osmocom/trx/trxc.h @@ -4,6 +4,9 @@ #include <stdint.h> #include <stddef.h> +#include <stdbool.h> + +#include <osmocom/core/utils.h> /*! Maximum length of a command verb (incl. '\0') */ #define OSMO_TRXC_CMD_LEN_MAX 32 @@ -62,6 +65,66 @@ #define OSMO_TRXC_CMD_SETFH "SETFH" #define OSMO_TRXC_CMD_ERR "ERR" /*!< verb of a reject response */ +/* SETSLOT: "<tn> <chan_comb> [C<tsc>/S<tsc_set> ...]" */ + +/*! Classic (non-VAMOS) GSM TS 05.02 channel combinations, as used by the + * \c chan_comb parameter of SETSLOT. Numeric values match the wire format. */ +enum osmo_trxc_chan_comb { + OSMO_TRXC_CHAN_COMB_UNUSED = 0, /*!< Channel is transmitted, but unused */ + OSMO_TRXC_CHAN_COMB_TCHF = 1, + OSMO_TRXC_CHAN_COMB_TCHH_IDLE = 2, /*!< TCH/HS, idle every other slot */ + OSMO_TRXC_CHAN_COMB_TCHH = 3, + OSMO_TRXC_CHAN_COMB_BCCH = 4, /*!< DL: FCCH+SCH+CCCH+BCCH, UL: RACH */ + OSMO_TRXC_CHAN_COMB_BCCH_SDCCH4 = 5, /*!< DL: +SDCCH/4+SACCH/4, UL: +SDCCH/4 */ + OSMO_TRXC_CHAN_COMB_CCCH = 6, /*!< DL: CCCH+BCCH, UL: RACH */ + OSMO_TRXC_CHAN_COMB_SDCCH8 = 7, /*!< SDCCH/8 + SACCH/8 */ + OSMO_TRXC_CHAN_COMB_TCHF_FACCH_SACCHM = 8, + OSMO_TRXC_CHAN_COMB_TCHF_SACCHM = 9, + OSMO_TRXC_CHAN_COMB_TCHFD_SACCHMD = 10, + OSMO_TRXC_CHAN_COMB_PBCCH = 11, /*!< PBCCH+PCCCH+PDTCH+PACCH+PTCCH */ + OSMO_TRXC_CHAN_COMB_PCCCH = 12, /*!< PCCCH+PDTCH+PACCH+PTCCH */ + OSMO_TRXC_CHAN_COMB_PDTCH = 13, /*!< PDTCH+PACCH+PTCCH */ +}; + +/*! VAMOS-enabled channel combinations: the \c chan_comb parameter of SETSLOT + * is symbolic (not numeric) for these. */ +enum osmo_trxc_vamos_comb { + OSMO_TRXC_VAMOS_COMB_VFF = 1, /*!< V0(TCH/F) & V1(TCH/F) */ + OSMO_TRXC_VAMOS_COMB_VHH, /*!< V0(TCH/H0)&V1(TCH/H0) + V0(TCH/H1)&V1(TCH/H1) */ + OSMO_TRXC_VAMOS_COMB_VFH, /*!< V0(TCH/F) & V1(TCH/H0) + V0(TCH/F) & V1(TCH/H1) */ + OSMO_TRXC_VAMOS_COMB_HVHH, /*!< TCH/H0 + V0(TCH/H1) & V1(TCH/H1) (mixed) */ +}; + +extern const struct value_string osmo_trxc_vamos_comb_names[]; +static inline const char *osmo_trxc_vamos_comb_name(enum osmo_trxc_vamos_comb comb) +{ + return get_value_string(osmo_trxc_vamos_comb_names, comb); +} + +#define OSMO_TRXC_SETSLOT_TSC_MAX 3 /*!< up to 3 sub-channels (VAMOS "HVHH") */ + +/*! One "C<tsc>/S<tsc_set>" override, as used by SETSLOT for (VAMOS) + * sub-channels that don't use the endpoint-wide TSC (SETTSC). */ +struct osmo_trxc_setslot_tsc { + uint8_t tsc; + uint8_t tsc_set; +}; + +/*! Parsed SETSLOT parameters. */ +struct osmo_trxc_setslot { + uint8_t tn; + bool vamos; /*!< false: chan_comb is valid, true: vamos_comb is valid */ + union { + enum osmo_trxc_chan_comb chan_comb; + enum osmo_trxc_vamos_comb vamos_comb; + }; + unsigned int num_tsc; /*!< number of valid entries in tsc[] */ + struct osmo_trxc_setslot_tsc tsc[OSMO_TRXC_SETSLOT_TSC_MAX]; +}; + +int osmo_trxc_setslot_parse(struct osmo_trxc_setslot *ss, const struct osmo_trxc_msg *msg); +int osmo_trxc_setslot_build(char *buf, size_t buf_size, const struct osmo_trxc_setslot *ss); + /* Clock socket: "IND CLOCK <fn>" */ int osmo_trxc_clock_ind_parse(uint32_t *fn, const char *buf, size_t len); int osmo_trxc_clock_ind_build(char *buf, size_t buf_size, uint32_t fn); diff --git a/libosmo-trx/src/trxc.c b/libosmo-trx/src/trxc.c index 70d8c16..9425068 100644 --- a/libosmo-trx/src/trxc.c +++ b/libosmo-trx/src/trxc.c @@ -44,6 +44,14 @@ { 0, NULL } }; +const struct value_string osmo_trxc_vamos_comb_names[] = { + { OSMO_TRXC_VAMOS_COMB_VFF, "VFF" }, + { OSMO_TRXC_VAMOS_COMB_VHH, "VHH" }, + { OSMO_TRXC_VAMOS_COMB_VFH, "VFH" }, + { OSMO_TRXC_VAMOS_COMB_HVHH, "HVHH" }, + { 0, NULL } +}; + /*! Parse a TRXC message ("CMD <verb> [<params>]", "RSP <verb> <status> * [<params>]" or "IND <verb> <params>") from a zero-terminated buffer. * \param[out] msg parsed message @@ -170,6 +178,89 @@ return osmo_trxc_msg_build(buf, buf_size, msg) < 0 ? NULL : buf; } +/*! Parse SETSLOT parameters ("<tn> <chan_comb> [C<tsc>/S<tsc_set> ...]") + * from an already-parsed TRXC message's msg->params. + * \returns 0 on success; negative on error */ +int osmo_trxc_setslot_parse(struct osmo_trxc_setslot *ss, const struct osmo_trxc_msg *msg) +{ + char params[OSMO_TRXC_PARAMS_LEN_MAX]; + char *saveptr, *tok; + unsigned int tn; + int comb; + + memset(ss, 0, sizeof(*ss)); + + OSMO_STRLCPY_ARRAY(params, msg->params); + + tok = strtok_r(params, " ", &saveptr); + if (tok == NULL || sscanf(tok, "%u", &tn) != 1) + return -EINVAL; + if (tn > 7) + return -ERANGE; + ss->tn = tn; + + tok = strtok_r(NULL, " ", &saveptr); + if (tok == NULL) + return -EINVAL; + comb = get_string_value(osmo_trxc_vamos_comb_names, tok); + if (comb >= 0) { + ss->vamos = true; + ss->vamos_comb = comb; + } else { + if (sscanf(tok, "%d", &comb) != 1) + return -EINVAL; + if (comb < OSMO_TRXC_CHAN_COMB_UNUSED || comb > OSMO_TRXC_CHAN_COMB_PDTCH) + return -ERANGE; + ss->vamos = false; + ss->chan_comb = comb; + } + + while ((tok = strtok_r(NULL, " ", &saveptr)) != NULL) { + unsigned int tsc, tsc_set; + + if (ss->num_tsc >= OSMO_TRXC_SETSLOT_TSC_MAX) + return -E2BIG; + if (sscanf(tok, "C%u/S%u", &tsc, &tsc_set) != 2) + return -EINVAL; + ss->tsc[ss->num_tsc].tsc = tsc; + ss->tsc[ss->num_tsc].tsc_set = tsc_set; + ss->num_tsc++; + } + + return 0; +} + +/*! Serialize SETSLOT parameters ("<tn> <chan_comb> [C<tsc>/S<tsc_set> ...]") + * into the given buffer (zero-terminated), for use as msg->params. + * \returns length of the string (excl. '\0') on success; negative on error */ +int osmo_trxc_setslot_build(char *buf, size_t buf_size, const struct osmo_trxc_setslot *ss) +{ + unsigned int i; + int rc, len; + + if (ss->tn > 7) + return -ERANGE; + + if (ss->vamos) + rc = snprintf(buf, buf_size, "%u %s", ss->tn, + osmo_trxc_vamos_comb_name(ss->vamos_comb)); + else + rc = snprintf(buf, buf_size, "%u %d", ss->tn, ss->chan_comb); + if (rc < 0 || (size_t)rc >= buf_size) + return -EMSGSIZE; + len = rc; + + for (i = 0; i < ss->num_tsc; i++) { + rc = snprintf(buf + len, buf_size - len, " C%u/S%u", + ss->tsc[i].tsc, ss->tsc[i].tsc_set); + if (rc < 0 || (size_t)rc >= buf_size - (size_t)len) + return -EMSGSIZE; + len += rc; + } + + return len; +} + /*! Parse a clock indication ("IND CLOCK <fn>"). * \param[out] fn TDMA frame number (validated to be < GSM_TDMA_HYPERFRAME) * \param[in] buf message buffer (not necessarily zero-terminated) diff --git a/tests/libosmo-trx/trxc_test.c b/tests/libosmo-trx/trxc_test.c index 93eea83..b4653cd 100644 --- a/tests/libosmo-trx/trxc_test.c +++ b/tests/libosmo-trx/trxc_test.c @@ -194,6 +194,79 @@ OSMO_ASSERT(rc < 0); } +static void test_setslot_parse_one(const char *params) +{ + struct osmo_trxc_msg msg = { .type = OSMO_TRXC_MT_CMD, .cmd = "SETSLOT" }; + struct osmo_trxc_setslot ss; + char buf[OSMO_TRXC_MSG_BUF_SIZE]; + int rc; + + snprintf(msg.params, sizeof(msg.params), "%s", params); + + rc = osmo_trxc_setslot_parse(&ss, &msg); + if (rc < 0) { + printf("'%s' -> rc=%d\n", params, rc); + return; + } + + printf("'%s' -> tn=%u vamos=%d comb=%s num_tsc=%u", + params, ss.tn, ss.vamos, + ss.vamos ? osmo_trxc_vamos_comb_name(ss.vamos_comb) : "(numeric)", + ss.num_tsc); + if (!ss.vamos) + printf(" chan_comb=%d", ss.chan_comb); + for (unsigned int i = 0; i < ss.num_tsc; i++) + printf(" C%u/S%u", ss.tsc[i].tsc, ss.tsc[i].tsc_set); + printf("\n"); + + /* re-encode and compare against the original params */ + rc = osmo_trxc_setslot_build(buf, sizeof(buf), &ss); + OSMO_ASSERT(rc > 0 && rc == (int)strlen(buf)); + printf("\tre-encoded: '%s'\n", buf); + OSMO_ASSERT(strcmp(buf, params) == 0); +} + +static void test_setslot(void) +{ + static const char * const good[] = { + "0 0", + "7 13", + "4 1 C7/S1", /* manual example */ + "0 VFF C0/S1 C0/S2", /* manual example */ + "3 VHH C1/S3 C1/S4", /* manual example */ + "1 VFH C2/S1 C2/S4", /* manual example */ + "2 HVHH C0/S1 C0/S1 C0/S2", /* manual example */ + }; + static const char * const bad[] = { + "8 0", /* tn out of range */ + "0 14", /* chan_comb out of range */ + "0 -1", /* chan_comb out of range */ + "0", /* missing chan_comb */ + "", /* missing everything */ + "x 0", /* tn not numeric */ + "0 x", /* chan_comb not numeric, not a known VAMOS name */ + "0 0 bogus", /* trailing token not a C<tsc>/S<tsc_set> */ + "0 HVHH C0/S1 C0/S1 C0/S2 C0/S3", /* too many TSC overrides */ + }; + struct osmo_trxc_msg msg = { .type = OSMO_TRXC_MT_CMD, .cmd = "SETSLOT" }; + struct osmo_trxc_setslot ss; + int rc; + + printf("=== %s ===\n", __func__); + + for (unsigned int i = 0; i < ARRAY_SIZE(good); i++) + test_setslot_parse_one(good[i]); + for (unsigned int i = 0; i < ARRAY_SIZE(bad); i++) + test_setslot_parse_one(bad[i]); + + /* build() range check */ + memset(&ss, 0, sizeof(ss)); + ss.tn = 8; + rc = osmo_trxc_setslot_build(msg.params, sizeof(msg.params), &ss); + printf("build with out of range tn: rc=%d\n", rc); + OSMO_ASSERT(rc < 0); +} + int main(int argc, char **argv) { test_msg_parse(); @@ -201,6 +274,7 @@ test_params_scan(); test_long_params(); test_clk_ind(); + test_setslot(); printf("Done\n"); return 0; diff --git a/tests/libosmo-trx/trxc_test.ok b/tests/libosmo-trx/trxc_test.ok index 845d4da..30b47ff 100644 --- a/tests/libosmo-trx/trxc_test.ok +++ b/tests/libosmo-trx/trxc_test.ok @@ -52,4 +52,29 @@ 'IND KCOLC 123' -> rc=-22 'CMD CLOCK 123' -> rc=-22 build with out of range fn: rc=-34 +=== test_setslot === +'0 0' -> tn=0 vamos=0 comb=(numeric) num_tsc=0 chan_comb=0 + re-encoded: '0 0' +'7 13' -> tn=7 vamos=0 comb=(numeric) num_tsc=0 chan_comb=13 + re-encoded: '7 13' +'4 1 C7/S1' -> tn=4 vamos=0 comb=(numeric) num_tsc=1 chan_comb=1 C7/S1 + re-encoded: '4 1 C7/S1' +'0 VFF C0/S1 C0/S2' -> tn=0 vamos=1 comb=VFF num_tsc=2 C0/S1 C0/S2 + re-encoded: '0 VFF C0/S1 C0/S2' +'3 VHH C1/S3 C1/S4' -> tn=3 vamos=1 comb=VHH num_tsc=2 C1/S3 C1/S4 + re-encoded: '3 VHH C1/S3 C1/S4' +'1 VFH C2/S1 C2/S4' -> tn=1 vamos=1 comb=VFH num_tsc=2 C2/S1 C2/S4 + re-encoded: '1 VFH C2/S1 C2/S4' +'2 HVHH C0/S1 C0/S1 C0/S2' -> tn=2 vamos=1 comb=HVHH num_tsc=3 C0/S1 C0/S1 C0/S2 + re-encoded: '2 HVHH C0/S1 C0/S1 C0/S2' +'8 0' -> rc=-34 +'0 14' -> rc=-34 +'0 -1' -> rc=-34 +'0' -> rc=-22 +'' -> rc=-22 +'x 0' -> rc=-22 +'0 x' -> rc=-22 +'0 0 bogus' -> rc=-22 +'0 HVHH C0/S1 C0/S1 C0/S2 C0/S3' -> rc=-7 +build with out of range tn: rc=-34 Done -- To view, visit
https://gerrit.osmocom.org/c/osmo-trx/+/43107?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged Gerrit-Project: osmo-trx Gerrit-Branch: master Gerrit-Change-Id: I17a1176b1418edd0caf750d50acda0482d4ad04a Gerrit-Change-Number: 43107 Gerrit-PatchSet: 5 Gerrit-Owner: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: laforge <laforge(a)osmocom.org> Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
1
0
0
0
[S] Change in osmo-trx[master]: libosmo-trx/trxc: add osmo_trxc_chan_comb_name()
by fixeria
17 Sep '26
17 Sep '26
fixeria has submitted this change. (
https://gerrit.osmocom.org/c/osmo-trx/+/43638?usp=email
) Change subject: libosmo-trx/trxc: add osmo_trxc_chan_comb_name() ...................................................................... libosmo-trx/trxc: add osmo_trxc_chan_comb_name() Change-Id: Ibba82407cd2ab0c564c85059fa1b5bcf4aa86c2e --- M libosmo-trx/include/osmocom/trx/trxc.h M libosmo-trx/src/trxc.c 2 files changed, 24 insertions(+), 0 deletions(-) Approvals: Jenkins Builder: Verified laforge: Looks good to me, approved pespin: Looks good to me, but someone else must approve diff --git a/libosmo-trx/include/osmocom/trx/trxc.h b/libosmo-trx/include/osmocom/trx/trxc.h index 3844357..9c2799a 100644 --- a/libosmo-trx/include/osmocom/trx/trxc.h +++ b/libosmo-trx/include/osmocom/trx/trxc.h @@ -86,6 +86,12 @@ OSMO_TRXC_CHAN_COMB_PDTCH = 13, /*!< PDTCH+PACCH+PTCCH */ }; +extern const struct value_string osmo_trxc_chan_comb_names[]; +static inline const char *osmo_trxc_chan_comb_name(enum osmo_trxc_chan_comb comb) +{ + return get_value_string(osmo_trxc_chan_comb_names, comb); +} + /*! VAMOS-enabled channel combinations: the \c chan_comb parameter of SETSLOT * is symbolic (not numeric) for these. */ enum osmo_trxc_vamos_comb { diff --git a/libosmo-trx/src/trxc.c b/libosmo-trx/src/trxc.c index 9425068..553df3e 100644 --- a/libosmo-trx/src/trxc.c +++ b/libosmo-trx/src/trxc.c @@ -44,6 +44,24 @@ { 0, NULL } }; +const struct value_string osmo_trxc_chan_comb_names[] = { + { OSMO_TRXC_CHAN_COMB_UNUSED, "UNUSED" }, + { OSMO_TRXC_CHAN_COMB_TCHF, "TCH/F" }, + { OSMO_TRXC_CHAN_COMB_TCHH_IDLE, "TCH/H (sub-ch 1 idle)" }, + { OSMO_TRXC_CHAN_COMB_TCHH, "TCH/H" }, + { OSMO_TRXC_CHAN_COMB_BCCH, "BCCH" }, + { OSMO_TRXC_CHAN_COMB_BCCH_SDCCH4, "BCCH+SDCCH/4" }, + { OSMO_TRXC_CHAN_COMB_CCCH, "CCCH" }, + { OSMO_TRXC_CHAN_COMB_SDCCH8, "SDCCH/8" }, + { OSMO_TRXC_CHAN_COMB_TCHF_FACCH_SACCHM, "TCH/F+FACCH+SACCH/M" }, + { OSMO_TRXC_CHAN_COMB_TCHF_SACCHM, "TCH/F+SACCH/M" }, + { OSMO_TRXC_CHAN_COMB_TCHFD_SACCHMD, "TCH/FD+SACCH/MD" }, + { OSMO_TRXC_CHAN_COMB_PBCCH, "PBCCH" }, + { OSMO_TRXC_CHAN_COMB_PCCCH, "PCCCH" }, + { OSMO_TRXC_CHAN_COMB_PDTCH, "PDTCH" }, + { 0, NULL } +}; + const struct value_string osmo_trxc_vamos_comb_names[] = { { OSMO_TRXC_VAMOS_COMB_VFF, "VFF" }, { OSMO_TRXC_VAMOS_COMB_VHH, "VHH" }, -- To view, visit
https://gerrit.osmocom.org/c/osmo-trx/+/43638?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged Gerrit-Project: osmo-trx Gerrit-Branch: master Gerrit-Change-Id: Ibba82407cd2ab0c564c85059fa1b5bcf4aa86c2e Gerrit-Change-Number: 43638 Gerrit-PatchSet: 2 Gerrit-Owner: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: laforge <laforge(a)osmocom.org> Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
1
0
0
0
[XL] Change in osmo-trx[master]: libosmo-trx/client: add TRXC client (command queue) API
by fixeria
17 Sep '26
17 Sep '26
fixeria has submitted this change. (
https://gerrit.osmocom.org/c/osmo-trx/+/43108?usp=email
) ( 4 is the latest approved patch-set. No files were changed between the latest approved patch-set and the submitted one. )Change subject: libosmo-trx/client: add TRXC client (command queue) API ...................................................................... libosmo-trx/client: add TRXC client (command queue) API Add the osmo_trxc_client module: a transport-agnostic generalization of the TRXC command handling logic in osmo-bts-trx (trx_if.c): * command queue with a single command in flight; * retransmission on response timeout (default: 2 s); * suppression of consecutive duplicate commands; * RSP<->CMD matching, optionally including the parameters; * filtering of duplicate responses caused by retransmissions; * escalation of NACKed critical commands (OSMO_TRXC_F_CRITICAL) to the fatal_error call-back; * TRXD PDU version negotiation (SETFORMAT), incl. the fallback to version 0 for old transceivers rejecting it with 'RSP ERR 1'. The client neither opens nor owns a socket: the application transmits serialized messages in the tx_msg call-back and feeds received datagrams into osmo_trxc_client_rx(). The response call-back may return N > 0 to request a re-transmission of the same command after N seconds (e.g. POWERON retry, as implemented in osmo-bts-trx). Change-Id: I817e394f74a10e3adae4a0b58342c82acdf0794e --- M .gitignore M libosmo-trx/include/Makefile.am A libosmo-trx/include/osmocom/trx/trxc_client.h M libosmo-trx/src/Makefile.am A libosmo-trx/src/trxc_client.c M tests/libosmo-trx/Makefile.am A tests/libosmo-trx/trxc_client_test.c A tests/libosmo-trx/trxc_client_test.err A tests/libosmo-trx/trxc_client_test.ok M tests/testsuite.at 10 files changed, 1,117 insertions(+), 0 deletions(-) Approvals: Jenkins Builder: Verified fixeria: Looks good to me, approved laforge: Looks good to me, but someone else must approve pespin: Looks good to me, but someone else must approve diff --git a/.gitignore b/.gitignore index e33145b..699daa6 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ # tests tests/libosmo-trx/trxc_test +tests/libosmo-trx/trxc_client_test tests/libosmo-trx/trxd_test tests/CommonLibs/BitVectorTest tests/CommonLibs/F16Test diff --git a/libosmo-trx/include/Makefile.am b/libosmo-trx/include/Makefile.am index 3c5f908..facdd52 100644 --- a/libosmo-trx/include/Makefile.am +++ b/libosmo-trx/include/Makefile.am @@ -1,4 +1,5 @@ nobase_include_HEADERS = \ osmocom/trx/trxc.h \ + osmocom/trx/trxc_client.h \ osmocom/trx/trxd.h \ $(NULL) diff --git a/libosmo-trx/include/osmocom/trx/trxc_client.h b/libosmo-trx/include/osmocom/trx/trxc_client.h new file mode 100644 index 0000000..dc9f290 --- /dev/null +++ b/libosmo-trx/include/osmocom/trx/trxc_client.h @@ -0,0 +1,113 @@ +/*! \file osmocom/trx/trxc_client.h + * TRXC client command queue engine: queue, retransmission, RSP matching. */ +#pragma once + +#include <stdint.h> +#include <stddef.h> + +#include <osmocom/trx/trxc.h> + +/*! TRXC client engine, driving the ctrl connection towards a transceiver: + * command queue with a single command in flight, retransmit timer, + * consecutive-duplicate suppression, RSP<->CMD matching and duplicate-RSP + * filtering. This structure is opaque. + * + * The engine is transport-agnostic: it neither opens nor owns a socket. + * The application transmits serialized messages in the tx_msg call-back + * and feeds received datagrams into osmo_trxc_client_rx(). */ +struct osmo_trxc_client; + +/*! Response call-back, invoked when a response to a command is received. + * \param[in] client TRXC client instance + * \param[in] rsp received response message + * \param[in] cb_data opaque data passed to osmo_trxc_client_send_cmd() + * \returns 0 when done (the command gets dequeued); + * N > 0 to re-send the same command after N seconds; + * negative to indicate a fatal error (like a NACKed critical + * command, ends up in the fatal_error call-back) */ +typedef int osmo_trxc_client_rsp_cb(struct osmo_trxc_client *client, + const struct osmo_trxc_msg *rsp, + void *cb_data); + +/*! Transmit call-back, invoked to emit a serialized TRXC message (mandatory, + * see osmo_trxc_client_set_tx_msg_cb()). + * E.g. write() / osmo_iofd_write_msgb() on the app's ctrl socket. + * \param[in] client TRXC client instance + * \param[in] buf serialized TRXC message to transmit + * \param[in] len length of buf, in bytes + * \returns number of bytes transmitted on success; negative on error + * (logged by the engine, otherwise ignored: the retransmit + * timer still governs delivery) */ +typedef int osmo_trxc_client_tx_msg_cb(struct osmo_trxc_client *client, + const char *buf, size_t len); + +/*! Fatal error call-back, invoked when a critical command definitively fails + * (optional; default: log), see osmo_trxc_client_set_fatal_error_cb(). + * The command queue remains frozen after this call. + * \param[in] client TRXC client instance + * \param[in] rsp the offending response (NULL on a retransmission timeout, + * i.e. no response was ever received) */ +typedef void osmo_trxc_client_fatal_error_cb(struct osmo_trxc_client *client, + const struct osmo_trxc_msg *rsp); + +struct osmo_trxc_client *osmo_trxc_client_alloc(void *ctx); +void osmo_trxc_client_free(struct osmo_trxc_client *client); +void osmo_trxc_client_set_priv(struct osmo_trxc_client *client, void *priv); +void *osmo_trxc_client_get_priv(const struct osmo_trxc_client *client); +int osmo_trxc_client_set_name(struct osmo_trxc_client *client, const char *fmt, ...); +void osmo_trxc_client_set_log_cat(struct osmo_trxc_client *client, int log_cat); +int osmo_trxc_client_set_retrans(struct osmo_trxc_client *client, unsigned int sec); +/*! set the tx_msg call-back (mandatory before the first command is sent) */ +void osmo_trxc_client_set_tx_msg_cb(struct osmo_trxc_client *client, + osmo_trxc_client_tx_msg_cb *cb); +/*! set the fatal_error call-back (optional; default: log) */ +void osmo_trxc_client_set_fatal_error_cb(struct osmo_trxc_client *client, + osmo_trxc_client_fatal_error_cb *cb); + +/*! escalate to the fatal_error call-back on NACK */ +#define OSMO_TRXC_F_CRITICAL (1 << 0) +/*! RSP params must echo CMD params (e.g. SETSLOT, SETFORMAT) */ +#define OSMO_TRXC_F_MATCH_PARAMS (1 << 1) + +int osmo_trxc_client_send_cmd(struct osmo_trxc_client *client, uint32_t flags, + osmo_trxc_client_rsp_cb *cb, void *cb_data, + const char *cmd, const char *fmt, ...); +void osmo_trxc_client_flush(struct osmo_trxc_client *client); + +int osmo_trxc_client_rx(struct osmo_trxc_client *client, const char *buf, size_t len); + +/*! TRXD PDU version negotiation result call-back. + * \param[in] ver_use the negotiated version to be used */ +typedef void osmo_trxc_setformat_cb(struct osmo_trxc_client *client, + uint8_t ver_use, void *cb_data); +int osmo_trxc_client_negotiate_format(struct osmo_trxc_client *client, + uint8_t ver_max, + osmo_trxc_setformat_cb *cb, void *cb_data); + +/* Convenience wrappers for the common command set (thin, optional) */ +static inline int osmo_trxc_client_poweron(struct osmo_trxc_client *client, + osmo_trxc_client_rsp_cb *cb, void *cb_data) +{ + return osmo_trxc_client_send_cmd(client, OSMO_TRXC_F_CRITICAL, cb, cb_data, + OSMO_TRXC_CMD_POWERON, NULL); +} +static inline int osmo_trxc_client_poweroff(struct osmo_trxc_client *client, + osmo_trxc_client_rsp_cb *cb, void *cb_data) +{ + return osmo_trxc_client_send_cmd(client, OSMO_TRXC_F_CRITICAL, cb, cb_data, + OSMO_TRXC_CMD_POWEROFF, NULL); +} +static inline int osmo_trxc_client_rxtune(struct osmo_trxc_client *client, + unsigned int freq_khz, + osmo_trxc_client_rsp_cb *cb, void *cb_data) +{ + return osmo_trxc_client_send_cmd(client, OSMO_TRXC_F_CRITICAL, cb, cb_data, + OSMO_TRXC_CMD_RXTUNE, "%u", freq_khz); +} +static inline int osmo_trxc_client_txtune(struct osmo_trxc_client *client, + unsigned int freq_khz, + osmo_trxc_client_rsp_cb *cb, void *cb_data) +{ + return osmo_trxc_client_send_cmd(client, OSMO_TRXC_F_CRITICAL, cb, cb_data, + OSMO_TRXC_CMD_TXTUNE, "%u", freq_khz); +} diff --git a/libosmo-trx/src/Makefile.am b/libosmo-trx/src/Makefile.am index cec05ec..17fb5f1 100644 --- a/libosmo-trx/src/Makefile.am +++ b/libosmo-trx/src/Makefile.am @@ -18,6 +18,7 @@ libosmo_trx_la_SOURCES = \ trxc.c \ + trxc_client.c \ trxd.c \ $(NULL) diff --git a/libosmo-trx/src/trxc_client.c b/libosmo-trx/src/trxc_client.c new file mode 100644 index 0000000..95f300c --- /dev/null +++ b/libosmo-trx/src/trxc_client.c @@ -0,0 +1,531 @@ +/*! \file src/trxc_client.c + * TRXC client command queue engine: queue, retransmission, RSP matching. + * Based on the TRXC command queue logic in osmo-bts-trx (trx_if.c). */ + +/* + * (C) 2013 Andreas Eversberg <jolly(a)eversberg.eu> + * (C) 2016-2017 Harald Welte <laforge(a)gnumonks.org> + * (C) 2019 Vadim Yanitskiy <axilirator(a)gmail.com> + * (C) 2021-2026 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <
http://www.gnu.org/licenses/
>. + */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <stdbool.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/utils.h> + +#include <osmocom/trx/trxc.h> +#include <osmocom/trx/trxc_client.h> + +/*! Default retransmit timeout (in seconds) */ +#define TRXC_CLIENT_RETRANS_SEC 2 + +/*! A single command in the queue */ +struct trxc_cmd_entry { + struct llist_head list; + struct osmo_trxc_msg msg; /* type == OSMO_TRXC_MT_CMD */ + uint32_t flags; /* OSMO_TRXC_F_* */ + osmo_trxc_client_rsp_cb *rsp_cb; + void *cb_data; +}; + +struct osmo_trxc_client { + osmo_trxc_client_tx_msg_cb *tx_msg_cb; + osmo_trxc_client_fatal_error_cb *fatal_error_cb; + void *priv; /* opaque application-private data */ + char *name; /* log prefix */ + int log_cat; /* logging category (default DLGLOBAL) */ + unsigned int retrans_sec; /* retransmit timeout */ + + struct llist_head cmd_queue; /* list of struct trxc_cmd_entry */ + struct trxc_cmd_entry *last_acked; + struct osmo_timer_list retrans_timer; + + /* guards for osmo_trxc_client_flush() from within a rsp_cb */ + bool in_rx; + bool flushed_in_rx; +}; + +#define LOGCL(client, level, fmt, args...) \ + LOGP((client)->log_cat, level, "%s: " fmt, (client)->name, ## args) + +#define CMD_NAME_FMT "CMD %s%s%s" +#define CMD_NAME_ARGS(e) \ + (e)->msg.cmd, (e)->msg.params[0] != '\0' ? " " : "", (e)->msg.params + +/* Transmit the first command in the queue (if any), (re)start the timer */ +static void trxc_client_send_next(struct osmo_trxc_client *client) +{ + char buf[OSMO_TRXC_MSG_BUF_SIZE]; + struct trxc_cmd_entry *e; + int rc; + + if (llist_empty(&client->cmd_queue)) + return; + e = llist_first_entry(&client->cmd_queue, struct trxc_cmd_entry, list); + + rc = osmo_trxc_msg_build(buf, sizeof(buf), &e->msg); + OSMO_ASSERT(rc > 0); /* validated in osmo_trxc_client_send_cmd() */ + + OSMO_ASSERT(client->tx_msg_cb != NULL); /* set via osmo_trxc_client_set_tx_msg_cb() */ + LOGCL(client, LOGL_DEBUG, "Tx '%s'\n", buf); + rc = client->tx_msg_cb(client, buf, rc); + if (rc < 0) + LOGCL(client, LOGL_ERROR, "tx_msg() failed with rc=%d\n", rc); + + osmo_timer_schedule(&client->retrans_timer, client->retrans_sec, 0); +} + +static void trxc_client_retrans_timer_cb(void *data) +{ + struct osmo_trxc_client *client = data; + struct trxc_cmd_entry *e; + + OSMO_ASSERT(!llist_empty(&client->cmd_queue)); + e = llist_first_entry(&client->cmd_queue, struct trxc_cmd_entry, list); + + LOGCL(client, LOGL_NOTICE, "No response from transceiver for '" CMD_NAME_FMT "'\n", + CMD_NAME_ARGS(e)); + + trxc_client_send_next(client); +} + +/*! Allocate a TRXC client instance. The tx_msg call-back must be set via + * osmo_trxc_client_set_tx_msg_cb() before the first command is sent. + * \param[in] ctx talloc context to allocate from + * \returns pointer to the allocated instance; NULL on error */ +struct osmo_trxc_client *osmo_trxc_client_alloc(void *ctx) +{ + struct osmo_trxc_client *client; + + client = talloc_zero(ctx, struct osmo_trxc_client); + if (client == NULL) + return NULL; + + client->name = talloc_strdup(client, "trxc_client"); + client->log_cat = DLGLOBAL; + client->retrans_sec = TRXC_CLIENT_RETRANS_SEC; + + INIT_LLIST_HEAD(&client->cmd_queue); + osmo_timer_setup(&client->retrans_timer, &trxc_client_retrans_timer_cb, client); + + return client; +} + +/*! Free the given TRXC client instance (flushes the command queue). + * Must not be called from within a response call-back. */ +void osmo_trxc_client_free(struct osmo_trxc_client *client) +{ + if (client == NULL) + return; + OSMO_ASSERT(!client->in_rx); + osmo_trxc_client_flush(client); + talloc_free(client); +} + +/*! Set the application-private data */ +void osmo_trxc_client_set_priv(struct osmo_trxc_client *client, void *priv) +{ + client->priv = priv; +} + +/*! Obtain the application-private data */ +void *osmo_trxc_client_get_priv(const struct osmo_trxc_client *client) +{ + return client->priv; +} + +/*! Set the tx_msg call-back (mandatory before the first command is sent) */ +void osmo_trxc_client_set_tx_msg_cb(struct osmo_trxc_client *client, + osmo_trxc_client_tx_msg_cb *cb) +{ + client->tx_msg_cb = cb; +} + +/*! Set the fatal_error call-back (optional; default: log) */ +void osmo_trxc_client_set_fatal_error_cb(struct osmo_trxc_client *client, + osmo_trxc_client_fatal_error_cb *cb) +{ + client->fatal_error_cb = cb; +} + +/*! Set the name (log prefix) of the given instance, e.g. "phy0.trx0" */ +int osmo_trxc_client_set_name(struct osmo_trxc_client *client, const char *fmt, ...) +{ + char name[64]; + va_list ap; + int rc; + + va_start(ap, fmt); + rc = vsnprintf(name, sizeof(name), fmt, ap); + va_end(ap); + + if (rc < 0 || rc >= (int)sizeof(name)) + return -EMSGSIZE; + osmo_talloc_replace_string(client, &client->name, name); + + return 0; +} + +/*! Set the logging category (e.g. DTRX in osmo-bts; default: DLGLOBAL) */ +void osmo_trxc_client_set_log_cat(struct osmo_trxc_client *client, int log_cat) +{ + client->log_cat = log_cat; +} + +/*! Set the retransmit timeout in seconds (default: 2). + * \param[in] client TRXC client instance + * \param[in] sec retransmit timeout; 0 is not allowed + * \returns 0 on success; -EINVAL if sec == 0 */ +int osmo_trxc_client_set_retrans(struct osmo_trxc_client *client, unsigned int sec) +{ + if (sec == 0) + return -EINVAL; + client->retrans_sec = sec; + return 0; +} + +/*! Enqueue a new command for transmission. + * + * The new command is added to the end of the queue; there's at most one + * command in flight at any time. Consecutive duplicate commands are not + * enqueued. Commands are retransmitted until a matching response is + * received (see osmo_trxc_client_rx()). + * + * \param[in] client TRXC client instance + * \param[in] flags OSMO_TRXC_F_* + * \param[in] cb call-back invoked on the response (optional); without it, + * a NACKed OSMO_TRXC_F_CRITICAL command is escalated to the + * fatal_error call-back, other responses are just logged + * \param[in] cb_data opaque data for the response call-back + * \param[in] cmd command verb, e.g. "POWERON" + * \param[in] fmt format string for the parameters (optional, may be NULL) + * \returns 0 on success; negative on error */ +int osmo_trxc_client_send_cmd(struct osmo_trxc_client *client, uint32_t flags, + osmo_trxc_client_rsp_cb *cb, void *cb_data, + const char *cmd, const char *fmt, ...) +{ + struct trxc_cmd_entry *e, *prev = NULL; + va_list ap; + int rc; + + e = talloc_zero(client, struct trxc_cmd_entry); + if (e == NULL) + return -ENOMEM; + + e->msg.type = OSMO_TRXC_MT_CMD; + if (osmo_strlcpy(e->msg.cmd, cmd, sizeof(e->msg.cmd)) >= sizeof(e->msg.cmd)) { + talloc_free(e); + return -EMSGSIZE; + } + if (fmt != NULL && fmt[0] != '\0') { + va_start(ap, fmt); + rc = vsnprintf(e->msg.params, sizeof(e->msg.params), fmt, ap); + va_end(ap); + if (rc < 0 || rc >= (int)sizeof(e->msg.params)) { + talloc_free(e); + return -EMSGSIZE; + } + } + + e->flags = flags; + e->rsp_cb = cb; + e->cb_data = cb_data; + + /* avoid enqueueing consecutive duplicates, e.g. two POWEROFF */ + if (!llist_empty(&client->cmd_queue)) + prev = llist_last_entry(&client->cmd_queue, struct trxc_cmd_entry, list); + if (prev != NULL && strcmp(prev->msg.cmd, e->msg.cmd) == 0 + && strcmp(prev->msg.params, e->msg.params) == 0) { + LOGCL(client, LOGL_DEBUG, + "Not enqueueing duplicate '" CMD_NAME_FMT "'\n", + CMD_NAME_ARGS(e)); + talloc_free(e); + return -EEXIST; + } + + LOGCL(client, LOGL_INFO, "Enqueueing '" CMD_NAME_FMT "'\n", CMD_NAME_ARGS(e)); + llist_add_tail(&e->list, &client->cmd_queue); + + /* transmit, unless we already have a command in flight. + * If we are in the rx code path, skip transmitting: it's done + * when returning from the response handling. */ + if (prev == NULL && !client->in_rx) + trxc_client_send_next(client); + + return 0; +} + +/*! Flush (drop) all pending commands. May be called from within + * a response call-back. */ +void osmo_trxc_client_flush(struct osmo_trxc_client *client) +{ + struct trxc_cmd_entry *e, *e2; + + llist_for_each_entry_safe(e, e2, &client->cmd_queue, list) { + llist_del(&e->list); + talloc_free(e); + } + + TALLOC_FREE(client->last_acked); + + /* the queue is empty now, no point in keeping the timer armed */ + osmo_timer_del(&client->retrans_timer); + + /* if we are in the rx code path, signal to the returning code path */ + if (client->in_rx) + client->flushed_in_rx = true; +} + +static bool cmd_matches_rsp(const struct trxc_cmd_entry *e, + const struct osmo_trxc_msg *rsp) +{ + if (strcmp(e->msg.cmd, rsp->cmd) != 0) + return false; + /* Some commands (e.g. SETSLOT) may be pending for different params, + * so the response shall additionally be matched by the params. */ + if ((e->flags & OSMO_TRXC_F_MATCH_PARAMS) && strcmp(e->msg.params, rsp->params) != 0) + return false; + return true; +} + +/* Default response handling, when no rsp_cb was given */ +static int trxc_client_default_rsp_cb(struct osmo_trxc_client *client, + const struct trxc_cmd_entry *e, + const struct osmo_trxc_msg *rsp) +{ + if (rsp->status == 0) + return 0; + + LOGCL(client, (e->flags & OSMO_TRXC_F_CRITICAL) ? LOGL_FATAL : LOGL_NOTICE, + "Transceiver rejected '" CMD_NAME_FMT "' with response '%s'\n", + CMD_NAME_ARGS(e), osmo_trxc_msg_name(rsp)); + + if (e->flags & OSMO_TRXC_F_CRITICAL) + return -EINVAL; + return 0; +} + +static int trxc_client_fatal(struct osmo_trxc_client *client, + const struct osmo_trxc_msg *rsp) +{ + if (client->fatal_error_cb != NULL) { + client->fatal_error_cb(client, rsp); + } else { + LOGCL(client, LOGL_FATAL, "A critical command failed ('%s'), " + "and no fatal_error call-back is given\n", + rsp ? osmo_trxc_msg_name(rsp) : "timeout"); + } + + /* keep the command queue frozen, so the processing is stopped */ + return -EINVAL; +} + +/*! Feed a datagram received on the ctrl socket into the engine. + * + * To be called by the application for every datagram read from the TRXC + * socket. The engine parses the message, filters duplicate responses + * caused by retransmissions, matches the response against the command + * in flight, invokes its response call-back and transmits the next + * queued command (if any). + * + * \param[in] client TRXC client instance + * \param[in] buf received datagram (not necessarily zero-terminated) + * \param[in] len length of the datagram + * \returns 0 on success; negative on error */ +int osmo_trxc_client_rx(struct osmo_trxc_client *client, const char *buf, size_t len) +{ + struct osmo_trxc_msg rsp; + struct trxc_cmd_entry *e; + bool flushed; + int rc; + + rc = osmo_trxc_msg_parse(&rsp, buf, len); + if (rc < 0) { + LOGCL(client, LOGL_NOTICE, "Rx malformed TRXC message (rc=%d)\n", rc); + return rc; + } + if (rsp.type != OSMO_TRXC_MT_RSP) { + LOGCL(client, LOGL_NOTICE, "Rx unexpected TRXC message '%s'\n", + osmo_trxc_msg_name(&rsp)); + return -EINVAL; + } + + LOGCL(client, LOGL_INFO, "Rx '%s'\n", osmo_trxc_msg_name(&rsp)); + + /* abort the retransmit timer */ + osmo_timer_del(&client->retrans_timer); + + if (llist_empty(&client->cmd_queue)) { + /* a response from a retransmission, skip it */ + if (client->last_acked != NULL && cmd_matches_rsp(client->last_acked, &rsp)) { + LOGCL(client, LOGL_NOTICE, "Discarding duplicate response '%s'\n", + osmo_trxc_msg_name(&rsp)); + return 0; + } + LOGCL(client, LOGL_NOTICE, "Rx response without a pending command\n"); + return -ENOENT; + } + + e = llist_first_entry(&client->cmd_queue, struct trxc_cmd_entry, list); + + if (!cmd_matches_rsp(e, &rsp)) { + /* a response from a retransmission, skip it */ + if (client->last_acked != NULL && cmd_matches_rsp(client->last_acked, &rsp)) { + LOGCL(client, LOGL_NOTICE, "Discarding duplicate response '%s'\n", + osmo_trxc_msg_name(&rsp)); + /* the command in flight still awaits its response */ + osmo_timer_schedule(&client->retrans_timer, client->retrans_sec, 0); + return 0; + } + + LOGCL(client, (e->flags & OSMO_TRXC_F_CRITICAL) ? LOGL_FATAL : LOGL_NOTICE, + "Response '%s' does not match pending '" CMD_NAME_FMT "'\n", + osmo_trxc_msg_name(&rsp), CMD_NAME_ARGS(e)); + + if (e->flags & OSMO_TRXC_F_CRITICAL) + return trxc_client_fatal(client, &rsp); + + /* We may get 'RSP ERR 1' for non-critical commands not + * supported by the transceiver. Deliver such responses to + * the call-back of the command in flight, so that it can + * implement a fallback (see the SETFORMAT negotiation). */ + } + + client->in_rx = true; + if (e->rsp_cb != NULL) + rc = e->rsp_cb(client, &rsp, e->cb_data); + else + rc = trxc_client_default_rsp_cb(client, e, &rsp); + flushed = client->flushed_in_rx; + client->flushed_in_rx = false; + client->in_rx = false; + + if (rc < 0) + return trxc_client_fatal(client, &rsp); + + /* the call-back requested a re-transmission in rc seconds */ + if (rc > 0) { + /* the queue may have been flushed by the call-back */ + if (!flushed && !llist_empty(&client->cmd_queue)) + osmo_timer_schedule(&client->retrans_timer, rc, 0); + return 0; + } + + if (!flushed) { + /* dequeue the command, keep it for duplicate-RSP filtering */ + llist_del(&e->list); + talloc_free(client->last_acked); + client->last_acked = e; + } /* else: e was freed by osmo_trxc_client_flush(), do not access it */ + + /* transmit the next command waiting in the queue */ + trxc_client_send_next(client); + + return 0; +} + +/*********************************************************************** + * TRXD PDU version negotiation (SETFORMAT) + ***********************************************************************/ + +/*! Per-call SETFORMAT negotiation state, passed as cb_data through + * osmo_trxc_client_send_cmd() and freed in setformat_rsp_cb() */ +struct trxc_setformat_ctx { + uint8_t ver_req; + osmo_trxc_setformat_cb *cb; + void *cb_data; +}; + +static int setformat_rsp_cb(struct osmo_trxc_client *client, + const struct osmo_trxc_msg *rsp, void *cb_data) +{ + struct trxc_setformat_ctx *sf = cb_data; + int rc = 0; + + /* Old transceivers reject 'SETFORMAT' with 'RSP ERR 1' */ + if (strcmp(rsp->cmd, OSMO_TRXC_CMD_SETFORMAT) != 0) { + LOGCL(client, LOGL_NOTICE, "Transceiver rejected the format " + "negotiation command, using TRXD PDU version 0\n"); + if (sf->cb != NULL) + sf->cb(client, 0, sf->cb_data); + goto out_free; + } + + /* Status shall indicate a proper version supported by the transceiver */ + if (rsp->status < 0 || rsp->status > sf->ver_req) { + LOGCL(client, LOGL_ERROR, "Transceiver indicated an out of range " + "TRXD PDU version %d (requested %u)\n", + rsp->status, sf->ver_req); + rc = -EINVAL; + goto out_free; + } + + LOGCL(client, LOGL_INFO, "Using TRXD PDU version %d\n", rsp->status); + if (sf->cb != NULL) + sf->cb(client, rsp->status, sf->cb_data); + +out_free: + talloc_free(sf); + return rc; +} + +/*! Negotiate the TRXD PDU version with the transceiver (SETFORMAT). + * + * If the transceiver does not support the format negotiation at all, + * it rejects the command with 'RSP ERR 1' and version 0 is assumed. + * If the requested version is not supported by the transceiver, the + * status code of the response indicates a preferred lower version. + * + * \param[in] client TRXC client instance + * \param[in] ver_max the maximum (desired) TRXD PDU version + * \param[in] cb call-back invoked with the negotiated version + * \param[in] cb_data opaque data for the call-back + * \returns 0 on success; negative on error */ +int osmo_trxc_client_negotiate_format(struct osmo_trxc_client *client, + uint8_t ver_max, + osmo_trxc_setformat_cb *cb, void *cb_data) +{ + struct trxc_setformat_ctx *sf; + int rc; + + sf = talloc_zero(client, struct trxc_setformat_ctx); + if (sf == NULL) + return -ENOMEM; + sf->ver_req = ver_max; + sf->cb = cb; + sf->cb_data = cb_data; + + LOGCL(client, LOGL_INFO, "Requesting TRXD PDU version %u\n", ver_max); + + rc = osmo_trxc_client_send_cmd(client, OSMO_TRXC_F_MATCH_PARAMS, + &setformat_rsp_cb, sf, + OSMO_TRXC_CMD_SETFORMAT, "%u", ver_max); + if (rc < 0) + talloc_free(sf); + return rc; +} diff --git a/tests/libosmo-trx/Makefile.am b/tests/libosmo-trx/Makefile.am index 162cbac..cf45cf0 100644 --- a/tests/libosmo-trx/Makefile.am +++ b/tests/libosmo-trx/Makefile.am @@ -21,14 +21,19 @@ EXTRA_DIST = \ trxc_test.ok \ + trxc_client_test.ok \ + trxc_client_test.err \ trxd_test.ok \ $(NULL) check_PROGRAMS = \ trxc_test \ + trxc_client_test \ trxd_test \ $(NULL) trxc_test_SOURCES = trxc_test.c +trxc_client_test_SOURCES = trxc_client_test.c + trxd_test_SOURCES = trxd_test.c diff --git a/tests/libosmo-trx/trxc_client_test.c b/tests/libosmo-trx/trxc_client_test.c new file mode 100644 index 0000000..4b93a30 --- /dev/null +++ b/tests/libosmo-trx/trxc_client_test.c @@ -0,0 +1,316 @@ +/*! \file tests/trxc_client_test.c + * Regression test for the TRXC client command queue engine. */ + +/* + * (C) 2026 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de> + * Author: Vadim Yanitskiy <vyanitskiy(a)sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <
http://www.gnu.org/licenses/
>. + */ + +#include <stdio.h> +#include <string.h> + +#include <osmocom/core/application.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/timer_compat.h> +#include <osmocom/core/utils.h> + +#include <osmocom/trx/trxc.h> +#include <osmocom/trx/trxc_client.h> + +static void *test_ctx = NULL; + +/* feed a response into the engine, printing what happens */ +static void rx_rsp(struct osmo_trxc_client *client, const char *rsp) +{ + int rc; + + printf("rx_rsp: '%s'\n", rsp); + rc = osmo_trxc_client_rx(client, rsp, strlen(rsp)); + if (rc != 0) + printf("\trc=%d\n", rc); +} + +/* advance the (overridden) time and fire expired timers */ +static void fake_time_passes(time_t sec) +{ + printf("(time passes: %ld s)\n", (long)sec); + osmo_gettimeofday_override_add(sec, 0); + osmo_timers_prepare(); + osmo_timers_update(); +} + +static int tx_msg_cb(struct osmo_trxc_client *client, const char *buf, size_t len) +{ + printf("tx_msg: '%s'\n", buf); + return 0; +} + +static void fatal_error_cb(struct osmo_trxc_client *client, + const struct osmo_trxc_msg *rsp) +{ + printf("fatal_error: '%s'\n", rsp ? osmo_trxc_msg_name(rsp) : "(null)"); +} + +static struct osmo_trxc_client *client_alloc(void) +{ + struct osmo_trxc_client *client; + + client = osmo_trxc_client_alloc(test_ctx); + OSMO_ASSERT(client != NULL); + osmo_trxc_client_set_tx_msg_cb(client, &tx_msg_cb); + osmo_trxc_client_set_fatal_error_cb(client, &fatal_error_cb); + osmo_trxc_client_set_priv(client, "test-priv"); + OSMO_ASSERT(strcmp(osmo_trxc_client_get_priv(client), "test-priv") == 0); + osmo_trxc_client_set_name(client, "phy%u.trx%u", 0, 0); + + return client; +} + +static int rsp_cb(struct osmo_trxc_client *client, + const struct osmo_trxc_msg *rsp, void *cb_data) +{ + printf("rsp_cb(%s): '%s'\n", (const char *)cb_data, osmo_trxc_msg_name(rsp)); + return 0; +} + +static void test_basic(void) +{ + struct osmo_trxc_client *client = client_alloc(); + + printf("=== %s ===\n", __func__); + + /* a command is transmitted immediately when the queue is empty */ + osmo_trxc_client_poweron(client, &rsp_cb, "poweron"); + rx_rsp(client, "RSP POWERON 0"); + + osmo_trxc_client_free(client); +} + +static void test_queueing(void) +{ + struct osmo_trxc_client *client = client_alloc(); + + printf("=== %s ===\n", __func__); + + /* only the first command is transmitted... */ + osmo_trxc_client_rxtune(client, 890000, &rsp_cb, "rxtune"); + osmo_trxc_client_txtune(client, 935000, &rsp_cb, "txtune"); + osmo_trxc_client_send_cmd(client, 0, &rsp_cb, "setslot", + OSMO_TRXC_CMD_SETSLOT, "%u %u", 0, 1); + /* ... consecutive duplicates are not enqueued at all */ + osmo_trxc_client_send_cmd(client, 0, &rsp_cb, "setslot", + OSMO_TRXC_CMD_SETSLOT, "%u %u", 0, 1); + + /* each response triggers transmission of the next command */ + rx_rsp(client, "RSP RXTUNE 0 890000"); + rx_rsp(client, "RSP TXTUNE 0 935000"); + rx_rsp(client, "RSP SETSLOT 0 0 1"); + + osmo_trxc_client_free(client); +} + +static void test_dup_rsp(void) +{ + struct osmo_trxc_client *client = client_alloc(); + + printf("=== %s ===\n", __func__); + + osmo_trxc_client_poweron(client, &rsp_cb, "poweron"); + rx_rsp(client, "RSP POWERON 0"); + /* a duplicate response (e.g. caused by retransmission) is discarded */ + rx_rsp(client, "RSP POWERON 0"); + /* an unexpected response is reported */ + rx_rsp(client, "RSP POWEROFF 0"); + + osmo_trxc_client_free(client); +} + +static void test_retrans(void) +{ + struct osmo_trxc_client *client = client_alloc(); + + printf("=== %s ===\n", __func__); + + osmo_trxc_client_poweron(client, &rsp_cb, "poweron"); + /* no response: the command is retransmitted (default: every 2 s) */ + fake_time_passes(2); + fake_time_passes(2); + rx_rsp(client, "RSP POWERON 0"); + /* no pending commands anymore, the timer shall be inactive */ + fake_time_passes(10); + + osmo_trxc_client_free(client); +} + +static int rsp_retry_cb(struct osmo_trxc_client *client, + const struct osmo_trxc_msg *rsp, void *cb_data) +{ + printf("rsp_retry_cb: '%s'\n", osmo_trxc_msg_name(rsp)); + + /* POWERON failed: re-send it after 5 seconds */ + if (rsp->status != 0) + return 5; + return 0; +} + +static void test_rsp_cb_retry(void) +{ + struct osmo_trxc_client *client = client_alloc(); + + printf("=== %s ===\n", __func__); + + osmo_trxc_client_poweron(client, &rsp_retry_cb, NULL); + /* transceiver is not ready yet, the call-back requests a retry */ + rx_rsp(client, "RSP POWERON 1"); + fake_time_passes(5); + rx_rsp(client, "RSP POWERON 0"); + + osmo_trxc_client_free(client); +} + +static void test_fatal_error(void) +{ + struct osmo_trxc_client *client = client_alloc(); + + printf("=== %s ===\n", __func__); + + /* no rsp_cb given: a NACKed critical command is escalated */ + osmo_trxc_client_rxtune(client, 890000, NULL, NULL); + rx_rsp(client, "RSP RXTUNE 1 890000"); + + osmo_trxc_client_free(client); +} + +static void setformat_cb(struct osmo_trxc_client *client, + uint8_t ver_use, void *cb_data) +{ + printf("setformat_cb: ver_use=%u\n", ver_use); +} + +static void test_negotiate_format(void) +{ + struct osmo_trxc_client *client = client_alloc(); + + printf("=== %s ===\n", __func__); + + /* case a) the transceiver confirms the requested version */ + osmo_trxc_client_negotiate_format(client, 2, &setformat_cb, NULL); + rx_rsp(client, "RSP SETFORMAT 2 2"); + + /* case b) the transceiver indicates a lower version */ + osmo_trxc_client_negotiate_format(client, 2, &setformat_cb, NULL); + rx_rsp(client, "RSP SETFORMAT 1 2"); + + /* case c) an old transceiver rejects the command ('RSP ERR 1') */ + osmo_trxc_client_negotiate_format(client, 2, &setformat_cb, NULL); + rx_rsp(client, "RSP ERR 1"); + + /* case d) the transceiver indicates an out of range version */ + osmo_trxc_client_negotiate_format(client, 2, &setformat_cb, NULL); + rx_rsp(client, "RSP SETFORMAT 5 2"); + + osmo_trxc_client_free(client); +} + +static void test_flush(void) +{ + struct osmo_trxc_client *client = client_alloc(); + + printf("=== %s ===\n", __func__); + + osmo_trxc_client_rxtune(client, 890000, &rsp_cb, "rxtune"); + osmo_trxc_client_txtune(client, 935000, &rsp_cb, "txtune"); + osmo_trxc_client_flush(client); + + /* a late response finds no pending command */ + rx_rsp(client, "RSP RXTUNE 0 890000"); + /* the retransmit timer shall be inactive */ + fake_time_passes(10); + + osmo_trxc_client_free(client); +} + +static int rsp_flush_cb(struct osmo_trxc_client *client, + const struct osmo_trxc_msg *rsp, void *cb_data) +{ + printf("rsp_flush_cb: '%s', flushing the queue\n", osmo_trxc_msg_name(rsp)); + osmo_trxc_client_flush(client); + return 0; +} + +static void test_flush_in_rsp_cb(void) +{ + struct osmo_trxc_client *client = client_alloc(); + + printf("=== %s ===\n", __func__); + + /* flushing the queue from within a response call-back */ + osmo_trxc_client_poweroff(client, &rsp_flush_cb, NULL); + osmo_trxc_client_rxtune(client, 890000, &rsp_cb, "rxtune"); + rx_rsp(client, "RSP POWEROFF 0"); + fake_time_passes(10); + + osmo_trxc_client_free(client); +} + +static void test_malformed(void) +{ + struct osmo_trxc_client *client = client_alloc(); + + printf("=== %s ===\n", __func__); + + rx_rsp(client, "MALFORMED MESSAGE"); + rx_rsp(client, "IND CLOCK 1234"); /* not a RSP */ + + osmo_trxc_client_free(client); +} + +int main(int argc, char **argv) +{ + test_ctx = talloc_named_const(NULL, 0, "trxc_client_test"); + osmo_init_logging2(test_ctx, NULL); + log_set_use_color(osmo_stderr_target, 0); + log_set_print_timestamp(osmo_stderr_target, 0); + log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE); + log_set_print_category(osmo_stderr_target, 1); + log_set_print_category_hex(osmo_stderr_target, 0); + log_set_print_level(osmo_stderr_target, 1); + log_set_category_filter(osmo_stderr_target, DLGLOBAL, 1, LOGL_DEBUG); + + /* take control over the clock */ + osmo_gettimeofday_override = true; + osmo_gettimeofday_override_time = (struct timeval){ 1000000, 0 }; + + test_basic(); + test_queueing(); + test_dup_rsp(); + test_retrans(); + test_rsp_cb_retry(); + test_fatal_error(); + test_negotiate_format(); + test_flush(); + test_flush_in_rsp_cb(); + test_malformed(); + + printf("Done\n"); + return 0; +} diff --git a/tests/libosmo-trx/trxc_client_test.err b/tests/libosmo-trx/trxc_client_test.err new file mode 100644 index 0000000..e280bf1 --- /dev/null +++ b/tests/libosmo-trx/trxc_client_test.err @@ -0,0 +1,69 @@ +DLGLOBAL INFO phy0.trx0: Enqueueing 'CMD POWERON' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD POWERON' +DLGLOBAL INFO phy0.trx0: Rx 'RSP POWERON 0' +DLGLOBAL INFO phy0.trx0: Enqueueing 'CMD RXTUNE 890000' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD RXTUNE 890000' +DLGLOBAL INFO phy0.trx0: Enqueueing 'CMD TXTUNE 935000' +DLGLOBAL INFO phy0.trx0: Enqueueing 'CMD SETSLOT 0 1' +DLGLOBAL DEBUG phy0.trx0: Not enqueueing duplicate 'CMD SETSLOT 0 1' +DLGLOBAL INFO phy0.trx0: Rx 'RSP RXTUNE 0 890000' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD TXTUNE 935000' +DLGLOBAL INFO phy0.trx0: Rx 'RSP TXTUNE 0 935000' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD SETSLOT 0 1' +DLGLOBAL INFO phy0.trx0: Rx 'RSP SETSLOT 0 0 1' +DLGLOBAL INFO phy0.trx0: Enqueueing 'CMD POWERON' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD POWERON' +DLGLOBAL INFO phy0.trx0: Rx 'RSP POWERON 0' +DLGLOBAL INFO phy0.trx0: Rx 'RSP POWERON 0' +DLGLOBAL NOTICE phy0.trx0: Discarding duplicate response 'RSP POWERON 0' +DLGLOBAL INFO phy0.trx0: Rx 'RSP POWEROFF 0' +DLGLOBAL NOTICE phy0.trx0: Rx response without a pending command +DLGLOBAL INFO phy0.trx0: Enqueueing 'CMD POWERON' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD POWERON' +DLGLOBAL NOTICE phy0.trx0: No response from transceiver for 'CMD POWERON' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD POWERON' +DLGLOBAL NOTICE phy0.trx0: No response from transceiver for 'CMD POWERON' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD POWERON' +DLGLOBAL INFO phy0.trx0: Rx 'RSP POWERON 0' +DLGLOBAL INFO phy0.trx0: Enqueueing 'CMD POWERON' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD POWERON' +DLGLOBAL INFO phy0.trx0: Rx 'RSP POWERON 1' +DLGLOBAL NOTICE phy0.trx0: No response from transceiver for 'CMD POWERON' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD POWERON' +DLGLOBAL INFO phy0.trx0: Rx 'RSP POWERON 0' +DLGLOBAL INFO phy0.trx0: Enqueueing 'CMD RXTUNE 890000' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD RXTUNE 890000' +DLGLOBAL INFO phy0.trx0: Rx 'RSP RXTUNE 1 890000' +DLGLOBAL FATAL phy0.trx0: Transceiver rejected 'CMD RXTUNE 890000' with response 'RSP RXTUNE 1 890000' +DLGLOBAL INFO phy0.trx0: Requesting TRXD PDU version 2 +DLGLOBAL INFO phy0.trx0: Enqueueing 'CMD SETFORMAT 2' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD SETFORMAT 2' +DLGLOBAL INFO phy0.trx0: Rx 'RSP SETFORMAT 2 2' +DLGLOBAL INFO phy0.trx0: Using TRXD PDU version 2 +DLGLOBAL INFO phy0.trx0: Requesting TRXD PDU version 2 +DLGLOBAL INFO phy0.trx0: Enqueueing 'CMD SETFORMAT 2' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD SETFORMAT 2' +DLGLOBAL INFO phy0.trx0: Rx 'RSP SETFORMAT 1 2' +DLGLOBAL INFO phy0.trx0: Using TRXD PDU version 1 +DLGLOBAL INFO phy0.trx0: Requesting TRXD PDU version 2 +DLGLOBAL INFO phy0.trx0: Enqueueing 'CMD SETFORMAT 2' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD SETFORMAT 2' +DLGLOBAL INFO phy0.trx0: Rx 'RSP ERR 1' +DLGLOBAL NOTICE phy0.trx0: Response 'RSP ERR 1' does not match pending 'CMD SETFORMAT 2' +DLGLOBAL NOTICE phy0.trx0: Transceiver rejected the format negotiation command, using TRXD PDU version 0 +DLGLOBAL INFO phy0.trx0: Requesting TRXD PDU version 2 +DLGLOBAL INFO phy0.trx0: Enqueueing 'CMD SETFORMAT 2' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD SETFORMAT 2' +DLGLOBAL INFO phy0.trx0: Rx 'RSP SETFORMAT 5 2' +DLGLOBAL ERROR phy0.trx0: Transceiver indicated an out of range TRXD PDU version 5 (requested 2) +DLGLOBAL INFO phy0.trx0: Enqueueing 'CMD RXTUNE 890000' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD RXTUNE 890000' +DLGLOBAL INFO phy0.trx0: Enqueueing 'CMD TXTUNE 935000' +DLGLOBAL INFO phy0.trx0: Rx 'RSP RXTUNE 0 890000' +DLGLOBAL NOTICE phy0.trx0: Rx response without a pending command +DLGLOBAL INFO phy0.trx0: Enqueueing 'CMD POWEROFF' +DLGLOBAL DEBUG phy0.trx0: Tx 'CMD POWEROFF' +DLGLOBAL INFO phy0.trx0: Enqueueing 'CMD RXTUNE 890000' +DLGLOBAL INFO phy0.trx0: Rx 'RSP POWEROFF 0' +DLGLOBAL NOTICE phy0.trx0: Rx malformed TRXC message (rc=-22) +DLGLOBAL NOTICE phy0.trx0: Rx unexpected TRXC message 'IND CLOCK 1234' diff --git a/tests/libosmo-trx/trxc_client_test.ok b/tests/libosmo-trx/trxc_client_test.ok new file mode 100644 index 0000000..5e908ce --- /dev/null +++ b/tests/libosmo-trx/trxc_client_test.ok @@ -0,0 +1,73 @@ +=== test_basic === +tx_msg: 'CMD POWERON' +rx_rsp: 'RSP POWERON 0' +rsp_cb(poweron): 'RSP POWERON 0' +=== test_queueing === +tx_msg: 'CMD RXTUNE 890000' +rx_rsp: 'RSP RXTUNE 0 890000' +rsp_cb(rxtune): 'RSP RXTUNE 0 890000' +tx_msg: 'CMD TXTUNE 935000' +rx_rsp: 'RSP TXTUNE 0 935000' +rsp_cb(txtune): 'RSP TXTUNE 0 935000' +tx_msg: 'CMD SETSLOT 0 1' +rx_rsp: 'RSP SETSLOT 0 0 1' +rsp_cb(setslot): 'RSP SETSLOT 0 0 1' +=== test_dup_rsp === +tx_msg: 'CMD POWERON' +rx_rsp: 'RSP POWERON 0' +rsp_cb(poweron): 'RSP POWERON 0' +rx_rsp: 'RSP POWERON 0' +rx_rsp: 'RSP POWEROFF 0' + rc=-2 +=== test_retrans === +tx_msg: 'CMD POWERON' +(time passes: 2 s) +tx_msg: 'CMD POWERON' +(time passes: 2 s) +tx_msg: 'CMD POWERON' +rx_rsp: 'RSP POWERON 0' +rsp_cb(poweron): 'RSP POWERON 0' +(time passes: 10 s) +=== test_rsp_cb_retry === +tx_msg: 'CMD POWERON' +rx_rsp: 'RSP POWERON 1' +rsp_retry_cb: 'RSP POWERON 1' +(time passes: 5 s) +tx_msg: 'CMD POWERON' +rx_rsp: 'RSP POWERON 0' +rsp_retry_cb: 'RSP POWERON 0' +=== test_fatal_error === +tx_msg: 'CMD RXTUNE 890000' +rx_rsp: 'RSP RXTUNE 1 890000' +fatal_error: 'RSP RXTUNE 1 890000' + rc=-22 +=== test_negotiate_format === +tx_msg: 'CMD SETFORMAT 2' +rx_rsp: 'RSP SETFORMAT 2 2' +setformat_cb: ver_use=2 +tx_msg: 'CMD SETFORMAT 2' +rx_rsp: 'RSP SETFORMAT 1 2' +setformat_cb: ver_use=1 +tx_msg: 'CMD SETFORMAT 2' +rx_rsp: 'RSP ERR 1' +setformat_cb: ver_use=0 +tx_msg: 'CMD SETFORMAT 2' +rx_rsp: 'RSP SETFORMAT 5 2' +fatal_error: 'RSP SETFORMAT 5 2' + rc=-22 +=== test_flush === +tx_msg: 'CMD RXTUNE 890000' +rx_rsp: 'RSP RXTUNE 0 890000' + rc=-2 +(time passes: 10 s) +=== test_flush_in_rsp_cb === +tx_msg: 'CMD POWEROFF' +rx_rsp: 'RSP POWEROFF 0' +rsp_flush_cb: 'RSP POWEROFF 0', flushing the queue +(time passes: 10 s) +=== test_malformed === +rx_rsp: 'MALFORMED MESSAGE' + rc=-22 +rx_rsp: 'IND CLOCK 1234' + rc=-22 +Done diff --git a/tests/testsuite.at b/tests/testsuite.at index 6e72c81..090906b 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -56,6 +56,13 @@ AT_CHECK([$abs_top_builddir/tests/libosmo-trx/trxc_test], [], [expout], []) AT_CLEANUP +AT_SETUP([trxc_client_test]) +AT_KEYWORDS([trxc_client_test]) +cat $abs_srcdir/libosmo-trx/trxc_client_test.ok > expout +cat $abs_srcdir/libosmo-trx/trxc_client_test.err > experr +AT_CHECK([$abs_top_builddir/tests/libosmo-trx/trxc_client_test], [], [expout], [experr]) +AT_CLEANUP + AT_SETUP([trxd_test]) AT_KEYWORDS([trxd_test]) cat $abs_srcdir/libosmo-trx/trxd_test.ok > expout -- To view, visit
https://gerrit.osmocom.org/c/osmo-trx/+/43108?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged Gerrit-Project: osmo-trx Gerrit-Branch: master Gerrit-Change-Id: I817e394f74a10e3adae4a0b58342c82acdf0794e Gerrit-Change-Number: 43108 Gerrit-PatchSet: 6 Gerrit-Owner: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de> Gerrit-Reviewer: laforge <laforge(a)osmocom.org> Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
1
0
0
0
[L] Change in libosmo-sigtran[master]: WIP: Cisco like DSCP configuration support
by pespin
17 Sep '26
17 Sep '26
Attention is currently required from: jolly. pespin has posted comments on this change by jolly. (
https://gerrit.osmocom.org/c/libosmo-sigtran/+/43667?usp=email
) Change subject: WIP: Cisco like DSCP configuration support ...................................................................... Patch Set 1: (14 comments) This change is ready for review. Patchset: PS1: TODO: figure out if it makes sense to provide a default qos, and with with ip-dscp, etc. File include/osmocom/sigtran/osmo_ss7.h:
https://gerrit.osmocom.org/c/libosmo-sigtran/+/43667/comment/b87f878f_eb6b2…
: PS1, Line 155: * QoS Class Better create a new private ss7_qos_class.h + ss7_qos_class.c inside src/, and avoid prefixing it with osmo_ to avoid making them public until there's a real need for them to be public. This way we can extend without breaking the API.
https://gerrit.osmocom.org/c/libosmo-sigtran/+/43667/comment/afd2067c_a1e1b…
: PS1, Line 167: osmo_ss7_qos_class_find(struct osmo_ss7_instance *inst, uint8_t qos_class); This one can go in ss7_instance.h + ss7_instance.c File src/Makefile.am:
https://gerrit.osmocom.org/c/libosmo-sigtran/+/43667/comment/ed222008_955d4…
: PS1, Line 92: ss7_qos_class_vty.c \ good, you need to do the same with ss7_qos_class.c and .h. File src/ss7_asp_vty.c:
https://gerrit.osmocom.org/c/libosmo-sigtran/+/43667/comment/6a792eb9_260a6…
: PS1, Line 414: "qos-class " IP_QOS_CLASS_RANGE_STR, SS7_QOS_CLASS_RANGE_STR
https://gerrit.osmocom.org/c/libosmo-sigtran/+/43667/comment/0e0613f2_81db4…
: PS1, Line 423: asp->cfg.ip_dscp = 0; why do you keep asp->cfg.ip_dscp? IMHO it should be removed now that we have the qos-class stuff.
https://gerrit.osmocom.org/c/libosmo-sigtran/+/43667/comment/a1562528_b2991…
: PS1, Line 443: DEFUN_ATTR(asp_ip_dscps, asp_ip_dscp_cmd, This should be deprecated in some way imho. File src/ss7_instance.c:
https://gerrit.osmocom.org/c/libosmo-sigtran/+/43667/comment/3837f739_7db64…
: PS1, Line 363: llist_for_each_entry(xua, &inst->xua_servers, list) { Cisco ITP doesn't have qos-class node for "listen" node, but I think it's actually a good idea to use it here.
https://gerrit.osmocom.org/c/libosmo-sigtran/+/43667/comment/ae9c485b_bcc2f…
: PS1, Line 392: void osmo_ss7_qos_class_update(struct osmo_ss7_qos_class *qos) This can go into ss7_qos_class.c too. btw I don't recall seeing it declared in the header file? File src/ss7_qos_class_vty.c:
https://gerrit.osmocom.org/c/libosmo-sigtran/+/43667/comment/bf895354_4d8f1…
: PS1, Line 112: if (qos->ip_dscp) { I think is "qos" exists it should always print the "qos class" node, even if empty.
https://gerrit.osmocom.org/c/libosmo-sigtran/+/43667/comment/b1000191_f8c93…
: PS1, Line 114: if (qos->ip_dscp) this is always true in this path? File src/ss7_vty.c:
https://gerrit.osmocom.org/c/libosmo-sigtran/+/43667/comment/baaa5a1e_d2538…
: PS1, Line 1409: /* then dump ASPs, as ASs reference them */ I think you can simplify and drop all the connectors "first, then, now, finally" ;)
https://gerrit.osmocom.org/c/libosmo-sigtran/+/43667/comment/ef4efdbd_1854a…
: PS1, Line 1442: return ss7_vty_node_qos_class_go_parent(vty); Are you sure the func is needed? or simply apply it directly here like others?
https://gerrit.osmocom.org/c/libosmo-sigtran/+/43667/comment/823a30d6_ac7f5…
: PS1, Line 1548: remove this empty line. -- To view, visit
https://gerrit.osmocom.org/c/libosmo-sigtran/+/43667?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment Gerrit-Project: libosmo-sigtran Gerrit-Branch: master Gerrit-Change-Id: Ic346698fc63771d95e474aec53ffb6c776636b3b Gerrit-Change-Number: 43667 Gerrit-PatchSet: 1 Gerrit-Owner: jolly <andreas(a)eversberg.eu> Gerrit-Reviewer: Jenkins Builder Gerrit-CC: pespin <pespin(a)sysmocom.de> Gerrit-Attention: jolly <andreas(a)eversberg.eu> Gerrit-Comment-Date: Thu, 17 Sep 2026 17:03:10 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No
1
0
0
0
← Newer
1
...
37
38
39
40
41
42
43
...
162
Older →
Jump to page:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
Results per page:
10
25
50
100
200