lib/quic/src/connection/stream/state.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 /// States one half of a stream, the sending part, passes through, returned by
4 /// `Connection.sendState` so a caller learns how far the sending part has traveled. These states
5 /// are ready, send, data_sent, data_recvd, reset_sent, and reset_recvd.
6 pub const SendState = enum {
7 ready,
8 send,
9 data_sent,
10 data_recvd,
11 reset_sent,
12 reset_recvd,
13 };
14
15 /// Events that carry the sending part from one state to the next, grouped so a caller names
16 /// everything that can move that half.
17 pub const SendEvent = enum {
18 /// A frame carrying data without the closing flag, or a STREAM_DATA_BLOCKED frame, has gone
19 /// out.
20 send_data,
21 /// A frame carrying the closing flag has gone out.
22 send_fin,
23 /// The peer has acknowledged every byte up to the final size, and the closing flag with them.
24 all_acknowledged,
25 /// A RESET_STREAM frame has gone out.
26 send_reset,
27 /// The peer has acknowledged the RESET_STREAM frame.
28 reset_acknowledged,
29 };
30
31 /// States the other half of a stream, the receiving part, passes through, returned by
32 /// `Connection.receiveState` so a caller learns how far the receiving part has traveled. These
33 /// states are recv, size_known, data_recvd, data_read, reset_recvd, and reset_read.
34 pub const ReceiveState = enum {
35 recv,
36 size_known,
37 data_recvd,
38 data_read,
39 reset_recvd,
40 reset_read,
41 };
42
43 /// Events that carry the receiving part from one state to the next, grouped so a caller names
44 /// everything that can move that half.
45 pub const ReceiveEvent = enum {
46 /// A frame carrying data without the closing flag has arrived.
47 receive_data,
48 /// A frame carrying the closing flag has arrived.
49 receive_fin,
50 /// Every byte up to the final size has arrived.
51 all_received,
52 /// The application has taken every byte up to the final size.
53 read_all,
54 /// A RESET_STREAM frame has arrived.
55 receive_reset,
56 /// The application has seen the reset.
57 read_reset,
58 };
59
60 pub const TransitionError = error{InvalidTransition};
61
62 /// Applies every move of the sending part, taking one event against the current state and giving
63 /// back the state it lands in so a move the table has no entry for is caught at the table. An event
64 /// the current state has no entry for answers `InvalidTransition`. Data Recvd and Reset Recvd lead
65 /// nowhere further.
66 pub fn sendTransition(state: SendState, event: SendEvent) TransitionError!SendState {
67 return switch (state) {
68 .ready, .send => switch (event) {
69 .send_data => .send,
70 .send_fin => .data_sent,
71 .send_reset => .reset_sent,
72 .all_acknowledged, .reset_acknowledged => error.InvalidTransition,
73 },
74 .data_sent => switch (event) {
75 .all_acknowledged => .data_recvd,
76 .send_reset => .reset_sent,
77 .send_data, .send_fin, .reset_acknowledged => error.InvalidTransition,
78 },
79 .reset_sent => switch (event) {
80 .reset_acknowledged => .reset_recvd,
81 .send_data, .send_fin, .all_acknowledged, .send_reset => error.InvalidTransition,
82 },
83 .data_recvd, .reset_recvd => error.InvalidTransition,
84 };
85 }
86
87 /// Applies every move of the receiving part, taking one event against the current state and giving
88 /// back the state it lands in so a move the table has no entry for is caught at the table. An event
89 /// the current state has no entry for answers `InvalidTransition`. Data Read and Reset Read lead
90 /// nowhere further.
91 pub fn receiveTransition(state: ReceiveState, event: ReceiveEvent) TransitionError!ReceiveState {
92 return switch (state) {
93 .recv => switch (event) {
94 .receive_data => .recv,
95 .receive_fin => .size_known,
96 .receive_reset => .reset_recvd,
97 .all_received, .read_all, .read_reset => error.InvalidTransition,
98 },
99 .size_known => switch (event) {
100 .receive_data => .size_known,
101 .all_received => .data_recvd,
102 .receive_reset => .reset_recvd,
103 .receive_fin, .read_all, .read_reset => error.InvalidTransition,
104 },
105 .data_recvd => if (event == .read_all) .data_read else error.InvalidTransition,
106 .reset_recvd => if (event == .read_reset) .reset_read else error.InvalidTransition,
107 .data_read, .reset_read => error.InvalidTransition,
108 };
109 }
110
111 const SendRow = struct { from: SendState, event: SendEvent, to: SendState };
112 const ReceiveRow = struct { from: ReceiveState, event: ReceiveEvent, to: ReceiveState };
113
114 const send_table = [_]SendRow{
115 .{ .from = .ready, .event = .send_data, .to = .send },
116 .{ .from = .ready, .event = .send_fin, .to = .data_sent },
117 .{ .from = .ready, .event = .send_reset, .to = .reset_sent },
118 .{ .from = .send, .event = .send_data, .to = .send },
119 .{ .from = .send, .event = .send_fin, .to = .data_sent },
120 .{ .from = .send, .event = .send_reset, .to = .reset_sent },
121 .{ .from = .data_sent, .event = .all_acknowledged, .to = .data_recvd },
122 .{ .from = .data_sent, .event = .send_reset, .to = .reset_sent },
123 .{ .from = .reset_sent, .event = .reset_acknowledged, .to = .reset_recvd },
124 };
125
126 const receive_table = [_]ReceiveRow{
127 .{ .from = .recv, .event = .receive_data, .to = .recv },
128 .{ .from = .recv, .event = .receive_fin, .to = .size_known },
129 .{ .from = .recv, .event = .receive_reset, .to = .reset_recvd },
130 .{ .from = .size_known, .event = .receive_data, .to = .size_known },
131 .{ .from = .size_known, .event = .all_received, .to = .data_recvd },
132 .{ .from = .size_known, .event = .receive_reset, .to = .reset_recvd },
133 .{ .from = .data_recvd, .event = .read_all, .to = .data_read },
134 .{ .from = .reset_recvd, .event = .read_reset, .to = .reset_read },
135 };
136
137 fn sendRow(from: SendState, event: SendEvent) ?SendState {
138 for (send_table) |row| {
139 if (row.from == from and row.event == event) return row.to;
140 }
141 return null;
142 }
143
144 fn receiveRow(from: ReceiveState, event: ReceiveEvent) ?ReceiveState {
145 for (receive_table) |row| {
146 if (row.from == from and row.event == event) return row.to;
147 }
148 return null;
149 }
150
151 test "RFC 9000 section 3.1 sending part transitions equal the state table" {
152 var illegal_per_state: [@typeInfo(SendState).@"enum".field_names.len]u8 = @splat(0);
153 for (std.enums.values(SendState)) |from| {
154 for (std.enums.values(SendEvent)) |event| {
155 const result = sendTransition(from, event);
156 if (sendRow(from, event)) |to| {
157 try std.testing.expectEqual(to, try result);
158 } else {
159 try std.testing.expectError(error.InvalidTransition, result);
160 illegal_per_state[@backingInt(from)] += 1;
161 }
162 }
163 }
164 for (illegal_per_state) |count| try std.testing.expect(count >= 1);
165 }
166
167 test "RFC 9000 section 3.2 receiving part transitions equal the state table" {
168 var illegal_per_state: [@typeInfo(ReceiveState).@"enum".field_names.len]u8 = @splat(0);
169 for (std.enums.values(ReceiveState)) |from| {
170 for (std.enums.values(ReceiveEvent)) |event| {
171 const result = receiveTransition(from, event);
172 if (receiveRow(from, event)) |to| {
173 try std.testing.expectEqual(to, try result);
174 } else {
175 try std.testing.expectError(error.InvalidTransition, result);
176 illegal_per_state[@backingInt(from)] += 1;
177 }
178 }
179 }
180 for (illegal_per_state) |count| try std.testing.expect(count >= 1);
181 }