diff --git a/docs/source/pcapkit/protocols/misc/pcapng.rst b/docs/source/pcapkit/protocols/misc/pcapng.rst index 77518260f0..1e7b14ef8c 100644 --- a/docs/source/pcapkit/protocols/misc/pcapng.rst +++ b/docs/source/pcapkit/protocols/misc/pcapng.rst @@ -34,7 +34,7 @@ which implements an extractor for PCAP-NG file format [*]_, and the .. autoproperty:: name .. autoproperty:: length - .. autoproperty:: context + .. autoproperty:: section_context .. autoproperty:: byteorder .. autoproperty:: nanosecond .. autoproperty:: ts_resolution diff --git a/pcapkit/protocols/misc/pcapng.py b/pcapkit/protocols/misc/pcapng.py index f3387e2fb3..fc7416f721 100644 --- a/pcapkit/protocols/misc/pcapng.py +++ b/pcapkit/protocols/misc/pcapng.py @@ -680,6 +680,14 @@ class PCAPNG(ProtocolBase[Data_PCAPNG, Schema_PCAPNG], - :meth:`~pcapkit.protocols.misc.pcapng.PCAPNG._read_secrets_zigbee_aps` - :meth:`~pcapkit.protocols.misc.pcapng.PCAPNG._make_secrets_zigbee_aps` + .. versionchanged:: 1.5.0 + + ``context`` returns the caller supplied + :class:`~pcapkit.corekit.context.ContextRegistry` inherited from + :class:`~pcapkit.protocols.protocol.ProtocolBase`, on every block. The + block's section ``Context``, which ``context`` returned through 1.4.x, + is :attr:`section_context` (:issue:`2172`). + """ if TYPE_CHECKING: @@ -945,10 +953,23 @@ def packet(self) -> 'Data_Packet': ) @property - def context(self) -> 'Context': - """Context of current PCAP-NG block.""" + def section_context(self) -> 'Context': + """Section context of current PCAP-NG block. + + Raises: + UnsupportedCall: On a Section Header Block, which has no section + context of its own. + + .. versionadded:: 1.5.0 + + Renamed from ``context``, with no alias. ``context`` is the one + inherited from :class:`~pcapkit.protocols.protocol.ProtocolBase`, + the caller supplied parsing context of + :mod:`pcapkit.corekit.context` (:issue:`2172`). + + """ if self._ctx is None: - raise UnsupportedCall(f"'{self.__class__.__name__}' object has no attribute 'context'") + raise UnsupportedCall(f"'{self.__class__.__name__}' object has no attribute 'section_context'") return self._ctx @property diff --git a/tests/protocols/misc/test_pcapng_unit.py b/tests/protocols/misc/test_pcapng_unit.py index 012428bd8d..d786fce5c9 100644 --- a/tests/protocols/misc/test_pcapng_unit.py +++ b/tests/protocols/misc/test_pcapng_unit.py @@ -493,7 +493,7 @@ def test_pcapng_top_level_properties_registry_and_dispatch_paths(self) -> None: self.assertEqual(pcapng.block, BlockType.Section_Header_Block) self.assertEqual(pcapng.index('Raw'), 33) with self.assertRaises(UnsupportedCall): - _ = pcapng.context + _ = pcapng.section_context with mock.patch('pcapkit.protocols.misc.pcapng.warn') as warn: self.assertEqual(pcapng.ts_resolution, 1_000_000) self.assertEqual(pcapng.ts_offset, 0) @@ -522,7 +522,7 @@ def test_pcapng_top_level_properties_registry_and_dispatch_paths(self) -> None: pcapng._ctx = ctx pcapng._type = BlockType.Enhanced_Packet_Block pcapng._info = DummyData(type=BlockType.Enhanced_Packet_Block, length=32, interface_id=0) - self.assertIs(pcapng.context, ctx) + self.assertIs(pcapng.section_context, ctx) self.assertTrue(pcapng.nanosecond) self.assertEqual(pcapng.linktype, LinkType.NULL) self.assertEqual(pcapng._get_linktype(0), LinkType.NULL) @@ -753,6 +753,33 @@ class Other(Raw): self.assertIs(pcapng._decode_next_layer(decoded_no_chain, LinkType.NULL, 0), decoded_no_chain) self.assertEqual(decoded_no_chain['protocols'], '') + def test_pcapng_context_is_the_base_caller_context_not_the_section(self) -> None: + # #2172: ``context`` is ProtocolBase's caller supplied registry on every + # block, a Section Header Block included; the section is ``section_context``. + from pcapkit.const.pcapng.block_type import BlockType + from pcapkit.corekit.context import ContextRegistry + from pcapkit.protocols.internet.esp import ESPContext, SecurityAssociation + from pcapkit.protocols.misc.pcapng import PCAPNG + from pcapkit.protocols.protocol import ProtocolBase + + esp = ESPContext(SecurityAssociation(spi=7)) + registry = ContextRegistry(esp) + section = types.SimpleNamespace(interfaces=[], section=types.SimpleNamespace(byteorder='little')) + for block, ctx in ((BlockType.Section_Header_Block, None), (BlockType.Enhanced_Packet_Block, section)): + with self.subTest(block=block): + pcapng = object.__new__(PCAPNG) + pcapng._type = block + pcapng._ctx = ctx + pcapng._exctx = registry + + context = pcapng.context + self.assertIsInstance(context, ContextRegistry) + self.assertIsNot(context, registry) + self.assertEqual(dict(context), {'ESP': esp}) + self.assertEqual(dict(context), dict(ProtocolBase.context.fget(pcapng))) # type: ignore[attr-defined] + if ctx is not None: + self.assertIs(pcapng.section_context, ctx) + def test_pcapng_register_stays_quiet_on_same_object_reregistration(self) -> None: """GitHub issue #718: replaying a call with the identical class is a no-op.