Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
320
crates/rvf/rvf-runtime/tests/qr_seed_e2e.rs
Normal file
320
crates/rvf/rvf-runtime/tests/qr_seed_e2e.rs
Normal file
@@ -0,0 +1,320 @@
|
||||
//! End-to-end integration tests for the QR Cognitive Seed pipeline.
|
||||
//!
|
||||
//! Tests the full zero-dependency chain:
|
||||
//! Build → Compress → Hash → Sign → Serialize → Parse → Verify → Decompress
|
||||
|
||||
use rvf_runtime::compress;
|
||||
use rvf_runtime::qr_seed::*;
|
||||
use rvf_runtime::seed_crypto;
|
||||
use rvf_types::qr_seed::*;
|
||||
|
||||
const SIGNING_KEY: &[u8] = b"test-secret-key-for-hmac-sha256!";
|
||||
|
||||
/// Build a realistic fake WASM module.
|
||||
fn fake_wasm(size: usize) -> Vec<u8> {
|
||||
let mut wasm = Vec::with_capacity(size);
|
||||
// WASM magic + version.
|
||||
wasm.extend_from_slice(&[0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00]);
|
||||
// Repeated section patterns (compressible).
|
||||
while wasm.len() < size {
|
||||
wasm.extend_from_slice(&[0x01, 0x06, 0x01, 0x60, 0x01, 0x7F, 0x01, 0x7F]);
|
||||
}
|
||||
wasm.truncate(size);
|
||||
wasm
|
||||
}
|
||||
|
||||
fn default_layers_with_real_hashes() -> Vec<(LayerEntry, Vec<u8>)> {
|
||||
let layer_data_0 = vec![0x42u8; 4096];
|
||||
let layer_data_1 = vec![0x43u8; 51200];
|
||||
let layer_data_2 = vec![0x44u8; 204800];
|
||||
|
||||
vec![
|
||||
(
|
||||
LayerEntry {
|
||||
offset: 0,
|
||||
size: 4096,
|
||||
content_hash: seed_crypto::layer_content_hash(&layer_data_0),
|
||||
layer_id: layer_id::LEVEL0,
|
||||
priority: 0,
|
||||
required: 1,
|
||||
_pad: 0,
|
||||
},
|
||||
layer_data_0,
|
||||
),
|
||||
(
|
||||
LayerEntry {
|
||||
offset: 4096,
|
||||
size: 51200,
|
||||
content_hash: seed_crypto::layer_content_hash(&layer_data_1),
|
||||
layer_id: layer_id::HOT_CACHE,
|
||||
priority: 1,
|
||||
required: 1,
|
||||
_pad: 0,
|
||||
},
|
||||
layer_data_1,
|
||||
),
|
||||
(
|
||||
LayerEntry {
|
||||
offset: 55296,
|
||||
size: 204800,
|
||||
content_hash: seed_crypto::layer_content_hash(&layer_data_2),
|
||||
layer_id: layer_id::HNSW_LAYER_A,
|
||||
priority: 2,
|
||||
required: 0,
|
||||
_pad: 0,
|
||||
},
|
||||
layer_data_2,
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_round_trip_with_real_crypto() {
|
||||
// 1. Create and compress a fake WASM microkernel.
|
||||
let wasm = fake_wasm(5500);
|
||||
let compressed = compress::compress(&wasm);
|
||||
assert!(
|
||||
compressed.len() < wasm.len(),
|
||||
"compression failed: {} >= {}",
|
||||
compressed.len(),
|
||||
wasm.len()
|
||||
);
|
||||
|
||||
// 2. Build seed with real signing.
|
||||
let host = make_host_entry("https://cdn.example.com/brain.rvf", 0, 1, [0xAA; 16]).unwrap();
|
||||
let layers = default_layers_with_real_hashes();
|
||||
|
||||
let mut builder = SeedBuilder::new([0x01; 8], 384, 100_000)
|
||||
.with_microkernel(compressed.clone())
|
||||
.add_host(host);
|
||||
|
||||
builder.content_hash_full = Some(seed_crypto::full_content_hash(b"full rvf data"));
|
||||
builder.total_file_size = Some(260_096);
|
||||
|
||||
for (layer, _data) in &layers {
|
||||
builder = builder.add_layer(*layer);
|
||||
}
|
||||
|
||||
let (payload, header) = builder.build_and_sign(SIGNING_KEY).unwrap();
|
||||
|
||||
// 3. Verify QR capacity.
|
||||
assert!(header.fits_in_qr());
|
||||
assert!(payload.len() <= QR_MAX_BYTES);
|
||||
|
||||
// 4. Parse it back.
|
||||
let parsed = ParsedSeed::parse(&payload).unwrap();
|
||||
assert_eq!(parsed.header.seed_magic, SEED_MAGIC);
|
||||
assert!(parsed.header.is_signed());
|
||||
assert_eq!(parsed.header.sig_algo, seed_crypto::SIG_ALGO_HMAC_SHA256);
|
||||
|
||||
// 5. Full verification (magic + content hash + signature).
|
||||
parsed.verify_all(SIGNING_KEY, &payload).unwrap();
|
||||
|
||||
// 6. Individual verification steps.
|
||||
assert!(parsed.verify_content_hash());
|
||||
parsed.verify_signature(SIGNING_KEY, &payload).unwrap();
|
||||
|
||||
// 7. Wrong key must fail.
|
||||
assert!(parsed
|
||||
.verify_signature(b"wrong-key-must-fail-immediately!", &payload)
|
||||
.is_err());
|
||||
|
||||
// 8. Decompress microkernel.
|
||||
let decompressed = parsed.decompress_microkernel().unwrap();
|
||||
assert_eq!(decompressed, wasm);
|
||||
|
||||
// 9. Parse manifest and verify layer hashes.
|
||||
let manifest = parsed.parse_manifest().unwrap();
|
||||
assert_eq!(manifest.hosts.len(), 1);
|
||||
assert_eq!(manifest.layers.len(), 3);
|
||||
|
||||
for (layer, data) in &layers {
|
||||
assert!(
|
||||
seed_crypto::verify_layer(&layer.content_hash, data),
|
||||
"layer {} hash mismatch",
|
||||
layer.layer_id
|
||||
);
|
||||
}
|
||||
|
||||
// 10. Tampered layer data must fail.
|
||||
let tampered = vec![0xFF; 4096];
|
||||
assert!(!seed_crypto::verify_layer(
|
||||
&layers[0].0.content_hash,
|
||||
&tampered
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compress_microkernel_method() {
|
||||
let wasm = fake_wasm(5500);
|
||||
|
||||
let builder = SeedBuilder::new([0x02; 8], 128, 1000).compress_microkernel(&wasm);
|
||||
|
||||
let (payload, header) = builder.build_and_sign(SIGNING_KEY).unwrap();
|
||||
assert!(header.has_microkernel());
|
||||
assert!(header.fits_in_qr());
|
||||
|
||||
// Parse and decompress.
|
||||
let parsed = ParsedSeed::parse(&payload).unwrap();
|
||||
let decompressed = parsed.decompress_microkernel().unwrap();
|
||||
assert_eq!(decompressed, wasm);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsigned_build_still_works() {
|
||||
// The original build() method must still work for backward compatibility.
|
||||
let builder = SeedBuilder::new([0x03; 8], 128, 1000).with_content_hash([0xAA; 8]);
|
||||
let (payload, header) = builder.build().unwrap();
|
||||
assert!(!header.is_signed());
|
||||
assert_eq!(header.content_hash, [0xAA; 8]);
|
||||
|
||||
let parsed = ParsedSeed::parse(&payload).unwrap();
|
||||
assert!(parsed.signature.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tampered_payload_fails_signature() {
|
||||
let builder = SeedBuilder::new([0x04; 8], 128, 1000).compress_microkernel(&fake_wasm(2000));
|
||||
let (mut payload, _) = builder.build_and_sign(SIGNING_KEY).unwrap();
|
||||
|
||||
// Tamper with a byte in the microkernel area.
|
||||
payload[SEED_HEADER_SIZE + 10] ^= 0xFF;
|
||||
|
||||
let parsed = ParsedSeed::parse(&payload).unwrap();
|
||||
assert!(parsed.verify_signature(SIGNING_KEY, &payload).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tampered_payload_fails_content_hash() {
|
||||
let builder = SeedBuilder::new([0x05; 8], 128, 1000).compress_microkernel(&fake_wasm(2000));
|
||||
let (mut payload, _) = builder.build_and_sign(SIGNING_KEY).unwrap();
|
||||
|
||||
// Tamper with a byte in the microkernel.
|
||||
payload[SEED_HEADER_SIZE + 10] ^= 0xFF;
|
||||
|
||||
let parsed = ParsedSeed::parse(&payload).unwrap();
|
||||
assert!(!parsed.verify_content_hash());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_all_catches_bad_signature() {
|
||||
let builder = SeedBuilder::new([0x06; 8], 128, 1000).compress_microkernel(&fake_wasm(2000));
|
||||
let (payload, _) = builder.build_and_sign(SIGNING_KEY).unwrap();
|
||||
|
||||
let parsed = ParsedSeed::parse(&payload).unwrap();
|
||||
let err = parsed.verify_all(b"wrong-key-should-definitely-fail", &payload);
|
||||
assert!(err.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bootstrap_progress_with_real_layers() {
|
||||
let layers = default_layers_with_real_hashes();
|
||||
let manifest = DownloadManifest {
|
||||
hosts: vec![],
|
||||
content_hash: None,
|
||||
total_file_size: Some(260_096),
|
||||
layers: layers.iter().map(|(l, _)| *l).collect(),
|
||||
session_token: None,
|
||||
token_ttl: None,
|
||||
cert_pin: None,
|
||||
};
|
||||
|
||||
let mut progress = BootstrapProgress::new(&manifest);
|
||||
assert!(!progress.query_ready);
|
||||
assert_eq!(progress.phase, 0);
|
||||
|
||||
// Download layers progressively.
|
||||
progress.record_layer(&layers[0].0);
|
||||
assert!(!progress.query_ready); // Level0 alone isn't query-ready.
|
||||
|
||||
progress.record_layer(&layers[1].0);
|
||||
assert!(progress.query_ready);
|
||||
assert_eq!(progress.phase, 1);
|
||||
assert!((progress.estimated_recall - 0.50).abs() < f32::EPSILON);
|
||||
|
||||
progress.record_layer(&layers[2].0);
|
||||
assert!((progress.estimated_recall - 0.70).abs() < f32::EPSILON);
|
||||
assert_eq!(progress.phase, 2); // All layers downloaded.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compression_ratio_for_wasm() {
|
||||
let wasm = fake_wasm(5500);
|
||||
let compressed = compress::compress(&wasm);
|
||||
let ratio = wasm.len() as f64 / compressed.len() as f64;
|
||||
assert!(
|
||||
ratio > 1.2,
|
||||
"expected compression ratio > 1.2x, got {:.2}x",
|
||||
ratio
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sha256_produces_correct_hash() {
|
||||
// Verify the built-in SHA-256 against a known vector.
|
||||
let hash = rvf_types::sha256::sha256(b"abc");
|
||||
// NIST test vector for SHA-256("abc").
|
||||
assert_eq!(hash[0], 0xba);
|
||||
assert_eq!(hash[1], 0x78);
|
||||
assert_eq!(hash[2], 0x16);
|
||||
assert_eq!(hash[3], 0xbf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hmac_sha256_produces_correct_mac() {
|
||||
// RFC 4231 Test Case 2.
|
||||
let key = b"Jefe";
|
||||
let data = b"what do ya want for nothing?";
|
||||
let mac = rvf_types::sha256::hmac_sha256(key, data);
|
||||
assert_eq!(mac[0], 0x5b);
|
||||
assert_eq!(mac[1], 0xdc);
|
||||
assert_eq!(mac[2], 0xc1);
|
||||
assert_eq!(mac[3], 0x46);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maximal_seed_size() {
|
||||
// Build the largest possible seed that still fits in QR.
|
||||
let wasm = fake_wasm(5500);
|
||||
let compressed = compress::compress(&wasm);
|
||||
|
||||
let host1 = make_host_entry("https://cdn.example.com/brain.rvf", 0, 1, [0xAA; 16]).unwrap();
|
||||
let host2 = make_host_entry("https://mirror.example.com/brain.rvf", 1, 2, [0xBB; 16]).unwrap();
|
||||
|
||||
let layers = default_layers_with_real_hashes();
|
||||
let mut builder = SeedBuilder::new([0x07; 8], 384, 100_000)
|
||||
.with_microkernel(compressed)
|
||||
.add_host(host1)
|
||||
.add_host(host2);
|
||||
|
||||
builder.content_hash_full = Some([0xDD; 32]);
|
||||
builder.total_file_size = Some(10_000_000);
|
||||
builder.session_token = Some([0xEE; 16]);
|
||||
builder.token_ttl = Some(3600);
|
||||
builder.cert_pin = Some([0xFF; 32]);
|
||||
builder.stream_upgrade = true;
|
||||
|
||||
for (layer, _) in &layers {
|
||||
builder = builder.add_layer(*layer);
|
||||
}
|
||||
|
||||
let (payload, header) = builder.build_and_sign(SIGNING_KEY).unwrap();
|
||||
assert!(
|
||||
header.fits_in_qr(),
|
||||
"seed size {} exceeds QR max {}",
|
||||
header.total_seed_size,
|
||||
QR_MAX_BYTES
|
||||
);
|
||||
assert!(payload.len() <= QR_MAX_BYTES);
|
||||
|
||||
// Full round-trip verification.
|
||||
let parsed = ParsedSeed::parse(&payload).unwrap();
|
||||
parsed.verify_all(SIGNING_KEY, &payload).unwrap();
|
||||
|
||||
let manifest = parsed.parse_manifest().unwrap();
|
||||
assert_eq!(manifest.hosts.len(), 2);
|
||||
assert_eq!(manifest.layers.len(), 3);
|
||||
assert_eq!(manifest.session_token, Some([0xEE; 16]));
|
||||
assert_eq!(manifest.token_ttl, Some(3600));
|
||||
assert_eq!(manifest.cert_pin, Some([0xFF; 32]));
|
||||
}
|
||||
Reference in New Issue
Block a user