use std::collections::BTreeMap; pub use mtp_type_map::{ CommunicationType, CommunicationTypeId, DataType, DataTypeId, TypeMap, Version, }; /* * Reserved communication & data-type namespace (0..32). * These are fixed across all protocol versions for version negotiation * and security. They are never assigned by user configuration. */ pub const INTERNAL_COMM_RESERVED: std::ops::Range = 0..32; pub const INTERNAL_DATA_RESERVED: std::ops::Range = 0..32; /* * Multi-version type-map registry. * * Stores one `TypeMap` per protocol version and provides version * negotiation for the host. Hosts build this from the compiled-in * type maps (via `Registry::builtin()` or programmatically). */ #[derive(Clone, Debug)] pub struct Registry { versions: BTreeMap, } impl Registry { pub fn new() -> Self { Self { versions: BTreeMap::new(), } } // Register a type map, keyed by its version. pub fn register(&mut self, typemap: TypeMap) { self.versions.insert(typemap.version.clone(), typemap); } // Look up the type map for an exact version. pub fn get(&self, version: &Version) -> Option<&TypeMap> { self.versions.get(version) } // Check if a specific version is supported. pub fn supports(&self, version: &Version) -> bool { self.versions.contains_key(version) } /* * Negotiate the highest mutually supported version from a list. * * For single-version clients (the typical case) this is equivalent * to checking `supports(&client_versions[0])`. */ pub fn negotiate(&self, client_versions: &[Version]) -> Option { client_versions .iter() .filter(|v| self.versions.contains_key(v)) .max() .cloned() } // Return the latest (highest) registered version's type map. pub fn latest(&self) -> Option<&TypeMap> { self.versions.last_key_value().map(|(_, v)| v) } /* * Build the registry from the compiled-in configuration. * * Uses the `TypeMap::vX_Y()` constructors generated by * `type-map/build.rs` from the YAML config. */ pub fn builtin() -> Self { let mut r = Self::new(); for tm in mtp_type_map::builtin_type_maps() { r.register(tm); } r } } impl Default for Registry { fn default() -> Self { Self::new() } } /* ================================ TESTS ================================ */ #[cfg(test)] mod tests { use super::*; #[test] fn builtin_contains_versions() { let r = Registry::builtin(); // At least the reserved-only fallback should exist assert!(r.supports(&Version(0, 0))); } #[test] fn negotiate_finds_highest() { let mut r = Registry::new(); r.register(TypeMap::new(Version(0, 0))); r.register(TypeMap::new(Version(1, 0))); r.register(TypeMap::new(Version(2, 0))); let client = &[Version(0, 0)]; assert_eq!(r.negotiate(client), Some(Version(0, 0))); let client = &[Version(1, 0), Version(0, 0)]; assert_eq!(r.negotiate(client), Some(Version(1, 0))); let client = &[Version(5, 0)]; assert_eq!(r.negotiate(client), None); } #[test] fn latest_returns_highest() { let mut r = Registry::new(); r.register(TypeMap::new(Version(0, 0))); r.register(TypeMap::new(Version(2, 0))); r.register(TypeMap::new(Version(1, 0))); assert_eq!(r.latest().unwrap().version, Version(2, 0)); } }