TCP.read seeds the connection flags with cast('Enum_Flags', 0), which is a no-op at runtime. A segment with no flags set therefore leaves self._flags as a plain int, and any membership test against it raises TypeError instead of answering.
Mechanism
pcapkit/protocols/transport/tcp.py:481 and :563 both do:
_flag = cast('Enum_Flags', 0)
...
self._flags = _flag
typing.cast performs no conversion — it is a static-checker annotation only. When at least one flag bit is set the subsequent _flag |= Enum_Flags.get(...) promotes the value to an aenum.IntFlag, which masks the problem. When no bit is set, nothing promotes it and the plain 0 survives.
Measured
On origin/main (4529fdb1f), CPython 3.14.7, parsing a TCP segment with an all-zero flags octet:
parsed flagless segment: type(_flags) = int value = 0
Flags.SYN in _flags -> TypeError: argument of type 'int' is not a container or iterable
Why it matters, and why it is narrow
self._flags is read by the MPTCP option dispatchers — _read_mptcp_join around tcp.py:1558 and _make_mptcp_join around :2812 — to choose between RFC 8684 §3.2's three MP_JOIN layouts. Those are the same reads that #587 was about.
The saving grace is that mptcp_data_selector at pcapkit/protocols/schema/transport/tcp.py:204-212 rejects a flagless MP_JOIN before the dispatcher is reached, so the TypeError is not currently reachable from a caller on the read path. That makes this a latent defect rather than a live one — but it is latent only by virtue of a guard in a different file, which is a fragile reason for a TypeError not to happen.
Enum_Flags(0) is the correct seed and is what #597 used on the make path for exactly this reason.
Notes
TCP.readseeds the connection flags withcast('Enum_Flags', 0), which is a no-op at runtime. A segment with no flags set therefore leavesself._flagsas a plainint, and any membership test against it raisesTypeErrorinstead of answering.Mechanism
pcapkit/protocols/transport/tcp.py:481and:563both do:typing.castperforms no conversion — it is a static-checker annotation only. When at least one flag bit is set the subsequent_flag |= Enum_Flags.get(...)promotes the value to anaenum.IntFlag, which masks the problem. When no bit is set, nothing promotes it and the plain0survives.Measured
On
origin/main(4529fdb1f), CPython 3.14.7, parsing a TCP segment with an all-zero flags octet:Why it matters, and why it is narrow
self._flagsis read by the MPTCP option dispatchers —_read_mptcp_joinaroundtcp.py:1558and_make_mptcp_joinaround:2812— to choose between RFC 8684 §3.2's three MP_JOIN layouts. Those are the same reads that #587 was about.The saving grace is that
mptcp_data_selectoratpcapkit/protocols/schema/transport/tcp.py:204-212rejects a flagless MP_JOIN before the dispatcher is reached, so theTypeErroris not currently reachable from a caller on the read path. That makes this a latent defect rather than a live one — but it is latent only by virtue of a guard in a different file, which is a fragile reason for aTypeErrornot to happen.Enum_Flags(0)is the correct seed and is what #597 used on the make path for exactly this reason.Notes
tests/protocols/transport/test_tcp_udp_unit.pyto construct through the public path; it is in a production file outside that change's scope, so it reported rather than fixing. Verified independently here by execution._flag = cast('Enum_Flags', 0)there was one of the two defects test(tcp): reach the MP_JOIN dispatchers through TCP(), not a hand-written _flags (#603) #612's demonstration re-introduced to prove its tests have teeth). The read path was deliberately left, and this issue is that remainder.0is indistinguishable from a correctEnum_Flags(0)by equality —0 == Enum_Flags(0)isTrue.