1use std::collections::HashMap;
25use std::fmt;
26
27#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub struct ProtocolVersion {
37 pub major: u32,
38 pub minor: u32,
39 pub patch: u32,
40}
41
42impl ProtocolVersion {
43 pub fn new(major: u32, minor: u32, patch: u32) -> Self {
45 Self {
46 major,
47 minor,
48 patch,
49 }
50 }
51
52 pub fn parse(s: &str) -> Option<Self> {
57 let parts: Vec<&str> = s.split('.').collect();
58 if parts.len() != 3 {
59 return None;
60 }
61 let major = parts[0].parse::<u32>().ok()?;
62 let minor = parts[1].parse::<u32>().ok()?;
63 let patch = parts[2].parse::<u32>().ok()?;
64 Some(Self::new(major, minor, patch))
65 }
66
67 pub fn is_compatible_with(&self, other: &Self, min_compatible: &Self) -> CompatibilityLevel {
78 if other < min_compatible {
79 return CompatibilityLevel::Incompatible;
80 }
81 if other == self {
82 CompatibilityLevel::FullyCompatible
83 } else if other < self {
84 CompatibilityLevel::BackwardCompatible
85 } else {
86 CompatibilityLevel::ForwardCompatible
87 }
88 }
89
90 pub fn to_string_repr(&self) -> String {
92 format!("{}.{}.{}", self.major, self.minor, self.patch)
93 }
94}
95
96impl fmt::Display for ProtocolVersion {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
99 }
100}
101
102#[derive(Debug, Clone, PartialEq, Eq)]
109pub enum CompatibilityLevel {
110 FullyCompatible,
112 BackwardCompatible,
115 ForwardCompatible,
118 Incompatible,
121}
122
123#[derive(Debug, Clone)]
133pub struct ProtocolDescriptor {
134 pub name: String,
136 pub version: ProtocolVersion,
138 pub min_compatible: ProtocolVersion,
140 pub features: Vec<String>,
142}
143
144impl ProtocolDescriptor {
145 pub fn new(
147 name: impl Into<String>,
148 version: ProtocolVersion,
149 min_compatible: ProtocolVersion,
150 features: Vec<String>,
151 ) -> Self {
152 Self {
153 name: name.into(),
154 version,
155 min_compatible,
156 features,
157 }
158 }
159}
160
161#[derive(Debug, Clone)]
170pub struct NegotiationResult {
171 pub protocol: String,
173 pub agreed_version: ProtocolVersion,
178 pub compatibility: CompatibilityLevel,
180 pub common_features: Vec<String>,
182 pub dropped_features: Vec<String>,
185}
186
187#[derive(Debug, Clone, Default)]
194pub struct VersionStats {
195 pub negotiations: u64,
197 pub successful: u64,
199 pub failed: u64,
201 pub backward_compat: u64,
203 pub forward_compat: u64,
205}
206
207pub struct ProtocolVersionManager {
217 supported: HashMap<String, ProtocolDescriptor>,
218 stats: VersionStats,
219}
220
221impl ProtocolVersionManager {
222 pub fn new() -> Self {
224 Self {
225 supported: HashMap::new(),
226 stats: VersionStats::default(),
227 }
228 }
229
230 pub fn register(&mut self, descriptor: ProtocolDescriptor) -> bool {
235 let is_new = !self.supported.contains_key(&descriptor.name);
236 self.supported.insert(descriptor.name.clone(), descriptor);
237 is_new
238 }
239
240 pub fn negotiate(
259 &mut self,
260 protocol: &str,
261 remote: &ProtocolDescriptor,
262 ) -> Option<NegotiationResult> {
263 self.stats.negotiations += 1;
264
265 let local = match self.supported.get(protocol) {
266 Some(d) => d.clone(),
267 None => {
268 self.stats.failed += 1;
269 return None;
270 }
271 };
272
273 let compat_local_pov = local
275 .version
276 .is_compatible_with(&remote.version, &local.min_compatible);
277
278 if compat_local_pov == CompatibilityLevel::Incompatible {
279 self.stats.failed += 1;
280 return None;
281 }
282
283 let compat_remote_pov = remote
285 .version
286 .is_compatible_with(&local.version, &remote.min_compatible);
287
288 if compat_remote_pov == CompatibilityLevel::Incompatible {
289 self.stats.failed += 1;
290 return None;
291 }
292
293 let agreed_version = if local.version <= remote.version {
295 local.version.clone()
296 } else {
297 remote.version.clone()
298 };
299
300 let common_features = Self::find_common_features(&local.features, &remote.features);
301
302 let dropped_features: Vec<String> = local
303 .features
304 .iter()
305 .filter(|f| !common_features.contains(f))
306 .cloned()
307 .collect();
308
309 match &compat_local_pov {
311 CompatibilityLevel::BackwardCompatible => self.stats.backward_compat += 1,
312 CompatibilityLevel::ForwardCompatible => self.stats.forward_compat += 1,
313 _ => {}
314 }
315 self.stats.successful += 1;
316
317 Some(NegotiationResult {
318 protocol: protocol.to_owned(),
319 agreed_version,
320 compatibility: compat_local_pov,
321 common_features,
322 dropped_features,
323 })
324 }
325
326 pub fn find_common_features(local: &[String], remote: &[String]) -> Vec<String> {
329 let remote_set: std::collections::HashSet<&String> = remote.iter().collect();
330 local
331 .iter()
332 .filter(|f| remote_set.contains(f))
333 .cloned()
334 .collect()
335 }
336
337 pub fn get_descriptor(&self, protocol: &str) -> Option<&ProtocolDescriptor> {
339 self.supported.get(protocol)
340 }
341
342 pub fn is_registered(&self, protocol: &str) -> bool {
344 self.supported.contains_key(protocol)
345 }
346
347 pub fn supported_protocols(&self) -> Vec<&str> {
349 self.supported.keys().map(String::as_str).collect()
350 }
351
352 pub fn stats(&self) -> &VersionStats {
354 &self.stats
355 }
356}
357
358impl Default for ProtocolVersionManager {
359 fn default() -> Self {
360 Self::new()
361 }
362}
363
364#[cfg(test)]
369mod tests {
370 use super::*;
371
372 #[test]
375 fn test_version_new_stores_components() {
376 let v = ProtocolVersion::new(1, 2, 3);
377 assert_eq!(v.major, 1);
378 assert_eq!(v.minor, 2);
379 assert_eq!(v.patch, 3);
380 }
381
382 #[test]
383 fn test_version_display() {
384 let v = ProtocolVersion::new(2, 0, 1);
385 assert_eq!(format!("{}", v), "2.0.1");
386 }
387
388 #[test]
389 fn test_version_to_string_repr() {
390 let v = ProtocolVersion::new(0, 10, 99);
391 assert_eq!(v.to_string_repr(), "0.10.99");
392 }
393
394 #[test]
395 fn test_version_roundtrip() {
396 let original = ProtocolVersion::new(3, 14, 7);
397 let s = original.to_string_repr();
398 let parsed = ProtocolVersion::parse(&s).expect("roundtrip must succeed");
399 assert_eq!(original, parsed);
400 }
401
402 #[test]
405 fn test_parse_valid() {
406 let v = ProtocolVersion::parse("1.2.3").expect("valid parse");
407 assert_eq!(v, ProtocolVersion::new(1, 2, 3));
408 }
409
410 #[test]
411 fn test_parse_zeros() {
412 let v = ProtocolVersion::parse("0.0.0").expect("valid zeros");
413 assert_eq!(v, ProtocolVersion::new(0, 0, 0));
414 }
415
416 #[test]
417 fn test_parse_invalid_not_enough_parts() {
418 assert!(ProtocolVersion::parse("1.2").is_none());
419 }
420
421 #[test]
422 fn test_parse_invalid_too_many_parts() {
423 assert!(ProtocolVersion::parse("1.2.3.4").is_none());
424 }
425
426 #[test]
427 fn test_parse_invalid_non_numeric() {
428 assert!(ProtocolVersion::parse("a.b.c").is_none());
429 }
430
431 #[test]
432 fn test_parse_empty_string() {
433 assert!(ProtocolVersion::parse("").is_none());
434 }
435
436 #[test]
439 fn test_version_ordering_major_dominates() {
440 assert!(ProtocolVersion::new(2, 0, 0) > ProtocolVersion::new(1, 9, 9));
441 }
442
443 #[test]
444 fn test_version_ordering_minor_secondary() {
445 assert!(ProtocolVersion::new(1, 3, 0) > ProtocolVersion::new(1, 2, 99));
446 }
447
448 #[test]
449 fn test_version_ordering_patch_tertiary() {
450 assert!(ProtocolVersion::new(1, 2, 4) > ProtocolVersion::new(1, 2, 3));
451 }
452
453 #[test]
454 fn test_version_equality() {
455 assert_eq!(ProtocolVersion::new(1, 2, 3), ProtocolVersion::new(1, 2, 3));
456 }
457
458 #[test]
461 fn test_compat_fully_compatible() {
462 let local = ProtocolVersion::new(1, 2, 3);
463 let remote = ProtocolVersion::new(1, 2, 3);
464 let min = ProtocolVersion::new(1, 0, 0);
465 assert_eq!(
466 local.is_compatible_with(&remote, &min),
467 CompatibilityLevel::FullyCompatible
468 );
469 }
470
471 #[test]
472 fn test_compat_backward_compatible_remote_older() {
473 let local = ProtocolVersion::new(2, 0, 0);
474 let remote = ProtocolVersion::new(1, 5, 0); let min = ProtocolVersion::new(1, 0, 0);
476 assert_eq!(
477 local.is_compatible_with(&remote, &min),
478 CompatibilityLevel::BackwardCompatible
479 );
480 }
481
482 #[test]
483 fn test_compat_forward_compatible_remote_newer() {
484 let local = ProtocolVersion::new(1, 0, 0);
485 let remote = ProtocolVersion::new(2, 0, 0); let min = ProtocolVersion::new(1, 0, 0);
487 assert_eq!(
488 local.is_compatible_with(&remote, &min),
489 CompatibilityLevel::ForwardCompatible
490 );
491 }
492
493 #[test]
494 fn test_compat_incompatible_below_min() {
495 let local = ProtocolVersion::new(2, 0, 0);
496 let remote = ProtocolVersion::new(0, 5, 0); let min = ProtocolVersion::new(1, 0, 0);
498 assert_eq!(
499 local.is_compatible_with(&remote, &min),
500 CompatibilityLevel::Incompatible
501 );
502 }
503
504 #[test]
505 fn test_compat_exactly_at_min_floor() {
506 let local = ProtocolVersion::new(2, 0, 0);
507 let remote = ProtocolVersion::new(1, 0, 0); let min = ProtocolVersion::new(1, 0, 0);
509 assert_eq!(
511 local.is_compatible_with(&remote, &min),
512 CompatibilityLevel::BackwardCompatible
513 );
514 }
515
516 #[test]
519 fn test_common_features_full_intersection() {
520 let local = vec!["a".to_owned(), "b".to_owned(), "c".to_owned()];
521 let remote = vec!["b".to_owned(), "a".to_owned(), "c".to_owned()];
522 let common = ProtocolVersionManager::find_common_features(&local, &remote);
523 assert_eq!(common, vec!["a", "b", "c"]);
525 }
526
527 #[test]
528 fn test_common_features_partial_intersection() {
529 let local = vec!["x".to_owned(), "y".to_owned(), "z".to_owned()];
530 let remote = vec!["y".to_owned(), "w".to_owned()];
531 let common = ProtocolVersionManager::find_common_features(&local, &remote);
532 assert_eq!(common, vec!["y"]);
533 }
534
535 #[test]
536 fn test_common_features_empty_intersection() {
537 let local = vec!["a".to_owned()];
538 let remote = vec!["b".to_owned()];
539 let common = ProtocolVersionManager::find_common_features(&local, &remote);
540 assert!(common.is_empty());
541 }
542
543 #[test]
544 fn test_common_features_empty_inputs() {
545 let common = ProtocolVersionManager::find_common_features(&[], &[]);
546 assert!(common.is_empty());
547 }
548
549 #[test]
552 fn test_register_new_returns_true() {
553 let mut mgr = ProtocolVersionManager::new();
554 let desc = ProtocolDescriptor::new(
555 "proto/a",
556 ProtocolVersion::new(1, 0, 0),
557 ProtocolVersion::new(1, 0, 0),
558 vec![],
559 );
560 assert!(mgr.register(desc));
561 }
562
563 #[test]
564 fn test_register_duplicate_returns_false() {
565 let mut mgr = ProtocolVersionManager::new();
566 let mk = || {
567 ProtocolDescriptor::new(
568 "proto/a",
569 ProtocolVersion::new(1, 0, 0),
570 ProtocolVersion::new(1, 0, 0),
571 vec![],
572 )
573 };
574 mgr.register(mk());
575 assert!(!mgr.register(mk()));
576 }
577
578 #[test]
579 fn test_is_registered() {
580 let mut mgr = ProtocolVersionManager::new();
581 assert!(!mgr.is_registered("proto/b"));
582 let desc = ProtocolDescriptor::new(
583 "proto/b",
584 ProtocolVersion::new(1, 0, 0),
585 ProtocolVersion::new(1, 0, 0),
586 vec![],
587 );
588 mgr.register(desc);
589 assert!(mgr.is_registered("proto/b"));
590 }
591
592 #[test]
593 fn test_get_descriptor() {
594 let mut mgr = ProtocolVersionManager::new();
595 let desc = ProtocolDescriptor::new(
596 "proto/c",
597 ProtocolVersion::new(2, 1, 0),
598 ProtocolVersion::new(1, 0, 0),
599 vec!["compression".to_owned()],
600 );
601 mgr.register(desc);
602 let retrieved = mgr.get_descriptor("proto/c").expect("must exist");
603 assert_eq!(retrieved.name, "proto/c");
604 assert_eq!(retrieved.version, ProtocolVersion::new(2, 1, 0));
605 }
606
607 #[test]
608 fn test_supported_protocols_lists_names() {
609 let mut mgr = ProtocolVersionManager::new();
610 mgr.register(ProtocolDescriptor::new(
611 "p1",
612 ProtocolVersion::new(1, 0, 0),
613 ProtocolVersion::new(1, 0, 0),
614 vec![],
615 ));
616 mgr.register(ProtocolDescriptor::new(
617 "p2",
618 ProtocolVersion::new(1, 0, 0),
619 ProtocolVersion::new(1, 0, 0),
620 vec![],
621 ));
622 let mut names = mgr.supported_protocols();
623 names.sort();
624 assert_eq!(names, vec!["p1", "p2"]);
625 }
626
627 fn make_mgr_with(
630 name: &str,
631 local_ver: ProtocolVersion,
632 min: ProtocolVersion,
633 features: Vec<String>,
634 ) -> ProtocolVersionManager {
635 let mut mgr = ProtocolVersionManager::new();
636 mgr.register(ProtocolDescriptor::new(name, local_ver, min, features));
637 mgr
638 }
639
640 #[test]
641 fn test_negotiate_unknown_protocol_returns_none() {
642 let mut mgr = ProtocolVersionManager::new();
643 let remote = ProtocolDescriptor::new(
644 "unknown",
645 ProtocolVersion::new(1, 0, 0),
646 ProtocolVersion::new(1, 0, 0),
647 vec![],
648 );
649 assert!(mgr.negotiate("unknown", &remote).is_none());
650 assert_eq!(mgr.stats().failed, 1);
651 assert_eq!(mgr.stats().negotiations, 1);
652 }
653
654 #[test]
655 fn test_negotiate_fully_compatible() {
656 let mut mgr = make_mgr_with(
657 "proto/x",
658 ProtocolVersion::new(1, 0, 0),
659 ProtocolVersion::new(1, 0, 0),
660 vec!["feat-a".to_owned()],
661 );
662 let remote = ProtocolDescriptor::new(
663 "proto/x",
664 ProtocolVersion::new(1, 0, 0),
665 ProtocolVersion::new(1, 0, 0),
666 vec!["feat-a".to_owned()],
667 );
668 let result = mgr.negotiate("proto/x", &remote).expect("should succeed");
669 assert_eq!(result.compatibility, CompatibilityLevel::FullyCompatible);
670 assert_eq!(result.agreed_version, ProtocolVersion::new(1, 0, 0));
671 assert_eq!(result.common_features, vec!["feat-a"]);
672 assert!(result.dropped_features.is_empty());
673 }
674
675 #[test]
676 fn test_negotiate_backward_compat_remote_older() {
677 let mut mgr = make_mgr_with(
678 "proto/y",
679 ProtocolVersion::new(2, 0, 0),
680 ProtocolVersion::new(1, 0, 0),
681 vec!["new-feat".to_owned(), "old-feat".to_owned()],
682 );
683 let remote = ProtocolDescriptor::new(
684 "proto/y",
685 ProtocolVersion::new(1, 5, 0), ProtocolVersion::new(1, 0, 0),
687 vec!["old-feat".to_owned()],
688 );
689 let result = mgr.negotiate("proto/y", &remote).expect("should succeed");
690 assert_eq!(result.compatibility, CompatibilityLevel::BackwardCompatible);
691 assert_eq!(result.agreed_version, ProtocolVersion::new(1, 5, 0));
693 assert_eq!(result.common_features, vec!["old-feat"]);
694 assert_eq!(result.dropped_features, vec!["new-feat"]);
695 assert_eq!(mgr.stats().backward_compat, 1);
696 }
697
698 #[test]
699 fn test_negotiate_forward_compat_remote_newer() {
700 let mut mgr = make_mgr_with(
701 "proto/z",
702 ProtocolVersion::new(1, 0, 0),
703 ProtocolVersion::new(1, 0, 0),
704 vec!["base".to_owned()],
705 );
706 let remote = ProtocolDescriptor::new(
707 "proto/z",
708 ProtocolVersion::new(2, 0, 0), ProtocolVersion::new(1, 0, 0),
710 vec!["base".to_owned(), "advanced".to_owned()],
711 );
712 let result = mgr.negotiate("proto/z", &remote).expect("should succeed");
713 assert_eq!(result.compatibility, CompatibilityLevel::ForwardCompatible);
714 assert_eq!(result.agreed_version, ProtocolVersion::new(1, 0, 0));
716 assert_eq!(result.common_features, vec!["base"]);
717 assert_eq!(mgr.stats().forward_compat, 1);
718 }
719
720 #[test]
721 fn test_negotiate_incompatible_remote_below_local_min() {
722 let mut mgr = make_mgr_with(
723 "proto/q",
724 ProtocolVersion::new(3, 0, 0),
725 ProtocolVersion::new(2, 0, 0), vec![],
727 );
728 let remote = ProtocolDescriptor::new(
729 "proto/q",
730 ProtocolVersion::new(1, 9, 9), ProtocolVersion::new(1, 0, 0),
732 vec![],
733 );
734 assert!(mgr.negotiate("proto/q", &remote).is_none());
735 assert_eq!(mgr.stats().failed, 1);
736 }
737
738 #[test]
739 fn test_negotiate_incompatible_local_below_remote_min() {
740 let mut mgr = make_mgr_with(
742 "proto/r",
743 ProtocolVersion::new(1, 0, 0),
744 ProtocolVersion::new(1, 0, 0),
745 vec![],
746 );
747 let remote = ProtocolDescriptor::new(
748 "proto/r",
749 ProtocolVersion::new(3, 0, 0),
750 ProtocolVersion::new(3, 0, 0), vec![],
752 );
753 assert!(mgr.negotiate("proto/r", &remote).is_none());
754 assert_eq!(mgr.stats().failed, 1);
755 }
756
757 #[test]
758 fn test_negotiate_stats_accumulate() {
759 let mut mgr = make_mgr_with(
760 "proto/s",
761 ProtocolVersion::new(1, 0, 0),
762 ProtocolVersion::new(1, 0, 0),
763 vec![],
764 );
765 let good_remote = ProtocolDescriptor::new(
766 "proto/s",
767 ProtocolVersion::new(1, 0, 0),
768 ProtocolVersion::new(1, 0, 0),
769 vec![],
770 );
771 mgr.negotiate("proto/s", &good_remote);
772 mgr.negotiate("proto/s", &good_remote);
773 mgr.negotiate("unknown", &good_remote);
774
775 let s = mgr.stats();
776 assert_eq!(s.negotiations, 3);
777 assert_eq!(s.successful, 2);
778 assert_eq!(s.failed, 1);
779 }
780
781 #[test]
782 fn test_negotiate_feature_dropping() {
783 let mut mgr = make_mgr_with(
784 "proto/t",
785 ProtocolVersion::new(1, 0, 0),
786 ProtocolVersion::new(1, 0, 0),
787 vec!["alpha".to_owned(), "beta".to_owned(), "gamma".to_owned()],
788 );
789 let remote = ProtocolDescriptor::new(
790 "proto/t",
791 ProtocolVersion::new(1, 0, 0),
792 ProtocolVersion::new(1, 0, 0),
793 vec!["alpha".to_owned(), "gamma".to_owned()], );
795 let result = mgr.negotiate("proto/t", &remote).expect("success");
796 assert_eq!(result.common_features, vec!["alpha", "gamma"]);
797 assert_eq!(result.dropped_features, vec!["beta"]);
798 }
799
800 #[test]
801 fn test_protocol_name_in_result() {
802 let mut mgr = make_mgr_with(
803 "ipfrs/custom",
804 ProtocolVersion::new(1, 0, 0),
805 ProtocolVersion::new(1, 0, 0),
806 vec![],
807 );
808 let remote = ProtocolDescriptor::new(
809 "ipfrs/custom",
810 ProtocolVersion::new(1, 0, 0),
811 ProtocolVersion::new(1, 0, 0),
812 vec![],
813 );
814 let result = mgr.negotiate("ipfrs/custom", &remote).expect("success");
815 assert_eq!(result.protocol, "ipfrs/custom");
816 }
817
818 #[test]
819 fn test_default_manager_has_no_protocols() {
820 let mgr = ProtocolVersionManager::default();
821 assert!(mgr.supported_protocols().is_empty());
822 assert_eq!(mgr.stats().negotiations, 0);
823 }
824}