1#![deny(unsafe_code)]
2#![warn(missing_docs)]
3pub mod classify;
6pub mod extract;
7pub mod from_file;
8pub mod regex_classifier;
9pub mod yara_classifier;
10pub mod yara_scanner;
11
12#[derive(Debug, Clone)]
14pub struct ClassifiedString {
15 pub value: String,
17 pub physical_offset: u64,
19 pub encoding: StringEncoding,
21 pub categories: Vec<(StringCategory, f32)>,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum StringEncoding {
28 Ascii,
30 Utf8,
32 Utf16Le,
34}
35
36#[derive(Debug, Clone, PartialEq)]
38pub enum StringCategory {
39 Url,
41 IpV4,
43 IpV6,
45 Email,
47 UnixPath,
49 WindowsPath,
51 RegistryKey,
53 DomainName,
55 CryptoAddress,
57 PrivateKey,
59 Base64Blob,
61 ShellCommand,
63 YaraMatch(String),
65}
66
67#[derive(Debug, thiserror::Error)]
69pub enum Error {
70 #[error("I/O error: {0}")]
72 Io(#[from] std::io::Error),
73
74 #[error("format error: {0}")]
76 Format(#[from] memf_format::Error),
77
78 #[error("YARA error: {0}")]
80 Yara(String),
81}
82
83pub type Result<T> = std::result::Result<T, Error>;
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn classified_string_basic() {
92 let cs = ClassifiedString {
93 value: "https://example.com".into(),
94 physical_offset: 0x1234,
95 encoding: StringEncoding::Ascii,
96 categories: vec![(StringCategory::Url, 0.95)],
97 };
98 assert_eq!(cs.value, "https://example.com");
99 assert_eq!(cs.physical_offset, 0x1234);
100 assert_eq!(cs.categories.len(), 1);
101 }
102
103 #[test]
104 fn error_from_io() {
105 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "missing");
106 let err: Error = Error::from(io_err);
107 assert!(matches!(err, Error::Io(_)));
108 assert!(err.to_string().contains("missing"));
109 }
110
111 #[test]
112 fn error_from_format() {
113 let fmt_err = memf_format::Error::UnknownFormat;
114 let err: Error = Error::from(fmt_err);
115 assert!(matches!(err, Error::Format(_)));
116 assert!(err.to_string().contains("unknown dump format"));
117 }
118
119 #[test]
120 fn error_yara_display() {
121 let err = Error::Yara("compilation failed".into());
122 assert!(err.to_string().contains("compilation failed"));
123 }
124
125 #[test]
126 fn string_encoding_variants() {
127 assert_ne!(StringEncoding::Ascii, StringEncoding::Utf16Le);
128 assert_ne!(StringEncoding::Ascii, StringEncoding::Utf8);
129 assert_ne!(StringEncoding::Utf8, StringEncoding::Utf16Le);
130 }
131
132 #[test]
133 fn string_category_equality() {
134 assert_eq!(StringCategory::Url, StringCategory::Url);
135 assert_ne!(StringCategory::Url, StringCategory::Email);
136 assert_eq!(
137 StringCategory::YaraMatch("test".into()),
138 StringCategory::YaraMatch("test".into())
139 );
140 assert_ne!(
141 StringCategory::YaraMatch("a".into()),
142 StringCategory::YaraMatch("b".into())
143 );
144 }
145}