fixeria has uploaded a new patch set (#2). ( https://gerrit.osmocom.org/c/erlang/osmo-s1gw/+/40647?usp=email )
Change subject: s1gw_metrics: add ctr_list/0 and gauge_list/0
......................................................................
s1gw_metrics: add ctr_list/0 and gauge_list/0
Allow other modules to obtain lists of counters/gauges. This will
be used in a follow-up patch registering per-eNB counters.
Change-Id: I21f9b2b3dfe6b454b9dee2750cc692fd2dc13475
Related: SYS#7065
---
M src/s1gw_metrics.erl
1 file changed, 9 insertions(+), 1 deletion(-)
git pull ssh://gerrit.osmocom.org:29418/erlang/osmo-s1gw refs/changes/47/40647/2
--
To view, visit https://gerrit.osmocom.org/c/erlang/osmo-s1gw/+/40647?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newpatchset
Gerrit-Project: erlang/osmo-s1gw
Gerrit-Branch: master
Gerrit-Change-Id: I21f9b2b3dfe6b454b9dee2750cc692fd2dc13475
Gerrit-Change-Number: 40647
Gerrit-PatchSet: 2
Gerrit-Owner: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-CC: Jenkins Builder
falconia has posted comments on this change by falconia. ( https://gerrit.osmocom.org/c/libosmo-abis/+/40646?usp=email )
Change subject: trau: add TFO frame insert/extract functions
......................................................................
Set Ready For Review
--
To view, visit https://gerrit.osmocom.org/c/libosmo-abis/+/40646?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: libosmo-abis
Gerrit-Branch: master
Gerrit-Change-Id: Idf149ec682e4064f0e63d67ac757d92402f22dca
Gerrit-Change-Number: 40646
Gerrit-PatchSet: 3
Gerrit-Owner: falconia <falcon(a)freecalypso.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Comment-Date: Sat, 12 Jul 2025 08:02:43 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: No
falconia has uploaded this change for review. ( https://gerrit.osmocom.org/c/libosmo-abis/+/40646?usp=email )
Change subject: WIP: TFO frame insert/extract functions
......................................................................
WIP: TFO frame insert/extract functions
In 2025-02 functions were added for encoding and decoding TFO frames,
which are slightly modified TRAU-UL frames. Those functions are
slightly modified versions of regular TRAU frame encoding and
decoding functions, implemented in trau_frame layer. However,
a more useful API for TFO applications (TFO-equipped transcoder
implementations) is one level up: inserting a TFO frame into a block
of 160 G.711 PCM samples or extracting a frame from such sample
block. This slightly higher level is also more amenable to unit
testing: unit tests can perform decoding of TFO frames captured
from historical E1-based TRAU equipment, providing a cross-check
against actual reality and not just our own reading of the specs.
The present patch adds the desired next-level-up TFO frame insertion
and extraction functions, as well as unit tests that exercise both
the present new addition and the underlying functions added in
2025-02.
The present WIP version is incomplete: TFO frame insertion and
extraction functions are implemented, unit test programs are
implemented for both, but only the decoder unit test set is
properly integrated. Assistance is sought with integrating
unit tests for the encoder - see tests/tfo/round-trip-test.sh.
Change-Id: Idf149ec682e4064f0e63d67ac757d92402f22dca
---
M .gitignore
M include/Makefile.am
A include/osmocom/trau/tfo_frame.h
M src/Makefile.am
A src/trau/tfo_frame.c
M tests/Makefile.am
M tests/testsuite.at
A tests/tfo/enc_test_efr.in
A tests/tfo/enc_test_fr.in
A tests/tfo/enc_test_hr.in
A tests/tfo/extract_test_efr.ok
A tests/tfo/extract_test_fr.ok
A tests/tfo/extract_test_hr.ok
A tests/tfo/nokia_tcsm2/README
A tests/tfo/nokia_tcsm2/tfo-efr.hex
A tests/tfo/nokia_tcsm2/tfo-fr.hex
A tests/tfo/nokia_tcsm2/tfo-hr.hex
A tests/tfo/round-trip-test.sh
A tests/tfo/rtp2tfo.c
A tests/tfo/tfo_extr_test.c
20 files changed, 962 insertions(+), 0 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/libosmo-abis refs/changes/46/40646/1
diff --git a/.gitignore b/.gitignore
index ee4b9f8..688f868 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,6 +42,8 @@
tests/ipa_recv/ipa_recv_test
tests/raa_prime/test_dec
tests/raa_prime/test_enc
+tests/tfo/rtp2tfo
+tests/tfo/tfo_extr_test
tests/trau_pcu_ericsson/trau_pcu_ericsson_test
tests/trau_conv/rtp2trau_gen
tests/trau_conv/trau16_to_rtp
diff --git a/include/Makefile.am b/include/Makefile.am
index 16c61a5..e9820c8 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -26,6 +26,7 @@
osmocom/abis/version.h \
osmocom/trau/csd_ra2.h \
osmocom/trau/csd_raa_prime.h \
+ osmocom/trau/tfo_frame.h \
osmocom/trau/trau_frame.h \
osmocom/trau/trau_pcu_ericsson.h \
osmocom/trau/trau_rtp.h \
diff --git a/include/osmocom/trau/tfo_frame.h b/include/osmocom/trau/tfo_frame.h
new file mode 100644
index 0000000..9254dbb
--- /dev/null
+++ b/include/osmocom/trau/tfo_frame.h
@@ -0,0 +1,31 @@
+/*
+ * This header file defines the public API for working with TFO frames
+ * of GSM 08.62 (or 3GPP TS 28.062) chapter 5. These frames are overlapped
+ * onto G.711 PCM speech, replacing one or two lsbs of each PCM sample,
+ * hence the two required functions are insertion for output and extraction
+ * for input.
+ *
+ * Author: Mychaela N. Falconia <falcon(a)freecalypso.org>, 2025 - however,
+ * Mother Mychaela's contributions are NOT subject to copyright.
+ * No rights reserved, all rights relinquished.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#pragma once
+
+#include <osmocom/trau/trau_frame.h>
+
+int osmo_tfo_insert_frame_16k(uint8_t *pcm, const struct osmo_trau_frame *fr);
+int osmo_tfo_insert_frame_hr1(uint8_t *pcm, const struct osmo_trau_frame *fr);
+
+int osmo_tfo_extract_frame_16k(struct osmo_trau_frame *fr, const uint8_t *pcm);
+int osmo_tfo_extract_frame_hr1(struct osmo_trau_frame *fr, const uint8_t *pcm);
diff --git a/src/Makefile.am b/src/Makefile.am
index f25fb8b..fc5b59b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -55,6 +55,7 @@
libosmotrau_la_SOURCES = trau/csd_ra2.c \
trau/raa_prime_decode.c \
trau/raa_prime_encode.c \
+ trau/tfo_frame.c \
trau/trau_frame.c \
trau/trau_pcu_ericsson.c \
trau/trau_sync.c \
diff --git a/src/trau/tfo_frame.c b/src/trau/tfo_frame.c
new file mode 100644
index 0000000..178f026
--- /dev/null
+++ b/src/trau/tfo_frame.c
@@ -0,0 +1,149 @@
+/*
+ * This C module contains the implementation of TFO frame functions
+ * defined in <osmocom/trau/tfo_frame.h>.
+ *
+ * Author: Mychaela N. Falconia <falcon(a)freecalypso.org>, 2025 - however,
+ * Mother Mychaela's contributions are NOT subject to copyright.
+ * No rights reserved, all rights relinquished.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/trau/trau_frame.h>
+#include <osmocom/trau/tfo_frame.h>
+
+#define NUM_PCM_BYTES 160
+#define NUM_FRAME_BITS_16k 320
+#define NUM_FRAME_BITS_8k 160
+
+/*! Insert TFO-FR/EFR or AMR_TFO_16k frame into PCM sample block
+ * \param[out] pcm Block of 160 PCM samples into which the TFO frame is to be
+ * inserted
+ * \param[in] fr Decoded representation of TRAU-UL/TFO frame to be encoded
+ * \returns 0 if successful, negative on errors
+ *
+ * For each of the 160 PCM samples in the buffer pointed to by \ref pcm,
+ * the two least significant bits are replaced with TFO frame bits, while
+ * the upper 6 bits remain unchanged. The application is expected to fill
+ * this buffer with its intended G.711 A-law or mu-law output just prior
+ * to calling this function to add TFO.
+ *
+ * If the application is emitting not only TFO frames, but also embedded
+ * TFO messages, the proper sequence is: (1) set bit C5 (fr->c_bits[4])
+ * in the frame to be encoded, (2) compute and fill in CRC (only for AMR),
+ * (3) call the present function, and (4) insert the TFO message into the
+ * same PCM sample block (overwriting the lsb of samples 0, 16, 32, ..., 144)
+ * in the same way how it would be done in the absence of TFO frame output.
+ * The bits of TFO frame overwritten in the last step will be part of the
+ * sync pattern, following GSM 08.62 section 6.2 (renumbered to 7.2 in
+ * TS 28.062).
+ */
+int osmo_tfo_insert_frame_16k(uint8_t *pcm, const struct osmo_trau_frame *fr)
+{
+ ubit_t bits[NUM_FRAME_BITS_16k];
+ unsigned i, o;
+ int rc;
+
+ rc = osmo_trau_frame_encode_tfo(bits, sizeof(bits), fr);
+ if (rc < 0)
+ return rc;
+ if (rc != NUM_FRAME_BITS_16k)
+ return -EINVAL;
+ /* See TS 28.062 section 5.2.3 for bit packing order: it is the
+ * opposite of the bit order in 16 kbit/s subslots on Abis! */
+ i = 0;
+ for (o = 0; o < NUM_PCM_BYTES; o++) {
+ pcm[o] &= 0xFC;
+ pcm[o] |= bits[i++] << 0;
+ pcm[o] |= bits[i++] << 1;
+ }
+ return 0;
+}
+
+/*! Insert TFO-HRv1 frame into PCM sample block
+ * \param[out] pcm Block of 160 PCM samples into which the TFO frame is to be
+ * inserted
+ * \param[in] fr Decoded representation of TRAU-UL/TFO frame to be encoded
+ * \returns 0 if successful, negative on errors
+ *
+ * This functions is just like osmo_tfo_insert_frame_16k() except that
+ * it operates on the 1 bit wide TFO frame format of GSM 08.62 section 5.2
+ * or TS 28.062 section 5.3, which is used only for HRv1 codec.
+ * For each of the 160 PCM samples in the buffer pointed to by \ref pcm,
+ * the least significant bit is replaced with TFO frame bits, while
+ * the upper 7 bits remain unchanged.
+ */
+int osmo_tfo_insert_frame_hr1(uint8_t *pcm, const struct osmo_trau_frame *fr)
+{
+ ubit_t bits[NUM_FRAME_BITS_8k];
+ unsigned i;
+ int rc;
+
+ rc = osmo_trau_frame_encode_tfo(bits, sizeof(bits), fr);
+ if (rc < 0)
+ return rc;
+ if (rc != NUM_FRAME_BITS_8k)
+ return -EINVAL;
+ for (i = 0; i < NUM_PCM_BYTES; i++) {
+ pcm[i] &= 0xFE;
+ pcm[i] |= bits[i];
+ }
+ return 0;
+}
+
+/*! Extract TFO-FR/EFR or AMR_TFO_16k frame from PCM samples
+ * \param[out] fr Caller-allocated output data structure
+ * \param[in] pcm Block of 160 PCM samples containing an aligned TFO frame
+ * \returns 0 if successful, negative on errors
+ *
+ * The application is responsible for detecting the presence of TFO frames
+ * in the incoming G.711 PCM sample stream and locating their alignment
+ * (both functions are based on the known sync pattern); once an aligned
+ * TFO frame is located, call this function to extract and parse it into
+ * struct osmo_trau_frame.
+ */
+int osmo_tfo_extract_frame_16k(struct osmo_trau_frame *fr, const uint8_t *pcm)
+{
+ ubit_t bits[NUM_FRAME_BITS_16k];
+ unsigned i, o;
+
+ o = 0;
+ for (i = 0; i < NUM_PCM_BYTES; i++) {
+ bits[o++] = (pcm[i] >> 0) & 1;
+ bits[o++] = (pcm[i] >> 1) & 1;
+ }
+ return osmo_trau_frame_decode_tfo_16k(fr, bits);
+}
+
+/*! Extract TFO-HRv1 frame from PCM samples
+ * \param[out] fr Caller-allocated output data structure
+ * \param[in] pcm Block of 160 PCM samples containing an aligned TFO frame
+ * \returns 0 if successful, negative on errors
+ *
+ * The application is responsible for detecting the presence of TFO frames
+ * in the incoming G.711 PCM sample stream and locating their alignment
+ * (both functions are based on the known sync pattern); once an aligned
+ * TFO frame is located, call this function to extract and parse it into
+ * struct osmo_trau_frame.
+ */
+int osmo_tfo_extract_frame_hr1(struct osmo_trau_frame *fr, const uint8_t *pcm)
+{
+ ubit_t bits[NUM_FRAME_BITS_8k];
+ unsigned i;
+
+ for (i = 0; i < NUM_PCM_BYTES; i++)
+ bits[i] = pcm[i] & 1;
+ return osmo_trau_frame_decode_tfo_hr1(fr, bits);
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 73341a2..e214bda 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -8,6 +8,8 @@
ipa_recv/ipa_recv_test \
raa_prime/test_dec \
raa_prime/test_enc \
+ tfo/rtp2tfo \
+ tfo/tfo_extr_test \
trau_conv/rtp2trau_gen \
trau_conv/trau16_to_rtp \
trau_conv/trau2rtp_gen \
@@ -53,6 +55,12 @@
rtp_test_rtp_test_SOURCES = rtp_test/rtp_test.c
rtp_test_rtp_test_LDADD = $(TRAU_LA_LIBS)
+tfo_rtp2tfo_SOURCES = tfo/rtp2tfo.c trau_conv/tw5reader.c
+tfo_rtp2tfo_LDADD = $(TRAU_LA_LIBS)
+
+tfo_tfo_extr_test_SOURCES = tfo/tfo_extr_test.c
+tfo_tfo_extr_test_LDADD = $(TRAU_LA_LIBS)
+
trau_conv_rtp2trau_gen_SOURCES = trau_conv/rtp2trau_gen.c trau_conv/tw5reader.c
trau_conv_rtp2trau_gen_LDADD = $(TRAU_LA_LIBS)
@@ -94,6 +102,10 @@
ipa_recv/ipa_recv_test.ok \
raa_prime/d144-ul-input.asc raa_prime/nokia-tcsm2-atrau.hex \
rtp_test/rtp_test.ok \
+ tfo/enc_test_efr.in tfo/enc_test_fr.in tfo/enc_test_hr.in \
+ tfo/extract_test_efr.ok tfo/extract_test_fr.ok tfo/extract_test_hr.ok \
+ tfo/nokia_tcsm2/tfo-efr.hex tfo/nokia_tcsm2/tfo-fr.hex \
+ tfo/nokia_tcsm2/tfo-hr.hex \
trau_conv/efr_speech_basic.hex trau_conv/efr_speech_twts001_good.hex \
trau_conv/efr_speech_twts001_mix.hex \
trau_conv/fr_speech_basic.hex trau_conv/fr_speech_twts001_good.hex \
diff --git a/tests/testsuite.at b/tests/testsuite.at
index b3c2764..bb2196e 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -180,3 +180,21 @@
cat $abs_srcdir/trau_conv/rtp2trau_hr_ul3.ok > expout
AT_CHECK([$abs_top_builddir/tests/trau_conv/rtp2trau_gen $abs_srcdir/trau_conv/hr_speech_invsid_bits.hex hr ul], [0], [expout], [ignore])
AT_CLEANUP
+
+AT_SETUP([tfo_decode_fr])
+AT_KEYWORDS([tfo_decode_fr])
+cat $abs_srcdir/tfo/extract_test_fr.ok > expout
+AT_CHECK([$abs_top_builddir/tests/tfo/tfo_extr_test $abs_srcdir/tfo/nokia_tcsm2/tfo-fr.hex fr], [0], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([tfo_decode_efr])
+AT_KEYWORDS([tfo_decode_efr])
+cat $abs_srcdir/tfo/extract_test_efr.ok > expout
+AT_CHECK([$abs_top_builddir/tests/tfo/tfo_extr_test $abs_srcdir/tfo/nokia_tcsm2/tfo-efr.hex fr], [0], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([tfo_decode_hr])
+AT_KEYWORDS([tfo_decode_hr])
+cat $abs_srcdir/tfo/extract_test_hr.ok > expout
+AT_CHECK([$abs_top_builddir/tests/tfo/tfo_extr_test $abs_srcdir/tfo/nokia_tcsm2/tfo-hr.hex hr], [0], [expout], [ignore])
+AT_CLEANUP
diff --git a/tests/tfo/enc_test_efr.in b/tests/tfo/enc_test_efr.in
new file mode 100644
index 0000000..cb00be9
--- /dev/null
+++ b/tests/tfo/enc_test_efr.in
@@ -0,0 +1,31 @@
+# This hex file (TW-TS-005 Annex A format) contains some RTP payloads for
+# GSM-EFR codec, using the extended RTP payload format of TW-TS-001,
+# semantically corresponding to the uplink direction of a GSM call leg.
+#
+# The present example contains a mix of good and bad frames, the same kind
+# of mix that can be represented in a TRAU-UL frame stream. It has been
+# put together for the purpose of testing TFO frame encoding functions.
+
+# from ../trau_conv/efr_speech_twts001_good.hex, last 6 frames
+E0C6AF4E83850574103F7B4C9CFD1418E16A480B2CFC63CAE0361AA38E02916D
+E0C56F83F90685D665DBCDEDED15F9D9D601088BF302165EAFBA3C5939E5FB72
+E0C56F4F6A37D938405A8056E115FA97AAE0188CAE24EF5B1B420B091B56B90D
+E0C56F4F69458677D6764D8DA30D6F1EC260588D2D0A05C06B363C91F5C64110
+E0C56ECF63A65A7749EEDFAC990D0E91C1678A0DEAC6B0630931FB0C805B6D30
+E1C56FC4F404C7152210F2655D0D98C8C2E5F80E6F19BA804BBE2B1B5D7B7F93
+
+# from ../trau_conv/efr_speech_twts001_mix.hex, DTXd=0
+E0C56F510999C278B23386B76AA713CFD60D070530DE0152A4C55CC93032854F
+E0C6AF4F63A502B7560FFB36A2B5D10BFE8C3785EC5026496FC17C1E4C8D248F
+E2C56F4F69375336549EF23661157859F7D6D806B28BDCCE43BDAA9697FFFFFF
+E0C6AECF63550376301D108824DD2B1E1C9267072E6CA14017ADDB84A09202AC
+E2C56FC4F935D4963DA918A5250D0C0243B69887F2278593343A0B97F3000000
+E2C6AF5069E8041AFE322B00E10E4C24538AAAAAA20271A9E24A2C16F52ED90F
+
+# from ../trau_conv/efr_speech_twts001_mix.hex, DTXd=1
+E8C56F510999C278B23386B76AA713CFD60D070530DE0152A4C55CC93032854F
+E8C6AF4F63A502B7560FFB36A2B5D10BFE8C3785EC5026496FC17C1E4C8D248F
+EAC56F4F69375336549EF23661157859F7D6D806B28BDCCE43BDAA9697FFFFFF
+E8C6AECF63550376301D108824DD2B1E1C9267072E6CA14017ADDB84A09202AC
+EAC56FC4F935D4963DA918A5250D0C0243B69887F2278593343A0B97F3000000
+EAC6AF5069E8041AFE322B00E10E4C24538AAAAAA20271A9E24A2C16F52ED90F
diff --git a/tests/tfo/enc_test_fr.in b/tests/tfo/enc_test_fr.in
new file mode 100644
index 0000000..272ca57
--- /dev/null
+++ b/tests/tfo/enc_test_fr.in
@@ -0,0 +1,31 @@
+# This hex file (TW-TS-005 Annex A format) contains some RTP payloads for
+# GSM-FR codec, using the extended RTP payload format of TW-TS-001,
+# semantically corresponding to the uplink direction of a GSM call leg.
+#
+# The present example contains a mix of good and bad frames, the same kind
+# of mix that can be represented in a TRAU-UL frame stream. It has been
+# put together for the purpose of testing TFO frame encoding functions.
+
+# from ../trau_conv/fr_speech_twts001_good.hex, last 6 frames
+E0D8628AE8DFD7F8B8DD7C45257FF93C9C7DC165BF18A65EAD2EA07B37B9589640E5
+E0D92092A997B759E11D4D69119DF6E760AFB32F7F98F31C71A7237FF9B532824A99
+E0D81FA2E5A7B939BAE0AAC8D4D9757495CD0DEA9DB62317516555DDB47C13E2AB54
+E0D8609AE92779191CF98DA9A4B991E9A1A7A6DD5FD5BE618ACAE39FFAB91B8E38FA
+E0D92192A9575D97236551CD045936CFD08B947F9B59976C2E5A18D9DA26DC7531A6
+E1D9DD8AE6979F1706B48CE55D9AB83517505754D6F93B619308BAD7DA48E38D57C5
+
+# from ../trau_conv/fr_speech_twts001_mix.hex, DTXd=0
+E0D81FA2E19F79D7F7149226757FD98AEB6DB8DB9FF44AD49148E761FD48E492355F
+E0D85F9AE5A7B9178F48EE199BB7F80CEA6DD45C5FB4F4B8A29CDB7F16AD206B4507
+E2DA208AA60F5BD836EFFF0005E17B569DA8AEEAAAAAAB2A3374265F59B12589ACE1
+E0D8E48AE8D79D1943E44F14ECDD56C3DD0E4C29B938DA84B286BD5DFB41A56B38DF
+E2D89F9AE96760BB2A1BB1B5657F5734B1777777793B4E9391C90C9956F98A5B18AF
+E2D81EA2E5E7619576447641111119594C90E5D57DF946DB8DC3A779170C23A93AE9
+
+# from ../trau_conv/fr_speech_twts001_mix.hex, DTXd=1
+E8D81FA2E19F79D7F7149226757FD98AEB6DB8DB9FF44AD49148E761FD48E492355F
+E8D85F9AE5A7B9178F48EE199BB7F80CEA6DD45C5FB4F4B8A29CDB7F16AD206B4507
+EADA208AA60F5BD836EFFF0005E17B569DA8AEEAAAAAAB2A3374265F59B12589ACE1
+E8D8E48AE8D79D1943E44F14ECDD56C3DD0E4C29B938DA84B286BD5DFB41A56B38DF
+EAD89F9AE96760BB2A1BB1B5657F5734B1777777793B4E9391C90C9956F98A5B18AF
+EAD81EA2E5E7619576447641111119594C90E5D57DF946DB8DC3A779170C23A93AE9
diff --git a/tests/tfo/enc_test_hr.in b/tests/tfo/enc_test_hr.in
new file mode 100644
index 0000000..b79f756
--- /dev/null
+++ b/tests/tfo/enc_test_hr.in
@@ -0,0 +1,37 @@
+# This hex file (TW-TS-005 Annex B format) contains some RTP payloads for
+# GSM-HR codec, using the super-5993 RTP payload format of TW-TS-002,
+# semantically corresponding to the uplink direction of a GSM call leg.
+#
+# The present example contains a mix of good and bad frames, the same kind
+# of mix that can be represented in a TRAU-UL frame stream. This example
+# has been constructed by hand: some frames were taken from GSM 06.07 test
+# sequences and then tweaked, other are outright concoctions. The setting
+# of DTXd bit is also exercised.
+
+# good speech frame (DHF)
+000371AF61C8F2802531C000000000
+
+# good speech frame (regular)
+00B77916FC7D902F9372B569F5D17F
+# same with UFI
+02B77916FC7D902F9372B569F5D17F
+
+# valid SID frame (first SID from dtx06.cod)
+2000D9EA65FFFFFFFFFFFFFFFFFFFF
+# same with UFI
+2200D9EA65FFFFFFFFFFFFFFFFFFFF
+
+# BFI with data
+608FE9B77000000000000000000000
+# same with TAF
+618FE9B77000000000000000000000
+
+# invalid SID with payload bits
+1400D9EA65D5555555555555555555
+# same with TAF
+1500D9EA65D5555555555555555555
+
+# some of these same frames with DTXd=1
+08B77916FC7D902F9372B569F5D17F
+2800D9EA65FFFFFFFFFFFFFFFFFFFF
+688FE9B77000000000000000000000
diff --git a/tests/tfo/extract_test_efr.ok b/tests/tfo/extract_test_efr.ok
new file mode 100644
index 0000000..ee889fa
--- /dev/null
+++ b/tests/tfo/extract_test_efr.ok
@@ -0,0 +1,16 @@
+E0C085EB490FAAD603E3A18607B0C42C080480558000000000036B0000000000
+E0C085EB490FAAD603E3A18607B0C42C080480558000000000036B0000000000
+E0C085EB490FAAD603E3A18607B0C42C080480558000000000036B0000000000
+E0C085EB490FAAD603E3A18607B0C42C080480558000000000036B0000000000
+E0C085EB490FAAD603E3A18607B0C42C080480558000000000036B0000000000
+E1C085EB490FAAD603E3A18607B0C42C080480558000000000036B0000000000
+E0C085EB490FAAD603E3A18607B0C42C080480558000000000036B0000000000
+E0C085EB490FAAD603E3A18607B0C42C080480558000000000036B0000000000
+E0C085EB490FAAD603E3A18607B0C42C080480558000000000036B0000000000
+E0C085EB490FAAD603E3A18607B0C42C080480558000000000036B0000000000
+E0C085EB490FAAD603E3A18607B0C42C080480558000000000036B0000000000
+E0C085EB490FAAD603E3A18607B0C42C080480558000000000036B0000000000
+E0C085EB490FAAD603E3A18607B0C42C080480558000000000036B0000000000
+E0C085EB490FAAD603E3A18607B0C42C080480558000000000036B0000000000
+E0C085EB490FAAD603E3A18607B0C42C080480558000000000036B0000000000
+E0C085EB490FAAD603E3A18607B0C42C080480558000000000036B0000000000
diff --git a/tests/tfo/extract_test_fr.ok b/tests/tfo/extract_test_fr.ok
new file mode 100644
index 0000000..18f198d
--- /dev/null
+++ b/tests/tfo/extract_test_fr.ok
@@ -0,0 +1,16 @@
+E0D2577A1CDA50004924924924500049249249245000492492492450004923924924
+E0D2577A1CDA50004924924924500049249249245000492492492450004923924924
+E0D2577A1CDA50004924924924500049249249245000492492492450004923924924
+E0D2577A1CDA50004924924924500049249249245000492492492450004923924924
+E0D2577A1CDA50004924924924500049249249245000492492492450004923924924
+E0D2577A1CDA50004924924924500049249249245000492492492450004923924924
+E0D2577A1CDA50004924924924500049249249245000492492492450004923924924
+E0D2577A1CDA50004924924924500049249249245000492492492450004923924924
+E0D2577A1CDA50004924924924500049249249245000492492492450004923924924
+E1D2577A1CDA50004924924924500049249249245000492492492450004923924924
+E0D2577A1CDA50004924924924500049249249245000492492492450004923924924
+E0D2577A1CDA50004924924924500049249249245000492492492450004923924924
+E0D2577A1CDA50004924924924500049249249245000492492492450004923924924
+E0D2577A1CDA50004924924924500049249249245000492492492450004923924924
+E0D2577A1CDA50004924924924500049249249245000492492492450004923924924
+E0D2577A1CDA50004924924924500049249249245000492492492450004923924924
diff --git a/tests/tfo/extract_test_hr.ok b/tests/tfo/extract_test_hr.ok
new file mode 100644
index 0000000..8e011ba
--- /dev/null
+++ b/tests/tfo/extract_test_hr.ok
@@ -0,0 +1,16 @@
+000371AF61C8F2802531C000000000
+000371AF61C8F2802531C000000000
+000371AF61C8F2802531C000000000
+000371AF61C8F2802531C000000000
+000371AF61C8F2802531C000000000
+000371AF61C8F2802531C000000000
+000371AF61C8F2802531C000000000
+000371AF61C8F2802531C000000000
+000371AF61C8F2802531C000000000
+000371AF61C8F2802531C000000000
+000371AF61C8F2802531C000000000
+000371AF61C8F2802531C000000000
+000371AF61C8F2802531C000000000
+000371AF61C8F2802531C000000000
+000371AF61C8F2802531C000000000
+000371AF61C8F2802531C000000000
diff --git a/tests/tfo/nokia_tcsm2/README b/tests/tfo/nokia_tcsm2/README
new file mode 100644
index 0000000..8dfe007
--- /dev/null
+++ b/tests/tfo/nokia_tcsm2/README
@@ -0,0 +1,18 @@
+Each of the 3 tfo-*.hex files in this directory is an extract from an MSC-side
+E1 timeslot recording in a Nokia TCSM2 TFO session involving a cross-connect
+between two TRAU channels. The extracts have been chosen to begin at the point
+where the TRAU starts emitting TFO frames, thereby beginning with a series of
+TFO frames that contain an embedded TFO_TRANS message. In each experiment, one
+of the two cross-connected TRAU channels emitted two "plain" TFO frames (not
+containing embedded TFO messages) in between the initial embedded TFO_TRANS and
+the subsequent embedded TFO_REQ_L; this channel was chosen for the present
+extracts. Each extract is thus 2560 PCM samples, containing 16 aligned TFO
+frames: 5 carrying TFO_TRANS, 2 plain, 9 carrying TFO_REQ_L.
+
+In each of the three TCSM2 sessions from which these TFO frame extracts were
+taken, the Ater UL input was fed with a stream of good DHFs (decoder homing
+frames) for the respective codec, hence the TFO frame content is nothing but
+these DHFs. In the case of FR and EFR captures, one of the 16 consecutive
+frames also exhibits TAF=1. OTOH, the same does not hold for HR: the TRAU-8k
+frame format used in TFO and in most Abis implementations carries TAF only for
+BFIs and invalid SID, but not for good speech or valid SID frames.
diff --git a/tests/tfo/nokia_tcsm2/tfo-efr.hex b/tests/tfo/nokia_tcsm2/tfo-efr.hex
new file mode 100644
index 0000000..e6b03f6
--- /dev/null
+++ b/tests/tfo/nokia_tcsm2/tfo-efr.hex
@@ -0,0 +1,64 @@
+D4D4D4D4D4D4D4D4D7D5D7D7D7D7D4D4D7D4D4D5D4D6D6D7D7D6D6D5D5D6D4D5D4D4D7D7D5D6D6D6
+D5D5D7D6D6D5D4D4D5D6D7D7D4D6D7D6D5D4D6D5D4D6D5D4D4D4D7D7D6D5D7D4D5D6D5D4D5D4D6D6
+D7D4D4D4D5D4D4D4D5D5D6D4D4D4D4D5D5D5D5D5D7D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D7D5D7D5D5D7D4D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D7D5D7D7D7D7
+D4D4D4D4D4D4D4D4D7D5D7D7D7D7D4D4D7D4D4D5D4D6D6D7D7D6D6D5D5D6D4D5D5D4D7D7D5D6D6D6
+D5D5D7D6D6D5D4D4D4D6D7D7D4D6D7D6D5D4D6D5D4D6D5D4D5D4D7D7D6D5D7D4D5D6D5D4D5D4D6D6
+D6D4D4D4D5D4D4D4D5D5D6D4D4D4D4D5D5D5D5D5D7D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D7D5D6D5D5D7D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D7D5D7D7D7D7
+D4D4D4D4D4D4D4D4D7D5D7D7D7D7D4D4D6D4D4D5D4D6D6D7D7D6D6D5D5D6D4D5D5D4D7D7D5D6D6D6
+D5D5D7D6D6D5D4D4D5D6D7D7D4D6D7D6D5D4D6D5D4D6D5D4D5D4D7D7D6D5D7D4D5D6D5D4D5D4D6D6
+D6D4D4D4D5D4D4D4D5D5D6D4D4D4D4D5D4D5D5D5D7D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4
+D5D4D4D4D4D4D7D5D7D5D5D7D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D7D5D7D7D7D7
+D4D4D4D4D4D4D4D4D7D5D7D7D7D7D4D4D6D4D4D5D4D6D6D7D7D6D6D5D5D6D4D5D4D4D7D7D5D6D6D6
+D5D5D7D6D6D5D4D4D4D6D7D7D4D6D7D6D5D4D6D5D4D6D5D4D5D4D7D7D6D5D7D4D5D6D5D4D5D4D6D6
+D6D4D4D4D5D4D4D4D5D5D6D4D4D4D4D5D4D5D5D5D7D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D7D5D7D5D5D7D4D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D7D5D7D7D7D7
+D4D4D4D4D4D4D4D4D7D5D7D7D7D7D4D4D7D4D4D5D4D6D6D7D7D6D6D5D5D6D4D5D5D4D7D7D5D6D6D6
+D5D5D7D6D6D5D4D4D4D6D7D7D4D6D7D6D5D4D6D5D4D6D5D4D5D4D7D7D6D5D7D4D5D6D5D4D5D4D6D6
+D7D4D4D4D5D4D4D4D5D5D6D4D4D4D4D5D5D5D5D5D7D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D7D5D6D5D5D7D4D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D7D5D7D7D7D7
+D4D4D4D4D4D4D4D4D7D5D5D7D7D7D4D6D7D4D4D5D4D6D6D7D7D6D6D5D5D6D4D5D5D4D7D7D5D6D6D6
+D5D5D7D6D6D5D4D4D5D6D7D7D4D6D7D6D5D4D6D5D4D6D5D4D5D4D7D7D6D5D7D4D5D6D5D4D5D4D6D6
+D7D4D4D4D5D4D4D4D5D5D6D4D4D4D4D5D5D5D5D5D7D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4
+D5D4D4D4D4D4D7D5D7D5D5D7D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D7D5D7D7D7D7
+D4D4D4D4D4D4D4D4D7D5D5D7D7D7D4D4D7D4D4D5D4D6D6D7D7D6D6D5D5D6D4D5D5D4D7D7D5D6D6D6
+D5D5D7D6D6D5D4D4D5D6D7D7D4D6D7D6D5D4D6D5D4D6D5D4D5D4D7D7D6D5D7D4D5D6D5D4D5D4D6D6
+D7D4D4D4D5D4D4D4D5D5D6D4D4D4D4D5D5D5D5D5D7D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4
+D5D4D4D4D4D4D7D5D7D5D5D7D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D7D5D7D7D7D7
+D4D4D4D4D4D4D4D4D7D5D7D7D7D7D4D4D7D4D4D5D4D6D6D7D7D6D6D5D5D6D4D5D4D4D7D7D5D6D6D6
+D5D5D7D6D6D5D4D4D5D6D7D7D4D6D7D6D5D4D6D5D4D6D5D4D4D4D7D7D6D5D7D4D5D6D5D4D5D4D6D6
+D7D4D4D4D5D4D4D4D5D5D6D4D4D4D4D5D5D5D5D5D7D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D7D5D7D5D5D7D4D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D7D5D7D7D7D7
+D4D4D4D4D4D4D4D4D7D5D7D7D7D7D4D4D7D4D4D5D4D6D6D7D7D6D6D5D5D6D4D5D5D4D7D7D5D6D6D6
+D5D5D7D6D6D5D4D4D4D6D7D7D4D6D7D6D5D4D6D5D4D6D5D4D5D4D7D7D6D5D7D4D5D6D5D4D5D4D6D6
+D6D4D4D4D5D4D4D4D5D5D6D4D4D4D4D5D5D5D5D5D7D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D7D5D6D5D5D7D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D7D5D7D7D7D7
+D4D4D4D4D4D4D4D4D7D5D7D7D7D7D4D4D6D4D4D5D4D6D6D7D7D6D6D5D5D6D4D5D4D4D7D7D5D6D6D6
+D5D5D7D6D6D5D4D4D5D6D7D7D4D6D7D6D5D4D6D5D4D6D5D4D4D4D7D7D6D5D7D4D5D6D5D4D5D4D6D6
+D7D4D4D4D5D4D4D4D5D5D6D4D4D4D4D5D5D5D5D5D7D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4
+D5D4D4D4D4D4D7D5D6D5D5D7D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D7D5D7D7D7D7
+D4D4D4D4D4D4D4D4D7D5D7D7D7D7D4D4D7D4D4D5D4D6D6D7D7D6D6D5D5D6D4D5D4D4D7D7D5D6D6D6
+D5D5D7D6D6D5D4D4D5D6D7D7D4D6D7D6D5D4D6D5D4D6D5D4D4D4D7D7D6D5D7D4D5D6D5D4D5D4D6D6
+D6D4D4D4D5D4D4D4D5D5D6D4D4D4D4D5D5D5D5D5D7D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4
+D5D4D4D4D4D4D7D5D7D5D5D7D4D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D7D5D7D7D7D7
+D4D4D4D4D4D4D4D4D7D5D7D7D7D7D4D4D7D4D4D5D4D6D6D7D7D6D6D5D5D6D4D5D4D4D7D7D5D6D6D6
+D5D5D7D6D6D5D4D4D5D6D7D7D4D6D7D6D5D4D6D5D4D6D5D4D4D4D7D7D6D5D7D4D5D6D5D4D5D4D6D6
+D6D4D4D4D5D4D4D4D5D5D6D4D4D4D4D5D5D5D5D5D7D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D7D5D7D5D5D7D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D7D5D7D7D7D7
+D4D4D4D4D4D4D4D4D7D5D7D7D7D7D4D4D6D4D4D5D4D6D6D7D7D6D6D5D5D6D4D5D5D4D7D7D5D6D6D6
+D5D5D7D6D6D5D4D4D5D6D7D7D4D6D7D6D5D4D6D5D4D6D5D4D5D4D7D7D6D5D7D4D5D6D5D4D5D4D6D6
+D7D4D4D4D5D4D4D4D5D5D6D4D4D4D4D5D4D5D5D5D7D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D7D5D7D5D5D7D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D7D5D7D7D7D7
+D4D4D4D4D4D4D4D4D7D5D7D7D7D7D4D4D6D4D4D5D4D6D6D7D7D6D6D5D5D6D4D5D4D4D7D7D5D6D6D6
+D5D5D7D6D6D5D4D4D5D6D7D7D4D6D7D6D5D4D6D5D4D6D5D4D4D4D7D7D6D5D7D4D5D6D5D4D5D4D6D6
+D7D4D4D4D5D4D4D4D5D5D6D4D4D4D4D5D5D5D5D5D7D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D7D5D7D5D5D7D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D7D5D7D7D7D7
+D4D4D4D4D4D4D4D4D7D5D7D7D7D7D4D4D6D4D4D5D4D6D6D7D7D6D6D5D5D6D4D5D4D4D7D7D5D6D6D6
+D5D5D7D6D6D5D4D4D5D6D7D7D4D6D7D6D5D4D6D5D4D6D5D4D4D4D7D7D6D5D7D4D5D6D5D4D5D4D6D6
+D6D4D4D4D5D4D4D4D5D5D6D4D4D4D4D5D4D5D5D5D7D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D7D5D6D5D5D7D4D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D7D5D7D7D7D7
+D4D4D4D4D4D4D4D4D7D5D7D7D7D7D4D4D6D4D4D5D4D6D6D7D7D6D6D5D5D6D4D5D4D4D7D7D5D6D6D6
+D5D5D7D6D6D5D4D4D4D6D7D7D4D6D7D6D5D4D6D5D4D6D5D4D4D4D7D7D6D5D7D4D5D6D5D4D5D4D6D6
+D7D4D4D4D5D4D4D4D5D5D6D4D4D4D4D5D5D5D5D5D7D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D7D5D6D5D5D7D4D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D7D5D7D7D7D7
diff --git a/tests/tfo/nokia_tcsm2/tfo-fr.hex b/tests/tfo/nokia_tcsm2/tfo-fr.hex
new file mode 100644
index 0000000..2fc6846
--- /dev/null
+++ b/tests/tfo/nokia_tcsm2/tfo-fr.hex
@@ -0,0 +1,64 @@
+F4F4F4F4F4F4F4F4F5F4F7F7F7F7F4F4F7F4F5F6F7F6F6F7F7F4F4F5F7F5F7F4F6F5F6F4F4F5F5F4
+F5F4F4F4F4F6F4F5F5F5F6F4F5F6F4F5F5F5F6F4F5F6F4F5F4F5F4F5F5F4F4F4F5F4F4F6F4F5F6F4
+F7F4F5F6F4F5F6F4F7F4F5F6F4F5F6F4F5F5F5F4F4F4F4F4F5F6F4F5F6F4F5F6F4F6F4F5F6F4F5F6
+F5F6F4F5F6F4F6F6F5F4F4F4F4F4F4F5F5F5F6F4F7F5F4F5F4F5F6F4F5F6F4F5F5F5F6F5F7F7F7F7
+F4F4F4F4F4F4F4F4F5F4F7F7F7F7F4F4F7F4F5F6F7F6F6F7F7F4F4F5F7F5F7F4F7F5F6F4F4F5F5F4
+F5F4F4F4F4F6F4F5F4F5F6F4F5F6F4F5F5F5F6F4F5F6F4F5F5F5F4F5F5F4F4F4F5F4F4F6F4F5F6F4
+F6F4F5F6F4F5F6F4F7F4F5F6F4F5F6F4F5F5F5F4F4F4F4F4F5F6F4F5F6F4F5F6F4F6F4F5F6F4F5F6
+F5F6F4F5F6F4F6F6F4F4F4F4F4F4F4F5F5F5F6F4F7F5F4F5F5F5F6F4F5F6F4F5F5F5F6F5F7F7F7F7
+F4F4F4F4F4F4F4F4F5F4F7F7F7F7F4F4F6F4F5F6F7F6F6F7F7F4F4F5F7F5F7F4F7F5F6F4F4F5F5F4
+F5F4F4F4F4F6F4F5F5F5F6F4F5F6F4F5F5F5F6F4F5F6F4F5F5F5F4F5F5F4F4F4F5F4F4F6F4F5F6F4
+F6F4F5F6F4F5F6F4F7F4F5F6F4F5F6F4F4F5F5F4F4F4F4F4F5F6F4F5F6F4F5F6F5F6F4F5F6F4F5F6
+F5F6F4F5F6F4F6F6F5F4F4F4F4F4F4F5F5F5F6F4F7F5F4F5F5F5F6F4F5F6F4F5F5F5F6F5F7F7F7F7
+F4F4F4F4F4F4F4F4F5F4F7F7F7F7F4F4F6F4F5F6F7F6F6F7F7F4F4F5F7F5F7F4F6F5F6F4F4F5F5F4
+F5F4F4F4F4F6F4F5F4F5F6F4F5F6F4F5F5F5F6F4F5F6F4F5F5F5F4F5F5F4F4F4F5F4F4F6F4F5F6F4
+F6F4F5F6F4F5F6F4F7F4F5F6F4F5F6F4F4F5F5F4F4F4F4F4F5F6F4F5F6F4F5F6F4F6F4F5F6F4F5F6
+F5F6F4F5F6F4F6F6F5F4F4F4F4F4F4F5F5F5F6F4F7F5F4F5F4F5F6F4F5F6F4F5F5F5F6F5F7F7F7F7
+F4F4F4F4F4F4F4F4F5F4F7F7F7F7F4F4F7F4F5F6F7F6F6F7F7F4F4F5F7F5F7F4F7F5F6F4F4F5F5F4
+F5F4F4F4F4F6F4F5F4F5F6F4F5F6F4F5F5F5F6F4F5F6F4F5F5F5F4F5F5F4F4F4F5F4F4F6F4F5F6F4
+F7F4F5F6F4F5F6F4F7F4F5F6F4F5F6F4F5F5F5F4F4F4F4F4F5F6F4F5F6F4F5F6F4F6F4F5F6F4F5F6
+F5F6F4F5F6F4F6F6F4F4F4F4F4F4F4F5F5F5F6F4F7F5F4F5F4F5F6F4F5F6F4F5F5F5F6F5F7F7F7F7
+F4F4F4F4F4F4F4F4F5F4F5F7F7F7F4F4F7F4F5F6F7F6F6F7F7F4F4F5F7F5F7F4F7F5F6F4F4F5F5F4
+F5F4F4F4F4F6F4F5F5F5F6F4F5F6F4F5F5F5F6F4F5F6F4F5F5F5F4F5F5F4F4F4F5F4F4F6F4F5F6F4
+F7F4F5F6F4F5F6F4F7F4F5F6F4F5F6F4F5F5F5F4F4F4F4F4F5F6F4F5F6F4F5F6F5F6F4F5F6F4F5F6
+F5F6F4F5F6F4F6F6F5F4F4F4F4F4F4F5F5F5F6F4F7F5F4F5F5F5F6F4F5F6F4F5F5F5F6F5F7F7F7F7
+F4F4F4F4F4F4F4F4F5F4F5F7F7F7F4F4F7F4F5F6F7F6F6F7F7F4F4F5F7F5F7F4F7F5F6F4F4F5F5F4
+F5F4F4F4F4F6F4F5F5F5F6F4F5F6F4F5F5F5F6F4F5F6F4F5F5F5F4F5F5F4F4F4F5F4F4F6F4F5F6F4
+F7F4F5F6F4F5F6F4F7F4F5F6F4F5F6F4F5F5F5F4F4F4F4F4F5F6F4F5F6F4F5F6F5F6F4F5F6F4F5F6
+F5F6F4F5F6F4F6F6F5F4F4F4F4F4F4F5F5F5F6F4F7F5F4F5F5F5F6F4F5F6F4F5F5F5F6F5F7F7F7F7
+F4F4F4F4F4F4F4F4F5F4F7F7F7F7F4F4F7F4F5F6F7F6F6F7F7F4F4F5F7F5F7F4F6F5F6F4F4F5F5F4
+F5F4F4F4F4F6F4F5F5F5F6F4F5F6F4F5F5F5F6F4F5F6F4F5F4F5F4F5F5F4F4F4F5F4F4F6F4F5F6F4
+F7F4F5F6F4F5F6F4F7F4F5F6F4F5F6F4F5F5F5F4F4F4F4F4F5F6F4F5F6F4F5F6F4F6F4F5F6F4F5F6
+F5F6F4F5F6F4F6F6F5F4F4F4F4F4F4F5F5F5F6F4F7F5F4F5F4F5F6F4F5F6F4F5F5F5F6F5F7F7F7F7
+F4F4F4F4F4F4F4F4F5F4F7F7F7F7F4F4F7F4F5F6F7F6F6F7F7F4F4F5F7F5F7F4F7F5F6F4F4F5F5F4
+F5F4F4F4F4F6F4F5F4F5F6F4F5F6F4F5F5F5F6F4F5F6F4F5F5F5F4F5F5F4F4F4F5F4F4F6F4F5F6F4
+F6F4F5F6F4F5F6F4F7F4F5F6F4F5F6F4F5F5F5F4F4F4F4F4F5F6F4F5F6F4F5F6F4F6F4F5F6F4F5F6
+F5F6F4F5F6F4F6F6F4F4F4F4F4F4F4F5F5F5F6F4F7F5F4F5F5F5F6F4F5F6F4F5F5F5F6F5F7F7F7F7
+F4F4F4F4F4F4F4F4F5F4F7F7F7F7F4F6F6F4F5F6F7F6F6F7F7F4F4F5F7F5F7F4F6F5F6F4F4F5F5F4
+F5F4F4F4F4F6F4F5F5F5F6F4F5F6F4F5F5F5F6F4F5F6F4F5F4F5F4F5F5F4F4F4F5F4F4F6F4F5F6F4
+F7F4F5F6F4F5F6F4F7F4F5F6F4F5F6F4F5F5F5F4F4F4F4F4F5F6F4F5F6F4F5F6F5F6F4F5F6F4F5F6
+F5F6F4F5F6F4F6F6F4F4F4F4F4F4F4F5F5F5F6F4F7F5F4F5F5F5F6F4F5F6F4F5F5F5F6F5F7F7F7F7
+F4F4F4F4F4F4F4F4F5F4F7F7F7F7F4F4F7F4F5F6F7F6F6F7F7F4F4F5F7F5F7F4F6F5F6F4F4F5F5F4
+F5F4F4F4F4F6F4F5F5F5F6F4F5F6F4F5F5F5F6F4F5F6F4F5F4F5F4F5F5F4F4F4F5F4F4F6F4F5F6F4
+F6F4F5F6F4F5F6F4F7F4F5F6F4F5F6F4F5F5F5F4F4F4F4F4F5F6F4F5F6F4F5F6F5F6F4F5F6F4F5F6
+F5F6F4F5F6F4F6F6F5F4F4F4F4F4F4F5F5F5F6F4F7F5F4F5F4F5F6F4F5F6F4F5F5F5F6F5F7F7F7F7
+F4F4F4F4F4F4F4F4F5F4F7F7F7F7F4F4F7F4F5F6F7F6F6F7F7F4F4F5F7F5F7F4F6F5F6F4F4F5F5F4
+F5F4F4F4F4F6F4F5F5F5F6F4F5F6F4F5F5F5F6F4F5F6F4F5F4F5F4F5F5F4F4F4F5F4F4F6F4F5F6F4
+F6F4F5F6F4F5F6F4F7F4F5F6F4F5F6F4F5F5F5F4F4F4F4F4F5F6F4F5F6F4F5F6F4F6F4F5F6F4F5F6
+F5F6F4F5F6F4F6F6F5F4F4F4F4F4F4F5F5F5F6F4F7F5F4F5F5F5F6F4F5F6F4F5F5F5F6F5F7F7F7F7
+F4F4F4F4F4F4F4F4F5F4F7F7F7F7F4F4F6F4F5F6F7F6F6F7F7F4F4F5F7F5F7F4F7F5F6F4F4F5F5F4
+F5F4F4F4F4F6F4F5F5F5F6F4F5F6F4F5F5F5F6F4F5F6F4F5F5F5F4F5F5F4F4F4F5F4F4F6F4F5F6F4
+F6F4F5F6F4F5F6F4F7F4F5F6F4F5F6F4F4F5F5F4F4F4F4F4F5F6F4F5F6F4F5F6F5F6F4F5F6F4F5F6
+F5F6F4F5F6F4F6F6F4F4F4F4F4F4F4F5F5F5F6F4F7F5F4F5F4F5F6F4F5F6F4F5F5F5F6F5F7F7F7F7
+F4F4F4F4F4F4F4F4F5F4F7F7F7F7F4F4F6F4F5F6F7F6F6F7F7F4F4F5F7F5F7F4F6F5F6F4F4F5F5F4
+F5F4F4F4F4F6F4F5F4F5F6F4F5F6F4F5F5F5F6F4F5F6F4F5F4F5F4F5F5F4F4F4F5F4F4F6F4F5F6F4
+F6F4F5F6F4F5F6F4F7F4F5F6F4F5F6F4F5F5F5F4F4F4F4F4F5F6F4F5F6F4F5F6F4F6F4F5F6F4F5F6
+F5F6F4F5F6F4F6F6F5F4F4F4F4F4F4F5F5F5F6F4F7F5F4F5F5F5F6F4F5F6F4F5F5F5F6F5F7F7F7F7
+F4F4F4F4F4F4F4F4F5F4F7F7F7F7F4F4F7F4F5F6F7F6F6F7F7F4F4F5F7F5F7F4F6F5F6F4F4F5F5F4
+F5F4F4F4F4F6F4F5F4F5F6F4F5F6F4F5F5F5F6F4F5F6F4F5F4F5F4F5F5F4F4F4F5F4F4F6F4F5F6F4
+F6F4F5F6F4F5F6F4F7F4F5F6F4F5F6F4F4F5F5F4F4F4F4F4F5F6F4F5F6F4F5F6F4F6F4F5F6F4F5F6
+F5F6F4F5F6F4F6F6F4F4F4F4F4F4F4F5F5F5F6F4F7F5F4F5F4F5F6F4F5F6F4F5F5F5F6F5F7F7F7F7
+F4F4F4F4F4F4F4F4F5F4F7F7F7F7F4F4F6F4F5F6F7F6F6F7F7F4F4F5F7F5F7F4F6F5F6F4F4F5F5F4
+F5F4F4F4F4F6F4F5F4F5F6F4F5F6F4F5F5F5F6F4F5F6F4F5F4F5F4F5F5F4F4F4F5F4F4F6F4F5F6F4
+F6F4F5F6F4F5F6F4F7F4F5F6F4F5F6F4F5F5F5F4F4F4F4F4F5F6F4F5F6F4F5F6F5F6F4F5F6F4F5F6
+F5F6F4F5F6F4F6F6F4F4F4F4F4F4F4F5F5F5F6F4F7F5F4F5F4F5F6F4F5F6F4F5F5F5F6F5F7F7F7F7
diff --git a/tests/tfo/nokia_tcsm2/tfo-hr.hex b/tests/tfo/nokia_tcsm2/tfo-hr.hex
new file mode 100644
index 0000000..98ca6b6
--- /dev/null
+++ b/tests/tfo/nokia_tcsm2/tfo-hr.hex
@@ -0,0 +1,64 @@
+D4D4D4D4D4D4D4D4D5D4D4D4D5D5D4D4D5D5D4D4D4D5D4D4D5D4D4D4D4D5D5D4D4D5D5D5D4D4D4D5
+D5D5D4D5D4D5D5D5D5D5D4D5D5D4D4D4D5D4D5D5D5D4D4D5D4D4D4D4D5D5D5D5D5D5D5D5D4D4D5D4
+D5D5D4D4D4D4D4D4D5D4D4D4D5D4D4D5D5D4D5D4D4D5D5D4D5D4D4D5D5D5D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D5D5D5D4D5D5
+D4D4D4D4D4D4D4D4D5D4D4D4D5D5D4D4D5D5D4D4D4D5D4D4D5D4D4D4D4D5D5D4D5D5D5D5D4D4D4D5
+D5D5D4D5D4D5D5D5D4D5D4D5D5D4D4D4D5D4D5D5D5D4D4D5D5D4D4D4D5D5D5D5D5D5D5D5D4D4D5D4
+D4D5D4D4D4D4D4D4D5D4D4D4D5D4D4D5D5D4D5D4D4D5D5D4D5D4D4D5D5D5D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D5D5D5D4D5D5
+D4D4D4D4D4D4D4D4D5D4D4D4D5D5D4D4D4D5D4D4D4D5D4D4D5D4D4D4D4D5D5D4D5D5D5D5D4D4D4D5
+D5D5D4D5D4D5D5D5D5D5D4D5D5D4D4D4D5D4D5D5D5D4D4D5D5D4D4D4D5D5D5D5D5D5D5D5D4D4D5D4
+D4D5D4D4D4D4D4D4D5D4D4D4D5D4D4D5D4D4D5D4D4D5D5D4D5D4D4D5D5D5D4D4D5D4D4D4D4D4D4D4
+D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D5D5D5D4D5D5
+D4D4D4D4D4D4D4D4D5D4D4D4D5D5D4D4D4D5D4D4D4D5D4D4D5D4D4D4D4D5D5D4D4D5D5D5D4D4D4D5
+D5D5D4D5D4D5D5D5D4D5D4D5D5D4D4D4D5D4D5D5D5D4D4D5D4D4D4D4D5D5D5D5D5D5D5D5D4D4D5D4
+D5D5D4D4D4D4D4D4D5D4D4D4D5D4D4D5D4D4D5D4D4D5D5D4D5D4D4D5D5D5D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D5D5D5D4D5D5
+D4D4D4D4D4D4D4D4D5D4D4D4D5D5D4D4D4D5D4D4D4D5D4D4D5D4D4D4D4D5D5D4D5D5D5D5D4D4D4D5
+D5D5D4D5D4D5D5D5D5D5D4D5D5D4D4D4D5D4D5D5D5D4D4D5D4D4D4D4D5D5D5D5D5D5D5D5D4D4D5D4
+D5D5D4D4D4D4D4D4D5D4D4D4D5D4D4D5D5D4D5D4D4D5D5D4D5D4D4D5D5D5D4D4D5D4D4D4D4D4D4D4
+D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D5D5D5D4D5D5
+D4D4D4D4D4D4D4D4D5D4D4D4D5D4D4D4D4D5D4D4D4D5D4D4D5D4D4D4D4D5D5D4D5D5D5D5D4D4D4D5
+D5D5D4D5D4D5D5D5D5D5D4D5D5D4D4D4D5D4D5D5D5D4D4D5D5D4D4D4D5D5D5D5D5D5D5D5D4D4D5D4
+D5D5D4D4D4D4D4D4D5D4D4D4D5D4D4D5D5D4D5D4D4D5D5D4D5D4D4D5D5D5D4D4D5D4D4D4D4D4D4D4
+D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D5D5D5D4D5D5
+D4D4D4D4D4D4D4D4D5D4D4D4D5D4D4D4D4D5D4D4D4D5D4D4D5D4D4D4D4D5D5D4D5D5D5D5D4D4D4D5
+D5D5D4D5D4D5D5D5D5D5D4D5D5D4D4D4D5D4D5D5D5D4D4D5D5D4D4D4D5D5D5D5D5D5D5D5D4D4D5D4
+D5D5D4D4D4D4D4D4D5D4D4D4D5D4D4D5D5D4D5D4D4D5D5D4D5D4D4D5D5D5D4D4D5D4D4D4D4D4D4D4
+D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D5D5D5D4D5D5
+D4D4D4D4D4D4D4D4D5D4D4D4D5D5D4D4D5D5D4D4D4D5D4D4D5D4D4D4D4D5D5D4D4D5D5D5D4D4D4D5
+D5D5D4D5D4D5D5D5D5D5D4D5D5D4D4D4D5D4D5D5D5D4D4D5D4D4D4D4D5D5D5D5D5D5D5D5D4D4D5D4
+D5D5D4D4D4D4D4D4D5D4D4D4D5D4D4D5D5D4D5D4D4D5D5D4D5D4D4D5D5D5D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D5D5D5D4D5D5
+D4D4D4D4D4D4D4D4D5D4D4D4D5D5D4D4D5D5D4D4D4D5D4D4D5D4D4D4D4D5D5D4D5D5D5D5D4D4D4D5
+D5D5D4D5D4D5D5D5D4D5D4D5D5D4D4D4D5D4D5D5D5D4D4D5D5D4D4D4D5D5D5D5D5D5D5D5D4D4D5D4
+D4D5D4D4D4D4D4D4D5D4D4D4D5D4D4D5D5D4D5D4D4D5D5D4D5D4D4D5D5D5D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D5D5D5D4D5D5
+D4D4D4D4D4D4D4D4D5D4D4D4D5D5D4D4D4D5D4D4D4D5D4D4D5D4D4D4D4D5D5D4D4D5D5D5D4D4D4D5
+D5D5D4D5D4D5D5D5D5D5D4D5D5D4D4D4D5D4D5D5D5D4D4D5D4D4D4D4D5D5D5D5D5D5D5D5D4D4D5D4
+D5D5D4D4D4D4D4D4D5D4D4D4D5D4D4D5D5D4D5D4D4D5D5D4D5D4D4D5D5D5D4D4D5D4D4D4D4D4D4D4
+D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D5D5D5D4D5D5
+D4D4D4D4D4D4D4D4D5D4D4D4D5D5D4D4D5D5D4D4D4D5D4D4D5D4D4D4D4D5D5D4D4D5D5D5D4D4D4D5
+D5D5D4D5D4D5D5D5D5D5D4D5D5D4D4D4D5D4D5D5D5D4D4D5D4D4D4D4D5D5D5D5D5D5D5D5D4D4D5D4
+D4D5D4D4D4D4D4D4D5D4D4D4D5D4D4D5D5D4D5D4D4D5D5D4D5D4D4D5D5D5D4D4D5D4D4D4D4D4D4D4
+D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D5D5D5D4D5D5
+D4D4D4D4D4D4D4D4D5D4D4D4D5D5D4D4D5D5D4D4D4D5D4D4D5D4D4D4D4D5D5D4D4D5D5D5D4D4D4D5
+D5D5D4D5D4D5D5D5D5D5D4D5D5D4D4D4D5D4D5D5D5D4D4D5D4D4D4D4D5D5D5D5D5D5D5D5D4D4D5D4
+D4D5D4D4D4D4D4D4D5D4D4D4D5D4D4D5D5D4D5D4D4D5D5D4D5D4D4D5D5D5D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D5D5D5D4D5D5
+D4D4D4D4D4D4D4D4D5D4D4D4D5D5D4D4D4D5D4D4D4D5D4D4D5D4D4D4D4D5D5D4D4D5D5D5D4D4D4D5
+D5D5D4D5D4D5D5D5D4D5D4D5D5D4D4D4D5D4D5D5D5D4D4D5D5D4D4D4D5D5D5D5D5D5D5D5D4D4D5D4
+D5D5D4D4D4D4D4D4D5D4D4D4D5D4D4D5D5D4D5D4D4D5D5D4D5D4D4D5D5D5D4D4D5D4D4D4D4D4D4D4
+D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D5D5D5D4D5D5
+D4D4D4D4D4D4D4D4D5D4D4D4D5D5D4D4D4D5D4D4D4D5D4D4D5D4D4D4D4D5D5D4D4D5D5D5D4D4D4D5
+D5D5D4D5D4D5D5D5D4D5D4D5D5D4D4D4D5D4D5D5D5D4D4D5D5D4D4D4D5D5D5D5D5D5D5D5D4D4D5D4
+D4D5D4D4D4D4D4D4D5D4D4D4D5D4D4D5D5D4D5D4D4D5D5D4D5D4D4D5D5D5D4D4D5D4D4D4D4D4D4D4
+D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D5D4D5D5D5D4D5D5
+D4D4D4D4D4D4D4D4D5D4D4D4D5D5D4D4D4D5D4D4D4D5D4D4D5D4D4D4D4D5D5D4D5D5D5D5D4D4D4D5
+D5D5D4D5D4D5D5D5D4D5D4D5D5D4D4D4D5D4D5D5D5D4D4D5D4D4D4D4D5D5D5D5D5D5D5D5D4D4D5D4
+D4D5D4D4D4D4D4D4D5D4D4D4D5D4D4D5D4D4D5D4D4D5D5D4D5D4D4D5D5D5D4D4D4D4D4D4D4D4D4D4
+D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D5D5D5D4D5D5
+D4D4D4D4D4D4D4D4D5D4D4D4D5D5D4D4D4D5D4D4D4D5D4D4D5D4D4D4D4D5D5D4D4D5D5D5D4D4D4D5
+D5D5D4D5D4D5D5D5D4D5D4D5D5D4D4D4D5D4D5D5D5D4D4D5D4D4D4D4D5D5D5D5D5D5D5D5D4D4D5D4
+D5D5D4D4D4D4D4D4D5D4D4D4D5D4D4D5D4D4D5D4D4D5D5D4D5D4D4D5D5D5D4D4D5D4D4D4D4D4D4D4
+D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D4D4D4D4D4D4D4D4D4D4D4D4D4D4D5D4D5D5D5D4D5D5
diff --git a/tests/tfo/round-trip-test.sh b/tests/tfo/round-trip-test.sh
new file mode 100755
index 0000000..d847528
--- /dev/null
+++ b/tests/tfo/round-trip-test.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+# This shell script implements manual testing of round-trip encoding
+# followed by decoding of TFO frames. For each of enc_test_efr.in,
+# enc_test_fr.in and enc_test_hr.in, we convert the provided TW-TS-005
+# test sequence into TFO with rtp2tfo test program, then convert back
+# with tfo_extr_test - the latter has already been tested against
+# real TFO captures from Nokia TCSM2. The round trip conversion
+# result must be an exact match.
+
+# Assistance is sought with integrating the logic of this script
+# into testsuite.at - I am not familiar enough with Autotools test suite
+# magic.
+
+set -e
+
+if [ $# != 1 ]
+then
+ echo "usage: $0 fr|hr|efr" 1>&2
+ exit 1
+fi
+
+case "$1" in
+ fr|hr)
+ codec=$1
+ second_conv=$1
+ ;;
+ efr)
+ codec=$1
+ second_conv=fr
+ ;;
+ *)
+ echo "usage: $0 fr|hr|efr" 1>&2
+ exit 1
+ ;;
+esac
+
+./rtp2tfo enc_test_$codec.in $codec enc_test_$codec.tfo
+./tfo_extr_test enc_test_$codec.tfo $second_conv enc_test_$codec.rt
+
+case "$codec" in
+ fr|efr)
+ grep '^E' enc_test_$codec.in > enc_test_$codec.distill
+ ;;
+ hr)
+ grep '^[0-7]' enc_test_$codec.in > enc_test_$codec.distill
+ ;;
+esac
+
+diff -q enc_test_$codec.distill enc_test_$codec.rt
diff --git a/tests/tfo/rtp2tfo.c b/tests/tfo/rtp2tfo.c
new file mode 100644
index 0000000..d1d0bd6
--- /dev/null
+++ b/tests/tfo/rtp2tfo.c
@@ -0,0 +1,156 @@
+/*
+ * This program reads RTP payloads for FR/HR/EFR speech from a TW-TS-005
+ * hex file, converts them to TFO frames (modified TRAU-UL), inserts
+ * these TFO frames into dummy G.711 PCM samples, and emits the result
+ * in hex format with 80 characters per line, mimicking the format used
+ * at Themyscira Wireless for hex extracts from E1 timeslot captures.
+ * TFO frame encoding and insertion functions are exercised in the process.
+ *
+ * Author: Mychaela N. Falconia <falcon(a)freecalypso.org>, 2025 - however,
+ * Mother Mychaela's contributions are NOT subject to copyright.
+ * No rights reserved, all rights relinquished.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/trau/trau_frame.h>
+#include <osmocom/trau/tfo_frame.h>
+#include <osmocom/trau/trau_rtp.h>
+
+#include "../trau_conv/tw5reader.h"
+
+#define SAMPLES_PER_20MS 160
+
+static struct osmo_trau2rtp_state trau2rtp_st;
+static bool is_hr;
+static FILE *out_file;
+
+static void print_hex_output(const uint8_t *bin)
+{
+ int i, j, n;
+
+ n = 0;
+ for (i = 0; i < 4; i++) {
+ for (j = 0; j < 40; j++)
+ fprintf(out_file, "%02X", bin[n++]);
+ putc('\n', out_file);
+ }
+}
+
+static void process_record(const uint8_t *rtp_pl, unsigned rtp_pl_len,
+ const char *filename, unsigned lineno)
+{
+ struct osmo_trau_frame tf;
+ uint8_t pcm[SAMPLES_PER_20MS];
+ int rc;
+
+ tf.dir = OSMO_TRAU_DIR_UL;
+ rc = osmo_rtp2trau(&tf, rtp_pl, rtp_pl_len, &trau2rtp_st);
+ if (rc < 0) {
+ fprintf(stderr, "%s line %u: not valid for osmo_rtp2trau()\n",
+ filename, lineno);
+ exit(1);
+ }
+ /* our dummy G.711 PCM fill is A-law silence */
+ memset(pcm, 0xD5, SAMPLES_PER_20MS);
+ if (is_hr)
+ rc = osmo_tfo_insert_frame_hr1(pcm, &tf);
+ else
+ rc = osmo_tfo_insert_frame_16k(pcm, &tf);
+ if (rc < 0) {
+ fprintf(stderr,
+ "error: TFO frame insertion function returned %d\n",
+ rc);
+ exit(1);
+ }
+ print_hex_output(pcm);
+}
+
+static void process_file(const char *infname, const char *outfname)
+{
+ FILE *inf;
+ unsigned lineno;
+ uint8_t frame[TWTS005_MAX_FRAME];
+ unsigned frame_len;
+ int rc;
+
+ inf = fopen(infname, "r");
+ if (!inf) {
+ perror(infname);
+ exit(1);
+ }
+ if (outfname) {
+ out_file = fopen(outfname, "w");
+ if (!out_file) {
+ perror(outfname);
+ exit(1);
+ }
+ } else {
+ out_file = stdout;
+ }
+
+ lineno = 0;
+ for (;;) {
+ rc = twts005_read_frame(inf, &lineno, frame, &frame_len);
+ if (rc < 0) {
+ fprintf(stderr, "%s line %u: not valid TW-TS-005\n",
+ infname, lineno);
+ exit(1);
+ }
+ if (!rc)
+ break;
+ process_record(frame, frame_len, infname, lineno);
+ }
+
+ fclose(inf);
+ if (outfname) {
+ fclose(out_file);
+ out_file = NULL;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ char *infname, *outfname;
+
+ if (argc < 3 || argc > 4)
+ goto usage;
+ infname = argv[1];
+ if (strcmp(argv[2], "fr") == 0) {
+ trau2rtp_st.type = OSMO_TRAU16_FT_FR;
+ is_hr = false;
+ } else if (strcmp(argv[2], "hr") == 0) {
+ trau2rtp_st.type = OSMO_TRAU8_SPEECH;
+ is_hr = true;
+ } else if (strcmp(argv[2], "efr") == 0) {
+ trau2rtp_st.type = OSMO_TRAU16_FT_EFR;
+ is_hr = false;
+ } else {
+ goto usage;
+ }
+ outfname = argv[3];
+
+ process_file(infname, outfname);
+ exit(0);
+
+usage: fprintf(stderr,
+ "usage: %s input-file fr|hr|efr [output-file]\n",
+ argv[0]);
+ exit(1);
+}
diff --git a/tests/tfo/tfo_extr_test.c b/tests/tfo/tfo_extr_test.c
new file mode 100644
index 0000000..fbde071
--- /dev/null
+++ b/tests/tfo/tfo_extr_test.c
@@ -0,0 +1,185 @@
+/*
+ * This program exercises extraction of TFO frames from G.711 PCM samples,
+ * using osmo_tfo_extract_frame_16k() and osmo_tfo_extract_frame_hr1()
+ * functions. Each extracted TFO frame is then passed through osmo_trau2rtp(),
+ * converted into TW-TS-001 or TW-TS-002 RTP format, and finally emitted in
+ * TW-TS-005 hex format. The final output can then be checked for correctness
+ * with tw5a-dump and tw5b-dump utilities from Themyscira Wireless GSM codec
+ * libraries and utilities package.
+ *
+ * Author: Mychaela N. Falconia <falcon(a)freecalypso.org>, 2025 - however,
+ * Mother Mychaela's contributions are NOT subject to copyright.
+ * No rights reserved, all rights relinquished.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/trau/trau_frame.h>
+#include <osmocom/trau/tfo_frame.h>
+#include <osmocom/trau/trau_rtp.h>
+#include <osmocom/gsm/rtp_extensions.h>
+
+#define SAMPLES_PER_20MS 160
+
+static uint8_t frame_buf[SAMPLES_PER_20MS];
+static unsigned hex_chunk_count;
+static bool is_hr;
+static FILE *out_file;
+
+static void emit_hex_frame(const uint8_t *frame, unsigned nbytes)
+{
+ unsigned n;
+
+ for (n = 0; n < nbytes; n++)
+ fprintf(out_file, "%02X", frame[n]);
+ putc('\n', out_file);
+}
+
+static void process_frame(void)
+{
+ struct osmo_trau2rtp_state trau2rtp_st;
+ struct osmo_trau_frame tf;
+ uint8_t rtp_pl[40]; /* maximum RTP payload length for TW-TS-005 */
+ int rc;
+
+ memset(&trau2rtp_st, 0, sizeof(trau2rtp_st));
+ if (is_hr) {
+ trau2rtp_st.rtp_extensions = OSMO_RTP_EXT_TWTS002;
+ rc = osmo_tfo_extract_frame_hr1(&tf, frame_buf);
+ } else {
+ trau2rtp_st.rtp_extensions = OSMO_RTP_EXT_TWTS001;
+ rc = osmo_tfo_extract_frame_16k(&tf, frame_buf);
+ }
+ if (rc < 0) {
+ fprintf(stderr,
+ "error: TFO frame extraction function returned %d\n",
+ rc);
+ exit(1);
+ }
+ trau2rtp_st.type = tf.type;
+ rc = osmo_trau2rtp(rtp_pl, sizeof(rtp_pl), &tf, &trau2rtp_st);
+ if (rc < 0) {
+ fprintf(stderr, "error: osmo_trau2rtp() returned %d\n", rc);
+ exit(1);
+ }
+ if (rc == 0) {
+ /* TW-TS-005 section 4.3.1 */
+ fputs("NULL\n", out_file);
+ return;
+ }
+ emit_hex_frame(rtp_pl, rc);
+}
+
+static void process_hex_input(const char *hex_str)
+{
+ osmo_hexparse(hex_str, frame_buf + hex_chunk_count * 40, 40);
+ hex_chunk_count++;
+ if (hex_chunk_count >= 4) {
+ process_frame();
+ hex_chunk_count = 0;
+ }
+}
+
+static void process_line(char *linebuf, const char *infname, int lineno)
+{
+ char *cp = linebuf, *hex_str;
+ int ndig;
+
+ while (isspace(*cp))
+ cp++;
+ if (*cp == '\0' || *cp == '#')
+ return;
+ /* expect string of 80 hex digits */
+ hex_str = cp;
+ for (ndig = 0; ndig < 80; ndig++) {
+ if (!isxdigit(*cp))
+ goto inv;
+ cp++;
+ }
+ if (*cp) {
+ if (!isspace(*cp))
+ goto inv;
+ *cp++ = '\0';
+ }
+ /* must be end of non-comment line */
+ while (isspace(*cp))
+ cp++;
+ if (*cp != '\0' && *cp != '#')
+ goto inv;
+
+ process_hex_input(hex_str);
+ return;
+
+inv: fprintf(stderr, "%s line %d: invalid syntax\n", infname, lineno);
+ exit(1);
+}
+
+static void process_file(const char *infname, const char *outfname)
+{
+ FILE *inf;
+ char linebuf[256];
+ int lineno;
+
+ inf = fopen(infname, "r");
+ if (!inf) {
+ perror(infname);
+ exit(1);
+ }
+ if (outfname) {
+ out_file = fopen(outfname, "w");
+ if (!out_file) {
+ perror(outfname);
+ exit(1);
+ }
+ } else {
+ out_file = stdout;
+ }
+ hex_chunk_count = 0;
+ for (lineno = 1; fgets(linebuf, sizeof(linebuf), inf); lineno++)
+ process_line(linebuf, infname, lineno);
+ fclose(inf);
+ if (outfname) {
+ fclose(out_file);
+ out_file = NULL;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ const char *infname, *outfname;
+
+ if (argc < 3 || argc > 4)
+ goto usage;
+ infname = argv[1];
+ if (strcmp(argv[2], "fr") == 0)
+ is_hr = false;
+ else if (strcmp(argv[2], "hr") == 0)
+ is_hr = true;
+ else
+ goto usage;
+ outfname = argv[3];
+
+ process_file(infname, outfname);
+ exit(0);
+
+usage: fprintf(stderr, "usage: %s input-file fr|hr [output-file]\n",
+ argv[0]);
+ exit(1);
+}
--
To view, visit https://gerrit.osmocom.org/c/libosmo-abis/+/40646?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: libosmo-abis
Gerrit-Branch: master
Gerrit-Change-Id: Idf149ec682e4064f0e63d67ac757d92402f22dca
Gerrit-Change-Number: 40646
Gerrit-PatchSet: 1
Gerrit-Owner: falconia <falcon(a)freecalypso.org>
pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-msc/+/40645?usp=email )
Change subject: vty: Get rid of unneeded iu_client vty config
......................................................................
vty: Get rid of unneeded iu_client vty config
g_rab_assign_addr_enc doesn't really seem to be used at all, only
place is that file for vty related stuff which has actually no use in
osmo-msc.
Other 2 are asn1c debugging related, which can also probably be dropped.
This completelly removes references to iu_client.h in osmo-msc.
Related: OS#5487
Change-Id: I9cf83f2255e1e9aa83f3139b88ea81b2f5b686c3
---
M src/libmsc/msc_vty.c
1 file changed, 0 insertions(+), 10 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-msc refs/changes/45/40645/1
diff --git a/src/libmsc/msc_vty.c b/src/libmsc/msc_vty.c
index 2b21f70..41cb0d4 100644
--- a/src/libmsc/msc_vty.c
+++ b/src/libmsc/msc_vty.c
@@ -43,10 +43,6 @@
#include <osmocom/vty/misc.h>
#include <osmocom/vty/stats.h>
-#ifdef BUILD_IU
-#include <osmocom/ranap/iu_client.h>
-#endif
-
#include <osmocom/msc/vty.h>
#include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/gsm_subscriber.h>
@@ -815,9 +811,6 @@
}
mgcp_client_config_write(vty, " ");
-#ifdef BUILD_IU
- ranap_iu_vty_config_write(vty, " ");
-#endif
neighbor_ident_vty_write(vty);
@@ -2125,9 +2118,6 @@
/* Deprecated: Old MGCP config without pooling support in MSC node: */
mgcp_client_vty_init(msc_network, MSC_NODE, msc_network->mgw.conf);
-#ifdef BUILD_IU
- ranap_iu_vty_init(MSC_NODE, (enum ranap_nsap_addr_enc*)&msc_network->iu.rab_assign_addr_enc);
-#endif
sgs_vty_init();
smsc_vty_init(msc_network);
asci_vty_init(msc_network);
--
To view, visit https://gerrit.osmocom.org/c/osmo-msc/+/40645?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: osmo-msc
Gerrit-Branch: master
Gerrit-Change-Id: I9cf83f2255e1e9aa83f3139b88ea81b2f5b686c3
Gerrit-Change-Number: 40645
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <pespin(a)sysmocom.de>
pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/40644?usp=email )
Change subject: sgsn: Introduce test TC_stat_rnc_sctp_disconnected
......................................................................
sgsn: Introduce test TC_stat_rnc_sctp_disconnected
The commit doesn't validate everything it should, because passing the
indication from osmo-iuh's iu_client to app (osmo-sgsn) is not
supported in current API, and hence the statsd metrics are not yet
implemented in osmo-sgsn.
Still, this test already validates that osmo-sgsn is capable of
surviving an sctp link down event in IuPS, and continue to work
afterwards. IT also allows easily debugging this scenario by inspecting
pcap files, logs, etc.
Related: OS#5917
Change-Id: I35888630ea9e9005e1252677c5d42f66f84e177b
---
M sgsn/SGSN_Tests.default
M sgsn/SGSN_Tests.ttcn
M sgsn/SGSN_Tests_Iu.ttcn
M sgsn/expected-results.xml
4 files changed, 78 insertions(+), 1 deletion(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-ttcn3-hacks refs/changes/44/40644/1
diff --git a/sgsn/SGSN_Tests.default b/sgsn/SGSN_Tests.default
index e44e234..f8d3e46 100644
--- a/sgsn/SGSN_Tests.default
+++ b/sgsn/SGSN_Tests.default
@@ -17,6 +17,15 @@
*.SGSNVTY.CTRL_CLIENT_CLEANUP_LINEFEED := "yes"
*.SGSNVTY.CTRL_DETECT_CONNECTION_ESTABLISHMENT_RESULT := "yes"
*.SGSNVTY.PROMPT1 := "OsmoSGSN> "
+*.STPVTY.CTRL_MODE := "client"
+*.STPVTY.CTRL_HOSTNAME := "127.0.0.1"
+*.STPVTY.CTRL_PORTNUM := "4239"
+*.STPVTY.CTRL_LOGIN_SKIPPED := "yes"
+*.STPVTY.CTRL_DETECT_SERVER_DISCONNECTED := "yes"
+*.STPVTY.CTRL_READMODE := "buffered"
+*.STPVTY.CTRL_CLIENT_CLEANUP_LINEFEED := "yes"
+*.STPVTY.CTRL_DETECT_CONNECTION_ESTABLISHMENT_RESULT := "yes"
+*.STPVTY.PROMPT1 := "OsmoSTP> "
[MODULE_PARAMETERS]
diff --git a/sgsn/SGSN_Tests.ttcn b/sgsn/SGSN_Tests.ttcn
index b2d9731..d5f98ce 100644
--- a/sgsn/SGSN_Tests.ttcn
+++ b/sgsn/SGSN_Tests.ttcn
@@ -192,6 +192,7 @@
var GTP_Emulation_CT vc_GGSN_GTP;
port TELNETasp_PT SGSNVTY;
+ port TELNETasp_PT STPVTY;
var boolean g_initialized := false;
var boolean g_use_echo := false;
@@ -311,6 +312,26 @@
f_vty_config(SGSNVTY, "sgsn", "auth-policy remote");
}
+friend function f_init_vty_stp() runs on test_CT {
+ if (STPVTY.checkstate("Mapped")) {
+ /* skip initialization if already executed once */
+ return;
+ }
+ map(self:STPVTY, system:STPVTY);
+ f_vty_set_prompts(STPVTY, prompt_prefix := "OsmoSTP");
+ f_vty_transceive(STPVTY, "enable");
+}
+
+friend function f_vty_stp_sgsn_asp_cmd(charstring cmd) runs on test_CT
+{
+ var charstring asp_name := "sgsn0-0";
+ var charstring loc_port := int2str(1905);
+ var charstring rem_port := int2str(2905);
+ var charstring asp_proto := "m3ua";
+ var charstring asp_node := "asp " & asp_name & " " & loc_port & " " & rem_port & " " & asp_proto;
+ f_vty_config2(STPVTY, {"cs7 instance 0", asp_node}, cmd);
+}
+
private function f_vty_enable_echo_interval(boolean enable) runs on test_CT {
if (enable) {
f_vty_config(SGSNVTY, "sgsn", "ggsn 0 echo-interval " & int2str(mp_echo_interval));
diff --git a/sgsn/SGSN_Tests_Iu.ttcn b/sgsn/SGSN_Tests_Iu.ttcn
index 53a1080..3de89b6 100644
--- a/sgsn/SGSN_Tests_Iu.ttcn
+++ b/sgsn/SGSN_Tests_Iu.ttcn
@@ -416,6 +416,45 @@
f_cleanup();
}
+/* When the last local IPA/SCTP link/ASP is terminated, last AS goes inactive
+ * and a SCCP N-PCSTATE.ind (unavailable) should arrive to upper layers.
+ * As a result, osmo-sgsn should immediately mark the RNC as unusable.
+ * In AoIP, the SCCP N-PCSTATE is triggered by instructing the STP to
+ * shutdown the ASP serving the SGSN, which will close the SCTP assoc between
+ * itself and IUT (SGSN), which should then generate an N-PCSTATE.ind.
+ */
+/* TODO: OS#5917: Once we get rid of iu_client layer and handle SCCP SAP
+ * directly in osmo-sgsn, we'll be able to track IuPS status (eg RESET FSM) like
+ * we do in osmo-msc. Once that's in place we can add stats metrics to figure
+ * out connection status and use them here.
+ * See as a reference for improvement: MSC_Tests.TC_stat_rnc_sctp_disconnected
+ */
+testcase TC_stat_rnc_sctp_disconnected() runs on test_CT {
+ var BSSGP_ConnHdlr vc_conn;
+
+ f_init();
+ f_init_vty_stp();
+ f_sleep(1.0);
+ f_vty_config(SGSNVTY, "sgsn", "encryption uea 0");
+
+ vc_conn := f_start_handler(refers(f_TC_iu_attach), testcasename(), g_gb, 1008);
+ vc_conn.done;
+
+ f_logp(SGSNVTY, "Shutting down SGSN ASP in STP");
+ f_vty_stp_sgsn_asp_cmd("shutdown");
+
+ /* Wait for AS PENDING->INACTIVE transition (T(r) timeout after 2000msec): */
+ f_sleep(3.0);
+
+ f_logp(SGSNVTY, "Restarting SGSN ASP in STP");
+ f_vty_stp_sgsn_asp_cmd("no shutdown");
+
+ f_sleep(15.0);
+
+ vc_conn := f_start_handler(refers(f_TC_iu_attach), testcasename(), g_gb, 1009);
+ vc_conn.done;
+ f_cleanup();
+}
control {
execute( TC_iu_attach() );
execute( TC_iu_attach_encr() );
@@ -429,6 +468,13 @@
execute( TC_attach_pdp_act_pmm_idle() );
execute( TC_pmm_idle_rx_mt_data() );
execute( TC_update_ctx_err_ind_from_ggsn() );
+
+ /* This "nightly" if can be removed once libosmo-sigtran (osmo-stp) > 2.1.0 is released.
+ * Currently released osmo-stp 2.1.0 ends up in in broken AS-ACTIVE+ASP-DOWN state when
+ * restarting the ASP after exiting the related asp node through VTY: */
+ if (Misc_Helpers.f_osmo_repo_is("nightly")) {
+ execute( TC_stat_rnc_sctp_disconnected() );
+ }
}
diff --git a/sgsn/expected-results.xml b/sgsn/expected-results.xml
index bdd0f29..e2e099b 100644
--- a/sgsn/expected-results.xml
+++ b/sgsn/expected-results.xml
@@ -1,5 +1,5 @@
<?xml version="1.0"?>
-<testsuite name='Titan' tests='74' failures='4' errors='0' skipped='0' inconc='0' time='MASKED'>
+<testsuite name='Titan' tests='75' failures='4' errors='0' skipped='0' inconc='0' time='MASKED'>
<testcase classname='SGSN_Tests' name='TC_attach' time='MASKED'/>
<testcase classname='SGSN_Tests' name='TC_attach_mnc3' time='MASKED'/>
<testcase classname='SGSN_Tests' name='TC_attach_umts_aka_umts_res' time='MASKED'/>
@@ -101,6 +101,7 @@
<testcase classname='SGSN_Tests_Iu' name='TC_attach_pdp_act_pmm_idle_lost_pdp_status' time='MASKED'/>
<testcase classname='SGSN_Tests_Iu' name='TC_pmm_idle_rx_mt_data' time='MASKED'/>
<testcase classname='SGSN_Tests_Iu' name='TC_update_ctx_err_ind_from_ggsn' time='MASKED'/>
+ <testcase classname='SGSN_Tests_Iu' name='TC_stat_rnc_sctp_disconnected' time='MASKED'/>
<!-- SGSN_Tests_NS (handle_sns == true) testcases start here -->
<testcase classname='SGSN_Tests_NS' name='TC_SNS_size' time='MASKED'/>
<testcase classname='SGSN_Tests_NS' name='TC_SNS_size_too_big' time='MASKED'/>
--
To view, visit https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/40644?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: osmo-ttcn3-hacks
Gerrit-Branch: master
Gerrit-Change-Id: I35888630ea9e9005e1252677c5d42f66f84e177b
Gerrit-Change-Number: 40644
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <pespin(a)sysmocom.de>
pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-msc/+/40642?usp=email )
Change subject: msc_main: Remove unnedeed include of ranap/iu_client.h
......................................................................
msc_main: Remove unnedeed include of ranap/iu_client.h
iu_client is so far only used to set up some VTY configs in msc_vty.c,
hence not needed here.
Change-Id: I4716c5229273b74ee165ebaaf0914e9eebef9e6f
---
M src/osmo-msc/msc_main.c
1 file changed, 0 insertions(+), 1 deletion(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-msc refs/changes/42/40642/1
diff --git a/src/osmo-msc/msc_main.c b/src/osmo-msc/msc_main.c
index 2435029..3c09742 100644
--- a/src/osmo-msc/msc_main.c
+++ b/src/osmo-msc/msc_main.c
@@ -76,7 +76,6 @@
#include <osmocom/msc/mncc_call.h>
#ifdef BUILD_IU
-#include <osmocom/ranap/iu_client.h>
#include <asn1c/asn_internal.h>
#endif
--
To view, visit https://gerrit.osmocom.org/c/osmo-msc/+/40642?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: osmo-msc
Gerrit-Branch: master
Gerrit-Change-Id: I4716c5229273b74ee165ebaaf0914e9eebef9e6f
Gerrit-Change-Number: 40642
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <pespin(a)sysmocom.de>