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 }