From afa5d76a55aef069b3b23ad0d6992338adf832a5 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Sat, 23 Feb 2019 12:18:33 +0100 Subject: contrib: Import BER testing tool Change-Id: I1cffa0ae959e29ec61775b13185fd1057ed7485a Signed-off-by: Sylvain Munaut --- contrib/ber/Makefile | 14 ++ contrib/ber/README | 26 +++ contrib/ber/rtp_ber.c | 461 ++++++++++++++++++++++++++++++++++++++++++++++ contrib/ber/rtp_gen_map.c | 127 +++++++++++++ 4 files changed, 628 insertions(+) create mode 100644 contrib/ber/Makefile create mode 100644 contrib/ber/README create mode 100644 contrib/ber/rtp_ber.c create mode 100644 contrib/ber/rtp_gen_map.c diff --git a/contrib/ber/Makefile b/contrib/ber/Makefile new file mode 100644 index 00000000..5063107c --- /dev/null +++ b/contrib/ber/Makefile @@ -0,0 +1,14 @@ +CC=gcc +LD=gcc +CFLAGS=`pkg-config --cflags libosmocore libosmotrau libosmocodec` -O2 -Wall -ggdb +LDLIBS=`pkg-config --libs libosmocore libosmotrau libosmocodec` -ggdb + +all: rtp_ber + +rtp_ber.o: codec_bit_class.h + +codec_bit_class.h: rtp_gen_map + ./rtp_gen_map > codec_bit_class.h + +clean: + rm -f rtp_ber rtp_gen_map codec_bit_class.h *.o diff --git a/contrib/ber/README b/contrib/ber/README new file mode 100644 index 00000000..07c4a785 --- /dev/null +++ b/contrib/ber/README @@ -0,0 +1,26 @@ +BER testing tool +---------------- + +* Check all configs (MSC/BSC/BTS) for proper codec support + - FR enabled + - EFR enabled + - AMR 12.2 enabled (and all other modes disabled !) + - Use `amr-payload octet-aligned` in BSC config + +* Check BTS config + - Disable jitter buffer : `bts N / rtp jitter-buffer 0` + +* Check BSC config + - Disable radio timeout : `network / bts n / radio-link-timeout infinite` + +* Start BER testing tool + - `./rtp_ber 4000` + +* On the MSC CLI, start a silent-call, then request GSM to test loop + - `subscriber imsi silent-call start tch/f speech-amr` + - `subscriber imsi ms-test close-loop b` + + Don't forget to terminate the loop and terminate the silent call ! + + - `subscriber imsi ms-test open-loop` + - `subscriber imsi silent-call stop` diff --git a/contrib/ber/rtp_ber.c b/contrib/ber/rtp_ber.c new file mode 100644 index 00000000..718e247e --- /dev/null +++ b/contrib/ber/rtp_ber.c @@ -0,0 +1,461 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "codec_bit_class.h" + + +struct app_state { + struct osmo_rtp_socket *rs; + + enum { + WAIT_CONN = 0, /* Wait for incoming connection */ + WAIT_LOOP, /* Wait for a somewhat valid packet to start measuring */ + RUNNING, /* Main state */ + } state; + + int pt; + + int ref_len; + uint8_t ref_bytes[GSM_FR_BYTES]; /* FR is the largest possible one */ + ubit_t ref_bits[8*GSM_FR_BYTES]; + + struct osmo_timer_list rtp_timer; + + uint16_t rx_last_seq; + uint32_t rx_last_ts; + uint32_t rx_idx; + uint32_t tx_idx; + + const int *err_tbl; /* Classification table */ + int err_frames; /* Number of accumulated frames */ + int err_cnt[3]; /* Bit error counter */ + int err_tot[3]; /* Total # bits in that class */ +}; + +#define FLOW_REG_TX_MAX_ADVANCE 200 +#define FLOW_REG_TX_MIN_ADVANCE 50 + + +const struct log_info log_info; + + +static const uint8_t amr_size_by_ft[] = { + [0] = 12, /* 4.75 */ + [1] = 13, /* 5.15 */ + [2] = 15, /* 5.9 */ + [3] = 17, /* 6.7 */ + [4] = 19, /* 7.4 */ + [5] = 20, /* 7.95 */ + [6] = 26, /* 10.2 */ + [7] = 31, /* 12.2 */ + [8] = 5, /* SID */ +}; + +static const char *amr_rate_by_ft[] = { + [0] = "4.75", + [1] = "5.15", + [2] = "5.9", + [3] = "6.7", + [4] = "7.4", + [5] = "7.95", + [6] = "10.2", + [7] = "12.2", + [8] = "SID", +}; + + +static void +_gsm_fr_gen_ref(struct app_state *as) +{ + struct osmo_prbs_state pn9; + + /* Length */ + as->ref_len = GSM_FR_BYTES; + + /* Marker */ + as->ref_bits[0] = 1; + as->ref_bits[1] = 1; + as->ref_bits[2] = 0; + as->ref_bits[3] = 1; + + /* PN */ + osmo_prbs_state_init(&pn9, &osmo_prbs9); + pn9.state = 31; + osmo_prbs_get_ubits(&as->ref_bits[4], 260, &pn9); + + /* Convert to bytes */ + osmo_ubit2pbit_ext(as->ref_bytes, 0, as->ref_bits, 0, 8*GSM_FR_BYTES, 0); + + /* Init error classes */ + as->err_tot[0] = 50; + as->err_tot[1] = 132; + as->err_tot[2] = 78; + as->err_tbl = gsm_fr_bitclass; +} + +static void +_gsm_efr_gen_ref(struct app_state *as) +{ + struct osmo_prbs_state pn9; + + /* Length */ + as->ref_len = GSM_EFR_BYTES; + + /* Marker */ + as->ref_bits[0] = 1; + as->ref_bits[1] = 1; + as->ref_bits[2] = 0; + as->ref_bits[3] = 0; + + /* PN */ + osmo_prbs_state_init(&pn9, &osmo_prbs9); + pn9.state = 31; + osmo_prbs_get_ubits(&as->ref_bits[4], 244, &pn9); + + /* Convert to bytes */ + osmo_ubit2pbit_ext(as->ref_bytes, 0, as->ref_bits, 0, 8*GSM_EFR_BYTES, 0); + + /* Init error classes */ + as->err_tot[0] = 50; + as->err_tot[1] = 125; + as->err_tot[2] = 73; + as->err_tbl = gsm_efr_bitclass; +} + +static void +_gsm_amr_gen_ref(struct app_state *as) +{ + struct osmo_prbs_state pn9; + uint8_t hdr[2]; + + /* Length */ + as->ref_len = 33; + + /* Header */ + hdr[0] = 0x70; + hdr[1] = 0x3c; + osmo_pbit2ubit_ext(as->ref_bits, 0, hdr, 0, 16, 0); + + /* PN */ + osmo_prbs_state_init(&pn9, &osmo_prbs9); + pn9.state = 31; + osmo_prbs_get_ubits(&as->ref_bits[16], 244, &pn9); + + /* Unused bits */ + as->ref_bits[260] = 0; + as->ref_bits[261] = 0; + as->ref_bits[262] = 0; + as->ref_bits[263] = 0; + + /* Convert to bytes */ + osmo_ubit2pbit_ext(as->ref_bytes, 0, as->ref_bits, 0, 264, 0); + + /* Init error classes */ + as->err_tot[0] = 81; + as->err_tot[1] = 163; + as->err_tot[2] = -1; + as->err_tbl = gsm_amr_12_2_bitclass; +} + + +static void +_gsm_gen_ref(struct app_state *as) +{ + switch (as->pt) { + case RTP_PT_GSM_FULL: + _gsm_fr_gen_ref(as); + break; + case RTP_PT_GSM_EFR: + _gsm_efr_gen_ref(as); + break; + case RTP_PT_AMR: + _gsm_amr_gen_ref(as); + break; + default: + fprintf(stderr, "[!] Unsupported payload type for BER measurement\n"); + } +} + +static int +_gsm_ber(struct app_state *as, const uint8_t *payload, unsigned int payload_len) +{ + ubit_t rx_bits[8*33]; + int err[3]; /* Class 1a, 1b, 2 */ + int ones; + int i, j; + + if (payload) { + /* Process real-payload */ + osmo_pbit2ubit_ext(rx_bits, 0, payload, 0, 8*payload_len, 0); + + err[0] = err[1] = err[2] = 0; + ones = 0; + + for (i=0; i<8*payload_len; i++) + { + j = as->err_tbl[i]; + if (j >= 0) { + err[j] += rx_bits[i] ^ as->ref_bits[i]; + ones += rx_bits[i]; + } + } + + if (ones < 32) // This frames is probably us underrunning Tx, don't use it + { + fprintf(stderr, "[w] Frame ignored as probably TX underrun %d %d\n", as->tx_idx, as->rx_idx); + return 1; + } + } else { + /* No payload -> Lost frame completely */ + err[0] = as->err_tot[0] / 2; + err[1] = as->err_tot[1] / 2; + err[2] = as->err_tot[2] / 2; + } + + if (as->state == RUNNING) + { + /* Update records */ + if (err[0] != 0) { + /* Class 1a bits bad -> Frame error */ + as->err_cnt[0]++; + } + + as->err_cnt[1] += err[1]; /* Class 1b */ + as->err_cnt[2] += err[2]; /* class 2 */ + + as->err_frames++; + + /* Enough for a read-out ? */ + if (as->err_frames == 200) { + printf("FBER: %4.2f C1b RBER: %5.3f C2 RBER: %5.3f\n", + 100.0f * as->err_cnt[0] / as->err_frames, + 100.0f * as->err_cnt[1] / (as->err_tot[1] * as->err_frames), + 100.0f * as->err_cnt[2] / (as->err_tot[2] * as->err_frames) + ); + memset(as->err_cnt, 0, sizeof(as->err_cnt)); + as->err_frames = 0; + } + } + + return err[0] != 0; +} + +static int +_rtp_check_payload_type(const uint8_t *payload, unsigned int payload_len) +{ + uint8_t ft; + int pt = -1; + + switch (payload_len) { + case GSM_FR_BYTES: /* FR or AMR 12.2k */ + /* Check for AMR */ + ft = (payload[1] >> 3) & 0xf; + if (ft == 7) + pt = RTP_PT_AMR; + + /* Check for FR */ + else if ((payload[0] & 0xF0) == 0xD0) + pt = RTP_PT_GSM_FULL; + + /* None of the above */ + else + fprintf(stderr, "[!] FR without 0xD0 signature or AMR with unknwon Frame Type ?!?\n"); + + break; + case GSM_EFR_BYTES: /* EFR */ + if ((payload[0] & 0xF0) != 0xC0) + fprintf(stderr, "[!] EFR without 0xC0 signature ?!?\n"); + pt = RTP_PT_GSM_EFR; + break; + case GSM_HR_BYTES: /* HR */ + pt = RTP_PT_GSM_HALF; + break; + default: /* AMR */ + { + uint8_t cmr, cmi, sti; + cmr = payload[0] >> 4; + ft = (payload[1] >> 3) & 0xf; + + if (payload_len != amr_size_by_ft[ft]+2) + fprintf(stderr, "AMR FT %u(%s) but size %u\n", + ft, amr_rate_by_ft[ft], payload_len); + + switch (ft) { + case 0: case 1: case 2: case 3: + case 4: case 5: case 6: case 7: + cmi = ft; + printf("AMR SPEECH with FT/CMI %u(%s), " + "CMR %u\n", + cmi, amr_rate_by_ft[cmi], + cmr); + break; + case 8: /* SID */ + cmi = (payload[2+4] >> 1) & 0x7; + sti = payload[2+4] & 0x10; + printf("AMR SID %s with CMI %u(%s), CMR %u(%s)\n", + sti ? "UPDATE" : "FIRST", + cmi, amr_rate_by_ft[cmi], + cmr, amr_rate_by_ft[cmr]); + break; + } + } + break; + } + + return pt; +} + +static void +rtp_timer_cb(void *priv) +{ + struct app_state *as = (struct app_state *)priv; + + /* Send at least one frame if we're not too far ahead */ + if (as->tx_idx < (as->rx_idx + FLOW_REG_TX_MAX_ADVANCE)) { + osmo_rtp_send_frame(as->rs, as->ref_bytes, as->ref_len, GSM_RTP_DURATION); + as->tx_idx++; + } + else + fprintf(stderr, "Skipped\n"); + + /* Then maybe a second one to try and catch up to RX */ + if (as->tx_idx < (as->rx_idx + FLOW_REG_TX_MIN_ADVANCE)) { + osmo_rtp_send_frame(as->rs, as->ref_bytes, as->ref_len, GSM_RTP_DURATION); + as->tx_idx++; + } + + /* Re-schedule */ + osmo_timer_schedule(&as->rtp_timer, 0, 20000); +} + +static int +rtp_seq_num_diff(uint16_t new, uint16_t old) +{ + int d = (int)new - (int)old; + while (d > 49152) + d -= 65536; + while (d < -49152) + d += 65536; + return d; +} + +static void +rtp_rx_cb(struct osmo_rtp_socket *rs, + const uint8_t *payload, unsigned int payload_len, + uint16_t seq_number, uint32_t timestamp, bool marker) +{ + struct app_state *as = (struct app_state *)rs->priv; + int pt, rc, d; + +// printf("Rx(%u, %d, %d, %d): %s\n", payload_len, seq_number, timestamp, marker, osmo_hexdump(payload, payload_len)); + + /* Identify payload */ + pt = _rtp_check_payload_type(payload, payload_len); + + /* First packet ? */ + if (as->state == WAIT_CONN) + { + /* Setup for this payload type */ + as->pt = pt; + osmo_rtp_socket_set_pt(as->rs, pt); + _gsm_gen_ref(as); + + /* Timer every 20 ms */ + osmo_timer_setup(&as->rtp_timer, rtp_timer_cb, as); + osmo_timer_add(&as->rtp_timer); + osmo_timer_schedule(&as->rtp_timer, 0, 20000); + + /* Init our time tracking */ + as->rx_last_seq = seq_number; + as->rx_last_ts = timestamp; + + /* Now we wait for a loop */ + as->state = WAIT_LOOP; + } + + /* RX sequence & timstamp tracking */ + if (rtp_seq_num_diff(seq_number, as->rx_last_seq) > 1) { + fprintf(stderr, "[!] RTP sequence number discontinuity (%d -> %d)\n", as->rx_last_seq, seq_number); + } + + d = (timestamp - as->rx_last_ts) / GSM_RTP_DURATION; + + as->rx_idx += d; + as->rx_last_seq = seq_number; + as->rx_last_ts = timestamp; + + /* Account for missing frames in BER tracking */ + if (d > 1) + { + fprintf(stderr, "[!] RTP %d missing frames assumed lost @%d\n", d-1, seq_number); + while (--d) + _gsm_ber(as, NULL, 0); + } + + /* BER analysis */ + rc = _gsm_ber(as, payload, payload_len); + + if ((as->state == WAIT_LOOP) && (rc == 0)) + as->state = RUNNING; +} + + +int main(int argc, char **argv) +{ + struct app_state _as, *as = &_as; + int rc, port; + + /* Args */ + if (argc < 2) + return -1; + + port = atoi(argv[1]); + + /* App init */ + memset(as, 0x00, sizeof(struct app_state)); + + log_init(&log_info, NULL); + osmo_rtp_init(NULL); + + /* Start auto-connect RTP socket */ + as->rs = osmo_rtp_socket_create(NULL, 0); + + as->rs->priv = as; + as->rs->rx_cb = rtp_rx_cb; + + /* Jitter buffer gets in the way, we want the raw traffic */ + osmo_rtp_socket_set_param(as->rs, OSMO_RTP_P_JIT_ADAP, 0); + osmo_rtp_socket_set_param(as->rs, OSMO_RTP_P_JITBUF, 0); + + /* Bind to requested port */ + fprintf(stderr, "[+] Binding RTP socket on port %u...\n", port); + rc = osmo_rtp_socket_bind(as->rs, "0.0.0.0", port); + if (rc < 0) { + fprintf(stderr, "[!] error binding RTP socket: %d\n", rc); + return rc; + } + + /* We 'connect' to the first source we hear from */ + osmo_rtp_socket_autoconnect(as->rs); + + /* Main loop */ + while (1) { + osmo_select_main(0); + } + + return 0; +} diff --git a/contrib/ber/rtp_gen_map.c b/contrib/ber/rtp_gen_map.c new file mode 100644 index 00000000..cbf1587f --- /dev/null +++ b/contrib/ber/rtp_gen_map.c @@ -0,0 +1,127 @@ +#include + +#include + + +static int +gen_table_fr(int *tbl) +{ + int i,j; + + tbl[0] = tbl[1] = tbl[2] = tbl[3] = -1; + + for (i=0; i<260; i++) + { + j = 4 + gsm610_bitorder[i]; + if (i < 50) + tbl[j] = 0; /* Class 1a */ + else if (i < 182) + tbl[j] = 1; /* Class 1b */ + else + tbl[j] = 2; /* Class 2 */ + } + + return GSM_FR_BYTES * 8; +} + +static int +gen_table_efr(int *tbl) +{ + int i, j, k; + + tbl[0] = tbl[1] = tbl[2] = tbl[3] = -1; + + for (i=0; i<260; i++) + { + j = gsm660_bitorder[i]; + + if (j < 71) + k = j; + else if (j < 73) + k = 71; + else if (j < 123) + k = j - 2; + else if (j < 125) + k = 119; + else if (j < 178) + k = j - 4; + else if (j < 180) + k = 172; + else if (j < 230) + k = j - 6; + else if (j < 232) + k = 222; + else if (j < 252) + k = j - 8; + else + continue; + + if (i < 50) + tbl[k] = 0; /* Class 1a */ + else if (i < 182) + tbl[k] = 1; /* Class 1b */ + else + tbl[k] = 2; /* Class 2 */ + } + + return GSM_EFR_BYTES * 8; +} + +static int +gen_table_amr_12_2(int *tbl) +{ + int i; + + for (i=0; i<16; i++) + tbl[i] = -1; + + for (i=0; i<244; i++) + tbl[i+16] = i < 81 ? 0 : 1; + + for (i=0; i<4; i++) + tbl[i+16+244] = -1; + + return 8 * 33; +} + + +static void +print_table(const char *name, int *tbl, int len) +{ + int i; + + printf("static const int %s[] = {\n", name); + + for (i=0; i