feat(core): workspace, Observation, content-derived id
This commit is contained in:
Generated
+220
@@ -0,0 +1,220 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cobs"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hash32"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heapless"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
|
||||||
|
dependencies = [
|
||||||
|
"hash32",
|
||||||
|
"serde",
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postcard"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
|
||||||
|
dependencies = [
|
||||||
|
"cobs",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_core"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stable_deref_trait"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tines-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"blake2",
|
||||||
|
"heapless",
|
||||||
|
"postcard",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tines-sim"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.20.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[workspace]
|
||||||
|
members = ["tines-core", "tines-sim"]
|
||||||
|
resolver = "2"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "stable"
|
||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
rustup target add thumbv7em-none-eabihf
|
||||||
|
cargo build -p tines-core --no-default-features --target thumbv7em-none-eabihf
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "tines-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["alloc"]
|
||||||
|
alloc = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
heapless = { version = "0.8", features = ["serde"] }
|
||||||
|
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||||
|
postcard = { version = "1", default-features = false }
|
||||||
|
blake2 = { version = "0.10", default-features = false }
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
//! Compile-time constants for the tines workspace.
|
||||||
|
//!
|
||||||
|
//! Every value here is a compile-time `const` so it can drive generic
|
||||||
|
//! parameters (e.g. `heapless::Vec` capacities) and be optimized away.
|
||||||
|
|
||||||
|
/// Maximum topic length in bytes.
|
||||||
|
pub const MAX_TOPIC: usize = 64;
|
||||||
|
|
||||||
|
/// Maximum payload length in bytes.
|
||||||
|
pub const MAX_PAYLOAD: usize = 1024;
|
||||||
|
|
||||||
|
/// Maximum number of peers a node tracks.
|
||||||
|
pub const MAX_PEERS: usize = 8;
|
||||||
|
|
||||||
|
/// Maximum size, in bytes, of a per-key summary.
|
||||||
|
pub const SUMMARY_BYTES: usize = 512;
|
||||||
|
|
||||||
|
/// Number of hash functions used by the per-key Bloom filter.
|
||||||
|
pub const BLOOM_K: usize = 3;
|
||||||
|
|
||||||
|
/// Interval, in milliseconds, between summary emissions.
|
||||||
|
pub const SUMMARY_INTERVAL_MS: u64 = 2000;
|
||||||
|
|
||||||
|
/// Timeout, in milliseconds, for inbound frame reassembly.
|
||||||
|
pub const REASSEMBLY_TIMEOUT_MS: u64 = 5000;
|
||||||
|
|
||||||
|
/// Maximum number of frames buffered for outbound delivery.
|
||||||
|
pub const SEND_QUEUE_CAP: usize = 256;
|
||||||
|
|
||||||
|
/// Maximum UDP frame size in bytes.
|
||||||
|
pub const MAX_FRAME: usize = 1500;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
//! tines-core: content-defined Observation identity and shared-memory core.
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
pub mod consts;
|
||||||
|
pub mod observation;
|
||||||
|
|
||||||
|
pub use observation::{Observation, NodeId, ObservationId, Payload, Tick, Topic};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate std;
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
//! Content-defined `Observation` identity.
|
||||||
|
|
||||||
|
use crate::consts;
|
||||||
|
use blake2::{Blake2s256, Digest};
|
||||||
|
use heapless::Vec;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// 16-byte node identifier.
|
||||||
|
pub type NodeId = [u8; 16];
|
||||||
|
/// 16-byte content-derived observation identifier (first 16 bytes of Blake2s-256).
|
||||||
|
pub type ObservationId = [u8; 16];
|
||||||
|
/// Monotonic sequence number / tick.
|
||||||
|
pub type Tick = u64;
|
||||||
|
/// Heapless, fixed-capacity topic.
|
||||||
|
pub type Topic = Vec<u8, { consts::MAX_TOPIC }>;
|
||||||
|
/// Heapless, fixed-capacity payload.
|
||||||
|
pub type Payload = Vec<u8, { consts::MAX_PAYLOAD }>;
|
||||||
|
|
||||||
|
/// An observation published by a node, with content-derived identity.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct Observation {
|
||||||
|
pub observer: NodeId,
|
||||||
|
pub seq: u64,
|
||||||
|
pub age_ms: u64,
|
||||||
|
pub observed_at: Option<u64>,
|
||||||
|
pub topic: Topic,
|
||||||
|
pub payload: Payload,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Observation {
|
||||||
|
/// Content-derived identity: Blake2s-256 over
|
||||||
|
/// `(observer, seq, topic_len, topic, payload_len, payload)`, first 16 bytes.
|
||||||
|
///
|
||||||
|
/// The hash ignores `age_ms` and `observed_at`, so two observations that
|
||||||
|
/// differ only in age/time are still treated as the same key.
|
||||||
|
pub fn id(&self) -> ObservationId {
|
||||||
|
let mut hasher = Blake2s256::new();
|
||||||
|
hasher.update(self.observer);
|
||||||
|
hasher.update(self.seq.to_le_bytes());
|
||||||
|
hasher.update((self.topic.len() as u16).to_le_bytes());
|
||||||
|
hasher.update(&self.topic[..]);
|
||||||
|
hasher.update((self.payload.len() as u16).to_le_bytes());
|
||||||
|
hasher.update(&self.payload[..]);
|
||||||
|
let digest = hasher.finalize();
|
||||||
|
let mut id = [0u8; 16];
|
||||||
|
id.copy_from_slice(&digest[..16]);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a copy with `age_ms` advanced by `elapsed_ms` (saturating).
|
||||||
|
pub fn aged(&self, elapsed_ms: u64) -> Observation {
|
||||||
|
Observation {
|
||||||
|
observer: self.observer,
|
||||||
|
seq: self.seq,
|
||||||
|
age_ms: self.age_ms.saturating_add(elapsed_ms),
|
||||||
|
observed_at: self.observed_at,
|
||||||
|
topic: self.topic.clone(),
|
||||||
|
payload: self.payload.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn obs(seq: u64, age: u64, at: Option<u64>) -> Observation {
|
||||||
|
Observation {
|
||||||
|
observer: [1; 16],
|
||||||
|
seq,
|
||||||
|
age_ms: age,
|
||||||
|
observed_at: at,
|
||||||
|
topic: Topic::from_slice(b"door/3").unwrap(),
|
||||||
|
payload: Payload::from_slice(b"open").unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn id_ignores_age_and_observed_at() {
|
||||||
|
assert_eq!(obs(1, 0, None).id(), obs(1, 999, Some(5)).id());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn id_changes_with_seq() {
|
||||||
|
assert_ne!(obs(1, 0, None).id(), obs(2, 0, None).id());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn id_changes_with_observer() {
|
||||||
|
let mut b = obs(1, 0, None);
|
||||||
|
b.observer = [2; 16];
|
||||||
|
assert_ne!(obs(1, 0, None).id(), b.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn id_distinguishes_topic_payload_boundary() {
|
||||||
|
// topic "ab" payload "c" vs topic "a" payload "bc" must differ
|
||||||
|
let mut x = obs(1, 0, None);
|
||||||
|
x.topic = Topic::from_slice(b"ab").unwrap();
|
||||||
|
x.payload = Payload::from_slice(b"c").unwrap();
|
||||||
|
let mut y = obs(1, 0, None);
|
||||||
|
y.topic = Topic::from_slice(b"a").unwrap();
|
||||||
|
y.payload = Payload::from_slice(b"bc").unwrap();
|
||||||
|
assert_ne!(x.id(), y.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn aged_saturates() {
|
||||||
|
assert_eq!(obs(1, u64::MAX - 1, None).aged(10).age_ms, u64::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn id_known_vector() {
|
||||||
|
// pin the hash layout: observer=[1;16], seq=7, topic="t", payload="p"
|
||||||
|
let mut o = obs(7, 0, None);
|
||||||
|
o.topic = Topic::from_slice(b"t").unwrap();
|
||||||
|
o.payload = Payload::from_slice(b"p").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
o.id(),
|
||||||
|
[
|
||||||
|
0x92, 0x37, 0x37, 0xa1, 0x06, 0x9b, 0xd9, 0x5e, 0x08, 0xc2, 0xe6, 0x39, 0x7d, 0x4a, 0x8f, 0xd0
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[package]
|
||||||
|
name = "tines-sim"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
Reference in New Issue
Block a user