Core Question
Core Question
Sequence numbers are 32-bit and wrap back to zero — so what does "before" even mean once a connection has been running long enough to cross that boundary, and why is plain < the wrong tool to answer it?
Outcome
Outcome
By the end of this session, the learner should be able to:
- state
SEQ_SPACEand explain why it is232, not232 - 1 - explain the signed-difference trick
seq_lt()uses and why it only works because half the ring is defined as "ahead" - give a concrete pair of sequence numbers where plain
<andseq_lt()disagree - explain what
in_receive_window()checks and why it needs bothrcv_nxtandwindow
Read Order
Read Order
- Read
SEQ_SPACE - Read
seq_add() - Read
seq_lt() - Read
seq_le() - Read
in_receive_window() - Run
examples/tcp/session_03_walkthrough.py
Read It Like Code
Read It Like Code
seq_lt(a, b) # is a "before" b on the ring? seq_le(a, b) # a == b or seq_lt(a, b) in_receive_window(seq, rcv_nxt, window) # does seq fall in [rcv_nxt, rcv_nxt + window)?
Fields That Matter
Fields That Matter
There are no dataclasses in this module — every function in seqnum.py takes plain integers. That absence is the point: this session is about arithmetic on a ring, not about a structure to inspect.
| Name | Why it matters |
|---|---|
SEQ_SPACE | 2**32. The modulus every function in this module reduces against. |
a, b (in seq_lt/seq_le) | Two raw sequence numbers, each already in [0, SEQ_SPACE). Neither is privileged as "current" — the function just orders them. |
rcv_nxt, window (in in_receive_window) | Together they define the acceptable range [rcv_nxt, rcv_nxt + window), which itself may wrap past SEQ_SPACE. |
Decision Flow
Decision Flow
seq_add(a, n) -> (a + n) % SEQ_SPACE
seq_lt(a, b):
(b - a) % SEQ_SPACE < SEQ_SPACE // 2 and a != b -> True
otherwise -> False
seq_le(a, b):
a == b or seq_lt(a, b) -> True
otherwise -> False
in_receive_window(seq, rcv_nxt, window):
window <= 0 -> seq == rcv_nxt
otherwise: offset = (seq - rcv_nxt) % SEQ_SPACE
offset < window -> TrueReading Lens
Reading Lens
The important move in this session is to stop thinking "sequence numbers are just big integers, so I can compare them with <" and start asking:
- how far apart are these two numbers going *forward* around the ring, not by subtraction in the ordinary sense?
- is that forward distance less than half the ring? That's what "ahead" means here.
- for
in_receive_window(): does the *offset* fromrcv_nxtfall insidewindow, treating the whole check as a distance-and-wrap problem, not a min/max range check?
seq_lt(a, b) is not "a's numeric value is smaller." It is "walking forward from a, you reach b before you've gone halfway around the ring." That reframing is the entire session.
Toy Model Boundary
Toy Model Boundary
Real TCP sequence-number comparisons (RFC 9293 uses the term "sequence number arithmetic modulo 2**32") also have to account for retransmission ambiguity, PAWS (Protection Against Wrapped Sequences) using timestamps, and ISN randomization to defend against blind spoofing. This module skips all of that: it implements exactly the ring arithmetic needed to answer "is a before b" and "is seq in the acceptable receive range," nothing about how those functions get used to defend a real connection.
seq_lt()'s half-the-ring rule breaks down if the true distance between two sequence numbers is close to or exceeds SEQ_SPACE // 2 — real implementations avoid this by keeping window sizes far smaller than half the sequence space. This module does not enforce that constraint; it is a property the caller must respect for the comparison to mean anything.
Code Landmarks
Code Landmarks
SEQ_SPACE
2**32, matching the 32-bit sequence number field from Session 01's Segment.seq. Every other function in this file reduces modulo this constant.
seq_add()
The simplest function here, and the one that makes wrapping concrete: adding past SEQ_SPACE - 1 lands back near zero.
seq_lt()
The main reading target. (b - a) % SEQ_SPACE < SEQ_SPACE // 2 computes the forward distance from a to b and asks if it's in the "near" half of the ring. The and a != b guard is what makes a value never "less than" itself — without it, (b - a) % SEQ_SPACE for a == b is 0, which is less than half the ring, and the function would wrongly say a number is before itself.
seq_le()
Built entirely from seq_lt() plus an equality check — no independent ring arithmetic of its own.
in_receive_window()
Two branches. window <= 0 collapses the acceptable range to exactly one value, rcv_nxt itself — this is the zero-window case that Session 04's advertised_window() can produce. Otherwise, offset = (seq - rcv_nxt) % SEQ_SPACE turns the wraparound range check into a plain offset < window comparison.
Failure Questions
Failure Questions
Use the source file to answer these:
- For
a = 2**32 - 1andb = 0, what doesseq_lt(a, b)return? What would plain integer<return for the same two values, and why do they disagree? - Why does
seq_lt(a, a)returnFalsefor everya? Which specific piece of the expression makes that guaranteed rather than incidental? - At exactly
seq == rcv_nxt + window(not one less), doesin_receive_window()returnTrueorFalse? Trace theoffset < windowcomparison to be sure. - When
window == 0, what is the onlyseqvaluein_receive_window()accepts? Which branch decides this, and why does it not fall through to the offset computation? - Is
seq_le(a, b)everTruewhenseq_lt(a, b)isFalseanda != b? Read the definition ofseq_le()to justify your answer.
Walkthrough
Walkthrough
Run this:
PYTHONPATH=src python3 examples/tcp/session_03_walkthrough.py
The walkthrough picks a sequence number 10 below the top of the ring, adds past the wrap, and shows a case where plain < gives the wrong answer but seq_lt() gives the right one — then extends the same idea to seq_le() and to a receive window that spans the wrap.
Done When
Done When
The learner can say all of the following without looking at notes:
- "Sequence numbers live on a ring of size 2**32; 'before' means forward-distance-within-half-the-ring, not numeric less-than."
- "Plain
<is wrong exactly when the comparison straddles the wrap point —seq_lt()is the only correct tool near 2**32 -> 0." - "A receive window is a range starting at
rcv_nxt, and that range itself can wrap, which is whyin_receive_window()works in offsets, not absolute bounds."
References
References
- RFC 9293 Section 3.4 (Sequence Numbers)
- RFC 9293 Section 3.4.1 (specifically the "Sequence Number Wraparound" discussion of modulo-2**32 arithmetic)
- RFC 9293 Section 3.8.6 (Managing the Window — the receive-window range
in_receive_window()implements)
Continue