Easy use, Logging & Example Usage
This commit is contained in:
parent
907b9f04db
commit
1dd240861d
20 changed files with 579 additions and 210 deletions
16
TRANSPORT.md
16
TRANSPORT.md
|
|
@ -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?;
|
|
||||||
```
|
|
||||||
|
|
@ -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"] }
|
||||||
|
|
|
||||||
|
|
@ -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()));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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::*;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
2
example-useage/.cargo/config.toml
Normal file
2
example-useage/.cargo/config.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[env]
|
||||||
|
MTP_TYPE_MAPS = { value = "type-maps.yaml", relative = true }
|
||||||
6
example-useage/Cargo.toml
Normal file
6
example-useage/Cargo.toml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"server",
|
||||||
|
"client",
|
||||||
|
]
|
||||||
|
resolver = "3"
|
||||||
13
example-useage/client/Cargo.toml
Normal file
13
example-useage/client/Cargo.toml
Normal 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"] }
|
||||||
48
example-useage/client/src/main.rs
Normal file
48
example-useage/client/src/main.rs
Normal 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(())
|
||||||
|
}
|
||||||
14
example-useage/server/Cargo.toml
Normal file
14
example-useage/server/Cargo.toml
Normal 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"] }
|
||||||
90
example-useage/server/src/main.rs
Normal file
90
example-useage/server/src/main.rs
Normal 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(())
|
||||||
|
}
|
||||||
14
example-useage/type-maps.yaml
Normal file
14
example-useage/type-maps.yaml
Normal 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
|
||||||
|
|
@ -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"] }
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"] }
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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!(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue