Skip to content

MH MN-ID: bytes and str are documented interchangeably but each subtype accepts only one, and the wrong one fails with a bare stdlib exception #469

Description

@JarryShaw

Summary

MH._make_opt_mn_id documents bytes | str as accepted identifier types for
every MN-ID subtype, but each subtype's field accepts only one of the two.
Passing the other builds a schema that cannot be packed, and fails with a bare
stdlib exception rather than an in-library one.

This is the same family as #467 (which covered int) and #448 (which covered
the IPv6_Address width), but it is about bytes/str and is not fixed by
either.

Reproduction

Measured on fa128959e (current main). Identical on PR #468's branch, so
pre-existing and not a regression from that PR — I checked both trees.

from pcapkit.const.mh.option import Option
from pcapkit.const.mh.mn_id_subtype import MNIDSubtype
from pcapkit.protocols.internet.mh import MH

proto = object.__new__(MH)

# NAI's field is text, so bytes cannot be encoded
proto._make_opt_mn_id(Option.MN_ID_OPTION_TYPE,
                      subtype=MNIDSubtype.NAI,
                      identifier=b'node@example').pack()

# IMSI's field is raw octets, so str cannot be packed
proto._make_opt_mn_id(Option.MN_ID_OPTION_TYPE,
                      subtype=MNIDSubtype.IMSI,
                      identifier='12345').pack()
NAI  + bytes -> AttributeError: 'bytes' object has no attribute 'encode'
IMSI + str   -> struct.error: argument for 's' must be a bytes object
NAI  + str   -> length=13, packs 15 octets            (correct)

So the defect is symmetric: bytes fails for the one text subtype, and str
fails for the octet subtypes. Only the matching pairing works.

Both failures happen at pack() rather than at construction, and neither is an
instance of pcapkit.utilities.exceptions.BaseError`, so a caller cannot catch
them with the library's own exception hierarchy.

Why it matters

The type union is documented, not accidental — _make_opt_mn_id's identifier
parameter is annotated bytes | str | IPv6Address | int, and the
Schema_MNIDOption.__init__ stub carries a matching union. So the signature
promises bytes and str interchangeably for all subtypes when in fact the
choice is fixed per subtype by whether that subtype's field is a StringField
or a BytesField.

NAI is the only text subtype; IMSI, P_TMSI, EUI_48_address,
EUI_64_address, GUTI and DUID are octet subtypes.

Suggested direction

The same shape as #467's fix: decide per subtype, then either convert (encode a
str for the octet subtypes, decode bytes for NAI — noting that an encoding
has to be chosen and named if so) or reject with a ProtocolError saying which
type that subtype takes. #467's fix already added exactly that kind of
per-subtype rejection for int, so the mechanism and message style are in
place to extend.

Whichever is chosen, the documented union should end up describing what is
actually accepted.

Provenance

Surfaced while verifying #468 (the fix for #467). Its author flagged the
bytes-for-NAI half and correctly left it out of scope; the str-for-octet-
subtypes half is mine, found while measuring the first. Both verified on main
and on the PR branch before filing.

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