From d4e1d4f3804b1acf23e19f350f1d0dd7c36cc8a2 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Tue, 20 Oct 2026 11:45:35 -0400 Subject: [PATCH] chore: cite issues as #NNN in plain comments Plain # comments are never rendered by Sphinx, so the :issue: role there is noise around the number. Convert every plain comment (not docstrings, not #: doc-comments) to #NNN, and add tests/project/test_issue_refs_in_comments.py, which tokenises every tracked .py file and fails on a role in a plain comment, bar three comments that talk about the roles themselves. --- docs/source/conf.py | 4 +- pcapkit/const/reg/apptype/__init__.py | 2 +- pcapkit/corekit/decorators.py | 2 +- pcapkit/corekit/fields/numbers.py | 4 +- pcapkit/corekit/infoclass.py | 10 +- pcapkit/corekit/io.py | 2 +- pcapkit/corekit/multidict.py | 4 +- pcapkit/foundation/engines/dpkt.py | 4 +- pcapkit/foundation/extraction.py | 4 +- pcapkit/foundation/reassembly/ip.py | 12 +- pcapkit/foundation/traceflow/traceflow.py | 2 +- pcapkit/protocols/application/application.py | 2 +- pcapkit/protocols/application/ospf.py | 6 +- pcapkit/protocols/internet/hip.py | 2 +- pcapkit/protocols/internet/ipv4.py | 16 +-- pcapkit/protocols/internet/ipv6.py | 30 ++--- pcapkit/protocols/internet/ipv6_route.py | 4 +- pcapkit/protocols/internet/ipx.py | 4 +- pcapkit/protocols/link/l2tpv2.py | 10 +- pcapkit/protocols/misc/pcap/frame.py | 4 +- pcapkit/protocols/misc/pcapng.py | 54 ++++----- pcapkit/protocols/misc/raw.py | 2 +- pcapkit/protocols/protocol.py | 6 +- pcapkit/protocols/schema/schema.py | 16 +-- pcapkit/protocols/transport/sctp.py | 2 +- pcapkit/protocols/transport/tcp.py | 2 +- pcapkit/protocols/transport/udp.py | 8 +- pcapkit/toolkit/scapy.py | 10 +- tests/project/test_issue_refs_in_comments.py | 103 ++++++++++++++++++ ...t_ipv4_header_truncated_payload_runtime.py | 2 +- 30 files changed, 218 insertions(+), 115 deletions(-) create mode 100644 tests/project/test_issue_refs_in_comments.py diff --git a/docs/source/conf.py b/docs/source/conf.py index 8168d573e8..fdfe0da2e4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -178,7 +178,7 @@ # ``:class:`` or ``:func:`` cross-reference into them could ever resolve. # # Those packages are therefore linked to their project pages instead, through -# :data:`rst_epilog` below (:issue:`1936`). +# :data:`rst_epilog` below (#1936). #: The label on the note in :file:`pcapkit/foundation/engines/3rdparty.rst` that #: says which distribution installs the ``pcap`` module, which ``|pcap|_`` links @@ -254,7 +254,7 @@ # NB: a default renders as its source text rather than its ``repr()``. By value, # every ``byteorder=sys.byteorder`` showed the build host's ``'little'``, and the # ``corekit.fields`` pages showed ``default=`` and ``callback=>`` (:issue:`1859`). +# object>`` and ``callback=>`` (#1859). autodoc_preserve_defaults = True #autodoc_member_order = 'bysource' #autodoc_member_order = 'alphabetic' diff --git a/pcapkit/const/reg/apptype/__init__.py b/pcapkit/const/reg/apptype/__init__.py index 35919fa011..6292eb91ee 100644 --- a/pcapkit/const/reg/apptype/__init__.py +++ b/pcapkit/const/reg/apptype/__init__.py @@ -138,7 +138,7 @@ def __ne__(self, other: 'object') -> 'bool': return dict.__ne__(self, other) # NOTE: dict union is :pep:`584`, Python 3.9 and up, so a registry has it - # from 3.9 only, as :obj:`dict` does (GitHub issue :issue:`1694`): before + # from 3.9 only, as :obj:`dict` does (GitHub issue #1694): before # 3.9, ``|`` and ``|=`` raise dict's :exc:`TypeError` and import nothing. # ``|=`` stays dict's own, since it updates in place and reads no value. if sys.version_info >= (3, 9): # pragma: no branch -- Python 3.9 and up diff --git a/pcapkit/corekit/decorators.py b/pcapkit/corekit/decorators.py index af2f56be5d..bb85b065eb 100644 --- a/pcapkit/corekit/decorators.py +++ b/pcapkit/corekit/decorators.py @@ -172,7 +172,7 @@ def behold(*args: 'P.args', **kwargs: 'P.kwargs') -> 'R_beholder': # the payload *past* the extension headers, so falling back to the # whole IPv6 payload would hold those headers twice, once in the # extension header chain and once more in this ``Raw`` - # (:issue:`1471`). + # (#1471). file_ = cast('Optional[bytes]', kwargs.get('payload')) if file_ is None: file_ = self._get_payload() diff --git a/pcapkit/corekit/fields/numbers.py b/pcapkit/corekit/fields/numbers.py index 7b4157c980..52389c527d 100644 --- a/pcapkit/corekit/fields/numbers.py +++ b/pcapkit/corekit/fields/numbers.py @@ -781,7 +781,7 @@ def post_process(self, value: 'int | bytes', packet: 'dict[str, Any]') -> 'Stdli return member # NOTE: A registry member is shared, so the captured bits # outside ``bit_length`` go on an unregistered copy of it - # rather than on the member itself (:issue:`1488`). + # rather than on the member itself (#1488). attrs = {key: val for key, val in vars(member).items() if key not in ('_name_', '_value_', '__reduce_ex__')} attrs[_OUTSIDE_BITS] = record @@ -1066,7 +1066,7 @@ def pre_process(self, value: 'int | AppType', packet: 'dict[str, Any]') -> 'int # NOTE: imported here rather than at module level, as # :mod:`pcapkit.const` itself imports :mod:`pcapkit.corekit`; and tested # against AppType rather than ``self._namespace``, as the - # per-transport copies this class replaced did (:issue:`1516`). + # per-transport copies this class replaced did (#1516). from pcapkit.const.reg.apptype import AppType # pylint: disable=import-outside-toplevel if isinstance(value, AppType): diff --git a/pcapkit/corekit/infoclass.py b/pcapkit/corekit/infoclass.py index 21ac598050..7964d6a7be 100644 --- a/pcapkit/corekit/infoclass.py +++ b/pcapkit/corekit/infoclass.py @@ -160,7 +160,7 @@ def info_final(cls: 'ST', *, _finalised: 'bool' = True) -> 'ST': # ``Info`` class, rather than itself, plus that such class does not # override the ``__init__`` method of the meta class. A multi-mapping # :class:`Info` (:class:`_MultiInfo`) declares no fields and keeps the - # mapping constructor it inherits (:issue:`1484`). + # mapping constructor it inherits (#1484). if '__init__' not in cls.__dict__ and cls is not Info and not issubclass(cls, _MultiInfo): args_ = [] # type: list[str] dict_ = [] # type: list[str] @@ -177,7 +177,7 @@ def info_final(cls: 'ST', *, _finalised: 'bool' = True) -> 'ST': # Python version: below 3.10 (bpo-43901), ``__annotations__`` of a # class declaring none is its nearest base's, so a field-less # subclass read Info's ``__finalised__``, ``__additional__`` and - # ``__excluded__`` as its fields (:issue:`1692`). See + # ``__excluded__`` as its fields (#1692). See # :func:`~pcapkit.utilities.compat.get_annotations`. for key in reversed(get_annotations(cls_)): # NOTE: We skip duplicated annotations to avoid duplicate @@ -498,7 +498,7 @@ def __update__(self, dict_: 'Optional[Mapping[str, VT] | Iterable[tuple[str, VT] # NOTE: A :class:`MultiDict` or :class:`OrderedMultiDict` held as a # *value* -- the option lists the parsers build -- is stored as a # :class:`MultiInfo` or :class:`OrderedMultiInfo`, so that a finalised - # instance holds no mutable mapping (:issue:`1484`). + # instance holds no mutable mapping (#1484). __name__ = type(self).__name__ # pylint: disable=redefined-builtin @@ -945,7 +945,7 @@ def __eq__(self, other: 'object') -> 'bool': # in buckets -- it answered False where the OrderedMultiDict, asked the # other way round, answered True. The ordered side decides, as it does # for a bare MultiDict, which Python asks it first for being its - # subclass (:issue:`1484`). + # subclass (#1484). if isinstance(other, OrderedMultiDict): return NotImplemented return MultiDict.__eq__(self, other) @@ -1016,7 +1016,7 @@ class OrderedMultiInfo(_MultiInfo[KT, VT], OrderedMultiDict[KT, VT]): # OrderedMultiDict's own. ``__ne__`` is not: OrderedMultiDict has none, and # dict's compares the internal buckets, so two equal option lists compared # ``!=`` as well as ``==``. It is :func:`_ne`, as for the exports - # (:issue:`1484`). + # (#1484). __getitem__ = OrderedMultiDict.__getitem__ # type: ignore[assignment] __iter__ = OrderedMultiDict.__iter__ # type: ignore[assignment] __len__ = OrderedMultiDict.__len__ diff --git a/pcapkit/corekit/io.py b/pcapkit/corekit/io.py index 05df937ff8..5994b3c79f 100644 --- a/pcapkit/corekit/io.py +++ b/pcapkit/corekit/io.py @@ -515,7 +515,7 @@ def seek(self, offset: 'int', whence: 'int' = io.SEEK_SET, /) -> 'int': # # The gap is waited for and the rest is not, and the position is # the target, or the end of the stream if that comes first -- not - # wherever an over-asking read happened to stop (:issue:`1586`). + # wherever an over-asking read happened to stop (#1586). tmp_gap = min(self._tell - tmp_end, self._buffer_size) tmp_len = min(max(tmp_gap, self._buffer_size // 4), self._buffer_size) diff --git a/pcapkit/corekit/multidict.py b/pcapkit/corekit/multidict.py index 5a68cab2df..7d27f8f6dc 100644 --- a/pcapkit/corekit/multidict.py +++ b/pcapkit/corekit/multidict.py @@ -146,7 +146,7 @@ def __init__(self, mapping: 'Optional[MultiDict[_KT, _VT] | Mapping[_KT, _VT | l super().__init__() elif isinstance(mapping, MultiDict): # NOTE: a dict, not pairs: GraalPy's dict.__init__ stores pairs - # through __setitem__, wrapping each list in another (:issue:`1963`). + # through __setitem__, wrapping each list in another (#1963). super().__init__({k: vs[:] for k, vs in mapping.lists()}) elif isinstance(mapping, cabc.Mapping): tmp = {} # type: dict[_KT, list[_VT]] @@ -171,7 +171,7 @@ def __getstate__(self) -> 'Any': def __setstate__(self, value: 'Any') -> 'None': super().clear() - # NOTE: a dict, not pairs, as in __init__ (GraalPy, :issue:`1963`). + # NOTE: a dict, not pairs, as in __init__ (GraalPy, #1963). super().update(dict(value)) def __iter__(self) -> 'Iterator[_KT]': # pylint: disable=useless-parent-delegation diff --git a/pcapkit/foundation/engines/dpkt.py b/pcapkit/foundation/engines/dpkt.py index 2f57e3d81a..c33a4d645d 100644 --- a/pcapkit/foundation/engines/dpkt.py +++ b/pcapkit/foundation/engines/dpkt.py @@ -356,7 +356,7 @@ def __iter__(self) -> 'Iterator[tuple[float | decimal.Decimal, bytes]]': yield DecimalTimestamp(value, resolution, hdr.len), buf else: # at a precision of 64 digits, not the caller's, which may - # be too low to hold it (:issue:`1668`) + # be too low to hold it (#1668) with localcontext(prec=64): exact = decimal.Decimal(hdr.tv_sec) + decimal.Decimal(hdr.tv_usec) / resolution yield Timestamp(value, exact, resolution, hdr.len), buf @@ -556,7 +556,7 @@ def read_frame(self) -> 'DPKTPacket': # more than its own ``UnpackError`` while parsing -- dpkt 1.9.8 reads # ``frag_off`` from whatever header follows an IPv6 Fragment header, # so a Fragment header followed by ESP or Destination Options raises - # :exc:`AttributeError` (:issue:`1351`). The frame is kept as a raw + # :exc:`AttributeError` (#1351). The frame is kept as a raw # packet, so one undecodable frame neither ends the extraction nor # shifts the numbering of the frames after it. warn(f'Frame {ext._frnum + 1}: dpkt cannot parse the frame ' diff --git a/pcapkit/foundation/extraction.py b/pcapkit/foundation/extraction.py index f8ba29eeb8..3a9f69b07d 100644 --- a/pcapkit/foundation/extraction.py +++ b/pcapkit/foundation/extraction.py @@ -311,7 +311,7 @@ class Extractor(Generic[_P]): # Read-only copies of the registries as shipped. A public registrar # handed the built-in a name shipped with restores this entry, so an - # override can be undone (:issue:`1363`). + # override can be undone (#1363). _shipped_output = types.MappingProxyType(dict(__output__)) _shipped_engine = types.MappingProxyType(dict(__engine__)) _shipped_reassembly = types.MappingProxyType(dict(__reassembly__)) @@ -1466,7 +1466,7 @@ def __init__(self, if len(self._magic) < 4: # NOTE: ``peek`` makes at most one read of the stream, which a pipe or a # raw stream may answer with fewer than four octets, so a short peek is - # not a short file. Read them instead, and seek back (:issue:`1586`). + # not a short file. Read them instead, and seek back (#1586). position = self._ifile.tell() self._magic = self._ifile.read(4) self._ifile.seek(position, os.SEEK_SET) diff --git a/pcapkit/foundation/reassembly/ip.py b/pcapkit/foundation/reassembly/ip.py index a3dae0ab45..432a5a99e3 100644 --- a/pcapkit/foundation/reassembly/ip.py +++ b/pcapkit/foundation/reassembly/ip.py @@ -161,7 +161,7 @@ def reassembly(self, info: 'Packet[_AT]') -> 'None': # initialise buffer with BUFID; ``RCVBT`` holds one entry per 8-octet # block of the data buffer, its partial last block included -- 8191 # covered octets 0 to 65527 only, so a fragment reaching octet 65528, - # e.g. at the largest Fragment Offset, indexed past it (:issue:`1565`) + # e.g. at the largest Fragment Offset, indexed past it (#1565) if BUFID not in self._buffer: self._buffer[BUFID] = Buffer( TDL=-1, # Total Data Length @@ -190,11 +190,11 @@ def reassembly(self, info: 'Packet[_AT]') -> 'None': # # A Total Length of 0 declares nothing. It is what TCP segmentation # offload leaves in a capture taken on the sending host, and the - # datagram then runs to the end of the captured frame (:issue:`1547`). + # datagram then runs to the end of the captured frame (#1547). # Wireshark ("presumed TSO") substitutes that length for the field # before it reassembles anything, and so does this, whatever **MF** and # the offset say: an unfragmented one is then whole rather than ``-IHL`` - # octets long (:issue:`1555`). Linux's BIG TCP zeroes the field + # octets long (#1555). Linux's BIG TCP zeroes the field # precisely because the datagram, header included, outgrows the 65535 # octets it can declare -- and such a datagram never completes in a # buffer of that length (:meth:`_is_complete`). So the buffer grows past @@ -247,7 +247,7 @@ def reassembly(self, info: 'Packet[_AT]') -> 'None': # # * a truncated fragment's, since the octets after the cut are missing. # That includes one clipped by the data buffer's end, whose octets - # past it cannot be held, so it counts as cut there (:issue:`1566`). + # past it cannot be held, so it counts as cut there (#1566). # * a non-final fragment's. :rfc:`791#section-3.2` and # :rfc:`8200#section-4.5` make every fragment but the last carry a # multiple of 8 octets, so one that ends mid-block was cut or is @@ -255,7 +255,7 @@ def reassembly(self, info: 'Packet[_AT]') -> 'None': # complete the datagram with zeros in their place. This is the only # way a cut shows when the length is not declared: under Total # Length 0, and on IPv6, whose adapters derive it from the captured - # payload (:issue:`1567`). + # payload (#1567). start = FO // 8 if held == length and not MF: stop = (FO + held + 7) // 8 @@ -441,7 +441,7 @@ def submit(self, buf: 'Buffer[_AT]', *, bufid: 'tuple[_AT, _AT, int, TransType]' data.append(bytes(byte)) # Report it even with no run received, its payload ``()``, as # ``strict=False`` reports it with an empty payload: ``index`` - # still says which frames were its fragments (:issue:`1566`). + # still says which frames were its fragments (#1566). packet = Datagram( completed=completion, id=DatagramID( diff --git a/pcapkit/foundation/traceflow/traceflow.py b/pcapkit/foundation/traceflow/traceflow.py index ce652aba2a..94257ec307 100644 --- a/pcapkit/foundation/traceflow/traceflow.py +++ b/pcapkit/foundation/traceflow/traceflow.py @@ -163,7 +163,7 @@ class TraceFlowBase(Generic[_DT, _BT, _IT, _PT], metaclass=TraceFlowMeta): # Read-only copy of ``__output__`` as shipped. :meth:`register_dumper` # handed the dumper a format shipped with restores this entry, so an - # override can be undone (:issue:`1363`). + # override can be undone (#1363). _shipped_output = types.MappingProxyType(dict(__output__)) ########################################################################## diff --git a/pcapkit/protocols/application/application.py b/pcapkit/protocols/application/application.py index d4d0712982..f33d1050de 100644 --- a/pcapkit/protocols/application/application.py +++ b/pcapkit/protocols/application/application.py @@ -93,7 +93,7 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, super().__post_init__(file, length, **kwargs) # type: ignore[arg-type] # NOTE: A header the data ends inside keeps that in its ``info``, so - # that :meth:`from_data` rebuilds only what was captured (:issue:`1458`). + # that :meth:`from_data` rebuilds only what was captured (#1458). keep_short_read(self) # ``read`` may have dispatched the undissected remainder through diff --git a/pcapkit/protocols/application/ospf.py b/pcapkit/protocols/application/ospf.py index 8d83c3ac1f..ecbeb18202 100644 --- a/pcapkit/protocols/application/ospf.py +++ b/pcapkit/protocols/application/ospf.py @@ -209,7 +209,7 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_OSPF': # NOTE: Octets captured past the Packet Length belong to this layer, # since its own length field is what leaves them out of the payload -- # the message digest of cryptographic authentication among them -- and - # the rebuild keeps them as captured (:issue:`1455`). + # the rebuild keeps them as captured (#1455). if schema.trailer: ospf.__update__([ ('trailer', schema.trailer), @@ -360,7 +360,7 @@ def _make_data(cls, data: 'Data_OSPF') -> 'dict[str, Any]': # type: ignore[over 'auth_type': data.autype, 'auth_data': data.auth, 'payload': cls._make_payload(data), - # NOTE: And the octets captured past the Packet Length (:issue:`1455`). + # NOTE: And the octets captured past the Packet Length (#1455). 'trailer': data.get('trailer', b''), } @@ -473,7 +473,7 @@ def _make_encrypt_auth(self, seq=auth_data.seq, ) # NOTE: The ``to_dict()`` form of the info above, which ``from_data`` - # passes through as is (:issue:`1464`). + # passes through as is (#1464). if isinstance(auth_data, dict): try: return Schema_CryptographicAuthentication( diff --git a/pcapkit/protocols/internet/hip.py b/pcapkit/protocols/internet/hip.py index 29893ce42a..83d480545f 100644 --- a/pcapkit/protocols/internet/hip.py +++ b/pcapkit/protocols/internet/hip.py @@ -853,7 +853,7 @@ def _read_hip_param(self, length: 'int', *, version: 'int') -> 'Parameter': # p # NOTE: The trailing padding (:rfc:`7401#section-5.2.1`) is kept on # the data model only when it is not all zeros -- the form a fresh - # build writes -- so that a rebuild reproduces it (:issue:`1223`). + # build writes -- so that a rebuild reproduces it (#1223). # :meth:`_make_hip_param` writes it back. padding = schema.__dict__.get('padding') if isinstance(padding, bytes) and any(padding): diff --git a/pcapkit/protocols/internet/ipv4.py b/pcapkit/protocols/internet/ipv4.py index 338b302336..d9ff253297 100644 --- a/pcapkit/protocols/internet/ipv4.py +++ b/pcapkit/protocols/internet/ipv4.py @@ -345,7 +345,7 @@ def read(self, length: 'Optional[int]' = None, *, # pylint: disable=unused-argu # NOTE: An ``ihl`` declaring more header than was captured leaves the # missing octets to read as zeros, which ``from_data`` then rebuilds; - # rejecting it keeps the octets as captured inside the parent (:issue:`1404`). + # rejecting it keeps the octets as captured inside the parent (#1404). if len(self) < ipv4.hdr_len: raise ProtocolError(f'{self.alias}: header length {ipv4.hdr_len} runs past the end of ' f'the data ({len(self)} octets)') @@ -363,7 +363,7 @@ def read(self, length: 'Optional[int]' = None, *, # pylint: disable=unused-argu # NOTE: Octets captured past the Total Length belong to this layer, # since its own length field is what leaves them out of the payload: an # Ethernet carrier hands the whole rest of its frame here, padding - # included, and the rebuild keeps them as captured (:issue:`1209`). + # included, and the rebuild keeps them as captured (#1209). if schema.trailer: ipv4.__update__([ ('trailer', schema.trailer), @@ -380,7 +380,7 @@ def read(self, length: 'Optional[int]' = None, *, # pylint: disable=unused-argu # NOTE: A capture that ends at or inside the header has no payload # octets, and parsing the next layer from none would default-fill a # header that was never captured, which ``from_data`` then rebuilds - # (:issue:`1167`). A zero length makes it a ``NoPayload`` instead. + # (#1167). A zero length makes it a ``NoPayload`` instead. # # A Total Length of 0 is what TCP segmentation offload leaves in a # capture taken on the sending host: the payload is then the rest of @@ -389,7 +389,7 @@ def read(self, length: 'Optional[int]' = None, *, # pylint: disable=unused-argu # Wireshark's "Bogus IP length", which it dissects no further: no # payload, and the octets after the header are the trailer, as they are # for a Total Length equal to the header's. Either way the rebuild - # writes the Total Length back as it was (:issue:`1547`). + # writes the Total Length back as it was (#1547). if len(self) > ipv4.hdr_len: if ipv4.len == 0: _plen = len(self) - ipv4.hdr_len @@ -405,7 +405,7 @@ def read(self, length: 'Optional[int]' = None, *, # pylint: disable=unused-argu # code is dispatched on, so the registry's fallback leaves the octets as # :class:`~pcapkit.protocols.misc.raw.Raw` with no protocol to alias it # as, and ``'UDP' in frame`` does not hold of a slice of UDP payload - # (:issue:`1553`). Reassembly takes ``packet.payload``, the octets as + # (#1553). Reassembly takes ``packet.payload``, the octets as # captured, which this leaves as they were. return self._decode_next_layer(ipv4, None if ipv4.offset else ipv4.protocol, _plen, packet=__packet__) @@ -532,7 +532,7 @@ def make(self, # NOTE: An explicit ``total_length`` is written as given, so that a # ``from_data`` rebuild of a capture truncated by snaplen keeps the # length the sender declared instead of the one that was recorded - # (:issue:`1155`). Computed from the octets at hand otherwise, which is + # (#1155). Computed from the octets at hand otherwise, which is # what a plain ``IPv4(payload=...)`` wants. if total_length is None: total_length = ihl * 4 + len(payload) @@ -661,14 +661,14 @@ def _make_data(cls, data: 'Data_IPv4') -> 'dict[str, Any]': # type: ignore[over 'options': getattr(data, 'options', None), # NOTE: The parsed total length is passed through rather than left # to ``make`` to recompute, so that a truncated capture rebuilds - # with the length that was on the wire (:issue:`1155`). + # with the length that was on the wire (#1155). 'total_length': data.len, # NOTE: And the octets after the End of Option List, which no option # records. The header length is not passed: it follows from the # options and these octets, so edited options recompute it. 'padding': getattr(data, 'padding', None), 'payload': cls._make_payload(data), - # NOTE: And the octets captured past the Total Length (:issue:`1209`). + # NOTE: And the octets captured past the Total Length (#1209). 'trailer': data.get('trailer', b''), } diff --git a/pcapkit/protocols/internet/ipv6.py b/pcapkit/protocols/internet/ipv6.py index ab50daab07..75122f5a90 100644 --- a/pcapkit/protocols/internet/ipv6.py +++ b/pcapkit/protocols/internet/ipv6.py @@ -239,11 +239,11 @@ def read(self, length: 'Optional[int]' = None, *, # NOTE: This class reads IPv6 only, as :meth:`IPv4.read # ` reads IPv4 only; telling - # the two apart by the Version is left to the dispatchers (:issue:`1680`). + # the two apart by the Version is left to the dispatchers (#1680). if schema.hextet['version'] != 6: raise ProtocolError(f"[IPv6] invalid version: {schema.hextet['version']}") - # NOTE: A receiver treats each of these as an error (:issue:`1687`), so + # NOTE: A receiver treats each of these as an error (#1687), so # the frame stops here, see :meth:`IPv4.read # `. A Fragment header beside # the Jumbo Payload option is caught on the walk, see @@ -266,7 +266,7 @@ def read(self, length: 'Optional[int]' = None, *, # NOTE: Octets captured past the Payload Length are kept as captured, # see :meth:`IPv4.read ` - # (:issue:`1209`). + # (#1209). if schema.trailer: ipv6.__update__({ 'trailer': schema.trailer, @@ -275,7 +275,7 @@ def read(self, length: 'Optional[int]' = None, *, # NOTE: A jumbogram's extent is its Jumbo Payload Length, not the zero # Payload Length, see :meth:`Schema_IPv6.post_process # ` - # (:issue:`1434`); the check above leaves a zero one beside it. + # (#1434); the check above leaves a zero one beside it. jumbo = jumbo_payload_length(schema.next, rest) extent = schema.length if jumbo is None else jumbo @@ -339,12 +339,12 @@ def make(self, # NOTE: An explicit ``payload_length`` is written as given, so that a # ``from_data`` rebuild of a capture truncated by snaplen keeps the # length the sender declared instead of the one that was recorded - # (:issue:`1155`). Computed from the octets at hand otherwise, which is + # (#1155). Computed from the octets at hand otherwise, which is # what a plain ``IPv6(payload=...)`` wants -- or zero for a payload too # long for the field, or one whose Hop-by-Hop Options header carries a # Jumbo Payload option, i.e. a jumbogram, whose length the caller gives # there (:rfc:`2675#section-2`); its Payload Length must be zero - # (:issue:`1687`). + # (#1687). if payload_length is None: payload_length = len(payload) if payload_length > IP_MAX_LENGTH or jumbo_payload_length(next_val, bytes(payload)) is not None: @@ -435,10 +435,10 @@ def _make_data(cls, data: 'Data_IPv6') -> 'dict[str, Any]': # type: ignore[over # NOTE: ``data.payload`` is the parsed payload length, passed through # rather than left to ``make`` to recompute, so that a truncated # capture rebuilds with the length that was on the wire - # (:issue:`1155`). + # (#1155). 'payload_length': data.payload, 'payload': payload, - # NOTE: And the octets captured past the Payload Length (:issue:`1209`). + # NOTE: And the octets captured past the Payload Length (#1209). 'trailer': data.get('trailer', b''), } @@ -507,7 +507,7 @@ def _lookup_exthdr(cls, data: 'Data_IPv6') -> 'tuple[tuple[str, ...], tuple[tupl # see :meth:`_import_next_layer`, and written under that class's # key rather than the dedicated one's. Rebuilding it with the # dedicated class would look for fields only that class parses - # (:issue:`1473`), so the key decides the parser. + # (#1473), so the key decides the parser. if key == cls._exthdr_key(IPv6_Ext.alias.fget(None)): # type: ignore[attr-defined] parser = IPv6_Ext # type: Type[ProtocolBase] else: @@ -604,13 +604,13 @@ def _decode_next_layer(self, ipv6: 'Data_IPv6', proto: 'Optional[int]' = None, # headers are walked, as at most that many are dissected after any # other layer. This header and the rest are then the upper layer, # which :meth:`_import_next_layer` keeps raw, with one warning - # (:issue:`1604`). + # (#1604). if config.EXTENSION_HEADER_LIMIT != -1 and len(_protos) >= config.EXTENSION_HEADER_LIMIT: break # NOTE: Likewise past :data:`FRAME_LAYER_LIMIT # ` layers of the - # frame (:issue:`1610`), since each header walked is one of them. + # frame (#1610), since each header walked is one of them. if config.FRAME_LAYER_LIMIT != -1 and depth + len(_protos) >= config.FRAME_LAYER_LIMIT: break @@ -657,7 +657,7 @@ def _decode_next_layer(self, ipv6: 'Data_IPv6', proto: 'Optional[int]' = None, # NOTE: Added through a :class:`MultiDict`, so a repeated header # keeps its predecessor's info rather than overwriting it, and # :meth:`Info.to_dict ` - # writes every one of them, in wire order (:issue:`1453`). + # writes every one of them, in wire order (#1453). name = self._exthdr_key(next_.alias) ipv6.__update__(OrderedMultiDict([(name, info)])) @@ -687,7 +687,7 @@ def _decode_next_layer(self, ipv6: 'Data_IPv6', proto: 'Optional[int]' = None, # reassembly, so ``fragment``, ``hdr_len`` and ``raw_len`` keep # that split however far the walk goes on. if split is None: - # NOTE: Nor may a jumbogram carry one (:issue:`1687`), so the + # NOTE: Nor may a jumbogram carry one (#1687), so the # frame stops here as for the errors :meth:`read` checks; # past those, its Jumbo Payload option is in the payload. error = jumbogram_error(ipv6.payload, ipv6.next, self.__header__.get_payload(), lambda: True) @@ -701,7 +701,7 @@ def _decode_next_layer(self, ipv6: 'Data_IPv6', proto: 'Optional[int]' = None, # NOTE: Only a first fragment goes on. :rfc:`8200#section-4.5` # puts every header through the upper-layer one in the fragment # at offset zero, so what follows its Fragment header is walked - # exactly as it would be without one (:issue:`1539`). A later + # exactly as it would be without one (#1539). A later # fragment carries a slice of the Fragmentable Part from its # offset on, and that slice starts with no header at all. if not self._is_first_fragment(info): @@ -727,7 +727,7 @@ def _decode_next_layer(self, ipv6: 'Data_IPv6', proto: 'Optional[int]' = None, # fragment carries. No code is dispatched on, so the registry's # fallback leaves the octets as :class:`~pcapkit.protocols.misc.raw.Raw` # with no protocol to alias it as, and ``'UDP' in frame`` does not hold - # of a slice of UDP payload (:issue:`1545`). Reassembly takes + # of a slice of UDP payload (#1545). Reassembly takes # ``fragment`` above, which this leaves as it was. ipv6_exthdr = ProtoChain.from_list(_protos) # type: ignore[arg-type] self._exthdr_depth = len(_protos) # the headers the upper layer follows diff --git a/pcapkit/protocols/internet/ipv6_route.py b/pcapkit/protocols/internet/ipv6_route.py index 05fa3a12d8..70e6a6100a 100644 --- a/pcapkit/protocols/internet/ipv6_route.py +++ b/pcapkit/protocols/internet/ipv6_route.py @@ -367,7 +367,7 @@ def make(self, # NOTE: An explicit ``hdr_ext_len`` is written as given, so that a # ``from_data`` rebuild of a header the capture cut short keeps the # length the sender declared instead of the one that was recorded - # (:issue:`1457`), the same as IPv6's ``payload_length``. + # (#1457), the same as IPv6's ``payload_length``. if hdr_ext_len is not None: length = hdr_ext_len @@ -488,7 +488,7 @@ def _make_data(cls, data: 'Data_IPv6_Route') -> 'dict[str, Any]': # type: ignor # NOTE: ``data.length`` is the parsed header length in octets, # passed through as ``Hdr Ext Len`` rather than left to ``make`` to # recompute, so that a truncated capture rebuilds with the length - # that was on the wire (:issue:`1457`). + # that was on the wire (#1457). 'hdr_ext_len': (data.length - 8) // 8, 'payload': cls._make_payload(data), } diff --git a/pcapkit/protocols/internet/ipx.py b/pcapkit/protocols/internet/ipx.py index 7a6bd321dd..f55b73b12b 100644 --- a/pcapkit/protocols/internet/ipx.py +++ b/pcapkit/protocols/internet/ipx.py @@ -123,7 +123,7 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_IPX': # NOTE: Octets captured past the Packet Length belong to this layer, # since its own length field is what leaves them out of the payload: an # Ethernet carrier hands the whole rest of its frame here, padding - # included, and the rebuild keeps them as captured (:issue:`1433`). + # included, and the rebuild keeps them as captured (#1433). if schema.trailer: ipx.__update__([ ('trailer', schema.trailer), @@ -230,7 +230,7 @@ def _make_data(cls, data: 'Data_IPX') -> 'dict[str, Any]': # type: ignore[overr 'dst': cls._make_ipx_address(data.dst), 'src': cls._make_ipx_address(data.src), 'payload': cls._make_payload(data), - # NOTE: And the octets captured past the Packet Length (:issue:`1433`). + # NOTE: And the octets captured past the Packet Length (#1433). 'trailer': data.get('trailer', b''), } diff --git a/pcapkit/protocols/link/l2tpv2.py b/pcapkit/protocols/link/l2tpv2.py index 4ff88ad976..307e9519c8 100644 --- a/pcapkit/protocols/link/l2tpv2.py +++ b/pcapkit/protocols/link/l2tpv2.py @@ -200,7 +200,7 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_L2TP': # neither: an :rfc:`3931` ยง4.1.1 L2TPv3-over-IP datagram would otherwise # yield ``version=4``, ``tunnelid=0x5678`` and ``sessionid=0xff03``, read # out of the top half of a Session ID and the first two octets of the PPP - # frame behind it (:issue:`548`). This is also what keeps the hard-coded + # frame behind it (#548). This is also what keeps the hard-coded # ``Literal[2]`` of ``version`` true, instead of disagreeing with # ``info.version`` on the same datagram. if _flag['version'] != 2: @@ -238,7 +238,7 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_L2TP': # NOTE: Octets captured past the Length field belong to this layer, # since its own length field is what leaves them out of the payload, - # and the rebuild keeps them as captured (:issue:`1455`). + # and the rebuild keeps them as captured (#1455). if schema.trailer: l2tp.__update__([ ('trailer', schema.trailer), @@ -331,7 +331,7 @@ def make(self, # NOTE: An explicit ``total_length`` is not checked against the octets # at hand, so that a ``from_data`` rebuild of a capture truncated by # snaplen keeps the length the sender declared, as UDP does - # (:issue:`1155`). + # (#1155). if length_flag and total_length is None: total_length = 6 + 2 * (1 + 2 * seq + (offset is not None)) + (offset or 0) + len(payload) @@ -430,7 +430,7 @@ def _make_data(cls, data: 'Data_L2TP') -> 'dict[str, Any]': # type: ignore[over # NOTE: Passed as ``total_length`` since ``length`` is the parse # length :class:`~pcapkit.protocols.protocol.ProtocolBase` takes, so # under that key it never reached :meth:`make`, and the rebuild - # dropped both the Length field and the ``L`` flag (:issue:`1172`). + # dropped both the Length field and the ``L`` flag (#1172). 'total_length': data.length, # NOTE: Passed so that a control message captured without ``L`` # rebuilds as captured, rather than gaining the Length field @@ -443,6 +443,6 @@ def _make_data(cls, data: 'Data_L2TP') -> 'dict[str, Any]': # type: ignore[over 'offset': data.offset, 'padding': data.get('padding'), 'payload': cls._make_payload(data), - # NOTE: And the octets captured past the Length field (:issue:`1455`). + # NOTE: And the octets captured past the Length field (#1455). 'trailer': data.get('trailer', b''), } diff --git a/pcapkit/protocols/misc/pcap/frame.py b/pcapkit/protocols/misc/pcap/frame.py index 472f4f9980..681fe08dc3 100644 --- a/pcapkit/protocols/misc/pcap/frame.py +++ b/pcapkit/protocols/misc/pcap/frame.py @@ -346,11 +346,11 @@ def read(self, length: 'Optional[int]' = None, *, _read: 'bool' = True, # NOTE: A record whose ``incl_len`` runs past the end of the data is # kept as captured, as the PCAP-NG reader keeps a captured length - # running past its block (:issue:`1405`): ``incl_len`` and ``cap_len`` + # running past its block (#1405): ``incl_len`` and ``cap_len`` # stay as declared, so the record rebuilds byte-exactly, and the # packet is the octets present. A writer must not copy the declared # length; see :meth:`PCAPIO._append_value - # ` (:issue:`1541`). + # ` (#1541). held = len(self._data) - self.length if held < _ilen: warn(f'PCAP: [Frame {self._fnum}] captured length {_ilen} runs past the data, ' diff --git a/pcapkit/protocols/misc/pcapng.py b/pcapkit/protocols/misc/pcapng.py index 3cd4e5144f..5af05f3512 100644 --- a/pcapkit/protocols/misc/pcapng.py +++ b/pcapkit/protocols/misc/pcapng.py @@ -419,7 +419,7 @@ def _missing_(cls, value: 'object') -> 'PacketReception': # closed, hand-picked set, so the TLS key label enumeration is the generated # :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel`, imported above as # ``Enum_TLSKeyLabel`` like its :mod:`pcapkit.const.pcapng` siblings. This -# module does not re-export it under the unaliased name (:issue:`1744`). +# module does not re-export it under the unaliased name (#1744). # :class:`WireGuardKeyLabel` below stays hand-written: it is a closed set # (draft-ietf-opsawg-pcapng-06 section 4.7's "is one of" four names), not a # registry, so filing it under :mod:`pcapkit.const` would misrepresent it as @@ -1251,7 +1251,7 @@ def unpack(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_PCAPN packet.setdefault('snaplen', interfaces[0].snaplen) # NOTE: A zero snaplen handed in, e.g. interface 0's passed through, # means no limit as well; kept, it reads as a limit of no octets and - # the block's padding becomes packet data (:issue:`1558`). + # the block's padding becomes packet data (#1558). if 'snaplen' in packet and not packet['snaplen']: del packet['snaplen'] self.__header__ = cast('Schema_PCAPNG', self.__schema__.unpack(self._file, length, packet)) # type: ignore[call-arg,misc] @@ -1332,7 +1332,7 @@ def read(self, length: 'Optional[int]' = None, *, _read: 'bool' = True, # :class:`~pcapkit.protocols.schema.misc.pcapng.OptionAreaField`), and # parses as no options. The octets are kept on the block as # ``options_raw`` or ``records_raw``, which the block's ``make`` writes - # back verbatim (:issue:`1325`). + # back verbatim (#1325). for name in ('records', 'options'): area = getattr(schema.block, name, None) if isinstance(area, list) and len(area) == 1 and isinstance(area[0], bytes): @@ -1380,7 +1380,7 @@ def read(self, length: 'Optional[int]' = None, *, _read: 'bool' = True, # its first octets, so the block cannot be rebuilt from them at # the length it declares. The octets captured are kept as # ``__truncated_raw__`` instead, which :meth:`__post_init__` parses - # again in place of building the block (:issue:`1470`). + # again in place of building the block (#1470). block.__update__([('__truncated_raw__', self._data)]) # move backward to the beginning of next block @@ -1484,7 +1484,7 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, length: 'Opt # NOTE: A block the capture ended inside rebuilds as the octets that # were captured, parsed again as they were the first time (see - # :meth:`read`), rather than at the length it declares (:issue:`1470`). + # :meth:`read`), rather than at the length it declares (#1470). block = kwargs.get('block') if file is None and isinstance(block, collections.abc.Mapping) and '__truncated_raw__' in block: file = bytes(block['__truncated_raw__']) @@ -2258,7 +2258,7 @@ def _read_block_shb(self, schema: 'Schema_SectionHeaderBlock', *, section_length=schema.section_length, options=self._read_pcapng_options(schema.options), ) - # NOTE: The octets after ``opt_endofopt``, if any (:issue:`1437`). + # NOTE: The octets after ``opt_endofopt``, if any (#1437). self._read_padding(schema, data, 'padding', 'padding_opts', nonzero=False) return data @@ -2303,9 +2303,9 @@ def _read_block_idb(self, schema: 'Schema_InterfaceDescriptionBlock', *, snaplen=schema.snaplen, options=self._read_pcapng_options(schema.options), ) - # NOTE: The reserved octets, if they are not all zeros (:issue:`1437`). + # NOTE: The reserved octets, if they are not all zeros (#1437). self._read_padding(schema, data, 'reserved') - # NOTE: The octets after ``opt_endofopt``, if any (:issue:`1437`). + # NOTE: The octets after ``opt_endofopt``, if any (#1437). self._read_padding(schema, data, 'padding', 'padding_opts', nonzero=False) return data @@ -2370,13 +2370,13 @@ def _read_block_epb(self, schema: 'Schema_EnhancedPacketBlock', *, options=self._read_pcapng_options(schema.options), ) # NOTE: The padding of the packet data to 32 bits, if it is not all - # zeros (:issue:`1437`). + # zeros (#1437). self._read_padding(schema, data, 'padding_data') - # NOTE: The octets after ``opt_endofopt``, if any (:issue:`1437`). + # NOTE: The octets after ``opt_endofopt``, if any (#1437). self._read_padding(schema, data, 'padding_opts', 'padding_opts', nonzero=False) # NOTE: A captured length running past the block is kept as declared, - # and the packet data is the octets the block holds (:issue:`1405`). + # and the packet data is the octets the block holds (#1405). if schema.captured_len > len(schema.packet_data): warn(f'PCAP-NG: [Block {header.type}] captured length {schema.captured_len} runs past ' f'the block, which holds {len(schema.packet_data)} octet(s); kept as captured', @@ -2426,11 +2426,11 @@ def _read_block_spb(self, schema: 'Schema_SimplePacketBlock', *, original_len=schema.original_len, ) # NOTE: The padding of the packet data to 32 bits, if it is not all - # zeros (:issue:`1437`). + # zeros (#1437). self._read_padding(schema, data, 'padding', 'padding_data') # NOTE: A block holding more packet data than the snaplen of interface 0 - # allows keeps the excess (:issue:`1384`); the specification says the + # allows keeps the excess (#1384); the specification says the # snaplen determines the length, so the excess is reported. snaplen = self._ctx.interfaces[0].snaplen if self._ctx is not None and self._ctx.interfaces else 0 if snaplen and data.captured_len > snaplen: @@ -2438,7 +2438,7 @@ def _read_block_spb(self, schema: 'Schema_SimplePacketBlock', *, f'exceeds the snaplen of {snaplen}; kept as captured', ProtocolWarning, stacklevel=stacklevel()) # NOTE: Likewise for more packet data than the original length, padding - # aside (:issue:`1414`). + # aside (#1414). if data.captured_len > (data.original_len + 3) // 4 * 4: warn(f'PCAP-NG: [Block {header.type}] packet data of {data.captured_len} octet(s) ' f'exceeds the original length of {data.original_len}; kept as captured', @@ -2492,7 +2492,7 @@ def _read_block_nrb(self, schema: 'Schema_NameResolutionBlock', *, records=self._read_nrb_records(schema.records), options=self._read_pcapng_options(schema.options), ) - # NOTE: The octets after ``opt_endofopt``, if any (:issue:`1437`). + # NOTE: The octets after ``opt_endofopt``, if any (#1437). self._read_padding(schema, data, 'padding', 'padding_opts', nonzero=False) return data @@ -2544,7 +2544,7 @@ def _read_block_isb(self, schema: 'Schema_InterfaceStatisticsBlock', *, timestamp_epoch=timestamp_epoch, options=self._read_pcapng_options(schema.options), ) - # NOTE: The octets after ``opt_endofopt``, if any (:issue:`1437`). + # NOTE: The octets after ``opt_endofopt``, if any (#1437). self._read_padding(schema, data, 'padding', 'padding_opts', nonzero=False) return data @@ -2583,13 +2583,13 @@ def _read_block_systemd(self, schema: 'Schema_SystemdJournalExportBlock', *, # NOTE: A malformed entry is kept as the octets captured, as one # :obj:`bytes` item, and parses as no entries. The octets are kept on # the block as ``entry_raw``, which the block's ``make`` writes back - # verbatim (:issue:`1406`). + # verbatim (#1406). kept = len(entries) == 1 and isinstance(entries[0], bytes) data = Data_SystemdJournalExportBlock( type=header.type, length=schema.length, # NOTE: each entry as an OrderedMultiInfo, which ``Info`` does not - # do for a mapping held in a tuple (:issue:`1484`). + # do for a mapping held in a tuple (#1484). data=() if kept else tuple(OrderedMultiInfo(entry) for entry in entries), # type: ignore[arg-type] ) if kept: @@ -2653,9 +2653,9 @@ def _read_block_dsb(self, schema: 'Schema_DecryptionSecretsBlock', *, options=self._read_pcapng_options(schema.options), ) # NOTE: The padding of the secrets to 32 bits, if it is not all zeros - # (:issue:`1437`). + # (#1437). self._read_padding(schema, data, 'padding_data') - # NOTE: The octets after ``opt_endofopt``, if any (:issue:`1437`). + # NOTE: The octets after ``opt_endofopt``, if any (#1437). self._read_padding(schema, data, 'padding_opts', 'padding_opts', nonzero=False) return data @@ -2769,13 +2769,13 @@ def _read_block_packet(self, schema: 'Schema_PacketBlock', *, options=self._read_pcapng_options(schema.options), ) # NOTE: The padding of the packet data to 32 bits, if it is not all - # zeros (:issue:`1437`). + # zeros (#1437). self._read_padding(schema, data, 'padding_data') - # NOTE: The octets after ``opt_endofopt``, if any (:issue:`1437`). + # NOTE: The octets after ``opt_endofopt``, if any (#1437). self._read_padding(schema, data, 'padding_opts', 'padding_opts', nonzero=False) # NOTE: A captured length running past the block is kept as declared, - # and the packet data is the octets the block holds (:issue:`1405`). + # and the packet data is the octets the block holds (#1405). if schema.captured_length > len(schema.packet_data): warn(f'PCAP-NG: [Block {header.type}] captured length {schema.captured_length} runs past ' f'the block, which holds {len(schema.packet_data)} octet(s); kept as captured', @@ -2969,7 +2969,7 @@ def _read_pcapng_options(self, options_schema: 'list[Schema_Option]') -> 'Option else: meth = name[0] data = meth(schema, options=options) - # NOTE: The padding to 32 bits, if it is not all zeros (:issue:`1437`). + # NOTE: The padding to 32 bits, if it is not all zeros (#1437). self._read_padding(schema, data) # record option data @@ -4086,7 +4086,7 @@ def _read_nrb_records(self, records_schema: 'list[Schema_NameResolutionRecord]') else: meth = name[0] data = meth(schema, records=records) - # NOTE: The padding to 32 bits, if it is not all zeros (:issue:`1437`). + # NOTE: The padding to 32 bits, if it is not all zeros (#1437). self._read_padding(schema, data) # record option data @@ -4203,7 +4203,7 @@ def _read_secrets_tls(self, schema: 'Schema_TLSKeyLog', *, """ secrets = Data_TLSKeyLog( # NOTE: each label's entries as an OrderedMultiInfo, which ``Info`` - # does not do for a mapping held in a dict (:issue:`1484`). + # does not do for a mapping held in a dict (#1484). entries={label: OrderedMultiInfo(entry) for (label, entry) in schema.entries.items()}, ) # NOTE: the log text is kept as well, since :attr:`entries` holds @@ -4552,7 +4552,7 @@ def _make_block_spb(self, block: 'Optional[Data_SimplePacketBlock]' = None, *, original_len = block.original_len packet_data = self._make_packet_data(block) # NOTE: The data model's ``captured_len`` already bounds the data, - # including a block that kept octets past the snaplen (:issue:`1384`). + # including a block that kept octets past the snaplen (#1384). captured_len = block.captured_len else: if original_len is None: diff --git a/pcapkit/protocols/misc/raw.py b/pcapkit/protocols/misc/raw.py index 0a1549f2fb..58467df3e3 100644 --- a/pcapkit/protocols/misc/raw.py +++ b/pcapkit/protocols/misc/raw.py @@ -143,7 +143,7 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, # NOTE: The buffer above is all there is to parse, so its size is # declared rather than left for :func:`~pcapkit.corekit.decorators.prepare` # to derive: a derived zero is read as the end of the stream, whereas - # an empty raw payload is a payload all the same (:issue:`1285`). + # an empty raw payload is a payload all the same (#1285). if length is None: length = len(self._data) diff --git a/pcapkit/protocols/protocol.py b/pcapkit/protocols/protocol.py index 3d4ffbecdd..7fb13784ed 100644 --- a/pcapkit/protocols/protocol.py +++ b/pcapkit/protocols/protocol.py @@ -862,7 +862,7 @@ def unpack(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> '_PT': elif self._make_raw and _holds_raw(self.__header__): # NOTE: A schema may accept octets that :meth:`read` then rejects, # e.g. an option whose length runs past the option area, so the - # read is covered too (:issue:`1394`). + # read is covered too (#1394). try: parsed = cast('_ST', type(self.__header__).unpack(io.BytesIO(self._data), length, packet)) # type: ignore[call-arg,misc] if _raw_where_parsed(self.__header__, parsed): @@ -1342,7 +1342,7 @@ def __init__(self, file: 'Optional[IO[bytes] | bytes]' = None, length: 'Optional # keyword would reach the schema as nothing at all and the field would # keep its default -- ``TCP_BASE`` asking for ``seq=1``, which # ``TCP.make`` spells ``seq_no``, would yield ``seq = 0`` with no warning - # (:issue:`617`). The schema layer is not that permissive: :meth:`Schema.__update__ + # (#617). The schema layer is not that permissive: :meth:`Schema.__update__ # ` warns # :exc:`~pcapkit.utilities.warnings.UnknownFieldWarning` for a field it # does not know, and this closes the asymmetry from the other end. @@ -1489,7 +1489,7 @@ def __init_subclass__(cls, /, schema: 'Optional[Type[_ST]]' = None, # NOTE: a class defining its own ``__proto__`` ships what its body put # there; copy it now, before anything can register into it, so that # ``_next_layer_entry`` can restore a shipped descriptor without - # resolving it (:issue:`1504`). A class that inherits ``__proto__`` + # resolving it (#1504). A class that inherits ``__proto__`` # inherits this copy with it. if '__proto__' in cls.__dict__: table = cls.__dict__['__proto__'] diff --git a/pcapkit/protocols/schema/schema.py b/pcapkit/protocols/schema/schema.py index cd275271f0..62ee5d5213 100644 --- a/pcapkit/protocols/schema/schema.py +++ b/pcapkit/protocols/schema/schema.py @@ -585,8 +585,8 @@ def __update__(self, dict_: 'Optional[Mapping[str, _VT] | Iterable[tuple[str, _V for (key, value) in data_iter: # NOTE: Not a field, but what :meth:`to_dict` emits for the octets an # enclosing ``SchemaField`` span held beyond this schema, and where, - # so that a rebuilt schema still re-emits them (:issue:`1380`). - # NOTE: Nor is the short read :meth:`unpack` records (:issue:`1458`). + # so that a rebuilt schema still re-emits them (#1380). + # NOTE: Nor is the short read :meth:`unpack` records (#1458). if key in ('__remainder__', '__remainder_offset__', '__short_read__'): self.__dict__[key] = value continue @@ -668,7 +668,7 @@ def __getitem__(self, name: 'str') -> '_VT': # fields: a ``post_process`` may store derived values on the instance, # as :class:`~pcapkit.protocols.schema.internet.ipv4.TSOption` does, and # iteration reports them, so ``dict(schema)`` must be able to read them - # back (:issue:`1291`). A mangled key is reachable only under the name + # back (#1291). A mangled key is reachable only under the name # it was mangled from, as in iteration. if name in self.__fields__ or (name not in self.__builtin__ and name not in self.__map_reverse__): key = self.__map__.get(name, name) @@ -885,14 +885,14 @@ class will consider negative value as a placeholder. # the packed options do not fill. Without it every padding # field sized from that key packs nothing, and the header # comes out shorter than its own length field says - # (:issue:`1289`). An undeclared area (a negative length) + # (#1289). An undeclared area (a negative length) # leaves no remainder. packet['__option_padding__'] = max(field.length - len(self.__buffer__[field.name]), 0) continue if isinstance(field, PaddingField): # NOTE: the octets as captured, or zeros when there are none - # (:issue:`1223`) -- see :meth:`PaddingField.pack + # (#1223) -- see :meth:`PaddingField.pack # `. self.__buffer__[field.name] = field.pack(data, packet) continue @@ -928,8 +928,8 @@ class will consider negative value as a placeholder. # stale octets at a shifted offset, so then, or for a schema that # never had any, the rest of the span is zero-filled. # Either way the field keeps the width its length field declares - # (:issue:`1292`), and a schema rebuilt through :meth:`from_dict` - # packs what the parsed one did (:issue:`1380`). + # (#1292), and a schema rebuilt through :meth:`from_dict` + # packs what the parsed one did (#1380). # # A field reached through a # :class:`~pcapkit.corekit.fields.misc.SwitchField` is not handled @@ -1065,7 +1065,7 @@ def unpack(cls, data: 'bytes | IO[bytes]', # NOTE: A negative length is the unset default and takes what # the declared length leaves, never less than nothing; a # computed zero is an empty payload, not an unset one, or it - # would swallow the fields after it (:issue:`1275`). + # would swallow the fields after it (#1275). length = field.length if length < 0: length = max(cast('int', packet['__length__']), 0) diff --git a/pcapkit/protocols/transport/sctp.py b/pcapkit/protocols/transport/sctp.py index 1a4ec1229a..38cb3b320f 100644 --- a/pcapkit/protocols/transport/sctp.py +++ b/pcapkit/protocols/transport/sctp.py @@ -633,7 +633,7 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_SCTP': # NOTE: With no packet length field, octets after the last whole chunk # cannot be told apart from a chunk cut short, so they are kept as - # captured rather than rejected (:issue:`1468`). + # captured rather than rejected (#1468). if schema.trailer: sctp.__update__([ ('trailer', schema.trailer), diff --git a/pcapkit/protocols/transport/tcp.py b/pcapkit/protocols/transport/tcp.py index 10ef15f15f..92b1f82f57 100644 --- a/pcapkit/protocols/transport/tcp.py +++ b/pcapkit/protocols/transport/tcp.py @@ -540,7 +540,7 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_TCP': # NOTE: A Data Offset declaring more header than was captured leaves the # missing octets to read as zeros, which ``from_data`` then rebuilds; - # rejecting it keeps the octets as captured inside the parent (:issue:`1404`). + # rejecting it keeps the octets as captured inside the parent (#1404). if len(self) < tcp.hdr_len: raise ProtocolError(f'{self.alias}: header length {tcp.hdr_len} runs past the end of ' f'the data ({len(self)} octets)') diff --git a/pcapkit/protocols/transport/udp.py b/pcapkit/protocols/transport/udp.py index c4fcbefd6f..6dd61ff53d 100644 --- a/pcapkit/protocols/transport/udp.py +++ b/pcapkit/protocols/transport/udp.py @@ -184,7 +184,7 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_UDP': # NOTE: Octets captured past the Length field belong to this layer, # since its own length field is what leaves them out of the payload, - # and the rebuild keeps them as captured (:issue:`1461`). + # and the rebuild keeps them as captured (#1461). if schema.trailer: udp.__update__([ ('trailer', schema.trailer), @@ -228,7 +228,7 @@ def make(self, # NOTE: An explicit ``total_length`` is written as given, so that a # ``from_data`` rebuild of a capture truncated by snaplen keeps the # length the sender declared instead of the one that was recorded - # (:issue:`1155`). Computed from the octets at hand otherwise, which is + # (#1155). Computed from the octets at hand otherwise, which is # what a plain ``UDP(payload=...)`` wants. if total_length is None: total_length = 8 + len(payload) @@ -283,9 +283,9 @@ def _make_data(cls, data: 'Data_UDP') -> 'dict[str, Any]': # type: ignore[overr 'checksum': data.checksum, # NOTE: The parsed length is passed through rather than left to # ``make`` to recompute, so that a truncated capture rebuilds with - # the length that was on the wire (:issue:`1155`). + # the length that was on the wire (#1155). 'total_length': data.len, 'payload': cls._make_payload(data), - # NOTE: And the octets captured past the Length field (:issue:`1461`). + # NOTE: And the octets captured past the Length field (#1461). 'trailer': data.get('trailer', b''), } diff --git a/pcapkit/toolkit/scapy.py b/pcapkit/toolkit/scapy.py index ce8926b54c..a46a31d3ea 100644 --- a/pcapkit/toolkit/scapy.py +++ b/pcapkit/toolkit/scapy.py @@ -476,14 +476,14 @@ def ipv4_reassembly(packet: 'Packet', *, count: 'int' = -1) -> 'IP_Packet[IPv4Ad ipv4 = cast('IP', packet['IP']) if ipv4.flags.DF: # dismiss not fragmented packet return None - if not _frame(packet): # a zero-length frame (:issue:`1676`) + if not _frame(packet): # a zero-length frame (#1676) return None # NOTE: ``bytes(ipv4)`` ends with the frame's trailer, which Scapy files # as padding at the end of the packet however deep the IPv4 layer, so # only the octets inside every IP datagram above it are kept, the - # Payload Length of IPv6 carrying it included (:issue:`1623`). The IHL + # Payload Length of IPv6 carrying it included (#1623). The IHL # and Total Length are read off them, as Scapy leaves ``ihl`` and - # ``len`` None on a layer built by hand until it is built (:issue:`1676`). + # ``len`` None on a layer built by hand until it is built (#1676). octets = _within_datagrams(ipv4) ihl = (octets[0] & 0x0F) * 4 if octets else IPV4_HEADER_LEN if len(octets) < max(ihl, IPV4_HEADER_LEN): # a header those octets cut short @@ -512,7 +512,7 @@ def ipv4_reassembly(packet: 'Packet', *, count: 'int' = -1) -> 'IP_Packet[IPv4Ad # NOTE: this keeps the octets past the Total Length, since the # reassembly machinery cuts the payload at ``tl - ihl`` itself. # :func:`ipv6_reassembly` derives its ``tl`` from the payload, so it - # has to cut them (:issue:`1584`). + # has to cut them (#1584). payload=bytearray(octets[ihl:]), # raw bytearray type payload timestamp=float(packet.time), # capture timestamp ) @@ -576,7 +576,7 @@ def ipv6_reassembly(packet: 'Packet', *, count: 'int' = -1) -> 'IP_Packet[IPv6Ad # reassembled packet keeps (:rfc:`8200#section-4.5`). hdr_len = len(ipv6) - len(ipv6_frag) # the fragment's data stops at the Payload Length, so neither it nor - # ``tl`` below counts the frame's trailer (:issue:`1584`) + # ``tl`` below counts the frame's trailer (#1584) payload = bytearray(_within_datagrams(ipv6_frag.payload)) data = IP_Packet( diff --git a/tests/project/test_issue_refs_in_comments.py b/tests/project/test_issue_refs_in_comments.py new file mode 100644 index 0000000000..1d7ad23a4a --- /dev/null +++ b/tests/project/test_issue_refs_in_comments.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +"""Plain comments cite issues and pull requests as ``#NNN``, not by Sphinx role. + +The ``:issue:`` and ``:pr:`` roles render only where Sphinx reads the text: in +docstrings, in ``#:`` attribute doc-comments that autodoc picks up, and in +``.rst`` files. In a plain ``#`` comment nothing renders them, so the role is +noise around the number a reader wants, and ``#NNN`` is what GitHub links. + +Comments are read from :mod:`tokenize` ``COMMENT`` tokens, so a docstring that +cites an issue by role is not a comment, and a ``#:`` doc-comment is skipped on +purpose. :data:`META_MENTIONS` holds the comments that talk about the roles +themselves rather than cite an issue with one. This module is unit-tier: it +imports nothing it scans. + +""" +from __future__ import annotations + +import io +import re +import shutil +import subprocess # nosec: B404 +import tokenize +import unittest +from typing import TYPE_CHECKING + +from tests._tiers import ROOT + +if TYPE_CHECKING: + from typing import Iterator + +#: A Sphinx issue or pull request role, cited or merely named. +ROLE = re.compile(r':(?:issue|pr):`') + +#: Comments that name a role without citing anything by it, as the path and the +#: comment's text. Each is checked to still exist, so the list cannot go stale. +META_MENTIONS = frozenset({ + # Explains that the two roles share one ``#%s`` caption. + ('docs/source/conf.py', + '# The caption is ``#%s`` for both ``:issue:`` and ``:pr:`` so that converting a bare'), + # Explains why a Discussion number cannot be cited with the issue role. + ('docs/source/conf.py', + '# ``:issue:`` would link to a page that does not exist.'), + # Describes the changelog converter's test input, which uses the role. + ('tests/project/test_changelog_md.py', + '# ``:ref:`` half passed before the change too -- only the ``:issue:`` half'), +}) + + +def tracked() -> 'list[str]': + """Every ``.py`` path git tracks, or skip when this is not a checkout.""" + if shutil.which('git') is None or not (ROOT / '.git').exists(): + raise unittest.SkipTest('not a git checkout') + result = subprocess.run(['git', 'ls-files', '-z', '--', '*.py'], cwd=ROOT, # nosec + capture_output=True, check=True) + return sorted(name for name in result.stdout.decode('utf-8').split('\0') if name) + + +def role_comments(source: 'bytes') -> 'Iterator[tuple[int, str]]': + """The line and text of each plain comment in ``source`` that holds a role.""" + for token in tokenize.tokenize(io.BytesIO(source).readline): + if token.type == tokenize.COMMENT and not token.string.startswith('#:') \ + and ROLE.search(token.string): + yield token.start[0], token.string + + +def repository_role_comments() -> 'list[tuple[str, int, str]]': + """Every plain comment holding a role in a tracked ``.py`` file.""" + return [(path, line, text) + for path in tracked() + for line, text in role_comments((ROOT / path).read_bytes())] + + +class ScannerTests(unittest.TestCase): + """The scan reads plain comments only.""" + + def test_flags_a_plain_comment(self) -> None: + source = b'x = 1 # see :issue:`12`\n# and :pr:`34`\n' + self.assertEqual([line for line, _ in role_comments(source)], [1, 2]) + + def test_skips_docstrings_and_strings(self) -> None: + source = b'def f():\n """Fixed in :issue:`12`."""\n return ":pr:`34`" # #56\n' + self.assertEqual(list(role_comments(source)), []) + + def test_skips_attribute_doc_comments(self) -> None: + self.assertEqual(list(role_comments(b'#: Added in :issue:`12`.\nx = 1\n')), []) + + def test_meta_mentions_still_exist(self) -> None: + found = {(path, text) for path, _, text in repository_role_comments()} + self.assertEqual(sorted(META_MENTIONS - found), [], 'stale META_MENTIONS entries') + + +class IssueRefTests(unittest.TestCase): + """No plain comment cites an issue or pull request by Sphinx role.""" + + def test_plain_comments_use_hash_numbers(self) -> None: + offending = [f'{path}:{line}: {text}' for path, line, text in repository_role_comments() + if (path, text) not in META_MENTIONS] + self.assertEqual(offending, [], 'cite issues and pull requests as #NNN in plain comments; ' + 'the roles belong in docstrings and #: doc-comments') + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/protocols/internet/test_ipv4_header_truncated_payload_runtime.py b/tests/protocols/internet/test_ipv4_header_truncated_payload_runtime.py index eaebb18af8..828611ee71 100644 --- a/tests/protocols/internet/test_ipv4_header_truncated_payload_runtime.py +++ b/tests/protocols/internet/test_ipv4_header_truncated_payload_runtime.py @@ -46,7 +46,7 @@ def test_capture_cut_inside_the_header(self) -> None: parsed = list(extractor.frame)[2][Ethernet] # 18 octets are short of the 20-octet IPv4 header, so they are kept as - # raw octets (:issue:`1170`) rather than parsed into a ``dst`` whose low + # raw octets (#1170) rather than parsed into a ``dst`` whose low # half is zero-filled, and the rebuild is exactly what was captured. self.assertIsInstance(parsed.payload, Raw) self.assertEqual(parsed.payload.data, bytes.fromhex('4500012ca8370000fa11178a00000000ffff'))