1372 Commits

Author SHA1 Message Date
roman 6609cad980 Update tools/meson.build 2026-06-11 15:13:56 +00:00
roman f42b6a3293 Add tools/moo-ristsender.c 2026-06-11 15:12:18 +00:00
Sergio Ammirata 5e73e582f4 Merge branch 'feat/recovery-priority' into 'master'
feat(nack): route retransmissions by per-peer recovery_priority

See merge request rist/librist!350
2026-06-11 00:23:19 +00:00
Sergio Ammirata 8da9e7f737 feat(nack): route retransmissions by per-peer recovery_priority
When a receiver flow is carried by more than one RTCP-capable peer,
send_nack_group historically sent every NACK to the eligible peer
with the lowest RTT.  That is wrong when the lowest-RTT peer cannot
actually retransmit: e.g. a duplicate/relay feed that delivers the
same packets but keeps no retransmit buffer.  NACKs go to it, the
peer that holds the buffer never hears them, and recovery stalls.

Add a per-peer recovery_priority (new ?recovery-priority=<n> URL
parameter, rist_peer_config.recovery_priority).  NACK groups now go
to the eligible peer with the highest recovery_priority, ties broken
by lowest RTT.  The default of 0 on every peer reduces exactly to the
prior lowest-RTT selection, so existing deployments are unchanged.

The selection predicate is factored into src/rist-nack-select.h
(rist_nack_peer_preferred) so it can be unit-tested without building
peers or starting threads.

Also name the weight=0 duplicate sentinel: new public macro
RIST_PEER_WEIGHT_DUPLICATE makes the load-balancer's "duplicate to
all peers" branch read as intent rather than a magic 0.

ABI: RIST_PEER_CONFIG_VERSION 4 -> 5 (one new trailing field,
defaulted to 0 by rist_peer_config_defaults_set; zero-initialised
configs keep legacy behaviour).

Tests:
  - test/rist/unit/test_url_recovery_priority_parse.c: ?recovery-priority=
    parsing through the public rist_parse_address2 API.
  - test/rist/unit/test_nack_peer_select.c: priority/RTT selection,
    tie-breaks, legacy-equivalence and unmeasured-RTT edge cases.
  - meson test --suite unit: 8/8 OK.
  - meson test (full): 51 OK, 11 Expected Fail, 0 unexpected.
2026-06-10 17:07:37 -04:00
Sergio Ammirata 2bf4ad2ad8 Merge branch 'fix/abi-peer-buffer-overflow' into 'master'
core: implement versioned struct initialization and library guards to prevent ABI memory issues

See merge request rist/librist!349
2026-06-10 20:40:49 +00:00
Sergio Ammirata 250c11c119 test, news: cover versioned peer-config init and document legacy fallback
Add a unit test asserting rist_peer_config_defaults_set_versioned() writes
only the fields defined at or below the requested version (split_mode and
merge_mode from version 1, profile and profile_set from version 4) and that
the rist_peer_config_defaults_set() inline records the caller's compiled
RIST_PEER_CONFIG_VERSION.

Document in NEWS the new versioned initialiser, the version guards in
parse_url_options() / store_peer_settings(), and the retained exported
rist_peer_config_defaults_set() symbol, which assumes version 0 for binaries
linked against an earlier library and therefore leaves version-1+ fields
untouched.
2026-06-10 16:27:50 -04:00
RossWang b8dc4832f8 core: implement versioned struct initialization and library guards to prevent ABI memory issues
Introduces version checks and a versioned struct initialization mechanism for
`struct rist_peer_config` to protect against memory corruption and out-of-bounds
access when older client binaries run against a newer dynamic library.

Changes:
- Declared `rist_peer_config_defaults_set_versioned()` to initialize defaults
  conditionally based on the version of the calling client.
- Redefined `rist_peer_config_defaults_set()` as a static inline function in
  `peer.h` for external clients to capture their compiled version, while keeping
  the non-inline legacy wrapper in `rist.c` (gated by `LIBRIST_INTERNAL` and
  defaulted to version 0) for ABI linkage compatibility.
- Added version checks to `parse_url_options()` inside `src/rist-common.c` to prevent
  writing out-of-bounds (`multicast_ttl`, `multicast_source`, `reflector`,
  `local_port` require version >= 2; `srp_compat_legacy` requires version >= 3).
- Added version checks to `store_peer_settings()` inside `src/rist-common.c` to prevent
  reading out-of-bounds when copying configuration settings to internal structures
  (`reflector` requires version >= 2; `srp_compat_legacy` requires version >= 3).
2026-06-10 16:27:26 -04:00
Sergio Ammirata 8c23f24c86 Merge branch 'fix/log-cb-use-after-free' into 'master'
logging: unset global logs when settings or context are freed

See merge request rist/librist!348
2026-06-10 20:25:19 +00:00
Sergio Ammirata ea35c55b82 news: document global-logger use-after-free fix
Record the fix that clears the global logging settings when an
application frees its rist_logging_settings (directly or by destroying a
context that embeds them), preventing later logs from calling a freed
callback.
2026-06-10 16:02:42 -04:00
RossWang 2790539e88 logging: unset global logs when settings or context are freed
Fixes an access violation/use-after-free in client applications (such
as FFmpeg) when a stream is quickly closed and opened.

If a client allocates or embeds logging settings, they are copied into
the static global logging settings. When the stream is destroyed, the
memory hosting the logging callback and its user argument is freed.
If they are not unset from the global logger, subsequent library logs
routed through rist_log_priv3 (e.g., in background evsocket threads
or new context initialization) will attempt to use the dangling
callback argument, triggering a crash inside the logging callback.

This fixes the issue by unsetting the global logging configuration
if the settings structure being cleaned up matches the currently
configured global logging settings.

- Add rist_logging_unset_global_if_matches() to check and unset settings.
- Call it in rist_logging_settings_free2() when freeing settings directly.
- Call it in rist_receiver_destroy_local() and rist_sender_destroy_local()
  to handle clients like FFmpeg that embed the settings structure.
2026-06-10 20:37:23 +08:00
Sergio Ammirata 977ef1b329 Merge branch 'feature/prometheus-receiver-peer-url' into 'master'
prometheus-exporter: add peer_url label to rist_receiver_peer_*

See merge request rist/librist!347
2026-06-09 09:44:22 +00:00
Sergio Ammirata 460032f555 prometheus-exporter: add peer_url label to rist_receiver_peer_*
Mirrors the existing peer_url label on rist_sender_peer_*.  peer_id
is reissued (higher) on reconnect, so it isn't a stable per-peer
identifier; peer_url is.

Sender peers carry the URL through rist_prometheus_sender_add_peer.
Receiver peers are auto-discovered from the stats callback, so the
URL is added to the receiver-flow stats JSON ("url" field on each
peer object) and parsed at first registration in the exporter.
For listener-mode peers the URL lives on the parent (the inbound
child is created with url=NULL); the stats populator walks the
parent chain.
2026-06-09 05:38:49 -04:00
Sergio Ammirata 8461d46117 Merge branch 'fix/prometheus-unix-socket-reliability' into 'master'
fix(prometheus-exporter): six fixes for flaky --metrics-unixsock scrapes

See merge request rist/librist!346
2026-06-06 01:07:23 +00:00
Sergio Ammirata bae99c6c64 prometheus-exporter: fix flaky unix-socket scrapes
The unix-socket scraper exposed by ristreceiver / ristsender
(activated by --metrics-unixsock) returned empty or stub-only
bodies on a substantial fraction of scrapes in production.
Six separate defects in tools/prometheus-exporter.c contributed:

1. listen(fd, 1) -- a backlog of one is too small for any
   real deployment where Prometheus + an external collector
   may scrape within the same RTT. Replaced with SOMAXCONN.

2. accept() returning EINTR was treated as fatal and broke the
   thread out of the loop, silently disabling the exporter for
   the remainder of the process lifetime. EINTR is now retried.

3. The hot path issued a single unchecked write() with the
   result discarded via (void)!. Partial writes and transient
   EINTR therefore truncated the response, and an early peer
   close raised SIGPIPE on the exporter thread. Replaced with
   a write_all helper that loops on partial writes, retries
   EINTR, and uses MSG_NOSIGNAL where available.

4. shutdown(SHUT_RDWR) immediately before close() can behave
   as an abortive teardown on AF_UNIX SOCK_STREAM under some
   kernels, discarding bytes that write() had just queued.
   Switched to shutdown(SHUT_WR), which is the documented
   "no more writes -- let the receive queue drain" sequence.

5. close() with unread bytes in the receive queue is the
   biggest source of empty-body scrapes in the field. The
   exporter is a dump-on-connect protocol that never reads
   the client's request, but tools like nc / curl / the
   Prometheus scraper itself all send "GET /metrics HTTP/1.0
   \r\n\r\n". On Linux, close() on an AF_UNIX SOCK_STREAM
   with unread input is treated as an abortive teardown: the
   kernel sends RST and discards everything in the transmit
   queue -- including the 10 KB body we just wrote. The
   client sees ECONNRESET / EOF with zero bytes. We now
   shutdown(SHUT_WR) and drain the receive queue with a
   SO_RCVTIMEO-bounded recv loop before close, so the kernel
   does a clean FIN-FIN handshake and the body actually
   reaches the client. Confirmed by a Python probe that
   went from 5/5 ECONNRESET to 5/5 success in the same
   environment.

6. In single-stat-point mode (the default for ristreceiver
   and ristsender) rist_prometheus_stats_format() was zeroing
   container_count after every scrape. Any subsequent scrape
   that arrived before the next 1 Hz stats callback then
   emitted only HELP/TYPE/UNIT preambles with no data points,
   which manifested in the field as metrics flickering and
   downstream consumers parsing NaN / blank bandwidth. The
   ring is now only reset in --metrics-multipoint mode;
   single-point mode keeps the latest sample visible until
   the next callback overwrites it. cleanup_stale_locked()
   still ages stale flows / peers out after 15 s.
2026-06-05 21:02:54 -04:00
Sergio Ammirata dc4b993302 Merge branch 'fix/prometheus-single-stat-point-slot' into 'master'
fix(prometheus-exporter): write to container[0] in single-stat-point mode

See merge request rist/librist!345
2026-06-06 00:48:34 +00:00
Sergio Ammirata 70f5fc7089 fix(prometheus-exporter): write to container[0] in single-stat-point mode
In single-stat-point mode the writer set container_offset = 1 after
each callback, but the format() macro reads only container[0..count-1]
and container_count stays at 1.  container[0] was therefore frozen at
the value from the first callback while every subsequent callback
silently overwrote container[1], which the reader never touches.

Three sites had the same pattern:
- rist_prometheus_handle_client_stats (receiver flow stats)
- rist_prometheus_handle_client_stats (per-receiver-peer stats)
- rist_prometheus_handle_sender_peer_stats

NEWS entry added under 0.2.18 Bug Fixes.
2026-06-05 20:41:48 -04:00
Sergio Ammirata 003270fbba Merge branch 'feature/url-param-profile' into 'master'
feat: tighten ?profile= and ?srp-compat= URL parameters (numeric, library-side)

See merge request rist/librist!344
2026-06-06 00:37:58 +00:00
Sergio Ammirata e21f1e644c fix(url): parse ?srp-compat= as 0|1, not "legacy"
The original librist convention for URL parameters that select from a
small fixed enum is numeric values (aes-type, congestion-control,
timing-mode, reflector, and as of the previous commit profile).
?srp-compat= was added in v0.2.18-rc1 with a symbolic alias ("legacy")
and a permissive parser that mapped any unrecognised value, including
a typo of the alias, to 0 (compliant).  Tightened to numeric-only
before v0.2.18 final.

The parser now accepts only 0 or 1.  Out-of-range, non-numeric, and
trailing-garbage values cause rist_parse_address2 to return non-zero
with stderr output.

Tests
-----
- test/rist/unit/test_url_srp_compat_parse.c (new): two accepted forms
  plus six rejected forms.
- meson test --suite unit:        6/6 OK (was 5/5).
- meson test --suite url-profile: 4/4 OK.
- meson test (full):              49 OK, 11 Expected Fail, 0 unexpected.
2026-06-05 20:35:00 -04:00
Sergio Ammirata 69e308d5a9 fix(url): make ?profile= override the context wire profile in the library
RIST_URL_PARAM_PROFILE has been declared in the public urlparam.h header
since at least the rist2rist merge but the URL parser never honored it
and the field had no library-side effect.  This commit makes the
parameter do what the public header has always advertised: select the
wire profile of the context that owns the peer, from inside the URL
itself.

The override is implemented entirely in the library so every consumer
that already passes URLs through rist_parse_address2 + rist_peer_create
picks it up without application-side changes.

URL syntax
----------
  ?profile=0    Simple
  ?profile=1    Main
  ?profile=2    Advanced

Out-of-range, non-numeric, or trailing-garbage values cause
rist_parse_address2 to return non-zero with stderr output.

ABI
---
RIST_PEER_CONFIG_VERSION bumps from 3 to 4.  Two trailing fields added
to struct rist_peer_config (profile, profile_set).

Tests
-----
- test/rist/unit/test_url_profile_parse.c: parser assertions including
  the rejected forms.
- test/rist/unit/test_url_profile_override.c: three scenarios through
  the public API - override applies, conflicting peer refused, late
  peer refused.
- test/rist/meson.build: four end-to-end tests where rist_*_create is
  called with a profile that differs from the URL ?profile= value;
  both ends must converge on the URL profile for the test to pass.
- meson test --suite unit:        5/5 OK
- meson test --suite url-profile: 4/4 OK
- meson test (full):              48 OK, 11 Expected Fail, 0 unexpected.

NEWS updated under ABI/API and Bug Fixes.
2026-06-05 20:35:00 -04:00
Sergio Ammirata 6377b5c333 Merge branch 'feature/prometheus-avg-buffer-time' into 'master'
Feature/prometheus avg buffer time

See merge request rist/librist!342
2026-06-05 05:05:35 +00:00
Manuel 10ca00d691 Feature/prometheus avg buffer time 2026-06-05 05:05:32 +00:00
Sergio Ammirata 1c34d95cca Merge branch 'fix/issue-217-receiver-flow-bitrate-decay' into 'master'
stats: refresh receiver flow bitrate counters at tick time

Closes #217

See merge request rist/librist!343
2026-06-05 04:58:14 +00:00
Sergio Ammirata 96740d6647 stats: refresh receiver flow bitrate counters at tick time (#217)
The receiver-side flow bitrate fields (`bitrate`, `bitrate_retries`,
`bitrate_rejected`, `bitrate_ts_nulls`) are only updated when a packet
arrives on the corresponding path, because the bw->bitrate field is
recomputed inside rist_calculate_flow_bitrate() and that helper is only
called from the ingest path. As soon as the traffic on a counter stops
(e.g. retries cease), the matching `bitrate_*` field stays frozen at
the last value it was assigned while traffic was flowing.

Add a small rist_refresh_flow_bitrate() helper that recomputes
bw->bitrate with zero new bytes, mirroring what
rist_calculate_bitrate(0, ...) already does for the sender peer stats
path. The helper deliberately skips the per-flow inter-packet-spacing
bookkeeping that lives in rist_calculate_flow_bitrate(), so calling it
from the stats tick does not perturb the IPS counters.

Call the new helper for all four flow bitrate counters at the start of
the JSON/binary stats emission in rist_receiver_flow_statistics(), so
each counter naturally decays to zero when its traffic stops.
2026-06-05 00:50:05 -04:00
Sergio Ammirata 6955cd4c27 Merge branch 'docs/news-prom-exporter-peer-metrics' into 'master'
NEWS: prom-exporter per-peer metrics and counter format fix (follow-up to !340)

See merge request rist/librist!341
2026-06-04 20:18:34 +00:00
Sergio Ammirata 6ea98b9532 NEWS: prom-exporter per-peer metrics and counter format fix
Two 0.2.18 pre-release bullets for the changes that landed in !340.
2026-06-04 16:17:05 -04:00
Sergio Ammirata 168e863a09 Merge branch 'feat/prometheus-receiver-peer-stats' into 'master'
prom-exporter: add per-peer receiver metrics + fix counter timestamp spacing

See merge request rist/librist!340
2026-06-04 20:10:16 +00:00
Sergio Ammirata 251905d8d0 prom-exporter: separate value and timestamp in counter output
PROMETHEUS_COUNTER_PRINT_R wrote the per-sample timestamp using
"%"PRIu64"\n" with no leading separator, so when single_stat_point
is false (the multiple_metric_datapoints=true setup), every counter
row came out as

  rist_client_flow_received_packets_total{...} 62001780601685

i.e. the value and the timestamp glued together. The sibling
PROMETHEUS_GAUGE_PRINT_R macro uses " %"PRIu64"\n" and emits the
correct "<value> <timestamp>" form. OpenMetrics requires the
timestamp to be space-separated from the value, so a scraper
reading these lines either rejects them or stores one large bogus
number.

Add the missing space. Only the multiple-datapoint branch is
affected; the single_stat_point branch omits the timestamp
entirely and is unchanged.
2026-06-04 15:57:56 -04:00
Sergio Ammirata d2148dbd58 prom-exporter: add per-peer receiver metrics
The Prometheus exporter publishes flow-level aggregates for receivers
(rist_client_flow_*) and per-peer breakdowns for senders
(rist_sender_peer_*) but no per-peer view on the receive side, even
though that data is already populated in rist_stats_receiver_flow.peers[].

Add the receiver-side counterpart as rist_receiver_peer_* with labels
{peer_id, flow_id, receiver_id}, mirroring the sender_peer schema:

  rist_receiver_peer_bandwidth_bps              gauge
  rist_receiver_peer_avg_bandwidth_bps          gauge
  rist_receiver_peer_received_data_packets      counter
  rist_receiver_peer_received_bytes             counter
  rist_receiver_peer_received_rtcp_packets      counter
  rist_receiver_peer_sent_rtcp_packets          counter
  rist_receiver_peer_rtt_seconds                gauge
  rist_receiver_peer_avg_rtt_seconds            gauge

Entries auto-register on first sight of a (receiver_id, flow_id,
peer_id) tuple, so callers do not need to wire peer_ids in at peer
creation time. Re-uses the existing stale-entry sweep (15s) and the
same realloc/abort-on-OOM idiom as sender_peers.

No public API change.
2026-06-04 15:29:39 -04:00
Sergio Ammirata eb00ca04e4 Merge branch 'fix-issue-216-msvc-windows' into 'master'
Fix MSVC build errors and ssize_t warning on Windows (#216)

See merge request rist/librist!339
2026-06-04 19:18:07 +00:00
Sergio Ammirata 9ad7840d17 common: guard ssize_t typedefs with _SSIZE_T_DEFINED
Public header <librist/transport.h> (0.2.18-rc1) typedefs ssize_t
on Windows under an _SSIZE_T_DEFINED guard.  common/attributes.h
defines the same type without a guard, so any TU that includes
both pulls in a duplicate definition and trips MSVC C4142
(benign redefinition).  Wrap each of the three Windows code paths
here in the same _SSIZE_T_DEFINED guard so the two headers are
order-independent.  Closes the warning reported in #216.
2026-06-03 08:05:06 -04:00
Sergio Ammirata 900eda94c3 test(srp): add stdatomic_dependency to srp_unit target
The main librist library target already picks up the compat/msvc
<stdatomic.h> shim when MSVC's system header requires
/experimental:c11atomics, but the srp_unit test target compiles
random.c (and friends) without that dependency and so hits
'C atomic support is not enabled' on plain MSVC builds.  Mirror
the library target's deps so the test follows the same code path.
Closes the second build error in #216.
2026-06-03 08:05:06 -04:00
Sergio Ammirata 9194ed9cef test(srp): only include <sched.h> on POSIX
MSVC has no <sched.h> and pulls calloc's prototype in via <stdlib.h>
(transitively, through <cmocka.h>), so the musl-specific shim is
only needed on POSIX targets.  Closes the first build error in #216.
2026-06-03 08:05:06 -04:00
Sergio Ammirata 20c7958a2c Merge branch 'release/v0.2.18-rc1' into 'master'
NEWS, meson: prepare 0.2.18-rc1

See merge request rist/librist!338
v0.2.18-rc1
2026-06-03 02:02:50 +00:00
Sergio Ammirata 6d59e5ba9e NEWS, meson: prepare 0.2.18-rc1 with SRP interop, XP fix, EAGAIN hardening
Bump project version to 0.2.18, API to 4.10.0, ABI to 13:0:9
(soversion 4 unchanged, binary-compatible with 0.2.15/0.2.16/0.2.17).

Headlining the release:
  - srp-compat=legacy URL opt-in restores pre-0.2.16 interop with the
    RFC 5054 PAD-compliant exchange landed in 0.2.16.
  - Windows XP support restored: rist_transport_poll() uses select()
    via the existing poll_win.c shim instead of WSAPoll() (Vista+).
  - rist_peer_recv() accepts both numeric EAGAIN values (11 and 35) so
    custom transport_ops backends in cross-compile environments work.
  - <librist/transport.h> now defines ssize_t on Windows (#214).
  - CI: srp_unit_test now runs via a cmocka wrap subproject, closing
    the silent-skip that hid the SRP fixture drift reported in #215.
2026-06-02 22:00:39 -04:00
Sergio Ammirata 57c58dcda4 Merge branch 'feat/v0.2.18-fixes' into 'master'
v0.2.18: SRP wire-format compat (#215), transport.h ssize_t (#214), --blind-send, CI coverage

Closes #215 and #214

See merge request rist/librist!337
2026-06-02 21:57:51 +00:00
Sergio Ammirata 43eda0aa6f news: document srp-compat=legacy and v0.2.18 ABI bump
- Replace the placeholder "No source or binary changes" ABI/API entry
  with the actual change: RIST_PEER_CONFIG_VERSION 2 -> 3 plus the
  one new field (srp_compat_legacy).  Existing zero-initialised
  peer configs continue to work; the new field defaults to 0.
- Promote the SRP interop note to its own section and document the
  new srp-compat=legacy URL option together with the advisory hint
  emitted on M1/M2 failure.
- Mention the four new end-to-end SRP unit tests under CI / Test
  Coverage alongside the existing fixture-regeneration entry.
2026-06-02 17:52:25 -04:00
Sergio Ammirata 8ed026a350 test(srp): cover both PAD and legacy modes plus cross-mode failures
Adds srp_run_exchange() — a self-contained authenticator+client
handshake on the 2048-bit NG_DEFAULT group using only the public SRP
API, then drives it through four scenarios:

  1. Both ends in PAD mode (default)            -> verify_m1 == 0
  2. Both ends in legacy unpadded mode          -> verify_m1 == 0
  3. Authenticator PAD, client legacy           -> verify_m1 != 0
  4. Authenticator legacy, client PAD           -> verify_m1 != 0

Cases 3 and 4 are the "operator forgot to set srp-compat on one side"
configurations.  They MUST fail at M1 — otherwise the bypass would be
silently leaking through.  The tests deliberately do NOT assert on the
specific cross-mode failure mode, because the SRP-6a identity
  (A * v^u)^b = (B - k*g^x)^(a + u*x)
only holds when both peers use the same k, so no purely-local check
can produce a deterministic cross-mode M1 to compare against.

Also propagates the new legacy_pad parameter through every existing
ctx_create call site so the deterministic fixture tests continue to
exercise the spec-compliant path.
2026-06-02 17:52:25 -04:00
Sergio Ammirata 25256615bf feat(srp): add srp-compat=legacy URL option for pre-0.2.16 interop
0.2.16 made TLS-SRP RFC 5054 / TR-06-2 §6.4 compliant by adding the
PAD operation to the u and k hash inputs.  That change breaks
SRP-authenticated handshakes against peers still running 0.2.15 or
earlier.  The recommended fix is to upgrade both ends, but
mixed-version deployments need a temporary escape hatch.

This change adds an opt-in legacy wire-format mode:

  rist://user:pass@host:port?srp-compat=legacy

Must be set on BOTH ends.  The library logs an explicit RIST_LOG_WARN
banner on startup whenever the legacy mode is active, marking it as
not TR-06-2 / RFC 5054 compliant.  Default behaviour is unchanged.

Failure-side diagnostic
-----------------------
On SRP M1 (authenticator) or M2 (client) verification failure, the
library now emits a one-shot INFO hint pointing at this option as a
possible cause.  The hint is advisory only — the SRP-6a transcript
binds the M1/M2 hashes to whichever wire mode the authenticator used
when constructing B, so there is no purely-local way to distinguish
"wrong password" from "peer is on the other wire format" once the
handshake has reached M1.  Both possible causes are surfaced.

Plumbing
--------
- include/librist/peer.h: bump RIST_PEER_CONFIG_VERSION 2 -> 3, add
  one trailing int (srp_compat_legacy); zero-initialised configs and
  older clients keep the spec-compliant default.
- include/librist/urlparam.h: new RIST_URL_PARAM_SRP_COMPAT.
- src/rist-common.c: parse srp-compat=legacy | 1 into the new field
  (anything else, including the explicit "auto", leaves PAD mode on).
  store_peer_settings() also copies the flag into peer->config so
  rist_enable_eap_srp_2(), which runs after the caller's config may
  have been freed, can still see it.
- src/crypto/srp.{c,h}: new librist_crypto_srp_hash_2_bignum_unpadded()
  helper (the pre-0.2.16 form) and a single srp_hash_uk() dispatch.
  Both authenticator and client SRP contexts grow a bool legacy_pad
  field set at ctx_create time; the four u/k hash sites pick padded
  vs unpadded based on it.
- src/proto/eap.c: eapsrp_ctx grows srp_legacy_pad + a one-shot
  srp_legacy_peer_warned latch; the flag is read from
  peer->config.srp_compat_legacy when enable_eap_srp_2 builds the
  ctx (both authenticator and authenticatee paths), propagated by
  eap_clone_ctx, and passed into both SRP ctx_create calls.

Default behaviour is unchanged: peers without srp-compat=legacy keep
the PAD-compliant exchange.
2026-06-02 17:50:42 -04:00
Sergio Ammirata 63b11b693d news: SRP interop note, ssize_t fix, CI coverage gap closure
Three additions under 0.2.18 in development:
  - SRP interop note explaining why 0.2.16+ does not handshake
    with 0.2.15 (PAD compliance is the reason; symmetric upgrade
    required).
  - Bug-fix entry for the new Windows ssize_t typedef (#214).
  - CI/test coverage entry noting libcmocka-dev install and the
    PAD-compliant test fixture regeneration (#215).
2026-06-02 17:50:42 -04:00
Sergio Ammirata 97a5242572 transport: define ssize_t on Windows in public transport.h
The recvfrom and sendto callbacks in struct rist_transport_ops
return ssize_t.  On Windows that type is not a Win32 standard
type and is not provided by <winsock2.h>, so a downstream Windows
project that #includes <librist/transport.h> fails to compile
with "unknown type name 'ssize_t'".

Library-internal code worked because librist's private
common/attributes.h provides a typedef, but that header is not
installed.  Add a public typedef inside the existing _WIN32 block,
guarded by _SSIZE_T_DEFINED to coexist with MSVC's CRT and other
public Windows headers that already conditionally define it.

Fixes #214.
2026-06-02 17:50:42 -04:00
Sergio Ammirata f8d31daefe test(srp): regenerate fixtures for 2048-bit NG against PAD-compliant exchange
Since 0.2.16 the SRP authenticator zero-pads the u and k hash inputs
to N-length per RFC 5054 §2.6 (and therefore TR-06-2).  The fixtures
in srp_examples.c were captured against the pre-PAD code and have
failed deterministically since 0.2.16 with both gnutls, system
mbedtls 3.6, and bundled mbedtls — see #215.

A separate hardening change also enforced a 1024-bit minimum for
caller-supplied N in librist_crypto_srp_client_ctx_create(), which
caused test_srp_client_ctx_create (and three downstream tests) to
deref a NULL ctx and segfault on the deprecated 512-bit modulus.

Switch the deterministic setup to the 2048-bit RFC 5054 group
(NG_DEFAULT) and re-record every fixture from the current
PAD-compliant exchange.  The DEBUG_USE_EXAMPLE_CONSTANTS hooks in
srp.c still pin a, b, and salt, so the run remains deterministic.
test_srp_client_ctx_create now exercises both the default_ng=true
path and a custom-N path with NG_2048, asserts non-NULL on every
create, and uses the standard sizeof()/mbedtls_mpi_size() byte
widths instead of the obsolete 64-byte buffers.

Fixes #215 (segfaults + hash mismatches).
2026-06-02 17:50:42 -04:00
Sergio Ammirata c236ac295e ci/test: source cmocka via meson wrap so srp_unit_test always builds
srp_unit_test had been silently skipping in test-ubuntu because the
runner image (libplacebo-ubuntu-jammy) ships without libcmocka-dev
and does not give the CI user write access to /var/lib/apt/lists, so
an in-script apt-get install cannot fix it.  The silent skip is how
the SRP fixture drift reported in #215 reached a release tag.

Switch the meson detection from cc.find_library('cmocka', required:
false) to dependency('cmocka', required: false, fallback:
['cmocka', 'cmocka_dep']) and add subprojects/cmocka.wrap pointing
at WrapDB cmocka_1.1.8-1.  When the runner image has cmocka the
system copy is used; otherwise meson builds it as a subproject.
Existing .gitignore rules (/subprojects/* with !*.wrap) keep the
fetched source out of the tree.

srp_unit's link line now picks up bcrypt on Windows because
random.c calls BCryptGenRandom; the main librist target was already
pulling it in via the top-level deps list, but srp_unit links
random.c directly.

Drop the broken apt-get line from the test-ubuntu script and add
retry: 2 to both test jobs to absorb the simple+multicast environmental
flake that surfaces on the shared runner regardless of branch.
2026-06-02 17:49:55 -04:00
Sergio Ammirata 5ecacd25d9 ci: mark test-win64 as allow_failure pending wine runner fix
The shared CI runner used by test-win64 cannot complete the
send/receive tests within meson's default 30s per-test timeout when
the binaries are executed under wine.  Recent pipelines on master
have been showing 38-41 of 55 tests marked TIMEOUT (with 0-3 real
failures) while the same suite passes on test-ubuntu.

The logs show peer authentication completing, packets flowing, NACK
/ retransmit loops operating normally — there are no crashes,
asserts or hangs, only tests being killed at the 30s boundary
because the wine layer adds enough overhead per test that they
cannot reach their target packet count in time.

Mark test-win64 as allow_failure so wine throughput jitter does not
block unrelated MRs.  The job still runs and its log is still
uploaded on failure, so genuine Windows-runtime regressions remain
visible — they just no longer flip the overall pipeline status.

A TODO is left in the file describing the two ways to drop
allow_failure: bump meson's per-test timeout multiplier for this
job, or move it to a faster runner.
2026-06-02 15:26:54 -04:00
Sergio Ammirata d2ce51690d tools: ristsender: add --blind-send for fire-and-forget broadcast mode
By default ristsender couples the input flow to RIST peer state:
multicast input groups are bound but the IGMP join is deferred until
at least one RIST output peer has completed its handshake, and
incoming UDP data is dropped until peer_connected_count > 0.  That
keeps a freshly-started sender from generating multicast traffic the
network has not yet asked for and from buffering data nobody will
ever read, which is the right behaviour for point-to-point RIST.

The peer-handshake gate is a poor fit for broadcast topologies where
the sender is meant to stream regardless of who is (or isn't)
listening — multicast output in simple/main/advanced profile, or
ristsender as a fan-out in front of non-RIST consumers.

Add --blind-send.  When set:

  - the multicast input socket performs udpsocket_join_mcast_group()
    immediately instead of marking it mcast_deferred, and
  - input_udp_recv() writes data to the RIST context unconditionally.

Default behaviour is unchanged when the option is absent.
2026-06-02 14:42:51 -04:00
Sergio Ammirata d1dcd83a08 transport: match canonical EAGAIN errno values defensively in rist_peer_recv
When a custom transport_ops backend returns -1 from recvfrom() to signal
"no data ready", librist's poll loop relies on errno == EAGAIN to treat
the call as transient and silently retry.  In most environments this is
straightforward, but cross-compilation setups can end up with the
EAGAIN macro resolving to a different numeric value than the one the
custom backend actually wrote into errno.

This has been observed concretely with VLC's WebAssembly build, where
librist is compiled by Emscripten as part of VLC's contrib system: the
WebTransport-backed transport_ops returns -1 with errno = EAGAIN (= 11
in musl/Emscripten), but librist's own translation unit ends up with
EAGAIN expanding to 35 due to header search paths from the host
toolchain leaking into the cross build.  The result was the poll loop
mistaking the transient case for a hard error and spamming
"Receive failed: errno=11" until the RIST handshake gave up.

Recognise both canonical EAGAIN errno values (11 on Linux / Emscripten /
musl, 35 on macOS / *BSD / iOS) explicitly when ret == -1.  On any
platform where EAGAIN resolves consistently the existing macro check
already returns first, so this addition is unreachable and behaviour is
unchanged.  It only fires in the pathological cross-compile case where
the macro and the runtime errno disagree, and protects the same set of
errno values that POSIX recvfrom(2) is documented to use for the
non-blocking "no data" path.

The Windows path (_WIN32) is unaffected.
2026-06-02 14:28:02 -04:00
Sergio Ammirata fdeea28907 Merge branch 'fix/transport-poll-xp-compat' into 'master'
fix(transport): use select()-based poll() shim instead of WSAPoll for XP compatibility

See merge request rist/librist!336
2026-06-02 15:58:29 +00:00
Sergio Ammirata 769a05a797 fix(transport): use select()-based poll() shim instead of WSAPoll
librist 0.2.17 introduced a pluggable transport abstraction
(rist_transport_set + companion rist_transport_poll wrapper) that on
Windows fell back to WSAPoll().  WSAPoll is a Vista+ symbol, so adding
it to the librist link surface dropped runtime support for Windows XP
on the rist_transport_poll() code path — a regression compared with
0.2.16, which only ever called select() on Windows (via the
public-domain poll() shim included from contrib/poll_win.c into
src/libevsocket.c since 2019).

Route rist_transport_poll() through that same select()-based shim
instead of WSAPoll() so the pluggable-transport layer preserves XP
compatibility.  The fix mirrors libevsocket.c's pattern: include
contrib/poll_win.c as a translation-unit-private static function on
Windows, then call poll() unconditionally on both platforms.

Also expose <winsock2.h>'s struct pollfd declaration at the prototype
site (transport-private.h) so the prototype and definition agree even
when callers pin _WIN32_WINNT below 0x0600 (e.g. VLC's 3.0.x contrib
system, which targets legacy Windows XP).  The redefinition is
header-only and pulls in zero Vista+ runtime symbols — only the
struct pollfd typedef visibility changes.

After the fix, mingw-w64 cross-built librist.dll has WSAPoll removed
from its WS2_32.dll import table (verified locally with `objdump -p`);
XP runtime support is preserved.

Reported by Steve Lhomme (@robUx4) during review of VLC merge request
videolan/vlc!9297.
2026-06-02 11:36:43 -04:00
Sergio Ammirata e939a9a4e3 Merge branch 'release/v0.2.17-news' into 'master'
release: mark NEWS for v0.2.17 final

See merge request rist/librist!335
v0.2.17
2026-05-31 14:10:56 +00:00
Sergio Ammirata 04634937ac release: mark NEWS for v0.2.17 final 2026-05-31 14:10:56 +00:00
Sergio Ammirata b971cfa074 Merge branch 'fix/mbedtls-ios-availability' into 'master'
fix(mbedtls): suppress -Wunguarded-availability for bundled build

See merge request rist/librist!334
v0.2.17-rc2
2026-05-30 23:17:05 +00:00