Types Creation
This commit is contained in:
parent
94f2280570
commit
8337fa3d8f
22 changed files with 1141 additions and 202 deletions
|
|
@ -5,3 +5,4 @@ edition = "2024"
|
|||
|
||||
[dependencies]
|
||||
mtp-common = { path = "../common" }
|
||||
mtp-type-map = { path = "../type-map" }
|
||||
|
|
|
|||
|
|
@ -1,94 +1,128 @@
|
|||
use mtp_common::RegistryError;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct CommTypeId(pub u16);
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct DataTypeId(pub u16);
|
||||
pub use mtp_type_map::{
|
||||
CommunicationType, CommunicationTypeId, DataType, DataTypeId, TypeMap, Version,
|
||||
};
|
||||
|
||||
/*
|
||||
* # Reserved Internal Namespace
|
||||
* These are NEVER assigned by a RegistryConfig. They are fixed
|
||||
* across all versions for version negotiation & security.
|
||||
* Reserved communication-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<u16> = 0..32;
|
||||
|
||||
pub const INTERNAL_COMM_RESERVED: std::ops::Range<u16> = 0..16;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum InternalCommType {
|
||||
VersionNegotiate = 0,
|
||||
SecurityHandshake = 1,
|
||||
RegistrySync = 2,
|
||||
// 3-15 reserved for future internal use
|
||||
}
|
||||
|
||||
impl InternalCommType {
|
||||
pub fn as_id(self) -> CommTypeId {
|
||||
CommTypeId(self as u16)
|
||||
}
|
||||
}
|
||||
/* Reserved data-type namespace (0..32). */
|
||||
pub const INTERNAL_DATA_RESERVED: std::ops::Range<u16> = 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)]
|
||||
// (major , minor , patch );
|
||||
pub struct Version(pub u16, pub u16, pub u16);
|
||||
|
||||
pub struct Registry {
|
||||
pub version: Version,
|
||||
comm_name_to_id: HashMap<String, u16>,
|
||||
comm_id_to_name: HashMap<u16, String>,
|
||||
data_name_to_id: HashMap<String, u16>,
|
||||
data_id_to_name: HashMap<u16, String>,
|
||||
}
|
||||
|
||||
pub struct RegistryConfig {
|
||||
pub version: Version,
|
||||
pub communication_types: HashMap<String, u16>,
|
||||
pub data_types: HashMap<String, u16>,
|
||||
versions: BTreeMap<Version, TypeMap>,
|
||||
}
|
||||
|
||||
impl Registry {
|
||||
pub fn from_config(cfg: RegistryConfig) -> Result<Self, RegistryError> {
|
||||
for (name, &id) in &cfg.communication_types {
|
||||
if INTERNAL_COMM_RESERVED.contains(&id) {
|
||||
return Err(RegistryError::ReservedCommId(id, name.clone()));
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
versions: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
let mut comm_id_to_name = HashMap::with_capacity(cfg.communication_types.len());
|
||||
for (name, id) in &cfg.communication_types {
|
||||
comm_id_to_name.insert(*id, name.clone());
|
||||
// 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<Version> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
let mut data_id_to_name = HashMap::with_capacity(cfg.data_types.len());
|
||||
for (name, id) in &cfg.data_types {
|
||||
data_id_to_name.insert(*id, name.clone());
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
version: cfg.version,
|
||||
comm_name_to_id: cfg.communication_types,
|
||||
comm_id_to_name,
|
||||
data_name_to_id: cfg.data_types,
|
||||
data_id_to_name,
|
||||
})
|
||||
impl Default for Registry {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
pub fn resolve_comm(&self, name: &str) -> Option<CommTypeId> {
|
||||
self.comm_name_to_id.get(name).copied().map(CommTypeId)
|
||||
#[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)));
|
||||
|
||||
pub fn parse_comm(&self, id: CommTypeId) -> Option<&str> {
|
||||
self.comm_id_to_name.get(&id.0).map(|s| s.as_str())
|
||||
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);
|
||||
}
|
||||
|
||||
pub fn resolve_data(&self, name: &str) -> Option<DataTypeId> {
|
||||
self.data_name_to_id.get(name).copied().map(DataTypeId)
|
||||
}
|
||||
#[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)));
|
||||
|
||||
pub fn parse_data(&self, id: DataTypeId) -> Option<&str> {
|
||||
self.data_id_to_name.get(&id.0).map(|s| s.as_str())
|
||||
assert_eq!(r.latest().unwrap().version, Version(2, 0));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue