diff --git a/.github/workflows/test-suite.yaml b/.github/workflows/test-suite.yaml index dbe2466a0..8ef6d70e5 100644 --- a/.github/workflows/test-suite.yaml +++ b/.github/workflows/test-suite.yaml @@ -189,7 +189,7 @@ jobs: - name: Archive config log if: always() - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.name }}_config.log path: config.log diff --git a/configure.ac b/configure.ac index 4c1d5fa67..cd29f4cfe 100644 --- a/configure.ac +++ b/configure.ac @@ -270,6 +270,7 @@ AC_CONFIG_FILES([Makefile include/upipe-dvbcsa/Makefile include/upipe-ebur128/Makefile include/upipe-bearssl/Makefile + include/upipe-srt/Makefile lib/Makefile lib/upipe/Makefile lib/upipe/libupipe.pc @@ -337,6 +338,8 @@ AC_CONFIG_FILES([Makefile lib/upipe-ebur128/libupipe_ebur128.pc lib/upipe-bearssl/Makefile lib/upipe-bearssl/libupipe_bearssl.pc + lib/upipe-srt/Makefile + lib/upipe-srt/libupipe_srt.pc x86/Makefile x86/config.asm tests/Makefile diff --git a/examples/Makefile.am b/examples/Makefile.am index 4262b6c83..f5aec7add 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -19,11 +19,16 @@ UPIPEOSX_LIBS = $(top_builddir)/lib/upipe-osx/libupipe_osx.la UPIPEDVBCSA_LIBS = $(top_builddir)/lib/upipe-dvbcsa/libupipe_dvbcsa.la UPIPEDVB_LIBS = $(top_builddir)/lib/upipe-dvb/libupipe_dvb.la UPIPEBEARSSL_LIBS = $(top_builddir)/lib/upipe-bearssl/libupipe_bearssl.la +UPIPESRT_LIBS = $(top_builddir)/lib/upipe-srt/libupipe_srt.la noinst_PROGRAMS = fec_LDADD = $(LDADD) $(UPUMPEV_LIBS) $(UPIPEMODULES_LIBS) $(UPIPETS_LIBS) rist_rx_LDADD = $(LDADD) $(UPUMPEV_LIBS) $(UPIPEMODULES_LIBS) $(UPIPEFILTERS_LIBS) +srt_rx_CFLAGS = $(AM_CFLAGS) +srt_rx_LDADD = $(LDADD) $(UPUMPEV_LIBS) $(UPIPEMODULES_LIBS) $(UPIPESRT_LIBS) +srt_tx_CFLAGS = $(AM_CFLAGS) $(BITSTREAM_CFLAGS) +srt_tx_LDADD = $(LDADD) $(UPUMPEV_LIBS) $(UPIPEMODULES_LIBS) $(UPIPESRT_LIBS) rist_tx_CFLAGS = $(AM_CFLAGS) $(BITSTREAM_CFLAGS) rist_tx_LDADD = $(LDADD) $(UPUMPEV_LIBS) $(UPIPEMODULES_LIBS) $(UPIPEFILTERS_LIBS) udpmulticat_LDADD = $(LDADD) $(UPUMPEV_LIBS) $(UPIPEMODULES_LIBS) @@ -63,6 +68,10 @@ frame_LDADD = $(LDADD) $(UPIPEMODULES_LIBS) $(UPUMPEV_LIBS) $(UPIPEFRAMERS_LIBS) if HAVE_GCRYPT ts_encrypt_CFLAGS += $(GCRYPT_CFLAGS) ts_encrypt_LDADD += $(GCRYPT_LIBS) +srt_tx_CFLAGS += $(GCRYPT_CFLAGS) +srt_tx_LDADD += $(GCRYPT_LIBS) +srt_rx_CFLAGS += $(GCRYPT_CFLAGS) +srt_rx_LDADD += $(GCRYPT_LIBS) endif if HAVE_EV @@ -70,7 +79,7 @@ if HAVE_WRITEV noinst_PROGRAMS += udpmulticat multicatudp noinst_PROGRAMS += decrypt if HAVE_BITSTREAM -noinst_PROGRAMS += hls2rtp fec rist_rx rist_tx +noinst_PROGRAMS += hls2rtp fec rist_rx rist_tx srt_rx srt_tx endif endif diff --git a/examples/rist_rx.c b/examples/rist_rx.c index 65520f552..8040c6339 100644 --- a/examples/rist_rx.c +++ b/examples/rist_rx.c @@ -331,7 +331,7 @@ int main(int argc, char *argv[]) udp_sink_mgr = upipe_udpsink_mgr_alloc(); - uclock = uclock_std_alloc(UCLOCK_FLAG_REALTIME); + uclock = uclock_std_alloc(0); logger = uprobe_uclock_alloc(logger, uclock); assert(logger != NULL); diff --git a/examples/rist_tx.c b/examples/rist_tx.c index af0ed000d..018f22707 100644 --- a/examples/rist_tx.c +++ b/examples/rist_tx.c @@ -305,7 +305,7 @@ int main(int argc, char *argv[]) 0); struct upump_mgr *upump_mgr = upump_ev_mgr_alloc_default(UPUMP_POOL, UPUMP_BLOCKER_POOL); - struct uclock *uclock = uclock_std_alloc(UCLOCK_FLAG_REALTIME); + struct uclock *uclock = uclock_std_alloc(0); struct uprobe uprobe; uprobe_init(&uprobe, catch, NULL); struct uprobe *logger = uprobe_stdio_alloc(&uprobe, stdout, loglevel); diff --git a/examples/srt_rx.c b/examples/srt_rx.c new file mode 100644 index 000000000..ac29c2692 --- /dev/null +++ b/examples/srt_rx.c @@ -0,0 +1,518 @@ +/* + * Copyright (C) 2016-2024 Open Broadcast Systems Ltd. + * + * Authors: Rafaël Carré + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject + * to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include + +#undef NDEBUG +#include "upipe/uprobe.h" +#include "upipe/upipe_dump.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/uprobe_dejitter.h" +#include "upipe/uclock.h" +#include "upipe/uclock_std.h" +#include "upipe/uref_clock.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_std.h" +#include "upipe/upump.h" +#include "upump-ev/upump_ev.h" +#include "upipe/uuri.h" +#include "upipe/ustring.h" +#include "upipe/upipe.h" +#include "upipe-modules/upipe_udp_source.h" +#include +#include "upipe-modules/upipe_udp_sink.h" +#include "upipe-srt/upipe_srt_handshake.h" +#include "upipe-srt/upipe_srt_receiver.h" +#include "upipe/uprobe_helper_uprobe.h" +#include "upipe/uprobe_helper_alloc.h" + +#include + +#ifdef UPIPE_HAVE_GCRYPT_H +#include +#endif + +#define UDICT_POOL_DEPTH 10 +#define UREF_POOL_DEPTH 10 +#define UBUF_POOL_DEPTH 10 +#define UPUMP_POOL 10 +#define UPUMP_BLOCKER_POOL 10 +#define READ_SIZE 4096 + + +/* structure */ +struct uprobe_log { + struct urefcount urefcount; + struct uclock *uclock; + struct uprobe uprobe; + uint64_t start; + uatomic_uint32_t loglevel; +}; + +/* helper */ +UPROBE_HELPER_UPROBE(uprobe_log, uprobe) + +/* alloc */ +struct uprobe *uprobe_log_alloc(struct uprobe *next); + +static int uprobe_log_throw(struct uprobe *uprobe, struct upipe *upipe, + int event, va_list args) +{ + struct uprobe_log *probe_obe_log = uprobe_log_from_uprobe(uprobe); + if (event != UPROBE_LOG) + return uprobe_throw_next(uprobe, upipe, event, args); + + va_list args_copy; + va_copy(args_copy, args); + struct ulog *ulog = va_arg(args_copy, struct ulog *); + + uint32_t loglevel = uatomic_load(&probe_obe_log->loglevel); + if (loglevel > ulog->level) + return UBASE_ERR_NONE; + + char time_str[22]; + if (probe_obe_log->uclock) { + uint64_t t = uclock_now(probe_obe_log->uclock) - probe_obe_log->start; + snprintf(time_str, sizeof(time_str), "%.2f", (float)t / 27000.); + } else { + snprintf(time_str, sizeof(time_str), "?"); + } + struct ulog_pfx ulog_pfx = { + .tag = time_str, + }; + ulist_add(&ulog->prefixes, ulog_pfx_to_uchain(&ulog_pfx)); + + return uprobe_throw_next(uprobe, upipe, event, args); +} + +static void uprobe_log_set_loglevel(struct uprobe *uprobe, int loglevel) +{ + struct uprobe_log *probe_obe_log = uprobe_log_from_uprobe(uprobe); + uatomic_store(&probe_obe_log->loglevel, loglevel); +} + +static void uprobe_log_set_uclock(struct uprobe *uprobe, struct uclock *uclock) +{ + struct uprobe_log *probe_obe_log = uprobe_log_from_uprobe(uprobe); + uclock_release(probe_obe_log->uclock); + probe_obe_log->uclock = uclock_use(uclock); + probe_obe_log->start = uclock_now(uclock); +} + +static struct uprobe *uprobe_log_init(struct uprobe_log *probe_obe_log, + struct uprobe *next) +{ + struct uprobe *probe = uprobe_log_to_uprobe(probe_obe_log); + probe_obe_log->uclock = NULL; + probe_obe_log->start = UINT64_MAX; + uatomic_init(&probe_obe_log->loglevel, UPROBE_LOG_DEBUG); + uprobe_init(probe, uprobe_log_throw, next); + return probe; +} + +static void uprobe_log_clean(struct uprobe_log *probe_obe_log) +{ + uprobe_clean(uprobe_log_to_uprobe(probe_obe_log)); + uclock_release(probe_obe_log->uclock); + uatomic_clean(&probe_obe_log->loglevel); +} + +#define ARGS_DECL struct uprobe *next +#define ARGS next +UPROBE_HELPER_ALLOC(uprobe_log); +#undef ARGS +#undef ARGS_DECL + +static enum uprobe_log_level loglevel = UPROBE_LOG_DEBUG; + +static struct upipe_mgr *udp_sink_mgr; +static struct upump_mgr *upump_mgr; + +static struct upipe *upipe_udpsrc; +static struct upipe *upipe_udp_sink; +static struct upipe *upipe_srtr; +static struct upipe *upipe_srtr_sub; + +static struct uprobe uprobe_udp; +static struct uprobe uprobe_srt; +static struct uprobe *logger; +static struct uprobe *uprobe_dejitter; + +static char *dirpath; +static char *srcpath; +static char *password; +static int key_length = 128; +static char *latency; + +static bool restart; + +static struct upipe_mgr *rtpd_mgr; + +static void addr_to_str(const struct sockaddr *s, char uri[INET6_ADDRSTRLEN+6]) +{ + uint16_t port = 0; + switch(s->sa_family) { + case AF_INET: { + struct sockaddr_in *in = (struct sockaddr_in *)s; + inet_ntop(AF_INET, &in->sin_addr, uri, INET6_ADDRSTRLEN); + port = ntohs(in->sin_port); + break; + } + case AF_INET6: { + struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)s; + inet_ntop(AF_INET6, &in6->sin6_addr, uri, INET6_ADDRSTRLEN); + port = ntohs(in6->sin6_port); + break; + } + default: + uri[0] = '\0'; + } + + size_t uri_len = strlen(uri); + sprintf(&uri[uri_len], ":%hu", port); +} + +static int start(void) +{ + bool listener = srcpath && strchr(srcpath, '@'); + struct upipe_mgr *upipe_udpsrc_mgr = upipe_udpsrc_mgr_alloc(); + upipe_udpsrc = upipe_void_alloc(upipe_udpsrc_mgr, &uprobe_udp); + upipe_mgr_release(upipe_udpsrc_mgr); + + struct upipe_mgr *upipe_srt_handshake_mgr = upipe_srt_handshake_mgr_alloc((long)&upipe_udpsrc); + struct upipe *upipe_srth = upipe_void_alloc_output(upipe_udpsrc, + upipe_srt_handshake_mgr, &uprobe_srt); + assert(upipe_srth); + upipe_set_option(upipe_srth, "listener", listener ? "1" : "0"); + if (!ubase_check(upipe_set_option(upipe_srth, "latency", latency))) + return EXIT_FAILURE; + + upipe_srt_handshake_set_password(upipe_srth, password, key_length / 8); + upipe_mgr_release(upipe_srt_handshake_mgr); + + struct upipe_mgr *upipe_srt_receiver_mgr = upipe_srt_receiver_mgr_alloc(); + upipe_srtr = upipe_void_chain_output(upipe_srth, + upipe_srt_receiver_mgr, uprobe_pfx_alloc(uprobe_use(logger), loglevel, "srtr")); + assert(upipe_srtr); + + upipe_mgr_release(upipe_srt_receiver_mgr); + + upipe_srtr_sub = upipe_void_alloc_sub(upipe_srtr, + uprobe_pfx_alloc(uprobe_use(logger), loglevel, "srtr_sub")); + assert(upipe_srtr_sub); + + upipe_udp_sink = upipe_void_alloc_output(upipe_srtr_sub, + udp_sink_mgr, uprobe_pfx_alloc(uprobe_use(logger), loglevel, + "udpsink")); + upipe_release(upipe_udp_sink); + + if (rtpd_mgr) { + upipe_srtr = upipe_void_chain_output(upipe_srtr, rtpd_mgr, + uprobe_pfx_alloc(uprobe_use(logger), + loglevel, "rtpd")); + assert(upipe_srtr); + } + + int udp_fd; + /* receive SRT */ + if (listener) { + if (!ubase_check(upipe_set_uri(upipe_udpsrc, srcpath))) + return EXIT_FAILURE; + ubase_assert(upipe_udpsrc_get_fd(upipe_udpsrc, &udp_fd)); + + } else { + if (!ubase_check(upipe_set_uri(upipe_udp_sink, srcpath))) + return EXIT_FAILURE; + + ubase_assert(upipe_udpsink_get_fd(upipe_udp_sink, &udp_fd)); + ubase_assert(upipe_udpsrc_set_fd(upipe_udpsrc, dup(udp_fd))); + } + + struct sockaddr_storage ad; + socklen_t peer_len = sizeof(ad); + struct sockaddr *peer = (struct sockaddr*) &ad; + + if (!getsockname(udp_fd, peer, &peer_len)) { + char uri[INET6_ADDRSTRLEN+6]; + addr_to_str(peer, uri); + upipe_warn_va(upipe_srth, "Local %s", uri); // XXX: INADDR_ANY when listening + upipe_srt_handshake_set_peer(upipe_srth, peer, peer_len); + } + + upipe_attach_uclock(upipe_udpsrc); + struct upipe *upipe_udp_sink_data = upipe_void_chain_output(upipe_srtr, + udp_sink_mgr, uprobe_pfx_alloc(uprobe_use(logger), loglevel, + "udpsink data")); + upipe_set_uri(upipe_udp_sink_data, dirpath); + upipe_release(upipe_udp_sink_data); + + return 0; +} + +static void stop(struct upump *upump) +{ + if (upump) { + upump_stop(upump); + upump_free(upump); + } + + upipe_release(upipe_udpsrc); + upipe_release(upipe_srtr_sub); + upipe_srtr = NULL; + + if (restart) { + restart = false; + start(); + } +} + +static void sig_cb(struct upump *upump) +{ + static int done = false; + + if (done) + abort(); + done = true; + + restart = false; + stop(NULL); +} + +static int catch_srt(struct uprobe *uprobe, struct upipe *upipe, + int event, va_list args) +{ + if (event == UPROBE_SRT_HANDSHAKE_CONNECTED) { + if (ubase_get_signature(args) != UPIPE_SRT_HANDSHAKE_SIGNATURE) { + return uprobe_throw_next(uprobe, upipe, event, args); + } + va_arg(args, unsigned int); // signature + bool connected = va_arg(args, int ); + bool listener = srcpath && strchr(srcpath, '@'); + upipe_notice_va(upipe, "%sCONNECTED", connected ? "" : "DIS"); + if (!connected && listener) + ubase_assert(upipe_set_uri(upipe_udp_sink, NULL)); + return UBASE_ERR_NONE; + + } + + if (event == UPROBE_SOURCE_END) { + restart = true; + struct upump *u = upump_alloc_timer(upump_mgr, stop, NULL, NULL, 0, 0); + upump_start(u); + return UBASE_ERR_NONE; + } + + if (event == UPROBE_NEW_FLOW_DEF && upipe_srtr) { + uint16_t latency_ms; + if (!ubase_check(upipe_srt_handshake_get_latency(upipe, &latency_ms))) + upipe_err(upipe, "Couldn't get latency"); + else { + upipe_notice_va(upipe, "Latency %hu ms", latency_ms); + char latency_ms_str[16]; + snprintf(latency_ms_str, sizeof(latency_ms_str), "%hu", latency_ms); + if (!ubase_check(upipe_set_option(upipe_srtr, "latency", latency_ms_str))) + upipe_err(upipe, "Couldn't set receiver latency"); + } + if (uprobe_dejitter) { + const uint64_t deviation = latency_ms * UCLOCK_FREQ / 1000 / 3; // actual delay is 3 * this + uprobe_dejitter_set(uprobe_dejitter, true, 1); + uprobe_dejitter_set_minimum_deviation(uprobe_dejitter, deviation); + uprobe_dejitter_set_maximum_deviation(uprobe_dejitter, deviation); + uprobe_dejitter_set_maximum_jitter(uprobe_dejitter, (uint64_t)latency_ms * UCLOCK_FREQ / 1000 / 2); + } + } + + return uprobe_throw_next(uprobe, upipe, event, args); +} + +static int catch_udp(struct uprobe *uprobe, struct upipe *upipe, + int event, va_list args) +{ + if (event == UPROBE_SOURCE_END) { + /* This control can not fail, and will trigger restart of upump */ + const char *uri; + upipe_get_uri(upipe, &uri); + return UBASE_ERR_NONE; + } + + if (event != UPROBE_UDPSRC_NEW_PEER) + return uprobe_throw_next(uprobe, upipe, event, args); + + int sig = va_arg(args, int); + if (sig != UPIPE_UDPSRC_SIGNATURE) + return uprobe_throw_next(uprobe, upipe, event, args); + + const struct sockaddr *s = va_arg(args, struct sockaddr*); + const socklen_t *len = va_arg(args, socklen_t *); + char uri[INET6_ADDRSTRLEN+6]; + + addr_to_str(s, uri); + upipe_warn_va(upipe, "Remote %s", uri); + + int udp_fd; + ubase_assert(upipe_udpsrc_get_fd(upipe_udpsrc, &udp_fd)); + ubase_assert(upipe_udpsink_set_fd(upipe_udp_sink, dup(udp_fd))); + ubase_assert(upipe_udpsink_set_peer(upipe_udp_sink, s, *len)); + + return UBASE_ERR_NONE; +} + +static void usage(const char *argv0) { + fprintf(stdout, "Usage: %s [-dr] [-k password] [-l 128] ", argv0); + fprintf(stdout, " -d: more verbose\n"); + fprintf(stdout, " -q: more quiet\n"); + fprintf(stdout, " -r: rtp demux\n"); + fprintf(stdout, " -k encryption password\n"); + fprintf(stdout, " -l key length in bits\n"); + exit(EXIT_FAILURE); +} + +int main(int argc, char *argv[]) +{ + int opt; + + /* parse options */ + while ((opt = getopt(argc, argv, "qrdk:l:")) != -1) { + switch (opt) { + case 'd': + loglevel--; + break; + case 'q': + loglevel++; + break; + case 'k': + password = optarg; + break; + case 'l': + key_length = atoi(optarg); + break; + case 'r': + rtpd_mgr = upipe_rtpd_mgr_alloc(); + break; + default: + usage(argv[0]); + } + } + if (argc - optind < 3) { + usage(argv[0]); + } + srcpath = argv[optind++]; + dirpath = argv[optind++]; + latency = argv[optind++]; + +#ifdef UPIPE_HAVE_GCRYPT_H + gcry_check_version(NULL); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); +#endif + + /* setup environment */ + + struct umem_mgr *umem_mgr = umem_alloc_mgr_alloc(); + 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); + upump_mgr = upump_ev_mgr_alloc_default(UPUMP_POOL, + UPUMP_BLOCKER_POOL); + logger = uprobe_stdio_alloc(NULL, stdout, loglevel); + assert(logger != NULL); + const uint64_t deviation = UCLOCK_FREQ / 30; // actual delay is 3 * this + uprobe_dejitter = uprobe_dejitter_alloc(logger, true /* enabled */, deviation); + uprobe_dejitter_set_minimum_deviation(uprobe_dejitter, deviation); + uprobe_dejitter_set_maximum_deviation(uprobe_dejitter, deviation); + assert(uprobe_dejitter != NULL); + + logger = uprobe_uref_mgr_alloc(uprobe_dejitter, 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 uclock *uclock = NULL; + + udp_sink_mgr = upipe_udpsink_mgr_alloc(); + + uclock = uclock_std_alloc(0); + + logger = uprobe_log_alloc(logger); + + uprobe_log_set_loglevel(logger, loglevel); + uprobe_log_set_uclock(logger, uclock); + + logger = uprobe_uclock_alloc(logger, uclock); + assert(logger != NULL); + + uprobe_init(&uprobe_udp, catch_udp, uprobe_pfx_alloc(uprobe_use(logger), loglevel, "udp source")); + uprobe_init(&uprobe_srt, catch_srt, uprobe_pfx_alloc(uprobe_use(logger), loglevel, "srth")); + + int ret = start(); + if (ret) + return ret; + + if (0) { + //upipe_dump_open(NULL, NULL, "dump.dot", NULL, upipe_udpsrc, NULL); + + struct upump *u = upump_alloc_timer(upump_mgr, stop, NULL, NULL, + UCLOCK_FREQ, 0); + upump_start(u); + } + + struct upump *sigint_pump = + upump_alloc_signal(upump_mgr, sig_cb, + (void *)SIGINT, NULL, SIGINT); + upump_set_status(sigint_pump, false); + upump_start(sigint_pump); + + /* fire loop ! */ + upump_mgr_run(upump_mgr, NULL); + + upump_free(sigint_pump); + + uprobe_clean(&uprobe_srt); + uprobe_clean(&uprobe_udp); + uprobe_release(logger); + + upump_mgr_release(upump_mgr); + uref_mgr_release(uref_mgr); + udict_mgr_release(udict_mgr); + umem_mgr_release(umem_mgr); + uclock_release(uclock); + upipe_mgr_release(udp_sink_mgr); + upipe_mgr_release(rtpd_mgr); + + return 0; +} diff --git a/examples/srt_tx.c b/examples/srt_tx.c new file mode 100644 index 000000000..1d131bf4f --- /dev/null +++ b/examples/srt_tx.c @@ -0,0 +1,480 @@ +/* + * Copyright (C) 2016-2024 Open Broadcast Systems Ltd. + * + * Authors: Rafaël Carré + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject + * to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#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/uprobe_dejitter.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/ubuf.h" +#include "upipe/ubuf_block.h" +#include "upipe/uref.h" +#include "upipe/uref_block.h" +#include "upipe/uref_clock.h" +#include "upipe/uref_std.h" +#include "upipe/upump.h" +#include "upipe/upipe_dump.h" +#include "upump-ev/upump_ev.h" +#include "upipe/uuri.h" +#include "upipe/ustring.h" +#include "upipe/upipe.h" +#include "upipe-modules/upipe_udp_source.h" +#include "upipe-modules/upipe_udp_sink.h" +#include "upipe-modules/upipe_probe_uref.h" + +#include "upipe-srt/upipe_srt_sender.h" +#include "upipe-srt/upipe_srt_handshake.h" + +#include +#include + +#include + +#ifdef UPIPE_HAVE_GCRYPT_H +#include +#endif + +#define UDICT_POOL_DEPTH 10 +#define UREF_POOL_DEPTH 10 +#define UBUF_POOL_DEPTH 10 +#define UPUMP_POOL 10 +#define UPUMP_BLOCKER_POOL 10 + +static void usage(const char *argv0) { + fprintf(stdout, "Usage: %s [-d] \n", argv0); + fprintf(stdout, " -d: more verbose\n"); + fprintf(stdout, " -q: more quiet\n"); + fprintf(stdout, " -k encryption password\n"); + fprintf(stdout, " -i stream_id\n"); + fprintf(stdout, " -l key length in bits\n"); + exit(EXIT_FAILURE); +} + +static struct upipe *upipe_udpsink; +static struct upipe *upipe_udpsrc_srt; +static struct upipe *upipe_udpsrc; +static struct upipe *upipe_srt_sender; +static struct upipe *upipe_srt_sender_sub; + +static struct upipe *upipe_srt_handshake; + +static struct upump_mgr *upump_mgr; +static struct uref_mgr *uref_mgr; + +static char *srcpath; +static char *dirpath; +static char *latency; +static char *password; +static char *stream_id; +static int key_length = 128; + +static enum uprobe_log_level loglevel = UPROBE_LOG_DEBUG; + +static struct uprobe *logger; + +static bool restart = true; + +static size_t packets = 0; +static const size_t km_refresh_period = 1 << 25; + +static void addr_to_str(const struct sockaddr *s, char uri[INET6_ADDRSTRLEN+6]) +{ + uint16_t port = 0; + switch(s->sa_family) { + case AF_INET: { + struct sockaddr_in *in = (struct sockaddr_in *)s; + inet_ntop(AF_INET, &in->sin_addr, uri, INET6_ADDRSTRLEN); + port = ntohs(in->sin_port); + break; + } + case AF_INET6: { + struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)s; + inet_ntop(AF_INET6, &in6->sin6_addr, uri, INET6_ADDRSTRLEN); + port = ntohs(in6->sin6_port); + break; + } + default: + uri[0] = '\0'; + } + + size_t uri_len = strlen(uri); + sprintf(&uri[uri_len], ":%hu", port); +} + +static void stop(struct upump *upump); + +/** definition of our uprobe */ +static int catch_hs(struct uprobe *uprobe, struct upipe *upipe, + int event, va_list args) +{ + uint16_t latency_ms; + + switch (event) { + case UPROBE_SRT_HANDSHAKE_CONNECTED: + if (ubase_get_signature(args) != UPIPE_SRT_HANDSHAKE_SIGNATURE) { + return uprobe_throw_next(uprobe, upipe, event, args); + } + va_arg(args, unsigned int); // signature + bool connected = va_arg(args, int ); + upipe_notice_va(upipe, "%sCONNECTED", connected ? "" : "DIS"); + + bool listener = dirpath && strchr(dirpath, '@'); + if (!connected && listener) + ubase_assert(upipe_set_uri(upipe_udpsink, NULL)); + return UBASE_ERR_NONE; + case UPROBE_SOURCE_END: + upipe_warn(upipe, "Remote shutdown"); + struct upump *u = upump_alloc_timer(upump_mgr, stop, upipe_udpsrc, + NULL, UCLOCK_FREQ, 0); + upump_start(u); + return uprobe_throw_next(uprobe, upipe, event, args); + case UPROBE_NEW_FLOW_DEF: + if (!ubase_check(upipe_srt_handshake_get_latency(upipe, &latency_ms))) + upipe_err(upipe, "Couldn't get latency"); + else { + upipe_notice_va(upipe, "Latency %hu ms", latency_ms); + char latency_ms_str[16]; + snprintf(latency_ms_str, sizeof(latency_ms_str), "%hu", latency_ms); + if (!ubase_check(upipe_set_option(upipe_srt_sender, "latency", latency_ms_str))) + upipe_err(upipe, "Couldn't set sender latency"); + } + } + return uprobe_throw_next(uprobe, upipe, event, args); +} + +/** definition of our uprobe */ +static int catch_uref(struct uprobe *uprobe, struct upipe *upipe, + int event, va_list args) +{ + switch (event) { + case UPROBE_PROBE_UREF: + UBASE_SIGNATURE_CHECK(args, UPIPE_PROBE_UREF_SIGNATURE); + struct uref *uref = va_arg(args, struct uref *); + + const uint8_t *buf; + int s = -1; + if (!ubase_check(uref_block_read(uref, 0, &s, &buf))) + return UBASE_ERR_INVALID; + bool ctrl = srt_get_packet_control(buf); + uref_block_unmap(uref, 0); + + if (ctrl) + return UBASE_ERR_NONE; + + if (packets++ == km_refresh_period) { + packets = 0; + if (upipe_srt_handshake) + upipe_srt_handshake_set_password(upipe_srt_handshake, password, key_length / 8); + } + + return UBASE_ERR_NONE; + } + return uprobe_throw_next(uprobe, upipe, event, args); +} + +/** definition of our uprobe */ +static int catch_udp(struct uprobe *uprobe, struct upipe *upipe, + int event, va_list args) +{ + switch (event) { + case UPROBE_SOURCE_END: + upipe_warn(upipe, "Remote end not listening, can't receive SRT"); + struct upump *u = upump_alloc_timer(upump_mgr, stop, upipe_udpsrc, + NULL, UCLOCK_FREQ, 0); + upump_start(u); + return uprobe_throw_next(uprobe, upipe, event, args); + case UPROBE_UDPSRC_NEW_PEER: { + if (ubase_get_signature(args) != UPIPE_UDPSRC_SIGNATURE) + break; + + va_arg(args, unsigned int); // signature + int udp_fd; + ubase_assert(upipe_udpsink_get_fd(upipe_udpsink, &udp_fd)); + if (udp_fd >= 0) { + upipe_err(upipe, "Already connected, ignoring"); + return UBASE_ERR_UNKNOWN; + } + + const struct sockaddr *s = va_arg(args, struct sockaddr*); + const socklen_t *len = va_arg(args, socklen_t *); + + char uri[INET6_ADDRSTRLEN+6]; + addr_to_str(s, uri); + upipe_warn_va(upipe, "Remote %s", uri); + + ubase_assert(upipe_udpsrc_get_fd(upipe_udpsrc_srt, &udp_fd)); + ubase_assert(upipe_udpsink_set_fd(upipe_udpsink, dup(udp_fd))); + + ubase_assert(upipe_udpsink_set_peer(upipe_udpsink, s, *len)); + + return UBASE_ERR_NONE; + } + } + return uprobe_throw_next(uprobe, upipe, event, args); +} + +static int start(void) +{ + packets = 0; + static unsigned z = 0; + z++; + + bool listener = dirpath && strchr(dirpath, '@'); + + /* rtp source */ + struct upipe_mgr *upipe_udpsrc_mgr = upipe_udpsrc_mgr_alloc(); + upipe_udpsrc = upipe_void_alloc(upipe_udpsrc_mgr, + uprobe_pfx_alloc_va(uprobe_use(logger), loglevel, "udp source data %u", z)); + + if (!ubase_check(upipe_set_uri(upipe_udpsrc, srcpath))) { + return EXIT_FAILURE; + } + upipe_attach_uclock(upipe_udpsrc); + + /* send through srt sender */ + struct upipe_mgr *upipe_srt_sender_mgr = upipe_srt_sender_mgr_alloc(); + upipe_srt_sender = upipe_void_alloc_output(upipe_udpsrc, upipe_srt_sender_mgr, + uprobe_pfx_alloc_va(uprobe_use(logger), loglevel, "srt sender %u", z)); + upipe_mgr_release(upipe_srt_sender_mgr); + + if (!ubase_check(upipe_set_option(upipe_srt_sender, "latency", latency))) + return EXIT_FAILURE; + + upipe_udpsrc_srt = upipe_void_alloc(upipe_udpsrc_mgr, + uprobe_pfx_alloc_va(uprobe_alloc(catch_udp, uprobe_use(logger)), loglevel, "udp source srt %u", z)); + upipe_attach_uclock(upipe_udpsrc_srt); + + struct upipe_mgr *upipe_srt_handshake_mgr = upipe_srt_handshake_mgr_alloc((long)&upipe_udpsrc_srt); + upipe_srt_handshake = upipe_void_alloc_output(upipe_udpsrc_srt, upipe_srt_handshake_mgr, + uprobe_pfx_alloc_va(uprobe_alloc(catch_hs, uprobe_use(logger)), loglevel, "srt handshake %u", z)); + upipe_set_option(upipe_srt_handshake, "listener", listener ? "1" : "0"); + if (!ubase_check(upipe_set_option(upipe_srt_handshake, "latency", latency))) + return EXIT_FAILURE; + upipe_srt_handshake_set_password(upipe_srt_handshake, password, key_length / 8); + if (stream_id) + upipe_set_option(upipe_srt_handshake, "stream_id", stream_id); + + upipe_mgr_release(upipe_srt_handshake_mgr); + + upipe_mgr_release(upipe_udpsrc_mgr); + + upipe_srt_sender_sub = upipe_void_chain_output_sub(upipe_srt_handshake, + upipe_srt_sender, + uprobe_pfx_alloc_va(uprobe_use(logger), loglevel, "srt sender sub %u", z)); + assert(upipe_srt_sender_sub); + upipe_release(upipe_srt_sender_sub); + + /* send to udp */ + + struct upipe_mgr *upipe_probe_uref_mgr = upipe_probe_uref_mgr_alloc(); + struct upipe *upipe = upipe_void_chain_output(upipe_srt_sender, upipe_probe_uref_mgr, + uprobe_pfx_alloc_va(uprobe_alloc(catch_uref, uprobe_use(logger)), loglevel, "probe %u", z)); + + struct upipe_mgr *upipe_udpsink_mgr = upipe_udpsink_mgr_alloc(); + upipe_udpsink = upipe_void_chain_output(upipe, upipe_udpsink_mgr, + uprobe_pfx_alloc_va(uprobe_use(logger), loglevel, "udp sink %u", z)); + upipe_release(upipe_udpsink); + + int udp_fd = -1; + if (listener) { + if (!ubase_check(upipe_set_uri(upipe_udpsrc_srt, dirpath))) { + return EXIT_FAILURE; + } + ubase_assert(upipe_udpsrc_get_fd(upipe_udpsrc_srt, &udp_fd)); + } else { + if (!ubase_check(upipe_set_uri(upipe_udpsink, dirpath))) { + return EXIT_FAILURE; + } + + ubase_assert(upipe_udpsink_get_fd(upipe_udpsink, &udp_fd)); + int flags = fcntl(udp_fd, F_GETFL); + flags |= O_NONBLOCK; + if (fcntl(udp_fd, F_SETFL, flags) < 0) + upipe_err(upipe_udpsink, "Could not set flags");; + ubase_assert(upipe_udpsrc_set_fd(upipe_udpsrc_srt, udp_fd)); + } + + struct sockaddr_storage ad; + socklen_t peer_len = sizeof(ad); + struct sockaddr *peer = (struct sockaddr*) &ad; + + if (!getsockname(udp_fd, peer, &peer_len)) { + char uri[INET6_ADDRSTRLEN+6]; + addr_to_str(peer, uri); + upipe_warn_va(upipe_srt_handshake, "Local %s (%u)", uri, z); // XXX: INADDR_ANY when listening + upipe_srt_handshake_set_peer(upipe_srt_handshake, peer, peer_len); + } + + struct uref *flow_def = uref_alloc_control(uref_mgr); + uref_flow_set_def(flow_def, "block."); + upipe_set_flow_def(upipe_srt_sender, flow_def); + uref_free(flow_def); + + return 0; +} + +static void stop(struct upump *upump) +{ + if (upump) { + upump_stop(upump); + upump_free(upump); + } + + upipe_release(upipe_udpsrc_srt); + upipe_udpsrc_srt = NULL; + upipe_release(upipe_udpsrc); + upipe_udpsrc = NULL; + + upipe_srt_handshake = NULL; + + if (restart) + start(); +} + +static void sig_cb(struct upump *upump) +{ + static int done = false; + + if (done) + abort(); + done = true; + + restart = false; + stop(NULL); +} + + +int main(int argc, char *argv[]) +{ + int opt; + + /* parse options */ + while ((opt = getopt(argc, argv, "qdk:i:l:")) != -1) { + switch (opt) { + case 'q': + loglevel++; + break; + case 'd': + loglevel--; + break; + break; + case 'k': + password = optarg; + break; + case 'i': + stream_id = optarg; + break; + case 'l': + key_length = atoi(optarg); + break; + default: + usage(argv[0]); + } + } + if (argc - optind < 3) { + usage(argv[0]); + } + srcpath = argv[optind++]; + dirpath = argv[optind++]; + latency = argv[optind++]; + +#ifdef UPIPE_HAVE_GCRYPT_H + gcry_check_version(NULL); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); +#endif + + /* setup environment */ + + struct umem_mgr *umem_mgr = umem_alloc_mgr_alloc(); + struct udict_mgr *udict_mgr = udict_inline_mgr_alloc(UDICT_POOL_DEPTH, + umem_mgr, -1, -1); + uref_mgr = uref_std_mgr_alloc(UREF_POOL_DEPTH, udict_mgr, + 0); + upump_mgr = upump_ev_mgr_alloc_default(UPUMP_POOL, + UPUMP_BLOCKER_POOL); + struct uclock *uclock = uclock_std_alloc(0); + logger = uprobe_stdio_alloc(NULL, stdout, loglevel); + assert(logger != NULL); + struct uprobe *uprobe_dejitter = uprobe_dejitter_alloc(logger, true, 0); + assert(uprobe_dejitter != NULL); + + logger = uprobe_uref_mgr_alloc(uprobe_dejitter, 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); + + logger = uprobe_uclock_alloc(logger, uclock); + assert(logger != NULL); + + int ret = start(); + if (ret) + return ret; + + if (0) { + restart = false; + //upipe_dump_open(NULL, NULL, "dump.dot", NULL, upipe_udpsink, upipe_udpsrc, upipe_udpsrc_srt, NULL); + struct upump *u = upump_alloc_timer(upump_mgr, stop, upipe_udpsrc, + NULL, UCLOCK_FREQ, 0); + upump_start(u); + } + + struct upump *sigint_pump = + upump_alloc_signal(upump_mgr, sig_cb, + (void *)SIGINT, NULL, SIGINT); + upump_set_status(sigint_pump, false); + upump_start(sigint_pump); + + /* fire loop ! */ + upump_mgr_run(upump_mgr, NULL); + + upump_free(sigint_pump); + + uprobe_release(logger); + + upump_mgr_release(upump_mgr); + uref_mgr_release(uref_mgr); + udict_mgr_release(udict_mgr); + umem_mgr_release(umem_mgr); + uclock_release(uclock); + + printf("done\n"); + + return 0; +} diff --git a/include/Makefile.am b/include/Makefile.am index f9e993d02..adffeb3e8 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -2,7 +2,8 @@ SUBDIRS = \ upipe \ upipe-modules \ upipe-filters \ - upipe-dveo + upipe-dveo \ + upipe-srt if HAVE_QTWEBKIT SUBDIRS += upipe-qt diff --git a/include/upipe-srt/Makefile.am b/include/upipe-srt/Makefile.am new file mode 100644 index 000000000..8e1861f9e --- /dev/null +++ b/include/upipe-srt/Makefile.am @@ -0,0 +1,5 @@ +myincludedir = $(includedir)/upipe-srt +myinclude_HEADERS = \ + upipe_srt_handshake.h \ + upipe_srt_sender.h \ + upipe_srt_receiver.h diff --git a/include/upipe-srt/upipe_srt_handshake.h b/include/upipe-srt/upipe_srt_handshake.h new file mode 100644 index 000000000..88d7cd188 --- /dev/null +++ b/include/upipe-srt/upipe_srt_handshake.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2023 Open Broadcast Systems Ltd + * + * Authors: Rafaël Carré + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject + * to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + * @short Upipe module for SRT handshakes + */ + +#ifndef _UPIPE_SRT_UPIPE_SRT_HANDSHAKE_H_ +/** @hidden */ +#define _UPIPE_SRT_UPIPE_SRT_HANDSHAKE_H_ +#ifdef __cplusplus +extern "C" { +#endif + +#include "upipe/upipe.h" +#include +#include + +#define UPIPE_SRT_HANDSHAKE_SIGNATURE UBASE_FOURCC('s','r','t','h') +#define UPIPE_SRT_HANDSHAKE_OUTPUT_SIGNATURE UBASE_FOURCC('s','r','h','o') + +/** @This extends upipe_command with specific commands for srt handshake. */ +enum upipe_srt_handshake_command { + UPIPE_SRT_HANDSHAKE_SENTINEL = UPIPE_CONTROL_LOCAL, + + /** set our peer address (const struct sockaddr *, socklen_t) **/ + UPIPE_SRT_HANDSHAKE_SET_PEER, + + /** set the encryption password (const char *, int) */ + UPIPE_SRT_HANDSHAKE_SET_PASSWORD, + + /** gets negociated latency (int *) */ + UPIPE_SRT_HANDSHAKE_GET_LATENCY, +}; + +/** @This extends uprobe_throw with specific events. */ +enum uprobe_srt_handshake_event { + UPROBE_SRT_HANDSHAKE_SENTINEL = UPROBE_LOCAL, + + /** connection status changed (bool, bool, bool) */ + UPROBE_SRT_HANDSHAKE_CONNECTED, +}; + +/** @This sets the peer address + * + * @param upipe description structure of the pipe + * @param addr our address + * @param addrlen the size of addr + * @return false in case of error + */ +static inline int upipe_srt_handshake_set_peer(struct upipe *upipe, + const struct sockaddr *addr, socklen_t addrlen) +{ + return upipe_control(upipe, UPIPE_SRT_HANDSHAKE_SET_PEER, UPIPE_SRT_HANDSHAKE_SIGNATURE, + addr, addrlen); +} + +/** @This sets the encryption key + * + * @param upipe description structure of the pipe + * @param password passphrase + * @param key_len key length in bytes + * @return false in case of error + */ +static inline int upipe_srt_handshake_set_password(struct upipe *upipe, + const char *password, int key_len) +{ + return upipe_control(upipe, UPIPE_SRT_HANDSHAKE_SET_PASSWORD, UPIPE_SRT_HANDSHAKE_SIGNATURE, + password, key_len); +} + +/** @This sets the encryption key + * + * @param upipe description structure of the pipe + * @param latency_ms pointer to latency in ms + * @return false in case of error + */ +static inline int upipe_srt_handshake_get_latency(struct upipe *upipe, uint16_t *latency_ms) +{ + return upipe_control(upipe, UPIPE_SRT_HANDSHAKE_GET_LATENCY, UPIPE_SRT_HANDSHAKE_SIGNATURE, latency_ms); +} + +/** @This returns the management structure for all srt handshakes sources. + * + * @return pointer to manager + */ +struct upipe_mgr *upipe_srt_handshake_mgr_alloc(long seed); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/include/upipe-srt/upipe_srt_receiver.h b/include/upipe-srt/upipe_srt_receiver.h new file mode 100644 index 000000000..6aae36b05 --- /dev/null +++ b/include/upipe-srt/upipe_srt_receiver.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 Open Broadcast Systems Ltd + * + * Authors: Rafaël Carré + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject + * to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + * @short Upipe module receiving SRT + */ + +#ifndef _UPIPE_SRT_UPIPE_SRT_RECEIVER_H_ +/** @hidden */ +#define _UPIPE_SRT_UPIPE_SRT_RECEIVER_H_ +#ifdef __cplusplus +extern "C" { +#endif + +#include "upipe/upipe.h" + +#define UPIPE_SRT_RECEIVER_SIGNATURE UBASE_FOURCC('s','r','t','r') +#define UPIPE_SRT_RECEIVER_OUTPUT_SIGNATURE UBASE_FOURCC('s','r','r','o') + +enum upipe_srt_receiver_command { + UPIPE_SRTR_SENTINEL = UPIPE_CONTROL_LOCAL, + + /** get counters (unsigned *, unsigned *, size_t *, size_t *, size_t *, size_t *, size_t *, unsigned *) */ + UPIPE_SRTR_GET_STATS, +}; + +static inline int upipe_srt_receiver_get_stats(struct upipe *upipe, + unsigned *expected_seqnum, unsigned *last_output_seqnum, + size_t *buffered, size_t *nacks, size_t *repaired, + size_t *lost, size_t *duplicates, unsigned *rtt) +{ + return upipe_control(upipe, UPIPE_SRTR_GET_STATS, + UPIPE_SRT_RECEIVER_SIGNATURE, expected_seqnum, last_output_seqnum, + buffered, nacks, repaired, lost, duplicates, rtt); +} + + +/** @This returns the management structure for all srt receiver sources. + * + * @return pointer to manager + */ +struct upipe_mgr *upipe_srt_receiver_mgr_alloc(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/include/upipe-srt/upipe_srt_sender.h b/include/upipe-srt/upipe_srt_sender.h new file mode 100644 index 000000000..7ec9025af --- /dev/null +++ b/include/upipe-srt/upipe_srt_sender.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 Open Broadcast Systems Ltd + * + * Authors: Rafaël Carré + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject + * to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + * @short Upipe module sending SRT + */ + +#ifndef _UPIPE_SRT_UPIPE_SRT_SENDER_H_ +/** @hidden */ +#define _UPIPE_SRT_UPIPE_SRT_SENDER_H_ +#ifdef __cplusplus +extern "C" { +#endif + +#include "upipe/upipe.h" + +#define UPIPE_SRT_SENDER_SIGNATURE UBASE_FOURCC('s','r','t','s') +#define UPIPE_SRT_SENDER_INPUT_SIGNATURE UBASE_FOURCC('s','r','s','i') + +/** @This returns the management structure for srt_sender pipes. + * + * @return pointer to manager + */ +struct upipe_mgr *upipe_srt_sender_mgr_alloc(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/include/upipe/uprobe_dejitter.h b/include/upipe/uprobe_dejitter.h index f2bf8a376..206e304e9 100644 --- a/include/upipe/uprobe_dejitter.h +++ b/include/upipe/uprobe_dejitter.h @@ -58,6 +58,11 @@ struct uprobe_dejitter { double deviation; /** minimum deviation */ double minimum_deviation; + /** maximum deviation */ + double maximum_deviation; + + /** maximum jitter */ + uint64_t maximum_jitter; /** cr_prog of last clock ref */ uint64_t last_cr_prog; @@ -120,6 +125,22 @@ void uprobe_dejitter_set(struct uprobe *uprobe, bool enabled, void uprobe_dejitter_set_minimum_deviation(struct uprobe *uprobe, double deviation); +/** @This sets the maximum deviation of the dejittering probe. + * + * @param uprobe pointer to probe + * @param deviation maximum deviation to set + */ +void uprobe_dejitter_set_maximum_deviation(struct uprobe *uprobe, + double deviation); + +/** @This sets the maximum jitter of the dejittering probe. + * + * @param uprobe pointer to probe + * @param jitter maximum jitter to set + */ +void uprobe_dejitter_set_maximum_jitter(struct uprobe *uprobe, + uint64_t jitter); + #ifdef __cplusplus } #endif diff --git a/lib/Makefile.am b/lib/Makefile.am index 0cc1dfb8d..6eb3b4eca 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -2,7 +2,8 @@ SUBDIRS = \ upipe \ upipe-modules \ upipe-filters \ - upipe-dveo + upipe-dveo \ + upipe-srt if HAVE_QTWEBKIT SUBDIRS += upipe-qt diff --git a/lib/upipe-modules/upipe_udp_sink.c b/lib/upipe-modules/upipe_udp_sink.c index d9ab6ff9b..e88e34f4e 100644 --- a/lib/upipe-modules/upipe_udp_sink.c +++ b/lib/upipe-modules/upipe_udp_sink.c @@ -408,7 +408,7 @@ static int _upipe_udpsink_set_uri(struct upipe *upipe, const char *uri) if (unlikely(upipe_udpsink->fd != -1)) { if (likely(upipe_udpsink->uri != NULL)) upipe_notice_va(upipe, "closing socket %s", upipe_udpsink->uri); - close(upipe_udpsink->fd); + ubase_clean_fd(&upipe_udpsink->fd); } ubase_clean_str(&upipe_udpsink->uri); upipe_udpsink_set_upump(upipe, NULL); diff --git a/lib/upipe-srt/Makefile.am b/lib/upipe-srt/Makefile.am new file mode 100644 index 000000000..fa41efe98 --- /dev/null +++ b/lib/upipe-srt/Makefile.am @@ -0,0 +1,18 @@ +lib_LTLIBRARIES = libupipe_srt.la + +libupipe_srt_la_SOURCES = upipe_srt_handshake.c \ + upipe_srt_sender.c \ + upipe_srt_receiver.c + +libupipe_srt_la_CPPFLAGS = -I$(top_builddir) -I$(top_builddir)/include -I$(top_srcdir)/include +libupipe_srt_la_CFLAGS = $(AM_CFLAGS) $(BITSTREAM_CFLAGS) +libupipe_srt_la_LIBADD = $(top_builddir)/lib/upipe/libupipe.la +libupipe_srt_la_LDFLAGS = -no-undefined + +if HAVE_GCRYPT +libupipe_srt_la_CPPFLAGS += $(GCRYPT_CFLAGS) +libupipe_srt_la_LIBADD += $(GCRYPT_LIBS) +endif + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libupipe_srt.pc diff --git a/lib/upipe-srt/libupipe_srt.pc.in b/lib/upipe-srt/libupipe_srt.pc.in new file mode 100644 index 000000000..5acea2e39 --- /dev/null +++ b/lib/upipe-srt/libupipe_srt.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ +Name: libupipe_srt +Description: Upipe multimedia framework, SRT modules +Version: @VERSION@ +Requires: libupipe +Requires.private: libgcrypt +Libs: -L${libdir} -lupipe_srt +Cflags: -I${includedir} diff --git a/lib/upipe-srt/upipe_srt_handshake.c b/lib/upipe-srt/upipe_srt_handshake.c new file mode 100644 index 000000000..d2bf4dfd9 --- /dev/null +++ b/lib/upipe-srt/upipe_srt_handshake.c @@ -0,0 +1,1878 @@ +/* + * Copyright (C) 2023 Open Broadcast Systems Ltd + * + * Authors: Rafaël Carré + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject + * to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + * @short Upipe module for SRT handshakes + */ + +#include "upipe/config.h" +#include "upipe/ubase.h" +#include "upipe/uclock.h" +#include "upipe/uref.h" +#include "upipe/uref_block.h" +#include "upipe/uref_block_flow.h" +#include "upipe/uref_pic.h" // XXX are we abusing picture number? +#include "upipe/uref_clock.h" +#include "upipe/uref_attr.h" +#include "upipe/upipe.h" +#include "upipe/upipe_helper_upipe.h" +#include "upipe/upipe_helper_urefcount.h" +#include "upipe/upipe_helper_void.h" +#include "upipe/upipe_helper_uref_mgr.h" +#include "upipe/upipe_helper_ubuf_mgr.h" +#include "upipe/upipe_helper_output.h" +#include "upipe/upipe_helper_upump_mgr.h" +#include "upipe/upipe_helper_upump.h" +#include "upipe/upipe_helper_uclock.h" + +#include "upipe-srt/upipe_srt_handshake.h" + +#include + +#include +#include + +#include + +/** @hidden */ +static int upipe_srt_handshake_check(struct upipe *upipe, struct uref *flow_format); + +/** @internal @This is the private context of a SRT handshake pipe. */ +struct upipe_srt_handshake { + /** refcount management structure */ + struct urefcount urefcount; + + struct upump_mgr *upump_mgr; + struct upump *upump_handshake_send; /* send handshakes every 250ms until connected */ + struct upump *upump_handshake_timeout; /* abort connection if not successful */ + struct upump *upump_keepalive_timeout; /* reset connection if no keep alive in 10s */ + struct upump *upump_kmreq; /* re-send key update if not acknowledged */ + struct uclock *uclock; + struct urequest uclock_request; + + + /** 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; + + /** pipe acting as output */ + struct upipe *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; + + uint32_t syn_cookie; + uint32_t socket_id; /* ours */ + uint32_t remote_socket_id; /* theirs */ + uint32_t isn; + uint32_t mtu; + uint32_t mfw; + + + uint16_t receiver_tsbpd_delay; /* stores negotiated latency */ + uint16_t sender_tsbpd_delay; + uint32_t flags; + uint16_t major; + uint8_t minor, patch; + + uint8_t salt[16]; + uint8_t sek[2][32]; + uint8_t sek_len; + uint8_t kk; + uint8_t cipher; + + struct uref *kmreq; + + char *password; + + struct sockaddr_storage addr; + uint64_t establish_time; + + bool expect_conclusion; + struct uref *caller_conclusion; + + bool listener; + + uint8_t *stream_id; + size_t stream_id_len; + + uint64_t last_hs_sent; + + bool end; + + /** public upipe structure */ + struct upipe upipe; +}; + +UPIPE_HELPER_UPIPE(upipe_srt_handshake, upipe, UPIPE_SRT_HANDSHAKE_SIGNATURE) +UPIPE_HELPER_UREFCOUNT(upipe_srt_handshake, urefcount, upipe_srt_handshake_free); + +UPIPE_HELPER_VOID(upipe_srt_handshake) + +UPIPE_HELPER_OUTPUT(upipe_srt_handshake, output, flow_def, output_state, request_list) +UPIPE_HELPER_UPUMP_MGR(upipe_srt_handshake, upump_mgr) +UPIPE_HELPER_UPUMP(upipe_srt_handshake, upump_handshake_send, upump_mgr) +UPIPE_HELPER_UPUMP(upipe_srt_handshake, upump_handshake_timeout, upump_mgr) +UPIPE_HELPER_UPUMP(upipe_srt_handshake, upump_keepalive_timeout, upump_mgr) +UPIPE_HELPER_UPUMP(upipe_srt_handshake, upump_kmreq, upump_mgr) +UPIPE_HELPER_UCLOCK(upipe_srt_handshake, uclock, uclock_request, NULL, upipe_throw_provide_request, NULL) + +UPIPE_HELPER_UREF_MGR(upipe_srt_handshake, uref_mgr, uref_mgr_request, + upipe_srt_handshake_check, + upipe_srt_handshake_register_output_request, + upipe_srt_handshake_unregister_output_request) +UPIPE_HELPER_UBUF_MGR(upipe_srt_handshake, ubuf_mgr, flow_format, ubuf_mgr_request, + upipe_srt_handshake_check, + upipe_srt_handshake_register_output_request, + upipe_srt_handshake_unregister_output_request) + +/** @internal @This is the private context of a SRT handshake output pipe. */ +struct upipe_srt_handshake_output { + /** refcount management structure */ + struct urefcount urefcount; + /** structure for double-linked lists */ + struct uchain uchain; + + /** 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; + + /** pipe acting as output */ + struct upipe *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; + + /** public upipe structure */ + struct upipe upipe; +}; + +static void upipe_srt_handshake_shutdown(struct upipe *upipe) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + + uint64_t now = uclock_now(upipe_srt_handshake->uclock); + uint32_t timestamp = (now - upipe_srt_handshake->establish_time) / 27; + + struct uref *uref = uref_block_alloc(upipe_srt_handshake->uref_mgr, + upipe_srt_handshake->ubuf_mgr, SRT_HEADER_SIZE \ + /* + libsrt will not handle this packet if we don't add an undocumented 4 bytes. + libsrt source code has this to say: + "control info field should be none but "writev" does not allow this" + */ + + 4 + ); + if (!uref) + return; + uint8_t *out; + int output_size = -1; + if (unlikely(!ubase_check(uref_block_write(uref, 0, &output_size, &out)))) { + uref_free(uref); + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + } + + srt_set_packet_control(out, true); + srt_set_packet_timestamp(out, timestamp); + srt_set_packet_dst_socket_id(out, upipe_srt_handshake->remote_socket_id); + srt_set_control_packet_type(out, SRT_CONTROL_TYPE_SHUTDOWN); + srt_set_control_packet_subtype(out, 0); + srt_set_control_packet_type_specific(out, 0); + uint8_t *extra = (uint8_t*)srt_get_control_packet_cif(out); + memset(extra, 0, 4); + + uref_block_unmap(uref, 0); + upipe_srt_handshake_output(&upipe_srt_handshake->upipe, uref, + &upipe_srt_handshake->upump_handshake_send); +} + + +static struct uref *upipe_srt_handshake_alloc_hs(struct upipe *upipe, int ext_size, uint32_t timestamp, uint8_t **cif, bool reject) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + + int size = SRT_HEADER_SIZE + SRT_HANDSHAKE_CIF_SIZE + ext_size; + + struct uref *uref = uref_block_alloc(upipe_srt_handshake->uref_mgr, + upipe_srt_handshake->ubuf_mgr, size); + if (!uref) { + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + return NULL; + } + + uint8_t *out; + int output_size = -1; + if (unlikely(!ubase_check(uref_block_write(uref, 0, &output_size, &out)))) { + uref_free(uref); + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + return NULL; + } + + memset(out, 0, output_size); + + srt_set_packet_control(out, true); + srt_set_packet_timestamp(out, timestamp); + srt_set_packet_dst_socket_id(out, upipe_srt_handshake->remote_socket_id); + + srt_set_control_packet_type(out, SRT_CONTROL_TYPE_HANDSHAKE); + srt_set_control_packet_subtype(out, 0); + srt_set_control_packet_type_specific(out, 0); + + + uint8_t *out_cif = (uint8_t*)srt_get_control_packet_cif(out); + *cif = out_cif; + + srt_set_handshake_syn_cookie(out_cif, upipe_srt_handshake->syn_cookie); + srt_set_handshake_mtu(out_cif, upipe_srt_handshake->mtu); + srt_set_handshake_mfw(out_cif, upipe_srt_handshake->mfw); + srt_set_handshake_socket_id(out_cif, reject ? 0 : upipe_srt_handshake->socket_id); + srt_set_handshake_isn(out_cif, upipe_srt_handshake->isn); + + srt_set_handshake_ip(out_cif, (const struct sockaddr*)&upipe_srt_handshake->addr); + + srt_set_handshake_version(out_cif, SRT_HANDSHAKE_VERSION); + srt_set_handshake_encryption(out_cif, SRT_HANDSHAKE_CIPHER_NONE); + + return uref; +} + +static void upipe_srt_handshake_kmreq(struct upump *upump) +{ + struct upipe *upipe = upump_get_opaque(upump, struct upipe *); + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + + struct uref *kmreq = upipe_srt_handshake->kmreq; + + uint8_t *out; + int output_size = -1; + if (unlikely(!ubase_check(uref_block_write(kmreq, 0, &output_size, &out)))) { + return; + } + + uint64_t now = uclock_now(upipe_srt_handshake->uclock); + uint32_t timestamp = (now - upipe_srt_handshake->establish_time) / 27; + + srt_set_packet_timestamp(out, timestamp); + + uref_block_unmap(kmreq, 0); + + upipe_dbg(upipe, "Sending key update"); + + upipe_srt_handshake_output(&upipe_srt_handshake->upipe, uref_dup(kmreq), NULL); +} + +static int upipe_srt_handshake_set_flow_def(struct upipe *upipe, struct uref *flow_def); +static void upipe_srt_handshake_timeout(struct upump *upump); + +static void upipe_srt_handshake_disconnect(struct upipe *upipe, bool end, bool blacklist) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + + /* Connection has just been aborted already */ + + /* No need to keep waiting for keepalives */ + upipe_srt_handshake_set_upump_keepalive_timeout(upipe, NULL); + /* No need to keep sending KMREQ packets */ + upipe_srt_handshake_set_upump_kmreq(upipe, NULL); + /* No need to keep sending handshake packets */ + upipe_srt_handshake_set_upump_handshake_send(upipe, NULL); + + if (upipe_srt_handshake->upump_handshake_timeout) /* if timeout was running we die */ + end = true; + + upipe_throw(upipe, UPROBE_SRT_HANDSHAKE_CONNECTED, UPIPE_SRT_HANDSHAKE_SIGNATURE, false, blacklist, end); + upipe_srt_handshake->expect_conclusion = false; + + if (end) { + upipe_throw_source_end(upipe); + upipe_srt_handshake->end = true; + /* No need to wait for a timeout */ + upipe_srt_handshake_set_upump_handshake_timeout(upipe, NULL); + } else { + /* (new) connection has to succeed within 3 seconds */ + struct upump *upump = + upump_alloc_timer(upipe_srt_handshake->upump_mgr, + upipe_srt_handshake_timeout, + upipe, upipe->refcount, + 3 * UCLOCK_FREQ, 0); + upump_start(upump); + upipe_srt_handshake_set_upump_handshake_timeout(upipe, upump); + } + + struct uref *flow_def = uref_block_flow_alloc_def(upipe_srt_handshake->uref_mgr, ""); + if (flow_def) { + upipe_srt_handshake_set_flow_def(upipe, flow_def); + /* force sending flow definition immediately */ + upipe_srt_handshake_output(upipe, NULL, NULL); + } +} + +static void upipe_srt_handshake_keepalive_timeout(struct upump *upump) +{ + struct upipe *upipe = upump_get_opaque(upump, struct upipe *); + + upipe_err(upipe, "No data in 10s"); + upipe_srt_handshake_disconnect(upipe, true, false); +} + +static void upipe_srt_handshake_timeout(struct upump *upump) +{ + struct upipe *upipe = upump_get_opaque(upump, struct upipe *); + + upipe_err(upipe, "Connection timed out"); + upipe_srt_handshake_disconnect(upipe, false, false); +} + +static void upipe_srt_handshake_send_timer(struct upump *upump) +{ + struct upipe *upipe = upump_get_opaque(upump, struct upipe *); + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + + uint64_t now = uclock_now(upipe_srt_handshake->uclock); + if (!upipe_srt_handshake->establish_time) + upipe_srt_handshake->establish_time = now; + uint32_t timestamp = (now - upipe_srt_handshake->establish_time) / 27; + + /* 250 ms between handshakes, just like libsrt */ + if (now - upipe_srt_handshake->last_hs_sent < UCLOCK_FREQ / 4) + return; + + if (upipe_srt_handshake->expect_conclusion) { + if (upipe_srt_handshake->caller_conclusion) { + struct uref *uref = uref_dup(upipe_srt_handshake->caller_conclusion); + /* copy because we need to rewrite the timestamp */ + struct ubuf *ubuf = ubuf_block_copy(uref->ubuf->mgr, uref->ubuf, 0, -1); + if (!ubuf) { + upipe_err_va(upipe, "Malloc failed"); + return; + } + uref_attach_ubuf(uref, ubuf); + + uint8_t *out; + int output_size = -1; + if (unlikely(!ubase_check(uref_block_write(uref, 0, &output_size, &out)))) { + uref_free(uref); + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + return; + } + srt_set_packet_timestamp(out, timestamp); + uref_block_unmap(uref, 0); + + upipe_srt_handshake_output(&upipe_srt_handshake->upipe, uref, + &upipe_srt_handshake->upump_handshake_send); + } + } else { + //send HS + uint8_t *out_cif; + struct uref *uref = upipe_srt_handshake_alloc_hs(upipe, 0, timestamp, &out_cif, false); + if (!uref) + return; + + srt_set_handshake_version(out_cif, SRT_HANDSHAKE_VERSION_MIN); // XXX + srt_set_handshake_extension(out_cif, SRT_HANDSHAKE_EXT_KMREQ); // draft-sharabayko-srt-01#section-4.3.1.1 * Extension Field: 2 + srt_set_handshake_type(out_cif, SRT_HANDSHAKE_TYPE_INDUCTION); + + uref_block_unmap(uref, 0); + + upipe_srt_handshake_output(&upipe_srt_handshake->upipe, uref, + &upipe_srt_handshake->upump_handshake_send); + } + upipe_srt_handshake->last_hs_sent = now; +} + +/** @internal @This allocates a SRT handshake pipe. + * + * @param mgr common management structure + * @param uprobe structure used to raise events + * @param signature signature of the pipe allocator + * @param args optional arguments + * @return pointer to upipe or NULL in case of allocation error + */ +static struct upipe *upipe_srt_handshake_alloc(struct upipe_mgr *mgr, + struct uprobe *uprobe, + uint32_t signature, va_list args) +{ + struct upipe *upipe = upipe_srt_handshake_alloc_void(mgr, uprobe, signature, args); + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + +#ifdef UPIPE_HAVE_GCRYPT_H + if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { + uprobe_err(uprobe, upipe, "Application did not initialize libgcrypt, see " + "https://www.gnupg.org/documentation/manuals/gcrypt/Initializing-the-library.html"); + upipe_srt_handshake_free_void(upipe); + return NULL; + } + + gcry_randomize(upipe_srt_handshake->sek[0], 32, GCRY_STRONG_RANDOM); + gcry_randomize(upipe_srt_handshake->sek[1], 32, GCRY_STRONG_RANDOM); + gcry_randomize(upipe_srt_handshake->salt, 16, GCRY_STRONG_RANDOM); +#endif + + upipe_srt_handshake_init_urefcount(upipe); + + upipe_srt_handshake_init_uref_mgr(upipe); + upipe_srt_handshake_init_ubuf_mgr(upipe); + upipe_srt_handshake_init_output(upipe); + + upipe_srt_handshake_init_upump_mgr(upipe); + upipe_srt_handshake_init_upump_handshake_send(upipe); + upipe_srt_handshake_init_upump_handshake_timeout(upipe); + upipe_srt_handshake_init_upump_keepalive_timeout(upipe); + upipe_srt_handshake_init_upump_kmreq(upipe); + upipe_srt_handshake_init_uclock(upipe); + upipe_srt_handshake_require_uclock(upipe); + + upipe_srt_handshake->isn = 0; + upipe_srt_handshake->remote_socket_id = 0; // will be set with remote first packet + upipe_srt_handshake->mtu = 1500; + upipe_srt_handshake->mfw = 8192; + upipe_srt_handshake->addr.ss_family = 0; + + upipe_srt_handshake->listener = true; + upipe_srt_handshake->last_hs_sent = 0; + + upipe_srt_handshake->expect_conclusion = false; + upipe_srt_handshake->caller_conclusion = NULL; + + upipe_srt_handshake->stream_id = NULL; + upipe_srt_handshake->stream_id_len = 0; + + upipe_srt_handshake->receiver_tsbpd_delay = 0; + upipe_srt_handshake->sender_tsbpd_delay = 0; + upipe_srt_handshake->flags = SRT_HANDSHAKE_EXT_FLAG_CRYPT | SRT_HANDSHAKE_EXT_FLAG_PERIODICNAK + | SRT_HANDSHAKE_EXT_FLAG_REXMITFLG | SRT_HANDSHAKE_EXT_FLAG_TSBPDSND | SRT_HANDSHAKE_EXT_FLAG_TSBPDRCV | SRT_HANDSHAKE_EXT_FLAG_TLPKTDROP; + + /* made up version */ + upipe_srt_handshake->major = 1; + upipe_srt_handshake->minor = 5; + upipe_srt_handshake->patch = 3; + + upipe_srt_handshake->socket_id = 0; + + upipe_srt_handshake->sek_len = 0; + upipe_srt_handshake->kk = 1; + upipe_srt_handshake->cipher = SRT_KMREQ_CIPHER_NONE; + upipe_srt_handshake->kmreq = NULL; + upipe_srt_handshake->password = NULL; + + upipe_srt_handshake->establish_time = 0; + upipe_srt_handshake->end = false; + + upipe_throw_ready(upipe); + return upipe; +} + + +/** @internal @This checks if the pump may be allocated. + * + * @param upipe description structure of the pipe + * @param flow_format amended flow format + * @return an error code + */ +static int upipe_srt_handshake_check(struct upipe *upipe, struct uref *flow_format) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + + upipe_srt_handshake_check_upump_mgr(upipe); + + if (flow_format != NULL) { + upipe_srt_handshake_store_flow_def(upipe, flow_format); + } + + if (upipe_srt_handshake->uref_mgr == NULL) { + upipe_srt_handshake_require_uref_mgr(upipe); + return UBASE_ERR_NONE; + } + + if (upipe_srt_handshake->ubuf_mgr == NULL) { + struct uref *flow_format = + uref_block_flow_alloc_def(upipe_srt_handshake->uref_mgr, NULL); + if (unlikely(flow_format == NULL)) { + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + return UBASE_ERR_ALLOC; + } + upipe_srt_handshake_require_ubuf_mgr(upipe, flow_format); + return UBASE_ERR_NONE; + } + + if (upipe_srt_handshake->upump_mgr && !upipe_srt_handshake->upump_keepalive_timeout && !upipe_srt_handshake->upump_handshake_send && !upipe_srt_handshake->listener) { + upipe_srt_handshake->socket_id = mrand48(); + upipe_srt_handshake->syn_cookie = 0; + struct upump *upump = + upump_alloc_timer(upipe_srt_handshake->upump_mgr, + upipe_srt_handshake_send_timer, + upipe, upipe->refcount, + UCLOCK_FREQ/300, UCLOCK_FREQ/300); + upump_start(upump); + upipe_srt_handshake_set_upump_handshake_send(upipe, upump); + } + + return UBASE_ERR_NONE; +} + +/** @internal @This sets the input flow definition. + * + * @param upipe description structure of the pipe + * @param flow_def flow definition packet + * @return an error code + */ +static int upipe_srt_handshake_set_flow_def(struct upipe *upipe, struct uref *flow_def) +{ + if (flow_def == NULL) + return UBASE_ERR_INVALID; + + const char *def; + UBASE_RETURN(uref_flow_get_def(flow_def, &def)) + + if (ubase_ncmp(def, "block.")) { + upipe_err_va(upipe, "Unknown def %s", def); + return UBASE_ERR_INVALID; + } + + flow_def = uref_dup(flow_def); + if (!flow_def) + return UBASE_ERR_ALLOC; + + upipe_srt_handshake_store_flow_def(upipe, flow_def); + + return UBASE_ERR_NONE; +} + +static int upipe_srt_handshake_set_option(struct upipe *upipe, const char *option, + const char *value) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + + if (!option || !value) + return UBASE_ERR_INVALID; + + if (!strcmp(option, "listener")) { + upipe_srt_handshake->listener = strcmp(value, "0"); + return UBASE_ERR_NONE; + } + + if (!strcmp(option, "stream_id")) { + free(upipe_srt_handshake->stream_id); + upipe_srt_handshake->stream_id = NULL; + + size_t src_len = strlen(value); + size_t stream_id_len = (src_len + 3) & ~3; // round up to 4 bytes + uint8_t *stream_id = calloc(1, stream_id_len); + if (!stream_id) { + return UBASE_ERR_ALLOC; + } + + memcpy(stream_id, value, src_len); + + for (size_t i = 0; i < stream_id_len; i += 4) { + uint8_t tmp = stream_id[i+0]; + stream_id[i+0] = stream_id[i+3]; + stream_id[i+3] = tmp; + tmp = stream_id[i+2]; + stream_id[i+2] = stream_id[i+1]; + stream_id[i+1] = tmp; + } + + upipe_srt_handshake->stream_id = stream_id; + upipe_srt_handshake->stream_id_len = stream_id_len; + return UBASE_ERR_NONE; + } + + if (!strcmp(option, "latency")) { + upipe_srt_handshake->receiver_tsbpd_delay = atoi(value); + upipe_srt_handshake->sender_tsbpd_delay = 0; + return UBASE_ERR_NONE; + } + + upipe_err_va(upipe, "Unknown option %s", option); + return UBASE_ERR_INVALID; +} + +#ifdef UPIPE_HAVE_GCRYPT_H +static struct uref *upipe_srt_handshake_make_kmreq(struct upipe *upipe, uint32_t timestamp); +#endif + +/** @internal @This processes control commands on a SRT handshake pipe. + * + * @param upipe description structure of the pipe + * @param command type of command to process + * @param args arguments of the command + * @return an error code + */ +static int _upipe_srt_handshake_control(struct upipe *upipe, + int command, va_list args) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + UBASE_HANDLED_RETURN(upipe_srt_handshake_control_output(upipe, command, args)); + + switch (command) { + case UPIPE_ATTACH_UPUMP_MGR: + upipe_srt_handshake_set_upump_handshake_send(upipe, NULL); + upipe_srt_handshake_set_upump_handshake_timeout(upipe, NULL); + upipe_srt_handshake_set_upump_keepalive_timeout(upipe, NULL); + upipe_srt_handshake_set_upump_kmreq(upipe, NULL); + return upipe_srt_handshake_attach_upump_mgr(upipe); + + case UPIPE_SET_FLOW_DEF: { + struct uref *flow = va_arg(args, struct uref *); + return upipe_srt_handshake_set_flow_def(upipe, flow); + } + + case UPIPE_SET_OPTION: { + const char *option = va_arg(args, const char *); + const char *value = va_arg(args, const char *); + return upipe_srt_handshake_set_option(upipe, option, value); + } + + case UPIPE_SRT_HANDSHAKE_SET_PEER: { + UBASE_SIGNATURE_CHECK(args, UPIPE_SRT_HANDSHAKE_SIGNATURE) + const struct sockaddr *s = va_arg(args, const struct sockaddr *); + socklen_t addrlen = va_arg(args, socklen_t); + if (addrlen > sizeof(upipe_srt_handshake->addr)) + addrlen = sizeof(upipe_srt_handshake->addr); + memcpy(&upipe_srt_handshake->addr, s, addrlen); + return UBASE_ERR_NONE; + } + + case UPIPE_SRT_HANDSHAKE_SET_PASSWORD: { + UBASE_SIGNATURE_CHECK(args, UPIPE_SRT_HANDSHAKE_SIGNATURE) + const char *password = va_arg(args, const char*); + upipe_srt_handshake->sek_len = va_arg(args, int); + free(upipe_srt_handshake->password); + if (password) { + upipe_srt_handshake->password = strdup(password); + switch (upipe_srt_handshake->sek_len) { + case 128/8: + case 192/8: + case 256/8: + break; + default: + upipe_err_va(upipe, "Invalid key length %d, using 128 bits", 8*upipe_srt_handshake->sek_len); + upipe_srt_handshake->sek_len = 128/8; + } + if (upipe_srt_handshake->upump_keepalive_timeout) { /* already started */ +#ifdef UPIPE_HAVE_GCRYPT_H + // KM refresh + gcry_randomize(upipe_srt_handshake->sek[upipe_srt_handshake->kk == 2], upipe_srt_handshake->sek_len, GCRY_STRONG_RANDOM); + + uint64_t now = uclock_now(upipe_srt_handshake->uclock); + uint32_t timestamp = (now - upipe_srt_handshake->establish_time) / 27; + upipe_srt_handshake->kk = (upipe_srt_handshake->kk == 2) ? 1 : 2; + struct uref *kmreq = upipe_srt_handshake_make_kmreq(upipe, timestamp); + if (kmreq) { + if (upipe_srt_handshake->kmreq) + uref_free(upipe_srt_handshake->kmreq); + upipe_srt_handshake->kmreq = kmreq; + struct upump *upump = + upump_alloc_timer(upipe_srt_handshake->upump_mgr, + upipe_srt_handshake_kmreq, + upipe, upipe->refcount, + 0, UCLOCK_FREQ); // every second + upump_start(upump); + upipe_srt_handshake_set_upump_kmreq(upipe, upump); + } +#endif + } + } else { + upipe_srt_handshake->password = NULL; + upipe_srt_handshake->sek_len = 0; + } + return UBASE_ERR_NONE; + } + + case UPIPE_SRT_HANDSHAKE_GET_LATENCY: { + UBASE_SIGNATURE_CHECK(args, UPIPE_SRT_HANDSHAKE_SIGNATURE) + uint16_t *latency_ms = va_arg(args, uint16_t *); + *latency_ms = upipe_srt_handshake->receiver_tsbpd_delay; + return UBASE_ERR_NONE; + } + + default: + return UBASE_ERR_UNHANDLED; + } +} + +/** @internal @This processes control commands on a SRT handshake pipe, and + * checks the status of the pipe afterwards. + * + * @param upipe description structure of the pipe + * @param command type of command to process + * @param args arguments of the command + * @return an error code + */ +static int upipe_srt_handshake_control(struct upipe *upipe, int command, va_list args) +{ + UBASE_RETURN(_upipe_srt_handshake_control(upipe, command, args)); + + return upipe_srt_handshake_check(upipe, NULL); +} + +static const char ctrl_type[][10] = +{ + [SRT_CONTROL_TYPE_HANDSHAKE] = "handshake", + [SRT_CONTROL_TYPE_KEEPALIVE] = "keepalive", + [SRT_CONTROL_TYPE_ACK] = "ack", + [SRT_CONTROL_TYPE_NAK] = "nak", + [SRT_CONTROL_TYPE_SHUTDOWN] = "shutdown", + [SRT_CONTROL_TYPE_ACKACK] = "ackack", + [SRT_CONTROL_TYPE_DROPREQ] = "dropreq", + [SRT_CONTROL_TYPE_PEERERROR] = "peererror", +}; + +static const char *get_ctrl_type(uint16_t type) +{ + if (type == SRT_CONTROL_TYPE_USER) + return "user"; + if (type >= (sizeof(ctrl_type) / sizeof(*ctrl_type))) + return "?"; + return ctrl_type[type]; +} + +static const char hs_error[][40] = { + [SRT_HANDSHAKE_TYPE_REJ_UNKNOWN - SRT_HANDSHAKE_TYPE_REJ_UNKNOWN] = "Unknown reason", + [SRT_HANDSHAKE_TYPE_REJ_SYSTEM - SRT_HANDSHAKE_TYPE_REJ_UNKNOWN] = "System function error", + [SRT_HANDSHAKE_TYPE_REJ_PEER - SRT_HANDSHAKE_TYPE_REJ_UNKNOWN] = "Rejected by peer", + [SRT_HANDSHAKE_TYPE_REJ_RESOURCE - SRT_HANDSHAKE_TYPE_REJ_UNKNOWN] = "Resource allocation problem", + [SRT_HANDSHAKE_TYPE_REJ_ROGUE - SRT_HANDSHAKE_TYPE_REJ_UNKNOWN] = "incorrect data in handshake", + [SRT_HANDSHAKE_TYPE_REJ_BACKLOG - SRT_HANDSHAKE_TYPE_REJ_UNKNOWN] = "listener's backlog exceeded", + [SRT_HANDSHAKE_TYPE_REJ_IPE - SRT_HANDSHAKE_TYPE_REJ_UNKNOWN] = "internal program error", + [SRT_HANDSHAKE_TYPE_REJ_CLOSE - SRT_HANDSHAKE_TYPE_REJ_UNKNOWN] = "socket is closing", + [SRT_HANDSHAKE_TYPE_REJ_VERSION - SRT_HANDSHAKE_TYPE_REJ_UNKNOWN] = "peer is older version than agent's min", + [SRT_HANDSHAKE_TYPE_REJ_RDVCOOKIE - SRT_HANDSHAKE_TYPE_REJ_UNKNOWN] = "rendezvous cookie collision", + [SRT_HANDSHAKE_TYPE_REJ_BADSECRET - SRT_HANDSHAKE_TYPE_REJ_UNKNOWN] = "wrong password", + [SRT_HANDSHAKE_TYPE_REJ_UNSECURE - SRT_HANDSHAKE_TYPE_REJ_UNKNOWN] = "password required or unexpected", + [SRT_HANDSHAKE_TYPE_REJ_MESSAGEAPI - SRT_HANDSHAKE_TYPE_REJ_UNKNOWN] = "Stream flag collision", + [SRT_HANDSHAKE_TYPE_REJ_CONGESTION - SRT_HANDSHAKE_TYPE_REJ_UNKNOWN] = "incompatible congestion-controller type", + [SRT_HANDSHAKE_TYPE_REJ_FILTER - SRT_HANDSHAKE_TYPE_REJ_UNKNOWN] = "incompatible packet filter", + [SRT_HANDSHAKE_TYPE_REJ_GROUP - SRT_HANDSHAKE_TYPE_REJ_UNKNOWN] = "incompatible group", +}; + +static const char *get_hs_error(uint32_t hs_type) +{ + if (hs_type < SRT_HANDSHAKE_TYPE_REJ_UNKNOWN) + hs_type = SRT_HANDSHAKE_TYPE_REJ_UNKNOWN; + + hs_type -= SRT_HANDSHAKE_TYPE_REJ_UNKNOWN; + if (hs_type >= (sizeof(hs_error) / sizeof(*hs_error))) + hs_type = 0; + + return hs_error[hs_type]; +} + +static void upipe_srt_handshake_restart_keepalive_timeout(struct upipe *upipe) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + struct upump *upump = + upump_alloc_timer(upipe_srt_handshake->upump_mgr, + upipe_srt_handshake_keepalive_timeout, + upipe, upipe->refcount, + 10*UCLOCK_FREQ, 0); + upump_start(upump); + + upipe_srt_handshake_set_upump_keepalive_timeout(upipe, upump); +} + +static void upipe_srt_handshake_finalize(struct upipe *upipe) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + upipe_srt_handshake->expect_conclusion = false; + upipe_srt_handshake_set_upump_handshake_timeout(upipe, NULL); + + if (!upipe_srt_handshake->upump_keepalive_timeout) { + upipe_dbg_va(upipe, "[%s] Remote connected", + upipe_srt_handshake->listener ? "listener" : "caller"); + } + upipe_srt_handshake_restart_keepalive_timeout(upipe); + + struct uref *flow_def; + if (ubase_check(upipe_srt_handshake_get_flow_def(upipe, &flow_def))) { + flow_def = uref_dup(flow_def); + if (flow_def) { + uref_flow_set_id(flow_def, upipe_srt_handshake->remote_socket_id); + struct udict_opaque opaque; + opaque.v = upipe_srt_handshake->salt; + opaque.size = (upipe_srt_handshake->cipher == SRT_KMREQ_CIPHER_AES_GCM) ? 12 : 14; + if (!ubase_check(uref_attr_set_opaque(flow_def, opaque, UDICT_TYPE_OPAQUE, "enc.salt"))) + upipe_err(upipe, "damn"); + + uint8_t kk = upipe_srt_handshake->kk; + + if (kk & (1<<0)) { + opaque.v = upipe_srt_handshake->sek[0]; + opaque.size = upipe_srt_handshake->sek_len; + if (!ubase_check(uref_attr_set_opaque(flow_def, opaque, UDICT_TYPE_OPAQUE, "enc.even_key"))) + upipe_err(upipe, "damn"); + } + + if (kk & (1<<1)) { + opaque.v = upipe_srt_handshake->sek[1]; + opaque.size = upipe_srt_handshake->sek_len; + if (!ubase_check(uref_attr_set_opaque(flow_def, opaque, UDICT_TYPE_OPAQUE, "enc.odd_key"))) + upipe_err(upipe, "damn"); + } + + uref_pic_set_number(flow_def, upipe_srt_handshake->isn); + upipe_srt_handshake_store_flow_def(upipe, flow_def); + /* force sending flow definition immediately */ + upipe_srt_handshake_output(upipe, NULL, NULL); + } + } +} + +static void upipe_srt_handshake_parse_hsreq(struct upipe *upipe, const uint8_t *ext) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + + srt_get_handshake_extension_srt_version(ext, &upipe_srt_handshake->major, &upipe_srt_handshake->minor, &upipe_srt_handshake->patch); + upipe_dbg_va(upipe, "SRT lib version %u.%u.%u", upipe_srt_handshake->major, upipe_srt_handshake->minor, upipe_srt_handshake->patch); + + uint32_t flags = srt_get_handshake_extension_srt_flags(ext); + upipe_dbg_va(upipe, "%s%s%s%s%s%s%s%s", + (flags & SRT_HANDSHAKE_EXT_FLAG_TSBPDSND) ? "TSBPDSND " : "", + (flags & SRT_HANDSHAKE_EXT_FLAG_TSBPDRCV) ? "TSBPDRCV " : "", + (flags & SRT_HANDSHAKE_EXT_FLAG_CRYPT) ? "CRYPT " : "", + (flags & SRT_HANDSHAKE_EXT_FLAG_TLPKTDROP) ? "TLPKTDROP " : "", + (flags & SRT_HANDSHAKE_EXT_FLAG_PERIODICNAK) ? "PERIODICNAK " : "", + (flags & SRT_HANDSHAKE_EXT_FLAG_REXMITFLG) ? "REXMITFLG " : "", + (flags & SRT_HANDSHAKE_EXT_FLAG_STREAM) ? "STREAM " : "", + (flags & SRT_HANDSHAKE_EXT_FLAG_PACKET_FILTER) ? "PACKET_FILTER " : ""); + upipe_srt_handshake->flags = flags; + + /* Latency is MAX(sender_tsbpd_delay, receiver_tsbpd_delay). Arbitrarily use receiver_tsbpd_delay variable as the latency */ + uint16_t receiver_tsbpd = upipe_srt_handshake->receiver_tsbpd_delay; + + upipe_srt_handshake->receiver_tsbpd_delay = srt_get_handshake_extension_receiver_tsbpd_delay(ext); + upipe_srt_handshake->sender_tsbpd_delay = srt_get_handshake_extension_sender_tsbpd_delay(ext); + if (upipe_srt_handshake->receiver_tsbpd_delay < upipe_srt_handshake->sender_tsbpd_delay) + upipe_srt_handshake->receiver_tsbpd_delay = upipe_srt_handshake->sender_tsbpd_delay; + + if (upipe_srt_handshake->receiver_tsbpd_delay < receiver_tsbpd) + upipe_srt_handshake->receiver_tsbpd_delay = receiver_tsbpd; + + upipe_dbg_va(upipe, "Negotiated latency %u ms", upipe_srt_handshake->receiver_tsbpd_delay); +} + +static bool upipe_srt_handshake_parse_kmreq(struct upipe *upipe, const uint8_t *ext, const size_t ext_len, const uint8_t **wrap, uint8_t *wrap_len) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + if (!srt_check_km(ext, ext_len)) { + upipe_err(upipe, "Malformed KMREQ"); + return false; + } + + if (!upipe_srt_handshake->password) { + upipe_warn(upipe, "Received a Key Message, but passphrase was not set"); + return false; + } + +#ifdef UPIPE_HAVE_GCRYPT_H + + upipe_srt_handshake->kk = srt_km_get_kk(ext); + uint8_t cipher = srt_km_get_cipher(ext); + if (cipher != SRT_KMREQ_CIPHER_AES_CTR && cipher != SRT_KMREQ_CIPHER_AES_GCM) { + upipe_err_va(upipe, "Unsupported cipher %u", cipher); + return false; + } + + uint8_t auth = srt_km_get_auth(ext); + if (cipher == SRT_KMREQ_CIPHER_AES_GCM && auth != SRT_KMREQ_AUTH_AES_GCM) { + upipe_err_va(upipe, "Unsupported auth %u with AES-GCM cipher", auth); + return false; + } + + uint8_t klen = 4 * srt_km_get_klen(ext); + if (upipe_srt_handshake->sek_len != klen) + upipe_warn_va(upipe, "Requested key length %u, got %u. Proceeding.", + 8*upipe_srt_handshake->sek_len, 8*klen); + + memcpy(upipe_srt_handshake->salt, srt_km_get_salt(ext), 16); + + *wrap = srt_km_get_wrap((uint8_t*)ext); + + uint8_t kek[32]; + + gpg_error_t err = gcry_kdf_derive(upipe_srt_handshake->password, + strlen(upipe_srt_handshake->password), GCRY_KDF_PBKDF2, GCRY_MD_SHA1, + &upipe_srt_handshake->salt[8], 8, 2048, klen, kek); + if (err) { + upipe_err_va(upipe, "pbkdf2 failed (%s)", gcry_strerror(err)); + return false; + } + + *wrap_len = ((upipe_srt_handshake->kk == 3) ? 2 : 1) * klen + 8; + + uint8_t osek[64]; /* 2x 256 bits keys */ + + gcry_cipher_hd_t aes; + err = gcry_cipher_open(&aes, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_AESWRAP, 0); + if (err) { + upipe_err_va(upipe, "Cipher open failed (0x%x)", err); + return false; + } + + err = gcry_cipher_setkey(aes, kek, klen); + if (err) { + upipe_err_va(upipe, "Couldn't use key encrypting key (%s)", gcry_strerror(err)); + goto key_error; + } + + err = gcry_cipher_decrypt(aes, osek, ((upipe_srt_handshake->kk == 3) ? 2 : 1) * klen, *wrap, *wrap_len); + if (err) { + upipe_err_va(upipe, "Couldn't decrypt session key (%s)", gcry_strerror(err)); + goto key_error; + } + + gcry_cipher_close(aes); + + upipe_srt_handshake->sek_len = klen; + + if (upipe_srt_handshake->kk == 3) { + memcpy(upipe_srt_handshake->sek[0], osek, klen); + memcpy(upipe_srt_handshake->sek[1], &osek[klen], klen); + } else { + memcpy(upipe_srt_handshake->sek[(upipe_srt_handshake->kk & (1<<0)) ? 0 : 1], osek, klen); + } + + upipe_srt_handshake->cipher = cipher; + + return true; + +key_error: + gcry_cipher_close(aes); + upipe_srt_handshake->sek_len = 0; + upipe_err(upipe, "Couldn't recover encryption key"); + + return false; +#else + upipe_err(upipe, "Encryption not built in"); + return false; +#endif +} + +static void make_km_msg(struct upipe *upipe, uint8_t *out_ext, const uint8_t *wrap, size_t wrap_len) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + const uint8_t klen = upipe_srt_handshake->sek_len; + + memset(out_ext, 0, SRT_KMREQ_COMMON_SIZE); + + out_ext[0] = 0x12; // S V PT + out_ext[1] = 0x20; out_ext[2] = 0x29; // Sign + srt_km_set_kk(out_ext, upipe_srt_handshake->kk); + out_ext[10] = 2; // SE + out_ext[14] = 4; // slen; + + uint8_t cipher = upipe_srt_handshake->cipher; + if (cipher == SRT_KMREQ_CIPHER_NONE) + cipher = SRT_KMREQ_CIPHER_AES_CTR; // TODO : favor GCM? + srt_km_set_cipher(out_ext, cipher); + if (cipher == SRT_KMREQ_CIPHER_AES_GCM) + srt_km_set_auth(out_ext, SRT_KMREQ_AUTH_AES_GCM); + + srt_km_set_klen(out_ext, klen / 4); + memcpy(&out_ext[SRT_KMREQ_COMMON_SIZE-16], upipe_srt_handshake->salt, 16); + memcpy(&out_ext[SRT_KMREQ_COMMON_SIZE], wrap, wrap_len); +} + +#ifdef UPIPE_HAVE_GCRYPT_H +static bool upipe_srt_handshake_wrap_key(struct upipe *upipe, uint8_t *wrap) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + const uint8_t klen = upipe_srt_handshake->sek_len; + size_t wrap_len = ((upipe_srt_handshake->kk == 3) ? 2 : 1) * klen + 8; + + uint8_t kek[32]; + gpg_error_t err = gcry_kdf_derive(upipe_srt_handshake->password, + strlen(upipe_srt_handshake->password), GCRY_KDF_PBKDF2, GCRY_MD_SHA1, + &upipe_srt_handshake->salt[8], 8, 2048, klen, kek); + if (err) { + upipe_err_va(upipe, "pbkdf2 failed (%s)", gcry_strerror(err)); + return false; + } + + gcry_cipher_hd_t aes; + err = gcry_cipher_open(&aes, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_AESWRAP, 0); + if (err) { + upipe_err_va(upipe, "Cipher open failed (0x%x)", err); + return false; + } + + err = gcry_cipher_setkey(aes, kek, klen); + if (err) { + gcry_cipher_close(aes); + upipe_err_va(upipe, "Couldn't use key encrypting key (0x%x)", err); + return false; + } + + uint8_t clear_wrap[2*256/8]; + memcpy(&clear_wrap[0], upipe_srt_handshake->sek[(upipe_srt_handshake->kk == 2) ? 1 : 0], klen); + memcpy(&clear_wrap[klen], upipe_srt_handshake->sek[1], klen); + + err = gcry_cipher_encrypt(aes, wrap, wrap_len, clear_wrap, wrap_len - 8); + if (err) { + gcry_cipher_close(aes); + upipe_err_va(upipe, "Couldn't encrypt session key (0x%x)", err); + return false; + } + + gcry_cipher_close(aes); + + return true; +} + +static struct uref *upipe_srt_handshake_make_kmreq(struct upipe *upipe, uint32_t timestamp) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + const uint8_t klen = upipe_srt_handshake->sek_len; + size_t wrap_len = ((upipe_srt_handshake->kk == 3) ? 2 : 1) * klen + 8; + struct uref *next = uref_block_alloc(upipe_srt_handshake->uref_mgr, + upipe_srt_handshake->ubuf_mgr, SRT_HEADER_SIZE + SRT_KMREQ_COMMON_SIZE + wrap_len); + if (!next) { + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + return NULL; + } + + uint8_t *out; + int output_size = -1; + if (unlikely(!ubase_check(uref_block_write(next, 0, &output_size, &out)))) { + goto error; + } + + srt_set_packet_control(out, true); + srt_set_packet_timestamp(out, timestamp); + srt_set_packet_dst_socket_id(out, upipe_srt_handshake->remote_socket_id); + + srt_set_control_packet_type(out, SRT_CONTROL_TYPE_USER); + srt_set_control_packet_subtype(out, SRT_HANDSHAKE_EXT_TYPE_KMREQ); + srt_set_control_packet_type_specific(out, 0); + + uint8_t *out_ext = &out[SRT_HEADER_SIZE]; + uint8_t wrap[8+256/8] = {0}; + if (!upipe_srt_handshake_wrap_key(upipe, wrap)) + goto error; + + make_km_msg(upipe, out_ext, wrap, wrap_len); + + uref_block_unmap(next, 0); + + return next; + +error: + uref_free(next); + return NULL; +} +#endif + +static void build_hs(struct upipe *upipe, uint8_t *out_cif, int extension, bool rsp, const uint8_t *wrap, size_t wrap_len) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + uint8_t *out_ext = srt_get_handshake_extension_buf(out_cif); + const size_t ext_size = SRT_HANDSHAKE_HSREQ_SIZE; + + srt_set_handshake_type(out_cif, SRT_HANDSHAKE_TYPE_CONCLUSION); + + if (extension) { + srt_set_handshake_extension(out_cif, extension); + srt_set_handshake_extension_type(out_ext, rsp ? SRT_HANDSHAKE_EXT_TYPE_HSRSP : SRT_HANDSHAKE_EXT_TYPE_HSREQ); + srt_set_handshake_extension_len(out_ext, ext_size / 4); + out_ext += SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE; + + srt_set_handshake_extension_srt_version(out_ext, upipe_srt_handshake->major, + upipe_srt_handshake->minor, upipe_srt_handshake->patch); + srt_set_handshake_extension_srt_flags(out_ext, upipe_srt_handshake->flags); + srt_set_handshake_extension_receiver_tsbpd_delay(out_ext, upipe_srt_handshake->receiver_tsbpd_delay); + srt_set_handshake_extension_sender_tsbpd_delay(out_ext, upipe_srt_handshake->sender_tsbpd_delay); + out_ext += ext_size; + } else { + srt_set_handshake_extension(out_cif, 2 /* SRT_DGRAM */); + srt_set_handshake_version(out_cif, SRT_HANDSHAKE_VERSION_MIN); + } + + if (upipe_srt_handshake->stream_id) { + srt_set_handshake_extension_type(out_ext, SRT_HANDSHAKE_EXT_TYPE_SID); + srt_set_handshake_extension_len(out_ext, upipe_srt_handshake->stream_id_len / 4); + out_ext += SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE; + memcpy(out_ext, upipe_srt_handshake->stream_id, upipe_srt_handshake->stream_id_len); + out_ext += upipe_srt_handshake->stream_id_len; + } + + if (wrap_len) { + srt_set_handshake_extension_type(out_ext, rsp ? SRT_HANDSHAKE_EXT_TYPE_KMRSP : SRT_HANDSHAKE_EXT_TYPE_KMREQ); + srt_set_handshake_extension_len(out_ext, (SRT_KMREQ_COMMON_SIZE + wrap_len) / 4); + out_ext += SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE; + + make_km_msg(upipe, out_ext, wrap, wrap_len); + } +} + +struct hs_packet { + uint8_t *ext_buf; + uint32_t version; + uint16_t encryption; + uint16_t extension; + uint32_t syn_cookie; + uint32_t dst_socket_id; + uint32_t remote_socket_id; + uint32_t isn; + uint32_t mtu; + uint32_t mfw; +}; + +static struct uref *upipe_srt_handshake_handle_hs_caller_conclusion(struct upipe *upipe, int size, uint32_t timestamp, const struct hs_packet *hs_packet) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + + upipe_srt_handshake_set_upump_handshake_send(upipe, NULL); + upipe_srt_handshake->remote_socket_id = hs_packet->remote_socket_id; + + /* At least HSREQ is expected */ + if (size < SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE + SRT_HANDSHAKE_HSREQ_SIZE) { + upipe_err_va(upipe, "Malformed conclusion handshake (size %u)", size); + upipe_srt_handshake->expect_conclusion = false; + return NULL; + } + + uint8_t *ext = hs_packet->ext_buf; + + while (size >= SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE) { + uint16_t ext_type = srt_get_handshake_extension_type(ext); + uint16_t ext_len = 4 * srt_get_handshake_extension_len(ext); + + size -= SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE; + ext += SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE; + + if (ext_len > size) { + upipe_err_va(upipe, "Malformed extension: %u > %u", ext_len, size); + break; + } + + if (ext_type == SRT_HANDSHAKE_EXT_TYPE_HSRSP) { + if (ext_len >= SRT_HANDSHAKE_HSREQ_SIZE) + upipe_srt_handshake_parse_hsreq(upipe, ext); + else + upipe_err_va(upipe, "Malformed HSRSP: %u < %u\n", ext_len, + SRT_HANDSHAKE_HSREQ_SIZE); + } + + ext += ext_len; + size -= ext_len; + } + + upipe_throw(upipe, UPROBE_SRT_HANDSHAKE_CONNECTED, UPIPE_SRT_HANDSHAKE_SIGNATURE, true, false, false); + + upipe_srt_handshake_finalize(upipe); + return NULL; +} + +static struct uref *upipe_srt_handshake_handle_hs_caller_induction(struct upipe *upipe, int size, uint32_t timestamp, const struct hs_packet *hs_packet) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + + upipe_srt_handshake->mtu = hs_packet->mtu; + upipe_srt_handshake->mfw = hs_packet->mfw; + upipe_srt_handshake->isn = hs_packet->isn; + + upipe_dbg_va(upipe, "mtu %u mfw %u isn %u", upipe_srt_handshake->mtu, upipe_srt_handshake->mfw, upipe_srt_handshake->isn); + upipe_verbose_va(upipe, "cookie %08x", hs_packet->syn_cookie); + + upipe_srt_handshake->syn_cookie = hs_packet->syn_cookie; + size = SRT_HANDSHAKE_HSREQ_SIZE + SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE; + uint16_t extension = SRT_HANDSHAKE_EXT_HSREQ; + + size_t wrap_len = 0; + uint8_t wrap[8+256/8] = {0}; +#ifdef UPIPE_HAVE_GCRYPT_H + if (upipe_srt_handshake->password) { + const uint8_t klen = upipe_srt_handshake->sek_len; + wrap_len = ((upipe_srt_handshake->kk == 3) ? 2 : 1) * klen + 8; + if (!upipe_srt_handshake_wrap_key(upipe, wrap)) { + return NULL; + } + size += SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE + SRT_KMREQ_COMMON_SIZE + wrap_len; + extension |= SRT_HANDSHAKE_EXT_KMREQ; + } +#endif + + if (upipe_srt_handshake->stream_id) { + size += SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE + upipe_srt_handshake->stream_id_len; + extension |= SRT_HANDSHAKE_EXT_CONFIG; + } + + uint8_t *out_cif; + struct uref *uref = upipe_srt_handshake_alloc_hs(upipe, size, timestamp, &out_cif, false); + if (!uref) + return NULL; + + build_hs(upipe, out_cif, extension, false, wrap, wrap_len); + + uref_block_unmap(uref, 0); + return uref; +} + +static struct uref *upipe_srt_handshake_handle_hs_listener_induction(struct upipe *upipe, int size, uint32_t timestamp, const struct hs_packet *hs_packet) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + + timestamp = 0; + upipe_srt_handshake->remote_socket_id = hs_packet->remote_socket_id; + upipe_srt_handshake->socket_id = mrand48(); + upipe_srt_handshake->syn_cookie = mrand48(); + + uint8_t *out_cif; + struct uref *uref = upipe_srt_handshake_alloc_hs(upipe, 0, timestamp, &out_cif, false); + if (!uref) + return NULL; + + srt_set_handshake_extension(out_cif, SRT_MAGIC_CODE); + srt_set_handshake_type(out_cif, SRT_HANDSHAKE_TYPE_INDUCTION); + + uref_block_unmap(uref, 0); + return uref; +} + +static struct uref *upipe_srt_handshake_alloc_hs_reject(struct upipe *upipe, uint32_t timestamp, uint32_t dst_socket_id, uint32_t rejection_type) +{ + uint8_t *out_cif; + struct uref *uref = upipe_srt_handshake_alloc_hs(upipe, 0, timestamp, &out_cif, true); + if (!uref) + return NULL; + + srt_set_handshake_type(out_cif, rejection_type); + uint8_t *out = &out_cif[-SRT_HEADER_SIZE]; + srt_set_packet_dst_socket_id(out, dst_socket_id); + + uref_block_unmap(uref, 0); + return uref; +} + +static struct uref *upipe_srt_handshake_handle_hs_listener_conclusion(struct upipe *upipe, int size, uint32_t timestamp, const struct hs_packet *hs_packet) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + + if (hs_packet->syn_cookie != upipe_srt_handshake->syn_cookie) { + upipe_err(upipe, "Malformed conclusion handshake (invalid syn cookie)"); + upipe_srt_handshake_disconnect(upipe, false, false); + return upipe_srt_handshake_alloc_hs_reject(upipe, timestamp, + hs_packet->remote_socket_id, SRT_HANDSHAKE_TYPE_REJ_UNKNOWN); + } + + /* At least HSREQ is expected */ + if (hs_packet->version == SRT_HANDSHAKE_VERSION && size < SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE + SRT_HANDSHAKE_HSREQ_SIZE) { + upipe_err(upipe, "Malformed conclusion handshake (size)"); + upipe_srt_handshake_disconnect(upipe, false, false); + return upipe_srt_handshake_alloc_hs_reject(upipe, timestamp, + hs_packet->remote_socket_id, SRT_HANDSHAKE_TYPE_REJ_UNKNOWN); + } + + upipe_srt_handshake->isn = hs_packet->isn; + + uint8_t *ext = hs_packet->ext_buf; + + const uint8_t *wrap; + uint8_t wrap_len = 0; + + bool got_key = false; + + while (size >= SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE) { + uint16_t ext_type = srt_get_handshake_extension_type(ext); + uint16_t ext_len = 4 * srt_get_handshake_extension_len(ext); + + size -= SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE; + ext += SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE; + + if (ext_len > size) { + upipe_err_va(upipe, "Malformed extension: %u > %u", ext_len, size); + break; + } + + if (ext_type == SRT_HANDSHAKE_EXT_TYPE_HSREQ) { + if (ext_len >= SRT_HANDSHAKE_HSREQ_SIZE) + upipe_srt_handshake_parse_hsreq(upipe, ext); + else + upipe_err_va(upipe, "Malformed HSREQ: %u < %u\n", ext_len, + SRT_HANDSHAKE_HSREQ_SIZE); + } else if (ext_type == SRT_HANDSHAKE_EXT_TYPE_KMREQ) { + if (!upipe_srt_handshake->password) { + upipe_err(upipe, "Password not specified but remote requested encryption."); + upipe_throw(upipe, UPROBE_SRT_HANDSHAKE_CONNECTED, UPIPE_SRT_HANDSHAKE_SIGNATURE, false, true, false); + upipe_srt_handshake->expect_conclusion = false; + return upipe_srt_handshake_alloc_hs_reject(upipe, timestamp, + hs_packet->remote_socket_id, SRT_HANDSHAKE_TYPE_REJ_BADSECRET); + } + got_key = upipe_srt_handshake_parse_kmreq(upipe, ext, ext_len, &wrap, &wrap_len); + } + + ext += ext_len; + size -= ext_len; + } + + if (upipe_srt_handshake->password && !got_key) { + upipe_err(upipe, "Password specified but could not get streaming key"); + upipe_srt_handshake_disconnect(upipe, false, true); + return upipe_srt_handshake_alloc_hs_reject(upipe, timestamp, + hs_packet->remote_socket_id, SRT_HANDSHAKE_TYPE_REJ_BADSECRET); + } + + int extension = 0; + size = 0; + if (hs_packet->version == SRT_HANDSHAKE_VERSION) { + extension |= SRT_HANDSHAKE_EXT_HSREQ; + size += SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE + SRT_HANDSHAKE_HSREQ_SIZE; + } + if (wrap_len) { + size += SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE + SRT_KMREQ_COMMON_SIZE + wrap_len; + extension |= SRT_HANDSHAKE_EXT_KMREQ; + } + if (upipe_srt_handshake->stream_id) { + size += SRT_HANDSHAKE_CIF_EXTENSION_MIN_SIZE + upipe_srt_handshake->stream_id_len; + extension |= SRT_HANDSHAKE_EXT_CONFIG; + } + + uint8_t *out_cif; + struct uref *uref = upipe_srt_handshake_alloc_hs(upipe, size, timestamp, &out_cif, false); + if (!uref) + return NULL; + + build_hs(upipe, out_cif, extension, true, wrap, wrap_len); + + if (hs_packet->version == SRT_HANDSHAKE_VERSION_MIN) { + struct uref *next = uref_block_alloc(upipe_srt_handshake->uref_mgr, + upipe_srt_handshake->ubuf_mgr, SRT_HEADER_SIZE + SRT_HANDSHAKE_HSREQ_SIZE); + if (!next) + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + else { + uint8_t *out; + int output_size = -1; + if (likely(ubase_check(uref_block_write(next, 0, &output_size, &out)))) { + srt_set_packet_control(out, true); + srt_set_packet_timestamp(out, timestamp); + srt_set_packet_dst_socket_id(out, upipe_srt_handshake->remote_socket_id); + + srt_set_control_packet_type(out, SRT_CONTROL_TYPE_USER); + srt_set_control_packet_subtype(out, SRT_HANDSHAKE_EXT_TYPE_HSREQ); + srt_set_control_packet_type_specific(out, 0); + + uint8_t *out_ext = &out[SRT_HEADER_SIZE]; + srt_set_handshake_extension_srt_version(out_ext, upipe_srt_handshake->major, + upipe_srt_handshake->minor, upipe_srt_handshake->patch); + srt_set_handshake_extension_srt_flags(out_ext, upipe_srt_handshake->flags); + srt_set_handshake_extension_sender_tsbpd_delay(out_ext, upipe_srt_handshake->sender_tsbpd_delay); + srt_set_handshake_extension_receiver_tsbpd_delay(out_ext, upipe_srt_handshake->receiver_tsbpd_delay); + + uref_block_unmap(next, 0); + uref->uchain.next = &next->uchain; + } else { + uref_free(next); + } + } + +#ifdef UPIPE_HAVE_GCRYPT_H + if (upipe_srt_handshake->password) { + next = upipe_srt_handshake_make_kmreq(upipe, timestamp); + if (next) + uref->uchain.next->next = &next->uchain; + } +#endif + } + + upipe_throw(upipe, UPROBE_SRT_HANDSHAKE_CONNECTED, UPIPE_SRT_HANDSHAKE_SIGNATURE, true, false); + upipe_srt_handshake_finalize(upipe); + + uref_block_unmap(uref, 0); + return uref; +} + +static const char *get_hs_type(uint32_t type) +{ + switch (type) { + case SRT_HANDSHAKE_TYPE_DONE: return "done"; + case SRT_HANDSHAKE_TYPE_AGREEMENT: return "agreement"; + case SRT_HANDSHAKE_TYPE_CONCLUSION: return "conclusion"; + case SRT_HANDSHAKE_TYPE_WAVEHAND: return "wavehand"; + case SRT_HANDSHAKE_TYPE_INDUCTION: return "induction"; + /* rejections */ + } + return "?"; +} + +static struct uref *upipe_srt_handshake_handle_hs(struct upipe *upipe, const uint8_t *buf, int size, uint64_t now) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + uint32_t timestamp = (now - upipe_srt_handshake->establish_time) / 27; + + struct hs_packet hs_packet; + + const uint8_t *cif = srt_get_control_packet_cif(buf); + size -= SRT_HEADER_SIZE; + if (!srt_check_handshake(cif, size)) { + upipe_err(upipe, "Malformed handshake"); + return NULL; + } + + hs_packet.version = srt_get_handshake_version(cif); + hs_packet.encryption = srt_get_handshake_encryption(cif); + hs_packet.extension = srt_get_handshake_extension(cif); + hs_packet.syn_cookie = srt_get_handshake_syn_cookie(cif); + hs_packet.dst_socket_id = srt_get_packet_dst_socket_id(buf); + hs_packet.remote_socket_id = srt_get_handshake_socket_id(cif); + hs_packet.mtu = srt_get_handshake_mtu(cif); + hs_packet.mfw = srt_get_handshake_mfw(cif); + hs_packet.isn = srt_get_handshake_isn(cif); + hs_packet.ext_buf = srt_get_handshake_extension_buf((uint8_t*)cif); + + size -= SRT_HANDSHAKE_CIF_SIZE; + + uint32_t hs_type = srt_get_handshake_type(cif); + if (hs_type >= SRT_HANDSHAKE_TYPE_REJ_UNKNOWN && hs_type <= SRT_HANDSHAKE_TYPE_REJ_GROUP) { + upipe_err_va(upipe, "Remote rejected handshake (%s)", get_hs_error(hs_type)); + if (!upipe_srt_handshake->listener) + upipe_srt_handshake->syn_cookie = 0; + upipe_srt_handshake_disconnect(upipe, true, false); + return NULL; + } + + if (upipe_srt_handshake->upump_keepalive_timeout && hs_packet.dst_socket_id == upipe_srt_handshake->socket_id) { + upipe_dbg_va(upipe, "Already connected, ignoring hs type 0x%x", hs_type); + return NULL; + } + + bool conclusion = upipe_srt_handshake->expect_conclusion; + + if (conclusion) { + /* Don't send a rejection as it could be a duplicate Induction on a long latency link */ + if (hs_type != SRT_HANDSHAKE_TYPE_CONCLUSION) { + upipe_dbg_va(upipe, "Expected conclusion, ignore hs type %s", get_hs_type(hs_type)); + return NULL; + } + } else { + /* We do not expect a conclusion, this means we're either connected, + * or still expecting first packet */ + if (upipe_srt_handshake->upump_keepalive_timeout) { + /* We're already connected but received a new socket id */ + upipe_dbg(upipe, "Ignore handshake, already connected"); + return NULL; + } + + if (hs_type != SRT_HANDSHAKE_TYPE_INDUCTION) { + upipe_err_va(upipe, "Expected induction, ignore hs type %s", get_hs_type(hs_type)); + upipe_srt_handshake_disconnect(upipe, false, false); + return upipe_srt_handshake_alloc_hs_reject(upipe, timestamp, + hs_packet.remote_socket_id, SRT_HANDSHAKE_TYPE_REJ_UNKNOWN); + } + } + + upipe_dbg_va(upipe, "[%s] got %s handshake packet", + upipe_srt_handshake->listener ? "listener" : "caller", + conclusion ? "conclusion" : "induction"); + + struct uref *uref; + if (!upipe_srt_handshake->listener) { + if (conclusion) { + uref = upipe_srt_handshake_handle_hs_caller_conclusion(upipe, size, timestamp, &hs_packet); + /* We don't need the cached conclusion packet any more */ + if (upipe_srt_handshake->caller_conclusion) { + uref_free(upipe_srt_handshake->caller_conclusion); + upipe_srt_handshake->caller_conclusion = NULL; + } + } else { + if (hs_packet.version != SRT_HANDSHAKE_VERSION || hs_packet.dst_socket_id != upipe_srt_handshake->socket_id) { + upipe_err_va(upipe, "Malformed handshake (%08x != %08x)", + hs_packet.dst_socket_id, upipe_srt_handshake->socket_id); + upipe_srt_handshake_disconnect(upipe, false, false); + return upipe_srt_handshake_alloc_hs_reject(upipe, timestamp, + hs_packet.remote_socket_id, SRT_HANDSHAKE_TYPE_REJ_UNKNOWN); + } + uref = upipe_srt_handshake_handle_hs_caller_induction(upipe, size, timestamp, &hs_packet); + if (upipe_srt_handshake->caller_conclusion) + uref_free(upipe_srt_handshake->caller_conclusion); + upipe_srt_handshake->caller_conclusion = uref_dup(uref); + } + } else { /* listener */ + if (conclusion) { + uref = upipe_srt_handshake_handle_hs_listener_conclusion(upipe, size, timestamp, &hs_packet); + } else { + if (hs_packet.version != SRT_HANDSHAKE_VERSION_MIN || hs_packet.encryption != SRT_HANDSHAKE_CIPHER_NONE || + hs_packet.extension != SRT_HANDSHAKE_EXT_KMREQ || + hs_packet.syn_cookie != 0 || hs_packet.dst_socket_id != 0) { + upipe_err_va(upipe, "Malformed first handshake syn %u dst_id %u", hs_packet.syn_cookie, hs_packet.dst_socket_id); + upipe_srt_handshake_disconnect(upipe, false, false); + return upipe_srt_handshake_alloc_hs_reject(upipe, timestamp, + hs_packet.remote_socket_id, SRT_HANDSHAKE_TYPE_REJ_UNKNOWN); + } + + uref = upipe_srt_handshake_handle_hs_listener_induction(upipe, size, timestamp, &hs_packet); + if (uref) + upipe_srt_handshake->establish_time = now; + } + } + + if (uref) { + if (!conclusion) { + upipe_srt_handshake->expect_conclusion = true; + + if (!upipe_srt_handshake->upump_handshake_timeout) { + /* connection has to succeed within 3 seconds */ + struct upump *upump = + upump_alloc_timer(upipe_srt_handshake->upump_mgr, + upipe_srt_handshake_timeout, + upipe, upipe->refcount, + 3 * UCLOCK_FREQ, 0); + upump_start(upump); + upipe_srt_handshake_set_upump_handshake_timeout(upipe, upump); + } + } + } + + return uref; +} + +static struct uref *upipe_srt_handshake_handle_user(struct upipe *upipe, const uint8_t *buf, int size, uint64_t now) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + uint32_t timestamp = (now - upipe_srt_handshake->establish_time) / 27; + + uint16_t subtype = srt_get_control_packet_subtype(buf); + if (subtype != SRT_HANDSHAKE_EXT_TYPE_KMRSP && subtype != SRT_HANDSHAKE_EXT_TYPE_KMREQ) + return NULL; + + const uint8_t *wrap; + uint8_t wrap_len = 0; + + const uint8_t *cif = srt_get_control_packet_cif(buf); + size -= SRT_HEADER_SIZE; + + if (!upipe_srt_handshake->password) { + upipe_err(upipe, "Password not specified but remote requested encryption in user packet."); + upipe_throw(upipe, UPROBE_SRT_HANDSHAKE_CONNECTED, UPIPE_SRT_HANDSHAKE_SIGNATURE, false, true, false); + upipe_srt_handshake->expect_conclusion = false; + return upipe_srt_handshake_alloc_hs_reject(upipe, timestamp, + srt_get_packet_dst_socket_id(buf), SRT_HANDSHAKE_TYPE_REJ_BADSECRET); + } + + if (!upipe_srt_handshake_parse_kmreq(upipe, cif, size, &wrap, &wrap_len)) { + return NULL; + } + + if (subtype == SRT_HANDSHAKE_EXT_TYPE_KMRSP) { + if (upipe_srt_handshake->kmreq) { + upipe_srt_handshake_set_upump_kmreq(upipe, NULL); + uref_free(upipe_srt_handshake->kmreq); + upipe_srt_handshake->kmreq = NULL; + } + upipe_srt_handshake_finalize(upipe); + return NULL; + } + + struct uref *uref = uref_block_alloc(upipe_srt_handshake->uref_mgr, + upipe_srt_handshake->ubuf_mgr, SRT_HEADER_SIZE + SRT_KMREQ_COMMON_SIZE + wrap_len); + if (!uref) + return NULL; + uint8_t *out; + int output_size = -1; + if (unlikely(!ubase_check(uref_block_write(uref, 0, &output_size, &out)))) { + uref_free(uref); + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + } + + srt_set_packet_control(out, true); + srt_set_packet_timestamp(out, timestamp); + srt_set_packet_dst_socket_id(out, upipe_srt_handshake->remote_socket_id); + srt_set_control_packet_type(out, SRT_CONTROL_TYPE_USER); + srt_set_control_packet_subtype(out, SRT_HANDSHAKE_EXT_TYPE_KMRSP); + srt_set_control_packet_type_specific(out, 0); + uint8_t *extra = (uint8_t*)srt_get_control_packet_cif(out); + memset(extra, 0, SRT_KMREQ_COMMON_SIZE); + + if (wrap_len) { + make_km_msg(upipe, extra, wrap, wrap_len); + } + upipe_srt_handshake_finalize(upipe); + + uref_block_unmap(uref, 0); + return uref; +} + +static struct uref *upipe_srt_handshake_handle_ack(struct upipe *upipe, const uint8_t *buf, int size, uint64_t now) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + uint32_t timestamp = (now - upipe_srt_handshake->establish_time) / 27; + uint32_t ack_number = srt_get_control_packet_type_specific(buf); + + /* Don't output an ACKACK on Light ACKs or Small ACKs */ + if (!ack_number) + return NULL; + + struct uref *uref = uref_block_alloc(upipe_srt_handshake->uref_mgr, + upipe_srt_handshake->ubuf_mgr, SRT_HEADER_SIZE + 4 /* undocumented extra padding */); + if (!uref) + return NULL; + uint8_t *out; + int output_size = -1; + if (unlikely(!ubase_check(uref_block_write(uref, 0, &output_size, &out)))) { + uref_free(uref); + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + } + + srt_set_packet_control(out, true); + srt_set_packet_timestamp(out, timestamp); + srt_set_packet_dst_socket_id(out, upipe_srt_handshake->remote_socket_id); + srt_set_control_packet_type(out, SRT_CONTROL_TYPE_ACKACK); + srt_set_control_packet_subtype(out, 0); + srt_set_control_packet_type_specific(out, ack_number); + uint8_t *extra = (uint8_t*)srt_get_control_packet_cif(out); + memset(extra, 0, 4); + + uref_block_unmap(uref, 0); + return uref; + // should go to sender +} + +static struct uref *upipe_srt_handshake_input_control(struct upipe *upipe, const uint8_t *buf, int size, bool *handled) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + + uint16_t type = srt_get_control_packet_type(buf); + uint64_t now = uclock_now(upipe_srt_handshake->uclock); + const uint8_t *cif = srt_get_control_packet_cif(buf); + + upipe_verbose_va(upipe, "control pkt %s", get_ctrl_type(type)); + *handled = true; + + if (size < SRT_HEADER_SIZE) { + upipe_err_va(upipe, "control packet too small (%d)", size); + return NULL; + } + + switch (type) { + case SRT_CONTROL_TYPE_HANDSHAKE: + return upipe_srt_handshake_handle_hs(upipe, buf, size, now); + + case SRT_CONTROL_TYPE_KEEPALIVE: + break; + + case SRT_CONTROL_TYPE_ACK: + return upipe_srt_handshake_handle_ack(upipe, buf, size, now); + + case SRT_CONTROL_TYPE_USER: + return upipe_srt_handshake_handle_user(upipe, buf, size, now); + + case SRT_CONTROL_TYPE_SHUTDOWN: + upipe_err_va(upipe, "shutdown requested"); + upipe_srt_handshake_disconnect(upipe, true, false); + break; + + case SRT_CONTROL_TYPE_DROPREQ: + if (!srt_check_dropreq(cif, size - SRT_HEADER_SIZE)) { + upipe_err_va(upipe, "dropreq pkt invalid"); + } else { + uint32_t first = srt_get_dropreq_first_seq(cif); + uint32_t last = srt_get_dropreq_last_seq(cif); + upipe_dbg_va(upipe, "sender dropped packets from %u to %u", first, last); + } + break; + + case SRT_CONTROL_TYPE_NAK: /* fallthrough */ + case SRT_CONTROL_TYPE_ACKACK: // send to next pipe for RTT estimation + default: + *handled = false; + break; + } + + return NULL; +} + +static void upipe_srt_handshake_input(struct upipe *upipe, struct uref *uref, + struct upump **upump_p) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + + if (!upipe_srt_handshake->upump_mgr) { + upipe_warn(upipe, "No upump mgr"); + upipe_srt_handshake_check(upipe, NULL); + uref_free(uref); + return; + } + + if (!upipe_srt_handshake->uclock) { + upipe_warn(upipe, "No uclock"); + upipe_srt_handshake_check(upipe, NULL); + uref_free(uref); + return; + } + + /* Pipe is gonna die soon */ + if (upipe_srt_handshake->end) { + uref_free(uref); + return; + } + size_t total_size; + ubase_assert(uref_block_size(uref, &total_size)); + + const uint8_t *buf; + int size = total_size; + + ubase_assert(uref_block_read(uref, 0, &size, &buf)); + assert(size == total_size); + + if (size < SRT_HEADER_SIZE) { + upipe_err_va(upipe, "Packet too small (%d)", size); + ubase_assert(uref_block_unmap(uref, 0)); + uref_free(uref); + return; + } + + bool control = srt_get_packet_control(buf); + uint32_t dst_socket_id = srt_get_packet_dst_socket_id(buf); + + if (dst_socket_id == 0) { + uint16_t type = srt_get_control_packet_type(buf); + if (!control || type != SRT_CONTROL_TYPE_HANDSHAKE) { + upipe_dbg_va(upipe, "dst socket id unset (%s)", + control ? get_ctrl_type(type) : "data"); + ubase_assert(uref_block_unmap(uref, 0)); + uref_free(uref); + // blacklist? + // XXX: find out who is sending shutdown without dst socket set + // XXX: sometimes the legitimate caller does it too + upipe_srt_handshake_disconnect(upipe, true, false); + return; + } + } + + if (dst_socket_id && dst_socket_id != upipe_srt_handshake->socket_id) { + upipe_dbg_va(upipe, "0x%08x != 0x%08x", dst_socket_id, + upipe_srt_handshake->socket_id); + ubase_assert(uref_block_unmap(uref, 0)); + uref_free(uref); + upipe_srt_handshake_disconnect(upipe, true, false); + return; + } + + /* Only restart if timer is already alive, it means we're connected */ + if (upipe_srt_handshake->upump_keepalive_timeout && dst_socket_id == upipe_srt_handshake->socket_id) + upipe_srt_handshake_restart_keepalive_timeout(upipe); + + if (control) { + bool handled = false; + struct uref *reply = upipe_srt_handshake_input_control(upipe, buf, size, &handled); + ubase_assert(uref_block_unmap(uref, 0)); + if (!handled && !reply) { + upipe_srt_handshake_output(upipe, uref, upump_p); + } else { + uref_free(uref); + if (reply) { + struct uchain *next = reply->uchain.next; + upipe_srt_handshake_output(&upipe_srt_handshake->upipe, reply, upump_p); + if (next) { + struct uchain *next2 = next->next; + upipe_srt_handshake_output(&upipe_srt_handshake->upipe, uref_from_uchain(next), upump_p); + if (next2) + upipe_srt_handshake_output(&upipe_srt_handshake->upipe, uref_from_uchain(next2), upump_p); + } + } + } + } else { + ubase_assert(uref_block_unmap(uref, 0)); + /* let data packets pass through */ + upipe_srt_handshake_output(upipe, uref, upump_p); + } +} + +/** @This frees a upipe. + * + * @param upipe description structure of the pipe + */ +static void upipe_srt_handshake_free(struct upipe *upipe) +{ + struct upipe_srt_handshake *upipe_srt_handshake = upipe_srt_handshake_from_upipe(upipe); + upipe_srt_handshake_shutdown(upipe); + upipe_throw_dead(upipe); + + free(upipe_srt_handshake->password); + free(upipe_srt_handshake->stream_id); + + if (upipe_srt_handshake->kmreq) + uref_free(upipe_srt_handshake->kmreq); + + if (upipe_srt_handshake->caller_conclusion) + uref_free(upipe_srt_handshake->caller_conclusion); + + upipe_srt_handshake_clean_output(upipe); + upipe_srt_handshake_clean_upump_handshake_send(upipe); + upipe_srt_handshake_clean_upump_handshake_timeout(upipe); + upipe_srt_handshake_clean_upump_keepalive_timeout(upipe); + upipe_srt_handshake_clean_upump_kmreq(upipe); + upipe_srt_handshake_clean_upump_mgr(upipe); + upipe_srt_handshake_clean_uclock(upipe); + upipe_srt_handshake_clean_ubuf_mgr(upipe); + upipe_srt_handshake_clean_uref_mgr(upipe); + upipe_srt_handshake_clean_urefcount(upipe); + upipe_srt_handshake_free_void(upipe); +} + +/** module manager static descriptor */ +static struct upipe_mgr upipe_srt_handshake_mgr = { + .refcount = NULL, + .signature = UPIPE_SRT_HANDSHAKE_SIGNATURE, + + .upipe_alloc = upipe_srt_handshake_alloc, + .upipe_input = upipe_srt_handshake_input, + .upipe_control = upipe_srt_handshake_control, + + .upipe_mgr_control = NULL +}; + +/** @This returns the management structure for all SRT handshake sources + * + * @return pointer to manager + * @param seed random seed for socket id and syn cookie + */ +struct upipe_mgr *upipe_srt_handshake_mgr_alloc(long seed) +{ + srand48(seed); + return &upipe_srt_handshake_mgr; +} diff --git a/lib/upipe-srt/upipe_srt_receiver.c b/lib/upipe-srt/upipe_srt_receiver.c new file mode 100644 index 000000000..678acb5a3 --- /dev/null +++ b/lib/upipe-srt/upipe_srt_receiver.c @@ -0,0 +1,1458 @@ +/* + * Copyright (C) 2023 Open Broadcast Systems Ltd + * + * Authors: Rafaël Carré + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject + * to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + * @short Upipe module for SRT receivers + */ + +#include "upipe/config.h" +#include "upipe/ubase.h" +#include "upipe/uclock.h" +#include "upipe/uref.h" +#include "upipe/uref_block.h" +#include "upipe/uref_block_flow.h" +#include "upipe/uref_clock.h" +#include "upipe/upipe.h" +#include "upipe/upipe_helper_upipe.h" +#include "upipe/upipe_helper_subpipe.h" +#include "upipe/upipe_helper_urefcount.h" +#include "upipe/upipe_helper_urefcount_real.h" +#include "upipe/upipe_helper_void.h" +#include "upipe/upipe_helper_uref_mgr.h" +#include "upipe/upipe_helper_ubuf_mgr.h" +#include "upipe/upipe_helper_output.h" +#include "upipe/upipe_helper_upump_mgr.h" +#include "upipe/upipe_helper_upump.h" +#include "upipe/upipe_helper_uclock.h" + +#include "upipe-srt/upipe_srt_receiver.h" + +#include + +#include +#include + +#include + +/** @hidden */ +static int upipe_srt_receiver_check(struct upipe *upipe, struct uref *flow_format); + +struct ack_entry { + uint32_t ack_num; + uint64_t timestamp; +}; + +/** @internal @This is the private context of a SRT receiver pipe. */ +struct upipe_srt_receiver { + /** real refcount management structure */ + struct urefcount urefcount_real; + /** refcount management structure exported to the public structure */ + struct urefcount urefcount; + + struct upipe_mgr sub_mgr; + /** list of output subpipes */ + struct uchain outputs; + + struct upump_mgr *upump_mgr; + struct upump *upump_timer; + struct upump *upump_timer_lost; + struct uclock *uclock; + struct urequest uclock_request; + + + /** 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; + + /** pipe acting as output */ + struct upipe *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; + + uint64_t socket_id; + + struct upipe *control; + + struct uchain queue; + /** expected sequence number */ + uint64_t expected_seqnum; + + /** last seq output */ + uint64_t last_output_seqnum; + + /* stats */ + size_t buffered; + size_t nacks; + size_t repaired; + size_t loss; + size_t dups; + + /** buffer latency */ + uint64_t latency; + /** last time a NACK was sent */ + uint64_t last_nack[65536]; + + /** last time any SRT packet was sent */ + uint64_t last_sent; + + /** number of packets in the buffer */ + uint64_t packets; + + /** number of bytes in the buffer*/ + uint64_t bytes; + + uint32_t ack_num; + uint64_t last_ack; + struct ack_entry *acks; + size_t n_acks; + size_t ack_ridx; + size_t ack_widx; + + uint64_t rtt; + uint64_t rtt_variance; + + uint8_t salt[14]; + uint8_t salt_len; + uint8_t sek[2][32]; + uint8_t sek_len; + + uint64_t establish_time; + + uint64_t previous_ts; + + /** public upipe structure */ + struct upipe upipe; +}; + +UPIPE_HELPER_UPIPE(upipe_srt_receiver, upipe, UPIPE_SRT_RECEIVER_SIGNATURE) +UPIPE_HELPER_UREFCOUNT(upipe_srt_receiver, urefcount, upipe_srt_receiver_no_input) +UPIPE_HELPER_UREFCOUNT_REAL(upipe_srt_receiver, urefcount_real, upipe_srt_receiver_free); + +UPIPE_HELPER_VOID(upipe_srt_receiver) + +UPIPE_HELPER_OUTPUT(upipe_srt_receiver, output, flow_def, output_state, request_list) +UPIPE_HELPER_UPUMP_MGR(upipe_srt_receiver, upump_mgr) +UPIPE_HELPER_UPUMP(upipe_srt_receiver, upump_timer, upump_mgr) +UPIPE_HELPER_UPUMP(upipe_srt_receiver, upump_timer_lost, upump_mgr) +UPIPE_HELPER_UCLOCK(upipe_srt_receiver, uclock, uclock_request, NULL, upipe_throw_provide_request, NULL) + +UPIPE_HELPER_UREF_MGR(upipe_srt_receiver, uref_mgr, uref_mgr_request, + upipe_srt_receiver_check, + upipe_srt_receiver_register_output_request, + upipe_srt_receiver_unregister_output_request) +UPIPE_HELPER_UBUF_MGR(upipe_srt_receiver, ubuf_mgr, flow_format, ubuf_mgr_request, + upipe_srt_receiver_check, + upipe_srt_receiver_register_output_request, + upipe_srt_receiver_unregister_output_request) + +/** @internal @This is the private context of a SRT receiver output pipe. */ +struct upipe_srt_receiver_output { + /** refcount management structure */ + struct urefcount urefcount; + /** structure for double-linked lists */ + struct uchain uchain; + + /** 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; + + /** pipe acting as output */ + struct upipe *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; + + /** public upipe structure */ + struct upipe upipe; +}; + +static int upipe_srt_receiver_output_check(struct upipe *upipe, struct uref *flow_format); +UPIPE_HELPER_UPIPE(upipe_srt_receiver_output, upipe, UPIPE_SRT_RECEIVER_OUTPUT_SIGNATURE) +UPIPE_HELPER_VOID(upipe_srt_receiver_output); +UPIPE_HELPER_UREFCOUNT(upipe_srt_receiver_output, urefcount, upipe_srt_receiver_output_free) +UPIPE_HELPER_OUTPUT(upipe_srt_receiver_output, output, flow_def, output_state, request_list) +UPIPE_HELPER_UREF_MGR(upipe_srt_receiver_output, uref_mgr, uref_mgr_request, + upipe_srt_receiver_output_check, + upipe_srt_receiver_output_register_output_request, + upipe_srt_receiver_output_unregister_output_request) +UPIPE_HELPER_UBUF_MGR(upipe_srt_receiver_output, ubuf_mgr, flow_format, ubuf_mgr_request, + upipe_srt_receiver_output_check, + upipe_srt_receiver_output_register_output_request, + upipe_srt_receiver_output_unregister_output_request) +UPIPE_HELPER_SUBPIPE(upipe_srt_receiver, upipe_srt_receiver_output, output, sub_mgr, outputs, + uchain) + +static int upipe_srt_receiver_output_check(struct upipe *upipe, struct uref *flow_format) +{ + struct upipe_srt_receiver_output *upipe_srt_receiver_output = upipe_srt_receiver_output_from_upipe(upipe); + if (flow_format) + upipe_srt_receiver_output_store_flow_def(upipe, flow_format); + + if (upipe_srt_receiver_output->uref_mgr == NULL) { + upipe_srt_receiver_output_require_uref_mgr(upipe); + return UBASE_ERR_NONE; + } + + if (upipe_srt_receiver_output->ubuf_mgr == NULL) { + struct uref *flow_format = + uref_block_flow_alloc_def(upipe_srt_receiver_output->uref_mgr, NULL); + if (unlikely(flow_format == NULL)) { + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + return UBASE_ERR_ALLOC; + } + upipe_srt_receiver_output_require_ubuf_mgr(upipe, flow_format); + return UBASE_ERR_NONE; + } + + return UBASE_ERR_NONE; +} + +/** @This is called when there is no external reference to the pipe anymore. + * + * @param upipe description structure of the pipe + */ +static void upipe_srt_receiver_no_input(struct upipe *upipe) +{ + upipe_srt_receiver_throw_sub_outputs(upipe, UPROBE_SOURCE_END); + upipe_srt_receiver_release_urefcount_real(upipe); +} +/** @internal @This allocates an output subpipe of a dup pipe. + * + * @param mgr common management structure + * @param uprobe structure used to raise events + * @param signature signature of the pipe allocator + * @param args optional arguments + * @return pointer to upipe or NULL in case of allocation error + */ +static struct upipe *upipe_srt_receiver_output_alloc(struct upipe_mgr *mgr, + struct uprobe *uprobe, + uint32_t signature, va_list args) +{ + if (mgr->signature != UPIPE_SRT_RECEIVER_OUTPUT_SIGNATURE) + return NULL; + + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_sub_mgr(mgr); + if (upipe_srt_receiver->control) + return NULL; + + struct upipe *upipe = upipe_srt_receiver_output_alloc_void(mgr, uprobe, signature, args); + if (unlikely(upipe == NULL)) + return NULL; + + upipe_srt_receiver->control = upipe; + + upipe_srt_receiver_output_init_urefcount(upipe); + upipe_srt_receiver_output_init_output(upipe); + upipe_srt_receiver_output_init_sub(upipe); + upipe_srt_receiver_output_init_ubuf_mgr(upipe); + upipe_srt_receiver_output_init_uref_mgr(upipe); + + upipe_throw_ready(upipe); + + upipe_srt_receiver_output_require_uref_mgr(upipe); + + return upipe; +} + + +/** @This frees a upipe. + * + * @param upipe description structure of the pipe + */ +static void upipe_srt_receiver_output_free(struct upipe *upipe) +{ + //struct upipe_srt_receiver_output *upipe_srt_receiver_output = upipe_srt_receiver_output_from_upipe(upipe); + upipe_throw_dead(upipe); + + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_sub_mgr(upipe->mgr); + upipe_srt_receiver_set_upump_timer_lost(&upipe_srt_receiver->upipe, NULL); + upipe_srt_receiver->control = NULL; + upipe_srt_receiver_output_clean_output(upipe); + upipe_srt_receiver_output_clean_sub(upipe); + upipe_srt_receiver_output_clean_urefcount(upipe); + upipe_srt_receiver_output_clean_ubuf_mgr(upipe); + upipe_srt_receiver_output_clean_uref_mgr(upipe); + upipe_srt_receiver_output_free_void(upipe); +} + +static uint64_t upipe_srt_receiver_get_rtt(struct upipe *upipe) +{ + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + + /* VSF TR-06 doesn't give a mean to retrieve RTT, but defaults to 7 + * retransmissions requests per packet. + * XXX: make it configurable ? */ + + uint64_t rtt = upipe_srt_receiver->rtt; + if (!rtt) + rtt = upipe_srt_receiver->latency / 7; + return rtt; +} + + +/** @internal @This periodic timer checks for missing seqnums. + */ +static void upipe_srt_receiver_timer_lost(struct upump *upump) +{ + struct upipe *upipe = upump_get_opaque(upump, struct upipe *); + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + + uint64_t expected_seq = UINT64_MAX; + + uint64_t rtt = upipe_srt_receiver_get_rtt(upipe); + + uint64_t now = uclock_now(upipe_srt_receiver->uclock); + + if (upipe_srt_receiver->socket_id != UINT64_MAX && now - upipe_srt_receiver->last_sent > UCLOCK_FREQ) { + struct uref *uref = uref_block_alloc(upipe_srt_receiver->uref_mgr, + upipe_srt_receiver->ubuf_mgr, SRT_HEADER_SIZE + 4 /* undocumented extra padding */); + if (uref) { + uint8_t *out; + int output_size = -1; + if (unlikely(!ubase_check(uref_block_write(uref, 0, &output_size, &out)))) { + uref_free(uref); + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + } + + srt_set_packet_control(out, true); + srt_set_packet_timestamp(out, (now - upipe_srt_receiver->establish_time) / 27); + srt_set_packet_dst_socket_id(out, upipe_srt_receiver->socket_id); + srt_set_control_packet_type(out, SRT_CONTROL_TYPE_KEEPALIVE); + srt_set_control_packet_subtype(out, 0); + srt_set_control_packet_type_specific(out, 0); + uint8_t *extra = (uint8_t*)srt_get_control_packet_cif(out); + memset(extra, 0, 4); + + uref_block_unmap(uref, 0); + + upipe_srt_receiver->last_sent = now; + upipe_srt_receiver_output_output(upipe_srt_receiver->control, uref, + &upipe_srt_receiver->upump_timer_lost); + } + } + + if (upipe_srt_receiver->buffered == 0) + return; + + /* space out NACKs a bit more than RTT. XXX: tune me */ + uint64_t next_nack = now - rtt * 12 / 10; + + /* TODO: do not look at the last pkts/s * rtt + * It it too late to send a NACK for these + * XXX: use cr_sys, because pkts/s also accounts for + * the retransmitted packets */ + + struct uchain *uchain; + int holes = 0; + + uint64_t last_received = UINT64_MAX; + + + int s = 1472; /* 1500 - IP - UDP */ + struct uref *pkt = NULL; + uint8_t *cif = NULL; + + ulist_foreach(&upipe_srt_receiver->queue, uchain) { + struct uref *uref = uref_from_uchain(uchain); + uint64_t seqnum = 0; + uref_attr_get_priv(uref, &seqnum); + + if (likely(expected_seq != UINT64_MAX) && seqnum != expected_seq) { + /* hole found */ + upipe_verbose_va(upipe, "Found hole from %"PRIu64" (incl) to %"PRIu64" (excl)", + expected_seq, seqnum); + if (last_received == UINT64_MAX) + last_received = expected_seq - 1; + + for (uint32_t seq = expected_seq; seq != seqnum; seq = (seq + 1) & ~(1 << 31)) { + /* if packet was lost, we should have detected it already */ + if (upipe_srt_receiver->last_nack[seq & 0xffff] == 0) { + upipe_err_va(upipe, "packet %u missing but was not marked as lost!", seq); + continue; + } + + /* if we sent a NACK not too long ago, do not repeat it */ + /* since NACKs are sent in a batch, break loop if the first packet is too early */ + if (upipe_srt_receiver->last_nack[seq & 0xffff] > next_nack) { + if (0) upipe_err_va(upipe, "Cancelling NACK due to RTT (seq %u diff %"PRId64"", + seq, next_nack - upipe_srt_receiver->last_nack[seq & 0xffff] + ); + goto next; + } + } + + /* update NACK request time */ + for (uint32_t seq = expected_seq; seq != seqnum; seq = (seq + 1) & ~(1 << 31)) { + upipe_srt_receiver->last_nack[seq & 0xffff] = now; + } + + if (!pkt) { + pkt = uref_block_alloc(upipe_srt_receiver->uref_mgr, upipe_srt_receiver->ubuf_mgr, s); + if (unlikely(!pkt)) { + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + return; + } + + uint8_t *buf; + uref_block_write(pkt, 0, &s, &buf); + memset(buf, 0, s); + + s -= SRT_HEADER_SIZE; + + srt_set_packet_control(buf, true); + srt_set_packet_timestamp(buf, (now - upipe_srt_receiver->establish_time) / 27); + srt_set_packet_dst_socket_id(buf, upipe_srt_receiver->socket_id); + srt_set_control_packet_type(buf, SRT_CONTROL_TYPE_NAK); + srt_set_control_packet_subtype(buf, 0); + cif = (uint8_t*)srt_get_control_packet_cif(buf); + } + + // TODO : if full, transmit and realloc next one + if (seqnum - expected_seq > 1) { + if (s < 8) { + upipe_warn_va(upipe, "NAK FULL"); + break; + } + cif[0] = ((expected_seq >> 24) & 0x7f) | 0x80; + cif[1] = (expected_seq >> 16) & 0xff; + cif[2] = (expected_seq >> 8) & 0xff; + cif[3] = (expected_seq ) & 0xff; + cif[4] = ((seqnum - 1) >> 24) & 0x7f; + cif[5] = ((seqnum - 1) >> 16) & 0xff; + cif[6] = ((seqnum - 1) >> 8) & 0xff; + cif[7] = ((seqnum - 1) ) & 0xff; + s -= 8; + cif += 8; + } else { + cif[0] = (expected_seq >> 24) & 0x7f; + cif[1] = (expected_seq >> 16) & 0xff; + cif[2] = (expected_seq >> 8) & 0xff; + cif[3] = (expected_seq ) & 0xff; + s -= 4; + cif += 4; + if (s < 4) { + upipe_warn_va(upipe, "NAK FULL"); + break; + } + } + + holes++; + } + +next: + expected_seq = (seqnum + 1) & ~(1 << 31); + } + + // A Full ACK control packet is sent every 10 ms and has all the fields of Figure 13. + if (upipe_srt_receiver->last_ack == UINT64_MAX || (now - upipe_srt_receiver->last_ack > UCLOCK_FREQ / 100)) { + struct uref *uref = uref_block_alloc(upipe_srt_receiver->uref_mgr, + upipe_srt_receiver->ubuf_mgr, SRT_HEADER_SIZE + SRT_ACK_CIF_SIZE_3); + // + if (uref) { + uint8_t *out; + int output_size = -1; + if (unlikely(!ubase_check(uref_block_write(uref, 0, &output_size, &out)))) { + uref_free(uref); + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + } else { + uint32_t ack_num = upipe_srt_receiver->ack_num++; + srt_set_packet_control(out, true); + srt_set_packet_timestamp(out, (now - upipe_srt_receiver->establish_time) / 27); + srt_set_packet_dst_socket_id(out, upipe_srt_receiver->socket_id); + srt_set_control_packet_type(out, SRT_CONTROL_TYPE_ACK); + srt_set_control_packet_subtype(out, 0); + srt_set_control_packet_type_specific(out, ack_num); + uint8_t *out_cif = (uint8_t*)srt_get_control_packet_cif(out); + + uint64_t last_seq = 0; + uref_attr_get_priv(uref_from_uchain(upipe_srt_receiver->queue.prev), &last_seq); + if (last_received != UINT64_MAX) + last_seq = last_received; + srt_set_ack_last_ack_seq(out_cif, last_seq); + srt_set_ack_rtt(out_cif, upipe_srt_receiver->rtt * 1000000 / UCLOCK_FREQ); + srt_set_ack_rtt_variance(out_cif, upipe_srt_receiver->rtt_variance * 1000000 / UCLOCK_FREQ); + uint64_t t = upipe_srt_receiver->latency; + + uint64_t packets_per_sec = upipe_srt_receiver->packets * UCLOCK_FREQ / t; + srt_set_ack_packets_receiving_rate(out_cif, packets_per_sec); + /* If we sent value 0, libsrt will stop sending */ + srt_set_ack_avail_bufsize(out_cif, 8192); + srt_set_ack_estimated_link_capacity(out_cif, 10 * packets_per_sec); /* ? */ + srt_set_ack_receiving_rate(out_cif, upipe_srt_receiver->bytes * UCLOCK_FREQ / t); + + uref_block_unmap(uref, 0); + upipe_srt_receiver->last_ack = now; + if (!upipe_srt_receiver->control) { + uref_free(uref); + return; + } + upipe_srt_receiver->last_sent = now; + upipe_srt_receiver_output_output(upipe_srt_receiver->control, uref, NULL); + + upipe_srt_receiver->acks[upipe_srt_receiver->ack_widx].ack_num = ack_num; + upipe_srt_receiver->acks[upipe_srt_receiver->ack_widx].timestamp = now; + upipe_srt_receiver->ack_widx++; + upipe_srt_receiver->ack_widx %= upipe_srt_receiver->n_acks; + } + } + } + + if (holes) { /* debug stats */ + static uint64_t old; + if (likely(old != 0)) + upipe_verbose_va(upipe, "%d holes after %"PRIu64" ms", + holes, 1000 * (now - old) / UCLOCK_FREQ); + old = now; + + upipe_srt_receiver->nacks += holes; // XXX + + uref_block_unmap(pkt, 0); + + // XXX : date NACK packet? + //uref_clock_set_date_sys(pkt, /* cr */ 0, UREF_DATE_CR); + + uref_block_resize(pkt, 0, 1472 - s); + + upipe_srt_receiver->last_sent = now; + upipe_srt_receiver_output_output(upipe_srt_receiver->control, pkt, NULL); + } +} + +/** @internal @This periodic timer remove seqnums from the buffer. + */ +static void upipe_srt_receiver_timer(struct upump *upump) +{ + struct upipe *upipe = upump_get_opaque(upump, struct upipe *); + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + + uint64_t now = uclock_now(upipe_srt_receiver->uclock); + + struct uchain *uchain, *uchain_tmp; + ulist_delete_foreach(&upipe_srt_receiver->queue, uchain, uchain_tmp) { + struct uref *uref = uref_from_uchain(uchain); + uint64_t seqnum = 0; + if (!ubase_check(uref_attr_get_priv(uref, &seqnum))) { + upipe_err_va(upipe, "Could not read seqnum from uref"); + } + + uint64_t cr_sys = 0; + if (unlikely(!ubase_check(uref_clock_get_cr_sys(uref, &cr_sys)))) + upipe_warn_va(upipe, "Couldn't read cr_sys in %s()", __func__); + + if (now <= cr_sys + upipe_srt_receiver->latency) + break; + + upipe_verbose_va(upipe, "Output seq %"PRIu64" after %"PRIu64" clocks", seqnum, now - cr_sys); + if (likely(upipe_srt_receiver->last_output_seqnum != UINT64_MAX)) { + uint32_t diff = seqnum - upipe_srt_receiver->last_output_seqnum - 1; + diff &= ~(1 << 31); // seqnums are 31 bits + if (diff) { + upipe_srt_receiver->loss += diff; + upipe_dbg_va(upipe, "PKT LOSS: %" PRIu64 " -> %"PRIu64" DIFF %u", + upipe_srt_receiver->last_output_seqnum, seqnum, diff); + } + } + + upipe_srt_receiver->last_output_seqnum = seqnum; + + ulist_delete(uchain); + upipe_srt_receiver->packets--; + size_t size; + if (unlikely(!ubase_check(uref_block_size(uref, &size)))) + size = 0; + upipe_srt_receiver->bytes -= size; + + uref_clock_set_cr_sys(uref, cr_sys + upipe_srt_receiver->latency); + uref_clock_delete_date_prog(uref); + uref_clock_delete_rate(uref); + upipe_srt_receiver_output(upipe, uref, NULL); // XXX: use timer upump ? + + static uint64_t old; + if (now - old > UCLOCK_FREQ) { + upipe_dbg_va(upipe, "level %zu , %zu nacks (repaired %zu, lost %zu, dups %zu)", + upipe_srt_receiver->buffered, upipe_srt_receiver->nacks, + upipe_srt_receiver->repaired, upipe_srt_receiver->loss, + upipe_srt_receiver->dups); + old = now; + } + + if (--upipe_srt_receiver->buffered == 0) { + upipe_dbg_va(upipe, "Exhausted buffer"); + } + } +} + +static void upipe_srt_receiver_restart_timer(struct upipe *upipe) +{ + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + uint64_t rtt = upipe_srt_receiver_get_rtt(upipe); + + upipe_srt_receiver_set_upump_timer_lost(upipe, NULL); + if (upipe_srt_receiver->upump_mgr) { + struct upump *upump= + upump_alloc_timer(upipe_srt_receiver->upump_mgr, + upipe_srt_receiver_timer_lost, + upipe, upipe->refcount, + 0, rtt / 10); + upump_start(upump); + upipe_srt_receiver_set_upump_timer_lost(upipe, upump); + } +} + +/** @internal @This sets the input flow definition. + * + * @param upipe description structure of the pipe + * @param flow_def flow definition packet + * @return an error code + */ +static int upipe_srt_receiver_output_set_flow_def(struct upipe *upipe, struct uref *flow_def) +{ + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + if (flow_def == NULL) + return UBASE_ERR_INVALID; + UBASE_RETURN(uref_flow_match_def(flow_def, "block.")) + + if (upipe_srt_receiver->control) { + struct uref *flow_def_dup = uref_dup(flow_def); + if (unlikely(flow_def_dup == NULL)) + return UBASE_ERR_ALLOC; + upipe_srt_receiver_output_store_flow_def(upipe_srt_receiver->control, flow_def_dup); + } + + return UBASE_ERR_NONE; +} + +/** @internal @This processes control commands on an output subpipe of a dup + * pipe. + * + * @param upipe description structure of the pipe + * @param command type of command to process + * @param args arguments of the command + * @return an error code + */ +static int _upipe_srt_receiver_output_control(struct upipe *upipe, + int command, va_list args) +{ + UBASE_HANDLED_RETURN(upipe_srt_receiver_output_control_super(upipe, command, args)); + UBASE_HANDLED_RETURN(upipe_srt_receiver_output_control_output(upipe, command, args)); + switch (command) { + case UPIPE_SET_FLOW_DEF: { + struct uref *flow_def = va_arg(args, struct uref *); + return upipe_srt_receiver_output_set_flow_def(upipe, flow_def); + } + default: + return UBASE_ERR_UNHANDLED; + } +} +static int upipe_srt_receiver_output_control(struct upipe *upipe, int command, va_list args) +{ + UBASE_RETURN(_upipe_srt_receiver_output_control(upipe, command, args)) + return upipe_srt_receiver_output_check(upipe, NULL); +} + +/** @internal @This initializes the output manager for a srt set pipe. + * + * @param upipe description structure of the pipe + */ +static void upipe_srt_receiver_init_sub_mgr(struct upipe *upipe) +{ + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + struct upipe_mgr *sub_mgr = &upipe_srt_receiver->sub_mgr; + sub_mgr->refcount = upipe_srt_receiver_to_urefcount_real(upipe_srt_receiver); + sub_mgr->signature = UPIPE_SRT_RECEIVER_OUTPUT_SIGNATURE; + sub_mgr->upipe_alloc = upipe_srt_receiver_output_alloc; + sub_mgr->upipe_input = NULL; + sub_mgr->upipe_control = upipe_srt_receiver_output_control; +} + + +/** @internal @This allocates a SRT receiver pipe. + * + * @param mgr common management structure + * @param uprobe structure used to raise events + * @param signature signature of the pipe allocator + * @param args optional arguments + * @return pointer to upipe or NULL in case of allocation error + */ +static struct upipe *upipe_srt_receiver_alloc(struct upipe_mgr *mgr, + struct uprobe *uprobe, + uint32_t signature, va_list args) +{ + struct upipe *upipe = upipe_srt_receiver_alloc_void(mgr, uprobe, signature, args); + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + +#ifdef UPIPE_HAVE_GCRYPT_H + if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { + uprobe_err(uprobe, upipe, "Application did not initialize libgcrypt, see " + "https://www.gnupg.org/documentation/manuals/gcrypt/Initializing-the-library.html"); + upipe_srt_receiver_free_void(upipe); + return NULL; + } +#endif + + upipe_srt_receiver_init_urefcount(upipe); + upipe_srt_receiver_init_urefcount_real(upipe); + upipe_srt_receiver_init_sub_outputs(upipe); + upipe_srt_receiver_init_sub_mgr(upipe); + + upipe_srt_receiver_init_uref_mgr(upipe); + upipe_srt_receiver_init_ubuf_mgr(upipe); + upipe_srt_receiver_init_output(upipe); + + upipe_srt_receiver_init_upump_mgr(upipe); + upipe_srt_receiver_init_upump_timer(upipe); + upipe_srt_receiver_init_upump_timer_lost(upipe); + upipe_srt_receiver_init_uclock(upipe); + upipe_srt_receiver_require_uclock(upipe); + + // FIXME + upipe_srt_receiver->socket_id = UINT64_MAX; + upipe_srt_receiver->control = NULL; + + ulist_init(&upipe_srt_receiver->queue); + memset(upipe_srt_receiver->last_nack, 0, sizeof(upipe_srt_receiver->last_nack)); + upipe_srt_receiver->rtt = 100 * UCLOCK_FREQ / 1000; + upipe_srt_receiver->rtt_variance = 50 * UCLOCK_FREQ / 1000; + upipe_srt_receiver->expected_seqnum = UINT64_MAX; + + upipe_srt_receiver->last_output_seqnum = UINT64_MAX; + upipe_srt_receiver->last_ack = UINT64_MAX; + upipe_srt_receiver->ack_num = 0; + + upipe_srt_receiver->acks = NULL; + upipe_srt_receiver->n_acks = 0; + upipe_srt_receiver->ack_ridx = 0; + upipe_srt_receiver->ack_widx = 0; + + upipe_srt_receiver->buffered = 0; + upipe_srt_receiver->nacks = 0; + upipe_srt_receiver->repaired = 0; + upipe_srt_receiver->loss = 0; + upipe_srt_receiver->dups = 0; + + upipe_srt_receiver->latency = 0; + upipe_srt_receiver->last_sent = 0; + + upipe_srt_receiver->packets = 0; + upipe_srt_receiver->bytes = 0; + + upipe_srt_receiver->sek_len = 0; + upipe_srt_receiver->salt_len = 0; + + upipe_srt_receiver->establish_time = 0; + + upipe_srt_receiver->previous_ts = 0; + + upipe_throw_ready(upipe); + return upipe; +} + + +/** @internal @This checks if the pump may be allocated. + * + * @param upipe description structure of the pipe + * @param flow_format amended flow format + * @return an error code + */ +static int upipe_srt_receiver_check(struct upipe *upipe, struct uref *flow_format) +{ + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + + upipe_srt_receiver_check_upump_mgr(upipe); + + if (flow_format != NULL) { + uint64_t latency; + if (!ubase_check(uref_clock_get_latency(flow_format, &latency))) + latency = 0; + uref_clock_set_latency(flow_format, latency + upipe_srt_receiver->latency); + + upipe_srt_receiver_store_flow_def(upipe, flow_format); + } + + if (upipe_srt_receiver->flow_def == NULL) + return UBASE_ERR_NONE; + + if (upipe_srt_receiver->uref_mgr == NULL) { + upipe_srt_receiver_require_uref_mgr(upipe); + return UBASE_ERR_NONE; + } + + if (upipe_srt_receiver->ubuf_mgr == NULL) { + struct uref *flow_format = + uref_block_flow_alloc_def(upipe_srt_receiver->uref_mgr, NULL); + if (unlikely(flow_format == NULL)) { + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + return UBASE_ERR_ALLOC; + } + upipe_srt_receiver_require_ubuf_mgr(upipe, flow_format); + return UBASE_ERR_NONE; + } + + if (upipe_srt_receiver->upump_mgr && !upipe_srt_receiver->upump_timer && upipe_srt_receiver->control) { + struct upump *upump = + upump_alloc_timer(upipe_srt_receiver->upump_mgr, + upipe_srt_receiver_timer, + upipe, upipe->refcount, + UCLOCK_FREQ/1000, UCLOCK_FREQ/1000); + upump_start(upump); + upipe_srt_receiver_set_upump_timer(upipe, upump); + + /* every 10ms, check for lost packets + * interval is reduced each time we get the current RTT from sender */ + upipe_srt_receiver_restart_timer(upipe); + } + + return UBASE_ERR_NONE; +} + +static void upipe_srt_receiver_empty_buffer(struct upipe *upipe) +{ + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + + /* empty buffer */ + upipe_warn(upipe, "Emptying buffer"); + upipe_srt_receiver->expected_seqnum = UINT64_MAX; + upipe_srt_receiver->last_output_seqnum = UINT64_MAX; + upipe_srt_receiver->buffered = 0; + struct uchain *uchain, *uchain_tmp; + ulist_delete_foreach(&upipe_srt_receiver->queue, uchain, uchain_tmp) { + struct uref *uref = uref_from_uchain(uchain); + ulist_delete(uchain); + uref_free(uref); + } +} + +/** @internal @This sets the input flow definition. + * + * @param upipe description structure of the pipe + * @param flow_def flow definition packet + * @return an error code + */ +static int upipe_srt_receiver_set_flow_def(struct upipe *upipe, struct uref *flow_def) +{ + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + + if (flow_def == NULL) + return UBASE_ERR_INVALID; + + const char *def; + UBASE_RETURN(uref_flow_get_def(flow_def, &def)) + + if (ubase_ncmp(def, "block.")) { + upipe_err_va(upipe, "Unknown def %s", def); + return UBASE_ERR_INVALID; + } + + uint64_t id; + if (ubase_check(uref_flow_get_id(flow_def, &id))) { + if (upipe_srt_receiver->socket_id != id) + upipe_srt_receiver_empty_buffer(upipe); + + upipe_srt_receiver->socket_id = id; + } + else { + /* XXX: Is this reachable in reality? */ + upipe_srt_receiver_empty_buffer(upipe); + } + + struct udict_opaque opaque; + if (ubase_check(uref_attr_get_opaque(flow_def, &opaque, UDICT_TYPE_OPAQUE, "enc.salt"))) { + upipe_srt_receiver->salt_len = opaque.size; + if (upipe_srt_receiver->salt_len > sizeof(upipe_srt_receiver->salt)) + upipe_srt_receiver->salt_len = sizeof(upipe_srt_receiver->salt); + memcpy(upipe_srt_receiver->salt, opaque.v, upipe_srt_receiver->salt_len); + } + +#ifdef UPIPE_HAVE_GCRYPT_H + if (ubase_check(uref_attr_get_opaque(flow_def, &opaque, UDICT_TYPE_OPAQUE, "enc.even_key"))) { + if (opaque.size > sizeof(upipe_srt_receiver->sek[0])) + opaque.size = sizeof(upipe_srt_receiver->sek[0]); + upipe_srt_receiver->sek_len = opaque.size; + memcpy(upipe_srt_receiver->sek[0], opaque.v, opaque.size); + } + + if (ubase_check(uref_attr_get_opaque(flow_def, &opaque, UDICT_TYPE_OPAQUE, "enc.odd_key"))) { + if (opaque.size > sizeof(upipe_srt_receiver->sek[1])) + opaque.size = sizeof(upipe_srt_receiver->sek[1]); + upipe_srt_receiver->sek_len = opaque.size; + memcpy(upipe_srt_receiver->sek[1], opaque.v, opaque.size); + } +#endif + + flow_def = uref_dup(flow_def); + if (!flow_def) + return UBASE_ERR_ALLOC; + + uref_clock_set_wrap(flow_def, UINT64_C(4294967296) * UCLOCK_FREQ / 1000000); + + upipe_srt_receiver_store_flow_def(upipe, flow_def); + + return UBASE_ERR_NONE; +} + +/** @internal @This processes control commands on a SRT receiver pipe. + * + * @param upipe description structure of the pipe + * @param command type of command to process + * @param args arguments of the command + * @return an error code + */ +static int _upipe_srt_receiver_control(struct upipe *upipe, + int command, va_list args) +{ + UBASE_HANDLED_RETURN(upipe_srt_receiver_control_output(upipe, command, args)); + UBASE_HANDLED_RETURN(upipe_srt_receiver_control_outputs(upipe, command, args)); + + switch (command) { + case UPIPE_ATTACH_UPUMP_MGR: + upipe_srt_receiver_set_upump_timer(upipe, NULL); + upipe_srt_receiver_set_upump_timer_lost(upipe, NULL); + return upipe_srt_receiver_attach_upump_mgr(upipe); + + case UPIPE_SET_FLOW_DEF: { + struct uref *flow = va_arg(args, struct uref *); + return upipe_srt_receiver_set_flow_def(upipe, flow); + } + + case UPIPE_SET_OPTION: { + const char *k = va_arg(args, const char *); + const char *v = va_arg(args, const char *); + if (strcmp(k, "latency")) + return UBASE_ERR_INVALID; + + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + unsigned latency_ms = atoi(v); + upipe_srt_receiver->latency = latency_ms * UCLOCK_FREQ / 1000; + upipe_dbg_va(upipe, "Set latency to %u msecs", latency_ms); + + upipe_srt_receiver->n_acks = (latency_ms + 9) / 10; + upipe_srt_receiver->acks = malloc(sizeof(*upipe_srt_receiver->acks) * upipe_srt_receiver->n_acks); + if (!upipe_srt_receiver->acks) + return UBASE_ERR_ALLOC; + + return UBASE_ERR_NONE; + } + + case UPIPE_SRTR_GET_STATS: { + UBASE_SIGNATURE_CHECK(args, UPIPE_SRT_RECEIVER_SIGNATURE) + unsigned *expected_seqnum = va_arg(args, unsigned*); + unsigned *last_output_seqnum = va_arg(args, unsigned*); + size_t *buffered = va_arg(args, size_t*); + size_t *nacks = va_arg(args, size_t*); + size_t *repaired = va_arg(args, size_t*); + size_t *loss = va_arg(args, size_t*); + size_t *dups = va_arg(args, size_t*); + unsigned *rtt = va_arg(args, unsigned*); + + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + *buffered = upipe_srt_receiver->buffered; + *expected_seqnum = upipe_srt_receiver->expected_seqnum; + *last_output_seqnum = upipe_srt_receiver->last_output_seqnum; + *nacks = upipe_srt_receiver->nacks; + *repaired = upipe_srt_receiver->repaired; + *loss = upipe_srt_receiver->loss; + *dups = upipe_srt_receiver->dups; + *rtt = upipe_srt_receiver->rtt * 1000 / UCLOCK_FREQ; + + upipe_srt_receiver->nacks = 0; + upipe_srt_receiver->repaired = 0; + upipe_srt_receiver->loss = 0; + upipe_srt_receiver->dups = 0; + + return UBASE_ERR_NONE; + } + + default: + return UBASE_ERR_UNHANDLED; + } +} + +/** @internal @This processes control commands on a SRT receiver pipe, and + * checks the status of the pipe afterwards. + * + * @param upipe description structure of the pipe + * @param command type of command to process + * @param args arguments of the command + * @return an error code + */ +static int upipe_srt_receiver_control(struct upipe *upipe, int command, va_list args) +{ + UBASE_RETURN(_upipe_srt_receiver_control(upipe, command, args)); + + return upipe_srt_receiver_check(upipe, NULL); +} + +/* returns true if uref was inserted in the queue */ +static bool upipe_srt_receiver_insert_inner(struct upipe *upipe, struct uref *uref, + const uint32_t seqnum, struct uref *next) +{ + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + uint64_t next_seqnum = 0; + uref_attr_get_priv(next, &next_seqnum); + + uint32_t diff = seqnum - next_seqnum; + if (!diff) { + upipe_verbose_va(upipe, "dropping duplicate %u", seqnum); + upipe_srt_receiver->dups++; + uref_free(uref); + return true; + } + + diff &= ~(1<<31); /* seqnums are 31 bits */ + + /* browse the list until we find a seqnum bigger than ours */ + + if (diff < 1<<30) // make sure seqnum > next_seqnum + return false; + + /* if there's no previous packet we're too late */ + struct uchain *uchain = uref_to_uchain(next); + if (unlikely(ulist_is_first(&upipe_srt_receiver->queue, uchain))) { + upipe_dbg_va(upipe, + "LATE packet drop: Expected %" PRIu64 ", got %u, didn't insert after %"PRIu64, + upipe_srt_receiver->expected_seqnum, seqnum, next_seqnum); + uref_free(uref); + return true; + } + + /* Read previous packet seq & cr_sys */ + uint64_t prev_seqnum = 0, cr_sys = 0; + + struct uref *prev = uref_from_uchain(uchain->prev); + uref_attr_get_priv(prev, &prev_seqnum); + + /* overwrite this uref' cr_sys with previous one's + * so it get scheduled at the right time */ + if (ubase_check(uref_clock_get_cr_sys(prev, &cr_sys))) + uref_clock_set_cr_sys(uref, cr_sys); + else + upipe_err_va(upipe, "Couldn't read cr_sys in %s() - %zu buffered", + __func__, upipe_srt_receiver->buffered); + + upipe_srt_receiver->buffered++; + ulist_insert(uchain->prev, uchain, uref_to_uchain(uref)); + upipe_srt_receiver->repaired++; + upipe_srt_receiver->last_nack[seqnum & 0xffff] = 0; + + upipe_verbose_va(upipe, "Repaired %"PRIu64" > %u > %"PRIu64" -diff %d", + prev_seqnum, seqnum, next_seqnum, -diff); + + return true; +} + +static bool upipe_srt_receiver_insert(struct upipe *upipe, struct uref *uref, const uint32_t seqnum) +{ + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + + struct uchain *uchain, *uchain_tmp; + ulist_delete_foreach(&upipe_srt_receiver->queue, uchain, uchain_tmp) { + struct uref *next = uref_from_uchain(uchain); + if (upipe_srt_receiver_insert_inner(upipe, uref, seqnum, next)) + return true; + } + + /* Could not insert packet */ + return false; +} + +static uint64_t upipe_srt_receiver_ackack(struct upipe *upipe, uint32_t ack_num, uint64_t ts) +{ + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + + const size_t n = upipe_srt_receiver->n_acks; + const size_t ridx = upipe_srt_receiver->ack_ridx; + + //upipe_verbose_va(upipe,"%s(%u), start at %zu", __func__, ack_num, ridx); + + size_t max = (ridx > 0) ? (ridx - 1) : (n - 1); // end of loop + + for (size_t i = ridx; i != max; i = (i + 1) % n) { + uint32_t a = upipe_srt_receiver->acks[i].ack_num; + + if (upipe_srt_receiver->acks[i].timestamp == UINT64_MAX) { // already acked + //upipe_verbose_va(upipe, "break at %zu", i); + break; + } + + if (ack_num < a) { // too late + //upipe_verbose_va(upipe, "break2 at %zu", i); + break; + } else if (ack_num == a) { + uint64_t rtt = ts - upipe_srt_receiver->acks[i].timestamp; + //upipe_verbose_va(upipe, "rtt[%d] %" PRId64, a, rtt); + upipe_srt_receiver->acks[i].timestamp = UINT64_MAX; // do not ack twice + upipe_srt_receiver->ack_ridx = (i+1) % n; // advance + return rtt; + } + } + + //upipe_verbose_va(upipe, "%d not found", ack_num); + + return 0; +} + +static void upipe_srt_receiver_input(struct upipe *upipe, struct uref *uref, + struct upump **upump_p) +{ + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + + size_t total_size; + ubase_assert(uref_block_size(uref, &total_size)); + + const uint8_t *buf; + int size = total_size; + + ubase_assert(uref_block_read(uref, 0, &size, &buf)); + assert(size == total_size); + + if (size < SRT_HEADER_SIZE) { + upipe_err_va(upipe, "Packet too small (%d)", size); + ubase_assert(uref_block_unmap(uref, 0)); + uref_free(uref); + return; + } + + uint64_t now = uclock_now(upipe_srt_receiver->uclock); + + if (srt_get_packet_control(buf)) { + uint16_t type = srt_get_control_packet_type(buf); + + if (type == SRT_CONTROL_TYPE_ACKACK) { + uint32_t ack_num = srt_get_control_packet_type_specific(buf); + uint64_t rtt = upipe_srt_receiver_ackack(upipe, ack_num, now); + upipe_verbose_va(upipe, "RTT %.2f", (float) rtt / 27000.); + if (rtt) { + uint64_t var = 0; + if (rtt > upipe_srt_receiver->rtt) { + var = rtt - upipe_srt_receiver->rtt; + } else { + var = upipe_srt_receiver->rtt - rtt; + } + upipe_srt_receiver->rtt *= 7; + upipe_srt_receiver->rtt += rtt; + upipe_srt_receiver->rtt /= 8; + + upipe_srt_receiver->rtt_variance *= 3; + upipe_srt_receiver->rtt_variance += var; + upipe_srt_receiver->rtt_variance /= 4; + } + ubase_assert(uref_block_unmap(uref, 0)); + uref_free(uref); + } else { + + if (type == SRT_CONTROL_TYPE_HANDSHAKE && upipe_srt_receiver->establish_time == 0 && now > 0) { + uint64_t ts = srt_get_packet_timestamp(buf); + upipe_srt_receiver->establish_time = now - ts * UCLOCK_FREQ / 1000000; + } + + ubase_assert(uref_block_unmap(uref, 0)); + if (upipe_srt_receiver->control) { + upipe_srt_receiver->last_sent = now; + upipe_srt_receiver_output_output(upipe_srt_receiver->control, uref, upump_p); + } else + uref_free(uref); + } + return; + } + + /* data */ + if (!upipe_srt_receiver->control) { + ubase_assert(uref_block_unmap(uref, 0)); + uref_free(uref); + return; + } + + uint32_t seqnum = srt_get_data_packet_seq(buf); + uint32_t position = srt_get_data_packet_position(buf); + bool order = srt_get_data_packet_order(buf); + uint8_t encryption = srt_get_data_packet_encryption(buf); + bool retransmit = srt_get_data_packet_retransmit(buf); + uint32_t num = srt_get_data_packet_message_number(buf); + uint32_t ts = srt_get_packet_timestamp(buf); + uint8_t kk = srt_get_data_packet_encryption(buf); + + uint8_t aad[16]; + bool gcm = encryption && upipe_srt_receiver->salt_len == 12; + if (gcm) + memcpy(aad, buf, 16); + + ubase_assert(uref_block_unmap(uref, 0)); + uref_block_resize(uref, SRT_HEADER_SIZE, -1); /* skip SRT header */ + total_size -= SRT_HEADER_SIZE; + + static const uint64_t wrap = UINT64_C(4294967296); + uint64_t delta = (wrap + ts - (upipe_srt_receiver->previous_ts % wrap)) % wrap; + int32_t d32 = delta; + bool discontinuity = upipe_srt_receiver->previous_ts == 0; + + uint64_t latency_us = (upipe_srt_receiver->latency * 1000000) / UCLOCK_FREQ; + /* Note: d32 is converted to unsigned implictly */ + if (!discontinuity && (d32 <= latency_us || -d32 <= latency_us)) { + if (d32 <= latency_us) { + upipe_srt_receiver->previous_ts += delta; + assert(d32 >= 0); + } else { + /* out of order but not too old or new */ + } + } else { + upipe_warn_va(upipe, "clock ref discontinuity %"PRIu64, delta); + upipe_srt_receiver->previous_ts = ts; + discontinuity = true; + } + + upipe_verbose_va(upipe, "Data seq %u (retx %u)", seqnum, retransmit); + if (d32 < 0 && !discontinuity) + uref_clock_set_cr_prog(uref, (upipe_srt_receiver->previous_ts + d32) * UCLOCK_FREQ / 1000000); + else + uref_clock_set_cr_prog(uref, (upipe_srt_receiver->previous_ts) * UCLOCK_FREQ / 1000000); + + (void)order; + (void)num; + + /* store seqnum in uref */ + uref_attr_set_priv(uref, seqnum); + if (position != 3) { + upipe_err_va(upipe, "PP %d not handled for live streaming", position); + uref_free(uref); + return; + } + + if (encryption != SRT_DATA_ENCRYPTION_CLEAR) { + if (upipe_srt_receiver->sek_len == 0) { + upipe_err(upipe, "Encryption not handled"); + uref_free(uref); + return; +#ifdef UPIPE_HAVE_GCRYPT_H + } else { + const uint8_t *salt = upipe_srt_receiver->salt; + const uint8_t *sek = upipe_srt_receiver->sek[(kk & (1<<0))? 0 : 1]; + int key_len = upipe_srt_receiver->sek_len; + + uint8_t iv[16]; + memset(&iv, 0, 16); + uint8_t seq_idx = upipe_srt_receiver->salt_len - 4; + iv[seq_idx++] = (seqnum >> 24) & 0xff; + iv[seq_idx++] = (seqnum >> 16) & 0xff; + iv[seq_idx++] = (seqnum >> 8) & 0xff; + iv[seq_idx++] = seqnum & 0xff; + for (int i = 0; i < upipe_srt_receiver->salt_len; i++) + iv[i] ^= salt[i]; + + uint8_t *buf; + size = total_size; + + ubase_assert(uref_block_write(uref, 0, &size, &buf)); + assert(size == total_size); + + gcry_cipher_hd_t aes; + gpg_error_t err; + err = gcry_cipher_open(&aes, GCRY_CIPHER_AES, + gcm ? GCRY_CIPHER_MODE_GCM : GCRY_CIPHER_MODE_CTR, 0); + if (err) { + upipe_err_va(upipe, "Cipher open failed (0x%x)", err); + goto error; + } + + err = gcry_cipher_setkey(aes, sek, key_len); + if (err) { + upipe_err_va(upipe, "Couldn't set session key (0x%x)", err); + goto error_close; + } + + if (gcm) { + err = gcry_cipher_setiv(aes, iv, upipe_srt_receiver->salt_len); + if (err) { + upipe_err_va(upipe, "Couldn't set iv (0x%x)", err); + goto error_close; + } + + err = gcry_cipher_authenticate(aes, aad, 16); + if (err) { + upipe_err_va(upipe, "Couldn't set aad (0x%x)", err); + goto error_close; + } + + err = gcry_cipher_decrypt(aes, buf, size - 16, NULL, 0); + if (err) { + upipe_err_va(upipe, "Couldn't decrypt packet (0x%x)", err); + goto error_close; + } + + err = gcry_cipher_checktag(aes, &buf[size - 16], 16); + if (err) { + upipe_err_va(upipe, "Couldn't check tag (0x%x)", err); + goto error_close; + } + + // remove tag + if (unlikely(!ubase_check(uref_block_resize(uref, 0, size - 16)))) + upipe_err(upipe, "cannot resize"); + } else { + err = gcry_cipher_setctr(aes, iv, 16); + if (err) { + upipe_err_va(upipe, "Couldn't set ctr (0x%x)", err); + goto error_close; + } + + err = gcry_cipher_encrypt(aes, buf, size, NULL, 0); + if (err) { + upipe_err_va(upipe, "Couldn't decrypt packet (0x%x)", err); + goto error_close; + } + } + +error_close: + gcry_cipher_close(aes); +error: + uref_block_unmap(uref, 0); +#endif + } + } + + /* first packet */ + if (unlikely(upipe_srt_receiver->expected_seqnum == UINT64_MAX)) + upipe_srt_receiver->expected_seqnum = seqnum; + + uint32_t diff = seqnum - upipe_srt_receiver->expected_seqnum; + diff &= ~(1 << 31); // seqnums are 31 bits + + if (diff < 1<<30) { // seqnum > last seq, insert at the end + /* packet is from the future */ + upipe_srt_receiver->buffered++; + + upipe_srt_receiver->packets++; + upipe_srt_receiver->bytes += total_size + SRT_HEADER_SIZE; + ulist_add(&upipe_srt_receiver->queue, uref_to_uchain(uref)); + upipe_srt_receiver->last_nack[seqnum & 0xffff] = 0; + + if (diff != 0) { + uint64_t rtt = upipe_srt_receiver_get_rtt(upipe); + /* wait a bit to send a NACK, in case of reordering */ + uint64_t fake_last_nack = uclock_now(upipe_srt_receiver->uclock) - rtt; + for (uint32_t seq = upipe_srt_receiver->expected_seqnum; seq != seqnum; seq++) + if (upipe_srt_receiver->last_nack[seq & 0xffff] == 0) + upipe_srt_receiver->last_nack[seq & 0xffff] = fake_last_nack; + } + + if (!retransmit && diff == 0) { + uint64_t cr_prog; + if (unlikely(!ubase_check(uref_clock_get_cr_prog(uref, &cr_prog)))) { + upipe_warn(upipe, "[srtr] no cr_prog in packet"); + return; + } + + upipe_throw_clock_ref(upipe, uref, cr_prog, discontinuity); + } + + upipe_srt_receiver->expected_seqnum = (seqnum + 1) & ~(1 << 31); + + upipe_throw_clock_ts(upipe, uref); + return; + } + + /* packet is from the past, reordered or retransmitted */ + if (upipe_srt_receiver_insert(upipe, uref, seqnum)) { + upipe_throw_clock_ts(upipe, uref); + return; + } + + uint64_t first_seq = 0, last_seq = 0; + uref_attr_get_priv(uref_from_uchain(upipe_srt_receiver->queue.next), &first_seq); + uref_attr_get_priv(uref_from_uchain(upipe_srt_receiver->queue.prev), &last_seq); + // XXX : when much too late, it could mean RTP source restart + upipe_err_va(upipe, "LATE packet %u, dropped (buffered %"PRIu64" -> %"PRIu64")", + seqnum, first_seq, last_seq); + uref_free(uref); +} + +/** @This frees a upipe. + * + * @param upipe description structure of the pipe + */ +static void upipe_srt_receiver_free(struct upipe *upipe) +{ + struct upipe_srt_receiver *upipe_srt_receiver = upipe_srt_receiver_from_upipe(upipe); + upipe_throw_dead(upipe); + + free(upipe_srt_receiver->acks); + upipe_srt_receiver->acks = NULL; + + upipe_srt_receiver_clean_output(upipe); + upipe_srt_receiver_clean_upump_timer(upipe); + upipe_srt_receiver_clean_upump_timer_lost(upipe); + upipe_srt_receiver_clean_upump_mgr(upipe); + upipe_srt_receiver_clean_uclock(upipe); + upipe_srt_receiver_clean_ubuf_mgr(upipe); + upipe_srt_receiver_clean_uref_mgr(upipe); + upipe_srt_receiver_clean_urefcount(upipe); + upipe_srt_receiver_clean_urefcount_real(upipe); + upipe_srt_receiver_clean_sub_outputs(upipe); + upipe_srt_receiver_empty_buffer(upipe); + upipe_srt_receiver_free_void(upipe); +} + +/** module manager static descriptor */ +static struct upipe_mgr upipe_srt_receiver_mgr = { + .refcount = NULL, + .signature = UPIPE_SRT_RECEIVER_SIGNATURE, + + .upipe_alloc = upipe_srt_receiver_alloc, + .upipe_input = upipe_srt_receiver_input, + .upipe_control = upipe_srt_receiver_control, + + .upipe_mgr_control = NULL +}; + +/** @This returns the management structure for all SRT receiver sources + * + * @return pointer to manager + */ +struct upipe_mgr *upipe_srt_receiver_mgr_alloc(void) +{ + return &upipe_srt_receiver_mgr; +} diff --git a/lib/upipe-srt/upipe_srt_sender.c b/lib/upipe-srt/upipe_srt_sender.c new file mode 100644 index 000000000..be95a3c3f --- /dev/null +++ b/lib/upipe-srt/upipe_srt_sender.c @@ -0,0 +1,950 @@ +/* + * Copyright (C) 2023 Open Broadcast Systems Ltd + * + * Authors: Rafaël Carré + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + */ + +/** @file + * @short Upipe module for SRT senders + */ + +#include "upipe/config.h" +#include "upipe/ubase.h" +#include "upipe/uprobe.h" +#include "upipe/uref.h" +#include "upipe/uclock.h" +#include "upipe/uref_clock.h" +#include "upipe/upipe.h" +#include "upipe/uref_block.h" +#include "upipe/uref_block_flow.h" +#include "upipe/uref_pic.h" // XXX are we abusing picture number? +#include "upipe/uref_flow.h" +#include "upipe/upipe_helper_upipe.h" +#include "upipe/upipe_helper_subpipe.h" +#include "upipe/upipe_helper_urefcount.h" +#include "upipe/upipe_helper_urefcount_real.h" +#include "upipe/upipe_helper_void.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_upump_mgr.h" +#include "upipe/upipe_helper_upump.h" +#include "upipe/upipe_helper_uclock.h" +#include "upipe-srt/upipe_srt_sender.h" + +#include + +#include + +#include + +#define EXPECTED_FLOW_DEF "block." + +#define SRT_KM_PRE_ANNOUNCEMENT_PERIOD 4000 // XXX: make configurable? + +/** upipe_srt_sender structure */ +struct upipe_srt_sender { + /** real refcount management structure */ + struct urefcount urefcount_real; + /** refcount management structure exported to the public structure */ + struct urefcount urefcount; + + /** uref manager */ + struct uref_mgr *uref_mgr; + /** uref manager request */ + struct urequest uref_mgr_request; + + /** ubuf mgr structures */ + struct ubuf_mgr *ubuf_mgr; + struct urequest ubuf_mgr_request; + struct uref *flow_format; + + struct upipe_mgr sub_mgr; + + struct upump_mgr *upump_mgr; + struct upump *upump_timer; + struct uclock *uclock; + struct urequest uclock_request; + struct uchain queue; + + /** list of input subpipes */ + struct uchain inputs; + + /** output pipe */ + struct upipe *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; + + uint64_t socket_id; + uint32_t seqnum; + + uint64_t establish_time; + + /** buffer latency */ + uint64_t latency; + + uint8_t salt[14]; + uint8_t salt_len; + uint8_t sek[2][32]; + uint8_t sek_len[2]; + + int key_index; + + size_t packets_since_key; + + uint64_t last_sent; + + /** public upipe structure */ + struct upipe upipe; +}; + +static int upipe_srt_sender_check(struct upipe *upipe, struct uref *flow_format); + +UPIPE_HELPER_UPIPE(upipe_srt_sender, upipe, UPIPE_SRT_SENDER_SIGNATURE); +UPIPE_HELPER_UREFCOUNT(upipe_srt_sender, urefcount, upipe_srt_sender_no_input); +UPIPE_HELPER_UREFCOUNT_REAL(upipe_srt_sender, urefcount_real, upipe_srt_sender_free); +UPIPE_HELPER_VOID(upipe_srt_sender); +UPIPE_HELPER_OUTPUT(upipe_srt_sender, output, flow_def, output_state, request_list); +UPIPE_HELPER_UREF_MGR(upipe_srt_sender, uref_mgr, uref_mgr_request, + upipe_srt_sender_check, + upipe_srt_sender_register_output_request, + upipe_srt_sender_unregister_output_request) +UPIPE_HELPER_UBUF_MGR(upipe_srt_sender, ubuf_mgr, flow_format, ubuf_mgr_request, + upipe_srt_sender_check, + upipe_srt_sender_register_output_request, + upipe_srt_sender_unregister_output_request) +UPIPE_HELPER_UPUMP_MGR(upipe_srt_sender, upump_mgr) +UPIPE_HELPER_UPUMP(upipe_srt_sender, upump_timer, upump_mgr) +UPIPE_HELPER_UCLOCK(upipe_srt_sender, uclock, uclock_request, + upipe_srt_sender_check, upipe_throw_provide_request, NULL) + +struct upipe_srt_sender_input { + /** refcount management structure */ + struct urefcount urefcount; + /** structure for double-linked lists */ + struct uchain uchain; + /** public upipe structure */ + struct upipe upipe; +}; + +static void upipe_srt_sender_lost_sub_n(struct upipe *upipe, uint32_t seq, uint32_t pkts, struct upump **upump_p); + +UPIPE_HELPER_UPIPE(upipe_srt_sender_input, upipe, UPIPE_SRT_SENDER_INPUT_SIGNATURE) +UPIPE_HELPER_UREFCOUNT(upipe_srt_sender_input, urefcount, upipe_srt_sender_input_free) +UPIPE_HELPER_VOID(upipe_srt_sender_input); +UPIPE_HELPER_SUBPIPE(upipe_srt_sender, upipe_srt_sender_input, output, sub_mgr, inputs, + uchain) + +/** @internal @This handles SRT messages. + * + * @param upipe description structure of the pipe + * @param uref uref structure + * @param upump_p reference to pump that generated the buffer + */ +static void upipe_srt_sender_input_sub(struct upipe *upipe, struct uref *uref, + struct upump **upump_p) +{ + struct upipe *upipe_super = NULL; + upipe_srt_sender_input_get_super(upipe, &upipe_super); + struct upipe_srt_sender *upipe_srt_sender = upipe_srt_sender_from_upipe(upipe_super); + + upipe_srt_sender_check(upipe_super, NULL); + + uint64_t now = 0; + + if (upipe_srt_sender->uclock) + now = uclock_now(upipe_srt_sender->uclock); + + size_t total_size; + ubase_assert(uref_block_size(uref, &total_size)); + + const uint8_t *buf; + int size = total_size; + + ubase_assert(uref_block_read(uref, 0, &size, &buf)); + assert(size == total_size); + + if (size < SRT_HEADER_SIZE || !srt_get_packet_control(buf)) { + upipe_err_va(upipe, "Invalid SRT control packet (%d)", size); + ubase_assert(uref_block_unmap(uref, 0)); + uref_free(uref); + return; + } + + uint16_t type = srt_get_control_packet_type(buf); + + if (type == SRT_CONTROL_TYPE_NAK) { + buf += SRT_HEADER_SIZE; + size -= SRT_HEADER_SIZE; + size_t s = size; + uint32_t seq; + uint32_t packets; + while (srt_get_nak_range(&buf, &s, &seq, &packets)) { + upipe_srt_sender_lost_sub_n(upipe, seq, packets, upump_p); + } + uref_block_unmap(uref, 0); + uref_free(uref); + } else { + if (type == SRT_CONTROL_TYPE_HANDSHAKE && upipe_srt_sender->establish_time == 0 && now > 0) { + uint64_t ts = srt_get_packet_timestamp(buf); + upipe_srt_sender->establish_time = now - ts * UCLOCK_FREQ / 1000000; + } + + uref_block_unmap(uref, 0); + if (upipe_srt_sender->flow_def) { + upipe_srt_sender->last_sent = now; + upipe_srt_sender_output(upipe_super, uref, upump_p); + } else + uref_free(uref); + } +} + +/** @internal @This retransmits a number of packets */ +static void upipe_srt_sender_lost_sub_n(struct upipe *upipe, uint32_t seq, uint32_t pkts, struct upump **upump_p) +{ + struct upipe *upipe_super = NULL; + upipe_srt_sender_input_get_super(upipe, &upipe_super); + struct upipe_srt_sender *upipe_srt_sender = upipe_srt_sender_from_upipe(upipe_super); + + uint64_t now = uclock_now(upipe_srt_sender->uclock); + + struct uchain *uchain; + ulist_foreach(&upipe_srt_sender->queue, uchain) { + struct uref *uref = uref_from_uchain(uchain); + uint64_t uref_seqnum = 0; + uref_attr_get_priv(uref, &uref_seqnum); + + uint32_t diff = uref_seqnum - seq; + diff &= ~(1<<31); + + if (diff > (1<<30)) { + /* Look at next packet */ + continue; + } else if (diff >= pkts) { + /* packet after range, give up */ + break; + } + + upipe_verbose_va(upipe, "Retransmit %" PRIu64, uref_seqnum); + + uint8_t *buf; + int s = 0; + if (ubase_check(uref_block_write(uref, 0, &s, &buf))) { + srt_set_data_packet_retransmit(buf, true); + uref_block_unmap(uref, 0); + } + + upipe_srt_sender->last_sent = now; + upipe_srt_sender_output(upipe_super, uref_dup(uref), NULL); + + diff++; + seq += diff; + pkts -= diff; + if (pkts == 0) + return; + } + + /* XXX: Do we really need to send DROPREQ ? */ + + int s = SRT_HEADER_SIZE + SRT_DROPREQ_CIF_SIZE; + struct uref *uref = uref_block_alloc(upipe_srt_sender->uref_mgr, + upipe_srt_sender->ubuf_mgr, s); + if (!uref) { + upipe_throw_fatal(upipe, UBASE_ERR_UNKNOWN); + return; + } + + uint8_t *buf; + s = -1; + if (unlikely(!ubase_check(uref_block_write(uref, 0, &s, &buf)))) { + upipe_throw_fatal(upipe, UBASE_ERR_UNKNOWN); + uref_free(uref); + return; + } + + memset(buf, 0, s); + srt_set_packet_control(buf, true); + srt_set_control_packet_type(buf, SRT_CONTROL_TYPE_DROPREQ); + srt_set_control_packet_subtype(buf, 0); // message number + + srt_set_packet_timestamp(buf, (now - upipe_srt_sender->establish_time) / 27); + srt_set_packet_dst_socket_id(buf, upipe_srt_sender->socket_id); + + uint8_t *cif = (uint8_t*)srt_get_control_packet_cif(buf); + srt_set_dropreq_first_seq(cif, seq); + srt_set_dropreq_last_seq(cif, seq + pkts - 1); + + uref_block_unmap(uref, 0); + upipe_srt_sender->last_sent = now; + upipe_srt_sender_output(&upipe_srt_sender->upipe, uref, upump_p); +} + +/** @This is called when there is no external reference to the pipe anymore. + * + * @param upipe description structure of the pipe + */ +static void upipe_srt_sender_no_input(struct upipe *upipe) +{ + upipe_srt_sender_throw_sub_outputs(upipe, UPROBE_SOURCE_END); + upipe_srt_sender_release_urefcount_real(upipe); +} + +/** @internal @This allocates an output subpipe of a dup pipe. + * + * @param mgr common management structure + * @param uprobe structure used to raise events + * @param signature signature of the pipe allocator + * @param args optional arguments + * @return pointer to upipe or NULL in case of allocation error + */ +static struct upipe *upipe_srt_sender_input_alloc(struct upipe_mgr *mgr, + struct uprobe *uprobe, + uint32_t signature, va_list args) +{ + struct upipe *upipe = + upipe_srt_sender_input_alloc_void(mgr, uprobe, signature, args); + if (unlikely(upipe == NULL)) + return NULL; + + upipe_srt_sender_input_init_urefcount(upipe); + upipe_srt_sender_input_init_sub(upipe); + + upipe_throw_ready(upipe); + return upipe; +} + +/** @This frees a upipe. + * + * @param upipe description structure of the pipe + */ +static void upipe_srt_sender_input_free(struct upipe *upipe) +{ + upipe_throw_dead(upipe); + + upipe_srt_sender_input_clean_sub(upipe); + upipe_srt_sender_input_clean_urefcount(upipe); + upipe_srt_sender_input_free_void(upipe); +} + +/** @internal this timer removes from the queue packets that are too + * early to be recovered by receiver. + */ +static void upipe_srt_sender_timer(struct upump *upump) +{ + struct upipe *upipe = upump_get_opaque(upump, struct upipe *); + struct upipe_srt_sender *upipe_srt_sender = upipe_srt_sender_from_upipe(upipe); + + uint64_t now = uclock_now(upipe_srt_sender->uclock); + + if (upipe_srt_sender->socket_id != UINT64_MAX && now - upipe_srt_sender->last_sent > UCLOCK_FREQ) { + struct uref *uref = uref_block_alloc(upipe_srt_sender->uref_mgr, + upipe_srt_sender->ubuf_mgr, SRT_HEADER_SIZE + 4 /* undocumented extra padding */); + if (uref) { + uint8_t *out; + int output_size = -1; + if (unlikely(!ubase_check(uref_block_write(uref, 0, &output_size, &out)))) { + uref_free(uref); + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + } + + srt_set_packet_control(out, true); + srt_set_packet_timestamp(out, (now - upipe_srt_sender->establish_time) / 27); + srt_set_packet_dst_socket_id(out, upipe_srt_sender->socket_id); + srt_set_control_packet_type(out, SRT_CONTROL_TYPE_KEEPALIVE); + srt_set_control_packet_subtype(out, 0); + srt_set_control_packet_type_specific(out, 0); + uint8_t *extra = (uint8_t*)srt_get_control_packet_cif(out); + memset(extra, 0, 4); + + uref_block_unmap(uref, 0); + + upipe_srt_sender->last_sent = now; + upipe_srt_sender_output(upipe, uref, &upipe_srt_sender->upump_timer); + } + } + + struct uchain *uchain, *uchain_tmp; + ulist_delete_foreach(&upipe_srt_sender->queue, uchain, uchain_tmp) { + struct uref *uref = uref_from_uchain(uchain); + + uint64_t seqnum = 0; + uref_attr_get_priv(uref, &seqnum); + + uint64_t cr_sys = 0; + if (unlikely(!ubase_check(uref_clock_get_cr_sys(uref, &cr_sys)))) + upipe_warn(upipe, "Couldn't read cr_sys"); + + if (now - cr_sys < upipe_srt_sender->latency) + return; + + upipe_verbose_va(upipe, "Delete seq %" PRIu64 " after %"PRIu64" clocks", + seqnum, now - cr_sys); + + ulist_delete(uchain); + uref_free(uref); + } +} + +static int upipe_srt_sender_check(struct upipe *upipe, struct uref *flow_format) +{ + struct upipe_srt_sender *upipe_srt_sender = upipe_srt_sender_from_upipe(upipe); + + if (flow_format != NULL) + upipe_srt_sender_store_flow_def(upipe, flow_format); + + if (upipe_srt_sender->flow_def == NULL) + return UBASE_ERR_NONE; + + if (upipe_srt_sender->uref_mgr == NULL) { + upipe_srt_sender_require_uref_mgr(upipe); + return UBASE_ERR_NONE; + } + + if (upipe_srt_sender->uclock == NULL) { + upipe_srt_sender_require_uclock(upipe); + return UBASE_ERR_NONE; + } + + if (upipe_srt_sender->ubuf_mgr == NULL) { + struct uref *flow_format = + uref_block_flow_alloc_def(upipe_srt_sender->uref_mgr, NULL); + if (unlikely(flow_format == NULL)) { + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + return UBASE_ERR_ALLOC; + } + upipe_srt_sender_require_ubuf_mgr(upipe, flow_format); + return UBASE_ERR_NONE; + } + + upipe_srt_sender_check_upump_mgr(upipe); + if (upipe_srt_sender->upump_mgr == NULL) + return UBASE_ERR_NONE; + + if (upipe_srt_sender->upump_timer == NULL) { + struct upump *upump = + upump_alloc_timer(upipe_srt_sender->upump_mgr, + upipe_srt_sender_timer, upipe, upipe->refcount, + UCLOCK_FREQ, UCLOCK_FREQ / 4); + upipe_srt_sender_set_upump_timer(upipe, upump); + upump_start(upump); + + } + + return UBASE_ERR_NONE; +} + +/** @internal */ +static int upipe_srt_sender_input_set_flow_def(struct upipe *upipe, struct uref *flow_def) +{ + struct upipe *upipe_super = NULL; + upipe_srt_sender_input_get_super(upipe, &upipe_super); + struct upipe_srt_sender *upipe_srt_sender = upipe_srt_sender_from_upipe(upipe_super); + + if (flow_def == NULL) + return UBASE_ERR_INVALID; + + uint64_t id; + if (ubase_check(uref_flow_get_id(flow_def, &id))) + upipe_srt_sender->socket_id = id; + + uint64_t isn; + if (ubase_check(uref_pic_get_number(flow_def, &isn))) + if (!upipe_srt_sender->seqnum) + upipe_srt_sender->seqnum = isn; + + struct udict_opaque opaque; + if (ubase_check(uref_attr_get_opaque(flow_def, &opaque, UDICT_TYPE_OPAQUE, "enc.salt"))) { + upipe_srt_sender->salt_len = opaque.size; + if (upipe_srt_sender->salt_len > sizeof(upipe_srt_sender->salt)) + upipe_srt_sender->salt_len = sizeof(upipe_srt_sender->salt); + memcpy(upipe_srt_sender->salt, opaque.v, upipe_srt_sender->salt_len); + } + +#ifdef UPIPE_HAVE_GCRYPT_H + if (ubase_check(uref_attr_get_opaque(flow_def, &opaque, UDICT_TYPE_OPAQUE, "enc.even_key"))) { + if (opaque.size > sizeof(upipe_srt_sender->sek[0])) + opaque.size = sizeof(upipe_srt_sender->sek[0]); + upipe_srt_sender->sek_len[0] = opaque.size; + memcpy(upipe_srt_sender->sek[0], opaque.v, opaque.size); + } + + if (ubase_check(uref_attr_get_opaque(flow_def, &opaque, UDICT_TYPE_OPAQUE, "enc.odd_key"))) { + if (opaque.size > sizeof(upipe_srt_sender->sek[1])) + opaque.size = sizeof(upipe_srt_sender->sek[1]); + upipe_srt_sender->sek_len[1] = opaque.size; + memcpy(upipe_srt_sender->sek[1], opaque.v, opaque.size); + } + + int even_key = upipe_srt_sender->key_index; + if (upipe_srt_sender->sek_len[0] && upipe_srt_sender->sek_len[1]) + upipe_srt_sender->packets_since_key = 0; + else + upipe_srt_sender->packets_since_key = SRT_KM_PRE_ANNOUNCEMENT_PERIOD; + + if (!upipe_srt_sender->sek_len[!even_key] && upipe_srt_sender->sek_len[even_key]) { + upipe_err_va(upipe, "%s key is absent", even_key ? "even" : "odd"); + even_key = !even_key; + } + + if (upipe_srt_sender->sek_len[0] || upipe_srt_sender->sek_len[1]) { + upipe_dbg_va(upipe, "Using %s key", even_key ? "even" : "odd"); + } else { + if (upipe_srt_sender->socket_id != UINT64_MAX) + upipe_dbg(upipe, "No encryption key in handshake"); + } + + upipe_srt_sender->key_index = even_key; +#endif + + return uref_flow_match_def(flow_def, EXPECTED_FLOW_DEF); +} + +/** @internal @This processes control commands on an output subpipe of a dup + * pipe. + * + * @param upipe description structure of the pipe + * @param command type of command to process + * @param args arguments of the command + * @return an error code + */ +static int upipe_srt_sender_input_control(struct upipe *upipe, + int command, va_list args) +{ + UBASE_HANDLED_RETURN( + upipe_srt_sender_input_control_super(upipe, command, args)); + UBASE_HANDLED_RETURN(upipe_control_provide_request(upipe, command, args)); + switch (command) { + case UPIPE_SET_FLOW_DEF: { + struct uref *flow_def = va_arg(args, struct uref *); + return upipe_srt_sender_input_set_flow_def(upipe, flow_def); + } + default: + return UBASE_ERR_UNHANDLED; + } +} + +/** @internal @This initializes the output manager for a srt_sender set pipe. + * + * @param upipe description structure of the pipe + */ +static void upipe_srt_sender_init_sub_mgr(struct upipe *upipe) +{ + struct upipe_srt_sender *upipe_srt_sender = upipe_srt_sender_from_upipe(upipe); + struct upipe_mgr *sub_mgr = &upipe_srt_sender->sub_mgr; + sub_mgr->refcount = upipe_srt_sender_to_urefcount_real(upipe_srt_sender); + sub_mgr->signature = UPIPE_SRT_SENDER_INPUT_SIGNATURE; + sub_mgr->upipe_alloc = upipe_srt_sender_input_alloc; + sub_mgr->upipe_input = upipe_srt_sender_input_sub; + sub_mgr->upipe_control = upipe_srt_sender_input_control; +} + +/** @internal @This allocates a srt_sender pipe. + * + * @param mgr common management structure + * @param uprobe structure used to raise events + * @param signature signature of the pipe allocator + * @param args optional arguments + * @return pointer to upipe or NULL in case of allocation error + */ +static struct upipe *upipe_srt_sender_alloc(struct upipe_mgr *mgr, + struct uprobe *uprobe, + uint32_t signature, va_list args) +{ + struct upipe *upipe = upipe_srt_sender_alloc_void(mgr, uprobe, signature, args); + if (unlikely(upipe == NULL)) + return NULL; + +#ifdef UPIPE_HAVE_GCRYPT_H + if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { + uprobe_err(uprobe, upipe, "Application did not initialize libgcrypt, see " + "https://www.gnupg.org/documentation/manuals/gcrypt/Initializing-the-library.html"); + upipe_srt_sender_free_void(upipe); + return NULL; + } +#endif + + struct upipe_srt_sender *upipe_srt_sender = upipe_srt_sender_from_upipe(upipe); + upipe_srt_sender_init_urefcount(upipe); + upipe_srt_sender_init_urefcount_real(upipe); + upipe_srt_sender_init_upump_mgr(upipe); + upipe_srt_sender_init_upump_timer(upipe); + upipe_srt_sender_init_uclock(upipe); + upipe_srt_sender_init_output(upipe); + upipe_srt_sender_init_sub_outputs(upipe); + upipe_srt_sender_init_sub_mgr(upipe); + upipe_srt_sender_init_ubuf_mgr(upipe); + upipe_srt_sender_init_uref_mgr(upipe); + ulist_init(&upipe_srt_sender->queue); + upipe_srt_sender->latency = UCLOCK_FREQ; /* 1 sec */ + upipe_srt_sender->socket_id = UINT64_MAX; + upipe_srt_sender->seqnum = 0; + upipe_srt_sender->establish_time = 0; + + /* Note: 0 is the even key and 1 is the odd key. + When upipe_srt_sender->key_index = true + upipe_srt_sender->sek[!upipe_srt_sender->key_index] is the even key + */ + upipe_srt_sender->sek_len[0] = 0; + upipe_srt_sender->sek_len[1] = 0; + upipe_srt_sender->salt_len = 0; + upipe_srt_sender->key_index = true; + upipe_srt_sender->packets_since_key = 0; + + upipe_srt_sender->last_sent = 0; + + upipe_throw_ready(upipe); + return upipe; +} + +/** @internal @This handles data. + * + * @param upipe description structure of the pipe + * @param uref uref structure + * @param upump_p reference to pump that generated the buffer + */ +static inline void upipe_srt_sender_input(struct upipe *upipe, struct uref *uref, + struct upump **upump_p) +{ + struct upipe_srt_sender *upipe_srt_sender = upipe_srt_sender_from_upipe(upipe); + + upipe_srt_sender_check(upipe, NULL); + + if (!upipe_srt_sender->ubuf_mgr) { + uref_free(uref); + return; + } + + if (upipe_srt_sender->socket_id == UINT64_MAX) { + uref_free(uref); + return; + } + + size_t total_size; + ubase_assert(uref_block_size(uref, &total_size)); + + struct ubuf *tag = NULL; + struct ubuf *insert = ubuf_block_alloc(upipe_srt_sender->ubuf_mgr, SRT_HEADER_SIZE); + if (!insert) { + upipe_throw_fatal(upipe, UBASE_ERR_UNKNOWN); + uref_free(uref); + return; + } + + uint8_t *buf; + int s = -1; + if (unlikely(!ubase_check(ubuf_block_write(insert, 0, &s, &buf)))) { + upipe_throw_fatal(upipe, UBASE_ERR_UNKNOWN); + ubuf_free(insert); + uref_free(uref); + return; + } + + uint64_t now = uclock_now(upipe_srt_sender->uclock); + + uint32_t seqnum = upipe_srt_sender->seqnum++; + seqnum &= ~(1 << 31); + memset(buf, 0, SRT_HEADER_SIZE); + srt_set_packet_control(buf, false); + srt_set_packet_timestamp(buf, (now - upipe_srt_sender->establish_time) / 27); + srt_set_packet_dst_socket_id(buf, upipe_srt_sender->socket_id); + srt_set_data_packet_message_number(buf, seqnum); + srt_set_data_packet_seq(buf, seqnum); + srt_set_data_packet_position(buf, SRT_DATA_POSITION_ONLY); + srt_set_data_packet_order(buf, false); + srt_set_data_packet_retransmit(buf, false); + +#ifdef UPIPE_HAVE_GCRYPT_H + if (++upipe_srt_sender->packets_since_key == SRT_KM_PRE_ANNOUNCEMENT_PERIOD) { + /* invert the boolean to get the right index */ + int even_key = !upipe_srt_sender->key_index; + + if (!upipe_srt_sender->sek_len[!even_key] && upipe_srt_sender->sek_len[even_key]) { + upipe_err_va(upipe, "Couldn't switch encryption keys: %s key is absent", + even_key ? "even" : "odd"); + even_key = !even_key; + } + + if (upipe_srt_sender->sek_len[0] || upipe_srt_sender->sek_len[1]) { + upipe_dbg_va(upipe, "Switching to %s key", even_key ? "even" : "odd"); + } else { + upipe_dbg(upipe, "Encryption disabled"); + } + + upipe_srt_sender->key_index = even_key; + } else if (upipe_srt_sender->packets_since_key == 2 * SRT_KM_PRE_ANNOUNCEMENT_PERIOD) { + memset(upipe_srt_sender->sek[upipe_srt_sender->key_index], 0, sizeof(upipe_srt_sender->sek[0])); + } + + /* invert the boolean to get the right index */ + int key = !upipe_srt_sender->key_index; + if (upipe_srt_sender->sek_len[key]) { + srt_set_data_packet_encryption(buf, key ? SRT_DATA_ENCRYPTION_ODD : SRT_DATA_ENCRYPTION_EVEN); + + bool gcm = upipe_srt_sender->salt_len == 12; + + uint8_t *data; + int s = -1; + if (ubase_check(uref_block_write(uref, 0, &s, &data))) { + const uint8_t *salt = upipe_srt_sender->salt; + const uint8_t *sek = upipe_srt_sender->sek[key]; + int key_len = upipe_srt_sender->sek_len[key]; + + uint8_t iv[16]; + memset(&iv, 0, 16); + uint8_t seq_idx = upipe_srt_sender->salt_len - 4; + iv[seq_idx++] = (seqnum >> 24) & 0xff; + iv[seq_idx++] = (seqnum >> 16) & 0xff; + iv[seq_idx++] = (seqnum >> 8) & 0xff; + iv[seq_idx++] = seqnum & 0xff; + for (int i = 0; i < upipe_srt_sender->salt_len; i++) + iv[i] ^= salt[i]; + + gcry_cipher_hd_t aes; + gpg_error_t err; + err = gcry_cipher_open(&aes, GCRY_CIPHER_AES, + gcm ? GCRY_CIPHER_MODE_GCM : GCRY_CIPHER_MODE_CTR, 0); + if (err) { + upipe_err_va(upipe, "Cipher open failed (0x%x)", err); + goto error; + } + + err = gcry_cipher_setkey(aes, sek, key_len); + if (err) { + upipe_err_va(upipe, "Couldn't set session key (0x%x)", err); + goto error_close; + } + + if (gcm) { + err = gcry_cipher_setiv(aes, iv, upipe_srt_sender->salt_len); + if (err) { + upipe_err_va(upipe, "Couldn't set iv (0x%x)", err); + goto error_close; + } + + err = gcry_cipher_authenticate(aes, buf, 16); + if (err) { + upipe_err_va(upipe, "Couldn't set aad (0x%x)", err); + goto error_close; + } + + err = gcry_cipher_encrypt(aes, data, s, NULL, 0); + if (err) { + upipe_err_va(upipe, "Couldn't decrypt packet (0x%x)", err); + goto error_close; + } + + tag = ubuf_block_alloc(upipe_srt_sender->ubuf_mgr, 16); + if (!tag) { + upipe_throw_fatal(upipe, UBASE_ERR_UNKNOWN); + goto error_close; + } + + uint8_t *buf_tag; + int s_tag = -1; + if (unlikely(!ubase_check(ubuf_block_write(tag, 0, &s_tag, &buf_tag)))) { + upipe_throw_fatal(upipe, UBASE_ERR_UNKNOWN); + goto error_close; + } + + err = gcry_cipher_gettag(aes, buf_tag, 16); + ubuf_block_unmap(tag, 0); + if (err) { + upipe_err_va(upipe, "Couldn't get tag (0x%x)", err); + goto error_close; + } + } else { + err = gcry_cipher_setctr(aes, iv, 16); + if (err) { + upipe_err_va(upipe, "Couldn't set encryption ctr (0x%x)", err); + goto error_close; + } + + err = gcry_cipher_encrypt(aes, data, s, NULL, 0); + if (err) { + upipe_err_va(upipe, "Couldn't encrypt packet (0x%x)", err); + goto error_close; + } + } + +error_close: + gcry_cipher_close(aes); +error: + uref_block_unmap(uref, 0); + + if (err) { + upipe_err(upipe, "Dropping packet"); + ubuf_block_unmap(insert, 0); + ubuf_free(insert); + uref_free(uref); + if (tag) + ubuf_free(tag); + return; + } + } + } else +#endif + srt_set_data_packet_encryption(buf, SRT_DATA_ENCRYPTION_CLEAR); + + ubuf_block_unmap(insert, 0); + if (!ubase_check(uref_block_insert(uref, 0, insert))) { + upipe_throw_fatal(upipe, UBASE_ERR_UNKNOWN); + ubuf_free(insert); + uref_free(uref); + if (tag) + ubuf_free(tag); + return; + } + + if (tag ) { + int ret = uref_block_append(uref, tag); + if (!ubase_check(ret)) { + upipe_throw_fatal(upipe, ret); + uref_free(uref); + ubuf_free(tag); + return; + } + } + + uref_attr_set_priv(uref, seqnum); + + /* Output packet immediately */ + upipe_srt_sender->last_sent = now; + upipe_srt_sender_output(upipe, uref_dup(uref), upump_p); + + upipe_verbose_va(upipe, "Output & buffer %u", seqnum); + + /* Buffer packet in case retransmission is needed */ + ulist_add(&upipe_srt_sender->queue, uref_to_uchain(uref)); +} + +/** @internal @This sets the input flow definition. + * + * @param upipe description structure of the pipe + * @param flow_def flow definition packet + * @return an error code + */ +static int upipe_srt_sender_set_flow_def(struct upipe *upipe, struct uref *flow_def) +{ + if (flow_def == NULL) + return UBASE_ERR_INVALID; + UBASE_RETURN(uref_flow_match_def(flow_def, EXPECTED_FLOW_DEF)) + struct uref *flow_def_dup; + if (unlikely((flow_def_dup = uref_dup(flow_def)) == NULL)) + return UBASE_ERR_ALLOC; + upipe_srt_sender_store_flow_def(upipe, flow_def_dup); + + return UBASE_ERR_NONE; +} + +/** @internal @This processes control commands on a srt_sender pipe. + * + * @param upipe description structure of the pipe + * @param command type of command to process + * @param args arguments of the command + * @return an error code + */ +static int _upipe_srt_sender_control(struct upipe *upipe, int command, va_list args) +{ + UBASE_HANDLED_RETURN(upipe_srt_sender_control_output(upipe, command, args)); + UBASE_HANDLED_RETURN(upipe_srt_sender_control_outputs(upipe, command, args)); + switch (command) { + case UPIPE_ATTACH_UPUMP_MGR: + upipe_srt_sender_set_upump_timer(upipe, NULL); + return upipe_srt_sender_attach_upump_mgr(upipe); + case UPIPE_ATTACH_UCLOCK: + upipe_srt_sender_set_upump_timer(upipe, NULL); + upipe_srt_sender_require_uclock(upipe); + return UBASE_ERR_NONE; + case UPIPE_SET_FLOW_DEF: { + struct uref *flow_def = va_arg(args, struct uref *); + return upipe_srt_sender_set_flow_def(upipe, flow_def); + } + case UPIPE_SET_OPTION: { + const char *k = va_arg(args, const char *); + const char *v = va_arg(args, const char *); + if (strcmp(k, "latency")) + return UBASE_ERR_INVALID; + + struct upipe_srt_sender *upipe_srt_sender = upipe_srt_sender_from_upipe(upipe); + upipe_srt_sender->latency = atoi(v) * UCLOCK_FREQ / 1000; + upipe_dbg_va(upipe, "Set latency to %s msecs", v); + return UBASE_ERR_NONE; + } + default: + return UBASE_ERR_UNHANDLED; + } +} + +static int upipe_srt_sender_control(struct upipe *upipe, int command, va_list args) +{ + UBASE_RETURN(_upipe_srt_sender_control(upipe, command, args)) + return upipe_srt_sender_check(upipe, NULL); +} + +/** @internal @This frees all resources allocated. + * + * @param upipe the public structure of the pipe + */ +static void upipe_srt_sender_free(struct upipe *upipe) +{ + struct upipe_srt_sender *upipe_srt_sender = upipe_srt_sender_from_upipe(upipe); + + upipe_throw_dead(upipe); + + upipe_srt_sender_clean_output(upipe); + upipe_srt_sender_clean_sub_outputs(upipe); + upipe_srt_sender_clean_urefcount_real(upipe); + upipe_srt_sender_clean_urefcount(upipe); + upipe_srt_sender_clean_ubuf_mgr(upipe); + upipe_srt_sender_clean_uref_mgr(upipe); + upipe_srt_sender_clean_upump_timer(upipe); + upipe_srt_sender_clean_upump_mgr(upipe); + upipe_srt_sender_clean_uclock(upipe); + + struct uchain *uchain, *uchain_tmp; + ulist_delete_foreach(&upipe_srt_sender->queue, uchain, uchain_tmp) { + struct uref *uref = uref_from_uchain(uchain); + ulist_delete(uchain); + uref_free(uref); + } + + upipe_srt_sender_free_void(upipe); +} + +static struct upipe_mgr upipe_srt_sender_mgr = { + .refcount = NULL, + .signature = UPIPE_SRT_SENDER_SIGNATURE, + + .upipe_alloc = upipe_srt_sender_alloc, + .upipe_input = upipe_srt_sender_input, + .upipe_control = upipe_srt_sender_control, + + .upipe_mgr_control = NULL +}; + +/** @This returns the management structure for srt_sender pipes. + * + * @return pointer to manager + */ +struct upipe_mgr *upipe_srt_sender_mgr_alloc(void) +{ + return &upipe_srt_sender_mgr; +} diff --git a/lib/upipe/uprobe_dejitter.c b/lib/upipe/uprobe_dejitter.c index 1a236268d..f2a077918 100644 --- a/lib/upipe/uprobe_dejitter.c +++ b/lib/upipe/uprobe_dejitter.c @@ -101,8 +101,8 @@ static int uprobe_dejitter_clock_ref(struct uprobe *uprobe, struct upipe *upipe, double offset = (double)((int64_t)cr_sys - (int64_t)cr_prog); if (unlikely(discontinuity)) upipe_warn(upipe, "[dejitter] discontinuity"); - else if (unlikely(fabs(offset - uprobe_dejitter->offset) > - MAX_JITTER + 3 * uprobe_dejitter->deviation)) { + else if (unlikely(uprobe_dejitter->offset && fabs(offset - uprobe_dejitter->offset) > + uprobe_dejitter->maximum_jitter + 3 * uprobe_dejitter->deviation)) { upipe_warn_va(upipe, "[dejitter] max jitter reached (%f ms)", (offset - uprobe_dejitter->offset) * 1000 / UCLOCK_FREQ); discontinuity = 1; @@ -121,15 +121,18 @@ static int uprobe_dejitter_clock_ref(struct uprobe *uprobe, struct upipe *upipe, uprobe_dejitter->offset_count++; double deviation = offset - uprobe_dejitter->offset; - uprobe_dejitter->deviation = - sqrt((uprobe_dejitter->deviation * uprobe_dejitter->deviation * - uprobe_dejitter->deviation_count + deviation * deviation) / - (uprobe_dejitter->deviation_count + 1)); + if (uprobe_dejitter->deviation_count) + uprobe_dejitter->deviation = + sqrt((uprobe_dejitter->deviation * uprobe_dejitter->deviation * + uprobe_dejitter->deviation_count + deviation * deviation) / + (uprobe_dejitter->deviation_count + 1)); if (uprobe_dejitter->deviation_count < uprobe_dejitter->deviation_divider) uprobe_dejitter->deviation_count++; if (uprobe_dejitter->deviation < uprobe_dejitter->minimum_deviation) uprobe_dejitter->deviation = uprobe_dejitter->minimum_deviation; + if (uprobe_dejitter->deviation > uprobe_dejitter->maximum_deviation) + uprobe_dejitter->deviation = uprobe_dejitter->maximum_deviation; int64_t wanted_offset = uprobe_dejitter->offset + 3 * uprobe_dejitter->deviation; @@ -194,16 +197,19 @@ static int uprobe_dejitter_clock_ref(struct uprobe *uprobe, struct upipe *upipe, if (cr_sys > uprobe_dejitter->last_print + PRINT_PERIODICITY) { upipe_dbg_va(upipe, - "dejitter drift %f error %"PRId64" deviation %g", + "dejitter drift %f error %g ms deviation %g ms", (double)uprobe_dejitter->drift_rate.num / uprobe_dejitter->drift_rate.den, - error_offset, uprobe_dejitter->deviation); + (double)error_offset * 1000 / UCLOCK_FREQ, + uprobe_dejitter->deviation * 1000 / UCLOCK_FREQ); uprobe_dejitter->last_print = cr_sys; } upipe_verbose_va(upipe, - "new ref offset %"PRId64" error %"PRId64" deviation %g", - real_offset, error_offset, uprobe_dejitter->deviation); + "new ref offset %.2g ms error %.2g ms deviation %g ms", + (double)real_offset * 1000. / UCLOCK_FREQ, + (double)error_offset * 1000. / UCLOCK_FREQ, + uprobe_dejitter->deviation * 1000. / UCLOCK_FREQ); return UBASE_ERR_NONE; } @@ -282,7 +288,7 @@ void uprobe_dejitter_set(struct uprobe *uprobe, bool enabled, uprobe_dejitter->offset_divider = enabled ? OFFSET_DIVIDER : 0; uprobe_dejitter->deviation_divider = enabled ? DEVIATION_DIVIDER : 0; uprobe_dejitter->offset_count = 0; - uprobe_dejitter->deviation_count = 1; + uprobe_dejitter->deviation_count = 0; uprobe_dejitter->offset = 0; if (deviation) uprobe_dejitter->deviation = deviation; @@ -291,6 +297,8 @@ void uprobe_dejitter_set(struct uprobe *uprobe, bool enabled, if (uprobe_dejitter->deviation < uprobe_dejitter->minimum_deviation) uprobe_dejitter->deviation = uprobe_dejitter->minimum_deviation; + if (uprobe_dejitter->deviation > uprobe_dejitter->maximum_deviation) + uprobe_dejitter->deviation = uprobe_dejitter->maximum_deviation; } /** @This sets the minimum deviation of the dejittering probe. @@ -308,6 +316,34 @@ void uprobe_dejitter_set_minimum_deviation(struct uprobe *uprobe, uprobe_dejitter->deviation = deviation; } +/** @This sets the maximum deviation of the dejittering probe. + * + * @param uprobe pointer to probe + * @param deviation maximum deviation to set + */ +void uprobe_dejitter_set_maximum_deviation(struct uprobe *uprobe, + double deviation) +{ + struct uprobe_dejitter *uprobe_dejitter = + uprobe_dejitter_from_uprobe(uprobe); + uprobe_dejitter->maximum_deviation = deviation; + if (uprobe_dejitter->deviation > deviation) + uprobe_dejitter->deviation = deviation; +} + +/** @This sets the maximum jitter of the dejittering probe. + * + * @param uprobe pointer to probe + * @param jitter maximum jitter to set + */ +void uprobe_dejitter_set_maximum_jitter(struct uprobe *uprobe, + uint64_t jitter) +{ + struct uprobe_dejitter *uprobe_dejitter = + uprobe_dejitter_from_uprobe(uprobe); + uprobe_dejitter->maximum_jitter = jitter; +} + /** @This initializes an already allocated uprobe_dejitter structure. * * @param uprobe_pfx pointer to the already allocated structure @@ -325,6 +361,8 @@ struct uprobe *uprobe_dejitter_init(struct uprobe_dejitter *uprobe_dejitter, uprobe_dejitter->drift_rate.num = uprobe_dejitter->drift_rate.den = 1; uprobe_dejitter->last_print = 0; uprobe_dejitter->minimum_deviation = 0; + uprobe_dejitter->maximum_deviation = 0; + uprobe_dejitter->maximum_jitter = MAX_JITTER; uprobe_dejitter_set(uprobe, enabled, deviation); uprobe_init(uprobe, uprobe_dejitter_throw, next); return uprobe;