use rns_core::buffer::{BufferReader, BufferWriter, NoopCompressor, StreamDataMessage};
use rns_core::channel::envelope::{pack_envelope, unpack_envelope};
use rns_core::channel::Channel;
use rns_core::link::crypto::create_session_token;
use rns_core::link::handshake::{
build_signalling_bytes, derive_session_key, pack_rtt, parse_signalling_bytes, unpack_rtt,
};
use rns_core::link::{LinkEngine, LinkMode, LinkState};
use rns_crypto::ed25519::Ed25519PrivateKey;
use rns_crypto::x25519::X25519PrivateKey;
use rns_crypto::FixedRng;
use std::fs;
use std::path::PathBuf;
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("link");
path.push(name);
path
}
fn load_fixture(name: &str) -> serde_json::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_link_handshake_interop() {
let vectors = load_fixture("link_handshake_vectors.json");
for v in vectors.as_array().unwrap() {
let desc = v["description"].as_str().unwrap();
let mode_val = v["mode"].as_u64().unwrap() as u8;
let mode = if mode_val == 0 {
LinkMode::Aes128Cbc
} else {
LinkMode::Aes256Cbc
};
let init_x25519_prv_bytes: [u8; 32] =
hex_to_bytes(v["initiator_x25519_prv"].as_str().unwrap())
.try_into()
.unwrap();
let resp_x25519_prv_bytes: [u8; 32] =
hex_to_bytes(v["responder_x25519_prv"].as_str().unwrap())
.try_into()
.unwrap();
let expected_link_id = hex_to_bytes(v["link_id"].as_str().unwrap());
let expected_shared_key = hex_to_bytes(v["shared_key"].as_str().unwrap());
let expected_derived_key = hex_to_bytes(v["derived_key"].as_str().unwrap());
let expected_signalling = hex_to_bytes(v["signalling_bytes"].as_str().unwrap());
let mtu = v["mtu"].as_u64().unwrap() as u32;
let sig_bytes = build_signalling_bytes(mtu, mode);
assert_eq!(
sig_bytes.to_vec(),
expected_signalling,
"{desc}: signalling bytes mismatch"
);
let (parsed_mtu, parsed_mode) = parse_signalling_bytes(&sig_bytes).unwrap();
assert_eq!(parsed_mtu, mtu, "{desc}: parsed MTU mismatch");
assert_eq!(parsed_mode, mode, "{desc}: parsed mode mismatch");
let hashable_for_linkid = hex_to_bytes(v["hashable_for_linkid"].as_str().unwrap());
let link_id = rns_core::hash::truncated_hash(&hashable_for_linkid);
assert_eq!(
link_id.to_vec(),
expected_link_id,
"{desc}: link_id mismatch"
);
let init_prv = X25519PrivateKey::from_bytes(&init_x25519_prv_bytes);
let resp_prv = X25519PrivateKey::from_bytes(&resp_x25519_prv_bytes);
let init_pub = init_prv.public_key();
let shared_key = resp_prv.exchange(&init_pub);
assert_eq!(
shared_key.to_vec(),
expected_shared_key,
"{desc}: shared key mismatch"
);
let link_id_arr: [u8; 16] = link_id;
let derived = derive_session_key(&shared_key, &link_id_arr, mode).unwrap();
assert_eq!(
derived, expected_derived_key,
"{desc}: derived key mismatch"
);
let rtt_value = v["rtt_value"].as_f64().unwrap();
let expected_rtt_msgpack = hex_to_bytes(v["rtt_data_msgpack"].as_str().unwrap());
let packed_rtt = pack_rtt(rtt_value);
assert_eq!(
packed_rtt, expected_rtt_msgpack,
"{desc}: RTT msgpack mismatch"
);
let unpacked_rtt = unpack_rtt(&packed_rtt).unwrap();
assert_eq!(unpacked_rtt, rtt_value, "{desc}: RTT unpack mismatch");
let token = create_session_token(&derived).unwrap();
let expected_encrypted_rtt = hex_to_bytes(v["encrypted_rtt"].as_str().unwrap());
let fixed_iv: [u8; 16] = hex_to_bytes(v["fixed_iv"].as_str().unwrap())
.try_into()
.unwrap();
let encrypted = token.encrypt_with_iv(&expected_rtt_msgpack, &fixed_iv);
assert_eq!(
encrypted, expected_encrypted_rtt,
"{desc}: encrypted RTT mismatch"
);
let decrypted = token.decrypt(&encrypted).unwrap();
assert_eq!(
decrypted, expected_rtt_msgpack,
"{desc}: decrypted RTT mismatch"
);
eprintln!(" PASS: {desc}");
}
}
#[test]
fn test_link_crypto_interop() {
let vectors = load_fixture("link_crypto_vectors.json");
for v in vectors.as_array().unwrap() {
let desc = v["description"].as_str().unwrap();
let key = hex_to_bytes(v["derived_key"].as_str().unwrap());
let iv: [u8; 16] = hex_to_bytes(v["fixed_iv"].as_str().unwrap())
.try_into()
.unwrap();
let plaintext = hex_to_bytes(v["plaintext"].as_str().unwrap());
let expected_ct = hex_to_bytes(v["ciphertext"].as_str().unwrap());
let token = create_session_token(&key).unwrap();
let encrypted = token.encrypt_with_iv(&plaintext, &iv);
assert_eq!(encrypted, expected_ct, "{desc}: ciphertext mismatch");
let decrypted = token.decrypt(&encrypted).unwrap();
assert_eq!(decrypted, plaintext, "{desc}: decrypt mismatch");
eprintln!(" PASS: {desc}");
}
}
#[test]
fn test_link_identify_interop() {
let vectors = load_fixture("link_identify_vectors.json");
for v in vectors.as_array().unwrap() {
let desc = v["description"].as_str().unwrap();
let link_id: [u8; 16] = hex_to_bytes(v["link_id"].as_str().unwrap())
.try_into()
.unwrap();
let key = hex_to_bytes(v["derived_key"].as_str().unwrap());
let iv: [u8; 16] = hex_to_bytes(v["fixed_iv"].as_str().unwrap())
.try_into()
.unwrap();
let expected_plaintext = hex_to_bytes(v["proof_data_plaintext"].as_str().unwrap());
let expected_encrypted = hex_to_bytes(v["proof_data_encrypted"].as_str().unwrap());
let pub_key = hex_to_bytes(v["public_key"].as_str().unwrap());
let signature = hex_to_bytes(v["signature"].as_str().unwrap());
let mut expected_proof = Vec::new();
expected_proof.extend_from_slice(&pub_key);
expected_proof.extend_from_slice(&signature);
assert_eq!(
expected_proof, expected_plaintext,
"{desc}: proof plaintext structure"
);
let token = create_session_token(&key).unwrap();
let encrypted = token.encrypt_with_iv(&expected_plaintext, &iv);
assert_eq!(encrypted, expected_encrypted, "{desc}: encryption mismatch");
let decrypted = token.decrypt(&encrypted).unwrap();
let result = rns_core::link::identify::validate_identify_data(&decrypted, &link_id);
assert!(result.is_ok(), "{desc}: identify validation failed");
eprintln!(" PASS: {desc}");
}
}
#[test]
fn test_channel_envelope_interop() {
let vectors = load_fixture("channel_envelope_vectors.json");
for v in vectors.as_array().unwrap() {
let desc = v["description"].as_str().unwrap();
let msgtype = v["msgtype"].as_u64().unwrap() as u16;
let sequence = v["sequence"].as_u64().unwrap() as u16;
let data = hex_to_bytes(v["data"].as_str().unwrap());
let expected_packed = hex_to_bytes(v["packed"].as_str().unwrap());
let packed = pack_envelope(msgtype, sequence, &data);
assert_eq!(packed, expected_packed, "{desc}: pack mismatch");
let (mt, seq, pl) = unpack_envelope(&packed).unwrap();
assert_eq!(mt, msgtype, "{desc}: msgtype mismatch");
assert_eq!(seq, sequence, "{desc}: sequence mismatch");
assert_eq!(pl, &data[..], "{desc}: payload mismatch");
eprintln!(" PASS: {desc}");
}
}
#[test]
fn test_stream_data_interop() {
let vectors = load_fixture("stream_data_vectors.json");
for v in vectors.as_array().unwrap() {
let desc = v["description"].as_str().unwrap();
let stream_id = v["stream_id"].as_u64().unwrap() as u16;
let eof = v["eof"].as_bool().unwrap();
let compressed = v["compressed"].as_bool().unwrap();
let data = hex_to_bytes(v["data"].as_str().unwrap());
let expected_packed = hex_to_bytes(v["packed"].as_str().unwrap());
let msg = StreamDataMessage::new(stream_id, data.clone(), eof, compressed);
let packed = msg.pack();
assert_eq!(packed, expected_packed, "{desc}: pack mismatch");
if !compressed {
let unpacked = StreamDataMessage::unpack(&packed, &NoopCompressor).unwrap();
assert_eq!(unpacked.stream_id, stream_id, "{desc}: stream_id mismatch");
assert_eq!(unpacked.eof, eof, "{desc}: eof mismatch");
assert_eq!(unpacked.data, data, "{desc}: data mismatch");
}
eprintln!(" PASS: {desc}");
}
}
#[test]
fn test_full_handshake_two_engines() {
let mut rng_id = FixedRng::new(&[0x01; 128]);
let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
let dest_hash = [0xDD; 16];
let mut rng_init = FixedRng::new(&[0x10; 128]);
let (mut initiator, request_data) = LinkEngine::new_initiator(
&dest_hash,
2,
LinkMode::Aes256Cbc,
Some(500),
100.0,
&mut rng_init,
);
assert_eq!(initiator.state(), LinkState::Pending);
let mut hashable = Vec::new();
hashable.push(0x02); hashable.push(0x00); hashable.extend_from_slice(&dest_hash);
hashable.push(0x00);
hashable.extend_from_slice(&request_data);
initiator.set_link_id_from_hashable(&hashable, request_data.len());
let mut rng_resp = FixedRng::new(&[0x20; 128]);
let (mut responder, lrproof_data) = LinkEngine::new_responder(
&dest_sig_prv,
&dest_sig_pub_bytes,
&request_data,
&hashable,
&dest_hash,
2,
100.5,
&mut rng_resp,
)
.unwrap();
assert_eq!(responder.state(), LinkState::Handshake);
assert_eq!(responder.link_id(), initiator.link_id());
let mut rng_rtt = FixedRng::new(&[0x30; 128]);
let (lrrtt_enc, actions) = initiator
.handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 101.0, &mut rng_rtt)
.unwrap();
assert_eq!(initiator.state(), LinkState::Active);
assert_eq!(actions.len(), 2);
let actions = responder.handle_lrrtt(&lrrtt_enc, 101.5).unwrap();
assert_eq!(responder.state(), LinkState::Active);
assert_eq!(actions.len(), 2);
let mut rng_enc = FixedRng::new(&[0x40; 128]);
let ct = initiator.encrypt(b"ping", &mut rng_enc).unwrap();
let pt = responder.decrypt(&ct).unwrap();
assert_eq!(pt, b"ping");
let mut rng_enc2 = FixedRng::new(&[0x50; 128]);
let ct2 = responder.encrypt(b"pong", &mut rng_enc2).unwrap();
let pt2 = initiator.decrypt(&ct2).unwrap();
assert_eq!(pt2, b"pong");
}
#[test]
fn test_channel_messaging_over_link() {
let mut rng_id = FixedRng::new(&[0x01; 128]);
let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
let dest_hash = [0xDD; 16];
let mut rng_init = FixedRng::new(&[0x10; 128]);
let (mut initiator, request_data) = LinkEngine::new_initiator(
&dest_hash,
1,
LinkMode::Aes256Cbc,
Some(500),
100.0,
&mut rng_init,
);
let mut hashable = Vec::new();
hashable.push(0x02);
hashable.push(0x00);
hashable.extend_from_slice(&dest_hash);
hashable.push(0x00);
hashable.extend_from_slice(&request_data);
initiator.set_link_id_from_hashable(&hashable, request_data.len());
let mut rng_resp = FixedRng::new(&[0x20; 128]);
let (mut responder, lrproof_data) = LinkEngine::new_responder(
&dest_sig_prv,
&dest_sig_pub_bytes,
&request_data,
&hashable,
&dest_hash,
1,
100.5,
&mut rng_resp,
)
.unwrap();
let mut rng_rtt = FixedRng::new(&[0x30; 128]);
let (lrrtt_enc, _) = initiator
.handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 101.0, &mut rng_rtt)
.unwrap();
responder.handle_lrrtt(&lrrtt_enc, 101.5).unwrap();
let rtt = initiator.rtt().unwrap();
let mut ch_init = Channel::new(rtt);
let mut ch_resp = Channel::new(rtt);
let link_mdu = initiator.mdu();
let actions = ch_init
.send(0x01, b"Hello Channel!", 102.0, link_mdu)
.unwrap();
assert_eq!(actions.len(), 1);
let raw_envelope = match &actions[0] {
rns_core::channel::ChannelAction::SendOnLink { raw, .. } => raw.clone(),
_ => panic!("Expected SendOnLink"),
};
let mut rng_ch = FixedRng::new(&[0x60; 128]);
let encrypted = initiator.encrypt(&raw_envelope, &mut rng_ch).unwrap();
let decrypted = responder.decrypt(&encrypted).unwrap();
let recv_actions = ch_resp.receive(&decrypted, 102.1);
assert_eq!(recv_actions.len(), 1);
match &recv_actions[0] {
rns_core::channel::ChannelAction::MessageReceived {
msgtype, payload, ..
} => {
assert_eq!(*msgtype, 0x01);
assert_eq!(payload, b"Hello Channel!");
}
_ => panic!("Expected MessageReceived"),
}
}
#[test]
fn test_buffer_streaming() {
let mut writer = BufferWriter::new(1);
let mut reader = BufferReader::new(1);
let data = b"Hello buffer streaming! This is a test of chunked data transfer.";
let msgs = writer.write(data, 30, &NoopCompressor);
assert!(msgs.len() > 1);
for msg in &msgs {
reader.receive(msg);
}
let eof = writer.close();
reader.receive(&eof);
assert!(reader.is_eof());
let result = reader.read(1000);
assert_eq!(result, data);
assert!(reader.is_done());
}
#[test]
fn test_establishment_timeout_integration() {
let mut rng = FixedRng::new(&[0x42; 128]);
let dest_hash = [0xDD; 16];
let (mut engine, _) =
LinkEngine::new_initiator(&dest_hash, 1, LinkMode::Aes256Cbc, None, 0.0, &mut rng);
let actions = engine.tick(10.0);
assert!(actions.is_empty());
let actions = engine.tick(15.0);
assert_eq!(engine.state(), LinkState::Closed);
assert_eq!(actions.len(), 1);
}