use super::*;
use clawft_types::placement::{Capability, CapabilityId, CapabilityState, Provenance, StateChange};
use rand::rngs::OsRng;
fn key() -> SigningKey {
SigningKey::generate(&mut OsRng)
}
fn facts_for(k: &SigningKey, issued_at: u64) -> NodeFacts {
let id = node_id_from_pubkey(&k.verifying_key().to_bytes());
let mut f = NodeFacts::new(id, issued_at, 600, 1);
f.capabilities = vec![
Capability::new(
CapabilityId::new("cpu.arch.aarch64").unwrap(),
Provenance::Probed,
),
Capability::new(
CapabilityId::new("accel.gpu.metal").unwrap(),
Provenance::Probed,
)
.with_attr("unified", true)
.with_attr("cores", 40i64),
];
f
}
#[test]
fn sign_then_verify_roundtrips_through_json() {
let k = key();
let f = facts_for(&k, 1_000);
let s = sign_node_facts(&f, &k).unwrap();
let wire = serde_json::to_string(&s).unwrap();
let back: SignedNodeFacts = serde_json::from_str(&wire).unwrap();
assert_eq!(verify_node_facts(&back, 1_100).unwrap(), f);
}
#[test]
fn tampered_payload_is_rejected() {
let k = key();
let mut s = sign_node_facts(&facts_for(&k, 1_000), &k).unwrap();
s.payload = s.payload.replace("\"cores\":40", "\"cores\":80");
assert_eq!(
verify_node_facts(&s, 1_100),
Err(NodeFactsAdvertError::BadSignature)
);
}
#[test]
fn facts_resigned_by_another_key_cannot_claim_the_node() {
let (k, other) = (key(), key());
let f = facts_for(&k, 1_000);
assert!(matches!(
sign_node_facts(&f, &other),
Err(NodeFactsAdvertError::NodeMismatch { .. })
));
let mut s = sign_node_facts(&f, &k).unwrap();
s.public_key = other.verifying_key().to_bytes().to_vec();
assert_eq!(
verify_node_facts(&s, 1_100),
Err(NodeFactsAdvertError::BadSignature)
);
}
#[test]
fn expired_and_future_facts_are_rejected() {
let k = key();
let s = sign_node_facts(&facts_for(&k, 1_000), &k).unwrap();
assert!(matches!(
verify_node_facts(&s, 1_600),
Err(NodeFactsAdvertError::Facts(FactsError::Expired { .. }))
));
assert!(matches!(
verify_node_facts(&s, 900),
Err(NodeFactsAdvertError::Facts(FactsError::FromFuture { .. }))
));
assert!(verify_node_facts(&s, 1_599).is_ok());
}
#[test]
fn a_delta_signature_cannot_be_replayed_as_facts() {
let k = key();
let f = facts_for(&k, 1_000);
let s = sign_node_facts(&f, &k).unwrap();
let forged_sig = k.sign(&signed_bytes(DELTA_DOMAIN, &s.payload));
let forged = SignedNodeFacts {
signature: forged_sig.to_bytes().to_vec(),
..s
};
assert_eq!(
verify_node_facts(&forged, 1_100),
Err(NodeFactsAdvertError::BadSignature)
);
}
#[test]
fn malformed_envelopes_are_rejected() {
let k = key();
let mut s = sign_node_facts(&facts_for(&k, 1_000), &k).unwrap();
s.signature.pop();
assert_eq!(
verify_node_facts(&s, 1_100),
Err(NodeFactsAdvertError::BadSignatureLen(63))
);
let mut s = sign_node_facts(&facts_for(&k, 1_000), &k).unwrap();
s.public_key = vec![0; 31];
assert_eq!(
verify_node_facts(&s, 1_100),
Err(NodeFactsAdvertError::BadPublicKey)
);
let mut s = sign_node_facts(&facts_for(&k, 1_000), &k).unwrap();
s.payload = "x".repeat(MAX_PAYLOAD_BYTES + 1);
assert!(matches!(
verify_node_facts(&s, 1_100),
Err(NodeFactsAdvertError::TooLarge(_))
));
}
#[test]
fn delta_sign_verify_and_tamper() {
let k = key();
let f = facts_for(&k, 1_000);
let d = FactsDelta {
node_id: f.node_id.clone(),
base_seq: 1,
seq: 1,
issued_at: 1_010,
changes: vec![StateChange {
index: 1,
id: CapabilityId::new("accel.gpu.metal").unwrap(),
state: CapabilityState::Busy,
}],
mem_free: Some(1),
};
let s = sign_facts_delta(&d, &k).unwrap();
let (back, pk) = verify_facts_delta(&s, 1_020).unwrap();
assert_eq!(back, d);
assert_eq!(pk, k.verifying_key().to_bytes());
let mut t = s.clone();
t.payload = t.payload.replace("busy", "available");
assert_eq!(
verify_facts_delta(&t, 1_020),
Err(NodeFactsAdvertError::BadSignature)
);
assert!(verify_facts_delta(&s, 900).is_err(), "future-dated delta");
}