Skip to content

Commit 45afe40

Browse files
authored
Merge pull request #36 from cceckman/slongfield/coffee
Implement minimal RFC2324 🫖
2 parents d9aee1c + e74cdd1 commit 45afe40

3 files changed

Lines changed: 106 additions & 9 deletions

File tree

http_server/parse_start.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class ParseStart(Component):
3737
METHOD_NO_MATCH = 0
3838
METHOD_GET = 1
3939
METHOD_POST = 2
40+
METHOD_BREW = 3
4041

4142
PROTOCOL_NO_MATCH = 0
4243
PROTOCOL_HTTP1_0 = 1
@@ -46,7 +47,7 @@ def __init__(self, paths):
4647
"input": In(stream.Signature(8)),
4748
"reset": In(1),
4849
"done": Out(1),
49-
"method": Out(3),
50+
"method": Out(4),
5051
"path": Out(len(paths)+1),
5152
"protocol": Out(2),
5253
})
@@ -68,12 +69,16 @@ def elaborate(self, _platform):
6869
resets.append(post_matcher.reset)
6970
m.d.comb += self.method[self.METHOD_POST].eq(post_matcher.accepted)
7071

72+
brew_matcher = m.submodules.brew_matcher = StringContainsMatch("BREW")
73+
resets.append(brew_matcher.reset)
74+
m.d.comb += self.method[self.METHOD_BREW].eq(brew_matcher.accepted)
75+
7176
any_method_match = stream_utils.tree_or(
72-
m, [get_matcher.accepted, post_matcher.accepted])
77+
m, [get_matcher.accepted, post_matcher.accepted, brew_matcher.accepted])
7378
m.d.comb += self.method[0].eq(~any_method_match)
7479

7580
stream_utils.fanout_stream(
76-
m, method_stream, [get_matcher.input, post_matcher.input])
81+
m, method_stream, [get_matcher.input, post_matcher.input, brew_matcher.input])
7782

7883
path_stream = stream.Signature(8).create()
7984
path_streams = []

http_server/simple_led_http.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ def elaborate(self, _platform):
4747
parser_demux = m.submodules.parser_demux = StreamDemux(mux_width=4, stream_width=8)
4848
connect(m, self.session.inbound.data, parser_demux.input)
4949

50-
# TODO: #4 - Add packet count and RFC2324 endpoints
5150
MATCHED_LED_PATH = 1 # start_matcher path match is in the order the paths are connected.
5251
MATCHED_COUNT_PATH = 2
53-
start_matcher = m.submodules.start_matcher = ParseStart(["/led", "/count"])
52+
MATCHED_COFFEE_PATH = 3
53+
start_matcher = m.submodules.start_matcher = ParseStart(["/led", "/count", "/coffee"])
5454
HTTP_PARSER_START = 0
5555
connect(m, start_matcher.input, parser_demux.outs[HTTP_PARSER_START])
5656

@@ -72,7 +72,7 @@ def elaborate(self, _platform):
7272
m.d.comb += parser_demux.outs[HTTP_PARSER_SINK].ready.eq(1)
7373

7474
## Responders
75-
response_mux = m.submodules.response_mux = StreamMux(mux_width=4, stream_width=8)
75+
response_mux = m.submodules.response_mux = StreamMux(mux_width=5, stream_width=8)
7676
connect(m, response_mux.out, self.session.outbound.data)
7777
count_body = m.submodules.count_body = CountBody()
7878

@@ -130,7 +130,25 @@ def elaborate(self, _platform):
130130
count_body.inc_error.eq(1),
131131
]
132132

133-
RESPONSE_COUNT = 3
133+
teapot_response = "\r\n".join(
134+
["HTTP/1.0 418 I'm a teapot",
135+
"Host: Fomu",
136+
"Content-Type: text/plain; charset=utf-8",
137+
"",
138+
"",
139+
"short and stout"]) + "\r\n"
140+
teapot_response = teapot_response.encode("utf-8")
141+
teapot_printer = m.submodules.teapot_printer = Printer(teapot_response)
142+
RESPONSE_TEAPOT = 3
143+
connect(m, teapot_printer.output, response_mux.input[RESPONSE_TEAPOT])
144+
send_teapot = [
145+
response_mux.select.eq(RESPONSE_TEAPOT),
146+
parser_demux.select.eq(HTTP_PARSER_SINK),
147+
teapot_printer.en.eq(1),
148+
count_body.inc_error.eq(1),
149+
]
150+
151+
RESPONSE_COUNT = 4
134152
connect(m, count_body.output, response_mux.input[RESPONSE_COUNT])
135153
send_count = [
136154
response_mux.select.eq(RESPONSE_COUNT),
@@ -185,6 +203,14 @@ def elaborate(self, _platform):
185203
with m.Else():
186204
m.next = "writing"
187205
m.d.sync += send_405
206+
with m.Elif(start_matcher.path[MATCHED_COFFEE_PATH]):
207+
with m.If(start_matcher.method[start_matcher.METHOD_GET]
208+
| start_matcher.method[start_matcher.METHOD_BREW]):
209+
m.next = "writing"
210+
m.d.sync += send_teapot
211+
with m.Else():
212+
m.next = "writing"
213+
m.d.sync += send_405
188214
with m.Else():
189215
m.next = "writing"
190216
m.d.sync += send_404
@@ -218,6 +244,7 @@ def elaborate(self, _platform):
218244
ok_printer.en.eq(0),
219245
not_found_printer.en.eq(0),
220246
not_allowed_printer.en.eq(0),
247+
teapot_printer.en.eq(0),
221248
count_body.en.eq(0),
222249
self.session.outbound.active.eq(1),
223250
count_body.inc_ok.eq(0),
@@ -226,7 +253,8 @@ def elaborate(self, _platform):
226253
with m.If( ((response_mux.select == RESPONSE_OK) & ok_printer.done)
227254
| ((response_mux.select == RESPONSE_404) & not_found_printer.done)
228255
| ((response_mux.select == RESPONSE_405) & not_allowed_printer.done)
229-
| ((response_mux.select == RESPONSE_COUNT) & count_body.done)):
256+
| ((response_mux.select == RESPONSE_COUNT) & count_body.done)
257+
| ((response_mux.select == RESPONSE_TEAPOT) & teapot_printer.done)):
230258
m.d.sync += self.session.outbound.active.eq(0)
231259
# Can finish writing before all the input is collected,
232260
# since a bad request migh trigger an early 404. Wait

http_server/simple_led_http_test.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,13 @@ def test_count_handling():
177177
"\r\n"
178178
"\r\n"
179179
"123456\r\n")
180+
error_input = ("BREW /cocoa HTTP/1.0\r\n"
181+
"Host: test\r\n"
182+
"User-Agent: test-agent\r\n"
183+
"Content-Type: text/plain\r\n"
184+
"\r\n"
185+
"\r\n"
186+
"With marshmallows, please\r\n")
180187
count_input = ("GET /count HTTP/1.0\r\n"
181188
"Host: test\r\n"
182189
"User-Agent: test-agent\r\n"
@@ -191,13 +198,19 @@ def test_count_handling():
191198
"\r\n"
192199
"\r\n"
193200
"👍\r\n"
201+
"HTTP/1.0 404 Not Found\r\n"
202+
"Host: Fomu\r\n"
203+
"Content-Type: text/plain; charset=utf-8\r\n"
204+
"\r\n"
205+
"\r\n"
206+
"👎\r\n"
194207
"HTTP/1.0 200 OK\r\n"
195208
"Host: Fomu\r\n"
196209
"Content-Type: text/plain; charset=utf-8\r\n"
197210
"\r\n"
198211
"\r\n"
199212
"👍\r\n"
200-
"requests: 0002 ok_responses: 0002 error_responses: 0000")
213+
"requests: 0003 ok_responses: 0002 error_responses: 0001")
201214

202215
async def driver(ctx):
203216

@@ -220,6 +233,7 @@ async def send_data(data):
220233
# assert not ctx.get(dut.session.outbound.data.valid)
221234

222235
await send_data(led_input)
236+
await send_data(error_input)
223237
await send_data(count_input)
224238

225239
assert ctx.get(dut.red) == 0x12
@@ -238,3 +252,53 @@ async def send_data(data):
238252

239253
# Now that the test is done:
240254
collector.assert_eq(expected_output)
255+
256+
def test_coffee_handling():
257+
dut = SimpleLedHttp()
258+
sim = Simulator(dut)
259+
sim.add_clock(1e-6)
260+
261+
input = ("BREW /coffee HTTP/1.0\r\n"
262+
"Host: curious_test\r\n"
263+
"User-Agent: evil-agent\r\n"
264+
"Content-Type: text/bad\r\n"
265+
"\r\n"
266+
"\r\n"
267+
"Black, medium roast Ethiopian, pour over\r\n")
268+
expected_output = ("HTTP/1.0 418 I'm a teapot\r\n"
269+
"Host: Fomu\r\n"
270+
"Content-Type: text/plain; charset=utf-8\r\n"
271+
"\r\n"
272+
"\r\n"
273+
"short and stout\r\n")
274+
275+
async def driver(ctx):
276+
ctx.set(dut.session.inbound.active, 1)
277+
await ctx.tick().until(dut.session.outbound.active)
278+
279+
in_stream = dut.session.inbound.data
280+
ctx.set(in_stream.valid, 1)
281+
idx = 0
282+
while idx < len(input):
283+
ctx.set(in_stream.payload, ord(input[idx]))
284+
if ctx.get(in_stream.ready):
285+
idx += 1
286+
await ctx.tick()
287+
# After all input data is read, deassert inbound session
288+
ctx.set(dut.session.inbound.active, 0)
289+
# Keep driving clock until the outbound session is deasserted
290+
await ctx.tick().until(~dut.session.outbound.active)
291+
assert not ctx.get(dut.session.outbound.data.valid)
292+
293+
# Add some nice margins for our vcd
294+
await ctx.tick()
295+
296+
sim.add_testbench(driver)
297+
298+
collector = StreamCollector(stream=dut.session.outbound.data)
299+
sim.add_process(collector.collect())
300+
301+
sim.run_until(0.001)
302+
303+
# Now that the test is done:
304+
collector.assert_eq(expected_output)

0 commit comments

Comments
 (0)