feat(core): reserved Signature slot on Observation
This commit is contained in:
@@ -108,6 +108,7 @@ mod tests {
|
|||||||
topic: Topic::from_slice(b"door/3").unwrap(),
|
topic: Topic::from_slice(b"door/3").unwrap(),
|
||||||
payload: Payload::from_slice(b"open").unwrap(),
|
payload: Payload::from_slice(b"open").unwrap(),
|
||||||
dest: None,
|
dest: None,
|
||||||
|
sig: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ pub const MAX_TOPIC: usize = 64;
|
|||||||
/// Maximum payload length in bytes.
|
/// Maximum payload length in bytes.
|
||||||
pub const MAX_PAYLOAD: usize = 1024;
|
pub const MAX_PAYLOAD: usize = 1024;
|
||||||
|
|
||||||
|
/// Maximum signature length in bytes. The 1.0 signature slot reserves this on
|
||||||
|
/// every observation (`[crate::observation::Signature]`); it is carried verbatim
|
||||||
|
/// and never signed or verified until 1.1.
|
||||||
|
pub const MAX_SIG: usize = 64;
|
||||||
|
|
||||||
/// Maximum number of peers a node tracks.
|
/// Maximum number of peers a node tracks.
|
||||||
pub const MAX_PEERS: usize = 8;
|
pub const MAX_PEERS: usize = 8;
|
||||||
|
|
||||||
@@ -36,6 +41,12 @@ pub const REASSEMBLY_TIMEOUT_MS: u64 = 5000;
|
|||||||
/// Maximum number of frames buffered for outbound delivery.
|
/// Maximum number of frames buffered for outbound delivery.
|
||||||
pub const SEND_QUEUE_CAP: usize = 256;
|
pub const SEND_QUEUE_CAP: usize = 256;
|
||||||
|
|
||||||
|
/// Minimum usable UDP frame size in bytes (the BLE floor below which a
|
||||||
|
/// fragment header leaves no room for payload). [`crate::frag::fragment`] gives
|
||||||
|
/// up cleanly with `WireError::TooLarge` for any `mtu` below this rather than
|
||||||
|
/// emitting a truncated frame; the MTU sweep runs from here to [`MAX_FRAME`].
|
||||||
|
pub const MIN_MTU: usize = 23;
|
||||||
|
|
||||||
/// Maximum UDP frame size in bytes.
|
/// Maximum UDP frame size in bytes.
|
||||||
pub const MAX_FRAME: usize = 1500;
|
pub const MAX_FRAME: usize = 1500;
|
||||||
|
|
||||||
|
|||||||
@@ -1292,6 +1292,7 @@ mod tests {
|
|||||||
topic: Topic::from_slice(topic).unwrap(),
|
topic: Topic::from_slice(topic).unwrap(),
|
||||||
payload: Payload::from_slice(payload).unwrap(),
|
payload: Payload::from_slice(payload).unwrap(),
|
||||||
dest: None,
|
dest: None,
|
||||||
|
sig: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -218,6 +218,113 @@ mod tests {
|
|||||||
topic: Topic::from_slice(b"door/3").unwrap(),
|
topic: Topic::from_slice(b"door/3").unwrap(),
|
||||||
payload: Payload::from_slice(&std::vec![0u8; payload_len]).unwrap(),
|
payload: Payload::from_slice(&std::vec![0u8; payload_len]).unwrap(),
|
||||||
dest: None,
|
dest: None,
|
||||||
|
sig: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signed_max_obs(sig: crate::Signature) -> Observation {
|
||||||
|
let mut obs = obs_with_payload([1; 16], consts::MAX_PAYLOAD);
|
||||||
|
obs.sig = Some(sig);
|
||||||
|
obs
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reassembled_bad_signature_is_malformed() {
|
||||||
|
// A maximum-size signed obs carrying a malformed Ed25519 signature (wrong
|
||||||
|
// length) must be rejected on reassembly — never returned — exactly as a
|
||||||
|
// single-frame decode would reject it.
|
||||||
|
let obs = signed_max_obs(crate::Signature {
|
||||||
|
version: 1,
|
||||||
|
algo: crate::SigAlgo::Ed25519,
|
||||||
|
bytes: heapless::Vec::from_slice(&[0u8; 63]).unwrap(),
|
||||||
|
});
|
||||||
|
let mut frames: Vec<Vec<u8>> = Vec::new();
|
||||||
|
fragment([1; 16], &obs, 200, &mut |b| frames.push(b.to_vec()))
|
||||||
|
.expect("fragment signed max obs");
|
||||||
|
let mut r = Reassembler::new();
|
||||||
|
let mut last: Option<Observation> = None;
|
||||||
|
for f in frames.iter() {
|
||||||
|
if let Frame::Frag {
|
||||||
|
id,
|
||||||
|
index,
|
||||||
|
count,
|
||||||
|
data,
|
||||||
|
..
|
||||||
|
} = Frame::decode(f).unwrap()
|
||||||
|
{
|
||||||
|
last = r.feed(id, index, count, &data, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
last.is_none(),
|
||||||
|
"a malformed signature on reassembly must be rejected, never returned"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reassembled_unknown_algo_signature_roundtrips() {
|
||||||
|
// An unknown-algorithm signature is carried verbatim on reassembly too.
|
||||||
|
let obs = signed_max_obs(crate::Signature {
|
||||||
|
version: 2,
|
||||||
|
algo: crate::SigAlgo::Unknown(7),
|
||||||
|
bytes: heapless::Vec::from_slice(&[9u8; 10]).unwrap(),
|
||||||
|
});
|
||||||
|
let mut frames: Vec<Vec<u8>> = Vec::new();
|
||||||
|
fragment([1; 16], &obs, 200, &mut |b| frames.push(b.to_vec()))
|
||||||
|
.expect("fragment signed max obs");
|
||||||
|
let mut r = Reassembler::new();
|
||||||
|
let mut last: Option<Observation> = None;
|
||||||
|
for f in frames.iter() {
|
||||||
|
if let Frame::Frag {
|
||||||
|
id,
|
||||||
|
index,
|
||||||
|
count,
|
||||||
|
data,
|
||||||
|
..
|
||||||
|
} = Frame::decode(f).unwrap()
|
||||||
|
{
|
||||||
|
last = r.feed(id, index, count, &data, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(last, Some(obs), "unknown-algo sig must reassemble verbatim");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fragmentation_fits_mtu_sweep() {
|
||||||
|
// Review Focus #6: a full-size signed obs (MAX_PAYLOAD + 64-byte Ed25519
|
||||||
|
// sig) must fragment into <= MAX_FRAGMENTS frames each <= mtu, or — below
|
||||||
|
// the BLE floor — be given up cleanly as TooLarge, never emitted
|
||||||
|
// oversized, across every mtu from MIN_MTU to MAX_FRAME.
|
||||||
|
let obs = signed_max_obs(crate::Signature {
|
||||||
|
version: 1,
|
||||||
|
algo: crate::SigAlgo::Ed25519,
|
||||||
|
bytes: heapless::Vec::from_slice(&[0u8; 64]).unwrap(),
|
||||||
|
});
|
||||||
|
for mtu in consts::MIN_MTU..=consts::MAX_FRAME {
|
||||||
|
let mut frames: Vec<Vec<u8>> = Vec::new();
|
||||||
|
match fragment([1; 16], &obs, mtu, &mut |b| frames.push(b.to_vec())) {
|
||||||
|
Ok(count) => {
|
||||||
|
assert!(
|
||||||
|
count <= MAX_FRAGMENTS,
|
||||||
|
"frame count {count} exceeds MAX_FRAGMENTS {MAX_FRAGMENTS}"
|
||||||
|
);
|
||||||
|
assert_eq!(frames.len(), count, "emit count must match at mtu {mtu}");
|
||||||
|
for f in &frames {
|
||||||
|
assert!(
|
||||||
|
f.len() <= mtu,
|
||||||
|
"frame of {} bytes exceeds mtu {mtu}",
|
||||||
|
f.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(crate::wire::WireError::TooLarge) => {
|
||||||
|
assert!(
|
||||||
|
frames.is_empty(),
|
||||||
|
"TooLarge at mtu {mtu} must emit no frames"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(other) => panic!("unexpected error {other:?} at mtu {mtu}"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ pub use bloom::Bloom;
|
|||||||
pub use exchange::{DropReason, Exchange, NodeEvent, Peer};
|
pub use exchange::{DropReason, Exchange, NodeEvent, Peer};
|
||||||
pub use frag::{fragment, Reassembler};
|
pub use frag::{fragment, Reassembler};
|
||||||
pub use node::{Node, ObserveError};
|
pub use node::{Node, ObserveError};
|
||||||
pub use observation::{Dest, NodeId, Observation, ObservationId, Payload, Tick, Topic};
|
pub use observation::{
|
||||||
|
Dest, NodeId, Observation, ObservationId, Payload, SigAlgo, Signature, Tick, Topic,
|
||||||
|
};
|
||||||
pub use store::{Store, StoreError, TrimPolicy};
|
pub use store::{Store, StoreError, TrimPolicy};
|
||||||
pub use transport::{Event, FrameBuf, SendError, Transport};
|
pub use transport::{Event, FrameBuf, SendError, Transport};
|
||||||
pub use wire::{Frame, WireError};
|
pub use wire::{Frame, WireError};
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ mod tests {
|
|||||||
topic: Topic::from_slice(topic).unwrap(),
|
topic: Topic::from_slice(topic).unwrap(),
|
||||||
payload: Payload::from_slice(b"").unwrap(),
|
payload: Payload::from_slice(b"").unwrap(),
|
||||||
dest: None,
|
dest: None,
|
||||||
|
sig: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ impl<S: Store, T: Transport> Node<S, T> {
|
|||||||
topic,
|
topic,
|
||||||
payload,
|
payload,
|
||||||
dest: None,
|
dest: None,
|
||||||
|
sig: None,
|
||||||
};
|
};
|
||||||
self.next_seq += 1;
|
self.next_seq += 1;
|
||||||
let id = obs.id();
|
let id = obs.id();
|
||||||
@@ -128,6 +129,7 @@ impl<S: Store, T: Transport> Node<S, T> {
|
|||||||
topic,
|
topic,
|
||||||
payload,
|
payload,
|
||||||
dest: Some(Dest { to, lifetime_ms }),
|
dest: Some(Dest { to, lifetime_ms }),
|
||||||
|
sig: None,
|
||||||
};
|
};
|
||||||
self.next_seq += 1;
|
self.next_seq += 1;
|
||||||
let id = obs.id();
|
let id = obs.id();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use crate::consts;
|
use crate::consts;
|
||||||
use blake2::{Blake2s256, Digest};
|
use blake2::{Blake2s256, Digest};
|
||||||
use heapless::Vec;
|
use heapless::Vec;
|
||||||
|
use serde::de::Deserializer;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// 16-byte node identifier.
|
/// 16-byte node identifier.
|
||||||
@@ -55,6 +56,13 @@ pub struct Observation {
|
|||||||
/// Addressing: `None` is a broadcast, `Some(dest)` is addressed to
|
/// Addressing: `None` is a broadcast, `Some(dest)` is addressed to
|
||||||
/// `dest.to` and expires per `dest.lifetime_ms`.
|
/// `dest.to` and expires per `dest.lifetime_ms`.
|
||||||
pub dest: Option<Dest>,
|
pub dest: Option<Dest>,
|
||||||
|
|
||||||
|
/// The observer's signature over this observation, if any.
|
||||||
|
///
|
||||||
|
/// The 1.0 wire reserves this slot on every observation and carries a
|
||||||
|
/// signature verbatim (see [`Signature`] / [`SigAlgo`]), but 1.0 does not sign
|
||||||
|
/// or verify it. Signing and verification arrive in 1.1.
|
||||||
|
pub sig: Option<Signature>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Observation {
|
impl Observation {
|
||||||
@@ -103,6 +111,7 @@ impl Observation {
|
|||||||
topic: self.topic.clone(),
|
topic: self.topic.clone(),
|
||||||
payload: self.payload.clone(),
|
payload: self.payload.clone(),
|
||||||
dest: self.dest.clone(),
|
dest: self.dest.clone(),
|
||||||
|
sig: self.sig.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +127,7 @@ impl Observation {
|
|||||||
topic: Topic::from_slice(topic).expect("topic fits MAX_TOPIC"),
|
topic: Topic::from_slice(topic).expect("topic fits MAX_TOPIC"),
|
||||||
payload: Payload::from_slice(payload).expect("payload fits MAX_PAYLOAD"),
|
payload: Payload::from_slice(payload).expect("payload fits MAX_PAYLOAD"),
|
||||||
dest: None,
|
dest: None,
|
||||||
|
sig: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +151,7 @@ impl Observation {
|
|||||||
topic: Topic::from_slice(topic).expect("topic fits MAX_TOPIC"),
|
topic: Topic::from_slice(topic).expect("topic fits MAX_TOPIC"),
|
||||||
payload: Payload::from_slice(payload).expect("payload fits MAX_PAYLOAD"),
|
payload: Payload::from_slice(payload).expect("payload fits MAX_PAYLOAD"),
|
||||||
dest: Some(Dest { to, lifetime_ms }),
|
dest: Some(Dest { to, lifetime_ms }),
|
||||||
|
sig: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +163,115 @@ impl Observation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A signature scheme identifier, carried in [`Signature::algo`].
|
||||||
|
///
|
||||||
|
/// On the wire `algo` is a raw `u8` — the `#[repr(u8)]` discriminant, *not* a
|
||||||
|
/// postcard enum tag: [`SigAlgo::Ed25519`] is the byte `1`, and an algorithm
|
||||||
|
/// `tines-core` does not recognize is carried as [`SigAlgo::Unknown`] with its
|
||||||
|
/// raw id, never rejected. `0` is never valid here (it is `Malformed` on decode).
|
||||||
|
///
|
||||||
|
/// Signing and verification arrive in 1.1.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum SigAlgo {
|
||||||
|
/// Ed25519: a 64-byte Ed25519 signature.
|
||||||
|
Ed25519 = 1,
|
||||||
|
/// An algorithm id `tines-core` does not recognize, carried verbatim.
|
||||||
|
/// Any `u8` other than `0` (the malformed sentinel) and `1` (`Ed25519`) is
|
||||||
|
/// preserved so a 1.0 node can forward it untouched to a 1.1 node.
|
||||||
|
Unknown(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An observation's signature: the observer's over the whole observation.
|
||||||
|
///
|
||||||
|
/// The slot is reserved on the 1.0 wire but *unsigned* in 1.0: this type only
|
||||||
|
/// carries a signature verbatim, enforcing the shape rules below on decode.
|
||||||
|
/// Signing and verification arrive in 1.1.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||||
|
pub struct Signature {
|
||||||
|
/// Signature format version. `0` is rejected as `Malformed` on decode;
|
||||||
|
/// every other value (including any version `tines-core` does not know) is
|
||||||
|
/// carried verbatim.
|
||||||
|
pub version: u8,
|
||||||
|
/// The algorithm the signature is over. Serialized as a raw `u8` on the
|
||||||
|
/// wire (its `#[repr(u8)]` discriminant, so [`SigAlgo::Ed25519`] is `1`);
|
||||||
|
/// unknown ids are preserved as [`SigAlgo::Unknown`] rather than rejected.
|
||||||
|
#[serde(with = "sig_algo_wire")]
|
||||||
|
pub algo: SigAlgo,
|
||||||
|
/// The signature bytes. For [`SigAlgo::Ed25519`] these MUST be exactly 64
|
||||||
|
/// bytes (a different length is `Malformed`); other algorithms carry any
|
||||||
|
/// length. Never rejected for being unparseable — carried as-is.
|
||||||
|
pub bytes: heapless::Vec<u8, { consts::MAX_SIG }>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Signature {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct RawSignature {
|
||||||
|
version: u8,
|
||||||
|
#[serde(with = "sig_algo_wire")]
|
||||||
|
algo: SigAlgo,
|
||||||
|
bytes: heapless::Vec<u8, { consts::MAX_SIG }>,
|
||||||
|
}
|
||||||
|
let raw = RawSignature::deserialize(deserializer)?;
|
||||||
|
// A malformed signature (bad version, bad or wrong-length algorithm) is
|
||||||
|
// carried by no frame: it is a decode failure, never a panic.
|
||||||
|
if raw.version == 0 {
|
||||||
|
return Err(serde::de::Error::custom("signature version 0 is malformed"));
|
||||||
|
}
|
||||||
|
if matches!(raw.algo, SigAlgo::Ed25519) && raw.bytes.len() != 64 {
|
||||||
|
return Err(serde::de::Error::custom(
|
||||||
|
"Ed25519 signature must be exactly 64 bytes",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(Signature {
|
||||||
|
version: raw.version,
|
||||||
|
algo: raw.algo,
|
||||||
|
bytes: raw.bytes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wire encoding of [`SigAlgo`]: a raw `u8` (its `#[repr(u8)]` discriminant),
|
||||||
|
/// not a postcard enum tag. Used via `#[serde(with = "sig_algo_wire")]` on the
|
||||||
|
/// [`Signature::algo`] field so an unknown id survives decode as [`SigAlgo::Unknown`].
|
||||||
|
mod sig_algo_wire {
|
||||||
|
use super::SigAlgo;
|
||||||
|
use serde::de::Deserializer;
|
||||||
|
use serde::ser::Serializer;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
pub fn serialize<S>(algo: &SigAlgo, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
match algo {
|
||||||
|
// `Ed25519` is the byte `1`; any carried `Unknown(n)` is its raw id.
|
||||||
|
SigAlgo::Ed25519 => serializer.serialize_u8(1),
|
||||||
|
SigAlgo::Unknown(id) => serializer.serialize_u8(*id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<SigAlgo, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let raw = u8::deserialize(deserializer)?;
|
||||||
|
// `0` is the malformed sentinel on the wire; `1` is `Ed25519`; every
|
||||||
|
// other id is carried as `Unknown(n)` so it forwards untouched.
|
||||||
|
match raw {
|
||||||
|
0 => Err(serde::de::Error::custom("signature algo id 0 is malformed")),
|
||||||
|
1 => Ok(SigAlgo::Ed25519),
|
||||||
|
id => Ok(SigAlgo::Unknown(id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -165,9 +285,31 @@ mod tests {
|
|||||||
topic: Topic::from_slice(b"door/3").unwrap(),
|
topic: Topic::from_slice(b"door/3").unwrap(),
|
||||||
payload: Payload::from_slice(b"open").unwrap(),
|
payload: Payload::from_slice(b"open").unwrap(),
|
||||||
dest,
|
dest,
|
||||||
|
sig: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sig(version: u8, algo: SigAlgo, bytes: &[u8]) -> Signature {
|
||||||
|
Signature {
|
||||||
|
version,
|
||||||
|
algo,
|
||||||
|
bytes: Vec::from_slice(bytes).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sig_excluded_from_id() {
|
||||||
|
// A signature must not affect identity: a bare, a full Ed25519 and an
|
||||||
|
// arbitrary unknown-algorithm sig all share the observation's id.
|
||||||
|
let base = obs(5, 0, None, None);
|
||||||
|
let mut signed = obs(5, 0, None, None);
|
||||||
|
signed.sig = Some(sig(1, SigAlgo::Ed25519, &[0u8; 64]));
|
||||||
|
assert_eq!(base.id(), signed.id(), "sig must not affect id");
|
||||||
|
let mut other = obs(5, 0, None, None);
|
||||||
|
other.sig = Some(sig(2, SigAlgo::Unknown(9), &[1u8; 10]));
|
||||||
|
assert_eq!(base.id(), other.id());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn id_ignores_age_and_observed_at() {
|
fn id_ignores_age_and_observed_at() {
|
||||||
assert_eq!(obs(1, 0, None, None).id(), obs(1, 999, Some(5), None).id());
|
assert_eq!(obs(1, 0, None, None).id(), obs(1, 999, Some(5), None).id());
|
||||||
@@ -360,4 +502,47 @@ mod tests {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sig_algo_wire_roundtrip() {
|
||||||
|
// Review Focus #3: the raw-u8 wire encoding round-trips a known Ed25519
|
||||||
|
// and every unknown id verbatim, rejects the malformed sentinel, and
|
||||||
|
// never maps a wire byte of 1 to Unknown(1). Exercises the algo field
|
||||||
|
// through Signature, since SigAlgo serializes only via its serde module.
|
||||||
|
let make = |algo: SigAlgo| Signature {
|
||||||
|
version: 1,
|
||||||
|
algo,
|
||||||
|
bytes: heapless::Vec::<u8, { crate::consts::MAX_SIG }>::from_slice(&[0u8; 64]).unwrap(),
|
||||||
|
};
|
||||||
|
for (algo, wire_byte) in [
|
||||||
|
(SigAlgo::Ed25519, 1u8),
|
||||||
|
(SigAlgo::Unknown(7), 7u8),
|
||||||
|
(SigAlgo::Unknown(255), 255u8),
|
||||||
|
] {
|
||||||
|
let sig = make(algo);
|
||||||
|
let mut buf = [0u8; consts::MAX_SIG + 8];
|
||||||
|
let n = postcard::to_slice(&sig, &mut buf).unwrap();
|
||||||
|
assert_eq!(n[0], 1, "version serializes as the first byte");
|
||||||
|
assert_eq!(n[1], wire_byte, "algo is its raw repr discriminant");
|
||||||
|
let decoded: Signature = postcard::from_bytes(&n[..]).unwrap();
|
||||||
|
assert_eq!(decoded.algo, algo, "algo must round-trip verbatim");
|
||||||
|
}
|
||||||
|
// `0` is the malformed sentinel on the wire — it must fail to decode.
|
||||||
|
let sig = make(SigAlgo::Unknown(0));
|
||||||
|
let mut buf = [0u8; consts::MAX_SIG + 8];
|
||||||
|
let n = postcard::to_slice(&sig, &mut buf).unwrap();
|
||||||
|
assert_eq!(n[1], 0);
|
||||||
|
let bad: Result<Signature, _> = postcard::from_bytes::<Signature>(n);
|
||||||
|
assert!(bad.is_err(), "algo id 0 must be malformed");
|
||||||
|
// A wire byte of 1 is Ed25519, never Unknown(1).
|
||||||
|
let sig = make(SigAlgo::Ed25519);
|
||||||
|
let mut buf = [0u8; consts::MAX_SIG + 8];
|
||||||
|
let n = postcard::to_slice(&sig, &mut buf).unwrap();
|
||||||
|
assert_eq!(n[1], 1);
|
||||||
|
assert_eq!(
|
||||||
|
postcard::from_bytes::<Signature>(n).unwrap().algo,
|
||||||
|
SigAlgo::Ed25519,
|
||||||
|
"wire byte 1 must decode to Ed25519"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+207
-2
@@ -126,8 +126,7 @@ pub enum WireError {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::Payload;
|
use crate::{Payload, SigAlgo, Signature, Topic};
|
||||||
use crate::Topic;
|
|
||||||
|
|
||||||
fn obs_frame() -> Frame {
|
fn obs_frame() -> Frame {
|
||||||
Frame::Obs {
|
Frame::Obs {
|
||||||
@@ -140,6 +139,7 @@ mod tests {
|
|||||||
topic: Topic::from_slice(b"door/3").unwrap(),
|
topic: Topic::from_slice(b"door/3").unwrap(),
|
||||||
payload: Payload::from_slice(b"open").unwrap(),
|
payload: Payload::from_slice(b"open").unwrap(),
|
||||||
dest: None,
|
dest: None,
|
||||||
|
sig: None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,6 +155,7 @@ mod tests {
|
|||||||
topic: Topic::from_slice(b"t").unwrap(),
|
topic: Topic::from_slice(b"t").unwrap(),
|
||||||
payload: Payload::from_slice(&[0u8; 1024]).unwrap(),
|
payload: Payload::from_slice(&[0u8; 1024]).unwrap(),
|
||||||
dest: None,
|
dest: None,
|
||||||
|
sig: None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,4 +477,208 @@ mod tests {
|
|||||||
let mut buf = [0u8; 16];
|
let mut buf = [0u8; 16];
|
||||||
assert_eq!(f.encode(&mut buf).err(), Some(WireError::TooLarge));
|
assert_eq!(f.encode(&mut buf).err(), Some(WireError::TooLarge));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sig(version: u8, algo: SigAlgo, bytes: &[u8]) -> Signature {
|
||||||
|
Signature {
|
||||||
|
version,
|
||||||
|
algo,
|
||||||
|
bytes: Vec::from_slice(bytes).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signed_obs(sig: Signature) -> Observation {
|
||||||
|
Observation {
|
||||||
|
observer: [7; 16],
|
||||||
|
seq: 42,
|
||||||
|
age_ms: 123,
|
||||||
|
observed_at: Some(999),
|
||||||
|
topic: Topic::from_slice(b"door/3").unwrap(),
|
||||||
|
payload: Payload::from_slice(b"open").unwrap(),
|
||||||
|
dest: None,
|
||||||
|
sig: Some(sig),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn obs_roundtrip_with_ed25519_signature() {
|
||||||
|
let f = Frame::Obs {
|
||||||
|
from: [7; 16],
|
||||||
|
obs: signed_obs(sig(1, SigAlgo::Ed25519, &[0u8; 64])),
|
||||||
|
};
|
||||||
|
let mut buf = [0u8; consts::MAX_FRAME];
|
||||||
|
let n = f.encode(&mut buf).unwrap();
|
||||||
|
let decoded = Frame::decode(&buf[..n]).unwrap();
|
||||||
|
assert_eq!(f, decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn obs_roundtrip_with_unknown_algo() {
|
||||||
|
// An unknown algorithm id with short bytes is carried verbatim.
|
||||||
|
let f = Frame::Obs {
|
||||||
|
from: [7; 16],
|
||||||
|
obs: signed_obs(sig(2, SigAlgo::Unknown(7), &[9u8; 10])),
|
||||||
|
};
|
||||||
|
let mut buf = [0u8; consts::MAX_FRAME];
|
||||||
|
let n = f.encode(&mut buf).unwrap();
|
||||||
|
let decoded = Frame::decode(&buf[..n]).unwrap();
|
||||||
|
assert_eq!(f, decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn algo_zero_is_malformed() {
|
||||||
|
let f = Frame::Obs {
|
||||||
|
from: [7; 16],
|
||||||
|
obs: signed_obs(sig(1, SigAlgo::Unknown(0), &[0u8; 64])),
|
||||||
|
};
|
||||||
|
let mut buf = [0u8; consts::MAX_FRAME];
|
||||||
|
let n = f.encode(&mut buf).unwrap();
|
||||||
|
assert_eq!(Frame::decode(&buf[..n]).err(), Some(WireError::Malformed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_zero_is_malformed() {
|
||||||
|
let f = Frame::Obs {
|
||||||
|
from: [7; 16],
|
||||||
|
obs: signed_obs(sig(0, SigAlgo::Ed25519, &[0u8; 64])),
|
||||||
|
};
|
||||||
|
let mut buf = [0u8; consts::MAX_FRAME];
|
||||||
|
let n = f.encode(&mut buf).unwrap();
|
||||||
|
assert_eq!(Frame::decode(&buf[..n]).err(), Some(WireError::Malformed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ed25519_wrong_length_is_malformed() {
|
||||||
|
let f = Frame::Obs {
|
||||||
|
from: [7; 16],
|
||||||
|
obs: signed_obs(sig(1, SigAlgo::Ed25519, &[0u8; 63])),
|
||||||
|
};
|
||||||
|
let mut buf = [0u8; consts::MAX_FRAME];
|
||||||
|
let n = f.encode(&mut buf).unwrap();
|
||||||
|
assert_eq!(Frame::decode(&buf[..n]).err(), Some(WireError::Malformed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_one_decodes_to_ed25519() {
|
||||||
|
// A wire algo byte of 1 must decode to Ed25519, never Unknown(1).
|
||||||
|
let f = Frame::Obs {
|
||||||
|
from: [7; 16],
|
||||||
|
obs: signed_obs(sig(1, SigAlgo::Unknown(1), &[0u8; 64])),
|
||||||
|
};
|
||||||
|
let mut buf = [0u8; consts::MAX_FRAME];
|
||||||
|
let n = f.encode(&mut buf).unwrap();
|
||||||
|
let decoded = Frame::decode(&buf[..n]).unwrap();
|
||||||
|
match decoded {
|
||||||
|
Frame::Obs { obs, .. } => {
|
||||||
|
assert_eq!(obs.sig.as_ref().unwrap().algo, SigAlgo::Ed25519);
|
||||||
|
}
|
||||||
|
other => panic!("expected Obs, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pinned wire vector for a full-size (MAX_PAYLOAD + 64-byte Ed25519) signed
|
||||||
|
// Observation, exactly as a 1.0 node would emit it: the golden vector for
|
||||||
|
// review focus #5. Decoding it must yield the bare Observation with a
|
||||||
|
// SigAlgo::Ed25519 carrying 64 zeroed signature bytes.
|
||||||
|
const OBS_GOLDEN_VECTOR_ED25519: [u8; 1139] = [
|
||||||
|
0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||||
|
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||||
|
0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x06, 0x64, 0x6f, 0x6f, 0x72, 0x2f, 0x33, 0x80,
|
||||||
|
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn golden_vector_ed25519_decodes_to_bare_obs() {
|
||||||
|
// Review Focus #5: the pinned vector is the golden Obs vector — decoding
|
||||||
|
// it must yield the bare Observation with an Ed25519 signature carrying
|
||||||
|
// 64 zeroed bytes.
|
||||||
|
let frame = Frame::decode(&OBS_GOLDEN_VECTOR_ED25519).unwrap();
|
||||||
|
let Frame::Obs { from, obs } = frame else {
|
||||||
|
panic!("expected an Obs frame")
|
||||||
|
};
|
||||||
|
assert_eq!(from, [1; 16]);
|
||||||
|
assert_eq!(obs.observer, [1; 16]);
|
||||||
|
assert_eq!(obs.seq, 1);
|
||||||
|
assert_eq!(obs.age_ms, 0);
|
||||||
|
assert_eq!(obs.observed_at, None);
|
||||||
|
assert_eq!(&obs.topic[..], b"door/3");
|
||||||
|
assert_eq!(obs.payload.len(), consts::MAX_PAYLOAD);
|
||||||
|
assert_eq!(obs.dest, None);
|
||||||
|
let sig = obs.sig.expect("a signed obs carries a signature");
|
||||||
|
assert_eq!(sig.version, 1);
|
||||||
|
assert_eq!(sig.algo, SigAlgo::Ed25519);
|
||||||
|
assert_eq!(sig.bytes.len(), 64);
|
||||||
|
assert!(sig.bytes.iter().all(|b| *b == 0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1024,6 +1024,7 @@ mod tests {
|
|||||||
topic: tines_core::Topic::from_slice(b"door/3").unwrap(),
|
topic: tines_core::Topic::from_slice(b"door/3").unwrap(),
|
||||||
payload: tines_core::Payload::from_slice(b"open").unwrap(),
|
payload: tines_core::Payload::from_slice(b"open").unwrap(),
|
||||||
dest: None,
|
dest: None,
|
||||||
|
sig: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ fn obs(observer: [u8; 16], seq: u64, age: u64, at: Option<u64>) -> Observation {
|
|||||||
topic: Topic::from_slice(b"door/3").unwrap(),
|
topic: Topic::from_slice(b"door/3").unwrap(),
|
||||||
payload: Payload::from_slice(b"open").unwrap(),
|
payload: Payload::from_slice(b"open").unwrap(),
|
||||||
dest: None,
|
dest: None,
|
||||||
|
sig: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,6 +347,7 @@ mod fleet_fixes_f5 {
|
|||||||
topic: Topic::from_slice(b"door/3").unwrap(),
|
topic: Topic::from_slice(b"door/3").unwrap(),
|
||||||
payload: Payload::from_slice(b"open").unwrap(),
|
payload: Payload::from_slice(b"open").unwrap(),
|
||||||
dest: None,
|
dest: None,
|
||||||
|
sig: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user