From 907b9f04db2a51edc7d294e015022c441bfedd85 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 22 Jun 2026 14:39:18 +0200 Subject: [PATCH] [WIP] Version Parsing --- .gitignore | 2 + client/src/lib.rs | 20 +------ codec/src/communication_value.rs | 38 ++++++++++++- crypto/Cargo.toml | 2 +- host/src/lib.rs | 8 +-- registry/src/lib.rs | 5 +- transport/src/lib.rs | 3 +- type-map/build.rs | 95 ++++++++++++++++++++++++++++---- type-map/src/lib.rs | 2 +- 9 files changed, 134 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index 4c1b18f..fdb1a18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ **target/ **Cargo.lock +*.pem +*.key diff --git a/client/src/lib.rs b/client/src/lib.rs index 6bec9e9..b212dca 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -10,9 +10,8 @@ use mtp_transport::{Policy, Receiver, Sender}; * which type maps are available, and the client always advertises * the latest one. */ -pub const PROTOCOL_VERSION: Version = Version(2, 0); +pub use mtp_type_map::PROTOCOL_VERSION; -/* Client configuration. */ pub struct ClientConfig { pub url: String, pub server_cert: Option>, @@ -25,7 +24,6 @@ pub struct MTPConnection { pub receiver: Receiver, } -// High-level MTP client. pub struct MTPClient; impl MTPClient { @@ -42,11 +40,7 @@ impl MTPClient { let (sender, receiver) = mtp_transport::connect(&config.url, config.server_cert, Policy::default()).await?; - /* - * Build the initial identification message with the protocol - * version. Uses only reserved type IDs (0-31), which are - * identical across all protocol versions. - */ + // Build the initial identification message with the protocol version. let version_str = format!("{}", PROTOCOL_VERSION); let ident = CommunicationValue::new(mtp_codec::CommunicationTypeId(15)) .add_data(DataTypeId(3), DataValue::Str(version_str)) @@ -61,13 +55,3 @@ impl MTPClient { }) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn protocol_version_is_compiled_in() { - assert_eq!(PROTOCOL_VERSION, Version(2, 0)); - } -} diff --git a/codec/src/communication_value.rs b/codec/src/communication_value.rs index 26babe9..51b4f0f 100644 --- a/codec/src/communication_value.rs +++ b/codec/src/communication_value.rs @@ -4,7 +4,7 @@ use std::io::{Cursor, Read}; use crate::data_value::DataValue; use crate::rand_u32; -use mtp_type_map::{CommunicationTypeId, DataTypeId}; +use mtp_type_map::{CommunicationType, CommunicationTypeId, DataType, DataTypeId, TypeMap}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct CommunicationValue { @@ -13,6 +13,7 @@ pub struct CommunicationValue { sender: u64, receiver: u64, data: BTreeMap, + type_map: Option, } #[allow(dead_code)] @@ -24,9 +25,27 @@ impl CommunicationValue { sender: 0, receiver: 0, data: BTreeMap::new(), + type_map: None, } } + pub fn from_comm(comm_type: CommunicationType, tm: &TypeMap) -> Self { + let id = comm_type.to_id(tm); + Self { + id: rand_u32(), + comm_type: id, + sender: 0, + receiver: 0, + data: BTreeMap::new(), + type_map: Some(tm.clone()), + } + } + + pub fn from_comm_default(comm_type: CommunicationType) -> Self { + let tm = TypeMap::latest(); + Self::from_comm(comm_type, &tm) + } + pub fn with_id(mut self, p0: u32) -> Self { self.id = p0; self @@ -67,6 +86,20 @@ impl CommunicationValue { self } + pub fn add_typed(mut self, data: DataType, tm: &TypeMap, value: DataValue) -> Self { + self.data.insert(data.to_id(tm), value); + self + } + + pub fn add_typed_default(mut self, data: DataType, value: DataValue) -> Self { + let tm = self + .type_map + .clone() + .unwrap_or_else(TypeMap::latest); + self.data.insert(data.to_id(&tm), value); + self + } + pub fn get_data(&self, data_type: DataTypeId) -> &DataValue { self.data.get(&data_type).unwrap_or(&DataValue::Null) } @@ -193,15 +226,16 @@ impl CommunicationValue { sender, receiver, data, + type_map: None, }) } } +/* ================================ TESTS ================================ */ #[cfg(test)] mod tests { use super::*; use crate::data_value::DataValue; - use mtp_type_map::{CommunicationTypeId, DataTypeId}; fn roundtrip(cv: CommunicationValue) -> CommunicationValue { let bytes = cv.to_bytes(); diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index 9d7dc79..1b37377 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mtp-crypto" -version = "0.2.0" +version = "0.1.0" edition = "2024" [dependencies] diff --git a/host/src/lib.rs b/host/src/lib.rs index f4b314b..f7b6e07 100644 --- a/host/src/lib.rs +++ b/host/src/lib.rs @@ -62,13 +62,11 @@ impl MTPHost { * Extract the client's version from the first message. * The client is expected to send DataType::Version (reserved ID 3) * as a DataValue::Str("X.Y"). + * + * Then negotiate the version for single-version clients */ let client_version = extract_version(&first_msg)?; - /* - * Negotiate; for single-version clients, this is a simple - * membership check. - */ let negotiated = self.registry.negotiate(&[client_version])?; let codec = VersionedCodec::new(self.registry.clone()); @@ -97,7 +95,6 @@ impl MTPHost { * (reserved ID 3) mapping to `DataValue::Str("major.minor")`. */ fn extract_version(msg: &CommunicationValue) -> Option { - /* Reserved type: DataType::Version has fixed wire ID 3 */ let value = msg.get_data(DataTypeId(3)); match value { DataValue::Str(s) => Version::parse(s.as_str()), @@ -105,6 +102,7 @@ fn extract_version(msg: &CommunicationValue) -> Option { } } +/* ================================ TESTS ================================ */ #[cfg(test)] mod tests { use super::*; diff --git a/registry/src/lib.rs b/registry/src/lib.rs index 465ff45..a738684 100644 --- a/registry/src/lib.rs +++ b/registry/src/lib.rs @@ -5,13 +5,11 @@ pub use mtp_type_map::{ }; /* - * Reserved communication-type namespace (0..32). + * 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; - -/* Reserved data-type namespace (0..32). */ pub const INTERNAL_DATA_RESERVED: std::ops::Range = 0..32; /* @@ -88,6 +86,7 @@ impl Default for Registry { } } +/* ================================ TESTS ================================ */ #[cfg(test)] mod tests { use super::*; diff --git a/transport/src/lib.rs b/transport/src/lib.rs index f4be9ee..707e74e 100644 --- a/transport/src/lib.rs +++ b/transport/src/lib.rs @@ -2,8 +2,9 @@ pub mod client; pub mod connection; pub mod connection_handle; -pub use client::connect; pub use connection::{Policy, Receiver, SendMode, Sender}; + +pub use client::connect; pub use connection_handle::ConnectionHandle; #[cfg(feature = "host")] diff --git a/type-map/build.rs b/type-map/build.rs index cfd1c95..c3f47fe 100644 --- a/type-map/build.rs +++ b/type-map/build.rs @@ -151,15 +151,25 @@ const RESERVED_DATA_TYPES: &[ReservedEntry] = &[ ]; fn main() { - let config_path = std::env::var("MTP_TYPE_MAPS").expect( - "MTP_TYPE_MAPS environment variable must be set to the path of a type-maps.yaml file", - ); - - let content = std::fs::read_to_string(&config_path).expect("Failed to read type-maps.yaml"); - - let config: Config = serde_yaml::from_str(&content).expect("Failed to parse type-maps.yaml"); - let out = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()); + + let config = match std::env::var("MTP_TYPE_MAPS") { + Ok(config_path) => { + let content = + std::fs::read_to_string(&config_path).expect("Failed to read type-maps.yaml"); + serde_yaml::from_str(&content).expect("Failed to parse type-maps.yaml") + } + Err(_) => { + eprintln!( + "warning: MTP_TYPE_MAPS not set; generating types with reserved entries only" + ); + Config { + protocol_version: String::new(), + type_maps: BTreeMap::new(), + } + } + }; + let code = generate(&config); std::fs::write(out.join("types.rs"), code).unwrap(); } @@ -187,7 +197,7 @@ fn generate(config: &Config) -> String { let mut out = String::new(); writeln!( out, - "// Auto-generated by type-map/build.rs, they should not be edited manually" + "// Auto-generated by type-map/build.rs, this file should not be edited manually" ) .unwrap(); writeln!(out).unwrap(); @@ -206,15 +216,39 @@ fn generate(config: &Config) -> String { let sorted = sorted_versions(config); + generate_protocol_version(&mut out, config); generate_comm_type_enum(&mut out, &all_comm_names); generate_data_type_enum(&mut out, &all_data_names); generate_type_map_constructors(&mut out, &sorted); generate_lookup_methods(&mut out, config, &sorted); generate_builtin_type_maps(&mut out, &sorted); + generate_enum_conversion_methods(&mut out, &sorted); out } +fn generate_protocol_version(out: &mut String, config: &Config) { + let (major, minor) = if config.protocol_version.is_empty() { + (0, 0) + } else { + let parts: Vec<&str> = config.protocol_version.split('.').collect(); + if parts.len() == 2 { + let maj = parts[0].parse::().unwrap_or(0); + let min = parts[1].parse::().unwrap_or(0); + (maj, min) + } else { + (0, 0) + } + }; + writeln!( + out, + "pub const PROTOCOL_VERSION: Version = Version({}, {});", + major, minor + ) + .unwrap(); + writeln!(out).unwrap(); +} + fn generate_comm_type_enum(out: &mut String, user_names: &BTreeSet<&str>) { writeln!( out, @@ -343,6 +377,22 @@ fn generate_type_map_constructors(out: &mut String, sorted_versions: &[(String, writeln!(out, "}}").unwrap(); writeln!(out).unwrap(); } + + let (latest_major, latest_minor) = match sorted_versions.last() { + Some((_key, major, minor)) => (*major, *minor), + None => (0, 0), + }; + writeln!(out, "impl TypeMap {{").unwrap(); + writeln!(out, " pub fn latest() -> Self {{").unwrap(); + writeln!( + out, + " Self {{ version: Version({}, {}) }}", + latest_major, latest_minor + ) + .unwrap(); + writeln!(out, " }}").unwrap(); + writeln!(out, "}}").unwrap(); + writeln!(out).unwrap(); } fn generate_lookup_methods( @@ -351,7 +401,7 @@ fn generate_lookup_methods( sorted_versions: &[(String, u16, u16)], ) { // comm_id_enum - writeln!(out, "#[allow(unreachable_patterns)]").unwrap(); + writeln!(out, "#[allow(unreachable_patterns, unused_variables)]").unwrap(); writeln!(out, "impl TypeMap {{").unwrap(); writeln!( out, @@ -392,6 +442,7 @@ fn generate_lookup_methods( writeln!(out).unwrap(); // data_id_enum + writeln!(out, " #[allow(unused_variables)]").unwrap(); writeln!( out, " pub fn data_id_enum(&self, dt: DataType) -> Option {{" @@ -438,3 +489,27 @@ fn generate_builtin_type_maps(out: &mut String, sorted_versions: &[(String, u16, writeln!(out, "}}").unwrap(); writeln!(out).unwrap(); } + +fn generate_enum_conversion_methods(out: &mut String, _sorted_versions: &[(String, u16, u16)]) { + writeln!(out, "impl CommunicationType {{").unwrap(); + writeln!(out, " pub fn to_id(self, tm: &TypeMap) -> CommunicationTypeId {{").unwrap(); + writeln!( + out, + " CommunicationTypeId(tm.comm_id_enum(self).expect(\"CommunicationType not found in this TypeMap version\"))" + ) + .unwrap(); + writeln!(out, " }}").unwrap(); + writeln!(out, "}}").unwrap(); + writeln!(out).unwrap(); + + writeln!(out, "impl DataType {{").unwrap(); + writeln!(out, " pub fn to_id(self, tm: &TypeMap) -> DataTypeId {{").unwrap(); + writeln!( + out, + " DataTypeId(tm.data_id_enum(self).expect(\"DataType not found in this TypeMap version\"))" + ) + .unwrap(); + writeln!(out, " }}").unwrap(); + writeln!(out, "}}").unwrap(); + writeln!(out).unwrap(); +} diff --git a/type-map/src/lib.rs b/type-map/src/lib.rs index e61e445..3173de2 100644 --- a/type-map/src/lib.rs +++ b/type-map/src/lib.rs @@ -31,7 +31,7 @@ impl std::fmt::Display for Version { * Type-to-wire-ID mappings are generated at compile time and dispatched * via `comm_id_enum` / `data_id_enum` based on `self.version`. */ -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct TypeMap { pub version: Version, }