diff --git a/CMakeLists.txt b/CMakeLists.txt index 789945ae..dc7d32c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,7 @@ target_sources(flute PRIVATE src/Receiver.cpp src/Transmitter.cpp src/AlcPacket.cpp src/File.cpp src/EncodingSymbol.cpp src/FileDeliveryTable.cpp src/IpSec.cpp src/fec/GF2LinearSystem.cpp src/fec/RaptorCodec.cpp + src/fec/GF256LinearSystem.cpp src/fec/RaptorQCodec.cpp utils/base64.cpp PUBLIC include/Receiver.h include/Transmitter.h include/File.h diff --git a/examples/flute-transmitter.cpp b/examples/flute-transmitter.cpp index 31c2ac86..50b743e3 100644 --- a/examples/flute-transmitter.cpp +++ b/examples/flute-transmitter.cpp @@ -14,6 +14,7 @@ // See the License for the specific language governing permissions and limitations // under the License. // +#include #include #include @@ -66,6 +67,9 @@ static struct argp_option options[] = { // NOLINT {"new-api", 'n', nullptr, 0, "Use the new FileDescription API", 0}, {"retransmit", 'R', "COUNT", 0, "Number of times to repeatedly transmit a file, implies -n option (default: 1)", 0}, {"etags", 'e', nullptr, 0, "Enable generation of ETag values for each file, implies -n option (default: no ETags)", 0}, + {"fdt-schema", 'S', "NAME", 0, "FDT schema to emit: draft2005 (default), rfc3926, or profiled (TS 26.346 annex L.6.1)", 0}, + {"fec", 'F', "NAME", 0, "FEC scheme for content objects: compact (default), raptor, or raptorq", 0}, + {"fec-redundancy-level", 'L', "PERCENT", 0, "FEC redundancy as a percentage of a source block (default: 10), ignored for -F compact", 0}, {nullptr, 0, nullptr, 0, nullptr, 0}}; /** @@ -82,11 +86,35 @@ struct ft_arguments { unsigned short mtu = 1500; uint32_t rate_limit = 1000; uint64_t tsi = 16; + const char *fdt_schema = "draft2005"; + const char *fec_scheme = "compact"; + uint32_t fec_redundancy_level = LibFlute::kDefaultFecRedundancyLevel; size_t retransmit_count = 1; unsigned log_level = 2; /**< log level */ char **files; }; +/** Map the --fdt-schema name onto the library's namespace selector. */ +static LibFlute::FileDeliveryTable::FdtNamespace fdt_namespace_from(const char *name) { + if (name != nullptr) { + const std::string n(name); + if (n == "profiled") return LibFlute::FileDeliveryTable::FDT_NS_3GPP_CONSOLIDATED_V2; + if (n == "rfc3926") return LibFlute::FileDeliveryTable::FDT_NS_RFC3926; + } + return LibFlute::FileDeliveryTable::FDT_NS_DRAFT_2005; +} + +/** Map the --fec name onto a content FEC OTI, or nullopt for the library default. */ +static std::optional content_fec_oti_from(const char *name) { + if (name == nullptr) return std::nullopt; + const std::string n(name); + if (n != "raptor" && n != "raptorq") return std::nullopt; + LibFlute::FecOti oti{}; + oti.encoding_id = (n == "raptorq") ? LibFlute::FecScheme::RaptorQ : LibFlute::FecScheme::Raptor; + oti.max_source_block_length = 64; + return oti; +} + /** * Parses the command line options into the arguments struct. */ @@ -126,6 +154,15 @@ static auto parse_opt(int key, char *arg, struct argp_state *state) -> error_t { case 'n': arguments->new_api = true; break; + case 'S': + arguments->fdt_schema = arg; + break; + case 'F': + arguments->fec_scheme = arg; + break; + case 'L': + arguments->fec_redundancy_level = (uint32_t)std::stoul(arg); + break; case 'R': arguments->retransmit_count = static_cast(strtoul(arg, nullptr, 10)); arguments->new_api = true; @@ -191,7 +228,9 @@ static void send_with_new_api(struct ft_arguments &arguments) arguments.tsi, arguments.mtu, arguments.rate_limit, - io, std::nullopt, LibFlute::FileDeliveryTable::FDT_NS_DRAFT_2005); + io, std::nullopt, fdt_namespace_from(arguments.fdt_schema), true, std::nullopt, + content_fec_oti_from(arguments.fec_scheme), + LibFlute::Profile::Ts26517, arguments.fec_redundancy_level); // Configure IPSEC ESP, if enabled if (arguments.enable_ipsec) @@ -259,7 +298,9 @@ static void send_with_old_api(struct ft_arguments &arguments) arguments.tsi, arguments.mtu, arguments.rate_limit, - io, std::nullopt, LibFlute::FileDeliveryTable::FDT_NS_DRAFT_2005); + io, std::nullopt, fdt_namespace_from(arguments.fdt_schema), true, std::nullopt, + content_fec_oti_from(arguments.fec_scheme), + LibFlute::Profile::Ts26517, arguments.fec_redundancy_level); // Configure IPSEC ESP, if enabled if (arguments.enable_ipsec) diff --git a/include/File.h b/include/File.h index fe99333c..52076f25 100644 --- a/include/File.h +++ b/include/File.h @@ -24,6 +24,7 @@ #include "Transmitter.h" #include "fec/FecBlockCodec.h" #include "fec/RaptorCodec.h" +#include "fec/RaptorQCodec.h" namespace LibFlute { /** @@ -203,8 +204,8 @@ namespace LibFlute { void calculate_partitioning(); // have_source_data: true from the transmit-side constructors (the // file's bytes are already in _buffer, so source symbols start out - // complete and, for Raptor, the intermediate symbols get solved - // immediately); false from the receive-side constructor (empty + // complete and, for Raptor/RaptorQ, the intermediate symbols get + // solved immediately); false from the receive-side constructor (empty // buffer, everything arrives via put_symbol()). void create_blocks(bool have_source_data); @@ -222,13 +223,13 @@ namespace LibFlute { void check_source_block_completion(uint16_t source_block_number, SourceBlock& block); void check_file_completion(); - // -- Raptor support ----------------------------------------------------- + // -- Raptor/RaptorQ support ------------------------------------------- // File keeps the same SourceBlock/Symbol bookkeeping above for every // scheme (source symbols are always the file's raw bytes, chopped up - // identically -- Raptor is a systematic code); a RaptorCodec per source - // block is the only extra state needed, handling the pre-coding/LT maths - // in complete isolation from this class. See fec/RaptorCodec.h for the - // codec itself. + // identically -- Raptor/RaptorQ are systematic codes); a RaptorCodec + // per source block is the only extra state needed, handling the + // pre-coding/LT maths in complete isolation from this class. See + // fec/RaptorCodec.h for the codec itself. // // Encoder side: create_blocks() feeds all K source symbols of a block // into its codec once and keeps the resulting intermediate symbols @@ -240,7 +241,7 @@ namespace LibFlute { // of that block's source symbol slots are filled in one shot, whether // or not they'd individually arrived. bool is_raptor_family() const { - return _meta.fec_oti.encoding_id == FecScheme::Raptor; + return _meta.fec_oti.encoding_id == FecScheme::Raptor || _meta.fec_oti.encoding_id == FecScheme::RaptorQ; } void calculate_partitioning_raptor(); void setup_raptor_codec_for_block(uint16_t sbn, uint32_t k); @@ -256,7 +257,7 @@ namespace LibFlute { // sets it, and why it is not signalled. uint32_t _fec_redundancy_level = kDefaultFecRedundancyLevel; - std::map> _raptor_codecs; // one RaptorCodec per source block + std::map> _raptor_codecs; // one per source block; Raptor or RaptorQ depending on fec_oti.encoding_id std::map>> _raptor_intermediate; // encoder side only, filled once per block std::map _raptor_repair_sent; // encoder side only: how many repair ESIs already queued for this block // encoder side only: generated repair symbol bytes, cached so the diff --git a/include/Transmitter.h b/include/Transmitter.h index de6ece55..388f94a9 100644 --- a/include/Transmitter.h +++ b/include/Transmitter.h @@ -433,7 +433,7 @@ namespace LibFlute { * meaning Compact No-Code -- today's behaviour, unchanged). Only * encoding_id, max_source_block_length and max_number_of_encoding_symbols * are read from it; encoding_symbol_length is always sized to this - * Transmitter's own path MTU, and the Raptor-specific OTI fields + * Transmitter's own path MTU, and the Raptor/RaptorQ-specific OTI fields * are computed fresh per file by LibFlute::File, not taken from here. * * @throw boost::system::system_error When @p source_address is given a value and @p tunnel_endpoint has no value and the diff --git a/include/fec/FecBlockCodec.h b/include/fec/FecBlockCodec.h index d8cb672c..186a8cb2 100644 --- a/include/fec/FecBlockCodec.h +++ b/include/fec/FecBlockCodec.h @@ -19,10 +19,13 @@ namespace LibFlute { -/// Shared shape of a per-source-block FEC codec, implemented by -/// Raptor::RaptorCodec (RFC 5053). Lets File hold this behind a polymorphic -/// interface rather than a Raptor-specific one directly, so a future scheme -/// can slot in alongside it without changing File's own bookkeeping. +/// Shared shape of a per-source-block FEC codec, implemented by both +/// Raptor::RaptorCodec (RFC 5053) and RaptorQ::RaptorQCodec (RFC 6330). +/// Lets File hold one polymorphic codec per source block instead of +/// duplicating its Raptor-family handling once per scheme -- the two +/// schemes' internal maths are quite different (GF(2) vs GF(256), three- +/// vs six-element tuples, no K->K' padding step vs one), but from File's +/// point of view they're both "feed it symbols, ask if it can decode yet". class FecBlockCodec { public: virtual ~FecBlockCodec() = default; diff --git a/include/fec/GF256.h b/include/fec/GF256.h new file mode 100644 index 00000000..f3e2fd30 --- /dev/null +++ b/include/fec/GF256.h @@ -0,0 +1,55 @@ +// libflute - FLUTE/ALC library +// +// Copyright (C) 2026 5G-MAG Association (Jordi J. Gimenez ) +// +// Licensed under the License terms and conditions for use, reproduction, and +// distribution of 5G-MAG software (the “License”). You may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// https://www.5g-mag.com/reference-tools. Unless required by applicable law or +// agreed to in writing, software distributed under the License is distributed on +// an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. +// +// See the License for the specific language governing permissions and limitations +// under the License. +// +#pragma once +#include +#include "fec/RaptorQTables.h" + +// GF(256) octet arithmetic, RFC 6330 Section 5.7.2, transcribed directly +// from the RFC text. Addition/subtraction is XOR (free); multiplication and +// division go through the OCT_EXP/OCT_LOG tables in RaptorQTables.h. + +namespace LibFlute { +namespace RaptorQ { + +inline uint8_t gf_add(uint8_t u, uint8_t v) { return u ^ v; } +inline uint8_t gf_sub(uint8_t u, uint8_t v) { return u ^ v; } + +// u * v = 0 if either is 0, else OCT_EXP[OCT_LOG[u] + OCT_LOG[v]]. +// OCT_LOG entries are <= 254, so the sum is <= 508 -- within OCT_EXP's +// 510-entry range (that's exactly why the table has 510, not 255, entries). +inline uint8_t gf_mul(uint8_t u, uint8_t v) { + if (u == 0 || v == 0) return 0; + return kOctExp[kOctLog[u - 1] + kOctLog[v - 1]]; +} + +// u / v (v != 0) = 0 if u == 0, else OCT_EXP[OCT_LOG[u] - OCT_LOG[v] + 255]. +inline uint8_t gf_div(uint8_t u, uint8_t v) { + if (u == 0) return 0; + return kOctExp[kOctLog[u - 1] - kOctLog[v - 1] + 255]; +} + +// Multiplicative inverse of a non-zero octet: OCT_EXP[255 - OCT_LOG[u]]. +inline uint8_t gf_inv(uint8_t u) { + return kOctExp[255 - kOctLog[u - 1]]; +} + +// alpha^^i for 0 <= i < 256, where alpha is the octet 2. +inline uint8_t gf_alpha_pow(uint32_t i) { + return kOctExp[i]; +} + +} // namespace RaptorQ +} // namespace LibFlute diff --git a/include/fec/GF256LinearSystem.h b/include/fec/GF256LinearSystem.h new file mode 100644 index 00000000..0ec70e99 --- /dev/null +++ b/include/fec/GF256LinearSystem.h @@ -0,0 +1,90 @@ +// libflute - FLUTE/ALC library +// +// Copyright (C) 2026 5G-MAG Association (Jordi J. Gimenez ) +// +// Licensed under the License terms and conditions for use, reproduction, and +// distribution of 5G-MAG software (the “License”). You may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// https://www.5g-mag.com/reference-tools. Unless required by applicable law or +// agreed to in writing, software distributed under the License is distributed on +// an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. +// +// See the License for the specific language governing permissions and limitations +// under the License. +// +#pragma once +#include +#include +#include +#include +#include + +namespace LibFlute { +namespace RaptorQ { + +/// An incrementally-solved linear system over GF(256), the RaptorQ +/// counterpart to Raptor's GF2LinearSystem. The difference that actually +/// matters here (not just the field size) is that RaptorQ's HDPC relations +/// (RFC 6330 Section 5.3.3.3, the MT*GAMMA construction) have real, +/// non-unity octet coefficients -- unlike Raptor, where every pre-coding/LT +/// relation is a plain XOR (coefficient always 1) -- so this can't reuse +/// GF2LinearSystem's packed-bit rows; each row genuinely needs one octet +/// coefficient per column. +/// +/// Rows are stored densely (one octet per unknown). That's the right +/// trade-off for correctness-first: RaptorQ blocks are bounded by a +/// caller-supplied K cap in practice (same as this library's Raptor +/// implementation -- see RaptorCodec.h), and a dense L*L octet matrix is +/// entirely reasonable at the block sizes that cap implies. It would not be +/// reasonable at RFC 6330's full K'_max = 56403 (L^2 bytes would be +/// gigabytes); that's a scaling concern for a future sparse/inactivation +/// implementation, not a correctness one. +class GF256LinearSystem { + public: + explicit GF256LinearSystem(uint32_t num_unknowns); + + uint32_t num_unknowns() const { return _num_unknowns; } + uint32_t rank() const { return _rank; } + bool fully_determined() const { return _rank == _num_unknowns; } + size_t symbol_length() const { return _symbol_length; } + + /// Add one equation: sum over (column, coefficient) pairs in `terms` of + /// coefficient * unknown[column] == rhs. Repeated columns accumulate + /// (their coefficients add, i.e. XOR) rather than overwriting, matching + /// the RFC's repeated "D[b] = D[b] + C[i]" construction. Pass an empty + /// `rhs` for an implicit all-zero right-hand side. + /// + /// Returns true if this equation increased the rank. + bool add_equation(const std::vector>& terms, std::vector rhs); + + /// Once fully_determined(), returns the solved value for unknown `index`. + const std::vector& solved_value(uint32_t index) const; + + private: + struct Row { + std::vector coeffs; // length num_unknowns; pivot column's entry is exactly 1 + std::optional> rhs; // nullopt == implicit all-zero + uint32_t pivot = 0; + }; + + void ensure_symbol_length(size_t len); + const std::vector& materialize(const std::optional>& rhs) const; + static void scale_and_add_row(std::vector& dst, const std::vector& src, uint8_t factor); + void scale_and_add_rhs(std::optional>& dst, const std::optional>& src, uint8_t factor); + void scale_rhs_in_place(std::optional>& rhs, uint8_t factor); + int first_nonzero(const std::vector& coeffs) const; + + uint32_t _num_unknowns; + uint32_t _rank = 0; + size_t _symbol_length = 0; + bool _symbol_length_known = false; + + std::vector _row_of_pivot; // size num_unknowns, -1 if none + std::vector _rows; + + mutable std::vector _zero_scratch; +}; + +} // namespace RaptorQ +} // namespace LibFlute diff --git a/include/fec/RaptorQCodec.h b/include/fec/RaptorQCodec.h new file mode 100644 index 00000000..69ba1a74 --- /dev/null +++ b/include/fec/RaptorQCodec.h @@ -0,0 +1,79 @@ +// libflute - FLUTE/ALC library +// +// Copyright (C) 2026 5G-MAG Association (Jordi J. Gimenez ) +// +// Licensed under the License terms and conditions for use, reproduction, and +// distribution of 5G-MAG software (the “License”). You may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// https://www.5g-mag.com/reference-tools. Unless required by applicable law or +// agreed to in writing, software distributed under the License is distributed on +// an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. +// +// See the License for the specific language governing permissions and limitations +// under the License. +// +#pragma once +#include +#include +#include "fec/RaptorQMath.h" +#include "fec/GF256LinearSystem.h" +#include "fec/FecBlockCodec.h" + +namespace LibFlute { +namespace RaptorQ { + +/// A complete RFC 6330 RaptorQ codec for a single source block of K source +/// symbols. Structured to mirror LibFlute::Raptor::RaptorCodec's public +/// shape (compute_intermediate_symbols / generate_encoding_symbol on the +/// encoder side, add_received_symbol / can_decode / decode_source_symbols +/// on the decoder side) even though the underlying maths are quite +/// different -- GF(256) throughout rather than GF(2), a six-element tuple +/// (LT part + PI part) instead of Raptor's three, and a K -> K' padding step +/// this scheme has and Raptor doesn't. +/// +/// Encoding Symbol IDs (ESI, this class's public numbering, 0-based, same +/// convention as Raptor) map to the RFC's Internal Symbol IDs (ISI, what +/// the tuple/encoding generators actually take) as: ISI == ESI for source +/// ESIs (< K), and ISI == ESI + (K' - K) for repair ESIs (>= K) -- see RFC +/// 6330 Section 5.3.2's closing paragraph. The K' - K padding symbols +/// (ISI in [K, K')) are always zero and are seeded into the linear system +/// up front, for free, in the constructor -- exactly like the S+H +/// pre-coding rows -- so decoding only ever needs K genuinely-received +/// symbols' worth of new information, not K'. +class RaptorQCodec : public FecBlockCodec { + public: + /// @param K number of source symbols in the block (RFC 6330 supports K + /// up to K'_max = 56403; this K may be smaller, it's padded up to the + /// nearest supported K' internally). + explicit RaptorQCodec(uint32_t K); + + uint32_t K() const { return _K; } + uint32_t Kprime() const { return _params.Kprime; } + uint32_t L() const { return _params.L; } + + std::vector> compute_intermediate_symbols( + const std::vector>& source_symbols) override; + + std::vector generate_encoding_symbol( + uint32_t esi, const std::vector>& intermediate_symbols) const override; + + bool add_received_symbol(uint32_t esi, const std::vector& data) override; + + bool can_decode() const override { return _system.fully_determined(); } + uint32_t symbols_needed() const override { return _system.num_unknowns() - _system.rank(); } + + std::vector> decode_source_symbols() override; + + private: + uint32_t esi_to_isi(uint32_t esi) const { + return (esi < _K) ? esi : esi + (_params.Kprime - _K); + } + + uint32_t _K; + PreCodeParams _params; + GF256LinearSystem _system; +}; + +} // namespace RaptorQ +} // namespace LibFlute diff --git a/include/fec/RaptorQMath.h b/include/fec/RaptorQMath.h new file mode 100644 index 00000000..e92dafd1 --- /dev/null +++ b/include/fec/RaptorQMath.h @@ -0,0 +1,175 @@ +// libflute - FLUTE/ALC library +// +// Copyright (C) 2026 5G-MAG Association (Jordi J. Gimenez ) +// +// Licensed under the License terms and conditions for use, reproduction, and +// distribution of 5G-MAG software (the “License”). You may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// https://www.5g-mag.com/reference-tools. Unless required by applicable law or +// agreed to in writing, software distributed under the License is distributed on +// an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. +// +// See the License for the specific language governing permissions and limitations +// under the License. +// +#pragma once +#include +#include +#include +#include +#include +#include "fec/RaptorQTables.h" +#include "fec/RaptorTables.h" // shares Raptor's kRandTableV0/V1 -- see RaptorQTables.h + +// The scalar building blocks of RFC 6330 ("RaptorQ Forward Error Correction +// Scheme for Object Delivery"): the 4-way Rand(), the degree generator, the +// six-element tuple generator, and source block parameter derivation. +// Transcribed directly from the RFC text, section numbers cited throughout. + +namespace LibFlute { +namespace RaptorQ { + +/// RFC 6330 Section 5.3.5.1: Rand[y, i, m] using all four V-tables. +inline uint32_t Rand(uint32_t y, uint32_t i, uint32_t m) { + uint32_t x0 = (y + i) % 256; + uint32_t x1 = (y / 256 + i) % 256; + uint32_t x2 = (y / 65536 + i) % 256; + uint32_t x3 = (y / 16777216 + i) % 256; + uint32_t v = LibFlute::Raptor::kRandTableV0[x0] ^ LibFlute::Raptor::kRandTableV1[x1] ^ + kRandTableV2[x2] ^ kRandTableV3[x3]; + return v % m; +} + +inline uint32_t smallest_prime_at_least(uint32_t n) { + if (n <= 2) return 2; + auto is_prime = [](uint32_t v) { + if (v < 2) return false; + if (v % 2 == 0) return v == 2; + for (uint32_t d = 3; (uint64_t)d * d <= v; d += 2) { + if (v % d == 0) return false; + } + return true; + }; + uint32_t v = (n % 2 == 0) ? n + 1 : n; + while (!is_prime(v)) v += 2; + return v; +} + +/// Parameters derived from K' (RFC 6330 Section 5.3.3.3), via a lookup into +/// Table 2 (Section 5.6) for J/S/H/W, then straightforward arithmetic for +/// the rest. +struct PreCodeParams { + uint32_t Kprime = 0; + uint32_t J = 0, S = 0, H = 0, W = 0; + uint32_t L = 0, P = 0, P1 = 0, U = 0, B = 0; +}; + +/// Looks up the smallest supported K' >= k (RFC 6330 Section 5.3.2: "K' MUST +/// be selected as the smallest value of K' from the table of Section 5.6 +/// that is greater than or equal to K"). +inline uint32_t smallest_supported_Kprime(uint32_t k) { + size_t n = sizeof(kTable2_Kprime) / sizeof(kTable2_Kprime[0]); + auto it = std::lower_bound(kTable2_Kprime, kTable2_Kprime + n, k); + if (it == kTable2_Kprime + n) { + throw std::invalid_argument("RaptorQ: K exceeds the largest supported K' (56403)"); + } + return *it; +} + +inline PreCodeParams derive_precode_params(uint32_t Kprime) { + size_t n = sizeof(kTable2_Kprime) / sizeof(kTable2_Kprime[0]); + auto it = std::lower_bound(kTable2_Kprime, kTable2_Kprime + n, Kprime); + if (it == kTable2_Kprime + n || *it != Kprime) { + throw std::invalid_argument("RaptorQ: K' is not one of Table 2's supported values -- " + "call smallest_supported_Kprime(K) first"); + } + size_t row = it - kTable2_Kprime; + + PreCodeParams p; + p.Kprime = Kprime; + p.J = kTable2_J[row]; + p.S = kTable2_S[row]; + p.H = kTable2_H[row]; + p.W = kTable2_W[row]; + p.L = Kprime + p.S + p.H; + p.P = p.L - p.W; + p.P1 = smallest_prime_at_least(p.P); + p.U = p.P - p.H; + p.B = p.W - p.S; + return p; +} + +/// RFC 6330 Table 1 / Section 5.3.5.2: degree generator. v is drawn from +/// [0, 2^20); W bounds the maximum usable degree (min(d, W-2)). +inline uint32_t Deg(uint32_t v, uint32_t W) { + uint32_t d = 0; + for (uint32_t j = 1; j < 31; j++) { + if (v < kDegreeTableF[j]) { d = j; break; } + } + return std::min(d, W - 2); +} + +/// One source/repair tuple, as produced by the Tuple[] generator. +struct Tuple { + uint32_t d, a, b; // LT part: degree, step, start column (in [0, W)) + uint32_t d1, a1, b1; // PI part: degree, step, start column (in [0, P)) +}; + +/// RFC 6330 Section 5.3.5.4. Tuple[K', X] -- note the RFC's own text in +/// Section 5.3.3.2 writes this as "Tuple[K, X]" (unprimed K), which is +/// inconsistent with the generator's own formal signature "Tuple[K', X]" +/// (Section 5.3.5.4's own heading and parameter list) and with how Section +/// 5.3.3.4.1 invokes it. This implementation follows the generator's formal +/// definition and always passes K' (the extended/padded source symbol +/// count), matching every other use of Tuple[] in the RFC. +inline Tuple tuple_generator(uint32_t X, const PreCodeParams& p) { + uint64_t A = 53591ULL + (uint64_t)p.J * 997ULL; + if (A % 2 == 0) A += 1; + uint64_t B = 10267ULL * (p.J + 1ULL); + uint64_t y = (B + (uint64_t)X * A) % (1ULL << 32); + uint32_t v = Rand((uint32_t)y, 0, 1u << 20); + + Tuple t; + t.d = Deg(v, p.W); + t.a = 1 + Rand((uint32_t)y, 1, p.W - 1); + t.b = Rand((uint32_t)y, 2, p.W); + t.d1 = (t.d < 4) ? (2 + Rand(X, 3, 2)) : 2; + t.a1 = 1 + Rand(X, 4, p.P1 - 1); + t.b1 = Rand(X, 5, p.P1); + return t; +} + +/// The column-index walk implicit in the Encoding Symbol Generator Enc[] +/// (RFC 6330 Section 5.3.5.3): which of the L intermediate symbols a given +/// tuple XORs together. The LT part touches columns in [0, W); the PI part +/// touches columns [W, W+P) (skipping the "b1 >= P" gap exactly as the RFC's +/// pseudocode does). Kept separate from actually reading/summing symbol data +/// so it can be reused for both encoding and for building the matrix rows +/// used by the linear system. +inline std::vector encoding_indices(uint32_t X, const PreCodeParams& p) { + Tuple t = tuple_generator(X, p); + std::vector indices; + indices.reserve(t.d + t.d1); + + uint32_t b = t.b; + indices.push_back(b); + for (uint32_t j = 1; j < t.d; j++) { + b = (b + t.a) % p.W; + indices.push_back(b); + } + + uint32_t b1 = t.b1; + while (b1 >= p.P) b1 = (b1 + t.a1) % p.P1; + indices.push_back(p.W + b1); + for (uint32_t j = 1; j < t.d1; j++) { + b1 = (b1 + t.a1) % p.P1; + while (b1 >= p.P) b1 = (b1 + t.a1) % p.P1; + indices.push_back(p.W + b1); + } + + return indices; +} + +} // namespace RaptorQ +} // namespace LibFlute diff --git a/include/fec/RaptorQTables.h b/include/fec/RaptorQTables.h new file mode 100644 index 00000000..74d47434 --- /dev/null +++ b/include/fec/RaptorQTables.h @@ -0,0 +1,417 @@ +// libflute - FLUTE/ALC library +// +// Copyright (C) 2026 5G-MAG Association (Jordi J. Gimenez ) +// +// Licensed under the License terms and conditions for use, reproduction, and +// distribution of 5G-MAG software (the “License”). You may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// https://www.5g-mag.com/reference-tools. Unless required by applicable law or +// agreed to in writing, software distributed under the License is distributed on +// an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. +// +// See the License for the specific language governing permissions and limitations +// under the License. +// +#pragma once +#include +#include "fec/RaptorTables.h" + +// Constant tables mandated by IETF RFC 6330 ("RaptorQ Forward Error +// Correction Scheme for Object Delivery"), the FEC scheme referenced by +// 3GPP TS 26.346 as an alternative to Raptor (FEC Encoding ID 6). All +// values below were transcribed by directly reading the RFC's own published +// text (https://www.rfc-editor.org/rfc/rfc6330.txt) -- not summarized, +// character for character -- and cross-checked for internal consistency: +// +// - kRandTableV2 / kRandTableV3 (RFC 6330 Section 5.5.3/5.5.4): two more +// 256-entry uint32 tables extending the Rand[] function to a 4-way XOR +// (Section 5.3.5.1). V0 and V1 are the *same* tables RFC 5053's Raptor +// uses (confirmed: their values match this implementation's +// Raptor::kRandTableV0/V1 exactly), so they aren't duplicated here. +// - kDegreeTableF (RFC 6330 Table 1, Section 5.3.5.2): the 31-entry +// cumulative threshold table backing the degree generator Deg[v]. +// - kTable2_Kprime/J/S/H/W (RFC 6330 Table 2, Section 5.6): 477 rows, one +// per *supported* K' value (not one per integer -- unlike Raptor's J(K) +// table, most integers aren't valid K' and must be rounded up to the +// next supported one). Parallel arrays indexed together, K' ascending. +// - kOctExp / kOctLog (RFC 6330 Section 5.7.3/5.7.4): the GF(256) +// exponential/logarithm tables (510 and 255 entries respectively). +// Sanity-checked against the field's own structure before use: OCT_EXP +// repeats with period 255 as it must, and OCT_EXP[8] == 29, matching the +// standard GF(256) reduction polynomial x^8+x^4+x^3+x^2+1 (0x11D). +// +// All algorithmic logic that uses these tables (Rand(), the degree +// distribution, the tuple generator, octet/matrix arithmetic, the LDPC/HDPC +// pre-coding construction) is implemented independently in RaptorQCodec.cpp, +// transcribed from the RFC text directly, section-cited throughout. + +namespace LibFlute { +namespace RaptorQ { + +static const uint32_t kRandTableV2[256] = { + 1629829892, 282540176, 2794583710, 496504798, 2990494426, 3070701851, 2575963183, 4094823972, + 2775723650, 4079480416, 176028725, 2246241423, 3732217647, 2196843075, 1306949278, 4170992780, + 4039345809, 3209664269, 3387499533, 293063229, 3660290503, 2648440860, 2531406539, 3537879412, + 773374739, 4184691853, 1804207821, 3347126643, 3479377103, 3970515774, 1891731298, 2368003842, + 3537588307, 2969158410, 4230745262, 831906319, 2935838131, 264029468, 120852739, 3200326460, + 355445271, 2296305141, 1566296040, 1760127056, 20073893, 3427103620, 2866979760, 2359075957, + 2025314291, 1725696734, 3346087406, 2690756527, 99815156, 4248519977, 2253762642, 3274144518, + 598024568, 3299672435, 556579346, 4121041856, 2896948975, 3620123492, 918453629, 3249461198, + 2231414958, 3803272287, 3657597946, 2588911389, 242262274, 1725007475, 2026427718, 46776484, + 2873281403, 2919275846, 3177933051, 1918859160, 2517854537, 1857818511, 3234262050, 479353687, + 200201308, 2801945841, 1621715769, 483977159, 423502325, 3689396064, 1850168397, 3359959416, + 3459831930, 841488699, 3570506095, 930267420, 1564520841, 2505122797, 593824107, 1116572080, + 819179184, 3139123629, 1414339336, 1076360795, 512403845, 177759256, 1701060666, 2239736419, + 515179302, 2935012727, 3821357612, 1376520851, 2700745271, 966853647, 1041862223, 715860553, + 171592961, 1607044257, 1227236688, 3647136358, 1417559141, 4087067551, 2241705880, 4194136288, + 1439041934, 20464430, 119668151, 2021257232, 2551262694, 1381539058, 4082839035, 498179069, + 311508499, 3580908637, 2889149671, 142719814, 1232184754, 3356662582, 2973775623, 1469897084, + 1728205304, 1415793613, 50111003, 3133413359, 4074115275, 2710540611, 2700083070, 2457757663, + 2612845330, 3775943755, 2469309260, 2560142753, 3020996369, 1691667711, 4219602776, 1687672168, + 1017921622, 2307642321, 368711460, 3282925988, 213208029, 4150757489, 3443211944, 2846101972, + 4106826684, 4272438675, 2199416468, 3710621281, 497564971, 285138276, 765042313, 916220877, + 3402623607, 2768784621, 1722849097, 3386397442, 487920061, 3569027007, 3424544196, 217781973, + 2356938519, 3252429414, 145109750, 2692588106, 2454747135, 1299493354, 4120241887, 2088917094, + 932304329, 1442609203, 952586974, 3509186750, 753369054, 854421006, 1954046388, 2708927882, + 4047539230, 3048925996, 1667505809, 805166441, 1182069088, 4265546268, 4215029527, 3374748959, + 373532666, 2454243090, 2371530493, 3651087521, 2619878153, 1651809518, 1553646893, 1227452842, + 703887512, 3696674163, 2552507603, 2635912901, 895130484, 3287782244, 3098973502, 990078774, + 3780326506, 2290845203, 41729428, 1949580860, 2283959805, 1036946170, 1694887523, 4880696, + 466000198, 2765355283, 3318686998, 1266458025, 3919578154, 3545413527, 2627009988, 3744680394, + 1696890173, 3250684705, 4142417708, 915739411, 3308488877, 1289361460, 2942552331, 1169105979, + 3342228712, 698560958, 1356041230, 2401944293, 107705232, 3701895363, 903928723, 3646581385, + 844950914, 1944371367, 3863894844, 2946773319, 1972431613, 1706989237, 29917467, 3497665928, +}; + +static const uint32_t kRandTableV3[256] = { + 1191369816, 744902811, 2539772235, 3213192037, 3286061266, 1200571165, 2463281260, 754888894, + 714651270, 1968220972, 3628497775, 1277626456, 1493398934, 364289757, 2055487592, 3913468088, + 2930259465, 902504567, 3967050355, 2056499403, 692132390, 186386657, 832834706, 859795816, + 1283120926, 2253183716, 3003475205, 1755803552, 2239315142, 4271056352, 2184848469, 769228092, + 1249230754, 1193269205, 2660094102, 642979613, 1687087994, 2726106182, 446402913, 4122186606, + 3771347282, 37667136, 192775425, 3578702187, 1952659096, 3989584400, 3069013882, 2900516158, + 4045316336, 3057163251, 1702104819, 4116613420, 3575472384, 2674023117, 1409126723, 3215095429, + 1430726429, 2544497368, 1029565676, 1855801827, 4262184627, 1854326881, 2906728593, 3277836557, + 2787697002, 2787333385, 3105430738, 2477073192, 748038573, 1088396515, 1611204853, 201964005, + 3745818380, 3654683549, 3816120877, 3915783622, 2563198722, 1181149055, 33158084, 3723047845, + 3790270906, 3832415204, 2959617497, 372900708, 1286738499, 1932439099, 3677748309, 2454711182, + 2757856469, 2134027055, 2780052465, 3190347618, 3758510138, 3626329451, 1120743107, 1623585693, + 1389834102, 2719230375, 3038609003, 462617590, 260254189, 3706349764, 2556762744, 2874272296, + 2502399286, 4216263978, 2683431180, 2168560535, 3561507175, 668095726, 680412330, 3726693946, + 4180630637, 3335170953, 942140968, 2711851085, 2059233412, 4265696278, 3204373534, 232855056, + 881788313, 2258252172, 2043595984, 3758795150, 3615341325, 2138837681, 1351208537, 2923692473, + 3402482785, 2105383425, 2346772751, 499245323, 3417846006, 2366116814, 2543090583, 1828551634, + 3148696244, 3853884867, 1364737681, 2200687771, 2689775688, 232720625, 4071657318, 2671968983, + 3531415031, 1212852141, 867923311, 3740109711, 1923146533, 3237071777, 3100729255, 3247856816, + 906742566, 4047640575, 4007211572, 3495700105, 1171285262, 2835682655, 1634301229, 3115169925, + 2289874706, 2252450179, 944880097, 371933491, 1649074501, 2208617414, 2524305981, 2496569844, + 2667037160, 1257550794, 3399219045, 3194894295, 1643249887, 342911473, 891025733, 3146861835, + 3789181526, 938847812, 1854580183, 2112653794, 2960702988, 1238603378, 2205280635, 1666784014, + 2520274614, 3355493726, 2310872278, 3153920489, 2745882591, 1200203158, 3033612415, 2311650167, + 1048129133, 4206710184, 4209176741, 2640950279, 2096382177, 4116899089, 3631017851, 4104488173, + 1857650503, 3801102932, 445806934, 3055654640, 897898279, 3234007399, 1325494930, 2982247189, + 1619020475, 2720040856, 885096170, 3485255499, 2983202469, 3891011124, 546522756, 1524439205, + 2644317889, 2170076800, 2969618716, 961183518, 1081831074, 1037015347, 3289016286, 2331748669, + 620887395, 303042654, 3990027945, 1562756376, 3413341792, 2059647769, 2823844432, 674595301, + 2457639984, 4076754716, 2447737904, 1583323324, 625627134, 3076006391, 345777990, 1684954145, + 879227329, 3436182180, 1522273219, 3802543817, 1456017040, 1897819847, 2970081129, 1382576028, + 3820044861, 1044428167, 612252599, 3340478395, 2150613904, 3397625662, 3573635640, 3432275192, +}; + +static const uint32_t kDegreeTableF[31] = { + 0, 5243, 529531, 704294, 791675, 844104, 879057, 904023, 922747, 937311, + 948962, 958494, 966438, 973160, 978921, 983914, 988283, 992138, 995565, 998631, + 1001391, 1003887, 1006157, 1008229, 1010129, 1011876, 1013490, 1014983, 1016370, 1017662, + 1048576, +}; + +// Table 2 (RFC 6330 Section 5.6): 477 rows, K' ascending. +static const uint32_t kTable2_Kprime[477] = { + 10, 12, 18, 20, 26, 30, 32, 36, 42, 46, 48, 49, + 55, 60, 62, 69, 75, 84, 88, 91, 95, 97, 101, 114, + 119, 125, 127, 138, 140, 149, 153, 160, 166, 168, 179, 181, + 185, 187, 200, 213, 217, 225, 236, 242, 248, 257, 263, 269, + 280, 295, 301, 305, 324, 337, 341, 347, 355, 362, 368, 372, + 380, 385, 393, 405, 418, 428, 434, 447, 453, 466, 478, 486, + 491, 497, 511, 526, 532, 542, 549, 557, 563, 573, 580, 588, + 594, 600, 606, 619, 633, 640, 648, 666, 675, 685, 693, 703, + 718, 728, 736, 747, 759, 778, 792, 802, 811, 821, 835, 845, + 860, 870, 891, 903, 913, 926, 938, 950, 963, 977, 989, 1002, + 1020, 1032, 1050, 1074, 1085, 1099, 1111, 1136, 1152, 1169, 1183, 1205, + 1220, 1236, 1255, 1269, 1285, 1306, 1347, 1361, 1389, 1404, 1420, 1436, + 1461, 1477, 1502, 1522, 1539, 1561, 1579, 1600, 1616, 1649, 1673, 1698, + 1716, 1734, 1759, 1777, 1800, 1824, 1844, 1863, 1887, 1906, 1926, 1954, + 1979, 2005, 2040, 2070, 2103, 2125, 2152, 2195, 2217, 2247, 2278, 2315, + 2339, 2367, 2392, 2416, 2447, 2473, 2502, 2528, 2565, 2601, 2640, 2668, + 2701, 2737, 2772, 2802, 2831, 2875, 2906, 2938, 2979, 3015, 3056, 3101, + 3151, 3186, 3224, 3265, 3299, 3344, 3387, 3423, 3466, 3502, 3539, 3579, + 3616, 3658, 3697, 3751, 3792, 3840, 3883, 3924, 3970, 4015, 4069, 4112, + 4165, 4207, 4252, 4318, 4365, 4418, 4468, 4513, 4567, 4626, 4681, 4731, + 4780, 4838, 4901, 4954, 5008, 5063, 5116, 5172, 5225, 5279, 5334, 5391, + 5449, 5506, 5566, 5637, 5694, 5763, 5823, 5896, 5975, 6039, 6102, 6169, + 6233, 6296, 6363, 6427, 6518, 6589, 6655, 6730, 6799, 6878, 6956, 7033, + 7108, 7185, 7281, 7360, 7445, 7520, 7596, 7675, 7770, 7855, 7935, 8030, + 8111, 8194, 8290, 8377, 8474, 8559, 8654, 8744, 8837, 8928, 9019, 9111, + 9206, 9303, 9400, 9497, 9601, 9708, 9813, 9916, 10017, 10120, 10241, 10351, + 10458, 10567, 10676, 10787, 10899, 11015, 11130, 11245, 11358, 11475, 11590, 11711, + 11829, 11956, 12087, 12208, 12333, 12460, 12593, 12726, 12857, 13002, 13143, 13284, + 13417, 13558, 13695, 13833, 13974, 14115, 14272, 14415, 14560, 14713, 14862, 15011, + 15170, 15325, 15496, 15651, 15808, 15977, 16161, 16336, 16505, 16674, 16851, 17024, + 17195, 17376, 17559, 17742, 17929, 18116, 18309, 18503, 18694, 18909, 19126, 19325, + 19539, 19740, 19939, 20152, 20355, 20564, 20778, 20988, 21199, 21412, 21629, 21852, + 22073, 22301, 22536, 22779, 23010, 23252, 23491, 23730, 23971, 24215, 24476, 24721, + 24976, 25230, 25493, 25756, 26022, 26291, 26566, 26838, 27111, 27392, 27682, 27959, + 28248, 28548, 28845, 29138, 29434, 29731, 30037, 30346, 30654, 30974, 31285, 31605, + 31948, 32272, 32601, 32932, 33282, 33623, 33961, 34302, 34654, 35031, 35395, 35750, + 36112, 36479, 36849, 37227, 37606, 37992, 38385, 38787, 39176, 39576, 39980, 40398, + 40816, 41226, 41641, 42067, 42490, 42916, 43388, 43840, 44279, 44729, 45183, 45638, + 46104, 46574, 47047, 47523, 48007, 48489, 48976, 49470, 49978, 50511, 51017, 51530, + 52062, 52586, 53114, 53650, 54188, 54735, 55289, 55843, 56403, +}; + +static const uint32_t kTable2_J[477] = { + 254, 630, 682, 293, 80, 566, 860, 267, 822, 506, 589, 87, + 520, 159, 235, 157, 502, 334, 583, 66, 352, 365, 562, 5, + 603, 721, 28, 660, 829, 900, 930, 814, 661, 693, 780, 605, + 551, 777, 491, 396, 764, 843, 646, 557, 608, 265, 505, 722, + 263, 999, 874, 160, 575, 210, 513, 503, 558, 932, 404, 520, + 846, 485, 728, 554, 471, 641, 732, 193, 934, 864, 790, 912, + 617, 587, 800, 923, 998, 92, 497, 559, 667, 912, 262, 152, + 526, 268, 212, 45, 898, 527, 558, 460, 5, 895, 996, 282, + 513, 865, 870, 239, 452, 862, 852, 643, 543, 447, 321, 287, + 12, 251, 30, 621, 555, 127, 400, 91, 916, 935, 691, 299, + 282, 824, 536, 596, 28, 947, 162, 536, 1000, 251, 673, 559, + 923, 81, 478, 198, 137, 75, 29, 231, 532, 58, 60, 964, + 624, 502, 636, 986, 950, 735, 866, 203, 83, 14, 522, 226, + 282, 88, 636, 860, 324, 424, 999, 682, 814, 979, 538, 278, + 580, 773, 911, 506, 628, 282, 309, 858, 442, 654, 82, 428, + 442, 283, 538, 189, 438, 912, 1, 167, 272, 209, 927, 386, + 653, 669, 431, 793, 588, 777, 939, 864, 627, 265, 976, 988, + 507, 640, 15, 667, 24, 877, 240, 720, 93, 919, 635, 174, + 647, 820, 56, 485, 210, 124, 546, 954, 262, 927, 957, 726, + 583, 782, 37, 758, 777, 104, 476, 113, 313, 102, 501, 332, + 786, 99, 658, 794, 37, 471, 94, 873, 918, 945, 211, 341, + 11, 578, 494, 694, 252, 451, 83, 689, 488, 214, 17, 469, + 263, 309, 984, 123, 360, 863, 122, 522, 539, 181, 64, 387, + 967, 843, 999, 76, 142, 599, 576, 176, 392, 332, 291, 913, + 608, 212, 696, 931, 326, 228, 706, 144, 83, 743, 187, 654, + 359, 493, 369, 981, 276, 647, 389, 80, 396, 580, 873, 15, + 976, 584, 267, 876, 642, 794, 78, 736, 882, 251, 434, 204, + 256, 106, 375, 148, 496, 88, 826, 71, 925, 760, 130, 641, + 400, 480, 76, 665, 910, 467, 964, 625, 362, 759, 728, 343, + 113, 137, 308, 800, 177, 961, 958, 72, 732, 145, 577, 305, + 50, 351, 175, 727, 902, 409, 776, 586, 451, 287, 246, 222, + 563, 839, 897, 409, 618, 439, 95, 448, 133, 938, 423, 90, + 640, 922, 250, 367, 447, 559, 121, 623, 450, 253, 106, 863, + 148, 427, 138, 794, 247, 562, 53, 135, 21, 201, 169, 70, + 386, 226, 3, 769, 590, 672, 713, 967, 368, 348, 119, 503, + 181, 394, 189, 210, 62, 273, 554, 936, 483, 397, 241, 500, + 12, 958, 524, 8, 100, 339, 804, 510, 18, 412, 394, 830, + 535, 199, 27, 298, 368, 755, 379, 73, 387, 457, 761, 855, + 370, 261, 299, 920, 269, 862, 349, 103, 115, 93, 982, 432, + 340, 173, 421, 330, 624, 233, 362, 963, 471, +}; + +static const uint32_t kTable2_S[477] = { + 7, 7, 11, 11, 11, 11, 11, 11, 11, 13, 13, 13, + 13, 13, 13, 13, 17, 17, 17, 17, 17, 17, 17, 19, + 19, 19, 19, 19, 19, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 31, 31, 31, 31, 31, 31, 31, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 43, 43, 43, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 59, 59, 59, 59, 59, + 59, 59, 59, 59, 59, 59, 59, 59, 61, 61, 61, 61, + 61, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 71, + 71, 71, 71, 71, 71, 73, 73, 73, 73, 73, 79, 79, + 79, 79, 79, 79, 79, 79, 79, 83, 83, 83, 83, 83, + 83, 83, 89, 89, 89, 89, 89, 89, 89, 89, 97, 97, + 97, 97, 97, 97, 97, 97, 97, 97, 97, 101, 101, 101, + 101, 101, 101, 103, 103, 107, 107, 107, 107, 109, 109, 113, + 113, 113, 113, 113, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 131, 131, 131, 131, 137, + 137, 137, 137, 137, 137, 139, 139, 149, 149, 149, 149, 149, + 149, 149, 149, 149, 151, 151, 157, 157, 157, 157, 157, 157, + 163, 163, 163, 163, 163, 167, 167, 167, 173, 173, 173, 173, + 179, 179, 179, 179, 179, 181, 181, 191, 191, 191, 191, 191, + 191, 191, 193, 197, 197, 197, 199, 211, 211, 211, 211, 211, + 211, 211, 211, 223, 223, 223, 223, 223, 223, 223, 223, 227, + 227, 229, 233, 233, 239, 239, 239, 239, 241, 251, 251, 251, + 251, 251, 257, 257, 257, 257, 263, 263, 269, 269, 269, 269, + 271, 277, 277, 277, 281, 281, 293, 293, 293, 293, 293, 307, + 307, 307, 307, 307, 307, 311, 311, 313, 317, 317, 331, 331, + 331, 331, 331, 337, 337, 337, 347, 347, 347, 349, 353, 353, + 359, 359, 367, 367, 367, 373, 373, 379, 379, 383, 389, 389, + 397, 397, 401, 401, 409, 409, 419, 419, 419, 419, 431, 431, + 431, 433, 439, 439, 443, 449, 457, 457, 457, 461, 467, 467, + 479, 479, 479, 487, 487, 491, 499, 499, 503, 509, 521, 521, + 521, 523, 541, 541, 541, 541, 547, 547, 557, 557, 563, 569, + 571, 577, 587, 587, 593, 593, 599, 607, 607, 613, 619, 631, + 631, 641, 641, 643, 653, 653, 659, 673, 673, 677, 683, 691, + 701, 701, 709, 709, 719, 727, 727, 733, 739, 751, 751, 757, + 769, 769, 787, 787, 787, 797, 809, 809, 821, 821, 827, 839, + 853, 853, 857, 863, 877, 877, 883, 907, 907, +}; + +static const uint32_t kTable2_H[477] = { + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, +}; + +static const uint32_t kTable2_W[477] = { + 17, 19, 29, 31, 37, 41, 43, 47, 53, 59, 61, 61, + 67, 71, 73, 79, 89, 97, 101, 103, 107, 109, 113, 127, + 131, 137, 139, 149, 151, 163, 167, 173, 179, 181, 191, 193, + 197, 199, 211, 223, 233, 241, 251, 257, 263, 271, 277, 283, + 293, 307, 313, 317, 337, 349, 353, 359, 367, 373, 379, 389, + 397, 401, 409, 421, 433, 443, 449, 461, 467, 479, 491, 499, + 503, 509, 523, 541, 547, 557, 563, 571, 577, 587, 593, 601, + 607, 613, 619, 631, 647, 653, 661, 683, 691, 701, 709, 719, + 733, 743, 751, 761, 773, 797, 811, 821, 829, 839, 853, 863, + 877, 887, 907, 919, 929, 941, 953, 971, 983, 997, 1009, 1021, + 1039, 1051, 1069, 1093, 1103, 1117, 1129, 1153, 1171, 1187, 1201, 1223, + 1237, 1259, 1277, 1291, 1307, 1327, 1367, 1381, 1409, 1423, 1439, 1459, + 1483, 1499, 1523, 1543, 1559, 1583, 1601, 1621, 1637, 1669, 1699, 1723, + 1741, 1759, 1783, 1801, 1823, 1847, 1867, 1889, 1913, 1931, 1951, 1979, + 2003, 2029, 2069, 2099, 2131, 2153, 2179, 2221, 2243, 2273, 2311, 2347, + 2371, 2399, 2423, 2447, 2477, 2503, 2531, 2557, 2593, 2633, 2671, 2699, + 2731, 2767, 2801, 2833, 2861, 2909, 2939, 2971, 3011, 3049, 3089, 3137, + 3187, 3221, 3259, 3299, 3347, 3391, 3433, 3469, 3511, 3547, 3583, 3623, + 3659, 3701, 3739, 3793, 3833, 3881, 3923, 3967, 4013, 4057, 4111, 4159, + 4211, 4253, 4297, 4363, 4409, 4463, 4513, 4567, 4621, 4679, 4733, 4783, + 4831, 4889, 4951, 5003, 5059, 5113, 5171, 5227, 5279, 5333, 5387, 5443, + 5507, 5563, 5623, 5693, 5749, 5821, 5881, 5953, 6037, 6101, 6163, 6229, + 6299, 6361, 6427, 6491, 6581, 6653, 6719, 6803, 6871, 6949, 7027, 7103, + 7177, 7253, 7351, 7433, 7517, 7591, 7669, 7759, 7853, 7937, 8017, 8111, + 8191, 8273, 8369, 8467, 8563, 8647, 8741, 8831, 8923, 9013, 9103, 9199, + 9293, 9391, 9491, 9587, 9697, 9803, 9907, 10009, 10111, 10223, 10343, 10453, + 10559, 10667, 10781, 10891, 11003, 11119, 11239, 11353, 11471, 11587, 11701, 11821, + 11941, 12073, 12203, 12323, 12451, 12577, 12721, 12853, 12983, 13127, 13267, 13421, + 13553, 13693, 13829, 13967, 14107, 14251, 14407, 14551, 14699, 14851, 15013, 15161, + 15319, 15473, 15643, 15803, 15959, 16127, 16319, 16493, 16661, 16831, 17011, 17183, + 17359, 17539, 17729, 17911, 18097, 18289, 18481, 18679, 18869, 19087, 19309, 19507, + 19727, 19927, 20129, 20341, 20551, 20759, 20983, 21191, 21401, 21613, 21841, 22063, + 22283, 22511, 22751, 22993, 23227, 23473, 23719, 23957, 24197, 24443, 24709, 24953, + 25219, 25471, 25733, 26003, 26267, 26539, 26821, 27091, 27367, 27653, 27953, 28229, + 28517, 28817, 29131, 29423, 29717, 30013, 30323, 30631, 30949, 31267, 31583, 31907, + 32251, 32579, 32917, 33247, 33601, 33941, 34283, 34631, 34981, 35363, 35731, 36097, + 36457, 36833, 37201, 37579, 37967, 38351, 38749, 39163, 39551, 39953, 40361, 40787, + 41213, 41621, 42043, 42467, 42899, 43331, 43801, 44257, 44701, 45161, 45613, 46073, + 46549, 47017, 47507, 47981, 48463, 48953, 49451, 49943, 50461, 50993, 51503, 52027, + 52571, 53093, 53623, 54163, 54713, 55259, 55817, 56393, 56951, +}; + +static const uint8_t kOctExp[510] = { + 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, + 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, + 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, + 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, + 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, + 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, + 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, + 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, + 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, + 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, + 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, 46, 92, + 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, + 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, + 228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, + 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, + 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, + 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, + 221, 167, 83, 166, 81, 162, 89, 178, 121, 242, 249, 239, + 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, + 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, + 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, + 173, 71, 142, 1, 2, 4, 8, 16, 32, 64, 128, 29, + 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, + 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, + 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, + 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, + 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, + 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, + 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, + 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 13, + 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, + 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, + 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, + 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, + 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198, + 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, + 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, + 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, + 56, 112, 224, 221, 167, 83, 166, 81, 162, 89, 178, 121, + 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, + 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, + 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, + 54, 108, 216, 173, 71, 142, +}; + +static const uint16_t kOctLog[255] = { + 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, + 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, + 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, + 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, + 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, + 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, + 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, + 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, + 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, + 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, + 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, + 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, + 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, + 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, + 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, + 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, + 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, + 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, + 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, + 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, + 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, + 80, 88, 175, +}; + +} // namespace RaptorQ +} // namespace LibFlute diff --git a/include/flute_types.h b/include/flute_types.h index 527c85ef..8f7f50f3 100644 --- a/include/flute_types.h +++ b/include/flute_types.h @@ -44,16 +44,13 @@ namespace LibFlute { * Encoding ID registry (http://www.iana.org/assignments/rmt-fec-parameters, * RFC 5052) and are written to the wire (FEC-OTI-FEC-Encoding-ID) as-is, so * they are set explicitly here rather than left as sequential ordinals -- - * values 2-6 (Reed-Solomon GF(2^^m), LDPC Staircase, LDPC Triangle, - * Reed-Solomon GF(2^^8), RaptorQ) are registered but not implemented by - * this library. RaptorQ in particular is deliberately left out: 3GPP - * TS 26.346 cl.7.2.2/7.2.12 mandates Raptor (RFC 5053, this library's - * primary consumer base) but does not define or reference RaptorQ for - * this delivery method -- see the future/raptorq-support branch. + * values 2-5 (Reed-Solomon GF(2^^m), LDPC Staircase, LDPC Triangle, + * Reed-Solomon GF(2^^8)) are registered but not implemented by this library. */ enum class FecScheme { CompactNoCode = 0, - Raptor = 1 + Raptor = 1, + RaptorQ = 6 }; /** @@ -146,8 +143,8 @@ namespace LibFlute { uint32_t max_number_of_encoding_symbols; /** - * Raptor scheme-specific OTI (RFC 5053 §3.2.3). Unused (left at their - * defaults) for FecScheme::CompactNoCode. + * Raptor/RaptorQ scheme-specific OTI (RFC 5053 §3.2.3, RFC 6330 §4.2). + * Unused (left at their defaults) for FecScheme::CompactNoCode. * * nof_sub_blocks is always 1 in this implementation: RFC 5052 permits * N == 1 (no further sub-block byte-interleaving within a symbol), and diff --git a/src/AlcPacket.cpp b/src/AlcPacket.cpp index 9a8127ae..c86da6f1 100644 --- a/src/AlcPacket.cpp +++ b/src/AlcPacket.cpp @@ -123,13 +123,15 @@ LibFlute::AlcPacket::AlcPacket(char* data, size_t len) if (_lct_header.ert_flag) hdr_ptr += 4; /* The FEC scheme is taken from the Codepoint and from nothing else, using the identity mapping - onto the registered FEC Encoding IDs, which is what the send side writes. + onto the registered FEC Encoding IDs, which is what the send side writes. Codepoint 6 is + RaptorQ, which this branch adds and which is outside the 3GPP referenced set. RFC 3450 clause 2.2: "The LCT header contains a Codepoint field that MAY be used to communicate to a receiver the settings for information that may vary during a session." */ switch (_lct_header.codepoint) { case 0: _fec_oti.encoding_id = FecScheme::CompactNoCode; break; case 1: _fec_oti.encoding_id = FecScheme::Raptor; break; + case 6: _fec_oti.encoding_id = FecScheme::RaptorQ; break; default: throw std::runtime_error("Unsupported FEC scheme (codepoint " + std::to_string(_lct_header.codepoint) + ")"); } @@ -206,6 +208,16 @@ LibFlute::AlcPacket::AlcPacket(char* data, size_t len) _fec_oti.nof_sub_blocks = *(uint8_t*)ext_ptr; ext_ptr += 1; _fec_oti.symbol_alignment = *(uint8_t*)ext_ptr; + } else if (_fec_oti.encoding_id == FecScheme::RaptorQ) { + // Scheme-specific OTI (RFC 6330 §3.3.3): note the + // field widths differ from Raptor's despite the + // same 4-octet total -- 8-bit Z, 16-bit N (always 1 + // here), 8-bit Al. + _fec_oti.nof_source_blocks = *(uint8_t*)ext_ptr; + ext_ptr += 1; + _fec_oti.nof_sub_blocks = ntohs(*(uint16_t*)ext_ptr); + ext_ptr += 2; + _fec_oti.symbol_alignment = *(uint8_t*)ext_ptr; } else { throw std::runtime_error("EXT_FTI parsing not implemented for this FEC scheme"); } diff --git a/src/EncodingSymbol.cpp b/src/EncodingSymbol.cpp index 578bc935..61e2d8d1 100644 --- a/src/EncodingSymbol.cpp +++ b/src/EncodingSymbol.cpp @@ -33,11 +33,12 @@ auto LibFlute::EncodingSymbol::from_payload(char* encoded_data, size_t data_len, } // The FEC Payload ID wire format (16-bit SBN + 16-bit ESI, network byte - // order) is the same for Compact No-Code (RFC 5052 §5.1) and Raptor - // (RFC 5053 §3.1): only the *meaning* of an ESI >= the source block's - // symbol count differs (repair symbol vs. undefined). + // order) is the same for Compact No-Code (RFC 5052 §5.1) and for Raptor/ + // RaptorQ (RFC 5053 §3.1, RFC 6330 §4.4.1): only the *meaning* of an ESI + // >= the source block's symbol count differs (repair symbol vs. undefined). if (fec_oti.encoding_id == FecScheme::CompactNoCode || - fec_oti.encoding_id == FecScheme::Raptor) { + fec_oti.encoding_id == FecScheme::Raptor || + fec_oti.encoding_id == FecScheme::RaptorQ) { source_block_number = ntohs(*(uint16_t*)encoded_data); encoded_data += 2; encoding_symbol_id = ntohs(*(uint16_t*)encoded_data); @@ -74,7 +75,8 @@ auto LibFlute::EncodingSymbol::to_payload(const std::vector& sym auto first_symbol = symbols.begin(); bool scheme_supported = fec_oti.encoding_id == FecScheme::CompactNoCode || - fec_oti.encoding_id == FecScheme::Raptor; + fec_oti.encoding_id == FecScheme::Raptor || + fec_oti.encoding_id == FecScheme::RaptorQ; if (scheme_supported && data_len >= 4) { *((uint16_t*)ptr) = htons(first_symbol->source_block_number()); ptr += 2; @@ -102,7 +104,7 @@ auto LibFlute::EncodingSymbol::to_payload(const std::vector& sym auto LibFlute::EncodingSymbol::decode_to(char* buffer, size_t max_length) const -> void { // An encoding symbol's on-the-wire bytes are already its final content -- - // for Raptor that's true just as much as for Compact No-Code: a + // for Raptor/RaptorQ that's true just as much as for Compact No-Code: a // repair symbol's payload is the fully-computed LT combination by the time // it reaches this class. What to *do* with a repair symbol (recognising // its ESI is >= the source block's symbol count, feeding it to the block's diff --git a/src/File.cpp b/src/File.cpp index 6589de58..f9094b33 100644 --- a/src/File.cpp +++ b/src/File.cpp @@ -71,7 +71,8 @@ File::File(const std::shared_ptr &file_description _meta = _file_description->file_entry(); if (_meta.fec_oti.encoding_id == FecScheme::CompactNoCode || - _meta.fec_oti.encoding_id == FecScheme::Raptor) { + _meta.fec_oti.encoding_id == FecScheme::Raptor || + _meta.fec_oti.encoding_id == FecScheme::RaptorQ) { _meta.fec_oti.transfer_length = length; } else { throw std::runtime_error("Unsupported FEC scheme"); @@ -121,7 +122,8 @@ File::File(uint32_t toi, _meta.fec_oti = fec_oti; if (_meta.fec_oti.encoding_id == FecScheme::CompactNoCode || - _meta.fec_oti.encoding_id == FecScheme::Raptor) { + _meta.fec_oti.encoding_id == FecScheme::Raptor || + _meta.fec_oti.encoding_id == FecScheme::RaptorQ) { _meta.fec_oti.transfer_length = length; } else { throw std::runtime_error("Unsupported FEC scheme"); @@ -180,7 +182,7 @@ auto File::put_symbol( const EncodingSymbol& symbol ) -> void return; } - // Raptor: unlike Compact No-Code, an ESI at or beyond K is + // Raptor/RaptorQ: unlike Compact No-Code, an ESI at or beyond K is // meaningful -- it's a repair symbol, not an error -- so there's no upper // bound to enforce here beyond not letting a malicious/corrupt sender grow // our per-block state unboundedly. @@ -325,7 +327,8 @@ auto File::calculate_partitioning() -> void auto File::calculate_partitioning_raptor() -> void { - // RFC 5053 §4.2 partitioning: Kt = ceil(F/T); (KL, KS, ZL, ZS) = + // RFC 5053 §4.2 / RFC 6330 §4.4.1.2 partitioning (both schemes use the + // identical Partition[] function): Kt = ceil(F/T); (KL, KS, ZL, ZS) = // Partition[Kt, Z], where Partition[I, J] = (IL, IS, JL, JS) with // IL = ceil(I/J), IS = floor(I/J), JL = I - IS*J, JS = J - JL. // @@ -333,12 +336,13 @@ auto File::calculate_partitioning_raptor() -> void // FecOti::nof_sub_blocks's comment in flute_types.h -- so the second // partition, Partition[T/Al, N], is trivial and doesn't need computing. // - // K is capped well below Raptor's hard spec limit (8192, RFC 5053 §5.7) by - // default, since the codec's linear system scales at least quadratically - // in block size (see GF2LinearSystem.h) -- a caller who actually wants + // K is capped well below either scheme's hard spec limit (Raptor: 8192, + // RFC 5053 §5.7; RaptorQ: 56403, RFC 6330 §5.6) by default, since both + // codecs' linear systems scale at least quadratically in block size (see + // GF2LinearSystem.h / GF256LinearSystem.h) -- a caller who actually wants // larger blocks can ask for them via max_source_block_length, up to the // scheme's real limit. - const uint32_t kSchemeMaxK = 8192; + const uint32_t kSchemeMaxK = (_meta.fec_oti.encoding_id == FecScheme::RaptorQ) ? 56403 : 8192; const uint32_t kDefaultK = 8192; uint32_t k_cap = _meta.fec_oti.max_source_block_length; if (k_cap == 0) k_cap = kDefaultK; @@ -369,7 +373,11 @@ auto File::calculate_partitioning_raptor() -> void auto File::setup_raptor_codec_for_block(uint16_t sbn, uint32_t k) -> void { - _raptor_codecs[sbn] = std::make_shared(k); + if (_meta.fec_oti.encoding_id == FecScheme::RaptorQ) { + _raptor_codecs[sbn] = std::make_shared(k); + } else { + _raptor_codecs[sbn] = std::make_shared(k); + } } auto File::nof_repair_symbols_for_block(uint32_t k) const -> uint32_t diff --git a/src/FileDeliveryTable.cpp b/src/FileDeliveryTable.cpp index 43abd810..e03b11fa 100644 --- a/src/FileDeliveryTable.cpp +++ b/src/FileDeliveryTable.cpp @@ -38,23 +38,45 @@ namespace { Compact No-Code has no scheme-specific OTI to carry: RFC 3695 clause 3 specifies a FEC Payload ID for it and defines no scheme-specific element, and the schema makes the attribute use="optional", so it is simply omitted for that scheme. */ -std::string encode_raptor_scheme_specific(uint16_t z, uint8_t n, uint8_t al) +/* The two schemes use the SAME four octets for DIFFERENT field widths: Z and N are the opposite + way round. Encoding one with the other's layout silently corrupts both values, so the layout is + selected from the scheme rather than shared. + + RFC 5053 clause 3.2.3, Raptor: "a 4-octet field consisting of the parameters Z (2 octets), + N (1 octet), and Al (1 octet)" + + RFC 6330 clause 3.3.3, RaptorQ, the Scheme-Specific parameter list: + "The number of source blocks (Z): 8-bit unsigned integer." + "The number of sub-blocks (N): 16-bit unsigned integer." */ +std::string encode_scheme_specific(LibFlute::FecScheme scheme, uint16_t z, uint16_t n, uint8_t al) { std::string raw; - raw.push_back(static_cast((z >> 8) & 0xFF)); - raw.push_back(static_cast(z & 0xFF)); - raw.push_back(static_cast(n)); - raw.push_back(static_cast(al)); + if (scheme == LibFlute::FecScheme::RaptorQ) { + raw.push_back(static_cast(z & 0xFF)); // Z: 1 octet + raw.push_back(static_cast((n >> 8) & 0xFF)); // N: 2 octets + raw.push_back(static_cast(n & 0xFF)); + } else { + raw.push_back(static_cast((z >> 8) & 0xFF)); // Z: 2 octets + raw.push_back(static_cast(z & 0xFF)); + raw.push_back(static_cast(n & 0xFF)); // N: 1 octet + } + raw.push_back(static_cast(al)); // Al: 1 octet, both schemes return base64_encode(raw); } // Returns false when the attribute is not a well-formed 4-octet field. -bool decode_raptor_scheme_specific(const std::string &b64, uint16_t &z, uint8_t &n, uint8_t &al) +bool decode_scheme_specific(LibFlute::FecScheme scheme, const std::string &b64, + uint16_t &z, uint16_t &n, uint8_t &al) { const auto raw = base64_decode(b64); if (raw.size() != 4) return false; - z = static_cast((static_cast(raw[0]) << 8) | static_cast(raw[1])); - n = static_cast(raw[2]); + if (scheme == LibFlute::FecScheme::RaptorQ) { + z = static_cast(raw[0]); + n = static_cast((static_cast(raw[1]) << 8) | static_cast(raw[2])); + } else { + z = static_cast((static_cast(raw[0]) << 8) | static_cast(raw[1])); + n = static_cast(raw[2]); + } al = static_cast(raw[3]); return true; } @@ -284,14 +306,14 @@ LibFlute::FileDeliveryTable::FileDeliveryTable(uint32_t instance_id, char* buffe _global_fec_oti.max_number_of_encoding_symbols = strtoul(val->Value(), nullptr, 0); } - // Raptor scheme-specific OTI, read from the one attribute the schema defines for it. A - // malformed value is ignored rather than fatal: the Common FEC OTI alone is enough to receive - // a Compact No-Code session, and refusing the whole FDT would be worse than losing one scheme's + // Scheme-specific OTI, read from the one attribute the schema defines for it. A malformed + // value is ignored rather than fatal: the Common FEC OTI alone is enough to receive a Compact + // No-Code session, and refusing the whole FDT would be worse than losing one scheme's // parameters. val = root_ns.findAttribute(fdt_instance, "FEC-OTI-Scheme-Specific-Info", fdt_ns); if (val != nullptr) { - uint16_t z = 0; uint8_t n = 0, al = 0; - if (decode_raptor_scheme_specific(val->Value(), z, n, al)) { + uint16_t z = 0, n = 0; uint8_t al = 0; + if (decode_scheme_specific(_global_fec_oti.encoding_id, val->Value(), z, n, al)) { _global_fec_oti.nof_source_blocks = z; _global_fec_oti.nof_sub_blocks = n; _global_fec_oti.symbol_alignment = al; @@ -386,8 +408,8 @@ LibFlute::FileDeliveryTable::FileDeliveryTable(uint32_t instance_id, char* buffe auto symbol_alignment = _global_fec_oti.symbol_alignment; val = file_ns.findAttribute(file, "FEC-OTI-Scheme-Specific-Info", fdt_ns); if (val != nullptr) { - uint16_t z = 0; uint8_t n = 0, al = 0; - if (decode_raptor_scheme_specific(val->Value(), z, n, al)) { + uint16_t z = 0, n = 0; uint8_t al = 0; + if (decode_scheme_specific(encoding_id, val->Value(), z, n, al)) { nof_source_blocks = z; nof_sub_blocks = n; symbol_alignment = al; @@ -636,16 +658,14 @@ auto LibFlute::FileDeliveryTable::to_string() const -> std::string { (void)_global_fec_oti.instance_id; // never emitted: see above root->SetAttribute("FEC-OTI-Maximum-Source-Block-Length", (unsigned)_global_fec_oti.max_source_block_length); root->SetAttribute("FEC-OTI-Encoding-Symbol-Length", (unsigned)_global_fec_oti.encoding_symbol_length); - if (_global_fec_oti.encoding_id == FecScheme::Raptor) { - // Raptor scheme-specific OTI (RFC 5053 §3.2.3) - /* One base64 attribute, not three invented ones. The names previously emitted here, - FEC-OTI-Number-Of-Source-Blocks, FEC-OTI-Number-Of-Sub-Blocks and - FEC-OTI-Symbol-Alignment-Parameter, appear in no specification: zero occurrences in - TS 26.346, and RFC 5053 defines no FDT mapping of its own. See the helper above. */ + if (_global_fec_oti.encoding_id == FecScheme::Raptor || + _global_fec_oti.encoding_id == FecScheme::RaptorQ) { + // One base64 attribute, with the layout the scheme in use defines. See the helpers above. root->SetAttribute("FEC-OTI-Scheme-Specific-Info", - encode_raptor_scheme_specific(_global_fec_oti.nof_source_blocks, - _global_fec_oti.nof_sub_blocks, - _global_fec_oti.symbol_alignment).c_str()); + encode_scheme_specific(_global_fec_oti.encoding_id, + _global_fec_oti.nof_source_blocks, + _global_fec_oti.nof_sub_blocks, + _global_fec_oti.symbol_alignment).c_str()); } root->SetAttribute("xmlns:mbms2007", "urn:3GPP:metadata:2007:MBMS:FLUTE:FDT"); // 3GPP TS 26.346 Clause 7.2.10.2 root->SetAttribute("xmlns:mbms2012", "urn:3GPP:metadata:2012:MBMS:FLUTE:FDT"); // 3GPP TS 26.346 Clause 7.2.10.2 @@ -684,12 +704,13 @@ auto LibFlute::FileDeliveryTable::to_string() const -> std::string { if (file.fec_oti.encoding_symbol_length != 0 && file.fec_oti.encoding_symbol_length != _global_fec_oti.encoding_symbol_length) f->SetAttribute("FEC-OTI-Encoding-Symbol-Length", (unsigned)file.fec_oti.encoding_symbol_length); - if (file.fec_oti.encoding_id == FecScheme::Raptor) { + if (file.fec_oti.encoding_id == FecScheme::Raptor || file.fec_oti.encoding_id == FecScheme::RaptorQ) { if (file.fec_oti.nof_source_blocks != _global_fec_oti.nof_source_blocks) f->SetAttribute("FEC-OTI-Scheme-Specific-Info", - encode_raptor_scheme_specific(file.fec_oti.nof_source_blocks, - file.fec_oti.nof_sub_blocks, - file.fec_oti.symbol_alignment).c_str()); + encode_scheme_specific(file.fec_oti.encoding_id, + file.fec_oti.nof_source_blocks, + file.fec_oti.nof_sub_blocks, + file.fec_oti.symbol_alignment).c_str()); } if (!file.etag.empty()) f->SetAttribute("mbms2012:File-ETag", file.etag.c_str()); /* FileType's own Expires attribute, use="optional" in the annex L.6.1 profiled schema, so it diff --git a/src/Transmitter.cpp b/src/Transmitter.cpp index 49279658..49ee8819 100644 --- a/src/Transmitter.cpp +++ b/src/Transmitter.cpp @@ -514,6 +514,24 @@ Transmitter::Transmitter ( const std::string& destination_address, short port, , _profile(profile) , _fec_redundancy_level(fec_redundancy_level) { + /* The 3GPP profiles name the FEC schemes they admit, and RaptorQ is not among them. + + TS 26.346 V18.2.0 clause L.4.7: "Regarding Application Layer FEC support, the two FEC schemes + referenced in this specification, the Compact No-Code FEC scheme as specified in RFC 3695 [13], + and the Raptor FEC scheme as specified in RFC 5053 [91] are optional to implement by the BM-SC + and mandatory to support by the UE." + + RaptorQ is RFC 6330, which TS 26.346 does not reference at all, so a receiver operating either + profile has no obligation to decode it and in general will not. Refused here rather than sent, + since a session no receiver can decode is worse than a refusal at setup. Available outside the + profiles, which is what this branch adds it for. */ + if (is_3gpp(profile) && content_fec_oti.has_value() && + content_fec_oti->encoding_id == FecScheme::RaptorQ) { + throw std::runtime_error( + "RaptorQ is not one of the FEC schemes the 3GPP profiles admit; use the Compact No-Code or " + "Raptor scheme, or Profile::Unprofiled"); + } + /* The 3GPP profiles fix the TSI field at its narrowest width, so a value that would need the wider encoding cannot be signalled under either of them. This is a clause 7.2 rule, binding on MBMS download generally, not one of annex L.4's profile restrictions. @@ -734,8 +752,8 @@ auto Transmitter::send_fdt() -> void { // The FDT itself always goes out as Compact No-Code, regardless of what // FEC scheme protects this Transmitter's content: it's re-sent on its own // repeat timer already (real redundancy without needing FEC), and - // Raptor has a minimum source-block size (K >= 4) that a small FDT's - // single source block can fall below -- + // Raptor/RaptorQ both have a minimum source-block size (K >= 4 for + // Raptor) that a small FDT's single source block can fall below -- // caught by testing a real Transmitter -> Receiver transfer, not by any // in-process File/codec test, all of which constructed content Files // directly and never exercised send_fdt()'s own File construction. diff --git a/src/fec/GF256LinearSystem.cpp b/src/fec/GF256LinearSystem.cpp new file mode 100644 index 00000000..49e3c9fa --- /dev/null +++ b/src/fec/GF256LinearSystem.cpp @@ -0,0 +1,154 @@ +// libflute - FLUTE/ALC library +// +// Copyright (C) 2026 5G-MAG Association (Jordi J. Gimenez ) +// +// Licensed under the License terms and conditions for use, reproduction, and +// distribution of 5G-MAG software (the “License”). You may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// https://www.5g-mag.com/reference-tools. Unless required by applicable law or +// agreed to in writing, software distributed under the License is distributed on +// an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. +// +// See the License for the specific language governing permissions and limitations +// under the License. +// +#include "fec/GF256LinearSystem.h" +#include "fec/GF256.h" +#include + +namespace LibFlute { +namespace RaptorQ { + +GF256LinearSystem::GF256LinearSystem(uint32_t num_unknowns) + : _num_unknowns(num_unknowns) + , _row_of_pivot(num_unknowns, -1) +{ + _rows.reserve(num_unknowns); +} + +void GF256LinearSystem::ensure_symbol_length(size_t len) { + if (!_symbol_length_known) { + _symbol_length = len; + _symbol_length_known = true; + } else if (len != _symbol_length) { + throw std::invalid_argument("GF256LinearSystem: mismatched symbol length in rhs"); + } +} + +const std::vector& GF256LinearSystem::materialize(const std::optional>& rhs) const { + if (rhs.has_value()) return *rhs; + if (_zero_scratch.size() != _symbol_length) { + _zero_scratch.assign(_symbol_length, 0); + } + return _zero_scratch; +} + +void GF256LinearSystem::scale_and_add_row(std::vector& dst, const std::vector& src, uint8_t factor) { + if (factor == 0) return; + for (size_t j = 0; j < dst.size(); j++) { + dst[j] ^= gf_mul(factor, src[j]); + } +} + +void GF256LinearSystem::scale_and_add_rhs(std::optional>& dst, const std::optional>& src, uint8_t factor) { + if (factor == 0) return; + if (!src.has_value() && !_symbol_length_known) { + // Both operands are implicitly all-zero and the symbol length isn't + // known yet (this can happen while only zero-rhs equations -- LDPC/HDPC/ + // padding rows -- have been added so far, before any real symbol data + // has arrived). There's nothing to materialize correctly yet, and + // nothing useful to do: factor * 0 is still 0, so leave dst untouched + // rather than manifesting a bogus zero-length placeholder that would + // silently stay the wrong length forever once the real length *is* + // established by a later equation. + return; + } + const auto& s = materialize(src); + if (!dst.has_value()) dst.emplace(_symbol_length, 0); + for (size_t j = 0; j < dst->size(); j++) { + (*dst)[j] ^= gf_mul(factor, s[j]); + } +} + +void GF256LinearSystem::scale_rhs_in_place(std::optional>& rhs, uint8_t factor) { + if (!rhs.has_value()) return; // scaling the implicit zero vector is still zero + if (factor == 1) return; + for (auto& b : *rhs) b = gf_mul(b, factor); +} + +int GF256LinearSystem::first_nonzero(const std::vector& coeffs) const { + for (size_t j = 0; j < coeffs.size(); j++) { + if (coeffs[j] != 0) return (int)j; + } + return -1; +} + +bool GF256LinearSystem::add_equation(const std::vector>& terms, std::vector rhs) { + std::optional> rhs_opt; + if (!rhs.empty()) { + ensure_symbol_length(rhs.size()); + rhs_opt = std::move(rhs); + } + + std::vector row(_num_unknowns, 0); + for (auto& [col, coeff] : terms) { + if (col >= _num_unknowns) { + throw std::out_of_range("GF256LinearSystem: equation references unknown index out of range"); + } + row[col] ^= coeff; // repeated columns accumulate (GF(256) addition), matching the RFC's "+="-style construction + } + + // Reduce against every existing pivot row -- a single forward pass + // suffices for the same reason it does in GF2LinearSystem: the existing + // rows are already in full reduced row-echelon form. + for (uint32_t c = 0; c < _num_unknowns; c++) { + int r = _row_of_pivot[c]; + if (r < 0) continue; + uint8_t factor = row[c]; + if (factor == 0) continue; + scale_and_add_row(row, _rows[r].coeffs, factor); + scale_and_add_rhs(rhs_opt, _rows[r].rhs, factor); + } + + int pivot = first_nonzero(row); + if (pivot < 0) { + return false; // redundant equation (or, if rhs_opt ended up non-zero, inconsistent input data -- dropped either way) + } + + // Normalize so the pivot column's coefficient is exactly 1. + uint8_t pivot_val = row[pivot]; + if (pivot_val != 1) { + uint8_t inv = gf_inv(pivot_val); + for (auto& c : row) c = gf_mul(c, inv); + scale_rhs_in_place(rhs_opt, inv); + } + + // Back-substitute into every existing row with a non-zero entry at the + // new pivot column, to keep the whole system in full RREF. + for (auto& existing : _rows) { + uint8_t factor = existing.coeffs[pivot]; + if (factor == 0) continue; + scale_and_add_row(existing.coeffs, row, factor); + scale_and_add_rhs(existing.rhs, rhs_opt, factor); + } + + Row new_row; + new_row.coeffs = std::move(row); + new_row.rhs = std::move(rhs_opt); + new_row.pivot = (uint32_t)pivot; + _rows.push_back(std::move(new_row)); + _row_of_pivot[pivot] = (int)_rows.size() - 1; + _rank++; + return true; +} + +const std::vector& GF256LinearSystem::solved_value(uint32_t index) const { + if (index >= _num_unknowns) throw std::out_of_range("GF256LinearSystem: unknown index out of range"); + if (!fully_determined()) throw std::logic_error("GF256LinearSystem: system is not fully determined yet"); + int r = _row_of_pivot[index]; + return materialize(_rows[r].rhs); +} + +} // namespace RaptorQ +} // namespace LibFlute diff --git a/src/fec/RaptorQCodec.cpp b/src/fec/RaptorQCodec.cpp new file mode 100644 index 00000000..053bcaa8 --- /dev/null +++ b/src/fec/RaptorQCodec.cpp @@ -0,0 +1,210 @@ +// libflute - FLUTE/ALC library +// +// Copyright (C) 2026 5G-MAG Association (Jordi J. Gimenez ) +// +// Licensed under the License terms and conditions for use, reproduction, and +// distribution of 5G-MAG software (the “License”). You may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// https://www.5g-mag.com/reference-tools. Unless required by applicable law or +// agreed to in writing, software distributed under the License is distributed on +// an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. +// +// See the License for the specific language governing permissions and limitations +// under the License. +// +#include "fec/RaptorQCodec.h" +#include "fec/GF256.h" +#include +#include + +namespace LibFlute { +namespace RaptorQ { + +namespace { + +using Terms = std::vector>; + +/// Which of the S LDPC rows each of the first B intermediate symbols XORs into. This is a +/// paraphrase of the construction in RFC 6330 clause 5.3.3.3, not a quotation of it; the two +/// definitions it relies on, verbatim, are: +/// +/// RFC 6330 clause 5.3.3.3: "C[0], ..., C[B-1] denote the intermediate symbols that are LT +/// symbols but not LDPC symbols." +/// +/// RFC 6330 clause 5.3.3.3: "The pre-coding relationships amongst the L intermediate symbols are +/// defined by requiring that a set of S+H linear combinations of the intermediate symbols +/// evaluate to zero." +/// +/// B is W - S, per the same clause. Coefficients are all 1 -- the +/// LDPC relations are plain XOR, no GF(256) scaling. +std::vector> ldpc_first_loop(uint32_t B, uint32_t S) { + std::vector> rows(S); + for (uint32_t i = 0; i < B; i++) { + uint32_t a = 1 + i / S; + uint32_t b = i % S; + rows[b].push_back(i); + b = (b + a) % S; + rows[b].push_back(i); + b = (b + a) % S; + rows[b].push_back(i); + } + return rows; +} + +/// Builds the H HDPC equations' coefficients over columns [0, K'+S), via +/// the MT*GAMMA construction (RFC 6330 Section 5.3.3.3). Built as dense +/// H x (K'+S) and (K'+S) x (K'+S) matrices and multiplied directly -- see +/// GF256LinearSystem.h's class comment for why dense is an acceptable +/// trade-off here (bounded block sizes, correctness-first). +std::vector> hdpc_coefficient_rows(uint32_t Kprime, uint32_t S, uint32_t H) { + uint32_t n = Kprime + S; // MT is H x n, GAMMA is n x n + + std::vector> MT(H, std::vector(n, 0)); + for (uint32_t j = 0; j < n - 1; j++) { + uint32_t i0 = Rand(j + 1, 6, H); + // Rand(., ., H-1) returns a value in [0, H-2], so this is always in + // [1, H-1] -- never a multiple of H -- which guarantees i1 != i0. MT's + // definition ("is 1 if i==i0 or i==i1") is a plain boolean OR, not an + // accumulation, but since the two indices can never coincide there's + // no ambiguity to resolve either way. + uint32_t i1 = (i0 + Rand(j + 1, 7, H - 1) + 1) % H; + MT[i0][j] = 1; + MT[i1][j] = 1; + } + for (uint32_t i = 0; i < H; i++) { + MT[i][n - 1] = gf_alpha_pow(i); + } + + // GAMMA[i,j] = alpha^(i-j) for i >= j, else 0. Built row by row using the + // recurrence GAMMA[i,j] = alpha * GAMMA[i,j+1] for j < i (so each row is + // one gf_mul away from the previous entry) rather than an alpha_pow call + // per cell. + std::vector> GAMMA(n, std::vector(n, 0)); + for (uint32_t i = 0; i < n; i++) { + GAMMA[i][i] = 1; // alpha^0 + for (uint32_t j = i; j-- > 0;) { + GAMMA[i][j] = gf_mul(GAMMA[i][j + 1], 2 /* alpha */); + } + } + + std::vector> result(H, std::vector(n, 0)); + for (uint32_t h = 0; h < H; h++) { + for (uint32_t k = 0; k < n; k++) { + uint8_t mt_hk = MT[h][k]; + if (mt_hk == 0) continue; + const auto& gamma_row = GAMMA[k]; + auto& out = result[h]; + for (uint32_t j = 0; j <= k; j++) { // GAMMA[k][j] == 0 for j > k + out[j] ^= gf_mul(mt_hk, gamma_row[j]); + } + } + } + return result; +} + +} // namespace + +RaptorQCodec::RaptorQCodec(uint32_t K) + : _K(K) + , _params(derive_precode_params(smallest_supported_Kprime(K))) + , _system(_params.L) +{ + const auto& p = _params; + + // LDPC relations: S rows. Column B+row is the row's own LDPC symbol + // (coefficient 1); the first loop contributes B-domain columns; the + // second loop contributes two PI-domain columns (W+a, W+b). + auto ldpc1 = ldpc_first_loop(p.B, p.S); + for (uint32_t row = 0; row < p.S; row++) { + Terms terms; + for (uint32_t col : ldpc1[row]) terms.push_back({col, 1}); + terms.push_back({p.B + row, 1}); + uint32_t a = row % p.P; + uint32_t b = (row + 1) % p.P; + terms.push_back({p.W + a, 1}); + terms.push_back({p.W + b, 1}); + _system.add_equation(terms, {}); + } + + // HDPC relations: H rows over columns [0, K'+S), plus the row's own HDPC + // symbol at column K'+S+row (coefficient 1). + auto hdpc = hdpc_coefficient_rows(p.Kprime, p.S, p.H); + for (uint32_t row = 0; row < p.H; row++) { + Terms terms; + for (uint32_t col = 0; col < p.Kprime + p.S; col++) { + if (hdpc[row][col] != 0) terms.push_back({col, hdpc[row][col]}); + } + terms.push_back({p.Kprime + p.S + row, 1}); + _system.add_equation(terms, {}); + } + + // Padding symbols (ISI in [K, K')) are always zero, and are known without + // needing to receive anything -- feed them in now, for free, same as the + // pre-coding rows above. + for (uint32_t isi = K; isi < p.Kprime; isi++) { + Terms terms; + for (uint32_t col : encoding_indices(isi, p)) terms.push_back({col, 1}); + _system.add_equation(terms, {}); + } +} + +std::vector> RaptorQCodec::compute_intermediate_symbols( + const std::vector>& source_symbols) { + if (source_symbols.size() != _K) { + throw std::invalid_argument("RaptorQCodec: expected exactly K source symbols"); + } + for (uint32_t esi = 0; esi < _K; esi++) { + Terms terms; + for (uint32_t col : encoding_indices(esi_to_isi(esi), _params)) terms.push_back({col, 1}); + _system.add_equation(terms, source_symbols[esi]); + } + if (!_system.fully_determined()) { + throw std::logic_error("RaptorQCodec: pre-coding + source symbols did not fully " + "determine the intermediate symbols for K=" + std::to_string(_K) + + " (K'=" + std::to_string(_params.Kprime) + ", rank " + + std::to_string(_system.rank()) + "/" + std::to_string(_params.L) + ")"); + } + std::vector> intermediate; + intermediate.reserve(_params.L); + for (uint32_t i = 0; i < _params.L; i++) intermediate.push_back(_system.solved_value(i)); + return intermediate; +} + +std::vector RaptorQCodec::generate_encoding_symbol( + uint32_t esi, const std::vector>& intermediate_symbols) const { + auto indices = encoding_indices(esi_to_isi(esi), _params); + std::vector result = intermediate_symbols[indices[0]]; + for (size_t j = 1; j < indices.size(); j++) { + const auto& sym = intermediate_symbols[indices[j]]; + for (size_t b = 0; b < result.size(); b++) result[b] ^= sym[b]; + } + return result; +} + +bool RaptorQCodec::add_received_symbol(uint32_t esi, const std::vector& data) { + Terms terms; + for (uint32_t col : encoding_indices(esi_to_isi(esi), _params)) terms.push_back({col, 1}); + return _system.add_equation(terms, data); +} + +std::vector> RaptorQCodec::decode_source_symbols() { + if (!can_decode()) { + throw std::logic_error("RaptorQCodec: not enough independent symbols received yet"); + } + std::vector> source; + source.reserve(_K); + for (uint32_t esi = 0; esi < _K; esi++) { + auto indices = encoding_indices(esi_to_isi(esi), _params); + std::vector sym = _system.solved_value(indices[0]); + for (size_t j = 1; j < indices.size(); j++) { + const auto& v = _system.solved_value(indices[j]); + for (size_t b = 0; b < sym.size(); b++) sym[b] ^= v[b]; + } + source.push_back(std::move(sym)); + } + return source; +} + +} // namespace RaptorQ +} // namespace LibFlute diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a3a250db..54a9f1c8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -50,4 +50,5 @@ add_flute_test_executable(flute_protocol_tests test_protocol_fixes.cpp "protocol add_flute_test_executable(flute_fdt_growth_tests test_fdt_growth.cpp "fdt_growth:") add_flute_test_executable(flute_raptor_codec_tests test_raptor_codec.cpp "raptor_codec:") add_flute_test_executable(flute_raptor_tests test_raptor_fec.cpp "raptor:") +add_flute_test_executable(flute_raptorq_tests test_raptorq_fec.cpp "raptorq:") add_flute_test_executable(flute_raptor_e2e_tests test_raptor_e2e.cpp "raptor_e2e:") diff --git a/tests/test_protocol_fixes.cpp b/tests/test_protocol_fixes.cpp index b885ebf7..9aaab582 100644 --- a/tests/test_protocol_fixes.cpp +++ b/tests/test_protocol_fixes.cpp @@ -116,7 +116,7 @@ TEST(GeneralFluteTest, TransferLengthStillCarriedOutsideTheProfile) { /* The delimitation itself. A session is bound by the general FLUTE documents always, and by TS 26.346 annex L.4 only under the 3GPP profile, which is the default. */ -TEST(ProfileDefaultTest, DefaultIsTheMbms3gppProfile) { +TEST(ProfileDefaultTest, DefaultIsMbs5g) { auto oti = make_fec_oti(); FileDeliveryTable fdt(1, oti); EXPECT_EQ(fdt.profile(), Profile::Ts26517); diff --git a/tests/test_raptor_e2e.cpp b/tests/test_raptor_e2e.cpp index 0e691a94..b6a3b77d 100644 --- a/tests/test_raptor_e2e.cpp +++ b/tests/test_raptor_e2e.cpp @@ -13,18 +13,18 @@ // See the License for the specific language governing permissions and limitations // under the License. // -// Real Transmitter -> UDP multicast socket -> real Receiver, with Raptor -// actually enabled -- unlike test_raptor_fec.cpp, which exercises the codec -// and File/EncodingSymbol entirely in-process, this is the one thing that -// was *not* covered anywhere else: real wire serialization of Raptor -// encoding symbols over an actual socket, real FileDeliveryTable XML -// generation/parsing carrying the FEC-OTI-Number-Of-Source-Blocks/Sub- -// Blocks/Symbol-Alignment-Parameter attributes (the codec-level and -// File-level tests never serialize to XML at all -- they pass a FileEntry -// struct directly from encoder to decoder in memory), and real repair-symbol -// generation/consumption through Transmitter/Receiver's actual send/receive -// loops, not a hand-rolled loop in a test. Mirrors test_end_to_end.cpp's -// existing pattern. +// Real Transmitter -> UDP multicast socket -> real Receiver, with Raptor/ +// RaptorQ actually enabled -- unlike test_raptor_fec.cpp/test_raptorq_fec.cpp, +// which exercise the codecs and File/EncodingSymbol entirely in-process, this +// is the one thing that was *not* covered anywhere else: real wire +// serialization of Raptor/RaptorQ encoding symbols over an actual socket, +// real FileDeliveryTable XML generation/parsing carrying the FEC-OTI-Number- +// Of-Source-Blocks/Sub-Blocks/Symbol-Alignment-Parameter attributes (the +// codec-level and File-level tests never serialize to XML at all -- they +// pass a FileEntry struct directly from encoder to decoder in memory), and +// real repair-symbol generation/consumption through Transmitter/Receiver's +// actual send/receive loops, not a hand-rolled loop in a test. Mirrors +// test_end_to_end.cpp's existing pattern. #include #include @@ -53,9 +53,14 @@ std::shared_ptr run_transfer( boost::asio::io_context transmitter_io; LibFlute::Receiver receiver("0.0.0.0", mcast_addr, port, tsi, receiver_io); + /* General FLUTE, not the MBMS Download Profile. RaptorQ is not among the schemes TS 26.346 + clause L.4.7 admits, so a RaptorQ session is by definition a general-FLUTE one and the profile + refuses it at construction. Raptor would be accepted either way; both run here under the same + profile so the two cases stay comparable. */ LibFlute::Transmitter transmitter( mcast_addr, port, tsi, 1400, 0, transmitter_io, std::nullopt, - LibFlute::FileDeliveryTable::FDT_NS_DRAFT_2005, true, std::nullopt, fec_oti); + LibFlute::FileDeliveryTable::FDT_NS_DRAFT_2005, true, std::nullopt, fec_oti, + LibFlute::Profile::Unprofiled); auto file_description = std::make_shared( "e2e/payload.bin", expected_payload.c_str(), expected_payload.size()); @@ -125,3 +130,15 @@ TEST(RaptorE2ETest, RealTransmitterToReceiverOverMulticast) { ASSERT_EQ(received->length(), payload.size()); EXPECT_EQ(std::string(received->buffer(), received->length()), payload); } + +TEST(RaptorQE2ETest, RealTransmitterToReceiverOverMulticast) { + std::string payload = make_payload(30000, 43); + LibFlute::FecOti fec_oti{.encoding_id = LibFlute::FecScheme::RaptorQ, .max_source_block_length = 100}; + + auto received = run_transfer("239.255.0.3", 18093, 4244, fec_oti, payload); + ASSERT_NE(received, nullptr); + EXPECT_EQ(received->meta().fec_oti.encoding_id, LibFlute::FecScheme::RaptorQ); + EXPECT_GT(received->meta().fec_oti.nof_source_blocks, 0u); + ASSERT_EQ(received->length(), payload.size()); + EXPECT_EQ(std::string(received->buffer(), received->length()), payload); +} diff --git a/tests/test_raptorq_fec.cpp b/tests/test_raptorq_fec.cpp new file mode 100644 index 00000000..6e038000 --- /dev/null +++ b/tests/test_raptorq_fec.cpp @@ -0,0 +1,439 @@ +// libflute - FLUTE/ALC library +// +// Copyright (C) 2026 5G-MAG Association (Jordi J. Gimenez ) +// +// Licensed under the License terms and conditions for use, reproduction, and +// distribution of 5G-MAG software (the “License”). You may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// https://www.5g-mag.com/reference-tools. Unless required by applicable law or +// agreed to in writing, software distributed under the License is distributed on +// an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. +// +// See the License for the specific language governing permissions and limitations +// under the License. +// +// Unit tests for the from-scratch RFC 6330 RaptorQ codec (fec/RaptorQCodec.h) +// and its integration into LibFlute::File. Mirrors test_raptor_fec.cpp's +// structure; see that file's header comment for the rationale behind +// testing at both the codec level and the real File API level. +#include + +#include "Transmitter.h" + +#include +#include +#include +#include "fec/RaptorQCodec.h" +#include "File.h" + +using namespace LibFlute; +using namespace LibFlute::RaptorQ; + +namespace { + +std::vector> make_source(uint32_t K, size_t T, uint32_t seed) { + std::mt19937 rng(seed); + std::vector> src(K, std::vector(T)); + for (auto& sym : src) for (auto& b : sym) b = (uint8_t)rng(); + return src; +} + +void expect_round_trip(uint32_t K, size_t T, uint32_t seed, uint32_t received_overhead, uint32_t pool_overhead) { + RaptorQCodec encoder(K); + auto source = make_source(K, T, seed); + auto intermediate = encoder.compute_intermediate_symbols(source); + ASSERT_EQ(intermediate.size(), encoder.L()); + + for (uint32_t i = 0; i < K; i++) { + EXPECT_EQ(encoder.generate_encoding_symbol(i, intermediate), source[i]) << "systematic property failed at ESI " << i; + } + + uint32_t pool_size = K + pool_overhead; + uint32_t received_count = K + received_overhead; + ASSERT_LE(received_count, pool_size); + + std::mt19937 rng(seed * 7919u + 1); + std::vector pool(pool_size); + for (uint32_t i = 0; i < pool_size; i++) pool[i] = i; + std::shuffle(pool.begin(), pool.end(), rng); + + RaptorQCodec decoder(K); + for (uint32_t i = 0; i < received_count; i++) { + decoder.add_received_symbol(pool[i], encoder.generate_encoding_symbol(pool[i], intermediate)); + } + + ASSERT_TRUE(decoder.can_decode()) << "needs " << decoder.symbols_needed() << " more independent symbols"; + EXPECT_EQ(decoder.decode_source_symbols(), source); +} + +} // namespace + +TEST(RaptorQCodecTest, CanonicalExactlyKInOrderAlwaysDecodes) { + RaptorQCodec encoder(10); + auto source = make_source(10, 8, 1); + auto intermediate = encoder.compute_intermediate_symbols(source); + RaptorQCodec decoder(10); + for (uint32_t i = 0; i < 10; i++) { + decoder.add_received_symbol(i, encoder.generate_encoding_symbol(i, intermediate)); + } + ASSERT_TRUE(decoder.can_decode()); + EXPECT_EQ(decoder.decode_source_symbols(), source); +} + +TEST(RaptorQCodecTest, SmallBlockIncludingBelowMinimumSupportedKprime) { + expect_round_trip(10, 8, 15, /*received_overhead=*/2, /*pool_overhead=*/25); + // K=4 is below Table 2's smallest K' (10) -- exercises the K -> K' padding step. + expect_round_trip(4, 4, 8, /*received_overhead=*/12, /*pool_overhead=*/20); +} + +TEST(RaptorQCodecTest, MediumBlock) { + expect_round_trip(100, 16, 3, 15, 40); + expect_round_trip(100, 16, 4, 25, 60); +} + +TEST(RaptorQCodecTest, LargerBlockModestOverheadIsAlreadySafe) { + // K=300, not Raptor's test's K=1000: GF256LinearSystem is dense + // (GF256LinearSystem.h explains why -- real, non-unity HDPC + // coefficients), so it costs meaningfully more per equation than + // Raptor's bit-packed GF(2) system does at the same K. 300 is still a + // real, non-trivial block size; K=1000 was verified separately during + // development (see this codec's commit message for measured timing) and + // isn't worth this suite's runtime budget on every run. + expect_round_trip(300, 32, 5, 10, 40); + expect_round_trip(300, 32, 6, 20, 60); +} + +TEST(RaptorQCodecTest, DecodesReliablyAtLowerOverheadThanRaptor) { + // RaptorQ's well-documented design improvement over the original Raptor: + // near-zero decoding failure probability even at 0-1 overhead for K >= 30 + // (see the from-scratch implementation's commit message for measured + // failure rates). This isn't a statistical test (too slow/flaky for a + // unit test); it just confirms a single trial at K+1 succeeds where + // Raptor would need much more overhead to be reliable. + expect_round_trip(30, 8, 99, /*received_overhead=*/1, /*pool_overhead=*/10); + expect_round_trip(100, 16, 100, /*received_overhead=*/1, /*pool_overhead=*/10); +} + +TEST(FileRaptorQTest, EndToEndThroughRealFileApiSingleBlock) { + const size_t data_len = 20000; + std::vector data(data_len); + std::mt19937 rng(123); + for (auto& b : data) b = (char)rng(); + + FecOti oti{ + .encoding_id = FecScheme::RaptorQ, + .encoding_symbol_length = 256, + .max_source_block_length = 200, + .max_number_of_encoding_symbols = 90, // RaptorQ needs far less overhead than Raptor + }; + + File encoder(1, oti, "test.bin", "application/octet-stream", 0, data.data(), data_len, true); + auto symbols = encoder.get_next_symbols(1024 * 1024); + ASSERT_FALSE(symbols.empty()); + EXPECT_EQ(encoder.fec_oti().nof_source_blocks, 1u); + + File decoder(encoder.meta()); + + std::vector order(symbols.size()); + for (size_t i = 0; i < order.size(); i++) order[i] = i; + std::shuffle(order.begin(), order.end(), rng); + size_t to_deliver = std::min(symbols.size(), (size_t)85); // K(~79) + modest overhead + std::vector deliver(symbols.size(), false); + for (size_t i = 0; i < to_deliver; i++) deliver[order[i]] = true; + + for (size_t i = 0; i < symbols.size(); i++) { + if (deliver[i]) decoder.put_symbol(symbols[i]); + } + + ASSERT_TRUE(decoder.complete()); + ASSERT_EQ(decoder.length(), data_len); + EXPECT_EQ(0, memcmp(decoder.buffer(), data.data(), data_len)); +} + +TEST(FileRaptorQTest, EndToEndThroughRealFileApiMultiBlock) { + const size_t data_len = 20000; + std::vector data(data_len); + std::mt19937 rng(456); + for (auto& b : data) b = (char)rng(); + + FecOti oti{ + .encoding_id = FecScheme::RaptorQ, + .encoding_symbol_length = 256, + .max_source_block_length = 20, + .max_number_of_encoding_symbols = 32, + }; + + File encoder(2, oti, "test2.bin", "application/octet-stream", 0, data.data(), data_len, true); + auto symbols = encoder.get_next_symbols(1024 * 1024); + ASSERT_GT(encoder.fec_oti().nof_source_blocks, 1u); + + File decoder(encoder.meta()); + + std::vector order(symbols.size()); + for (size_t i = 0; i < order.size(); i++) order[i] = i; + std::shuffle(order.begin(), order.end(), rng); + size_t to_deliver = (size_t)(symbols.size() * 0.92); + std::vector deliver(symbols.size(), false); + for (size_t i = 0; i < to_deliver; i++) deliver[order[i]] = true; + + for (size_t i = 0; i < symbols.size(); i++) { + if (deliver[i]) decoder.put_symbol(symbols[i]); + } + + ASSERT_TRUE(decoder.complete()); + ASSERT_EQ(decoder.length(), data_len); + EXPECT_EQ(0, memcmp(decoder.buffer(), data.data(), data_len)); +} + +// ---------------------------------------------------------------------------------------------- +// The scheme-specific FEC OTI layout differs between the two schemes, in the same four octets: +// Z and N are the opposite way round. Encoding one with the other's layout silently corrupts both +// values, so this asserts the two encodings differ for identical inputs. +// +// RFC 5053 clause 3.2.3, Raptor: "a 4-octet field consisting of the parameters Z (2 octets), +// N (1 octet), and Al (1 octet)" +// +// RFC 6330 clause 3.3.3, RaptorQ: "The number of source blocks (Z): 8-bit unsigned integer." and +// "The number of sub-blocks (N): 16-bit unsigned integer." +// ---------------------------------------------------------------------------------------------- + +#include "FileDeliveryTable.h" + +namespace { + +std::string ssi_for(LibFlute::FecScheme scheme, uint16_t z, uint16_t n, uint8_t al) { + LibFlute::FecOti oti{}; + oti.encoding_id = scheme; + oti.transfer_length = 4096; + oti.encoding_symbol_length = 512; + oti.max_source_block_length = 64; + oti.nof_source_blocks = z; + oti.nof_sub_blocks = n; + oti.symbol_alignment = al; + LibFlute::FileDeliveryTable fdt(1, oti, LibFlute::FileDeliveryTable::FDT_NS_3GPP_CONSOLIDATED_V2); + LibFlute::FileDeliveryTable::FileEntry e{}; + e.toi = 1; + e.content_location = "http://example.invalid/a"; + e.content_length = 4096; + e.fec_oti = oti; + fdt.add(e); + return fdt.to_string(); +} + +} // namespace + +TEST(RaptorQSchemeSpecificOtiTest, LayoutDiffersFromRaptorForTheSameValues) { + // Z = 1, N = 0x0203, Al = 4. + // RaptorQ packs Z into one octet and N into two: 01 02 03 04 -> AQIDBA== + // Raptor packs Z into two and N into one: 00 01 03 04 -> AAEDBA== + const auto rq = ssi_for(LibFlute::FecScheme::RaptorQ, 1, 0x0203, 4); + const auto ra = ssi_for(LibFlute::FecScheme::Raptor, 1, 0x0203, 4); + EXPECT_NE(rq.find("AQIDBA=="), std::string::npos); + EXPECT_NE(ra.find("AAEDBA=="), std::string::npos); + EXPECT_EQ(rq.find("AAEDBA=="), std::string::npos); + EXPECT_EQ(ra.find("AQIDBA=="), std::string::npos); +} + +TEST(RaptorQSchemeSpecificOtiTest, RoundTripsWithTheRaptorQWidths) { + // N above 255 is the case a shared layout would truncate. + const auto out = ssi_for(LibFlute::FecScheme::RaptorQ, 7, 0x0140, 4); + std::vector buf(out.begin(), out.end()); + buf.push_back('\0'); + LibFlute::FileDeliveryTable parsed(1, buf.data(), buf.size()); + ASSERT_FALSE(parsed.file_entries().empty()); + const auto &oti = parsed.file_entries().front().fec_oti; + EXPECT_EQ(oti.nof_source_blocks, 7); + EXPECT_EQ(oti.nof_sub_blocks, 0x0140); + EXPECT_EQ(oti.symbol_alignment, 4); +} + +// The transmit loop as Transmitter actually drives it: one datagram-sized +// request at a time, each batch acknowledged before the next is asked for. The +// File-level tests above instead pull every symbol in a single 1 MB call and +// acknowledge nothing, so they cannot see whether a repair symbol would ever +// reach the wire in a real session. +namespace { +struct RqTransmitRun { + size_t total = 0; + size_t repair = 0; + uint32_t max_esi = 0; + bool complete = false; + std::vector symbols; +}; + +RqTransmitRun rq_drive_transmit_loop(LibFlute::File& f, size_t datagram_payload, uint32_t k) { + RqTransmitRun r; + for (int guard = 0; guard < 10000 && !f.complete(); guard++) { + auto batch = f.get_next_symbols(datagram_payload); + if (batch.empty()) break; + for (auto& s : batch) { + r.total++; + if (s.id() >= k) r.repair++; + r.max_esi = std::max(r.max_esi, s.id()); + r.symbols.push_back(s); + } + f.mark_completed(batch, true); + } + r.complete = f.complete(); + return r; +} +} // namespace + +// Kt = ceil(20000/256) = 79 source symbols in one source block, and a FEC OTI +// permitting 90 encoding symbols for it, so the repair budget is 90 - 79 = 11 +// and a fully transmitted block is exactly 90 symbols. +TEST(RaptorQRepairTransmissionTest, RepairSymbolsAreTransmittedAfterTheSourceSymbols) { + const size_t data_len = 20000; + const uint32_t K = 79; + std::vector data(data_len); + std::mt19937 rng(31337); + for (auto& b : data) b = (char)rng(); + + FecOti oti{ + .encoding_id = FecScheme::RaptorQ, + .encoding_symbol_length = 256, + .max_source_block_length = 200, + .max_number_of_encoding_symbols = 90, + }; + File encoder(1, oti, "test.bin", "application/octet-stream", 0, data.data(), data_len, true); + ASSERT_EQ(encoder.fec_oti().nof_source_blocks, 1u); + + auto run = rq_drive_transmit_loop(encoder, 256 + 4, K); + + EXPECT_EQ(run.total, 90u) << "the block's whole encoding symbol budget must go out"; + EXPECT_EQ(run.repair, 11u) << "repair symbols must reach the wire, not just be budgeted"; + EXPECT_EQ(run.max_esi, 89u); + EXPECT_TRUE(run.complete) << "the file must still finish once its repair budget is out"; +} + +TEST(RaptorQRepairTransmissionTest, FileIsNotCompleteWhileRepairSymbolsAreStillOwed) { + const size_t data_len = 20000; + const uint32_t K = 79; + std::vector data(data_len, 'q'); + FecOti oti{ + .encoding_id = FecScheme::RaptorQ, + .encoding_symbol_length = 256, + .max_source_block_length = 200, + .max_number_of_encoding_symbols = 90, + }; + File encoder(1, oti, "test.bin", "application/octet-stream", 0, data.data(), data_len, true); + + size_t sent = 0; + bool complete_at_last_source_symbol = false; + while (!encoder.complete()) { + auto batch = encoder.get_next_symbols(256 + 4); + if (batch.empty()) break; + sent += batch.size(); + encoder.mark_completed(batch, true); + if (sent == K) complete_at_last_source_symbol = encoder.complete(); + } + EXPECT_FALSE(complete_at_last_source_symbol) + << "completing at the last source symbol strands the repair budget"; + EXPECT_EQ(sent, 90u); +} + +// A RaptorQ receiver that holds every source symbol is finished, and knows +// nothing about any repair budget: the transmit-side condition must not leak in. +TEST(RaptorQRepairTransmissionTest, ReceiverCompletesOnSourceSymbolsAlone) { + const size_t data_len = 20000; + const uint32_t K = 79; + std::vector data(data_len); + std::mt19937 rng(777); + for (auto& b : data) b = (char)rng(); + + FecOti oti{ + .encoding_id = FecScheme::RaptorQ, + .encoding_symbol_length = 256, + .max_source_block_length = 200, + .max_number_of_encoding_symbols = 90, + }; + File encoder(1, oti, "test.bin", "application/octet-stream", 0, data.data(), data_len, true); + auto run = rq_drive_transmit_loop(encoder, 256 + 4, K); + + File decoder(encoder.meta()); + for (auto& s : run.symbols) { + if (s.id() < K) decoder.put_symbol(s); + } + EXPECT_TRUE(decoder.complete()) << "no repair symbol delivered, yet nothing is missing"; + EXPECT_EQ(memcmp(decoder.buffer(), data.data(), data_len), 0); +} + +// The point of the repair symbols: source symbols alone are not enough once +// some are lost, and the budget that now reaches the wire must close the gap. +TEST(RaptorQRepairTransmissionTest, RepairSymbolsRecoverLostSourceSymbols) { + const size_t data_len = 20000; + const uint32_t K = 79; + std::vector data(data_len); + std::mt19937 rng(2024); + for (auto& b : data) b = (char)rng(); + + FecOti oti{ + .encoding_id = FecScheme::RaptorQ, + .encoding_symbol_length = 256, + .max_source_block_length = 200, + .max_number_of_encoding_symbols = 90, + }; + File encoder(1, oti, "test.bin", "application/octet-stream", 0, data.data(), data_len, true); + auto run = rq_drive_transmit_loop(encoder, 256 + 4, K); + ASSERT_EQ(run.repair, 11u); + + // Drop one source symbol in every twelve, deliver every repair symbol. + File decoder(encoder.meta()); + size_t dropped = 0; + for (auto& s : run.symbols) { + if (s.id() < K && (s.id() % 12) == 11) { dropped++; continue; } + decoder.put_symbol(s); + } + ASSERT_GT(dropped, 0u); + EXPECT_TRUE(decoder.complete()) << dropped << " source symbols lost and not recovered"; + EXPECT_EQ(memcmp(decoder.buffer(), data.data(), data_len), 0); +} + + +/* RaptorQ is offered for general FLUTE only. TS 26.346 V18.2.0 clause L.4.7 names the schemes the + MBMS Download Profile admits: "Regarding Application Layer FEC support, the two FEC schemes + referenced in this specification, the Compact No-Code FEC scheme as specified in RFC 3695 [13], + and the Raptor FEC scheme as specified in RFC 5053 [91] are optional to implement by the BM-SC + and mandatory to support by the UE." RaptorQ is RFC 6330 and is not referenced by TS 26.346. */ +namespace { + +LibFlute::FecOti raptorq_oti() { + LibFlute::FecOti oti{}; + oti.encoding_id = LibFlute::FecScheme::RaptorQ; + oti.encoding_symbol_length = 1200; + oti.max_source_block_length = 64; + return oti; +} + +} // namespace + +TEST(ProfileFecSchemeTest, RaptorQRefusedUnderThe3gppProfiles) { + boost::asio::io_context io; + EXPECT_THROW( + LibFlute::Transmitter("239.1.4.10", 5000, /*tsi*/ 1, /*mtu*/ 1400, /*rate_limit*/ 0, io, + std::nullopt, LibFlute::FileDeliveryTable::FDT_NS_NONE, + /*active*/ false, std::nullopt, raptorq_oti(), + LibFlute::Profile::Ts26517), + std::runtime_error); +} + +TEST(ProfileFecSchemeTest, RaptorQAllowedOutsideThe3gppProfiles) { + boost::asio::io_context io; + EXPECT_NO_THROW( + LibFlute::Transmitter("239.1.4.11", 5000, /*tsi*/ 1, /*mtu*/ 1400, /*rate_limit*/ 0, io, + std::nullopt, LibFlute::FileDeliveryTable::FDT_NS_NONE, + /*active*/ false, std::nullopt, raptorq_oti(), + LibFlute::Profile::Unprofiled)); +} + +TEST(ProfileFecSchemeTest, RaptorRemainsAvailableUnderThe3gppProfiles) { + auto oti = raptorq_oti(); + oti.encoding_id = LibFlute::FecScheme::Raptor; + boost::asio::io_context io; + EXPECT_NO_THROW( + LibFlute::Transmitter("239.1.4.12", 5000, /*tsi*/ 1, /*mtu*/ 1400, /*rate_limit*/ 0, io, + std::nullopt, LibFlute::FileDeliveryTable::FDT_NS_NONE, + /*active*/ false, std::nullopt, oti, + LibFlute::Profile::Ts26517)); +}