| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| // Copyright by contributors to this project. |
| // SPDX-License-Identifier: (Apache-2.0 OR MIT) |
| |
| use alloc::string::String; |
| use alloc::{format, vec}; |
| use core::borrow::BorrowMut; |
| |
| use debug_tree::TreeBuilder; |
| |
| use super::node::{NodeIndex, NodeVec}; |
| use crate::{client::MlsError, tree_kem::math::TreeIndex}; |
| |
| pub(crate) fn build_tree( |
| tree: &mut TreeBuilder, |
| nodes: &NodeVec, |
| idx: NodeIndex, |
| ) -> Result<(), MlsError> { |
| let blank_tag = if nodes.is_blank(idx)? { "Blank " } else { "" }; |
| |
| // Leaf Node |
| if nodes.is_leaf(idx) { |
| let leaf_tag = format!("{blank_tag}Leaf ({idx})"); |
| tree.add_leaf(&leaf_tag); |
| return Ok(()); |
| } |
| |
| // Parent Leaf |
| let mut parent_tag = format!("{blank_tag}Parent ({idx})"); |
| |
| if nodes.total_leaf_count().root() == idx { |
| parent_tag = format!("{blank_tag}Root ({idx})"); |
| } |
| |
| // Add unmerged leaves indexes |
| let unmerged_leaves_idxs = match nodes.borrow_as_parent(idx) { |
| Ok(parent) => parent |
| .unmerged_leaves |
| .iter() |
| .map(|leaf_idx| format!("{}", leaf_idx.0)) |
| .collect(), |
| Err(_) => { |
| // Empty parent nodes throw `NotParent` error when borrow as Parent |
| vec![] |
| } |
| }; |
| |
| if !unmerged_leaves_idxs.is_empty() { |
| let unmerged_leaves_tag = |
| format!(" unmerged leaves idxs: {}", unmerged_leaves_idxs.join(",")); |
| parent_tag.push_str(&unmerged_leaves_tag); |
| } |
| |
| let mut branch = tree.add_branch(&parent_tag); |
| |
| //This cannot panic, as we already checked that idx is not a leaf |
| build_tree(tree, nodes, idx.left_unchecked())?; |
| build_tree(tree, nodes, idx.right_unchecked())?; |
| |
| branch.release(); |
| |
| Ok(()) |
| } |
| |
| pub(crate) fn build_ascii_tree(nodes: &NodeVec) -> String { |
| let leaves_count: u32 = nodes.total_leaf_count(); |
| let mut tree = TreeBuilder::new(); |
| build_tree(tree.borrow_mut(), nodes, leaves_count.root()).unwrap(); |
| tree.string() |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use alloc::vec; |
| |
| use crate::{ |
| client::test_utils::TEST_CIPHER_SUITE, |
| crypto::test_utils::test_cipher_suite_provider, |
| identity::basic::BasicIdentityProvider, |
| tree_kem::{ |
| node::Parent, |
| parent_hash::ParentHash, |
| test_utils::{get_test_leaf_nodes, get_test_tree}, |
| }, |
| }; |
| |
| use super::build_ascii_tree; |
| |
| #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))] |
| async fn print_fully_populated_tree() { |
| let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE); |
| |
| // Create a tree |
| let mut tree = get_test_tree(TEST_CIPHER_SUITE).await.public; |
| let key_packages = get_test_leaf_nodes(TEST_CIPHER_SUITE).await; |
| |
| tree.add_leaves(key_packages, &BasicIdentityProvider, &cipher_suite_provider) |
| .await |
| .unwrap(); |
| |
| let tree_str = concat!( |
| "Blank Root (3)\n", |
| "├╼ Blank Parent (1)\n", |
| "│ ├╼ Leaf (0)\n", |
| "│ └╼ Leaf (2)\n", |
| "└╼ Blank Parent (5)\n", |
| " ├╼ Leaf (4)\n", |
| " └╼ Leaf (6)", |
| ); |
| |
| assert_eq!(tree_str, build_ascii_tree(&tree.nodes)); |
| } |
| |
| #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))] |
| async fn print_tree_blank_leaves() { |
| let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE); |
| |
| // Create a tree |
| let mut tree = get_test_tree(TEST_CIPHER_SUITE).await.public; |
| let key_packages = get_test_leaf_nodes(TEST_CIPHER_SUITE).await; |
| |
| let to_remove = tree |
| .add_leaves(key_packages, &BasicIdentityProvider, &cipher_suite_provider) |
| .await |
| .unwrap()[0]; |
| |
| tree.remove_leaves( |
| vec![to_remove], |
| &BasicIdentityProvider, |
| &cipher_suite_provider, |
| ) |
| .await |
| .unwrap(); |
| |
| let tree_str = concat!( |
| "Blank Root (3)\n", |
| "├╼ Blank Parent (1)\n", |
| "│ ├╼ Leaf (0)\n", |
| "│ └╼ Blank Leaf (2)\n", |
| "└╼ Blank Parent (5)\n", |
| " ├╼ Leaf (4)\n", |
| " └╼ Leaf (6)", |
| ); |
| |
| assert_eq!(tree_str, build_ascii_tree(&tree.nodes)); |
| } |
| |
| #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))] |
| async fn print_tree_unmerged_leaves_on_parent() { |
| let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE); |
| |
| // Create a tree |
| let mut tree = get_test_tree(TEST_CIPHER_SUITE).await.public; |
| let key_packages = get_test_leaf_nodes(TEST_CIPHER_SUITE).await; |
| |
| tree.add_leaves( |
| [key_packages[0].clone(), key_packages[1].clone()].to_vec(), |
| &BasicIdentityProvider, |
| &cipher_suite_provider, |
| ) |
| .await |
| .unwrap(); |
| |
| tree.nodes[3] = Parent { |
| public_key: vec![].into(), |
| parent_hash: ParentHash::empty(), |
| unmerged_leaves: vec![], |
| } |
| .into(); |
| |
| tree.add_leaves( |
| [key_packages[2].clone()].to_vec(), |
| &BasicIdentityProvider, |
| &cipher_suite_provider, |
| ) |
| .await |
| .unwrap(); |
| |
| let tree_str = concat!( |
| "Root (3) unmerged leaves idxs: 3\n", |
| "├╼ Blank Parent (1)\n", |
| "│ ├╼ Leaf (0)\n", |
| "│ └╼ Leaf (2)\n", |
| "└╼ Blank Parent (5)\n", |
| " ├╼ Leaf (4)\n", |
| " └╼ Leaf (6)", |
| ); |
| |
| assert_eq!(tree_str, build_ascii_tree(&tree.nodes)); |
| } |
| } |