Skip to content

RFC 6330 RaptorQ FEC (not 3GPP-mandated — reference/future work, depends on #61) - #64

Open
jordijoangimenez wants to merge 19 commits into
5G-MAG:developmentfrom
jordijoangimenez:feature/raptorq-support
Open

RFC 6330 RaptorQ FEC (not 3GPP-mandated — reference/future work, depends on #61)#64
jordijoangimenez wants to merge 19 commits into
5G-MAG:developmentfrom
jordijoangimenez:feature/raptorq-support

Conversation

@jordijoangimenez

Copy link
Copy Markdown
Contributor

Not for merging yet — reference/future work

Adds RFC 6330 RaptorQ FEC on top of #61's Raptor implementation. Split out
of #61 because 3GPP TS 26.346 v18.2.0 cl.7.2.2/7.2.12 defines Compact No-Code
(ID 0) and Raptor/RFC 5053 (ID 1) for the download delivery method, but
doesn't reference RaptorQ (ID 6) anywhere — not forbidden, just outside
today's 3GPP-defined scheme set. See the discussion on #61 for the full
verification.

This PR exists so the work isn't lost, and so it's visible if:

  • a non-3GPP FLUTE deployment wants RaptorQ, or
  • 3GPP adopts RaptorQ in a future release.

Depends on #61 — this branch is #61 plus one commit re-adding RaptorQ.
Diff against development includes #61's Raptor changes too, until that
merges.

Testing

Full library + all 6 GTest binaries (25 tests, including RaptorQ unit + e2e)
build and pass locally.

dsilhavy and others added 19 commits March 10, 2026 07:59
…r alive until Boost.Asio calls the completion lambda.
…ebuild inner IPv4/UDP headers with byte-wise network-order checksum generation so tunneled end-to-end delivery passes reliably in release builds.
…eceive-buffer overflow

Squashed re-application of accumulated fixes/features previously
developed on a personal fork whose history had diverged from
5G-MAG/rt-libflute's actual current development/main (unrelated
histories, confirmed via git merge-base returning nothing, despite
matching content up to a shared point -- likely from a prior history
rewrite on one side only). Re-applied here as a single clean commit
against the real upstream base rather than replaying the original,
now-irreconcilable commit sequence:

- Add SSM (source-specific multicast) join support to Receiver, so a
  session can admit packets only from a specified source address
  rather than any-source multicast.
- Fix a use-after-free: an async_receive_from completion already
  queued on the io_context when a Receiver is destroyed could run
  after the destructor returns and touch a freed `this` -- boost::asio
  only guarantees a cancelled operation's handler eventually runs with
  operation_aborted, not that it runs before the destructor returns.
  Receiver now tracks its own liveness via a shared atomic flag copied
  into each completion handler, checked before touching `this`.
- Fix Receiver's fixed 2048-byte receive buffer silently truncating
  larger encoding symbols: recvfrom() on a datagram socket doesn't
  error on a too-small buffer, it silently truncates, so any FEC-OTI
  configuration with larger symbols corrupted every symbol beyond
  2048 bytes without any visible error until an FDT Content-MD5 check
  (if present) caught it. Buffer is now 65536 bytes, covering the
  maximum possible IPv4 UDP payload.
Transmitter::send() reused the same FileDescription/TOI correctly for
carousel-style repeated objects, but two bugs meant each resend still
grew the file delivery state without bound:

- _files.insert() is a no-op if the TOI key is already present, so a
  resend's updated File object was silently discarded in favour of
  the stale one already in the map.
- FileDeliveryTable::add() unconditionally appends a new <File> entry
  with no dedup by TOI, so every resend left the previous cycle's
  entry for the same TOI in place -- the serialised FDT grows by one
  entry per resend indefinitely (observed growing from ~850 bytes to
  several hundred KB over a couple of hours of a 10-second carousel),
  eventually becoming too large for a receiver to reassemble at all.

Per RFC 6726 SS3.3/3.4.2, an FDT Instance describes the current state
of the file delivery session; a Content-Location may be redescribed
under a new TOI to signal a new version, but parameters already
described for a given TOI must not change -- so on an actual resend
of unchanged content, the sender should replace that TOI's single
File entry, not accumulate duplicates of it. This restores that
invariant: track whether this send is a resend (TOI already
assigned), replace the File map entry instead of no-op'ing, and
remove the TOI's existing FDT entry before adding the current one.
The previous fix (same TOI, resent unchanged content) only handled
one growth vector. A second, distinct one remained: when
set_content()/set_compression() detect the content genuinely changed,
they zero the FileDescription's TOI so Transmitter::send() assigns a
fresh one -- but nothing ever removed the FDT entry for the TOI being
vacated. Confirmed live: a carousel object whose content legitimately
changes each cycle (e.g. a randomly-regenerated MIME boundary) grew
its FDT to 135-170KB within about a minute, well before the previous
fix's growth timescale, eventually failing to parse
(XML_ERROR_PARSING_ATTRIBUTE).

FileDescription now remembers the TOI it's vacating (_previous_toi,
set by a new _reset_toi() helper used everywhere the TOI was zeroed)
so Transmitter::send() can remove that stale entry before assigning
the replacement TOI, restoring the "one current entry per logical
object" invariant regardless of which of the two ways an object's
description changes.
- File.cpp: use fmt::format instead of manual std::to_string()
  concatenation for the two bounds-check exception messages, per the
  suggestion -- fmt::format rather than std::format since this project
  targets C++17 (std::format needs C++20); same result, already a
  dependency via spdlog.
- Transmitter.h/.cpp: added FileDescription::previous_toi()/
  reset_previous_toi() public accessors instead of Transmitter::send()
  reaching into FileDescription's private _previous_toi directly via
  friend access, per the suggestion.
- Receiver.cpp: restored IPv6 support the SSM/specific-interface-join fix
  had inadvertently dropped (the original code let Boost infer v4 vs v6
  from the address types passed; the fix hardcoded .to_v4() throughout).
  Now branches on the multicast address's actual family: IPv6 ASM join
  uses join_group(address_v6, interface_index) and SSM join uses
  MCAST_JOIN_SOURCE_GROUP/group_source_req, both interface-by-index
  (unlike IPv4's interface-by-address), so a new resolve_iface_index()
  helper resolves the existing iface address string to its owning
  interface's index via getifaddrs()/if_nametoindex(). IPv4 behaviour
  (bind/ASM/SSM) is unchanged, verified by rerunning the existing
  test_end_to_end.cpp unmodified.
…nflict

Both branches touched Transmitter::send_next_packet()'s tunnelled-send path:
this branch (PR 5G-MAG#56) made the encapsulated packet buffer lifetime-safe by
owning it in a shared_ptr<vector<char>> captured by the async_send_to
completion lambda, replacing a raw new[] with no matching delete[] (a leak
on every tunnelled packet). development, in parallel, added _source_address
as an explicit override for the local address used to build the inner
IPv4/UDP headers, falling back to _tunnel_local_address when unset.

Kept both: the shared_ptr-owned buffer from this branch, with the
_source_address-or-_tunnel_local_address fallback from development.
…ved PR 5G-MAG#56 branch

Integration base for building the Raptor/RaptorQ FEC work on top of both
outstanding PRs' assumed-merged state: PR 5G-MAG#56's tunnel-buffer lifetime fix
(reconciled with development's _source_address fallback) plus PR 5G-MAG#60's
FDT-growth fixes with the previous_toi()/reset_previous_toi() accessor
pattern and full IPv4/IPv6 parity in Receiver's SSM/ASM join and bind
logic. No conflicts with the prior merge; both touch disjoint enough
regions of Transmitter.cpp/.h that git combined them cleanly.
…assumed-merged state

This branch is built assuming both outstanding PRs land first: PR 5G-MAG#56's
tunnel-buffer lifetime fix (with development's _source_address fallback,
already reconciled in an earlier commit on this branch) and PR 5G-MAG#60's
FDT-growth fixes with the previous_toi()/reset_previous_toi() accessor
pattern and full IPv4/IPv6 parity in Receiver.

No shared git history exists between this fork's line of development and
the current upstream development branch (a previously-diagnosed history
rewrite upstream, see PR 5G-MAG#60's own 184e482), so this reconciliation was
done as a content-level 3-way patch apply against this integration base,
not a rebase -- 14 files had genuine overlapping hunks, resolved as
follows:

- flute_types.h, AlcPacket.cpp, EncodingSymbol.cpp, File.cpp,
  FileDeliveryTable.cpp: kept the Raptor/RaptorQ-aware superset (FecOti's
  scheme-specific fields, multi-scheme FEC Payload ID / EXT_FTI / FDT
  attribute parsing) -- these fully subsume the integration base's
  CompactNoCode-only logic.
- Receiver.cpp: kept the integration base's version entirely -- PR 5G-MAG#60's
  IPv6-aware SSM/ASM join and bind logic is strictly more complete than
  this branch's older IPv4-only copy of the same code, and Raptor/RaptorQ
  content needs no Receiver-side changes beyond what File/EncodingSymbol
  already provide.
- Transmitter.h/.cpp: combined per-hunk -- kept the accessor pattern
  (previous_toi()/reset_previous_toi()) and the graceful
  deactivate(bool finish_file_transmissions) lifecycle from the
  integration base; kept this branch's content_fec_oti constructor
  parameter and fec_oti()/fdt() accessors; and rebuilt this branch's
  dual-send fix (send both a plain copy and a tunnelled copy, needed for
  N3mb GTP-U tunnelling per TS 23.247 while still supporting direct SSM
  subscribers) on top of PR 5G-MAG#56's cleaner byte-wise checksum/header
  implementation (create_udp_pkt/create_ip_hdr/calculate_sum operating on
  uint8_t* with explicit write_uint16_be/write_uint32_be helpers, not the
  older uint16_t*-punned struct-overlay approach) instead of keeping two
  divergent checksum implementations.
- CMakeLists.txt, tests/CMakeLists.txt, examples/flute-transmitter.cpp,
  include/File.h: trivial additive conflicts (version bump, new test
  targets, new #includes, an unrelated exception-slicing/format-string
  bug fix already on this branch).
- tests/test_transmitter.cpp, tests/test_end_to_end.cpp: kept the
  integration base's superset (adds a UDP-tunnel e2e test and a graceful-
  deactivation lifecycle test this branch didn't have); this branch's
  own duplicate of the basic transmit/receive test added nothing Raptor-
  specific -- that coverage lives in test_raptor_e2e.cpp/test_raptorq_fec.cpp.

Verified: full library + all 6 test binaries (25 tests total) build and
pass against the reconciled tree.
PR 5G-MAG#56 landed this morning as a squash commit, superseding the hand-built
integration base this branch was previously reconciled against. Re-merged
against the real development tip:

- Transmitter.cpp: one conflict, same shape as before -- kept this branch's
  dual-send feature (plain + tunnelled copy, needed for N3mb GTP-U per
  TS 23.247) rebuilt on 5G-MAG#56's shared_ptr-owned buffer, since dual-send was
  never part of 5G-MAG#56 itself and 5G-MAG#56's actual merged content confirms that.
- tests/test_end_to_end.cpp: 12 conflicts, all the same underlying change --
  the real 5G-MAG#56 merge added a std::mutex protecting TunnelBridgeStats from a
  genuine data race between the tunnel-bridge thread and the test's main
  thread, which this branch's copy of the test lacked. Took development's
  thread-safe version throughout.
- tests/tmp/e2e_payload.bin deleted, matching development: the current test
  generates its payload in-code and no longer reads this fixture.

Verified: full library + all 6 test binaries (25 tests) build and pass.
…livery method

Checked directly against TS 26.346 v18.2.0 cl.7.2.2/7.2.12: this profile
mandates Compact No-Code (FEC Encoding ID 0) and Raptor/RFC 5053 (ID 1) --
'A UE that supports MBMS User Services shall support a decoder for the
Raptor FEC scheme' -- and does not define or reference RaptorQ/RFC 6330
(ID 6) anywhere for the download delivery method. RaptorQ isn't forbidden,
just outside today's 3GPP-defined scheme set, and this library's primary
consumers are 3GPP/5G-MAG MBMS applications.

Removed RaptorQCodec, GF256LinearSystem (RaptorQ's GF(256) linear algebra,
unneeded by Raptor's GF(2)), and their headers/tests, and stripped the
FecScheme::RaptorQ dispatch branches from AlcPacket/File/FileDeliveryTable/
EncodingSymbol, keeping every Raptor (FecScheme::Raptor) path unchanged.

The full Raptor+RaptorQ implementation is preserved on future/raptorq-support
(jordijoangimenez/rt-libflute) in case RaptorQ is wanted for a non-3GPP FLUTE
deployment, or 3GPP adopts it in a future release.

Verified: library + all 5 remaining test binaries (17 tests) build and pass.
The two RaptorQ-only test binaries (flute_raptorq_tests, and RaptorQE2ETest
within flute_raptor_e2e_tests) are gone; every remaining Raptor test is
unaffected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants