use std::fs;
use std::path::PathBuf;
use serde_json::Value;
use rns_core::announce::AnnounceData;
use rns_core::constants;
use rns_core::destination;
use rns_core::hash;
use rns_core::msgpack;
use rns_core::packet::{PacketFlags, RawPacket};
use rns_core::receipt::{self, ProofResult};
use rns_core::resource::advertisement::ResourceAdvertisement;
use rns_core::resource::parts::map_hash;
use rns_core::resource::proof::{compute_expected_proof, compute_resource_hash};
use rns_crypto::identity::Identity;
fn fixture_path(name: &str) -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("..");
path.push("tests");
path.push("fixtures");
path.push("protocol");
path.push(name);
path
}
fn load_fixture(name: &str) -> Vec<Value> {
let path = fixture_path(name);
let data = fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("Failed to read fixture {}: {}", path.display(), e));
serde_json::from_str(&data).unwrap()
}
fn hex_to_bytes(hex: &str) -> Vec<u8> {
(0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
.collect()
}
#[test]
fn test_hash_interop() {
let vectors = load_fixture("hash_vectors.json");
for v in &vectors {
let desc = v["description"].as_str().unwrap();
if desc.starts_with("name_hash_") {
let app_name = v["app_name"].as_str().unwrap();
let aspects: Vec<&str> = v["aspects"]
.as_array()
.unwrap()
.iter()
.map(|a| a.as_str().unwrap())
.collect();
let expected = hex_to_bytes(v["name_hash"].as_str().unwrap());
let result = hash::name_hash(app_name, &aspects);
assert_eq!(
result.as_slice(),
expected.as_slice(),
"name_hash mismatch for {}",
desc
);
} else {
let input = hex_to_bytes(v["input"].as_str().unwrap());
let expected_full = hex_to_bytes(v["full_hash"].as_str().unwrap());
let expected_trunc = hex_to_bytes(v["truncated_hash"].as_str().unwrap());
let full = hash::full_hash(&input);
assert_eq!(
full.as_slice(),
expected_full.as_slice(),
"full_hash mismatch for {}",
desc
);
let trunc = hash::truncated_hash(&input);
assert_eq!(
trunc.as_slice(),
expected_trunc.as_slice(),
"truncated_hash mismatch for {}",
desc
);
}
}
}
#[test]
fn test_flags_interop() {
let vectors = load_fixture("flags_vectors.json");
for v in &vectors {
let desc = v["description"].as_str().unwrap();
let expected_packed = v["packed"].as_u64().unwrap() as u8;
let flags = PacketFlags {
header_type: v["header_type"].as_u64().unwrap() as u8,
context_flag: v["context_flag"].as_u64().unwrap() as u8,
transport_type: v["transport_type"].as_u64().unwrap() as u8,
destination_type: v["destination_type"].as_u64().unwrap() as u8,
packet_type: v["packet_type"].as_u64().unwrap() as u8,
};
let packed = flags.pack();
assert_eq!(packed, expected_packed, "flags pack mismatch for {}", desc);
let unpacked = PacketFlags::unpack(expected_packed);
assert_eq!(unpacked, flags, "flags unpack mismatch for {}", desc);
}
}
#[test]
fn test_packet_interop() {
let vectors = load_fixture("packet_vectors.json");
for v in &vectors {
let desc = v["description"].as_str().unwrap();
let header_type = v["header_type"].as_u64().unwrap() as u8;
let context_flag = v["context_flag"].as_u64().unwrap() as u8;
let transport_type = v["transport_type"].as_u64().unwrap() as u8;
let destination_type = v["destination_type"].as_u64().unwrap() as u8;
let packet_type = v["packet_type"].as_u64().unwrap() as u8;
let hops = v["hops"].as_u64().unwrap() as u8;
let context = v["context"].as_u64().unwrap() as u8;
let dest_hash_bytes = hex_to_bytes(v["destination_hash"].as_str().unwrap());
let mut dest_hash = [0u8; 16];
dest_hash.copy_from_slice(&dest_hash_bytes);
let transport_id = if v["transport_id"].is_null() {
None
} else {
let bytes = hex_to_bytes(v["transport_id"].as_str().unwrap());
let mut tid = [0u8; 16];
tid.copy_from_slice(&bytes);
Some(tid)
};
let data = hex_to_bytes(v["data"].as_str().unwrap());
let expected_raw = hex_to_bytes(v["raw"].as_str().unwrap());
let expected_hashable = hex_to_bytes(v["hashable_part"].as_str().unwrap());
let expected_hash = hex_to_bytes(v["packet_hash"].as_str().unwrap());
let expected_trunc = hex_to_bytes(v["truncated_hash"].as_str().unwrap());
let flags = PacketFlags {
header_type,
context_flag,
transport_type,
destination_type,
packet_type,
};
let pkt = RawPacket::pack(
flags,
hops,
&dest_hash,
transport_id.as_ref(),
context,
&data,
)
.unwrap();
assert_eq!(pkt.raw, expected_raw, "pack raw mismatch for {}", desc);
assert_eq!(
pkt.get_hashable_part(),
expected_hashable,
"hashable_part mismatch for {}",
desc
);
assert_eq!(
pkt.get_hash().as_slice(),
expected_hash.as_slice(),
"packet_hash mismatch for {}",
desc
);
assert_eq!(
pkt.get_truncated_hash().as_slice(),
expected_trunc.as_slice(),
"truncated_hash mismatch for {}",
desc
);
let unpacked = RawPacket::unpack(&expected_raw).unwrap();
assert_eq!(unpacked.flags, flags, "unpack flags mismatch for {}", desc);
assert_eq!(unpacked.hops, hops, "unpack hops mismatch for {}", desc);
assert_eq!(
unpacked.destination_hash, dest_hash,
"unpack dest_hash mismatch for {}",
desc
);
assert_eq!(
unpacked.context, context,
"unpack context mismatch for {}",
desc
);
assert_eq!(unpacked.data, data, "unpack data mismatch for {}", desc);
if let Some(ref tid) = transport_id {
assert_eq!(
unpacked.transport_id.unwrap(),
*tid,
"unpack transport_id mismatch for {}",
desc
);
} else {
assert!(
unpacked.transport_id.is_none(),
"expected no transport_id for {}",
desc
);
}
assert_eq!(
unpacked.get_hash().as_slice(),
expected_hash.as_slice(),
"unpack packet_hash mismatch for {}",
desc
);
}
}
#[test]
fn test_destination_interop() {
let vectors = load_fixture("destination_vectors.json");
for v in &vectors {
let desc = v["description"].as_str().unwrap();
let app_name = v["app_name"].as_str().unwrap();
let aspects: Vec<&str> = v["aspects"]
.as_array()
.unwrap()
.iter()
.map(|a| a.as_str().unwrap())
.collect();
let identity_hash = if v["identity_hash"].is_null() {
None
} else {
let bytes = hex_to_bytes(v["identity_hash"].as_str().unwrap());
let mut h = [0u8; 16];
h.copy_from_slice(&bytes);
Some(h)
};
let expected_name = v["expanded_name"].as_str().unwrap();
let expected_name_hash = hex_to_bytes(v["name_hash"].as_str().unwrap());
let expected_dest_hash = hex_to_bytes(v["destination_hash"].as_str().unwrap());
let name = destination::expand_name(app_name, &aspects, identity_hash.as_ref()).unwrap();
assert_eq!(name, expected_name, "expand_name mismatch for {}", desc);
let nh = destination::name_hash(app_name, &aspects);
assert_eq!(
nh.as_slice(),
expected_name_hash.as_slice(),
"name_hash mismatch for {}",
desc
);
let dh = destination::destination_hash(app_name, &aspects, identity_hash.as_ref());
assert_eq!(
dh.as_slice(),
expected_dest_hash.as_slice(),
"destination_hash mismatch for {}",
desc
);
}
}
#[test]
fn test_announce_interop() {
let vectors = load_fixture("announce_vectors.json");
for v in &vectors {
let desc = v["description"].as_str().unwrap();
let prv_key_bytes = hex_to_bytes(v["private_key"].as_str().unwrap());
let mut prv_key = [0u8; 64];
prv_key.copy_from_slice(&prv_key_bytes);
let identity = Identity::from_private_key(&prv_key);
let pub_key_bytes = hex_to_bytes(v["public_key"].as_str().unwrap());
let identity_hash_bytes = hex_to_bytes(v["identity_hash"].as_str().unwrap());
let name_hash_bytes = hex_to_bytes(v["name_hash"].as_str().unwrap());
let dest_hash_bytes = hex_to_bytes(v["destination_hash"].as_str().unwrap());
let random_hash_bytes = hex_to_bytes(v["random_hash"].as_str().unwrap());
let expected_announce = hex_to_bytes(v["announce_data"].as_str().unwrap());
let has_ratchet = v["has_ratchet"].as_bool().unwrap();
let mut dest_hash = [0u8; 16];
dest_hash.copy_from_slice(&dest_hash_bytes);
let mut name_hash = [0u8; 10];
name_hash.copy_from_slice(&name_hash_bytes);
let mut random_hash = [0u8; 10];
random_hash.copy_from_slice(&random_hash_bytes);
let ratchet = if v["ratchet"].is_null() {
None
} else {
let bytes = hex_to_bytes(v["ratchet"].as_str().unwrap());
let mut r = [0u8; 32];
r.copy_from_slice(&bytes);
Some(r)
};
let app_data = if v["app_data"].is_null() {
None
} else {
Some(hex_to_bytes(v["app_data"].as_str().unwrap()))
};
let (packed, got_ratchet) = AnnounceData::pack(
&identity,
&dest_hash,
&name_hash,
&random_hash,
ratchet.as_ref(),
app_data.as_deref(),
)
.unwrap();
assert_eq!(
got_ratchet, has_ratchet,
"has_ratchet mismatch for {}",
desc
);
assert_eq!(packed, expected_announce, "pack mismatch for {}", desc);
let parsed = AnnounceData::unpack(&expected_announce, has_ratchet).unwrap();
assert_eq!(
parsed.public_key.as_slice(),
pub_key_bytes.as_slice(),
"unpack public_key mismatch for {}",
desc
);
assert_eq!(
parsed.name_hash.as_slice(),
name_hash_bytes.as_slice(),
"unpack name_hash mismatch for {}",
desc
);
assert_eq!(
parsed.random_hash.as_slice(),
random_hash_bytes.as_slice(),
"unpack random_hash mismatch for {}",
desc
);
if has_ratchet {
assert!(parsed.ratchet.is_some(), "expected ratchet for {}", desc);
} else {
assert!(parsed.ratchet.is_none(), "unexpected ratchet for {}", desc);
}
let validated = parsed.validate(&dest_hash).unwrap();
assert_eq!(
validated.identity_hash.as_slice(),
identity_hash_bytes.as_slice(),
"validate identity_hash mismatch for {}",
desc
);
}
}
#[test]
fn test_proof_interop() {
let vectors = load_fixture("proof_vectors.json");
for v in &vectors {
let desc = v["description"].as_str().unwrap();
let pub_key_bytes = hex_to_bytes(v["public_key"].as_str().unwrap());
let mut pub_key = [0u8; 64];
pub_key.copy_from_slice(&pub_key_bytes);
let identity = Identity::from_public_key(&pub_key);
let packet_hash_bytes = hex_to_bytes(v["packet_hash"].as_str().unwrap());
let mut packet_hash = [0u8; 32];
packet_hash.copy_from_slice(&packet_hash_bytes);
let proof = hex_to_bytes(v["proof"].as_str().unwrap());
let expected_result = v["result"].as_str().unwrap();
let result = receipt::validate_proof(&proof, &packet_hash, &identity);
let expected = match expected_result {
"valid" => ProofResult::Valid,
"invalid_hash" => ProofResult::InvalidHash,
"invalid_signature" => ProofResult::InvalidSignature,
"invalid_length" => ProofResult::InvalidLength,
other => panic!("Unknown result type: {}", other),
};
assert_eq!(result, expected, "proof result mismatch for {}", desc);
}
}
#[test]
fn test_milestone_announce_pipeline() {
let vectors = load_fixture("announce_vectors.json");
let v = &vectors[0];
let announce_bytes = hex_to_bytes(v["announce_data"].as_str().unwrap());
let dest_hash_bytes = hex_to_bytes(v["destination_hash"].as_str().unwrap());
let identity_hash_bytes = hex_to_bytes(v["identity_hash"].as_str().unwrap());
let has_ratchet = v["has_ratchet"].as_bool().unwrap();
let mut dest_hash = [0u8; 16];
dest_hash.copy_from_slice(&dest_hash_bytes);
let parsed = AnnounceData::unpack(&announce_bytes, has_ratchet).unwrap();
let validated = parsed
.validate(&dest_hash)
.expect("Milestone: announce validation failed");
assert_eq!(
validated.identity_hash.as_slice(),
identity_hash_bytes.as_slice(),
"Milestone: identity hash mismatch"
);
let flags = PacketFlags {
header_type: constants::HEADER_1,
context_flag: constants::FLAG_UNSET,
transport_type: constants::TRANSPORT_BROADCAST,
destination_type: constants::DESTINATION_SINGLE,
packet_type: constants::PACKET_TYPE_ANNOUNCE,
};
let pkt = RawPacket::pack(
flags,
0,
&dest_hash,
None,
constants::CONTEXT_NONE,
&announce_bytes,
)
.unwrap();
let unpacked = RawPacket::unpack(&pkt.raw).unwrap();
assert_eq!(unpacked.destination_hash, dest_hash);
assert_eq!(unpacked.data, announce_bytes);
let reparsed = AnnounceData::unpack(&unpacked.data, has_ratchet).unwrap();
let revalidated = reparsed.validate(&dest_hash).unwrap();
assert_eq!(revalidated.identity_hash, validated.identity_hash);
}
fn resource_fixture_path(name: &str) -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("..");
path.push("tests");
path.push("fixtures");
path.push("resource");
path.push(name);
path
}
fn load_resource_fixture(name: &str) -> Vec<Value> {
let path = resource_fixture_path(name);
let data = fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("Failed to read fixture {}: {}", path.display(), e));
serde_json::from_str(&data).unwrap()
}
#[test]
fn test_msgpack_interop() {
let vectors = load_resource_fixture("msgpack_vectors.json");
for v in &vectors {
let desc = v["description"].as_str().unwrap();
let expected_packed = hex_to_bytes(v["packed"].as_str().unwrap());
let vtype = v["type"].as_str().unwrap();
let (parsed, consumed) = msgpack::unpack(&expected_packed)
.unwrap_or_else(|e| panic!("unpack failed for {}: {:?}", desc, e));
assert_eq!(
consumed,
expected_packed.len(),
"consumed mismatch for {}",
desc
);
match vtype {
"nil" => assert!(
matches!(parsed, msgpack::Value::Nil),
"expected nil for {}",
desc
),
"bool" => {
let expected = v["bool_value"].as_bool().unwrap();
assert_eq!(
parsed.as_bool(),
Some(expected),
"bool mismatch for {}",
desc
);
}
"int" => {
let expected = v["int_value"].as_i64().unwrap();
assert_eq!(
parsed.as_integer(),
Some(expected),
"int mismatch for {}",
desc
);
}
"str" => {
let expected = v["str_value"].as_str().unwrap();
assert_eq!(parsed.as_str(), Some(expected), "str mismatch for {}", desc);
}
"bin" => {
let expected = hex_to_bytes(v["bin_value"].as_str().unwrap());
assert_eq!(
parsed.as_bin(),
Some(expected.as_slice()),
"bin mismatch for {}",
desc
);
}
"array" => {
assert!(parsed.as_array().is_some(), "expected array for {}", desc);
}
"map" => {
assert!(parsed.as_map().is_some(), "expected map for {}", desc);
}
_ => panic!("unknown type {} for {}", vtype, desc),
}
let repacked = msgpack::pack(&parsed);
assert_eq!(repacked, expected_packed, "roundtrip mismatch for {}", desc);
}
}
#[test]
fn test_resource_part_hash_interop() {
let vectors = load_resource_fixture("part_hash_vectors.json");
for v in &vectors {
let desc = v["description"].as_str().unwrap();
let part_data = hex_to_bytes(v["part_data"].as_str().unwrap());
let random_hash = hex_to_bytes(v["random_hash"].as_str().unwrap());
let expected_map_hash = hex_to_bytes(v["map_hash"].as_str().unwrap());
let result = map_hash(&part_data, &random_hash);
assert_eq!(
result.as_slice(),
expected_map_hash.as_slice(),
"map_hash mismatch for {}",
desc
);
}
}
#[test]
fn test_resource_proof_interop() {
let vectors = load_resource_fixture("resource_proof_vectors.json");
for v in &vectors {
let desc = v["description"].as_str().unwrap();
let data = hex_to_bytes(v["data"].as_str().unwrap());
let random_hash = hex_to_bytes(v["random_hash"].as_str().unwrap());
let expected_resource_hash = hex_to_bytes(v["resource_hash"].as_str().unwrap());
let expected_proof = hex_to_bytes(v["expected_proof"].as_str().unwrap());
let resource_hash = compute_resource_hash(&data, &random_hash);
assert_eq!(
resource_hash.as_slice(),
expected_resource_hash.as_slice(),
"resource_hash mismatch for {}",
desc
);
let proof = compute_expected_proof(&data, &resource_hash);
assert_eq!(
proof.as_slice(),
expected_proof.as_slice(),
"expected_proof mismatch for {}",
desc
);
}
}
#[test]
fn test_resource_advertisement_interop() {
let vectors = load_resource_fixture("advertisement_vectors.json");
for v in &vectors {
let desc = v["description"].as_str().unwrap();
let expected_packed = hex_to_bytes(v["packed"].as_str().unwrap());
let adv = ResourceAdvertisement::unpack(&expected_packed)
.unwrap_or_else(|e| panic!("unpack failed for {}: {:?}", desc, e));
assert_eq!(
adv.transfer_size,
v["transfer_size"].as_u64().unwrap(),
"transfer_size mismatch for {}",
desc
);
assert_eq!(
adv.data_size,
v["data_size"].as_u64().unwrap(),
"data_size mismatch for {}",
desc
);
assert_eq!(
adv.num_parts,
v["num_parts"].as_u64().unwrap(),
"num_parts mismatch for {}",
desc
);
let expected_resource_hash = hex_to_bytes(v["resource_hash"].as_str().unwrap());
assert_eq!(
adv.resource_hash, expected_resource_hash,
"resource_hash mismatch for {}",
desc
);
let expected_random_hash = hex_to_bytes(v["random_hash"].as_str().unwrap());
assert_eq!(
adv.random_hash, expected_random_hash,
"random_hash mismatch for {}",
desc
);
assert_eq!(
adv.segment_index,
v["segment_index"].as_u64().unwrap(),
"segment_index mismatch for {}",
desc
);
assert_eq!(
adv.total_segments,
v["total_segments"].as_u64().unwrap(),
"total_segments mismatch for {}",
desc
);
let expected_hashmap = hex_to_bytes(v["hashmap"].as_str().unwrap());
assert_eq!(
adv.hashmap, expected_hashmap,
"hashmap mismatch for {}",
desc
);
let expected_flags = v["flags"].as_u64().unwrap() as u8;
assert_eq!(
adv.flags.to_byte(),
expected_flags,
"flags mismatch for {}",
desc
);
let expected_original_hash = hex_to_bytes(v["original_hash"].as_str().unwrap());
assert_eq!(
adv.original_hash, expected_original_hash,
"original_hash mismatch for {}",
desc
);
if v["request_id"].is_null() {
assert!(
adv.request_id.is_none(),
"expected no request_id for {}",
desc
);
} else {
let expected_request_id = hex_to_bytes(v["request_id"].as_str().unwrap());
assert_eq!(
adv.request_id,
Some(expected_request_id),
"request_id mismatch for {}",
desc
);
}
let repacked = adv.pack(0);
let re_adv = ResourceAdvertisement::unpack(&repacked)
.unwrap_or_else(|e| panic!("re-unpack failed for {}: {:?}", desc, e));
assert_eq!(
re_adv.transfer_size, adv.transfer_size,
"roundtrip transfer_size mismatch for {}",
desc
);
assert_eq!(
re_adv.data_size, adv.data_size,
"roundtrip data_size mismatch for {}",
desc
);
assert_eq!(
re_adv.num_parts, adv.num_parts,
"roundtrip num_parts mismatch for {}",
desc
);
assert_eq!(
re_adv.resource_hash, adv.resource_hash,
"roundtrip resource_hash mismatch for {}",
desc
);
assert_eq!(
re_adv.flags.to_byte(),
adv.flags.to_byte(),
"roundtrip flags mismatch for {}",
desc
);
}
}
#[test]
fn test_resource_hmu_interop() {
let vectors = load_resource_fixture("hmu_vectors.json");
for v in &vectors {
let desc = v["description"].as_str().unwrap();
let payload = hex_to_bytes(v["payload"].as_str().unwrap());
let expected_segment = v["segment"].as_u64().unwrap();
let expected_hashmap = hex_to_bytes(v["hashmap_bytes"].as_str().unwrap());
let (value, _) = msgpack::unpack(&payload)
.unwrap_or_else(|e| panic!("unpack HMU payload failed for {}: {:?}", desc, e));
let arr = value.as_array().unwrap();
assert_eq!(arr.len(), 2, "HMU array length mismatch for {}", desc);
let segment = arr[0].as_uint().unwrap();
assert_eq!(segment, expected_segment, "segment mismatch for {}", desc);
let hashmap = arr[1].as_bin().unwrap();
assert_eq!(
hashmap,
expected_hashmap.as_slice(),
"hashmap mismatch for {}",
desc
);
}
}