Skip to content

Canonical, direction-independent flow keys - #131

Merged
EONRaider merged 1 commit into
masterfrom
claude/decoder-depth-polish-8m54to
Sep 4, 2026
Merged

Canonical, direction-independent flow keys#131
EONRaider merged 1 commit into
masterfrom
claude/decoder-depth-polish-8m54to

Conversation

@EONRaider

Copy link
Copy Markdown
Owner

Summary

Closes#89. netprotocols.flow is a new small module (FlowKey, flow_key(), mirroring checksum.py's footprint) that folds a TCP/UDP segment and its enclosing IPv4/IPv6 header into a key that is identical for both directions of one conversation — a request and its reply key equal, whichever one you feed in.

What's included

  • FlowKey — a NamedTuple (not a frozen dataclass): canonicalizing the two directions is comparing two (address, port) endpoint tuples and always emitting the lexicographically smaller one first, which a plain tuple already orders natively via <=. Every other frozen dataclass in this codebase models a wire format (decode()/__bytes__/_struct); a derived key isn't one, so NamedTuple is the right tool rather than bolting order=True onto a dataclass.
  • flow_key(layer, *, ip=None) — mirrors checksum.compute()'s signature shape (transport layer positionally, enclosing IP keyword-only). Reads whichever of IPv4.protocol / IPv6.next_header the enclosing header actually has (same semantic field, different attribute name).
  • Packet.flow_key() — the convenience method. Walks self.layers for the first IPv4/IPv6 and TCP/UDP layers (mirroring the exact enclosing-IP scan Packet.with_checksums() already uses) and delegates to the free function.
  • ICMP (or anything without ports) returns None from both forms — not an exception, and no invented port-slot convention (NetFlow's ICMP type/code-into-port trick is real prior art but unlabeled magic here) — matching this codebase's established "None rather than raising" pattern (the NDP accessors in icmp.py).
  • tests/test_flow.py: both directions of a TCP conversation produce equal keys (headers constructed independently, never assembled into a Packet); IPv4 and IPv6 both work; UDP works; ICMP returns None from both the free function and Packet.flow_key(); FlowKey is hashable and works as a dict key; a missing ip for a TCP/UDP layer raises InvalidFieldError; Packet.flow_key() matches the free function and returns None when either layer is missing.
  • README gains a "Flow keys" section; ARCHITECTURE.md's layout map lists the new module.
  • CHANGELOG.md entry under ## [Unreleased].

Verification

  • uv run --frozen ruff check . and uv run --frozen ruff format --check . are clean
  • uv run --frozen mypy is clean (strict)
  • uv run --frozen pytest passes locally
  • uv run --frozen python scripts/benchmark.py --check --threshold 15 — within threshold (+37.2% vs. baseline)
  • CHANGELOG.md has an entry under ## [Unreleased]

No new protocol/dispatch change, so that checklist block doesn't apply.

Notes

Second of the three remaining Tier 2 issues (#90#89#92, per the roadmap's #107 working agreement) — merged one at a time, not auto-merged.


🤖 Generated with Claude Code

https://claude.ai/code/session_01SVCFe7B1U5VeJRoUbx24vb


Generated by Claude Code

netprotocols.flow adds FlowKey (a NamedTuple, since canonicalizing the
two directions is comparing two (address, port) endpoint tuples, which
a plain tuple already orders natively) and flow_key(), mirroring
checksum.compute()'s signature shape: the transport layer positionally,
the enclosing IPv4/IPv6 header as keyword-only ip=.
Both directions of one TCP/UDP conversation produce the identical key:
the two endpoints are always emitted lexicographically-smaller-first.
Reads whichever of IPv4.protocol / IPv6.next_header the enclosing
header has (same semantic field, different attribute name).
A transport layer with no ports (ICMP) returns None from both the free
function and Packet.flow_key() -- no invented port-slot convention,
matching this codebase's established "None rather than raising"
pattern. Packet.flow_key() walks self.layers for the first IPv4/IPv6
and TCP/UDP layers and delegates to the free function, mirroring
with_checksums()'s enclosing-IP scan; None if either layer is missing.
Closes#89.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVCFe7B1U5VeJRoUbx24vb
@EONRaider
EONRaider merged commit ea62922 into masterSep 4, 2026
7 checks passed
This was referenced Sep 4, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Flow keys — hashable 5-tuples with a canonical bidirectional form

2 participants

@EONRaider@claude