From ea5e5df5974f5ee3d849d06664c395edde90f6d0 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Tue, 6 Oct 2026 14:55:04 -0400 Subject: [PATCH] fix(pcap): _make_magic ignores nanosecond with explicit endian flags; _read_protos ignores size - _make_magic: the lilendian/bigendian branch hard-coded the magic, always nanosecond for little-endian and microsecond for big-endian. It now looks up _MAGIC_NUM with the nanosecond flag, as the byteorder branch does. - Header._read_protos: read `size` bytes instead of a fixed 4, matching Link._read_protos and Internet._read_protos. - test_header_frame_unit pinned the old little-endian nanosecond magic for lilendian=True with nanosecond=False; corrected to d4c3b2a1. Closes #1096 --- pcapkit/protocols/misc/pcap/header.py | 6 +- .../misc/pcap/test_header_frame_unit.py | 2 +- .../pcap/test_header_magic_protos_unit.py | 61 +++++++++++++++++++ 3 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 tests/protocols/misc/pcap/test_header_magic_protos_unit.py diff --git a/pcapkit/protocols/misc/pcap/header.py b/pcapkit/protocols/misc/pcap/header.py index 8b26388eb..f0a2ffe51 100644 --- a/pcapkit/protocols/misc/pcap/header.py +++ b/pcapkit/protocols/misc/pcap/header.py @@ -324,7 +324,7 @@ def _read_protos(self, size: int) -> 'Enum_LinkType': Link layer protocol enumeration. """ - _byte = self._read_unpack(4, lilendian=True) + _byte = self._read_unpack(size, lilendian=True) _prot = Enum_LinkType.get(_byte) return _prot @@ -347,9 +347,9 @@ def _make_magic(self, byteorder: 'Literal["big", "little"]' = sys.byteorder, if lilendian == bigendian: raise EndianError('unresolved byte order') if bigendian: - return _MAGIC_NUM[('big', False)], False + return _MAGIC_NUM[('big', nanosecond)], False if lilendian: - return _MAGIC_NUM[('little', True)], True + return _MAGIC_NUM[('little', nanosecond)], True if byteorder not in ('little', 'big'): raise EndianError(f"unknown byte order: {byteorder!r}") diff --git a/tests/protocols/misc/pcap/test_header_frame_unit.py b/tests/protocols/misc/pcap/test_header_frame_unit.py index e8c267cd1..503ce9c38 100644 --- a/tests/protocols/misc/pcap/test_header_frame_unit.py +++ b/tests/protocols/misc/pcap/test_header_frame_unit.py @@ -143,7 +143,7 @@ class NamedBytesIO(io.BytesIO): self.assertEqual(header._make_magic(lilendian=False, bigendian=True), (b'\xa1\xb2\xc3\xd4', False)) self.assertEqual(header._make_magic(lilendian=True, bigendian=False), - (b'\x4d\x3c\xb2\xa1', True)) + (b'\xd4\xc3\xb2\xa1', True)) self.assertEqual(header._make_magic(byteorder='big', lilendian='', bigendian=False), # type: ignore[arg-type] (b'\xa1\xb2\xc3\xd4', False)) with self.assertRaises(EndianError): diff --git a/tests/protocols/misc/pcap/test_header_magic_protos_unit.py b/tests/protocols/misc/pcap/test_header_magic_protos_unit.py new file mode 100644 index 000000000..424de2254 --- /dev/null +++ b/tests/protocols/misc/pcap/test_header_magic_protos_unit.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +import importlib.util +import io +import unittest + +from tests._support import reimport_once_per_class + +RUNTIME_DEPS = ('tbtrim', 'aenum', 'chardet', 'dictdumper') +HAS_RUNTIME = all(importlib.util.find_spec(name) is not None for name in RUNTIME_DEPS) + + +@unittest.skipUnless(HAS_RUNTIME, 'runtime dependencies not installed') +class PCAPHeaderMagicProtosUnitTests(unittest.TestCase): + """``Header._make_magic`` and ``Header._read_protos`` (GH-1096).""" + + def setUp(self) -> None: + reimport_once_per_class(self) + + def test_make_magic_endian_flags_honour_nanosecond(self) -> None: + from pcapkit.protocols.misc.pcap.header import Header + + header = object.__new__(Header) + cases = [ + ((True, False, False), (b'\xd4\xc3\xb2\xa1', True)), + ((True, False, True), (b'\x4d\x3c\xb2\xa1', True)), + ((False, True, False), (b'\xa1\xb2\xc3\xd4', False)), + ((False, True, True), (b'\xa1\xb2\x3c\x4d', False)), + ] + for (lilendian, bigendian, nanosecond), expected in cases: + with self.subTest(lilendian=lilendian, bigendian=bigendian, nanosecond=nanosecond): + self.assertEqual(header._make_magic(lilendian=lilendian, bigendian=bigendian, + nanosecond=nanosecond), expected) + + def test_make_magic_byteorder_matches_endian_flags(self) -> None: + from pcapkit.protocols.misc.pcap.header import Header + + header = object.__new__(Header) + for byteorder in ('little', 'big'): + for nanosecond in (False, True): + with self.subTest(byteorder=byteorder, nanosecond=nanosecond): + self.assertEqual( + header._make_magic(byteorder, nanosecond=nanosecond), + header._make_magic(lilendian=byteorder == 'little', + bigendian=byteorder == 'big', nanosecond=nanosecond), + ) + + def test_read_protos_reads_size_bytes(self) -> None: + from pcapkit.const.reg.linktype import LinkType + from pcapkit.protocols.misc.pcap.header import Header + + header = object.__new__(Header) + for size in (1, 2, 4): + with self.subTest(size=size): + header._file = io.BytesIO(int(LinkType.ETHERNET).to_bytes(size, 'little') + b'\xff' * 4) + self.assertEqual(header._read_protos(size), LinkType.ETHERNET) + self.assertEqual(header._file.tell(), size) + + +if __name__ == '__main__': + unittest.main()