lib/quic/src/connection/stream/send.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const quic = @import("../../root.zig");
3
4 const stream = quic.connection.stream;
5
6 /// One STREAM frame that has gone out, held until an acknowledgment or a loss settles it, so the
7 /// caller can size the range table to hold one entry for each outstanding frame. The entry holds
8 /// the offset, the byte count, whether that frame closed the stream, and whether the entry is
9 /// taken.
10 pub const SentRange = struct {
11 offset: u62,
12 length: u16,
13 fin: bool,
14 outstanding: bool,
15
16 pub fn empty() SentRange {
17 return .{ .offset = 0, .length = 0, .fin = false, .outstanding = false };
18 }
19 };
20
21 /// The bytes and the closing flag picked out for one STREAM frame, so the packet builder can turn
22 /// the chunk into a frame. The chunk holds the offset, those bytes, and that flag. The bytes point
23 /// into the send ring.
24 pub const Chunk = struct {
25 offset: u62,
26 data: []const u8,
27 fin: bool,
28 };
29
30 /// The half of one stream that puts data out, working out of a ring and a range table the caller
31 /// supplies, so the connection holds one for stream 0 and drives it from application writes and
32 /// recovery. The half holds that ring and table, the peer's credit, the state, the offsets
33 /// acknowledged, sent, and written, the final size, and the reset state. Those three offsets keep
34 /// their order, acknowledged at or under sent and sent at or under written. `init` wants both the
35 /// ring and the table nonempty.
36 pub const Send = struct {
37 bytes: []u8,
38 ranges: []SentRange,
39 credit: stream.Credit,
40 state: stream.SendState = .ready,
41 head: usize = 0,
42 acknowledged: u62 = 0,
43 sent: u62 = 0,
44 written: u62 = 0,
45 final_size: ?u62 = null,
46 fin_sent: bool = false,
47 fin_acknowledged: bool = false,
48 /// The stretch of the stream that a lost frame put back in the queue, so the next chunk can
49 /// start below the sent offset. Those bytes go out ahead of anything the application has newly
50 /// written.
51 lost: quic.connection.recovery.LostRange = .{},
52 /// Flag set when a lost frame carried the closing flag before an acknowledgment covered it,
53 /// because a lost closing flag carries no bytes of its own and needs a dedicated flag. The flag
54 /// clears once a copy goes out, once an acknowledgment covers the flag, or once a reset
55 /// abandons the stream.
56 fin_lost: bool = false,
57 reset_code: ?u62 = null,
58 reset_pending: bool = false,
59
60 pub fn init(bytes: []u8, ranges: []SentRange, limit: u62) Send {
61 std.debug.assert(bytes.len > 0);
62 std.debug.assert(ranges.len > 0);
63 for (ranges) |*range| range.* = SentRange.empty();
64 return .{ .bytes = bytes, .ranges = ranges, .credit = stream.Credit.init(limit) };
65 }
66
67 /// Counts what the ring is holding, measured from the oldest byte the peer has yet to
68 /// acknowledge, so `write` accepts what room remains. The count fits inside the ring.
69 pub fn buffered(self: *const Send) usize {
70 std.debug.assert(self.acknowledged <= self.sent);
71 std.debug.assert(self.sent <= self.written);
72 const count: usize = @intCast(self.written - self.acknowledged);
73 std.debug.assert(count <= self.bytes.len);
74 return count;
75 }
76
77 /// Takes application bytes into the ring so they enter the stream, accepting as many as free
78 /// room allows, and reports how many it took. The call accepts nothing once the final size is
79 /// settled or a reset has abandoned the stream. The call also stops at the highest offset a
80 /// stream can reach.
81 pub fn write(self: *Send, input: []const u8) usize {
82 if (!self.writable()) return 0;
83 const free = self.bytes.len - self.buffered();
84 const offset_room: u64 = std.math.maxInt(u62) - self.written;
85 const count: usize = @intCast(@min(free, input.len, offset_room));
86 if (count == 0) return 0;
87 stream.ring.write(self.bytes, self.ringIndex(self.written), input[0..count]);
88 self.written += @intCast(count);
89 std.debug.assert(self.buffered() <= self.bytes.len);
90 return count;
91 }
92
93 /// Settles the final size at the last byte the application wrote, so the application ends its
94 /// side of the stream. The call leaves state unchanged when the final size is settled already
95 /// or the stream stands reset.
96 pub fn finish(self: *Send) void {
97 if (!self.writable()) return;
98 self.final_size = self.written;
99 }
100
101 /// Says whether this half has anything for the packet being built, so the send path decides
102 /// whether to request a frame. Lost bytes, a lost closing flag, bytes never sent, and a closing
103 /// flag never sent each count. A reset leaves nothing waiting, having abandoned what was there.
104 pub fn pending(self: *const Send) bool {
105 if (self.reset_code != null) return false;
106 if (!self.lost.isEmpty() or self.fin_lost) return true;
107 if (self.sent < self.written) return true;
108 const final = self.final_size orelse return false;
109 std.debug.assert(final == self.sent);
110 return !self.fin_sent;
111 }
112
113 /// Gives the offset the next STREAM frame will start at, so the packet builder can size the
114 /// frame header against that offset before asking for a chunk. The start is the first byte
115 /// waiting to go a second time, or, with none waiting, the first byte never sent.
116 pub fn nextOffset(self: *const Send) u62 {
117 if (self.lost.isEmpty()) return self.sent;
118 std.debug.assert(self.lost.start >= self.acknowledged);
119 std.debug.assert(self.lost.end <= self.sent);
120 return self.lost.start;
121 }
122
123 /// Picks out up to `data_max` bytes in order for the next STREAM frame, so the packet builder
124 /// receives the payload for that frame. Bytes going a second time come first, then a closing
125 /// flag on its own, and both travel free of credit. Bytes going out for the first time stop at
126 /// this stream's own credit and at `connection_credit`. The pick also stops at the buffer end
127 /// where the ring wraps, and at the longest payload a frame may carry. The closing flag travels
128 /// with the pick that reaches the final size. The call returns null when the half has nothing
129 /// waiting, and returns null when every entry in the table is taken.
130 pub fn nextChunk(self: *const Send, data_max: usize, connection_credit: u62) ?Chunk {
131 if (!self.pending()) return null;
132 if (self.freeRange() == null) return null;
133 if (!self.lost.isEmpty()) return self.lostChunk(data_max);
134 if (self.fin_lost) return self.lostFin();
135 const index = self.ringIndex(self.sent);
136 const credit: u64 = @min(self.credit.available(), connection_credit);
137 const waiting: u64 = self.written - self.sent;
138 const room: u64 = @min(data_max, self.bytes.len - index, std.math.maxInt(u16));
139 const count: usize = @intCast(@min(waiting, credit, room));
140 const end = self.sent + @as(u62, @intCast(count));
141 const fin = if (self.final_size) |final| end == final and !self.fin_sent else false;
142 if (count == 0 and !fin) return null;
143 return .{ .offset = self.sent, .data = self.bytes[index..][0..count], .fin = fin };
144 }
145
146 /// Takes an entry in the table for the pick that has gone out, and reports how many of its
147 /// bytes had never gone before, for the packet builder to call once the packet holding the
148 /// frame is sealed and charge that count to connection credit. Only that figure is charged to
149 /// the connection credit, because bytes going a second time sit under the highest offset
150 /// already reached. A pick that had gone before reports zero and takes its bytes out of the
151 /// waiting stretch. A pick of fresh bytes moves the sent offset on, spends this stream's
152 /// credit, and moves the half's state.
153 pub fn commitChunk(self: *Send, offset: u62, length: u16, fin: bool) u16 {
154 const slot = self.freeRange() orelse unreachable;
155 self.ranges[slot] = .{
156 .offset = offset,
157 .length = length,
158 .fin = fin,
159 .outstanding = true,
160 };
161 if (offset < self.sent or (fin and self.fin_sent)) {
162 self.commitLost(offset, length, fin);
163 return 0;
164 }
165 std.debug.assert(offset == self.sent);
166 std.debug.assert(length <= self.written - self.sent);
167 self.sent += length;
168 self.credit.consume(length);
169 if (fin) {
170 std.debug.assert(self.sent == self.final_size.?);
171 self.fin_sent = true;
172 }
173 const event: stream.SendEvent = if (fin) .send_fin else .send_data;
174 self.state = stream.sendTransition(self.state, event) catch unreachable;
175 return length;
176 }
177
178 /// Hands back the figure a STREAM_DATA_BLOCKED frame would report while this stream's own
179 /// credit holds the sender up, so the send path decides whether that frame belongs in the
180 /// packet being built. The sender counts as held up while the stream stands unreset and bytes
181 /// wait to go.
182 pub fn blocked(self: *const Send) ?u62 {
183 const waiting = self.reset_code == null and self.sent < self.written;
184 return self.credit.blocked(waiting);
185 }
186
187 /// Notes that a STREAM_DATA_BLOCKED frame went out, for the send path to call once the frame is
188 /// in a sealed packet. That frame also starts the half moving, taking it out of Ready.
189 pub fn commitBlocked(self: *Send, limit: u62) void {
190 self.credit.markBlocked(limit);
191 if (self.state != .ready) return;
192 self.state = stream.sendTransition(.ready, .send_data) catch unreachable;
193 }
194
195 /// Frees the ring bytes one acknowledged STREAM frame covered, and trims them off whichever end
196 /// of the waiting stretch they reach, so an acknowledged packet frees the send buffer. An
197 /// acknowledgment covering the closing flag clears a lost one. Bytes come free in offset order,
198 /// so an acknowledgment above a frame still outstanding frees nothing yet. The call passes over
199 /// a frame this half has already released. The closing flag acknowledged with every byte behind
200 /// it carries the half to Data Recvd.
201 pub fn acknowledge(self: *Send, offset: u62, length: u16, fin: bool) void {
202 const slot = self.findRange(offset, length, fin) orelse return;
203 self.ranges[slot] = SentRange.empty();
204 self.lost.acknowledge(offset, length);
205 if (fin) {
206 self.fin_acknowledged = true;
207 self.fin_lost = false;
208 }
209 self.releaseAcknowledged();
210 if (self.state != .data_sent) return;
211 if (!self.fin_acknowledged) return;
212 if (self.acknowledged != self.sent) return;
213 self.state = stream.sendTransition(.data_sent, .all_acknowledged) catch unreachable;
214 }
215
216 /// Gives up the table entry a lost STREAM frame held, and puts its bytes, with a closing flag
217 /// no acknowledgment has covered, in the stretch that goes ahead of fresh bytes, so lost bytes
218 /// go out again ahead of new ones. A frame whose entry an acknowledgment or a reset has already
219 /// given up calls for nothing.
220 pub fn lose(self: *Send, offset: u62, length: u16, fin: bool) void {
221 const slot = self.findRange(offset, length, fin) orelse return;
222 std.debug.assert(offset >= self.acknowledged);
223 std.debug.assert(offset + length <= self.sent);
224 self.ranges[slot] = SentRange.empty();
225 self.lost.add(offset, length);
226 if (fin and !self.fin_acknowledged) self.fin_lost = true;
227 }
228
229 /// Gives up on the bytes waiting, sent and unsent alike, and queues RESET_STREAM so an
230 /// application can abandon the stream, returning whether the reset was taken. What a reset
231 /// gives up on has no route back onto the wire. The call settles the final size at the highest
232 /// offset already sent. The call returns false on a second call, and false once the half holds
233 /// every acknowledgment or has sent its reset already.
234 pub fn reset(self: *Send, code: u62) bool {
235 if (self.reset_code != null) return false;
236 switch (self.state) {
237 .ready, .send, .data_sent => {},
238 .data_recvd, .reset_sent, .reset_recvd => return false,
239 }
240 self.reset_code = code;
241 self.reset_pending = true;
242 self.written = self.sent;
243 self.final_size = self.sent;
244 self.lost = .{};
245 self.fin_lost = false;
246 for (self.ranges) |*range| range.* = SentRange.empty();
247 self.releaseAcknowledged();
248 std.debug.assert(self.buffered() == 0);
249 return true;
250 }
251
252 /// Hands back the RESET_STREAM frame that is waiting for a packet, so the send path can insert
253 /// that frame into the packet being built. That frame carries the error code, and the highest
254 /// offset sent as its final size. The call returns null when no reset waits.
255 pub fn resetFrame(self: *const Send, stream_id: u62) ?quic.frame.ResetStream {
256 if (!self.reset_pending) return null;
257 const code = self.reset_code.?;
258 return .{ .stream_id = stream_id, .error_code = code, .final_size = self.sent };
259 }
260
261 /// Notes that a RESET_STREAM frame went out and carries the half into Reset Sent, for the send
262 /// path to call once the frame is in a sealed packet. When a loss has put one RESET_STREAM
263 /// frame out already, the half stands in Reset Sent as the copy goes, and stays there.
264 pub fn commitReset(self: *Send) void {
265 std.debug.assert(self.reset_pending);
266 self.reset_pending = false;
267 if (self.state == .reset_sent) return;
268 self.state = stream.sendTransition(self.state, .send_reset) catch unreachable;
269 }
270
271 /// Puts RESET_STREAM back in the queue after a loss, while the half sits in Reset Sent, so a
272 /// lost RESET_STREAM goes out again. The copy carries the same code and final size, because
273 /// `resetFrame` reads both out of the same fields. Once an acknowledgment has carried the half
274 /// to Reset Recvd the peer knows, so nothing goes in the queue.
275 pub fn loseReset(self: *Send) void {
276 if (self.state != .reset_sent) return;
277 std.debug.assert(self.reset_code != null);
278 self.reset_pending = true;
279 }
280
281 /// Notes the acknowledgment of the RESET_STREAM frame and carries the half into Reset Recvd, so
282 /// an acknowledged RESET_STREAM ends the sending part. Called from any state other than Reset
283 /// Sent, the call leaves state unchanged.
284 pub fn acknowledgeReset(self: *Send) void {
285 if (self.state != .reset_sent) return;
286 self.reset_pending = false;
287 self.state = stream.sendTransition(.reset_sent, .reset_acknowledged) catch unreachable;
288 }
289
290 fn writable(self: *const Send) bool {
291 if (self.final_size != null) return false;
292 if (self.reset_code != null) return false;
293 std.debug.assert(self.state != .data_sent);
294 std.debug.assert(self.state != .data_recvd);
295 return true;
296 }
297
298 fn lostChunk(self: *const Send, data_max: usize) ?Chunk {
299 std.debug.assert(!self.lost.isEmpty());
300 const offset = self.lost.start;
301 const index = self.ringIndex(offset);
302 const room: u64 = @min(data_max, self.bytes.len - index, std.math.maxInt(u16));
303 const count: usize = @intCast(@min(self.lost.length(), room));
304 if (count == 0) return null;
305 const end = offset + @as(u62, @intCast(count));
306 const fin = self.fin_lost and end == self.final_size.?;
307 return .{ .offset = offset, .data = self.bytes[index..][0..count], .fin = fin };
308 }
309
310 fn lostFin(self: *const Send) Chunk {
311 std.debug.assert(self.fin_lost);
312 std.debug.assert(self.fin_sent);
313 std.debug.assert(self.final_size.? == self.sent);
314 const index = self.ringIndex(self.sent);
315 return .{ .offset = self.sent, .data = self.bytes[index..][0..0], .fin = true };
316 }
317
318 fn commitLost(self: *Send, offset: u62, length: u16, fin: bool) void {
319 if (fin) {
320 std.debug.assert(self.fin_lost);
321 self.fin_lost = false;
322 }
323 if (length == 0) return;
324 std.debug.assert(offset == self.lost.start);
325 self.lost.advance(length);
326 }
327
328 fn releaseAcknowledged(self: *Send) void {
329 var lowest = self.sent;
330 if (!self.lost.isEmpty()) lowest = @min(lowest, self.lost.start);
331 for (self.ranges) |range| {
332 if (range.outstanding and range.offset < lowest) lowest = range.offset;
333 }
334 std.debug.assert(lowest >= self.acknowledged);
335 const released: usize = @intCast(lowest - self.acknowledged);
336 std.debug.assert(released <= self.bytes.len);
337 self.head = (self.head + released) % self.bytes.len;
338 self.acknowledged = lowest;
339 }
340
341 fn ringIndex(self: *const Send, offset: u62) usize {
342 std.debug.assert(offset >= self.acknowledged);
343 const distance: usize = @intCast(offset - self.acknowledged);
344 std.debug.assert(distance <= self.bytes.len);
345 return (self.head + distance) % self.bytes.len;
346 }
347
348 fn freeRange(self: *const Send) ?usize {
349 for (self.ranges, 0..) |range, index| {
350 if (!range.outstanding) return index;
351 }
352 return null;
353 }
354
355 fn findRange(self: *const Send, offset: u62, length: u16, fin: bool) ?usize {
356 for (self.ranges, 0..) |range, index| {
357 if (!range.outstanding) continue;
358 if (range.offset != offset) continue;
359 if (range.length == length and range.fin == fin) return index;
360 }
361 return null;
362 }
363 };
364
365 /// Counts the bytes a STREAM frame spends ahead of `length` bytes of data, so the packet builder
366 /// knows the header size before determining how many data bytes fit. Those bytes carry the frame
367 /// type and the stream identifier always, the offset whenever it stands above zero, and the length
368 /// whenever the caller asks for one.
369 pub fn frameHeaderBytes(stream_id: u62, offset: u62, length: usize, length_present: bool) usize {
370 std.debug.assert(length <= std.math.maxInt(u62));
371 var bytes: usize = 1 + @as(usize, quic.varint.encodedLength(stream_id));
372 if (offset != 0) bytes += quic.varint.encodedLength(offset);
373 if (length_present) bytes += quic.varint.encodedLength(@intCast(length));
374 return bytes;
375 }
376
377 /// Works out the most data a STREAM frame can carry inside `available` bytes, so the packet builder
378 /// turns the room left in a packet into a byte count it can ask the sending part for. The call
379 /// returns null when even a frame without data cannot fit. The length field widens as the data
380 /// grows, so the answer is settled in two passes.
381 pub fn frameDataCapacity(
382 stream_id: u62,
383 offset: u62,
384 available: usize,
385 length_present: bool,
386 ) ?usize {
387 const empty = frameHeaderBytes(stream_id, offset, 0, length_present);
388 if (empty > available) return null;
389 const first = available - empty;
390 const header = frameHeaderBytes(stream_id, offset, first, length_present);
391 if (header + first <= available) return first;
392 std.debug.assert(header <= available);
393 const second = available - header;
394 const second_header = frameHeaderBytes(stream_id, offset, second, length_present);
395 std.debug.assert(second_header + second <= available);
396 return second;
397 }
398
399 /// Assembles the STREAM frame around one pick, so the packet builder turns the chunk into the frame
400 /// it encodes. The OFF bit goes in whenever the offset stands above zero.
401 pub fn frame(stream_id: u62, chunk: Chunk, length_present: bool) quic.frame.Frame {
402 return .{ .stream = .{
403 .stream_id = stream_id,
404 .offset = chunk.offset,
405 .offset_present = chunk.offset != 0,
406 .length_present = length_present,
407 .fin = chunk.fin,
408 .data = chunk.data,
409 } };
410 }
411
412 fn commitNext(send: *Send, data_max: usize) !Chunk {
413 const chunk = send.nextChunk(data_max, std.math.maxInt(u62)) orelse return error.NoChunk;
414 _ = send.commitChunk(chunk.offset, @intCast(chunk.data.len), chunk.fin);
415 return chunk;
416 }
417
418 test "RFC 9000 section 2.2 send buffer maximum and maximum plus one" {
419 var bytes: [8]u8 = undefined;
420 var ranges: [2]SentRange = undefined;
421 var send = Send.init(&bytes, &ranges, 64);
422 try std.testing.expectEqual(@as(usize, 8), send.write("abcdefgh"));
423 try std.testing.expectEqual(@as(usize, 0), send.write("i"));
424 try std.testing.expectEqual(@as(usize, 8), send.buffered());
425 }
426
427 test "RFC 9000 section 4.1 chunks stop at stream and connection credit" {
428 var bytes: [8]u8 = undefined;
429 var ranges: [4]SentRange = undefined;
430 var send = Send.init(&bytes, &ranges, 3);
431 try std.testing.expectEqual(@as(usize, 6), send.write("abcdef"));
432 try std.testing.expectEqualStrings("abc", (try commitNext(&send, 64)).data);
433 try std.testing.expect(send.nextChunk(64, 64) == null);
434 try std.testing.expectEqual(@as(?u62, 3), send.blocked());
435 send.commitBlocked(3);
436 try std.testing.expectEqual(@as(?u62, null), send.blocked());
437 send.credit.raise(5);
438 const limited = send.nextChunk(64, 1) orelse return error.NoChunk;
439 try std.testing.expectEqualStrings("d", limited.data);
440 try std.testing.expect(send.nextChunk(64, 0) == null);
441 }
442
443 test "RFC 9000 section 13.3 acknowledged STREAM frames release bytes in offset order" {
444 var bytes: [8]u8 = undefined;
445 var ranges: [4]SentRange = undefined;
446 var send = Send.init(&bytes, &ranges, 64);
447 _ = send.write("abcdefgh");
448 for (0..3) |_| _ = try commitNext(&send, 2);
449 send.acknowledge(2, 2, false);
450 try std.testing.expectEqual(@as(u62, 0), send.acknowledged);
451 try std.testing.expectEqual(@as(usize, 0), send.write("x"));
452 send.acknowledge(0, 2, false);
453 try std.testing.expectEqual(@as(u62, 4), send.acknowledged);
454 try std.testing.expectEqual(@as(usize, 4), send.write("wxyz"));
455 try std.testing.expectEqualStrings("gh", (try commitNext(&send, 8)).data);
456 try std.testing.expectEqualStrings("wxyz", (try commitNext(&send, 8)).data);
457 send.acknowledge(4, 2, false);
458 send.acknowledge(6, 2, false);
459 send.acknowledge(8, 4, false);
460 try std.testing.expectEqual(@as(usize, 0), send.buffered());
461 }
462
463 test "RFC 9000 section 13.3 sent range records maximum and maximum plus one" {
464 var bytes: [8]u8 = undefined;
465 var ranges: [2]SentRange = undefined;
466 var send = Send.init(&bytes, &ranges, 64);
467 _ = send.write("abcd");
468 _ = try commitNext(&send, 1);
469 _ = try commitNext(&send, 1);
470 try std.testing.expect(send.nextChunk(1, 64) == null);
471 send.acknowledge(1, 1, false);
472 try std.testing.expectEqualStrings("c", (try commitNext(&send, 1)).data);
473 }
474
475 test "RFC 9000 section 13.3 lost STREAM bytes go out again first without spending credit" {
476 var bytes: [8]u8 = undefined;
477 var ranges: [2]SentRange = undefined;
478 var send = Send.init(&bytes, &ranges, 8);
479 _ = send.write("abcdefgh");
480 _ = try commitNext(&send, 4);
481 _ = try commitNext(&send, 2);
482 send.lose(0, 4, false);
483 try std.testing.expectEqual(@as(u62, 0), send.nextOffset());
484 const resent = send.nextChunk(3, 0) orelse return error.NoChunk;
485 try std.testing.expectEqualStrings("abc", resent.data);
486 try std.testing.expectEqual(@as(u16, 0), send.commitChunk(0, 3, false));
487 try std.testing.expectEqual(@as(u62, 6), send.credit.used);
488 try std.testing.expect(send.nextChunk(8, 64) == null);
489 send.acknowledge(0, 3, false);
490 try std.testing.expectEqual(@as(u62, 3), send.acknowledged);
491 try std.testing.expectEqualStrings("d", (try commitNext(&send, 8)).data);
492 try std.testing.expectEqual(@as(u62, 6), send.nextOffset());
493 send.acknowledge(4, 2, false);
494 try std.testing.expectEqualStrings("gh", (try commitNext(&send, 8)).data);
495 try std.testing.expectEqual(@as(u62, 8), send.credit.used);
496 send.acknowledge(3, 1, false);
497 send.acknowledge(6, 2, false);
498 try std.testing.expectEqual(@as(usize, 0), send.buffered());
499 }
500
501 test "RFC 9000 section 13.3 an acknowledged copy trims the lost bytes it delivered" {
502 var bytes: [8]u8 = undefined;
503 var ranges: [4]SentRange = undefined;
504 var send = Send.init(&bytes, &ranges, 64);
505 _ = send.write("abcdef");
506 for (0..3) |_| _ = try commitNext(&send, 2);
507 send.lose(0, 2, false);
508 send.lose(4, 2, false);
509 try std.testing.expectEqual(@as(u62, 6), send.lost.length());
510 try std.testing.expectEqualStrings("ab", (try commitNext(&send, 2)).data);
511 send.acknowledge(2, 2, false);
512 try std.testing.expectEqual(@as(u62, 4), send.nextOffset());
513 try std.testing.expectEqualStrings("ef", (try commitNext(&send, 8)).data);
514 try std.testing.expect(!send.pending());
515 }
516
517 test "RFC 9000 section 13.3 a lost FIN goes out again alone or on the last lost byte" {
518 var bytes: [8]u8 = undefined;
519 var ranges: [2]SentRange = undefined;
520 var send = Send.init(&bytes, &ranges, 64);
521 _ = send.write("ab");
522 _ = try commitNext(&send, 8);
523 send.finish();
524 const fin_only = try commitNext(&send, 8);
525 try std.testing.expect(fin_only.fin);
526 try std.testing.expectEqual(@as(usize, 0), fin_only.data.len);
527 send.lose(2, 0, true);
528 try std.testing.expect(send.pending());
529 const alone = send.nextChunk(8, 0) orelse return error.NoChunk;
530 try std.testing.expect(alone.fin);
531 try std.testing.expectEqual(@as(u62, 2), alone.offset);
532 try std.testing.expectEqual(@as(usize, 0), alone.data.len);
533 try std.testing.expectEqual(@as(u16, 0), send.commitChunk(2, 0, true));
534 try std.testing.expect(!send.pending());
535 send.lose(0, 2, false);
536 send.lose(2, 0, true);
537 const last = send.nextChunk(8, 0) orelse return error.NoChunk;
538 try std.testing.expectEqualStrings("ab", last.data);
539 try std.testing.expect(last.fin);
540 try std.testing.expectEqual(@as(u16, 0), send.commitChunk(0, 2, true));
541 try std.testing.expect(!send.pending());
542 send.acknowledge(0, 2, true);
543 try std.testing.expectEqual(stream.SendState.data_recvd, send.state);
544 try std.testing.expectEqual(@as(usize, 0), send.buffered());
545 }
546
547 test "RFC 9000 section 3.1 an acknowledged FIN reaches Data Recvd" {
548 var bytes: [8]u8 = undefined;
549 var ranges: [2]SentRange = undefined;
550 var send = Send.init(&bytes, &ranges, 64);
551 _ = send.write("ab");
552 send.finish();
553 try std.testing.expectEqual(@as(usize, 0), send.write("c"));
554 const chunk = try commitNext(&send, 8);
555 try std.testing.expect(chunk.fin);
556 try std.testing.expectEqual(stream.SendState.data_sent, send.state);
557 try std.testing.expect(!send.pending());
558 send.acknowledge(0, 2, true);
559 try std.testing.expectEqual(stream.SendState.data_recvd, send.state);
560 try std.testing.expectEqual(@as(usize, 0), send.buffered());
561 }
562
563 test "RFC 9000 section 19.4 reset reports the sent final size and discards unsent bytes" {
564 var bytes: [8]u8 = undefined;
565 var ranges: [2]SentRange = undefined;
566 var send = Send.init(&bytes, &ranges, 64);
567 _ = send.write("abcdef");
568 _ = try commitNext(&send, 4);
569 try std.testing.expect(send.reset(0x11));
570 try std.testing.expect(!send.reset(0x12));
571 const reset_frame = send.resetFrame(0) orelse return error.NoReset;
572 try std.testing.expectEqual(@as(u62, 4), reset_frame.final_size);
573 try std.testing.expectEqual(@as(u62, 0x11), reset_frame.error_code);
574 try std.testing.expect(send.nextChunk(8, 64) == null);
575 try std.testing.expectEqual(@as(usize, 0), send.buffered());
576 try std.testing.expectEqual(@as(usize, 0), send.write("g"));
577 send.commitReset();
578 try std.testing.expectEqual(stream.SendState.reset_sent, send.state);
579 send.acknowledgeReset();
580 try std.testing.expectEqual(stream.SendState.reset_recvd, send.state);
581 }
582
583 test "RFC 9000 section 13.3 a lost RESET_STREAM goes out again unchanged until acknowledged" {
584 var bytes: [8]u8 = undefined;
585 var ranges: [2]SentRange = undefined;
586 var send = Send.init(&bytes, &ranges, 64);
587 _ = send.write("abcdef");
588 _ = try commitNext(&send, 4);
589 send.lose(0, 4, false);
590 try std.testing.expect(send.reset(0x11));
591 try std.testing.expect(send.lost.isEmpty());
592 const first = send.resetFrame(0) orelse return error.NoReset;
593 send.commitReset();
594 send.lose(0, 4, false);
595 try std.testing.expect(!send.pending());
596 send.loseReset();
597 const again = send.resetFrame(0) orelse return error.NoReset;
598 try std.testing.expectEqual(first, again);
599 send.commitReset();
600 try std.testing.expectEqual(stream.SendState.reset_sent, send.state);
601 send.acknowledgeReset();
602 try std.testing.expectEqual(stream.SendState.reset_recvd, send.state);
603 send.loseReset();
604 try std.testing.expectEqual(@as(?quic.frame.ResetStream, null), send.resetFrame(0));
605 }
606
607 test "RFC 9000 section 19.8 STREAM packing round trips every OFF, LEN, and FIN combination" {
608 const data = "stream-bytes";
609 for (0..8) |bits| {
610 const offset: u62 = if (bits & 4 != 0) 1_000 else 0;
611 const length_present = bits & 2 != 0;
612 const chunk = Chunk{ .offset = offset, .data = data, .fin = bits & 1 != 0 };
613 const available = frameHeaderBytes(4, offset, data.len, length_present) + data.len;
614 const capacity = frameDataCapacity(4, offset, available, length_present);
615 try std.testing.expectEqual(@as(?usize, data.len), capacity);
616 var packet: [32]u8 = undefined;
617 var output = quic.cursor.Write.init(packet[0..available]);
618 try quic.frame.encode(frame(4, chunk, length_present), &output);
619 try std.testing.expectEqual(available, output.index);
620 try std.testing.expectEqual(@as(u8, @intCast(0x08 | bits)), packet[0]);
621 var input = quic.cursor.Read.init(output.written());
622 const decoded = (try quic.frame.decode(&input)).stream;
623 try std.testing.expectEqual(@as(usize, 0), input.remaining());
624 try std.testing.expectEqual(@as(u62, 4), decoded.stream_id);
625 try std.testing.expectEqual(offset, decoded.offset);
626 try std.testing.expectEqual(chunk.fin, decoded.fin);
627 try std.testing.expectEqual(length_present, decoded.length_present);
628 try std.testing.expectEqualStrings(data, decoded.data);
629 }
630 try std.testing.expectEqual(@as(?usize, null), frameDataCapacity(4, 0, 1, false));
631 try std.testing.expectEqual(@as(?usize, 0), frameDataCapacity(4, 0, 2, false));
632 try std.testing.expectEqual(@as(?usize, 63), frameDataCapacity(4, 0, 66, true));
633 try std.testing.expectEqual(@as(?usize, 63), frameDataCapacity(4, 0, 67, true));
634 }