falconia submitted this change.
bring twjit into libosmo-netif
twjit is the jitter buffer portion 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: Ia3be5834571ca18b68939abbcf1ce3a879156658
---
M configure.ac
M contrib/libosmo-netif.spec.in
M include/osmocom/netif/Makefile.am
A include/osmocom/netif/twjit.h
A include/osmocom/netif/twjit_private.h
M src/Makefile.am
A src/twjit.c
A src/twjit_conf.c
8 files changed, 1,318 insertions(+), 2 deletions(-)
diff --git a/configure.ac b/configure.ac
index 454bd13..6455319 100644
--- a/configure.ac
+++ b/configure.ac
@@ -92,6 +92,7 @@
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.11.0)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.11.0)
PKG_CHECK_MODULES(LIBOSMOCODEC, libosmocodec >= 1.11.0)
+PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.11.0)
AC_ARG_ENABLE([libsctp], [AS_HELP_STRING([--disable-libsctp], [Do not enable socket multiaddr APIs requiring libsctp])],
[ENABLE_LIBSCTP=$enableval], [ENABLE_LIBSCTP="yes"])
diff --git a/contrib/libosmo-netif.spec.in b/contrib/libosmo-netif.spec.in
index 2c1385b..5b182ff 100644
--- a/contrib/libosmo-netif.spec.in
+++ b/contrib/libosmo-netif.spec.in
@@ -27,6 +27,7 @@
BuildRequires: pkgconfig(libosmocore) >= 1.11.0
BuildRequires: pkgconfig(libosmogsm) >= 1.11.0
BuildRequires: pkgconfig(libosmocodec) >= 1.11.0
+BuildRequires: pkgconfig(libosmovty) >= 1.11.0
%description
Network interface demuxer library for OsmoCom projects.
diff --git a/include/osmocom/netif/Makefile.am b/include/osmocom/netif/Makefile.am
index 3771859..19678dc 100644
--- a/include/osmocom/netif/Makefile.am
+++ b/include/osmocom/netif/Makefile.am
@@ -1,5 +1,6 @@
noinst_HEADERS = \
stream_private.h \
+ twjit_private.h \
$(NULL)
version.h: version.h.tpl
@@ -26,6 +27,7 @@
rs232.h \
rtp.h \
stream.h \
+ twjit.h \
version.h \
$(NULL)
diff --git a/include/osmocom/netif/twjit.h b/include/osmocom/netif/twjit.h
new file mode 100644
index 0000000..a12420f
--- /dev/null
+++ b/include/osmocom/netif/twjit.h
@@ -0,0 +1,196 @@
+/*
+ * Themyscira Wireless RTP jitter buffer 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>
+
+/*! \defgroup twjit Themyscira Wireless RTP jitter buffer implementation
+ * @{
+ *
+ * The present twjit layer is an interface mechanism from an incoming
+ * RTP stream to an output application that has fixed timing requirements,
+ * e.g., the Tx side of GSM Um TCH or a T1/E1 TDM interface.
+ *
+ * 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 the present jitter buffer facility, its domain of application
+ * and how to use it. 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 twjit in the present version exists as struct osmo_twjit.
+ * This structure is opaque, and always constitutes a talloc context. */
+struct osmo_twjit;
+
+/*! A config structure is also needed, containing tunable settings
+ * that are usually managed via vty. This config structure is separate
+ * from individual twjit instances because there will be many more
+ * twjit instances than config instances: there will be a separate
+ * twjit instance for every call or other RTP stream handled by the
+ * application, but only one twjit config structure in the app's
+ * vty config tree, or a few such config instances as appropriate
+ * per app design.
+ *
+ * This config structure is likewise opaque and also constitutes a talloc
+ * context. */
+struct osmo_twjit_config;
+
+/*! Stats collected during the lifetime of a twjit instance.
+ * For a detailed description of each of these counters, see Chapter 3
+ * 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_twjit, while applications are given
+ * const pointers to these structs.
+ */
+struct osmo_twjit_stats {
+ /* For ABI reasons, none of the following fields may be deleted
+ * or reordered! */
+
+ /* normal operation */
+ uint32_t rx_packets;
+ uint32_t delivered_pkt;
+ uint32_t handovers_in;
+ uint32_t handovers_out;
+ uint32_t marker_resets;
+ /* undesirable, but not totally unexpected */
+ uint32_t too_old;
+ uint32_t underruns;
+ uint32_t ho_underruns;
+ uint32_t output_gaps;
+ uint32_t thinning_drops;
+ /* unusual error events */
+ uint32_t bad_packets;
+ uint32_t duplicate_ts;
+ /* independent analysis of Rx packet stream */
+ uint32_t ssrc_changes;
+ uint32_t seq_skips;
+ uint32_t seq_backwards;
+ uint32_t seq_repeats;
+ uint32_t intentional_gaps;
+ uint32_t ts_resets;
+ uint32_t jitter_max;
+ /* New fields may be added here at the end; once added, they become
+ * permanent like the initially defined ones. */
+};
+
+/*! Info collected from the incoming RTP data stream
+ * for the purpose of generating RTCP reception report blocks.
+ * See twrtp guide document section 5.1.
+ *
+ * Key point: unlike the counters in struct osmo_twjit_stats,
+ * all RR info is reset to initial whenever incoming SSRC changes,
+ * as necessitated by RTCP data model being organized per SSRC.
+ *
+ * The same ABI considerations apply to this struct as osmo_twjit_stats.
+ */
+struct osmo_twjit_rr_info {
+ /* For ABI reasons, none of the following fields may be deleted
+ * or reordered! */
+
+ /*! received SSRC to which all following info applies */
+ uint32_t ssrc;
+ /*! count of "received packets" for RTCP RR packet loss calculation */
+ uint32_t rx_packets;
+ /*! "base" sequence number for "expected packets" computation */
+ uint32_t base_seq;
+ /*! "extended highest sequence number" field of RTCP RR */
+ uint32_t max_seq_ext;
+ /*! count of "expected packets" for RTCP RR packet loss calculation */
+ uint32_t expected_pkt;
+ /*! "interarrival jitter" measure of RFC 3550, accumulator for the
+ * leaky integrator algorithm prescribed by the RFC, sans-FP version.
+ * Right-shift this accumulator by 4 bits when emitting RTCP RR. */
+ uint32_t jitter_accum;
+ /* New fields may be added here at the end; once added, they become
+ * permanent like the initially defined ones. */
+};
+
+/* twjit API: managing configuration structures */
+
+struct osmo_twjit_config *osmo_twjit_config_alloc(void *ctx);
+void osmo_twjit_config_free(struct osmo_twjit_config *conf);
+
+int osmo_twjit_config_set_buffer_depth(struct osmo_twjit_config *conf,
+ uint16_t bd_start, uint16_t bd_hiwat);
+int osmo_twjit_config_set_thinning_int(struct osmo_twjit_config *conf,
+ uint16_t thinning_int);
+int osmo_twjit_config_set_max_future_sec(struct osmo_twjit_config *conf,
+ uint16_t max_future_sec);
+int osmo_twjit_config_set_start_min_delta(struct osmo_twjit_config *conf,
+ uint16_t delta_ms);
+int osmo_twjit_config_set_start_max_delta(struct osmo_twjit_config *conf,
+ uint16_t delta_ms);
+int osmo_twjit_config_set_handover_on_marker(struct osmo_twjit_config *conf,
+ bool hom);
+
+/* twjit API: actual twjit instances */
+
+struct osmo_twjit *osmo_twjit_create(void *ctx, uint16_t clock_khz,
+ uint16_t quantum_ms,
+ const struct osmo_twjit_config *config);
+void osmo_twjit_destroy(struct osmo_twjit *twjit);
+
+int osmo_twjit_set_config(struct osmo_twjit *twjit,
+ const struct osmo_twjit_config *config);
+void osmo_twjit_reset(struct osmo_twjit *twjit);
+
+/* RTP input, takes ownership of msgb */
+void osmo_twjit_input(struct osmo_twjit *twjit, struct msgb *msg);
+
+/* output function, to be called by TDM/GSM/etc fixed-timing side */
+struct msgb *osmo_twjit_output(struct osmo_twjit *twjit);
+
+/* Stats and RR info structures are contained inside opaque struct osmo_twjit.
+ * We need to provide access to these stats and RR info structures to API
+ * users, but we don't want to make the whole twjit instance struct public.
+ * Also we would like to have fast external access to these stats, hence an API
+ * that copies our stats to caller-provided storage would be very inefficient.
+ * Compromise: we allow direct external access to just these selected parts
+ * of the full internal state structure by providing API functions that
+ * return pointers to these selected parts.
+ */
+const struct osmo_twjit_stats *
+osmo_twjit_get_stats(struct osmo_twjit *twjit);
+
+const struct osmo_twjit_rr_info *
+osmo_twjit_get_rr_info(struct osmo_twjit *twjit);
+
+/* When we compose outgoing RTCP packets in the upper layer of twrtp,
+ * we need to know whether or not we have received at least one valid
+ * RTP data packet so far. If we haven't received any RTP yet, then
+ * we have no Rx SSRC, all data in struct osmo_twjit_rr_info are invalid,
+ * and we cannot send RTCP reception reports.
+ */
+bool osmo_twjit_rr_info_valid(struct osmo_twjit *twjit);
+
+/* vty configuration functions */
+
+void osmo_twjit_vty_init(int twjit_node);
+
+struct vty;
+
+int osmo_twjit_config_write(struct vty *vty,
+ const struct osmo_twjit_config *conf,
+ const char *prefix);
+
+/*! @} */
diff --git a/include/osmocom/netif/twjit_private.h b/include/osmocom/netif/twjit_private.h
new file mode 100644
index 0000000..8050be2
--- /dev/null
+++ b/include/osmocom/netif/twjit_private.h
@@ -0,0 +1,61 @@
+/*
+ * Themyscira Wireless RTP jitter buffer implementation:
+ * internal config structure.
+ *
+ * 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>
+
+/*! \cond private */
+
+/*! twjit configuration tunings, usually set via vty.
+ * This config structure always has to be provided in order to
+ * create a twjit instance. However, due to ABI concerns
+ * (retaining ability to add new fields to this structure),
+ * the struct itself has been made opaque, with actual definition
+ * visible only inside the library.
+ *
+ * In most twjit-using applications, the library's vty module
+ * will take care of both displaying and changing these tunable
+ * settings. However, setter APIs are also provided for non-vty
+ * users.
+ *
+ * The set of configurable parameters contained in this structure
+ * is covered in twrtp guide document section 2.4.
+ */
+struct osmo_twjit_config {
+ /*! buffer depth: starting minimum, formally called flow-starting
+ * fill level. Document section: 2.3.3. */
+ uint16_t bd_start;
+ /*! buffer depth: high water mark, formally called high water mark
+ * fill level. Document section: 2.3.4.2. */
+ uint16_t bd_hiwat;
+ /*! interval for thinning of too-deep standing queue;
+ * document section: 2.3.4.2. */
+ uint16_t thinning_int;
+ /*! guard against time traveler RTP packets, 1 s units;
+ * document section: 2.3.4.3. */
+ uint16_t max_future_sec;
+ /*! min time delta in starting state, 1 ms units, 0 means not set;
+ * document section: 2.3.3.2. */
+ uint16_t start_min_delta;
+ /*! max time delta in starting state, 1 ms units, 0 means not set;
+ * document section: 2.3.3.2. */
+ uint16_t start_max_delta;
+ /*! Osmocom addition, not in ThemWi original: should RTP packets
+ * with M bit set cause a handover or HUNT state reset just like
+ * an SSRC change? With this option enabled, M bit is treated
+ * like an SSRC change in that the timestamp is not considered
+ * at all, on the reasoning that the sender may have switched
+ * to an entirely unrelated source of timestamps. */
+ bool handover_on_marker;
+};
+
+/*! \endcond */
diff --git a/src/Makefile.am b/src/Makefile.am
index 98dc276..73b497f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -3,12 +3,14 @@
LIBVERSION=14:0:3
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)
-AM_CFLAGS= -fPIC -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(COVERAGE_CFLAGS) $(LIBSCTP_CFLAGS)
+AM_CFLAGS= -fPIC -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) $(LIBSCTP_CFLAGS)
AM_LDFLAGS = $(COVERAGE_LDFLAGS)
lib_LTLIBRARIES = libosmonetif.la
-libosmonetif_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBSCTP_LIBS)
+libosmonetif_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) $(LIBSCTP_LIBS)
libosmonetif_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBVERSION) -no-undefined
libosmonetif_la_SOURCES = amr.c \
@@ -26,6 +28,8 @@
stream.c \
stream_cli.c \
stream_srv.c \
+ twjit.c \
+ twjit_conf.c \
$(NULL)
if ENABLE_LIBSCTP
diff --git a/src/twjit.c b/src/twjit.c
new file mode 100644
index 0000000..4079ce9
--- /dev/null
+++ b/src/twjit.c
@@ -0,0 +1,706 @@
+/*
+ * Themyscira Wireless RTP jitter buffer 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 <string.h>
+#include <errno.h>
+#include <arpa/inet.h> /* for network byte order functions */
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/netif/twjit.h>
+#include <osmocom/netif/twjit_private.h>
+#include <osmocom/netif/rtp.h>
+
+/*! \addgroup twjit
+ * @{
+ */
+
+/*! \cond private */
+
+/*! Each twjit instance has two sub-buffers; each subbuf is a queue of
+ * received RTP packets that have the same SSRC and whose timestamps
+ * increment in the expected cadence, with each ts delta being an
+ * integral multiple of the samples-per-quantum constant.
+ * See document section 2.3.2.
+ */
+struct twjit_subbuf {
+ /*! SSRC of the stream portion captured in this subbuf */
+ uint32_t ssrc;
+ /*! Current timestamp at the head of the queue */
+ uint32_t head_ts;
+ /*! Queue of packets held by this subbuf */
+ struct llist_head queue;
+ /*! Current depth as defined in document section 2.3.2 */
+ uint32_t depth;
+ /*! Time delta in ms between arrival times of the two most recently
+ * received packets, used only in starting state */
+ uint32_t delta_ms;
+ /*! thinning mechanism: countdown before next quantum deletion */
+ uint16_t drop_int_count;
+};
+
+/*! Each twjit instance is in one of 4 fundamental states at any moment,
+ * as enumerated here. See document section 2.3.1 for state transition
+ * diagram.
+ */
+enum twjit_state {
+ /*! completely empty: neither subbuf is valid */
+ TWJIT_STATE_EMPTY,
+ /*! one subbuf is non-empty, but it hasn't started flowing out yet */
+ TWJIT_STATE_HUNT,
+ /*! one subbuf is both flowing out and accepting new packets */
+ TWJIT_STATE_FLOWING,
+ /*! one subbuf is flowing out while another receives new packets */
+ TWJIT_STATE_HANDOVER,
+};
+
+/*! Main structure for one instance of twjit */
+struct osmo_twjit {
+ /*! twjit tuning config, can be changed with osmo_twjit_set_config() */
+ struct osmo_twjit_config config;
+ /*! count of RTP timestamp units per quantum */
+ uint32_t ts_quantum;
+ /*! quanta per second, used to scale max_future_sec */
+ uint16_t quanta_per_sec;
+ /* scaling factors for time delta conversions */
+ /*! RTP timestamp units per millisecond */
+ uint16_t ts_units_per_ms;
+ /*! 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;
+ /*! current fundamental state of this twjit instance */
+ enum twjit_state state;
+ /*! the two sub-buffers */
+ struct twjit_subbuf sb[2];
+ /*! current subbuf being read, 0 or 1 */
+ uint8_t read_sb;
+ /*! current subbuf being written, 0 or 1 */
+ uint8_t write_sb;
+ /*! RTP timestamp of the most recently received packet */
+ uint32_t last_ts;
+ /*! RTP sequence number of the most recently received packet */
+ uint16_t last_seq;
+ /*! Have we received at least one packet? This bool serves as
+ * the validity flag for last_ts, last_ts and rr_info. */
+ bool got_first_packet;
+ /*! CLOCK_MONOTONIC time of last packet arrival */
+ struct timespec last_arrival;
+ /*! Delta between the two most recent RTP packet arrival times,
+ * converted to RTP timestamp units. */
+ uint32_t last_arrival_delta;
+ /*! analytics for RTCP RR, also remembers last SSRC */
+ struct osmo_twjit_rr_info rr_info;
+ /*! stats over lifetime of this instance */
+ struct osmo_twjit_stats stats;
+};
+
+/*! \endcond */
+
+/* create and destroy functions */
+
+/*! Create a twjit instance
+ *
+ * \param[in] ctx Parent talloc context under which struct osmo_twjit
+ * 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] config Set of tunable configuration parameters to be used.
+ * \returns pointer to the newly created twjit instance, or NULL on errors.
+ *
+ * In contrast to the original twrtp-proto version, this version of
+ * osmo_twjit_create() copies the config structure, thus the application
+ * is not required to maintain it in the originally passed memory.
+ */
+struct osmo_twjit *osmo_twjit_create(void *ctx, uint16_t clock_khz,
+ uint16_t quantum_ms,
+ const struct osmo_twjit_config *config)
+{
+ struct osmo_twjit *twjit;
+
+ /* Note to developers comparing this function against its twrtp-native
+ * counterpart: the original version performed a validity check on
+ * the passed config structure at this point, but that check has been
+ * omitted in this version. Rationale: since the config structure
+ * has been made opaque, it is now impossible for applications
+ * to pass in a config structure that has not been constructed
+ * by the library itself and thus known to be valid. */
+
+ twjit = talloc_zero(ctx, struct osmo_twjit);
+ if (!twjit)
+ return NULL;
+
+ memcpy(&twjit->config, config, sizeof(struct osmo_twjit_config));
+ twjit->state = TWJIT_STATE_EMPTY;
+ INIT_LLIST_HEAD(&twjit->sb[0].queue);
+ INIT_LLIST_HEAD(&twjit->sb[1].queue);
+ twjit->ts_quantum = (uint32_t) quantum_ms * clock_khz;
+ twjit->quanta_per_sec = 1000 / quantum_ms;
+ twjit->ts_units_per_ms = clock_khz;
+ twjit->ts_units_per_sec = (uint32_t) clock_khz * 1000;
+ twjit->ns_to_ts_units = 1000000 / clock_khz;
+
+ return twjit;
+}
+
+/*! Destroy a twjit instance
+ *
+ * \param[in] twjit Instance to be freed
+ *
+ * Memory freed by this function includes not only the instance structure,
+ * but also any msgbs that are held by this instance.
+ */
+void osmo_twjit_destroy(struct osmo_twjit *twjit)
+{
+ if (!twjit)
+ return;
+ msgb_queue_free(&twjit->sb[0].queue);
+ msgb_queue_free(&twjit->sb[1].queue);
+ talloc_free(twjit);
+}
+
+/*! Change twjit config parameters
+ *
+ * \param[in] twjit Instance to be reconfigured
+ * \param[in] config Structure with new tuning parameters to be used
+ * \returns 0 if successful, negative on errors
+ *
+ * The intended use for this API is applications that can use one of
+ * several different twjit config profiles depending on various
+ * conditions, but might not know the correct choice of profile
+ * at the time they create the twjit instance - or more likely,
+ * the containing twrtp instance.
+ */
+int osmo_twjit_set_config(struct osmo_twjit *twjit,
+ const struct osmo_twjit_config *config)
+{
+ /* Note to developers comparing this function against its twrtp-native
+ * counterpart: the original version performed a validity check on
+ * the passed config structure at this point, but that check has been
+ * omitted in this version. Rationale: since the config structure
+ * has been made opaque, it is now impossible for applications
+ * to pass in a config structure that has not been constructed
+ * by the library itself and thus known to be valid. */
+
+ memcpy(&twjit->config, config, sizeof(struct osmo_twjit_config));
+ return 0;
+}
+
+/*! Reset twjit instance to empty initial state
+ *
+ * \param[in] twjit Instance to be reset
+ *
+ * This reset function is intended to be called when the application
+ * stops doing regular (once every time quantum) reads from the jitter
+ * buffer, but may resume this activity later. All packet Rx state and
+ * queues are cleared, but "lifetime" statistical counters are NOT reset.
+ */
+void osmo_twjit_reset(struct osmo_twjit *twjit)
+{
+ msgb_queue_free(&twjit->sb[0].queue);
+ msgb_queue_free(&twjit->sb[1].queue);
+ twjit->state = TWJIT_STATE_EMPTY;
+ twjit->sb[0].depth = 0;
+ twjit->sb[1].depth = 0;
+ twjit->got_first_packet = false;
+}
+
+/* input processing of received RTP packets */
+
+/*! \cond private */
+
+/* raw analytics on the Rx packet stream */
+
+/* This "init" function is called for the very first RTP packet we receive,
+ * as well as for received RTP packets that exhibit a change of SSRC
+ * from the previously received stream.
+ */
+static void analytics_init(struct osmo_twjit *twjit, uint32_t rx_ssrc,
+ uint16_t rx_seq)
+{
+ struct osmo_twjit_rr_info *rri = &twjit->rr_info;
+
+ rri->ssrc = rx_ssrc;
+ rri->rx_packets = 1;
+ rri->base_seq = rx_seq;
+ rri->max_seq_ext = rx_seq;
+ rri->expected_pkt = 1;
+ rri->jitter_accum = 0;
+}
+
+/* This "continue" function is called for newly received RTP packets that
+ * follow previously received ones with the same SSRC, not necessarily
+ * in order.
+ */
+static void analytics_cont(struct osmo_twjit *twjit, uint16_t rx_seq,
+ uint32_t rx_ts, const struct timespec *now)
+{
+ struct osmo_twjit_rr_info *rri = &twjit->rr_info;
+ uint16_t seq_ext_lo = rri->max_seq_ext;
+ uint16_t seq_ext_hi = rri->max_seq_ext >> 16;
+ int16_t seq_delta = (int16_t)(rx_seq - twjit->last_seq);
+ int16_t seq_delta2 = (int16_t)(rx_seq - seq_ext_lo);
+ int32_t ts_delta = (int32_t)(rx_ts - twjit->last_ts);
+ struct timespec time_delta;
+ uint32_t time_delta_tsu;
+ int32_t jitter_new, ts_delta_clamp;
+
+ /* analytics for our own stats */
+ if (seq_delta < 0)
+ twjit->stats.seq_backwards++;
+ else if (seq_delta == 0)
+ twjit->stats.seq_repeats++;
+ else if (seq_delta == 1) {
+ if (ts_delta != twjit->ts_quantum) {
+ if (ts_delta > 0 && (ts_delta % twjit->ts_quantum) == 0)
+ twjit->stats.intentional_gaps++;
+ else
+ twjit->stats.ts_resets++;
+ }
+ } else
+ twjit->stats.seq_skips++;
+
+ /* analytics for RTCP RR: packet counts */
+ rri->rx_packets++;
+ if (seq_delta2 > 0) {
+ if (rx_seq < seq_ext_lo)
+ seq_ext_hi++;
+ seq_ext_lo = rx_seq;
+ rri->max_seq_ext = ((uint32_t) seq_ext_hi << 16) | seq_ext_lo;
+ rri->expected_pkt = rri->max_seq_ext - rri->base_seq + 1;
+ }
+
+ /* time-of-arrival analytics */
+ time_delta.tv_sec = now->tv_sec - twjit->last_arrival.tv_sec;
+ time_delta.tv_nsec = now->tv_nsec - twjit->last_arrival.tv_nsec;
+ if (time_delta.tv_nsec < 0) {
+ time_delta.tv_sec--;
+ time_delta.tv_nsec += 1000000000;
+ }
+ /* to avoid overflows in downstream math, clamp to 1 hour */
+ if (time_delta.tv_sec >= 3600) {
+ time_delta.tv_sec = 3600;
+ time_delta.tv_nsec = 0;
+ }
+ /* convert to RTP timestamp units */
+ time_delta_tsu = time_delta.tv_sec * twjit->ts_units_per_sec +
+ time_delta.tv_nsec / twjit->ns_to_ts_units;
+ twjit->last_arrival_delta = time_delta_tsu;
+ /* jitter calculation for RTCP RR */
+ ts_delta_clamp = twjit->ts_units_per_sec * 3600;
+ if (ts_delta > ts_delta_clamp)
+ ts_delta = ts_delta_clamp;
+ else if (ts_delta < -ts_delta_clamp)
+ ts_delta = -ts_delta_clamp;
+ jitter_new = time_delta_tsu - ts_delta;
+ if (jitter_new < 0)
+ jitter_new = -jitter_new;
+ /* RFC 3550 section 6.4.1 prescribes a very specific algorithm
+ * for computing the interarrival jitter reported via RTCP.
+ * This prescribed algorithm is a type of leaky integrator.
+ * Here we implement the fixed-point (no floating point operations)
+ * version presented in section A.8 of the same RFC. */
+ rri->jitter_accum += jitter_new - ((rri->jitter_accum + 8) >> 4);
+ if (jitter_new > twjit->stats.jitter_max)
+ twjit->stats.jitter_max = jitter_new;
+}
+
+/* actual twjit input logic */
+
+static void
+init_subbuf_first_packet(struct osmo_twjit *twjit, struct msgb *msg,
+ uint32_t rx_ssrc, uint32_t rx_ts)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->write_sb];
+
+ OSMO_ASSERT(llist_empty(&sb->queue));
+ OSMO_ASSERT(sb->depth == 0);
+ /* all good, proceed */
+ sb->ssrc = rx_ssrc;
+ sb->head_ts = rx_ts;
+ msgb_enqueue(&sb->queue, msg);
+ sb->depth = 1;
+ sb->drop_int_count = 0;
+ /* The setting of delta_ms is needed in order to pacify the check
+ * in starting_sb_is_ready() in configurations with bd_start=1.
+ * An alternative would be to enforce start_min_delta being not set
+ * with bd_start=1, but the present solution is simpler than doing
+ * cross-enforcement between two different parameter settings in vty.
+ */
+ sb->delta_ms = UINT32_MAX;
+}
+
+enum input_decision {
+ INPUT_CONTINUE,
+ INPUT_TOO_OLD,
+ INPUT_RESET,
+};
+
+static enum input_decision
+check_input_for_subbuf(struct osmo_twjit *twjit, bool starting,
+ uint32_t rx_ssrc, uint32_t rx_ts, bool marker)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->write_sb];
+ int32_t ts_delta;
+
+ if (rx_ssrc != sb->ssrc)
+ return INPUT_RESET;
+ if (marker && twjit->config.handover_on_marker) {
+ twjit->stats.marker_resets++;
+ return INPUT_RESET;
+ }
+ sb->delta_ms = twjit->last_arrival_delta / twjit->ts_units_per_ms;
+ ts_delta = (int32_t)(rx_ts - sb->head_ts);
+ if (ts_delta < 0)
+ return INPUT_TOO_OLD;
+ if (ts_delta % twjit->ts_quantum)
+ return INPUT_RESET;
+ if (starting) {
+ if (twjit->config.start_max_delta &&
+ sb->delta_ms > twjit->config.start_max_delta)
+ return INPUT_RESET;
+ } else {
+ uint32_t fwd = ts_delta / twjit->ts_quantum;
+
+ if (fwd >= (uint32_t) twjit->config.max_future_sec *
+ twjit->quanta_per_sec)
+ return INPUT_RESET;
+ }
+ return INPUT_CONTINUE;
+}
+
+static void toss_write_queue(struct osmo_twjit *twjit)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->write_sb];
+
+ msgb_queue_free(&sb->queue);
+ sb->depth = 0;
+}
+
+static void insert_pkt_write_sb(struct osmo_twjit *twjit, struct msgb *new_msg,
+ uint32_t rx_ts)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->write_sb];
+ uint32_t ts_delta = rx_ts - sb->head_ts;
+ uint32_t ins_depth = ts_delta / twjit->ts_quantum;
+ struct msgb *old_msg;
+ uint32_t old_ts_delta;
+
+ /* are we increasing total depth, and can we do simple tail append? */
+ if (ins_depth >= sb->depth) {
+ msgb_enqueue(&sb->queue, new_msg);
+ sb->depth = ins_depth + 1;
+ return;
+ }
+ /* nope - do it the hard way */
+ llist_for_each_entry(old_msg, &sb->queue, list) {
+ old_ts_delta = old_msg->cb[0] - sb->head_ts;
+ if (old_ts_delta == ts_delta) {
+ /* two packets with the same timestamp! */
+ twjit->stats.duplicate_ts++;
+ msgb_free(new_msg);
+ return;
+ }
+ if (old_ts_delta > ts_delta)
+ break;
+ }
+ llist_add_tail(&new_msg->list, &old_msg->list);
+}
+
+static void trim_starting_sb(struct osmo_twjit *twjit)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->write_sb];
+ struct msgb *msg;
+ uint32_t msg_ts, ts_adv, quantum_adv;
+
+ while (sb->depth > twjit->config.bd_start) {
+ msg = msgb_dequeue(&sb->queue);
+ OSMO_ASSERT(msg);
+ msgb_free(msg);
+ OSMO_ASSERT(!llist_empty(&sb->queue));
+ msg = llist_entry(sb->queue.next, struct msgb, list);
+ msg_ts = msg->cb[0];
+ ts_adv = msg_ts - sb->head_ts;
+ quantum_adv = ts_adv / twjit->ts_quantum;
+ OSMO_ASSERT(sb->depth > quantum_adv);
+ sb->head_ts = msg_ts;
+ sb->depth -= quantum_adv;
+ }
+}
+
+/*! \endcond */
+
+/*! Feed received RTP packet to twjit
+ *
+ * \param[in] twjit Instance to which input is being fed
+ * \param[in] msg Message buffer containing the received packet
+ *
+ * The msgb fed to this API is always consumed by the called function:
+ * if it isn't freed for being invalid or too old, it is queued to be
+ * regurgitated some time later on the output side. The design of
+ * twjit assumes that this API will be called as soon as each incoming
+ * RTP packet is received from the IP network, without any additional
+ * delays; in most applications, thus function will be called by twrtp
+ * layer from osmo_io Rx callback path.
+ */
+void osmo_twjit_input(struct osmo_twjit *twjit, struct msgb *msg)
+{
+ bool got_previous_input = twjit->got_first_packet;
+ const struct rtp_hdr *rtph;
+ uint32_t rx_ssrc, rx_ts;
+ uint16_t rx_seq;
+ struct timespec now;
+ enum input_decision id;
+
+ rtph = osmo_rtp_get_hdr(msg);
+ if (!rtph) {
+ twjit->stats.bad_packets++;
+ msgb_free(msg);
+ return;
+ }
+ rx_ssrc = ntohl(rtph->ssrc);
+ rx_ts = ntohl(rtph->timestamp);
+ rx_seq = ntohs(rtph->sequence);
+ osmo_clock_gettime(CLOCK_MONOTONIC, &now);
+ if (!got_previous_input) {
+ analytics_init(twjit, rx_ssrc, rx_seq);
+ twjit->got_first_packet = true;
+ } else if (rx_ssrc != twjit->rr_info.ssrc) {
+ twjit->stats.ssrc_changes++;
+ analytics_init(twjit, rx_ssrc, rx_seq);
+ } else {
+ analytics_cont(twjit, rx_seq, rx_ts, &now);
+ }
+ twjit->last_seq = rx_seq;
+ twjit->last_ts = rx_ts;
+ memcpy(&twjit->last_arrival, &now, sizeof(struct timespec));
+ twjit->stats.rx_packets++;
+ msg->cb[0] = rx_ts;
+
+ switch (twjit->state) {
+ case TWJIT_STATE_EMPTY:
+ /* first packet into totally empty buffer */
+ if (got_previous_input)
+ twjit->stats.underruns++;
+ twjit->state = TWJIT_STATE_HUNT;
+ twjit->write_sb = 0;
+ init_subbuf_first_packet(twjit, msg, rx_ssrc, rx_ts);
+ return;
+ case TWJIT_STATE_HUNT:
+ case TWJIT_STATE_HANDOVER:
+ id = check_input_for_subbuf(twjit, true, rx_ssrc, rx_ts,
+ rtph->marker);
+ if (id == INPUT_TOO_OLD) {
+ msgb_free(msg);
+ return;
+ }
+ if (id == INPUT_RESET) {
+ toss_write_queue(twjit);
+ init_subbuf_first_packet(twjit, msg, rx_ssrc, rx_ts);
+ return;
+ }
+ insert_pkt_write_sb(twjit, msg, rx_ts);
+ trim_starting_sb(twjit);
+ return;
+ case TWJIT_STATE_FLOWING:
+ id = check_input_for_subbuf(twjit, false, rx_ssrc, rx_ts,
+ rtph->marker);
+ if (id == INPUT_TOO_OLD) {
+ twjit->stats.too_old++;
+ msgb_free(msg);
+ return;
+ }
+ if (id == INPUT_RESET) {
+ twjit->state = TWJIT_STATE_HANDOVER;
+ twjit->write_sb = !twjit->write_sb;
+ init_subbuf_first_packet(twjit, msg, rx_ssrc, rx_ts);
+ twjit->stats.handovers_in++;
+ return;
+ }
+ insert_pkt_write_sb(twjit, msg, rx_ts);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* output to the fixed timing system */
+
+/*! \cond private */
+
+static bool starting_sb_is_ready(struct osmo_twjit *twjit)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->write_sb];
+
+ if (sb->depth < twjit->config.bd_start)
+ return false;
+ if (sb->delta_ms < twjit->config.start_min_delta)
+ return false;
+ return true;
+}
+
+static bool read_sb_is_empty(struct osmo_twjit *twjit)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->read_sb];
+
+ return sb->depth == 0;
+}
+
+static struct msgb *pull_from_read_sb(struct osmo_twjit *twjit)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->read_sb];
+ struct msgb *msg;
+
+ OSMO_ASSERT(!llist_empty(&sb->queue));
+ OSMO_ASSERT(sb->depth > 0);
+ msg = llist_entry(sb->queue.next, struct msgb, list);
+ if (msg->cb[0] == sb->head_ts) {
+ llist_del(&msg->list);
+ twjit->stats.delivered_pkt++;
+ } else {
+ msg = NULL;
+ twjit->stats.output_gaps++;
+ }
+ sb->head_ts += twjit->ts_quantum;
+ sb->depth--;
+ return msg;
+}
+
+static void read_sb_thinning(struct osmo_twjit *twjit)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->read_sb];
+ struct msgb *msg;
+
+ if (sb->drop_int_count) {
+ sb->drop_int_count--;
+ return;
+ }
+ if (sb->depth <= twjit->config.bd_hiwat)
+ return;
+ twjit->stats.thinning_drops++;
+ msg = pull_from_read_sb(twjit);
+ if (msg)
+ msgb_free(msg);
+ sb->drop_int_count = twjit->config.thinning_int - 2;
+}
+
+static void toss_read_queue(struct osmo_twjit *twjit)
+{
+ struct twjit_subbuf *sb = &twjit->sb[twjit->read_sb];
+
+ msgb_queue_free(&sb->queue);
+ sb->depth = 0;
+}
+
+/*! \endcond */
+
+/*! Fixed-timing output poll from twjit buffer
+ *
+ * \param[in] twjit Instance 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.
+ */
+struct msgb *osmo_twjit_output(struct osmo_twjit *twjit)
+{
+ switch (twjit->state) {
+ case TWJIT_STATE_EMPTY:
+ return NULL;
+ case TWJIT_STATE_HUNT:
+ if (!starting_sb_is_ready(twjit))
+ return NULL;
+ twjit->state = TWJIT_STATE_FLOWING;
+ twjit->read_sb = twjit->write_sb;
+ return pull_from_read_sb(twjit);
+ case TWJIT_STATE_FLOWING:
+ if (read_sb_is_empty(twjit)) {
+ twjit->state = TWJIT_STATE_EMPTY;
+ return NULL;
+ }
+ read_sb_thinning(twjit);
+ return pull_from_read_sb(twjit);
+ case TWJIT_STATE_HANDOVER:
+ if (starting_sb_is_ready(twjit)) {
+ toss_read_queue(twjit);
+ twjit->stats.handovers_out++;
+ twjit->state = TWJIT_STATE_FLOWING;
+ twjit->read_sb = twjit->write_sb;
+ return pull_from_read_sb(twjit);
+ }
+ if (read_sb_is_empty(twjit)) {
+ twjit->state = TWJIT_STATE_HUNT;
+ twjit->stats.ho_underruns++;
+ return NULL;
+ }
+ read_sb_thinning(twjit);
+ return pull_from_read_sb(twjit);
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* simple information retrieval functions */
+
+/*! Retrieve lifetime stats from twjit instance
+ *
+ * \param[in] twjit Instance to query
+ * \returns pointer to lifetime stats structure
+ */
+const struct osmo_twjit_stats *
+osmo_twjit_get_stats(struct osmo_twjit *twjit)
+{
+ return &twjit->stats;
+}
+
+/*! Retrieve RR info from twjit instance
+ *
+ * \param[in] twjit Instance to query
+ * \returns pointer to RR info structure
+ *
+ * The structure retrieved with this API is called RR info because it contains
+ * info that is needed in order to constuct RTCP reception reports describing
+ * the RTP stream received by this twjit instance. But of course this info
+ * can also be used for other statistics-related or monitoring-related purposes.
+ *
+ * The structure returned by this API is fully valid only if
+ * osmo_twjit_rr_info_valid() returns true. If that API returns false,
+ * the RR info structure returned by the present API should be considered
+ * invalid. More precisely, the "invalid" RR info structure will be all
+ * zeros on a freshly created twjit, or stale info if this twjit received
+ * some RTP input prior to being reset. There may be some applications
+ * that retrieve the RR info structure to report some non-critical stats;
+ * such uses are allowed even when this structure is invalid in the strict
+ * sense.
+ */
+const struct osmo_twjit_rr_info *
+osmo_twjit_get_rr_info(struct osmo_twjit *twjit)
+{
+ return &twjit->rr_info;
+}
+
+/*! Did this twjit instance ever receive RTP input?
+ *
+ * \param[in] twjit Instance to query
+ * \returns true if this twjit instance received RTP input since it was
+ * created or last reset, false otherwise.
+ */
+bool osmo_twjit_rr_info_valid(struct osmo_twjit *twjit)
+{
+ return twjit->got_first_packet;
+}
+
+/*! @} */
diff --git a/src/twjit_conf.c b/src/twjit_conf.c
new file mode 100644
index 0000000..b00d3dc
--- /dev/null
+++ b/src/twjit_conf.c
@@ -0,0 +1,345 @@
+/*
+ * Themyscira Wireless RTP jitter buffer implementation:
+ * configuration by vty or otherwise.
+ *
+ * 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 <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+
+#include <osmocom/netif/twjit.h>
+#include <osmocom/netif/twjit_private.h>
+
+/*! \addgroup twjit
+ * @{
+ */
+
+/*! Allocate and initialize twjit config structure
+ *
+ * \param[in] ctx Parent talloc context under which struct osmo_twjit_config
+ * should be allocated.
+ * \returns pointer to the newly created twjit config instance, or NULL on
+ * errors.
+ *
+ * A typical application will have a struct osmo_twjit_config somewhere
+ * in the application config data structures, editable via vty.
+ * More complex applications may even have several such twjit config
+ * structures, to be used in different contexts such as GSM vs PSTN.
+ * However, in the present Osmocom-integrated version of twjit, this config
+ * structure has been made opaque for ABI reasons - hence config instances
+ * now have to be allocated by the library, rather than merely initialized
+ * in content.
+ */
+struct osmo_twjit_config *osmo_twjit_config_alloc(void *ctx)
+{
+ struct osmo_twjit_config *config;
+
+ config = talloc_zero(ctx, struct osmo_twjit_config);
+ if (!config)
+ return NULL;
+
+ /* Initialize defaults, corresponding to twna_twjit_config_init()
+ * in twrtp-native version. */
+
+ /* While the theoretical minimum starting fill level is 1, the
+ * practically useful minimum (achieving lowest latency, but not
+ * incurring underruns in normal healthy operation) is 2 for typical
+ * network configurations that combine elements with "perfect" 20 ms
+ * timing (T1/E1 interfaces, external IP-PSTN links, software
+ * transcoders timed by system clock etc) and GSM-to-IP OsmoBTS
+ * whose 20 ms timing contains the small inherent jitter of TDMA. */
+ config->bd_start = 2;
+
+ /* The high water mark setting determines when the standing queue
+ * thinning mechanism kicks in. A standing queue that is longer
+ * than the starting fill level will occur when the flow starts
+ * during a network latency spike, but then the network latency
+ * goes down. If this setting is too high, deep standing queues
+ * will persist, adding needless latency to speech or CSD.
+ * If this setting is too low, the thinning mechanism will be
+ * too invasive, needlessly and perhaps frequently deleting a quantum
+ * of speech or data from the stream and incurring a phase shift.
+ * Starting fill level plus 2 seems like a good default. */
+ config->bd_hiwat = 4;
+
+ /* When the standing queue thinning mechanism does kick in,
+ * it drops every Nth packet, where N is the thinning interval.
+ * Given that this mechanism forcibly deletes a quantum of speech
+ * or data from the stream, these induced disruptions should be
+ * spaced out, and the managing operator should also keep in mind
+ * that the incurred phase shift may be a problem for some
+ * applications, particularly CSD. Our current default is
+ * a prime number, reducing the probability that the thinning
+ * mechanism will interfere badly with intrinsic features of the
+ * stream being thinned. 17 quantum units at 20 ms per quantum
+ * is 340 ms, which should be sufficiently long spacing to make
+ * speech quantum deletions tolerable. */
+ config->thinning_int = 17;
+
+ /* Guard against time traveler packets,
+ * see document section 2.3.4.3. */
+ config->max_future_sec = 10;
+
+ return config;
+}
+
+/*! Free a twjit config instance
+ *
+ * \param[in] conf Instance to be freed
+ */
+void osmo_twjit_config_free(struct osmo_twjit_config *conf)
+{
+ if (!conf)
+ return;
+ talloc_free(conf);
+}
+
+/*! Write out vty form of twjit config structure
+ *
+ * \param[in] vty The vty instance to which vty_out() calls should be made
+ * \param[in] conf The config structure to write out
+ * \param[in] prefix Additional indent prefix to be prepended to each output
+ * line, defaults to "" if NULL
+ * \returns CMD_SUCCESS for vty system
+ */
+int osmo_twjit_config_write(struct vty *vty,
+ const struct osmo_twjit_config *conf,
+ const char *prefix)
+{
+ if (!prefix)
+ prefix = "";
+ vty_out(vty, "%s buffer-depth %u %u%s", prefix, conf->bd_start,
+ conf->bd_hiwat, VTY_NEWLINE);
+ vty_out(vty, "%s thinning-interval %u%s", prefix, conf->thinning_int,
+ VTY_NEWLINE);
+ vty_out(vty, "%s max-future-sec %u%s", prefix, conf->max_future_sec,
+ VTY_NEWLINE);
+
+ if (conf->start_min_delta) {
+ vty_out(vty, "%s start-min-delta %u%s", prefix,
+ conf->start_min_delta, VTY_NEWLINE);
+ }
+ if (conf->start_max_delta) {
+ vty_out(vty, "%s start-max-delta %u%s", prefix,
+ conf->start_max_delta, VTY_NEWLINE);
+ }
+
+ vty_out(vty, "%s marker-handling %s%s", prefix,
+ conf->handover_on_marker ? "handover" : "ignore", VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_buffer_depth, cfg_buffer_depth_cmd,
+ "buffer-depth <1-65535> <1-65535>",
+ "Buffer depth configuration\n"
+ "Minimum fill required to start flow\n"
+ "High water mark fill level\n")
+{
+ struct osmo_twjit_config *conf = vty->index;
+ unsigned bd_start = atoi(argv[0]);
+ unsigned bd_hiwat = atoi(argv[1]);
+
+ if (bd_hiwat < bd_start) {
+ vty_out(vty, "%% Error: high water mark cannot be less than starting level%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ conf->bd_start = bd_start;
+ conf->bd_hiwat = bd_hiwat;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_thinning, cfg_thinning_cmd,
+ "thinning-interval <2-65535>",
+ "Standing queue thinning configuration\n"
+ "Drop every Nth packet\n")
+{
+ struct osmo_twjit_config *conf = vty->index;
+ conf->thinning_int = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_max_future, cfg_max_future_cmd,
+ "max-future-sec <1-65535>",
+ "Guard against time traveler packets\n"
+ "Maximum permissible number of seconds into the future\n")
+{
+ struct osmo_twjit_config *conf = vty->index;
+ conf->max_future_sec = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_start_min_delta, cfg_start_min_delta_cmd,
+ "start-min-delta <1-65535>",
+ "Minimum required delta in time-of-arrival to start flow\n"
+ "Time delta value in ms\n")
+{
+ struct osmo_twjit_config *conf = vty->index;
+ conf->start_min_delta = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_start_min_delta, cfg_no_start_min_delta_cmd,
+ "no start-min-delta",
+ NO_STR "Minimum required delta in time-of-arrival to start flow\n")
+{
+ struct osmo_twjit_config *conf = vty->index;
+ conf->start_min_delta = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_start_max_delta, cfg_start_max_delta_cmd,
+ "start-max-delta <1-65535>",
+ "Maximum permitted gap in time-of-arrival in starting state\n"
+ "Time delta value in ms\n")
+{
+ struct osmo_twjit_config *conf = vty->index;
+ conf->start_max_delta = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_start_max_delta, cfg_no_start_max_delta_cmd,
+ "no start-max-delta",
+ NO_STR "Maximum permitted gap in time-of-arrival in starting state\n")
+{
+ struct osmo_twjit_config *conf = vty->index;
+ conf->start_max_delta = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_marker_handling, cfg_marker_handling_cmd,
+ "marker-handling (handover|ignore)",
+ "How to handle RTP packets with marker bit set\n"
+ "Invoke handover handling, same as SSRC change\n"
+ "Ignore marker bit\n")
+{
+ struct osmo_twjit_config *conf = vty->index;
+ conf->handover_on_marker = (strcmp(argv[0], "handover") == 0);
+ return CMD_SUCCESS;
+}
+
+/* Install vty configuration elements for twjit
+ *
+ * \param[in] twjit_node The application-defined vty node ID for twjit
+ */
+void osmo_twjit_vty_init(int twjit_node)
+{
+ install_lib_element(twjit_node, &cfg_buffer_depth_cmd);
+ install_lib_element(twjit_node, &cfg_thinning_cmd);
+ install_lib_element(twjit_node, &cfg_max_future_cmd);
+ install_lib_element(twjit_node, &cfg_start_min_delta_cmd);
+ install_lib_element(twjit_node, &cfg_no_start_min_delta_cmd);
+ install_lib_element(twjit_node, &cfg_start_max_delta_cmd);
+ install_lib_element(twjit_node, &cfg_no_start_max_delta_cmd);
+ install_lib_element(twjit_node, &cfg_marker_handling_cmd);
+}
+
+/* config setter functions for non-vty users */
+
+/*! Non-vty function for buffer-depth setting
+ *
+ * \param[in] conf twjit config instance to operate on.
+ * \param[in] bd_start Flow-starting fill level, document section 2.3.3.
+ * \param[in] bd_hiwat High water mark fill level, document section 2.3.4.2.
+ * \returns 0 if successful, negative on errors.
+ */
+int osmo_twjit_config_set_buffer_depth(struct osmo_twjit_config *conf,
+ uint16_t bd_start, uint16_t bd_hiwat)
+{
+ if (bd_start < 1)
+ return -EINVAL;
+ if (bd_hiwat < bd_start)
+ return -EINVAL;
+ conf->bd_start = bd_start;
+ conf->bd_hiwat = bd_hiwat;
+ return 0;
+}
+
+/*! Non-vty function for thinning-interval setting
+ *
+ * \param[in] conf twjit config instance to operate on.
+ * \param[in] thinning_int Thinning interval setting, document section 2.3.4.2.
+ * \returns 0 if successful, negative on errors.
+ */
+int osmo_twjit_config_set_thinning_int(struct osmo_twjit_config *conf,
+ uint16_t thinning_int)
+{
+ if (thinning_int < 2)
+ return -EINVAL;
+ conf->thinning_int = thinning_int;
+ return 0;
+}
+
+/*! Non-vty function for max-future-sec setting
+ *
+ * \param[in] conf twjit config instance to operate on.
+ * \param[in] max_future_sec Maximum number of seconds into the future,
+ * document section 2.3.4.3.
+ * \returns 0 if successful, negative on errors.
+ */
+int osmo_twjit_config_set_max_future_sec(struct osmo_twjit_config *conf,
+ uint16_t max_future_sec)
+{
+ if (max_future_sec < 1)
+ return -EINVAL;
+ conf->max_future_sec = max_future_sec;
+ return 0;
+}
+
+/*! Non-vty function for start-min-delta setting
+ *
+ * \param[in] conf twjit config instance to operate on.
+ * \param[in] delta_ms Minimum required ToA delta in ms, or 0 to disable
+ * this check; document section 2.3.3.2.
+ * \returns 0 if successful, negative on errors.
+ */
+int osmo_twjit_config_set_start_min_delta(struct osmo_twjit_config *conf,
+ uint16_t delta_ms)
+{
+ conf->start_min_delta = delta_ms;
+ return 0;
+}
+
+/*! Non-vty function for start-max-delta setting
+ *
+ * \param[in] conf twjit config instance to operate on.
+ * \param[in] delta_ms Maximum permitted ToA gap in ms, or 0 to disable
+ * this check; document section 2.3.3.2.
+ * \returns 0 if successful, negative on errors.
+ */
+int osmo_twjit_config_set_start_max_delta(struct osmo_twjit_config *conf,
+ uint16_t delta_ms)
+{
+ conf->start_max_delta = delta_ms;
+ return 0;
+}
+
+/*! Non-vty function for marker-handling setting
+ *
+ * \param[in] conf twjit config instance to operate on.
+ * \param[in] hom Handover on marker if true, ignore marker bit if false.
+ * \returns 0 if successful, negative on errors.
+ */
+int osmo_twjit_config_set_handover_on_marker(struct osmo_twjit_config *conf,
+ bool hom)
+{
+ conf->handover_on_marker = hom;
+ return 0;
+}
+
+/*! @} */
To view, visit change 39280. To unsubscribe, or for help writing mail filters, visit settings.