ic_http_certification/tree/
certification_tree.rs

1use super::{
2    certification_tree_entry::HttpCertificationTreeEntry,
3    certification_tree_path::CertificationTreePathSegment,
4};
5use crate::{
6    tree::HttpCertificationPathType,
7    utils::{more_specific_wildcards_for, PATH_PREFIX_BYTES},
8    HttpCertificationError, HttpCertificationPath, HttpCertificationResult,
9};
10use ic_certification::{labeled, labeled_hash, merge_hash_trees, AsHashTree, HashTree, NestedTree};
11use ic_representation_independent_hash::Sha256Digest;
12use std::fmt::{Debug, Formatter};
13
14type CertificationTree = NestedTree<CertificationTreePathSegment, Vec<u8>>;
15
16/// A certification tree for generic HTTP requests.
17#[derive(Clone)]
18pub struct HttpCertificationTree {
19    tree: CertificationTree,
20}
21
22impl Debug for HttpCertificationTree {
23    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
24        write!(f, "tree: {:#?}", self.tree)
25    }
26}
27
28impl Default for HttpCertificationTree {
29    fn default() -> Self {
30        Self::new(CertificationTree::default())
31    }
32}
33
34impl HttpCertificationTree {
35    /// Creates a new empty [HttpCertificationTree] from a given [CertificationTree](ic_certification::NestedTree).
36    /// The [default](HttpCertificationTree::default) implementation should be used in most cases.
37    pub fn new(tree: CertificationTree) -> Self {
38        Self { tree }
39    }
40
41    /// Returns the root hash of the tree.
42    /// This hash can be used as the canister's certified variable.
43    pub fn root_hash(&self) -> Sha256Digest {
44        labeled_hash(PATH_PREFIX_BYTES, &self.tree.root_hash())
45    }
46
47    /// Inserts a given [HttpCertificationTreeEntry] into the tree.
48    /// After performing this operation, the canister's certified variable will need to be updated
49    /// with the new [root hash](HttpCertificationTree::root_hash) of the tree.
50    pub fn insert(&mut self, entry: &HttpCertificationTreeEntry) {
51        let tree_path = entry.to_tree_path();
52        self.tree.insert(&tree_path, vec![]);
53    }
54
55    /// Deletes a given [HttpCertificationTreeEntry] from the tree.
56    /// After performing this operation, the canister's certified variable will need to be updated
57    /// with the new [root hash](HttpCertificationTree::root_hash) of the tree.
58    pub fn delete(&mut self, entry: &HttpCertificationTreeEntry) {
59        let tree_path = entry.to_tree_path();
60        self.tree.delete(&tree_path);
61    }
62
63    /// Deletes all [HttpCertificationTreeEntry]s that match a given [HttpCertificationPath].
64    /// After performing this operation, the canister's certified variable will need to be updated
65    /// with the new [root hash](HttpCertificationTree::root_hash) of the tree.
66    pub fn delete_by_path(&mut self, path: &HttpCertificationPath) {
67        let tree_path = path.to_tree_path();
68        self.tree.delete(&tree_path);
69    }
70
71    /// Clears the tree of all [HttpCertificationTreeEntry].
72    /// After performing this operation, the canister's certified variable will need to be updated
73    /// with the new [root hash](HttpCertificationTree::root_hash) of the tree.
74    pub fn clear(&mut self) {
75        self.tree.clear();
76    }
77
78    /// Returns a pruned [HashTree] that will prove the presence of a given [HttpCertificationTreeEntry]
79    /// in the full [HttpCertificationTree], without needing to return the full tree.
80    ///
81    /// `request_url` is required so that the witness can be generated with respect to the request URL.
82    pub fn witness(
83        &self,
84        entry: &HttpCertificationTreeEntry,
85        request_url: &str,
86    ) -> HttpCertificationResult<HashTree> {
87        let witness = match entry.path.get_type() {
88            HttpCertificationPathType::Exact(_) => self.tree.witness(&entry.to_tree_path()),
89
90            HttpCertificationPathType::Wildcard(wildcard_path) => {
91                let requested_tree_path = HttpCertificationPath::exact(request_url).to_tree_path();
92                let responding_tree_path = entry.path.to_tree_path();
93
94                if responding_tree_path.len() > requested_tree_path.len() {
95                    return Err(HttpCertificationError::WildcardPathNotValidForRequestPath {
96                        wildcard_path: wildcard_path.to_string(),
97                        request_path: request_url.to_string(),
98                    });
99                }
100
101                // For wildcards we start by witnessing the path that was requested so the verifier can be sure
102                // that there is no exact match in the tree.
103                let witness = self.tree.witness(&requested_tree_path);
104
105                // Then we witness the wildcard path that we will be responding with.
106                let witness = merge_hash_trees(witness, self.tree.witness(&responding_tree_path));
107
108                // Then we need to prove that there is not a more specific wildcard in the tree that
109                // matches the request URL.
110                more_specific_wildcards_for(&requested_tree_path, &responding_tree_path)
111                    .iter()
112                    .fold(witness, |acc, path| {
113                        merge_hash_trees(acc, self.tree.witness(path))
114                    })
115            }
116        };
117
118        Ok(labeled(PATH_PREFIX_BYTES, witness))
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use crate::{
126        DefaultCelBuilder, DefaultResponseCertification, HttpCertification, HttpRequest,
127        HttpResponse, StatusCode, CERTIFICATE_EXPRESSION_HEADER_NAME,
128    };
129    use assert_matches::assert_matches;
130    use ic_certification::SubtreeLookupResult;
131    use rstest::*;
132
133    #[rstest]
134    fn test_witness() {
135        let mut tree = HttpCertificationTree::default();
136
137        let cel_expr = DefaultCelBuilder::full_certification()
138            .with_response_certification(DefaultResponseCertification::response_header_exclusions(
139                vec![],
140            ))
141            .build();
142
143        let not_found_request = HttpRequest::get("/assets/js/not-found.js").build();
144        let not_found_response = HttpResponse::not_found(
145            br#"404 Not Found"#,
146            vec![(
147                CERTIFICATE_EXPRESSION_HEADER_NAME.into(),
148                cel_expr.to_string(),
149            )],
150        )
151        .build();
152
153        let hello_world_request = HttpRequest::get("/assets/js/hello-world.js").build();
154        let hello_world_response = HttpResponse::not_found(
155            br#"console.log("Hello, World!")"#,
156            vec![(
157                CERTIFICATE_EXPRESSION_HEADER_NAME.into(),
158                cel_expr.to_string(),
159            )],
160        )
161        .build();
162
163        let not_found_entry = HttpCertificationTreeEntry::new(
164            HttpCertificationPath::wildcard("/assets/js"),
165            HttpCertification::full(&cel_expr, &not_found_request, &not_found_response, None)
166                .unwrap(),
167        );
168        tree.insert(&not_found_entry);
169
170        let hello_world_entry = HttpCertificationTreeEntry::new(
171            HttpCertificationPath::exact(hello_world_request.url()),
172            HttpCertification::full(&cel_expr, &hello_world_request, &hello_world_response, None)
173                .unwrap(),
174        );
175        tree.insert(&hello_world_entry);
176
177        let not_found_witness = tree.witness(&not_found_entry, "/assets/js/0.js").unwrap();
178
179        assert_eq!(
180            not_found_witness.lookup_subtree(["http_expr", "<*>"]),
181            SubtreeLookupResult::Absent
182        );
183        assert_eq!(
184            not_found_witness.lookup_subtree(["http_expr", "", "<*>"]),
185            SubtreeLookupResult::Absent
186        );
187        assert_eq!(
188            not_found_witness.lookup_subtree(["http_expr", "assets", "<*>"]),
189            SubtreeLookupResult::Absent
190        );
191        assert_eq!(
192            not_found_witness.lookup_subtree(["http_expr", "assets", "", "<*>"]),
193            SubtreeLookupResult::Absent
194        );
195        assert_matches!(
196            not_found_witness.lookup_subtree(["http_expr", "assets", "js", "<*>"]),
197            SubtreeLookupResult::Found(_)
198        );
199        assert_eq!(
200            not_found_witness.lookup_subtree(["http_expr", "assets", "js", "0.js", "<*>"]),
201            SubtreeLookupResult::Absent
202        );
203
204        let hello_world_witness = tree
205            .witness(&hello_world_entry, "/assets/js/hello-world.js")
206            .unwrap();
207
208        assert_matches!(
209            hello_world_witness.lookup_subtree([
210                "http_expr",
211                "assets",
212                "js",
213                "hello-world.js",
214                "<$>"
215            ]),
216            SubtreeLookupResult::Found(_)
217        );
218    }
219
220    #[rstest]
221    fn test_delete_by_path() {
222        let mut http_tree = HttpCertificationTree::default();
223        let cel_expr = DefaultCelBuilder::full_certification()
224            .with_response_certification(DefaultResponseCertification::response_header_exclusions(
225                vec![],
226            ))
227            .build();
228        let req_one_url = "/assets/js/hello.js";
229        let req_two_url = "/assets/js/world.js";
230
231        // arrange four paths in the tree,
232        // two requests, with two responses each
233        //
234        // The tree should look like this:
235        // -- "assets" -- "js" -- "hello.js"
236        //                 |            |-- ${cel_expr_hash}
237        //                 |                |-- ${req_one_hash}
238        //                 |                    |-- ${res_hash}
239        //                 |                    |-- ${alt_res_hash}
240        //                 | ---- "world.js"
241        //                              |-- ${cel_expr_hash}
242        //                                  |-- ${req_two_hash}
243        //                                      |-- ${res_hash}
244        //                                      |-- ${res_hash}
245        //
246        // Resulting in the following paths (number labels are referenced in comments below):
247        // (1) "assets" -- "js" -- "hello.js" -- ${cel_expr_hash} -- ${req_one_hash} -- ${res_hash}
248        // (2) "assets" -- "js" -- "hello.js" -- ${cel_expr_hash} -- ${req_one_hash} -- ${alt_res_hash}
249        // (3) "assets" -- "js" -- "world.js" -- ${cel_expr_hash} -- ${req_two_hash} -- ${res_hash}
250        // (4) "assets" -- "js" -- "world.js" -- ${cel_expr_hash} -- ${req_two_hash} -- ${alt_res_hash}
251
252        let req_one = HttpRequest::get(req_one_url).build();
253        let req_two = HttpRequest::get(req_two_url).build();
254
255        let res = HttpResponse::builder()
256            .with_status_code(StatusCode::OK)
257            .with_body(br#"console.log("Hello, World!")"#)
258            .with_headers(vec![(
259                CERTIFICATE_EXPRESSION_HEADER_NAME.into(),
260                cel_expr.to_string(),
261            )])
262            .build();
263        let alt_res = HttpResponse::builder()
264            .with_status_code(StatusCode::OK)
265            .with_body(br#"console.log("Hello, ALT World!")"#)
266            .with_headers(vec![(
267                CERTIFICATE_EXPRESSION_HEADER_NAME.into(),
268                cel_expr.to_string(),
269            )])
270            .build();
271
272        let req_one_entry = HttpCertificationTreeEntry::new(
273            HttpCertificationPath::exact(req_one.url()),
274            HttpCertification::full(&cel_expr, &req_one, &res, None).unwrap(),
275        );
276        http_tree.insert(&req_one_entry);
277
278        let req_one_alt_entry = HttpCertificationTreeEntry::new(
279            HttpCertificationPath::exact(req_one.url()),
280            HttpCertification::full(&cel_expr, &req_one, &alt_res, None).unwrap(),
281        );
282        http_tree.insert(&req_one_alt_entry);
283
284        let req_two_entry = HttpCertificationTreeEntry::new(
285            HttpCertificationPath::exact(req_two.url()),
286            HttpCertification::full(&cel_expr, &req_two, &res, None).unwrap(),
287        );
288        http_tree.insert(&req_two_entry);
289
290        let req_two_alt_entry = HttpCertificationTreeEntry::new(
291            HttpCertificationPath::exact(req_two.url()),
292            HttpCertification::full(&cel_expr, &req_two, &alt_res, None).unwrap(),
293        );
294        http_tree.insert(&req_two_alt_entry);
295
296        assert_matches!(
297            http_tree
298                .witness(&req_one_entry, req_one_url)
299                .unwrap()
300                .lookup_subtree(&lookup_path_from_entry(&req_one_entry)),
301            SubtreeLookupResult::Found(_)
302        );
303        assert_matches!(
304            http_tree
305                .witness(&req_one_alt_entry, req_one_url)
306                .unwrap()
307                .lookup_subtree(&lookup_path_from_entry(&req_one_alt_entry)),
308            SubtreeLookupResult::Found(_)
309        );
310
311        assert_matches!(
312            http_tree
313                .witness(&req_two_entry, req_two_url)
314                .unwrap()
315                .lookup_subtree(&lookup_path_from_entry(&req_two_entry)),
316            SubtreeLookupResult::Found(_)
317        );
318        assert_matches!(
319            http_tree
320                .witness(&req_two_alt_entry, req_two_url)
321                .unwrap()
322                .lookup_subtree(&lookup_path_from_entry(&req_two_alt_entry)),
323            SubtreeLookupResult::Found(_)
324        );
325
326        assert!(http_tree
327            .tree
328            .contains_path(&HttpCertificationPath::exact(req_one_url).to_tree_path()));
329        assert!(http_tree.tree.contains_path(&req_one_entry.to_tree_path()));
330        assert!(http_tree
331            .tree
332            .contains_path(&req_one_alt_entry.to_tree_path()));
333
334        assert!(http_tree
335            .tree
336            .contains_path(&HttpCertificationPath::exact(req_two_url).to_tree_path()));
337        assert!(http_tree.tree.contains_path(&req_two_entry.to_tree_path()));
338        assert!(http_tree
339            .tree
340            .contains_path(&req_two_alt_entry.to_tree_path()));
341
342        // delete the (1) and (2) paths, all other paths should remain in the tree
343        http_tree.delete_by_path(&HttpCertificationPath::exact(req_one.url()));
344
345        assert_matches!(
346            http_tree
347                .witness(&req_one_entry, req_one_url)
348                .unwrap()
349                .lookup_subtree(&lookup_path_from_entry(&req_one_entry)),
350            SubtreeLookupResult::Absent
351        );
352        assert_matches!(
353            http_tree
354                .witness(&req_one_alt_entry, req_one_url)
355                .unwrap()
356                .lookup_subtree(&lookup_path_from_entry(&req_one_alt_entry)),
357            SubtreeLookupResult::Absent
358        );
359
360        assert_matches!(
361            http_tree
362                .witness(&req_two_entry, req_two_url)
363                .unwrap()
364                .lookup_subtree(&lookup_path_from_entry(&req_two_entry)),
365            SubtreeLookupResult::Found(_)
366        );
367        assert_matches!(
368            http_tree
369                .witness(&req_two_alt_entry, req_two_url)
370                .unwrap()
371                .lookup_subtree(&lookup_path_from_entry(&req_two_alt_entry)),
372            SubtreeLookupResult::Found(_)
373        );
374
375        assert!(!http_tree
376            .tree
377            .contains_path(&HttpCertificationPath::exact(req_one_url).to_tree_path()));
378        assert!(!http_tree.tree.contains_path(&req_one_entry.to_tree_path()));
379        assert!(!http_tree
380            .tree
381            .contains_path(&req_one_alt_entry.to_tree_path()));
382
383        assert!(http_tree
384            .tree
385            .contains_path(&HttpCertificationPath::exact(req_two_url).to_tree_path()));
386        assert!(http_tree.tree.contains_path(&req_two_entry.to_tree_path()));
387        assert!(http_tree
388            .tree
389            .contains_path(&req_two_alt_entry.to_tree_path()));
390
391        // delete the (3) and (4) paths, now the tree should be empty
392        http_tree.delete_by_path(&HttpCertificationPath::exact(req_two.url()));
393
394        assert_matches!(
395            http_tree
396                .witness(&req_one_entry, req_one_url)
397                .unwrap()
398                .lookup_subtree(&lookup_path_from_entry(&req_one_entry)),
399            SubtreeLookupResult::Absent
400        );
401        assert_matches!(
402            http_tree
403                .witness(&req_one_alt_entry, req_one_url)
404                .unwrap()
405                .lookup_subtree(&lookup_path_from_entry(&req_one_alt_entry)),
406            SubtreeLookupResult::Absent
407        );
408
409        assert_matches!(
410            http_tree
411                .witness(&req_two_entry, req_two_url)
412                .unwrap()
413                .lookup_subtree(&lookup_path_from_entry(&req_two_entry)),
414            SubtreeLookupResult::Absent
415        );
416        assert_matches!(
417            http_tree
418                .witness(&req_two_alt_entry, req_two_url)
419                .unwrap()
420                .lookup_subtree(&lookup_path_from_entry(&req_two_alt_entry)),
421            SubtreeLookupResult::Absent
422        );
423
424        assert!(!http_tree
425            .tree
426            .contains_path(&HttpCertificationPath::exact(req_one_url).to_tree_path()));
427        assert!(!http_tree.tree.contains_path(&req_one_entry.to_tree_path()));
428        assert!(!http_tree
429            .tree
430            .contains_path(&req_one_alt_entry.to_tree_path()));
431
432        assert!(!http_tree
433            .tree
434            .contains_path(&HttpCertificationPath::exact(req_two_url).to_tree_path()));
435        assert!(!http_tree.tree.contains_path(&req_two_entry.to_tree_path()));
436        assert!(!http_tree
437            .tree
438            .contains_path(&req_two_alt_entry.to_tree_path()));
439    }
440
441    #[rstest]
442    fn delete_removes_empty_subpaths() {
443        let mut http_tree = HttpCertificationTree::default();
444        let cel_expr = DefaultCelBuilder::full_certification()
445            .with_response_certification(DefaultResponseCertification::response_header_exclusions(
446                vec![],
447            ))
448            .build();
449        let req_url = "/assets/js/hello-world.js";
450
451        // arrange four paths in the tree,
452        // two requests, with two responses each
453        //
454        // The tree should look like this:
455        // -- "assets" -- "js" -- "hello-world.js"
456        //                              |-- ${cel_expr_hash}
457        //                                  |-- ${get_request_hash}
458        //                                  |   |-- ${response_hash}
459        //                                  |   |-- ${alt_response_hash}
460        //                                  |-- ${post_request_hash}
461        //                                      |-- ${response_hash}
462        //                                      |-- ${alt_response_hash}
463        //
464        // Resulting in the following paths (number labels are referenced in comments below):
465        // (1) "assets" -- "js" -- "hello-world.js" -- ${cel_expr_hash} -- ${get_request_hash} -- ${response_hash}
466        // (2) "assets" -- "js" -- "hello-world.js" -- ${cel_expr_hash} -- ${get_request_hash} -- ${alt_response_hash}
467        // (3) "assets" -- "js" -- "hello-world.js" -- ${cel_expr_hash} -- ${post_request_hash} -- ${response_hash}
468        // (4) "assets" -- "js" -- "hello-world.js" -- ${cel_expr_hash} -- ${post_request_hash} -- ${alt_response_hash}
469
470        let get_request = HttpRequest::get(req_url).build();
471        let post_request = HttpRequest::post(req_url).build();
472
473        let response = HttpResponse::ok(
474            br#"console.log("Hello, World!")"#,
475            vec![(
476                CERTIFICATE_EXPRESSION_HEADER_NAME.into(),
477                cel_expr.to_string(),
478            )],
479        )
480        .build();
481        let alt_response = HttpResponse::ok(
482            br#"console.log("Hello, ALT World!")"#,
483            vec![(
484                CERTIFICATE_EXPRESSION_HEADER_NAME.into(),
485                cel_expr.to_string(),
486            )],
487        )
488        .build();
489
490        let get_entry = HttpCertificationTreeEntry::new(
491            HttpCertificationPath::exact(get_request.url()),
492            HttpCertification::full(&cel_expr, &get_request, &response, None).unwrap(),
493        );
494        http_tree.insert(&get_entry);
495
496        let post_entry = HttpCertificationTreeEntry::new(
497            HttpCertificationPath::exact(post_request.url()),
498            HttpCertification::full(&cel_expr, &post_request, &response, None).unwrap(),
499        );
500        http_tree.insert(&post_entry);
501
502        let alt_get_entry = HttpCertificationTreeEntry::new(
503            HttpCertificationPath::exact(get_request.url()),
504            HttpCertification::full(&cel_expr, &get_request, &alt_response, None).unwrap(),
505        );
506        http_tree.insert(&alt_get_entry);
507
508        let alt_post_entry = HttpCertificationTreeEntry::new(
509            HttpCertificationPath::exact(post_request.url()),
510            HttpCertification::full(&cel_expr, &post_request, &alt_response, None).unwrap(),
511        );
512        http_tree.insert(&alt_post_entry);
513
514        assert_matches!(
515            http_tree
516                .witness(&get_entry, req_url)
517                .unwrap()
518                .lookup_subtree(&lookup_path_from_entry(&get_entry)),
519            SubtreeLookupResult::Found(_)
520        );
521        assert_matches!(
522            http_tree
523                .witness(&post_entry, req_url)
524                .unwrap()
525                .lookup_subtree(&lookup_path_from_entry(&post_entry)),
526            SubtreeLookupResult::Found(_)
527        );
528        assert_matches!(
529            http_tree
530                .witness(&alt_get_entry, req_url)
531                .unwrap()
532                .lookup_subtree(&lookup_path_from_entry(&alt_get_entry)),
533            SubtreeLookupResult::Found(_)
534        );
535        assert_matches!(
536            http_tree
537                .witness(&alt_post_entry, req_url)
538                .unwrap()
539                .lookup_subtree(&lookup_path_from_entry(&alt_post_entry)),
540            SubtreeLookupResult::Found(_)
541        );
542
543        assert!(http_tree
544            .tree
545            .contains_path(&HttpCertificationPath::exact(req_url).to_tree_path()));
546        assert!(http_tree.tree.contains_path(&get_entry.to_tree_path()));
547        assert!(http_tree.tree.contains_path(&post_entry.to_tree_path()));
548        assert!(http_tree.tree.contains_path(&alt_get_entry.to_tree_path()));
549        assert!(http_tree.tree.contains_path(&alt_post_entry.to_tree_path()));
550
551        // delete the (1) path, all other paths should remain in the tree
552        http_tree.delete(&get_entry);
553
554        assert_matches!(
555            http_tree
556                .witness(&get_entry, req_url)
557                .unwrap()
558                .lookup_subtree(&lookup_path_from_entry(&get_entry)),
559            SubtreeLookupResult::Absent
560        );
561        assert_matches!(
562            http_tree
563                .witness(&post_entry, req_url)
564                .unwrap()
565                .lookup_subtree(&lookup_path_from_entry(&post_entry)),
566            SubtreeLookupResult::Found(_)
567        );
568        assert_matches!(
569            http_tree
570                .witness(&alt_get_entry, req_url)
571                .unwrap()
572                .lookup_subtree(&lookup_path_from_entry(&alt_get_entry)),
573            SubtreeLookupResult::Found(_)
574        );
575        assert_matches!(
576            http_tree
577                .witness(&alt_post_entry, req_url)
578                .unwrap()
579                .lookup_subtree(&lookup_path_from_entry(&alt_post_entry)),
580            SubtreeLookupResult::Found(_)
581        );
582
583        assert!(http_tree
584            .tree
585            .contains_path(&HttpCertificationPath::exact(req_url).to_tree_path()));
586        assert!(!http_tree.tree.contains_path(&get_entry.to_tree_path()));
587        assert!(http_tree.tree.contains_path(&post_entry.to_tree_path()));
588        assert!(http_tree.tree.contains_path(&alt_get_entry.to_tree_path()));
589        assert!(http_tree.tree.contains_path(&alt_post_entry.to_tree_path()));
590
591        // delete the (3) path, now only (2) and (4) should remain in the tree
592        http_tree.delete(&post_entry);
593
594        assert_matches!(
595            http_tree
596                .witness(&get_entry, req_url)
597                .unwrap()
598                .lookup_subtree(&lookup_path_from_entry(&get_entry)),
599            SubtreeLookupResult::Absent
600        );
601        assert_matches!(
602            http_tree
603                .witness(&post_entry, req_url)
604                .unwrap()
605                .lookup_subtree(&lookup_path_from_entry(&post_entry)),
606            SubtreeLookupResult::Absent
607        );
608        assert_matches!(
609            http_tree
610                .witness(&alt_get_entry, req_url)
611                .unwrap()
612                .lookup_subtree(&lookup_path_from_entry(&alt_get_entry)),
613            SubtreeLookupResult::Found(_)
614        );
615        assert_matches!(
616            http_tree
617                .witness(&alt_post_entry, req_url)
618                .unwrap()
619                .lookup_subtree(&lookup_path_from_entry(&alt_post_entry)),
620            SubtreeLookupResult::Found(_)
621        );
622
623        assert!(http_tree
624            .tree
625            .contains_path(&HttpCertificationPath::exact(req_url).to_tree_path()));
626        assert!(!http_tree.tree.contains_path(&get_entry.to_tree_path()));
627        assert!(!http_tree.tree.contains_path(&post_entry.to_tree_path()));
628        assert!(http_tree.tree.contains_path(&alt_get_entry.to_tree_path()));
629        assert!(http_tree.tree.contains_path(&alt_post_entry.to_tree_path()));
630
631        // delete the (2) path, now only (4) should remain in the tree
632        http_tree.delete(&alt_get_entry);
633
634        assert_matches!(
635            http_tree
636                .witness(&get_entry, req_url)
637                .unwrap()
638                .lookup_subtree(&lookup_path_from_entry(&get_entry)),
639            SubtreeLookupResult::Absent
640        );
641        assert_matches!(
642            http_tree
643                .witness(&post_entry, req_url)
644                .unwrap()
645                .lookup_subtree(&lookup_path_from_entry(&post_entry)),
646            SubtreeLookupResult::Absent
647        );
648        assert_matches!(
649            http_tree
650                .witness(&alt_get_entry, req_url)
651                .unwrap()
652                .lookup_subtree(&lookup_path_from_entry(&alt_get_entry)),
653            SubtreeLookupResult::Absent
654        );
655        assert_matches!(
656            http_tree
657                .witness(&alt_post_entry, req_url)
658                .unwrap()
659                .lookup_subtree(&lookup_path_from_entry(&alt_post_entry)),
660            SubtreeLookupResult::Found(_)
661        );
662
663        assert!(http_tree
664            .tree
665            .contains_path(&HttpCertificationPath::exact(req_url).to_tree_path()));
666        assert!(!http_tree.tree.contains_path(&get_entry.to_tree_path()));
667        assert!(!http_tree.tree.contains_path(&post_entry.to_tree_path()));
668        assert!(!http_tree.tree.contains_path(&alt_get_entry.to_tree_path()));
669        assert!(http_tree.tree.contains_path(&alt_post_entry.to_tree_path()));
670
671        // delete the (4) path, now the tree should be empty
672        http_tree.delete(&alt_post_entry);
673
674        assert_matches!(
675            http_tree
676                .witness(&get_entry, req_url)
677                .unwrap()
678                .lookup_subtree(&lookup_path_from_entry(&get_entry)),
679            SubtreeLookupResult::Absent
680        );
681        assert_matches!(
682            http_tree
683                .witness(&post_entry, req_url)
684                .unwrap()
685                .lookup_subtree(&lookup_path_from_entry(&post_entry)),
686            SubtreeLookupResult::Absent
687        );
688        assert_matches!(
689            http_tree
690                .witness(&alt_get_entry, req_url)
691                .unwrap()
692                .lookup_subtree(&lookup_path_from_entry(&alt_get_entry)),
693            SubtreeLookupResult::Absent
694        );
695        assert_matches!(
696            http_tree
697                .witness(&alt_post_entry, req_url)
698                .unwrap()
699                .lookup_subtree(&lookup_path_from_entry(&alt_post_entry)),
700            SubtreeLookupResult::Absent
701        );
702
703        assert!(!http_tree
704            .tree
705            .contains_path(&HttpCertificationPath::exact(req_url).to_tree_path()));
706        assert!(!http_tree.tree.contains_path(&get_entry.to_tree_path()));
707        assert!(!http_tree.tree.contains_path(&post_entry.to_tree_path()));
708        assert!(!http_tree.tree.contains_path(&alt_get_entry.to_tree_path()));
709        assert!(!http_tree.tree.contains_path(&alt_post_entry.to_tree_path()));
710    }
711
712    #[rstest]
713    fn test_witness_wildcard_too_long() {
714        let mut tree = HttpCertificationTree::default();
715
716        let cel_expr = DefaultCelBuilder::full_certification()
717            .with_response_certification(DefaultResponseCertification::response_header_exclusions(
718                vec![],
719            ))
720            .build();
721
722        let not_found_request = HttpRequest::get("/assets/js/not-found.js").build();
723        let not_found_response = HttpResponse::not_found(
724            br#"404 Not Found"#,
725            vec![(
726                CERTIFICATE_EXPRESSION_HEADER_NAME.into(),
727                cel_expr.to_string(),
728            )],
729        )
730        .build();
731
732        let not_found_entry = HttpCertificationTreeEntry::new(
733            HttpCertificationPath::wildcard("/assets/js"),
734            HttpCertification::full(&cel_expr, &not_found_request, &not_found_response, None)
735                .unwrap(),
736        );
737        tree.insert(&not_found_entry);
738
739        let witness = tree.witness(&not_found_entry, "/assets");
740
741        assert_matches!(
742            witness,
743            Err(HttpCertificationError::WildcardPathNotValidForRequestPath { .. })
744        );
745    }
746
747    #[rstest]
748    fn test_witness_wildcard_matches_asset() {
749        let mut tree = HttpCertificationTree::default();
750
751        let cel_expr = DefaultCelBuilder::full_certification()
752            .with_response_certification(DefaultResponseCertification::response_header_exclusions(
753                vec![],
754            ))
755            .build();
756
757        let index_html_request = HttpRequest::get("/").build();
758
759        let index_html_body = b"<html><body><h1>Hello World!</h1></body></html>".to_vec();
760        let index_html_response = HttpResponse::not_found(
761            index_html_body,
762            vec![(
763                CERTIFICATE_EXPRESSION_HEADER_NAME.into(),
764                cel_expr.to_string(),
765            )],
766        )
767        .build();
768
769        let certification =
770            HttpCertification::full(&cel_expr, &index_html_request, &index_html_response, None)
771                .unwrap();
772        let index_html_entry =
773            HttpCertificationTreeEntry::new(HttpCertificationPath::wildcard("/"), certification);
774        tree.insert(&index_html_entry);
775
776        let witness = tree.witness(&index_html_entry, "/").unwrap();
777
778        let mut path = index_html_entry.to_tree_path();
779        path.insert(0, b"http_expr".to_vec());
780
781        assert_matches!(witness.lookup_subtree(&path), SubtreeLookupResult::Found(_));
782    }
783
784    fn lookup_path_from_entry(entry: &HttpCertificationTreeEntry) -> Vec<Vec<u8>> {
785        let mut lookup_path = entry.to_tree_path();
786        lookup_path.insert(0, b"http_expr".to_vec());
787        lookup_path
788    }
789}