[WIP] Version Parsing
This commit is contained in:
parent
8337fa3d8f
commit
907b9f04db
9 changed files with 133 additions and 40 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,2 +1,4 @@
|
||||||
**target/
|
**target/
|
||||||
**Cargo.lock
|
**Cargo.lock
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
|
|
||||||
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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::*;
|
||||||
|
|
|
||||||
|
|
@ -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::*;
|
||||||
|
|
|
||||||
|
|
@ -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")]
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue