use super::super::Directory; use super::fixture::digest; use std::io::{Read, Write}; use std::os::unix::fs::PermissionsExt; use std::os::unix::net::UnixListener; #[test] fn cache_client_authenticates_receipts_and_rejects_missing_corrupt_or_truncated_bytes() { for behavior in [ "valid", "missing", "receipt", "digest", "truncated", "oversized", ] { let directory = Directory::new(); let state = directory.0.join("state"); std::fs::create_dir(&state).unwrap(); std::fs::set_permissions(&state, std::fs::Permissions::from_mode(0o700)).unwrap(); let token = "a".repeat(64); let token_path = state.join("deverd.token"); std::fs::write(&token_path, &token).unwrap(); std::fs::set_permissions(token_path, std::fs::Permissions::from_mode(0o600)).unwrap(); let endpoint = directory.0.join("cache.sock"); let server = UnixListener::bind(&endpoint).unwrap(); let bytes = b"archive fixture"; let sha256 = digest(bytes); let expected = sha256.clone(); let worker = std::thread::spawn(move || { let (mut stream, _) = server.accept().unwrap(); let mut length = [0; 4]; stream.read_exact(&mut length).unwrap(); let mut payload = vec![0; u32::from_be_bytes(length) as usize]; stream.read_exact(&mut payload).unwrap(); let request: serde_json::Value = serde_json::from_slice(&payload).unwrap(); assert_eq!(request["token"], token); assert_eq!(request["operation"]["artifact_get"]["sha256"], expected); assert_eq!(request["operation"]["artifact_get"]["bytes"], bytes.len()); if behavior == "oversized" { stream .write_all(&(1024_u32 * 1024 + 1).to_be_bytes()) .unwrap(); return; } let artifact = if behavior == "missing" { serde_json::Value::Null } else { serde_json::json!({"sha256":if behavior == "receipt" { "0".repeat(64) } else { expected }, "bytes":bytes.len()}) }; let response = serde_json::to_vec( &serde_json::json!({"ok":true, "status":null, "error":null, "artifact":artifact}), ) .unwrap(); stream .write_all(&(response.len() as u32).to_be_bytes()) .unwrap(); stream.write_all(&response).unwrap(); if !matches!(behavior, "missing" | "receipt") { let mut content = bytes.to_vec(); if behavior == "digest" { content[0] ^= 1; } if behavior == "truncated" { content.pop(); } stream.write_all(&content).unwrap(); } }); let result = dever::process::cache::read(&state, &endpoint, &sha256, bytes.len() as u64); worker.join().unwrap(); if behavior == "valid" { assert_eq!(result.unwrap(), bytes); } else { assert!(result.is_err(), "accepted {behavior}"); } } } #[test] fn cache_client_rejects_untrusted_state_before_connecting() { let directory = Directory::new(); std::fs::set_permissions(&directory.0, std::fs::Permissions::from_mode(0o777)).unwrap(); let error = dever::process::cache::read( &directory.0, &directory.0.join("absent.sock"), &"a".repeat(64), 1, ) .unwrap_err(); assert!(error.contains("not writable"), "{error}"); } #[test] fn cache_upload_requires_both_authenticated_admission_and_final_receipts() { for rejected in ["none", "admission", "publication"] { let directory = Directory::new(); let state = directory.0.join("state"); std::fs::create_dir(&state).unwrap(); std::fs::set_permissions(&state, std::fs::Permissions::from_mode(0o700)).unwrap(); let token = "b".repeat(64); let path = state.join("deverd.token"); std::fs::write(&path, &token).unwrap(); std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600)).unwrap(); let endpoint = directory.0.join("cache.sock"); let listener = UnixListener::bind(&endpoint).unwrap(); let bytes = b"content addressed upload"; let server = std::thread::spawn(move || { let (mut stream, _) = listener.accept().unwrap(); stream .set_read_timeout(Some(std::time::Duration::from_secs(5))) .unwrap(); let mut prefix = [0; 4]; stream.read_exact(&mut prefix).unwrap(); let mut payload = vec![0; u32::from_be_bytes(prefix) as usize]; stream.read_exact(&mut payload).unwrap(); let request: serde_json::Value = serde_json::from_slice(&payload).unwrap(); assert_eq!(request["token"], token); let expected = serde_json::json!({"sha256":digest(bytes), "bytes":bytes.len()}); assert_eq!(request["operation"]["artifact_put"], expected); for phase in ["admission", "publication"] { let artifact = if rejected == phase { serde_json::json!({"sha256":"0".repeat(64),"bytes":bytes.len()}) } else { expected.clone() }; let response = serde_json::to_vec( &serde_json::json!({"ok":true,"status":null,"error":null,"artifact":artifact}), ) .unwrap(); stream .write_all(&(response.len() as u32).to_be_bytes()) .unwrap(); stream.write_all(&response).unwrap(); if phase == "admission" { if rejected == phase { assert_eq!(stream.read(&mut prefix).unwrap(), 0); break; } let mut received = vec![0; bytes.len()]; stream.read_exact(&mut received).unwrap(); assert_eq!(received, bytes); } } }); let result = dever::process::cache::write(&state, &endpoint, bytes); server.join().unwrap(); assert_eq!(result.is_ok(), rejected == "none"); } } #[test] fn signed_resource_get_uses_the_historical_deverd_envelope_and_exact_receipt() { // 原 service.rs::resource 请求版本/catalog/扩展身份;服务先回复 receipt,再写裸资产。 for corrupt in [false, true] { let directory = Directory::new(); let state = directory.0.join("state"); std::fs::create_dir(&state).unwrap(); let token = "c".repeat(64); let token_path = state.join("deverd.token"); std::fs::write(&token_path, &token).unwrap(); std::fs::set_permissions(token_path, std::fs::Permissions::from_mode(0o600)).unwrap(); let endpoint = directory.0.join("resources.sock"); let listener = UnixListener::bind(&endpoint).unwrap(); let bytes = b"signed runtime fixture"; let catalog = "d".repeat(64); let extension = serde_json::json!({"kind":{"type":"runtime","ecosystem":"npm"},"target":"linux-x86_64"}); let expected = serde_json::json!({"token":token,"operation":{"resource_get":{"version":"0.0.1","catalog":catalog,"extension":extension,"artifact":{"sha256":digest(bytes),"bytes":bytes.len()}}},"installed":[]}); let worker = std::thread::spawn(move || { let (mut stream, _) = listener.accept().unwrap(); stream .set_read_timeout(Some(std::time::Duration::from_secs(5))) .unwrap(); let mut prefix = [0; 4]; stream.read_exact(&mut prefix).unwrap(); let mut request = vec![0; u32::from_be_bytes(prefix) as usize]; stream.read_exact(&mut request).unwrap(); assert_eq!( serde_json::from_slice::(&request).unwrap(), expected ); let response = serde_json::to_vec(&serde_json::json!({"ok":true,"status":null,"error":null,"artifact":{"sha256":digest(bytes),"bytes":bytes.len()}})).unwrap(); stream .write_all(&(response.len() as u32).to_be_bytes()) .unwrap(); stream.write_all(&response).unwrap(); let mut content = bytes.to_vec(); if corrupt { content[0] ^= 1; } stream.write_all(&content).unwrap(); }); let result = dever::process::cache::resource( &state, &endpoint, "0.0.1", &catalog, Some(&extension), &digest(bytes), bytes.len() as u64, ); worker.join().unwrap(); if corrupt { assert!(result.unwrap_err().contains("SHA-256")); } else { assert_eq!(result.unwrap(), bytes); } } }