12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582 |
- /*
- * 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
|