From 5caa7b4d468bfecb40a866d96ae53b26007dbc1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C3=ABl=20Carr=C3=A9?= Date: Thu, 15 Jan 2026 23:08:48 +0100 Subject: [PATCH] upipe_pcap_src: new pipe to read pcap files --- .github/workflows/test-suite.yaml | 5 +- examples/.gitignore | 1 + examples/Build.mk | 4 + examples/pcap.c | 111 ++++++++ include/upipe-pcap/upipe_pcap_src.h | 28 ++ lib/Build.mk | 1 + lib/upipe-pcap/Build.mk | 7 + lib/upipe-pcap/upipe_pcap_src.c | 383 ++++++++++++++++++++++++++++ tests/Build.mk | 9 + tests/upipe_pcap_src_test.c | 179 +++++++++++++ tests/upipe_pcap_src_test.pcap | Bin 0 -> 189 bytes tests/upipe_pcap_src_test.sh | 12 + tests/upipe_pcap_src_test.txt | 47 ++++ 13 files changed, 785 insertions(+), 2 deletions(-) create mode 100644 examples/pcap.c create mode 100644 include/upipe-pcap/upipe_pcap_src.h create mode 100644 lib/upipe-pcap/Build.mk create mode 100644 lib/upipe-pcap/upipe_pcap_src.c create mode 100644 tests/upipe_pcap_src_test.c create mode 100644 tests/upipe_pcap_src_test.pcap create mode 100755 tests/upipe_pcap_src_test.sh create mode 100644 tests/upipe_pcap_src_test.txt diff --git a/.github/workflows/test-suite.yaml b/.github/workflows/test-suite.yaml index 22cf7ca5d..386181723 100644 --- a/.github/workflows/test-suite.yaml +++ b/.github/workflows/test-suite.yaml @@ -120,7 +120,7 @@ jobs: LIBS: > ev asound2 x264 x265 speexdsp png freetype6 zvbi gcrypt tasn1 dvbv5 udev avcodec avformat avfilter swresample swscale gl1-mesa glu1-mesa - srt-gnutls gnutls28 ssl bearssl ebur128 dvbcsa + srt-gnutls gnutls28 ssl bearssl ebur128 dvbcsa pcap run: | if [ "${{ matrix.arch }}" != "amd64" ]; then sudo apt-get install qemu-user-binfmt @@ -135,7 +135,7 @@ jobs: - name: Install dependencies (macOS) if: runner.os == 'macOS' run: | - brew install autoconf automake libtool pkg-config nasm ffmpeg \ + brew install autoconf automake libtool pkg-config nasm ffmpeg libpcap \ freetype libebur128 libev libgcrypt libtasn1 speexdsp x264 x265 luajit BREW_PREFIX="$(brew --prefix)" set-env HOST_CC "clang" @@ -146,6 +146,7 @@ jobs: set-env ld_preload_san "$(clang --print-file-name libclang_rt.asan_osx_dynamic.dylib)" set-env UBSAN_OPTIONS "print_stacktrace=1" set-env LLVM_DWARFDUMP "dwarfdump" + set-env PKG_CONFIG_PATH "$PKG_CONFIG_PATH:$BREW_PREFIX/opt/libpcap/lib/pkgconfig" - name: Install bitstream run: | diff --git a/examples/.gitignore b/examples/.gitignore index 274551174..fb13ebe64 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -19,3 +19,4 @@ /ts_encrypt /dvbsrc /frame +/pcap diff --git a/examples/Build.mk b/examples/Build.mk index f746bcf96..141d428f2 100644 --- a/examples/Build.mk +++ b/examples/Build.mk @@ -70,6 +70,10 @@ multicatudp-src = multicatudp.c multicatudp-deps = upipe_fsink multicatudp-libs = libupipe libupipe_modules libupipe_pthread libupump_ev +noinst-targets += pcap +pcap-src = pcap.c +pcap-libs = libupipe libupipe_modules libupipe_pcap libupump_ev + noinst-targets += rist_rx rist_rx-src = rist_rx.c rist_rx-deps = upipe_udpsink upipe_rtpfb diff --git a/examples/pcap.c b/examples/pcap.c new file mode 100644 index 000000000..74d86eb2a --- /dev/null +++ b/examples/pcap.c @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2025 Open Broadcast Systems Ltd + * + * Authors: Rafaël Carré + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include "upipe/uprobe.h" +#include "upipe/uprobe_stdio.h" +#include "upipe/uprobe_prefix.h" +#include "upipe/uprobe_uref_mgr.h" +#include "upipe/uprobe_uclock.h" +#include "upipe/uprobe_upump_mgr.h" +#include "upipe/uprobe_ubuf_mem.h" +#include "upipe/umem.h" +#include "upipe/uclock.h" +#include "upipe/uclock_std.h" +#include "upipe/umem_pool.h" +#include "upipe/udict.h" +#include "upipe/udict_inline.h" +#include "upipe/uref.h" +#include "upipe/uref_std.h" +#include "upipe/upipe.h" +#include "upipe/upump.h" +#include "upump-ev/upump_ev.h" +#include "upipe-pcap/upipe_pcap_src.h" +#include "upipe-modules/upipe_null.h" + +#define UPROBE_LOG_LEVEL UPROBE_LOG_INFO +#define UMEM_POOL 512 +#define UDICT_POOL_DEPTH 500 +#define UREF_POOL_DEPTH 500 +#define UBUF_POOL_DEPTH 3000 +#define UBUF_SHARED_POOL_DEPTH 50 +#define UPUMP_POOL 10 +#define UPUMP_BLOCKER_POOL 10 + +int main(int argc, char **argv) +{ + if (argc < 2) { + printf("Usage: %s \n", argv[0]); + exit(-1); + } + + const char *input = argv[1]; + + /* structures managers */ + struct upump_mgr *upump_mgr = + upump_ev_mgr_alloc_default(UPUMP_POOL, UPUMP_BLOCKER_POOL); + assert(upump_mgr != NULL); + struct umem_mgr *umem_mgr = umem_pool_mgr_alloc_simple(UMEM_POOL); + struct udict_mgr *udict_mgr = + udict_inline_mgr_alloc(UDICT_POOL_DEPTH, umem_mgr, -1, -1); + struct uref_mgr *uref_mgr = + uref_std_mgr_alloc(UREF_POOL_DEPTH, udict_mgr, 0); + udict_mgr_release(udict_mgr); + + struct uclock *uclock = uclock_std_alloc(0); + + /* probes */ + struct uprobe *uprobe; + uprobe = uprobe_stdio_alloc(NULL, stderr, UPROBE_LOG_DEBUG); + assert(uprobe != NULL); + uprobe = uprobe_uref_mgr_alloc(uprobe, uref_mgr); + assert(uprobe != NULL); + uprobe = uprobe_upump_mgr_alloc(uprobe, upump_mgr); + assert(uprobe != NULL); + uprobe = uprobe_ubuf_mem_alloc(uprobe, umem_mgr, UBUF_POOL_DEPTH, + UBUF_SHARED_POOL_DEPTH); + assert(uprobe != NULL); + uprobe = uprobe_uclock_alloc(uprobe, uclock); + assert(uprobe != NULL); + + uref_mgr_release(uref_mgr); + upump_mgr_release(upump_mgr); + umem_mgr_release(umem_mgr); + + /* pipes */ + + struct upipe_mgr *upipe_pcap_src_mgr = upipe_pcap_src_mgr_alloc(); + struct upipe *upipe_src = upipe_void_alloc(upipe_pcap_src_mgr, + uprobe_pfx_alloc(uprobe_use(uprobe), UPROBE_LOG_DEBUG, "pcap")); + upipe_mgr_release(upipe_pcap_src_mgr); + + upipe_attach_uclock(upipe_src); + + struct upipe_mgr *upipe_null_mgr = upipe_null_mgr_alloc(); + struct upipe *upipe = upipe_void_alloc_output(upipe_src, upipe_null_mgr, + uprobe_pfx_alloc(uprobe_use(uprobe), UPROBE_LOG_DEBUG, "null")); + upipe_mgr_release(upipe_null_mgr); + upipe_release(upipe); + + if (!ubase_check(upipe_set_uri(upipe_src, input))) { + fprintf(stderr, "invalid input\n"); + return EXIT_FAILURE; + } + + uprobe_release(uprobe); + + /* main loop */ + upump_mgr_run(upump_mgr, NULL); + + uclock_release(uclock); + upipe_release(upipe_src); + + return 0; +} diff --git a/include/upipe-pcap/upipe_pcap_src.h b/include/upipe-pcap/upipe_pcap_src.h new file mode 100644 index 000000000..b3b31a009 --- /dev/null +++ b/include/upipe-pcap/upipe_pcap_src.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2025 Open Broadcast Systems Ltd + * + * Authors: Rafaël Carré + * + * SPDX-License-Identifier: MIT + */ + +/** @file + * @short Upipe module to output data from a pcap + */ + +#ifndef _UPIPE_PCAP_UPIPE_PCAP_SRC_H_ +# define _UPIPE_PCAP_UPIPE_PCAP_SRC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define UPIPE_PCAP_SRC_SIGNATURE UBASE_FOURCC('p','c','a','p') + +struct upipe_mgr *upipe_pcap_src_mgr_alloc(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/Build.mk b/lib/Build.mk index b9798cab2..322667708 100644 --- a/lib/Build.mk +++ b/lib/Build.mk @@ -19,6 +19,7 @@ subdirs = \ upipe-netmap \ upipe-openssl \ upipe-osx \ + upipe-pcap \ upipe-pthread \ upipe-speexdsp \ upipe-swresample \ diff --git a/lib/upipe-pcap/Build.mk b/lib/upipe-pcap/Build.mk new file mode 100644 index 000000000..66e1e6233 --- /dev/null +++ b/lib/upipe-pcap/Build.mk @@ -0,0 +1,7 @@ +lib-targets = libupipe_pcap + +libupipe_pcap-desc = pcap file source +libupipe_pcap-so-version = 1.0.0 +libupipe_pcap-includes = upipe_pcap_src.h +libupipe_pcap-src = upipe_pcap_src.c +libupipe_pcap-libs = libupipe libpcap bitstream diff --git a/lib/upipe-pcap/upipe_pcap_src.c b/lib/upipe-pcap/upipe_pcap_src.c new file mode 100644 index 000000000..e33118aaf --- /dev/null +++ b/lib/upipe-pcap/upipe_pcap_src.c @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2025 Open Broadcast Systems Ltd + * + * Authors: Rafaël Carré + * + * SPDX-License-Identifier: MIT + */ + +/** @file + * @short Upipe module to parse pcap files + */ + +/* TODO: + - avoid memcpy? custom ubuf_mgr to delay pcap_close? + - filtering? pcap_setfilter() or something else + - set uref source ip? + */ + +#include + +#include "upipe/upipe.h" +#include "upipe/upump.h" +#include "upipe/uref_block.h" +#include "upipe/uref_clock.h" +#include "upipe/uclock.h" +#include "upipe/uref_block_flow.h" +#include "upipe/upipe_helper_upipe.h" +#include "upipe/upipe_helper_urefcount.h" +#include "upipe/upipe_helper_void.h" +#include "upipe/upipe_helper_upump_mgr.h" +#include "upipe/upipe_helper_upump.h" +#include "upipe/upipe_helper_output.h" +#include "upipe/upipe_helper_uref_mgr.h" +#include "upipe/upipe_helper_ubuf_mgr.h" +#include "upipe/upipe_helper_uclock.h" +#include "upipe-pcap/upipe_pcap_src.h" + +#include + +#include +#include +#include + +struct upipe_pcap_src { + /** refcount management structure */ + struct urefcount urefcount; + + /* output stuff */ + /** pipe acting as output */ + struct upipe *output; + /** output flow definition packet */ + struct uref *flow_def; + /** output state */ + enum upipe_helper_output_state output_state; + /** list of output requests */ + struct uchain request_list; + + /** upump manager */ + struct upump_mgr *upump_mgr; + /** read watcher */ + struct upump *upump; + + /** uref manager */ + struct uref_mgr *uref_mgr; + /** uref manager request */ + struct urequest uref_mgr_request; + + /** ubuf manager */ + struct ubuf_mgr *ubuf_mgr; + /** flow format packet */ + struct uref *flow_format; + /** ubuf manager request */ + struct urequest ubuf_mgr_request; + + /** uclock structure, if not NULL we are in live mode */ + struct uclock *uclock; + /** uclock request */ + struct urequest uclock_request; + + pcap_t *pcap; + char errbuf[PCAP_ERRBUF_SIZE]; + uint64_t cr_offset; + struct uref *uref; + + /** public upipe structure */ + struct upipe upipe; +}; + +/** @hidden */ +static int upipe_pcap_src_check(struct upipe *upipe, struct uref *flow_format); + +UPIPE_HELPER_UPIPE(upipe_pcap_src, upipe, UPIPE_PCAP_SRC_SIGNATURE) +UPIPE_HELPER_UREFCOUNT(upipe_pcap_src, urefcount, upipe_pcap_src_free) +UPIPE_HELPER_VOID(upipe_pcap_src) +UPIPE_HELPER_OUTPUT(upipe_pcap_src, output, flow_def, output_state, + request_list) +UPIPE_HELPER_UREF_MGR(upipe_pcap_src, uref_mgr, uref_mgr_request, upipe_pcap_src_check, + upipe_pcap_src_register_output_request, + upipe_pcap_src_unregister_output_request) +UPIPE_HELPER_UBUF_MGR(upipe_pcap_src, ubuf_mgr, flow_format, ubuf_mgr_request, + upipe_pcap_src_check, + upipe_pcap_src_register_output_request, + upipe_pcap_src_unregister_output_request) +UPIPE_HELPER_UPUMP_MGR(upipe_pcap_src, upump_mgr) +UPIPE_HELPER_UPUMP(upipe_pcap_src, upump, upump_mgr) +UPIPE_HELPER_UCLOCK(upipe_pcap_src, uclock, uclock_request, upipe_pcap_src_check, + upipe_pcap_src_register_output_request, + upipe_pcap_src_unregister_output_request) + +/* Skip straight to UDP data */ +static size_t upipe_pcap_skip(struct upipe *upipe, const uint8_t *buf, size_t len) +{ + if (len < ETHERNET_HEADER_LEN + ETHERNET_VLAN_LEN) + return 0; + + const uint8_t *ip = ethernet_payload((uint8_t*)buf); + + len -= ip - buf; + + if (len < IP_HEADER_MINSIZE) + return 0; + + if (len < 4 * ip_get_ihl(ip)) + return 0; + + if (ip_get_proto(ip) != IP_PROTO_UDP) + return 0; + + len -= 4 * ip_get_ihl(ip); + if (len < UDP_HEADER_SIZE) + return 0; + + return len - UDP_HEADER_SIZE; +} + +static void upipe_pcap_src_worker(struct upump *upump) +{ + struct upipe *upipe = upump_get_opaque(upump, struct upipe *); + struct upipe_pcap_src *upipe_pcap_src = upipe_pcap_src_from_upipe(upipe); + + pcap_t *pcap = upipe_pcap_src->pcap; + + if (upipe_pcap_src->uref) { + upipe_pcap_src_output(upipe, upipe_pcap_src->uref, &upipe_pcap_src->upump); + upipe_pcap_src->uref = NULL; + } + + struct pcap_pkthdr *hdr; + const u_char *data; + switch (pcap_next_ex(pcap, &hdr, &data)) { + case 1: /* no problem */ + break; + + case PCAP_ERROR_BREAK: + upipe_throw_source_end(upipe); + upipe_pcap_src_set_upump(upipe, NULL); + return; + + case PCAP_ERROR: + upipe_err_va(upipe, "%s", pcap_geterr(pcap)); + return; + + default: /* 0, PCAP_ERROR_NOT_ACTIVATED, only for live capture */ + return; + } + + size_t len = hdr->len; + if (hdr->len != hdr->caplen) { + upipe_warn_va(upipe, "Length captured (%d) is not packet len (%d)", + hdr->caplen, hdr->len); + if (len > hdr->caplen) + len = hdr->caplen; + } + + size_t udp_len = upipe_pcap_skip(upipe, data, len); + assert(udp_len < len); + if (udp_len == 0) { + return; + } + + struct uref *uref = uref_block_alloc(upipe_pcap_src->uref_mgr, + upipe_pcap_src->ubuf_mgr, + udp_len); + if (unlikely(!uref)) { + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + return; + } + + uint8_t *buffer; + int output_size = -1; + if (unlikely(!ubase_check(uref_block_write(uref, 0, &output_size, + &buffer)))) { + uref_free(uref); + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + return; + } + + memcpy(buffer, &data[len - udp_len], udp_len); + + uref_block_unmap(uref, 0); + + uint64_t ts = hdr->ts.tv_sec * UCLOCK_FREQ + hdr->ts.tv_usec * (UCLOCK_FREQ / 1000000); + + if (upipe_pcap_src->uclock) { + uint64_t now = uclock_now(upipe_pcap_src->uclock); + + if (upipe_pcap_src->cr_offset == 0) + upipe_pcap_src->cr_offset = now - ts; + + ts += upipe_pcap_src->cr_offset; + + uref_clock_set_cr_sys(uref, ts); + upipe_pcap_src->uref = uref; + + uint64_t ticks = 0; + if (now < ts) + ticks = ts - now; + + upipe_pcap_src_wait_upump(upipe, ticks, upipe_pcap_src_worker); + return; + } + + uref_clock_set_cr_sys(uref, ts); + + upipe_pcap_src_output(upipe, uref, &upipe_pcap_src->upump); +} + +static int upipe_pcap_src_check(struct upipe *upipe, struct uref *flow_format) +{ + struct upipe_pcap_src *upipe_pcap_src = upipe_pcap_src_from_upipe(upipe); + + if (flow_format) + upipe_pcap_src_store_flow_def(upipe, flow_format); + + upipe_pcap_src_check_upump_mgr(upipe); + if (!upipe_pcap_src->upump_mgr) + return UBASE_ERR_NONE; + + if (!upipe_pcap_src->uref_mgr) { + upipe_pcap_src_require_uref_mgr(upipe); + return UBASE_ERR_NONE; + } + + if (!upipe_pcap_src->ubuf_mgr) { + struct uref *flow_format = uref_block_flow_alloc_def(upipe_pcap_src->uref_mgr, NULL); + if (unlikely(!flow_format)) { + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + return UBASE_ERR_ALLOC; + } + upipe_pcap_src_require_ubuf_mgr(upipe, flow_format); + return UBASE_ERR_NONE; + } + + if (upipe_pcap_src->pcap && !upipe_pcap_src->upump) { + struct upump *upump; + upump = upump_alloc_idler(upipe_pcap_src->upump_mgr, + upipe_pcap_src_worker, upipe, + upipe->refcount); + if (unlikely(!upump)) { + upipe_throw_fatal(upipe, UBASE_ERR_UPUMP); + return UBASE_ERR_UPUMP; + } + upipe_pcap_src_set_upump(upipe, upump); + upump_start(upump); + } + + return UBASE_ERR_NONE; +} + +static int upipe_pcap_set_uri(struct upipe *upipe, const char *uri) +{ + struct upipe_pcap_src *upipe_pcap_src = upipe_pcap_src_from_upipe(upipe); + + if (!uri) { + if (upipe_pcap_src->pcap) { + pcap_close(upipe_pcap_src->pcap); + upipe_pcap_src_set_upump(upipe, NULL); + } + upipe_pcap_src->pcap = NULL; + return UBASE_ERR_NONE; + } + + upipe_pcap_src->pcap = pcap_open_offline(uri, upipe_pcap_src->errbuf); + if (!upipe_pcap_src->pcap) { + upipe_err_va(upipe, "%s: %s", uri, upipe_pcap_src->errbuf); + return UBASE_ERR_UNHANDLED; + } + + return UBASE_ERR_NONE; +} + +static int _upipe_pcap_src_control(struct upipe *upipe, int command, + va_list args) +{ + switch (command) { + case UPIPE_ATTACH_UPUMP_MGR: + upipe_pcap_src_set_upump(upipe, NULL); + return upipe_pcap_src_attach_upump_mgr(upipe); + case UPIPE_ATTACH_UCLOCK: + upipe_pcap_src_require_uclock(upipe); + return UBASE_ERR_NONE; + case UPIPE_GET_OUTPUT: + case UPIPE_SET_OUTPUT: + case UPIPE_GET_FLOW_DEF: + return upipe_pcap_src_control_output(upipe, command, args); + case UPIPE_SET_URI: + return upipe_pcap_set_uri(upipe, va_arg(args, const char *)); + default: + return UBASE_ERR_UNHANDLED; + } +} + +static int upipe_pcap_src_control(struct upipe *upipe, int command, + va_list args) +{ + UBASE_RETURN(_upipe_pcap_src_control(upipe, command, args)) + + return upipe_pcap_src_check(upipe, NULL); +} + +static void upipe_pcap_src_free(struct upipe *upipe) +{ + struct upipe_pcap_src *upipe_pcap_src = upipe_pcap_src_from_upipe(upipe); + upipe_throw_dead(upipe); + + if (upipe_pcap_src->pcap) + pcap_close(upipe_pcap_src->pcap); + if (upipe_pcap_src->uref) + uref_free(upipe_pcap_src->uref); + + upipe_pcap_src_clean_output(upipe); + upipe_pcap_src_clean_urefcount(upipe); + upipe_pcap_src_clean_ubuf_mgr(upipe); + upipe_pcap_src_clean_uref_mgr(upipe); + upipe_pcap_src_clean_upump_mgr(upipe); + upipe_pcap_src_clean_upump(upipe); + upipe_pcap_src_clean_uclock(upipe); + upipe_pcap_src_free_void(upipe); +} + +static struct upipe *upipe_pcap_src_alloc(struct upipe_mgr *mgr, + struct uprobe *uprobe, + uint32_t signature, + va_list args) +{ + struct upipe *upipe = + upipe_pcap_src_alloc_void(mgr, uprobe, signature, args); + if (unlikely(!upipe)) + return NULL; + + struct upipe_pcap_src *upipe_pcap_src = upipe_pcap_src_from_upipe(upipe); + upipe_pcap_src->pcap = NULL; + upipe_pcap_src->uref = NULL; + upipe_pcap_src->cr_offset = 0; + + upipe_pcap_src_init_urefcount(upipe); + upipe_pcap_src_init_ubuf_mgr(upipe); + upipe_pcap_src_init_uref_mgr(upipe); + upipe_pcap_src_init_output(upipe); + upipe_pcap_src_init_upump_mgr(upipe); + upipe_pcap_src_init_upump(upipe); + upipe_pcap_src_init_uclock(upipe); + + upipe_throw_ready(upipe); + + return upipe; +} + +static struct upipe_mgr upipe_pcap_src_mgr = { + .refcount = NULL, + .signature = UPIPE_PCAP_SRC_SIGNATURE, + + .upipe_alloc = upipe_pcap_src_alloc, + .upipe_input = NULL, + .upipe_control = upipe_pcap_src_control, + + .upipe_mgr_control = NULL, +}; + +struct upipe_mgr *upipe_pcap_src_mgr_alloc(void) +{ + return &upipe_pcap_src_mgr; +} diff --git a/tests/Build.mk b/tests/Build.mk index c0d3efb12..459c32db7 100644 --- a/tests/Build.mk +++ b/tests/Build.mk @@ -7,6 +7,8 @@ log-env = $(if $(have_san),DISABLE_VALGRIND=1) \ distfiles = valgrind_wrapper.sh valgrind.supp \ udict_inline_test.txt \ + upipe_pcap_src_test.pcap \ + upipe_pcap_src_test.txt \ upipe_ts_test.ts \ uprobe_prefix_test.txt \ uprobe_stdio_test.txt \ @@ -279,6 +281,13 @@ tests += upipe_pack10_test upipe_pack10_test-src = upipe_pack10_test.c upipe_pack10_test-libs = libupipe libupipe_hbrmt +tests += upipe_pcap_src_test.sh +upipe_pcap_src_test.sh-deps = upipe_pcap_src_test + +test-targets += upipe_pcap_src_test +upipe_pcap_src_test-src = upipe_pcap_src_test.c +upipe_pcap_src_test-libs = libupipe libupump_ev libupipe_modules libupipe_pcap + tests += upipe_play_test upipe_play_test-src = upipe_play_test.c upipe_play_test-libs = libupipe libupipe_modules diff --git a/tests/upipe_pcap_src_test.c b/tests/upipe_pcap_src_test.c new file mode 100644 index 000000000..c822b710c --- /dev/null +++ b/tests/upipe_pcap_src_test.c @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2026 Open Broadcast Systems Ltd. + * + * Authors: Rafaël Carré + * + * SPDX-License-Identifier: MIT + */ + +/** @file + * @short unit tests for pcap source pipe + */ + +#undef NDEBUG + +#include "upipe/uprobe.h" +#include "upipe/uprobe_stdio.h" +#include "upipe/uprobe_prefix.h" +#include "upipe/uprobe_uref_mgr.h" +#include "upipe/uprobe_upump_mgr.h" +#include "upipe/uprobe_uclock.h" +#include "upipe/uprobe_ubuf_mem.h" +#include "upipe/uclock.h" +#include "upipe/uclock_std.h" +#include "upipe/umem.h" +#include "upipe/umem_alloc.h" +#include "upipe/udict.h" +#include "upipe/udict_inline.h" +#include "upipe/uref.h" +#include "upipe/uref_block.h" +#include "upipe/uref_std.h" +#include "upipe/upump.h" +#include "upump-ev/upump_ev.h" +#include "upipe/upipe.h" +#include "upipe-modules/upipe_null.h" +#include "upipe-modules/upipe_probe_uref.h" +#include "upipe-pcap/upipe_pcap_src.h" + +#include +#include +#include + +#define UDICT_POOL_DEPTH 0 +#define UREF_POOL_DEPTH 0 +#define UBUF_POOL_DEPTH 0 +#define UPUMP_POOL 0 +#define UPUMP_BLOCKER_POOL 0 +#define READ_SIZE 4096 +#define UPROBE_LOG_LEVEL UPROBE_LOG_DEBUG + +static void usage(const char *argv0) { + fprintf(stdout, "Usage: %s \n", argv0); + exit(EXIT_FAILURE); +} + +/** definition of our uprobe */ +static int catch(struct uprobe *uprobe, struct upipe *upipe, + int event, va_list args) +{ + return UBASE_ERR_NONE; +} + +enum { + SOURCE_NO_URI = 1, + SOURCE_RELEASE = 3, + SOURCE_RESET_NO_URI = 5, + SOURCE_MAX +}; + +unsigned source_nb = SOURCE_MAX + 1; + +static int catch_probe_uref(struct uprobe *uprobe, struct upipe *upipe, + int event, va_list args) +{ + struct uref *uref = NULL; + struct upump **upump_p = NULL; + bool *drop = NULL; + + if (uprobe_probe_uref_check(event, args, &uref, &upump_p, &drop)) { + size_t s; + ubase_assert(uref_block_size(uref, &s)); + upipe_notice_va(upipe, "packet size %zu", s); + return UBASE_ERR_NONE; + } + return uprobe_throw_next(uprobe, upipe, event, args); +} + + +int main(int argc, char *argv[]) +{ + if (argc != 2) + usage(argv[0]); + const char *file = argv[1]; + + struct umem_mgr *umem_mgr = umem_alloc_mgr_alloc(); + assert(umem_mgr != NULL); + struct udict_mgr *udict_mgr = udict_inline_mgr_alloc(UDICT_POOL_DEPTH, + umem_mgr, -1, -1); + assert(udict_mgr != NULL); + struct uref_mgr *uref_mgr = uref_std_mgr_alloc(UREF_POOL_DEPTH, udict_mgr, + 0); + assert(uref_mgr != NULL); + struct upump_mgr *upump_mgr = upump_ev_mgr_alloc_default(UPUMP_POOL, + UPUMP_BLOCKER_POOL); + assert(upump_mgr != NULL); + struct uclock *uclock = uclock_std_alloc(0); + assert(uclock != NULL); + struct uprobe uprobe; + uprobe_init(&uprobe, catch, NULL); + struct uprobe *logger = + uprobe_stdio_alloc(&uprobe, stdout, UPROBE_LOG_LEVEL); + assert(logger != NULL); + logger = uprobe_uref_mgr_alloc(logger, uref_mgr); + assert(logger != NULL); + logger = uprobe_upump_mgr_alloc(logger, upump_mgr); + assert(logger != NULL); + logger = uprobe_ubuf_mem_alloc(logger, umem_mgr, UBUF_POOL_DEPTH, + UBUF_POOL_DEPTH); + assert(logger != NULL); + + struct upipe_mgr *upipe_pcap_src_mgr = upipe_pcap_src_mgr_alloc(); + assert(upipe_pcap_src_mgr != NULL); + + struct upipe *sources[source_nb]; + for (unsigned i = 0; i < source_nb; i++) { + sources[i] = upipe_void_alloc( + upipe_pcap_src_mgr, + uprobe_pfx_alloc_va(uprobe_use(logger), + UPROBE_LOG_INFO, "pcap %u", i)); + assert(sources[i]); + } + upipe_mgr_release(upipe_pcap_src_mgr); + + for (unsigned i = 0; i < source_nb; i++) { + if (i != SOURCE_NO_URI) + ubase_assert(upipe_set_uri(sources[i], file)); + + struct upipe *upipe; + upipe = upipe_use(sources[i]); + + struct upipe_mgr *upipe_probe_uref_mgr = upipe_probe_uref_mgr_alloc(); + assert(upipe_probe_uref_mgr != NULL); + struct upipe *output = upipe_void_chain_output( + upipe, upipe_probe_uref_mgr, + uprobe_pfx_alloc_va(uprobe_alloc(catch_probe_uref, uprobe_use(logger)), + UPROBE_LOG_INFO, "probe_uref %u", i)); + assert(output != NULL); + + struct upipe_mgr *upipe_null_mgr = upipe_null_mgr_alloc(); + assert(upipe_null_mgr != NULL); + output = upipe_void_chain_output( + output, upipe_null_mgr, + uprobe_pfx_alloc_va(uprobe_use(logger), + UPROBE_LOG_LEVEL, "null %u", i)); + assert(output != NULL); + upipe_release(output); + + if (i == SOURCE_RELEASE) { + upipe_release(sources[i]); + sources[i] = NULL; + } + if (i == SOURCE_RESET_NO_URI) + ubase_assert(upipe_set_uri(sources[i], NULL)); + } + + upump_mgr_run(upump_mgr, NULL); + + for (unsigned i = 0; i < source_nb; i++) + upipe_release(sources[i]); + + upump_mgr_release(upump_mgr); + uref_mgr_release(uref_mgr); + udict_mgr_release(udict_mgr); + umem_mgr_release(umem_mgr); + uclock_release(uclock); + uprobe_release(logger); + uprobe_clean(&uprobe); + + return 0; +} diff --git a/tests/upipe_pcap_src_test.pcap b/tests/upipe_pcap_src_test.pcap new file mode 100644 index 0000000000000000000000000000000000000000..6b785fe88c10b6b30351520f6de9019dd3b472b0 GIT binary patch literal 189 zcmca|c+)~A1{MYcU}0bca$aOMWlp%t!C(VqgD^7SU~pw%(BqSIU~mxBTT;)!zzD+g z3Rx~ONd41w$ycb%FIC9S%P&{R$jnu+=lTvb>wpIjgD21s5Jm=Iv#d22fy@%-gPGO4 i7igCHKP&$ "$TMP"/logs +diff -u "$srcdir"/upipe_pcap_src_test.txt "$TMP"/logs diff --git a/tests/upipe_pcap_src_test.txt b/tests/upipe_pcap_src_test.txt new file mode 100644 index 000000000..65c26096f --- /dev/null +++ b/tests/upipe_pcap_src_test.txt @@ -0,0 +1,47 @@ +debug: [null 0] throw ready event +debug: [null 0] throw provide request type uref mgr +debug: [null 0] throw provide request type ubuf mgr +debug: [null 1] throw ready event +debug: [null 1] throw provide request type uref mgr +debug: [null 1] throw provide request type ubuf mgr +debug: [null 2] throw ready event +debug: [null 2] throw provide request type uref mgr +debug: [null 2] throw provide request type ubuf mgr +debug: [null 3] throw ready event +debug: [null 3] throw provide request type uref mgr +debug: [null 3] throw provide request type ubuf mgr +debug: [null 3] freed 0 packets +debug: [null 3] throw dead event +debug: [null 4] throw ready event +debug: [null 4] throw provide request type uref mgr +debug: [null 4] throw provide request type ubuf mgr +debug: [null 5] throw ready event +debug: [null 5] throw provide request type uref mgr +debug: [null 5] throw provide request type ubuf mgr +debug: [null 6] throw ready event +debug: [null 6] throw provide request type uref mgr +debug: [null 6] throw provide request type ubuf mgr +notice: [probe_uref 6] packet size 18 +debug: [null 6] accepted flow def +notice: [probe_uref 4] packet size 18 +debug: [null 4] accepted flow def +notice: [probe_uref 2] packet size 18 +debug: [null 2] accepted flow def +notice: [probe_uref 0] packet size 18 +debug: [null 0] accepted flow def +notice: [probe_uref 6] packet size 31 +notice: [probe_uref 4] packet size 31 +notice: [probe_uref 2] packet size 31 +notice: [probe_uref 0] packet size 31 +debug: [null 0] freed 2 packets +debug: [null 0] throw dead event +debug: [null 1] freed 0 packets +debug: [null 1] throw dead event +debug: [null 2] freed 2 packets +debug: [null 2] throw dead event +debug: [null 4] freed 2 packets +debug: [null 4] throw dead event +debug: [null 5] freed 0 packets +debug: [null 5] throw dead event +debug: [null 6] freed 2 packets +debug: [null 6] throw dead event