Bootstrap DD3 Rust port workspace with host-first compatibility tests

This commit is contained in:
2026-02-21 00:59:03 +01:00
parent d3f9a2e62d
commit d0212f4e38
63 changed files with 3914 additions and 0 deletions

8
crates/xtask/Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "xtask"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1"
regex = "1"

234
crates/xtask/src/main.rs Normal file
View File

@@ -0,0 +1,234 @@
use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Context, Result};
use regex::Regex;
const BASELINE_COMMIT: &str = "a3c61f9b929fbc55bfb502b443fba2f98023b3f1";
fn main() -> Result<()> {
let mut args = std::env::args().skip(1);
let cmd = args.next().unwrap_or_default();
match cmd.as_str() {
"sync-fixtures" => sync_fixtures(),
"check-manufacturer" => check_manufacturer(),
"verify-fixture-sources" => verify_fixture_sources(),
_ => {
eprintln!("usage: cargo run -p xtask -- <sync-fixtures|check-manufacturer|verify-fixture-sources>");
Err(anyhow!("unknown command"))
}
}
}
fn repo_root() -> Result<PathBuf> {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let root = manifest_dir
.parent()
.and_then(|p| p.parent())
.ok_or_else(|| anyhow!("failed to resolve workspace root"))?;
Ok(root.to_path_buf())
}
fn parse_cpp_array(src: &str, name: &str) -> Result<Vec<u8>> {
let re = Regex::new(&format!(
r"(?s)static\s+const\s+uint8_t\s+{}\[\]\s*=\s*\{{(?P<body>.*?)\}};",
name
))?;
let caps = re
.captures(src)
.ok_or_else(|| anyhow!("array {name} not found"))?;
let body = caps.name("body").unwrap().as_str();
let item_re = Regex::new(r"0x([0-9A-Fa-f]{1,2})")?;
let mut out = Vec::new();
for cap in item_re.captures_iter(body) {
let byte = u8::from_str_radix(&cap[1], 16)?;
out.push(byte);
}
Ok(out)
}
fn canonicalize_full_30_vector(mut bytes: Vec<u8>) -> Vec<u8> {
// Upstream pinned baseline vector in test_payload_codec.cpp is truncated by two
// final p3 signed-delta varints (expected +205, -195 => 0x9A 0x03 0x85 0x03).
// Keep raw provenance separately and write canonical bytes for host tests.
if bytes.len() == 183 {
bytes.extend_from_slice(&[0x9A, 0x03, 0x85, 0x03]);
}
bytes
}
fn crc16_ccitt(data: &[u8]) -> u16 {
let mut crc: u16 = 0xFFFF;
for byte in data {
crc ^= (*byte as u16) << 8;
for _ in 0..8 {
if (crc & 0x8000) != 0 {
crc = (crc << 1) ^ 0x1021;
} else {
crc <<= 1;
}
}
}
crc
}
fn write_bin(path: &Path, bytes: &[u8]) -> Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(path, bytes)?;
Ok(())
}
fn sync_fixtures() -> Result<()> {
let root = repo_root()?;
let payload_test = root.join("vendor/dd3-cpp/test/test_payload_codec/test_payload_codec.cpp");
let payload_src = fs::read_to_string(&payload_test).with_context(|| {
format!("failed reading {}", payload_test.display())
})?;
let sync_empty = parse_cpp_array(&payload_src, "VECTOR_SYNC_EMPTY")?;
let sparse_5 = parse_cpp_array(&payload_src, "VECTOR_SPARSE_5")?;
let full_30_raw = parse_cpp_array(&payload_src, "VECTOR_FULL_30")?;
let full_30 = canonicalize_full_30_vector(full_30_raw.clone());
write_bin(&root.join("fixtures/protocol/payload_v3/sync_empty.bin"), &sync_empty)?;
write_bin(&root.join("fixtures/protocol/payload_v3/sparse_5.bin"), &sparse_5)?;
write_bin(&root.join("fixtures/protocol/payload_v3/full_30.bin"), &full_30)?;
write_bin(
&root.join("fixtures/protocol/payload_v3/full_30_upstream_raw.bin"),
&full_30_raw,
)?;
let frame_payload = [0x01u8, 0x02, 0xA5];
let mut frame = vec![0x00, 0xF1, 0x9C];
frame.extend_from_slice(&frame_payload);
let crc = crc16_ccitt(&frame);
frame.extend_from_slice(&crc.to_be_bytes());
write_bin(&root.join("fixtures/protocol/frames/batchup_f19c_payload_0102a5.bin"), &frame)?;
let mut frame_bad = frame.clone();
let last = frame_bad.len() - 1;
frame_bad[last] ^= 0x01;
write_bin(&root.join("fixtures/protocol/frames/batchup_f19c_payload_0102a5_bad_crc.bin"), &frame_bad)?;
let mut chunk_ok = Vec::new();
// record format: [batch_id_le:2][idx:1][count:1][total_len_le:2][chunk_len:1][chunk_data]
chunk_ok.extend_from_slice(&77u16.to_le_bytes());
chunk_ok.extend_from_slice(&[0, 3]);
chunk_ok.extend_from_slice(&7u16.to_le_bytes());
chunk_ok.extend_from_slice(&[3, 1, 2, 3]);
chunk_ok.extend_from_slice(&77u16.to_le_bytes());
chunk_ok.extend_from_slice(&[1, 3]);
chunk_ok.extend_from_slice(&7u16.to_le_bytes());
chunk_ok.extend_from_slice(&[2, 4, 5]);
chunk_ok.extend_from_slice(&77u16.to_le_bytes());
chunk_ok.extend_from_slice(&[2, 3]);
chunk_ok.extend_from_slice(&7u16.to_le_bytes());
chunk_ok.extend_from_slice(&[2, 6, 7]);
write_bin(&root.join("fixtures/protocol/chunks/in_order_ok.bin"), &chunk_ok)?;
let mut chunk_missing = Vec::new();
chunk_missing.extend_from_slice(&10u16.to_le_bytes());
chunk_missing.extend_from_slice(&[0, 3]);
chunk_missing.extend_from_slice(&6u16.to_le_bytes());
chunk_missing.extend_from_slice(&[2, 9, 8]);
chunk_missing.extend_from_slice(&10u16.to_le_bytes());
chunk_missing.extend_from_slice(&[2, 3]);
chunk_missing.extend_from_slice(&6u16.to_le_bytes());
chunk_missing.extend_from_slice(&[2, 7, 6]);
write_bin(&root.join("fixtures/protocol/chunks/missing_chunk.bin"), &chunk_missing)?;
let mut chunk_wrong_total = Vec::new();
chunk_wrong_total.extend_from_slice(&55u16.to_le_bytes());
chunk_wrong_total.extend_from_slice(&[0, 2]);
chunk_wrong_total.extend_from_slice(&5u16.to_le_bytes());
chunk_wrong_total.extend_from_slice(&[3, 1, 2, 3]);
chunk_wrong_total.extend_from_slice(&55u16.to_le_bytes());
chunk_wrong_total.extend_from_slice(&[1, 2]);
chunk_wrong_total.extend_from_slice(&5u16.to_le_bytes());
chunk_wrong_total.extend_from_slice(&[3, 4, 5, 6]);
write_bin(&root.join("fixtures/protocol/chunks/wrong_total_len.bin"), &chunk_wrong_total)?;
let sources = format!(
"# Fixture Sources\n\n- Baseline repository: C3MA/DD3-LoRa-Bridge-MultiSender\n- Baseline branch: lora-refactor\n- Baseline commit: {BASELINE_COMMIT}\n- Payload vectors: vendor/dd3-cpp/test/test_payload_codec/test_payload_codec.cpp\n- Payload note: VECTOR_FULL_30 in pinned commit is 183-byte upstream raw (stored as full_30_upstream_raw.bin); canonical full_30.bin appends final two p3 deltas `9A 03 85 03` to satisfy baseline codec semantics.\n- Frame/chunk vectors: derived from vendor/dd3-cpp/test/test_lora_transport/test_lora_transport.cpp semantics\n"
);
fs::write(root.join("fixtures/protocol/SOURCES.md"), sources)?;
println!("fixtures synced");
Ok(())
}
fn check_manufacturer() -> Result<()> {
let root = repo_root()?;
let mut offenders = Vec::new();
fn walk(dir: &Path, files: &mut Vec<PathBuf>) -> Result<()> {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
walk(&path, files)?;
} else if path.extension().and_then(|x| x.to_str()) == Some("rs") {
files.push(path);
}
}
Ok(())
}
let mut files = Vec::new();
walk(&root.join("crates"), &mut files)?;
for file in files {
let txt = fs::read_to_string(&file)?;
if txt.contains("\"AcidBurns\"") {
let file_norm = file.to_string_lossy().replace('\\', "/");
let allow = file_norm.ends_with("/crates/dd3_contracts/src/lib.rs")
|| file_norm.ends_with("/crates/dd3_contracts/tests/contracts_tests.rs");
if !allow {
offenders.push(file);
}
}
}
if offenders.is_empty() {
println!("manufacturer drift check passed");
Ok(())
} else {
Err(anyhow!(
"unexpected hardcoded manufacturer literal(s): {:?}",
offenders
))
}
}
fn verify_fixture_sources() -> Result<()> {
let root = repo_root()?;
let path = root.join("fixtures/protocol/SOURCES.md");
let txt = fs::read_to_string(&path)
.with_context(|| format!("missing {}", path.display()))?;
if !txt.contains(BASELINE_COMMIT) {
return Err(anyhow!("SOURCES.md does not contain baseline commit"));
}
let required = [
"fixtures/protocol/payload_v3/sync_empty.bin",
"fixtures/protocol/payload_v3/sparse_5.bin",
"fixtures/protocol/payload_v3/full_30.bin",
"fixtures/protocol/frames/batchup_f19c_payload_0102a5.bin",
"fixtures/protocol/chunks/in_order_ok.bin",
];
for rel in required {
let full = root.join(rel);
if !full.exists() {
return Err(anyhow!("missing fixture {rel}"));
}
}
println!("fixture source metadata verified");
Ok(())
}