Schema.pack's ListField branch accepts list but not tuple, so no data object a _read_param_* produces can be handed back to its _make_param_*. Sixteen HIP parameters are affected, and the round-trip harness has been recording it as sixteen separate EXPECTED_FAILURES entries rather than one defect.
The three facts, and the writer is the one out of step
The tuple is not an accident on the reader's side — it is the declared, documented data type:
pcapkit/protocols/data/internet/hip.py:258 declares group_id: 'tuple[Group, ...]', and the TYPE_CHECKING __init__ stub on the next line repeats it.
pcapkit/protocols/internet/hip.py:1216 therefore builds one deliberately: group_id=tuple(schema.groups).
pcapkit/protocols/schema/schema.py:615-624 then refuses it:
if isinstance(field, ListField):
if data is None:
self.__buffer__[field.name] = b''
elif isinstance(data, bytes):
self.__buffer__[field.name] = data
elif isinstance(data, list):
self.__buffer__[field.name] = field.pack(data, packet)
else:
raise ProtocolUnbound(f'unsupported type {type(data)}')
continue
So the reader honours its own declared type and the writer rejects it. Describing this as "the reader returns a tuple where the maker needs a list" gets the blame backwards: nothing says the data model should have been list, and changing it would be an API break across every Parameter data class.
Reproduction
$ python -c "...roundtrip(DH_GROUP_LIST case)..."
DH_GROUP_LIST roundtrip -> RECONSTRUCT
fragment: ProtocolUnbound: unsupported type <class 'tuple'>
The schema layer itself is fine — _make_param_dh_group_list(..., group_id=[1,2,3]) packs and DHGroupListParameter.unpack recovers it. The break is only on the parse-then-reconstruct path, which is why unit tests that build schemas directly never see it.
Scope: 16 entries, verified by enumeration
Every EXPECTED_FAILURES entry in tests/protocols/test_option_roundtrip_unit.py whose recorded defect names schema.py:624, all with status='RECONSTRUCT' and fragment unsupported type <class 'tuple'>:
hip-parameter/ACK, ACK_DATA, DH_GROUP_LIST, ESP_TRANSFORM, HIP_CIPHER, HIP_TRANSPORT_MODE, HIT_SUITE_LIST, NAT_TRAVERSAL_MODE, REG_FAILED, REG_INFO, REG_REQUEST, REG_RESPONSE, ROUTE_DST, ROUTE_VIA, TRANSPORT_FORMAT_LIST, VIA_RVS.
That is 16 of the 87 entries — over one in six of the whole table — collapsing to a single root cause. (A review of #475 put the figure at 15 "other" parameters; the enumerated count is 16 in total, so 14 besides the two #475 touches. I enumerated rather than accepting the tally, and the numbers differ, so the 16 above is the one to work from.)
Suggested fix
Accept any non-bytes sequence in that branch rather than list specifically — ListField.pack does not care which it gets. Something in the shape of:
elif isinstance(data, (list, tuple)):
self.__buffer__[field.name] = field.pack(list(data), packet)
or a broader collections.abc.Sequence test with bytes/str excluded first, if other sequence types are wanted. Either keeps ProtocolUnbound for genuinely unsupported types, which is the branch's real job.
Worth checking as part of the fix, and not verified here: whether the same list-only assumption exists in the unpack direction or in other schema families (PCAP-NG options, MH extensions) that also declare tuple in their data models. I have only enumerated the HIP EXPECTED_FAILURES entries.
Deleting the 16 EXPECTED_FAILURES entries is the acceptance test, but the entries must be deleted and the cases re-enumerated through options.cases(), not inferred from a green suite — a table that tracks the defect in lockstep with the code cannot distinguish a fix from a disguised regression.
Schema.pack'sListFieldbranch acceptslistbut nottuple, so no data object a_read_param_*produces can be handed back to its_make_param_*. Sixteen HIP parameters are affected, and the round-trip harness has been recording it as sixteen separateEXPECTED_FAILURESentries rather than one defect.The three facts, and the writer is the one out of step
The tuple is not an accident on the reader's side — it is the declared, documented data type:
pcapkit/protocols/data/internet/hip.py:258declaresgroup_id: 'tuple[Group, ...]', and theTYPE_CHECKING__init__stub on the next line repeats it.pcapkit/protocols/internet/hip.py:1216therefore builds one deliberately:group_id=tuple(schema.groups).pcapkit/protocols/schema/schema.py:615-624then refuses it:So the reader honours its own declared type and the writer rejects it. Describing this as "the reader returns a tuple where the maker needs a list" gets the blame backwards: nothing says the data model should have been
list, and changing it would be an API break across everyParameterdata class.Reproduction
The schema layer itself is fine —
_make_param_dh_group_list(..., group_id=[1,2,3])packs andDHGroupListParameter.unpackrecovers it. The break is only on the parse-then-reconstruct path, which is why unit tests that build schemas directly never see it.Scope: 16 entries, verified by enumeration
Every
EXPECTED_FAILURESentry intests/protocols/test_option_roundtrip_unit.pywhose recorded defect namesschema.py:624, all withstatus='RECONSTRUCT'and fragmentunsupported type <class 'tuple'>:hip-parameter/ACK,ACK_DATA,DH_GROUP_LIST,ESP_TRANSFORM,HIP_CIPHER,HIP_TRANSPORT_MODE,HIT_SUITE_LIST,NAT_TRAVERSAL_MODE,REG_FAILED,REG_INFO,REG_REQUEST,REG_RESPONSE,ROUTE_DST,ROUTE_VIA,TRANSPORT_FORMAT_LIST,VIA_RVS.That is 16 of the 87 entries — over one in six of the whole table — collapsing to a single root cause. (A review of #475 put the figure at 15 "other" parameters; the enumerated count is 16 in total, so 14 besides the two #475 touches. I enumerated rather than accepting the tally, and the numbers differ, so the 16 above is the one to work from.)
Suggested fix
Accept any non-
bytessequence in that branch rather thanlistspecifically —ListField.packdoes not care which it gets. Something in the shape of:or a broader
collections.abc.Sequencetest withbytes/strexcluded first, if other sequence types are wanted. Either keepsProtocolUnboundfor genuinely unsupported types, which is the branch's real job.Worth checking as part of the fix, and not verified here: whether the same
list-only assumption exists in theunpackdirection or in other schema families (PCAP-NG options, MH extensions) that also declaretuplein their data models. I have only enumerated the HIPEXPECTED_FAILURESentries.Deleting the 16
EXPECTED_FAILURESentries is the acceptance test, but the entries must be deleted and the cases re-enumerated throughoptions.cases(), not inferred from a green suite — a table that tracks the defect in lockstep with the code cannot distinguish a fix from a disguised regression.