Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions docs/source/pcapkit/protocols/transport/tcp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Octets Bits Name Description
8 64 ``tcp.ack`` Acknowledgement Number (if ACK set)
12 96 ``tcp.hdr_len`` Data Offset
12 100 Reserved (must be ``\x00``)
12 103 ``tcp.flags.ns`` ECN Concealment Protection (NS) [*]_
12 103 ``tcp.flags.ns`` ECN Concealment Protection (NS)
13 104 ``tcp.flags.cwr`` Congestion Window Reduced (CWR)
13 105 ``tcp.flags.ece`` ECN-Echo (ECE)
13 106 ``tcp.flags.urg`` Urgent (URG)
Expand Down Expand Up @@ -535,7 +535,3 @@ Data Models
.. rubric:: Footnotes

.. [*] https://en.wikipedia.org/wiki/Transmission_Control_Protocol
.. [*] The NS bit is read off the wire but is **not** surfaced in
:class:`~pcapkit.protocols.data.transport.tcp.Flags`. :rfc:`3540` was
reclassified as Historic, so the field is left unexposed; the row is kept
because the bit is part of the header as the RFC defines it.
4 changes: 2 additions & 2 deletions pcapkit/protocols/data/transport/tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class Flags(Data):
"""Data model for TCP flags."""

#: ECN-nonce concealment protection.
#ns: 'bool'
ns: 'bool'
#: Congestion window reduced.
cwr: 'bool'
#: ECN-Echo.
Expand All @@ -68,7 +68,7 @@ class Flags(Data):
fin: 'bool'

if TYPE_CHECKING:
def __init__(self, cwr: 'bool', ece: 'bool', urg: 'bool', ack: 'bool',
def __init__(self, ns: 'bool', cwr: 'bool', ece: 'bool', urg: 'bool', ack: 'bool',
psh: 'bool', rst: 'bool', syn: 'bool', fin: 'bool') -> 'None': ... # pylint: disable=unused-argument,super-init-not-called,multiple-statements,line-too-long,redefined-builtin


Expand Down
6 changes: 3 additions & 3 deletions pcapkit/protocols/transport/tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
8 64 ``tcp.ack`` Acknowledgement Number (if ACK set)
12 96 ``tcp.hdr_len`` Data Offset
12 100 Reserved (must be ``\\x00``)
12 103 ECN Concealment Protection (NS), not parsed
12 103 ``tcp.flags.ns`` ECN Concealment Protection (NS)
13 104 ``tcp.flags.cwr`` Congestion Window Reduced (CWR)
13 105 ``tcp.flags.ece`` ECN-Echo (ECE)
13 106 ``tcp.flags.urg`` Urgent (URG)
Expand Down Expand Up @@ -471,7 +471,7 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_TCP':
ack=schema.ack,
hdr_len=schema.offset['offset'] * 4,
flags=Data_Flags(
#ns=bool(schema.offset['ns']),
ns=bool(schema.offset['ns']),
cwr=bool(schema.flags['cwr']),
ece=bool(schema.flags['ece']),
urg=bool(schema.flags['urg']),
Expand Down Expand Up @@ -692,7 +692,7 @@ def _make_data(cls, data: 'Data_TCP') -> 'dict[str, Any]': # type: ignore[overr
'dstport': data.dstport,
'seq_no': data.seq,
'ack_no': data.ack,
#'ns': data.flags.ns,
'ns': data.flags.ns,
'cwr': data.flags.cwr,
'ece': data.flags.ece,
'urg': data.flags.urg,
Expand Down
65 changes: 65 additions & 0 deletions tests/protocols/transport/test_tcp_ns_flag_roundtrip_unit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""#1098 -- the TCP NS flag round-trips through read, ``make()`` and ``from_data``.

:meth:`TCP.make <pcapkit.protocols.transport.tcp.TCP.make>` accepts ``ns`` and
packs it into bit 103 (the low bit of byte 12), and
:class:`~pcapkit.protocols.schema.transport.tcp.OffsetFlag` declares the bit. The
reader and :meth:`TCP._make_data <pcapkit.protocols.transport.tcp.TCP._make_data>`
must carry it too, so a parsed segment exposes ``flags.ns`` and a
:meth:`~pcapkit.protocols.protocol.ProtocolBase.from_data` rebuild keeps the bit
on the wire rather than clearing it silently.

"""
from __future__ import annotations

import importlib.util
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 TCPNSFlagRoundTripUnitTests(unittest.TestCase):
def setUp(self) -> None:
reimport_once_per_class(self)

def test_make_sets_ns_on_the_wire(self) -> None:
"""``make(ns=True)`` sets the low bit of byte 12, next to a data offset of 5."""
from pcapkit.protocols.transport.tcp import TCP

self.assertEqual(TCP(srcport=1, dstport=2, ns=True).data[12], 0x51)
self.assertEqual(TCP(srcport=1, dstport=2, ns=False).data[12], 0x50)

def test_read_exposes_ns(self) -> None:
"""A parsed segment reports ``flags.ns`` as read from bit 103."""
from pcapkit.protocols.transport.tcp import TCP

for ns in (True, False):
with self.subTest(ns=ns):
raw = TCP(srcport=1, dstport=2, ns=ns).data
self.assertIs(TCP(raw).info.flags.ns, ns)

def test_from_data_keeps_ns(self) -> None:
"""A ``from_data`` rebuild of a parsed segment reproduces its bytes, NS included."""
from pcapkit.protocols.transport.tcp import TCP

for ns in (True, False):
with self.subTest(ns=ns):
raw = TCP(srcport=1, dstport=2, ns=ns, ack=True).data
rebuilt = TCP.from_data(TCP(raw).info)
self.assertEqual(rebuilt.data, raw)
self.assertIs(rebuilt.info.flags.ns, ns)

def test_flags_field_order(self) -> None:
"""``ns`` leads the flags, in wire order, ahead of ``cwr``."""
from pcapkit.protocols.transport.tcp import TCP

flags = TCP(TCP(srcport=1, dstport=2).data).info.flags
self.assertEqual(list(flags.keys()),
['ns', 'cwr', 'ece', 'urg', 'ack', 'psh', 'rst', 'syn', 'fin'])


if __name__ == '__main__':
unittest.main()
2 changes: 1 addition & 1 deletion tests/protocols/transport/test_tcp_udp_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def test_tcp_index_and_make_data_preserve_flags_and_ports(self) -> None:
from pcapkit.const.reg.transtype import TransType
from pcapkit.protocols.transport.tcp import TCP

flags = DummyData(cwr=False, ece=False, urg=False, ack=True, psh=True, rst=False, syn=False, fin=True)
flags = DummyData(ns=False, cwr=False, ece=False, urg=False, ack=True, psh=True, rst=False, syn=False, fin=True)
data = DummyData(
srcport=443,
dstport=59000,
Expand Down
Loading