Skip to content

fix(fields): #811 misreports a malformed template as a negative length, and never prevents the negative length #825

Description

@JarryShaw

Two related defects in #811's negative-length guard, both surfaced by #823's cross-review and both confirmed here.

1. The message is a category error. FieldBase.length (pcapkit/corekit/fields/field.py:284-290) catches struct.error blanket and unconditionally reports f'Field {self.name} resolved to a negative length; template={self.template!r}'. But struct.calcsize raises the same error for a malformed template as for a negative count — measured on 3.14.7:

calcsize('-1s') -> struct.error: bad char in struct format
calcsize('Xs')  -> struct.error: bad char in struct format

So a schema with a typo'd template is reported as a negative length, which sends the reader to the wrong cause. The guard should distinguish the two — the negative case is detectable from the resolved length before calcsize is called at all.

2. The negative length is reported, never prevented. A SchemaWarning: packet length < 0: -1 fires before the ProtocolError, so pkt['__length__'] still goes negative and the schema still builds a '-1s' template; #811 converts the resulting exception rather than stopping the arithmetic. #823's reviewer found 32 inputs across two different fields (debug and fragment) taking this path in a 1210-case sweep, so it is a class of input, not one packet. The real fix bounds pkt['__length__'] at the schema layer.

Related and worth re-measuring in the same change: http.py:318-319 (except (ValueError, struct.error) in the preface arm) took zero hits across that 1210-case sweep and may now be unreachable, and http.py:416's contextlib.suppress(ProtocolError, struct.error) is likely in the same position. Neither is safe to simply delete — StructError subclasses struct.error, and the comment at :401-402 explains why removing a suppression from a non-final arm previously caused an HTTP/1→HTTP/2 mislabel.

Note the test pinned by #823 asserts the current message verbatim, so fixing (1) will red it; assertIn('negative length', …) there would survive.

Activity

  1. added
    bugIssues reporting a defect (set by the bug report template; a default, not an assessment)
    fixPull requests that fix a defect (fix: subject prefix)
    on Sep 26, 2026
  2. JarryShaw commented on Sep 26, 2026

    @JarryShaw
    OwnerAuthor

    Checkable blocker: #823 merged.

    gh pr view 823 -R JarryShaw/PyPCAPKit --json state,mergedAt
    

    Fixing (1) changes the message FieldBase.length raises, and #823 pins that message verbatim at tests/protocols/application/test_http_unit.py:662:

    self.assertEqual(str(ctx.exception),
                     "Field debug resolved to a negative length; template='-1s'")

    So landing this first would red the very test #823 exists to fix, and main is already red on #822. Once #823 is in, this change should relax that assertion to assertIn('negative length', str(ctx.exception)) in the same commit — which is what #823's cross-review recommended as a follow-up anyway, and what makes the test survive the correction.

  3. added
    blockedDeferred pending another issue or decision; see the last comment for what unblocks it
    wipWork in flight - a covering PR is open or an agent is actively on it
    and removed
    blockedDeferred pending another issue or decision; see the last comment for what unblocks it
    on Sep 26, 2026
  4. JarryShaw commented on Sep 26, 2026

    @JarryShaw
    OwnerAuthor

    Unblocked — #823 landed on main as 21e9588af (direct push at the maintainer's instruction; #823 closed as delivered).

    gh pr view 823 -R JarryShaw/PyPCAPKit --json state,mergedAt   # CLOSED
    git log --oneline -1 origin/main                              # 21e9588af
    

    One consequence for whoever takes this: the assertion that pinned the current message is now on main, at tests/protocols/application/test_http_unit.py:662:

    self.assertEqual(str(ctx.exception),
                     "Field debug resolved to a negative length; template='-1s'")

    So fixing (1) reds main unless the same commit relaxes it to assertIn('negative length', str(ctx.exception)) — which is what #823's cross-review recommended anyway, and is now a hard requirement rather than a suggestion. A worker is on it.

  5. removed
    wipWork in flight - a covering PR is open or an agent is actively on it
    on Sep 26, 2026
  6. added this to the 1.5 milestone on Oct 6, 2026
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)fixPull requests that fix a defect (fix: subject prefix)

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions