Skip to main content

zakura_network/zakura/
handshake.rs

1//! Zakura P2P v2 handshake and bounded wire encodings.
2
3use std::{
4    cmp::min,
5    collections::HashMap,
6    io::{self, Cursor, Read, Write},
7    time::{Duration, Instant},
8};
9
10use blake2b_simd::Params as Blake2bParams;
11use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
12use thiserror::Error;
13
14use zakura_chain::{parameters::Network, serialization::ZcashSerialize};
15
16use crate::{protocol::external::types::Nonce, VersionMessage};
17
18/// The Zcash command used for Zakura upgrade prelude messages.
19pub const P2P_V2_UPGRADE_COMMAND: &str = "p2pv2up";
20
21/// The padded Zcash command used for Zakura upgrade prelude messages.
22pub const P2P_V2_UPGRADE_COMMAND_BYTES: &[u8; 12] = b"p2pv2up\0\0\0\0\0";
23
24/// The ALPN used by the first Zakura P2P v2 protocol version.
25pub const P2P_V2_ALPN: &[u8] = b"p2p-v2/1";
26
27/// Magic bytes for Zakura legacy upgrade prelude messages.
28pub const PRELUDE_MAGIC: [u8; 8] = *b"ZAKURA1\0";
29
30/// Magic bytes for Zakura control hello messages.
31pub const CONTROL_HELLO_MAGIC: [u8; 8] = *b"ZAKCTRL\0";
32
33/// Magic bytes for Zakura control acknowledgement messages.
34pub const CONTROL_ACK_MAGIC: [u8; 8] = *b"ZAKACK\0\0";
35
36/// Magic bytes for per-stream Zakura preludes.
37pub const STREAM_PRELUDE_MAGIC: [u8; 4] = *b"ZKST";
38
39/// The only prelude encoding version supported by this implementation.
40pub const PRELUDE_VERSION: u16 = 1;
41
42/// The only control handshake encoding version supported by this implementation.
43pub const CONTROL_VERSION: u16 = 1;
44
45/// The first Zakura wire protocol version.
46pub const ZAKURA_PROTOCOL_VERSION_1: u16 = 1;
47
48/// Hard cap for any legacy TCP upgrade prelude payload.
49pub const MAX_PRELUDE_PAYLOAD_BYTES: usize = 4 * 1024;
50
51/// Hard cap for any control handshake payload.
52pub const MAX_CONTROL_PAYLOAD_BYTES: usize = 16 * 1024;
53
54/// Maximum encoded Iroh node id length accepted in Zakura handshakes.
55pub const MAX_IROH_NODE_ID_BYTES: usize = 128;
56
57/// Maximum number of direct Iroh address hints in the legacy prelude.
58pub const MAX_IROH_DIRECT_ADDRESSES: usize = 8;
59
60/// Maximum length of one encoded direct Iroh address hint.
61pub const MAX_IROH_DIRECT_ADDRESS_BYTES: usize = 512;
62
63/// Maximum length of one encoded relay hint.
64pub const MAX_IROH_RELAY_HINT_BYTES: usize = 512;
65
66/// Maximum locally accepted control frame size.
67pub const LOCAL_MAX_CONTROL_FRAME_BYTES: u32 = 1024 * 1024;
68
69/// Maximum locally accepted application message size advertised in control hello.
70pub const LOCAL_MAX_MESSAGE_BYTES: u32 = 4 * 1024 * 1024;
71
72/// Maximum locally accepted open streams advertised in control hello.
73pub const LOCAL_MAX_OPEN_STREAMS: u16 = 1024;
74
75/// Maximum locally accepted inbound queue depth advertised in control hello.
76pub const LOCAL_MAX_INBOUND_QUEUE_DEPTH: u16 = 4096;
77
78/// Maximum locally accepted idle timeout advertised in control hello.
79pub const LOCAL_MAX_IDLE_TIMEOUT_MILLIS: u32 = 10 * 60 * 1000;
80
81/// The fixed output size for Zakura transcript bindings.
82pub const TRANSCRIPT_HASH_BYTES: usize = 32;
83
84const INIT_DISCRIMINATOR: u8 = 1;
85const ACCEPT_DISCRIMINATOR: u8 = 2;
86const REJECT_DISCRIMINATOR: u8 = 3;
87pub(crate) const FRAME_HEADER_BYTES: usize = 2 + 2 + 4;
88
89/// A bounded authenticated Zakura peer identity.
90#[derive(Clone, Debug, Eq, Hash, PartialEq)]
91#[cfg_attr(
92    any(test, feature = "proptest-impl"),
93    derive(proptest_derive::Arbitrary)
94)]
95pub struct ZakuraPeerId(Vec<u8>);
96
97impl ZakuraPeerId {
98    /// Creates a peer id after applying the Zakura node-id bound.
99    pub fn new(bytes: impl Into<Vec<u8>>) -> Result<Self, ZakuraProtocolError> {
100        let bytes = bytes.into();
101        validate_bounded_bytes(&bytes, MAX_IROH_NODE_ID_BYTES, "iroh node id")?;
102        Ok(Self(bytes))
103    }
104
105    /// Returns the encoded peer id bytes.
106    pub fn as_bytes(&self) -> &[u8] {
107        &self.0
108    }
109
110    /// Returns a stable fixed-width identity for scheduler and state ownership.
111    pub fn digest(&self) -> [u8; 32] {
112        if let Ok(digest) = <[u8; 32]>::try_from(self.0.as_slice()) {
113            return digest;
114        }
115        let hash = Blake2bParams::new()
116            .hash_length(32)
117            .personal(b"ZakuraPeerId-v1")
118            .hash(&self.0);
119        hash.as_bytes()
120            .try_into()
121            .expect("the peer identity digest has the configured 32-byte length")
122    }
123}
124
125/// A small stable network id for fast Zakura rejects.
126#[derive(Copy, Clone, Debug, Eq, PartialEq)]
127#[repr(u32)]
128pub enum ZakuraNetworkId {
129    /// Zcash Mainnet.
130    Mainnet = 1,
131    /// The default public Zcash Testnet.
132    Testnet = 2,
133    /// A local Regtest network.
134    Regtest = 3,
135    /// A configured test network.
136    Configured = 4,
137}
138
139impl ZakuraNetworkId {
140    /// Returns the Zakura network id for `network`.
141    pub fn from_network(network: &Network) -> Self {
142        match network {
143            Network::Mainnet => Self::Mainnet,
144            Network::Testnet(params) if params.is_regtest() => Self::Regtest,
145            Network::Testnet(params) if params.is_default_testnet() => Self::Testnet,
146            Network::Testnet(_) => Self::Configured,
147        }
148    }
149
150    fn from_u32(value: u32) -> Result<Self, ZakuraProtocolError> {
151        match value {
152            1 => Ok(Self::Mainnet),
153            2 => Ok(Self::Testnet),
154            3 => Ok(Self::Regtest),
155            4 => Ok(Self::Configured),
156            _ => Err(ZakuraProtocolError::InvalidNetworkId(value)),
157        }
158    }
159
160    /// Returns this network id's pinned wire value.
161    pub fn code(self) -> u32 {
162        // Safe: `ZakuraNetworkId` has `#[repr(u32)]`, so the cast uses the pinned wire value.
163        self as u32
164    }
165}
166
167/// Static local Zakura handshake policy derived from configuration.
168#[derive(Copy, Clone, Debug, Eq, PartialEq)]
169pub struct ZakuraHandshakeConfig {
170    /// Supported prelude version.
171    pub prelude_version: u16,
172    /// Lowest supported Zakura wire protocol.
173    pub zakura_protocol_min: u16,
174    /// Highest supported Zakura wire protocol.
175    pub zakura_protocol_max: u16,
176    /// Local network id.
177    pub network_id: ZakuraNetworkId,
178    /// Zakura peer-matching chain id.
179    ///
180    /// Normally the local genesis block hash, so peers on the same chain match
181    /// and peers on a different chain reject (`WrongChain`). For a private dev
182    /// cohort it is instead derived from the genesis hash and the cohort tag
183    /// (see [`derive_dev_chain_id`]), which isolates the Zakura overlay without
184    /// touching real block-validation consensus.
185    pub chain_id: [u8; 32],
186    /// Required peer capabilities.
187    pub required_capabilities: u64,
188    /// Locally supported capabilities.
189    pub supported_capabilities: u64,
190    /// Locally supported channel bits.
191    pub supported_channels: u64,
192    /// Largest control frame accepted locally.
193    pub max_control_frame_bytes: u32,
194    /// Largest application message accepted locally.
195    pub max_message_bytes: u32,
196    /// Largest open-stream count accepted locally.
197    pub max_open_streams: u16,
198    /// Largest inbound queue depth accepted locally.
199    pub max_inbound_queue_depth: u16,
200    /// Largest idle timeout accepted locally.
201    pub max_idle_timeout_millis: u32,
202}
203
204impl ZakuraHandshakeConfig {
205    /// Returns the conservative default-off Zakura v1 local policy for `network`.
206    pub fn for_network(network: &Network) -> Self {
207        Self {
208            prelude_version: PRELUDE_VERSION,
209            zakura_protocol_min: ZAKURA_PROTOCOL_VERSION_1,
210            zakura_protocol_max: ZAKURA_PROTOCOL_VERSION_1,
211            network_id: ZakuraNetworkId::from_network(network),
212            chain_id: network.genesis_hash().0,
213            required_capabilities: 0,
214            supported_capabilities: 0,
215            supported_channels: 0,
216            max_control_frame_bytes: LOCAL_MAX_CONTROL_FRAME_BYTES,
217            max_message_bytes: LOCAL_MAX_MESSAGE_BYTES,
218            max_open_streams: LOCAL_MAX_OPEN_STREAMS,
219            max_inbound_queue_depth: LOCAL_MAX_INBOUND_QUEUE_DEPTH,
220            max_idle_timeout_millis: LOCAL_MAX_IDLE_TIMEOUT_MILLIS,
221        }
222    }
223
224    /// Returns the local Zakura policy for `network`, optionally scoped to a
225    /// private dev-network cohort.
226    ///
227    /// With `dev_network = None` (or an empty tag) this is identical to
228    /// [`for_network`](Self::for_network). With a non-empty tag the node joins a
229    /// private overlay: its [`network_id`](Self::network_id) becomes
230    /// [`ZakuraNetworkId::Configured`] and its [`chain_id`](Self::chain_id) is
231    /// derived from the real genesis hash and the tag, so only same-tag peers
232    /// match. Consensus is unchanged; this only scopes the Zakura v2 overlay.
233    pub fn for_network_with_dev_cohort(network: &Network, dev_network: Option<&str>) -> Self {
234        let mut config = Self::for_network(network);
235
236        if let Some(tag) = dev_network.filter(|tag| !tag.is_empty()) {
237            config.network_id = ZakuraNetworkId::Configured;
238            config.chain_id = derive_dev_chain_id(network.genesis_hash().0, tag);
239        }
240
241        config
242    }
243
244    /// Returns the network label used by low-cardinality metrics.
245    pub fn network_label(&self) -> &'static str {
246        match self.network_id {
247            ZakuraNetworkId::Mainnet => "mainnet",
248            ZakuraNetworkId::Testnet => "testnet",
249            ZakuraNetworkId::Regtest => "regtest",
250            ZakuraNetworkId::Configured => "configured",
251        }
252    }
253}
254
255/// Derives the Zakura peer-matching chain id for a private dev cohort.
256///
257/// The result binds the real `genesis_hash` and the cohort `tag` under a fixed
258/// personalization, so:
259/// - two nodes with the same tag on the same network produce the same id and match;
260/// - different tags, or a public node using the bare genesis hash, never match
261///   (`WrongChain`).
262///
263/// Domain separation via the personalization guarantees a derived cohort id can
264/// never collide with any real chain's genesis hash, so a dev node can never be
265/// mistaken for a public peer. This id only gates Zakura peer matching; block
266/// validation continues to use the unchanged network parameters.
267pub fn derive_dev_chain_id(genesis_hash: [u8; 32], tag: &str) -> [u8; 32] {
268    let hash = Blake2bParams::new()
269        .hash_length(32)
270        .personal(b"zebra-zk-cohort1")
271        .to_state()
272        .update(&genesis_hash)
273        .update(tag.as_bytes())
274        .finalize();
275
276    let mut out = [0; 32];
277    out.copy_from_slice(hash.as_bytes());
278    out
279}
280
281/// The legacy Zebra nonces as observed locally.
282#[derive(Copy, Clone, Debug, Eq, PartialEq)]
283pub struct ZakuraLegacyNonces {
284    /// The nonce sent by this node in its legacy `version` message.
285    pub local_zebra_nonce: Nonce,
286    /// The nonce received from the peer in its legacy `version` message.
287    pub remote_zebra_nonce: Nonce,
288}
289
290/// Why a well-formed Zakura upgrade was rejected.
291#[derive(Copy, Clone, Debug, Eq, PartialEq)]
292#[repr(u16)]
293pub enum ZakuraRejectReason {
294    /// Unsupported prelude version.
295    UnsupportedPreludeVersion = 1,
296    /// No compatible Zakura protocol version exists.
297    IncompatibleZakuraProtocol = 2,
298    /// The peer is on a different network.
299    WrongNetwork = 3,
300    /// The peer is on a different chain.
301    WrongChain = 4,
302    /// A required capability was missing.
303    MissingRequiredCapability = 5,
304    /// A peer-advertised resource limit was unusable or too large.
305    ResourceLimit = 6,
306    /// The authenticated peer is already connected.
307    AlreadyConnected = 7,
308    /// The local node cannot accept this upgrade now.
309    TemporaryUnavailable = 8,
310}
311
312impl ZakuraRejectReason {
313    fn from_u16(value: u16) -> Result<Self, ZakuraProtocolError> {
314        match value {
315            1 => Ok(Self::UnsupportedPreludeVersion),
316            2 => Ok(Self::IncompatibleZakuraProtocol),
317            3 => Ok(Self::WrongNetwork),
318            4 => Ok(Self::WrongChain),
319            5 => Ok(Self::MissingRequiredCapability),
320            6 => Ok(Self::ResourceLimit),
321            7 => Ok(Self::AlreadyConnected),
322            8 => Ok(Self::TemporaryUnavailable),
323            _ => Err(ZakuraProtocolError::InvalidRejectReason(value)),
324        }
325    }
326
327    fn code(self) -> u16 {
328        // Safe: `ZakuraRejectReason` has `#[repr(u16)]`, so the cast uses the pinned wire value.
329        self as u16
330    }
331}
332
333/// Whether a Zakura validation failure is neutral or may indicate abuse.
334#[derive(Copy, Clone, Debug, Eq, PartialEq)]
335pub enum ZakuraFailureClass {
336    /// A routine mismatch or resource condition that should close neutrally.
337    Neutral,
338    /// A malformed or forged handshake condition that may be punitive if repeated.
339    PotentiallyPunitive,
340}
341
342/// Internal validation failures after the legacy wire reject boundary.
343///
344/// Unlike [`ZakuraRejectReason`], these variants are not serialized on the wire.
345/// They preserve the failure-policy distinction needed by the control handshake.
346#[derive(Copy, Clone, Debug, Eq, Error, PartialEq)]
347pub enum ZakuraValidationError {
348    /// Unsupported control or prelude version.
349    #[error("unsupported Zakura {0} version")]
350    UnsupportedVersion(&'static str),
351
352    /// No compatible Zakura protocol version exists.
353    #[error("incompatible Zakura protocol")]
354    IncompatibleZakuraProtocol,
355
356    /// The peer is on a different network.
357    #[error("wrong Zakura network")]
358    WrongNetwork,
359
360    /// The peer is on a different chain.
361    #[error("wrong Zakura chain")]
362    WrongChain,
363
364    /// A required capability or channel is missing.
365    #[error("missing required Zakura capability or channel")]
366    MissingRequiredCapability,
367
368    /// A peer-advertised resource limit was unusable or too large.
369    #[error("invalid Zakura resource limit")]
370    ResourceLimit,
371
372    /// Authenticated Iroh identity does not match the handshake.
373    #[error("authenticated Zakura identity mismatch")]
374    IdentityMismatch,
375
376    /// The legacy upgrade transcript does not match.
377    #[error("Zakura legacy upgrade transcript mismatch")]
378    TranscriptMismatch,
379
380    /// One or both upgrade nonces do not match.
381    #[error("Zakura upgrade nonce mismatch")]
382    UpgradeNonceMismatch,
383
384    /// The control handshake magic or path is malformed.
385    #[error("malformed Zakura control handshake")]
386    MalformedControl,
387
388    /// The control acknowledgement did not bind to the exact nonce exchange.
389    #[error("Zakura control nonce mismatch")]
390    ControlNonceMismatch,
391}
392
393impl ZakuraValidationError {
394    /// Returns the failure-policy class for this validation error.
395    pub fn failure_class(self) -> ZakuraFailureClass {
396        match self {
397            Self::MalformedControl
398            | Self::IdentityMismatch
399            | Self::TranscriptMismatch
400            | Self::UpgradeNonceMismatch
401            | Self::ControlNonceMismatch => ZakuraFailureClass::PotentiallyPunitive,
402
403            Self::UnsupportedVersion(_)
404            | Self::IncompatibleZakuraProtocol
405            | Self::WrongNetwork
406            | Self::WrongChain
407            | Self::MissingRequiredCapability
408            | Self::ResourceLimit => ZakuraFailureClass::Neutral,
409        }
410    }
411
412    fn from_wire_reject(reason: ZakuraRejectReason) -> Self {
413        match reason {
414            ZakuraRejectReason::UnsupportedPreludeVersion => Self::UnsupportedVersion("prelude"),
415            ZakuraRejectReason::IncompatibleZakuraProtocol => Self::IncompatibleZakuraProtocol,
416            ZakuraRejectReason::WrongNetwork => Self::WrongNetwork,
417            ZakuraRejectReason::WrongChain => Self::WrongChain,
418            ZakuraRejectReason::MissingRequiredCapability => Self::MissingRequiredCapability,
419            ZakuraRejectReason::ResourceLimit => Self::ResourceLimit,
420            ZakuraRejectReason::AlreadyConnected | ZakuraRejectReason::TemporaryUnavailable => {
421                Self::MalformedControl
422            }
423        }
424    }
425}
426
427/// A decoded legacy TCP Zakura upgrade message.
428#[derive(Clone, Debug, Eq, PartialEq)]
429pub enum P2pV2Upgrade {
430    /// The TCP initiator's upgrade prelude.
431    Init(P2pV2UpgradeInit),
432    /// The TCP responder's acceptance prelude.
433    Accept(P2pV2UpgradeAccept),
434    /// The TCP responder's neutral rejection.
435    Reject(P2pV2UpgradeReject),
436}
437
438impl P2pV2Upgrade {
439    /// Encodes this prelude message and enforces the hard prelude cap.
440    pub fn encode(&self) -> Result<Vec<u8>, ZakuraProtocolError> {
441        let mut bytes = Vec::new();
442        match self {
443            Self::Init(init) => {
444                bytes.write_u8(INIT_DISCRIMINATOR)?;
445                init.encode_to(&mut bytes)?;
446            }
447            Self::Accept(accept) => {
448                bytes.write_u8(ACCEPT_DISCRIMINATOR)?;
449                accept.encode_to(&mut bytes)?;
450            }
451            Self::Reject(reject) => {
452                bytes.write_u8(REJECT_DISCRIMINATOR)?;
453                reject.encode_to(&mut bytes)?;
454            }
455        }
456        if bytes.len() > MAX_PRELUDE_PAYLOAD_BYTES {
457            return Err(ZakuraProtocolError::OversizedPayload {
458                actual: bytes.len(),
459                max: MAX_PRELUDE_PAYLOAD_BYTES,
460            });
461        }
462        Ok(bytes)
463    }
464
465    /// Decodes a prelude message after applying the hard prelude cap.
466    pub fn decode(bytes: &[u8]) -> Result<Self, ZakuraProtocolError> {
467        if bytes.len() > MAX_PRELUDE_PAYLOAD_BYTES {
468            return Err(ZakuraProtocolError::OversizedPayload {
469                actual: bytes.len(),
470                max: MAX_PRELUDE_PAYLOAD_BYTES,
471            });
472        }
473
474        let mut reader = Cursor::new(bytes);
475        let message = match reader.read_u8()? {
476            INIT_DISCRIMINATOR => Self::Init(P2pV2UpgradeInit::decode_from(&mut reader)?),
477            ACCEPT_DISCRIMINATOR => Self::Accept(P2pV2UpgradeAccept::decode_from(&mut reader)?),
478            REJECT_DISCRIMINATOR => Self::Reject(P2pV2UpgradeReject::decode_from(&mut reader)?),
479            value => return Err(ZakuraProtocolError::InvalidDiscriminator(value)),
480        };
481        reject_trailing(bytes, &reader)?;
482        Ok(message)
483    }
484}
485
486/// The TCP initiator's legacy upgrade prelude.
487#[derive(Clone, Debug, Eq, PartialEq)]
488pub struct P2pV2UpgradeInit {
489    /// Prelude magic bytes.
490    pub magic: [u8; 8],
491    /// Prelude encoding version.
492    pub prelude_version: u16,
493    /// Lowest supported Zakura protocol version.
494    pub zakura_protocol_min: u16,
495    /// Highest supported Zakura protocol version.
496    pub zakura_protocol_max: u16,
497    /// Local network id.
498    pub network_id: ZakuraNetworkId,
499    /// Local genesis block hash.
500    pub chain_id: [u8; 32],
501    /// High-level Zakura capabilities.
502    pub capabilities: u64,
503    /// The nonce this side sent in its legacy `version`.
504    pub local_zebra_nonce: Nonce,
505    /// The nonce this side received in the remote legacy `version`.
506    pub remote_zebra_nonce: Nonce,
507    /// Fresh nonce for transcript binding.
508    pub upgrade_nonce: [u8; 32],
509    /// Expected Iroh identity for this side.
510    pub iroh_node_id: Vec<u8>,
511    /// Direct Iroh dial hints.
512    pub iroh_direct_addresses: Vec<Vec<u8>>,
513    /// Optional relay hint.
514    pub iroh_relay_hint: Option<Vec<u8>>,
515    /// Local receive cap for control frames.
516    pub max_control_frame_bytes: u32,
517    /// Local initial open-stream limit.
518    pub max_open_streams: u16,
519}
520
521impl P2pV2UpgradeInit {
522    /// Validates this init from the responder's perspective.
523    pub fn validate(
524        &self,
525        local: &ZakuraHandshakeConfig,
526        nonces: ZakuraLegacyNonces,
527    ) -> Result<u16, ZakuraRejectReason> {
528        validate_prelude_static_fields(
529            PreludeStaticFields {
530                magic: self.magic,
531                prelude_version: self.prelude_version,
532                network_id: self.network_id,
533                chain_id: self.chain_id,
534                capabilities: self.capabilities,
535                iroh_node_id: &self.iroh_node_id,
536                iroh_direct_addresses: &self.iroh_direct_addresses,
537                iroh_relay_hint: self.iroh_relay_hint.as_deref(),
538                max_control_frame_bytes: self.max_control_frame_bytes,
539                max_open_streams: self.max_open_streams,
540            },
541            local,
542        )?;
543
544        validate_peer_nonce_labels(self.local_zebra_nonce, self.remote_zebra_nonce, nonces)?;
545
546        select_zakura_protocol(
547            local.zakura_protocol_min,
548            local.zakura_protocol_max,
549            self.zakura_protocol_min,
550            self.zakura_protocol_max,
551        )
552    }
553
554    fn encode_to<W: Write>(&self, writer: &mut W) -> Result<(), ZakuraProtocolError> {
555        writer.write_all(&self.magic)?;
556        writer.write_u16::<LittleEndian>(self.prelude_version)?;
557        writer.write_u16::<LittleEndian>(self.zakura_protocol_min)?;
558        writer.write_u16::<LittleEndian>(self.zakura_protocol_max)?;
559        writer.write_u32::<LittleEndian>(self.network_id.code())?;
560        writer.write_all(&self.chain_id)?;
561        writer.write_u64::<LittleEndian>(self.capabilities)?;
562        writer.write_u64::<LittleEndian>(self.local_zebra_nonce.0)?;
563        writer.write_u64::<LittleEndian>(self.remote_zebra_nonce.0)?;
564        writer.write_all(&self.upgrade_nonce)?;
565        write_bounded_bytes(writer, &self.iroh_node_id, MAX_IROH_NODE_ID_BYTES)?;
566        write_bounded_bytes_list(
567            writer,
568            &self.iroh_direct_addresses,
569            MAX_IROH_DIRECT_ADDRESSES,
570            MAX_IROH_DIRECT_ADDRESS_BYTES,
571        )?;
572        write_optional_bounded_bytes(
573            writer,
574            self.iroh_relay_hint.as_deref(),
575            MAX_IROH_RELAY_HINT_BYTES,
576        )?;
577        writer.write_u32::<LittleEndian>(self.max_control_frame_bytes)?;
578        writer.write_u16::<LittleEndian>(self.max_open_streams)?;
579        Ok(())
580    }
581
582    fn decode_from<R: Read>(reader: &mut R) -> Result<Self, ZakuraProtocolError> {
583        let mut magic = [0; 8];
584        reader.read_exact(&mut magic)?;
585        let prelude_version = reader.read_u16::<LittleEndian>()?;
586        let zakura_protocol_min = reader.read_u16::<LittleEndian>()?;
587        let zakura_protocol_max = reader.read_u16::<LittleEndian>()?;
588        let network_id = ZakuraNetworkId::from_u32(reader.read_u32::<LittleEndian>()?)?;
589        let mut chain_id = [0; 32];
590        reader.read_exact(&mut chain_id)?;
591        let capabilities = reader.read_u64::<LittleEndian>()?;
592        let local_zebra_nonce = Nonce(reader.read_u64::<LittleEndian>()?);
593        let remote_zebra_nonce = Nonce(reader.read_u64::<LittleEndian>()?);
594        let mut upgrade_nonce = [0; 32];
595        reader.read_exact(&mut upgrade_nonce)?;
596        let iroh_node_id = read_bounded_bytes(reader, MAX_IROH_NODE_ID_BYTES)?;
597        let iroh_direct_addresses = read_bounded_bytes_list(
598            reader,
599            MAX_IROH_DIRECT_ADDRESSES,
600            MAX_IROH_DIRECT_ADDRESS_BYTES,
601        )?;
602        let iroh_relay_hint = read_optional_bounded_bytes(reader, MAX_IROH_RELAY_HINT_BYTES)?;
603        let max_control_frame_bytes = reader.read_u32::<LittleEndian>()?;
604        let max_open_streams = reader.read_u16::<LittleEndian>()?;
605
606        Ok(Self {
607            magic,
608            prelude_version,
609            zakura_protocol_min,
610            zakura_protocol_max,
611            network_id,
612            chain_id,
613            capabilities,
614            local_zebra_nonce,
615            remote_zebra_nonce,
616            upgrade_nonce,
617            iroh_node_id,
618            iroh_direct_addresses,
619            iroh_relay_hint,
620            max_control_frame_bytes,
621            max_open_streams,
622        })
623    }
624}
625
626/// The TCP responder's legacy upgrade acceptance prelude.
627#[derive(Clone, Debug, Eq, PartialEq)]
628pub struct P2pV2UpgradeAccept {
629    /// Prelude magic bytes.
630    pub magic: [u8; 8],
631    /// Prelude encoding version.
632    pub prelude_version: u16,
633    /// Selected Zakura protocol version.
634    pub selected_zakura_protocol: u16,
635    /// Local network id.
636    pub network_id: ZakuraNetworkId,
637    /// Local genesis block hash.
638    pub chain_id: [u8; 32],
639    /// High-level Zakura capabilities.
640    pub capabilities: u64,
641    /// The initiator nonce echoed from the init.
642    pub initiator_upgrade_nonce: [u8; 32],
643    /// Fresh responder nonce for transcript binding.
644    pub responder_upgrade_nonce: [u8; 32],
645    /// The nonce this side sent in its legacy `version`.
646    pub local_zebra_nonce: Nonce,
647    /// The nonce this side received in the remote legacy `version`.
648    pub remote_zebra_nonce: Nonce,
649    /// Expected Iroh identity for this side.
650    pub iroh_node_id: Vec<u8>,
651    /// Direct Iroh dial hints.
652    pub iroh_direct_addresses: Vec<Vec<u8>>,
653    /// Optional relay hint.
654    pub iroh_relay_hint: Option<Vec<u8>>,
655    /// Local receive cap for control frames.
656    pub max_control_frame_bytes: u32,
657    /// Local initial open-stream limit.
658    pub max_open_streams: u16,
659}
660
661impl P2pV2UpgradeAccept {
662    /// Validates this accept from the initiator's perspective.
663    pub fn validate(
664        &self,
665        local: &ZakuraHandshakeConfig,
666        nonces: ZakuraLegacyNonces,
667        init: &P2pV2UpgradeInit,
668    ) -> Result<(), ZakuraValidationError> {
669        validate_prelude_static_fields(
670            PreludeStaticFields {
671                magic: self.magic,
672                prelude_version: self.prelude_version,
673                network_id: self.network_id,
674                chain_id: self.chain_id,
675                capabilities: self.capabilities,
676                iroh_node_id: &self.iroh_node_id,
677                iroh_direct_addresses: &self.iroh_direct_addresses,
678                iroh_relay_hint: self.iroh_relay_hint.as_deref(),
679                max_control_frame_bytes: self.max_control_frame_bytes,
680                max_open_streams: self.max_open_streams,
681            },
682            local,
683        )
684        .map_err(ZakuraValidationError::from_wire_reject)?;
685        validate_peer_nonce_labels(self.local_zebra_nonce, self.remote_zebra_nonce, nonces)
686            .map_err(|_| ZakuraValidationError::UpgradeNonceMismatch)?;
687        if self.initiator_upgrade_nonce != init.upgrade_nonce {
688            return Err(ZakuraValidationError::UpgradeNonceMismatch);
689        }
690
691        let selected = select_zakura_protocol(
692            local.zakura_protocol_min,
693            local.zakura_protocol_max,
694            init.zakura_protocol_min,
695            init.zakura_protocol_max,
696        )
697        .map_err(ZakuraValidationError::from_wire_reject)?;
698        if self.selected_zakura_protocol != selected {
699            return Err(ZakuraValidationError::IncompatibleZakuraProtocol);
700        }
701
702        Ok(())
703    }
704
705    fn encode_to<W: Write>(&self, writer: &mut W) -> Result<(), ZakuraProtocolError> {
706        writer.write_all(&self.magic)?;
707        writer.write_u16::<LittleEndian>(self.prelude_version)?;
708        writer.write_u16::<LittleEndian>(self.selected_zakura_protocol)?;
709        writer.write_u32::<LittleEndian>(self.network_id.code())?;
710        writer.write_all(&self.chain_id)?;
711        writer.write_u64::<LittleEndian>(self.capabilities)?;
712        writer.write_all(&self.initiator_upgrade_nonce)?;
713        writer.write_all(&self.responder_upgrade_nonce)?;
714        writer.write_u64::<LittleEndian>(self.local_zebra_nonce.0)?;
715        writer.write_u64::<LittleEndian>(self.remote_zebra_nonce.0)?;
716        write_bounded_bytes(writer, &self.iroh_node_id, MAX_IROH_NODE_ID_BYTES)?;
717        write_bounded_bytes_list(
718            writer,
719            &self.iroh_direct_addresses,
720            MAX_IROH_DIRECT_ADDRESSES,
721            MAX_IROH_DIRECT_ADDRESS_BYTES,
722        )?;
723        write_optional_bounded_bytes(
724            writer,
725            self.iroh_relay_hint.as_deref(),
726            MAX_IROH_RELAY_HINT_BYTES,
727        )?;
728        writer.write_u32::<LittleEndian>(self.max_control_frame_bytes)?;
729        writer.write_u16::<LittleEndian>(self.max_open_streams)?;
730        Ok(())
731    }
732
733    fn decode_from<R: Read>(reader: &mut R) -> Result<Self, ZakuraProtocolError> {
734        let mut magic = [0; 8];
735        reader.read_exact(&mut magic)?;
736        let prelude_version = reader.read_u16::<LittleEndian>()?;
737        let selected_zakura_protocol = reader.read_u16::<LittleEndian>()?;
738        let network_id = ZakuraNetworkId::from_u32(reader.read_u32::<LittleEndian>()?)?;
739        let mut chain_id = [0; 32];
740        reader.read_exact(&mut chain_id)?;
741        let capabilities = reader.read_u64::<LittleEndian>()?;
742        let mut initiator_upgrade_nonce = [0; 32];
743        reader.read_exact(&mut initiator_upgrade_nonce)?;
744        let mut responder_upgrade_nonce = [0; 32];
745        reader.read_exact(&mut responder_upgrade_nonce)?;
746        let local_zebra_nonce = Nonce(reader.read_u64::<LittleEndian>()?);
747        let remote_zebra_nonce = Nonce(reader.read_u64::<LittleEndian>()?);
748        let iroh_node_id = read_bounded_bytes(reader, MAX_IROH_NODE_ID_BYTES)?;
749        let iroh_direct_addresses = read_bounded_bytes_list(
750            reader,
751            MAX_IROH_DIRECT_ADDRESSES,
752            MAX_IROH_DIRECT_ADDRESS_BYTES,
753        )?;
754        let iroh_relay_hint = read_optional_bounded_bytes(reader, MAX_IROH_RELAY_HINT_BYTES)?;
755        let max_control_frame_bytes = reader.read_u32::<LittleEndian>()?;
756        let max_open_streams = reader.read_u16::<LittleEndian>()?;
757
758        Ok(Self {
759            magic,
760            prelude_version,
761            selected_zakura_protocol,
762            network_id,
763            chain_id,
764            capabilities,
765            initiator_upgrade_nonce,
766            responder_upgrade_nonce,
767            local_zebra_nonce,
768            remote_zebra_nonce,
769            iroh_node_id,
770            iroh_direct_addresses,
771            iroh_relay_hint,
772            max_control_frame_bytes,
773            max_open_streams,
774        })
775    }
776}
777
778/// The TCP responder's legacy upgrade rejection.
779#[derive(Clone, Debug, Eq, PartialEq)]
780pub struct P2pV2UpgradeReject {
781    /// Prelude magic bytes.
782    pub magic: [u8; 8],
783    /// Prelude encoding version.
784    pub prelude_version: u16,
785    /// Neutral rejection reason.
786    pub reason: ZakuraRejectReason,
787}
788
789impl P2pV2UpgradeReject {
790    fn encode_to<W: Write>(&self, writer: &mut W) -> Result<(), ZakuraProtocolError> {
791        writer.write_all(&self.magic)?;
792        writer.write_u16::<LittleEndian>(self.prelude_version)?;
793        writer.write_u16::<LittleEndian>(self.reason.code())?;
794        Ok(())
795    }
796
797    fn decode_from<R: Read>(reader: &mut R) -> Result<Self, ZakuraProtocolError> {
798        let mut magic = [0; 8];
799        reader.read_exact(&mut magic)?;
800        let prelude_version = reader.read_u16::<LittleEndian>()?;
801        let reason = ZakuraRejectReason::from_u16(reader.read_u16::<LittleEndian>()?)?;
802        Ok(Self {
803            magic,
804            prelude_version,
805            reason,
806        })
807    }
808}
809
810/// The role in the control handshake.
811#[derive(Copy, Clone, Debug, Eq, PartialEq)]
812#[repr(u8)]
813pub enum ZakuraControlRole {
814    /// The side that initiated the legacy TCP upgrade or native dial.
815    Initiator = 1,
816    /// The side that responded to the legacy TCP upgrade or accepted the native dial.
817    Responder = 2,
818}
819
820impl ZakuraControlRole {
821    fn from_u8(value: u8) -> Result<Self, ZakuraProtocolError> {
822        match value {
823            1 => Ok(Self::Initiator),
824            2 => Ok(Self::Responder),
825            _ => Err(ZakuraProtocolError::InvalidRole(value)),
826        }
827    }
828
829    fn code(self) -> u8 {
830        // Safe: `ZakuraControlRole` has `#[repr(u8)]`, so the cast uses the pinned wire value.
831        self as u8
832    }
833}
834
835/// Whether the control handshake is bound to a legacy upgrade transcript.
836#[derive(Copy, Clone, Debug, Eq, PartialEq)]
837#[repr(u8)]
838pub enum ZakuraHandshakePath {
839    /// Legacy TCP upgrade path, with transcript binding required.
840    Upgraded = 0,
841    /// Native Iroh bootstrap path, with transcript binding skipped.
842    Native = 1,
843}
844
845impl ZakuraHandshakePath {
846    fn from_u8(value: u8) -> Result<Self, ZakuraProtocolError> {
847        match value {
848            0 => Ok(Self::Upgraded),
849            1 => Ok(Self::Native),
850            _ => Err(ZakuraProtocolError::InvalidHandshakePath(value)),
851        }
852    }
853
854    fn code(self) -> u8 {
855        // Safe: `ZakuraHandshakePath` has `#[repr(u8)]`, so the cast uses the pinned wire value.
856        self as u8
857    }
858}
859
860/// Bounded resource limits exchanged during the Zakura control handshake.
861#[derive(Copy, Clone, Debug, Eq, PartialEq)]
862pub struct ZakuraLimits {
863    /// Maximum frame bytes.
864    pub max_frame_bytes: u32,
865    /// Maximum full message bytes.
866    pub max_message_bytes: u32,
867    /// Maximum open streams.
868    pub max_open_streams: u16,
869    /// Maximum inbound queue depth.
870    pub max_inbound_queue_depth: u16,
871    /// Idle timeout in milliseconds.
872    pub idle_timeout_millis: u32,
873}
874
875/// Initial limits advertised in a Zakura control hello.
876pub type ZakuraInitialLimits = ZakuraLimits;
877
878/// Accepted limits sent in a Zakura control ack.
879pub type ZakuraAcceptedLimits = ZakuraLimits;
880
881/// The authenticated Zakura control hello.
882#[derive(Clone, Debug, Eq, PartialEq)]
883pub struct ZakuraControlHello {
884    /// Control hello magic bytes.
885    pub magic: [u8; 8],
886    /// Control encoding version.
887    pub control_version: u16,
888    /// Selected Zakura protocol version.
889    pub selected_zakura_protocol: u16,
890    /// Native or upgraded handshake path.
891    pub handshake_path: ZakuraHandshakePath,
892    /// Control role.
893    pub role: ZakuraControlRole,
894    /// Local network id.
895    pub network_id: ZakuraNetworkId,
896    /// Local genesis block hash.
897    pub chain_id: [u8; 32],
898    /// Authenticated Iroh node id this peer claims.
899    pub iroh_node_id: Vec<u8>,
900    /// Fresh nonce for this control exchange.
901    pub peer_nonce: [u8; 32],
902    /// Initiator upgrade nonce, or zeroes for native handshakes.
903    pub initiator_upgrade_nonce: [u8; 32],
904    /// Responder upgrade nonce, or zeroes for native handshakes.
905    pub responder_upgrade_nonce: [u8; 32],
906    /// Legacy upgrade transcript hash, or zeroes for native handshakes.
907    pub legacy_upgrade_transcript: [u8; 32],
908    /// High-level Zakura capabilities.
909    pub capabilities: u64,
910    /// Channel bits required by this peer.
911    pub required_channels: u64,
912    /// Initial resource limits.
913    pub initial_limits: ZakuraInitialLimits,
914}
915
916impl ZakuraControlHello {
917    /// Encodes this control hello with the hard control cap.
918    pub fn encode(&self) -> Result<Vec<u8>, ZakuraProtocolError> {
919        let mut bytes = Vec::new();
920        bytes.write_all(&self.magic)?;
921        bytes.write_u16::<LittleEndian>(self.control_version)?;
922        bytes.write_u16::<LittleEndian>(self.selected_zakura_protocol)?;
923        bytes.write_u8(self.handshake_path.code())?;
924        bytes.write_u8(self.role.code())?;
925        bytes.write_u32::<LittleEndian>(self.network_id.code())?;
926        bytes.write_all(&self.chain_id)?;
927        write_bounded_bytes(&mut bytes, &self.iroh_node_id, MAX_IROH_NODE_ID_BYTES)?;
928        bytes.write_all(&self.peer_nonce)?;
929        bytes.write_all(&self.initiator_upgrade_nonce)?;
930        bytes.write_all(&self.responder_upgrade_nonce)?;
931        bytes.write_all(&self.legacy_upgrade_transcript)?;
932        bytes.write_u64::<LittleEndian>(self.capabilities)?;
933        bytes.write_u64::<LittleEndian>(self.required_channels)?;
934        self.initial_limits.encode_to(&mut bytes)?;
935        if bytes.len() > MAX_CONTROL_PAYLOAD_BYTES {
936            return Err(ZakuraProtocolError::OversizedPayload {
937                actual: bytes.len(),
938                max: MAX_CONTROL_PAYLOAD_BYTES,
939            });
940        }
941        Ok(bytes)
942    }
943
944    /// Decodes this control hello with the hard control cap.
945    pub fn decode(bytes: &[u8]) -> Result<Self, ZakuraProtocolError> {
946        if bytes.len() > MAX_CONTROL_PAYLOAD_BYTES {
947            return Err(ZakuraProtocolError::OversizedPayload {
948                actual: bytes.len(),
949                max: MAX_CONTROL_PAYLOAD_BYTES,
950            });
951        }
952        let mut reader = Cursor::new(bytes);
953        let mut magic = [0; 8];
954        reader.read_exact(&mut magic)?;
955        let control_version = reader.read_u16::<LittleEndian>()?;
956        let selected_zakura_protocol = reader.read_u16::<LittleEndian>()?;
957        let handshake_path = ZakuraHandshakePath::from_u8(reader.read_u8()?)?;
958        let role = ZakuraControlRole::from_u8(reader.read_u8()?)?;
959        let network_id = ZakuraNetworkId::from_u32(reader.read_u32::<LittleEndian>()?)?;
960        let mut chain_id = [0; 32];
961        reader.read_exact(&mut chain_id)?;
962        let iroh_node_id = read_bounded_bytes(&mut reader, MAX_IROH_NODE_ID_BYTES)?;
963        let mut peer_nonce = [0; 32];
964        reader.read_exact(&mut peer_nonce)?;
965        let mut initiator_upgrade_nonce = [0; 32];
966        reader.read_exact(&mut initiator_upgrade_nonce)?;
967        let mut responder_upgrade_nonce = [0; 32];
968        reader.read_exact(&mut responder_upgrade_nonce)?;
969        let mut legacy_upgrade_transcript = [0; 32];
970        reader.read_exact(&mut legacy_upgrade_transcript)?;
971        let capabilities = reader.read_u64::<LittleEndian>()?;
972        let required_channels = reader.read_u64::<LittleEndian>()?;
973        let initial_limits = ZakuraInitialLimits::decode_from(&mut reader)?;
974        reject_trailing(bytes, &reader)?;
975
976        Ok(Self {
977            magic,
978            control_version,
979            selected_zakura_protocol,
980            handshake_path,
981            role,
982            network_id,
983            chain_id,
984            iroh_node_id,
985            peer_nonce,
986            initiator_upgrade_nonce,
987            responder_upgrade_nonce,
988            legacy_upgrade_transcript,
989            capabilities,
990            required_channels,
991            initial_limits,
992        })
993    }
994
995    /// Validates a peer hello against authenticated Iroh identity and local policy.
996    pub fn validate(
997        &self,
998        expected: &ZakuraControlValidation<'_>,
999    ) -> Result<(), ZakuraValidationError> {
1000        if self.magic != CONTROL_HELLO_MAGIC {
1001            return Err(ZakuraValidationError::MalformedControl);
1002        }
1003        if self.control_version != CONTROL_VERSION {
1004            return Err(ZakuraValidationError::UnsupportedVersion("control"));
1005        }
1006        if self.selected_zakura_protocol != expected.selected_zakura_protocol {
1007            return Err(ZakuraValidationError::IncompatibleZakuraProtocol);
1008        }
1009        if self.handshake_path != expected.handshake_path {
1010            return Err(ZakuraValidationError::MalformedControl);
1011        }
1012        if self.role != expected.remote_role {
1013            return Err(ZakuraValidationError::MalformedControl);
1014        }
1015        if self.network_id != expected.local.network_id {
1016            return Err(ZakuraValidationError::WrongNetwork);
1017        }
1018        if self.chain_id != expected.local.chain_id {
1019            return Err(ZakuraValidationError::WrongChain);
1020        }
1021        if self.iroh_node_id.as_slice() != expected.authenticated_remote_id {
1022            return Err(ZakuraValidationError::IdentityMismatch);
1023        }
1024        if self.capabilities & expected.local.required_capabilities
1025            != expected.local.required_capabilities
1026        {
1027            return Err(ZakuraValidationError::MissingRequiredCapability);
1028        }
1029        if self.required_channels & !expected.local.supported_channels != 0 {
1030            return Err(ZakuraValidationError::MissingRequiredCapability);
1031        }
1032        validate_initial_limits(self.initial_limits, expected.local)
1033            .map_err(ZakuraValidationError::from_wire_reject)?;
1034
1035        match expected.handshake_path {
1036            ZakuraHandshakePath::Upgraded => {
1037                if self.initiator_upgrade_nonce != expected.initiator_upgrade_nonce
1038                    || self.responder_upgrade_nonce != expected.responder_upgrade_nonce
1039                {
1040                    return Err(ZakuraValidationError::UpgradeNonceMismatch);
1041                }
1042                if self.legacy_upgrade_transcript != expected.legacy_upgrade_transcript {
1043                    return Err(ZakuraValidationError::TranscriptMismatch);
1044                }
1045            }
1046            ZakuraHandshakePath::Native => {
1047                if self.initiator_upgrade_nonce != [0; 32]
1048                    || self.responder_upgrade_nonce != [0; 32]
1049                {
1050                    return Err(ZakuraValidationError::UpgradeNonceMismatch);
1051                }
1052                if self.legacy_upgrade_transcript != [0; 32] {
1053                    return Err(ZakuraValidationError::TranscriptMismatch);
1054                }
1055            }
1056        }
1057
1058        Ok(())
1059    }
1060}
1061
1062/// Inputs needed to validate a Zakura control hello.
1063#[derive(Copy, Clone, Debug)]
1064pub struct ZakuraControlValidation<'a> {
1065    /// Local Zakura policy.
1066    pub local: &'a ZakuraHandshakeConfig,
1067    /// The authenticated Iroh remote node id.
1068    pub authenticated_remote_id: &'a [u8],
1069    /// Selected Zakura protocol.
1070    pub selected_zakura_protocol: u16,
1071    /// Native or upgraded handshake path.
1072    pub handshake_path: ZakuraHandshakePath,
1073    /// Expected role claimed by the remote peer.
1074    pub remote_role: ZakuraControlRole,
1075    /// Expected initiator upgrade nonce.
1076    pub initiator_upgrade_nonce: [u8; 32],
1077    /// Expected responder upgrade nonce.
1078    pub responder_upgrade_nonce: [u8; 32],
1079    /// Expected legacy upgrade transcript hash.
1080    pub legacy_upgrade_transcript: [u8; 32],
1081}
1082
1083/// The authenticated Zakura control acknowledgement.
1084#[derive(Clone, Debug, Eq, PartialEq)]
1085pub struct ZakuraControlAck {
1086    /// Control ack magic bytes.
1087    pub magic: [u8; 8],
1088    /// Control encoding version.
1089    pub control_version: u16,
1090    /// Selected Zakura protocol version.
1091    pub selected_zakura_protocol: u16,
1092    /// This peer's control nonce.
1093    pub peer_nonce: [u8; 32],
1094    /// The nonce from the remote peer's control hello.
1095    pub remote_peer_nonce: [u8; 32],
1096    /// Capabilities accepted by this peer.
1097    pub accepted_capabilities: u64,
1098    /// Channels accepted by this peer.
1099    pub accepted_channels: u64,
1100    /// Accepted resource limits.
1101    pub accepted_limits: ZakuraAcceptedLimits,
1102}
1103
1104impl ZakuraControlAck {
1105    /// Encodes this control ack with the hard control cap.
1106    pub fn encode(&self) -> Result<Vec<u8>, ZakuraProtocolError> {
1107        let mut bytes = Vec::new();
1108        bytes.write_all(&self.magic)?;
1109        bytes.write_u16::<LittleEndian>(self.control_version)?;
1110        bytes.write_u16::<LittleEndian>(self.selected_zakura_protocol)?;
1111        bytes.write_all(&self.peer_nonce)?;
1112        bytes.write_all(&self.remote_peer_nonce)?;
1113        bytes.write_u64::<LittleEndian>(self.accepted_capabilities)?;
1114        bytes.write_u64::<LittleEndian>(self.accepted_channels)?;
1115        self.accepted_limits.encode_to(&mut bytes)?;
1116        if bytes.len() > MAX_CONTROL_PAYLOAD_BYTES {
1117            return Err(ZakuraProtocolError::OversizedPayload {
1118                actual: bytes.len(),
1119                max: MAX_CONTROL_PAYLOAD_BYTES,
1120            });
1121        }
1122        Ok(bytes)
1123    }
1124
1125    /// Decodes this control ack with the hard control cap.
1126    pub fn decode(bytes: &[u8]) -> Result<Self, ZakuraProtocolError> {
1127        if bytes.len() > MAX_CONTROL_PAYLOAD_BYTES {
1128            return Err(ZakuraProtocolError::OversizedPayload {
1129                actual: bytes.len(),
1130                max: MAX_CONTROL_PAYLOAD_BYTES,
1131            });
1132        }
1133        let mut reader = Cursor::new(bytes);
1134        let mut magic = [0; 8];
1135        reader.read_exact(&mut magic)?;
1136        let control_version = reader.read_u16::<LittleEndian>()?;
1137        let selected_zakura_protocol = reader.read_u16::<LittleEndian>()?;
1138        let mut peer_nonce = [0; 32];
1139        reader.read_exact(&mut peer_nonce)?;
1140        let mut remote_peer_nonce = [0; 32];
1141        reader.read_exact(&mut remote_peer_nonce)?;
1142        let accepted_capabilities = reader.read_u64::<LittleEndian>()?;
1143        let accepted_channels = reader.read_u64::<LittleEndian>()?;
1144        let accepted_limits = ZakuraAcceptedLimits::decode_from(&mut reader)?;
1145        reject_trailing(bytes, &reader)?;
1146
1147        Ok(Self {
1148            magic,
1149            control_version,
1150            selected_zakura_protocol,
1151            peer_nonce,
1152            remote_peer_nonce,
1153            accepted_capabilities,
1154            accepted_channels,
1155            accepted_limits,
1156        })
1157    }
1158
1159    /// Validates this acknowledgement against the exact control exchange.
1160    pub fn validate(
1161        &self,
1162        selected_zakura_protocol: u16,
1163        local_peer_nonce: [u8; 32],
1164        remote_peer_nonce: [u8; 32],
1165        requested_limits: &ZakuraInitialLimits,
1166        local: &ZakuraHandshakeConfig,
1167    ) -> Result<(), ZakuraValidationError> {
1168        if self.magic != CONTROL_ACK_MAGIC || self.control_version != CONTROL_VERSION {
1169            return Err(ZakuraValidationError::MalformedControl);
1170        }
1171        if self.selected_zakura_protocol != selected_zakura_protocol {
1172            return Err(ZakuraValidationError::IncompatibleZakuraProtocol);
1173        }
1174        if self.remote_peer_nonce != local_peer_nonce || self.peer_nonce != remote_peer_nonce {
1175            return Err(ZakuraValidationError::ControlNonceMismatch);
1176        }
1177        validate_initial_limits(self.accepted_limits, local)
1178            .map_err(ZakuraValidationError::from_wire_reject)?;
1179        if self.accepted_limits.max_frame_bytes > requested_limits.max_frame_bytes
1180            || self.accepted_limits.max_message_bytes > requested_limits.max_message_bytes
1181            || self.accepted_limits.max_open_streams > requested_limits.max_open_streams
1182            || self.accepted_limits.max_inbound_queue_depth
1183                > requested_limits.max_inbound_queue_depth
1184            || self.accepted_limits.idle_timeout_millis > requested_limits.idle_timeout_millis
1185        {
1186            return Err(ZakuraValidationError::ResourceLimit);
1187        }
1188        Ok(())
1189    }
1190}
1191
1192/// Prelude written immediately after opening each Zakura stream.
1193#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1194pub struct StreamPrelude {
1195    /// Fixed stream magic.
1196    pub magic: [u8; 4],
1197    /// Application stream kind.
1198    pub stream_kind: u16,
1199    /// Version of this stream kind.
1200    pub stream_version: u16,
1201    /// Optional request id.
1202    pub request_id: Option<u64>,
1203    /// Maximum frame bytes accepted by the stream opener.
1204    pub max_frame_bytes: u32,
1205}
1206
1207impl StreamPrelude {
1208    /// Encodes this stream prelude.
1209    pub fn encode(&self) -> Result<Vec<u8>, ZakuraProtocolError> {
1210        let mut bytes = Vec::new();
1211        bytes.write_all(&self.magic)?;
1212        bytes.write_u16::<LittleEndian>(self.stream_kind)?;
1213        bytes.write_u16::<LittleEndian>(self.stream_version)?;
1214        match self.request_id {
1215            Some(request_id) => {
1216                bytes.write_u8(1)?;
1217                bytes.write_u64::<LittleEndian>(request_id)?;
1218            }
1219            None => bytes.write_u8(0)?,
1220        }
1221        bytes.write_u32::<LittleEndian>(self.max_frame_bytes)?;
1222        Ok(bytes)
1223    }
1224
1225    /// Decodes a stream prelude.
1226    pub fn decode(bytes: &[u8]) -> Result<Self, ZakuraProtocolError> {
1227        let mut reader = Cursor::new(bytes);
1228        let mut magic = [0; 4];
1229        reader.read_exact(&mut magic)?;
1230        if magic != STREAM_PRELUDE_MAGIC {
1231            return Err(ZakuraProtocolError::InvalidMagic);
1232        }
1233        let stream_kind = reader.read_u16::<LittleEndian>()?;
1234        let stream_version = reader.read_u16::<LittleEndian>()?;
1235        let request_id = match reader.read_u8()? {
1236            0 => None,
1237            1 => Some(reader.read_u64::<LittleEndian>()?),
1238            value => return Err(ZakuraProtocolError::InvalidFlag(value)),
1239        };
1240        let max_frame_bytes = reader.read_u32::<LittleEndian>()?;
1241        reject_trailing(bytes, &reader)?;
1242        Ok(Self {
1243            magic,
1244            stream_kind,
1245            stream_version,
1246            request_id,
1247            max_frame_bytes,
1248        })
1249    }
1250}
1251
1252/// A bounded Zakura stream frame.
1253#[derive(Clone, Debug, Eq, PartialEq)]
1254pub struct Frame {
1255    /// Application message type.
1256    pub message_type: u16,
1257    /// Message flags.
1258    pub flags: u16,
1259    /// Bounded payload bytes.
1260    pub payload: Vec<u8>,
1261}
1262
1263impl Frame {
1264    /// Encodes this frame if it fits in `max_frame_bytes`.
1265    pub fn encode(&self, max_frame_bytes: u32) -> Result<Vec<u8>, ZakuraProtocolError> {
1266        let max_frame_bytes = usize_from_u32(max_frame_bytes, "frame cap")?;
1267        if self.payload.len() > max_frame_bytes.saturating_sub(FRAME_HEADER_BYTES) {
1268            return Err(ZakuraProtocolError::OversizedPayload {
1269                actual: self.payload.len(),
1270                max: max_frame_bytes.saturating_sub(FRAME_HEADER_BYTES),
1271            });
1272        }
1273        let mut bytes = Vec::new();
1274        bytes.write_u16::<LittleEndian>(self.message_type)?;
1275        bytes.write_u16::<LittleEndian>(self.flags)?;
1276        bytes.write_u32::<LittleEndian>(u32_from_usize(self.payload.len(), "payload length")?)?;
1277        bytes.write_all(&self.payload)?;
1278        Ok(bytes)
1279    }
1280
1281    /// Decodes this frame if it fits in `max_frame_bytes`.
1282    pub fn decode(bytes: &[u8], max_frame_bytes: u32) -> Result<Self, ZakuraProtocolError> {
1283        let max_frame_bytes = usize_from_u32(max_frame_bytes, "frame cap")?;
1284        if bytes.len() > max_frame_bytes {
1285            return Err(ZakuraProtocolError::OversizedPayload {
1286                actual: bytes.len(),
1287                max: max_frame_bytes,
1288            });
1289        }
1290        let mut reader = Cursor::new(bytes);
1291        let message_type = reader.read_u16::<LittleEndian>()?;
1292        let flags = reader.read_u16::<LittleEndian>()?;
1293        let payload_len = usize_from_u32(reader.read_u32::<LittleEndian>()?, "payload length")?;
1294        if payload_len > max_frame_bytes.saturating_sub(FRAME_HEADER_BYTES) {
1295            return Err(ZakuraProtocolError::OversizedPayload {
1296                actual: payload_len,
1297                max: max_frame_bytes.saturating_sub(FRAME_HEADER_BYTES),
1298            });
1299        }
1300        let payload = read_exact_vec(&mut reader, payload_len)?;
1301        reject_trailing(bytes, &reader)?;
1302        Ok(Self {
1303            message_type,
1304            flags,
1305            payload,
1306        })
1307    }
1308}
1309
1310/// A pending inbound Iroh upgrade that must match a legacy prelude.
1311#[derive(Clone, Debug, Eq, PartialEq)]
1312pub struct PendingUpgrade {
1313    /// Expected initiator peer id.
1314    pub expected_peer_id: ZakuraPeerId,
1315    /// Selected Zakura protocol.
1316    pub selected_zakura_protocol: u16,
1317    /// Initiator upgrade nonce.
1318    pub initiator_upgrade_nonce: [u8; 32],
1319    /// Responder upgrade nonce.
1320    pub responder_upgrade_nonce: [u8; 32],
1321    /// Legacy upgrade transcript hash.
1322    pub legacy_upgrade_transcript: [u8; 32],
1323}
1324
1325#[derive(Clone, Debug, Eq, PartialEq)]
1326struct PendingUpgradeEntry {
1327    pending: PendingUpgrade,
1328    expires_at: Instant,
1329}
1330
1331/// A small bounded pending-upgrade registry keyed by authenticated Iroh id.
1332#[derive(Debug)]
1333pub struct PendingUpgradeRegistry {
1334    entries: HashMap<ZakuraPeerId, PendingUpgradeEntry>,
1335    max_entries: usize,
1336    ttl: Duration,
1337}
1338
1339impl PendingUpgradeRegistry {
1340    /// Creates a bounded pending-upgrade registry.
1341    pub fn new(max_entries: usize, ttl: Duration) -> Self {
1342        Self {
1343            entries: HashMap::new(),
1344            max_entries,
1345            ttl,
1346        }
1347    }
1348
1349    /// Inserts an expected pending upgrade.
1350    pub fn insert(
1351        &mut self,
1352        now: Instant,
1353        pending: PendingUpgrade,
1354    ) -> Result<(), ZakuraRejectReason> {
1355        self.prune_expired(now);
1356        if self.entries.len() >= self.max_entries
1357            && !self.entries.contains_key(&pending.expected_peer_id)
1358        {
1359            return Err(ZakuraRejectReason::ResourceLimit);
1360        }
1361        self.entries.insert(
1362            pending.expected_peer_id.clone(),
1363            PendingUpgradeEntry {
1364                pending,
1365                expires_at: now + self.ttl,
1366            },
1367        );
1368        Ok(())
1369    }
1370
1371    /// Takes and consumes a matching pending upgrade, if one exists and has not expired.
1372    pub fn take(&mut self, now: Instant, peer_id: &ZakuraPeerId) -> Option<PendingUpgrade> {
1373        self.prune_expired(now);
1374        self.entries.remove(peer_id).map(|entry| entry.pending)
1375    }
1376
1377    /// Returns the number of live pending entries.
1378    pub fn len(&self) -> usize {
1379        self.entries.len()
1380    }
1381
1382    /// Returns true if there are no live pending entries.
1383    pub fn is_empty(&self) -> bool {
1384        self.entries.is_empty()
1385    }
1386
1387    fn prune_expired(&mut self, now: Instant) {
1388        self.entries.retain(|_, entry| entry.expires_at > now);
1389    }
1390}
1391
1392/// A tiny authenticated-peer registry used by the Zakura supervisor.
1393#[derive(Debug, Default)]
1394pub struct ZakuraPeerSupervisor {
1395    peers: HashMap<ZakuraPeerId, [u8; TRANSCRIPT_HASH_BYTES]>,
1396}
1397
1398/// Result of applying an authenticated transport to the identity registry.
1399#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1400pub enum AuthenticatedPeerRegistration {
1401    /// The transport became the active connection for its peer identity.
1402    Registered,
1403    /// The identity registry retained the existing connection.
1404    Duplicate,
1405}
1406
1407impl ZakuraPeerSupervisor {
1408    /// Registers an authenticated peer or returns `Duplicate` without punishment.
1409    pub fn register_authenticated(
1410        &mut self,
1411        peer_id: ZakuraPeerId,
1412        transcript_hash: [u8; TRANSCRIPT_HASH_BYTES],
1413    ) -> AuthenticatedPeerRegistration {
1414        if let Some(existing_hash) = self.peers.get(&peer_id) {
1415            // D6: the lexicographically smaller transcript hash wins; exact ties keep the incumbent.
1416            if existing_hash <= &transcript_hash {
1417                metrics::counter!("zakura.p2p.handshake.duplicate").increment(1);
1418                return AuthenticatedPeerRegistration::Duplicate;
1419            }
1420        }
1421
1422        self.peers.insert(peer_id, transcript_hash);
1423        metrics::counter!("zakura.p2p.handshake.upgraded").increment(1);
1424        AuthenticatedPeerRegistration::Registered
1425    }
1426
1427    /// Removes an authenticated peer registration.
1428    pub fn deregister_authenticated(&mut self, peer_id: &ZakuraPeerId) {
1429        self.peers.remove(peer_id);
1430    }
1431}
1432
1433impl PendingUpgrade {
1434    /// Creates a pending upgrade with an unset expiration.
1435    pub fn new(
1436        expected_peer_id: ZakuraPeerId,
1437        selected_zakura_protocol: u16,
1438        initiator_upgrade_nonce: [u8; 32],
1439        responder_upgrade_nonce: [u8; 32],
1440        legacy_upgrade_transcript: [u8; 32],
1441    ) -> Self {
1442        Self {
1443            expected_peer_id,
1444            selected_zakura_protocol,
1445            initiator_upgrade_nonce,
1446            responder_upgrade_nonce,
1447            legacy_upgrade_transcript,
1448        }
1449    }
1450}
1451
1452/// Computes the canonical legacy upgrade transcript hash.
1453pub fn legacy_upgrade_transcript(
1454    initiator_version_message: &VersionMessage,
1455    responder_version_message: &VersionMessage,
1456    p2p_v2_upgrade_init: &P2pV2UpgradeInit,
1457    p2p_v2_upgrade_accept: &P2pV2UpgradeAccept,
1458) -> Result<[u8; TRANSCRIPT_HASH_BYTES], ZakuraProtocolError> {
1459    let mut state = Blake2bParams::new()
1460        .hash_length(TRANSCRIPT_HASH_BYTES)
1461        .personal(b"zakura-upgrade1")
1462        .to_state();
1463
1464    write_version_message_to_hash(&mut state, initiator_version_message)?;
1465    write_version_message_to_hash(&mut state, responder_version_message)?;
1466    state.update(&P2pV2Upgrade::Init(p2p_v2_upgrade_init.clone()).encode()?);
1467    state.update(&P2pV2Upgrade::Accept(p2p_v2_upgrade_accept.clone()).encode()?);
1468
1469    let hash = state.finalize();
1470    let mut out = [0; TRANSCRIPT_HASH_BYTES];
1471    out.copy_from_slice(hash.as_bytes());
1472    Ok(out)
1473}
1474
1475/// Selects the highest shared Zakura protocol version.
1476pub fn select_zakura_protocol(
1477    local_min: u16,
1478    local_max: u16,
1479    remote_min: u16,
1480    remote_max: u16,
1481) -> Result<u16, ZakuraRejectReason> {
1482    if local_min > local_max || remote_min > remote_max {
1483        return Err(ZakuraRejectReason::IncompatibleZakuraProtocol);
1484    }
1485    let selected = min(local_max, remote_max);
1486    if selected < local_min || selected < remote_min {
1487        return Err(ZakuraRejectReason::IncompatibleZakuraProtocol);
1488    }
1489    Ok(selected)
1490}
1491
1492/// An encoding or validation error in bounded Zakura protocol data.
1493#[derive(Error, Debug)]
1494pub enum ZakuraProtocolError {
1495    /// A payload exceeded its hard cap.
1496    #[error("Zakura payload length {actual} exceeds hard cap {max}")]
1497    OversizedPayload {
1498        /// Actual payload length.
1499        actual: usize,
1500        /// Maximum allowed payload length.
1501        max: usize,
1502    },
1503
1504    /// An I/O error while encoding or decoding.
1505    #[error("Zakura wire I/O error: {0}")]
1506    Io(#[from] io::Error),
1507
1508    /// The message type discriminator is unknown.
1509    #[error("invalid Zakura message discriminator {0}")]
1510    InvalidDiscriminator(u8),
1511
1512    /// The network id is unknown.
1513    #[error("invalid Zakura network id {0}")]
1514    InvalidNetworkId(u32),
1515
1516    /// The reject reason is unknown.
1517    #[error("invalid Zakura reject reason {0}")]
1518    InvalidRejectReason(u16),
1519
1520    /// The control role is unknown.
1521    #[error("invalid Zakura control role {0}")]
1522    InvalidRole(u8),
1523
1524    /// The control path is unknown.
1525    #[error("invalid Zakura handshake path {0}")]
1526    InvalidHandshakePath(u8),
1527
1528    /// A boolean or option flag is invalid.
1529    #[error("invalid Zakura flag {0}")]
1530    InvalidFlag(u8),
1531
1532    /// Magic bytes did not match the expected value.
1533    #[error("invalid Zakura magic")]
1534    InvalidMagic,
1535
1536    /// A decoded payload had trailing bytes.
1537    #[error("trailing bytes in Zakura payload")]
1538    TrailingBytes,
1539
1540    /// A bounded byte string was empty when an identity was required.
1541    #[error("empty Zakura {0}")]
1542    Empty(&'static str),
1543
1544    /// A numeric conversion failed while handling bounded Zakura data.
1545    #[error("numeric overflow while encoding Zakura {0}")]
1546    NumericOverflow(&'static str),
1547}
1548
1549struct PreludeStaticFields<'a> {
1550    magic: [u8; 8],
1551    prelude_version: u16,
1552    network_id: ZakuraNetworkId,
1553    chain_id: [u8; 32],
1554    capabilities: u64,
1555    iroh_node_id: &'a [u8],
1556    iroh_direct_addresses: &'a [Vec<u8>],
1557    iroh_relay_hint: Option<&'a [u8]>,
1558    max_control_frame_bytes: u32,
1559    max_open_streams: u16,
1560}
1561
1562fn validate_prelude_static_fields(
1563    prelude: PreludeStaticFields<'_>,
1564    local: &ZakuraHandshakeConfig,
1565) -> Result<(), ZakuraRejectReason> {
1566    if prelude.magic != PRELUDE_MAGIC || prelude.prelude_version != local.prelude_version {
1567        return Err(ZakuraRejectReason::UnsupportedPreludeVersion);
1568    }
1569    if prelude.network_id != local.network_id {
1570        return Err(ZakuraRejectReason::WrongNetwork);
1571    }
1572    if prelude.chain_id != local.chain_id {
1573        return Err(ZakuraRejectReason::WrongChain);
1574    }
1575    if prelude.capabilities & local.required_capabilities != local.required_capabilities {
1576        return Err(ZakuraRejectReason::MissingRequiredCapability);
1577    }
1578    validate_peer_hints(
1579        prelude.iroh_node_id,
1580        prelude.iroh_direct_addresses,
1581        prelude.iroh_relay_hint,
1582    )
1583    .map_err(|_| ZakuraRejectReason::ResourceLimit)?;
1584    validate_resource_limits(
1585        prelude.max_control_frame_bytes,
1586        prelude.max_open_streams,
1587        local,
1588    )?;
1589    Ok(())
1590}
1591
1592fn validate_peer_nonce_labels(
1593    peer_local_zebra_nonce: Nonce,
1594    peer_remote_zebra_nonce: Nonce,
1595    local_observed: ZakuraLegacyNonces,
1596) -> Result<(), ZakuraRejectReason> {
1597    if peer_local_zebra_nonce != local_observed.remote_zebra_nonce
1598        || peer_remote_zebra_nonce != local_observed.local_zebra_nonce
1599    {
1600        return Err(ZakuraRejectReason::TemporaryUnavailable);
1601    }
1602    Ok(())
1603}
1604
1605fn validate_peer_hints(
1606    iroh_node_id: &[u8],
1607    iroh_direct_addresses: &[Vec<u8>],
1608    iroh_relay_hint: Option<&[u8]>,
1609) -> Result<(), ZakuraProtocolError> {
1610    validate_bounded_bytes(iroh_node_id, MAX_IROH_NODE_ID_BYTES, "iroh node id")?;
1611    if iroh_direct_addresses.len() > MAX_IROH_DIRECT_ADDRESSES {
1612        return Err(ZakuraProtocolError::OversizedPayload {
1613            actual: iroh_direct_addresses.len(),
1614            max: MAX_IROH_DIRECT_ADDRESSES,
1615        });
1616    }
1617    for address in iroh_direct_addresses {
1618        validate_bounded_bytes(address, MAX_IROH_DIRECT_ADDRESS_BYTES, "direct address")?;
1619    }
1620    if let Some(relay_hint) = iroh_relay_hint {
1621        validate_bounded_bytes(relay_hint, MAX_IROH_RELAY_HINT_BYTES, "relay hint")?;
1622    }
1623    Ok(())
1624}
1625
1626fn validate_resource_limits(
1627    max_control_frame_bytes: u32,
1628    max_open_streams: u16,
1629    local: &ZakuraHandshakeConfig,
1630) -> Result<(), ZakuraRejectReason> {
1631    if max_control_frame_bytes == 0
1632        || max_control_frame_bytes > local.max_control_frame_bytes
1633        || max_open_streams == 0
1634        || max_open_streams > local.max_open_streams
1635    {
1636        return Err(ZakuraRejectReason::ResourceLimit);
1637    }
1638    Ok(())
1639}
1640
1641fn validate_initial_limits(
1642    limits: ZakuraInitialLimits,
1643    local: &ZakuraHandshakeConfig,
1644) -> Result<(), ZakuraRejectReason> {
1645    // This negotiated ceiling is wider than most stream kinds need; per-kind
1646    // frame handling applies the effective cap before payload allocation.
1647    if limits.max_frame_bytes == 0
1648        || limits.max_frame_bytes > local.max_message_bytes
1649        || limits.max_message_bytes == 0
1650        || limits.max_message_bytes > local.max_message_bytes
1651        || limits.max_open_streams == 0
1652        || limits.max_open_streams > local.max_open_streams
1653        || limits.max_inbound_queue_depth == 0
1654        || limits.max_inbound_queue_depth > local.max_inbound_queue_depth
1655        || limits.idle_timeout_millis == 0
1656        || limits.idle_timeout_millis > local.max_idle_timeout_millis
1657    {
1658        return Err(ZakuraRejectReason::ResourceLimit);
1659    }
1660    Ok(())
1661}
1662
1663impl ZakuraLimits {
1664    fn encode_to<W: Write>(&self, writer: &mut W) -> Result<(), ZakuraProtocolError> {
1665        writer.write_u32::<LittleEndian>(self.max_frame_bytes)?;
1666        writer.write_u32::<LittleEndian>(self.max_message_bytes)?;
1667        writer.write_u16::<LittleEndian>(self.max_open_streams)?;
1668        writer.write_u16::<LittleEndian>(self.max_inbound_queue_depth)?;
1669        writer.write_u32::<LittleEndian>(self.idle_timeout_millis)?;
1670        Ok(())
1671    }
1672
1673    fn decode_from<R: Read>(reader: &mut R) -> Result<Self, ZakuraProtocolError> {
1674        Ok(Self {
1675            max_frame_bytes: reader.read_u32::<LittleEndian>()?,
1676            max_message_bytes: reader.read_u32::<LittleEndian>()?,
1677            max_open_streams: reader.read_u16::<LittleEndian>()?,
1678            max_inbound_queue_depth: reader.read_u16::<LittleEndian>()?,
1679            idle_timeout_millis: reader.read_u32::<LittleEndian>()?,
1680        })
1681    }
1682}
1683
1684fn write_bounded_bytes<W: Write>(
1685    writer: &mut W,
1686    bytes: &[u8],
1687    max_len: usize,
1688) -> Result<(), ZakuraProtocolError> {
1689    validate_bounded_bytes(bytes, max_len, "byte string")?;
1690    writer.write_u16::<LittleEndian>(
1691        u16::try_from(bytes.len())
1692            .map_err(|_| ZakuraProtocolError::NumericOverflow("byte string length"))?,
1693    )?;
1694    writer.write_all(bytes)?;
1695    Ok(())
1696}
1697
1698fn write_optional_bounded_bytes<W: Write>(
1699    writer: &mut W,
1700    bytes: Option<&[u8]>,
1701    max_len: usize,
1702) -> Result<(), ZakuraProtocolError> {
1703    match bytes {
1704        Some(bytes) => {
1705            writer.write_u8(1)?;
1706            write_bounded_bytes(writer, bytes, max_len)?;
1707        }
1708        None => writer.write_u8(0)?,
1709    }
1710    Ok(())
1711}
1712
1713fn write_bounded_bytes_list<W: Write>(
1714    writer: &mut W,
1715    items: &[Vec<u8>],
1716    max_count: usize,
1717    max_item_len: usize,
1718) -> Result<(), ZakuraProtocolError> {
1719    if items.len() > max_count {
1720        return Err(ZakuraProtocolError::OversizedPayload {
1721            actual: items.len(),
1722            max: max_count,
1723        });
1724    }
1725    writer.write_u16::<LittleEndian>(
1726        u16::try_from(items.len())
1727            .map_err(|_| ZakuraProtocolError::NumericOverflow("list count"))?,
1728    )?;
1729    for item in items {
1730        write_bounded_bytes(writer, item, max_item_len)?;
1731    }
1732    Ok(())
1733}
1734
1735fn read_bounded_bytes<R: Read>(
1736    reader: &mut R,
1737    max_len: usize,
1738) -> Result<Vec<u8>, ZakuraProtocolError> {
1739    let len = usize::from(reader.read_u16::<LittleEndian>()?);
1740    if len > max_len {
1741        return Err(ZakuraProtocolError::OversizedPayload {
1742            actual: len,
1743            max: max_len,
1744        });
1745    }
1746    read_exact_vec(reader, len)
1747}
1748
1749fn read_optional_bounded_bytes<R: Read>(
1750    reader: &mut R,
1751    max_len: usize,
1752) -> Result<Option<Vec<u8>>, ZakuraProtocolError> {
1753    match reader.read_u8()? {
1754        0 => Ok(None),
1755        1 => Ok(Some(read_bounded_bytes(reader, max_len)?)),
1756        value => Err(ZakuraProtocolError::InvalidFlag(value)),
1757    }
1758}
1759
1760fn read_bounded_bytes_list<R: Read>(
1761    reader: &mut R,
1762    max_count: usize,
1763    max_item_len: usize,
1764) -> Result<Vec<Vec<u8>>, ZakuraProtocolError> {
1765    let count = usize::from(reader.read_u16::<LittleEndian>()?);
1766    if count > max_count {
1767        return Err(ZakuraProtocolError::OversizedPayload {
1768            actual: count,
1769            max: max_count,
1770        });
1771    }
1772    let mut items = Vec::with_capacity(count);
1773    for _ in 0..count {
1774        items.push(read_bounded_bytes(reader, max_item_len)?);
1775    }
1776    Ok(items)
1777}
1778
1779fn read_exact_vec<R: Read>(reader: &mut R, len: usize) -> Result<Vec<u8>, ZakuraProtocolError> {
1780    let mut bytes = vec![0; len];
1781    reader.read_exact(&mut bytes)?;
1782    Ok(bytes)
1783}
1784
1785fn validate_bounded_bytes(
1786    bytes: &[u8],
1787    max_len: usize,
1788    field: &'static str,
1789) -> Result<(), ZakuraProtocolError> {
1790    if bytes.is_empty() {
1791        return Err(ZakuraProtocolError::Empty(field));
1792    }
1793    if bytes.len() > max_len {
1794        return Err(ZakuraProtocolError::OversizedPayload {
1795            actual: bytes.len(),
1796            max: max_len,
1797        });
1798    }
1799    Ok(())
1800}
1801
1802fn reject_trailing(bytes: &[u8], reader: &Cursor<&[u8]>) -> Result<(), ZakuraProtocolError> {
1803    let consumed = usize::try_from(reader.position())
1804        .map_err(|_| ZakuraProtocolError::NumericOverflow("cursor position"))?;
1805    if consumed != bytes.len() {
1806        return Err(ZakuraProtocolError::TrailingBytes);
1807    }
1808    Ok(())
1809}
1810
1811fn usize_from_u32(value: u32, field: &'static str) -> Result<usize, ZakuraProtocolError> {
1812    usize::try_from(value).map_err(|_| ZakuraProtocolError::NumericOverflow(field))
1813}
1814
1815fn u32_from_usize(value: usize, field: &'static str) -> Result<u32, ZakuraProtocolError> {
1816    u32::try_from(value).map_err(|_| ZakuraProtocolError::NumericOverflow(field))
1817}
1818
1819fn write_version_message_to_hash(
1820    state: &mut blake2b_simd::State,
1821    version: &VersionMessage,
1822) -> Result<(), ZakuraProtocolError> {
1823    let mut bytes = Vec::new();
1824    bytes.write_u32::<LittleEndian>(version.version.0)?;
1825    bytes.write_u64::<LittleEndian>(version.services.bits())?;
1826    bytes.write_i64::<LittleEndian>(version.timestamp.timestamp())?;
1827    version.address_recv.zcash_serialize(&mut bytes)?;
1828    version.address_from.zcash_serialize(&mut bytes)?;
1829    bytes.write_u64::<LittleEndian>(version.nonce.0)?;
1830    version.user_agent.zcash_serialize(&mut bytes)?;
1831    bytes.write_u32::<LittleEndian>(version.start_height.0)?;
1832    bytes.write_u8(u8::from(version.relay))?;
1833    state.update(&bytes);
1834    Ok(())
1835}
1836
1837#[cfg(test)]
1838mod tests {
1839    use super::*;
1840
1841    use std::net::SocketAddr;
1842
1843    use chrono::{TimeZone, Utc};
1844    use futures::{SinkExt, StreamExt};
1845    use proptest::prelude::*;
1846    use tokio_util::codec::{FramedRead, FramedWrite};
1847
1848    use crate::protocol::external::{types::*, AddrInVersion, Codec, Message};
1849
1850    fn local_config() -> ZakuraHandshakeConfig {
1851        ZakuraHandshakeConfig::for_network(&Network::Mainnet)
1852    }
1853
1854    fn nonces() -> ZakuraLegacyNonces {
1855        ZakuraLegacyNonces {
1856            local_zebra_nonce: Nonce(10),
1857            remote_zebra_nonce: Nonce(20),
1858        }
1859    }
1860
1861    #[test]
1862    fn peer_id_digest_preserves_native_ids_and_canonicalizes_bounded_legacy_ids() {
1863        let native = ZakuraPeerId::new(vec![7; 32]).expect("the native node ID is valid");
1864        assert_eq!(native.digest(), [7; 32]);
1865
1866        let legacy = ZakuraPeerId::new(vec![1, 2, 3]).expect("the bounded legacy ID is valid");
1867        assert_eq!(legacy.digest(), legacy.digest());
1868        assert_ne!(legacy.digest(), native.digest());
1869    }
1870
1871    fn init() -> P2pV2UpgradeInit {
1872        let local = local_config();
1873        P2pV2UpgradeInit {
1874            magic: PRELUDE_MAGIC,
1875            prelude_version: PRELUDE_VERSION,
1876            zakura_protocol_min: 1,
1877            zakura_protocol_max: 1,
1878            network_id: local.network_id,
1879            chain_id: local.chain_id,
1880            capabilities: 0,
1881            local_zebra_nonce: nonces().remote_zebra_nonce,
1882            remote_zebra_nonce: nonces().local_zebra_nonce,
1883            upgrade_nonce: [1; 32],
1884            iroh_node_id: vec![7; 32],
1885            iroh_direct_addresses: vec![b"127.0.0.1:0".to_vec()],
1886            iroh_relay_hint: None,
1887            max_control_frame_bytes: 1024,
1888            max_open_streams: 8,
1889        }
1890    }
1891
1892    fn accept(init: &P2pV2UpgradeInit) -> P2pV2UpgradeAccept {
1893        let local = local_config();
1894        P2pV2UpgradeAccept {
1895            magic: PRELUDE_MAGIC,
1896            prelude_version: PRELUDE_VERSION,
1897            selected_zakura_protocol: 1,
1898            network_id: local.network_id,
1899            chain_id: local.chain_id,
1900            capabilities: 0,
1901            initiator_upgrade_nonce: init.upgrade_nonce,
1902            responder_upgrade_nonce: [2; 32],
1903            local_zebra_nonce: nonces().remote_zebra_nonce,
1904            remote_zebra_nonce: nonces().local_zebra_nonce,
1905            iroh_node_id: vec![8; 32],
1906            iroh_direct_addresses: vec![b"127.0.0.1:1".to_vec()],
1907            iroh_relay_hint: None,
1908            max_control_frame_bytes: 1024,
1909            max_open_streams: 8,
1910        }
1911    }
1912
1913    #[test]
1914    fn prelude_roundtrip_and_rejects_trailing() {
1915        let upgrade = P2pV2Upgrade::Init(init());
1916        let encoded = upgrade.encode().expect("valid init encodes");
1917        assert_eq!(
1918            P2pV2Upgrade::decode(&encoded).expect("valid init decodes"),
1919            upgrade
1920        );
1921
1922        let mut with_trailing = encoded;
1923        with_trailing.push(0);
1924        assert!(matches!(
1925            P2pV2Upgrade::decode(&with_trailing),
1926            Err(ZakuraProtocolError::TrailingBytes)
1927        ));
1928    }
1929
1930    #[test]
1931    fn prelude_rejects_oversized_inputs_before_decoding() {
1932        let oversized = vec![0; MAX_PRELUDE_PAYLOAD_BYTES + 1];
1933        assert!(matches!(
1934            P2pV2Upgrade::decode(&oversized),
1935            Err(ZakuraProtocolError::OversizedPayload { .. })
1936        ));
1937    }
1938
1939    #[test]
1940    fn init_validation_selects_overlap_and_rejects_bad_ranges() {
1941        let local = local_config();
1942        assert_eq!(init().validate(&local, nonces()), Ok(1));
1943
1944        let mut bad = init();
1945        bad.zakura_protocol_min = 2;
1946        bad.zakura_protocol_max = 3;
1947        assert_eq!(
1948            bad.validate(&local, nonces()),
1949            Err(ZakuraRejectReason::IncompatibleZakuraProtocol)
1950        );
1951    }
1952
1953    #[test]
1954    fn prelude_validation_rejects_network_chain_and_nonce_mismatch() {
1955        let local = local_config();
1956
1957        let mut wrong_network = init();
1958        wrong_network.network_id = ZakuraNetworkId::Testnet;
1959        assert_eq!(
1960            wrong_network.validate(&local, nonces()),
1961            Err(ZakuraRejectReason::WrongNetwork)
1962        );
1963
1964        let mut wrong_chain = init();
1965        wrong_chain.chain_id = [9; 32];
1966        assert_eq!(
1967            wrong_chain.validate(&local, nonces()),
1968            Err(ZakuraRejectReason::WrongChain)
1969        );
1970
1971        let mut wrong_nonce = init();
1972        wrong_nonce.local_zebra_nonce = Nonce(99);
1973        assert_eq!(
1974            wrong_nonce.validate(&local, nonces()),
1975            Err(ZakuraRejectReason::TemporaryUnavailable)
1976        );
1977    }
1978
1979    fn cohort_local(tag: &str) -> ZakuraHandshakeConfig {
1980        ZakuraHandshakeConfig::for_network_with_dev_cohort(&Network::Mainnet, Some(tag))
1981    }
1982
1983    fn cohort_init(tag: &str) -> P2pV2UpgradeInit {
1984        let local = cohort_local(tag);
1985        let mut init = init();
1986        init.network_id = local.network_id;
1987        init.chain_id = local.chain_id;
1988        init
1989    }
1990
1991    #[test]
1992    fn dev_cohort_overlay_scopes_network_and_chain_id() {
1993        let plain = ZakuraHandshakeConfig::for_network(&Network::Mainnet);
1994
1995        // No tag (or an empty tag) is identical to the plain network policy.
1996        assert_eq!(
1997            ZakuraHandshakeConfig::for_network_with_dev_cohort(&Network::Mainnet, None),
1998            plain
1999        );
2000        assert_eq!(
2001            ZakuraHandshakeConfig::for_network_with_dev_cohort(&Network::Mainnet, Some("")),
2002            plain
2003        );
2004
2005        // A tag moves the node onto the private overlay without touching consensus.
2006        let cohort = cohort_local("evan-breaking-change");
2007        assert_eq!(cohort.network_id, ZakuraNetworkId::Configured);
2008        assert_ne!(cohort.chain_id, plain.chain_id);
2009
2010        // Derivation is deterministic and sensitive to the tag.
2011        assert_eq!(
2012            cohort.chain_id,
2013            cohort_local("evan-breaking-change").chain_id
2014        );
2015        assert_ne!(cohort.chain_id, cohort_local("roman-test").chain_id);
2016    }
2017
2018    #[test]
2019    fn derive_dev_chain_id_never_collides_with_genesis() {
2020        let genesis = Network::Mainnet.genesis_hash().0;
2021        for tag in ["", "a", "evan-breaking-change", "roman-test"] {
2022            assert_ne!(derive_dev_chain_id(genesis, tag), genesis);
2023        }
2024        // The genesis hash is mixed in, so the same tag on different networks differs.
2025        let testnet_genesis = Network::new_default_testnet().genesis_hash().0;
2026        assert_ne!(
2027            derive_dev_chain_id(genesis, "shared"),
2028            derive_dev_chain_id(testnet_genesis, "shared"),
2029        );
2030    }
2031
2032    #[test]
2033    fn dev_cohort_prelude_matches_same_tag_and_rejects_others() {
2034        // Same cohort: network id and chain id match, so the prelude validates.
2035        assert_eq!(
2036            cohort_init("alpha").validate(&cohort_local("alpha"), nonces()),
2037            Ok(1)
2038        );
2039
2040        // Different cohort: both are `Configured`, but the chain id differs.
2041        assert_eq!(
2042            cohort_init("beta").validate(&cohort_local("alpha"), nonces()),
2043            Err(ZakuraRejectReason::WrongChain)
2044        );
2045
2046        // A public mainnet peer (network id `Mainnet`) is ignored by a dev node.
2047        assert_eq!(
2048            init().validate(&cohort_local("alpha"), nonces()),
2049            Err(ZakuraRejectReason::WrongNetwork)
2050        );
2051
2052        // And a dev node is ignored by a public mainnet node.
2053        assert_eq!(
2054            cohort_init("alpha").validate(&local_config(), nonces()),
2055            Err(ZakuraRejectReason::WrongNetwork)
2056        );
2057    }
2058
2059    #[test]
2060    fn accept_validation_distinguishes_upgrade_nonce_mismatch() {
2061        let local = local_config();
2062        let init = init();
2063        let mut accept = accept(&init);
2064        accept.initiator_upgrade_nonce = [9; 32];
2065
2066        assert_eq!(
2067            accept.validate(&local, nonces(), &init),
2068            Err(ZakuraValidationError::UpgradeNonceMismatch)
2069        );
2070        assert_eq!(
2071            accept
2072                .validate(&local, nonces(), &init)
2073                .unwrap_err()
2074                .failure_class(),
2075            ZakuraFailureClass::PotentiallyPunitive
2076        );
2077    }
2078
2079    #[test]
2080    fn bounded_lists_enforce_maximum_lengths() {
2081        let mut too_many = init();
2082        too_many.iroh_direct_addresses = vec![b"x".to_vec(); MAX_IROH_DIRECT_ADDRESSES + 1];
2083        assert!(P2pV2Upgrade::Init(too_many).encode().is_err());
2084
2085        let mut too_long = init();
2086        too_long.iroh_direct_addresses = vec![vec![1; MAX_IROH_DIRECT_ADDRESS_BYTES + 1]];
2087        assert!(P2pV2Upgrade::Init(too_long).encode().is_err());
2088    }
2089
2090    proptest! {
2091        #[test]
2092        fn arbitrary_prelude_decode_never_panics(
2093            bytes in prop::collection::vec(any::<u8>(), 0..(MAX_PRELUDE_PAYLOAD_BYTES + 8))
2094        ) {
2095            let _ = P2pV2Upgrade::decode(&bytes);
2096        }
2097
2098        #[test]
2099        fn arbitrary_control_hello_decode_never_panics(
2100            bytes in prop::collection::vec(any::<u8>(), 0..2048usize)
2101        ) {
2102            let _ = ZakuraControlHello::decode(&bytes);
2103        }
2104
2105        #[test]
2106        fn arbitrary_control_ack_decode_never_panics(
2107            bytes in prop::collection::vec(any::<u8>(), 0..512usize)
2108        ) {
2109            let _ = ZakuraControlAck::decode(&bytes);
2110        }
2111
2112        #[test]
2113        fn arbitrary_stream_prelude_decode_never_panics(
2114            bytes in prop::collection::vec(any::<u8>(), 0..64usize)
2115        ) {
2116            let _ = StreamPrelude::decode(&bytes);
2117        }
2118
2119        #[test]
2120        fn arbitrary_frame_decode_never_panics(
2121            bytes in prop::collection::vec(any::<u8>(), 0..512usize),
2122            max_frame_bytes in 0u32..512
2123        ) {
2124            let _ = Frame::decode(&bytes, max_frame_bytes);
2125        }
2126    }
2127
2128    #[test]
2129    fn control_hello_validates_identity_transcript_and_native_zeroes() {
2130        let local = local_config();
2131        let hello = ZakuraControlHello {
2132            magic: CONTROL_HELLO_MAGIC,
2133            control_version: CONTROL_VERSION,
2134            selected_zakura_protocol: 1,
2135            handshake_path: ZakuraHandshakePath::Upgraded,
2136            role: ZakuraControlRole::Initiator,
2137            network_id: local.network_id,
2138            chain_id: local.chain_id,
2139            iroh_node_id: vec![7; 32],
2140            peer_nonce: [3; 32],
2141            initiator_upgrade_nonce: [1; 32],
2142            responder_upgrade_nonce: [2; 32],
2143            legacy_upgrade_transcript: [4; 32],
2144            capabilities: 0,
2145            required_channels: 0,
2146            initial_limits: ZakuraInitialLimits {
2147                max_frame_bytes: 1024,
2148                max_message_bytes: 2048,
2149                max_open_streams: 8,
2150                max_inbound_queue_depth: 8,
2151                idle_timeout_millis: 1000,
2152            },
2153        };
2154        let expected = ZakuraControlValidation {
2155            local: &local,
2156            authenticated_remote_id: &[7; 32],
2157            selected_zakura_protocol: 1,
2158            handshake_path: ZakuraHandshakePath::Upgraded,
2159            remote_role: ZakuraControlRole::Initiator,
2160            initiator_upgrade_nonce: [1; 32],
2161            responder_upgrade_nonce: [2; 32],
2162            legacy_upgrade_transcript: [4; 32],
2163        };
2164
2165        let encoded = hello.encode().expect("valid hello encodes");
2166        let decoded = ZakuraControlHello::decode(&encoded).expect("valid hello decodes");
2167        assert_eq!(decoded.validate(&expected), Ok(()));
2168
2169        let mut wrong_identity = decoded.clone();
2170        wrong_identity.iroh_node_id = vec![8; 32];
2171        assert_eq!(
2172            wrong_identity.validate(&expected),
2173            Err(ZakuraValidationError::IdentityMismatch)
2174        );
2175        assert_eq!(
2176            wrong_identity
2177                .validate(&expected)
2178                .unwrap_err()
2179                .failure_class(),
2180            ZakuraFailureClass::PotentiallyPunitive
2181        );
2182
2183        let mut wrong_transcript = decoded;
2184        wrong_transcript.legacy_upgrade_transcript = [5; 32];
2185        assert_eq!(
2186            wrong_transcript.validate(&expected),
2187            Err(ZakuraValidationError::TranscriptMismatch)
2188        );
2189        assert_eq!(
2190            wrong_transcript
2191                .validate(&expected)
2192                .unwrap_err()
2193                .failure_class(),
2194            ZakuraFailureClass::PotentiallyPunitive
2195        );
2196
2197        let native = ZakuraControlHello {
2198            handshake_path: ZakuraHandshakePath::Native,
2199            initiator_upgrade_nonce: [0; 32],
2200            responder_upgrade_nonce: [0; 32],
2201            legacy_upgrade_transcript: [0; 32],
2202            ..hello
2203        };
2204        let native_expected = ZakuraControlValidation {
2205            handshake_path: ZakuraHandshakePath::Native,
2206            initiator_upgrade_nonce: [0; 32],
2207            responder_upgrade_nonce: [0; 32],
2208            legacy_upgrade_transcript: [0; 32],
2209            ..expected
2210        };
2211        assert_eq!(native.validate(&native_expected), Ok(()));
2212    }
2213
2214    #[test]
2215    fn control_ack_echoes_exact_peer_nonces() {
2216        let ack = ZakuraControlAck {
2217            magic: CONTROL_ACK_MAGIC,
2218            control_version: CONTROL_VERSION,
2219            selected_zakura_protocol: 1,
2220            peer_nonce: [2; 32],
2221            remote_peer_nonce: [1; 32],
2222            accepted_capabilities: 0,
2223            accepted_channels: 0,
2224            accepted_limits: ZakuraAcceptedLimits {
2225                max_frame_bytes: 1024,
2226                max_message_bytes: 2048,
2227                max_open_streams: 8,
2228                max_inbound_queue_depth: 8,
2229                idle_timeout_millis: 1000,
2230            },
2231        };
2232        let requested_limits = ack.accepted_limits;
2233        let local = local_config();
2234        assert_eq!(
2235            ack.validate(1, [1; 32], [2; 32], &requested_limits, &local),
2236            Ok(())
2237        );
2238        assert_eq!(
2239            ack.validate(1, [9; 32], [2; 32], &requested_limits, &local),
2240            Err(ZakuraValidationError::ControlNonceMismatch)
2241        );
2242
2243        let malicious_ack = ZakuraControlAck {
2244            accepted_limits: ZakuraAcceptedLimits {
2245                max_frame_bytes: requested_limits.max_frame_bytes + 1,
2246                ..requested_limits
2247            },
2248            ..ack
2249        };
2250        assert_eq!(
2251            malicious_ack.validate(1, [1; 32], [2; 32], &requested_limits, &local),
2252            Err(ZakuraValidationError::ResourceLimit)
2253        );
2254
2255        // A malicious ack that over-caps any other field is rejected too.
2256        let over_cap_acks = [
2257            ZakuraAcceptedLimits {
2258                max_message_bytes: local.max_message_bytes + 1,
2259                ..requested_limits
2260            },
2261            ZakuraAcceptedLimits {
2262                max_open_streams: local.max_open_streams + 1,
2263                ..requested_limits
2264            },
2265            ZakuraAcceptedLimits {
2266                max_inbound_queue_depth: local.max_inbound_queue_depth + 1,
2267                ..requested_limits
2268            },
2269            ZakuraAcceptedLimits {
2270                idle_timeout_millis: local.max_idle_timeout_millis + 1,
2271                ..requested_limits
2272            },
2273        ];
2274        for accepted_limits in over_cap_acks {
2275            let over_cap_ack = ZakuraControlAck {
2276                accepted_limits,
2277                ..ack.clone()
2278            };
2279            assert_eq!(
2280                over_cap_ack.validate(1, [1; 32], [2; 32], &requested_limits, &local),
2281                Err(ZakuraValidationError::ResourceLimit)
2282            );
2283        }
2284
2285        // A malicious ack with any zero/below-floor limit is rejected.
2286        let zero_acks = [
2287            ZakuraAcceptedLimits {
2288                max_frame_bytes: 0,
2289                ..requested_limits
2290            },
2291            ZakuraAcceptedLimits {
2292                max_message_bytes: 0,
2293                ..requested_limits
2294            },
2295            ZakuraAcceptedLimits {
2296                max_open_streams: 0,
2297                ..requested_limits
2298            },
2299            ZakuraAcceptedLimits {
2300                max_inbound_queue_depth: 0,
2301                ..requested_limits
2302            },
2303            ZakuraAcceptedLimits {
2304                idle_timeout_millis: 0,
2305                ..requested_limits
2306            },
2307        ];
2308        for accepted_limits in zero_acks {
2309            // Grant the full requested cap so the zero floor is the only failure.
2310            let requested = ZakuraInitialLimits {
2311                max_frame_bytes: local.max_control_frame_bytes,
2312                max_message_bytes: local.max_message_bytes,
2313                max_open_streams: local.max_open_streams,
2314                max_inbound_queue_depth: local.max_inbound_queue_depth,
2315                idle_timeout_millis: local.max_idle_timeout_millis,
2316            };
2317            let zero_ack = ZakuraControlAck {
2318                accepted_limits,
2319                ..ack.clone()
2320            };
2321            assert_eq!(
2322                zero_ack.validate(1, [1; 32], [2; 32], &requested, &local),
2323                Err(ZakuraValidationError::ResourceLimit)
2324            );
2325        }
2326    }
2327
2328    #[test]
2329    fn initial_limits_allow_application_frames_above_control_cap() {
2330        let local = local_config();
2331        let limits = ZakuraInitialLimits {
2332            max_frame_bytes: local.max_message_bytes,
2333            max_message_bytes: local.max_message_bytes,
2334            max_open_streams: local.max_open_streams,
2335            max_inbound_queue_depth: local.max_inbound_queue_depth,
2336            idle_timeout_millis: local.max_idle_timeout_millis,
2337        };
2338
2339        assert_eq!(validate_initial_limits(limits, &local), Ok(()));
2340        assert!(limits.max_frame_bytes > local.max_control_frame_bytes);
2341    }
2342
2343    #[test]
2344    fn stream_prelude_and_frame_are_bounded() {
2345        let prelude = StreamPrelude {
2346            magic: STREAM_PRELUDE_MAGIC,
2347            stream_kind: 1,
2348            stream_version: 1,
2349            request_id: Some(10),
2350            max_frame_bytes: 16,
2351        };
2352        let encoded = prelude.encode().expect("stream prelude encodes");
2353        assert_eq!(StreamPrelude::decode(&encoded).unwrap(), prelude);
2354
2355        let frame = Frame {
2356            message_type: 1,
2357            flags: 0,
2358            payload: vec![1; 8],
2359        };
2360        let encoded = frame.encode(16).expect("frame fits");
2361        assert_eq!(Frame::decode(&encoded, 16).unwrap(), frame);
2362
2363        let oversized = Frame {
2364            payload: vec![1; 9],
2365            ..frame
2366        };
2367        assert!(oversized.encode(16).is_err());
2368    }
2369
2370    #[test]
2371    fn pending_upgrade_registry_matches_and_expires_by_peer_id() {
2372        let now = Instant::now();
2373        let peer_id = ZakuraPeerId::new(vec![7; 32]).unwrap();
2374        let pending = PendingUpgrade::new(peer_id.clone(), 1, [1; 32], [2; 32], [3; 32]);
2375        let mut registry = PendingUpgradeRegistry::new(1, Duration::from_secs(1));
2376
2377        registry.insert(now, pending).expect("under cap");
2378        assert_eq!(registry.len(), 1);
2379        assert!(registry
2380            .take(now + Duration::from_millis(500), &peer_id)
2381            .is_some());
2382        assert!(registry.is_empty());
2383
2384        let pending = PendingUpgrade::new(peer_id.clone(), 1, [1; 32], [2; 32], [3; 32]);
2385        registry.insert(now, pending).expect("under cap");
2386        assert!(registry
2387            .take(now + Duration::from_secs(2), &peer_id)
2388            .is_none());
2389    }
2390
2391    #[test]
2392    fn duplicate_supervisor_returns_duplicate_without_replacing_winner() {
2393        let peer_id = ZakuraPeerId::new(vec![7; 32]).unwrap();
2394        let mut supervisor = ZakuraPeerSupervisor::default();
2395
2396        assert!(matches!(
2397            supervisor.register_authenticated(peer_id.clone(), [1; 32]),
2398            AuthenticatedPeerRegistration::Registered
2399        ));
2400        assert!(matches!(
2401            supervisor.register_authenticated(peer_id.clone(), [2; 32]),
2402            AuthenticatedPeerRegistration::Duplicate
2403        ));
2404        assert!(matches!(
2405            supervisor.register_authenticated(peer_id, [0; 32]),
2406            AuthenticatedPeerRegistration::Registered
2407        ));
2408    }
2409
2410    fn version(nonce: Nonce) -> VersionMessage {
2411        let addr: SocketAddr = "127.0.0.1:8233".parse().unwrap();
2412        VersionMessage {
2413            version: Version(1),
2414            services: PeerServices::NODE_NETWORK,
2415            timestamp: Utc::now(),
2416            address_recv: AddrInVersion::new(addr, PeerServices::NODE_NETWORK),
2417            address_from: AddrInVersion::new(addr, PeerServices::NODE_NETWORK),
2418            nonce,
2419            user_agent: "/Zebra:test/".to_string(),
2420            start_height: zakura_chain::block::Height(0),
2421            relay: true,
2422        }
2423    }
2424
2425    fn frozen_version(services: PeerServices, user_agent: &str) -> VersionMessage {
2426        let addr: SocketAddr = "127.0.0.1:8233".parse().unwrap();
2427
2428        VersionMessage {
2429            version: crate::constants::CURRENT_NETWORK_PROTOCOL_VERSION,
2430            services,
2431            timestamp: Utc
2432                .timestamp_opt(1_700_000_000, 0)
2433                .single()
2434                .expect("fixed timestamp is in range"),
2435            address_recv: AddrInVersion::new(addr, PeerServices::NODE_NETWORK),
2436            address_from: AddrInVersion::new(addr, services),
2437            nonce: Nonce(0x0102_0304_0506_0708),
2438            user_agent: user_agent.to_string(),
2439            start_height: zakura_chain::block::Height(1),
2440            relay: true,
2441        }
2442    }
2443
2444    fn frozen_plain_zebra_version() -> VersionMessage {
2445        frozen_version(PeerServices::NODE_NETWORK, "/Zebra:compat/")
2446    }
2447
2448    fn frozen_zakura_version() -> VersionMessage {
2449        frozen_version(
2450            PeerServices::NODE_NETWORK | PeerServices::NODE_P2P_V2,
2451            "/Zakura:7.0.0/Zebra:compat/",
2452        )
2453    }
2454
2455    fn control_hello() -> ZakuraControlHello {
2456        let local = local_config();
2457
2458        ZakuraControlHello {
2459            magic: CONTROL_HELLO_MAGIC,
2460            control_version: CONTROL_VERSION,
2461            selected_zakura_protocol: 1,
2462            handshake_path: ZakuraHandshakePath::Upgraded,
2463            role: ZakuraControlRole::Initiator,
2464            network_id: local.network_id,
2465            chain_id: local.chain_id,
2466            iroh_node_id: vec![7; 32],
2467            peer_nonce: [3; 32],
2468            initiator_upgrade_nonce: [1; 32],
2469            responder_upgrade_nonce: [2; 32],
2470            legacy_upgrade_transcript: [4; 32],
2471            capabilities: 0,
2472            required_channels: 0,
2473            initial_limits: ZakuraInitialLimits {
2474                max_frame_bytes: 1024,
2475                max_message_bytes: 2048,
2476                max_open_streams: 8,
2477                max_inbound_queue_depth: 8,
2478                idle_timeout_millis: 1000,
2479            },
2480        }
2481    }
2482
2483    fn control_ack() -> ZakuraControlAck {
2484        ZakuraControlAck {
2485            magic: CONTROL_ACK_MAGIC,
2486            control_version: CONTROL_VERSION,
2487            selected_zakura_protocol: 1,
2488            peer_nonce: [2; 32],
2489            remote_peer_nonce: [1; 32],
2490            accepted_capabilities: 0,
2491            accepted_channels: 0,
2492            accepted_limits: ZakuraAcceptedLimits {
2493                max_frame_bytes: 1024,
2494                max_message_bytes: 2048,
2495                max_open_streams: 8,
2496                max_inbound_queue_depth: 8,
2497                idle_timeout_millis: 1000,
2498            },
2499        }
2500    }
2501
2502    fn stream_prelude() -> StreamPrelude {
2503        StreamPrelude {
2504            magic: STREAM_PRELUDE_MAGIC,
2505            stream_kind: 1,
2506            stream_version: 1,
2507            request_id: Some(10),
2508            max_frame_bytes: 1024,
2509        }
2510    }
2511
2512    fn frame_sample() -> Frame {
2513        Frame {
2514            message_type: 1,
2515            flags: 0,
2516            payload: b"zakura-compat-v1".to_vec(),
2517        }
2518    }
2519
2520    fn encode_version_message(version: VersionMessage) -> Vec<u8> {
2521        let (rt, _init_guard) = zakura_test::init_async();
2522
2523        rt.block_on(async {
2524            let mut bytes = Vec::new();
2525            {
2526                let mut writer = FramedWrite::new(&mut bytes, Codec::builder().finish());
2527                writer
2528                    .send(Message::Version(version))
2529                    .await
2530                    .expect("frozen version message serializes");
2531            }
2532            bytes
2533        })
2534    }
2535
2536    fn decode_version_message(bytes: &[u8]) -> VersionMessage {
2537        decode_version_message_result(bytes).expect("frozen version vector decodes")
2538    }
2539
2540    #[derive(Clone, Debug)]
2541    enum WireMessage {
2542        Version(VersionMessage),
2543        P2pV2Upgrade(P2pV2Upgrade),
2544        ControlHello(ZakuraControlHello),
2545        ControlAck(ZakuraControlAck),
2546        StreamPrelude(StreamPrelude),
2547        Frame { value: Frame, max_frame_bytes: u32 },
2548    }
2549
2550    impl WireMessage {
2551        fn encode(&self) -> Vec<u8> {
2552            match self {
2553                Self::Version(value) => encode_version_message(value.clone()),
2554                Self::P2pV2Upgrade(value) => value.encode().expect("valid message encodes"),
2555                Self::ControlHello(value) => value.encode().expect("valid message encodes"),
2556                Self::ControlAck(value) => value.encode().expect("valid message encodes"),
2557                Self::StreamPrelude(value) => value.encode().expect("valid message encodes"),
2558                Self::Frame {
2559                    value,
2560                    max_frame_bytes,
2561                } => value
2562                    .encode(*max_frame_bytes)
2563                    .expect("valid message encodes"),
2564            }
2565        }
2566
2567        fn assert_decodes(&self, bytes: &[u8]) {
2568            match self {
2569                Self::Version(value) => assert_eq!(decode_version_message(bytes), *value),
2570                Self::P2pV2Upgrade(value) => {
2571                    assert_eq!(P2pV2Upgrade::decode(bytes).unwrap(), *value)
2572                }
2573                Self::ControlHello(value) => {
2574                    assert_eq!(ZakuraControlHello::decode(bytes).unwrap(), *value)
2575                }
2576                Self::ControlAck(value) => {
2577                    assert_eq!(ZakuraControlAck::decode(bytes).unwrap(), *value)
2578                }
2579                Self::StreamPrelude(value) => {
2580                    assert_eq!(StreamPrelude::decode(bytes).unwrap(), *value)
2581                }
2582                Self::Frame {
2583                    value,
2584                    max_frame_bytes,
2585                } => assert_eq!(Frame::decode(bytes, *max_frame_bytes).unwrap(), *value),
2586            }
2587        }
2588
2589        fn assert_rejects_trailing_bytes(&self) {
2590            let mut bytes = self.encode();
2591            bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc]);
2592
2593            match self {
2594                Self::Version(_) => {}
2595                Self::P2pV2Upgrade(_) => assert!(
2596                    P2pV2Upgrade::decode(&bytes).is_err(),
2597                    "p2pv2up accepted trailing bytes",
2598                ),
2599                Self::ControlHello(_) => assert!(
2600                    ZakuraControlHello::decode(&bytes).is_err(),
2601                    "control hello accepted trailing bytes",
2602                ),
2603                Self::ControlAck(_) => assert!(
2604                    ZakuraControlAck::decode(&bytes).is_err(),
2605                    "control ack accepted trailing bytes",
2606                ),
2607                Self::StreamPrelude(_) => assert!(
2608                    StreamPrelude::decode(&bytes).is_err(),
2609                    "stream prelude accepted trailing bytes",
2610                ),
2611                Self::Frame {
2612                    max_frame_bytes, ..
2613                } => assert!(
2614                    Frame::decode(&bytes, *max_frame_bytes).is_err(),
2615                    "frame accepted trailing bytes",
2616                ),
2617            }
2618        }
2619    }
2620
2621    fn decode_version_message_result(bytes: &[u8]) -> Result<VersionMessage, crate::BoxError> {
2622        let (rt, _init_guard) = zakura_test::init_async();
2623
2624        rt.block_on(async {
2625            let mut reader = FramedRead::new(Cursor::new(bytes), Codec::builder().finish());
2626            match reader
2627                .next()
2628                .await
2629                .ok_or_else(|| -> crate::BoxError { "no message decoded".into() })??
2630            {
2631                Message::Version(version) => Ok(version),
2632                message => Err(format!("unexpected wire message: {message:?}").into()),
2633            }
2634        })
2635    }
2636
2637    fn wire_messages() -> Vec<WireMessage> {
2638        let init = init();
2639        vec![
2640            WireMessage::Version(frozen_plain_zebra_version()),
2641            WireMessage::Version(frozen_zakura_version()),
2642            WireMessage::P2pV2Upgrade(P2pV2Upgrade::Init(init.clone())),
2643            WireMessage::P2pV2Upgrade(P2pV2Upgrade::Accept(accept(&init))),
2644            WireMessage::P2pV2Upgrade(P2pV2Upgrade::Reject(P2pV2UpgradeReject {
2645                magic: PRELUDE_MAGIC,
2646                prelude_version: PRELUDE_VERSION,
2647                reason: ZakuraRejectReason::IncompatibleZakuraProtocol,
2648            })),
2649            WireMessage::ControlHello(control_hello()),
2650            WireMessage::ControlAck(control_ack()),
2651            WireMessage::StreamPrelude(stream_prelude()),
2652            WireMessage::Frame {
2653                value: frame_sample(),
2654                max_frame_bytes: 1024,
2655            },
2656        ]
2657    }
2658
2659    #[test]
2660    fn transcript_hash_binds_preludes() {
2661        let init = init();
2662        let accept = accept(&init);
2663        let hash =
2664            legacy_upgrade_transcript(&version(Nonce(1)), &version(Nonce(2)), &init, &accept)
2665                .expect("valid transcript hashes");
2666
2667        let mut tampered_accept = accept;
2668        tampered_accept.capabilities = 1;
2669        let tampered_hash = legacy_upgrade_transcript(
2670            &version(Nonce(1)),
2671            &version(Nonce(2)),
2672            &init,
2673            &tampered_accept,
2674        )
2675        .expect("valid tampered transcript hashes");
2676
2677        assert_ne!(hash, tampered_hash);
2678    }
2679
2680    #[test]
2681    fn compat_i1_i2_legacy_version_messages_roundtrip() {
2682        let expected = frozen_plain_zebra_version();
2683        let bytes = encode_version_message(expected.clone());
2684
2685        assert_eq!(decode_version_message(&bytes), expected);
2686        assert!(!expected.services.contains(PeerServices::NODE_P2P_V2));
2687        assert!(!expected
2688            .address_from
2689            .untrusted_services()
2690            .contains(PeerServices::NODE_P2P_V2));
2691
2692        let expected = frozen_zakura_version();
2693        let bytes = encode_version_message(expected.clone());
2694
2695        assert_eq!(decode_version_message(&bytes), expected);
2696        assert!(expected.services.contains(PeerServices::NODE_P2P_V2));
2697        assert!(expected
2698            .address_from
2699            .untrusted_services()
2700            .contains(PeerServices::NODE_P2P_V2));
2701        assert!(!expected
2702            .address_recv
2703            .untrusted_services()
2704            .contains(PeerServices::NODE_P2P_V2));
2705    }
2706
2707    #[test]
2708    fn compat_i5_p2pv2up_wire_messages_roundtrip() {
2709        for message in wire_messages()
2710            .into_iter()
2711            .filter(|message| matches!(message, WireMessage::P2pV2Upgrade(_)))
2712        {
2713            let bytes = message.encode();
2714            message.assert_decodes(&bytes);
2715        }
2716    }
2717
2718    #[test]
2719    fn compat_i5_control_and_stream_messages_roundtrip() {
2720        for message in wire_messages().into_iter().filter(|message| {
2721            matches!(
2722                message,
2723                WireMessage::ControlHello(_)
2724                    | WireMessage::ControlAck(_)
2725                    | WireMessage::StreamPrelude(_)
2726                    | WireMessage::Frame { .. }
2727            )
2728        }) {
2729            let bytes = message.encode();
2730            message.assert_decodes(&bytes);
2731        }
2732    }
2733
2734    #[test]
2735    fn compat_i3_v1_zakura_decoders_reject_unknown_trailing_data() {
2736        for message in wire_messages()
2737            .into_iter()
2738            .filter(|message| !matches!(message, WireMessage::Version(_)))
2739        {
2740            message.assert_rejects_trailing_bytes();
2741        }
2742    }
2743
2744    #[test]
2745    fn compat_i3_unknown_service_bits_are_truncated_without_selecting_zakura() {
2746        let unknown_high_bit = 1 << 63;
2747        let services =
2748            PeerServices::from_bits_truncate(PeerServices::NODE_NETWORK.bits() | unknown_high_bit);
2749
2750        assert_eq!(services, PeerServices::NODE_NETWORK);
2751        assert!(!services.contains(PeerServices::NODE_P2P_V2));
2752    }
2753}