dcbor_pattern/pattern/value/
digest_pattern.rs1use bc_components::{Digest, tags};
2use bc_ur::UREncodable;
3use dcbor::prelude::*;
4
5use crate::pattern::{Matcher, Path, Pattern, vm::Instr};
6
7#[derive(Debug, Clone)]
9pub enum DigestPattern {
10 Any,
12 Digest(Digest),
14 Prefix(Vec<u8>),
16 BinaryRegex(regex::bytes::Regex),
18}
19
20impl PartialEq for DigestPattern {
21 fn eq(&self, other: &Self) -> bool {
22 match (self, other) {
23 (DigestPattern::Any, DigestPattern::Any) => true,
24 (DigestPattern::Digest(a), DigestPattern::Digest(b)) => a == b,
25 (DigestPattern::Prefix(a), DigestPattern::Prefix(b)) => {
26 a.eq_ignore_ascii_case(b)
27 }
28 (DigestPattern::BinaryRegex(a), DigestPattern::BinaryRegex(b)) => {
29 a.as_str() == b.as_str()
30 }
31 _ => false,
32 }
33 }
34}
35
36impl Eq for DigestPattern {}
37
38impl std::hash::Hash for DigestPattern {
39 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
40 match self {
41 DigestPattern::Any => {
42 0u8.hash(state);
43 }
44 DigestPattern::Digest(a) => {
45 1u8.hash(state);
46 a.hash(state);
47 }
48 DigestPattern::Prefix(prefix) => {
49 2u8.hash(state);
50 prefix.hash(state);
51 }
52 DigestPattern::BinaryRegex(regex) => {
53 3u8.hash(state);
54 regex.as_str().hash(state);
56 }
57 }
58 }
59}
60
61impl DigestPattern {
62 pub fn any() -> Self { DigestPattern::Any }
64
65 pub fn digest(digest: Digest) -> Self { DigestPattern::Digest(digest) }
67
68 pub fn prefix(prefix: impl AsRef<[u8]>) -> Self {
70 DigestPattern::Prefix(prefix.as_ref().to_vec())
71 }
72
73 pub fn binary_regex(regex: regex::bytes::Regex) -> Self {
76 DigestPattern::BinaryRegex(regex)
77 }
78}
79
80impl Matcher for DigestPattern {
81 fn paths(&self, haystack: &CBOR) -> Vec<Path> {
82 if let CBORCase::Tagged(tag, content) = haystack.as_case() {
84 if tag.value() == tags::TAG_DIGEST {
85 match CBOR::try_into_byte_string(content.clone()) {
87 Ok(digest_bytes) => {
88 if digest_bytes.len() == Digest::DIGEST_SIZE {
89 let is_hit = match self {
90 DigestPattern::Any => true,
91 DigestPattern::Digest(pattern_digest) => {
92 digest_bytes == pattern_digest.data()
93 }
94 DigestPattern::Prefix(prefix) => {
95 digest_bytes.starts_with(prefix)
96 }
97 DigestPattern::BinaryRegex(regex) => {
98 regex.is_match(&digest_bytes)
99 }
100 };
101
102 if is_hit {
103 return vec![vec![haystack.clone()]];
104 }
105 }
106 }
107 Err(_) => {
108 }
110 }
111 }
112 }
113
114 vec![]
115 }
116
117 fn compile(
118 &self,
119 code: &mut Vec<Instr>,
120 literals: &mut Vec<Pattern>,
121 _captures: &mut Vec<String>,
122 ) {
123 let idx = literals.len();
124 literals.push(Pattern::Value(crate::pattern::ValuePattern::Digest(
125 self.clone(),
126 )));
127 code.push(Instr::MatchPredicate(idx));
128 }
129}
130
131impl std::fmt::Display for DigestPattern {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 match self {
134 DigestPattern::Any => write!(f, "digest"),
135 DigestPattern::Digest(digest) => {
136 write!(f, "digest'{}'", digest.ur_string())
137 }
138 DigestPattern::Prefix(prefix) => {
139 write!(f, "digest'{}'", hex::encode(prefix))
140 }
141 DigestPattern::BinaryRegex(regex) => {
142 write!(f, "digest'/{}/'", regex.as_str())
143 }
144 }
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use bc_components::{Digest, DigestProvider};
151 use bc_ur::UREncodable;
152
153 use super::*;
154
155 fn test_digest() -> Digest {
156 let data: &[u8] = b"test data";
157 data.digest().into_owned()
158 }
159
160 fn create_digest_cbor(digest: &Digest) -> CBOR { digest.to_cbor() }
161
162 #[test]
163 fn test_digest_pattern_display() {
164 bc_components::register_tags();
165
166 let digest = test_digest();
167 let pattern = DigestPattern::digest(digest.clone());
168 assert_eq!(
169 format!("{}", pattern),
170 format!("digest'{}'", digest.ur_string())
171 );
172
173 let prefix = vec![0x74, 0x65, 0x73]; let pattern = DigestPattern::prefix(prefix.clone());
175 assert_eq!(
176 format!("{}", pattern),
177 format!("digest'{}'", hex::encode(&prefix))
178 );
179
180 let regex = regex::bytes::Regex::new(r"^te.*").unwrap();
181 let pattern = DigestPattern::binary_regex(regex.clone());
182 assert_eq!(format!("{}", pattern), format!("digest'/{}/'", regex));
183 }
184
185 #[test]
186 fn test_digest_pattern_exact_match() {
187 let digest = test_digest();
188 let digest_cbor = create_digest_cbor(&digest);
189 let pattern = DigestPattern::digest(digest.clone());
190
191 assert!(pattern.matches(&digest_cbor));
192 assert_eq!(
193 pattern.paths(&digest_cbor),
194 vec![vec![digest_cbor.clone()]]
195 );
196
197 let other_data: &[u8] = b"other data";
199 let other_digest = other_data.digest().into_owned();
200 let other_digest_cbor = create_digest_cbor(&other_digest);
201 assert!(!pattern.matches(&other_digest_cbor));
202 assert!(pattern.paths(&other_digest_cbor).is_empty());
203 }
204
205 #[test]
206 fn test_digest_pattern_prefix_match() {
207 let digest = test_digest();
208 let digest_cbor = create_digest_cbor(&digest);
209
210 let prefix = digest.data()[..4].to_vec();
212 let pattern = DigestPattern::prefix(prefix);
213
214 assert!(pattern.matches(&digest_cbor));
215 assert_eq!(
216 pattern.paths(&digest_cbor),
217 vec![vec![digest_cbor.clone()]]
218 );
219
220 let other_data: &[u8] = b"completely different data";
222 let other_digest = other_data.digest().into_owned();
223 let other_digest_cbor = create_digest_cbor(&other_digest);
224
225 let matches = pattern.matches(&other_digest_cbor);
228 assert_eq!(
229 matches,
230 other_digest.data().starts_with(&digest.data()[..4])
231 );
232 }
233
234 #[test]
235 fn test_digest_pattern_regex_match() {
236 let digest = test_digest();
237 let digest_cbor = create_digest_cbor(&digest);
238
239 let match_all_regex = regex::bytes::Regex::new(r".*").unwrap();
241 let pattern = DigestPattern::binary_regex(match_all_regex);
242
243 assert!(pattern.matches(&digest_cbor));
244 assert_eq!(
245 pattern.paths(&digest_cbor),
246 vec![vec![digest_cbor.clone()]]
247 );
248
249 let no_match_regex =
252 regex::bytes::Regex::new(r"^\xFF\xFF\xFF\xFF").unwrap();
253 let no_match_pattern = DigestPattern::binary_regex(no_match_regex);
254
255 let matches = no_match_pattern.matches(&digest_cbor);
258 assert_eq!(
259 matches,
260 digest.data().starts_with(&[0xFF, 0xFF, 0xFF, 0xFF])
261 );
262 }
263
264 #[test]
265 fn test_digest_pattern_non_digest_cbor() {
266 let pattern = DigestPattern::digest(test_digest());
267
268 let text_cbor = "hello".to_cbor();
270 let number_cbor = 42.to_cbor();
271 let array_cbor = vec![1, 2, 3].to_cbor();
272
273 assert!(!pattern.matches(&text_cbor));
274 assert!(!pattern.matches(&number_cbor));
275 assert!(!pattern.matches(&array_cbor));
276
277 assert!(pattern.paths(&text_cbor).is_empty());
278 assert!(pattern.paths(&number_cbor).is_empty());
279 assert!(pattern.paths(&array_cbor).is_empty());
280 }
281
282 #[test]
283 fn test_digest_pattern_equality() {
284 let digest1 = test_digest();
285 let digest2 = test_digest();
286 assert_eq!(digest1, digest2); let pattern1 = DigestPattern::digest(digest1);
289 let pattern2 = DigestPattern::digest(digest2);
290 assert_eq!(pattern1, pattern2);
291
292 let prefix = vec![0x12, 0x34];
293 let prefix_pattern1 = DigestPattern::prefix(&prefix);
294 let prefix_pattern2 = DigestPattern::prefix(&prefix);
295 assert_eq!(prefix_pattern1, prefix_pattern2);
296
297 let regex = regex::bytes::Regex::new(r"^test").unwrap();
298 let regex_pattern1 = DigestPattern::binary_regex(regex.clone());
299 let regex_pattern2 = DigestPattern::binary_regex(regex);
300 assert_eq!(regex_pattern1, regex_pattern2);
301 }
302}