diff --git a/srtcore/core.cpp b/srtcore/core.cpp index e4577a27d..dbb221e1b 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -1053,7 +1053,7 @@ void srt::CUDT::clearData() // XXX use some constant for this 16 m_iDeliveryRate = 16; m_iByteDeliveryRate = 16 * m_iMaxSRTPayloadSize; - m_iAckSeqNo = 0; + m_iAckJournal = 0; m_tsLastAckTime = steady_clock::now(); // trace information @@ -8543,7 +8543,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) // than sequence number (it's a "journal" for ACK request-response, // and starts from 0, unlike sequence, which starts from a random // number), but still the numbers are from exactly the same domain. - m_iAckSeqNo = CAckNo::incack(m_iAckSeqNo); + m_iAckJournal = CAckNo::incack(m_iAckJournal); data[ACKD_RCVLASTACK] = m_iRcvLastAck; data[ACKD_RTT] = m_iSRTT; data[ACKD_RTTVAR] = m_iRTTVar; @@ -8572,12 +8572,12 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) } // ELSE: leave the buffer with ...UDTBASE size. - ctrlpkt.pack(UMSG_ACK, &m_iAckSeqNo, data, ctrlsz); + ctrlpkt.pack(UMSG_ACK, &m_iAckJournal, data, ctrlsz); m_tsLastAckTime = steady_clock::now(); } else { - ctrlpkt.pack(UMSG_ACK, &m_iAckSeqNo, data, ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_SMALL); + ctrlpkt.pack(UMSG_ACK, &m_iAckJournal, data, ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_SMALL); } bufflock.unlock(); @@ -8586,7 +8586,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); DebugAck(CONID() + "sendCtrl(UMSG_ACK): ", local_prevack, ack); - m_ACKWindow.store(m_iAckSeqNo, m_iRcvLastAck); + m_ACKWindow.store(m_iAckJournal, m_iRcvLastAck); enterCS(m_StatsLock); m_stats.rcvr.sentAck.count(1); @@ -8956,19 +8956,21 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr int32_t ack = 0; // Calculate RTT estimate on the receiver side based on ACK/ACKACK pair. - const int rtt = m_ACKWindow.acknowledge(ctrlpkt.getAckSeqNo(), ack, tsArrival); - - if (rtt == -1) + int rtt = 0; + const ACKWindow::Status astat = m_ACKWindow.acknowledge(ctrlpkt.getAckSeqNo(), tsArrival, (ack), (rtt)); + if (astat != ACKWindow::OK) { - if (ctrlpkt.getAckSeqNo() > (m_iAckSeqNo - static_cast(ACK_WND_SIZE)) && ctrlpkt.getAckSeqNo() <= m_iAckSeqNo) + if (astat != ACKWindow::OLD) // ignore old - can't measure, but that's not a problem { string why; if (frequentLogAllowed(FREQLOGFA_ACKACK_OUTOFORDER, tsArrival, (why))) { - LOGC(inlog.Note, - log << CONID() << "ACKACK out of order, skipping RTT calculation " - << "(ACK number: " << ctrlpkt.getAckSeqNo() << ", last ACK sent: " << m_iAckSeqNo - << ", RTT (EWMA): " << m_iSRTT << ")." << why); + // For WIPED and ROGUE report this by an error log; this is an unwanted situation. + LOGC(inlog.Error, + log << CONID() << "IPE/EPE: ACK node overwritten when acknowledging " + << ctrlpkt.getAckSeqNo() + << (astat == ACKWindow::WIPED ? ": No such node recorded when ACKing" + : ": ROGUE journal value")); } #if SRT_ENABLE_FREQUENT_LOG_TRACE else @@ -8977,13 +8979,8 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr } #endif - return; } - LOGC(inlog.Error, - log << CONID() << "ACK record not found, can't estimate RTT " - << "(ACK number: " << ctrlpkt.getAckSeqNo() << ", last ACK sent: " << m_iAckSeqNo - << ", RTT (EWMA): " << m_iSRTT << ")"); return; } diff --git a/srtcore/core.h b/srtcore/core.h index a4a9acaee..89ad3e605 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -1138,7 +1138,7 @@ class CUDT int32_t m_iDebugPrevLastAck; #endif int32_t m_iRcvLastAckAck; // (RCV) Latest packet seqno in a sent ACK acknowledged by ACKACK. RcvQTh (sendCtrlAck {r}, processCtrlAckAck {r}, processCtrlAck {r}, connection {w}). - int32_t m_iAckSeqNo; // Last ACK sequence number + int32_t m_iAckJournal; // Last ACK sequence number sync::atomic m_iRcvCurrSeqNo; // (RCV) Largest received sequence number. RcvQTh, TSBPDTh. int32_t m_iRcvCurrPhySeqNo; // Same as m_iRcvCurrSeqNo, but physical only (disregarding a filter) bool m_bBufferWasFull; // Indicate that RX buffer was full last time a ack was sent diff --git a/srtcore/window.cpp b/srtcore/window.cpp index 5a926c721..ff4a13d0d 100644 --- a/srtcore/window.cpp +++ b/srtcore/window.cpp @@ -1,11 +1,11 @@ /* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 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/. - * + * */ /***************************************************************************** @@ -63,98 +63,222 @@ using namespace srt::sync; namespace srt { -namespace ACKWindowTools +namespace ACKWindow { -void store(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t seq, int32_t ack) +void store(AckNode* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t jrn, int32_t ackseq) { - r_aSeq[r_iHead].iACKSeqNo = seq; - r_aSeq[r_iHead].iACK = ack; - r_aSeq[r_iHead].tsTimeStamp = steady_clock::now(); + r_aSeq[r_iHead].tsTimeStamp = steady_clock::now(); + r_aSeq[r_iHead].iJournal = jrn; + r_aSeq[r_iHead].iAckSeq = ackseq; - r_iHead = (r_iHead + 1) % size; + r_iHead = (r_iHead + 1) % size; - // overwrite the oldest ACK since it is not likely to be acknowledged - if (r_iHead == r_iTail) - r_iTail = (r_iTail + 1) % size; + // Overwrite the oldest ACK since it is not likely to be acknowledged. + // Eat your own tail. + if (r_iHead == r_iTail) + r_iTail = (r_iTail + 1) % size; } -int acknowledge(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t seq, int32_t& r_ack, const steady_clock::time_point& currtime) +struct Range +{ + int begin, end; +}; + +struct FIsJournal +{ + int32_t jrn; + FIsJournal(int32_t v): jrn(v) {} + + bool operator()(const AckNode& node) const { return node.iJournal == jrn; } +}; + +Status acknowledge(AckNode* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t jrn, + const steady_clock::time_point& currtime, int32_t& w_ack, int32_t& w_timediff) +{ + Range range1, range2; + range1.begin = r_iTail; + range2.begin = 0; + if (r_iHead < r_iTail) + { + // range2: [0 ... r_iHead] ... range1:[r_iTail ... end] + range1.end = size; + range2.end = r_iHead; + } + else + { + // [0 ... r_iTail-1] range1:[r_iTail ... r_iHead] [... end], range2: [0-0] (empty) + range1.end = r_iHead; + range2.end = 0; + } + + // Here we are certain that the range1 is contiguous and nonempty + // Contiguous is by extracting two contiguous ranges in case when the + // original range was non-contiguous. + // Emptiness is checked here: + if (range1.begin == range1.end) + { + // This can be as well rogue, but with empty + // container it would cost a lot of checks to + // confirm that it was the case, not worth a shot. + return OLD; + } + + // Check the first range. + // The first range is always "older" than the second range, if the second one exists. + if (CSeqNo::seqcmp(jrn, r_aSeq[range1.begin].iJournal) < 0) + { + return OLD; + } + + int found = -1; + + if (CSeqNo::seqcmp(jrn, r_aSeq[range1.end - 1].iJournal) <= 0) + { + // We have the value within this range, check if exists. + AckNode* pos = std::find_if(r_aSeq + range1.begin, r_aSeq + range1.end, FIsJournal(jrn)); + if (pos == r_aSeq + range1.end) + return WIPED; + + found = pos - r_aSeq; + } + else + { + // Not within the first range, check the second range. + // If second range is empty, report this as ROGUE. + if (range2.begin == range2.end) + { + return ROGUE; + } + + if (CSeqNo::seqcmp(jrn, r_aSeq[range2.begin].iJournal < 0)) + { + // The value is above range1, but below range2. Hence, not found. + return WIPED; + } + + if (CSeqNo::seqcmp(jrn, r_aSeq[range2.end - 1].iJournal) <= 0) + { + // We have the value within this range, check if exists. + AckNode* pos = std::find_if(r_aSeq + range2.begin, r_aSeq + range2.end, FIsJournal(jrn)); + if (pos == r_aSeq + range1.end) + return WIPED; + found = pos - r_aSeq; + } + else + { + // ABOVE range2 - ROGUE + return ROGUE; + } + } + + // As long as none of the above did abnormal termination by early return, + // pos contains our required node. + w_ack = r_aSeq[found].iAckSeq; + w_timediff = count_microseconds(currtime - r_aSeq[found].tsTimeStamp); + + int inext = found + 1; + if (inext == r_iHead) + { + // Clear the container completely. + r_iHead = 0; + r_iTail = 0; + r_aSeq[0].iJournal = SRT_SEQNO_NONE; + r_aSeq[0].iAckSeq = SRT_SEQNO_NONE; + r_aSeq[0].tsTimeStamp = steady_clock::time_point(); + } + else + { + // Just cut the tail. + if (inext == int(size)) + { + inext = 0; + } + r_iTail = inext; + // Keep r_iHead in existing position. + } + + return OK; +} + +/* Updated old version remains for historical reasons +Status old_acknowledge(AckNode* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t jrn, int32_t& w_ack, const steady_clock::time_point& currtime, int32_t& w_timediff) { // Head has not exceeded the physical boundary of the window if (r_iHead >= r_iTail) { for (int i = r_iTail, n = r_iHead; i < n; ++ i) { - // Looking for an identical ACK Seq. No. - if (seq == r_aSeq[i].iACKSeqNo) + // looking for an identical ACK AckNode. No. + if (jrn == r_aSeq[i].iJournal) { // Return the Data ACK it carried - r_ack = r_aSeq[i].iACK; + w_ack = r_aSeq[i].iAckSeq; // Calculate RTT estimate - const int rtt = (int)count_microseconds(currtime - r_aSeq[i].tsTimeStamp); + w_timediff = (int32_t)count_microseconds(currtime - r_aSeq[i].tsTimeStamp); if (i + 1 == r_iHead) { r_iTail = r_iHead = 0; - r_aSeq[0].iACKSeqNo = SRT_SEQNO_NONE; + r_aSeq[0].iJournal = SRT_SEQNO_NONE; } else r_iTail = (i + 1) % size; - return rtt; + return OK; } } // The record about ACK is not found in the buffer, RTT can not be calculated - return -1; + return ROGUE; } // Head has exceeded the physical window boundary, so it is behind tail for (int j = r_iTail, n = r_iHead + (int)size; j < n; ++ j) { // Looking for an identical ACK Seq. No. - if (seq == r_aSeq[j % size].iACKSeqNo) + if (jrn == r_aSeq[j % size].iJournal) { // Return the Data ACK it carried j %= size; - r_ack = r_aSeq[j].iACK; + w_ack = r_aSeq[j].iAckSeq; // Calculate RTT estimate - const int rtt = (int)count_microseconds(currtime - r_aSeq[j].tsTimeStamp); + w_timediff = (int32_t)count_microseconds(currtime - r_aSeq[j].tsTimeStamp); if (j == r_iHead) { r_iTail = r_iHead = 0; - r_aSeq[0].iACKSeqNo = -1; + r_aSeq[0].iJournal = -1; } else r_iTail = (j + 1) % size; - return rtt; + return OK; } } // The record about ACK is not found in the buffer, RTT can not be calculated - return -1; + return ROGUE; } - -} // namespace AckTools +*/ +} // namespace ACKWindow } // namespace srt //////////////////////////////////////////////////////////////////////////////// -void srt::CPktTimeWindowTools::initializeWindowArrays(int* r_pktWindow, int* r_probeWindow, int* r_bytesWindow, size_t asize, size_t psize, size_t max_payload_size) +void srt::CPktTimeWindowTools::initializeWindowArrays(int* r_pktWindow, int* r_probeWindow, int* r_bytesWindow, + size_t asize, size_t psize, size_t max_payload_size) { - for (size_t i = 0; i < asize; ++ i) - r_pktWindow[i] = 1000000; //1 sec -> 1 pkt/sec + for (size_t i = 0; i < asize; ++i) + r_pktWindow[i] = 1000000; // 1 sec -> 1 pkt/sec - for (size_t k = 0; k < psize; ++ k) - r_probeWindow[k] = 1000; //1 msec -> 1000 pkts/sec + for (size_t k = 0; k < psize; ++k) + r_probeWindow[k] = 1000; // 1 msec -> 1000 pkts/sec - for (size_t i = 0; i < asize; ++ i) - r_bytesWindow[i] = int(max_payload_size); //based on 1 pkt/sec set in r_pktWindow[i] + for (size_t i = 0; i < asize; ++i) + r_bytesWindow[i] = int(max_payload_size); // based on 1 pkt/sec set in r_pktWindow[i] } int srt::CPktTimeWindowTools::ceilPerMega(double value, double count) @@ -163,18 +287,18 @@ int srt::CPktTimeWindowTools::ceilPerMega(double value, double count) return int(::ceil(MEGA / (value / count))); } -int srt::CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica, const int* abytes, size_t asize, size_t hdr_size, int& w_bytesps) +int srt::CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica, const int* abytes, + size_t asize, size_t hdr_size, int& w_bytesps) { PassFilter filter = GetPeakRange(window, replica, asize); unsigned count = 0; - int sum = 0; + int sum = 0; - w_bytesps = 0; + w_bytesps = 0; unsigned long bytes = 0; // // (explicit specialization due to problems on MSVC 2013 and 2015) - AccumulatePassFilterParallel(window, asize, filter, abytes, - (sum), (count), (bytes)); + AccumulatePassFilterParallel(window, asize, filter, abytes, (sum), (count), (bytes)); // calculate speed, or return 0 if not enough valid value if (count <= (asize/2)) @@ -183,7 +307,7 @@ int srt::CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica, return 0; } - bytes += (unsigned long)(hdr_size * count); //Add protocol headers to bytes received + bytes += (unsigned long)(hdr_size * count); // Add protocol headers to bytes received w_bytesps = ceilPerMega(sum, bytes); return ceilPerMega(sum, count); } @@ -194,10 +318,8 @@ int srt::CPktTimeWindowTools::getBandwidth_in(const int* window, int* replica, s int sum, count; Tie2(sum, count) = AccumulatePassFilter(window, psize, filter); - sum += filter.median; + sum += filter.median; count += 1; return ceilPerMega(sum, count); } - - diff --git a/srtcore/window.h b/srtcore/window.h index 4e548d3ef..801a6d233 100644 --- a/srtcore/window.h +++ b/srtcore/window.h @@ -64,17 +64,27 @@ modified by namespace srt { -namespace ACKWindowTools +namespace ACKWindow { - struct Seq + struct AckNode { - int32_t iACKSeqNo; // Seq. No. of the ACK packet - int32_t iACK; // Data packet Seq. No. carried by the ACK packet + int32_t iJournal; // AckNode. No. for the ACK packet + int32_t iAckSeq; // Data AckNode. No. carried by the ACK packet sync::steady_clock::time_point tsTimeStamp; // The timestamp when the ACK was sent }; - void store(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t seq, int32_t ack); - int acknowledge(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t seq, int32_t& r_ack, const sync::steady_clock::time_point& currtime); + enum Status + { + OK, //< Node found and removed, together with all older nodes + OLD, //< Given node is in the 1/2 of the sequence cycle before the oldest node + ROGUE, //< Given node is in the 1/2 of the sequence cycle after the newest node + WIPED //< Given node is within the range of contained nodes, but wasn't found + }; + + void store(AckNode* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t seq, int32_t ack); + + Status acknowledge(AckNode* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t seq, const sync::steady_clock::time_point& currtime, int32_t& r_ack, int& w_timediff); + Status old_acknowledge(AckNode* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t jrn, int32_t& w_ack, const srt::sync::steady_clock::time_point& currtime, int32_t& w_timediff); } template @@ -86,7 +96,7 @@ class CACKWindow m_iHead(0), m_iTail(0) { - m_aSeq[0].iACKSeqNo = SRT_SEQNO_NONE; + m_aSeq[0].iJournal = SRT_SEQNO_NONE; } ~CACKWindow() {} @@ -95,9 +105,9 @@ class CACKWindow /// @param [in] seq Seq. No. of the ACK packet /// @param [in] ack Data packet Seq. No. carried by the ACK packet - void store(int32_t seq, int32_t ack) + void store(int32_t jrn, int32_t ackseq) { - return ACKWindowTools::store(m_aSeq, SIZE, m_iHead, m_iTail, seq, ack); + return ACKWindow::store(m_aSeq, SIZE, m_iHead, m_iTail, jrn, ackseq); } /// Search the ACKACK "seq" in the window, find out the data packet "ack" @@ -107,16 +117,29 @@ class CACKWindow /// @param [in] currtime The timestamp of ACKACK packet reception by the receiver /// @return RTT - int acknowledge(int32_t seq, int32_t& r_ack, const sync::steady_clock::time_point& currtime) + ACKWindow::Status acknowledge(int32_t jrn, const sync::steady_clock::time_point& currtime, int32_t& w_ackseq, int32_t& w_timediff) { - return ACKWindowTools::acknowledge(m_aSeq, SIZE, m_iHead, m_iTail, seq, r_ack, currtime); + return ACKWindow::acknowledge(m_aSeq, SIZE, m_iHead, m_iTail, jrn, currtime, (w_ackseq), (w_timediff)); } + /* + ACKWindow::Status old_acknowledge(int32_t jrn, int32_t& w_ackseq, const srt::sync::steady_clock::time_point& currtime, int32_t& w_timediff) + { + return ACKWindow::old_acknowledge(m_aSeq, SIZE, m_iHead, m_iTail, jrn, (w_ackseq), currtime, (w_timediff)); + } + unblock for testing + */ + + // For UT purposes + ACKWindow::AckNode first() { return m_aSeq[m_iTail]; } + ACKWindow::AckNode last() { return m_aSeq[(m_iHead - 1 + SIZE) % SIZE]; } + size_t size() { return (m_iHead - m_iTail + SIZE) % SIZE; } + private: - typedef ACKWindowTools::Seq Seq; + typedef ACKWindow::AckNode AckNode; - Seq m_aSeq[SIZE]; + AckNode m_aSeq[SIZE]; int m_iHead; // Pointer to the latest ACK record int m_iTail; // Pointer to the oldest ACK record diff --git a/test/test_utilities.cpp b/test/test_utilities.cpp index d14f5f9cc..c4949e64c 100644 --- a/test/test_utilities.cpp +++ b/test/test_utilities.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -7,6 +8,8 @@ #define SRT_TEST_CIRCULAR_BUFFER #include "api.h" #include "common.h" +#include "window.h" +#include "sync.h" using namespace std; using namespace srt; @@ -110,22 +113,22 @@ TEST(CircularBuffer, Overall) IF_HEAVY_LOGGING(cerr << "After adding 3 elements: size=" << buf.size() << " capacity=" << buf.capacity() << ":\n"); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); - ASSERT_EQ(buf.size(), 3); + ASSERT_EQ(buf.size(), (3)); IF_HEAVY_LOGGING(cerr << "Adding element at position 5:\n"); EXPECT_TRUE(buf.set(5, 15.5)); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); - ASSERT_EQ(buf.size(), 6); + ASSERT_EQ(buf.size(), (6)); IF_HEAVY_LOGGING(cerr << "Adding element at position 7 (should fail):\n"); EXPECT_FALSE(buf.set(7, 10.0)); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); - ASSERT_EQ(buf.size(), 6); + ASSERT_EQ(buf.size(), (6)); IF_HEAVY_LOGGING(cerr << "Dropping first 2 elements:\n"); buf.drop(2); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); - ASSERT_EQ(buf.size(), 4); + ASSERT_EQ(buf.size(), (4)); IF_HEAVY_LOGGING(cerr << "Adding again element at position 6 (should roll):\n"); buf.set(6, 22.1); @@ -190,7 +193,7 @@ TEST(CircularBuffer, Overall) EXPECT_TRUE(buf.set(offset, 99.1, true)); // size() - 1 is the latest possible offset - ASSERT_EQ(buf.size() - 1 + dropshift, 9); + ASSERT_EQ(buf.size() - 1 + dropshift, (9)); } else { @@ -273,3 +276,127 @@ TEST(ConfigString, Setting) EXPECT_TRUE(s.empty()); } +struct AckData +{ + int32_t journal; + int32_t ackseq; +}; + +static void TestAckWindow(const std::array& data, size_t initpos, const std::string& casename) +{ + using srt::sync::steady_clock; + + typedef CACKWindow<10> ackwindow_t; + ackwindow_t ackwindow; + + int b4 = data[0].journal - initpos; + + for (size_t i = 0; i < initpos; ++i) + { + ackwindow.store(b4, 0); + ++b4; + } + + for (auto& n: data) + ackwindow.store(n.journal, n.ackseq); + + steady_clock::time_point now = steady_clock::now(); + + // Now remove those initial ones + int32_t dummy1, dummy2; + ackwindow.acknowledge(data[0].journal-1, now, (dummy1), (dummy2)); + + ASSERT_EQ(ackwindow.first().iJournal, data[0].journal) << " (" << casename << ")"; + ASSERT_EQ(ackwindow.last().iJournal, data[4].journal) << " (" << casename << ")"; + ASSERT_EQ(ackwindow.size(), size_t(5)) << " (" << casename << ")"; + + int iack = 0; + int td =0; + + // Remove oldest node. Should go ok. + ACKWindow::Status stat = ackwindow.acknowledge(data[0].journal, now, (iack), (td)); + EXPECT_EQ(iack, data[0].ackseq) << " (" << casename << ")"; + EXPECT_EQ(stat, ACKWindow::OK) << " (" << casename << ")"; + EXPECT_EQ(ackwindow.size(), size_t(4)) << " (" << casename << ")"; + EXPECT_EQ(ackwindow.first().iJournal, data[1].journal) << " (" << casename << ")"; + + // Now remove the node +2 + stat = ackwindow.acknowledge(data[2].journal, now, (iack), (td)); + EXPECT_EQ(iack, data[2].ackseq) << " (" << casename << ")"; + EXPECT_EQ(stat, ACKWindow::OK) << " (" << casename << ")"; + EXPECT_EQ(ackwindow.size(), size_t(2)) << " (" << casename << ")"; + EXPECT_EQ(ackwindow.first().iJournal, data[3].journal) << " (" << casename << ")"; + + // Now remove too old node + stat = ackwindow.acknowledge(data[1].journal, now, (iack), (td)); + EXPECT_EQ(stat, ACKWindow::OLD) << "(" << casename << ")"; + // Like above - no changes were expected + EXPECT_EQ(ackwindow.size(), size_t(2)) << " (" << casename << ")"; + EXPECT_EQ(ackwindow.first().iJournal, data[3].journal) << " (" << casename << ")"; + + // And remove the node that wasn't inserted + int32_t wrongnode = data[4].journal+1; + stat = ackwindow.acknowledge(wrongnode, now, (iack), (td)); + EXPECT_EQ(stat, ACKWindow::ROGUE); + // Like above - no changes were expected + EXPECT_EQ(ackwindow.size(), size_t(2)) << " (" << casename << ")"; + EXPECT_EQ(ackwindow.first().iJournal, data[3].journal) << " (" << casename << ")"; + + // Now insert one value that jumps over. It's not exactly + // possible in the normal SRT runtime, but the reaction should be + // prepared just in case. + ackwindow.store(data[4].journal+2, data[4].ackseq); + // Now a search of data[4].journal+1 should fail appropriately. + stat = ackwindow.acknowledge(data[4].journal+1, now, (iack), (td)); + EXPECT_EQ(stat, ACKWindow::WIPED); +} + +TEST(ACKWindow, API) +{ + // We have a situation with circular buffer with circular + // numbers with two different cirtulations. We need then + // permutations of 4 special plus 1 regular, in total: + // + // 1. Regular numbers in a regular range + // 2. Regular numbers in a split range + // 3. Number overflow in a regular range. + // 4. Number ovewflow in a split range in lower part + // 5. Number overflow in a split range in upper part + + int32_t seq0 = CSeqNo::m_iSeqNoTH; + + int32_t basej = 100; + std::array regular = { + AckData {basej + 0, seq0}, + AckData {basej + 1, seq0 + 10}, + AckData {basej + 2, seq0 + 20}, + AckData {basej + 3, seq0 + 30}, + AckData {basej + 4, seq0 + 40} + }; + + // 1. + TestAckWindow(regular, 0, "regular/0"); + + // 2. + TestAckWindow(regular, 7, "regular/7"); + + basej = CSeqNo::m_iMaxSeqNo-2; + + std::array overflow = { + AckData {basej + 0, seq0}, + AckData {basej + 1, seq0 + 10}, + AckData {basej + 2, seq0 + 20}, + AckData {basej + 3, seq0 + 30}, + AckData {basej + 4, seq0 + 40} + }; + + // 3. + TestAckWindow(overflow, 0, "overflow/0"); + + // 4. + TestAckWindow(overflow, 3, "overflow/3"); + + // 5. + TestAckWindow(overflow, 7, "overflow/7"); +} +