@@ -13,6 +13,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313 can install one sink allocation in both the gNMI and NETCONF server cores
1414 without a product-local forwarding newtype; calls and errors are forwarded
1515 unchanged.
16+ - ** Correlated IPCP Configure-Nak receive in PCO/APCO — ` opc-proto-gtpv2c ` :**
17+ ` IpcpNakCorrelation ` , ` PcoDecoded ` , ` PcoIpcpDiscard ` , ` PcoIpcpDiscardReason `
18+ and ` PcoAddressConfiguration::decode_network_contents_correlated ` . RFC 1661
19+ §5.3 states verbatim: "On reception of a Configure-Nak, the Identifier field
20+ MUST match that of the last transmitted Configure-Request. Invalid packets are
21+ silently discarded." The previous decoder took no expected Identifier and so
22+ structurally could not correlate, which let the first non-zero address in any
23+ Configure-Nak become the session's DNS answer. The new entry point takes the
24+ caller's outstanding-request position and discards a Nak that does not match
25+ it. ` IpcpNakCorrelation::none ` is the ` Default ` and discards every
26+ Configure-Nak, so the fail-closed position is the one a caller reaches by
27+ accident; ` expecting(identifier) ` is the RFC-permissive constructor;
28+ ` for_request(sent) ` correlates against a request this SDK encoded.
29+ Every unit dropped on correlation, every unit dropped as malformed, and every
30+ DNS option skipped as unsolicited is reported through
31+ ` PcoDecoded::ipcp_discards ` with a reason code and a unit position and never
32+ an address or an Identifier value, extending the existing
33+ ` PcoAddressConfiguration ` redaction contract. Four drops stay deliberately
34+ silent and record no entry, as before: a well-formed code other than
35+ Configure-Nak, an unknown option type, an echoed RFC 1877 all-zero address,
36+ and a repeated option whose slot is already filled.
37+ ` for_request ` additionally declines a DNS option this side never solicited.
38+ ** That filter is engineering judgement, not a specification requirement:** RFC
39+ 1661 §5.3 permits a Configure-Nak to append Configuration Options the peer
40+ desires that were not in the Configure-Request, so the unit is kept and only
41+ the option is skipped, reported as ` UnsolicitedOption ` . Use ` expecting ` for
42+ the permissive reading. It is not an on-path control either — the Identifier
43+ is visible on the wire, and ` IpcpDnsRequest::identifier ` is documented as
44+ opaque with nothing obliging a caller to vary it.
1645- ** First-owner activation for destination-scoped steering — ` opc-ipsec-lb ` :**
1746 in ` HostXdpFenceDomain::PerOwnershipKey ` the only public owner-map writer was
1847 the fenced re-pin coordinator, and a re-pin cannot be formed for a fresh SA --
@@ -122,6 +151,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
122151 explicitly replace a v1 trail before enabling v2 writes; the SDK does not
123152 silently reseal it. A v2 anchor whose version tag alone is downgraded fails
124153 authentication and is not classified as benign legacy data.
154+ - ** A malformed IPCP unit is discarded unit-locally instead of failing the whole
155+ PCO/APCO value — ` opc-proto-gtpv2c ` (breaking, behavioural; no signature
156+ changes):** ` decode_network_contents ` propagated an IPCP fault out of the
157+ whole value, destroying P-CSCF and DNS containers that had already parsed.
158+ RFC 1661's discard unit is the * packet* , and TS 24.008 10.5.6.3 maps one
159+ ` 0x8021 ` unit to one RFC 1661 packet stripped of Protocol and Padding; the
160+ unit's outer container boundary is validated before its contents are read, so
161+ the sibling boundaries are recoverable and the siblings are now kept. Within
162+ one unit the disposition is atomic: an option that parsed before a later
163+ malformed one in the same packet is dropped with it, which the unit decoder
164+ now expresses in its type by returning no ` Result ` at all and merging only on
165+ whole-unit success. ` PcoDecodeError::IpcpHeaderTruncated ` , ` IpcpLengthInvalid ` ,
166+ ` IpcpOptionTruncated ` , ` IpcpOptionLengthInvalid ` and
167+ ` IpcpDnsOptionLengthInvalid ` stop being returned from the decode entry points;
168+ they remain public, constructible and reachable through
169+ ` PcoIpcpDiscardReason::Malformed ` , and no variant was removed or reordered.
170+ ** This is a reject-to-accept flip: a caller matching on those five variants
171+ will stop seeing them.**
172+ - ** ` decode_network_contents ` no longer surfaces IPCP-supplied DNS —
173+ ` opc-proto-gtpv2c ` (breaking, behavioural; signature unchanged):** holding no
174+ Identifier it cannot satisfy RFC 1661 §5.3, so the fail-closed answer is to
175+ supply nothing. ` ipcp_primary_dns ` /` ipcp_secondary_dns ` stay ` None ` ; for a
176+ value whose only DNS source was the IPCP reply ` is_empty() ` reports empty and
177+ the caller's configured-DNS fallback fires as that predicate already
178+ documents; and ` dns_server_ipv4_all() ` equals
179+ ` dns_server_ipv4 ` under this path. Migration:
180+ ` decode_network_contents_correlated(value, IpcpNakCorrelation::for_request(sent.ipcp_dns))?.into_configuration() ` .
181+ The function is deliberately ** not** ` #[deprecated] ` : it stays the correct call
182+ for a value that carries only containers.
183+ These two changes have opposite signs and are declared as such — the decoder
184+ is now ** more** permissive about a malformed sibling unit and ** less**
185+ permissive about an uncorrelated reply. Both move toward RFC 1661 fidelity;
186+ neither is simply hardening.
187+ Unchanged, stated explicitly: ` PcoAddressConfiguration ` 's fields, derives and
188+ redacting ` Debug ` ; every ` PcoDecodeError ` variant and its ` as_str ` ;
189+ container-framing fatality; whole-value rejection for a wrong-length address
190+ container; the IPv4 Link MTU local skip; and the entire encode path,
191+ ` PcoRequest ` , ` IpcpDnsRequest ` , ` PcscfRequest ` and ` PcscfAddressRequest ` .
125192- ** ` RePinAuditEvent ` carries a correlation digest, not the live transition
126193 secret — ` opc-ipsec-lb ` (breaking: ` transition_id: OwnershipTransitionId ` is
127194 replaced by ` correlation_id: RePinAuditCorrelationId ` ):** the coordinator
@@ -319,6 +386,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
319386 fixture, so no message that already round-tripped moves on the wire.
320387
321388### Fixed
389+ - ** Documentation corrections in the PCO codec — ` opc-proto-gtpv2c ` :** the RFC
390+ 1661 citation on the Configure-Ack option-echo rule said §5.3, which is
391+ Configure-Nak; §5.2 is Configure-Ack. ` dns_server_ipv4_all ` and the crate
392+ README claimed the accessor "drops duplicates" generally, but the container
393+ list is cloned verbatim and only the two IPCP-sourced addresses are checked
394+ against it; the wording now says so, and the behaviour is deliberately
395+ unchanged because a repeated address container is something the peer actually
396+ sent and collapsing it would destroy that evidence. The
397+ ` decode_network_contents ` contract said "parsing is all-or-nothing" beside a
398+ comment that does cite a clause, which read as spec-compelled; whole-value
399+ rejection for a wrong-length ** address** container is relabelled as this
400+ codec's configuration-atomicity policy, which TS 24.008 does not require. The
401+ comment claiming the IPCP Identifier "carries nothing this decoder interprets"
402+ is deleted.
322403- ** P-CSCF Re-selection support is no longer emittable on its own --
323404 ` opc-proto-gtpv2c ` (breaking to ` PcoRequest ` ):** TS 24.008 10.5.6.3 says of
324405 container ` 0x0012 ` that "This PCO parameter may be present only if a
@@ -610,16 +691,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
610691 - * Receive.* Emitting a request whose answer is discarded would deliver
611692 nothing, so ` PcoAddressConfiguration ` now decodes the reply into
612693 ` ipcp_primary_dns ` and ` ipcp_secondary_dns ` . Only a Configure-Nak is read
613- for addresses: RFC 1661 5.3 has a Configure-Ack echo the request's options
694+ for addresses: RFC 1661 §5.2 has a Configure-Ack echo the request's options
614695 verbatim, so it conveys no server, and an echoed all-zero address is not
615- treated as one. ` dns_server_ipv4_all() ` merges the container and IPCP
616- sources and drops duplicates, so a caller cannot silently miss the
617- mechanism its peer chose.
696+ treated as one. ` dns_server_ipv4_all() ` reports both the container and IPCP
697+ sources, so a caller cannot silently miss the mechanism its peer chose. Both
698+ the correlation requirement and the disposition of a malformed unit are
699+ superseded within this same unreleased section; see the correlated-receive
700+ entry under Added and the unit-local-discard entry under Changed.
618701 - Both structs gain fields, which breaks exhaustive struct literals;
619702 ` ..PcoRequest::none() ` and ` ..Default::default() ` are unaffected. Five new
620- ` PcoDecodeError ` variants report malformed IPCP framing, and a malformed
621- unit for this now-supported identifier rejects the whole value, matching
622- how a known container with a bad length is already handled. ` Debug ` reports
703+ ` PcoDecodeError ` variants report malformed IPCP framing. ` Debug ` reports
623704 presence, never addresses.
624705
625706- ** Credential-rotation observability closes three residual gaps —
0 commit comments