#[path = "src/schema_identity.rs"]
mod schema_identity;
use serde_json::Value;
use std::collections::HashMap;
use std::fmt::Write as _;
use std::fs;
use std::path::{Path, PathBuf};
fn required_str<'a>(value: &'a Value, key: &str) -> &'a str {
value[key]
.as_str()
.unwrap_or_else(|| panic!("schema field `{key}` must be a string"))
}
fn rust_string(value: &str) -> String {
format!("{value:?}")
}
fn generate_embedded_schema(schema_path: &Path, output_path: &Path) {
let mut generated = String::from(
"// Generated by build.rs. Static metadata stays in flash on no_std targets.\n",
);
if !schema_path.is_file() {
generated.push_str("const EMBEDDED_SCHEMA_ENDPOINTS: &[EndpointDefinition] = &[];\n");
generated.push_str("const EMBEDDED_SCHEMA_TYPES: &[DataTypeDefinition] = &[];\n");
fs::write(output_path, generated).expect("write empty embedded schema");
return;
}
let bytes = fs::read(schema_path).expect("read telemetry_config.json");
let schema: Value = serde_json::from_slice(&bytes).expect("parse telemetry_config.json");
let endpoints = schema["endpoints"]
.as_array()
.expect("schema endpoints must be an array");
let types = schema["types"]
.as_array()
.expect("schema types must be an array");
let mut endpoint_ids = HashMap::new();
let mut endpoint_identity_names = HashMap::new();
let mut type_identity_names = HashMap::new();
let mut endpoints = endpoints.iter().collect::<Vec<_>>();
endpoints
.sort_unstable_by_key(|value| schema_identity::endpoint_id(required_str(value, "name")));
let mut types = types.iter().collect::<Vec<_>>();
types.sort_unstable_by_key(|value| schema_identity::type_id(required_str(value, "name")));
generated.push_str("const EMBEDDED_SCHEMA_ENDPOINTS: &[EndpointDefinition] = &[\n");
for endpoint in endpoints {
let name = required_str(endpoint, "name");
let id = schema_identity::endpoint_id(name);
if let Some(previous) = endpoint_identity_names.insert(id, name) {
assert_eq!(previous, name, "endpoint identity collision");
}
let rust_name = endpoint["rust"].as_str().unwrap_or(name);
let link_local = endpoint["link_local_only"].as_bool().unwrap_or(false)
|| endpoint["broadcast_mode"].as_str() == Some("Never");
endpoint_ids.insert(rust_name.to_owned(), id);
writeln!(
generated,
" EndpointDefinition {{ id: DataEndpoint({id}), name: {}, description: {}, link_local_only: {link_local} }},",
rust_string(name),
rust_string(""),
)
.unwrap();
}
generated.push_str("];\n");
generated.push_str("const EMBEDDED_SCHEMA_TYPES: &[DataTypeDefinition] = &[\n");
for ty in types {
let name = required_str(ty, "name");
let id = schema_identity::type_id(name);
if let Some(previous) = type_identity_names.insert(id, name) {
assert_eq!(previous, name, "type identity collision");
}
let class = match required_str(ty, "class") {
"Data" => "MessageClass::Data",
"Error" => "MessageClass::Error",
"Warning" => "MessageClass::Warning",
other => panic!("unsupported message class `{other}`"),
};
let data_type = match required_str(&ty["element"], "data_type") {
"Float64" => "MessageDataType::Float64",
"Float32" => "MessageDataType::Float32",
"UInt8" => "MessageDataType::UInt8",
"UInt16" => "MessageDataType::UInt16",
"UInt32" => "MessageDataType::UInt32",
"UInt64" => "MessageDataType::UInt64",
"UInt128" => "MessageDataType::UInt128",
"Int8" => "MessageDataType::Int8",
"Int16" => "MessageDataType::Int16",
"Int32" => "MessageDataType::Int32",
"Int64" => "MessageDataType::Int64",
"Int128" => "MessageDataType::Int128",
"Bool" => "MessageDataType::Bool",
"String" => "MessageDataType::String",
"Binary" => "MessageDataType::Binary",
"NoData" => "MessageDataType::NoData",
other => panic!("unsupported message data type `{other}`"),
};
let element = match required_str(&ty["element"], "kind") {
"Static" => format!(
"MessageElement::Static({}, {data_type}, {class})",
ty["element"]["count"].as_u64().unwrap_or(1)
),
"Dynamic" => format!("MessageElement::Dynamic({data_type}, {class})"),
other => panic!("unsupported message element kind `{other}`"),
};
assert!(
ty.get("reliable_mode").is_none(),
"use a single reliable field"
);
let reliable = match required_str(ty, "reliable") {
"Off" => "ReliableMode::None",
"Ordered" => "ReliableMode::Ordered",
"Unordered" => "ReliableMode::Unordered",
other => panic!("unsupported reliability `{other}`"),
};
let e2e = match ty["e2e_encryption"].as_str().unwrap_or("PreferOff") {
"PreferOff" | "prefer_off" | "off" | "false" => "E2eEncryptionPolicy::PreferOff",
"PreferOn" | "prefer_on" | "preferred" | "true" => "E2eEncryptionPolicy::PreferOn",
"RequireOn" | "require_on" | "required" => "E2eEncryptionPolicy::RequireOn",
other => panic!("unsupported e2e encryption policy `{other}`"),
};
let endpoint_names = ty["endpoints"]
.as_array()
.expect("type endpoints must be an array");
let endpoint_expr = endpoint_names
.iter()
.map(|endpoint| {
let name = endpoint.as_str().expect("type endpoint must be a string");
let id = endpoint_ids
.get(name)
.copied()
.unwrap_or_else(|| schema_identity::endpoint_id(name));
format!("DataEndpoint({id})")
})
.collect::<Vec<_>>()
.join(", ");
let priority = ty["priority"].as_u64().unwrap_or(0);
writeln!(
generated,
" DataTypeDefinition {{ id: DataType({id}), name: {}, description: {}, element: {element}, endpoints: &[{endpoint_expr}], reliable: {reliable}, priority: {priority}, e2e_encryption: {e2e} }},",
rust_string(name),
rust_string(""),
)
.unwrap();
}
generated.push_str("];\n");
fs::write(output_path, generated).expect("write generated embedded schema");
}
fn main() {
println!("cargo:rustc-check-cfg=cfg(sedsnet_has_telemetry_config_json)");
println!("cargo:rerun-if-changed=telemetry_config.json");
println!("cargo:rerun-if-changed=src/schema_identity.rs");
println!("cargo:rerun-if-env-changed=SEDSNET_EMBEDDED_SCHEMA_PATH");
let schema_override = std::env::var_os("SEDSNET_EMBEDDED_SCHEMA_PATH").map(PathBuf::from);
let schema_path = schema_override
.as_deref()
.unwrap_or(Path::new("telemetry_config.json"));
println!("cargo:rerun-if-changed={}", schema_path.display());
if schema_path.is_file() {
println!("cargo:rustc-cfg=sedsnet_has_telemetry_config_json");
println!(
"cargo:rustc-env=SEDSNET_BUNDLED_SCHEMA_PATH={}",
schema_path.canonicalize().expect("schema path").display()
);
}
let output_path =
PathBuf::from(std::env::var_os("OUT_DIR").expect("OUT_DIR")).join("embedded_schema.rs");
generate_embedded_schema(schema_path, &output_path);
for key in [
"DEVICE_IDENTIFIER",
"MAX_RECENT_RX_IDS",
"STARTING_QUEUE_SIZE",
"MAX_QUEUE_BUDGET",
"MAX_QUEUE_SIZE",
"QUEUE_GROW_STEP",
"PAYLOAD_COMPRESS_THRESHOLD",
"STATIC_STRING_LENGTH",
"STATIC_HEX_LENGTH",
"STRING_PRECISION",
"MAX_STACK_PAYLOAD",
"MAX_HANDLER_RETRIES",
"RELIABLE_RETRANSMIT_MS",
"RELIABLE_MAX_RETRIES",
"RELIABLE_MAX_PENDING",
"RELIABLE_MAX_RETURN_ROUTES",
"RELIABLE_MAX_END_TO_END_PENDING",
"RELIABLE_MAX_END_TO_END_ACK_CACHE",
"SEDSNET_STATIC_SCHEMA_PATH",
"SEDSNET_STATIC_IPC_SCHEMA_PATH",
] {
println!("cargo:rerun-if-env-changed={key}");
}
}