Skip to main content

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