Types Creation
This commit is contained in:
parent
94f2280570
commit
8337fa3d8f
22 changed files with 1141 additions and 202 deletions
|
|
@ -2,5 +2,10 @@
|
|||
name = "mtp-type-map"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[build-dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_yaml = "0.9"
|
||||
|
|
|
|||
440
type-map/build.rs
Normal file
440
type-map/build.rs
Normal file
|
|
@ -0,0 +1,440 @@
|
|||
use serde::Deserialize;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Config {
|
||||
#[allow(dead_code)]
|
||||
protocol_version: String,
|
||||
type_maps: BTreeMap<String, TypeMapConfig>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
struct TypeMapConfig {
|
||||
#[serde(default)]
|
||||
communication_types: BTreeMap<String, u16>,
|
||||
#[serde(default)]
|
||||
data_types: BTreeMap<String, u16>,
|
||||
}
|
||||
|
||||
struct ReservedEntry {
|
||||
name: &'static str,
|
||||
id: u16,
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
];
|
||||
|
||||
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: "Id", id: 6 },
|
||||
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 code = generate(&config);
|
||||
std::fs::write(out.join("types.rs"), code).unwrap();
|
||||
}
|
||||
|
||||
fn sorted_versions(config: &Config) -> Vec<(String, u16, u16)> {
|
||||
let mut versions: Vec<(String, u16, u16)> = config
|
||||
.type_maps
|
||||
.keys()
|
||||
.filter_map(|k| {
|
||||
let parts: Vec<&str> = k.split('.').collect();
|
||||
if parts.len() == 2 {
|
||||
let major = parts[0].parse::<u16>().ok()?;
|
||||
let minor = parts[1].parse::<u16>().ok()?;
|
||||
Some((k.clone(), major, minor))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
versions.sort_by_key(|(_, major, minor)| (*major, *minor));
|
||||
versions
|
||||
}
|
||||
|
||||
fn generate(config: &Config) -> String {
|
||||
let mut out = String::new();
|
||||
writeln!(
|
||||
out,
|
||||
"// Auto-generated by type-map/build.rs, they should not be edited manually"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(out).unwrap();
|
||||
|
||||
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);
|
||||
|
||||
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_comm_type_enum(out: &mut String, user_names: &BTreeSet<&str>) {
|
||||
writeln!(
|
||||
out,
|
||||
"#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(out, "pub enum CommunicationType {{").unwrap();
|
||||
|
||||
for entry in RESERVED_COMM_TYPES {
|
||||
writeln!(out, " {},", entry.name).unwrap();
|
||||
}
|
||||
for name in user_names {
|
||||
writeln!(out, " {},", name).unwrap();
|
||||
}
|
||||
|
||||
writeln!(out, "}}").unwrap();
|
||||
writeln!(out).unwrap();
|
||||
|
||||
writeln!(out, "impl CommunicationType {{").unwrap();
|
||||
writeln!(out, " pub fn name(self) -> &'static str {{").unwrap();
|
||||
writeln!(out, " match self {{").unwrap();
|
||||
|
||||
for entry in RESERVED_COMM_TYPES {
|
||||
writeln!(
|
||||
out,
|
||||
" CommunicationType::{} => \"{}\",",
|
||||
entry.name, entry.name
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
for name in user_names {
|
||||
writeln!(
|
||||
out,
|
||||
" CommunicationType::{} => \"{}\",",
|
||||
name, name
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
writeln!(out, " }}").unwrap();
|
||||
writeln!(out, " }}").unwrap();
|
||||
writeln!(out, "}}").unwrap();
|
||||
writeln!(out).unwrap();
|
||||
|
||||
writeln!(out, "impl std::fmt::Display for CommunicationType {{").unwrap();
|
||||
writeln!(
|
||||
out,
|
||||
" fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(out, " f.write_str(self.name())").unwrap();
|
||||
writeln!(out, " }}").unwrap();
|
||||
writeln!(out, "}}").unwrap();
|
||||
writeln!(out).unwrap();
|
||||
}
|
||||
|
||||
fn generate_data_type_enum(out: &mut String, user_names: &BTreeSet<&str>) {
|
||||
writeln!(out, "/// Data type identifier; compile-time checked.").unwrap();
|
||||
writeln!(
|
||||
out,
|
||||
"#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(out, "pub enum DataType {{").unwrap();
|
||||
|
||||
for entry in RESERVED_DATA_TYPES {
|
||||
writeln!(out, " {},", entry.name).unwrap();
|
||||
}
|
||||
for name in user_names {
|
||||
writeln!(out, " {},", name).unwrap();
|
||||
}
|
||||
|
||||
writeln!(out, "}}").unwrap();
|
||||
writeln!(out).unwrap();
|
||||
|
||||
writeln!(out, "impl DataType {{").unwrap();
|
||||
writeln!(out, " pub fn name(self) -> &'static str {{").unwrap();
|
||||
writeln!(out, " match self {{").unwrap();
|
||||
|
||||
for entry in RESERVED_DATA_TYPES {
|
||||
writeln!(
|
||||
out,
|
||||
" DataType::{} => \"{}\",",
|
||||
entry.name, entry.name
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
for name in user_names {
|
||||
writeln!(out, " DataType::{} => \"{}\",", name, name).unwrap();
|
||||
}
|
||||
|
||||
writeln!(out, " }}").unwrap();
|
||||
writeln!(out, " }}").unwrap();
|
||||
writeln!(out, "}}").unwrap();
|
||||
writeln!(out).unwrap();
|
||||
|
||||
writeln!(out, "impl std::fmt::Display for DataType {{").unwrap();
|
||||
writeln!(
|
||||
out,
|
||||
" fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(out, " f.write_str(self.name())").unwrap();
|
||||
writeln!(out, " }}").unwrap();
|
||||
writeln!(out, "}}").unwrap();
|
||||
writeln!(out).unwrap();
|
||||
}
|
||||
|
||||
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('.', "_");
|
||||
|
||||
writeln!(out, "impl TypeMap {{").unwrap();
|
||||
writeln!(out, " pub fn v{}() -> Self {{", version_ident).unwrap();
|
||||
|
||||
let parts: Vec<&str> = version_key.split('.').collect();
|
||||
let major = parts[0];
|
||||
let minor = parts[1];
|
||||
writeln!(
|
||||
out,
|
||||
" Self {{ version: Version({}, {}) }}",
|
||||
major, minor
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(out, " }}").unwrap();
|
||||
writeln!(out, "}}").unwrap();
|
||||
writeln!(out).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_lookup_methods(
|
||||
out: &mut String,
|
||||
config: &Config,
|
||||
sorted_versions: &[(String, u16, u16)],
|
||||
) {
|
||||
// comm_id_enum
|
||||
writeln!(out, "#[allow(unreachable_patterns)]").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();
|
||||
for (version_key, major, minor) in sorted_versions {
|
||||
let tm_cfg = &config.type_maps[version_key];
|
||||
writeln!(
|
||||
out,
|
||||
" Version({}, {}) => match ct {{",
|
||||
major, minor
|
||||
)
|
||||
.unwrap();
|
||||
for entry in RESERVED_COMM_TYPES {
|
||||
writeln!(
|
||||
out,
|
||||
" CommunicationType::{} => Some({}),",
|
||||
entry.name, entry.id
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
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();
|
||||
|
||||
// data_id_enum
|
||||
writeln!(
|
||||
out,
|
||||
" pub fn data_id_enum(&self, dt: DataType) -> Option<u16> {{"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(out, " match self.version {{").unwrap();
|
||||
for (version_key, major, minor) in sorted_versions {
|
||||
let tm_cfg = &config.type_maps[version_key];
|
||||
writeln!(
|
||||
out,
|
||||
" Version({}, {}) => match dt {{",
|
||||
major, minor
|
||||
)
|
||||
.unwrap();
|
||||
for entry in RESERVED_DATA_TYPES {
|
||||
writeln!(
|
||||
out,
|
||||
" DataType::{} => Some({}),",
|
||||
entry.name, entry.id
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
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<TypeMap> {{").unwrap();
|
||||
writeln!(out, " vec![").unwrap();
|
||||
for (version_key, _major, _minor) in sorted_versions {
|
||||
let ident = version_key.replace('.', "_");
|
||||
writeln!(out, " TypeMap::v{}(),", ident).unwrap();
|
||||
}
|
||||
writeln!(out, " ]").unwrap();
|
||||
writeln!(out, "}}").unwrap();
|
||||
writeln!(out).unwrap();
|
||||
}
|
||||
|
|
@ -1,28 +1,45 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct CommTypeId(pub u16);
|
||||
pub struct CommunicationTypeId(pub u16);
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct DataTypeId(pub u16);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Version(pub u16);
|
||||
pub struct Version(pub u16, pub u16);
|
||||
|
||||
/// A single protocol version's type dictionary.
|
||||
/// Compiled into the client, or loaded by the host via the registry.
|
||||
impl Version {
|
||||
pub fn new(major: u16, minor: u16) -> Self {
|
||||
Self(major, minor)
|
||||
}
|
||||
|
||||
pub fn parse(s: &str) -> Option<Self> {
|
||||
let (major, minor) = s.split_once('.')?;
|
||||
Some(Self(major.parse().ok()?, minor.parse().ok()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Version {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}.{}", self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A single protocol version's type dictionary.
|
||||
* Compiled into the client, or loaded by the host via the registry.
|
||||
*
|
||||
* 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)]
|
||||
pub struct TypeMap {
|
||||
pub version: Version,
|
||||
pub comm_types: HashMap<String, u16>,
|
||||
pub data_types: HashMap<String, u16>,
|
||||
}
|
||||
|
||||
impl TypeMap {
|
||||
pub fn comm_id(&self, name: &str) -> Option<u16> {
|
||||
self.comm_types.get(name).copied()
|
||||
}
|
||||
pub fn data_id(&self, name: &str) -> Option<u16> {
|
||||
self.data_types.get(name).copied()
|
||||
pub fn new(version: Version) -> Self {
|
||||
Self { version }
|
||||
}
|
||||
}
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/types.rs"));
|
||||
|
|
|
|||
Loading…
Reference in a new issue