diff --git a/.gitignore b/.gitignore index fdb1a18..4c1b18f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ **target/ **Cargo.lock -*.pem -*.key diff --git a/TRANSPORT.md b/TRANSPORT.md new file mode 100644 index 0000000..c9599ec --- /dev/null +++ b/TRANSPORT.md @@ -0,0 +1,16 @@ +# Transport + +The Transport crate wraps QUIC using `wtransport`. It provides `Sender`/`Receiver` for bidirectional message passing over QUIC streams. + +```rust +use mtp_transport::{connect, Policy}; + +// Connect as a client +let (sender, receiver) = connect("https://example.com", None, Policy::default()).await?; + +// Send messages +sender.send(&communication_value).await?; + +// Receive messages +let msg = receiver.receive().await?; +``` diff --git a/client/Cargo.toml b/client/Cargo.toml index ee1bbb5..c42f2d7 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] mtp-common = { path = "../common" } +mtp-type-map = { path = "../type-map" } mtp-codec = { path = "../codec" } mtp-transport = { path = "../transport" } tokio = { version = "1", features = ["full"] } diff --git a/client/src/lib.rs b/client/src/lib.rs index 6c87d46..6bec9e9 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1,7 +1,18 @@ -use mtp_codec::{CommunicationValue, DataTypeId, DataValue, Version, PROTOCOL_VERSION}; +use mtp_codec::{CommunicationValue, DataTypeId, DataValue, Version}; use mtp_common::CommunicationError; use mtp_transport::{Policy, Receiver, Sender}; +/* + * The compiled-in protocol version for this client. + * + * This is set by the `protocol_version` field in `type-maps.yaml`. + * At build time, the generated `builtin_type_maps()` determines + * which type maps are available, and the client always advertises + * the latest one. + */ +pub const PROTOCOL_VERSION: Version = Version(2, 0); + +/* Client configuration. */ pub struct ClientConfig { pub url: String, pub server_cert: Option>, @@ -14,6 +25,7 @@ pub struct MTPConnection { pub receiver: Receiver, } +// High-level MTP client. pub struct MTPClient; impl MTPClient { @@ -30,9 +42,13 @@ 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. + /* + * 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 ident = CommunicationValue::new(mtp_codec::CommunicationType::Identification) + let ident = CommunicationValue::new(mtp_codec::CommunicationTypeId(15)) .add_data(DataTypeId(3), DataValue::Str(version_str)) .add_data(DataTypeId(6), DataValue::UnsignedNumber(client_id.into())); @@ -45,3 +61,13 @@ 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/Cargo.toml b/codec/Cargo.toml index 4b75c72..88ca803 100644 --- a/codec/Cargo.toml +++ b/codec/Cargo.toml @@ -14,5 +14,5 @@ rand = { version = "*", features = ["std", "std_rng"] } [features] default = [] -registry = ["dep:registry", "mtp-type-map/multi-version"] +registry = ["dep:registry"] crypto = ["dep:mtp-crypto"] diff --git a/codec/src/communication_value.rs b/codec/src/communication_value.rs index 68bcaf3..26babe9 100644 --- a/codec/src/communication_value.rs +++ b/codec/src/communication_value.rs @@ -1,14 +1,10 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use std::collections::BTreeMap; -use std::fmt; use std::io::{Cursor, Read}; use crate::data_value::DataValue; use crate::rand_u32; -use mtp_type_map::{ - communication_type_name, data_type_name, CommunicationType, CommunicationTypeId, DataType, - DataTypeId, TypeMap, PROTOCOL_VERSION, -}; +use mtp_type_map::{CommunicationTypeId, DataTypeId}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct CommunicationValue { @@ -17,47 +13,17 @@ pub struct CommunicationValue { sender: u64, receiver: u64, data: BTreeMap, - type_map: Option, } #[allow(dead_code)] impl CommunicationValue { - pub fn new(comm_type: CommunicationType) -> Self { - let tm = TypeMap::new(PROTOCOL_VERSION); - let id = comm_type.to_id(&tm); + pub fn new(comm_type: CommunicationTypeId) -> Self { Self { id: rand_u32(), - comm_type: id, + comm_type, sender: 0, receiver: 0, data: BTreeMap::new(), - type_map: Some(tm), - } - } - - #[cfg(feature = "registry")] - 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::new(PROTOCOL_VERSION); - 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), } } @@ -101,18 +67,6 @@ impl CommunicationValue { self } - #[cfg(feature = "registry")] - 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) } @@ -239,89 +193,15 @@ impl CommunicationValue { sender, receiver, data, - type_map: None, }) } } -fn fmt_data_value(val: &DataValue, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match val { - DataValue::Container(entries) => { - write!(f, "{{")?; - for (i, (key, value)) in entries.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - let name = data_type_name(key.0).unwrap_or("?"); - write!(f, "{}: ", name)?; - fmt_data_value(value, f)?; - } - write!(f, "}}") - } - DataValue::Array(arr) => { - write!(f, "[")?; - for (i, value) in arr.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - fmt_data_value(value, f)?; - } - write!(f, "]") - } - #[cfg(feature = "crypto")] - DataValue::EncryptedContainer(_) => write!(f, "(Secure)"), - DataValue::Bytes(_) => write!(f, "(Binary)"), - other => write!(f, "{}", other), - } -} - -const BOLD_BLUE: &str = "\x1b[1;34m"; -const GREEN: &str = "\x1b[32m"; -const YELLOW: &str = "\x1b[33m"; -const ORANGE: &str = "\x1b[38;5;208m"; -const RESET: &str = "\x1b[0m"; - -impl fmt::Display for CommunicationValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let version = self - .type_map - .as_ref() - .map(|tm| &tm.version) - .unwrap_or(&PROTOCOL_VERSION); - - write!(f, "V{}{}{}", BOLD_BLUE, version, RESET)?; - - if self.id != 0 { - write!(f, ", ID:{}{:X}{}", GREEN, self.id, RESET)?; - } - if self.sender != 0 { - write!(f, ", S:{}{:X}{}", YELLOW, self.sender, RESET)?; - } - if self.receiver != 0 { - write!(f, ", R:{}{:X}{}", ORANGE, self.receiver, RESET)?; - } - - let name = communication_type_name(self.comm_type.0).unwrap_or("?"); - write!(f, ", {}: ", name)?; - - write!(f, "{{")?; - for (i, (key, value)) in self.data.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - let dname = data_type_name(key.0).unwrap_or("?"); - write!(f, "{}: ", dname)?; - fmt_data_value(value, f)?; - } - write!(f, "}}") - } -} - -/* ================================ 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(); @@ -333,7 +213,7 @@ mod tests { #[test] fn test_flags_and_order_without_optional() { - let cv = CommunicationValue::from_comm_default(CommunicationType::ErrorParsing).with_id(0); + let cv = CommunicationValue::new(CommunicationTypeId(1)).with_id(0); let bytes = cv.to_bytes(); // [u32 len][u16 type][flags]... @@ -351,7 +231,7 @@ mod tests { #[test] fn test_flags_and_order_with_all_optional() { - let cv = CommunicationValue::from_comm_default(CommunicationType::ErrorBadVersion) + let cv = CommunicationValue::new(CommunicationTypeId(2)) .with_id(0xAABBCCDD) .with_sender(0x0000_1122_3344_5566) .with_receiver(0x0000_6677_8899_AABB); @@ -362,10 +242,10 @@ mod tests { let total_len = c.read_u32::().expect("len"); assert_eq!(total_len as usize + 4, bytes.len()); - let typ = c.read_u16::().expect("read type"); + let typ = c.read_u16::().expect("type"); assert_eq!(typ, 2); - let flags = c.read_u8().expect("read flags"); + let flags = c.read_u8().expect("flags"); assert_eq!(flags & 0b0000_0111, 0b0000_0111); let id = c.read_u32::().expect("id"); @@ -382,7 +262,7 @@ mod tests { #[test] fn test_roundtrip_complex() { - let cv = CommunicationValue::from_comm_default(CommunicationType::Disconnect) + let cv = CommunicationValue::new(CommunicationTypeId(3)) .with_id(1234) .with_sender(111) .with_receiver(222) diff --git a/codec/src/data_value.rs b/codec/src/data_value.rs index af1f361..0099e8b 100644 --- a/codec/src/data_value.rs +++ b/codec/src/data_value.rs @@ -2,7 +2,6 @@ use base64::Engine; use base64::engine::general_purpose; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use std::collections::BTreeMap; -use std::fmt; use std::hash::{Hash, Hasher}; use std::io::Cursor; @@ -636,44 +635,6 @@ impl DataValue { } } -impl fmt::Display for DataValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - DataValue::BoolTrue => write!(f, "true"), - DataValue::BoolFalse => write!(f, "false"), - DataValue::Bool(v) => write!(f, "{}", v), - DataValue::SignedNumber(n) => write!(f, "{}", n), - DataValue::UnsignedNumber(n) => write!(f, "{}", n), - DataValue::Float(exp, mant) => write!(f, "{}e{}", mant, exp), - DataValue::Str(s) => write!(f, "\"{}\"", s), - DataValue::Container(entries) => { - write!(f, "{{")?; - for (i, (key, value)) in entries.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "{}: {}", key.0, value)?; - } - write!(f, "}}") - } - DataValue::Array(arr) => { - write!(f, "[")?; - for (i, value) in arr.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "{}", value)?; - } - write!(f, "]") - } - DataValue::Bytes(_) => write!(f, "(Binary)"), - #[cfg(feature = "crypto")] - DataValue::EncryptedContainer(_) => write!(f, "(Secure)"), - DataValue::Null => write!(f, "null"), - } - } -} - impl PartialEq for DataValue { fn eq(&self, other: &Self) -> bool { use DataValue::*; diff --git a/codec/src/lib.rs b/codec/src/lib.rs index de8d858..6757825 100644 --- a/codec/src/lib.rs +++ b/codec/src/lib.rs @@ -8,8 +8,7 @@ pub use data_value::{DataKind, DataValue}; pub use util::rand_u32; pub use mtp_type_map::{ - communication_type_name, data_type_name, CommunicationType, CommunicationTypeId, DataType, - DataTypeId, PROTOCOL_VERSION, TypeMap, Version, + CommunicationType, CommunicationTypeId, DataType, DataTypeId, TypeMap, Version, }; use mtp_common::CodecError; diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index 1b37377..9d7dc79 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mtp-crypto" -version = "0.1.0" +version = "0.2.0" edition = "2024" [dependencies] diff --git a/example-type-maps.yaml b/example-type-maps.yaml index 0a78ca6..8ec285b 100644 --- a/example-type-maps.yaml +++ b/example-type-maps.yaml @@ -1,5 +1,5 @@ # The version a Client should use -protocol_version: "0.0" +protocol_version: "2.0" # Note that markers 0 to 31 are reserved for default use, manually working with them is not recommended # Fixed CommunicationType markers are: diff --git a/example-useage/.cargo/config.toml b/example-useage/.cargo/config.toml deleted file mode 100644 index d363b83..0000000 --- a/example-useage/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[env] -MTP_TYPE_MAPS = { value = "type-maps.yaml", relative = true } diff --git a/example-useage/Cargo.toml b/example-useage/Cargo.toml deleted file mode 100644 index 9ec3f02..0000000 --- a/example-useage/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[workspace] -members = [ - "server", - "client", -] -resolver = "3" diff --git a/example-useage/client/Cargo.toml b/example-useage/client/Cargo.toml deleted file mode 100644 index 67a0fed..0000000 --- a/example-useage/client/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "mtp-example-client" -version = "0.1.0" -edition = "2024" - -[[bin]] -name = "mtp-example-client" -path = "src/main.rs" - -[dependencies] -mtp-client = { path = "../../client" } -mtp-codec = { path = "../../codec" } -tokio = { version = "1", features = ["full"] } diff --git a/example-useage/client/src/main.rs b/example-useage/client/src/main.rs deleted file mode 100644 index d9be8a2..0000000 --- a/example-useage/client/src/main.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::fs; - -use mtp_client::{ClientConfig, MTPClient}; -use mtp_codec::{CommunicationType, CommunicationValue, DataType, DataValue}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let cert_pem = fs::read("server.pem") - .expect("Missing server.pem server generates it"); - - println!("Connecting to 127.0.0.1:8080 ..."); - - let config = ClientConfig { - url: "https://127.0.0.1:8080".into(), - server_cert: Some(cert_pem), - }; - - let conn = MTPClient::connect(config, 1001).await?; - println!("Connected (version {})", conn.version); - - let timestamp = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs(); - - let greeting = CommunicationValue::new(CommunicationType::Ping) - .add_typed_default(DataType::Description, DataValue::Str("Hello MTP!".into())) - .add_typed_default(DataType::Timestamp, DataValue::UnsignedNumber(timestamp as u128)) - .add_typed_default(DataType::Data, DataValue::UnsignedNumber(42)) - .with_sender(1001); - - println!("Sending: {greeting}"); - conn.sender.send(&greeting).await?; - - match conn.receiver.receive().await { - Ok(msg) => { - println!("Received: {msg}"); - } - Err(e) => { - eprintln!("Receive error: {e}"); - } - } - - conn.sender.close(); - println!("\nDone"); - - Ok(()) -} diff --git a/example-useage/server/Cargo.toml b/example-useage/server/Cargo.toml deleted file mode 100644 index 6da0426..0000000 --- a/example-useage/server/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "mtp-example-server" -version = "0.1.0" -edition = "2024" - -[[bin]] -name = "mtp-example-server" -path = "src/main.rs" - -[dependencies] -mtp-host = { path = "../../host" } -mtp-codec = { path = "../../codec" } -rcgen = "0.14" -tokio = { version = "1", features = ["full"] } diff --git a/example-useage/server/src/main.rs b/example-useage/server/src/main.rs deleted file mode 100644 index e58eaae..0000000 --- a/example-useage/server/src/main.rs +++ /dev/null @@ -1,90 +0,0 @@ -use std::fs; - -use mtp_codec::{CommunicationType, CommunicationValue, DataType, DataValue, TypeMap}; -use mtp_host::{HostConfig, MTPHost}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let cert_path = "server.pem"; - let key_path = "server.key"; - - let (cert_pem, key_pem) = if let (Ok(c), Ok(k)) = (fs::read(cert_path), fs::read(key_path)) { - println!("Using existing TLS cert from {cert_path}"); - (c, k) - } else { - println!("Generating self-signed TLS certificate ..."); - - let key_pair = rcgen::KeyPair::generate()?; - let params = rcgen::CertificateParams::new(vec![ - "localhost".into(), - "127.0.0.1".into(), - ])?; - let cert = params.self_signed(&key_pair)?; - - let cert_str = cert.pem(); - let key_str = key_pair.serialize_pem(); - - fs::write(cert_path, cert_str.as_bytes())?; - fs::write(key_path, key_str.as_bytes())?; - println!("Wrote {cert_path} and {key_path}"); - - (cert_str.into_bytes(), key_str.into_bytes()) - }; - - println!("Starting MTP server on port 8080 ..."); - - let config = HostConfig { - ip: "::".into(), - port: 8080, - tls_fullchain: cert_pem, - tls_key: key_pem, - }; - - let mut host = MTPHost::new(config).await?; - println!("Server listening on {}", host.local_addr()); - - while let Some(conn) = host.accept().await { - println!("\n--- New connection accepted (version {}) ---", conn.version); - - let tm: &TypeMap = conn.codec.registry().get(&conn.version).unwrap(); - - match conn.receiver.receive().await { - Ok(msg) => { - println!("Received: {msg}"); - - let timestamp = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs(); - - let response = CommunicationValue::from_comm( - CommunicationType::Pong, - tm, - ) - .add_data( - mtp_codec::DataTypeId(tm.data_id_enum(DataType::Description).unwrap()), - DataValue::Str("Hello from server!".into()), - ) - .add_data( - mtp_codec::DataTypeId(tm.data_id_enum(DataType::Timestamp).unwrap()), - DataValue::UnsignedNumber(timestamp as u128), - ) - .add_data( - mtp_codec::DataTypeId(tm.data_id_enum(DataType::Data).unwrap()), - DataValue::Str("custom payload".into()), - ); - - println!("Sending: {response}"); - conn.sender.send(&response).await?; - } - Err(e) => { - eprintln!("Receive error: {e}"); - } - } - - conn.sender.close(); - println!("Connection closed\n"); - } - - Ok(()) -} diff --git a/example-useage/type-maps.yaml b/example-useage/type-maps.yaml deleted file mode 100644 index 863d7f5..0000000 --- a/example-useage/type-maps.yaml +++ /dev/null @@ -1,14 +0,0 @@ -protocol_version: "1.0" - -type_maps: - "0.0": - CommunicationTypes: - DataTypes: - "1.0": - CommunicationTypes: - DataTypes: - Data: 32 - "2.0": - CommunicationTypes: - DataTypes: - Data: 34 diff --git a/host/Cargo.toml b/host/Cargo.toml index fd26447..861aa64 100644 --- a/host/Cargo.toml +++ b/host/Cargo.toml @@ -5,6 +5,8 @@ edition = "2024" [dependencies] mtp-common = { path = "../common" } +mtp-type-map = { path = "../type-map" } mtp-codec = { path = "../codec", features = ["registry"] } mtp-transport = { path = "../transport", features = ["host"] } +registry = { path = "../registry" } tokio = { version = "1", features = ["full"] } diff --git a/host/src/lib.rs b/host/src/lib.rs index 739bef0..f4b314b 100644 --- a/host/src/lib.rs +++ b/host/src/lib.rs @@ -62,11 +62,13 @@ 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()); @@ -95,6 +97,7 @@ 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()), @@ -102,15 +105,13 @@ fn extract_version(msg: &CommunicationValue) -> Option { } } -/* ================================ TESTS ================================ */ #[cfg(test)] mod tests { use super::*; #[test] fn version_extraction() { - let tm = mtp_codec::TypeMap::latest(); - let msg = CommunicationValue::from_comm(mtp_codec::CommunicationType::Identification, &tm) + let msg = CommunicationValue::new(mtp_codec::CommunicationTypeId(15)) .add_data(DataTypeId(3), DataValue::Str("2.0".to_string())); let version = extract_version(&msg); assert_eq!(version, Some(Version(2, 0))); @@ -118,15 +119,13 @@ mod tests { #[test] fn version_extraction_returns_none_for_missing() { - let tm = mtp_codec::TypeMap::latest(); - let msg = CommunicationValue::from_comm(mtp_codec::CommunicationType::Identification, &tm); + let msg = CommunicationValue::new(mtp_codec::CommunicationTypeId(15)); assert!(extract_version(&msg).is_none()); } #[test] fn version_extraction_bad_format() { - let tm = mtp_codec::TypeMap::latest(); - let msg = CommunicationValue::from_comm(mtp_codec::CommunicationType::Identification, &tm) + let msg = CommunicationValue::new(mtp_codec::CommunicationTypeId(15)) .add_data(DataTypeId(3), DataValue::UnsignedNumber(42)); assert!(extract_version(&msg).is_none()); } diff --git a/registry/Cargo.toml b/registry/Cargo.toml index 1188739..9967888 100644 --- a/registry/Cargo.toml +++ b/registry/Cargo.toml @@ -5,4 +5,4 @@ edition = "2024" [dependencies] mtp-common = { path = "../common" } -mtp-type-map = { path = "../type-map", features = ["multi-version"] } +mtp-type-map = { path = "../type-map" } diff --git a/registry/src/lib.rs b/registry/src/lib.rs index a738684..465ff45 100644 --- a/registry/src/lib.rs +++ b/registry/src/lib.rs @@ -5,11 +5,13 @@ pub use mtp_type_map::{ }; /* - * Reserved communication & data-type namespace (0..32). + * 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 = 0..32; + +/* Reserved data-type namespace (0..32). */ pub const INTERNAL_DATA_RESERVED: std::ops::Range = 0..32; /* @@ -86,7 +88,6 @@ impl Default for Registry { } } -/* ================================ TESTS ================================ */ #[cfg(test)] mod tests { use super::*; diff --git a/transport/src/lib.rs b/transport/src/lib.rs index 707e74e..f4be9ee 100644 --- a/transport/src/lib.rs +++ b/transport/src/lib.rs @@ -2,9 +2,8 @@ pub mod client; pub mod connection; pub mod connection_handle; -pub use connection::{Policy, Receiver, SendMode, Sender}; - pub use client::connect; +pub use connection::{Policy, Receiver, SendMode, Sender}; pub use connection_handle::ConnectionHandle; #[cfg(feature = "host")] diff --git a/type-map/Cargo.toml b/type-map/Cargo.toml index a233cc7..1468aef 100644 --- a/type-map/Cargo.toml +++ b/type-map/Cargo.toml @@ -4,10 +4,6 @@ version = "0.1.0" edition = "2024" build = "build.rs" -[features] -default = [] -multi-version = [] - [dependencies] [build-dependencies] diff --git a/type-map/build.rs b/type-map/build.rs index 46e7dd2..cfd1c95 100644 --- a/type-map/build.rs +++ b/type-map/build.rs @@ -24,64 +24,143 @@ struct ReservedEntry { } const RESERVED_COMM_TYPES: &[ReservedEntry] = &[ - ReservedEntry { name: "Error", id: 0 }, - ReservedEntry { name: "ErrorParsing", id: 1 }, - ReservedEntry { name: "ErrorBadVersion", id: 2 }, - ReservedEntry { name: "Disconnect", id: 3 }, - ReservedEntry { name: "Redirect", id: 4 }, - ReservedEntry { name: "Shutdown", id: 5 }, - ReservedEntry { name: "BadRequest", id: 6 }, - ReservedEntry { name: "Unauthorized", id: 7 }, - ReservedEntry { name: "Forbidden", id: 8 }, - ReservedEntry { name: "NotFound", id: 9 }, - ReservedEntry { name: "TooManyRequests", id: 10 }, - ReservedEntry { name: "InternalServerError", id: 11 }, - ReservedEntry { name: "BadGateway", id: 12 }, - ReservedEntry { name: "ServiceUnavailable", id: 13 }, - ReservedEntry { name: "GatewayTimeout", id: 14 }, - ReservedEntry { name: "Identification", id: 15 }, - ReservedEntry { name: "IdentificationResponse", id: 16 }, - ReservedEntry { name: "Register", id: 17 }, - ReservedEntry { name: "RegisterResponse", id: 18 }, - ReservedEntry { name: "Ping", id: 19 }, - ReservedEntry { name: "Pong", id: 20 }, + ReservedEntry { + name: "Error", + id: 0, + }, + ReservedEntry { + name: "ErrorParsing", + id: 1, + }, + ReservedEntry { + name: "ErrorBadVersion", + id: 2, + }, + ReservedEntry { + name: "Disconnect", + id: 3, + }, + ReservedEntry { + name: "Redirect", + id: 4, + }, + ReservedEntry { + name: "Shutdown", + id: 5, + }, + ReservedEntry { + name: "BadRequest", + id: 6, + }, + ReservedEntry { + name: "Unauthorized", + id: 7, + }, + ReservedEntry { + name: "Forbidden", + id: 8, + }, + ReservedEntry { + name: "NotFound", + id: 9, + }, + ReservedEntry { + name: "TooManyRequests", + id: 10, + }, + ReservedEntry { + name: "InternalServerError", + id: 11, + }, + ReservedEntry { + name: "BadGateway", + id: 12, + }, + ReservedEntry { + name: "ServiceUnavailable", + id: 13, + }, + ReservedEntry { + name: "GatewayTimeout", + id: 14, + }, + ReservedEntry { + name: "Identification", + id: 15, + }, + ReservedEntry { + name: "IdentificationResponse", + id: 16, + }, + ReservedEntry { + name: "Register", + id: 17, + }, + ReservedEntry { + name: "RegisterResponse", + id: 18, + }, + ReservedEntry { + name: "Ping", + id: 19, + }, + ReservedEntry { + name: "Pong", + id: 20, + }, ]; const RESERVED_DATA_TYPES: &[ReservedEntry] = &[ - ReservedEntry { name: "Error", id: 0 }, - ReservedEntry { name: "ErrorParsing", id: 1 }, - ReservedEntry { name: "ErrorMessage", id: 2 }, - ReservedEntry { name: "Version", id: 3 }, - ReservedEntry { name: "Description", id: 4 }, - ReservedEntry { name: "Timestamp", id: 5 }, + ReservedEntry { + name: "Error", + id: 0, + }, + ReservedEntry { + name: "ErrorParsing", + id: 1, + }, + ReservedEntry { + name: "ErrorMessage", + id: 2, + }, + ReservedEntry { + name: "Version", + id: 3, + }, + ReservedEntry { + name: "Description", + id: 4, + }, + ReservedEntry { + name: "Timestamp", + id: 5, + }, ReservedEntry { name: "Id", id: 6 }, - ReservedEntry { name: "Nonce", id: 7 }, - ReservedEntry { name: "PublicKeys", id: 8 }, - ReservedEntry { name: "Signature", id: 9 }, + ReservedEntry { + name: "Nonce", + id: 7, + }, + ReservedEntry { + name: "PublicKeys", + id: 8, + }, + ReservedEntry { + name: "Signature", + id: 9, + }, ]; 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 multi_version = std::env::var("CARGO_FEATURE_MULTI_VERSION").is_ok(); - - 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, multi_version); + let code = generate(&config); std::fs::write(out.join("types.rs"), code).unwrap(); } @@ -104,109 +183,38 @@ fn sorted_versions(config: &Config) -> Vec<(String, u16, u16)> { versions } -fn generate(config: &Config, multi_version: bool) -> String { +fn generate(config: &Config) -> String { let mut out = String::new(); writeln!( out, - "// Auto-generated by type-map/build.rs, this file should not be edited manually" + "// Auto-generated by type-map/build.rs, they should not be edited manually" ) .unwrap(); writeln!(out).unwrap(); - let (all_comm_names, all_data_names, sorted) = if multi_version { - let mut all_comm_names: BTreeSet<&str> = BTreeSet::new(); - let mut all_data_names: BTreeSet<&str> = BTreeSet::new(); - for (_version_key, tm) in &config.type_maps { - for name in tm.communication_types.keys() { - all_comm_names.insert(name.as_str()); - } - for name in tm.data_types.keys() { - all_data_names.insert(name.as_str()); - } - } - let sorted = sorted_versions(config); - (all_comm_names, all_data_names, sorted) - } else { - let mut all_comm_names: BTreeSet<&str> = BTreeSet::new(); - let mut all_data_names: BTreeSet<&str> = BTreeSet::new(); - if let Some(tm) = config.type_maps.get(&config.protocol_version) { - for name in tm.communication_types.keys() { - all_comm_names.insert(name.as_str()); - } - for name in tm.data_types.keys() { - all_data_names.insert(name.as_str()); - } - } - (all_comm_names, all_data_names, vec![]) - }; + let mut all_comm_names: BTreeSet<&str> = BTreeSet::new(); + let mut all_data_names: BTreeSet<&str> = BTreeSet::new(); - 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_latest_method(&mut out, config); - - if multi_version { - generate_versioned_constructors(&mut out, &sorted); - generate_builtin_type_maps(&mut out, &sorted); - generate_lookup_methods(&mut out, config, &sorted); - } else { - generate_single_version_lookup(&mut out, config); + for (_version_key, tm) in &config.type_maps { + for name in tm.communication_types.keys() { + all_comm_names.insert(name.as_str()); + } + for name in tm.data_types.keys() { + all_data_names.insert(name.as_str()); + } } - generate_enum_conversion_methods(&mut out); - generate_reverse_lookups(&mut out, config, &sorted, multi_version); + let sorted = sorted_versions(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); 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_latest_method(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, "impl TypeMap {{").unwrap(); - writeln!(out, " pub fn latest() -> Self {{").unwrap(); - writeln!( - out, - " Self {{ version: Version({}, {}) }}", - major, minor - ) - .unwrap(); - writeln!(out, " }}").unwrap(); - writeln!(out, "}}").unwrap(); - writeln!(out).unwrap(); -} - fn generate_comm_type_enum(out: &mut String, user_names: &BTreeSet<&str>) { writeln!( out, @@ -315,7 +323,7 @@ fn generate_data_type_enum(out: &mut String, user_names: &BTreeSet<&str>) { writeln!(out).unwrap(); } -fn generate_versioned_constructors(out: &mut String, sorted_versions: &[(String, u16, u16)]) { +fn generate_type_map_constructors(out: &mut String, sorted_versions: &[(String, u16, u16)]) { for (version_key, _major, _minor) in sorted_versions { let version_ident = version_key.replace('.', "_"); @@ -342,7 +350,8 @@ fn generate_lookup_methods( config: &Config, sorted_versions: &[(String, u16, u16)], ) { - writeln!(out, "#[allow(unreachable_patterns, unused_variables)]").unwrap(); + // comm_id_enum + writeln!(out, "#[allow(unreachable_patterns)]").unwrap(); writeln!(out, "impl TypeMap {{").unwrap(); writeln!( out, @@ -382,7 +391,7 @@ fn generate_lookup_methods( writeln!(out, " }}").unwrap(); writeln!(out).unwrap(); - writeln!(out, " #[allow(unused_variables)]").unwrap(); + // data_id_enum writeln!( out, " pub fn data_id_enum(&self, dt: DataType) -> Option {{" @@ -418,81 +427,6 @@ fn generate_lookup_methods( writeln!(out).unwrap(); } -fn generate_single_version_lookup(out: &mut String, config: &Config) { - let tm_cfg = config.type_maps.get(&config.protocol_version); - - writeln!(out, "#[allow(unreachable_patterns, unused_variables)]").unwrap(); - writeln!(out, "impl TypeMap {{").unwrap(); - writeln!( - out, - " pub fn comm_id_enum(&self, ct: CommunicationType) -> Option {{" - ) - .unwrap(); - writeln!(out, " match self.version {{").unwrap(); - writeln!( - out, - " PROTOCOL_VERSION => match ct {{" - ) - .unwrap(); - for entry in RESERVED_COMM_TYPES { - writeln!( - out, - " CommunicationType::{} => Some({}),", - entry.name, entry.id - ) - .unwrap(); - } - if let Some(tm_cfg) = tm_cfg { - for (name, id) in &tm_cfg.communication_types { - writeln!( - out, - " CommunicationType::{} => Some({}),", - name, id - ) - .unwrap(); - } - } - writeln!(out, " _ => None,").unwrap(); - writeln!(out, " }},").unwrap(); - writeln!(out, " _ => None,").unwrap(); - writeln!(out, " }}").unwrap(); - writeln!(out, " }}").unwrap(); - writeln!(out).unwrap(); - - writeln!(out, " #[allow(unused_variables)]").unwrap(); - writeln!( - out, - " pub fn data_id_enum(&self, dt: DataType) -> Option {{" - ) - .unwrap(); - writeln!(out, " match self.version {{").unwrap(); - writeln!( - out, - " PROTOCOL_VERSION => match dt {{" - ) - .unwrap(); - for entry in RESERVED_DATA_TYPES { - writeln!( - out, - " DataType::{} => Some({}),", - entry.name, entry.id - ) - .unwrap(); - } - if let Some(tm_cfg) = tm_cfg { - for (name, id) in &tm_cfg.data_types { - writeln!(out, " DataType::{} => Some({}),", name, id).unwrap(); - } - } - writeln!(out, " _ => None,").unwrap(); - writeln!(out, " }},").unwrap(); - writeln!(out, " _ => None,").unwrap(); - writeln!(out, " }}").unwrap(); - writeln!(out, " }}").unwrap(); - writeln!(out, "}}").unwrap(); - writeln!(out).unwrap(); -} - fn generate_builtin_type_maps(out: &mut String, sorted_versions: &[(String, u16, u16)]) { writeln!(out, "pub fn builtin_type_maps() -> Vec {{").unwrap(); writeln!(out, " vec![").unwrap(); @@ -504,90 +438,3 @@ fn generate_builtin_type_maps(out: &mut String, sorted_versions: &[(String, u16, writeln!(out, "}}").unwrap(); writeln!(out).unwrap(); } - -fn generate_reverse_lookups( - out: &mut String, - config: &Config, - sorted_versions: &[(String, u16, u16)], - multi_version: bool, -) { - let mut id_to_comm: BTreeMap = BTreeMap::new(); - for entry in RESERVED_COMM_TYPES { - id_to_comm.insert(entry.id, entry.name.to_string()); - } - if multi_version { - for (version_key, _major, _minor) in sorted_versions { - if let Some(tm_cfg) = config.type_maps.get(version_key) { - for (name, id) in &tm_cfg.communication_types { - id_to_comm.insert(*id, name.clone()); - } - } - } - } else if let Some(tm_cfg) = config.type_maps.get(&config.protocol_version) { - for (name, id) in &tm_cfg.communication_types { - id_to_comm.insert(*id, name.clone()); - } - } - - writeln!(out, "pub fn communication_type_name(id: u16) -> Option<&'static str> {{").unwrap(); - writeln!(out, " match id {{").unwrap(); - for (id, name) in &id_to_comm { - writeln!(out, " {} => Some(\"{}\"),", id, name).unwrap(); - } - writeln!(out, " _ => None,").unwrap(); - writeln!(out, " }}").unwrap(); - writeln!(out, "}}").unwrap(); - writeln!(out).unwrap(); - - let mut id_to_data: BTreeMap = BTreeMap::new(); - for entry in RESERVED_DATA_TYPES { - id_to_data.insert(entry.id, entry.name.to_string()); - } - if multi_version { - for (version_key, _major, _minor) in sorted_versions { - if let Some(tm_cfg) = config.type_maps.get(version_key) { - for (name, id) in &tm_cfg.data_types { - id_to_data.insert(*id, name.clone()); - } - } - } - } else if let Some(tm_cfg) = config.type_maps.get(&config.protocol_version) { - for (name, id) in &tm_cfg.data_types { - id_to_data.insert(*id, name.clone()); - } - } - - writeln!(out, "pub fn data_type_name(id: u16) -> Option<&'static str> {{").unwrap(); - writeln!(out, " match id {{").unwrap(); - for (id, name) in &id_to_data { - writeln!(out, " {} => Some(\"{}\"),", id, name).unwrap(); - } - writeln!(out, " _ => None,").unwrap(); - writeln!(out, " }}").unwrap(); - writeln!(out, "}}").unwrap(); - writeln!(out).unwrap(); -} - -fn generate_enum_conversion_methods(out: &mut String) { - 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 3173de2..e61e445 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, PartialEq, Eq)] +#[derive(Clone, Debug)] pub struct TypeMap { pub version: Version, }