zoe_wire_protocol/
version.rs

1//! # Protocol Version Negotiation
2//!
3//! This module provides semver-based protocol version negotiation with TLS certificate-embedded
4//! version information. The Zoe protocol supports multiple variants for different use cases:
5//!
6//! - `zoer` - Relay protocol for server-mediated communication
7//! - `zoep` - Peer-to-peer protocol for direct client communication  
8//! - `zoem` - Mesh protocol for distributed network communication
9//!
10//! ## Protocol Negotiation Architecture
11//!
12//! The protocol negotiation system combines **TLS ALPN** with **certificate-embedded versioning**
13//! to provide robust version compatibility checking with clear error reporting.
14//!
15//! ### Key Features
16//!
17//! - **Semantic Versioning**: Full semver compatibility rules (major.minor.patch)
18//! - **Multiple Protocol Variants**: Support for different protocol types
19//! - **Certificate-Embedded Negotiation**: Version information embedded in X.509 extensions
20//! - **Clear Error Reporting**: Specific errors for different failure modes
21//! - **Backward Compatibility**: Graceful handling of version mismatches
22//!
23//! ## ALPN Data Format
24//!
25//! Protocol versions are transmitted as **postcard-serialized `ProtocolVersion` structs**
26//! in ALPN protocol fields, not as text strings. Each ALPN protocol entry contains
27//! a binary-serialized `ProtocolVersion { variant, version }` structure.
28//!
29//! ## Version Compatibility Rules
30//!
31//! - **Major version**: Breaking changes, no backward compatibility
32//! - **Minor version**: New features, backward compatible
33//! - **Patch version**: Bug fixes, fully compatible (not included in ALPN)
34//!
35//! ## Protocol Negotiation Flow
36//!
37//! ### 1. Client Advertises Supported Versions
38//!
39//! ```rust
40//! use zoe_wire_protocol::version::{ClientProtocolConfig, ProtocolVersion, ProtocolVariant};
41//! use semver::Version;
42//!
43//! let client_config = ClientProtocolConfig::new(vec![
44//!     ProtocolVersion::new(ProtocolVariant::V1, Version::new(1, 3, 0)),
45//!     ProtocolVersion::new(ProtocolVariant::V1, Version::new(1, 2, 0)),
46//!     ProtocolVersion::new(ProtocolVariant::V0, Version::new(0, 9, 0)),
47//! ]);
48//!
49//! // Client sends these as postcard-serialized ALPN protocols during TLS handshake
50//! let alpn_protocols: Vec<Vec<u8>> = client_config.alpn_protocols();
51//! // Each Vec<u8> contains postcard::to_stdvec(&protocol_version).unwrap()
52//! ```
53//!
54//! ### 2. Server Negotiates Compatible Version
55//!
56//! ```rust
57//! use zoe_wire_protocol::version::{ServerProtocolConfig, ProtocolVariant};
58//! use semver::VersionReq;
59//!
60//! let server_config = ServerProtocolConfig::new(vec![
61//!     (ProtocolVariant::V1, VersionReq::parse(">=1.2.0").unwrap()),
62//!     (ProtocolVariant::V0, VersionReq::parse(">=0.8.0").unwrap()),
63//! ]);
64//!
65//! // Server finds highest compatible version
66//! if let Some(negotiated) = server_config.negotiate_version(&client_config) {
67//!     println!("✅ Negotiated: {}", negotiated);
68//!     // Version embedded in TLS certificate
69//! } else {
70//!     println!("❌ No compatible version found");
71//!     // Empty certificate extension sent
72//! }
73//! ```
74//!
75//! ### 3. Client Validates Post-Connection
76//!
77//! ```rust
78//! use zoe_wire_protocol::version::{validate_server_protocol_support, ProtocolVersionError};
79//!
80//! match validate_server_protocol_support(&connection, &client_config) {
81//!     Ok(version) => {
82//!         println!("✅ Server supports: {}", version);
83//!     }
84//!     Err(ProtocolVersionError::ProtocolNotSupportedByServer) => {
85//!         println!("❌ Server returned empty protocol extension");
86//!         println!("   This means no client versions are supported by server");
87//!     }
88//!     Err(ProtocolVersionError::ProtocolMismatch) => {
89//!         println!("❌ Server negotiated unsupported version");
90//!     }
91//!     Err(e) => {
92//!         println!("❌ Validation error: {}", e);
93//!     }
94//! }
95//! ```
96//!
97//! ## Error Handling
98//!
99//! The system provides specific error types for different failure scenarios:
100//!
101//! - [`ProtocolVersionError::ProtocolNotSupportedByServer`]: Server returned empty certificate extension
102//! - [`ProtocolVersionError::ProtocolMismatch`]: Version negotiation disagreement
103//! - [`ProtocolVersionError::NoAlpnData`]: Missing certificate or extension data
104//! - [`ProtocolVersionError::InvalidAlpnData`]: Malformed protocol data
105//!
106//! ## Certificate Extension Format
107//!
108//! Protocol versions are embedded in X.509 certificate extensions:
109//!
110//! - **OID**: `1.3.6.1.4.1.99999.1` (Custom enterprise OID)
111//! - **Format**: Postcard-serialized [`ProtocolVersion`] struct
112//! - **Serialization**: `postcard::to_stdvec(&protocol_version)` → `Vec<u8>`
113//! - **Deserialization**: `postcard::from_bytes::<ProtocolVersion>(&bytes)` → `ProtocolVersion`
114//! - **Empty Extension**: `[]` (empty byte array) indicates no compatible protocol found
115//!
116//! ### Binary Format Details
117//!
118//! The postcard format is a compact, deterministic binary serialization:
119//! - **Space-efficient**: Smaller than JSON or other text formats
120//! - **Type-safe**: Preserves Rust type information
121//! - **Deterministic**: Same struct always produces same bytes
122//! - **Schema evolution**: Handles version compatibility gracefully
123//!
124//! ## Integration Examples
125//!
126//! ### Automatic Client Integration
127//!
128//! ```rust
129//! use zoe_client::RelayClient;
130//!
131//! // RelayClient automatically performs protocol validation
132//! match RelayClient::connect(server_addr, server_public_key).await {
133//!     Ok(client) => {
134//!         println!("✅ Connected with compatible protocol");
135//!     }
136//!     Err(ClientError::ProtocolError(msg)) => {
137//!         println!("❌ Protocol incompatibility: {}", msg);
138//!         // Handle version mismatch (upgrade client, contact admin, etc.)
139//!     }
140//!     Err(e) => {
141//!         println!("❌ Connection error: {}", e);
142//!     }
143//! }
144//! ```
145//!
146//! ### Manual Server Setup
147//!
148//! ```rust
149//! use zoe_wire_protocol::connection::server::create_server_endpoint_with_protocols;
150//! use zoe_wire_protocol::version::ServerProtocolConfig;
151//!
152//! let server_config = ServerProtocolConfig::new(vec![
153//!     (ProtocolVariant::V1, VersionReq::parse(">=1.0.0").unwrap()),
154//! ]);
155//!
156//! let server_endpoint = create_server_endpoint_with_protocols(
157//!     "127.0.0.1:0",
158//!     &server_keypair,
159//!     server_config,
160//! ).await?;
161//! ```
162
163use serde::{Deserialize, Serialize};
164use std::fmt;
165
166// Re-export semver types for use by other crates
167pub use semver::{Version, VersionReq};
168
169static DEFAULT_PROTOCOL_VERSION: &str = "0.1.0-dev.0";
170static DEFAULT_PROTOCOL_VERSION_REQ: &str = ">=0.1.0-dev.0";
171
172/// Protocol variants supported by the Zoe wire protocol
173#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
174#[serde(from = "String", into = "String")]
175pub enum ProtocolVariant {
176    /// Relay protocol for server-mediated communication
177    Relay,
178    /// Peer-to-peer protocol for direct client communication
179    PeerToPeer,
180    /// Mesh protocol for distributed network communication  
181    Mesh,
182    /// Not yet known variant
183    Unknown(String),
184}
185
186impl From<String> for ProtocolVariant {
187    fn from(value: String) -> Self {
188        match value.as_str() {
189            "zoer" => ProtocolVariant::Relay,
190            "zoep" => ProtocolVariant::PeerToPeer,
191            "zoem" => ProtocolVariant::Mesh,
192            _ => ProtocolVariant::Unknown(value),
193        }
194    }
195}
196
197impl From<ProtocolVariant> for String {
198    fn from(val: ProtocolVariant) -> Self {
199        match val {
200            ProtocolVariant::Relay => "zoer".to_string(),
201            ProtocolVariant::PeerToPeer => "zoep".to_string(),
202            ProtocolVariant::Mesh => "zoem".to_string(),
203            ProtocolVariant::Unknown(value) => value,
204        }
205    }
206}
207
208impl ProtocolVariant {
209    /// Get all supported protocol variants
210    pub fn all_variants() -> Vec<Self> {
211        vec![
212            ProtocolVariant::Relay,
213            ProtocolVariant::PeerToPeer,
214            ProtocolVariant::Mesh,
215        ]
216    }
217}
218
219impl fmt::Display for ProtocolVariant {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        let name: String = self.clone().into();
222        write!(f, "{name}")
223    }
224}
225
226/// Protocol version information combining variant and semantic version
227#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
228pub struct ProtocolVersion {
229    /// Protocol variant (zoer, zoep, zoem)
230    pub variant: ProtocolVariant,
231    /// Semantic version
232    pub version: Version,
233}
234
235impl ProtocolVersion {
236    /// Create a new protocol version
237    pub fn new(variant: ProtocolVariant, version: Version) -> Self {
238        Self { variant, version }
239    }
240
241    /// Create protocol version from major.minor version numbers
242    pub fn new_simple(variant: ProtocolVariant, major: u64, minor: u64) -> Self {
243        Self {
244            variant,
245            version: Version::new(major, minor, 0),
246        }
247    }
248
249    /// Check if this version is compatible with a requirement
250    pub fn is_compatible_with(&self, req: &VersionReq) -> bool {
251        req.matches(&self.version)
252    }
253}
254
255impl fmt::Display for ProtocolVersion {
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        write!(f, "{} v{}", self.variant, self.version)
258    }
259}
260
261/// Client protocol configuration - defines what versions the client supports
262#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct ClientProtocolConfig(pub(crate) Vec<ProtocolVersion>);
264
265impl ClientProtocolConfig {
266    /// Create a new empty client protocol configuration
267    pub fn new() -> Self {
268        Self(vec![])
269    }
270
271    pub fn from_alpn_data<'a>(
272        alpn_data: impl Iterator<Item = &'a [u8]>,
273    ) -> Result<Self, postcard::Error> {
274        let versions: Vec<ProtocolVersion> = alpn_data
275            .filter_map(|v| postcard::from_bytes(v).ok())
276            .collect();
277        Ok(ClientProtocolConfig(versions))
278    }
279}
280
281/// Server protocol configuration - defines what requirements the server has
282#[derive(Debug, Clone, Serialize, Deserialize)]
283pub struct ServerProtocolConfig {
284    /// Semver requirements for each variant that the server accepts
285    pub variant_requirements: std::collections::BTreeMap<ProtocolVariant, VersionReq>,
286}
287
288impl ClientProtocolConfig {
289    /// Add a current version for a protocol variant
290    pub fn add_current_version(mut self, variant: ProtocolVariant, version: Version) -> Self {
291        let protocol_version = ProtocolVersion::new(variant.clone(), version);
292        self.0.push(protocol_version);
293        self
294    }
295
296    /// Get ALPN protocol identifiers for TLS negotiation
297    pub fn alpn_protocols(&self) -> Vec<Vec<u8>> {
298        self.0
299            .iter()
300            .filter_map(|v| postcard::to_stdvec(v).ok())
301            .collect()
302    }
303
304    /// Get all supported versions as a vector
305    pub fn supported_versions(&self) -> Vec<ProtocolVersion> {
306        self.0.clone()
307    }
308
309    /// Validate that client and server would negotiate to the same version
310    /// This should be called by the client after receiving server's ALPN requirements
311    pub fn validate_negotiation(
312        &self,
313        server_requirements: &ServerProtocolConfig,
314    ) -> Option<ProtocolVersion> {
315        // Use the same negotiate_version logic that the server uses
316        let client_versions = self.supported_versions();
317        server_requirements.negotiate_version(&client_versions)
318    }
319
320    /// Create default client configuration for relay protocol
321    pub fn relay_default() -> Self {
322        Self::new().add_current_version(
323            ProtocolVariant::Relay,
324            Version::parse(DEFAULT_PROTOCOL_VERSION).expect("Protocol version works"),
325        )
326    }
327}
328
329impl ServerProtocolConfig {
330    /// Create a new server protocol configuration
331    pub fn new() -> Self {
332        Self {
333            variant_requirements: std::collections::BTreeMap::new(),
334        }
335    }
336
337    /// Add a semver requirement for a protocol variant
338    pub fn add_variant_requirement(
339        mut self,
340        variant: ProtocolVariant,
341        requirement: VersionReq,
342    ) -> Self {
343        self.variant_requirements.insert(variant, requirement);
344        self
345    }
346
347    /// Get ALPN protocol identifiers for TLS negotiation
348    /// Advertises the server's version requirements as serialized data
349    pub fn alpn_protocols(&self) -> Vec<Vec<u8>> {
350        // Serialize the entire ServerProtocolConfig so clients know what we accept
351        if let Ok(serialized) = postcard::to_stdvec(self) {
352            vec![serialized]
353        } else {
354            vec![]
355        }
356    }
357
358    /// Check if client versions are acceptable and return the best match
359    pub fn negotiate_version(
360        &self,
361        client_versions: &[ProtocolVersion],
362    ) -> Option<ProtocolVersion> {
363        // Check each client version against our requirements
364        for client in client_versions {
365            if let Some(requirement) = self.variant_requirements.get(&client.variant) {
366                if requirement.matches(&client.version) {
367                    return Some(client.clone());
368                }
369            }
370        }
371        None
372    }
373
374    /// Deserialize ServerProtocolConfig from ALPN data
375    pub fn from_alpn_data(alpn_data: &[u8]) -> Result<Self, postcard::Error> {
376        postcard::from_bytes(alpn_data)
377    }
378
379    /// Create default server configuration for relay protocol
380    pub fn relay_default() -> Self {
381        Self::new().add_variant_requirement(
382            ProtocolVariant::Relay,
383            VersionReq::parse(DEFAULT_PROTOCOL_VERSION_REQ).expect("Protocol version works"),
384        )
385    }
386}
387
388impl Default for ClientProtocolConfig {
389    fn default() -> Self {
390        Self::relay_default()
391    }
392}
393
394impl Default for ServerProtocolConfig {
395    fn default() -> Self {
396        Self::relay_default()
397    }
398}
399
400/// Validate that ALPN protocol negotiation succeeded
401/// This should be called by the client after connecting to ensure a protocol was negotiated
402pub fn validate_alpn_negotiation(
403    connection: &quinn::Connection,
404) -> Result<(), ProtocolVersionError> {
405    // Check if ALPN negotiation resulted in a protocol
406    match connection.handshake_data() {
407        Some(handshake_data) => {
408            if let Some(rustls_data) =
409                handshake_data.downcast_ref::<quinn::crypto::rustls::HandshakeData>()
410            {
411                if rustls_data.protocol.is_some() {
412                    // ALPN negotiation succeeded - a protocol was selected
413                    Ok(())
414                } else {
415                    // No protocol was negotiated
416                    Err(ProtocolVersionError::ProtocolMismatch)
417                }
418            } else {
419                // Couldn't access handshake data
420                Err(ProtocolVersionError::NoAlpnData)
421            }
422        }
423        None => {
424            // No handshake data available
425            Err(ProtocolVersionError::NoAlpnData)
426        }
427    }
428}
429
430/// Get the negotiated protocol from ALPN
431/// Returns the raw ALPN protocol bytes that were negotiated
432pub fn get_negotiated_protocol(connection: &quinn::Connection) -> Option<Vec<u8>> {
433    if let Some(handshake_data) = connection.handshake_data() {
434        if let Some(rustls_data) =
435            handshake_data.downcast_ref::<quinn::crypto::rustls::HandshakeData>()
436        {
437            rustls_data.protocol.clone()
438        } else {
439            None
440        }
441    } else {
442        None
443    }
444}
445
446/// Validate protocol compatibility after TLS connection establishment
447///
448/// This function performs post-connection validation to ensure the server supports
449/// the client's protocol versions. It examines the server's TLS certificate for
450/// embedded protocol version information.
451///
452/// ## How It Works
453///
454/// 1. **Extracts server certificate** from the established TLS connection
455/// 2. **Reads protocol extension** (OID: 1.3.6.1.4.1.99999.1) from certificate
456/// 3. **Deserializes protocol version** from the extension data
457/// 4. **Validates compatibility** against client's supported versions
458///
459/// ## Return Values
460///
461/// - `Ok(ProtocolVersion)`: Server supports a compatible protocol version
462/// - `Err(ProtocolNotSupportedByServer)`: Server returned **empty extension** (no compatible versions)
463/// - `Err(ProtocolMismatch)`: Server negotiated a version client doesn't support
464/// - `Err(NoAlpnData)`: Missing certificate or extension data
465/// - `Err(InvalidAlpnData)`: Malformed protocol data in certificate
466///
467/// ## Empty Extension Behavior
468///
469/// When the server cannot find any compatible protocol versions during negotiation,
470/// it returns a certificate with an **empty protocol extension**. This is detected
471/// by the client and results in `ProtocolNotSupportedByServer` error.
472///
473/// This approach provides much better debugging than failing the TLS handshake:
474/// - TLS connection succeeds (can inspect certificates, logs, etc.)
475/// - Clear error message indicates protocol incompatibility
476/// - Distinguishes between TLS issues and protocol version issues
477///
478/// ## Example Usage
479///
480/// ```rust
481/// use zoe_wire_protocol::version::{validate_server_protocol_support, ClientProtocolConfig};
482///
483/// let client_config = ClientProtocolConfig::default();
484/// match validate_server_protocol_support(&connection, &client_config) {
485///     Ok(negotiated_version) => {
486///         println!("✅ Protocol negotiated: {}", negotiated_version);
487///         // Proceed with application protocol
488///     }
489///     Err(ProtocolVersionError::ProtocolNotSupportedByServer) => {
490///         eprintln!("❌ Server doesn't support any of our protocol versions");
491///         eprintln!("   Client versions: {:?}", client_config.supported_versions());
492///         eprintln!("   Consider upgrading client or contacting server admin");
493///     }
494///     Err(e) => {
495///         eprintln!("❌ Protocol validation failed: {}", e);
496///     }
497/// }
498/// ```
499pub fn validate_server_protocol_support(
500    connection: &quinn::Connection,
501    client_config: &ClientProtocolConfig,
502) -> Result<ProtocolVersion, ProtocolVersionError> {
503    // Get the peer certificates
504    let Some(peer_certs) = connection.peer_identity() else {
505        return Err(ProtocolVersionError::NoAlpnData);
506    };
507
508    let Some(rustls_certs) = peer_certs.downcast_ref::<Vec<rustls::pki_types::CertificateDer>>()
509    else {
510        return Err(ProtocolVersionError::NoAlpnData);
511    };
512
513    let Some(cert) = rustls_certs.first() else {
514        return Err(ProtocolVersionError::NoAlpnData);
515    };
516
517    // Extract the protocol version from the certificate
518    // This will contain either the negotiated version or the "no-protocol" marker
519    let cert_protocol_version = extract_protocol_version_from_cert(cert)?;
520    // Check if thi
521    // Verify this version is one the client actually supports
522    let client_versions = client_config.supported_versions();
523    if !client_versions.contains(&cert_protocol_version) {
524        return Err(ProtocolVersionError::ProtocolMismatch);
525    }
526
527    Ok(cert_protocol_version)
528}
529
530/// Extract protocol version from certificate extension
531/// This reads the custom extension that contains the negotiated protocol version
532fn extract_protocol_version_from_cert(
533    cert_der: &rustls::pki_types::CertificateDer,
534) -> Result<ProtocolVersion, ProtocolVersionError> {
535    use x509_parser::prelude::*;
536
537    // Parse the certificate
538    let (_, cert) = X509Certificate::from_der(cert_der.as_ref())
539        .map_err(|_| ProtocolVersionError::InvalidAlpnData)?;
540
541    // Look for our custom extension (OID: 1.3.6.1.4.1.99999.1)
542    let extensions = cert.extensions();
543    let mut found_extension = false;
544    for ext in extensions {
545        if ext.oid.to_string() == "1.3.6.1.4.1.99999.1" {
546            found_extension = true;
547            // Found our protocol version extension
548            if let Ok(protocol_version) = postcard::from_bytes::<ProtocolVersion>(ext.value) {
549                return Ok(protocol_version);
550            }
551        }
552    }
553    if found_extension {
554        // found but not any valid format - empty or invalid
555        Err(ProtocolVersionError::ProtocolNotSupportedByServer)
556    } else {
557        Err(ProtocolVersionError::InvalidAlpnData)
558    }
559}
560
561/// Validate that client and server negotiated to a compatible version
562/// This performs a full validation that both sides would agree on the same version
563pub fn validate_version_compatibility(
564    connection: &quinn::Connection,
565    client_config: &ClientProtocolConfig,
566) -> Result<ProtocolVersion, ProtocolVersionError> {
567    // First check that ALPN negotiation succeeded
568    validate_alpn_negotiation(connection)?;
569
570    // Get the negotiated protocol (should be one of the client's versions)
571    if let Some(negotiated_protocol) = get_negotiated_protocol(connection) {
572        // Try to deserialize as a ProtocolVersion (client's version)
573        if let Ok(negotiated_version) =
574            postcard::from_bytes::<ProtocolVersion>(&negotiated_protocol)
575        {
576            // Verify this version is one the client actually supports
577            let client_versions = client_config.supported_versions();
578            if client_versions.contains(&negotiated_version) {
579                Ok(negotiated_version)
580            } else {
581                Err(ProtocolVersionError::ProtocolMismatch)
582            }
583        } else {
584            Err(ProtocolVersionError::InvalidAlpnData)
585        }
586    } else {
587        Err(ProtocolVersionError::NoAlpnData)
588    }
589}
590
591/// Errors that can occur during protocol version negotiation
592#[derive(Debug, thiserror::Error)]
593pub enum ProtocolVersionError {
594    #[error("No compatible protocol version found")]
595    NoCompatibleVersion,
596    #[error("Protocol version {0} below minimum requirement {1}")]
597    VersionTooOld(Version, Version),
598    #[error("No ALPN data found in connection")]
599    NoAlpnData,
600    #[error("Invalid ALPN data format")]
601    InvalidAlpnData,
602    #[error("Protocol mismatch: client and server could not agree on a protocol version")]
603    ProtocolMismatch,
604    #[error("Protocol not supported by server: server returned empty ALPN list indicating no compatible protocol")]
605    ProtocolNotSupportedByServer,
606}
607
608#[cfg(test)]
609mod tests {
610    use super::*;
611
612    #[test]
613    fn test_client_protocol_config_alpn_protocols() {
614        let client_config = ClientProtocolConfig::new()
615            .add_current_version(ProtocolVariant::Relay, Version::new(1, 0, 0))
616            .add_current_version(ProtocolVariant::PeerToPeer, Version::new(1, 1, 0));
617
618        let alpn_protocols = client_config.alpn_protocols();
619        assert_eq!(alpn_protocols.len(), 2);
620
621        // Verify we can deserialize back to ProtocolVersion
622        // Note: BTreeMap ordering means Relay comes before PeerToPeer
623        let first_version: ProtocolVersion = postcard::from_bytes(&alpn_protocols[0]).unwrap();
624        let second_version: ProtocolVersion = postcard::from_bytes(&alpn_protocols[1]).unwrap();
625
626        // Check that we have both variants (order may vary due to BTreeMap)
627        let variants = [&first_version.variant, &second_version.variant];
628        assert!(variants.contains(&&ProtocolVariant::Relay));
629        assert!(variants.contains(&&ProtocolVariant::PeerToPeer));
630    }
631
632    #[test]
633    fn test_server_protocol_negotiation() {
634        let server_config = ServerProtocolConfig::new().add_variant_requirement(
635            ProtocolVariant::Relay,
636            VersionReq::parse(">=1.0.0").unwrap(),
637        );
638
639        let client_versions = vec![ProtocolVersion::new_simple(ProtocolVariant::Relay, 1, 2)];
640
641        let negotiated = server_config.negotiate_version(&client_versions).unwrap();
642        assert_eq!(negotiated.variant, ProtocolVariant::Relay);
643        // Should use the client's version (1.2) since it meets server requirements
644        assert_eq!(negotiated.version.major, 1);
645        assert_eq!(negotiated.version.minor, 2);
646    }
647
648    #[test]
649    fn test_server_protocol_minimum_version_rejection() {
650        let server_config = ServerProtocolConfig::new().add_variant_requirement(
651            ProtocolVariant::Relay,
652            VersionReq::parse(">=1.5.0").unwrap(),
653        );
654
655        // Client version too old
656        let client_versions = vec![ProtocolVersion::new_simple(ProtocolVariant::Relay, 1, 0)];
657        assert!(server_config.negotiate_version(&client_versions).is_none());
658
659        // Client version meets minimum
660        let client_versions = vec![ProtocolVersion::new_simple(ProtocolVariant::Relay, 1, 6)];
661        assert!(server_config.negotiate_version(&client_versions).is_some());
662    }
663
664    #[test]
665    fn test_default_configurations() {
666        let client_config = ClientProtocolConfig::relay_default();
667        let supported_versions = client_config.supported_versions();
668        assert_eq!(supported_versions.len(), 1);
669        assert_eq!(supported_versions[0].variant, ProtocolVariant::Relay);
670
671        let server_config = ServerProtocolConfig::relay_default();
672        assert_eq!(server_config.variant_requirements.len(), 1);
673        assert!(server_config
674            .variant_requirements
675            .contains_key(&ProtocolVariant::Relay));
676    }
677
678    #[test]
679    fn test_protocol_variant_serialization() {
680        // Test serialization roundtrip
681        let variants = vec![
682            ProtocolVariant::Relay,
683            ProtocolVariant::PeerToPeer,
684            ProtocolVariant::Mesh,
685            ProtocolVariant::Unknown("custom-protocol".to_string()),
686        ];
687
688        for variant in variants {
689            let serialized = postcard::to_stdvec(&variant).unwrap();
690            let deserialized: ProtocolVariant = postcard::from_bytes(&serialized).unwrap();
691            assert_eq!(variant, deserialized);
692        }
693    }
694
695    #[test]
696    fn test_protocol_version_serialization() {
697        let version = ProtocolVersion::new_simple(ProtocolVariant::Relay, 1, 2);
698        let serialized = postcard::to_stdvec(&version).unwrap();
699        let deserialized: ProtocolVersion = postcard::from_bytes(&serialized).unwrap();
700        assert_eq!(version, deserialized);
701    }
702
703    #[test]
704    fn test_server_selects_from_client_versions() {
705        // Server accepts relay 1.0+
706        let server_config = ServerProtocolConfig::new().add_variant_requirement(
707            ProtocolVariant::Relay,
708            VersionReq::parse(">=1.0.0").unwrap(),
709        );
710
711        // Client offers relay 1.0 and p2p 2.0
712        let client_versions = vec![
713            ProtocolVersion::new_simple(ProtocolVariant::Relay, 1, 0),
714            ProtocolVersion::new_simple(ProtocolVariant::PeerToPeer, 2, 0),
715        ];
716
717        // Server should select relay 1.0 (client's version, meets minimum)
718        let negotiated = server_config.negotiate_version(&client_versions).unwrap();
719        assert_eq!(negotiated.variant, ProtocolVariant::Relay);
720        assert_eq!(negotiated.version.major, 1);
721        assert_eq!(negotiated.version.minor, 0);
722    }
723
724    #[test]
725    fn test_client_version_too_old() {
726        // Server requires relay 1.5+
727        let server_config = ServerProtocolConfig::new().add_variant_requirement(
728            ProtocolVariant::Relay,
729            VersionReq::parse(">=1.5.0").unwrap(),
730        );
731
732        // Client only offers relay 1.0
733        let client_versions = vec![ProtocolVersion::new_simple(ProtocolVariant::Relay, 1, 0)];
734
735        // Should fail - client version too old
736        assert!(server_config.negotiate_version(&client_versions).is_none());
737    }
738
739    #[test]
740    fn test_alpn_serialization_roundtrip() {
741        // Test that we can serialize and deserialize protocol configs for ALPN
742        let server_config = ServerProtocolConfig::new()
743            .add_variant_requirement(
744                ProtocolVariant::Relay,
745                VersionReq::parse(">=1.0.0").unwrap(),
746            )
747            .add_variant_requirement(
748                ProtocolVariant::PeerToPeer,
749                VersionReq::parse(">=2.0.0").unwrap(),
750            );
751
752        let client_config = ClientProtocolConfig::new()
753            .add_current_version(ProtocolVariant::Relay, Version::new(1, 2, 0))
754            .add_current_version(ProtocolVariant::PeerToPeer, Version::new(2, 1, 0));
755
756        // Test server ALPN serialization
757        let server_alpn = server_config.alpn_protocols();
758        assert_eq!(server_alpn.len(), 1); // Server sends one serialized config
759
760        let deserialized_server = ServerProtocolConfig::from_alpn_data(&server_alpn[0]).unwrap();
761        assert_eq!(deserialized_server.variant_requirements.len(), 2);
762
763        // Test client ALPN serialization
764        let client_alpn = client_config.alpn_protocols();
765        assert_eq!(client_alpn.len(), 2); // Client sends individual versions
766
767        // Verify client versions can be deserialized
768        for alpn_data in &client_alpn {
769            let version: ProtocolVersion = postcard::from_bytes(alpn_data).unwrap();
770            assert!(matches!(
771                version.variant,
772                ProtocolVariant::Relay | ProtocolVariant::PeerToPeer
773            ));
774        }
775    }
776
777    #[test]
778    fn test_client_validation_logic() {
779        let server_config = ServerProtocolConfig::new().add_variant_requirement(
780            ProtocolVariant::Relay,
781            VersionReq::parse(">=1.0.0").unwrap(),
782        );
783
784        let client_config = ClientProtocolConfig::new()
785            .add_current_version(ProtocolVariant::Relay, Version::new(1, 2, 0));
786
787        // Test that client can validate against server requirements
788        let negotiated = client_config.validate_negotiation(&server_config).unwrap();
789        assert_eq!(negotiated.variant, ProtocolVariant::Relay);
790        assert_eq!(negotiated.version.major, 1);
791        assert_eq!(negotiated.version.minor, 2);
792
793        // Test incompatible case
794        let incompatible_client = ClientProtocolConfig::new()
795            .add_current_version(ProtocolVariant::PeerToPeer, Version::new(1, 0, 0));
796
797        assert!(incompatible_client
798            .validate_negotiation(&server_config)
799            .is_none());
800    }
801}