1//! Finds the end of an HTTP head in a stream.
2
3state: State = .start,
4
5pub const State = enum {
6 start,
7 seen_n,
8 seen_r,
9 seen_rn,
10 seen_rnr,
11 finished,
12};
13
14/// Returns the number of bytes consumed by headers. This is always less
15/// than or equal to `bytes.len`.
16///
17/// If the amount returned is less than `bytes.len`, the parser is in a
18/// content state and the first byte of content is located at
19/// `bytes[result]`.
20pub fn feed(p: *HeadParser, bytes: []const u8) usize {
21 const vector_len: comptime_int = @max(std.simd.suggestVectorLength(u8) orelse 1, 8);
22 var index: usize = 0;
23
24 while (true) {
25 switch (p.state) {
26 .finished => return index,
27 .start => switch (bytes.len - index) {
28 0 => return index,
29 1 => {
30 switch (bytes[index]) {
31 '\r' => p.state = .seen_r,
32 '\n' => p.state = .seen_n,
33 else => {},
34 }
35
36 return index + 1;
37 },
38 2 => {
39 const b16 = int16(bytes[index..][0..2]);
40 const b8 = intShift(u8, b16);
41
42 switch (b8) {
43 '\r' => p.state = .seen_r,
44 '\n' => p.state = .seen_n,
45 else => {},
46 }
47
48 switch (b16) {
49 int16("\r\n") => p.state = .seen_rn,
50 int16("\n\n") => p.state = .finished,
51 else => {},
52 }
53
54 return index + 2;
55 },
56 3 => {
57 const b24 = int24(bytes[index..][0..3]);
58 const b16 = intShift(u16, b24);
59 const b8 = intShift(u8, b24);
60
61 switch (b8) {
62 '\r' => p.state = .seen_r,
63 '\n' => p.state = .seen_n,
64 else => {},
65 }
66
67 switch (b16) {
68 int16("\r\n") => p.state = .seen_rn,
69 int16("\n\n") => p.state = .finished,
70 else => {},
71 }
72
73 switch (b24) {
74 int24("\r\n\r") => p.state = .seen_rnr,
75 else => {},
76 }
77
78 return index + 3;
79 },
80 4...vector_len - 1 => {
81 const b32 = int32(bytes[index..][0..4]);
82 const b24 = intShift(u24, b32);
83 const b16 = intShift(u16, b32);
84 const b8 = intShift(u8, b32);
85
86 switch (b8) {
87 '\r' => p.state = .seen_r,
88 '\n' => p.state = .seen_n,
89 else => {},
90 }
91
92 switch (b16) {
93 int16("\r\n") => p.state = .seen_rn,
94 int16("\n\n") => p.state = .finished,
95 else => {},
96 }
97
98 switch (b24) {
99 int24("\r\n\r") => p.state = .seen_rnr,
100 else => {},
101 }
102
103 switch (b32) {
104 int32("\r\n\r\n") => p.state = .finished,
105 else => {},
106 }
107
108 index += 4;
109 continue;
110 },
111 else => {
112 const Vector = @Vector(vector_len, u8);
113 // const BoolVector = @Vector(vector_len, bool);
114 const BitVector = @Vector(vector_len, u1);
115 const SizeVector = @Vector(vector_len, u8);
116
117 const chunk = bytes[index..][0..vector_len];
118 const v: Vector = chunk.*;
119 const matches_r: BitVector = @bitCast(v == @as(Vector, @splat('\r')));
120 const matches_n: BitVector = @bitCast(v == @as(Vector, @splat('\n')));
121 const matches_or: SizeVector = matches_r | matches_n;
122
123 const matches = @reduce(.Add, matches_or);
124 switch (matches) {
125 0 => {},
126 1 => switch (chunk[vector_len - 1]) {
127 '\r' => p.state = .seen_r,
128 '\n' => p.state = .seen_n,
129 else => {},
130 },
131 2 => {
132 const b16 = int16(chunk[vector_len - 2 ..][0..2]);
133 const b8 = intShift(u8, b16);
134
135 switch (b8) {
136 '\r' => p.state = .seen_r,
137 '\n' => p.state = .seen_n,
138 else => {},
139 }
140
141 switch (b16) {
142 int16("\r\n") => p.state = .seen_rn,
143 int16("\n\n") => p.state = .finished,
144 else => {},
145 }
146 },
147 3 => {
148 const b24 = int24(chunk[vector_len - 3 ..][0..3]);
149 const b16 = intShift(u16, b24);
150 const b8 = intShift(u8, b24);
151
152 switch (b8) {
153 '\r' => p.state = .seen_r,
154 '\n' => p.state = .seen_n,
155 else => {},
156 }
157
158 switch (b16) {
159 int16("\r\n") => p.state = .seen_rn,
160 int16("\n\n") => p.state = .finished,
161 else => {},
162 }
163
164 switch (b24) {
165 int24("\r\n\r") => p.state = .seen_rnr,
166 else => {},
167 }
168 },
169 4...vector_len => {
170 inline for (0..vector_len - 3) |i_usize| {
171 const i = @as(u32, @truncate(i_usize));
172
173 const b32 = int32(chunk[i..][0..4]);
174 const b16 = intShift(u16, b32);
175
176 if (b32 == int32("\r\n\r\n")) {
177 p.state = .finished;
178 return index + i + 4;
179 } else if (b16 == int16("\n\n")) {
180 p.state = .finished;
181 return index + i + 2;
182 }
183 }
184
185 const b24 = int24(chunk[vector_len - 3 ..][0..3]);
186 const b16 = intShift(u16, b24);
187 const b8 = intShift(u8, b24);
188
189 switch (b8) {
190 '\r' => p.state = .seen_r,
191 '\n' => p.state = .seen_n,
192 else => {},
193 }
194
195 switch (b16) {
196 int16("\r\n") => p.state = .seen_rn,
197 int16("\n\n") => p.state = .finished,
198 else => {},
199 }
200
201 switch (b24) {
202 int24("\r\n\r") => p.state = .seen_rnr,
203 else => {},
204 }
205 },
206 else => unreachable,
207 }
208
209 index += vector_len;
210 continue;
211 },
212 },
213 .seen_n => switch (bytes.len - index) {
214 0 => return index,
215 else => {
216 switch (bytes[index]) {
217 '\n' => p.state = .finished,
218 else => p.state = .start,
219 }
220
221 index += 1;
222 continue;
223 },
224 },
225 .seen_r => switch (bytes.len - index) {
226 0 => return index,
227 1 => {
228 switch (bytes[index]) {
229 '\n' => p.state = .seen_rn,
230 '\r' => p.state = .seen_r,
231 else => p.state = .start,
232 }
233
234 return index + 1;
235 },
236 2 => {
237 const b16 = int16(bytes[index..][0..2]);
238 const b8 = intShift(u8, b16);
239
240 switch (b8) {
241 '\r' => p.state = .seen_r,
242 '\n' => p.state = .seen_rn,
243 else => p.state = .start,
244 }
245
246 switch (b16) {
247 int16("\r\n") => p.state = .seen_rn,
248 int16("\n\r") => p.state = .seen_rnr,
249 int16("\n\n") => p.state = .finished,
250 else => {},
251 }
252
253 return index + 2;
254 },
255 else => {
256 const b24 = int24(bytes[index..][0..3]);
257 const b16 = intShift(u16, b24);
258 const b8 = intShift(u8, b24);
259
260 switch (b8) {
261 '\r' => p.state = .seen_r,
262 '\n' => p.state = .seen_n,
263 else => p.state = .start,
264 }
265
266 switch (b16) {
267 int16("\r\n") => p.state = .seen_rn,
268 int16("\n\n") => p.state = .finished,
269 else => {},
270 }
271
272 switch (b24) {
273 int24("\n\r\n") => p.state = .finished,
274 else => {},
275 }
276
277 index += 3;
278 continue;
279 },
280 },
281 .seen_rn => switch (bytes.len - index) {
282 0 => return index,
283 1 => {
284 switch (bytes[index]) {
285 '\r' => p.state = .seen_rnr,
286 '\n' => p.state = .seen_n,
287 else => p.state = .start,
288 }
289
290 return index + 1;
291 },
292 else => {
293 const b16 = int16(bytes[index..][0..2]);
294 const b8 = intShift(u8, b16);
295
296 switch (b8) {
297 '\r' => p.state = .seen_rnr,
298 '\n' => p.state = .seen_n,
299 else => p.state = .start,
300 }
301
302 switch (b16) {
303 int16("\r\n") => p.state = .finished,
304 int16("\n\n") => p.state = .finished,
305 else => {},
306 }
307
308 index += 2;
309 continue;
310 },
311 },
312 .seen_rnr => switch (bytes.len - index) {
313 0 => return index,
314 else => {
315 switch (bytes[index]) {
316 '\n' => p.state = .finished,
317 else => p.state = .start,
318 }
319
320 index += 1;
321 continue;
322 },
323 },
324 }
325
326 return index;
327 }
328}
329
330inline fn int16(array: *const [2]u8) u16 {
331 return std.mem.toNative(u16, @bitCast(array.*), .little);
332}
333
334inline fn int24(array: *const [3]u8) u24 {
335 return std.mem.toNative(u24, @bitCast(array.*), .little);
336}
337
338inline fn int32(array: *const [4]u8) u32 {
339 return std.mem.toNative(u32, @bitCast(array.*), .little);
340}
341
342inline fn intShift(comptime T: type, x: anytype) T {
343 switch (@import("builtin").cpu.arch.endian()) {
344 .little => return @truncate(x >> (@bitSizeOf(@TypeOf(x)) - @bitSizeOf(T))),
345 .big => return @truncate(x),
346 }
347}
348
349const HeadParser = @This();
350const std = @import("std");
351const builtin = @import("builtin");
352
353test feed {
354 const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\nHello";
355
356 for (0..36) |i| {
357 var p: HeadParser = .{};
358 try std.testing.expectEqual(i, p.feed(data[0..i]));
359 try std.testing.expectEqual(35 - i, p.feed(data[i..]));
360 }
361}