Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 43 additions & 2 deletions examples/flute-transmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// See the License for the specific language governing permissions and limitations
// under the License.
//
#include <optional>
#include <argp.h>

#include <cstdio>
Expand Down Expand Up @@ -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}};

/**
Expand All @@ -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<LibFlute::FecOti> 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.
*/
Expand Down Expand Up @@ -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<size_t>(strtoul(arg, nullptr, 10));
arguments->new_api = true;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
19 changes: 10 additions & 9 deletions include/File.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "Transmitter.h"
#include "fec/FecBlockCodec.h"
#include "fec/RaptorCodec.h"
#include "fec/RaptorQCodec.h"

namespace LibFlute {
/**
Expand Down Expand Up @@ -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);

Expand All @@ -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
Expand All @@ -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);
Expand All @@ -256,7 +257,7 @@ namespace LibFlute {
// sets it, and why it is not signalled.
uint32_t _fec_redundancy_level = kDefaultFecRedundancyLevel;

std::map<uint16_t, std::shared_ptr<FecBlockCodec>> _raptor_codecs; // one RaptorCodec per source block
std::map<uint16_t, std::shared_ptr<FecBlockCodec>> _raptor_codecs; // one per source block; Raptor or RaptorQ depending on fec_oti.encoding_id
std::map<uint16_t, std::vector<std::vector<uint8_t>>> _raptor_intermediate; // encoder side only, filled once per block
std::map<uint16_t, uint32_t> _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
Expand Down
2 changes: 1 addition & 1 deletion include/Transmitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 7 additions & 4 deletions include/fec/FecBlockCodec.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
55 changes: 55 additions & 0 deletions include/fec/GF256.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// libflute - FLUTE/ALC library
//
// Copyright (C) 2026 5G-MAG Association (Jordi J. Gimenez <gimenez@5g-mag.com>)
//
// 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 <cstdint>
#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
90 changes: 90 additions & 0 deletions include/fec/GF256LinearSystem.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// libflute - FLUTE/ALC library
//
// Copyright (C) 2026 5G-MAG Association (Jordi J. Gimenez <gimenez@5g-mag.com>)
//
// 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 <cstdint>
#include <cstddef>
#include <vector>
#include <optional>
#include <utility>

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<std::pair<uint32_t, uint8_t>>& terms, std::vector<uint8_t> rhs);

/// Once fully_determined(), returns the solved value for unknown `index`.
const std::vector<uint8_t>& solved_value(uint32_t index) const;

private:
struct Row {
std::vector<uint8_t> coeffs; // length num_unknowns; pivot column's entry is exactly 1
std::optional<std::vector<uint8_t>> rhs; // nullopt == implicit all-zero
uint32_t pivot = 0;
};

void ensure_symbol_length(size_t len);
const std::vector<uint8_t>& materialize(const std::optional<std::vector<uint8_t>>& rhs) const;
static void scale_and_add_row(std::vector<uint8_t>& dst, const std::vector<uint8_t>& src, uint8_t factor);
void scale_and_add_rhs(std::optional<std::vector<uint8_t>>& dst, const std::optional<std::vector<uint8_t>>& src, uint8_t factor);
void scale_rhs_in_place(std::optional<std::vector<uint8_t>>& rhs, uint8_t factor);
int first_nonzero(const std::vector<uint8_t>& coeffs) const;

uint32_t _num_unknowns;
uint32_t _rank = 0;
size_t _symbol_length = 0;
bool _symbol_length_known = false;

std::vector<int> _row_of_pivot; // size num_unknowns, -1 if none
std::vector<Row> _rows;

mutable std::vector<uint8_t> _zero_scratch;
};

} // namespace RaptorQ
} // namespace LibFlute
Loading