1#![allow(dead_code)]
18
19use anyhow::{bail, Context, Result};
20use serde::{Deserialize, Serialize};
21use sha2::{Digest, Sha256};
22
23use crate::pack_sign::double_hash_sign;
24
25const OXP_MAGIC: &[u8; 4] = b"OXP\x01";
27
28const INTEGRITY_HASH_LEN: usize = 32;
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct PackManifest {
36 pub name: String,
37 pub version: String,
38 pub author: String,
39 pub description: String,
40 pub license: String,
41 pub created_at: u64,
42 pub targets: Vec<PackTargetEntry>,
43 pub dependencies: Vec<PackDependency>,
44 pub integrity: PackIntegrity,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct PackTargetEntry {
50 pub name: String,
51 pub category: String,
52 pub file_path: String,
53 pub size_bytes: usize,
54 pub sha256: String,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct PackDependency {
60 pub name: String,
61 pub version_req: String,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct PackIntegrity {
67 pub algorithm: String,
68 pub manifest_hash: String,
69 pub signature: Option<String>,
70}
71
72#[derive(Debug, Clone)]
74pub struct InstalledPack {
75 pub manifest: PackManifest,
76 pub install_path: String,
77 pub installed_at: u64,
78}
79
80pub struct PackBuilder {
84 manifest: PackManifest,
85 files: Vec<(String, Vec<u8>)>,
86}
87
88impl PackBuilder {
89 pub fn new(name: &str, version: &str, author: &str) -> Self {
91 Self {
92 manifest: PackManifest {
93 name: name.to_string(),
94 version: version.to_string(),
95 author: author.to_string(),
96 description: String::new(),
97 license: String::new(),
98 created_at: 0,
99 targets: Vec::new(),
100 dependencies: Vec::new(),
101 integrity: PackIntegrity {
102 algorithm: "sha256".to_string(),
103 manifest_hash: String::new(),
104 signature: None,
105 },
106 },
107 files: Vec::new(),
108 }
109 }
110
111 pub fn set_description(&mut self, desc: &str) {
113 self.manifest.description = desc.to_string();
114 }
115
116 pub fn set_license(&mut self, license: &str) {
118 self.manifest.license = license.to_string();
119 }
120
121 pub fn set_created_at(&mut self, ts: u64) {
123 self.manifest.created_at = ts;
124 }
125
126 pub fn add_target_file(&mut self, name: &str, category: &str, data: &[u8]) -> Result<()> {
128 if name.is_empty() {
129 bail!("target file name must not be empty");
130 }
131 let sha_hex = sha256_hex(data);
132 let file_path = format!("{}/{}", category, name);
133
134 self.manifest.targets.push(PackTargetEntry {
135 name: name.to_string(),
136 category: category.to_string(),
137 file_path: file_path.clone(),
138 size_bytes: data.len(),
139 sha256: sha_hex,
140 });
141 self.files.push((file_path, data.to_vec()));
142 Ok(())
143 }
144
145 pub fn add_dependency(&mut self, name: &str, version_req: &str) {
147 self.manifest.dependencies.push(PackDependency {
148 name: name.to_string(),
149 version_req: version_req.to_string(),
150 });
151 }
152
153 pub fn build(&self) -> Result<Vec<u8>> {
155 self.build_internal(None)
156 }
157
158 pub fn build_signed(&self, signing_key: &[u8]) -> Result<Vec<u8>> {
160 self.build_internal(Some(signing_key))
161 }
162
163 fn build_internal(&self, signing_key: Option<&[u8]>) -> Result<Vec<u8>> {
165 let manifest_hash_hex = self.compute_manifest_hash();
167
168 let signature_hex = signing_key.map(|key| {
170 let sig_bytes = double_hash_sign(key, manifest_hash_hex.as_bytes());
171 hex::encode(sig_bytes)
172 });
173
174 let mut manifest = self.manifest.clone();
176 manifest.integrity = PackIntegrity {
177 algorithm: "sha256".to_string(),
178 manifest_hash: manifest_hash_hex,
179 signature: signature_hex,
180 };
181
182 let manifest_json = serde_json::to_vec(&manifest)
183 .with_context(|| "failed to serialize manifest to JSON")?;
184
185 let mut buf: Vec<u8> = Vec::new();
187
188 buf.extend_from_slice(OXP_MAGIC);
190
191 let manifest_len = u32::try_from(manifest_json.len())
193 .with_context(|| "manifest JSON too large for u32 length")?;
194 buf.extend_from_slice(&manifest_len.to_le_bytes());
195 buf.extend_from_slice(&manifest_json);
196
197 let file_count =
199 u32::try_from(self.files.len()).with_context(|| "file count too large for u32")?;
200 buf.extend_from_slice(&file_count.to_le_bytes());
201
202 for (path, data) in &self.files {
204 let path_bytes = path.as_bytes();
205 let path_len = u16::try_from(path_bytes.len())
206 .with_context(|| format!("file path too long: {}", path))?;
207 buf.extend_from_slice(&path_len.to_le_bytes());
208 buf.extend_from_slice(path_bytes);
209
210 let data_len = u32::try_from(data.len())
211 .with_context(|| format!("file data too large: {}", path))?;
212 buf.extend_from_slice(&data_len.to_le_bytes());
213 buf.extend_from_slice(data);
214 }
215
216 let trailing_hash = sha256_bytes(&buf);
218 buf.extend_from_slice(&trailing_hash);
219
220 Ok(buf)
221 }
222
223 fn compute_manifest_hash(&self) -> String {
225 let mut hasher = Sha256::new();
226 let mut sorted_targets: Vec<&PackTargetEntry> = self.manifest.targets.iter().collect();
228 sorted_targets.sort_by(|a, b| a.file_path.cmp(&b.file_path));
229 for t in sorted_targets {
230 let line = format!("{}:{}:{}\n", t.file_path, t.size_bytes, t.sha256);
231 hasher.update(line.as_bytes());
232 }
233 hex::encode(hasher.finalize())
234 }
235}
236
237pub struct PackVerifier;
241
242impl PackVerifier {
243 pub fn verify_integrity(package_data: &[u8]) -> Result<PackManifest> {
245 let min_size = OXP_MAGIC.len() + 4 + INTEGRITY_HASH_LEN;
246 if package_data.len() < min_size {
247 bail!("package data too small ({} bytes)", package_data.len());
248 }
249
250 let payload_len = package_data.len() - INTEGRITY_HASH_LEN;
252 let payload = &package_data[..payload_len];
253 let stored_hash = &package_data[payload_len..];
254 let computed_hash = sha256_bytes(payload);
255 if stored_hash != computed_hash.as_slice() {
256 bail!("integrity hash mismatch: package data is corrupted or tampered");
257 }
258
259 Self::read_manifest(package_data)
260 }
261
262 pub fn verify_signature(package_data: &[u8], public_key: &[u8]) -> Result<bool> {
264 let manifest = Self::verify_integrity(package_data)?;
265
266 let stored_signature = match &manifest.integrity.signature {
267 Some(sig) => sig.clone(),
268 None => bail!("package has no signature to verify"),
269 };
270
271 let expected_sig_bytes =
272 double_hash_sign(public_key, manifest.integrity.manifest_hash.as_bytes());
273 let expected_hex = hex::encode(expected_sig_bytes);
274
275 Ok(stored_signature == expected_hex)
276 }
277
278 pub fn read_manifest(package_data: &[u8]) -> Result<PackManifest> {
280 let (manifest, _offset) = parse_manifest(package_data)?;
281 Ok(manifest)
282 }
283
284 pub fn extract_file(package_data: &[u8], file_path: &str) -> Result<Vec<u8>> {
286 let files = parse_files(package_data)?;
287 for (path, data) in files {
288 if path == file_path {
289 return Ok(data);
290 }
291 }
292 bail!("file not found in package: {}", file_path);
293 }
294
295 pub fn list_files(package_data: &[u8]) -> Result<Vec<PackTargetEntry>> {
297 let manifest = Self::read_manifest(package_data)?;
298 Ok(manifest.targets)
299 }
300}
301
302pub struct PackRegistry {
306 packages: Vec<InstalledPack>,
307}
308
309impl Default for PackRegistry {
310 fn default() -> Self {
311 Self::new()
312 }
313}
314
315impl PackRegistry {
316 pub fn new() -> Self {
318 Self {
319 packages: Vec::new(),
320 }
321 }
322
323 pub fn register(&mut self, manifest: PackManifest, install_path: &str) {
325 let now = std::time::SystemTime::now()
326 .duration_since(std::time::UNIX_EPOCH)
327 .map(|d| d.as_secs())
328 .unwrap_or(0);
329 self.packages.push(InstalledPack {
330 manifest,
331 install_path: install_path.to_string(),
332 installed_at: now,
333 });
334 }
335
336 pub fn unregister(&mut self, name: &str) -> Result<()> {
338 let idx = self
339 .packages
340 .iter()
341 .position(|p| p.manifest.name == name)
342 .with_context(|| format!("package '{}' not found in registry", name))?;
343 self.packages.remove(idx);
344 Ok(())
345 }
346
347 pub fn find(&self, name: &str) -> Option<&InstalledPack> {
349 self.packages.iter().find(|p| p.manifest.name == name)
350 }
351
352 pub fn find_by_category(&self, category: &str) -> Vec<&InstalledPack> {
354 self.packages
355 .iter()
356 .filter(|p| p.manifest.targets.iter().any(|t| t.category == category))
357 .collect()
358 }
359
360 pub fn list_all(&self) -> &[InstalledPack] {
362 &self.packages
363 }
364
365 pub fn check_dependencies(&self, manifest: &PackManifest) -> Vec<String> {
368 manifest
369 .dependencies
370 .iter()
371 .filter(|dep| {
372 !self
373 .packages
374 .iter()
375 .any(|installed| installed.manifest.name == dep.name)
376 })
377 .map(|dep| dep.name.clone())
378 .collect()
379 }
380}
381
382fn sha256_hex(data: &[u8]) -> String {
385 let mut h = Sha256::new();
386 h.update(data);
387 hex::encode(h.finalize())
388}
389
390fn sha256_bytes(data: &[u8]) -> Vec<u8> {
391 let mut h = Sha256::new();
392 h.update(data);
393 h.finalize().to_vec()
394}
395
396fn parse_manifest(data: &[u8]) -> Result<(PackManifest, usize)> {
399 if data.len() < 8 {
400 bail!("package data too small to contain header");
401 }
402 if &data[..4] != OXP_MAGIC {
403 bail!("invalid OXP magic bytes");
404 }
405
406 let manifest_len = u32::from_le_bytes(
407 data[4..8]
408 .try_into()
409 .with_context(|| "reading manifest length")?,
410 ) as usize;
411
412 let manifest_end = 8 + manifest_len;
413 if data.len() < manifest_end {
414 bail!(
415 "package data truncated: need {} bytes for manifest, have {}",
416 manifest_end,
417 data.len()
418 );
419 }
420
421 let manifest: PackManifest = serde_json::from_slice(&data[8..manifest_end])
422 .with_context(|| "failed to deserialize manifest JSON")?;
423
424 Ok((manifest, manifest_end))
425}
426
427fn parse_files(data: &[u8]) -> Result<Vec<(String, Vec<u8>)>> {
429 let (_manifest, mut offset) = parse_manifest(data)?;
430
431 let payload_end = if data.len() >= INTEGRITY_HASH_LEN {
433 data.len() - INTEGRITY_HASH_LEN
434 } else {
435 data.len()
436 };
437
438 if offset + 4 > payload_end {
439 bail!("package data truncated: cannot read file count");
440 }
441
442 let file_count = u32::from_le_bytes(
443 data[offset..offset + 4]
444 .try_into()
445 .with_context(|| "reading file count")?,
446 ) as usize;
447 offset += 4;
448
449 let mut files = Vec::with_capacity(file_count);
450
451 for i in 0..file_count {
452 if offset + 2 > payload_end {
454 bail!("truncated at file {} path length", i);
455 }
456 let path_len = u16::from_le_bytes(
457 data[offset..offset + 2]
458 .try_into()
459 .with_context(|| format!("reading path length for file {}", i))?,
460 ) as usize;
461 offset += 2;
462
463 if offset + path_len > payload_end {
465 bail!("truncated at file {} path data", i);
466 }
467 let path = std::str::from_utf8(&data[offset..offset + path_len])
468 .with_context(|| format!("file {} path is not valid UTF-8", i))?
469 .to_string();
470 offset += path_len;
471
472 if offset + 4 > payload_end {
474 bail!("truncated at file {} data length", i);
475 }
476 let data_len = u32::from_le_bytes(
477 data[offset..offset + 4]
478 .try_into()
479 .with_context(|| format!("reading data length for file {}", i))?,
480 ) as usize;
481 offset += 4;
482
483 if offset + data_len > payload_end {
485 bail!("truncated at file {} data", i);
486 }
487 let file_data = data[offset..offset + data_len].to_vec();
488 offset += data_len;
489
490 files.push((path, file_data));
491 }
492
493 Ok(files)
494}
495
496#[cfg(test)]
499mod tests {
500 use super::*;
501
502 fn make_basic_builder() -> PackBuilder {
503 let mut b = PackBuilder::new("test-pack", "1.0.0", "tester");
504 b.set_description("A test package");
505 b.set_license("MIT");
506 b.set_created_at(1700000000);
507 b
508 }
509
510 #[test]
512 fn build_empty_package() {
513 let b = make_basic_builder();
514 let data = b.build().expect("should succeed");
515 assert!(data.len() > OXP_MAGIC.len() + INTEGRITY_HASH_LEN);
516 assert_eq!(&data[..4], OXP_MAGIC);
517 }
518
519 #[test]
521 fn build_and_verify_one_file() {
522 let mut b = make_basic_builder();
523 b.add_target_file("model.dat", "meshes", b"triangle-data")
524 .expect("should succeed");
525 let data = b.build().expect("should succeed");
526 let manifest = PackVerifier::verify_integrity(&data).expect("should succeed");
527 assert_eq!(manifest.name, "test-pack");
528 assert_eq!(manifest.targets.len(), 1);
529 assert_eq!(manifest.targets[0].name, "model.dat");
530 }
531
532 #[test]
534 fn build_multiple_files() {
535 let mut b = make_basic_builder();
536 b.add_target_file("a.bin", "cat_a", b"alpha")
537 .expect("should succeed");
538 b.add_target_file("b.bin", "cat_b", b"beta")
539 .expect("should succeed");
540 b.add_target_file("c.bin", "cat_a", b"gamma")
541 .expect("should succeed");
542 let data = b.build().expect("should succeed");
543 let manifest = PackVerifier::verify_integrity(&data).expect("should succeed");
544 assert_eq!(manifest.targets.len(), 3);
545 }
546
547 #[test]
549 fn integrity_fails_on_tampered_data() {
550 let mut b = make_basic_builder();
551 b.add_target_file("x.bin", "cat", b"data")
552 .expect("should succeed");
553 let mut data = b.build().expect("should succeed");
554 let mid = data.len() / 2;
556 data[mid] ^= 0xFF;
557 assert!(PackVerifier::verify_integrity(&data).is_err());
558 }
559
560 #[test]
562 fn signed_build_and_verify() {
563 let key = b"my-secret-key";
564 let mut b = make_basic_builder();
565 b.add_target_file("asset.glb", "models", b"glb-content")
566 .expect("should succeed");
567 let data = b.build_signed(key).expect("should succeed");
568 let ok = PackVerifier::verify_signature(&data, key).expect("should succeed");
569 assert!(ok);
570 }
571
572 #[test]
574 fn wrong_key_fails_signature() {
575 let mut b = make_basic_builder();
576 b.add_target_file("f.bin", "cat", b"stuff")
577 .expect("should succeed");
578 let data = b.build_signed(b"correct-key").expect("should succeed");
579 let ok = PackVerifier::verify_signature(&data, b"wrong-key").expect("should succeed");
580 assert!(!ok);
581 }
582
583 #[test]
585 fn unsigned_package_signature_check_fails() {
586 let mut b = make_basic_builder();
587 b.add_target_file("f.bin", "cat", b"stuff")
588 .expect("should succeed");
589 let data = b.build().expect("should succeed");
590 assert!(PackVerifier::verify_signature(&data, b"any-key").is_err());
591 }
592
593 #[test]
595 fn extract_file_by_path() {
596 let mut b = make_basic_builder();
597 b.add_target_file("mesh.obj", "models", b"obj-content")
598 .expect("should succeed");
599 b.add_target_file("tex.png", "textures", b"png-bytes")
600 .expect("should succeed");
601 let data = b.build().expect("should succeed");
602 let extracted =
603 PackVerifier::extract_file(&data, "textures/tex.png").expect("should succeed");
604 assert_eq!(extracted, b"png-bytes");
605 }
606
607 #[test]
609 fn extract_missing_file() {
610 let b = make_basic_builder();
611 let data = b.build().expect("should succeed");
612 assert!(PackVerifier::extract_file(&data, "no/such/file").is_err());
613 }
614
615 #[test]
617 fn list_files_returns_targets() {
618 let mut b = make_basic_builder();
619 b.add_target_file("a.bin", "cat_a", b"aaa")
620 .expect("should succeed");
621 b.add_target_file("b.bin", "cat_b", b"bbb")
622 .expect("should succeed");
623 let data = b.build().expect("should succeed");
624 let files = PackVerifier::list_files(&data).expect("should succeed");
625 assert_eq!(files.len(), 2);
626 }
627
628 #[test]
630 fn read_manifest_metadata() {
631 let mut b = make_basic_builder();
632 b.add_dependency("base-pack", ">=1.0");
633 let data = b.build().expect("should succeed");
634 let manifest = PackVerifier::read_manifest(&data).expect("should succeed");
635 assert_eq!(manifest.version, "1.0.0");
636 assert_eq!(manifest.author, "tester");
637 assert_eq!(manifest.license, "MIT");
638 assert_eq!(manifest.dependencies.len(), 1);
639 assert_eq!(manifest.dependencies[0].name, "base-pack");
640 }
641
642 #[test]
644 fn registry_register_find_unregister() {
645 let mut reg = PackRegistry::new();
646 let manifest = PackManifest {
647 name: "my-pack".to_string(),
648 version: "0.1.0".to_string(),
649 author: "author".to_string(),
650 description: String::new(),
651 license: "MIT".to_string(),
652 created_at: 0,
653 targets: vec![PackTargetEntry {
654 name: "f.bin".to_string(),
655 category: "meshes".to_string(),
656 file_path: "meshes/f.bin".to_string(),
657 size_bytes: 100,
658 sha256: "abc123".to_string(),
659 }],
660 dependencies: Vec::new(),
661 integrity: PackIntegrity {
662 algorithm: "sha256".to_string(),
663 manifest_hash: String::new(),
664 signature: None,
665 },
666 };
667 reg.register(manifest, "/tmp/my-pack");
668 assert!(reg.find("my-pack").is_some());
669 assert!(reg.find("nonexistent").is_none());
670 assert_eq!(reg.list_all().len(), 1);
671 reg.unregister("my-pack").expect("should succeed");
672 assert!(reg.find("my-pack").is_none());
673 assert_eq!(reg.list_all().len(), 0);
674 }
675
676 #[test]
678 fn registry_find_by_category() {
679 let mut reg = PackRegistry::new();
680 let make_manifest = |name: &str, cat: &str| PackManifest {
681 name: name.to_string(),
682 version: "1.0.0".to_string(),
683 author: "a".to_string(),
684 description: String::new(),
685 license: String::new(),
686 created_at: 0,
687 targets: vec![PackTargetEntry {
688 name: "f".to_string(),
689 category: cat.to_string(),
690 file_path: format!("{}/f", cat),
691 size_bytes: 0,
692 sha256: String::new(),
693 }],
694 dependencies: Vec::new(),
695 integrity: PackIntegrity {
696 algorithm: "sha256".to_string(),
697 manifest_hash: String::new(),
698 signature: None,
699 },
700 };
701
702 reg.register(make_manifest("pack-a", "meshes"), "/a");
703 reg.register(make_manifest("pack-b", "textures"), "/b");
704 reg.register(make_manifest("pack-c", "meshes"), "/c");
705
706 let meshes = reg.find_by_category("meshes");
707 assert_eq!(meshes.len(), 2);
708 let textures = reg.find_by_category("textures");
709 assert_eq!(textures.len(), 1);
710 let empty = reg.find_by_category("audio");
711 assert!(empty.is_empty());
712 }
713
714 #[test]
716 fn registry_check_dependencies() {
717 let mut reg = PackRegistry::new();
718 let base_manifest = PackManifest {
719 name: "base-pack".to_string(),
720 version: "1.0.0".to_string(),
721 author: "a".to_string(),
722 description: String::new(),
723 license: String::new(),
724 created_at: 0,
725 targets: Vec::new(),
726 dependencies: Vec::new(),
727 integrity: PackIntegrity {
728 algorithm: "sha256".to_string(),
729 manifest_hash: String::new(),
730 signature: None,
731 },
732 };
733 reg.register(base_manifest, "/base");
734
735 let dependent = PackManifest {
736 name: "top-pack".to_string(),
737 version: "1.0.0".to_string(),
738 author: "a".to_string(),
739 description: String::new(),
740 license: String::new(),
741 created_at: 0,
742 targets: Vec::new(),
743 dependencies: vec![
744 PackDependency {
745 name: "base-pack".to_string(),
746 version_req: ">=1.0".to_string(),
747 },
748 PackDependency {
749 name: "missing-pack".to_string(),
750 version_req: ">=0.5".to_string(),
751 },
752 ],
753 integrity: PackIntegrity {
754 algorithm: "sha256".to_string(),
755 manifest_hash: String::new(),
756 signature: None,
757 },
758 };
759
760 let missing = reg.check_dependencies(&dependent);
761 assert_eq!(missing, vec!["missing-pack"]);
762 }
763
764 #[test]
766 fn reject_empty_target_name() {
767 let mut b = make_basic_builder();
768 assert!(b.add_target_file("", "cat", b"data").is_err());
769 }
770
771 #[test]
773 fn too_small_package_rejected() {
774 assert!(PackVerifier::verify_integrity(b"OXP").is_err());
775 }
776
777 #[test]
779 fn wrong_magic_rejected() {
780 let b = make_basic_builder();
781 let mut data = b.build().expect("should succeed");
782 data[0] = b'Z';
783 assert!(PackVerifier::read_manifest(&data).is_err());
785 }
786
787 #[test]
789 fn large_file_round_trip() {
790 let mut b = make_basic_builder();
791 let large_data = vec![0xABu8; 100_000];
792 b.add_target_file("big.bin", "data", &large_data)
793 .expect("should succeed");
794 let pkg = b.build().expect("should succeed");
795 let extracted = PackVerifier::extract_file(&pkg, "data/big.bin").expect("should succeed");
796 assert_eq!(extracted.len(), 100_000);
797 assert!(extracted.iter().all(|&b| b == 0xAB));
798 }
799
800 #[test]
802 fn manifest_hash_deterministic() {
803 let mut b1 = make_basic_builder();
804 b1.add_target_file("a.bin", "cat", b"data")
805 .expect("should succeed");
806 let hash1 = b1.compute_manifest_hash();
807
808 let mut b2 = make_basic_builder();
809 b2.add_target_file("a.bin", "cat", b"data")
810 .expect("should succeed");
811 let hash2 = b2.compute_manifest_hash();
812
813 assert_eq!(hash1, hash2);
814 }
815
816 #[test]
818 fn unregister_missing_pack_fails() {
819 let mut reg = PackRegistry::new();
820 assert!(reg.unregister("ghost").is_err());
821 }
822
823 #[test]
825 fn target_entry_sha256_matches() {
826 let mut b = make_basic_builder();
827 let file_data = b"hello oxihuman";
828 b.add_target_file("hello.txt", "text", file_data)
829 .expect("should succeed");
830 let pkg = b.build().expect("should succeed");
831 let manifest = PackVerifier::read_manifest(&pkg).expect("should succeed");
832 let expected = sha256_hex(file_data);
833 assert_eq!(manifest.targets[0].sha256, expected);
834 }
835
836 #[test]
838 fn default_registry_is_empty() {
839 let reg = PackRegistry::default();
840 assert!(reg.list_all().is_empty());
841 }
842
843 #[test]
845 fn integrity_field_populated() {
846 let mut b = make_basic_builder();
847 b.add_target_file("f.bin", "cat", b"d")
848 .expect("should succeed");
849 let pkg = b.build().expect("should succeed");
850 let manifest = PackVerifier::read_manifest(&pkg).expect("should succeed");
851 assert_eq!(manifest.integrity.algorithm, "sha256");
852 assert!(!manifest.integrity.manifest_hash.is_empty());
853 assert!(manifest.integrity.signature.is_none());
854 }
855
856 #[test]
858 fn signed_manifest_has_signature() {
859 let mut b = make_basic_builder();
860 b.add_target_file("f.bin", "cat", b"d")
861 .expect("should succeed");
862 let pkg = b.build_signed(b"key").expect("should succeed");
863 let manifest = PackVerifier::read_manifest(&pkg).expect("should succeed");
864 assert!(manifest.integrity.signature.is_some());
865 }
866
867 #[test]
869 fn multiple_dependencies() {
870 let mut b = make_basic_builder();
871 b.add_dependency("dep-a", ">=1.0");
872 b.add_dependency("dep-b", ">=2.0");
873 b.add_dependency("dep-c", ">=0.1");
874 let pkg = b.build().expect("should succeed");
875 let manifest = PackVerifier::read_manifest(&pkg).expect("should succeed");
876 assert_eq!(manifest.dependencies.len(), 3);
877 }
878}