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}