[WIP] Version Parsing

This commit is contained in:
Alex Emmet 2026-06-22 14:39:18 +02:00
commit 907b9f04db
9 changed files with 133 additions and 40 deletions

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
**target/ **target/
**Cargo.lock **Cargo.lock
*.pem
*.key

View file

@ -10,9 +10,8 @@ use mtp_transport::{Policy, Receiver, Sender};
* which type maps are available, and the client always advertises * which type maps are available, and the client always advertises
* the latest one. * the latest one.
*/ */
pub const PROTOCOL_VERSION: Version = Version(2, 0); pub use mtp_type_map::PROTOCOL_VERSION;
/* Client configuration. */
pub struct ClientConfig { pub struct ClientConfig {
pub url: String, pub url: String,
pub server_cert: Option<Vec<u8>>, pub server_cert: Option<Vec<u8>>,
@ -25,7 +24,6 @@ pub struct MTPConnection {
pub receiver: Receiver, pub receiver: Receiver,
} }
// High-level MTP client.
pub struct MTPClient; pub struct MTPClient;
impl MTPClient { impl MTPClient {
@ -42,11 +40,7 @@ impl MTPClient {
let (sender, receiver) = let (sender, receiver) =
mtp_transport::connect(&config.url, config.server_cert, Policy::default()).await?; mtp_transport::connect(&config.url, config.server_cert, Policy::default()).await?;
/* // Build the initial identification message with the protocol version.
* Build the initial identification message with the protocol
* version. Uses only reserved type IDs (0-31), which are
* identical across all protocol versions.
*/
let version_str = format!("{}", PROTOCOL_VERSION); let version_str = format!("{}", PROTOCOL_VERSION);
let ident = CommunicationValue::new(mtp_codec::CommunicationTypeId(15)) let ident = CommunicationValue::new(mtp_codec::CommunicationTypeId(15))
.add_data(DataTypeId(3), DataValue::Str(version_str)) .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));
}
}

View file

@ -4,7 +4,7 @@ use std::io::{Cursor, Read};
use crate::data_value::DataValue; use crate::data_value::DataValue;
use crate::rand_u32; use crate::rand_u32;
use mtp_type_map::{CommunicationTypeId, DataTypeId}; use mtp_type_map::{CommunicationType, CommunicationTypeId, DataType, DataTypeId, TypeMap};
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommunicationValue { pub struct CommunicationValue {
@ -13,6 +13,7 @@ pub struct CommunicationValue {
sender: u64, sender: u64,
receiver: u64, receiver: u64,
data: BTreeMap<DataTypeId, DataValue>, data: BTreeMap<DataTypeId, DataValue>,
type_map: Option<TypeMap>,
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -24,9 +25,27 @@ impl CommunicationValue {
sender: 0, sender: 0,
receiver: 0, receiver: 0,
data: BTreeMap::new(), 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 { pub fn with_id(mut self, p0: u32) -> Self {
self.id = p0; self.id = p0;
self self
@ -67,6 +86,20 @@ impl CommunicationValue {
self 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 { pub fn get_data(&self, data_type: DataTypeId) -> &DataValue {
self.data.get(&data_type).unwrap_or(&DataValue::Null) self.data.get(&data_type).unwrap_or(&DataValue::Null)
} }
@ -193,15 +226,16 @@ impl CommunicationValue {
sender, sender,
receiver, receiver,
data, data,
type_map: None,
}) })
} }
} }
/* ================================ TESTS ================================ */
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::data_value::DataValue; use crate::data_value::DataValue;
use mtp_type_map::{CommunicationTypeId, DataTypeId};
fn roundtrip(cv: CommunicationValue) -> CommunicationValue { fn roundtrip(cv: CommunicationValue) -> CommunicationValue {
let bytes = cv.to_bytes(); let bytes = cv.to_bytes();

View file

@ -1,6 +1,6 @@
[package] [package]
name = "mtp-crypto" name = "mtp-crypto"
version = "0.2.0" version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View file

@ -62,13 +62,11 @@ impl MTPHost {
* Extract the client's version from the first message. * Extract the client's version from the first message.
* The client is expected to send DataType::Version (reserved ID 3) * The client is expected to send DataType::Version (reserved ID 3)
* as a DataValue::Str("X.Y"). * as a DataValue::Str("X.Y").
*
* Then negotiate the version for single-version clients
*/ */
let client_version = extract_version(&first_msg)?; 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 negotiated = self.registry.negotiate(&[client_version])?;
let codec = VersionedCodec::new(self.registry.clone()); let codec = VersionedCodec::new(self.registry.clone());
@ -97,7 +95,6 @@ impl MTPHost {
* (reserved ID 3) mapping to `DataValue::Str("major.minor")`. * (reserved ID 3) mapping to `DataValue::Str("major.minor")`.
*/ */
fn extract_version(msg: &CommunicationValue) -> Option<Version> { fn extract_version(msg: &CommunicationValue) -> Option<Version> {
/* Reserved type: DataType::Version has fixed wire ID 3 */
let value = msg.get_data(DataTypeId(3)); let value = msg.get_data(DataTypeId(3));
match value { match value {
DataValue::Str(s) => Version::parse(s.as_str()), DataValue::Str(s) => Version::parse(s.as_str()),
@ -105,6 +102,7 @@ fn extract_version(msg: &CommunicationValue) -> Option<Version> {
} }
} }
/* ================================ TESTS ================================ */
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -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 * These are fixed across all protocol versions for version negotiation
* and security. They are never assigned by user configuration. * 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..32;
/* Reserved data-type namespace (0..32). */
pub const INTERNAL_DATA_RESERVED: std::ops::Range<u16> = 0..32; pub const INTERNAL_DATA_RESERVED: std::ops::Range<u16> = 0..32;
/* /*
@ -88,6 +86,7 @@ impl Default for Registry {
} }
} }
/* ================================ TESTS ================================ */
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -2,8 +2,9 @@ pub mod client;
pub mod connection; pub mod connection;
pub mod connection_handle; pub mod connection_handle;
pub use client::connect;
pub use connection::{Policy, Receiver, SendMode, Sender}; pub use connection::{Policy, Receiver, SendMode, Sender};
pub use client::connect;
pub use connection_handle::ConnectionHandle; pub use connection_handle::ConnectionHandle;
#[cfg(feature = "host")] #[cfg(feature = "host")]

View file

@ -151,15 +151,25 @@ const RESERVED_DATA_TYPES: &[ReservedEntry] = &[
]; ];
fn main() { 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 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); let code = generate(&config);
std::fs::write(out.join("types.rs"), code).unwrap(); std::fs::write(out.join("types.rs"), code).unwrap();
} }
@ -187,7 +197,7 @@ fn generate(config: &Config) -> String {
let mut out = String::new(); let mut out = String::new();
writeln!( writeln!(
out, 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(); .unwrap();
writeln!(out).unwrap(); writeln!(out).unwrap();
@ -206,15 +216,39 @@ fn generate(config: &Config) -> String {
let sorted = sorted_versions(config); let sorted = sorted_versions(config);
generate_protocol_version(&mut out, config);
generate_comm_type_enum(&mut out, &all_comm_names); generate_comm_type_enum(&mut out, &all_comm_names);
generate_data_type_enum(&mut out, &all_data_names); generate_data_type_enum(&mut out, &all_data_names);
generate_type_map_constructors(&mut out, &sorted); generate_type_map_constructors(&mut out, &sorted);
generate_lookup_methods(&mut out, config, &sorted); generate_lookup_methods(&mut out, config, &sorted);
generate_builtin_type_maps(&mut out, &sorted); generate_builtin_type_maps(&mut out, &sorted);
generate_enum_conversion_methods(&mut out, &sorted);
out 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::<u16>().unwrap_or(0);
let min = parts[1].parse::<u16>().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>) { fn generate_comm_type_enum(out: &mut String, user_names: &BTreeSet<&str>) {
writeln!( writeln!(
out, out,
@ -343,6 +377,22 @@ fn generate_type_map_constructors(out: &mut String, sorted_versions: &[(String,
writeln!(out, "}}").unwrap(); writeln!(out, "}}").unwrap();
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( fn generate_lookup_methods(
@ -351,7 +401,7 @@ fn generate_lookup_methods(
sorted_versions: &[(String, u16, u16)], sorted_versions: &[(String, u16, u16)],
) { ) {
// comm_id_enum // comm_id_enum
writeln!(out, "#[allow(unreachable_patterns)]").unwrap(); writeln!(out, "#[allow(unreachable_patterns, unused_variables)]").unwrap();
writeln!(out, "impl TypeMap {{").unwrap(); writeln!(out, "impl TypeMap {{").unwrap();
writeln!( writeln!(
out, out,
@ -392,6 +442,7 @@ fn generate_lookup_methods(
writeln!(out).unwrap(); writeln!(out).unwrap();
// data_id_enum // data_id_enum
writeln!(out, " #[allow(unused_variables)]").unwrap();
writeln!( writeln!(
out, out,
" pub fn data_id_enum(&self, dt: DataType) -> Option<u16> {{" " pub fn data_id_enum(&self, dt: DataType) -> Option<u16> {{"
@ -438,3 +489,27 @@ fn generate_builtin_type_maps(out: &mut String, sorted_versions: &[(String, u16,
writeln!(out, "}}").unwrap(); writeln!(out, "}}").unwrap();
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();
}

View file

@ -31,7 +31,7 @@ impl std::fmt::Display for Version {
* Type-to-wire-ID mappings are generated at compile time and dispatched * Type-to-wire-ID mappings are generated at compile time and dispatched
* via `comm_id_enum` / `data_id_enum` based on `self.version`. * via `comm_id_enum` / `data_id_enum` based on `self.version`.
*/ */
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct TypeMap { pub struct TypeMap {
pub version: Version, pub version: Version,
} }