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