Statecharts · Problem 4

P4 · TCP, Flat and Then Not

The most widely implemented state diagram in the world, transcribed literally and then refactored into a hierarchy — with a nineteen-arrow acceptance table that is not allowed to move. 68 tests.


The system

The TCP connection state machine, exactly as RFC 9293 §3.3.2 draws it (Figure 5; unchanged in substance from RFC 793 in 1981). Eleven states, one of them — CLOSED — described in the RFC as “fictional”. No hierarchy, no regions, no history. It is the most widely implemented state diagram in the world and it is completely flat.

That is the point of the problem. Hierarchy is not a thing you apply to greenfield drawings; it is mostly a thing you apply to a flat machine that already works, without changing what it does. So: build it flat, get a table of nineteen transitions green, then refactor — and the table does not move.

Part A — transcribe it

Open Figure 5 and read it arrow by arrow. Two details repay attention:

Note 1. SYN-RECEIVED → LISTEN on RST happens only if SYN-RECEIVED was reached from a passive OPEN. Otherwise there is no LISTEN to fall back to. Two transitions on one event, distinguished by a guard over a flag set at OPEN time — your first piece of context.
Simultaneous open and simultaneous close. SYN-SENT → SYN-RECEIVED and FIN-WAIT-1 → CLOSING are the arrows people leave out. They are what CLOSING exists for.

Part B — group it

Now find the boxes. The eleven RFC names must survive unchanged — they are the protocol, and renaming them would make your implementation unreadable next to the spec. What changes is what contains them.

Build it

exercises/p4_tcp/machine.py — the skeleton. Its docstring is the contract: exact state ids, event names, and context attributes.
exercises/p4_tcp/test_machine.py — 68 acceptance tests. Do not edit them.
exercises/p4_tcp/_solution.py — a reference solution with commentary. Open it after yours is green, not before.
cd statecharts/exercises/p4_tcp
pytest -q

The suite is parametrised over both classes and the full transition table, which is why there are 68 tests for 19 arrows. Part A is done when the TCPFlat half is green; Part B when the TCPNested half is too, with the table untouched.

One trap, and it fails silently. The RFC's user call is SEND, but send is the method you deliver events with. Event names become attributes on the chart, so declaring send = listen.to(syn_sent) shadows it and every subsequent sm.send(...) stops working, with no error. The contract calls it user_send.

When it is green

Ask the honest question: is the grouped version better? It is shorter and it answers “is this connection being torn down?” directly. It is also one more level of indirection between a reader and Figure 5, which is the document they will actually have open. The RFC authors chose flat and they were not wrong to. Hierarchy buys you shared transitions and shared predicates; where there are few of either, it buys nothing and costs a level.

← P3Status mapP5 →

Sources: RFC 9293 §3.3.2 · RFC 793 · RESOURCES.md