1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
|
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef NET_DCSCTP_TX_OUTSTANDING_DATA_H_
#define NET_DCSCTP_TX_OUTSTANDING_DATA_H_
#include <map>
#include <set>
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "net/dcsctp/common/sequence_numbers.h"
#include "net/dcsctp/packet/chunk/forward_tsn_chunk.h"
#include "net/dcsctp/packet/chunk/iforward_tsn_chunk.h"
#include "net/dcsctp/packet/chunk/sack_chunk.h"
#include "net/dcsctp/packet/data.h"
#include "net/dcsctp/public/types.h"
namespace dcsctp {
// This class keeps track of outstanding data chunks (sent, not yet acked) and
// handles acking, nacking, rescheduling and abandoning.
class OutstandingData {
public:
// State for DATA chunks (message fragments) in the queue - used in tests.
enum class State {
// The chunk has been sent but not received yet (from the sender's point of
// view, as no SACK has been received yet that reference this chunk).
kInFlight,
// A SACK has been received which explicitly marked this chunk as missing -
// it's now NACKED and may be retransmitted if NACKED enough times.
kNacked,
// A chunk that will be retransmitted when possible.
kToBeRetransmitted,
// A SACK has been received which explicitly marked this chunk as received.
kAcked,
// A chunk whose message has expired or has been retransmitted too many
// times (RFC3758). It will not be retransmitted anymore.
kAbandoned,
};
// Contains variables scoped to a processing of an incoming SACK.
struct AckInfo {
explicit AckInfo(UnwrappedTSN cumulative_tsn_ack)
: highest_tsn_acked(cumulative_tsn_ack) {}
// Bytes acked by increasing cumulative_tsn_ack and gap_ack_blocks.
size_t bytes_acked = 0;
// Indicates if this SACK indicates that packet loss has occurred. Just
// because a packet is missing in the SACK doesn't necessarily mean that
// there is packet loss as that packet might be in-flight and received
// out-of-order. But when it has been reported missing consecutive times, it
// will eventually be considered "lost" and this will be set.
bool has_packet_loss = false;
// Highest TSN Newly Acknowledged, an SCTP variable.
UnwrappedTSN highest_tsn_acked;
// The set of lifecycle IDs that were acked using cumulative_tsn_ack.
std::vector<LifecycleId> acked_lifecycle_ids;
// The set of lifecycle IDs that were acked, but had been abandoned.
std::vector<LifecycleId> abandoned_lifecycle_ids;
};
OutstandingData(
size_t data_chunk_header_size,
UnwrappedTSN next_tsn,
UnwrappedTSN last_cumulative_tsn_ack,
std::function<bool(IsUnordered, StreamID, MID)> discard_from_send_queue)
: data_chunk_header_size_(data_chunk_header_size),
next_tsn_(next_tsn),
last_cumulative_tsn_ack_(last_cumulative_tsn_ack),
discard_from_send_queue_(std::move(discard_from_send_queue)) {}
AckInfo HandleSack(
UnwrappedTSN cumulative_tsn_ack,
rtc::ArrayView<const SackChunk::GapAckBlock> gap_ack_blocks,
bool is_in_fast_recovery);
// Returns as many of the chunks that are eligible for fast retransmissions
// and that would fit in a single packet of `max_size`. The eligible chunks
// that didn't fit will be marked for (normal) retransmission and will not be
// returned if this method is called again.
std::vector<std::pair<TSN, Data>> GetChunksToBeFastRetransmitted(
size_t max_size);
// Given `max_size` of space left in a packet, which chunks can be added to
// it?
std::vector<std::pair<TSN, Data>> GetChunksToBeRetransmitted(size_t max_size);
size_t outstanding_bytes() const { return outstanding_bytes_; }
// Returns the number of DATA chunks that are in-flight.
size_t outstanding_items() const { return outstanding_items_; }
// Given the current time `now_ms`, expire and abandon outstanding (sent at
// least once) chunks that have a limited lifetime.
void ExpireOutstandingChunks(TimeMs now);
bool empty() const { return outstanding_data_.empty(); }
bool has_data_to_be_fast_retransmitted() const {
return !to_be_fast_retransmitted_.empty();
}
bool has_data_to_be_retransmitted() const {
return !to_be_retransmitted_.empty() || !to_be_fast_retransmitted_.empty();
}
UnwrappedTSN last_cumulative_tsn_ack() const {
return last_cumulative_tsn_ack_;
}
UnwrappedTSN next_tsn() const { return next_tsn_; }
UnwrappedTSN highest_outstanding_tsn() const;
// Schedules `data` to be sent, with the provided partial reliability
// parameters. Returns the TSN if the item was actually added and scheduled to
// be sent, and absl::nullopt if it shouldn't be sent.
absl::optional<UnwrappedTSN> Insert(
const Data& data,
TimeMs time_sent,
MaxRetransmits max_retransmissions = MaxRetransmits::NoLimit(),
TimeMs expires_at = TimeMs::InfiniteFuture(),
LifecycleId lifecycle_id = LifecycleId::NotSet());
// Nacks all outstanding data.
void NackAll();
// Creates a FORWARD-TSN chunk.
ForwardTsnChunk CreateForwardTsn() const;
// Creates an I-FORWARD-TSN chunk.
IForwardTsnChunk CreateIForwardTsn() const;
// Given the current time and a TSN, it returns the measured RTT between when
// the chunk was sent and now. It takes into acccount Karn's algorithm, so if
// the chunk has ever been retransmitted, it will return absl::nullopt.
absl::optional<DurationMs> MeasureRTT(TimeMs now, UnwrappedTSN tsn) const;
// Returns the internal state of all queued chunks. This is only used in
// unit-tests.
std::vector<std::pair<TSN, State>> GetChunkStatesForTesting() const;
// Returns true if the next chunk that is not acked by the peer has been
// abandoned, which means that a FORWARD-TSN should be sent.
bool ShouldSendForwardTsn() const;
// Sets the next TSN to be used. This is used in handover.
void ResetSequenceNumbers(UnwrappedTSN next_tsn,
UnwrappedTSN last_cumulative_tsn);
private:
// A fragmented message's DATA chunk while in the retransmission queue, and
// its associated metadata.
class Item {
public:
enum class NackAction {
kNothing,
kRetransmit,
kAbandon,
};
Item(Data data,
TimeMs time_sent,
MaxRetransmits max_retransmissions,
TimeMs expires_at,
LifecycleId lifecycle_id)
: time_sent_(time_sent),
max_retransmissions_(max_retransmissions),
expires_at_(expires_at),
lifecycle_id_(lifecycle_id),
data_(std::move(data)) {}
Item(const Item&) = delete;
Item& operator=(const Item&) = delete;
TimeMs time_sent() const { return time_sent_; }
const Data& data() const { return data_; }
// Acks an item.
void Ack();
// Nacks an item. If it has been nacked enough times, or if `retransmit_now`
// is set, it might be marked for retransmission. If the item has reached
// its max retransmission value, it will instead be abandoned. The action
// performed is indicated as return value.
NackAction Nack(bool retransmit_now);
// Prepares the item to be retransmitted. Sets it as outstanding and
// clears all nack counters.
void MarkAsRetransmitted();
// Marks this item as abandoned.
void Abandon();
bool is_outstanding() const { return ack_state_ == AckState::kUnacked; }
bool is_acked() const { return ack_state_ == AckState::kAcked; }
bool is_nacked() const { return ack_state_ == AckState::kNacked; }
bool is_abandoned() const { return lifecycle_ == Lifecycle::kAbandoned; }
// Indicates if this chunk should be retransmitted.
bool should_be_retransmitted() const {
return lifecycle_ == Lifecycle::kToBeRetransmitted;
}
// Indicates if this chunk has ever been retransmitted.
bool has_been_retransmitted() const { return num_retransmissions_ > 0; }
// Given the current time, and the current state of this DATA chunk, it will
// indicate if it has expired (SCTP Partial Reliability Extension).
bool has_expired(TimeMs now) const;
LifecycleId lifecycle_id() const { return lifecycle_id_; }
private:
enum class Lifecycle : uint8_t {
// The chunk is alive (sent, received, etc)
kActive,
// The chunk is scheduled to be retransmitted, and will then transition to
// become active.
kToBeRetransmitted,
// The chunk has been abandoned. This is a terminal state.
kAbandoned
};
enum class AckState : uint8_t {
// The chunk is in-flight.
kUnacked,
// The chunk has been received and acknowledged.
kAcked,
// The chunk has been nacked and is possibly lost.
kNacked
};
// NOTE: This data structure has been optimized for size, by ordering fields
// to avoid unnecessary padding.
// When the packet was sent, and placed in this queue.
const TimeMs time_sent_;
// If the message was sent with a maximum number of retransmissions, this is
// set to that number. The value zero (0) means that it will never be
// retransmitted.
const MaxRetransmits max_retransmissions_;
// Indicates the life cycle status of this chunk.
Lifecycle lifecycle_ = Lifecycle::kActive;
// Indicates the presence of this chunk, if it's in flight (Unacked), has
// been received (Acked) or is possibly lost (Nacked).
AckState ack_state_ = AckState::kUnacked;
// The number of times the DATA chunk has been nacked (by having received a
// SACK which doesn't include it). Will be cleared on retransmissions.
uint8_t nack_count_ = 0;
// The number of times the DATA chunk has been retransmitted.
uint16_t num_retransmissions_ = 0;
// At this exact millisecond, the item is considered expired. If the message
// is not to be expired, this is set to the infinite future.
const TimeMs expires_at_;
// An optional lifecycle id, which may only be set for the last fragment.
const LifecycleId lifecycle_id_;
// The actual data to send/retransmit.
const Data data_;
};
// Returns how large a chunk will be, serialized, carrying the data
size_t GetSerializedChunkSize(const Data& data) const;
// Given a `cumulative_tsn_ack` from an incoming SACK, will remove those items
// in the retransmission queue up until this value and will update `ack_info`
// by setting `bytes_acked_by_cumulative_tsn_ack`.
void RemoveAcked(UnwrappedTSN cumulative_tsn_ack, AckInfo& ack_info);
// Will mark the chunks covered by the `gap_ack_blocks` from an incoming SACK
// as "acked" and update `ack_info` by adding new TSNs to `added_tsns`.
void AckGapBlocks(UnwrappedTSN cumulative_tsn_ack,
rtc::ArrayView<const SackChunk::GapAckBlock> gap_ack_blocks,
AckInfo& ack_info);
// Mark chunks reported as "missing", as "nacked" or "to be retransmitted"
// depending how many times this has happened. Only packets up until
// `ack_info.highest_tsn_acked` (highest TSN newly acknowledged) are
// nacked/retransmitted. The method will set `ack_info.has_packet_loss`.
void NackBetweenAckBlocks(
UnwrappedTSN cumulative_tsn_ack,
rtc::ArrayView<const SackChunk::GapAckBlock> gap_ack_blocks,
bool is_in_fast_recovery,
OutstandingData::AckInfo& ack_info);
// Process the acknowledgement of the chunk referenced by `iter` and updates
// state in `ack_info` and the object's state.
void AckChunk(AckInfo& ack_info, std::map<UnwrappedTSN, Item>::iterator iter);
// Helper method to process an incoming nack of an item and perform the
// correct operations given the action indicated when nacking an item (e.g.
// retransmitting or abandoning). The return value indicate if an action was
// performed, meaning that packet loss was detected and acted upon. If
// `do_fast_retransmit` is set and if the item has been nacked sufficiently
// many times so that it should be retransmitted, this will schedule it to be
// "fast retransmitted". This is only done just before going into fast
// recovery.
bool NackItem(UnwrappedTSN tsn,
Item& item,
bool retransmit_now,
bool do_fast_retransmit);
// Given that a message fragment, `item` has been abandoned, abandon all other
// fragments that share the same message - both never-before-sent fragments
// that are still in the SendQueue and outstanding chunks.
void AbandonAllFor(const OutstandingData::Item& item);
std::vector<std::pair<TSN, Data>> ExtractChunksThatCanFit(
std::set<UnwrappedTSN>& chunks,
size_t max_size);
bool IsConsistent() const;
// The size of the data chunk (DATA/I-DATA) header that is used.
const size_t data_chunk_header_size_;
// Next TSN to used.
UnwrappedTSN next_tsn_;
// The last cumulative TSN ack number.
UnwrappedTSN last_cumulative_tsn_ack_;
// Callback when to discard items from the send queue.
std::function<bool(IsUnordered, StreamID, MID)> discard_from_send_queue_;
std::map<UnwrappedTSN, Item> outstanding_data_;
// The number of bytes that are in-flight (sent but not yet acked or nacked).
size_t outstanding_bytes_ = 0;
// The number of DATA chunks that are in-flight (sent but not yet acked or
// nacked).
size_t outstanding_items_ = 0;
// Data chunks that are eligible for fast retransmission.
std::set<UnwrappedTSN> to_be_fast_retransmitted_;
// Data chunks that are to be retransmitted.
std::set<UnwrappedTSN> to_be_retransmitted_;
};
} // namespace dcsctp
#endif // NET_DCSCTP_TX_OUTSTANDING_DATA_H_
|