falconia has submitted this change. ( https://gerrit.osmocom.org/c/libosmo-netif/+/39281?usp=email )
Change subject: bring twrtp into libosmo-netif ......................................................................
bring twrtp into libosmo-netif
twrtp is the top layer of Themyscira Wireless RTP endpoint implementation, an alternative to Belledonne libortp. Unlike Belledonne software, ThemWi RTP library was developed specifically for use in Osmocom-based GSM, ISDN and IP-PSTN network elements, and is built on top of libosmocore primitives - thus it can function more natively in Osmocom universe than ortp. This ThemWi library was initially developed externally to Osmocom, but is now being brought into libosmo-netif so it can be used by native Osmocom projects, particularly OsmoBTS.
Related: OS#6474 Change-Id: Ib63215aaf13ef8ab8f2e0c8d310164cd5c8824eb --- M include/osmocom/netif/Makefile.am A include/osmocom/netif/rtcp_defs.h A include/osmocom/netif/twrtp.h M src/Makefile.am A src/twrtp.c 5 files changed, 1,559 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/include/osmocom/netif/Makefile.am b/include/osmocom/netif/Makefile.am index 19678dc..71f0931 100644 --- a/include/osmocom/netif/Makefile.am +++ b/include/osmocom/netif/Makefile.am @@ -1,4 +1,5 @@ noinst_HEADERS = \ + rtcp_defs.h \ stream_private.h \ twjit_private.h \ $(NULL) @@ -28,6 +29,7 @@ rtp.h \ stream.h \ twjit.h \ + twrtp.h \ version.h \ $(NULL)
diff --git a/include/osmocom/netif/rtcp_defs.h b/include/osmocom/netif/rtcp_defs.h new file mode 100644 index 0000000..ad552a6 --- /dev/null +++ b/include/osmocom/netif/rtcp_defs.h @@ -0,0 +1,49 @@ +/* + * Some definitions for RTCP, just enough to implement SR and RR + * generation and parsing in twrtp. + */ + +#pragma once + +#include <stdint.h> + +struct rtcp_sr_rr_hdr { + uint8_t v_p_rc; + uint8_t pt; + uint16_t len; + uint32_t ssrc; +} __attribute__((packed)); + +struct rtcp_sr_block { + uint32_t ntp_sec; + uint32_t ntp_fract; + uint32_t rtp_ts; + uint32_t pkt_count; + uint32_t octet_count; +} __attribute__((packed)); + +struct rtcp_rr_block { + uint32_t ssrc; + uint32_t lost_word; + uint32_t max_seq_ext; + uint32_t jitter; + uint16_t lsr_sec; + uint16_t lsr_fract; + uint16_t dlsr_sec; + uint16_t dlsr_fract; +} __attribute__((packed)); + +#define RTCP_PT_SR 200 +#define RTCP_PT_RR 201 +#define RTCP_PT_SDES 202 +#define RTCP_PT_BYE 203 +#define RTCP_PT_APP 204 + +#define SDES_ITEM_CNAME 1 +#define SDES_ITEM_NAME 2 +#define SDES_ITEM_EMAIL 3 +#define SDES_ITEM_PHONE 4 +#define SDES_ITEM_LOC 5 +#define SDES_ITEM_TOOL 6 +#define SDES_ITEM_NOTE 7 +#define SDES_ITEM_PRIV 8 diff --git a/include/osmocom/netif/twrtp.h b/include/osmocom/netif/twrtp.h new file mode 100644 index 0000000..f1852ec --- /dev/null +++ b/include/osmocom/netif/twrtp.h @@ -0,0 +1,234 @@ +/* + * Themyscira Wireless RTP endpoint implementation: + * public API definition for Osmocom-integrated version. + * + * This code was contributed to Osmocom Cellular Network Infrastructure + * project by Mother Mychaela N. Falconia of Themyscira Wireless. + * Mother Mychaela's contributions are NOT subject to copyright: + * no rights reserved, all rights relinquished. + */ + +#pragma once + +#include <stdint.h> +#include <stdbool.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/socket.h> + +/*! \defgroup twrtp Themyscira Wireless RTP endpoint implementation + * @{ + * + * osmo_twrtp is a complete RTP endpoint. It is primarily designed + * to be used together with twjit to build a bidirectional interface + * between an RTP stream and a fixed timing system such as GSM Um TCH + * or T1/E1 Abis, but it also has limited support for endpoints without + * twjit. A twrtp endpoint without twjit is either an output-only + * endpoint (playout of in-band tones and announcements etc), or one + * side of a pair of endpoints that forward RTP packets to each other + * without delay. + * + * The basic workflow is: + * + * 1. Create a twrtp instance with osmo_twrtp_create(). The decision + * to use or not use twjit is fixed at this time; if twjit is to be + * used, struct osmo_twjit_config needs to be provided. + * + * 2. Create and bind a pair of local UDP sockets for RTP and RTCP, + * or supply osmo_twrtp with an already-obtained pair of file descriptors + * for the same. Most users will find the high-level API + * osmo_twrtp_bind_local() most suitable, but some applications may + * prefer to use the low-level API osmo_twrtp_supply_fds() instead. + * + * 3. Set the IP:port address of the remote RTP end with + * osmo_twrtp_set_remote(). + * + * 4. Traffic can now be sent and received as detailed below. + * + * Receiving RTP traffic, interworking to a fixed timing system: + * + * 1. Provide struct osmo_twjit_config to osmo_twrtp_create(), so the twrtp + * instance will be created with twjit included. + * + * 2. When you are ready to start receiving traffic, call + * osmo_twrtp_twjit_rx_ctrl() with rx_enable argument set to true. + * + * 3. Once you've made the above call, commit to calling + * osmo_twrtp_twjit_rx_poll() every 20 ms (or whatever your quantum + * duration is) as timed by your GSM Um TCH or TDM system, every tick + * without fail. + * + * 4. You can pause operation by calling osmo_twrtp_twjit_rx_ctrl() with + * rx_enable argument set to false, and then later restart by returning + * to step 2 above. When you pause, the Rx jitter buffer will be + * flushed, and when you restart Rx, twjit will restart from empty state. + * + * Sending RTP traffic, coming from a fixed timing system: + * + * 1. Make the first call to osmo_twrtp_tx_quantum() whenever you are ready + * to send out the first quantum. + * + * 2. Once you've made that first call, commit to sending either another + * quantum or an intentional gap (osmo_twrtp_tx_skip()) every 20 ms + * without fail, as timed by your GSM Um TCH, T1/E1 TDM or other fixed + * timing system. + * + * 3. If you need to pause Tx output and restart later, or if some + * discontinuity occurs in your fixed timing system where you know that + * your interval between quantum sends is not the proper 20 ms or whatever + * your quantum duration is, call osmo_twrtp_tx_restart(), telling the + * library to reset the RTP timescale in its subsequent output. + * + * No-delay forwarding operation from one twrtp endpoint to another: + * + * 1. On the receiving side, call osmo_twrtp_set_raw_rx_cb() to set up an + * unbuffered/non-delayed Rx callback function. + * + * 2. In that Rx callback function, forward the packet to the other endpoint + * with osmo_twrtp_tx_forward(). + * + * 3. If you are building an MGW that mostly does forwarding as described here, + * but occasionally inserts its own in-band tones or announcements, you can + * switch in real time between just-described forwarding and "native" + * osmo_twrtp_tx_quantum() output. The receiving RTP end will see + * "handover" events as SSRC switches between the one emitted by twrtp + * and the one coming from the other remote party. Actual timing will + * also switch, as there is no realistic way your own 20 ms timing for + * announcement playout will exactly match the timing of the RTP stream + * switched from the other remote party. + * + * RTCP handling is mostly internal to the library - as a user, you don't need + * to concern yourself with it. More precisely, incoming RTCP packets are + * always handled internally; if you wish to send out RTCP, you have to set + * SDES and decide if you wish to send out SR or RR packets. Automatic + * emission of an SR packet after every so many RTP packets, with an RR block + * included in that SR, is the most common and most useful mode. OTOH, if + * your RTP application does not use RTCP, you don't need to concern yourself + * with RTCP at all: don't configure or enable RTCP sending, and ignore the + * existence of the built-in RTCP receiver. Any received RTCP packets will + * still be parsed, but you can ignore the data that result from this parsing. + * + * There also exists a detailed document titled _Guide to ThemWi RTP + * endpoint library_, located here: + * https://www.freecalypso.org/TW-doc/twrtp-guide-latest.pdf + * (See TW-doc directory listing for other formats and previous versions.) + * This document is required reading for anyone seeking to properly + * understand twrtp, its domain of application and all of its capabilities, + * beyond the brief summary given above. Specific section references to + * this document will be made in subsequent comments. + * + * FIXME: create an Osmocom-controlled version of this document + * that describes the version of twrtp+twjit modified for inclusion + * in Osmocom. + */ + +/*! Each instance of twrtp in the present version exists as struct osmo_twrtp. + * This structure is opaque, and always constitutes a talloc context. */ +struct osmo_twrtp; + +/*! Stats collected during the lifetime of a twrtp instance. + * For a detailed description of each of these counters, see Chapter 6 + * of twrtp guide document. + * + * This stats structure is never allocated or accessed in a writable + * manner by applications; instead it is allocated inside the library + * as part of opaque struct osmo_twrtp, while applications are given + * const pointers to these structs. + */ +struct osmo_twrtp_stats { + /* For ABI reasons, none of the following fields may be deleted + * or reordered! */ + uint32_t rx_rtp_pkt; + uint32_t rx_rtp_badsrc; + uint32_t rx_rtcp_pkt; + uint32_t rx_rtcp_badsrc; + uint32_t rx_rtcp_invalid; + uint32_t rx_rtcp_wrong_ssrc; + uint32_t tx_rtp_pkt; + uint32_t tx_rtp_bytes; + uint32_t tx_rtcp_pkt; + /* New fields may be added here at the end; once added, they become + * permanent like the initially defined ones. */ +}; + +/* declare structs that are used in our API */ + +struct osmo_twjit; +struct osmo_twjit_config; +struct osmo_twjit_stats; +struct osmo_twjit_rr_info; + +/* public API functions: create & destroy, local and remote addresses */ + +struct osmo_twrtp * +osmo_twrtp_create(void *ctx, uint16_t clock_khz, uint16_t quantum_ms, + bool random_ts_seq, + const struct osmo_twjit_config *twjit_config); +void osmo_twrtp_destroy(struct osmo_twrtp *endp); + +int osmo_twrtp_supply_fds(struct osmo_twrtp *endp, int rtp_fd, int rtcp_fd); +int osmo_twrtp_bind_local(struct osmo_twrtp *endp, + const struct osmo_sockaddr *rtp_addr, bool bind_rtcp); +int osmo_twrtp_set_remote(struct osmo_twrtp *endp, + const struct osmo_sockaddr *rtp_addr); + +/* receiving incoming RTP via twjit */ + +void osmo_twrtp_twjit_rx_ctrl(struct osmo_twrtp *endp, bool rx_enable); + +/* output function, to be called by TDM/GSM/etc fixed-timing side */ +struct msgb *osmo_twrtp_twjit_rx_poll(struct osmo_twrtp *endp); + +/* receiving incoming RTP without twjit */ + +/* callback function takes ownership of msgb *if* it returns true */ +typedef bool (*osmo_twrtp_raw_rx_cb)(struct osmo_twrtp *endp, void *user_data, + struct msgb *msg); + +void osmo_twrtp_set_raw_rx_cb(struct osmo_twrtp *endp, osmo_twrtp_raw_rx_cb cb, + void *user_data); + +/* RTP Tx direction */ + +int osmo_twrtp_tx_quantum(struct osmo_twrtp *endp, const uint8_t *payload, + unsigned payload_len, uint8_t payload_type, + bool marker, bool auto_marker, bool send_rtcp); +void osmo_twrtp_tx_skip(struct osmo_twrtp *endp); +void osmo_twrtp_tx_restart(struct osmo_twrtp *endp); + +int osmo_twrtp_tx_forward(struct osmo_twrtp *endp, struct msgb *msg); + +/* support for emitting RTCP SR & RR */ + +int osmo_twrtp_set_sdes(struct osmo_twrtp *endp, const char *cname, + const char *name, const char *email, const char *phone, + const char *loc, const char *tool, const char *note); +void osmo_twrtp_set_auto_rtcp_interval(struct osmo_twrtp *endp, + uint16_t interval); + +int osmo_twrtp_send_rtcp_rr(struct osmo_twrtp *endp); + +/* information retrieval functions */ + +struct osmo_twjit *osmo_twrtp_get_twjit(struct osmo_twrtp *endp); + +const struct osmo_twrtp_stats *osmo_twrtp_get_stats(struct osmo_twrtp *endp); + +/* have we received at least one RTCP RR matching our RTP Tx output? */ +bool osmo_twrtp_got_rtcp_rr(struct osmo_twrtp *endp); + +/* retrieving RTCP RR info: valid only if above function returned true */ +uint32_t osmo_twrtp_rr_lost_word(struct osmo_twrtp *endp); +int32_t osmo_twrtp_rr_lost_cumulative(struct osmo_twrtp *endp); +uint32_t osmo_twrtp_rr_jitter_last(struct osmo_twrtp *endp); +uint32_t osmo_twrtp_rr_jitter_max(struct osmo_twrtp *endp); + +/* socket-related miscellany */ + +int osmo_twrtp_get_rtp_fd(struct osmo_twrtp *endp); +int osmo_twrtp_get_rtcp_fd(struct osmo_twrtp *endp); + +int osmo_twrtp_set_dscp(struct osmo_twrtp *endp, uint8_t dscp); +int osmo_twrtp_set_socket_prio(struct osmo_twrtp *endp, int prio); + +/*! @} */ diff --git a/src/Makefile.am b/src/Makefile.am index 73b497f..b354e53 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -30,6 +30,7 @@ stream_srv.c \ twjit.c \ twjit_conf.c \ + twrtp.c \ $(NULL)
if ENABLE_LIBSCTP diff --git a/src/twrtp.c b/src/twrtp.c new file mode 100644 index 0000000..1a1b7be --- /dev/null +++ b/src/twrtp.c @@ -0,0 +1,1273 @@ +/* + * Themyscira Wireless RTP endpoint implementation: main body. + * + * This code was contributed to Osmocom Cellular Network Infrastructure + * project by Mother Mychaela N. Falconia of Themyscira Wireless. + * Mother Mychaela's contributions are NOT subject to copyright: + * no rights reserved, all rights relinquished. + */ + +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <arpa/inet.h> /* for network byte order functions */ + +#include <osmocom/core/msgb.h> +#include <osmocom/core/osmo_io.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/utils.h> + +#include <osmocom/netif/twrtp.h> +#include <osmocom/netif/twjit.h> +#include <osmocom/netif/rtp.h> +#include <osmocom/netif/rtcp_defs.h> + +/*! \addgroup twrtp + * @{ + */ + +/*! \cond private */ + +/*! This structure captures info that has been extracted from + * received and decoded RTCP SR and/or RR packets. + * See twrtp guide document section 5.2. + */ +struct rtcp_rx_state { + /* sender info extracted from SR packets */ + /*! sender SSRC from the most recently received SR packet */ + uint32_t sr_ssrc; + /*! NTP timestamp from SR: lower 16 bits of seconds word */ + uint16_t sr_ntp_sec; + /*! NTP timestamp from SR: upper 16 bits of fraction word */ + uint16_t sr_ntp_fract; + /*! CLOCK_MONOTONIC time-of-arrival of most recently received SR */ + struct timespec sr_rx_time; + /* info extracted from reception report blocks in SR/RR packets */ + /*! lost packets word from most recently received RR block */ + uint32_t rr_lost_word; + /*! interarrival jitter from most recently received RR block */ + uint32_t rr_jitter; + /*! highest value received in the interarrival jitter word */ + uint32_t rr_jitter_max; + /* bool flags at the end for structure packing optimization */ + /*! received at least one SR packet */ + bool got_sr; + /*! received at least one RR block */ + bool got_rr; +}; + +/*! This structure holds state for emission of RTCP reception report + * blocks based on RR info from twjit. This additional state element + * (beyond struct osmo_twjit_rr_info) is needed in order to compute + * the "fraction lost" field, which is based on the delta since + * the last emitted report and thus requires memory of previous + * "received" and "expected" counts. + */ +struct rtcp_tx_state { + uint32_t last_received; + uint32_t last_expected; +}; + +/*! Main structure for one instance of twrtp */ +struct osmo_twrtp { + /*! UDP socket for RTP data packets */ + struct osmo_io_fd *iofd_rtp; + /*! UDP socket for RTCP */ + struct osmo_io_fd *iofd_rtcp; + /*! remote IP:port address for RTP data packets */ + struct osmo_sockaddr rtp_remote; + /*! remote IP:port address for RTCP */ + struct osmo_sockaddr rtcp_remote; + /*! count of RTP timestamp units per quantum */ + uint32_t ts_quantum; + /* scaling factors for RTP Tx timestamp computation */ + /*! RTP timestamp units per second */ + uint32_t ts_units_per_sec; + /*! divisor to go from nanoseconds to RTP timestamp units */ + uint32_t ns_to_ts_units; + /* RTP Rx path: twjit and raw options */ + /*! twjit instance owned by the present twrtp instance */ + struct osmo_twjit *twjit; + /*! registered callback for unbuffered/non-delayed Rx path */ + osmo_twrtp_raw_rx_cb raw_rx_cb; + /*! user data for \ref raw_rx_cb */ + void *raw_rx_cb_data; + /*! RTP Tx state */ + struct { + /*! random SSRC chosen for RTP output we generate */ + uint32_t ssrc; + /*! output stream current timestamp */ + uint32_t ts; + /*! random addend for RTP timestamps we emit */ + uint32_t ts_addend; + /*! output stream current sequence number */ + uint16_t seq; + /*! we started output with osmo_twrtp_tx_quantum() */ + bool started; + /*! output timestamp needs to be reset discontinuously */ + bool restart; + } tx; + /* RTCP info */ + /*! RTCP Rx state */ + struct rtcp_rx_state rtcp_rx; + /*! RTCP Tx state */ + struct rtcp_tx_state rtcp_tx; + /*! buffer holding SDES packet of RFC 3550 section 6.5 */ + uint8_t *sdes_buf; + /*! length (in bytes) of SDES packet in \ref sdes_buf */ + uint16_t sdes_len; + /*! automatically emit RTCP SR after this many RTP data packets */ + uint16_t auto_rtcp_interval; + /*! current count within \ref auto_rtcp_interval */ + uint16_t auto_rtcp_count; + /*! twrtp-level stats over lifetime of this instance */ + struct osmo_twrtp_stats stats; + /* bool flags at the end for structure packing optimization */ + /*! \ref rtp_remote and \ref rtcp_remote are valid */ + bool remote_set; + /*! received RTP packets are to be fed to our subordinate twjit */ + bool twjit_rx_enable; +}; + +/* We need to know maximum expected sizes of RTP and RTCP Rx packets + * for osmo_io msgb allocation. For RTP, the largest packet size in + * 3GPP and IP-PSTN applications is 176 bytes: 12 bytes of RTP header + * plus 160 bytes of payload for 20 ms of uncompressed G.711 audio + * or CSData. Of course there may be other applications that use + * larger RTP packets, in which case we may have to add an API function + * that overrides our default msgb alloc size setting - but let's + * cross that bridge if and when we actually have such users. + * + * In case of RTCP, we fully process all received packets inside + * the present library, hence we can set osmo_io msgb alloc size + * based on what our RTCP Rx code can parse and make use of. Any + * additional RTCP Rx data, such as very long SDES strings, will + * simply be truncated at osmo_io level - but the subsequent parsing + * code will never get to those bits anyway. + */ + +#define MAX_RTP_RX_PACKET (sizeof(struct rtp_hdr) + 160) +#define MAX_RTCP_RX_PACKET (sizeof(struct rtcp_sr_rr_hdr) + \ + sizeof(struct rtcp_sr_block) + \ + sizeof(struct rtcp_rr_block) * 31) + +/* RTCP includes NTP timestamps, hence we need to convert from Unix-style + * CLOCK_REALTIME into NTP time format. */ + +#define NTP_EPOCH_MJD 15020 +#define UNIX_EPOCH_MJD 40587 + +#define NTP_UNIX_EPOCH_DIFF ((UNIX_EPOCH_MJD-NTP_EPOCH_MJD) * 86400UL) +#define TWO_TO_32_DOUBLE 4294967296.0 + +/* forward declarations for internal functions */ + +static void rtp_rx_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg, + const struct osmo_sockaddr *saddr); +static void rtcp_rx_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg, + const struct osmo_sockaddr *saddr); +static int send_rtcp_sr_rr(struct osmo_twrtp *endp, bool send_sr, + const struct timespec *utc, uint32_t rtp_ts); + +static const struct osmo_io_ops twrtp_iops_rtp = { + .recvfrom_cb = rtp_rx_cb, +}; + +static const struct osmo_io_ops twrtp_iops_rtcp = { + .recvfrom_cb = rtcp_rx_cb, +}; + +/*! \endcond */ + +/*! Create a twrtp endpoint + * + * \param[in] ctx Parent talloc context under which struct osmo_twrtp + * should be allocated. + * \param[in] clock_khz RTP clock rate in kHz, i.e., number of RTP timestamp + * units per millisecond. The most common value is 8. + * \param[in] quantum_ms Duration of a single quantum (unit of speech or data + * carried in one RTP packet) in milliseconds. The most common value is 20. + * \param[in] random_ts_seq For RTP packets we generate and emit, randomize + * not only SSRC for this session, but also the starting timestamp and the + * starting sequence number. Pass true to satisfy the SHOULD directive in + * RFC 3550 and for feature parity with ortp, or false for ease of debugging. + * \param[in] twjit_config If this RTP endpoint is to be equipped with twjit, + * pass twjit config structure with tunable parameters here. If a sans-twjit + * RTP endpoint is to be created, pass NULL here. + * \returns pointer to the newly created twrtp instance, or NULL on errors. + * + * Every twrtp endpoint is always capable of sending and receiving RTP and RTCP + * packets on the IP network, but it may be either twjit-equipped or sans-twjit. + * The decision to have or not have a twjit instance as part of a newly created + * twrtp instance must be made at the time of creation with this function, by + * passing either a valid twjit config structure or NULL as \ref twjit_config. + * + * Parameters \ref clock_khz and \ref quantum_ms are passed through to twjit + * if a twjit instance is created inside the new twrtp instance, but they are + * mandatory even if no twjit instance is to be created. With or without twjit, + * these parameters are used for locally generated RTP packets, i.e., those + * emitted via osmo_twrtp_tx_quantum() as opposed to osmo_twrtp_tx_forward(). + */ +struct osmo_twrtp * +osmo_twrtp_create(void *ctx, uint16_t clock_khz, uint16_t quantum_ms, + bool random_ts_seq, + const struct osmo_twjit_config *twjit_config) +{ + struct osmo_twrtp *endp; + + endp = talloc_zero(ctx, struct osmo_twrtp); + if (!endp) + return NULL; + + if (twjit_config) { + endp->twjit = osmo_twjit_create(endp, clock_khz, quantum_ms, + twjit_config); + if (!endp->twjit) { + talloc_free(endp); + return NULL; + } + } + + endp->ts_quantum = (uint32_t) quantum_ms * clock_khz; + endp->ts_units_per_sec = (uint32_t) clock_khz * 1000; + endp->ns_to_ts_units = 1000000 / clock_khz; + + endp->tx.ssrc = random(); + if (random_ts_seq) { + endp->tx.ts_addend = random(); + endp->tx.seq = random(); + } + + return endp; +} + +/*! Destroy a twrtp endpoint + * + * \param[in] endp Instance (endpoint) to be freed + */ +void osmo_twrtp_destroy(struct osmo_twrtp *endp) +{ + if (!endp) + return; + if (endp->iofd_rtp) + osmo_iofd_free(endp->iofd_rtp); + if (endp->iofd_rtcp) + osmo_iofd_free(endp->iofd_rtcp); + if (endp->twjit) + osmo_twjit_destroy(endp->twjit); + talloc_free(endp); +} + +/*! Equip twrtp endpoint with RTP and RTCP sockets (supplied file descriptors) + * + * \param[in] endp Endpoint to operate on + * \param[in] rtp_fd OS file descriptor for UDP socket for RTP + * \param[in] rtcp_fd OS file descriptor for UDP socket for RTCP + * \returns 0 if successful, negative on errors + * + * This function equips a newly created twrtp endpoint with file descriptors + * for RTP and RTCP sockets. Most applications will use the high-level API + * osmo_twrtp_bind_local() that creates and binds the right type of sockets, + * then calls the present function - however, some applications may call this + * function directly. In Themyscira Wireless CN environment, there is a + * separate daemon process that manages the pool of local UDP ports for + * RTP+RTCP pairs, and that daemon passes allocated sockets to its clients + * via UNIX domain socket file descriptor passing mechanism - hence twrtp layer + * must have a public API that takes in already-bound file descriptor pairs. + * + * This function always "consumes" the two file descriptors that are passed + * to it. If the operation succeeds, each of these fds becomes wrapped in + * an osmo_io_fd subordinate to struct osmo_twrtp, and both will eventually + * be closed upon osmo_twrtp_destroy(). OTOH, if the present function fails, + * it closes both fds before returning its error indication. The latter + * behavior may seem wrong, but it is more convenient for all current users, + * and consistent with twrtp-native and twrtp-proto versions. If we get a + * user application that prefers the other alternative (keeping the fds intact + * on EBUSY or if osmo_iofd_setup() or osmo_iofd_register() operations fail), + * we can create another variant of this API with that alternative behavior. + * + * It is also possible to pass -1 as \ref rtcp_fd - in this case the twrtp + * endpoint will operate with only an RTP socket, and no ability to send + * or receive RTCP. + */ +int osmo_twrtp_supply_fds(struct osmo_twrtp *endp, int rtp_fd, int rtcp_fd) +{ + int rc; + + if (endp->iofd_rtp) { + rc = -EBUSY; + goto close_both_ret; + } + + endp->iofd_rtp = osmo_iofd_setup(endp, -1, NULL, + OSMO_IO_FD_MODE_RECVFROM_SENDTO, + &twrtp_iops_rtp, endp); + if (!endp->iofd_rtp) { + rc = -EIO; + goto close_both_ret; + } + osmo_iofd_set_alloc_info(endp->iofd_rtp, MAX_RTP_RX_PACKET, 0); + rc = osmo_iofd_register(endp->iofd_rtp, rtp_fd); + if (rc < 0) { + osmo_iofd_free(endp->iofd_rtp); + endp->iofd_rtp = NULL; + goto close_both_ret; + } + + if (rtcp_fd >= 0) { + endp->iofd_rtcp = osmo_iofd_setup(endp, -1, NULL, + OSMO_IO_FD_MODE_RECVFROM_SENDTO, + &twrtp_iops_rtcp, endp); + if (!endp->iofd_rtcp) { + osmo_iofd_free(endp->iofd_rtp); + endp->iofd_rtp = NULL; + rc = -EIO; + goto close_rtcp_ret; + } + osmo_iofd_set_alloc_info(endp->iofd_rtcp, MAX_RTCP_RX_PACKET, 0); + rc = osmo_iofd_register(endp->iofd_rtcp, rtcp_fd); + if (rc < 0) { + osmo_iofd_free(endp->iofd_rtp); + osmo_iofd_free(endp->iofd_rtcp); + endp->iofd_rtp = NULL; + endp->iofd_rtcp = NULL; + goto close_rtcp_ret; + } + } + + /* endp->iofd_rtp and possibly endp->iofd_rtcp becoming set + * is how subsequent API functions will know that this step + * has completed successfully, and also prevent future fd + * changes with EBUSY. */ + return 0; + +close_both_ret: + close(rtp_fd); +close_rtcp_ret: + if (rtcp_fd >= 0) + close(rtcp_fd); + return rc; +} + +/*! Equip twrtp endpoint with locally bound RTP and RTCP sockets + * + * \param[in] endp Endpoint to operate on + * \param[in] rtp_addr IP:port address to be bound locally for RTP socket; + * the corresponding address for RTCP (port increment by 1) will be derived + * internally. + * \param[in] bind_rtcp Create and bind sockets for both RTP and RTCP if true, + * or for RTP only if false. + * \returns 0 if successful, negative on errors + * + * This function creates a pair of UDP sockets of the right address family + * (IPv4 or IPv6) for RTP and RTCP, binds them locally and installs them + * in the twrtp endpoint. (Or just one UDP socket for RTP, if \ref bind_rtcp + * is false.) Either the present API or the lower-level alternative + * osmo_twrtp_supply_fds() must be called after osmo_twrtp_create() in order + * for the endpoint to become functional, but neither function can be used + * again once this step is done. + */ +int osmo_twrtp_bind_local(struct osmo_twrtp *endp, + const struct osmo_sockaddr *rtp_addr, bool bind_rtcp) +{ + struct osmo_sockaddr rtcp_addr; + int rtp_fd, rtcp_fd; + + switch (rtp_addr->u.sa.sa_family) { + case AF_INET: + memcpy(&rtcp_addr, rtp_addr, sizeof(struct sockaddr_in)); + rtcp_addr.u.sin.sin_port = + htons(ntohs(rtp_addr->u.sin.sin_port) + 1); + break; + case AF_INET6: + memcpy(&rtcp_addr, rtp_addr, sizeof(struct sockaddr_in6)); + rtcp_addr.u.sin6.sin6_port = + htons(ntohs(rtp_addr->u.sin6.sin6_port) + 1); + break; + default: + return -EAFNOSUPPORT; + } + + rtp_fd = osmo_sock_init_osa(SOCK_DGRAM, IPPROTO_UDP, rtp_addr, NULL, + OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK); + if (rtp_fd < 0) + return rtp_fd; + + if (bind_rtcp) { + rtcp_fd = osmo_sock_init_osa(SOCK_DGRAM, IPPROTO_UDP, + &rtcp_addr, NULL, + OSMO_SOCK_F_BIND | + OSMO_SOCK_F_NONBLOCK); + if (rtcp_fd < 0) { + close(rtp_fd); + return rtcp_fd; + } + } else { + rtcp_fd = -1; + } + + return osmo_twrtp_supply_fds(endp, rtp_fd, rtcp_fd); +} + +static void set_remote_ipv4(struct osmo_twrtp *endp, + const struct sockaddr_in *user_addr) +{ + memcpy(&endp->rtp_remote.u.sin, user_addr, sizeof(struct sockaddr_in)); + memcpy(&endp->rtcp_remote.u.sin, user_addr, sizeof(struct sockaddr_in)); + endp->rtcp_remote.u.sin.sin_port = + htons(ntohs(user_addr->sin_port) + 1); +} + +static void set_remote_ipv6(struct osmo_twrtp *endp, + const struct sockaddr_in6 *user_addr) +{ + memcpy(&endp->rtp_remote.u.sin6, user_addr, + sizeof(struct sockaddr_in6)); + memcpy(&endp->rtcp_remote.u.sin6, user_addr, + sizeof(struct sockaddr_in6)); + endp->rtcp_remote.u.sin6.sin6_port = + htons(ntohs(user_addr->sin6_port) + 1); +} + +/*! Set RTP remote address + * + * \param[in] endp Endpoint to operate on + * \param[in] rtp_addr IP:port address to be set as the remote for RTP socket; + * the corresponding address for RTCP (port increment by 1) will be derived + * internally. + * \returns 0 if successful, negative on errors + * + * This function needs to be called at some point in order for the endpoint + * to become functional, but unlike osmo_twrtp_bind_local(), it can be called + * again to change the remote address as needed. + */ +int osmo_twrtp_set_remote(struct osmo_twrtp *endp, + const struct osmo_sockaddr *rtp_addr) +{ + switch (rtp_addr->u.sa.sa_family) { + case AF_INET: + set_remote_ipv4(endp, &rtp_addr->u.sin); + break; + case AF_INET6: + set_remote_ipv6(endp, &rtp_addr->u.sin6); + break; + default: + return -EAFNOSUPPORT; + } + + endp->remote_set = true; + return 0; +} + +/* RTP Rx path via osmo_io callback */ + +static void rtp_rx_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg, + const struct osmo_sockaddr *saddr) +{ + struct osmo_twrtp *endp = osmo_iofd_get_data(iofd); + + if (!msg) + return; + if (!endp->remote_set) { + msgb_free(msg); + return; + } + if (osmo_sockaddr_cmp(saddr, &endp->rtp_remote)) { + endp->stats.rx_rtp_badsrc++; + msgb_free(msg); + return; + } + endp->stats.rx_rtp_pkt++; + if (endp->raw_rx_cb) { + bool swallow; + + swallow = endp->raw_rx_cb(endp, endp->raw_rx_cb_data, msg); + if (swallow) /* did the cb consume the msgb we fed it? */ + return; + } + if (endp->twjit_rx_enable) + osmo_twjit_input(endp->twjit, msg); + else + msgb_free(msg); +} + +/* Rx-related API functions */ + +/*! Enable or disable Rx via twjit + * + * \param[in] endp Endpoint to operate on + * \param[in] rx_enable Self-explanatory Boolean flag + * + * This API is valid only for twrtp endpoints that were equipped with twjit + * at the time of creation. + */ +void osmo_twrtp_twjit_rx_ctrl(struct osmo_twrtp *endp, bool rx_enable) +{ + OSMO_ASSERT(endp->twjit); + endp->twjit_rx_enable = rx_enable; + if (!rx_enable) + osmo_twjit_reset(endp->twjit); +} + +/*! Fixed-timing output poll from the twrtp endpoint's twjit buffer + * + * \param[in] endp Endpoint to poll + * \returns pointer to msgb holding a previously received RTP packet that + * was successfully mapped to the present quantum in the fixed-timing output, + * or NULL if no such packet is available. + * + * This API is valid only for twrtp endpoints that were equipped with twjit + * at the time of creation. + */ +struct msgb *osmo_twrtp_twjit_rx_poll(struct osmo_twrtp *endp) +{ + return osmo_twjit_output(endp->twjit); +} + +/*! Set callback function for unbuffered/non-delayed Rx path + * + * \param[in] endp Endpoint to operate on + * \param[in] cb The callback function, or NULL to cancel this callback + * mechanism. + * \param[in] user_data Opaque user data for \ref cb function + * + * The callback function set with this API will be called from osmo_io Rx + * callback path whenever an RTP packet is received. If the callback function + * consumes (takes ownership of) the msgb passed to it, it must return true, + * otherwise it must return false. If the callback function returns true, + * osmo_io Rx processing ends there; if it returns false, twrtp's regular + * osmo_io Rx callback path passes the msgb to twjit if this endpoint is + * equipped with such and twjit Rx is enabled, or frees the msgb otherwise. + * + * It is possible to use twjit and this unbuffered/non-delayed Rx path + * at the same time. Consider a speech transcoder that supports AMR codec + * on the RAN side: such TC will use twjit to feed the incoming RTP stream + * to the speech decoder function that runs on fixed timing, but the + * non-delayed Rx path can also be used to "peek" at received RTP packets + * as they come in and extract the CMR field - to be fed to the speech + * encoder element, which is separate from the speech decoder fed via twjit. + */ +void osmo_twrtp_set_raw_rx_cb(struct osmo_twrtp *endp, osmo_twrtp_raw_rx_cb cb, + void *user_data) +{ + endp->raw_rx_cb = cb; + endp->raw_rx_cb_data = user_data; +} + +/* RTP Tx path */ + +static uint32_t gen_timestamp(const struct timespec *now, + const struct osmo_twrtp *endp) +{ + uint32_t ts; + + ts = now->tv_sec * endp->ts_units_per_sec + + now->tv_nsec / endp->ns_to_ts_units; + ts += endp->tx.ts_addend; + return ts; +} + +/*! Emit RTP packet carrying a locally sourced quantum of speech/data + * + * \param[in] endp Endpoint to operate on + * \param[in] payload The payload to emit in RTP, can be NULL iff + * \ref payload_len == 0. + * \param[in] payload_len The length of \ref payload in bytes. + * \param[in] payload_type The payload type number to be emitted in the + * generated RTP packet. + * \param[in] marker Value of the M bit to be emitted. + * \param[in] auto_marker Automatically set the M bit if the packet we are + * emitting is our very first or follows osmo_twrtp_tx_restart(). + * \param[in] send_rtcp Emit RTCP SR along with this RTP data packet. + * \returns 0 if successful, negative on errors. + * + * The design of the library assumes that RTP payloads sent out via this API + * originate from a fixed timing system such as GSM Um TCH, T1/E1 TDM or a + * software application driven by a CLOCK_MONOTONIC timerfd, such that once + * the application calls the present function, subsequent calls to the same + * will follow every 20 ms (or whatever other quantum duration is set at the + * time of osmo_twrtp_create()) without fail. + * + * The M bit will be set in the generated RTP packet if \ref marker argument + * is true OR if \ref auto_marker is true and the conditions for automatic + * marker setting are met. + * + * RTCP SR packets are emitted by the endpoint only as a result of this + * function being called, and not along any other path. An RTCP SR will be + * emitted if \ref send_rtcp argument is true OR if automatic RTCP SR + * generation was enabled with osmo_twrtp_set_auto_rtcp_interval() and + * it is time to emit RTCP SR per the count of emitted RTP data packets. + */ +int osmo_twrtp_tx_quantum(struct osmo_twrtp *endp, const uint8_t *payload, + unsigned payload_len, uint8_t payload_type, + bool marker, bool auto_marker, bool send_rtcp) +{ + struct msgb *msg; + struct timespec now; + uint32_t restart_ts; + int32_t ts_delta; + struct rtp_hdr *rtph; + uint8_t *pl_out; + int rc; + + if (!endp->iofd_rtp || !endp->remote_set) + return -EINVAL; + msg = msgb_alloc_c(endp, sizeof(struct rtp_hdr) + payload_len, + "ThemWi-RTP-Tx"); + if (!msg) { + osmo_twrtp_tx_skip(endp); + return -ENOMEM; + } + + /* timestamp generation is where we do some trickery */ + osmo_clock_gettime(CLOCK_REALTIME, &now); + if (!endp->tx.started) { + endp->tx.ts = gen_timestamp(&now, endp); + endp->tx.started = true; + endp->tx.restart = false; + if (auto_marker) + marker = true; + } else if (endp->tx.restart) { + restart_ts = gen_timestamp(&now, endp); + ts_delta = (int32_t)(restart_ts - endp->tx.ts); + if (ts_delta <= 0) { + /* shouldn't happen, unless something funky w/clock */ + endp->tx.ts++; + } else { + if (ts_delta % endp->ts_quantum == 0) + restart_ts++; + endp->tx.ts = restart_ts; + } + endp->tx.restart = false; + if (auto_marker) + marker = true; + } + + rtph = (struct rtp_hdr *) msgb_put(msg, sizeof(struct rtp_hdr)); + rtph->version = RTP_VERSION; + rtph->padding = 0; + rtph->extension = 0; + rtph->csrc_count = 0; + rtph->marker = marker; + rtph->payload_type = payload_type; + rtph->sequence = htons(endp->tx.seq); + rtph->timestamp = htonl(endp->tx.ts); + rtph->ssrc = htonl(endp->tx.ssrc); + pl_out = msgb_put(msg, payload_len); + memcpy(pl_out, payload, payload_len); + endp->tx.seq++; + endp->tx.ts += endp->ts_quantum; + + rc = osmo_iofd_sendto_msgb(endp->iofd_rtp, msg, 0, &endp->rtp_remote); + if (rc < 0) { + msgb_free(msg); + return rc; + } + endp->stats.tx_rtp_pkt++; + endp->stats.tx_rtp_bytes += payload_len; + + if (endp->auto_rtcp_interval) { + endp->auto_rtcp_count++; + if (endp->auto_rtcp_count >= endp->auto_rtcp_interval) { + endp->auto_rtcp_count = 0; + send_rtcp = true; + } + } + if (send_rtcp) { + send_rtcp_sr_rr(endp, true, &now, + endp->tx.ts - endp->ts_quantum); + } + + return 0; +} + +/*! Incur an intentional gap in the emitted RTP stream + * + * \param[in] endp Endpoint to operate on + * + * Many RTP profiles call for behavior where a stream sender incurs an + * intentional gap in its output (does not emit the otherwise-expected + * RTP packet for a given timestamp in the expected cadence of timestamp + * quantum increments) if the corresponding quantum carries speech silence, + * or if the data source has errors. Such operation is non-native to + * twrtp and generally recommended against (it creates an adverse condition + * for twjit on the receiving end), but the library provides mechanism + * rather than policy, hence the ability to incur intentional gaps is + * supported. This function advances the output timestamp by one quantum, + * thereby creating the requested intentional gap. + */ +void osmo_twrtp_tx_skip(struct osmo_twrtp *endp) +{ + if (!endp->tx.started || endp->tx.restart) + return; + endp->tx.ts += endp->ts_quantum; +} + +/*! Reset output stream cadence + * + * \param[in] endp Endpoint to operate on + * + * This function needs to be called if the application wishes to restart + * or resume output after it previously stopped calling osmo_twrtp_tx_quantum() + * or osmo_twrtp_tx_skip() every 20 ms, and it should also be called if + * the cadence of quantum-sized timestamp increments needs to be broken + * for some reason. + */ +void osmo_twrtp_tx_restart(struct osmo_twrtp *endp) +{ + endp->tx.restart = true; +} + +/*! Forward RTP packet between endpoints + * + * \param[in] endp Endpoint on which the packet should be sent out + * \param[in] msg RTP packet received from another endpoint + * \returns 0 if successful, negative on errors + * + * If an application needs to forward RTP packets from one endpoint to another + * without buffering delay, it should call the present function from the + * callback registered on the other endpoint with osmo_twrtp_set_raw_rx_cb(). + * + * This function always consumes the msgb passed to it - if the sending + * operation fails, the msgb is freed here. + */ +int osmo_twrtp_tx_forward(struct osmo_twrtp *endp, struct msgb *msg) +{ + int rc; + + if (!endp->iofd_rtp || !endp->remote_set) { + msgb_free(msg); + return -EINVAL; + } + rc = osmo_iofd_sendto_msgb(endp->iofd_rtp, msg, 0, &endp->rtp_remote); + if (rc < 0) + msgb_free(msg); + return rc; +} + +/* RTCP Rx path via osmo_io callback */ + +static void parse_rtcp(struct osmo_twrtp *endp, struct msgb *msg) +{ + struct rtcp_rx_state *rxs = &endp->rtcp_rx; + struct rtcp_sr_rr_hdr *base_hdr; + struct rtcp_sr_block *sr; + struct rtcp_rr_block *rr; + unsigned rc, i; + + if (msg->len < sizeof(struct rtcp_sr_rr_hdr)) { +invalid: endp->stats.rx_rtcp_invalid++; + return; + } + base_hdr = (struct rtcp_sr_rr_hdr *) msg->data; + msgb_pull(msg, sizeof(struct rtcp_sr_rr_hdr)); + if ((base_hdr->v_p_rc & 0xC0) != 0x80) + goto invalid; + switch (base_hdr->pt) { + case RTCP_PT_SR: + if (msg->len < sizeof(struct rtcp_sr_block)) + goto invalid; + sr = (struct rtcp_sr_block *) msg->data; + msgb_pull(msg, sizeof(struct rtcp_sr_block)); + rxs->got_sr = true; + osmo_clock_gettime(CLOCK_MONOTONIC, &rxs->sr_rx_time); + rxs->sr_ssrc = ntohl(base_hdr->ssrc); + rxs->sr_ntp_sec = ntohl(sr->ntp_sec); + rxs->sr_ntp_fract = ntohl(sr->ntp_fract) >> 16; + break; + case RTCP_PT_RR: + break; + default: + goto invalid; + } + rc = base_hdr->v_p_rc & 0x1F; + if (msg->len < sizeof(struct rtcp_rr_block) * rc) + goto invalid; + for (i = 0; i < rc; i++) { + rr = (struct rtcp_rr_block *) msg->data; + msgb_pull(msg, sizeof(struct rtcp_rr_block)); + if (ntohl(rr->ssrc) != endp->tx.ssrc) { + endp->stats.rx_rtcp_wrong_ssrc++; + continue; + } + rxs->got_rr = true; + rxs->rr_lost_word = ntohl(rr->lost_word); + rxs->rr_jitter = ntohl(rr->jitter); + if (rxs->rr_jitter > rxs->rr_jitter_max) + rxs->rr_jitter_max = rxs->rr_jitter; + } +} + +static void rtcp_rx_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg, + const struct osmo_sockaddr *saddr) +{ + struct osmo_twrtp *endp = osmo_iofd_get_data(iofd); + + if (!msg) + return; + if (!endp->remote_set) { + msgb_free(msg); + return; + } + if (osmo_sockaddr_cmp(saddr, &endp->rtcp_remote)) { + endp->stats.rx_rtcp_badsrc++; + msgb_free(msg); + return; + } + endp->stats.rx_rtcp_pkt++; + parse_rtcp(endp, msg); + msgb_free(msg); +} + +/* RTCP Tx functions */ + +static void fill_rr_block(struct osmo_twrtp *endp, struct rtcp_rr_block *rr) +{ + struct osmo_twjit *twjit = endp->twjit; + const struct osmo_twjit_rr_info *rri = osmo_twjit_get_rr_info(twjit); + const struct rtcp_rx_state *rxs = &endp->rtcp_rx; + struct rtcp_tx_state *txs = &endp->rtcp_tx; + uint32_t delta_expect, delta_rcvd; + int32_t cumulative_lost, newly_lost; + uint32_t lost_fract, lost_word; + struct timespec now, time_delta; + + cumulative_lost = (int32_t)(rri->expected_pkt - rri->rx_packets); + if (cumulative_lost > 0x7FFFFF) + cumulative_lost = 0x7FFFFF; + else if (cumulative_lost < -0x800000) + cumulative_lost = -0x800000; + delta_expect = rri->expected_pkt - txs->last_expected; + txs->last_expected = rri->expected_pkt; + delta_rcvd = rri->rx_packets - txs->last_received; + txs->last_received = rri->rx_packets; + newly_lost = (int32_t)(delta_expect - delta_rcvd); + if (delta_expect == 0 || newly_lost <= 0) + lost_fract = 0; + else + lost_fract = (newly_lost << 8) / delta_expect; + lost_word = (lost_fract << 8) | (cumulative_lost & 0xFFFFFF); + + rr->ssrc = htonl(rri->ssrc); + rr->lost_word = htonl(lost_word); + rr->max_seq_ext = htonl(rri->max_seq_ext); + rr->jitter = htonl(rri->jitter_accum >> 4); + + if (rxs->got_sr && rxs->sr_ssrc == rri->ssrc) { + osmo_clock_gettime(CLOCK_MONOTONIC, &now); + time_delta.tv_sec = now.tv_sec - rxs->sr_rx_time.tv_sec; + time_delta.tv_nsec = now.tv_nsec - rxs->sr_rx_time.tv_nsec; + if (time_delta.tv_nsec < 0) { + time_delta.tv_sec--; + time_delta.tv_nsec += 1000000000; + } + rr->lsr_sec = htons(rxs->sr_ntp_sec); + rr->lsr_fract = htons(rxs->sr_ntp_fract); + rr->dlsr_sec = htons(time_delta.tv_sec); + rr->dlsr_fract = htons(time_delta.tv_nsec / 1000000000.0f * + 65536.0f); + } else { + rr->lsr_sec = 0; + rr->lsr_fract = 0; + rr->dlsr_sec = 0; + rr->dlsr_fract = 0; + } +} + +static int send_rtcp_sr_rr(struct osmo_twrtp *endp, bool send_sr, + const struct timespec *utc, uint32_t rtp_ts) +{ + bool send_rr = false; + struct msgb *msg; + struct rtcp_sr_rr_hdr *hdr; + struct rtcp_sr_block *sr; + struct rtcp_rr_block *rr; + uint8_t *sdes_out; + int rc; + + if (!endp->iofd_rtcp || !endp->remote_set || !endp->sdes_buf) + return -EINVAL; + if (endp->twjit && osmo_twjit_rr_info_valid(endp->twjit)) + send_rr = true; + if (!send_sr && !send_rr) + return -ENODATA; /* nothing to send, neither SR nor RR */ + msg = msgb_alloc_c(endp, sizeof(struct rtcp_sr_rr_hdr) + + sizeof(struct rtcp_sr_block) + + sizeof(struct rtcp_rr_block) + endp->sdes_len, + "ThemWi-RTCP-Tx"); + if (!msg) + return -ENOMEM; + + hdr = (struct rtcp_sr_rr_hdr *) + msgb_put(msg, sizeof(struct rtcp_sr_rr_hdr)); + hdr->v_p_rc = send_rr ? 0x81 : 0x80; + if (send_sr) { + hdr->pt = RTCP_PT_SR; + hdr->len = htons(send_rr ? 12 : 6); + } else { + hdr->pt = RTCP_PT_RR; + hdr->len = htons(7); + } + hdr->ssrc = htonl(endp->tx.ssrc); + if (send_sr) { + sr = (struct rtcp_sr_block *) + msgb_put(msg, sizeof(struct rtcp_sr_block)); + sr->ntp_sec = htonl(utc->tv_sec + NTP_UNIX_EPOCH_DIFF); + sr->ntp_fract = htonl(utc->tv_nsec / 1000000000.0 * + TWO_TO_32_DOUBLE); + sr->rtp_ts = htonl(rtp_ts); + sr->pkt_count = htonl(endp->stats.tx_rtp_pkt); + sr->octet_count = htonl(endp->stats.tx_rtp_bytes); + } + if (send_rr) { + rr = (struct rtcp_rr_block *) + msgb_put(msg, sizeof(struct rtcp_rr_block)); + fill_rr_block(endp, rr); + } + sdes_out = msgb_put(msg, endp->sdes_len); + memcpy(sdes_out, endp->sdes_buf, endp->sdes_len); + + rc = osmo_iofd_sendto_msgb(endp->iofd_rtcp, msg, 0, &endp->rtcp_remote); + if (rc < 0) { + msgb_free(msg); + return rc; + } + endp->stats.tx_rtcp_pkt++; + return 0; +} + +/*! Emit RTCP RR packet + * + * \param[in] endp Endpoint to operate on + * \returns 0 if successful, negative on errors + * + * This function is safe to call on any twrtp endpoint, but it will actually + * result in an RTCP RR packet being emitted only if (1) the twrtp endpoint + * is equipped with twjit and (2) some RTP data packets have been successfully + * received and header-decoded. If these conditions aren't met, no RTCP + * packet will be emitted and the function will return -ENODATA. + * + * This API is rarely needed: in most RTCP-enabled RTP applications, + * it is more useful to enable automatic SR generation with + * osmo_twrtp_set_auto_rtcp_interval(), in which case the library will emit + * RTCP SR packets that also include the same reception report block as + * those standalone RR packets that are emitted by the present function. + * However, the present API is provided in case an application receives + * RTP traffic via twrtp+twjit, but does not emit any RTP traffic of its + * own - in this case only RTCP RR can be generated, not SR. + */ +int osmo_twrtp_send_rtcp_rr(struct osmo_twrtp *endp) +{ + return send_rtcp_sr_rr(endp, false, NULL, 0); +} + +/*! Configure automatic emission of periodic RTCP SR packets + * + * \param[in] endp Endpoint to operate on + * \param[in] interval Automatically emit RTCP SR after this many RTP data + * packets, or 0 to turn off this mechanism. + */ +void osmo_twrtp_set_auto_rtcp_interval(struct osmo_twrtp *endp, + uint16_t interval) +{ + endp->auto_rtcp_interval = interval; +} + +/*! Set SDES strings for RTCP SR and RR packet generation + * + * \param[in] endp Endpoint to operate on + * \param[in] cname Per RFC 3550 section 6.5.1 + * \param[in] name Per RFC 3550 section 6.5.2 + * \param[in] email Per RFC 3550 section 6.5.3 + * \param[in] phone Per RFC 3550 section 6.5.4 + * \param[in] loc Per RFC 3550 section 6.5.5 + * \param[in] tool Per RFC 3550 section 6.5.6 + * \param[in] note Per RFC 3550 section 6.5.7 + * \returns 0 if successful, negative on errors + * + * RFC 3550 section 6.1 stipulates that every RTCP SR or RR packet also + * needs to include an SDES block, containing at least a CNAME string. + * The present function sets the full complement of SDES strings: the + * mandatory CNAME string and 6 optional ones per RFC 3550. This function + * must be called successfully before any RTCP SR or RR packets will be + * emitted. + */ +int osmo_twrtp_set_sdes(struct osmo_twrtp *endp, const char *cname, + const char *name, const char *email, const char *phone, + const char *loc, const char *tool, const char *note) +{ + uint16_t len_str, len_padded, len_with_hdr, len; + struct rtcp_sr_rr_hdr *hdr; + uint8_t *dp; + + if (!cname) + return -EINVAL; + len_str = strlen(cname) + 2; + if (name) + len_str += strlen(name) + 2; + if (email) + len_str += strlen(email) + 2; + if (phone) + len_str += strlen(phone) + 2; + if (loc) + len_str += strlen(loc) + 2; + if (tool) + len_str += strlen(tool) + 2; + if (note) + len_str += strlen(note) + 2; + len_padded = (len_str + 4) & ~3; + len_with_hdr = len_padded + sizeof(struct rtcp_sr_rr_hdr); + + if (endp->sdes_buf) + talloc_free(endp->sdes_buf); + endp->sdes_buf = talloc_size(endp, len_with_hdr); + if (!endp->sdes_buf) + return -ENOMEM; + + hdr = (struct rtcp_sr_rr_hdr *) endp->sdes_buf; + hdr->v_p_rc = 0x81; + hdr->pt = RTCP_PT_SDES; + hdr->len = htons(len_with_hdr / 4 - 1); + hdr->ssrc = htonl(endp->tx.ssrc); + dp = endp->sdes_buf + sizeof(struct rtcp_sr_rr_hdr); + *dp++ = SDES_ITEM_CNAME; + *dp++ = len = strlen(cname); + memcpy(dp, cname, len); + dp += len; + if (name) { + *dp++ = SDES_ITEM_NAME; + *dp++ = len = strlen(name); + memcpy(dp, name, len); + dp += len; + } + if (email) { + *dp++ = SDES_ITEM_EMAIL; + *dp++ = len = strlen(email); + memcpy(dp, email, len); + dp += len; + } + if (phone) { + *dp++ = SDES_ITEM_PHONE; + *dp++ = len = strlen(phone); + memcpy(dp, phone, len); + dp += len; + } + if (loc) { + *dp++ = SDES_ITEM_LOC; + *dp++ = len = strlen(loc); + memcpy(dp, loc, len); + dp += len; + } + if (tool) { + *dp++ = SDES_ITEM_TOOL; + *dp++ = len = strlen(tool); + memcpy(dp, tool, len); + dp += len; + } + if (note) { + *dp++ = SDES_ITEM_NOTE; + *dp++ = len = strlen(note); + memcpy(dp, note, len); + dp += len; + } + memset(dp, 0, len_padded - len_str); + + endp->sdes_len = len_with_hdr; + return 0; +} + +/* retrieving info from reception reports we got from the peer */ + +/*! Have we received any RTCP RR? + * + * \param[in] endp Endpoint to query + * \returns true if at least one reception report block has been received + * whose SSRC matches that of our locally generated RTP output, + * false otherwise. + */ +bool osmo_twrtp_got_rtcp_rr(struct osmo_twrtp *endp) +{ + return endp->rtcp_rx.got_rr; +} + +/*! Info from received RTCP RR: lost packets word + * + * \param[in] endp Endpoint to query + * \returns lost packets word from the most recently received RR block. + * + * This API returns the 32-bit word from received RTCP RR in its raw form, + * exactly as it appears in RFC 3550 section 6.4.1: fraction lost in the + * upper 8 bits, cumulative number of packets lost in the lower 24 bits. + * + * If no RTCP RR has been received, this function returns 0. + */ +uint32_t osmo_twrtp_rr_lost_word(struct osmo_twrtp *endp) +{ + return endp->rtcp_rx.rr_lost_word; +} + +/*! Info from received RTCP RR: cumulative number of packets lost + * + * \param[in] endp Endpoint to query + * \returns "cumulative number of packets lost" value from the most recently + * received RR block, extracted and converted to a proper signed type. This + * number can be negative because of the way it is defined in RFC 3550. + * + * If no RTCP RR has been received, this function returns 0. + */ +int32_t osmo_twrtp_rr_lost_cumulative(struct osmo_twrtp *endp) +{ + int32_t lost_count; + + lost_count = endp->rtcp_rx.rr_lost_word & 0xFFFFFF; + if (lost_count & 0x800000) + lost_count |= 0xFF000000; + return lost_count; +} + +/*! Info from received RTCP RR: interarrival jitter, most recent + * + * \param[in] endp Endpoint to query + * \returns "interarrival jitter" value from the most recently received + * RR block. + */ +uint32_t osmo_twrtp_rr_jitter_last(struct osmo_twrtp *endp) +{ + return endp->rtcp_rx.rr_jitter; +} + +/*! Info from received RTCP RR: interarrival jitter, highest received + * + * \param[in] endp Endpoint to query + * \returns "interarrival jitter" value from received RR blocks, the highest + * value that was received in this session. + */ +uint32_t osmo_twrtp_rr_jitter_max(struct osmo_twrtp *endp) +{ + return endp->rtcp_rx.rr_jitter_max; +} + +/* misc functions */ + +/*! Get twjit from twrtp + * + * \param[in] endp Endpoint to query + * \returns pointer to twjit instance owned by this twrtp endpoint, + * or NULL if this twrtp endpoint has no associated twjit. + * + * The twjit instance made accessible via this function is still owned + * by the parent twrtp instance - calling osmo_twjit_destroy() on it + * would cause a crash the next time twrtp tries to use it! Safe twjit + * APIs are as follows, for dynamic reconfiguration and information + * retrieval: + * + * osmo_twjit_set_config() + * osmo_twjit_get_stats() + * osmo_twjit_get_rr_info() + * osmo_twjit_rr_info_valid() + */ +struct osmo_twjit *osmo_twrtp_get_twjit(struct osmo_twrtp *endp) +{ + return endp->twjit; +} + +/*! Retrieve lifetime stats from twrtp instance + * + * \param[in] endp Endpoint to query + * \returns pointer to lifetime stats structure + * + * Note that a twrtp endpoint equipped with twjit has two levels of stats: + * there are stats at twrtp level and at twjit level. The present function + * retrieves twrtp stats; to get twjit stats, call osmo_twrtp_get_twjit() + * followed by osmo_twjit_get_stats(). + */ +const struct osmo_twrtp_stats *osmo_twrtp_get_stats(struct osmo_twrtp *endp) +{ + return &endp->stats; +} + +/*! Retrieve file descriptor for RTP UDP socket + * + * \param[in] endp Endpoint to query + * \returns OS-level file descriptor of the UDP socket used for RTP. + * + * The file descriptor made accessible via this function is still owned + * by the parent twrtp instance - closing it, or otherwise manipulating it + * too heavily (e.g., doing kernel-level connect on it) will break the library. + */ +int osmo_twrtp_get_rtp_fd(struct osmo_twrtp *endp) +{ + if (!endp->iofd_rtp) + return -1; + return osmo_iofd_get_fd(endp->iofd_rtp); +} + +/*! Retrieve file descriptor for RTCP UDP socket + * + * \param[in] endp Endpoint to query + * \returns OS-level file descriptor of the UDP socket used for RTCP. + * + * The file descriptor made accessible via this function is still owned + * by the parent twrtp instance - closing it, or otherwise manipulating it + * too heavily (e.g., doing kernel-level connect on it) will break the library. + */ +int osmo_twrtp_get_rtcp_fd(struct osmo_twrtp *endp) +{ + if (!endp->iofd_rtcp) + return -1; + return osmo_iofd_get_fd(endp->iofd_rtcp); +} + +/*! Set DSCP (Differentiated Services Code Point) for emitted RTP and RTCP + * packets + * + * \param[in] endp Endpoint to operate on + * \param[in] dscp DSCP value + * \returns 0 if successful, negative on errors + * + * This function exists for feature parity with osmo_ortp: when migrating + * from osmo_ortp to osmo_twrtp, use this function in the place of + * osmo_rtp_socket_set_dscp(). + */ +int osmo_twrtp_set_dscp(struct osmo_twrtp *endp, uint8_t dscp) +{ + int rc; + + OSMO_ASSERT(endp->iofd_rtp); + rc = osmo_sock_set_dscp(osmo_iofd_get_fd(endp->iofd_rtp), dscp); + if (rc < 0) + return rc; + if (!endp->iofd_rtcp) + return 0; + return osmo_sock_set_dscp(osmo_iofd_get_fd(endp->iofd_rtcp), dscp); +} + +/*! Set socket priority for emitted RTP and RTCP packets + * + * \param[in] endp Endpoint to operate on + * \param[in] prio Socket priority + * \returns 0 if successful, negative on errors + * + * This function exists for feature parity with osmo_ortp: when migrating + * from osmo_ortp to osmo_twrtp, use this function in the place of + * osmo_rtp_socket_set_priority(). + */ +int osmo_twrtp_set_socket_prio(struct osmo_twrtp *endp, int prio) +{ + int rc; + + OSMO_ASSERT(endp->iofd_rtp); + rc = osmo_sock_set_priority(osmo_iofd_get_fd(endp->iofd_rtp), prio); + if (rc < 0) + return rc; + if (!endp->iofd_rtcp) + return 0; + return osmo_sock_set_priority(osmo_iofd_get_fd(endp->iofd_rtcp), prio); +} + +/*! @} */