@prepare treats a zero-length schema as end-of-file, so any schema whose fields sum to zero octets — or any nested schema handed an empty area — raises EOFError instead of unpacking to an empty instance.
Mechanism
pcapkit/utilities/decorators.py:221-222:
if length == 0:
raise EOFError
That is unconditional. It is a reasonable guard for the frame reader, where a zero-length read genuinely means the capture is exhausted, but @prepare wraps Schema.unpack, which is also called for nested schemas and for schemas that legitimately have nothing to read.
Reproduction, on e2d8ed6d1
@schema_final
class Empty(Schema):
pass
Empty.unpack(b'', 0, {})
A bare EOFError with no message, from a decorator the caller never mentioned.
Why this was unreachable until now
It is guarded behind the nested-packet fault in #445: before that is fixed, a nested schema sized from an enclosing field dies with KeyError: 'length' long before a zero-length area can be handed to unpack. PR #457 fixes #445, and five HTTP/2 frame schemas then reach this instead — so it becomes live the moment #457 lands.
Fix
Distinguish "the stream is exhausted" from "this schema has nothing to read". The frame-reader case needs the EOFError; the schema case wants an empty instance. Options, in rough order of invasiveness:
- Raise only when the underlying stream is genuinely at EOF (
data.read(0) semantics differ from a declared length == 0), rather than on the declared length.
- Let
Schema.unpack accept length == 0 and return an instance with empty buffers, and keep the EOFError in whichever caller actually needs it.
- Give
@prepare a flag for the nested case, which is the least attractive since it pushes the distinction onto every call site.
House convention would also prefer an in-library exception from pcapkit.utilities.exceptions over a bare EOFError wherever this does stay fatal, since a caller currently cannot tell this apart from a truncated capture.
Provenance
Surfaced by the agent implementing #445 (PR #457), which found that fixing the KeyError unblocks six HTTP/2 frame schemas whose pack-side flags lookup was previously unreachable — two now round-trip and five reach this and other latent defects. Verified and reproduced independently before filing.
@preparetreats a zero-length schema as end-of-file, so any schema whose fields sum to zero octets — or any nested schema handed an empty area — raisesEOFErrorinstead of unpacking to an empty instance.Mechanism
pcapkit/utilities/decorators.py:221-222:That is unconditional. It is a reasonable guard for the frame reader, where a zero-length read genuinely means the capture is exhausted, but
@preparewrapsSchema.unpack, which is also called for nested schemas and for schemas that legitimately have nothing to read.Reproduction, on
e2d8ed6d1A bare
EOFErrorwith no message, from a decorator the caller never mentioned.Why this was unreachable until now
It is guarded behind the nested-packet fault in #445: before that is fixed, a nested schema sized from an enclosing field dies with
KeyError: 'length'long before a zero-length area can be handed tounpack. PR #457 fixes #445, and five HTTP/2 frame schemas then reach this instead — so it becomes live the moment #457 lands.Fix
Distinguish "the stream is exhausted" from "this schema has nothing to read". The frame-reader case needs the
EOFError; the schema case wants an empty instance. Options, in rough order of invasiveness:data.read(0)semantics differ from a declaredlength == 0), rather than on the declared length.Schema.unpackacceptlength == 0and return an instance with empty buffers, and keep theEOFErrorin whichever caller actually needs it.@preparea flag for the nested case, which is the least attractive since it pushes the distinction onto every call site.House convention would also prefer an in-library exception from
pcapkit.utilities.exceptionsover a bareEOFErrorwherever this does stay fatal, since a caller currently cannot tell this apart from a truncated capture.Provenance
Surfaced by the agent implementing #445 (PR #457), which found that fixing the
KeyErrorunblocks six HTTP/2 frame schemas whose pack-sideflagslookup was previously unreachable — two now round-trip and five reach this and other latent defects. Verified and reproduced independently before filing.