|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | 3 | import importlib.util |
| 4 | +import io |
4 | 5 | import unittest |
| 6 | +import warnings |
5 | 7 | from unittest import mock |
6 | 8 |
|
7 | 9 | from tests._support import reimport_once_per_class |
@@ -185,5 +187,85 @@ def test_docstring_cites_the_extension_header_registry(self) -> None: |
185 | 187 | self.assertIn('qualifies it for a base besides', doc) |
186 | 188 |
|
187 | 189 |
|
| 190 | +@unittest.skipUnless(HAS_RUNTIME, 'runtime dependencies not installed') |
| 191 | +class AHMembershipTests(unittest.TestCase): |
| 192 | + """``in`` on a terminal AH layer parsed from an IPv6 extension chain (#2484). |
| 193 | +
|
| 194 | + The AH layer is taken from ``IPv6.extension_headers`` in extension mode. |
| 195 | + ``in`` on it does not raise, and is False for a selector that does not match AH. |
| 196 | + Frames are built in memory; no capture is read. |
| 197 | + """ |
| 198 | + |
| 199 | + def setUp(self) -> None: |
| 200 | + reimport_once_per_class(self) |
| 201 | + |
| 202 | + @staticmethod |
| 203 | + def _ipv6(first: int, body: bytes) -> bytes: |
| 204 | + import struct # pylint: disable=import-outside-toplevel |
| 205 | + |
| 206 | + return struct.pack('!IHBB', 6 << 28, len(body), first, 64) + bytes(32) + body |
| 207 | + |
| 208 | + @staticmethod |
| 209 | + def _ah(next_: int) -> bytes: |
| 210 | + import struct # pylint: disable=import-outside-toplevel |
| 211 | + |
| 212 | + # 12-octet ICV: AH is 24 octets, so the Payload Len field is 4 |
| 213 | + return struct.pack('!BBHII', next_, 4, 0, 0x1234, 1) + bytes(12) |
| 214 | + |
| 215 | + @staticmethod |
| 216 | + def _esp() -> bytes: |
| 217 | + import struct # pylint: disable=import-outside-toplevel |
| 218 | + |
| 219 | + return struct.pack('!II', 0x1234, 1) + bytes(16) |
| 220 | + |
| 221 | + def _parse_ipv6(self, data: bytes): # type: ignore[no-untyped-def] |
| 222 | + from pcapkit.protocols.internet.ipv6 import IPv6 |
| 223 | + |
| 224 | + with warnings.catch_warnings(): |
| 225 | + warnings.simplefilter('ignore') |
| 226 | + return IPv6(io.BytesIO(data), len(data)) |
| 227 | + |
| 228 | + def test_terminal_ah_membership_is_false_for_other_selectors(self) -> None: |
| 229 | + from pcapkit.protocols.internet.ah import AH |
| 230 | + |
| 231 | + ipv6 = self._parse_ipv6(self._ipv6(51, self._ah(59))) |
| 232 | + (ah,) = [ext for ext in ipv6.extension_headers.values() if isinstance(ext, AH)] |
| 233 | + |
| 234 | + self.assertIn('AH', ah) |
| 235 | + self.assertNotIn('ESP', ah) |
| 236 | + self.assertNotIn('TCP', ah) |
| 237 | + |
| 238 | + def test_ah_followed_by_esp_membership_does_not_raise(self) -> None: |
| 239 | + from pcapkit.protocols.internet.ah import AH |
| 240 | + |
| 241 | + ipv6 = self._parse_ipv6(self._ipv6(51, self._ah(50) + self._esp())) |
| 242 | + ah = next(ext for ext in ipv6.extension_headers.values() if isinstance(ext, AH)) |
| 243 | + |
| 244 | + self.assertNotIn('ESP', ah) |
| 245 | + |
| 246 | + def test_esp_extension_layer_membership_is_unchanged(self) -> None: |
| 247 | + from pcapkit.protocols.internet.esp import ESP |
| 248 | + |
| 249 | + ipv6 = self._parse_ipv6(self._ipv6(50, self._esp())) |
| 250 | + esp = next(ext for ext in ipv6.extension_headers.values() if isinstance(ext, ESP)) |
| 251 | + |
| 252 | + self.assertIn('ESP', esp) |
| 253 | + self.assertNotIn('AH', esp) |
| 254 | + |
| 255 | + def test_ipv4_ah_membership_is_unchanged(self) -> None: |
| 256 | + import struct # pylint: disable=import-outside-toplevel |
| 257 | + |
| 258 | + from pcapkit.protocols.internet.ipv4 import IPv4 |
| 259 | + |
| 260 | + body = self._ah(59) |
| 261 | + data = struct.pack('!BBHHHBBH', 0x45, 0, 20 + len(body), 0, 0, 64, 51, 0) + bytes(8) + body |
| 262 | + with warnings.catch_warnings(): |
| 263 | + warnings.simplefilter('ignore') |
| 264 | + ipv4 = IPv4(io.BytesIO(data), len(data)) |
| 265 | + |
| 266 | + self.assertNotIn('ESP', ipv4.payload) |
| 267 | + self.assertIn('AH', ipv4.payload) |
| 268 | + |
| 269 | + |
188 | 270 | if __name__ == '__main__': |
189 | 271 | unittest.main() |
0 commit comments