ListField.unpack's schema-item branch computes a per-item field and then discards it:
field = self._item_type(packet)
if is_schema:
data = cast('SchemaField', self._item_type).unpack(file, packet)
field is used by the non-schema branch below (length -= field.length, file.read(field.length)), but the schema branch unpacks from self._item_type — the unconfigured field — so anything SchemaField.__call__ applied to build field is thrown away:
def __call__(self, packet: 'dict[str, Any]') -> 'Self':
new_self = copy.copy(self)
new_self._callback(new_self, packet)
if new_self._length_callback is not None:
new_self._length = new_self._length_callback(packet)
new_self._template = f'{new_self._length}s' if self._length >= 0 else '1024s'
return new_self
So a ListField whose item is a SchemaField carrying a callback or a length_callback would parse with neither applied.
Not currently reachable
All four in-tree declarations pass only schema=, plus a static length= in two cases:
| site |
item |
pcapkit/protocols/schema/internet/mh.py:535 |
SchemaField(schema=CGAParameter) |
pcapkit/protocols/schema/internet/hip.py:260 |
SchemaField(schema=Locator) |
pcapkit/protocols/schema/transport/tcp.py:393 |
SchemaField(length=8, schema=SACKBlock) |
pcapkit/protocols/schema/transport/sctp.py:702 |
SchemaField(length=4, schema=GapAckBlock) |
None passes callback or length_callback, so __call__ returns a copy.copy whose callback is the default no-op and whose _length_callback is None, leaving the discarded field equivalent to self._item_type. The length=lambda pkt: … those declarations do carry is the ListField's own length, not the item's.
Worth being explicit that this is a latent trap rather than a live defect: no capture parses differently today, and the fix would be byte-identical on everything in examples/captures/. It matters because the trap is invisible at the call site — someone adding a ListField item with a length_callback, which the field supports and which the sibling branch honours, gets it silently ignored.
Dates to 86966fd7b (2023-04-13). Surfaced by a Copilot review on #432, where it was declined as out of scope: the line is pre-existing and the PR only added a progress guard beneath it.
ListField.unpack's schema-item branch computes a per-item field and then discards it:fieldis used by the non-schema branch below (length -= field.length,file.read(field.length)), but the schema branch unpacks fromself._item_type— the unconfigured field — so anythingSchemaField.__call__applied to buildfieldis thrown away:So a
ListFieldwhose item is aSchemaFieldcarrying acallbackor alength_callbackwould parse with neither applied.Not currently reachable
All four in-tree declarations pass only
schema=, plus a staticlength=in two cases:pcapkit/protocols/schema/internet/mh.py:535SchemaField(schema=CGAParameter)pcapkit/protocols/schema/internet/hip.py:260SchemaField(schema=Locator)pcapkit/protocols/schema/transport/tcp.py:393SchemaField(length=8, schema=SACKBlock)pcapkit/protocols/schema/transport/sctp.py:702SchemaField(length=4, schema=GapAckBlock)None passes
callbackorlength_callback, so__call__returns acopy.copywhose callback is the default no-op and whose_length_callbackisNone, leaving the discardedfieldequivalent toself._item_type. Thelength=lambda pkt: …those declarations do carry is theListField's own length, not the item's.Worth being explicit that this is a latent trap rather than a live defect: no capture parses differently today, and the fix would be byte-identical on everything in
examples/captures/. It matters because the trap is invisible at the call site — someone adding aListFielditem with alength_callback, which the field supports and which the sibling branch honours, gets it silently ignored.Dates to
86966fd7b(2023-04-13). Surfaced by a Copilot review on #432, where it was declined as out of scope: the line is pre-existing and the PR only added a progress guard beneath it.