diff --git a/tests/Makefile.am b/tests/Makefile.am index 555899c..8f53787 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -13,6 +13,7 @@ AM_CFLAGS = \ check_PROGRAMS = \ procqueue/pq_test \ io/pq_file_test \ + io/pq_rtp_test \ $(NULL) procqueue_pq_test_SOURCES = procqueue/pq_test.c @@ -31,6 +32,14 @@ io_pq_file_test_LDADD = \ $(TALLOC_LIBS) \ $(NULL) +io_pq_rtp_test_SOURCES = io/pq_rtp_test.c +io_pq_rtp_test_LDADD = \ + $(top_builddir)/src/libosmogapk.la \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOCODEC_LIBS) \ + $(TALLOC_LIBS) \ + $(NULL) + # The `:;' works around a Bash 3.2 bug when the output is not writeable. $(srcdir)/package.m4: $(top_srcdir)/configure.ac :;{ \ @@ -58,6 +67,7 @@ EXTRA_DIST = \ EXTRA_DIST += \ procqueue/pq_test.ok \ io/pq_file_test.ok \ + io/pq_rtp_test.ok \ io/io_sample.txt \ $(NULL) diff --git a/tests/io/pq_rtp_test.c b/tests/io/pq_rtp_test.c new file mode 100644 index 0000000..cf6e2a6 --- /dev/null +++ b/tests/io/pq_rtp_test.c @@ -0,0 +1,349 @@ +/* + * This file is part of GAPK (GSM Audio Pocket Knife). + * + * (C) 2017 by Vadim Yanitskiy + * + * GAPK 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 3 of the License, or + * (at your option) any later version. + * + * GAPK 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. + * + * You should have received a copy of the GNU General Public License + * along with GAPK. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +/** + * This test is intended to check the RTP source / sink operability. + * To do this, two processing queues are being allocated: + * + * "generator": source/random -> sink/rtp + * "checker": source/rtp -> sink/checker + * + * The first one generates some amount of random bytes (payload), + * and stores them inside a buffer that is shared between both + * queues. + * + * After generation, a payload is being sent from the first + * queue via an RTP sink, and then being received by the second + * via an RTP source. + * + * As both queues do use a shared buffer, the last item of the + * second queue (named 'sink/checker') is able to compare a + * received payload with expected. + */ + +static void talloc_ctx_walk_cb(const void *chunk, int depth, + int max_depth, int is_ref, void *data) +{ + const char *chunk_name = talloc_get_name(chunk); + int spaces_cnt; + + /* Hierarchical spacing */ + for (spaces_cnt = 0; spaces_cnt < depth; spaces_cnt++) + printf(" "); + + /* Chunk info */ + printf("chunk %s: depth=%d\n", chunk_name, depth); +} + +#define RTP_TEST_BUF_LEN 128 + +struct rtp_test_state { + unsigned int payload_len; + unsigned int rtp_port; + int rtp_src_fd; + int rtp_dst_fd; + + uint8_t data[RTP_TEST_BUF_LEN]; + uint8_t *ptr; +}; + +static int src_rand_proc(void *data, uint8_t *out, const uint8_t *in, + unsigned int in_len) +{ + struct rtp_test_state *state = (struct rtp_test_state *) data; + unsigned int i; + + /* Generate a random payload */ + for (i = 0; i < state->payload_len; i++) { + uint8_t byte = rand() % 0xff; + *(state->ptr + i) = byte; + out[i] = byte; + } + + return state->payload_len; +} + +static void src_rand_exit(void *data) +{ + struct rtp_test_state *state = (struct rtp_test_state *) data; + + if (state->rtp_src_fd >= 0) { + close(state->rtp_src_fd); + state->rtp_src_fd = -1; + } +} + +static int sink_chk_proc(void *data, uint8_t *out, const uint8_t *in, + unsigned int in_len) +{ + struct rtp_test_state *state = (struct rtp_test_state *) data; + unsigned int i; + + /* Make sure we have all bytes transferred */ + if (in_len != state->payload_len) { + printf("Data length mismatch!\n"); + return -EINVAL; + } + + for (i = 0; i < in_len; i++) { + if (in[i] != *(state->ptr + i)) { + printf("Data mismatch!\n"); + return -EINVAL; + } + } + + return in_len; +} + +static void sink_chk_exit(void *data) +{ + struct rtp_test_state *state = (struct rtp_test_state *) data; + + if (state->rtp_dst_fd >= 0) { + close(state->rtp_dst_fd); + state->rtp_dst_fd = -1; + } +} + +/* Allocates: source/random -> sink/rtp */ +static int init_gen_queue(struct osmo_gapk_pq *pq, + struct rtp_test_state *state, unsigned int payload_len) +{ + int rc; + + /* Allocate memory for the 'source/random' */ + struct osmo_gapk_pq_item *src_rand = osmo_gapk_pq_add_item(pq); + if (!src_rand) + return -ENOMEM; + + /* Fill in meta information */ + src_rand->type = OSMO_GAPK_ITEM_TYPE_SOURCE; + src_rand->cat_name = "source"; + src_rand->sub_name = "random"; + + /* Set I/O buffer lengths */ + state->payload_len = payload_len; + src_rand->len_out = payload_len; + + /* Set proc / exit callbacks and state */ + src_rand->proc = &src_rand_proc; + src_rand->exit = &src_rand_exit; + src_rand->state = state; + + + /* Init connection socket */ + state->rtp_dst_fd = osmo_sock_init(AF_UNSPEC, SOCK_DGRAM, + IPPROTO_UDP, "127.0.0.1", state->rtp_port, OSMO_SOCK_F_CONNECT); + if (state->rtp_dst_fd < 0) { + printf("Could not init connection socket\n"); + return -EINVAL; + } + + /* Init an RTP sink */ + rc = osmo_gapk_pq_queue_rtp_output(pq, state->rtp_dst_fd, payload_len); + if (rc) { + printf("Could not init an RTP sink\n"); + return rc; + } + + /* Check and prepare */ + rc = osmo_gapk_pq_check(pq, 1); + if (rc) { + printf("Queue check failed\n"); + return rc; + } + + rc = osmo_gapk_pq_prepare(pq); + if (rc) { + printf("Queue preparation failed\n"); + return rc; + } + + return 0; +} + +/* Allocates: source/rtp -> sink/checker */ +static int init_chk_queue(struct osmo_gapk_pq *pq, + struct rtp_test_state *state, unsigned int payload_len) +{ + int rc; + + /* Init listening socket */ + state->rtp_src_fd = osmo_sock_init(AF_UNSPEC, SOCK_DGRAM, + IPPROTO_UDP, "127.0.0.1", 0, OSMO_SOCK_F_BIND); + if (state->rtp_src_fd < 0) { + printf("Could not init listening socket\n"); + return -EINVAL; + } + + /* Init an RTP source on any available port */ + rc = osmo_gapk_pq_queue_rtp_input(pq, state->rtp_src_fd, payload_len); + if (rc) { + printf("Could not init an RTP sink\n"); + return rc; + } + + /* Determine on which port are we listening */ + struct sockaddr_in adr_inet; + socklen_t len_inet; + + len_inet = sizeof(adr_inet); + rc = getsockname(state->rtp_src_fd, + (struct sockaddr *) &adr_inet, &len_inet); + if (rc) + return -EINVAL; + + /* Save assigned port to shared state */ + state->rtp_port = (unsigned int) ntohs(adr_inet.sin_port); + + + /* Allocate memory for the 'sink/checker' */ + struct osmo_gapk_pq_item *sink_chk = osmo_gapk_pq_add_item(pq); + if (!sink_chk) + return -ENOMEM; + + /* Fill in meta information */ + sink_chk->type = OSMO_GAPK_ITEM_TYPE_SINK; + sink_chk->cat_name = "sink"; + sink_chk->sub_name = "checker"; + + /* Set I/O buffer lengths */ + sink_chk->len_in = payload_len; + + /* Set proc / exit callbacks and state */ + sink_chk->proc = &sink_chk_proc; + sink_chk->exit = &sink_chk_exit; + sink_chk->state = state; + + /* Check and prepare */ + rc = osmo_gapk_pq_check(pq, 1); + if (rc) { + printf("Queue check failed\n"); + return rc; + } + + rc = osmo_gapk_pq_prepare(pq); + if (rc) { + printf("Queue preparation failed\n"); + return rc; + } + + return 0; +} + +static int rtp_test(struct rtp_test_state *state, unsigned int payload_len) +{ + struct osmo_gapk_pq *q_gen, *q_chk; + unsigned int i, chunks; + int rc; + + /* Allocate two queues */ + q_gen = osmo_gapk_pq_create("generator"); + q_chk = osmo_gapk_pq_create("checker"); + + /* Make sure both queues are allocated */ + if (!q_gen || !q_chk) { + rc = -ENOMEM; + goto exit; + } + + /* Init both queues: generator and checker */ + rc = init_chk_queue(q_chk, state, payload_len); + if (rc) + goto exit; + + rc = init_gen_queue(q_gen, state, payload_len); + if (rc) + goto exit; + + /* Calculate how much chunks do we have */ + chunks = RTP_TEST_BUF_LEN / payload_len; + + /* Execute both queues */ + for (i = 0; i < chunks; i++) { + /* Move data pointer */ + state->ptr = state->data + i * payload_len; + + /* Generate and send a payload */ + rc = osmo_gapk_pq_execute(q_gen); + if (rc) { + printf("Queue '%s' execution aborted on chunk %u/%u\n", + q_gen->name, i + 1, chunks); + goto exit; + } + + /* TODO: prevent test hang if nothing was being sent */ + + /* Receive and check a payload */ + rc = osmo_gapk_pq_execute(q_chk); + if (rc) { + printf("Queue '%s' execution aborted on chunk %u/%u\n", + q_gen->name, i + 1, chunks); + goto exit; + } + } + + printf("Payload len=%u check ok\n", payload_len); + +exit: + /* Deallocate both queues and data */ + osmo_gapk_pq_destroy(q_gen); + osmo_gapk_pq_destroy(q_chk); + + return rc; +} + +int main(int argc, char **argv) +{ + struct rtp_test_state state; + unsigned int len; + + /* Enable tracking the use of NULL memory contexts */ + talloc_enable_null_tracking(); + + /* Init pseudo-random generator */ + srand(time(NULL)); + + /* Perform testing with different payload size values */ + for (len = 1; len <= RTP_TEST_BUF_LEN; len *= 2) + assert(rtp_test(&state, len) == 0); + printf("\n"); + + /* Memory leak detection test */ + talloc_report_depth_cb(NULL, 0, 10, &talloc_ctx_walk_cb, NULL); + + return 0; +} diff --git a/tests/io/pq_rtp_test.ok b/tests/io/pq_rtp_test.ok new file mode 100644 index 0000000..bae485f --- /dev/null +++ b/tests/io/pq_rtp_test.ok @@ -0,0 +1,10 @@ +Payload len=1 check ok +Payload len=2 check ok +Payload len=4 check ok +Payload len=8 check ok +Payload len=16 check ok +Payload len=32 check ok +Payload len=64 check ok +Payload len=128 check ok + +chunk null_context: depth=0 diff --git a/tests/testsuite.at b/tests/testsuite.at index bffbc52..1127f2b 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -7,7 +7,7 @@ cat $abs_srcdir/procqueue/pq_test.ok > expout AT_CHECK([$abs_top_builddir/tests/procqueue/pq_test], [0], [expout]) AT_CLEANUP -AT_SETUP([pq_file]) +AT_SETUP([io/pq_file]) AT_KEYWORDS([pq_file]) cat $abs_srcdir/io/pq_file_test.ok > expout AT_CHECK([ @@ -15,3 +15,10 @@ AT_CHECK([ $abs_top_builddir/tests/io/io_sample.txt], [0], [expout]) AT_CLEANUP + +AT_SETUP([io/pq_rtp]) +AT_KEYWORDS([pq_rtp]) +cat $abs_srcdir/io/pq_rtp_test.ok > expout +AT_CHECK([ + $abs_top_builddir/tests/io/pq_rtp_test], [0], [expout]) +AT_CLEANUP