Easy use, Logging & Example Usage

This commit is contained in:
Alex Emmet 2026-06-22 16:31:27 +02:00
commit 1dd240861d
20 changed files with 579 additions and 210 deletions

View file

@ -1,16 +0,0 @@
# 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?;
```

View file

@ -5,7 +5,6 @@ edition = "2024"
[dependencies] [dependencies]
mtp-common = { path = "../common" } mtp-common = { path = "../common" }
mtp-type-map = { path = "../type-map" }
mtp-codec = { path = "../codec" } mtp-codec = { path = "../codec" }
mtp-transport = { path = "../transport" } mtp-transport = { path = "../transport" }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }

View file

@ -1,17 +1,7 @@
use mtp_codec::{CommunicationValue, DataTypeId, DataValue, Version}; use mtp_codec::{CommunicationValue, DataTypeId, DataValue, Version, PROTOCOL_VERSION};
use mtp_common::CommunicationError; use mtp_common::CommunicationError;
use mtp_transport::{Policy, Receiver, Sender}; 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 use mtp_type_map::PROTOCOL_VERSION;
pub struct ClientConfig { pub struct ClientConfig {
pub url: String, pub url: String,
pub server_cert: Option<Vec<u8>>, pub server_cert: Option<Vec<u8>>,
@ -42,7 +32,7 @@ impl MTPClient {
// Build the initial identification message with the protocol version. // Build the initial identification message with the protocol version.
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::CommunicationType::Identification)
.add_data(DataTypeId(3), DataValue::Str(version_str)) .add_data(DataTypeId(3), DataValue::Str(version_str))
.add_data(DataTypeId(6), DataValue::UnsignedNumber(client_id.into())); .add_data(DataTypeId(6), DataValue::UnsignedNumber(client_id.into()));

View file

@ -14,5 +14,5 @@ rand = { version = "*", features = ["std", "std_rng"] }
[features] [features]
default = [] default = []
registry = ["dep:registry"] registry = ["dep:registry", "mtp-type-map/multi-version"]
crypto = ["dep:mtp-crypto"] crypto = ["dep:mtp-crypto"]

View file

@ -1,10 +1,14 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fmt;
use std::io::{Cursor, Read}; 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::{CommunicationType, CommunicationTypeId, DataType, DataTypeId, TypeMap}; use mtp_type_map::{
communication_type_name, data_type_name, CommunicationType, CommunicationTypeId, DataType,
DataTypeId, TypeMap, PROTOCOL_VERSION,
};
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommunicationValue { pub struct CommunicationValue {
@ -18,17 +22,20 @@ pub struct CommunicationValue {
#[allow(dead_code)] #[allow(dead_code)]
impl CommunicationValue { impl CommunicationValue {
pub fn new(comm_type: CommunicationTypeId) -> Self { pub fn new(comm_type: CommunicationType) -> Self {
let tm = TypeMap::new(PROTOCOL_VERSION);
let id = comm_type.to_id(&tm);
Self { Self {
id: rand_u32(), id: rand_u32(),
comm_type, comm_type: id,
sender: 0, sender: 0,
receiver: 0, receiver: 0,
data: BTreeMap::new(), data: BTreeMap::new(),
type_map: None, type_map: Some(tm),
} }
} }
#[cfg(feature = "registry")]
pub fn from_comm(comm_type: CommunicationType, tm: &TypeMap) -> Self { pub fn from_comm(comm_type: CommunicationType, tm: &TypeMap) -> Self {
let id = comm_type.to_id(tm); let id = comm_type.to_id(tm);
Self { Self {
@ -42,8 +49,16 @@ impl CommunicationValue {
} }
pub fn from_comm_default(comm_type: CommunicationType) -> Self { pub fn from_comm_default(comm_type: CommunicationType) -> Self {
let tm = TypeMap::latest(); let tm = TypeMap::new(PROTOCOL_VERSION);
Self::from_comm(comm_type, &tm) 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),
}
} }
pub fn with_id(mut self, p0: u32) -> Self { pub fn with_id(mut self, p0: u32) -> Self {
@ -86,16 +101,14 @@ impl CommunicationValue {
self self
} }
#[cfg(feature = "registry")]
pub fn add_typed(mut self, data: DataType, tm: &TypeMap, value: DataValue) -> Self { pub fn add_typed(mut self, data: DataType, tm: &TypeMap, value: DataValue) -> Self {
self.data.insert(data.to_id(tm), value); self.data.insert(data.to_id(tm), value);
self self
} }
pub fn add_typed_default(mut self, data: DataType, value: DataValue) -> Self { pub fn add_typed_default(mut self, data: DataType, value: DataValue) -> Self {
let tm = self let tm = self.type_map.clone().unwrap_or_else(TypeMap::latest);
.type_map
.clone()
.unwrap_or_else(TypeMap::latest);
self.data.insert(data.to_id(&tm), value); self.data.insert(data.to_id(&tm), value);
self self
} }
@ -231,6 +244,79 @@ impl CommunicationValue {
} }
} }
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 ================================ */ /* ================================ TESTS ================================ */
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -247,7 +333,7 @@ mod tests {
#[test] #[test]
fn test_flags_and_order_without_optional() { fn test_flags_and_order_without_optional() {
let cv = CommunicationValue::new(CommunicationTypeId(1)).with_id(0); let cv = CommunicationValue::from_comm_default(CommunicationType::ErrorParsing).with_id(0);
let bytes = cv.to_bytes(); let bytes = cv.to_bytes();
// [u32 len][u16 type][flags]... // [u32 len][u16 type][flags]...
@ -265,7 +351,7 @@ mod tests {
#[test] #[test]
fn test_flags_and_order_with_all_optional() { fn test_flags_and_order_with_all_optional() {
let cv = CommunicationValue::new(CommunicationTypeId(2)) let cv = CommunicationValue::from_comm_default(CommunicationType::ErrorBadVersion)
.with_id(0xAABBCCDD) .with_id(0xAABBCCDD)
.with_sender(0x0000_1122_3344_5566) .with_sender(0x0000_1122_3344_5566)
.with_receiver(0x0000_6677_8899_AABB); .with_receiver(0x0000_6677_8899_AABB);
@ -276,10 +362,10 @@ mod tests {
let total_len = c.read_u32::<BigEndian>().expect("len"); let total_len = c.read_u32::<BigEndian>().expect("len");
assert_eq!(total_len as usize + 4, bytes.len()); assert_eq!(total_len as usize + 4, bytes.len());
let typ = c.read_u16::<BigEndian>().expect("type"); let typ = c.read_u16::<BigEndian>().expect("read type");
assert_eq!(typ, 2); assert_eq!(typ, 2);
let flags = c.read_u8().expect("flags"); let flags = c.read_u8().expect("read flags");
assert_eq!(flags & 0b0000_0111, 0b0000_0111); assert_eq!(flags & 0b0000_0111, 0b0000_0111);
let id = c.read_u32::<BigEndian>().expect("id"); let id = c.read_u32::<BigEndian>().expect("id");
@ -296,7 +382,7 @@ mod tests {
#[test] #[test]
fn test_roundtrip_complex() { fn test_roundtrip_complex() {
let cv = CommunicationValue::new(CommunicationTypeId(3)) let cv = CommunicationValue::from_comm_default(CommunicationType::Disconnect)
.with_id(1234) .with_id(1234)
.with_sender(111) .with_sender(111)
.with_receiver(222) .with_receiver(222)

View file

@ -2,6 +2,7 @@ use base64::Engine;
use base64::engine::general_purpose; use base64::engine::general_purpose;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fmt;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::io::Cursor; use std::io::Cursor;
@ -635,6 +636,44 @@ 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 { impl PartialEq for DataValue {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
use DataValue::*; use DataValue::*;

View file

@ -8,7 +8,8 @@ pub use data_value::{DataKind, DataValue};
pub use util::rand_u32; pub use util::rand_u32;
pub use mtp_type_map::{ pub use mtp_type_map::{
CommunicationType, CommunicationTypeId, DataType, DataTypeId, TypeMap, Version, communication_type_name, data_type_name, CommunicationType, CommunicationTypeId, DataType,
DataTypeId, PROTOCOL_VERSION, TypeMap, Version,
}; };
use mtp_common::CodecError; use mtp_common::CodecError;

View file

@ -1,5 +1,5 @@
# The version a Client should use # The version a Client should use
protocol_version: "2.0" protocol_version: "0.0"
# Note that markers 0 to 31 are reserved for default use, manually working with them is not recommended # Note that markers 0 to 31 are reserved for default use, manually working with them is not recommended
# Fixed CommunicationType markers are: # Fixed CommunicationType markers are:

View file

@ -0,0 +1,2 @@
[env]
MTP_TYPE_MAPS = { value = "type-maps.yaml", relative = true }

View file

@ -0,0 +1,6 @@
[workspace]
members = [
"server",
"client",
]
resolver = "3"

View file

@ -0,0 +1,13 @@
[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"] }

View file

@ -0,0 +1,48 @@
use std::fs;
use mtp_client::{ClientConfig, MTPClient};
use mtp_codec::{CommunicationType, CommunicationValue, DataType, DataValue};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
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(())
}

View file

@ -0,0 +1,14 @@
[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"] }

View file

@ -0,0 +1,90 @@
use std::fs;
use mtp_codec::{CommunicationType, CommunicationValue, DataType, DataValue, TypeMap};
use mtp_host::{HostConfig, MTPHost};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
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(())
}

View file

@ -0,0 +1,14 @@
protocol_version: "1.0"
type_maps:
"0.0":
CommunicationTypes:
DataTypes:
"1.0":
CommunicationTypes:
DataTypes:
Data: 32
"2.0":
CommunicationTypes:
DataTypes:
Data: 34

View file

@ -5,8 +5,6 @@ edition = "2024"
[dependencies] [dependencies]
mtp-common = { path = "../common" } mtp-common = { path = "../common" }
mtp-type-map = { path = "../type-map" }
mtp-codec = { path = "../codec", features = ["registry"] } mtp-codec = { path = "../codec", features = ["registry"] }
mtp-transport = { path = "../transport", features = ["host"] } mtp-transport = { path = "../transport", features = ["host"] }
registry = { path = "../registry" }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }

View file

@ -109,7 +109,8 @@ mod tests {
#[test] #[test]
fn version_extraction() { fn version_extraction() {
let msg = CommunicationValue::new(mtp_codec::CommunicationTypeId(15)) let tm = mtp_codec::TypeMap::latest();
let msg = CommunicationValue::from_comm(mtp_codec::CommunicationType::Identification, &tm)
.add_data(DataTypeId(3), DataValue::Str("2.0".to_string())); .add_data(DataTypeId(3), DataValue::Str("2.0".to_string()));
let version = extract_version(&msg); let version = extract_version(&msg);
assert_eq!(version, Some(Version(2, 0))); assert_eq!(version, Some(Version(2, 0)));
@ -117,13 +118,15 @@ mod tests {
#[test] #[test]
fn version_extraction_returns_none_for_missing() { fn version_extraction_returns_none_for_missing() {
let msg = CommunicationValue::new(mtp_codec::CommunicationTypeId(15)); let tm = mtp_codec::TypeMap::latest();
let msg = CommunicationValue::from_comm(mtp_codec::CommunicationType::Identification, &tm);
assert!(extract_version(&msg).is_none()); assert!(extract_version(&msg).is_none());
} }
#[test] #[test]
fn version_extraction_bad_format() { fn version_extraction_bad_format() {
let msg = CommunicationValue::new(mtp_codec::CommunicationTypeId(15)) let tm = mtp_codec::TypeMap::latest();
let msg = CommunicationValue::from_comm(mtp_codec::CommunicationType::Identification, &tm)
.add_data(DataTypeId(3), DataValue::UnsignedNumber(42)); .add_data(DataTypeId(3), DataValue::UnsignedNumber(42));
assert!(extract_version(&msg).is_none()); assert!(extract_version(&msg).is_none());
} }

View file

@ -5,4 +5,4 @@ edition = "2024"
[dependencies] [dependencies]
mtp-common = { path = "../common" } mtp-common = { path = "../common" }
mtp-type-map = { path = "../type-map" } mtp-type-map = { path = "../type-map", features = ["multi-version"] }

View file

@ -4,6 +4,10 @@ version = "0.1.0"
edition = "2024" edition = "2024"
build = "build.rs" build = "build.rs"
[features]
default = []
multi-version = []
[dependencies] [dependencies]
[build-dependencies] [build-dependencies]

View file

@ -24,134 +24,45 @@ struct ReservedEntry {
} }
const RESERVED_COMM_TYPES: &[ReservedEntry] = &[ const RESERVED_COMM_TYPES: &[ReservedEntry] = &[
ReservedEntry { ReservedEntry { name: "Error", id: 0 },
name: "Error", ReservedEntry { name: "ErrorParsing", id: 1 },
id: 0, ReservedEntry { name: "ErrorBadVersion", id: 2 },
}, ReservedEntry { name: "Disconnect", id: 3 },
ReservedEntry { ReservedEntry { name: "Redirect", id: 4 },
name: "ErrorParsing", ReservedEntry { name: "Shutdown", id: 5 },
id: 1, ReservedEntry { name: "BadRequest", id: 6 },
}, ReservedEntry { name: "Unauthorized", id: 7 },
ReservedEntry { ReservedEntry { name: "Forbidden", id: 8 },
name: "ErrorBadVersion", ReservedEntry { name: "NotFound", id: 9 },
id: 2, ReservedEntry { name: "TooManyRequests", id: 10 },
}, ReservedEntry { name: "InternalServerError", id: 11 },
ReservedEntry { ReservedEntry { name: "BadGateway", id: 12 },
name: "Disconnect", ReservedEntry { name: "ServiceUnavailable", id: 13 },
id: 3, ReservedEntry { name: "GatewayTimeout", id: 14 },
}, ReservedEntry { name: "Identification", id: 15 },
ReservedEntry { ReservedEntry { name: "IdentificationResponse", id: 16 },
name: "Redirect", ReservedEntry { name: "Register", id: 17 },
id: 4, ReservedEntry { name: "RegisterResponse", id: 18 },
}, ReservedEntry { name: "Ping", id: 19 },
ReservedEntry { ReservedEntry { name: "Pong", id: 20 },
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] = &[ const RESERVED_DATA_TYPES: &[ReservedEntry] = &[
ReservedEntry { ReservedEntry { name: "Error", id: 0 },
name: "Error", ReservedEntry { name: "ErrorParsing", id: 1 },
id: 0, ReservedEntry { name: "ErrorMessage", id: 2 },
}, ReservedEntry { name: "Version", id: 3 },
ReservedEntry { ReservedEntry { name: "Description", id: 4 },
name: "ErrorParsing", ReservedEntry { name: "Timestamp", id: 5 },
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: "Id", id: 6 },
ReservedEntry { ReservedEntry { name: "Nonce", id: 7 },
name: "Nonce", ReservedEntry { name: "PublicKeys", id: 8 },
id: 7, ReservedEntry { name: "Signature", id: 9 },
},
ReservedEntry {
name: "PublicKeys",
id: 8,
},
ReservedEntry {
name: "Signature",
id: 9,
},
]; ];
fn main() { fn main() {
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 multi_version = std::env::var("CARGO_FEATURE_MULTI_VERSION").is_ok();
let config = match std::env::var("MTP_TYPE_MAPS") { let config = match std::env::var("MTP_TYPE_MAPS") {
Ok(config_path) => { Ok(config_path) => {
@ -170,7 +81,7 @@ fn main() {
} }
}; };
let code = generate(&config); let code = generate(&config, multi_version);
std::fs::write(out.join("types.rs"), code).unwrap(); std::fs::write(out.join("types.rs"), code).unwrap();
} }
@ -193,7 +104,7 @@ fn sorted_versions(config: &Config) -> Vec<(String, u16, u16)> {
versions versions
} }
fn generate(config: &Config) -> String { fn generate(config: &Config, multi_version: bool) -> String {
let mut out = String::new(); let mut out = String::new();
writeln!( writeln!(
out, out,
@ -202,27 +113,48 @@ fn generate(config: &Config) -> String {
.unwrap(); .unwrap();
writeln!(out).unwrap(); writeln!(out).unwrap();
let mut all_comm_names: BTreeSet<&str> = BTreeSet::new(); let (all_comm_names, all_data_names, sorted) = if multi_version {
let mut all_data_names: BTreeSet<&str> = BTreeSet::new(); 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 (_version_key, tm) in &config.type_maps {
for name in tm.communication_types.keys() { for name in tm.communication_types.keys() {
all_comm_names.insert(name.as_str()); all_comm_names.insert(name.as_str());
}
for name in tm.data_types.keys() {
all_data_names.insert(name.as_str());
}
} }
for name in tm.data_types.keys() { let sorted = sorted_versions(config);
all_data_names.insert(name.as_str()); (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 sorted = sorted_versions(config);
generate_protocol_version(&mut out, 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_latest_method(&mut out, config);
generate_lookup_methods(&mut out, config, &sorted);
generate_builtin_type_maps(&mut out, &sorted); if multi_version {
generate_enum_conversion_methods(&mut out, &sorted); 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);
}
generate_enum_conversion_methods(&mut out);
generate_reverse_lookups(&mut out, config, &sorted, multi_version);
out out
} }
@ -249,6 +181,32 @@ fn generate_protocol_version(out: &mut String, config: &Config) {
writeln!(out).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::<u16>().unwrap_or(0);
let min = parts[1].parse::<u16>().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>) { fn generate_comm_type_enum(out: &mut String, user_names: &BTreeSet<&str>) {
writeln!( writeln!(
out, out,
@ -357,7 +315,7 @@ fn generate_data_type_enum(out: &mut String, user_names: &BTreeSet<&str>) {
writeln!(out).unwrap(); writeln!(out).unwrap();
} }
fn generate_type_map_constructors(out: &mut String, sorted_versions: &[(String, u16, u16)]) { fn generate_versioned_constructors(out: &mut String, sorted_versions: &[(String, u16, u16)]) {
for (version_key, _major, _minor) in sorted_versions { for (version_key, _major, _minor) in sorted_versions {
let version_ident = version_key.replace('.', "_"); let version_ident = version_key.replace('.', "_");
@ -377,22 +335,6 @@ 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(
@ -400,7 +342,6 @@ fn generate_lookup_methods(
config: &Config, config: &Config,
sorted_versions: &[(String, u16, u16)], sorted_versions: &[(String, u16, u16)],
) { ) {
// comm_id_enum
writeln!(out, "#[allow(unreachable_patterns, unused_variables)]").unwrap(); writeln!(out, "#[allow(unreachable_patterns, unused_variables)]").unwrap();
writeln!(out, "impl TypeMap {{").unwrap(); writeln!(out, "impl TypeMap {{").unwrap();
writeln!( writeln!(
@ -441,7 +382,6 @@ fn generate_lookup_methods(
writeln!(out, " }}").unwrap(); writeln!(out, " }}").unwrap();
writeln!(out).unwrap(); writeln!(out).unwrap();
// data_id_enum
writeln!(out, " #[allow(unused_variables)]").unwrap(); writeln!(out, " #[allow(unused_variables)]").unwrap();
writeln!( writeln!(
out, out,
@ -478,6 +418,81 @@ fn generate_lookup_methods(
writeln!(out).unwrap(); 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<u16> {{"
)
.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<u16> {{"
)
.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)]) { fn generate_builtin_type_maps(out: &mut String, sorted_versions: &[(String, u16, u16)]) {
writeln!(out, "pub fn builtin_type_maps() -> Vec<TypeMap> {{").unwrap(); writeln!(out, "pub fn builtin_type_maps() -> Vec<TypeMap> {{").unwrap();
writeln!(out, " vec![").unwrap(); writeln!(out, " vec![").unwrap();
@ -490,7 +505,70 @@ 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)]) { fn generate_reverse_lookups(
out: &mut String,
config: &Config,
sorted_versions: &[(String, u16, u16)],
multi_version: bool,
) {
let mut id_to_comm: BTreeMap<u16, String> = 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<u16, String> = 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, "impl CommunicationType {{").unwrap();
writeln!(out, " pub fn to_id(self, tm: &TypeMap) -> CommunicationTypeId {{").unwrap(); writeln!(out, " pub fn to_id(self, tm: &TypeMap) -> CommunicationTypeId {{").unwrap();
writeln!( writeln!(