IPv4._make_opt_sec writes a SEC option that IPv4._read_opt_sec rejects, and raises IndexError on a single low-numbered authority.
Found while verifying #536's fixture evidence, not by reading the code looking for trouble: the project's own generated capture examples/captures/options-ipv4.pcap emits a warning on main today.
ProtocolWarning: IPv4: [OptNo 130] invalid format: field termination indicator not set
That capture is produced by examples/generators/options.py, which builds the option through IPv4.make. So the library's writer produces a SEC option its own reader flags as malformed.
Three distinct defects, all in pcapkit/protocols/internet/ipv4.py, all measured on 733000f65 (equivalent to origin/main c8fd97bcd).
1. The writer never sets the field termination indicator
RFC 1108 §2.2 gives bit 0 of each protection-authority octet as the field termination indicator: 0 means another octet follows, 1 means this is the last. The reader enforces it at ipv4.py:752-753:
if schema.data[-1] & 0x01 == 0:
warn(f'{self.alias}: [OptNo {schema.type}] invalid format: field termination indicator not set', ProtocolWarning)
_make_opt_sec at ipv4.py:1387-1393 builds data purely from the authority bit positions and never sets that bit:
>>> ip._make_opt_sec(OptionNumber.SEC, authorities=[ProtectionAuthority.GENSER,
... ProtectionAuthority.NSA]).data
b'\x90' # 0x90 & 0x01 == 0 -> own reader warns
Every SEC option the library writes with at least one authority is therefore malformed on the wire, unless the caller happens to pick authority 7 (see defect 3).
2. IndexError escapes on a single authority whose value is 0
int_len = math.ceil(max_auth / 8) at ipv4.py:1389 is off by one: it sizes the bitmap from the highest bit index rather than from the bit count. With the only authority being GENSER (value 0), int_len is 0, data_list is empty, and data_list[0] = b'1' raises:
>>> ip._make_opt_sec(OptionNumber.SEC, authorities=[ProtectionAuthority.GENSER])
IndexError: list assignment index out of range
Two consequences. It is a bare IndexError out of a _make_opt_* helper rather than an in-library exception from pcapkit.utilities.exceptions. And examples/generators/options.py:469-472 already documents a workaround for it in a comment rather than the library being fixed:
A single authority whose value is 0 makes _make_opt_sec compute a zero-octet bitmap and then index into it; two keeps it non-empty.
The off-by-one is also latent for max_auth == 8, which would need two octets and computes one. No enum member has value 8 today, so that half is not currently reachable.
3. Authority index 7 collides with the terminator bit, and the reader can never read it back
Enum_ProtectionAuthority member 7 is named Field_Termination_Indicator — it is not an authority at all, yet it is a member of the enum the writer accepts as one. Passing it produces an option that reads as validly terminated while carrying no authority:
>>> ip._make_opt_sec(OptionNumber.SEC, authorities=[ProtectionAuthority.Field_Termination_Indicator]).data
b'\x01' # terminator set, zero authorities encoded
Symmetrically, the reader's loop at ipv4.py:741 is for bit in range(7), deliberately excluding bit 7. So a real authority written at index 7 is silently dropped on read. Writer and reader disagree about whether index 7 is data.
Suggested shape of the fix
- Size the bitmap from the bit count,
math.ceil((max_auth + 1) / 8), and raise an in-library exception instead of letting IndexError escape.
- Set bit 0 of the final octet after building the bitmap, so the writer's output satisfies the reader.
- Reject
Field_Termination_Indicator as an authority argument rather than encoding it, since the enum names it as a structural bit.
- Once the writer is correct, the warning should disappear from
examples/captures/options-ipv4.pcap, which makes the fixture a regression test for free.
Coverage note
A test asserting that IPv4.make output round-trips through IPv4 without a ProtocolWarning would have caught defect 1 from the start; the existing option round-trip suite tolerates warnings.
IPv4._make_opt_secwrites a SEC option thatIPv4._read_opt_secrejects, and raisesIndexErroron a single low-numbered authority.Found while verifying #536's fixture evidence, not by reading the code looking for trouble: the project's own generated capture
examples/captures/options-ipv4.pcapemits a warning onmaintoday.That capture is produced by
examples/generators/options.py, which builds the option throughIPv4.make. So the library's writer produces a SEC option its own reader flags as malformed.Three distinct defects, all in
pcapkit/protocols/internet/ipv4.py, all measured on733000f65(equivalent toorigin/mainc8fd97bcd).1. The writer never sets the field termination indicator
RFC 1108 §2.2 gives bit 0 of each protection-authority octet as the field termination indicator:
0means another octet follows,1means this is the last. The reader enforces it atipv4.py:752-753:_make_opt_secatipv4.py:1387-1393buildsdatapurely from the authority bit positions and never sets that bit:Every SEC option the library writes with at least one authority is therefore malformed on the wire, unless the caller happens to pick authority 7 (see defect 3).
2.
IndexErrorescapes on a single authority whose value is 0int_len = math.ceil(max_auth / 8)atipv4.py:1389is off by one: it sizes the bitmap from the highest bit index rather than from the bit count. With the only authority beingGENSER(value0),int_lenis0,data_listis empty, anddata_list[0] = b'1'raises:Two consequences. It is a bare
IndexErrorout of a_make_opt_*helper rather than an in-library exception frompcapkit.utilities.exceptions. Andexamples/generators/options.py:469-472already documents a workaround for it in a comment rather than the library being fixed:The off-by-one is also latent for
max_auth == 8, which would need two octets and computes one. No enum member has value 8 today, so that half is not currently reachable.3. Authority index 7 collides with the terminator bit, and the reader can never read it back
Enum_ProtectionAuthoritymember7is namedField_Termination_Indicator— it is not an authority at all, yet it is a member of the enum the writer accepts as one. Passing it produces an option that reads as validly terminated while carrying no authority:Symmetrically, the reader's loop at
ipv4.py:741isfor bit in range(7), deliberately excluding bit 7. So a real authority written at index 7 is silently dropped on read. Writer and reader disagree about whether index 7 is data.Suggested shape of the fix
math.ceil((max_auth + 1) / 8), and raise an in-library exception instead of lettingIndexErrorescape.Field_Termination_Indicatoras an authority argument rather than encoding it, since the enum names it as a structural bit.examples/captures/options-ipv4.pcap, which makes the fixture a regression test for free.Coverage note
A test asserting that
IPv4.makeoutput round-trips throughIPv4without aProtocolWarningwould have caught defect 1 from the start; the existing option round-trip suite tolerates warnings.