libosmo-netif/tests/osmux/osmux_input_test.c

440 lines
12 KiB
C

/* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <getopt.h>
#include <osmocom/core/select.h>
#include <osmocom/core/application.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/msgb.h>
#include <osmocom/netif/rtp.h>
#include <osmocom/netif/osmux.h>
#include <osmocom/netif/amr.h>
static uint16_t rtp_next_seq;
static uint16_t rtp_next_ts;
void *tall_ctx;
#define TIME_RTP_PKT_MS 20
#define BATCH_FACTOR 6
/* ----------------------------- */
/* Logging related stuff */
#define INT2IDX(x) (-1*(x)-1)
struct log_info_cat jibuf_test_cat[] = {
[INT2IDX(DLMUX)] = {
.name = "DLMUX",
.description = "Osmocom Osmux",
.enabled = 1, .loglevel = LOGL_DEBUG,
},
};
const struct log_info log_info = {
.filter_fn = NULL,
.cat = jibuf_test_cat,
.num_cat = ARRAY_SIZE(jibuf_test_cat),
};
/* ----------------------------- */
static void rtp_init(uint16_t seq, uint16_t ts)
{
rtp_next_seq = seq;
rtp_next_ts = ts;
}
static struct msgb *rtp_new(uint16_t seq, uint8_t timestamp, uint8_t marker)
{
struct msgb *msg;
struct rtp_hdr *rtph;
msg = msgb_alloc(1500, "rtp");
if (!msg)
exit(EXIT_FAILURE);
msgb_put(msg, sizeof(struct rtp_hdr));
rtph = (struct rtp_hdr *)msg->data;
rtph->version = RTP_VERSION;
rtph->marker = marker;
rtph->sequence = htons(seq);
rtph->timestamp = htons(timestamp);
rtph->ssrc = 0x6789;
return msg;
}
static struct msgb *rtp_next(void)
{
rtp_next_seq++;
rtp_next_ts += TIME_RTP_PKT_MS;
return rtp_new(rtp_next_seq, rtp_next_ts, 0);
}
static struct amr_hdr *rtp_append_amr(struct msgb *msg, uint8_t ft)
{
struct amr_hdr *amrh;
struct rtp_hdr *rtph = (struct rtp_hdr *)msg->data;
msgb_put(msg, sizeof(struct amr_hdr));
amrh = (struct amr_hdr *)rtph->data;
amrh->cmr = 0;
amrh->q = 1;
amrh->f = 0;
amrh->ft = ft;
msgb_put(msg, osmo_amr_bytes(amrh->ft));
return amrh;
}
static void sigalarm_handler(int foo)
{
printf("FAIL: test did not run successfully\n");
exit(EXIT_FAILURE);
}
#define clock_debug(fmt, args...) \
do { \
struct timespec ts; \
struct timeval tv; \
osmo_clock_gettime(CLOCK_MONOTONIC, &ts); \
osmo_gettimeofday(&tv, NULL); \
fprintf(stdout, "sys={%lu.%06lu}, mono={%lu.%06lu}: " fmt "\n", \
tv.tv_sec, tv.tv_usec, ts.tv_sec, ts.tv_nsec/1000, ##args); \
} while (0)
static void clock_override_enable(bool enable)
{
osmo_gettimeofday_override = enable;
osmo_clock_override_enable(CLOCK_MONOTONIC, enable);
}
static void clock_override_set(long sec, long usec)
{
struct timespec *mono;
osmo_gettimeofday_override_time.tv_sec = sec;
osmo_gettimeofday_override_time.tv_usec = usec;
mono = osmo_clock_override_gettimespec(CLOCK_MONOTONIC);
mono->tv_sec = sec;
mono->tv_nsec = usec*1000;
clock_debug("clock_override_set");
}
static void clock_override_add_debug(long sec, long usec, bool dbg)
{
osmo_gettimeofday_override_add(sec, usec);
osmo_clock_override_add(CLOCK_MONOTONIC, sec, usec*1000);
if (dbg)
clock_debug("clock_override_add");
}
#define clock_override_add(sec, usec) clock_override_add_debug(sec, usec, true)
static void test_amr_ft_change_middle_batch_osmux_deliver_cb(struct msgb *batch_msg, void *data)
{
struct osmux_hdr *osmuxh;
char buf[2048];
int n = 0;
bool *osmux_transmitted = (bool *)data;
osmux_snprintf(buf, sizeof(buf), batch_msg);
clock_debug("OSMUX message (len=%d): %s\n", batch_msg->len, buf);
/* We expect 3 batches: */
while ((osmuxh = osmux_xfrm_output_pull(batch_msg))) {
n++;
OSMO_ASSERT(osmuxh->ft == OSMUX_FT_VOICE_AMR);
OSMO_ASSERT(osmuxh->rtp_m == 0);
OSMO_ASSERT(osmuxh->amr_cmr == 0);
OSMO_ASSERT(osmuxh->amr_q == 1);
switch (n) {
case 1:
OSMO_ASSERT(osmuxh->seq == 0);
OSMO_ASSERT(osmuxh->ctr == 1);
OSMO_ASSERT(osmuxh->amr_ft == AMR_FT_2);
break;
case 2:
OSMO_ASSERT(osmuxh->seq == 1);
OSMO_ASSERT(osmuxh->ctr == 0);
OSMO_ASSERT(osmuxh->amr_ft == AMR_FT_6);
break;
case 3:
OSMO_ASSERT(osmuxh->seq == 2);
OSMO_ASSERT(osmuxh->ctr == 0);
OSMO_ASSERT(osmuxh->amr_ft == AMR_FT_1);
break;
}
}
OSMO_ASSERT(n == 3);
msgb_free(batch_msg);
*osmux_transmitted = true;
}
/* Test if an RTP pkt with changed AMR FT passed to osmux_input is properly
* processed: The current batch ends and a new batch with a new osmux header is
* appeneded to the generated packet. */
static void test_amr_ft_change_middle_batch(void)
{
struct msgb *msg;
int rc;
const uint8_t cid = 30;
bool osmux_transmitted = false;
struct osmux_in_handle *h_input;
printf("===%s===\n", __func__);
clock_override_enable(true);
clock_override_set(0, 0);
rtp_init(0, 0);
h_input = osmux_xfrm_input_alloc(tall_ctx);
osmux_xfrm_input_set_initial_seqnum(h_input, 0);
osmux_xfrm_input_set_batch_factor(h_input, 4);
osmux_xfrm_input_set_deliver_cb(h_input,
test_amr_ft_change_middle_batch_osmux_deliver_cb,
&osmux_transmitted);
osmux_xfrm_input_open_circuit(h_input, cid, false);
/* First RTP frame at t=0 */
msg = rtp_next();
rtp_append_amr(msg, AMR_FT_2);
rc = osmux_xfrm_input(h_input, msg, cid);
OSMO_ASSERT(rc == 0);
/* Second RTP frame at t=20 */
clock_override_add(0, TIME_RTP_PKT_MS*1000);
msg = rtp_next();
rtp_append_amr(msg, AMR_FT_2);
rc = osmux_xfrm_input(h_input, msg, cid);
OSMO_ASSERT(rc == 0);
/* Third RTP frame at t=40, AMR FT changes: */
clock_debug("Submit RTP with 1st AMR FT change");
clock_override_add(0, TIME_RTP_PKT_MS*1000);
msg = rtp_next();
rtp_append_amr(msg, AMR_FT_6);
rc = osmux_xfrm_input(h_input, msg, cid);
OSMO_ASSERT(rc == 0);
/* Forth RTP frame at t=60, AMR FT changes again: */
clock_debug("Submit RTP with 2nd AMR FT change");
clock_override_add(0, TIME_RTP_PKT_MS*1000);
msg = rtp_next();
rtp_append_amr(msg, AMR_FT_1);
rc = osmux_xfrm_input(h_input, msg, cid);
OSMO_ASSERT(rc == 0);
/* t=80, osmux batch is scheduled to be transmitted: */
clock_override_add(0, TIME_RTP_PKT_MS*1000);
clock_debug("Osmux frame should now be transmitted");
osmo_select_main(0);
OSMO_ASSERT(osmux_transmitted == true);
clock_debug("Closing circuit");
osmux_xfrm_input_close_circuit(h_input, cid);
talloc_free(h_input);
}
static void test_last_amr_cmr_f_q_used_osmux_deliver_cb(struct msgb *batch_msg, void *data)
{
struct osmux_hdr *osmuxh;
char buf[2048];
bool *osmux_transmitted = (bool *)data;
osmux_snprintf(buf, sizeof(buf), batch_msg);
clock_debug("OSMUX message (len=%d): %s\n", batch_msg->len, buf);
/* We expect 1 batch: */
osmuxh = osmux_xfrm_output_pull(batch_msg);
OSMO_ASSERT(osmuxh->ft == OSMUX_FT_VOICE_AMR);
/* Check CMR and Q values are the ones from the last message: */
OSMO_ASSERT(osmuxh->amr_f == 0);
OSMO_ASSERT(osmuxh->amr_q == 0);
OSMO_ASSERT(osmuxh->amr_cmr == 2);
osmuxh = osmux_xfrm_output_pull(batch_msg);
OSMO_ASSERT(osmuxh == NULL);
msgb_free(batch_msg);
*osmux_transmitted = true;
}
/* Test that fields CMR, F and Q of the last RTP packet in the batch are the
* ones set in the osmux batch header. */
static void test_last_amr_cmr_f_q_used(void)
{
struct msgb *msg;
int rc;
const uint8_t cid = 32;
bool osmux_transmitted = false;
struct amr_hdr *amrh;
struct osmux_in_handle *h_input;
printf("===%s===\n", __func__);
clock_override_enable(true);
clock_override_set(0, 0);
rtp_init(0, 0);
h_input = osmux_xfrm_input_alloc(tall_ctx);
osmux_xfrm_input_set_initial_seqnum(h_input, 0);
osmux_xfrm_input_set_batch_factor(h_input, 3);
osmux_xfrm_input_set_deliver_cb(h_input,
test_last_amr_cmr_f_q_used_osmux_deliver_cb,
&osmux_transmitted);
osmux_xfrm_input_open_circuit(h_input, cid, false);
/* First RTP frame at t=0 */
msg = rtp_next();
amrh = rtp_append_amr(msg, AMR_FT_2);
amrh->f = 1;
amrh->q = 1;
amrh->cmr = 0;
rc = osmux_xfrm_input(h_input, msg, cid);
OSMO_ASSERT(rc == 0);
/* Second RTP frame at t=20, CMR changes 0->1 */
clock_debug("Submit 2nd RTP packet, CMR changes");
clock_override_add(0, TIME_RTP_PKT_MS*1000);
msg = rtp_next();
amrh = rtp_append_amr(msg, AMR_FT_2);
amrh->f = 1;
amrh->q = 1;
amrh->cmr = 1;
rc = osmux_xfrm_input(h_input, msg, cid);
OSMO_ASSERT(rc == 0);
/* Third RTP frame at t=40, q changes 1->0, CMR changes 1->2: */
clock_debug("Submit 3rd RTP packet with Q and CMR changes");
clock_override_add(0, TIME_RTP_PKT_MS*1000);
msg = rtp_next();
amrh = rtp_append_amr(msg, AMR_FT_2);
amrh->f = 0;
amrh->q = 0;
amrh->cmr = 2;
rc = osmux_xfrm_input(h_input, msg, cid);
OSMO_ASSERT(rc == 0);
/* t=60, osmux batch is scheduled to be transmitted: */
clock_override_add(0, TIME_RTP_PKT_MS*1000);
clock_debug("Osmux frame should now be transmitted");
osmo_select_main(0);
OSMO_ASSERT(osmux_transmitted == true);
clock_debug("Closing circuit");
osmux_xfrm_input_close_circuit(h_input, cid);
talloc_free(h_input);
}
static void test_initial_osmux_seqnum_osmux_deliver_cb(struct msgb *batch_msg, void *data)
{
struct osmux_hdr *osmuxh;
char buf[2048];
bool *osmux_transmitted = (bool *)data;
osmux_snprintf(buf, sizeof(buf), batch_msg);
clock_debug("OSMUX message (len=%d): %s\n", batch_msg->len, buf);
/* We expect 1 batch: */
osmuxh = osmux_xfrm_output_pull(batch_msg);
/* Check seqnum is the one configured beforehand: */
OSMO_ASSERT(osmuxh->seq == 123);
osmuxh = osmux_xfrm_output_pull(batch_msg);
OSMO_ASSERT(osmuxh == NULL);
msgb_free(batch_msg);
*osmux_transmitted = true;
}
/* Test that the first transmitted osmux header is set according to what has been configured. */
static void test_initial_osmux_seqnum(void)
{
struct msgb *msg;
int rc;
const uint8_t cid = 33;
bool osmux_transmitted = false;
struct amr_hdr *amrh;
struct osmux_in_handle *h_input;
printf("===%s===\n", __func__);
clock_override_enable(true);
clock_override_set(0, 0);
rtp_init(0, 0);
h_input = osmux_xfrm_input_alloc(tall_ctx);
osmux_xfrm_input_set_initial_seqnum(h_input, 123);
osmux_xfrm_input_set_batch_factor(h_input, 1);
osmux_xfrm_input_set_deliver_cb(h_input,
test_initial_osmux_seqnum_osmux_deliver_cb,
&osmux_transmitted);
osmux_xfrm_input_open_circuit(h_input, cid, false);
/* First RTP frame at t=0 */
msg = rtp_next();
amrh = rtp_append_amr(msg, AMR_FT_2);
amrh->f = 1;
amrh->q = 1;
amrh->cmr = 0;
rc = osmux_xfrm_input(h_input, msg, cid);
OSMO_ASSERT(rc == 0);
/* t=20, osmux batch is scheduled to be transmitted: */
clock_debug("Submit 2nd RTP packet, CMR changes");
clock_override_add(0, TIME_RTP_PKT_MS*1000);
osmo_select_main(0);
OSMO_ASSERT(osmux_transmitted == true);
clock_debug("Closing circuit");
osmux_xfrm_input_close_circuit(h_input, cid);
talloc_free(h_input);
}
int main(int argc, char **argv)
{
if (signal(SIGALRM, sigalarm_handler) == SIG_ERR) {
perror("signal");
exit(EXIT_FAILURE);
}
tall_ctx = talloc_named_const(NULL, 1, "Root context");
msgb_talloc_ctx_init(tall_ctx, 0);
osmo_init_logging2(tall_ctx, &log_info);
log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
log_set_log_level(osmo_stderr_target, LOGL_DEBUG);
log_set_category_filter(osmo_stderr_target, DLMUX, 1, LOGL_DEBUG);
alarm(10);
test_amr_ft_change_middle_batch();
test_last_amr_cmr_f_q_used();
test_initial_osmux_seqnum();
fprintf(stdout, "OK: Test passed\n");
return EXIT_SUCCESS;
}