Skip to content

feat(application): implement TLS/SSL and DTLS record layer + handshake framing - #2441

Merged
JarryShaw merged 11 commits into
mainfrom
feat/tls-dtls-2437
Oct 31, 2026
Merged

JarryShaw merged 11 commits into
mainfrom
feat/tls-dtls-2437

Conversation

@JarryShaw

@JarryShaw JarryShaw commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Summary

Implements TLS/SSL and DTLS (#2437), shaped like ARP:

  • TLS parses SSLv3 and TLS 1.0–1.3; DTLS parses DTLS 1.0/1.2/1.3 legacy-format records.
  • Each read() sets alias/name from the record's legacy version; id() stays fixed at ('TLS',)/('DTLS',).
  • TLS/DTLS 1.3 is identified by peeking supported_versions. An unknown record version gets the plain TLS/DTLS alias.
  • SSLv3(TLS) exists for explicit construction only, like InARP(ARP).
  • SSL is the umbrella over the SSL versions, TLS and DTLS. Its id() is ('TLS','DTLS'); the SSLv2 class, and SSLv2 delegation from TCP 443 with its own validation gate, join in feat(application): add an SSLv2 dissector #2466.
  • A non-TLS payload on 443 falls to Raw: 20,000/20,000 random segments do, while 21,504/21,504 valid TLS records still parse as TLS.
  • Bound at TCP 443 and UDP 5684, and exported from pcapkit.protocols.application, pcapkit.protocols, pcapkit.all and pcapkit. Constants live in pcapkit.const.tls.

Not covered: TCP segment reassembly; DTLS handshake reassembly (#2440); the DTLS 1.3 unified header (#2467); RFC 9146 connection-ID records, which fall back to Raw.

Test plan

  • test_ssl_tls_dtls_unit.py / test_tls_dtls_roundtrip_unit.py: every version, the branding rules, exports, and byte-exact round trips.
  • Docs build with pinned requirements: no new warnings. isort, mypy and pylint: no new findings.

Refs #2437

@github-actions github-actions Bot added this to the PyPCAPKit v1.5 milestone Oct 30, 2026
@JarryShaw JarryShaw moved this to In review in PyPCAPKit Oct 30, 2026
@JarryShaw JarryShaw added feat Pull requests that add a new capability (feat: subject prefix) review: running A cross-review is in flight against the current head - no verdict yet labels Oct 30, 2026
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Coverage: 94.50% (unit tier, Python 3.15, 27a7872fd, Unit Tests run success)

Package Statements Missed Branches Partial Cover
pcapkit (top level) 104 4 20 2 95.16%
pcapkit/const 19677 133 2628 120 98.85%
pcapkit/corekit 2605 74 818 37 96.52%
pcapkit/dumpkit 281 3 100 3 98.43%
pcapkit/foundation 3070 24 1072 26 98.79%
pcapkit/interface 138 4 52 3 96.32%
pcapkit/protocols 18031 194 4784 180 98.35%
pcapkit/toolkit 924 36 334 10 95.55%
pcapkit/utilities 502 7 144 5 98.14%
pcapkit/vendor 4449 1854 1020 167 55.11%

Per-file detail: the coverage-html artifact of this run.

@JarryShaw

Copy link
Copy Markdown
Owner Author

Review: NEEDS CHANGES at a8f37f1c0 (Opus, different model from the author).

Must fix:

  • DTLS ClientHello is missing the cookie field after session_id (RFC 6347 §4.2.1 / RFC 9147 §5.3's legacy_cookie). SSL._read_tls_client_hello reuses the TLS layout unchanged, so every real DTLS ClientHello fails to parse — confirmed by hand-decoding a real one against the RFC. The test fixture hides this because it's actually a TLS-shaped body with no cookie, not a real DTLS one.
  • No validation on content-type/version/length means any bytes on TCP 443 parse as a valid TLS record (short records get zero-padded to their declared length). Most TCP-443 segments are mid-record ciphertext continuations, so this is the common case, not an edge case — and it's already red in CI: tests/protocols/test_generated_pcap_runtime.py gets 'Ethernet:IPv6:TCP:TLS:Raw' != 'Ethernet:IPv6:TCP:Raw'. Out-of-range headers need to raise ProtocolError so the payload correctly falls back to Raw.
  • Six pre-existing tests regressed (confirmed by reverting to origin/main and reproducing): test_dispatch_bindings_unit/test_dispatch_registry_unit (port 443/5684 pinned lists), test_generated_pcap_runtime (above), test_package_reexports_1807_unit (TLS/DTLS/SSL not re-exported), test_rfc_constants_one_home_1739_unit (repeated literals at ssl.py:345,440 and dtls.py:40). Only application/transport/const suites were run before merge-readiness was claimed — the wider suite needs a pass too.
  • Doc/citation errors: pep.rst falsely claims NGAP-over-DTLS-over-SCTP (PPID 66) is now recognized — sctp.py:471 still maps it to NGAP directly; several stale "pcapkit implements no DTLS/TLS" comments remain (sctp.py:336,467, ngap.py:79, docs/.../ngap.rst:94, examples/generators/dispatch.py:61,916, test_dispatch_bindings_unit.py:285); the DTLS read docstring says DTLSCiphertext where the actual layout is DTLSPlaintext (RFC 9147 §4); the fragment-reassembly deferral cites :issue:2437 where it should cite DTLS handshake-fragment reassembly #2440.
  • HandshakeType registry incomplete: types 21-23 missing with no explanation; credits 9/10 to RFC 9146 where it should be 9147; no pcapkit/vendor/ generator exists for this const directory, unlike every other.

Should fix: TLS 1.2 encrypted Finished (content type 22) currently raises ProtocolError and drops the whole TCP layer to Raw rather than keeping it opaque; an odd cipher-suite-length ClientHello is accepted but doesn't round-trip; versionadded:: 1.5.0 is wrong since 1.5.0 already shipped without this (use the newly-opened 1.6.0); a second TLS record in the same buffer decodes as Raw; no Sphinx doc pages exist for the new modules so pep.rst's cross-references won't resolve; CHANGELOG.md/1.5.0.rst still say there's no DTLS dissector.

Confirmed correct, no change needed: the shared SSL base's content-type dispatch and handshake/extension framing are real shared logic (549 lines, not thin like IP/IPsec — defensible since the handshake bodies genuinely are shared between TLS/DTLS, not a design mistake on its own); TLS and DTLS-1.2's record-header schemas themselves (DTLS's epoch+seq fields are correctly placed); the fragmented-DTLS-message deferral behavior matches what #2440 describes (aside from fragment offsets/message_seq not resetting on epoch change); TCP 443 / UDP 5684 port wiring is live and reachable; mypy/isort clean, pylint no worse than existing baseline; const-count bumps are legitimate. Hand-decoded TLS fixtures (ClientHello, ServerHello, Alert, extension framing) and real OpenSSL-generated TLS 1.2/1.3 handshakes all round-trip byte-exact.

Confirmed the regression-catching claim independently too, though the real number is higher than claimed: breaking handshake dispatch fails 7 tests, not 3 — the two round-trip tests stay green even with zero decoding, since raw bytes trivially round-trip.

One item flagged but not resolved by the reviewer: the dumpkit plist/JSON escaping failures on this PR's CI are a separate, already-tracked regression (#2442, from the just-released dictdumper 0.8.5) — unrelated to this PR's own changes, already has a fix in flight.

@JarryShaw JarryShaw added review: needs-changes Cross-review at the current head says changes are required; see the verdict comment and removed review: running A cross-review is in flight against the current head - no verdict yet labels Oct 30, 2026
@JarryShaw JarryShaw added review: running A cross-review is in flight against the current head - no verdict yet and removed review: needs-changes Cross-review at the current head says changes are required; see the verdict comment labels Oct 30, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

All required fixes applied and verified directly (new head f5b9c252d): DTLS ClientHello.cookie added (RFC 6347 §4.2.1/RFC 9147 §5.3), a new _validate_tls_record rejects malformed records instead of fabricating a TLS decode, the 6 regressed tests fixed, and the pep.rst NGAP-over-DTLS claim corrected (PPID 66 still isn't rewired — this PR doesn't unblock it, only DTLS fragment handling). Also: HandshakeType gaps 17/21/22/23 added, cipher-suite odd-length now raises cleanly, 1.6.0 changelog bullet added. Deferred with reasoning (not silently dropped): a vendor/tls/ generator script, and two should-fix items needing real design (narrowing content-type-22 error handling, re-dispatching a second coalesced TLS record). Spot-verified: cookie tests (10 passed/30 subtests), test_generated_pcap_runtime (2 passed), test_const_registry_protocol (74 passed/344 subtests). Cross-review re-dispatched.

@JarryShaw JarryShaw added review: needs-changes Cross-review at the current head says changes are required; see the verdict comment and removed review: running A cross-review is in flight against the current head - no verdict yet labels Oct 30, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

NEEDS CHANGES (cross-review round 2, different model from the author).

Round 1's cookie fix and Raw fallback both hold up under real-capture testing (OpenSSL DTLS 1.2 handshake through a loopback proxy, 0/40000 random payloads false-positive). But round 2 introduces its own issues:

Required: (1) _validate_tls_record still accepts content type 0 (invalid) as TLS — IANA lists 0-19 as Unassigned, and the 'Unassigned' in name check doesn't catch the explicitly-named invalid member; reject 0 explicitly. (2) two of the three validation branches (wrong legacy version, length overrun) are completely untested — add tests that fail without each check. (3) the new HandshakeType additions have wrong citations: type 17's "QUIC's use of TLS [RFC 9261]" is wrong (9261 is Exported Authenticators, unrelated to QUIC; IANA's own comment is "used in TLS versions prior to 1.3"); types 21/22 "obsoleted by RFC 8446" is wrong (8446 updates RFC 6066, doesn't obsolete it); the RFC 9147 section anchor points at the wrong subsection (ClientHello is in §5.2, not §5.3 as cited for 9/10).

Should-fix: HandshakeType entries 9/10/25/26 and ContentType 27 are still missing from the registry despite the module claiming full IANA coverage; wrong section anchors for the DTLS record header/DTLSCiphertext (cited as §4.1, actually §4); the DTLS docstring's claim that 5684 is "the one fixed port a DTLS-borne protocol is registered at" is false (IANA lists several others: 853, 5349, 6514, 10161/10162, 4740); RFC 8446 should be RFC 9846 (obsoleted July 2026) throughout; the validator is measurably too strict on TCP-segment-split records (a real OpenSSL PQ ClientHello at 1546 bytes exceeds a 1460-byte MSS, so its first segment always falls back to Raw) — worth documenting if not fixing.

On the deferred content-type-22 item: the reviewer wrote and tested a ~10-line fix (DTLS: non-zero epoch means encrypted, keep as-is; TLS: keep unless the 4-byte handshake header tiles exactly) that passes all 22 existing tests plus real encrypted Finished/Alert records from a capture. Recommend landing this rather than deferring further, or opening a tracking issue with the corrected reasoning (the original deferral cited the wrong content-type number — 22 is Handshake, not Application Data).

Everything else (cookie correctness, pep.rst's NGAP paragraph, scope) verified clean. Full regression: 690/690 passed after generating sample captures (the 5 apparent failures were a fresh-worktree make_samples.py gap, not a regression).

@JarryShaw JarryShaw added review: running A cross-review is in flight against the current head - no verdict yet and removed review: needs-changes Cross-review at the current head says changes are required; see the verdict comment labels Oct 30, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

All required fixes applied and verified directly (new head 1032e286e): content type 0 (invalid) now explicitly rejected via an allowlist of IANA's assigned 20-27; the deferred content-type-22 fix landed (DTLS decodes cleartext only at epoch 0, TLS only when headers tile exactly) — real encrypted Finished records now round-trip instead of falling to Raw, and all 6 malformed-handshake tests still correctly raise; citation errors fixed (type 17's real IANA comment, 21/22/23's false "obsoleted" claim removed, correct §5.2 anchor); ssl.py/tls.py/dtls.py now at 100% statement+branch coverage. Also caught and fixed something the brief missed: RFC 9846 renumbers sections relative to 8446, so a blind citation swap would have produced wrong anchors — remapped each one and updated the quoted ClientHello/ServerHello struct text to 9846's actual wording. Spot-verified: 699 passed/27445 subtests, matching exactly. Cross-review re-dispatched (round 4).

@JarryShaw JarryShaw added review: needs-changes Cross-review at the current head says changes are required; see the verdict comment and removed review: running A cross-review is in flight against the current head - no verdict yet labels Oct 30, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

NEEDS CHANGES (cross-review round 4, different model from the author).

Round 3's fixes hold (content-type-0 allowlist verified across all 256 values; RFC 9846 renumbering independently re-derived and confirmed correct; HandshakeType registry complete against live IANA). Two real problems remain:

Required: (1) encrypted Alerts (type 21) still collapse to Raw, the same bug class round 3 just fixed for type 22 Handshake. Any TLS 1.2 close_notify sent after ChangeCipherSpec is encrypted and not exactly 2 octets, so _read_tls_content raises and the whole record — including its cleartext header — is dropped to Raw, which the PR's OWN docstring for the type-22 fix calls out as the wrong outcome. Measured: a 26-octet AES-GCM close_notify, a 48-octet CBC alert, and a DTLS epoch-1 alert all give Raw instead of round-tripping. Fix needs the same epoch/exact-length discipline already applied to type 22. (2) the "DTLS's one fixed port" claim is only HALF removed — pcapkit/protocols/transport/udp.py:111-114 still asserts it, contradicting dtls.py's already-corrected text citing IANA's real multi-port list (853, 4740, 5349, 6514, 10161/10162).

Should-fix: a DTLS record with a connection ID (type 25, RFC 9146) is misread — the CID sits before length in the real wire format, but the schema always reads length at a fixed offset, producing a fabricated fragment (exactly the invented-record problem the validator exists to prevent) — either drop 25 from the allowlist or implement CID-aware offset. A new Sphinx "duplicate object description" warning from .. module:: declared in both ssl.rst and tls.rst. Two coverage gaps that 100% line coverage hides: breaking the handshake-tiling walk to only check the first message, or dropping types 25/26 from the allowlist, both pass the full suite undetected — needs assertions on message count/types, not just byte-exact rebuild.

Several accuracy nits in docstrings (overclaiming "always tiles" when RFC 9846 allows split handshake messages; wrong claim about which message types travel under type 22 in TLS 1.3; a GCM-nonce edge case; a non-verbatim RFC 9147 struct quote; dead TYPE_CHECKING import; stale PR-body test counts).

CI note: the PR's own red Python 3.11-3.14/Integration-3.15 legs are the same inherited tests/dumpkit regression from main (fixed by #2445, not yet merged) — not a defect in this PR.

@JarryShaw JarryShaw added review: running A cross-review is in flight against the current head - no verdict yet and removed review: needs-changes Cross-review at the current head says changes are required; see the verdict comment labels Oct 30, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

Both required fixes applied and verified directly (new head 1b2290f02): _tls_content_is_cleartext_handshake generalized to _tls_content_is_cleartext covering Alert and ChangeCipherSpec too, same discipline as the type-22 fix (DTLS by epoch, TLS by exact cleartext length) — encrypted close_notify/CBC-alert/DTLS-epoch-1-alert now round-trip instead of falling to Raw. UDP's stale "one fixed port" claim corrected to match dtls.py. DTLS CID (type 25) dropped from the allowlist rather than fabricating a fragment at the wrong offset, with a test guarding the exclusion. Coverage gaps closed (coalesced-handshake message-count assertion, type-25/26 allowlist tests). Spot-verified: 708 passed/27445 subtests, matching exactly. Given this PR's review history, dispatching one more round focused specifically on the generalized Alert/CCS logic before calling it done.

@JarryShaw JarryShaw added the review: needs-changes Cross-review at the current head says changes are required; see the verdict comment label Oct 30, 2026
…ecords TLS

- TLS.read imports pcapkit.protocols.application.sslv2.SSLv2 (#2466's name);
  docstrings follow.
- _looks_like_sslv2 reads any first octet below 0x40 followed by 0x03 as TLS,
  so heartbeat/ack/return_routability_check and unaccepted types stay with
  the TLS validator instead of misparsing as SSLv2.
- Tests stub the module under the sslv2 name and cover an SSLv2 CLIENT-HELLO
  on TCP 443 and every first octet 0-255.
- SSL/DTLS docs state where the DTLS epoch and sequence number live; dtls.rst
  lists the unified-header and RFC 9146 limits; RFC 9846 also obsoletes 5246.
- Timed prose removed from pep.rst, ssl.py, dtls.py, sctp.py, ngap.py.

TLS/DTLS, application, dispatch, export and docs tests pass.
@JarryShaw

Copy link
Copy Markdown
Owner Author

Pushed cd5493f9b (held until the 1.5.1 release cleared): addresses the previous review. Sonnet review dispatched.

@JarryShaw JarryShaw added review: running A cross-review is in flight against the current head - no verdict yet and removed review: needs-changes Cross-review at the current head says changes are required; see the verdict comment labels Oct 30, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

NEEDS CHANGES (Sonnet, cd5493f9b). TLS records and real SSLv2 hellos on 443 route correctly, and <0x40 and 0x03 is exact for conforming traffic: padded SSLv2 lengths are block multiples, never …03.

  • Blocker: with feat(application): add an SSLv2 dissector #2466's real SSLv2 overlaid, anything not TLS-shaped is delegated without validation. 19,974 of 20,000 random mid-stream 443 segments become TCP:SSLv2:Raw.
  • An adopted SSLv2 is mis-shaped: TLS.length stays 5, frame['SSLv2'] raises, and info is Data_SSLv2.
  • Plan: drop SSLv2 delegation from this PR, since no SSLv2 class exists on main. feat(application): add an SSLv2 dissector #2466 adds it, with a validation gate and correct shape, when it subclasses SSL.
  • Prose: schema tls.py still says "byte-identical"; timed comments in test_dispatch_registry_unit.py and tls.py; the RFC 9846 obsoletes list is partial (5077, 5246, 6961, 7627, 8422, 8446).

@JarryShaw JarryShaw added review: needs-changes Cross-review at the current head says changes are required; see the verdict comment and removed review: running A cross-review is in flight against the current head - no verdict yet labels Oct 30, 2026
- TLS.read no longer delegates to SSLv2: remove the guarded import,
  _looks_like_sslv2, SSL._adopt_dispatched and their tests. A payload
  that is not a TLS record falls to Raw; SSLv2 delegation lands with
  its own class.
- Pin it: every first octet parses only as an accepted content type,
  and an SSLv2 hello and random segments on 443 are TCP:Raw.
- Give RFC 9846's full obsoletes list; one-line note in SSL.id and
  ssl.rst that the SSLv2 class joins it in its own PR.
- Trim the ssl/tls/dtls docstrings and their rst pages to the
  rationale; fix the schema's "byte-identical" and drop a timed
  comment in the dispatch registry test.
@JarryShaw

Copy link
Copy Markdown
Owner Author

Verified (780109ee3): SSLv2 delegation removed from code, tests and docs; non-TLS 443 payloads fall to Raw; RFC 9846 obsoletes list complete. Body updated. Sonnet review dispatched.

@JarryShaw JarryShaw added review: running A cross-review is in flight against the current head - no verdict yet and removed review: needs-changes Cross-review at the current head says changes are required; see the verdict comment labels Oct 30, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

Heads-up for merge order: this PR and #2466 both bump the tests/const census pins by +2. Git merges the pair cleanly to a stale value (5 tests off by 2), so whichever lands second must re-bump them.

@JarryShaw

Copy link
Copy Markdown
Owner Author

NEEDS CHANGES (Sonnet, 780109ee3), one prose line. The removal is clean (AST: removal only). 20,000 random 443 segments fall to Raw, real OpenSSL TLS 1.0–1.3 handshakes parse byte-exact, and docs warnings equal main's.

  • Delete "The SSLv2 class joins it in its own PR." from SSL.id()'s docstring and from ssl.rst. It's timed context and names an unshipped class.
  • Nit: E302 before _udp_to_5684 in the TLS test file.

@JarryShaw JarryShaw added review: needs-changes Cross-review at the current head says changes are required; see the verdict comment and removed review: running A cross-review is in flight against the current head - no verdict yet labels Oct 30, 2026
Remove "The SSLv2 class joins it in its own PR." from SSL.id() and
ssl.rst, and restore the E302 blank line before _udp_to_5684.
@JarryShaw

Copy link
Copy Markdown
Owner Author

GOOD TO GO (27a7872fd). The last review's only required fix is in: the diff is exactly the removed SSLv2 sentence (both places) plus the E302 blank line.

@JarryShaw JarryShaw added review: good-to-go Cross-review at the current head says ready; CI state is separate and removed review: needs-changes Cross-review at the current head says changes are required; see the verdict comment labels Oct 30, 2026
JarryShaw added a commit that referenced this pull request Oct 31, 2026
- Add `pcapkit.foundation.reassembly.dtls.DTLS`, a new `Reassembly`-shaped
  engine keyed on the UDP 5-tuple plus the DTLS epoch -- the record sequence
  number resets on an epoch change, so a new epoch opens a fresh buffer
  rather than folding into the previous one's. It ports the RFC 815
  hole-descriptor algorithm `TCP._update_hole_descriptors` already implements,
  since every fragment declares the message's total length up front
  (RFC 6347 s4.2.3). A disagreeing fragment (different `length`/`msg_type`)
  under a reused `message_seq` restarts the message, reporting the displaced
  partial as its own PARTIAL result rather than dropping it. The default
  timeout (120s) is an idle deadline from each buffer's most recently
  received fragment, not pinned to its first -- one buffer holds several
  independent messages, and RFC 6347 s4.2.4.1's 60s is only a floor a
  retransmission backoff's cap must not go below, not a safe ceiling.
- Wire it into `DTLS.read` via new, opt-in `reassembly`/`flow` arguments: a
  fragment that completes its message is decoded into a `ClientHello`/
  `ServerHello` in place, rather than staying raw. `flow` is required
  unconditionally once `reassembly` is given, not only for a cleartext
  handshake record; an encrypted (epoch >= 1) `handshake` record is skipped;
  and a reassembled message that fails to decode, or whose own fragment
  header runs past the message's declared length, keeps its raw bytes
  instead of raising or corrupting the round trip.
- Fix the construction path so a reassembled message still round-trips
  byte-exact: the completing record's fragment header keeps describing that
  record's own byte range, sliced back out of the full decoded body, rather
  than writing the full body under a mismatched `fragment_length`.
- `DTLSHandshake.is_fragmented` keeps reporting the wire fact that a message
  was split, even once reassembled -- only `body` changes.
- Add the `docs/source/pcapkit/foundation/reassembly/dtls.rst` page and its
  `reasm.dtls.*` glossary terms, list `DTLS_Reassembly` in
  `pcapkit.foundation.__all__`/`pcapkit.all.__all__`, fix a record's
  "at most one fragment" claim (s4.2.3 allows several), and fix three stale
  RFC citations (s4.2.2 -> s4.2.3 twice; an s4.1 citation overstating that
  fragment offsets, not just the sequence number, reset on an epoch change).

Built against `feat/tls-dtls-2437` (#2441), since `DTLS` does not exist on
`main` yet. `isort`, `pylint` (10.00/10 on touched files) and `mypy` (zero
new errors vs. base) are clean; `tests/project/` (2102 passed) and
`tests/foundation/reassembly/`+`tests/protocols/application/` (499 passed,
with `examples/generators/make_samples.py` run first) pass in full.
JarryShaw added a commit that referenced this pull request Oct 31, 2026
- SSLv2 (draft-hickman-netscape-ssl-00) subclasses SSL and joins
  SSL.id(). Its module and class are named SSLv2.
- TLS.read tries TLS first. SSLv2 is taken only when TLS rejects the
  record and the payload is a complete CLIENT-HELLO or SERVER-HELLO that
  passes the hello length probe; anything else stays Raw.
- Add pcapkit.const.sslv2 (HandshakeType, Version) and bump the const
  census pins by +2 over #2441's.

Part of #2457.
@JarryShaw
JarryShaw merged commit b6efe66 into main Oct 31, 2026
63 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in PyPCAPKit Oct 31, 2026
@JarryShaw
JarryShaw deleted the feat/tls-dtls-2437 branch October 31, 2026 02:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat Pull requests that add a new capability (feat: subject prefix)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant