Skip to content

@prepare makes Schema.unpack's optional arguments mandatory and positional, so SchemaField(default=bytes) always raises #444

Description

@JarryShaw

Schema.unpack is documented and typed as unpack(cls, data, length=None, packet=None), with both trailing parameters optional. The @prepare decorator that wraps it reads its arguments by position out of *args and never looks at **kwargs, so in practice exactly one call shape works: three positional arguments.

Measured on f50436a8a

unpack(data)                         IndexError: tuple index out of range
unpack(data, length)                 IndexError: tuple index out of range
unpack(data, length, packet)         OK   -> a=7 b=8
unpack(data, length=2)               IndexError: tuple index out of range
unpack(data, length=2, packet={})    IndexError: tuple index out of range

against a two-field schema and wire = b'\x07\x08'.

Mechanism

pcapkit/utilities/decorators.py:207-210:

def unpack(*args: 'P.args', **kwargs: 'P.kwargs') -> 'R_prepare':
    cls = cast('Type[R_prepare]', args[0])
    data = cast('bytes | IO[bytes]', args[1])
    length = cast('Optional[int]', args[2])
    packet = cast('Optional[dict[str, Any]]', args[3])

Four unconditional subscripts, and kwargs is accepted but never read. The body immediately below goes to some trouble to handle length is None and packet is None, so the defaults were clearly intended to be reachable — they just cannot be supplied. A keyword call fails for the same reason a short positional call does: the value lands in kwargs, where nothing looks for it.

There is already a casualty in the tree

pcapkit/corekit/fields/misc.py:539, in SchemaField.__init__:

if isinstance(default, bytes):
    default = cast('_TS', schema.unpack(default))  # type: ignore[call-arg,misc]

One argument, so this raises. Which makes SchemaField(schema=..., default=b'...') unusable:

SchemaField(schema=Inner, default=b'\x01\x02')
  File "pcapkit/corekit/fields/misc.py", line 539, in __init__
    default = cast('_TS', schema.unpack(default))
  File "pcapkit/utilities/decorators.py", line 209, in unpack
    length = cast('Optional[int]', args[2])
IndexError: tuple index out of range

default is a documented, typed constructor argument of SchemaField, and passing bytes to it — the one type that branch exists to handle — cannot succeed. Note the # type: ignore[call-arg,misc]: mypy already flagged this call as wrong and the diagnosis was suppressed rather than followed.

Nothing else in the tree is affected, because every other call site passes three positional arguments (corekit/fields/collections.py:420 is schema.unpack(file, length, packet)). That is why this has stayed hidden: the internal callers all happen to use the single shape that works.

Fix

Read the arguments the way an ordinary wrapper would, so that the documented signature is the real one:

cls, data = args[0], args[1]
length = args[2] if len(args) > 2 else kwargs.pop('length', None)
packet = args[3] if len(args) > 3 else kwargs.pop('packet', None)

or bind against the wrapped function's signature with inspect.signature(...).bind. Either way misc.py:539 then works as written and its type: ignore can go.

Found while investigating the CGA Parameters failure recorded in #437, which is a different fault in the same area — see the two companion issues. This one is independent of that story and of the mobility header entirely.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugIssues reporting a defect (set by the bug report template; a default, not an assessment)

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions