mirror of
https://github.com/ossrs/srs.git
synced 2025-02-15 04:42:04 +00:00
fix https://github.com/ossrs/srs/issues/3155 Build srt-1-fit fails with `standard attributes in middle of decl-specifiers` on GCC 12,Arch Linux. See https://github.com/Haivision/srt/releases/tag/v1.5.3
2582 lines
94 KiB
C++
2582 lines
94 KiB
C++
/*
|
|
* SRT - Secure, Reliable, Transport
|
|
* Copyright (c) 2019 Haivision Systems Inc.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
*/
|
|
|
|
#include "platform_sys.h"
|
|
|
|
#include <string>
|
|
#include <map>
|
|
#include <vector>
|
|
#include <deque>
|
|
#include <iterator>
|
|
|
|
#include "packetfilter.h"
|
|
#include "core.h"
|
|
#include "packet.h"
|
|
#include "logging.h"
|
|
|
|
#include "fec.h"
|
|
|
|
// Maximum allowed "history" remembered in the receiver groups.
|
|
// This is calculated in series, that is, this number will be
|
|
// multiplied by sizeRow() and sizeCol() to get the value being
|
|
// a maximum distance between the FEC group base sequence and
|
|
// the sequence to which a request comes in.
|
|
|
|
// XXX Might be that this parameter should be configurable
|
|
#define SRT_FEC_MAX_RCV_HISTORY 10
|
|
|
|
using namespace std;
|
|
using namespace srt_logging;
|
|
|
|
namespace srt {
|
|
|
|
const char FECFilterBuiltin::defaultConfig [] = "fec,rows:1,layout:staircase,arq:onreq";
|
|
|
|
struct StringKeys
|
|
{
|
|
string operator()(const pair<const string, const string> item)
|
|
{
|
|
return item.first;
|
|
}
|
|
};
|
|
|
|
bool FECFilterBuiltin::verifyConfig(const SrtFilterConfig& cfg, string& w_error)
|
|
{
|
|
string arspec = map_get(cfg.parameters, "layout");
|
|
|
|
if (arspec != "" && arspec != "even" && arspec != "staircase")
|
|
{
|
|
w_error = "value for 'layout' must be 'even' or 'staircase'";
|
|
return false;
|
|
}
|
|
|
|
string colspec = map_get(cfg.parameters, "cols"), rowspec = map_get(cfg.parameters, "rows");
|
|
|
|
int out_rows = 1;
|
|
|
|
if (colspec != "")
|
|
{
|
|
int out_cols = atoi(colspec.c_str());
|
|
if (out_cols < 2)
|
|
{
|
|
w_error = "at least 'cols' must be specified and > 1";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (rowspec != "")
|
|
{
|
|
out_rows = atoi(rowspec.c_str());
|
|
if (out_rows >= -1 && out_rows < 1)
|
|
{
|
|
w_error = "'rows' must be >=1 or negative < -1";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Extra interpret level, if found, default never.
|
|
// Check only those that are managed.
|
|
string level = map_get(cfg.parameters, "arq");
|
|
if (level != "")
|
|
{
|
|
static const char* const levelnames [] = {"never", "onreq", "always"};
|
|
size_t i = 0;
|
|
for (i = 0; i < Size(levelnames); ++i)
|
|
{
|
|
if (strcmp(level.c_str(), levelnames[i]) == 0)
|
|
break;
|
|
}
|
|
|
|
if (i == Size(levelnames))
|
|
{
|
|
w_error = "'arq' value '" + level + "' invalid. Allowed: never, onreq, always";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
set<string> keys;
|
|
transform(cfg.parameters.begin(), cfg.parameters.end(), inserter(keys, keys.begin()), StringKeys());
|
|
|
|
// Delete all default parameters
|
|
SrtFilterConfig defconf;
|
|
ParseFilterConfig(defaultConfig, (defconf));
|
|
for (map<string,string>::const_iterator i = defconf.parameters.begin();
|
|
i != defconf.parameters.end(); ++i)
|
|
keys.erase(i->first);
|
|
|
|
// Delete mandatory parameters
|
|
keys.erase("cols");
|
|
|
|
if (!keys.empty())
|
|
{
|
|
w_error = "Extra parameters. Allowed only: cols, rows, layout, arq";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
FECFilterBuiltin::FECFilterBuiltin(const SrtFilterInitializer &init, std::vector<SrtPacket> &provided, const string &confstr)
|
|
: SrtPacketFilterBase(init)
|
|
, m_fallback_level(SRT_ARQ_ONREQ)
|
|
, m_arrangement_staircase(true)
|
|
, rcv(provided)
|
|
{
|
|
if (!ParseFilterConfig(confstr, cfg))
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
string ermsg;
|
|
if (!verifyConfig(cfg, (ermsg)))
|
|
{
|
|
LOGC(pflog.Error, log << "IPE: Filter config failed: " << ermsg);
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
// Configuration supported:
|
|
// - row only (number_rows == 1)
|
|
// - columns only, no row FEC/CTL (number_rows < -1)
|
|
// - columns and rows (both > 1)
|
|
|
|
// Disallowed configurations:
|
|
// - number_cols < 1
|
|
// - number_rows [-1, 0]
|
|
|
|
string arspec = map_get(cfg.parameters, "layout");
|
|
|
|
string shorter = arspec.size() > 5 ? arspec.substr(0, 5) : arspec;
|
|
if (shorter == "even")
|
|
m_arrangement_staircase = false;
|
|
|
|
string colspec = map_get(cfg.parameters, "cols"), rowspec = map_get(cfg.parameters, "rows");
|
|
|
|
if (colspec == "")
|
|
{
|
|
LOGC(pflog.Error, log << "FEC filter config: parameter 'cols' is mandatory");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
int out_rows = 1;
|
|
int out_cols = atoi(colspec.c_str());
|
|
|
|
m_number_cols = out_cols;
|
|
|
|
if (rowspec != "")
|
|
{
|
|
out_rows = atoi(rowspec.c_str());
|
|
}
|
|
|
|
if (out_rows < 0)
|
|
{
|
|
m_number_rows = -out_rows;
|
|
m_cols_only = true;
|
|
}
|
|
else
|
|
{
|
|
m_number_rows = out_rows;
|
|
m_cols_only = false;
|
|
}
|
|
|
|
// Extra interpret level, if found, default never.
|
|
// Check only those that are managed.
|
|
string level = cfg.parameters["arq"];
|
|
int lv = -1;
|
|
if (level != "")
|
|
{
|
|
static const char* levelnames [] = { "never", "onreq", "always" };
|
|
|
|
for (size_t i = 0; i < Size(levelnames); ++i)
|
|
{
|
|
if (level == levelnames[i])
|
|
{
|
|
lv = int(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (lv != -1)
|
|
{
|
|
m_fallback_level = SRT_ARQLevel(lv);
|
|
}
|
|
else
|
|
{
|
|
m_fallback_level = SRT_ARQ_ONREQ;
|
|
}
|
|
|
|
// Required to store in the header when rebuilding
|
|
rcv.id = socketID();
|
|
|
|
// Setup the bit matrix, initialize everything with false.
|
|
|
|
// Vertical size (y)
|
|
rcv.cells.resize(sizeCol() * sizeRow(), false);
|
|
|
|
// These sequence numbers are both the value of ISN-1 at the moment
|
|
// when the handshake is done. The sender ISN is generated here, the
|
|
// receiver ISN by the peer. Both should be known after the handshake.
|
|
// Later they will be updated as packets are transmitted.
|
|
|
|
int32_t snd_isn = CSeqNo::incseq(sndISN());
|
|
int32_t rcv_isn = CSeqNo::incseq(rcvISN());
|
|
|
|
// Alright, now we need to get the ISN from m_parent
|
|
// to extract the sequence number allowing qualification to the group.
|
|
// The base values must be prepared so that feedSource can qualify them.
|
|
|
|
// SEPARATE FOR SENDING AND RECEIVING!
|
|
|
|
// Now, assignment of the groups requires:
|
|
// For row groups, simply the size of the group suffices.
|
|
// For column groups, you need a whole matrix of all sequence
|
|
// numbers that are base sequence numbers for the group.
|
|
// Sequences that belong to this group are:
|
|
// 1. First packet has seq+1 towards the base.
|
|
// 2. Every next packet has this value + the size of the row group.
|
|
// So: group dispatching is:
|
|
// - get the column number
|
|
// - extract the group data for that column
|
|
// - check if the sequence is later than the group base sequence, if not, report no group for the packet
|
|
// - sanity check, if the seqdiff divided by row size gets 0 remainder
|
|
// - The result from the above division can't exceed the column size, otherwise
|
|
// it's another group. The number of currently collected data should be in 'collected'.
|
|
|
|
// Now set up the group starting sequences.
|
|
// The very first group in both dimensions will have the value of ISN in particular direction.
|
|
|
|
// Set up sender part.
|
|
//
|
|
// Size: rows
|
|
// Step: 1 (next packet in group is 1 past the previous one)
|
|
// Slip: rows (first packet in the next group is distant to first packet in the previous group by 'rows')
|
|
HLOGC(pflog.Debug, log << "FEC: INIT: ISN { snd=" << snd_isn << " rcv=" << rcv_isn << " }; sender single row");
|
|
ConfigureGroup(snd.row, snd_isn, 1, sizeRow());
|
|
|
|
// In the beginning we need just one reception group. New reception
|
|
// groups will be created in tact with receiving packets outside this one.
|
|
// The value of rcv.row[0].base will be used as an absolute base for calculating
|
|
// the index of the group for a given received packet.
|
|
rcv.rowq.resize(1);
|
|
HLOGP(pflog.Debug, "FEC: INIT: receiver first row");
|
|
ConfigureGroup(rcv.rowq[0], rcv_isn, 1, sizeRow());
|
|
|
|
if (sizeCol() > 1)
|
|
{
|
|
// Size: cols
|
|
// Step: rows (the next packet in the group is one row later)
|
|
// Slip: rows+1 (the first packet in the next group is later by 1 column + one whole row down)
|
|
|
|
HLOGP(pflog.Debug, "FEC: INIT: sender first N columns");
|
|
ConfigureColumns(snd.cols, snd_isn);
|
|
HLOGP(pflog.Debug, "FEC: INIT: receiver first N columns");
|
|
ConfigureColumns(rcv.colq, rcv_isn);
|
|
}
|
|
|
|
// The bit markers that mark the received/lost packets will be expanded
|
|
// as packets come in.
|
|
rcv.cell_base = rcv_isn;
|
|
}
|
|
|
|
template <class Container>
|
|
void FECFilterBuiltin::ConfigureColumns(Container& which, int32_t isn)
|
|
{
|
|
// This is to initialize the first set of groups.
|
|
|
|
// which: group vector.
|
|
// numberCols(): number of packets in one group
|
|
// sizeCol(): seqdiff between two packets consecutive in the group
|
|
// m_column_slip: seqdiff between the first packet in one group and first packet in the next group
|
|
// isn: sequence number of the first packet in the first group
|
|
|
|
size_t zero = which.size();
|
|
|
|
// The first series of initialization should embrace:
|
|
// - if multiplyer == 1, EVERYTHING (also the case of SOLID matrix)
|
|
// - if more, ONLY THE FIRST SQUARE.
|
|
which.resize(zero + numberCols());
|
|
|
|
if (!m_arrangement_staircase)
|
|
{
|
|
HLOGC(pflog.Debug, log << "ConfigureColumns: new "
|
|
<< numberCols() << " columns, START AT: " << zero);
|
|
// With even arrangement, just use a plain loop.
|
|
// Initialize straight way all groups in the size.
|
|
int32_t seqno = isn;
|
|
for (size_t i = zero; i < which.size(); ++i)
|
|
{
|
|
// ARGS:
|
|
// - seqno: sequence number of the first packet in the group
|
|
// - step: distance between two consecutive packets in the group
|
|
// - drop: distance between base sequence numbers in groups in consecutive series
|
|
// (meaning: with row size 6, group with index 2 and 8 are in the
|
|
// same column 2, lying in 0 and 1 series respectively).
|
|
ConfigureGroup(which[i], seqno, sizeRow(), sizeCol() * numberCols());
|
|
seqno = CSeqNo::incseq(seqno);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// With staircase, the next column's base sequence is
|
|
// shifted by 1 AND the length of the row. When this shift
|
|
// becomes below the column 0 bottom, reset it to the row 0
|
|
// and continue.
|
|
|
|
// Start here. The 'isn' is still the absolute base sequence value.
|
|
size_t offset = 0;
|
|
|
|
HLOGC(pflog.Debug, log << "ConfigureColumns: " << (which.size() - zero)
|
|
<< " columns, START AT: " << zero);
|
|
|
|
for (size_t i = zero; i < which.size(); ++i)
|
|
{
|
|
int32_t seq = CSeqNo::incseq(isn, int(offset));
|
|
size_t col = i - zero;
|
|
|
|
HLOGC(pflog.Debug, log << "ConfigureColumns: [" << col << "]: -> ConfigureGroup...");
|
|
ConfigureGroup(which[i], seq, sizeRow(), sizeCol() * numberCols());
|
|
|
|
if (col % numberRows() == numberRows() - 1)
|
|
{
|
|
offset = col + 1; // +1 because we want it for the next column
|
|
HLOGC(pflog.Debug, log << "ConfigureColumns: [" << (col+1) << "]... (resetting to row 0: +"
|
|
<< offset << " %" << CSeqNo::incseq(isn, (int32_t)offset) << ")");
|
|
}
|
|
else
|
|
{
|
|
offset += 1 + sizeRow();
|
|
HLOGC(pflog.Debug, log << "ConfigureColumns: [" << (col+1) << "] ... (continue +"
|
|
<< offset << " %" << CSeqNo::incseq(isn, (int32_t)offset) << ")");
|
|
}
|
|
}
|
|
}
|
|
|
|
void FECFilterBuiltin::ConfigureGroup(Group& g, int32_t seqno, size_t gstep, size_t drop)
|
|
{
|
|
g.base = seqno;
|
|
g.step = gstep;
|
|
|
|
// This actually rewrites the size of the group here, but
|
|
// by having this value precalculated we simply close the
|
|
// group by adding this value to the base sequence.
|
|
g.drop = drop;
|
|
g.collected = 0;
|
|
|
|
// Now the buffer spaces for clips.
|
|
g.payload_clip.resize(payloadSize());
|
|
g.length_clip = 0;
|
|
g.flag_clip = 0;
|
|
g.timestamp_clip = 0;
|
|
|
|
HLOGC(pflog.Debug, log << "FEC: ConfigureGroup: base %" << seqno << " step=" << gstep << " drop=" << drop);
|
|
|
|
// Preallocate the buffer that will be used for storing it for
|
|
// the needs of passing the data through the network.
|
|
// This will be filled with zeros initially, which is unnecessary,
|
|
// but it happeens just once after connection.
|
|
}
|
|
|
|
|
|
void FECFilterBuiltin::ResetGroup(Group& g)
|
|
{
|
|
const int32_t new_seq_base = CSeqNo::incseq(g.base, int(g.drop));
|
|
|
|
HLOGC(pflog.Debug, log << "FEC: ResetGroup (step=" << g.step << "): base %" << g.base << " -> %" << new_seq_base);
|
|
|
|
g.base = new_seq_base;
|
|
g.collected = 0;
|
|
|
|
// This isn't necessary for ConfigureGroup because the
|
|
// vector after resizing is filled with a given value,
|
|
// by default the default value of the type, char(), that is 0.
|
|
g.length_clip = 0;
|
|
g.flag_clip = 0;
|
|
g.timestamp_clip = 0;
|
|
memset(&g.payload_clip[0], 0, g.payload_clip.size());
|
|
}
|
|
|
|
void FECFilterBuiltin::feedSource(CPacket& packet)
|
|
{
|
|
// Hang on the matrix. Find by packet->getSeqNo().
|
|
|
|
// (The "absolute base" is the cell 0 in vertical groups)
|
|
int32_t base = snd.row.base;
|
|
|
|
// (we are guaranteed that this packet is a data packet, so
|
|
// we don't have to check if this isn't a control packet)
|
|
int baseoff = CSeqNo::seqoff(base, packet.getSeqNo());
|
|
|
|
int horiz_pos = baseoff;
|
|
|
|
if (CheckGroupClose(snd.row, horiz_pos, sizeRow()))
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC:... HORIZ group closed, B=%" << snd.row.base);
|
|
}
|
|
ClipPacket(snd.row, packet);
|
|
snd.row.collected++;
|
|
|
|
// Don't do any column feeding if using column size 1
|
|
if (sizeCol() < 2)
|
|
{
|
|
// The above logging instruction in case of no columns
|
|
HLOGC(pflog.Debug, log << "FEC:feedSource: %" << packet.getSeqNo()
|
|
<< " B:%" << baseoff << " H:*[" << horiz_pos << "]"
|
|
<< " size=" << packet.size()
|
|
<< " TS=" << packet.getMsgTimeStamp()
|
|
<< " !" << BufferStamp(packet.data(), packet.size()));
|
|
HLOGC(pflog.Debug, log << "FEC collected: H: " << snd.row.collected);
|
|
return;
|
|
}
|
|
|
|
// 1. Get the number of group in both vertical and horizontal groups:
|
|
// - Vertical: offset towards base (% row size, but with updated Base seq unnecessary)
|
|
// (Just for a case).
|
|
int vert_gx = baseoff % sizeRow();
|
|
|
|
// 2. Define the position of this packet in the group
|
|
// - Horizontal: offset towards base (of the given group, not absolute!)
|
|
// - Vertical: (seq-base)/column_size
|
|
int32_t vert_base = snd.cols[vert_gx].base;
|
|
int vert_off = CSeqNo::seqoff(vert_base, packet.getSeqNo());
|
|
|
|
// It MAY HAPPEN that the base is newer than the sequence of the packet.
|
|
// This may normally happen in the beginning period, where the bases
|
|
// set up initially for all columns got the shift, so they are kinda from
|
|
// the future, and "this sequence" is in a group that is already closed.
|
|
// In this case simply can't clip the packet in the column group.
|
|
|
|
HLOGC(pflog.Debug, log << "FEC:feedSource: %" << packet.getSeqNo() << " rowoff=" << baseoff
|
|
<< " column=" << vert_gx << " .base=%" << vert_base << " coloff=" << vert_off);
|
|
|
|
// [[assert sizeCol() >= 2]]; // see the condition above.
|
|
|
|
if (vert_off >= 0)
|
|
{
|
|
// BEWARE! X % Y with different signedness upgrades int to unsigned!
|
|
|
|
// SANITY: check if the rule applies on the group
|
|
if (vert_off % sizeRow())
|
|
{
|
|
LOGC(pflog.Fatal, log << "FEC:feedSource: IPE: VGroup #" << vert_gx << " base=%" << vert_base
|
|
<< " WRONG with horiz base=%" << base << "coloff(" << vert_off
|
|
<< ") % sizeRow(" << sizeRow() << ") = " << (vert_off % sizeRow()));
|
|
|
|
// Do not place it, it would be wrong.
|
|
return;
|
|
}
|
|
|
|
// [[assert vert_off >= 0]]; // this condition branch
|
|
int vert_pos = vert_off / int(sizeRow());
|
|
|
|
HLOGC(pflog.Debug, log << "FEC:feedSource: %" << packet.getSeqNo()
|
|
<< " B:%" << baseoff << " H:*[" << horiz_pos << "] V(B=%" << vert_base
|
|
<< ")[col=" << vert_gx << "][" << vert_pos << "/" << sizeCol() << "] "
|
|
<< " size=" << packet.size()
|
|
<< " TS=" << packet.getMsgTimeStamp()
|
|
<< " !" << BufferStamp(packet.data(), packet.size()));
|
|
|
|
// 3. The group should be check for the necessity of being closed.
|
|
// Note that FEC packet extraction doesn't change the state of the
|
|
// VERTICAL groups (it can be potentially extracted multiple times),
|
|
// only the horizontal in order to mark that the vertical FEC is
|
|
// extracted already. So, anyway, check if the group limit was reached
|
|
// and it wasn't closed.
|
|
// 4. Apply the clip
|
|
// 5. Increase collected.
|
|
|
|
if (CheckGroupClose(snd.cols[vert_gx], vert_pos, sizeCol()))
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC:... VERT group closed, B=%" << snd.cols[vert_gx].base);
|
|
}
|
|
ClipPacket(snd.cols[vert_gx], packet);
|
|
snd.cols[vert_gx].collected++;
|
|
}
|
|
else
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC:feedSource: %" << packet.getSeqNo()
|
|
<< " B:%" << baseoff << " H:*[" << horiz_pos << "] V(B=%" << vert_base
|
|
<< ")[col=" << vert_gx << "]<NO-COLUMN>"
|
|
<< " size=" << packet.size()
|
|
<< " TS=" << packet.getMsgTimeStamp()
|
|
<< " !" << BufferStamp(packet.data(), packet.size()));
|
|
}
|
|
HLOGC(pflog.Debug, log << "FEC collected: H: " << snd.row.collected << " V[" << vert_gx << "]: " << snd.cols[vert_gx].collected);
|
|
}
|
|
|
|
bool FECFilterBuiltin::CheckGroupClose(Group& g, size_t pos, size_t size)
|
|
{
|
|
if (pos < size)
|
|
return false;
|
|
|
|
ResetGroup(g);
|
|
return true;
|
|
}
|
|
|
|
void FECFilterBuiltin::ClipPacket(Group& g, const CPacket& pkt)
|
|
{
|
|
// Both length and timestamp must be taken as NETWORK ORDER
|
|
// before applying the clip.
|
|
|
|
uint16_t length_net = htons(uint16_t(pkt.size()));
|
|
uint8_t kflg = uint8_t(pkt.getMsgCryptoFlags());
|
|
|
|
// NOTE: Unlike length, the TIMESTAMP is NOT endian-reordered
|
|
// because it will be written into the TIMESTAMP field in the
|
|
// header, and header is inverted automatically when sending,
|
|
// unlike the contents of the payload, where the length will be written.
|
|
uint32_t timestamp_hw = pkt.getMsgTimeStamp();
|
|
|
|
ClipData(g, length_net, kflg, timestamp_hw, pkt.data(), pkt.size());
|
|
|
|
HLOGC(pflog.Debug, log << "FEC DATA PKT CLIP: " << hex
|
|
<< "FLAGS=" << unsigned(kflg) << " LENGTH[ne]=" << (length_net)
|
|
<< " TS[he]=" << timestamp_hw
|
|
<< " CLIP STATE: FLAGS=" << unsigned(g.flag_clip)
|
|
<< " LENGTH[ne]=" << g.length_clip
|
|
<< " TS[he]=" << g.timestamp_clip
|
|
<< " PL4=" << (*(uint32_t*)&g.payload_clip[0]));
|
|
}
|
|
|
|
// Clipping a control packet does merely the same, just the packet has
|
|
// different contents, so it must be differetly interpreted.
|
|
void FECFilterBuiltin::ClipControlPacket(Group& g, const CPacket& pkt)
|
|
{
|
|
// Both length and timestamp must be taken as NETWORK ORDER
|
|
// before applying the clip.
|
|
|
|
const char* fec_header = pkt.data();
|
|
const char* payload = fec_header + 4;
|
|
size_t payload_clip_len = pkt.size() - 4;
|
|
|
|
const uint8_t* flag_clip = (const uint8_t*)(fec_header + 1);
|
|
const uint16_t* length_clip = (const uint16_t*)(fec_header + 2);
|
|
|
|
uint32_t timestamp_hw = pkt.getMsgTimeStamp();
|
|
|
|
ClipData(g, *length_clip, *flag_clip, timestamp_hw, payload, payload_clip_len);
|
|
|
|
HLOGC(pflog.Debug, log << "FEC/CTL CLIP: " << hex
|
|
<< "FLAGS=" << unsigned(*flag_clip) << " LENGTH[ne]=" << (*length_clip)
|
|
<< " TS[he]=" << timestamp_hw
|
|
<< " CLIP STATE: FLAGS=" << unsigned(g.flag_clip)
|
|
<< " LENGTH[ne]=" << g.length_clip
|
|
<< " TS[he]=" << g.timestamp_clip
|
|
<< " PL4=" << (*(uint32_t*)&g.payload_clip[0]));
|
|
}
|
|
|
|
void FECFilterBuiltin::ClipRebuiltPacket(Group& g, Receive::PrivPacket& pkt)
|
|
{
|
|
uint16_t length_net = htons(uint16_t(pkt.length));
|
|
uint8_t kflg = MSGNO_ENCKEYSPEC::unwrap(pkt.hdr[SRT_PH_MSGNO]);
|
|
|
|
// NOTE: Unlike length, the TIMESTAMP is NOT endian-reordered
|
|
// because it will be written into the TIMESTAMP field in the
|
|
// header, and header is inverted automatically when sending,
|
|
// unlike the contents of the payload, where the length will be written.
|
|
uint32_t timestamp_hw = pkt.hdr[SRT_PH_TIMESTAMP];
|
|
|
|
ClipData(g, length_net, kflg, timestamp_hw, pkt.buffer, pkt.length);
|
|
|
|
HLOGC(pflog.Debug, log << "FEC REBUILT DATA CLIP: " << hex
|
|
<< "FLAGS=" << unsigned(kflg) << " LENGTH[ne]=" << (length_net)
|
|
<< " TS[he]=" << timestamp_hw
|
|
<< " CLIP STATE: FLAGS=" << unsigned(g.flag_clip)
|
|
<< " LENGTH[ne]=" << g.length_clip
|
|
<< " TS[he]=" << g.timestamp_clip
|
|
<< " PL4=" << (*(uint32_t*)&g.payload_clip[0]));
|
|
}
|
|
|
|
void FECFilterBuiltin::ClipData(Group& g, uint16_t length_net, uint8_t kflg,
|
|
uint32_t timestamp_hw, const char* payload, size_t payload_size)
|
|
{
|
|
g.length_clip = g.length_clip ^ length_net;
|
|
g.flag_clip = g.flag_clip ^ kflg;
|
|
g.timestamp_clip = g.timestamp_clip ^ timestamp_hw;
|
|
|
|
// Payload goes "as is".
|
|
for (size_t i = 0; i < payload_size; ++i)
|
|
{
|
|
g.payload_clip[i] = g.payload_clip[i] ^ payload[i];
|
|
}
|
|
|
|
// Fill the rest with zeros. When this packet is going to be
|
|
// recovered, the payload extracted from this process will have
|
|
// the maximum length, but it will be cut to the right length
|
|
// and these padding 0s taken out.
|
|
for (size_t i = payload_size; i < payloadSize(); ++i)
|
|
g.payload_clip[i] = g.payload_clip[i] ^ 0;
|
|
}
|
|
|
|
bool FECFilterBuiltin::packControlPacket(SrtPacket& rpkt, int32_t seq)
|
|
{
|
|
// If the FEC packet is not yet ready for extraction, do nothing and return false.
|
|
// Check if seq is the last sequence of the group.
|
|
|
|
// Check VERTICAL group first, then HORIZONTAL.
|
|
//
|
|
// This is because when it happens that HORIZONTAL group is to be
|
|
// FEC-CTL reported, it also shifts the base to the next row, whereas
|
|
// this base sequence is used to determine the column index that is
|
|
// needed to reach the right column group and it must stay unupdated
|
|
// until the last packet in this row is checked for VERTICAL groups.
|
|
|
|
// If it's ready for extraction, extract it, and write into the packet.
|
|
//
|
|
// NOTE: seq is the sequence number of the LAST PACKET SENT regularly.
|
|
// This is only about to be shifted forward by 1 to be placed on the
|
|
// data packet. The packet in `r_packet` doesn't have the sequence number
|
|
// installed yet
|
|
|
|
// For BOTH vertical and horizontal snd groups:
|
|
// - Check if the "full group" condition is satisfied (all packets from the group are clipped)
|
|
// - If not, simply return false and do nothing
|
|
// - If so, store the current clip state into the referenced packet, give it the 'seq' sequence
|
|
|
|
// After packing the FEC packet:
|
|
// - update the base sequence in the group for which it's packed
|
|
// - make sure that pointers are reset to not suggest the packet is ready
|
|
|
|
// Handle the special case of m_number_rows == 1, which
|
|
// means we don't use columns.
|
|
if (m_number_rows <= 1)
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/CTL not checking VERT group - rows only config");
|
|
// PASS ON to Horizontal group check
|
|
}
|
|
else
|
|
{
|
|
int offset_to_row_base = CSeqNo::seqoff(snd.row.base, seq);
|
|
int vert_gx = (offset_to_row_base + int(m_number_cols)) % int(m_number_cols);
|
|
|
|
// This can actually happen only for the very first sent packet.
|
|
// It looks like "following the last packet from the previous group",
|
|
// however there was no previous group because this is the first packet.
|
|
if (offset_to_row_base < 0)
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/CTL not checking VERT group [" << vert_gx << "] - negative offset_to_row_base %"
|
|
<< snd.row.base << " -> %" << seq << " (" << offset_to_row_base
|
|
<< ") (collected " << snd.cols[abs(vert_gx)].collected << "/" << sizeCol() << ")");
|
|
// PASS ON to Horizontal group check
|
|
}
|
|
else
|
|
{
|
|
if (snd.cols[vert_gx].collected >= m_number_rows)
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/CTL ready for VERT group [" << vert_gx << "]: %" << seq
|
|
<< " (base %" << snd.cols[vert_gx].base << ")");
|
|
// SHIP THE VERTICAL FEC packet.
|
|
PackControl(snd.cols[vert_gx], vert_gx, rpkt, seq);
|
|
|
|
// RESET THE GROUP THAT WAS SENT
|
|
ResetGroup(snd.cols[vert_gx]);
|
|
return true;
|
|
}
|
|
|
|
HLOGC(pflog.Debug, log << "FEC/CTL NOT ready for VERT group [" << vert_gx << "]: %" << seq
|
|
<< " (base %" << snd.cols[vert_gx].base << ")"
|
|
<< " - collected " << snd.cols[vert_gx].collected << "/" << m_number_rows);
|
|
}
|
|
}
|
|
|
|
if (snd.row.collected >= m_number_cols)
|
|
{
|
|
if (!m_cols_only)
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/CTL ready for HORIZ group: %" << seq << " (base %" << snd.row.base << ")");
|
|
// SHIP THE HORIZONTAL FEC packet.
|
|
PackControl(snd.row, -1, rpkt, seq);
|
|
|
|
HLOGC(pflog.Debug, log << "...PACKET size=" << rpkt.length
|
|
<< " TS=" << rpkt.hdr[SRT_PH_TIMESTAMP]
|
|
<< " !" << BufferStamp(rpkt.buffer, rpkt.length));
|
|
|
|
}
|
|
|
|
// RESET THE HORIZONTAL GROUP.
|
|
// ALWAYS, even in columns-only.
|
|
ResetGroup(snd.row);
|
|
|
|
if (!m_cols_only)
|
|
{
|
|
// In columns-only you didn't pack anything, so check
|
|
// for column control.
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/CTL NOT ready for HORIZ group: %" << seq
|
|
<< " (base %" << snd.row.base << ")"
|
|
<< " - collected " << snd.row.collected << "/" << m_number_cols);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FECFilterBuiltin::PackControl(const Group& g, signed char index, SrtPacket& pkt, int32_t seq)
|
|
{
|
|
// Allocate as much space as needed, regardless of the PAYLOADSIZE value.
|
|
|
|
static const size_t INDEX_SIZE = 1;
|
|
|
|
size_t total_size =
|
|
INDEX_SIZE
|
|
+ sizeof(g.flag_clip)
|
|
+ sizeof(g.length_clip)
|
|
+ g.payload_clip.size();
|
|
|
|
// Sanity
|
|
#if ENABLE_DEBUG
|
|
if (g.output_buffer.size() < total_size)
|
|
{
|
|
LOGC(pflog.Fatal, log << "OUTPUT BUFFER TOO SMALL!");
|
|
abort();
|
|
}
|
|
#endif
|
|
|
|
char* out = pkt.buffer;
|
|
size_t off = 0;
|
|
// Spread the index. This is the index of the payload in the vertical group.
|
|
// For horizontal group this value is always -1.
|
|
out[off++] = index;
|
|
// Flags, currently only the encryption flags
|
|
out[off++] = g.flag_clip;
|
|
|
|
// Ok, now the length clip
|
|
memcpy((out + off), &g.length_clip, sizeof g.length_clip);
|
|
off += sizeof g.length_clip;
|
|
|
|
// And finally the payload clip
|
|
memcpy((out + off), &g.payload_clip[0], g.payload_clip.size());
|
|
|
|
// Ready. Now fill the header and finalize other data.
|
|
pkt.length = total_size;
|
|
|
|
pkt.hdr[SRT_PH_TIMESTAMP] = g.timestamp_clip;
|
|
pkt.hdr[SRT_PH_SEQNO] = seq;
|
|
|
|
HLOGC(pflog.Debug, log << "FEC: PackControl: hdr("
|
|
<< (total_size - g.payload_clip.size()) << "): INDEX="
|
|
<< int(index) << " LENGTH[ne]=" << hex << g.length_clip
|
|
<< " FLAGS=" << int(g.flag_clip) << " TS=" << g.timestamp_clip
|
|
<< " PL(" << dec << g.payload_clip.size() << ")[0-4]=" << hex
|
|
<< (*(uint32_t*)&g.payload_clip[0]));
|
|
|
|
}
|
|
|
|
bool FECFilterBuiltin::receive(const CPacket& rpkt, loss_seqs_t& loss_seqs)
|
|
{
|
|
// Add this packet to the group where it belongs.
|
|
// Light up the cell of this packet to mark it received.
|
|
// Check if any of the groups to which the packet belongs
|
|
// have changed the status into RECOVERABLE.
|
|
//
|
|
// The group has RECOVERABLE status when it has FEC
|
|
// packet received and the number of collected packets counts
|
|
// exactly group_size - 1.
|
|
|
|
bool want_packet = false;
|
|
|
|
struct IsFec
|
|
{
|
|
bool row;
|
|
bool col;
|
|
signed char colx;
|
|
} isfec = { false, false, -1 };
|
|
|
|
// The sequence number must be checked prematurely, or it can otherwise
|
|
// cause large resource allocation. This might be even survived, provided
|
|
// that this will make the packet seen as exceeding the series 0 matrix,
|
|
// so all matrices in previous series should be dismissed thereafter. But
|
|
// this short living resource spike may be destructive, so let's do
|
|
// matrix dismissal FIRST before this packet is going to be handled.
|
|
CheckLargeDrop(rpkt.getSeqNo());
|
|
|
|
if (rpkt.getMsgSeq() == SRT_MSGNO_CONTROL)
|
|
{
|
|
// Interpret the first byte of the contents.
|
|
const char* payload = rpkt.data();
|
|
isfec.colx = payload[0];
|
|
if (isfec.colx == -1)
|
|
{
|
|
isfec.row = true;
|
|
}
|
|
else
|
|
{
|
|
isfec.col = true;
|
|
}
|
|
|
|
HLOGC(pflog.Debug, log << "FEC: RECEIVED %" << rpkt.getSeqNo() << " msgno=0, FEC/CTL packet. INDEX=" << int(payload[0]));
|
|
|
|
// This marks the cell as NOT received, but still does extend the
|
|
// cell container up to this sequence. The HangHorizontal and HangVertical
|
|
// functions that would also do cell dismissal, RELY ON IT.
|
|
MarkCellReceived(rpkt.getSeqNo(), CELL_EXTEND);
|
|
}
|
|
else
|
|
{
|
|
// Data packet, check if this packet was already received.
|
|
// If so, ignore it. This may happen if you have configured
|
|
// FEC and ARQ to cooperate, so a packet once rebuilt might
|
|
// be simultaneously also retransmitted. This may confuse the tables.
|
|
int celloff = CSeqNo::seqoff(rcv.cell_base, rpkt.getSeqNo());
|
|
bool past = celloff < 0;
|
|
bool exists = celloff < int(rcv.cells.size()) && !past && rcv.cells[celloff];
|
|
|
|
if (past || exists)
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC: packet %" << rpkt.getSeqNo() << " "
|
|
<< (past ? "in the PAST" : "already known") << ", IGNORING.");
|
|
|
|
return true;
|
|
}
|
|
|
|
want_packet = true;
|
|
|
|
HLOGC(pflog.Debug, log << "FEC: RECEIVED %" << rpkt.getSeqNo() << " msgno=" << rpkt.getMsgSeq() << " DATA PACKET.");
|
|
MarkCellReceived(rpkt.getSeqNo());
|
|
|
|
// Remember this simply every time a packet comes in. In live mode usually
|
|
// this flag is ORD_RELAXED (false), but some earlier versions used ORD_REQUIRED.
|
|
// Even though this flag is now usually ORD_RELAXED, it's fate in live mode
|
|
// isn't completely decided yet, so stay flexible. We believe at least that this
|
|
// flag will stay unchanged during whole connection.
|
|
rcv.order_required = rpkt.getMsgOrderFlag();
|
|
}
|
|
|
|
loss_seqs_t irrecover_row, irrecover_col;
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
static string hangname [] = {"SUCCESS", "PAST", "CRAZY", "NOT-DONE"};
|
|
#endif
|
|
|
|
// Required for EHangStatus
|
|
using namespace std::rel_ops;
|
|
|
|
EHangStatus okh = HANG_NOTDONE;
|
|
if (!isfec.col) // == regular packet or FEC/ROW
|
|
{
|
|
// Don't manage this packet for horizontal group,
|
|
// if it was a vertical FEC/CTL packet.
|
|
okh = HangHorizontal(rpkt, isfec.row, irrecover_row);
|
|
HLOGC(pflog.Debug, log << "FEC: HangHorizontal %" << rpkt.getSeqNo()
|
|
<< " msgno=" << rpkt.getMsgSeq()
|
|
<< " RESULT=" << hangname[okh] << " IRRECOVERABLE: " << Printable(irrecover_row));
|
|
}
|
|
|
|
if (okh > HANG_SUCCESS)
|
|
{
|
|
// Just informative.
|
|
LOGC(pflog.Warn, log << "FEC/H: rebuilding/hanging FAILED.");
|
|
}
|
|
|
|
EHangStatus okv = HANG_NOTDONE;
|
|
// Don't do HangVertical in case of row-only configuration
|
|
if (!isfec.row && m_number_rows > 1) // == regular packet or FEC/COL
|
|
{
|
|
// NOTE FOR IPE REPORTING:
|
|
// It is allowed that
|
|
// - Both HangVertical and HangHorizontal
|
|
|
|
okv = HangVertical(rpkt, isfec.colx, irrecover_col);
|
|
IF_HEAVY_LOGGING(bool discrep = (okv == HANG_CRAZY) ? int(okh) < HANG_CRAZY : false);
|
|
HLOGC(pflog.Debug, log << "FEC: HangVertical %" << rpkt.getSeqNo()
|
|
<< " msgno=" << rpkt.getMsgSeq()
|
|
<< " RESULT=" << hangname[okh]
|
|
<< (discrep ? " IPE: H successul and V failed!" : "")
|
|
<< " IRRECOVERABLE: " << Printable(irrecover_col));
|
|
}
|
|
|
|
if (okv > HANG_SUCCESS)
|
|
{
|
|
// Just informative.
|
|
LOGC(pflog.Warn, log << "FEC/V: rebuilding/hanging FAILED.");
|
|
}
|
|
|
|
if (okv == HANG_CRAZY || okh == HANG_CRAZY)
|
|
{
|
|
// Mark the cell not received, if it was rejected by the
|
|
// FEC group facility, otherwise it will deny to try to rebuild an
|
|
// allegedly existing packet.
|
|
MarkCellReceived(rpkt.getSeqNo(), CELL_REMOVE);
|
|
}
|
|
|
|
// Pack the following packets as irrecoverable:
|
|
if (m_fallback_level == SRT_ARQ_ONREQ)
|
|
{
|
|
// Use irrecover_row with rows only because there is
|
|
// never anything collected in irrecover_col.
|
|
if (m_number_rows == 1)
|
|
loss_seqs = irrecover_row;
|
|
else
|
|
loss_seqs = irrecover_col;
|
|
}
|
|
|
|
return want_packet;
|
|
// Get the packet from the incoming stream, already recognized
|
|
// as data packet, and then:
|
|
//
|
|
// (Note that the default builtin FEC mechanism uses such rules:
|
|
// - allows SRT to get the packet, even if it follows the loss
|
|
// - depending on m_fallback_level, confirms or denies the need that SRT handle the loss
|
|
// - in loss_seqs we return those that are not recoverable at the current level
|
|
// - FEC has no extra header provided, so regular data are passed as is
|
|
//)
|
|
// So, the needs to implement:
|
|
//
|
|
// 1. If this is a FEC packet, close the group, check for lost packets, try to recover.
|
|
// Check if there is recovery possible, if so, request a new unit and pack the recovered packet there.
|
|
// Report the loss to be reported by SRT according to m_fallback_level:
|
|
// - ARQ_ALWAYS: N/A for a FEC packet
|
|
// - ARQ_EARLY: When Horizontal group is closed and the packet is not recoverable, report this in loss_seqs
|
|
// - ARQ_LATELY: When Horizontal and Vertical group is closed and the packet is not recoverable, report it.
|
|
// - ARQ_NEVER: Always return empty loss_seqs
|
|
//
|
|
// 2. If this is a regular packet, use it for building the FEC group.
|
|
// - ARQ_ALWAYS: always return true and leave loss_seqs empty.
|
|
// - others: return false and return nothing in loss_seqs
|
|
}
|
|
|
|
void FECFilterBuiltin::CheckLargeDrop(int32_t seqno)
|
|
{
|
|
// Ok, first try to pick up the column and series
|
|
|
|
int offset = CSeqNo::seqoff(rcv.rowq[0].base, seqno);
|
|
if (offset < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// For row-only configuration, check only parts referring
|
|
// to a row.
|
|
if (m_number_rows == 1)
|
|
{
|
|
// We have no columns. So just check if exceeds 5* the row size.
|
|
// If so, clear the rows and reconfigure them.
|
|
if (offset > int(5 * sizeRow()))
|
|
{
|
|
// Calculate the new row base, without breaking the current
|
|
// layout. Make a skip by some number of rows so that the new
|
|
// first row is prepared to receive this packet.
|
|
|
|
int32_t oldbase = rcv.rowq[0].base;
|
|
size_t rowdist = offset / sizeRow();
|
|
int32_t newbase = CSeqNo::incseq(oldbase, int(rowdist * sizeRow()));
|
|
|
|
LOGC(pflog.Warn, log << "FEC: LARGE DROP detected! Resetting row groups. Base: %" << oldbase
|
|
<< " -> %" << newbase << "(shift by " << CSeqNo::seqoff(oldbase, newbase) << ")");
|
|
|
|
rcv.rowq.clear();
|
|
rcv.cells.clear();
|
|
|
|
rcv.rowq.resize(1);
|
|
HLOGP(pflog.Debug, "FEC: RE-INIT: receiver first row");
|
|
ConfigureGroup(rcv.rowq[0], newbase, 1, sizeRow());
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
bool reset_anyway = false;
|
|
if (offset != CSeqNo::seqoff(rcv.colq[0].base, seqno))
|
|
{
|
|
reset_anyway = true;
|
|
HLOGC(pflog.Debug, log << "FEC: IPE: row.base %" << rcv.rowq[0].base << " != %" << rcv.colq[0].base << " - resetting");
|
|
}
|
|
|
|
// Number of column - regardless of series.
|
|
int colx = offset % numberCols();
|
|
|
|
// Base sequence from the group series 0 in this column
|
|
|
|
// [[assert rcv.colq.size() >= numberCols()]];
|
|
int32_t colbase = rcv.colq[colx].base;
|
|
|
|
// Offset between this base and seqno
|
|
int coloff = CSeqNo::seqoff(colbase, seqno);
|
|
|
|
// Might be that it's in the row above the column,
|
|
// still it's not a large-drop
|
|
if (coloff < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const size_t size_in_packets = colx * numberRows();
|
|
const size_t matrix = numberRows() * numberCols();
|
|
|
|
const int colseries = coloff / int(matrix);
|
|
|
|
if (size_in_packets > rcvBufferSize()/2 || colseries > SRT_FEC_MAX_RCV_HISTORY || reset_anyway)
|
|
{
|
|
// Ok, now define the new ABSOLUTE BASE. This is the base of the column 0
|
|
// column group from the series previous towards this one.
|
|
int32_t oldbase = rcv.colq[0].base;
|
|
int32_t newbase = CSeqNo::incseq(oldbase, (colseries-1) * int(matrix));
|
|
|
|
LOGC(pflog.Warn, log << "FEC: LARGE DROP detected! Resetting all groups. Base: %" << oldbase
|
|
<< " -> %" << newbase << "(shift by " << CSeqNo::seqoff(oldbase, newbase) << ")");
|
|
|
|
rcv.rowq.clear();
|
|
rcv.colq.clear();
|
|
rcv.cells.clear();
|
|
|
|
rcv.rowq.resize(1);
|
|
HLOGP(pflog.Debug, "FEC: RE-INIT: receiver first row");
|
|
ConfigureGroup(rcv.rowq[0], newbase, 1, sizeRow());
|
|
|
|
// Size: cols
|
|
// Step: rows (the next packet in the group is one row later)
|
|
// Slip: rows+1 (the first packet in the next group is later by 1 column + one whole row down)
|
|
HLOGP(pflog.Debug, "FEC: RE-INIT: receiver first N columns");
|
|
ConfigureColumns(rcv.colq, newbase);
|
|
|
|
rcv.cell_base = newbase;
|
|
}
|
|
}
|
|
|
|
void FECFilterBuiltin::CollectIrrecoverRow(RcvGroup& g, loss_seqs_t& irrecover) const
|
|
{
|
|
if (g.dismissed)
|
|
return; // already collected
|
|
|
|
// Obtain the group's packet shift
|
|
|
|
int32_t base = rcv.cell_base;
|
|
int offset = CSeqNo::seqoff(base, g.base);
|
|
if (offset < 0)
|
|
{
|
|
LOGC(pflog.Error, log << "FEC: IPE: row base %" << g.base << " is PAST to cell base %" << base);
|
|
return;
|
|
}
|
|
|
|
size_t maxoff = offset + m_number_cols;
|
|
// Sanity check, if all cells are really filled.
|
|
if (maxoff > rcv.cells.size())
|
|
{
|
|
LOGC(pflog.Error, log << "FEC: IPE: Collecting loss from row %"
|
|
<< g.base << "+" << m_number_cols << " while cells <= %"
|
|
<< CSeqNo::seqoff(rcv.cell_base, int(rcv.cells.size())-1));
|
|
return;
|
|
}
|
|
|
|
bool last = true;
|
|
loss_seqs_t::value_type val;
|
|
for (size_t i = offset; i < maxoff; ++i)
|
|
{
|
|
bool gone = last;
|
|
last = rcv.cells[i];
|
|
|
|
if (gone && !last)
|
|
{
|
|
// Switch full -> loss. Store the sequence, as single (for now)
|
|
val.first = val.second = CSeqNo::incseq(base, int(i));
|
|
}
|
|
else if (last && !gone)
|
|
{
|
|
val.second = CSeqNo::incseq(base, int(i));
|
|
irrecover.push_back(val);
|
|
}
|
|
}
|
|
|
|
// If it happened that 0 cells were until the end, we are
|
|
// sure that we have the val.first set to the first of the loss list
|
|
// and we've reached the end. Otherwise 'last' would be true.
|
|
if (!last)
|
|
{
|
|
val.second = CSeqNo::incseq(base, int(maxoff)-1);
|
|
irrecover.push_back(val);
|
|
}
|
|
|
|
g.dismissed = true;
|
|
}
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
static inline char CellMark(const std::deque<bool>& cells, int index)
|
|
{
|
|
if (index >= int(cells.size()))
|
|
return '/';
|
|
|
|
return cells[index] ? '#' : '.';
|
|
}
|
|
|
|
static void DebugPrintCells(int32_t base, const std::deque<bool>& cells, size_t row_size)
|
|
{
|
|
size_t i = 0;
|
|
// Shift to the first empty cell
|
|
for ( ; i < cells.size(); ++i)
|
|
if (cells[i] == false)
|
|
break;
|
|
|
|
if (i == cells.size())
|
|
{
|
|
LOGC(pflog.Debug, log << "FEC: ... cell[0-" << (cells.size()-1) << "]: ALL CELLS EXIST");
|
|
return;
|
|
}
|
|
|
|
// Ok, we have some empty cells, so just adjust to the start of a row.
|
|
size_t bstep = i % row_size;
|
|
if (i < bstep) // you never know...
|
|
i = 0;
|
|
else
|
|
i -= bstep;
|
|
|
|
for ( ; i < cells.size(); i += row_size )
|
|
{
|
|
std::ostringstream os;
|
|
os << "cell[" << i << "-" << (i+row_size-1) << "] %" << CSeqNo::incseq(base, (int32_t)i) << ":";
|
|
for (size_t y = 0; y < row_size; ++y)
|
|
{
|
|
os << " " << CellMark(cells, (int)(i+y));
|
|
}
|
|
LOGP(pflog.Debug, os.str());
|
|
}
|
|
}
|
|
#else
|
|
static void DebugPrintCells(int32_t /*base*/, const std::deque<bool>& /*cells*/, size_t /*row_size*/) {}
|
|
#endif
|
|
|
|
FECFilterBuiltin::EHangStatus FECFilterBuiltin::HangHorizontal(const CPacket& rpkt, bool isfec, loss_seqs_t& irrecover)
|
|
{
|
|
const int32_t seq = rpkt.getSeqNo();
|
|
|
|
EHangStatus stat;
|
|
const int rowx = RcvGetRowGroupIndex(seq, (stat));
|
|
if (rowx == -1)
|
|
return stat;
|
|
|
|
RcvGroup& rowg = rcv.rowq[rowx];
|
|
// Clip the packet into the horizontal group.
|
|
|
|
// If this was a regular packet, increase the number of collected.
|
|
// If this was a FEC/CTL packet, keep this number, just set the fec flag.
|
|
if (isfec)
|
|
{
|
|
if (!rowg.fec)
|
|
{
|
|
ClipControlPacket(rowg, rpkt);
|
|
rowg.fec = true;
|
|
HLOGC(pflog.Debug, log << "FEC/H: FEC/CTL packet clipped, %" << seq << " base=%" << rowg.base);
|
|
}
|
|
else
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/H: FEC/CTL at %" << seq << " DUPLICATED, skipping.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ClipPacket(rowg, rpkt);
|
|
rowg.collected++;
|
|
HLOGC(pflog.Debug, log << "FEC/H: DATA packet clipped, %" << seq
|
|
<< ", received " << rowg.collected << "/" << sizeRow()
|
|
<< " base=%" << rowg.base);
|
|
}
|
|
|
|
if (rowg.fec && rowg.collected == m_number_cols - 1)
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/H: HAVE " << rowg.collected << " collected & FEC; REBUILDING...");
|
|
// The group will provide the information for rebuilding.
|
|
// The sequence of the lost packet can be checked in cells.
|
|
// With the condition of 'collected == m_number_cols - 1', there
|
|
// should be only one lacking packet, so just rely on first found.
|
|
RcvRebuild(rowg, RcvGetLossSeqHoriz(rowg),
|
|
m_number_rows == 1 ? Group::SINGLE : Group::HORIZ);
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
std::ostringstream os;
|
|
for (size_t i = 0; i < rcv.rebuilt.size(); ++i)
|
|
{
|
|
os << " " << rcv.rebuilt[i].hdr[SRT_PH_SEQNO];
|
|
}
|
|
|
|
LOGC(pflog.Debug, log << "FEC: ... cached rebuilt packets (" << rcv.rebuilt.size() << "):" << os.str());
|
|
#endif
|
|
}
|
|
|
|
// When there are only rows, dismiss the oldest row when you have
|
|
// collected at least 1 packet in the next group. Do not dismiss
|
|
// any groups here otherwise - all will be decided during column
|
|
// processing.
|
|
|
|
bool want_collect_irrecover = false;
|
|
bool want_remove_cells = false;
|
|
|
|
if (rcv.rowq.size() > 1)
|
|
{
|
|
if (m_number_rows == 1)
|
|
{
|
|
want_remove_cells = true;
|
|
want_collect_irrecover = true;
|
|
}
|
|
else if (m_fallback_level == SRT_ARQ_ONREQ)
|
|
{
|
|
want_collect_irrecover = true;
|
|
}
|
|
}
|
|
|
|
if (want_collect_irrecover) // AND rcv.rowq.size() > 1
|
|
{
|
|
int current = int(rcv.rowq.size()) - 2;
|
|
// We know we have at least 2 rows.
|
|
// This value is then 0 or more.
|
|
int past = current - 1;
|
|
|
|
// To trigger irrecoverable collection, the current sequence
|
|
// must be further than 1/3 of the row size to start from
|
|
// the previous row. Otherwise, start with the past-previous
|
|
// one, as long as it still exists.
|
|
|
|
bool early SRT_ATR_UNUSED = false;
|
|
if (past > 0)
|
|
{
|
|
// If you already have at least 3 rows, sweep starting from
|
|
// the before-previous one (this will become 0 when the number
|
|
// of rows is exactly 3).
|
|
--past;
|
|
}
|
|
else
|
|
{
|
|
// If you have 2 rows, then in the current row (1) there must
|
|
// be the sequence passing already the 1/3 of the size. Otherwise
|
|
// decrease past to make it -1 and not pass the next test.
|
|
if (CSeqNo::seqoff(rcv.rowq[1].base, seq) <= int(m_number_cols/3))
|
|
{
|
|
--past;
|
|
}
|
|
else
|
|
{
|
|
early = true;
|
|
}
|
|
}
|
|
|
|
if (past >= 0)
|
|
{
|
|
// Collect irrecoverable since the 'past' index up to 0.
|
|
// If want_remove_cells, also remove these rows and corresponding cells.
|
|
|
|
int nrowremove = 1 + past;
|
|
HLOGC(pflog.Debug, log << "Collecting irrecoverable packets from " << nrowremove << " ROWS per offset "
|
|
<< CSeqNo::seqoff(rcv.rowq[1].base, seq) << " vs. " << m_number_cols << "/3");
|
|
|
|
for (int i = 0; i <= past; ++i)
|
|
{
|
|
CollectIrrecoverRow(rcv.rowq[i], irrecover);
|
|
}
|
|
|
|
// Sanity check condition - rcv.rowq must be of size
|
|
// greater than the number of rows to remove so that
|
|
// the rcv.rowq[0] exists after the operation.
|
|
if (want_remove_cells && rcv.rowq.size() > size_t(nrowremove))
|
|
{
|
|
// nrowremove >= 1
|
|
size_t npktremove = sizeRow() * nrowremove;
|
|
size_t ersize = min(npktremove, rcv.cells.size()); // ersize <= rcv.cells.size()
|
|
|
|
HLOGC(pflog.Debug, log << "FEC/H: Dismissing rows n=" << nrowremove
|
|
<< ", starting at %" << rcv.rowq[0].base
|
|
<< " AND " << npktremove << " CELLS, base switch %"
|
|
<< rcv.cell_base << " -> %" << rcv.rowq[past].base);
|
|
|
|
rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.begin() + nrowremove);
|
|
rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + ersize);
|
|
|
|
// We state that we have removed as many cells as for the removed
|
|
// rows. In case when the number of cells proved to be less than that,
|
|
// it will simply remove all cells. So now set the cell base to be
|
|
// in sync with the row base.
|
|
rcv.cell_base = rcv.rowq[0].base;
|
|
DebugPrintCells(rcv.cell_base, rcv.cells, sizeRow());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC: NOT collecting irrecover from rows: distance="
|
|
<< CSeqNo::seqoff(rcv.rowq[0].base, seq));
|
|
}
|
|
|
|
}
|
|
|
|
return HANG_SUCCESS;
|
|
}
|
|
|
|
int32_t FECFilterBuiltin::RcvGetLossSeqHoriz(Group& g)
|
|
{
|
|
int baseoff = CSeqNo::seqoff(rcv.cell_base, g.base);
|
|
if (baseoff < 0)
|
|
{
|
|
LOGC(pflog.Error, log << "FEC: IPE: negative cell offset, cell_base=%" << rcv.cell_base << " Group's base: %" << g.base << " - NOT ATTEMPTING TO REBUILD");
|
|
return -1;
|
|
}
|
|
|
|
// This is a row, so start from the first cell for this group
|
|
// and search lineraly for the first loss.
|
|
|
|
int offset = -1;
|
|
|
|
for (size_t cix = baseoff; cix < baseoff + m_number_cols; ++cix)
|
|
{
|
|
if (!rcv.CellAt(cix))
|
|
{
|
|
offset = int(cix);
|
|
#if ENABLE_HEAVY_LOGGING
|
|
// For heavy logging case, show all cells in the range
|
|
LOGC(pflog.Debug, log << "FEC/H: cell %" << CSeqNo::incseq(rcv.cell_base, int(cix))
|
|
<< " (+" << cix << "): MISSING");
|
|
|
|
#else
|
|
|
|
// Find just one. No more that just one shall be found
|
|
// because it was checked earlier that we have collected
|
|
// all but just one packet.
|
|
break;
|
|
#endif
|
|
}
|
|
#if ENABLE_HEAVY_LOGGING
|
|
else
|
|
{
|
|
LOGC(pflog.Debug, log << "FEC/H: cell %" << CSeqNo::incseq(rcv.cell_base, int(cix))
|
|
<< " (+" << cix << "): exists");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (offset == -1)
|
|
{
|
|
LOGC(pflog.Fatal, log << "FEC/H: IPE: rebuilding attempt, but no lost packet found");
|
|
return -1; // sanity, shouldn't happen
|
|
}
|
|
|
|
// Now that we have an offset towards the first packet in the cells,
|
|
// translate it to the sequence number of the lost packet.
|
|
return CSeqNo::incseq(rcv.cell_base, offset);
|
|
}
|
|
|
|
int32_t FECFilterBuiltin::RcvGetLossSeqVert(Group& g)
|
|
{
|
|
int baseoff = CSeqNo::seqoff(rcv.cell_base, g.base);
|
|
if (baseoff < 0)
|
|
{
|
|
LOGC(pflog.Error, log << "FEC: IPE: negative cell offset, cell_base=%" << rcv.cell_base << " Group's base: %" << g.base << " - NOT ATTEMPTING TO REBUILD");
|
|
return -1;
|
|
}
|
|
|
|
// This is a row, so start from the first cell for this group
|
|
// and search lineraly for the first loss.
|
|
|
|
int offset = -1;
|
|
|
|
for (size_t col = 0; col < sizeCol(); ++col)
|
|
{
|
|
size_t cix = baseoff + (col * sizeRow());
|
|
if (!rcv.CellAt(cix))
|
|
{
|
|
offset = int(cix);
|
|
#if ENABLE_HEAVY_LOGGING
|
|
// For heavy logging case, show all cells in the range
|
|
LOGC(pflog.Debug, log << "FEC/V: cell %" << CSeqNo::incseq(rcv.cell_base, int(cix))
|
|
<< " (+" << cix << "): MISSING");
|
|
|
|
#else
|
|
|
|
// Find just one. No more that just one shall be found
|
|
// because it was checked earlier that we have collected
|
|
// all but just one packet.
|
|
break;
|
|
#endif
|
|
}
|
|
#if ENABLE_HEAVY_LOGGING
|
|
else
|
|
{
|
|
LOGC(pflog.Debug, log << "FEC/V: cell %" << CSeqNo::incseq(rcv.cell_base, int(cix))
|
|
<< " (+" << cix << "): exists");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (offset == -1)
|
|
{
|
|
LOGC(pflog.Fatal, log << "FEC/V: IPE: rebuilding attempt, but no lost packet found");
|
|
return -1; // sanity, shouldn't happen
|
|
}
|
|
|
|
// Now that we have an offset towards the first packet in the cells,
|
|
// translate it to the sequence number of the lost packet.
|
|
return CSeqNo::incseq(rcv.cell_base, offset);
|
|
}
|
|
|
|
void FECFilterBuiltin::RcvRebuild(Group& g, int32_t seqno, Group::Type tp)
|
|
{
|
|
if (seqno == -1)
|
|
return;
|
|
|
|
uint16_t length_hw = ntohs(g.length_clip);
|
|
if (length_hw > payloadSize())
|
|
{
|
|
LOGC(pflog.Warn, log << "FEC: DECLIPPED length '" << length_hw << "' exceeds payload size. NOT REBUILDING.");
|
|
return;
|
|
}
|
|
|
|
// Rebuild the packet
|
|
// (length_hw is automatically converted through PrivPacket constructor)
|
|
rcv.rebuilt.push_back( length_hw );
|
|
|
|
Receive::PrivPacket& p = rcv.rebuilt.back();
|
|
|
|
p.hdr[SRT_PH_SEQNO] = seqno;
|
|
|
|
// This is for live mode only, for now, so the message
|
|
// number will be always 1, PB_SOLO, INORDER, and flags from clip.
|
|
// The REXMIT flag is set to 1 to fake that the packet was
|
|
// retransmitted. It is necessary because this packet will
|
|
// come out of sequence order, and if such a packet has
|
|
// no rexmit flag set, it's treated as reordered by network,
|
|
// which isn't true here.
|
|
p.hdr[SRT_PH_MSGNO] = 1
|
|
| MSGNO_PACKET_BOUNDARY::wrap(PB_SOLO)
|
|
| MSGNO_PACKET_INORDER::wrap(rcv.order_required)
|
|
| MSGNO_ENCKEYSPEC::wrap(g.flag_clip)
|
|
| MSGNO_REXMIT::wrap(true)
|
|
;
|
|
|
|
p.hdr[SRT_PH_TIMESTAMP] = g.timestamp_clip;
|
|
p.hdr[SRT_PH_ID] = rcv.id;
|
|
|
|
// Header ready, now we rebuild the contents
|
|
// First, rebuild the length.
|
|
|
|
// Allocate the buffer and assign to a packet.
|
|
// This is only temporary, it will be copied to
|
|
// the target place when needed, with the buffer coming
|
|
// from the unit queue.
|
|
|
|
// The payload clip may be longer than length_hw, but it
|
|
// contains only trailing zeros for completion, which are skipped.
|
|
copy(g.payload_clip.begin(), g.payload_clip.end(), p.buffer);
|
|
|
|
HLOGC(pflog.Debug, log << "FEC: REBUILT: %" << seqno
|
|
<< " msgno=" << MSGNO_SEQ::unwrap(p.hdr[SRT_PH_MSGNO])
|
|
<< " flags=" << PacketMessageFlagStr(p.hdr[SRT_PH_MSGNO])
|
|
<< " TS=" << p.hdr[SRT_PH_TIMESTAMP] << " ID=" << dec << p.hdr[SRT_PH_ID]
|
|
<< " size=" << length_hw
|
|
<< " !" << BufferStamp(p.buffer, p.length));
|
|
|
|
// Mark this packet received
|
|
MarkCellReceived(seqno);
|
|
|
|
// If this is a single request (filled from row and m_number_cols == 1),
|
|
// do not attempt recursive rebuilding
|
|
if (tp == Group::SINGLE)
|
|
return;
|
|
|
|
Group::Type crosstype = Group::FlipType(tp);
|
|
EHangStatus stat;
|
|
|
|
if (crosstype == Group::HORIZ)
|
|
{
|
|
// Find this packet in the horizontal group
|
|
const int rowx = RcvGetRowGroupIndex(seqno, (stat));
|
|
if (rowx == -1)
|
|
return; // can't access any group to rebuild
|
|
RcvGroup& rowg = rcv.rowq[rowx];
|
|
|
|
// Sanity check. It's impossible that the packet was already
|
|
// rebuilt and any attempt to rebuild a lacking packet was made.
|
|
if (rowg.collected > m_number_cols - 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Same as ClipPacket for the incoming packet, just this
|
|
// is extracting the data directly from the rebuilt one.
|
|
ClipRebuiltPacket(rowg, p);
|
|
rowg.collected++;
|
|
HLOGC(pflog.Debug, log << "FEC/H: REBUILT packet clipped, %" << seqno
|
|
<< ", received " << rowg.collected << "/" << m_number_cols
|
|
<< " FOR base=%" << rowg.base);
|
|
|
|
// Similar as by HangHorizontal, just don't collect irrecoverable packets.
|
|
// They are already known when the packets were collected.
|
|
if (rowg.fec && rowg.collected == m_number_cols - 1)
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/H: with FEC-rebuilt HAVE " << rowg.collected << " collected & FEC; REBUILDING");
|
|
// The group will provide the information for rebuilding.
|
|
// The sequence of the lost packet can be checked in cells.
|
|
// With the condition of 'collected == m_number_cols - 1', there
|
|
// should be only one lacking packet, so just rely on first found.
|
|
|
|
// NOTE: RECURSIVE CALL.
|
|
RcvRebuild(rowg, RcvGetLossSeqHoriz(rowg), crosstype);
|
|
}
|
|
}
|
|
else // crosstype == Group::VERT
|
|
{
|
|
// Find this packet in the vertical group
|
|
const int colx = RcvGetColumnGroupIndex(seqno, (stat));
|
|
if (colx == -1)
|
|
return; // can't access any group to rebuild
|
|
RcvGroup& colg = rcv.colq[colx];
|
|
|
|
// Sanity check. It's impossible that the packet was already
|
|
// rebuilt and any attempt to rebuild a lacking packet was made.
|
|
if (colg.collected > m_number_rows - 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Same as ClipPacket for the incoming packet, just this
|
|
// is extracting the data directly from the rebuilt one.
|
|
ClipRebuiltPacket(colg, p);
|
|
colg.collected++;
|
|
HLOGC(pflog.Debug, log << "FEC/V: REBUILT packet clipped, %" << seqno
|
|
<< ", received " << colg.collected << "/" << m_number_rows
|
|
<< " FOR base=%" << colg.base);
|
|
|
|
// Similar as by HangVertical, just don't collect irrecoverable packets.
|
|
// They are already known when the packets were collected.
|
|
if (colg.fec && colg.collected == m_number_rows - 1)
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/V: with FEC-rebuilt HAVE " << colg.collected << " collected & FEC; REBUILDING");
|
|
// The group will provide the information for rebuilding.
|
|
// The sequence of the lost packet can be checked in cells.
|
|
// With the condition of 'collected == m_number_rows - 1', there
|
|
// should be only one lacking packet, so just rely on first found.
|
|
|
|
// NOTE: RECURSIVE CALL.
|
|
RcvRebuild(colg, RcvGetLossSeqVert(colg), crosstype);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
size_t FECFilterBuiltin::ExtendRows(size_t rowx)
|
|
{
|
|
// Check if oversize. Oversize is when the
|
|
// index is > 2*m_number_cols. If so, shrink
|
|
// the container first.
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
LOGC(pflog.Debug, log << "FEC: ROW STATS BEFORE: n=" << rcv.rowq.size());
|
|
|
|
for (size_t i = 0; i < rcv.rowq.size(); ++i)
|
|
LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.rowq[i].DisplayStats());
|
|
#endif
|
|
|
|
const size_t size_in_packets = rowx * numberCols();
|
|
const int n_series = int(rowx / numberRows());
|
|
|
|
if (CheckEmergencyShrink(n_series, size_in_packets))
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC: DONE Emergency resize, rowx=" << rowx << " series=" << n_series
|
|
<< "npackets=" << size_in_packets << " exceeds buf=" << rcvBufferSize());
|
|
}
|
|
|
|
// Create and configure next groups.
|
|
size_t old = rcv.rowq.size();
|
|
|
|
// First, add the number of groups.
|
|
rcv.rowq.resize(rowx + 1);
|
|
|
|
// Starting from old size
|
|
for (size_t i = old; i < rcv.rowq.size(); ++i)
|
|
{
|
|
// Initialize the base for the row group
|
|
int32_t ibase = CSeqNo::incseq(rcv.rowq[0].base, int(i*m_number_cols));
|
|
ConfigureGroup(rcv.rowq[i], ibase, 1, m_number_cols);
|
|
}
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
LOGC(pflog.Debug, log << "FEC: ROW STATS AFTER: n=" << rcv.rowq.size());
|
|
|
|
for (size_t i = 0; i < rcv.rowq.size(); ++i)
|
|
LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.rowq[i].DisplayStats());
|
|
#endif
|
|
|
|
return rowx;
|
|
}
|
|
|
|
int FECFilterBuiltin::RcvGetRowGroupIndex(int32_t seq, EHangStatus& w_status)
|
|
{
|
|
RcvGroup& head = rcv.rowq[0];
|
|
const int32_t base = head.base;
|
|
|
|
const int offset = CSeqNo::seqoff(base, seq);
|
|
|
|
// Discard the packet, if older than base.
|
|
if (offset < 0)
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/H: Packet %" << seq << " is in the past, ignoring");
|
|
w_status = HANG_PAST;
|
|
return -1;
|
|
}
|
|
|
|
// Hang in the receiver group first.
|
|
size_t rowx = offset / m_number_cols;
|
|
|
|
/*
|
|
Don't.
|
|
Leaving this code for future if needed, but this check should not be done.
|
|
The resource management for "crazy" sequence numbers is done in the beginning,
|
|
so simply TRUST THIS SEQUENCE, no matter what. After the check it won't do any harm.
|
|
if (rowx > numberRows()*2) // past twice the matrix
|
|
{
|
|
LOGC(pflog.Error, log << "FEC/H: Packet %" << seq << " is in the far future, ignoring");
|
|
return -1;
|
|
}
|
|
*/
|
|
|
|
// The packet might have come completely out of the blue.
|
|
// The row group container must be prepared to extend
|
|
// itself in order to give place for the packet.
|
|
|
|
// First, possibly extend the row container
|
|
if (rowx >= rcv.rowq.size())
|
|
{
|
|
// Never returns -1
|
|
rowx = ExtendRows(rowx);
|
|
}
|
|
|
|
w_status = HANG_SUCCESS;
|
|
return int(rowx);
|
|
}
|
|
|
|
void FECFilterBuiltin::MarkCellReceived(int32_t seq, ECellReceived is_received)
|
|
{
|
|
// Mark the packet as received. This will allow later to
|
|
// determine, which exactly packet is lost and needs rebuilding.
|
|
const int cellsize = int(rcv.cells.size());
|
|
const int cell_offset = CSeqNo::seqoff(rcv.cell_base, seq);
|
|
bool resized SRT_ATR_UNUSED = false;
|
|
if (cell_offset >= cellsize)
|
|
{
|
|
// Expand the cell container with zeros, excluding the 'cell_offset'.
|
|
// Resize normally up to the required size, just set the lastmost
|
|
// item to true.
|
|
resized = true;
|
|
rcv.cells.resize(cell_offset+1, false);
|
|
}
|
|
|
|
if (resized || is_received != CELL_EXTEND)
|
|
{
|
|
// In both RECEIVED and REMOVE cases, forcefully set the value always.
|
|
// In EXTEND, only if it was received
|
|
// Value set should be true only if RECEIVED, false otherwise
|
|
rcv.cells[cell_offset] = (is_received == CELL_RECEIVED);
|
|
}
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
static string const cellop [] = { "RECEIVED", "EXTEND", "REMOVE" };
|
|
LOGC(pflog.Debug, log << "FEC: MARK CELL " << cellop[is_received]
|
|
<< "(" << (rcv.cells[cell_offset] ? "SET" : "CLR") << ")"
|
|
<< ": %" << seq << " - cells base=%"
|
|
<< rcv.cell_base << "[" << cell_offset << "]+" << rcv.cells.size()
|
|
<< (resized ? "(resized)":"") << " :");
|
|
#endif
|
|
|
|
DebugPrintCells(rcv.cell_base, rcv.cells, sizeRow());
|
|
}
|
|
|
|
bool FECFilterBuiltin::IsLost(int32_t seq) const
|
|
{
|
|
const int offset = CSeqNo::seqoff(rcv.cell_base, seq);
|
|
if (offset < 0)
|
|
{
|
|
LOGC(pflog.Error, log << "FEC: IsLost: IPE: %" << seq
|
|
<< " is earlier than the cell base %" << rcv.cell_base);
|
|
return true; // This might be due to emergency shrinking; pretend the packet is lost
|
|
}
|
|
if (offset >= int(rcv.cells.size()))
|
|
{
|
|
// XXX IPE!
|
|
LOGC(pflog.Error, log << "FEC: IsLost: IPE: %" << seq << " is past the cells %"
|
|
<< rcv.cell_base << " + " << rcv.cells.size());
|
|
return false; // Don't notify it yet
|
|
}
|
|
|
|
return rcv.cells[offset];
|
|
}
|
|
|
|
bool FECFilterBuiltin::CheckEmergencyShrink(size_t n_series, size_t size_in_packets)
|
|
{
|
|
// The minimum required size of the covered sequence range must be such
|
|
// that groups for packets from the previous range must be still reachable.
|
|
// It's then "this and previous" series in case of even arrangement.
|
|
//
|
|
// For staircase arrangement the potential range for a single column series
|
|
// (number of columns equal to a row size) spans for 2 matrices (rows * cols)
|
|
// minus one row. As dismissal is only allowed to be done by one full series
|
|
// of rows and columns, the history must keep as many groups as needed to reach
|
|
// out for this very packet of this group and all packets in the same row.
|
|
// Hence we need two series of columns to cover a similar range as two row, twice.
|
|
|
|
const size_t min_series_history = m_arrangement_staircase ? 4 : 2;
|
|
|
|
if (n_series <= min_series_history)
|
|
return false;
|
|
|
|
if (size_in_packets < rcvBufferSize() && n_series < SRT_FEC_MAX_RCV_HISTORY)
|
|
return false;
|
|
|
|
// Shrink is required in order to prepare place for
|
|
// either vertical or horizontal group in series `n_series`.
|
|
|
|
// The n_series can be calculated as:
|
|
// n_series = colgx / numberCols()
|
|
// n_series = rowgx / numberRows()
|
|
//
|
|
// The (Column or Row) Group Index value is calculated as
|
|
// the number of column where the desired sequence number
|
|
// should be located towards the very first container item
|
|
// (row/column 0).
|
|
|
|
// The task for this function is to leave only one series
|
|
// of groups and therefore initialize the containers. Likely
|
|
// the part that contains the last series should be already
|
|
// there, so in this case just remove some initial items from
|
|
// the container so that only those remain that are intended
|
|
// to remain. However, by various reasons (like e.g. that all
|
|
// packets from the whole series have been lost) particular
|
|
// container (colq, rowq, cell) doesn't contain this last
|
|
// series at all. In that case clear the container completely
|
|
// and just add an initial configuration for the first part
|
|
// (which will be then dynamically extended as packets come in).
|
|
|
|
const int32_t oldbase = rcv.colq[0].base;
|
|
const size_t shift_series = n_series - 1;
|
|
|
|
// This is simply a situation when the size is so excessive
|
|
// that it couldn't be withstood by the receiver buffer, so
|
|
// even if this isn't an extremely big size for allocation for
|
|
// FEC, it doesn't make sense anyway.
|
|
//
|
|
// Minimum of 2 series must remain in the group container,
|
|
// otherwise there's no need to guard the size.
|
|
|
|
// This requires simply resetting all group containers to
|
|
// the very initial state, just take the calculated base seq
|
|
// from the value of colgx reset to column 0.
|
|
|
|
// As colgx is calculated by stating that colgx == 0 represents
|
|
// the very first cell in the column groups, take this, shift
|
|
// by the number of series.
|
|
|
|
// SHIFT BY: n_series * matrix size
|
|
// n_series is at least 2 (see condition)
|
|
const size_t shift = shift_series * numberCols() * numberRows();
|
|
|
|
// Always positive: colgx, and so n_series, and so shift
|
|
const int32_t newbase = CSeqNo::incseq(oldbase, int(shift));
|
|
|
|
const size_t shift_rows = shift_series * numberRows();
|
|
|
|
bool need_reset = rcv.rowq.size() < shift_rows;
|
|
if (!need_reset)
|
|
{
|
|
// Sanity check - you should have the exact value
|
|
// of `newbase` at the next series beginning position
|
|
if (rcv.rowq[numberRows()].base != newbase)
|
|
{
|
|
LOGC(pflog.Error, log << "FEC: IPE: row start at %" << rcv.rowq[0].base << " next series %" << rcv.rowq[numberRows()].base
|
|
<< " (expected %" << newbase << "). RESETTING ROWS.");
|
|
need_reset = true;
|
|
}
|
|
}
|
|
|
|
if (need_reset)
|
|
{
|
|
rcv.rowq.clear();
|
|
// This n_series is the number rounded downwards,
|
|
// So you just need to prepare place for ONE series.
|
|
// The procedure below will extend them to the required
|
|
// size for the received colgx.
|
|
rcv.rowq.resize(1);
|
|
|
|
HLOGC(pflog.Debug, log << "FEC: Reset recv row %" << oldbase << " -> %" << newbase << ", INIT ROWS:");
|
|
ConfigureGroup(rcv.rowq[0], newbase, 1, sizeRow());
|
|
}
|
|
else
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC: Shifting rcv row %" << oldbase << " -> %" << newbase);
|
|
rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.begin() + shift_rows);
|
|
}
|
|
|
|
const size_t shift_cols = shift_series * numberCols();
|
|
need_reset = rcv.colq.size() < shift_cols;
|
|
if (!need_reset)
|
|
{
|
|
// Sanity check - you should have the exact value
|
|
// of `newbase` at the next series beginning position
|
|
if (rcv.colq[numberCols()].base != newbase)
|
|
{
|
|
LOGC(pflog.Error, log << "FEC: IPE: col start at %" << rcv.colq[0].base << " next series %" << rcv.colq[numberCols()].base
|
|
<< " (expected %" << newbase << "). RESETTING ROWS.");
|
|
need_reset = true;
|
|
}
|
|
}
|
|
|
|
if (need_reset)
|
|
{
|
|
rcv.colq.clear();
|
|
HLOGC(pflog.Debug, log << "FEC: Reset recv row %" << oldbase << " -> %" << newbase << ", INIT first " << numberCols() << ":");
|
|
ConfigureColumns(rcv.colq, newbase);
|
|
}
|
|
|
|
if (rcv.cells.size() > shift)
|
|
{
|
|
rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + shift);
|
|
}
|
|
else
|
|
{
|
|
rcv.cells.clear();
|
|
rcv.cells.push_back(false);
|
|
}
|
|
rcv.cell_base = newbase;
|
|
|
|
return true;
|
|
}
|
|
|
|
FECFilterBuiltin::EHangStatus FECFilterBuiltin::HangVertical(const CPacket& rpkt, signed char fec_col, loss_seqs_t& irrecover)
|
|
{
|
|
bool fec_ctl = (fec_col != -1);
|
|
// Now hang the packet in the vertical group
|
|
|
|
const int32_t seq = rpkt.getSeqNo();
|
|
|
|
// Ok, now we have the column index, we know it exists.
|
|
// Apply the packet.
|
|
|
|
EHangStatus stat;
|
|
const int colgx = RcvGetColumnGroupIndex(seq, (stat));
|
|
if (colgx == -1)
|
|
return stat;
|
|
|
|
RcvGroup& colg = rcv.colq[colgx];
|
|
|
|
if (fec_ctl)
|
|
{
|
|
if (!colg.fec)
|
|
{
|
|
ClipControlPacket(colg, rpkt);
|
|
colg.fec = true;
|
|
HLOGC(pflog.Debug, log << "FEC/V: FEC/CTL packet clipped, %" << seq << " FOR COLUMN " << int(fec_col)
|
|
<< " base=%" << colg.base);
|
|
}
|
|
else
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/V: FEC/CTL at %" << seq << " COLUMN " << int(fec_col) << " DUPLICATED, skipping.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Data packet, clip it as data
|
|
ClipPacket(colg, rpkt);
|
|
colg.collected++;
|
|
HLOGC(pflog.Debug, log << "FEC/V: DATA packet clipped, %" << seq
|
|
<< ", received " << colg.collected << "/" << sizeCol()
|
|
<< " base=%" << colg.base);
|
|
}
|
|
|
|
if (colg.fec && colg.collected == m_number_rows - 1)
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/V: HAVE " << colg.collected << " collected & FEC; REBUILDING");
|
|
RcvRebuild(colg, RcvGetLossSeqVert(colg), Group::VERT);
|
|
}
|
|
|
|
// Column dismissal takes place under very strictly specified condition,
|
|
// so simply call it in general here. At least it may happen potentially
|
|
// at any time of when a packet has been received.
|
|
RcvCheckDismissColumn(rpkt.getSeqNo(), colgx, irrecover);
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
LOGC(pflog.Debug, log << "FEC: COL STATS ATM: n=" << rcv.colq.size());
|
|
|
|
for (size_t i = 0; i < rcv.colq.size(); ++i)
|
|
LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats());
|
|
#endif
|
|
|
|
return HANG_SUCCESS;
|
|
}
|
|
|
|
void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t& irrecover)
|
|
{
|
|
// The first check we need to do is:
|
|
//
|
|
// - get the column number
|
|
// - get the series for this column
|
|
// - if series is 0, just return
|
|
|
|
const size_t series = colgx / numberCols();
|
|
if (series == 0)
|
|
return;
|
|
|
|
// - STARTING from the same column series 0:
|
|
// - unless DISMISSED, collect all irrecoverable packets from this group
|
|
// - mark this column DISMISSED
|
|
// - SAME CHECK for previous group, until index 0
|
|
|
|
set<int32_t> loss;
|
|
|
|
size_t colx SRT_ATR_UNUSED = colgx % numberCols();
|
|
|
|
HLOGC(pflog.Debug, log << "FEC/V: going to DISMISS cols past %" << seq
|
|
<< " at INDEX=" << colgx << " col=" << colx
|
|
<< " series=" << series << " - looking up candidates...");
|
|
|
|
// Walk through all column groups in series 0. Collect irrecov's
|
|
// from every group, for which the incoming 'seq' is in future.
|
|
for (size_t i = 0; i < numberCols(); ++i)
|
|
{
|
|
RcvGroup& pg = rcv.colq[i];
|
|
if (pg.dismissed)
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/V: ... [" << i << "] base=%"
|
|
<< pg.base << " ALREADY DISMISSED, skipping.");
|
|
continue;
|
|
}
|
|
|
|
// With multi-staircase it may happen that THIS column contains
|
|
// sequences that are all in the past, but the PREVIOUS column
|
|
// has some in the future, because THIS column is the top of
|
|
// the second staircase, and PREVIOUS is the bottom stair of
|
|
// the first staircase. When this is confirmed, simply skip
|
|
// the columns that have the highest sequence in the future
|
|
// because they can't be dismissed yet. Jump them over, so maybe
|
|
// they can be dismissed in future.
|
|
int this_col_offset = CSeqNo::seqoff(pg.base, seq);
|
|
int last_seq_offset = this_col_offset - int((sizeCol()-1)*sizeRow());
|
|
|
|
if (last_seq_offset < 0)
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/V: ... [" << i << "] base=%"
|
|
<< pg.base << " TOO EARLY (last=%"
|
|
<< CSeqNo::incseq(pg.base, (int32_t)((sizeCol()-1)*sizeRow()))
|
|
<< ")");
|
|
continue;
|
|
}
|
|
|
|
// NOTE: If it was standing on the second staircase top, there's
|
|
// still a chance that it hits the staircase top of the first
|
|
// staircase and will dismiss it as well.
|
|
|
|
HLOGC(pflog.Debug, log << "FEC/V: ... [" << i << "] base=%"
|
|
<< pg.base << " - PAST last=%"
|
|
<< CSeqNo::incseq(pg.base, (int32_t)((sizeCol()-1)*sizeRow()))
|
|
<< " - collecting losses.");
|
|
|
|
pg.dismissed = true; // mark irrecover already collected
|
|
for (size_t sof = 0; sof < pg.step * sizeCol(); sof += pg.step)
|
|
{
|
|
int32_t lseq = CSeqNo::incseq(pg.base, int(sof));
|
|
if (!IsLost(lseq))
|
|
{
|
|
loss.insert(lseq);
|
|
HLOGC(pflog.Debug, log << "FEC: ... cell +" << sof << " %" << lseq
|
|
<< " lost");
|
|
}
|
|
else
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC: ... cell +" << sof << " %" << lseq
|
|
<< " EXISTS");
|
|
}
|
|
}
|
|
}
|
|
|
|
// COLUMN DISMISAL:
|
|
|
|
// 1. We can only dismiss ONE SERIES OF COLUMNS - OR NOTHING.
|
|
// 2. The triggering 'seq' must be past ANY sequence embraced
|
|
// by any group in the first series of columns.
|
|
|
|
// Useful information:
|
|
//
|
|
// 1. It's not known from upside, which column contains a sequence
|
|
// number that reaches FURTHEST. The safe statement is then:
|
|
// - For even arrangement, it must be past BASE0 + matrix size
|
|
// - For staircase arrangement - BASE0 + matrix size * 2.
|
|
|
|
int32_t base0 = rcv.colq[0].base;
|
|
int this_off = CSeqNo::seqoff(base0, seq);
|
|
|
|
int mindist = int(
|
|
m_arrangement_staircase ?
|
|
(numberCols() * numberRows() * 2)
|
|
:
|
|
(numberCols() * numberRows()));
|
|
|
|
bool any_dismiss SRT_ATR_UNUSED = false;
|
|
|
|
// Here's a change.
|
|
// The number of existing column groups is supposed to always cover
|
|
// at least one full series, whereas the number of row groups are
|
|
// created always one per necessity, so the number of existing row
|
|
// groups may be less than required for a full series, whereas here
|
|
// it is intended to simply dismiss groups for full series. This may
|
|
// cause that it is aiming for removing more row groups than currently
|
|
// exist. This is completely ok, as the sequence that triggered removal
|
|
// is long past these series anyway, so the groups for packets that will
|
|
// never be received makes no sense. Simply accept this state and delete
|
|
// all row groups and reinitialize them into the new base, where the base
|
|
// is the current base for column 0 group.
|
|
//
|
|
// Therefore dismissal is triggered whenever you have a cover of one column
|
|
// series. If the number of row groups doesn't cover it, simply delete all
|
|
// row groups, that's all.
|
|
|
|
// if (base0 +% mindist) <% seq
|
|
if (this_off < mindist) // COND 1: minimum remaining
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/V: NOT dismissing any columns at %" << seq
|
|
<< ", need to pass %" << CSeqNo::incseq(base0, mindist));
|
|
}
|
|
else if (rcv.colq.size() - 1 < numberCols()) // COND 2: full matrix in columns
|
|
{
|
|
#if ENABLE_HEAVY_LOGGING
|
|
LOGC(pflog.Debug, log << "FEC/V: IPE: about to dismiss past %" << seq
|
|
<< " with required %" << CSeqNo::incseq(base0, mindist)
|
|
<< " but col container size still " << rcv.colq.size() << "; COL STATS:");
|
|
for (size_t i = 0; i < rcv.colq.size(); ++i)
|
|
LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats());
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// The condition for dismissal is now. The number of dismissed columns
|
|
// is numberCols(), regardless of the required 'mindinst'.
|
|
any_dismiss = true;
|
|
|
|
const int32_t newbase = rcv.colq[numberCols()].base;
|
|
int32_t newbase_row SRT_ATR_UNUSED; // For logging only, but including FATAL.
|
|
// Sanity check
|
|
// If sanity check failed OR if the number of existing row
|
|
// groups doesn't enclose those that need to be dismissed,
|
|
// clear row groups completely - these packets are lost and
|
|
// irrecoverable anyway.
|
|
bool insane = false;
|
|
bool undercounted = false;
|
|
|
|
if (rcv.rowq.size() - 1 < numberRows()) // COND 3: full matrix in rows
|
|
{
|
|
// Do not reach to index=numberRows() because it doesn't exist.
|
|
// Take the value from the columns as a good deal - actually
|
|
// row base and col base shall be always in sync.
|
|
newbase_row = newbase;
|
|
undercounted = true;
|
|
}
|
|
else
|
|
{
|
|
newbase_row = rcv.rowq[numberRows()].base;
|
|
insane = newbase_row != newbase;
|
|
}
|
|
const size_t matrix_size = numberCols() * numberRows();
|
|
|
|
HLOGC(pflog.Debug, log << "FEC/V: DISMISSING " << numberCols() << " COLS. Base %"
|
|
<< rcv.colq[0].base << " -> %" << newbase
|
|
<< " AND " << numberRows() << " ROWS Base %"
|
|
<< rcv.rowq[0].base << " -> %" << newbase_row
|
|
<< " AND " << matrix_size << " cells");
|
|
|
|
// ensured existence of the removed range: see COND 2 above.
|
|
rcv.colq.erase(rcv.colq.begin(), rcv.colq.begin() + numberCols());
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
LOGC(pflog.Debug, log << "FEC: COL STATS BEFORE: n=" << rcv.colq.size());
|
|
|
|
for (size_t i = 0; i < rcv.colq.size(); ++i)
|
|
LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats());
|
|
#endif
|
|
|
|
// Now erase accordingly one matrix of rows.
|
|
if (insane || undercounted)
|
|
{
|
|
if (insane)
|
|
{
|
|
LOGC(pflog.Fatal, log << "FEC/V: IPE: DISCREPANCY in new base0 col=%"
|
|
<< newbase << " row=%" << newbase_row << " - DELETING ALL ROWS");
|
|
}
|
|
else
|
|
{
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
LOGC(pflog.Debug, log << "FEC/V: about to dismiss past %" << seq
|
|
<< " with required %" << CSeqNo::incseq(base0, mindist)
|
|
<< " but row container size still " << rcv.rowq.size() << " (will clear to %" << newbase << " instead); ROW STATS:");
|
|
for (size_t i = 0; i < rcv.rowq.size(); ++i)
|
|
LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.rowq[i].DisplayStats());
|
|
#endif
|
|
}
|
|
|
|
// Delete all rows and reinitialize them.
|
|
rcv.rowq.clear();
|
|
rcv.rowq.resize(1);
|
|
ConfigureGroup(rcv.rowq[0], newbase, 1, sizeRow());
|
|
}
|
|
else
|
|
{
|
|
// Remove "legally" a matrix of rows.
|
|
// ensured existence of the removed range: see COND 3 above
|
|
rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.begin() + numberRows());
|
|
}
|
|
|
|
// And now accordingly remove cells. Exactly one matrix of cells.
|
|
// Sanity check first.
|
|
int32_t newbase_cell = CSeqNo::incseq(rcv.cell_base, int32_t(matrix_size));
|
|
if (newbase != newbase_cell)
|
|
{
|
|
LOGC(pflog.Fatal, log << "FEC/V: IPE: DISCREPANCY in new base0 col=%"
|
|
<< newbase << " cell_base=%" << newbase_cell << " - DELETING ALL CELLS");
|
|
|
|
// Try to shift it gently first. Find the cell that matches the base.
|
|
int shift = CSeqNo::seqoff(rcv.cell_base, newbase);
|
|
if (shift < 0 || size_t(shift) > rcv.cells.size())
|
|
rcv.cells.clear();
|
|
else
|
|
rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + shift);
|
|
}
|
|
else
|
|
{
|
|
if (rcv.cells.size() <= size_t(matrix_size))
|
|
rcv.cells.clear();
|
|
else
|
|
rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + matrix_size);
|
|
}
|
|
rcv.cell_base = newbase;
|
|
DebugPrintCells(rcv.cell_base, rcv.cells, sizeRow());
|
|
}
|
|
|
|
/*
|
|
OLD UNUSED CODE, leaving for historical reasons
|
|
|
|
// - check the last sequence of last column in series 0
|
|
// - if passed sequence number is earlier than this, just return
|
|
// - now that seq is newer than the last in the last column,
|
|
// - dismiss whole series 0 column groups
|
|
|
|
// First, index of the last column
|
|
size_t lastx = numberCols()-1;
|
|
if (lastx < rcv.colq.size())
|
|
{
|
|
int32_t lastbase = rcv.colq[lastx].base;
|
|
|
|
// Compare the base sequence with the sequence that caused the update
|
|
int dist = CSeqNo::seqoff(lastbase, seq);
|
|
|
|
// Shift this distance by the distance between the first and last
|
|
// sequence managed by a singled column. This counts (sizeCol()-1)*step.
|
|
dist -= (sizeCol()-1) * rcv.colq[lastx].step;
|
|
|
|
// Now, if this value is in the past (negative), it means that the
|
|
// 'seq' number is covered by this group or any earlier group. If so,
|
|
// do nothing. If this value is positive, it means that this
|
|
// sequence is in future towards the group that is in the last
|
|
// column of series 0. If so, whole series 0 may be now dismissed.
|
|
|
|
// NOTE: we don't care if lost packets have been collected for
|
|
// the groups being dismissed. They *SHOULD* be, just as a fallback
|
|
// SRT - if needed - will simply send LOSSREPORT request for all
|
|
// packets that are lossreported and all older ones.
|
|
|
|
if (dist > 0 && rcv.colq.size() > numberCols() )
|
|
{
|
|
any_dismiss = true;
|
|
int32_t newbase = rcv.colq[numberCols()].base;
|
|
rcv.colq.erase(rcv.colq.begin(), rcv.colq.begin() + numberCols());
|
|
|
|
// colgx is INVALIDATED after removal
|
|
int newcolgx SRT_ATR_UNUSED = colgx - numberCols();
|
|
|
|
// After a column series was dismissed, now dismiss also
|
|
// the same number of rows.
|
|
// Do some sanity checks first.
|
|
|
|
size_t nrowrem = 0;
|
|
int32_t oldrowbase = rcv.rowq[0].base; // before it gets deleted
|
|
if (rcv.rowq.size() > numberRows())
|
|
{
|
|
int32_t newrowbase = rcv.rowq[numberRows()].base;
|
|
if (newbase != newrowbase)
|
|
{
|
|
LOGC(pflog.Error, log << "FEC: IPE: ROW/COL base DISCREPANCY: Looking up lineraly for the right row.");
|
|
|
|
// Fallback implementation in order not to break everything
|
|
for (size_t r = 0; r < rcv.rowq.size(); ++r)
|
|
{
|
|
if (CSeqNo::seqoff(newbase, rcv.rowq[r].base) >= 0)
|
|
{
|
|
rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.begin() + r);
|
|
nrowrem = r;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.begin() + numberRows());
|
|
nrowrem = numberRows();
|
|
}
|
|
}
|
|
|
|
// If rows were removed, so remove also cells
|
|
if (nrowrem > 0)
|
|
{
|
|
int32_t newbase = rcv.rowq[0].base;
|
|
|
|
// This value SHOULD be == nrowrem * sizeRow(), but this
|
|
// calculation is safe against bugs. Report them, if found, though.
|
|
int nrem = CSeqNo::seqoff(rcv.cell_base, newbase);
|
|
|
|
if (oldrowbase != rcv.cell_base)
|
|
{
|
|
LOGC(pflog.Error, log << "FEC: CELL/ROW base discrepancy, calculating and resynchronizing");
|
|
}
|
|
else
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC: will remove " << nrem << " cells, SHOULD BE = "
|
|
<< (nrowrem * sizeRow()));
|
|
}
|
|
|
|
if (nrem > 0)
|
|
{
|
|
// Now collect losses from all rows about to be dismissed.
|
|
for (int sof = 0; sof < nrem; sof++)
|
|
{
|
|
int32_t lseq = CSeqNo::incseq(rcv.cell_base, sof);
|
|
if (!IsLost(lseq))
|
|
loss.insert(lseq);
|
|
}
|
|
|
|
HLOGC(pflog.Debug, log << "FEC: ERASING unused cells (" << nrem << "): %"
|
|
<< rcv.cell_base << " - %" << newbase
|
|
<< ", losses collected: " << Printable(loss));
|
|
|
|
rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + nrem);
|
|
rcv.cell_base = newbase;
|
|
|
|
DebugPrintCells(rcv.cell_base, rcv.cells, sizeRow());
|
|
}
|
|
else
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC: NOT ERASING cells, base %" << rcv.cell_base
|
|
<< " vs row base %" << rcv.rowq[0].base);
|
|
}
|
|
}
|
|
|
|
HLOGC(pflog.Debug, log << "FEC/V: updated g=" << colgx << " -> " << newcolgx << " %"
|
|
<< rcv.colq[newcolgx].base << ", DISMISS up to g=" << numberCols()
|
|
<< " base=%" << lastbase
|
|
<< " ROW=%" << rcv.rowq[0].base << "+" << nrowrem);
|
|
|
|
}
|
|
}
|
|
|
|
// */
|
|
|
|
// Now all collected lost packets translate into the range list format
|
|
TranslateLossRecords(loss, irrecover);
|
|
|
|
HLOGC(pflog.Debug, log << "FEC: ... COLLECTED IRRECOVER: " << Printable(loss) << (any_dismiss ? " CELLS DISMISSED" : " nothing dismissed"));
|
|
}
|
|
|
|
void FECFilterBuiltin::TranslateLossRecords(const set<int32_t>& loss, loss_seqs_t& irrecover)
|
|
{
|
|
if (loss.empty())
|
|
return;
|
|
|
|
// size() >= 1 granted
|
|
set<int32_t>::iterator i = loss.begin();
|
|
|
|
int32_t fi_start = *i;
|
|
int32_t fi_end = fi_start;
|
|
++i;
|
|
for (; i != loss.end(); ++i)
|
|
{
|
|
int dist = CSeqNo::seqoff(fi_end, *i);
|
|
if (dist == 1)
|
|
++fi_end;
|
|
else
|
|
{
|
|
// Jumped over some sequences, cut the range.
|
|
irrecover.push_back(make_pair(fi_start, fi_end));
|
|
fi_start = fi_end = *i;
|
|
}
|
|
}
|
|
|
|
// And ship the last one
|
|
irrecover.push_back(make_pair(fi_start, fi_end));
|
|
}
|
|
|
|
int FECFilterBuiltin::RcvGetColumnGroupIndex(int32_t seqno, EHangStatus& w_status)
|
|
{
|
|
// The column is only the column, not yet
|
|
// exactly the index of the column group in the container.
|
|
// It's like:
|
|
|
|
// 0 1 2 3 4
|
|
// [A] ' ' ' '
|
|
// [A] [A] ' ' '
|
|
// [A] [A] [A] ' '
|
|
// [A] [A] [A] [A] '++
|
|
// [A]+[A] [A] [A] [A]+
|
|
// [B] [A]+[A] [A] [A]+
|
|
// [B] [B] [A]+[A] [A]+
|
|
// [B] [B] [B] [A]+[A]+
|
|
// [B] [B] [B] [B] [A]++
|
|
// [B]+[B] [B] [B] [B]
|
|
// [B] [B] [B] [B]
|
|
// [B] [B] [B]
|
|
// [B] [B]
|
|
// [B]
|
|
//
|
|
// The same groups laid out in the container:
|
|
//
|
|
// [A]0 [A]1 [A]2 [A]3 [A]4 [B]0 [B]1 [B]2 [B]3 [B]4
|
|
|
|
// This means, vert_gx returns the number for the
|
|
// logical column, but we only know this column,
|
|
// not the exact group that is assigned to this
|
|
// packet. That is, in the above picture, if the
|
|
// vert_gx resulted in 3, we still don't know if
|
|
// this is A3 or B3.
|
|
//
|
|
// To know it, we need to first take the base
|
|
// sequence number for the very first column group
|
|
// in the container. Every next group in this
|
|
// container is shifted by the 'slip' factor,
|
|
// which in this case is m_number_cols + 1. The base
|
|
// sequence shifted by m_number_cols*m_number_rows
|
|
// becomes the sequence number of the next
|
|
// group in the same column.
|
|
//
|
|
// If a single column is dismissed, then the
|
|
// column 1 will become column 0, but the column
|
|
// 1 in the next series will also become column 0.
|
|
// All due to that the very base sequence for
|
|
// all groups will be the one in the first series
|
|
// column 1, now 0.
|
|
//
|
|
// Therefore, once we have the column, let's extract
|
|
// the column base sequence.
|
|
// As we can't count on that packets will surely come to close a group
|
|
// or just a particular sequence number will be received, we simply have
|
|
// to check all past groups at the moment when this packet is received:
|
|
//
|
|
// 1. Take the sequence number and determine both the GROUP INDEX and the
|
|
// COLUMN INDEX of this group.
|
|
//
|
|
// Important thing here is that the column group base is the base sequence
|
|
// number of the very first group, which NEED NOT GO HAND IN HAND with the
|
|
// row group sequence base. The rules are then:
|
|
//
|
|
// OFFSET = DISTANCE( colq[0].base TO seq )
|
|
//
|
|
// COLUMN_INDEX = OFFSET % m_number_cols
|
|
//
|
|
// COLUMN_BASE = colq[COLUMN_INDEX].base
|
|
//
|
|
// COLUMN_OFFSET = DISTANCE(COLUMN_BASE TO seq)
|
|
//
|
|
// COLUMN_SERIES = COLUMN_OFFSET / (m_number_cols * m_number_rows)
|
|
//
|
|
// GROUP_INDEX = COLUMN_INDEX + (COLUMN_SERIES * m_number_cols)
|
|
|
|
const int offset = CSeqNo::seqoff(rcv.colq[0].base, seqno);
|
|
if (offset < 0)
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/V: %" << seqno << " in the past of col ABSOLUTE base %" << rcv.colq[0].base);
|
|
w_status = HANG_PAST;
|
|
return -1;
|
|
}
|
|
|
|
if (offset > CSeqNo::m_iSeqNoTH/2)
|
|
{
|
|
LOGC(pflog.Error, log << "FEC/V: IPE/ATTACK: pkt %" << seqno << " has CRAZY OFFSET towards the base %" << rcv.colq[0].base);
|
|
w_status = HANG_CRAZY;
|
|
return -1;
|
|
}
|
|
|
|
const int colx = offset % m_number_cols;
|
|
const int32_t colbase = rcv.colq[colx].base;
|
|
const int coloff = CSeqNo::seqoff(colbase, seqno);
|
|
if (coloff < 0)
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC/V: %" << seqno << " in the past of col #" << colx << " base %" << colbase);
|
|
// This means that this sequence number predates the earliest
|
|
// sequence number supported by the very first column.
|
|
w_status = HANG_PAST;
|
|
return -1;
|
|
}
|
|
|
|
const int colseries = coloff / int(m_number_cols * m_number_rows);
|
|
size_t colgx = colx + int(colseries * m_number_cols);
|
|
|
|
HLOGC(pflog.Debug, log << "FEC/V: Lookup group for %" << seqno << ": cg_base=%" << rcv.colq[0].base
|
|
<< " column=" << colx << " with base %" << colbase << ": SERIES=" << colseries
|
|
<< " INDEX:" << colgx);
|
|
|
|
// Check oversize. Dismiss some earlier items if it exceeds the size.
|
|
// before you extend the size enormously.
|
|
if (colgx > m_number_rows * m_number_cols * SRT_FEC_MAX_RCV_HISTORY)
|
|
{
|
|
// That's too much
|
|
LOGC(pflog.Error, log << "FEC/V: IPE or ATTACK: offset " << colgx << " is too crazy, ABORTING lookup");
|
|
w_status = HANG_CRAZY;
|
|
return -1;
|
|
}
|
|
|
|
if (colgx >= rcv.colq.size())
|
|
{
|
|
colgx = ExtendColumns(colgx);
|
|
}
|
|
w_status = HANG_SUCCESS;
|
|
return int(colgx);
|
|
|
|
//
|
|
// Even though column groups are arranged in a "staircase", it only means
|
|
// that DISTANCE(colq[0].base TO colq[1].base) == 1 + m_number_cols, not 1.
|
|
// But still, this DISTANCE(...) % m_number_cols == 1. So:
|
|
//
|
|
// COLUMN_INDEX = GROUP_INDEX % m_number_cols
|
|
//
|
|
// What is special here is that, group base sequence numbers with m_number_cols == 5
|
|
// is, for example, these column groups in order of how they are arranged in
|
|
// the container have their [INDEX] BASE (stating the very first has base == 10):
|
|
//
|
|
// [0] 10, [1] 16, [2] 22, [3] 28, [4] 34, [5] 40, [6] 46
|
|
//
|
|
// Therefore, if we get the sequence number 51, then:
|
|
//
|
|
// OFFSET = 51 - 10 = 41
|
|
//
|
|
// COLUMN_INDEX = 41 % 5[m_number_cols] == 1
|
|
//
|
|
// COLUMN_BASE = colq[1].base == 16
|
|
//
|
|
// COLUMN_OFFSET = DISTANCE(colq[1].base TO 51) = 51 - 16 = 35
|
|
//
|
|
// COLUMN_SERIES = COLUMN_OFFSET / (m_number_cols * m_number_rows)
|
|
// = 35 / (5*5) = 35/25 = 1
|
|
//
|
|
// GROUP_INDEX = COLUMN_INDEX + (COLUMN_SERIES * m_number_cols)
|
|
// = 1 + 1*5 = 6
|
|
//
|
|
// --> We have identified column group with index 6, this one
|
|
// that is designated above as [6] 46. This column group collects
|
|
// the following sequence numbers:
|
|
// - 46
|
|
// - 51 (the one we were searching)
|
|
// - 56
|
|
// - 61
|
|
// - 66
|
|
//
|
|
// We have then column group with index 6. Now we go backward in the column
|
|
// group container starting from the direct previous column up to either
|
|
// the start of container or the size == m_number_cols (5), to see if the
|
|
// group "is closed". So:
|
|
//
|
|
// END = min(m_number_cols, colq.size()) <--- should be 5 or less, if there's less in the container
|
|
//
|
|
// FOR EACH i = 1 TO END:
|
|
// g = colq[GROUP_INDEX - i]
|
|
// gs = SHIFT(seq, -i)
|
|
// gmax = SHIFT(g.base, m_number_cols * m_number_rows)
|
|
// IF ( gs %> gmax )
|
|
// DISMISS COLUMNS from 0 to GROUP_INDEX - i; break
|
|
}
|
|
|
|
size_t FECFilterBuiltin::ExtendColumns(size_t colgx)
|
|
{
|
|
// This isn't safe to allow the group container to get expanded to any
|
|
// size, however with some very tolerant settings, such as 10 seconds of
|
|
// latency and very large receiver buffer, this might be tolerable.
|
|
//
|
|
// Therefore put only two conditions here:
|
|
//
|
|
// 1. The group containers must keep at most place for so many
|
|
// packets as it is intended for the receiver buffer. Keeping
|
|
// group cells for more packets doesn't make sense anyway.
|
|
//
|
|
// 2. Existing group containers should contain at least size
|
|
// for two series. If they don't contain that much, there's no
|
|
// need to do any emergency shrinking. Unknown whether this is
|
|
// physically possible, although it may also happen in case when
|
|
// you have very large FEC matrix size not coordinated with the
|
|
// receiver buffer size.
|
|
|
|
// colgx is the number of column + NSERIES * numberCols().
|
|
// We can state that for every column we should have a number
|
|
// of packets as many as the number of rows, so simply multiply this.
|
|
const size_t size_in_packets = colgx * numberRows();
|
|
const size_t n_series = colgx / numberCols();
|
|
|
|
if (CheckEmergencyShrink(n_series, size_in_packets))
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC: DONE Emergency resize, colgx=" << colgx << " series=" << n_series
|
|
<< "npackets=" << size_in_packets << " exceeds buf=" << rcvBufferSize());
|
|
}
|
|
else
|
|
{
|
|
HLOGC(pflog.Debug, log << "FEC: Will extend up to colgx=" << colgx << " series=" << n_series
|
|
<< " for npackets=" << size_in_packets);
|
|
}
|
|
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
LOGC(pflog.Debug, log << "FEC: COL STATS BEFORE: n=" << rcv.colq.size());
|
|
|
|
for (size_t i = 0; i < rcv.colq.size(); ++i)
|
|
LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats());
|
|
#endif
|
|
|
|
// First, obtain the "series" of columns, possibly fixed.
|
|
const int series = int(colgx / numberCols());
|
|
|
|
// Now, the base of the series is the base increased by one matrix size.
|
|
|
|
const int32_t base = rcv.colq[0].base;
|
|
|
|
// This is the base for series 0, but this procedure must be prepared
|
|
// for that the series will not necessarily be 1, may be greater.
|
|
// Extension requires to be done in order to achieve this very index
|
|
// existing in the column, so you need to add whole series in loop
|
|
// until the series covering this shift is created.
|
|
|
|
// Check, up to which series the columns are initialized.
|
|
// Start with the series that doesn't exist
|
|
const int old_series = int(rcv.colq.size() / numberCols());
|
|
|
|
// Each iteration of this loop adds one series of columns.
|
|
// One series count numberCols() columns.
|
|
for (int s = old_series; s <= series; ++s)
|
|
{
|
|
// We start with the base in series 0, the calculation of the
|
|
// sequence number must happen anew for each one anyway, so it
|
|
// doesn't matter from which start point.
|
|
|
|
// Every base sequence for a series of columns is the series 0
|
|
// base increased by one matrix size times series number.
|
|
// THIS REMAINS TRUE NO MATTER IF WE USE STRAIGNT OR STAIRCASE ARRANGEMENT.
|
|
const int32_t sbase = CSeqNo::incseq(base, int(numberCols()*numberRows()) * s);
|
|
HLOGC(pflog.Debug, log << "FEC/V: EXTENDING column groups series " << s
|
|
<< ", size " << rcv.colq.size() << " -> "
|
|
<< (rcv.colq.size() + numberCols())
|
|
<< ", base=%" << base << " -> %" << sbase);
|
|
|
|
// Every call to this function extends the given container
|
|
// by 'gsize' number and configures each so added column accordingly.
|
|
ConfigureColumns(rcv.colq, sbase);
|
|
}
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
LOGC(pflog.Debug, log << "FEC: COL STATS BEFORE: n=" << rcv.colq.size());
|
|
|
|
for (size_t i = 0; i < rcv.colq.size(); ++i)
|
|
LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats());
|
|
#endif
|
|
|
|
return colgx;
|
|
}
|
|
|
|
} // namespace srt
|