1use alloc::{string::String, vec::Vec};
10
11use super::{Dependency, PackageId, PackageMetadata, Version};
12
13#[derive(Debug, Clone, Copy)]
15#[allow(dead_code)] enum HttpMethod {
17 Get,
18 Head,
19}
20
21#[derive(Debug)]
23pub struct HttpResponse {
24 status_code: u16,
25 headers: Vec<(String, String)>,
26 body: Vec<u8>,
27}
28
29impl HttpResponse {
30 fn new() -> Self {
31 Self {
32 status_code: 0,
33 headers: Vec::new(),
34 body: Vec::new(),
35 }
36 }
37
38 fn is_success(&self) -> bool {
39 (200..300).contains(&self.status_code)
40 }
41}
42
43pub struct HttpClient {
45 base_url: String,
47 timeout_ms: u32,
49 user_agent: String,
51}
52
53impl HttpClient {
54 fn new(base_url: String) -> Self {
55 Self {
56 base_url,
57 timeout_ms: 30000, user_agent: String::from("VeridianOS-PackageManager/1.0"),
59 }
60 }
61
62 fn get(&self, path: &str) -> Result<HttpResponse, HttpError> {
64 self.request(HttpMethod::Get, path, None)
65 }
66
67 fn request(
69 &self,
70 method: HttpMethod,
71 path: &str,
72 _body: Option<&[u8]>,
73 ) -> Result<HttpResponse, HttpError> {
74 let url = self.build_url(path);
76
77 let (host, port, request_path) = self.parse_url(&url)?;
79
80 let request = self.build_request(method, &host, &request_path);
82
83 let response_data = self.send_request(&host, port, &request)?;
85
86 self.parse_response(&response_data)
88 }
89
90 fn build_url(&self, path: &str) -> String {
91 let mut url = self.base_url.clone();
92 if !url.ends_with('/') && !path.starts_with('/') {
93 url.push('/');
94 }
95 url.push_str(path);
96 url
97 }
98
99 fn parse_url(&self, url: &str) -> Result<(String, u16, String), HttpError> {
100 let url = url
102 .strip_prefix("https://")
103 .or_else(|| url.strip_prefix("http://"))
104 .unwrap_or(url);
105
106 let (host_port, path) = match url.find('/') {
108 Some(idx) => (&url[..idx], &url[idx..]),
109 None => (url, "/"),
110 };
111
112 let (host, port) = match host_port.find(':') {
114 Some(idx) => {
115 let port_str = &host_port[idx + 1..];
116 let port = port_str.parse::<u16>().unwrap_or(443);
117 (&host_port[..idx], port)
118 }
119 None => {
120 (host_port, 443)
122 }
123 };
124
125 Ok((String::from(host), port, String::from(path)))
126 }
127
128 fn build_request(&self, method: HttpMethod, host: &str, path: &str) -> Vec<u8> {
129 let method_str = match method {
130 HttpMethod::Get => "GET",
131 HttpMethod::Head => "HEAD",
132 };
133
134 let request = alloc::format!(
135 "{} {} HTTP/1.1\r\nHost: {}\r\nUser-Agent: {}\r\nAccept: application/octet-stream, \
136 application/json, */*\r\nConnection: close\r\n\r\n",
137 method_str,
138 path,
139 host,
140 self.user_agent
141 );
142
143 request.into_bytes()
144 }
145
146 fn send_request(&self, host: &str, port: u16, request: &[u8]) -> Result<Vec<u8>, HttpError> {
147 use crate::net::{
149 socket::{Socket, SocketDomain, SocketProtocol, SocketType},
150 Ipv4Address, SocketAddr,
151 };
152
153 let ip_addr = self.resolve_hostname(host)?;
155
156 let mut socket = Socket::new(SocketDomain::Inet, SocketType::Stream, SocketProtocol::Tcp)
158 .map_err(|_| HttpError::ConnectionFailed)?;
159
160 let addr = SocketAddr::v4(Ipv4Address(ip_addr), port);
162 socket
163 .connect(addr)
164 .map_err(|_| HttpError::ConnectionFailed)?;
165
166 socket.options.recv_timeout_ms = Some(self.timeout_ms as u64);
168
169 socket
171 .send(request, 0)
172 .map_err(|_| HttpError::NetworkError)?;
173
174 let mut response = Vec::new();
176 let mut buf = [0u8; 4096];
177
178 loop {
179 match socket.recv(&mut buf, 0) {
180 Ok(0) => break, Ok(n) => response.extend_from_slice(&buf[..n]),
182 Err(_) => break,
183 }
184
185 if response.len() > 64 * 1024 * 1024 {
187 return Err(HttpError::ResponseTooLarge);
188 }
189 }
190
191 let _ = socket.close();
193
194 Ok(response)
195 }
196
197 fn resolve_hostname(&self, host: &str) -> Result<[u8; 4], HttpError> {
198 let parts: Vec<&str> = host.split('.').collect();
203 if parts.len() == 4 {
204 let mut ip = [0u8; 4];
205 let mut valid = true;
206 for (i, part) in parts.iter().enumerate() {
207 match part.parse::<u8>() {
208 Ok(byte) => ip[i] = byte,
209 Err(_) => {
210 valid = false;
211 break;
212 }
213 }
214 }
215 if valid {
216 return Ok(ip);
217 }
218 }
219
220 Ok([127, 0, 0, 1])
223 }
224
225 fn parse_response(&self, data: &[u8]) -> Result<HttpResponse, HttpError> {
226 let mut response = HttpResponse::new();
227
228 let header_end = self
230 .find_header_end(data)
231 .ok_or(HttpError::InvalidResponse)?;
232 let header_data = &data[..header_end];
233 let body_start = header_end + 4; let header_str =
237 core::str::from_utf8(header_data).map_err(|_| HttpError::InvalidResponse)?;
238 let mut lines = header_str.lines();
239
240 let status_line = lines.next().ok_or(HttpError::InvalidResponse)?;
241 let parts: Vec<&str> = status_line.split_whitespace().collect();
242 if parts.len() < 2 {
243 return Err(HttpError::InvalidResponse);
244 }
245 response.status_code = parts[1].parse().unwrap_or(0);
246
247 for line in lines {
249 if let Some((key, value)) = line.split_once(':') {
250 response
251 .headers
252 .push((String::from(key.trim()), String::from(value.trim())));
253 }
254 }
255
256 if body_start < data.len() {
258 response.body = data[body_start..].to_vec();
259 }
260
261 Ok(response)
262 }
263
264 fn find_header_end(&self, data: &[u8]) -> Option<usize> {
265 for i in 0..data.len().saturating_sub(3) {
266 if &data[i..i + 4] == b"\r\n\r\n" {
267 return Some(i);
268 }
269 }
270 None
271 }
272}
273
274#[derive(Debug)]
276pub enum HttpError {
277 ConnectionFailed,
278 InvalidResponse,
279 ResponseTooLarge,
280 Timeout,
281 NetworkError,
282}
283
284#[derive(Debug, Clone)]
286pub struct Repository {
287 pub name: String,
289 pub url: String,
291 pub trusted: bool,
293 package_cache: Vec<PackageMetadata>,
295 last_updated: u64,
297}
298
299impl Repository {
300 pub fn new(name: String, url: String, trusted: bool) -> Self {
301 Self {
302 name,
303 url,
304 trusted,
305 package_cache: Vec::new(),
306 last_updated: 0,
307 }
308 }
309
310 pub fn fetch_package_list(&self) -> Vec<PackageMetadata> {
312 let client = HttpClient::new(self.url.clone());
313
314 match client.get("packages.json") {
316 Ok(response) if response.is_success() => self.parse_package_index(&response.body),
317 Ok(_response) => {
318 crate::println!("[PKG] Repository {} returned error, using cache", self.name);
320 self.package_cache.clone()
321 }
322 Err(_e) => {
323 crate::println!("[PKG] Failed to connect to repository {}", self.name);
325 self.package_cache.clone()
326 }
327 }
328 }
329
330 fn parse_package_index(&self, data: &[u8]) -> Vec<PackageMetadata> {
332 let mut packages = Vec::new();
333
334 let json_str = match core::str::from_utf8(data) {
336 Ok(s) => s,
337 Err(_) => return packages,
338 };
339
340 if !json_str.starts_with('[') {
343 return packages;
344 }
345
346 let mut depth = 0;
348 let mut obj_start = 0;
349 let chars: Vec<char> = json_str.chars().collect();
350
351 for (i, &c) in chars.iter().enumerate() {
352 match c {
353 '{' => {
354 if depth == 1 {
355 obj_start = i;
356 }
357 depth += 1;
358 }
359 '}' => {
360 depth -= 1;
361 if depth == 1 {
362 let obj_str: String = chars[obj_start..=i].iter().collect();
363 if let Some(pkg) = self.parse_package_object(&obj_str) {
364 packages.push(pkg);
365 }
366 }
367 }
368 _ => {}
369 }
370 }
371
372 packages
373 }
374
375 fn parse_package_object(&self, json: &str) -> Option<PackageMetadata> {
377 let name = self.extract_json_string(json, "name")?;
379 let version_str = self.extract_json_string(json, "version")?;
380 let author = self.extract_json_string(json, "author").unwrap_or_default();
381 let description = self
382 .extract_json_string(json, "description")
383 .unwrap_or_default();
384 let license = self
385 .extract_json_string(json, "license")
386 .unwrap_or_default();
387
388 let version = self.parse_version(&version_str)?;
390
391 let dependencies = self.extract_dependencies(json);
393 let conflicts = self.extract_conflicts(json);
394
395 Some(PackageMetadata {
396 name,
397 version,
398 author,
399 description,
400 license,
401 dependencies,
402 conflicts,
403 })
404 }
405
406 fn extract_json_string(&self, json: &str, key: &str) -> Option<String> {
407 let pattern = alloc::format!("\"{}\":\"", key);
408 let start = json.find(&pattern)? + pattern.len();
409 let end = json[start..].find('"')? + start;
410 Some(String::from(&json[start..end]))
411 }
412
413 fn parse_version(&self, version_str: &str) -> Option<Version> {
414 let parts: Vec<&str> = version_str.split('.').collect();
415 if parts.len() >= 3 {
416 let major = parts[0].parse().ok()?;
417 let minor = parts[1].parse().ok()?;
418 let patch = parts[2].parse().ok()?;
419 Some(Version::new(major, minor, patch))
420 } else {
421 None
422 }
423 }
424
425 fn extract_dependencies(&self, json: &str) -> Vec<Dependency> {
426 let mut deps = Vec::new();
427
428 if let Some(start) = json.find("\"dependencies\":[") {
430 let arr_start = start + "\"dependencies\":[".len();
431 if let Some(arr_end) = json[arr_start..].find(']') {
432 let arr_str = &json[arr_start..arr_start + arr_end];
433
434 for dep_obj in arr_str.split("},{") {
436 let dep_str = dep_obj.trim_matches(|c| c == '{' || c == '}');
437 if let (Some(name), Some(version)) = (
438 self.extract_json_string(dep_str, "name"),
439 self.extract_json_string(dep_str, "version"),
440 ) {
441 deps.push(Dependency {
442 name,
443 version_req: version,
444 });
445 }
446 }
447 }
448 }
449
450 deps
451 }
452
453 fn extract_conflicts(&self, json: &str) -> Vec<String> {
454 let mut conflicts = Vec::new();
455
456 if let Some(start) = json.find("\"conflicts\":[") {
458 let arr_start = start + "\"conflicts\":[".len();
459 if let Some(arr_end) = json[arr_start..].find(']') {
460 let arr_str = &json[arr_start..arr_start + arr_end];
461
462 for conflict in arr_str.split(',') {
464 let name = conflict.trim().trim_matches('"');
465 if !name.is_empty() {
466 conflicts.push(String::from(name));
467 }
468 }
469 }
470 }
471
472 conflicts
473 }
474
475 pub fn download_package(&self, package_id: &PackageId) -> Option<Vec<u8>> {
477 let client = HttpClient::new(self.url.clone());
478
479 let path = alloc::format!("packages/{}.vpkg", package_id);
481
482 match client.get(&path) {
483 Ok(response) if response.is_success() => {
484 crate::println!(
485 "[PKG] Downloaded {} ({} bytes)",
486 package_id,
487 response.body.len()
488 );
489 Some(response.body)
490 }
491 Ok(_response) => {
492 #[cfg(target_arch = "x86_64")]
493 crate::println!(
494 "[PKG] Failed to download {}: HTTP {}",
495 package_id,
496 _response.status_code
497 );
498 None
499 }
500 Err(_e) => {
501 crate::println!("[PKG] Network error downloading {}", package_id);
502 None
503 }
504 }
505 }
506
507 pub fn has_package(&self, package_id: &PackageId) -> bool {
509 self.package_cache.iter().any(|p| &p.name == package_id)
510 }
511
512 pub fn get_package(&self, package_id: &PackageId) -> Option<&PackageMetadata> {
514 self.package_cache.iter().find(|p| &p.name == package_id)
515 }
516
517 pub fn invalidate_cache(&mut self) {
519 self.package_cache.clear();
520 self.last_updated = 0;
521 }
522
523 pub fn is_cache_stale(&self, current_time: u64) -> bool {
525 current_time.saturating_sub(self.last_updated) > 3600
526 }
527}
528
529impl Default for Repository {
530 fn default() -> Self {
531 Self::new(
532 String::from("default"),
533 String::from("https://packages.veridian.org"),
534 true,
535 )
536 }
537}
538
539pub struct RepositoryIndex {
548 pub version: u32,
550 pub generated_at: u64,
552 pub entries: Vec<RepositoryIndexEntry>,
554 pub signature: Vec<u8>,
556}
557
558#[derive(Debug, Clone)]
560pub struct RepositoryIndexEntry {
561 pub name: String,
563 pub version: String,
565 pub hash: [u8; 32],
567 pub size: u64,
569 pub description: String,
571 pub license: String,
573}
574
575impl RepositoryIndex {
576 pub fn new() -> Self {
578 Self {
579 version: 1,
580 generated_at: crate::arch::timer::get_timestamp_secs(),
581 entries: Vec::new(),
582 signature: Vec::new(),
583 }
584 }
585
586 pub fn generate(packages: &[super::PackageMetadata]) -> Self {
588 let mut index = Self::new();
589 for pkg in packages {
590 index.entries.push(RepositoryIndexEntry {
591 name: pkg.name.clone(),
592 version: alloc::format!(
593 "{}.{}.{}",
594 pkg.version.major,
595 pkg.version.minor,
596 pkg.version.patch
597 ),
598 hash: [0u8; 32], size: 0,
600 description: pkg.description.clone(),
601 license: pkg.license.clone(),
602 });
603 }
604 index
605 }
606
607 pub fn to_bytes(&self) -> Vec<u8> {
609 let mut buf = Vec::new();
610 buf.extend_from_slice(b"{\"version\":");
611 let version_str = alloc::format!("{}", self.version);
612 buf.extend_from_slice(version_str.as_bytes());
613 buf.extend_from_slice(b",\"generated_at\":");
614 let ts_str = alloc::format!("{}", self.generated_at);
615 buf.extend_from_slice(ts_str.as_bytes());
616 buf.extend_from_slice(b",\"packages\":[");
617 for (i, entry) in self.entries.iter().enumerate() {
618 if i > 0 {
619 buf.push(b',');
620 }
621 let entry_json = alloc::format!(
622 "{{\"name\":\"{}\",\"version\":\"{}\",\"size\":{},\"description\":\"{}\",\"\
623 license\":\"{}\"}}",
624 entry.name,
625 entry.version,
626 entry.size,
627 entry.description,
628 entry.license
629 );
630 buf.extend_from_slice(entry_json.as_bytes());
631 }
632 buf.extend_from_slice(b"]}");
633 buf
634 }
635
636 pub fn verify_signature(&self, public_key: &[u8]) -> bool {
640 if self.signature.is_empty() || public_key.is_empty() {
641 return false;
642 }
643
644 let content = self.to_bytes();
645
646 let sig = match crate::crypto::asymmetric::Signature::from_bytes(&self.signature) {
648 Ok(s) => s,
649 Err(_) => return false,
650 };
651 let vk = match crate::crypto::asymmetric::VerifyingKey::from_bytes(public_key) {
652 Ok(v) => v,
653 Err(_) => return false,
654 };
655
656 matches!(vk.verify(&content, &sig), Ok(true))
657 }
658}
659
660impl Default for RepositoryIndex {
661 fn default() -> Self {
662 Self::new()
663 }
664}
665
666#[derive(Debug, Clone, Copy, PartialEq, Eq)]
672pub enum MirrorStatus {
673 Online,
675 Offline,
677 Unknown,
679}
680
681#[derive(Debug, Clone)]
683pub struct MirrorMetadata {
684 pub url: String,
686 pub priority: u32,
688 pub region: String,
690 pub last_sync: u64,
692 pub status: MirrorStatus,
694}
695
696pub struct MirrorManager {
698 mirrors: Vec<MirrorMetadata>,
700}
701
702impl MirrorManager {
703 pub fn new() -> Self {
705 Self {
706 mirrors: Vec::new(),
707 }
708 }
709
710 pub fn add_mirror(&mut self, mirror: MirrorMetadata) {
712 self.mirrors.push(mirror);
713 self.mirrors.sort_by_key(|m| m.priority);
715 }
716
717 pub fn remove_mirror(&mut self, url: &str) -> bool {
719 if let Some(pos) = self.mirrors.iter().position(|m| m.url == url) {
720 self.mirrors.remove(pos);
721 true
722 } else {
723 false
724 }
725 }
726
727 pub fn select_best_mirror(&self) -> Option<&MirrorMetadata> {
732 self.mirrors
734 .iter()
735 .find(|m| m.status != MirrorStatus::Offline)
736 .or(self.mirrors.first())
737 }
738
739 pub fn mark_offline(&mut self, url: &str) {
741 if let Some(mirror) = self.mirrors.iter_mut().find(|m| m.url == url) {
742 mirror.status = MirrorStatus::Offline;
743 }
744 }
745
746 pub fn mark_online(&mut self, url: &str) {
748 if let Some(mirror) = self.mirrors.iter_mut().find(|m| m.url == url) {
749 mirror.status = MirrorStatus::Online;
750 mirror.last_sync = crate::arch::timer::get_timestamp_secs();
751 }
752 }
753
754 pub fn list_mirrors(&self) -> &[MirrorMetadata] {
756 &self.mirrors
757 }
758
759 pub fn mirror_count(&self) -> usize {
761 self.mirrors.len()
762 }
763}
764
765impl Default for MirrorManager {
766 fn default() -> Self {
767 Self::new()
768 }
769}
770
771pub struct RepositoryConfig {
777 repositories: Vec<RepositoryEntry>,
779}
780
781#[derive(Debug, Clone)]
783pub struct RepositoryEntry {
784 pub name: String,
786 pub url: String,
788 pub enabled: bool,
790 pub trusted: bool,
792 pub priority: u32,
794 pub mirrors: Vec<MirrorMetadata>,
796}
797
798impl RepositoryConfig {
799 pub fn new() -> Self {
801 Self {
802 repositories: Vec::new(),
803 }
804 }
805
806 pub fn add_repository(&mut self, entry: RepositoryEntry) {
808 self.repositories.push(entry);
809 self.repositories.sort_by_key(|r| r.priority);
810 }
811
812 pub fn remove_repository(&mut self, name: &str) -> bool {
814 if let Some(pos) = self.repositories.iter().position(|r| r.name == name) {
815 self.repositories.remove(pos);
816 true
817 } else {
818 false
819 }
820 }
821
822 pub fn enable_repository(&mut self, name: &str) -> bool {
824 if let Some(repo) = self.repositories.iter_mut().find(|r| r.name == name) {
825 repo.enabled = true;
826 true
827 } else {
828 false
829 }
830 }
831
832 pub fn disable_repository(&mut self, name: &str) -> bool {
834 if let Some(repo) = self.repositories.iter_mut().find(|r| r.name == name) {
835 repo.enabled = false;
836 true
837 } else {
838 false
839 }
840 }
841
842 pub fn enabled_repositories(&self) -> Vec<&RepositoryEntry> {
844 self.repositories.iter().filter(|r| r.enabled).collect()
845 }
846
847 pub fn all_repositories(&self) -> &[RepositoryEntry] {
849 &self.repositories
850 }
851
852 pub fn get_repository(&self, name: &str) -> Option<&RepositoryEntry> {
854 self.repositories.iter().find(|r| r.name == name)
855 }
856}
857
858impl Default for RepositoryConfig {
859 fn default() -> Self {
860 Self::new()
861 }
862}
863
864#[derive(Debug, Clone, Copy, PartialEq, Eq)]
870pub enum UploadPolicy {
871 Open,
873 Restricted,
876 Closed,
878}
879
880#[derive(Debug, Clone)]
882pub struct AccessControl {
883 allowed_uploaders: Vec<[u8; 32]>,
885 pub upload_policy: UploadPolicy,
887}
888
889impl AccessControl {
890 pub fn new(policy: UploadPolicy) -> Self {
892 Self {
893 allowed_uploaders: Vec::new(),
894 upload_policy: policy,
895 }
896 }
897
898 pub fn add_uploader(&mut self, key_fingerprint: [u8; 32]) {
900 if !self.allowed_uploaders.contains(&key_fingerprint) {
901 self.allowed_uploaders.push(key_fingerprint);
902 }
903 }
904
905 pub fn remove_uploader(&mut self, key_fingerprint: &[u8; 32]) -> bool {
907 if let Some(pos) = self
908 .allowed_uploaders
909 .iter()
910 .position(|fp| fp == key_fingerprint)
911 {
912 self.allowed_uploaders.remove(pos);
913 true
914 } else {
915 false
916 }
917 }
918
919 pub fn verify_upload(
924 &self,
925 package_data: &[u8],
926 signature: &[u8],
927 uploader_key: &[u8],
928 ) -> Result<(), crate::error::KernelError> {
929 use crate::error::KernelError;
930
931 match self.upload_policy {
933 UploadPolicy::Open => { }
934 UploadPolicy::Closed => {
935 return Err(KernelError::PermissionDenied {
936 operation: "upload package",
937 });
938 }
939 UploadPolicy::Restricted => {
940 let fingerprint = crate::crypto::hash::sha256(uploader_key);
941 if !self.allowed_uploaders.contains(fingerprint.as_bytes()) {
942 return Err(KernelError::PermissionDenied {
943 operation: "upload package",
944 });
945 }
946 }
947 }
948
949 let sig = crate::crypto::asymmetric::Signature::from_bytes(signature).map_err(|_| {
951 KernelError::PermissionDenied {
952 operation: "upload package",
953 }
954 })?;
955 let vk =
956 crate::crypto::asymmetric::VerifyingKey::from_bytes(uploader_key).map_err(|_| {
957 KernelError::PermissionDenied {
958 operation: "upload package",
959 }
960 })?;
961
962 match vk.verify(package_data, &sig) {
963 Ok(true) => Ok(()),
964 _ => Err(KernelError::PermissionDenied {
965 operation: "upload package",
966 }),
967 }
968 }
969}
970
971impl Default for AccessControl {
972 fn default() -> Self {
973 Self::new(UploadPolicy::Restricted)
974 }
975}
976
977#[derive(Debug, Clone, PartialEq, Eq)]
983pub enum PatternType {
984 SuspiciousPath,
986 ExcessiveCapability,
988 KnownBadHash,
990}
991
992#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
994pub enum Severity {
995 Low,
997 Medium,
999 High,
1001 Critical,
1003}
1004
1005#[derive(Debug, Clone)]
1007pub struct MalwarePattern {
1008 pub pattern_type: PatternType,
1010 pub description: String,
1012 pub severity: Severity,
1014 pub pattern: String,
1017}
1018
1019#[derive(Debug, Clone)]
1021pub struct MalwareFinding {
1022 pub severity: Severity,
1024 pub description: String,
1026 pub file_path: String,
1028 pub pattern_matched: String,
1030}
1031
1032#[derive(Debug, Clone)]
1035pub struct SecurityScanner {
1036 patterns: Vec<MalwarePattern>,
1038}
1039
1040impl SecurityScanner {
1041 pub fn new() -> Self {
1043 let mut scanner = Self {
1044 patterns: Vec::new(),
1045 };
1046 scanner.add_default_patterns();
1047 scanner
1048 }
1049
1050 pub fn add_default_patterns(&mut self) {
1052 let sensitive_paths = [
1054 "/etc/shadow",
1055 "/etc/passwd",
1056 "/dev/mem",
1057 "/dev/kmem",
1058 "/proc/kcore",
1059 ];
1060 for path in &sensitive_paths {
1061 self.patterns.push(MalwarePattern {
1062 pattern_type: PatternType::SuspiciousPath,
1063 description: alloc::format!("Access to sensitive path: {}", path),
1064 severity: Severity::High,
1065 pattern: String::from(*path),
1066 });
1067 }
1068
1069 let dangerous_caps = ["CAP_SYS_ADMIN", "CAP_NET_RAW", "CAP_SYS_PTRACE"];
1071 for cap in &dangerous_caps {
1072 self.patterns.push(MalwarePattern {
1073 pattern_type: PatternType::ExcessiveCapability,
1074 description: alloc::format!("Excessive capability request: {}", cap),
1075 severity: Severity::Medium,
1076 pattern: String::from(*cap),
1077 });
1078 }
1079
1080 let perm_patterns = ["setuid", "world-writable"];
1082 for pat in &perm_patterns {
1083 self.patterns.push(MalwarePattern {
1084 pattern_type: PatternType::SuspiciousPath,
1085 description: alloc::format!("Suspicious permission pattern: {}", pat),
1086 severity: Severity::Medium,
1087 pattern: String::from(*pat),
1088 });
1089 }
1090 }
1091
1092 pub fn add_pattern(&mut self, pattern: MalwarePattern) {
1094 self.patterns.push(pattern);
1095 }
1096
1097 pub fn scan_package_paths(&self, file_paths: &[&str]) -> Vec<MalwareFinding> {
1099 let mut findings = Vec::new();
1100 for path in file_paths {
1101 for pat in &self.patterns {
1102 if pat.pattern_type != PatternType::SuspiciousPath {
1103 continue;
1104 }
1105 if path.contains(pat.pattern.as_str()) {
1106 findings.push(MalwareFinding {
1107 severity: pat.severity,
1108 description: pat.description.clone(),
1109 file_path: String::from(*path),
1110 pattern_matched: pat.pattern.clone(),
1111 });
1112 }
1113 }
1114 }
1115 findings
1116 }
1117
1118 pub fn scan_capabilities(&self, requested_caps: &[&str]) -> Vec<MalwareFinding> {
1120 let mut findings = Vec::new();
1121 for cap in requested_caps {
1122 for pat in &self.patterns {
1123 if pat.pattern_type != PatternType::ExcessiveCapability {
1124 continue;
1125 }
1126 if *cap == pat.pattern.as_str() {
1127 findings.push(MalwareFinding {
1128 severity: pat.severity,
1129 description: pat.description.clone(),
1130 file_path: String::new(),
1131 pattern_matched: pat.pattern.clone(),
1132 });
1133 }
1134 }
1135 }
1136 findings
1137 }
1138}
1139
1140impl Default for SecurityScanner {
1141 fn default() -> Self {
1142 Self::new()
1143 }
1144}
1145
1146#[derive(Debug, Clone)]
1152pub struct VulnerabilityAdvisory {
1153 pub id: String,
1155 pub affected_packages: Vec<(String, String)>,
1157 pub severity: Severity,
1159 pub description: String,
1161 pub fixed_version: Option<String>,
1163}
1164
1165#[derive(Debug, Clone)]
1167pub struct VulnerabilityDatabase {
1168 advisories: Vec<VulnerabilityAdvisory>,
1170}
1171
1172impl VulnerabilityDatabase {
1173 pub fn new() -> Self {
1175 Self {
1176 advisories: Vec::new(),
1177 }
1178 }
1179
1180 pub fn add_advisory(&mut self, advisory: VulnerabilityAdvisory) {
1182 self.advisories.push(advisory);
1183 }
1184
1185 pub fn check_package(&self, name: &str, version: &str) -> Vec<&VulnerabilityAdvisory> {
1190 self.advisories
1191 .iter()
1192 .filter(|adv| {
1193 adv.affected_packages.iter().any(|(pkg_name, ver_range)| {
1194 pkg_name == name
1195 && (ver_range == "*"
1196 || ver_range == version
1197 || version.contains(ver_range.as_str()))
1198 })
1199 })
1200 .collect()
1201 }
1202
1203 pub fn check_installed<'a>(
1208 &'a self,
1209 installed: &'a [(String, String)],
1210 ) -> Vec<(&'a str, &'a VulnerabilityAdvisory)> {
1211 let mut results = Vec::new();
1212 for (name, version) in installed {
1213 for adv in self.check_package(name, version) {
1214 results.push((name.as_str(), adv));
1215 }
1216 }
1217 results
1218 }
1219
1220 pub fn advisory_count(&self) -> usize {
1222 self.advisories.len()
1223 }
1224
1225 pub fn critical_count(&self) -> usize {
1227 self.advisories
1228 .iter()
1229 .filter(|a| a.severity == Severity::Critical)
1230 .count()
1231 }
1232}
1233
1234impl Default for VulnerabilityDatabase {
1235 fn default() -> Self {
1236 Self::new()
1237 }
1238}
1239
1240#[cfg(test)]
1241mod tests {
1242 #[allow(unused_imports)]
1243 use alloc::vec;
1244
1245 use super::*;
1246
1247 #[test]
1250 fn test_http_response_new() {
1251 let r = HttpResponse::new();
1252 assert_eq!(r.status_code, 0);
1253 assert!(r.headers.is_empty());
1254 assert!(r.body.is_empty());
1255 }
1256
1257 #[test]
1258 fn test_http_response_is_success() {
1259 let mut r = HttpResponse::new();
1260 r.status_code = 200;
1261 assert!(r.is_success());
1262 r.status_code = 299;
1263 assert!(r.is_success());
1264 r.status_code = 300;
1265 assert!(!r.is_success());
1266 r.status_code = 199;
1267 assert!(!r.is_success());
1268 r.status_code = 404;
1269 assert!(!r.is_success());
1270 }
1271
1272 #[test]
1275 fn test_http_client_build_url_with_trailing_slash() {
1276 let client = HttpClient::new(String::from("https://example.com/"));
1277 let url = client.build_url("packages.json");
1278 assert_eq!(url, "https://example.com/packages.json");
1279 }
1280
1281 #[test]
1282 fn test_http_client_build_url_without_trailing_slash() {
1283 let client = HttpClient::new(String::from("https://example.com"));
1284 let url = client.build_url("packages.json");
1285 assert_eq!(url, "https://example.com/packages.json");
1286 }
1287
1288 #[test]
1289 fn test_http_client_build_url_path_with_leading_slash() {
1290 let client = HttpClient::new(String::from("https://example.com"));
1291 let url = client.build_url("/packages.json");
1292 assert_eq!(url, "https://example.com/packages.json");
1293 }
1294
1295 #[test]
1296 fn test_http_client_parse_url_https() {
1297 let client = HttpClient::new(String::from("https://example.com"));
1298 let (host, port, path) = client.parse_url("https://example.com/path").unwrap();
1299 assert_eq!(host, "example.com");
1300 assert_eq!(port, 443);
1301 assert_eq!(path, "/path");
1302 }
1303
1304 #[test]
1305 fn test_http_client_parse_url_http() {
1306 let client = HttpClient::new(String::from("http://example.com"));
1307 let (host, port, path) = client.parse_url("http://example.com/path").unwrap();
1308 assert_eq!(host, "example.com");
1309 assert_eq!(port, 443); assert_eq!(path, "/path");
1311 }
1312
1313 #[test]
1314 fn test_http_client_parse_url_with_port() {
1315 let client = HttpClient::new(String::from(""));
1316 let (host, port, path) = client.parse_url("https://example.com:8080/api").unwrap();
1317 assert_eq!(host, "example.com");
1318 assert_eq!(port, 8080);
1319 assert_eq!(path, "/api");
1320 }
1321
1322 #[test]
1323 fn test_http_client_parse_url_no_path() {
1324 let client = HttpClient::new(String::from(""));
1325 let (host, port, path) = client.parse_url("https://example.com").unwrap();
1326 assert_eq!(host, "example.com");
1327 assert_eq!(port, 443);
1328 assert_eq!(path, "/");
1329 }
1330
1331 #[test]
1332 fn test_http_client_build_request_get() {
1333 let client = HttpClient::new(String::from(""));
1334 let req = client.build_request(HttpMethod::Get, "example.com", "/index.html");
1335 let req_str = core::str::from_utf8(&req).unwrap();
1336 assert!(req_str.starts_with("GET /index.html HTTP/1.1\r\n"));
1337 assert!(req_str.contains("Host: example.com"));
1338 }
1339
1340 #[test]
1341 fn test_http_client_build_request_head() {
1342 let client = HttpClient::new(String::from(""));
1343 let req = client.build_request(HttpMethod::Head, "example.com", "/");
1344 let req_str = core::str::from_utf8(&req).unwrap();
1345 assert!(req_str.starts_with("HEAD / HTTP/1.1\r\n"));
1346 }
1347
1348 #[test]
1349 fn test_http_client_resolve_hostname_ip() {
1350 let client = HttpClient::new(String::from(""));
1351 let ip = client.resolve_hostname("192.168.1.10").unwrap();
1352 assert_eq!(ip, [192, 168, 1, 10]);
1353 }
1354
1355 #[test]
1356 fn test_http_client_resolve_hostname_fallback() {
1357 let client = HttpClient::new(String::from(""));
1358 let ip = client.resolve_hostname("example.com").unwrap();
1359 assert_eq!(ip, [127, 0, 0, 1]);
1360 }
1361
1362 #[test]
1363 fn test_http_client_find_header_end() {
1364 let client = HttpClient::new(String::from(""));
1365 let data = b"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\nbody";
1366 let pos = client.find_header_end(data);
1367 assert!(pos.is_some());
1368 let p = pos.unwrap();
1370 assert_eq!(&data[p..p + 4], b"\r\n\r\n");
1371 }
1372
1373 #[test]
1374 fn test_http_client_find_header_end_missing() {
1375 let client = HttpClient::new(String::from(""));
1376 let data = b"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n";
1377 assert!(client.find_header_end(data).is_none());
1378 }
1379
1380 #[test]
1381 fn test_http_client_parse_response() {
1382 let client = HttpClient::new(String::from(""));
1383 let raw = b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello world";
1384 let response = client.parse_response(raw).unwrap();
1385 assert_eq!(response.status_code, 200);
1386 assert_eq!(response.headers.len(), 1);
1387 assert_eq!(response.headers[0].0, "Content-Type");
1388 assert_eq!(response.headers[0].1, "text/plain");
1389 assert_eq!(response.body, b"hello world");
1390 }
1391
1392 #[test]
1395 fn test_repository_new() {
1396 let repo = Repository::new(String::from("test"), String::from("https://test.org"), true);
1397 assert_eq!(repo.name, "test");
1398 assert_eq!(repo.url, "https://test.org");
1399 assert!(repo.trusted);
1400 assert!(repo.package_cache.is_empty());
1401 assert_eq!(repo.last_updated, 0);
1402 }
1403
1404 #[test]
1405 fn test_repository_default() {
1406 let repo = Repository::default();
1407 assert_eq!(repo.name, "default");
1408 assert!(repo.trusted);
1409 }
1410
1411 #[test]
1412 fn test_repository_has_package_empty() {
1413 let repo = Repository::default();
1414 assert!(!repo.has_package(&String::from("nonexistent")));
1415 }
1416
1417 #[test]
1418 fn test_repository_get_package_none() {
1419 let repo = Repository::default();
1420 assert!(repo.get_package(&String::from("nonexistent")).is_none());
1421 }
1422
1423 #[test]
1424 fn test_repository_invalidate_cache() {
1425 let mut repo = Repository::default();
1426 repo.last_updated = 1000;
1427 repo.invalidate_cache();
1428 assert!(repo.package_cache.is_empty());
1429 assert_eq!(repo.last_updated, 0);
1430 }
1431
1432 #[test]
1433 fn test_repository_is_cache_stale() {
1434 let repo = Repository::default();
1435 assert!(repo.is_cache_stale(3601));
1437 assert!(!repo.is_cache_stale(3600));
1439 assert!(!repo.is_cache_stale(100));
1441 }
1442
1443 #[test]
1446 fn test_repository_extract_json_string() {
1447 let repo = Repository::default();
1448 let json = r#"{"name":"curl","version":"8.5.0"}"#;
1449 assert_eq!(
1450 repo.extract_json_string(json, "name"),
1451 Some(String::from("curl"))
1452 );
1453 assert_eq!(
1454 repo.extract_json_string(json, "version"),
1455 Some(String::from("8.5.0"))
1456 );
1457 assert_eq!(repo.extract_json_string(json, "missing"), None);
1458 }
1459
1460 #[test]
1461 fn test_repository_parse_version() {
1462 let repo = Repository::default();
1463 let v = repo.parse_version("1.2.3").unwrap();
1464 assert_eq!(v, Version::new(1, 2, 3));
1465 }
1466
1467 #[test]
1468 fn test_repository_parse_version_too_short() {
1469 let repo = Repository::default();
1470 assert!(repo.parse_version("1.2").is_none());
1471 }
1472
1473 #[test]
1474 fn test_repository_parse_version_invalid() {
1475 let repo = Repository::default();
1476 assert!(repo.parse_version("abc").is_none());
1477 }
1478
1479 #[test]
1480 fn test_repository_extract_conflicts() {
1481 let repo = Repository::default();
1482 let json = r#"{"conflicts":["pkg-b","pkg-c"]}"#;
1483 let conflicts = repo.extract_conflicts(json);
1484 assert_eq!(conflicts.len(), 2);
1485 assert_eq!(conflicts[0], "pkg-b");
1486 assert_eq!(conflicts[1], "pkg-c");
1487 }
1488
1489 #[test]
1490 fn test_repository_extract_conflicts_empty() {
1491 let repo = Repository::default();
1492 let json = r#"{"name":"test"}"#;
1493 let conflicts = repo.extract_conflicts(json);
1494 assert!(conflicts.is_empty());
1495 }
1496
1497 #[test]
1498 fn test_repository_extract_dependencies() {
1499 let repo = Repository::default();
1500 let json = r#"{"dependencies":[{"name":"openssl","version":"1.0.0"}]}"#;
1501 let deps = repo.extract_dependencies(json);
1502 assert_eq!(deps.len(), 1);
1503 assert_eq!(deps[0].name, "openssl");
1504 assert_eq!(deps[0].version_req, "1.0.0");
1505 }
1506
1507 #[test]
1508 fn test_repository_parse_package_index_empty_array() {
1509 let repo = Repository::default();
1510 let data = b"[]";
1511 let pkgs = repo.parse_package_index(data);
1512 assert!(pkgs.is_empty());
1513 }
1514
1515 #[test]
1516 fn test_repository_parse_package_index_not_array() {
1517 let repo = Repository::default();
1518 let data = b"{}";
1519 let pkgs = repo.parse_package_index(data);
1520 assert!(pkgs.is_empty());
1521 }
1522
1523 #[test]
1524 fn test_repository_parse_package_index_invalid_utf8() {
1525 let repo = Repository::default();
1526 let data: &[u8] = &[0xFF, 0xFE];
1527 let pkgs = repo.parse_package_index(data);
1528 assert!(pkgs.is_empty());
1529 }
1530
1531 #[test]
1534 fn test_repository_index_generate() {
1535 let pkgs = vec![PackageMetadata {
1536 name: String::from("test-pkg"),
1537 version: Version::new(1, 0, 0),
1538 author: String::from("author"),
1539 description: String::from("desc"),
1540 license: String::from("MIT"),
1541 dependencies: vec![],
1542 conflicts: vec![],
1543 }];
1544 let index = RepositoryIndex::generate(&pkgs);
1545 assert_eq!(index.entries.len(), 1);
1546 assert_eq!(index.entries[0].name, "test-pkg");
1547 assert_eq!(index.entries[0].version, "1.0.0");
1548 assert_eq!(index.entries[0].description, "desc");
1549 assert_eq!(index.entries[0].license, "MIT");
1550 }
1551
1552 #[test]
1553 fn test_repository_index_to_bytes() {
1554 let pkgs = vec![PackageMetadata {
1555 name: String::from("a"),
1556 version: Version::new(0, 1, 0),
1557 author: String::new(),
1558 description: String::from("d"),
1559 license: String::from("MIT"),
1560 dependencies: vec![],
1561 conflicts: vec![],
1562 }];
1563 let index = RepositoryIndex::generate(&pkgs);
1564 let bytes = index.to_bytes();
1565 let s = core::str::from_utf8(&bytes).unwrap();
1566 assert!(s.contains("\"name\":\"a\""));
1567 assert!(s.contains("\"version\":\"0.1.0\""));
1568 assert!(s.contains("\"license\":\"MIT\""));
1569 }
1570
1571 #[test]
1572 fn test_repository_index_verify_signature_empty() {
1573 let index = RepositoryIndex::generate(&[]);
1574 assert!(!index.verify_signature(&[1, 2, 3]));
1576 let mut index2 = RepositoryIndex::generate(&[]);
1578 index2.signature = vec![1, 2, 3];
1579 assert!(!index2.verify_signature(&[]));
1580 }
1581
1582 #[test]
1585 fn test_mirror_manager_new() {
1586 let mm = MirrorManager::new();
1587 assert_eq!(mm.mirror_count(), 0);
1588 assert!(mm.list_mirrors().is_empty());
1589 }
1590
1591 #[test]
1592 fn test_mirror_manager_add_and_sort() {
1593 let mut mm = MirrorManager::new();
1594 mm.add_mirror(MirrorMetadata {
1595 url: String::from("https://mirror2.com"),
1596 priority: 20,
1597 region: String::from("us"),
1598 last_sync: 0,
1599 status: MirrorStatus::Unknown,
1600 });
1601 mm.add_mirror(MirrorMetadata {
1602 url: String::from("https://mirror1.com"),
1603 priority: 10,
1604 region: String::from("eu"),
1605 last_sync: 0,
1606 status: MirrorStatus::Unknown,
1607 });
1608 assert_eq!(mm.mirror_count(), 2);
1609 assert_eq!(mm.list_mirrors()[0].url, "https://mirror1.com");
1611 assert_eq!(mm.list_mirrors()[1].url, "https://mirror2.com");
1612 }
1613
1614 #[test]
1615 fn test_mirror_manager_remove() {
1616 let mut mm = MirrorManager::new();
1617 mm.add_mirror(MirrorMetadata {
1618 url: String::from("https://mirror1.com"),
1619 priority: 10,
1620 region: String::new(),
1621 last_sync: 0,
1622 status: MirrorStatus::Online,
1623 });
1624 assert!(mm.remove_mirror("https://mirror1.com"));
1625 assert!(!mm.remove_mirror("https://nonexistent.com"));
1626 assert_eq!(mm.mirror_count(), 0);
1627 }
1628
1629 #[test]
1630 fn test_mirror_manager_select_best() {
1631 let mut mm = MirrorManager::new();
1632 mm.add_mirror(MirrorMetadata {
1633 url: String::from("https://mirror1.com"),
1634 priority: 10,
1635 region: String::new(),
1636 last_sync: 0,
1637 status: MirrorStatus::Offline,
1638 });
1639 mm.add_mirror(MirrorMetadata {
1640 url: String::from("https://mirror2.com"),
1641 priority: 20,
1642 region: String::new(),
1643 last_sync: 0,
1644 status: MirrorStatus::Online,
1645 });
1646 let best = mm.select_best_mirror().unwrap();
1648 assert_eq!(best.url, "https://mirror2.com");
1649 }
1650
1651 #[test]
1652 fn test_mirror_manager_select_best_all_offline() {
1653 let mut mm = MirrorManager::new();
1654 mm.add_mirror(MirrorMetadata {
1655 url: String::from("https://mirror1.com"),
1656 priority: 10,
1657 region: String::new(),
1658 last_sync: 0,
1659 status: MirrorStatus::Offline,
1660 });
1661 let best = mm.select_best_mirror().unwrap();
1663 assert_eq!(best.url, "https://mirror1.com");
1664 }
1665
1666 #[test]
1667 fn test_mirror_manager_select_best_empty() {
1668 let mm = MirrorManager::new();
1669 assert!(mm.select_best_mirror().is_none());
1670 }
1671
1672 #[test]
1673 fn test_mirror_manager_mark_offline() {
1674 let mut mm = MirrorManager::new();
1675 mm.add_mirror(MirrorMetadata {
1676 url: String::from("https://mirror1.com"),
1677 priority: 10,
1678 region: String::new(),
1679 last_sync: 0,
1680 status: MirrorStatus::Online,
1681 });
1682 mm.mark_offline("https://mirror1.com");
1683 assert_eq!(mm.list_mirrors()[0].status, MirrorStatus::Offline);
1684 }
1685
1686 #[test]
1689 fn test_repo_config_new() {
1690 let cfg = RepositoryConfig::new();
1691 assert!(cfg.all_repositories().is_empty());
1692 assert!(cfg.enabled_repositories().is_empty());
1693 }
1694
1695 #[test]
1696 fn test_repo_config_add_and_sort() {
1697 let mut cfg = RepositoryConfig::new();
1698 cfg.add_repository(RepositoryEntry {
1699 name: String::from("b"),
1700 url: String::from("https://b.org"),
1701 enabled: true,
1702 trusted: true,
1703 priority: 20,
1704 mirrors: vec![],
1705 });
1706 cfg.add_repository(RepositoryEntry {
1707 name: String::from("a"),
1708 url: String::from("https://a.org"),
1709 enabled: true,
1710 trusted: false,
1711 priority: 10,
1712 mirrors: vec![],
1713 });
1714 assert_eq!(cfg.all_repositories().len(), 2);
1715 assert_eq!(cfg.all_repositories()[0].name, "a");
1716 }
1717
1718 #[test]
1719 fn test_repo_config_remove() {
1720 let mut cfg = RepositoryConfig::new();
1721 cfg.add_repository(RepositoryEntry {
1722 name: String::from("test"),
1723 url: String::from("https://test.org"),
1724 enabled: true,
1725 trusted: true,
1726 priority: 10,
1727 mirrors: vec![],
1728 });
1729 assert!(cfg.remove_repository("test"));
1730 assert!(!cfg.remove_repository("test"));
1731 assert!(cfg.all_repositories().is_empty());
1732 }
1733
1734 #[test]
1735 fn test_repo_config_enable_disable() {
1736 let mut cfg = RepositoryConfig::new();
1737 cfg.add_repository(RepositoryEntry {
1738 name: String::from("test"),
1739 url: String::from("https://test.org"),
1740 enabled: false,
1741 trusted: true,
1742 priority: 10,
1743 mirrors: vec![],
1744 });
1745 assert!(cfg.enabled_repositories().is_empty());
1746 assert!(cfg.enable_repository("test"));
1747 assert_eq!(cfg.enabled_repositories().len(), 1);
1748 assert!(cfg.disable_repository("test"));
1749 assert!(cfg.enabled_repositories().is_empty());
1750 assert!(!cfg.enable_repository("nonexistent"));
1751 assert!(!cfg.disable_repository("nonexistent"));
1752 }
1753
1754 #[test]
1755 fn test_repo_config_get_repository() {
1756 let mut cfg = RepositoryConfig::new();
1757 cfg.add_repository(RepositoryEntry {
1758 name: String::from("test"),
1759 url: String::from("https://test.org"),
1760 enabled: true,
1761 trusted: true,
1762 priority: 10,
1763 mirrors: vec![],
1764 });
1765 assert!(cfg.get_repository("test").is_some());
1766 assert!(cfg.get_repository("other").is_none());
1767 }
1768
1769 #[test]
1772 fn test_access_control_new() {
1773 let ac = AccessControl::new(UploadPolicy::Open);
1774 assert_eq!(ac.upload_policy, UploadPolicy::Open);
1775 }
1776
1777 #[test]
1778 fn test_access_control_add_remove_uploader() {
1779 let mut ac = AccessControl::new(UploadPolicy::Restricted);
1780 let fp = [0u8; 32];
1781 ac.add_uploader(fp);
1782 ac.add_uploader(fp);
1784 assert!(ac.remove_uploader(&fp));
1785 assert!(!ac.remove_uploader(&fp));
1786 }
1787
1788 #[test]
1789 fn test_access_control_default() {
1790 let ac = AccessControl::default();
1791 assert_eq!(ac.upload_policy, UploadPolicy::Restricted);
1792 }
1793
1794 #[test]
1797 fn test_security_scanner_default_patterns() {
1798 let scanner = SecurityScanner::new();
1799 assert!(!scanner.patterns.is_empty());
1801 }
1802
1803 #[test]
1804 fn test_security_scanner_scan_paths_match() {
1805 let scanner = SecurityScanner::new();
1806 let paths = &["/etc/shadow"];
1807 let findings = scanner.scan_package_paths(paths);
1808 assert!(!findings.is_empty());
1809 assert_eq!(findings[0].severity, Severity::High);
1810 }
1811
1812 #[test]
1813 fn test_security_scanner_scan_paths_no_match() {
1814 let scanner = SecurityScanner::new();
1815 let paths = &["/usr/bin/hello"];
1816 let findings = scanner.scan_package_paths(paths);
1817 assert!(findings.is_empty());
1818 }
1819
1820 #[test]
1821 fn test_security_scanner_scan_capabilities() {
1822 let scanner = SecurityScanner::new();
1823 let caps = &["CAP_SYS_ADMIN"];
1824 let findings = scanner.scan_capabilities(caps);
1825 assert!(!findings.is_empty());
1826 assert_eq!(findings[0].severity, Severity::Medium);
1827 }
1828
1829 #[test]
1830 fn test_security_scanner_scan_capabilities_no_match() {
1831 let scanner = SecurityScanner::new();
1832 let caps = &["CAP_READ"];
1833 let findings = scanner.scan_capabilities(caps);
1834 assert!(findings.is_empty());
1835 }
1836
1837 #[test]
1838 fn test_security_scanner_add_pattern() {
1839 let mut scanner = SecurityScanner::new();
1840 let initial_count = scanner.patterns.len();
1841 scanner.add_pattern(MalwarePattern {
1842 pattern_type: PatternType::KnownBadHash,
1843 description: String::from("bad hash"),
1844 severity: Severity::Critical,
1845 pattern: String::from("deadbeef"),
1846 });
1847 assert_eq!(scanner.patterns.len(), initial_count + 1);
1848 }
1849
1850 #[test]
1853 fn test_severity_ordering() {
1854 assert!(Severity::Low < Severity::Medium);
1855 assert!(Severity::Medium < Severity::High);
1856 assert!(Severity::High < Severity::Critical);
1857 }
1858
1859 #[test]
1862 fn test_vuln_db_new() {
1863 let db = VulnerabilityDatabase::new();
1864 assert_eq!(db.advisory_count(), 0);
1865 assert_eq!(db.critical_count(), 0);
1866 }
1867
1868 #[test]
1869 fn test_vuln_db_add_and_check() {
1870 let mut db = VulnerabilityDatabase::new();
1871 db.add_advisory(VulnerabilityAdvisory {
1872 id: String::from("CVE-2024-0001"),
1873 affected_packages: vec![(String::from("openssl"), String::from("1.0.0"))],
1874 severity: Severity::Critical,
1875 description: String::from("buffer overflow"),
1876 fixed_version: Some(String::from("1.0.1")),
1877 });
1878 assert_eq!(db.advisory_count(), 1);
1879 assert_eq!(db.critical_count(), 1);
1880
1881 let results = db.check_package("openssl", "1.0.0");
1882 assert_eq!(results.len(), 1);
1883 assert_eq!(results[0].id, "CVE-2024-0001");
1884
1885 let results = db.check_package("openssl", "1.0.1");
1886 assert!(results.is_empty());
1887 }
1888
1889 #[test]
1890 fn test_vuln_db_wildcard() {
1891 let mut db = VulnerabilityDatabase::new();
1892 db.add_advisory(VulnerabilityAdvisory {
1893 id: String::from("CVE-2024-0002"),
1894 affected_packages: vec![(String::from("curl"), String::from("*"))],
1895 severity: Severity::High,
1896 description: String::from("all versions"),
1897 fixed_version: None,
1898 });
1899 let results = db.check_package("curl", "999.0.0");
1900 assert_eq!(results.len(), 1);
1901 }
1902
1903 #[test]
1904 fn test_vuln_db_check_installed() {
1905 let mut db = VulnerabilityDatabase::new();
1906 db.add_advisory(VulnerabilityAdvisory {
1907 id: String::from("CVE-2024-0003"),
1908 affected_packages: vec![(String::from("pkg-a"), String::from("1.0.0"))],
1909 severity: Severity::Medium,
1910 description: String::from("test"),
1911 fixed_version: None,
1912 });
1913 let installed = vec![
1914 (String::from("pkg-a"), String::from("1.0.0")),
1915 (String::from("pkg-b"), String::from("2.0.0")),
1916 ];
1917 let results = db.check_installed(&installed);
1918 assert_eq!(results.len(), 1);
1919 assert_eq!(results[0].0, "pkg-a");
1920 }
1921}