From 74869221e00de024b6c961ba258bbbd8d0b9a733 Mon Sep 17 00:00:00 2001 From: enx01 Date: Thu, 27 Nov 2025 18:32:56 +0100 Subject: [PATCH] merkel generation + render --- client-gui/assets/icon.png | Bin 8566 -> 1075 bytes client-gui/src/gui_app.rs | 322 +++++++++++++++++++------------------ client-gui/src/main.rs | 2 +- client-network/src/data.rs | 30 +++- client-network/src/lib.rs | 16 +- 5 files changed, 203 insertions(+), 167 deletions(-) diff --git a/client-gui/assets/icon.png b/client-gui/assets/icon.png index 85ead308ad2fc697eca7dd32bb14ccc4da7ac1e8..f4cf946f8c0003d9e9f0ef5684f8b487d5d5dfff 100644 GIT binary patch literal 1075 zcmV-31kC%1P)}dYyJ!bB;n{OkvLH&~3JHPWgUn}_i{vtL2 z#En1#I*}@bPNdpk)M`F|Ts^|o?%MT7nvAb2QVMFHFt388pfC1n19xb-oEDrv(+8(R z!asu(+_cV|>_J_v6_5WN!>6ezcRx%-1VMnqUIV*bf+z^6by?_jkK4gLf$eP``tEXC z0aS>H$z;HVv;A;6CA^!M#lyefz}HlV=Py5D*Un$KCXh@9|KPJ=Sp=3r zND#aaMyS$b6SG|XsLonOgNO@f`><`RhlazKXp(EdQW$wTiG?po^mKXA+vR0$&!k9R zDL@hco6TJEJG>j6)oO;%TMtnbAW0_X_No<83Pt+Vve`UdkA|5CS_=iGfbl>Cvynxv zb*EYpgD7CY??X+E5#60l_z+t7;eNTrpWAJxsaT{bUP62^&D37CB4mLRHHt+bE9ZIU zx+*M^2?7bYBC~!(h~&HbTWM(d zyV5R0Xe$>zB?SXI7ONBb6Zv~R|T zj|()bMB^#CUbF;ByJnLC2luzr9scQ1fR;gOp(<+~JJdl-6Ov7C-w&{g=_(?Oi$6-u!cDWt?)yt7vGoG}pu9{#Kq3 zkA!G(s1>;XFmUDaKI+613+KUKuYR~^FofVueu`g*)brew7G+|&}y)Kh^WxQ;F!is3p?R)z z`D)-MLY(+b^>&;%+DSi?boyIg3;nHwSf9jUCxJuUb-T@sXe@=N&jLtia+(rY1~-w& z+!8g>$V6?{8iZ%!80huUd*s^l7aD1dZ2M^#xbl$CyES;4*gFKC#U%|;l+ tZ@Gh5tC@9r@};2vJE+>A6R9cy_zJ{Ade%J4Q-1&e002ovPDHLkV1nAo5+wit literal 8566 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 wq*Mv?C{=mlTk^Zi_n%ft>, -} - - -fn build_file_node_recursively( - hash: &NodeHash, - storage: &HashMap, - name: String, -) -> Option { - let node = storage.get(hash)?; - let hash_id = hex::encode(hash); - - match node { - MerkleNode::Directory(dir_node) => { - // Recurse through all entries to build children - let children: Vec = dir_node.entries.iter().filter_map(|entry| { - let filename_lossy = String::from_utf8_lossy(&entry.filename) - .trim_end_matches('\0') - .to_string(); - - build_file_node_recursively(&entry.content_hash, storage, filename_lossy) - }).collect(); - - Some(FileNode { - name, - is_dir: true, - hash_id, - children: Some(children), - }) - } - MerkleNode::BigDirectory(big_dir_node) => { - // In a real system, BigDirectory children would have names stored in an index. - // Here, we generate dummy names to show recursion working. - let children: Vec = big_dir_node.children_hashes.iter().filter_map(|child_hash| { - let dummy_name = format!("chunk_group_{}", &hex::encode(child_hash)[..4]); - build_file_node_recursively(child_hash, storage, dummy_name) - }).collect(); - - Some(FileNode { - name, - is_dir: true, - hash_id, - children: Some(children), - }) - } - // Chunk or Big nodes are files (leaves in the file tree) - _ => Some(FileNode { - name, - is_dir: false, - hash_id, - children: None, - }), - } -} - - -pub fn convert_merkle_to_file_nodes(root_hash: NodeHash, storage: &HashMap) -> Option { - let root_name = "/".to_string(); - build_file_node_recursively(&root_hash, storage, root_name) -} - // --- Main Application Struct --- pub struct P2PClientApp { // Communication channels @@ -79,24 +14,23 @@ pub struct P2PClientApp { known_peers: Vec, connect_address_input: String, - peer_root_hash: HashMap, // peer_id -> root_hash - // Key: Parent Directory Hash (String), Value: List of children FileNode - loaded_tree_nodes: HashMap, + loaded_fs: HashMap, - // Which peer's tree we are currently displaying - active_peer_id: Option, - active_root_hash: Option, + // Current peer tree displayed + active_peer: Option, } impl P2PClientApp { pub fn new(cmd_tx: Sender, event_rx: Receiver) -> Self { - let (root_hash, tree) = MerkleNode::generate_random_tree(5).expect("Couldn't generate tree"); + let (root_hash, tree_content) = MerkleNode::generate_random_tree(5).expect("Couldn't generate tree"); let mut peer_root_hash = HashMap::new(); peer_root_hash.insert("bob".to_string(), "yoyoyoyo".to_string()); - let mut loaded_tree_nodes = HashMap::new(); - loaded_tree_nodes.insert(node_hash_to_hex_string(&root_hash), convert_merkle_to_file_nodes(root_hash, &tree).expect("Couldn't convert tree")); + let mut loaded_fs = HashMap::new(); + let tree = MerkleTree::new(tree_content, root_hash); + loaded_fs.insert("bob".to_string(), tree); + Self { network_cmd_tx: cmd_tx, @@ -106,10 +40,8 @@ impl P2PClientApp { "bob".to_string() ], connect_address_input: "127.0.0.1:8080".to_string(), - peer_root_hash, - loaded_tree_nodes, - active_peer_id: None, - active_root_hash: None, + loaded_fs, + active_peer: None, } } } @@ -124,30 +56,38 @@ impl eframe::App for P2PClientApp { while let Ok(event) = self.network_event_rx.try_recv() { match event { NetworkEvent::PeerConnected(addr) => { + todo!(); + self.status_message = format!("✅ Peer connected: {}", addr); if !self.known_peers.contains(&addr) { self.known_peers.push(addr); } } NetworkEvent::PeerListUpdated(peers) => { + todo!(); + self.known_peers = peers; } NetworkEvent::FileTreeReceived(_peer_id, _) => { + todo!(); + // self.loaded_tree_nodes.insert(_peer_id, tree); self.status_message = "🔄 File tree updated successfully.".to_string(); } NetworkEvent::FileTreeRootReceived(peer_id, root_hash) => { - self.status_message = format!("🔄 Received Merkle Root from {}: {}", peer_id, &root_hash[..8]); - self.peer_root_hash.insert(peer_id.clone(), root_hash.clone()); + todo!(); - self.active_peer_id = Some(peer_id.clone()); - self.active_root_hash = Some(root_hash.clone()); - - // Request the content of the root directory immediately - let _ = self.network_cmd_tx.send(NetworkCommand::RequestDirectoryContent( - peer_id, - root_hash, - )); + // self.status_message = format!("🔄 Received Merkle Root from {}: {}", peer_id, &root_hash[..8]); + // + // + // self.active_peer_id = Some(peer_id.clone()); + // + // + // // Request the content of the root directory immediately + // let _ = self.network_cmd_tx.send(NetworkCommand::RequestDirectoryContent( + // peer_id, + // root_hash, + // )); } // Handle other events like Disconnect, Error, etc. _ => {} @@ -188,24 +128,18 @@ impl eframe::App for P2PClientApp { ui.label("No active peers."); } else { for peer in &self.known_peers { - let is_active = self.active_peer_id.as_ref().map_or(false, |id| id == peer); - let root_hash_str = self.peer_root_hash.get(peer) - .map(|h| format!("Root: {}", &h[..8])) - .unwrap_or_else(|| "Root: N/A".to_string()); + let is_active = self.active_peer.as_ref().map_or(false, |id| id == peer); // if peer.id == self.active_peer_id - if ui.selectable_label(is_active, format!("{} ({})", peer, root_hash_str)).clicked() { - // Switch to displaying this peer's tree - self.active_peer_id = Some(peer.clone()); - if let Some(hash) = self.peer_root_hash.get(peer) { - self.active_root_hash = Some(hash.clone()); - - // Request root content if not loaded - if !self.loaded_tree_nodes.contains_key(hash) { - let _ = self.network_cmd_tx.send(NetworkCommand::RequestDirectoryContent( - peer.clone(), - hash.clone(), - )); - } + if ui.selectable_label(is_active, format!("{}", peer)).clicked() { + // switch to displaying this peer's tree + self.active_peer = Some(peer.clone()); + // Request root content if not loaded + if !self.loaded_fs.contains_key(self.active_peer.as_ref().unwrap()) { + todo!(); + // let _ = self.network_cmd_tx.send(NetworkCommand::RequestDirectoryContent( + // peer.clone(), + // peer.clone(), + // )); } } } @@ -214,18 +148,25 @@ impl eframe::App for P2PClientApp { }); // 4. Central Panel (Filesystem Tree) + let heading = { + if let Some(peer) = &self.active_peer { + format!("📂 {}'s tree", peer) + } else { + "📂 p2p-merkel client".to_string() + } + }; CentralPanel::default().show(ctx, |ui| { - ui.heading("📂 Decentralized File System"); + ui.heading(heading); ui.separator(); - if let Some(root_hash) = &self.active_root_hash { - if let Some(root_nodes) = self.loaded_tree_nodes.get(root_hash) { + if let Some(active_peer) = &self.active_peer { + if let Some(tree) = self.loaded_fs.get(active_peer) { ScrollArea::vertical().show(ui, |ui| { // Start drawing the tree from the root hash - self.draw_file_tree(ui, root_nodes, 0); + self.draw_file_tree(ui, tree); }); } else { - ui.label(format!("Loading root content for hash: {}", &root_hash[..8])); + ui.label(format!("Loading root for peer: {}", active_peer)); } } else { ui.label("Connect to a peer to view a file tree."); @@ -244,56 +185,125 @@ impl eframe::App for P2PClientApp { // --- Helper for Drawing the Recursive File Tree --- impl P2PClientApp { - fn draw_file_tree(&self, ui: &mut Ui, node: &FileNode, depth: usize) { - let indent_space = 15.0 * depth as f32; - let active_peer_id = self.active_peer_id.clone(); + // fn draw_file_tree(&self, ui: &mut Ui, node: &FileNode, depth: usize) { + // let indent_space = 15.0 * depth as f32; + // let active_peer_id = self.active_peer_id.clone(); + // + // let entry_hash = &node.hash_id; + // let filename = &node.name; + // let is_dir = node.is_dir; + // + // if is_dir { + // // --- Directory Node: Check if content (children) is already loaded (stored in the map) --- + // + // if let Some(children) = node.children.as_ref() { + // // Content is already loaded: draw the collapsing header and recurse + // CollapsingHeader::new(format!("📁 {}", filename)) + // .default_open(false) + // .enabled(true) + // .show(ui, |ui| { + // // Recursive call: iterate over children and call draw_file_tree for each + // for child_node in children { + // self.draw_file_tree(ui, child_node, depth + 1); + // } + // }); + // } else { + // // Content is NOT loaded: show a clickable button to request loading + // let response = ui.with_layout(Layout::left_to_right(Align::Min), |ui| { + // ui.add_space(indent_space); + // ui.add(Button::new(format!("▶️ {} (Load)", filename)).small()).on_hover_text(format!("Hash: {}...", &entry_hash[..8])); + // }).response; + // + // if response.clicked() { + // if let Some(peer_id) = active_peer_id.clone() { + // let _ = self.network_cmd_tx.send(NetworkCommand::RequestDirectoryContent( + // peer_id, + // entry_hash.clone(), + // )); + // // self.status_message = format!("Requested directory content for: {}...", &entry_hash[..8]); + // } + // } + // } + // } else { + // // --- File Node (Chunk or Big) --- + // ui.with_layout(Layout::left_to_right(Align::Center), |ui| { + // ui.add_space(indent_space); + // if ui.selectable_label(false, format!("📄 {} (Hash: {}...)", filename, &entry_hash[..8])).on_hover_text("Click to request file chunks...").clicked() { + // if let Some(peer_id) = active_peer_id.clone() { + // let _ = self.network_cmd_tx.send(NetworkCommand::RequestChunk(peer_id, entry_hash.clone())); + // // self.status_message = format!("Requested file chunks for: {}...", &entry_hash[..8]); + // } + // } + // }); + // } + // } - let entry_hash = &node.hash_id; - let filename = &node.name; - let is_dir = node.is_dir; + fn draw_file_tree(&self, ui: &mut Ui, tree: &MerkleTree) { + assert!(self.active_peer.is_some()); + assert!(self.loaded_fs.get(&self.active_peer.clone().unwrap()).is_some()); + let root = tree.root; + CollapsingHeader::new(format!("📁 root")) + .default_open(true) + .enabled(true) + .show(ui, |ui| { + self.draw_file_node(ui, root, tree,0, None); + }); + } - if is_dir { - // --- Directory Node: Check if content (children) is already loaded (stored in the map) --- - - if let Some(children) = node.children.as_ref() { - // Content is already loaded: draw the collapsing header and recurse - CollapsingHeader::new(format!("📁 {}", filename)) - .default_open(false) - .enabled(true) - .show(ui, |ui| { - // Recursive call: iterate over children and call draw_file_tree for each - for child_node in children { - self.draw_file_tree(ui, child_node, depth + 1); - } - }); - } else { - // Content is NOT loaded: show a clickable button to request loading - let response = ui.with_layout(Layout::left_to_right(Align::Min), |ui| { - ui.add_space(indent_space); - ui.add(Button::new(format!("▶️ {} (Load)", filename)).small()).on_hover_text(format!("Hash: {}...", &entry_hash[..8])); - }).response; - - if response.clicked() { - if let Some(peer_id) = active_peer_id.clone() { - let _ = self.network_cmd_tx.send(NetworkCommand::RequestDirectoryContent( - peer_id, - entry_hash.clone(), - )); - // self.status_message = format!("Requested directory content for: {}...", &entry_hash[..8]); + fn draw_file_node(&self, ui: &mut Ui, to_draw: NodeHash, tree: &MerkleTree, depth: usize, filename: Option<[u8; 32]>) { + if depth >= 32 { + return; + } + if let Some(current) = tree.data.get(&to_draw) { + let name = { + if filename.is_some() { + filename_to_string(filename.unwrap()) + } else { + node_hash_to_hex_string(&to_draw) + } + }; + match current { + MerkleNode::Chunk(node) => { + if ui.selectable_label(false, format!("📄 (C) {}...", name)).on_hover_text("Click to request file chunks...").clicked() { + todo!(); + // if let Some(peer_id) = active_peer_id.clone() { + // let _ = self.network_cmd_tx.send(NetworkCommand::RequestChunk(peer_id, entry_hash.clone())); + // // self.status_message = format!("Requested file chunks for: {}...", &entry_hash[..8]); + // } } } + MerkleNode::Directory(node) => { + CollapsingHeader::new(format!("📁 (D) {}", name)) + .default_open(false) + .enabled(true) + .show(ui, |ui| { + for entry in &node.entries { + self.draw_file_node(ui, entry.content_hash, tree, depth + 1, Some(entry.filename)); + } + }); + } + MerkleNode::Big(node) => { + + CollapsingHeader::new(format!("📄 (B) {}", name)) + .default_open(false) + .enabled(true) + .show(ui, |ui| { + for child in &node.children_hashes { + self.draw_file_node(ui, child.clone(), tree, depth + 1, None); + } + }); + } + MerkleNode::BigDirectory(node) => { + CollapsingHeader::new(format!("📁 (BD) {}", name)) + .default_open(false) + .enabled(true) + .show(ui, |ui| { + for child in &node.children_hashes { + self.draw_file_node(ui, child.clone(), tree, depth + 1, None); + } + }); + } } - } else { - // --- File Node (Chunk or Big) --- - ui.with_layout(Layout::left_to_right(Align::Center), |ui| { - ui.add_space(indent_space); - if ui.selectable_label(false, format!("📄 {} (Hash: {}...)", filename, &entry_hash[..8])).on_hover_text("Click to request file chunks...").clicked() { - if let Some(peer_id) = active_peer_id.clone() { - let _ = self.network_cmd_tx.send(NetworkCommand::RequestChunk(peer_id, entry_hash.clone())); - // self.status_message = format!("Requested file chunks for: {}...", &entry_hash[..8]); - } - } - }); } } } \ No newline at end of file diff --git a/client-gui/src/main.rs b/client-gui/src/main.rs index 81e5f37..2d8d10b 100644 --- a/client-gui/src/main.rs +++ b/client-gui/src/main.rs @@ -26,7 +26,7 @@ async fn main() -> eframe::Result<()> { }; eframe::run_native( - "Rust P2P Client (Merkle Tree Sync)", + "p2p-merkle client", options, Box::new(|cc| { let app = P2PClientApp::new(network_cmd_tx, network_event_rx); diff --git a/client-network/src/data.rs b/client-network/src/data.rs index 00b3151..0781ef5 100644 --- a/client-network/src/data.rs +++ b/client-network/src/data.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::hash::{DefaultHasher, Hash, Hasher}; use rand::{rng, Rng}; +use sha2::{Digest, Sha256}; // --- Constants --- const MAX_CHUNK_DATA_SIZE: usize = 1024; @@ -10,7 +11,7 @@ const MIN_BIG_CHILDREN: usize = 2; const FILENAME_HASH_SIZE: usize = 32; const DIRECTORY_ENTRY_SIZE: usize = FILENAME_HASH_SIZE * 2; // 64 bytes -fn dummy_hash(data: &[u8]) -> NodeHash { +fn hash(data: &[u8]) -> NodeHash { let mut hasher = DefaultHasher::new(); data.hash(&mut hasher); let hash_u64 = hasher.finish(); @@ -70,6 +71,18 @@ pub enum MerkleNode { BigDirectory(BigDirectoryNode) = 4, } +#[derive(Debug, Clone)] +pub struct MerkleTree { + pub data: HashMap, + pub root: NodeHash, +} + +impl MerkleTree { + pub fn new(data: HashMap, root: NodeHash) -> MerkleTree { + MerkleTree { data, root } + } +} + fn generate_random_file_node(storage: &mut HashMap) -> Result { let mut rng = rng(); let is_big = rng.random_bool(0.2); // 20% chance of being a big file @@ -77,7 +90,7 @@ fn generate_random_file_node(storage: &mut HashMap) -> Res if !is_big { // Generate a simple Chunk Node let node = MerkleNode::Chunk(ChunkNode::new_random()); - let hash = dummy_hash(&node.serialize()); + let hash = hash(&node.serialize()); storage.insert(hash, node); Ok(hash) } else { @@ -88,13 +101,13 @@ fn generate_random_file_node(storage: &mut HashMap) -> Res for _ in 0..num_children { // Children must be Chunk or Big; for simplicity, we only generate Chunk children here. let chunk_node = MerkleNode::Chunk(ChunkNode::new_random()); - let chunk_hash = dummy_hash(&chunk_node.serialize()); + let chunk_hash = hash(&chunk_node.serialize()); storage.insert(chunk_hash, chunk_node); children_hashes.push(chunk_hash); } let node = MerkleNode::Big(BigNode::new(children_hashes)?); - let hash = dummy_hash(&node.serialize()); + let hash = hash(&node.serialize()); storage.insert(hash, node); Ok(hash) } @@ -141,7 +154,7 @@ fn generate_random_directory_node( } let node = MerkleNode::Directory(DirectoryNode::new(entries)?); - let hash = dummy_hash(&node.serialize()); + let hash = hash(&node.serialize()); storage.insert(hash, node); Ok(hash) @@ -157,7 +170,7 @@ fn generate_random_directory_node( } let node = MerkleNode::BigDirectory(BigDirectoryNode::new(children)?); - let hash = dummy_hash(&node.serialize()); + let hash = hash(&node.serialize()); storage.insert(hash, node); Ok(hash) } @@ -201,6 +214,11 @@ pub struct DirectoryEntry { pub content_hash: NodeHash, } +pub fn filename_to_string(filename: [u8; FILENAME_HASH_SIZE]) -> String { + let end_index = filename.iter().position(|&b| b == 0).unwrap_or(FILENAME_HASH_SIZE); + String::from_utf8_lossy(&filename[..end_index]).to_string() +} + #[derive(Debug, Clone)] pub struct DirectoryNode { pub entries: Vec, diff --git a/client-network/src/lib.rs b/client-network/src/lib.rs index 9e2df55..3576747 100644 --- a/client-network/src/lib.rs +++ b/client-network/src/lib.rs @@ -61,15 +61,23 @@ pub fn start_p2p_executor( if let Ok(cmd) = cmd_rx.try_recv() { match cmd { NetworkCommand::ConnectPeer(addr) => { - println!("Attempting to connect to: {}", addr); + println!("[Network] ConnectPeer() called"); + println!("[Network] Attempting to connect to: {}", addr); // Network logic to connect... // If successful, send an event back: // event_tx.send(NetworkEvent::PeerConnected(addr)).unwrap(); }, - NetworkCommand::RequestFileTree(_) => todo!(), + NetworkCommand::RequestFileTree(_) => { + println!("[Network] RequestFileTree() called"); + }, + // ... handle other commands - NetworkCommand::RequestDirectoryContent(_, _) => todo!(), - NetworkCommand::RequestChunk(_, _) => todo!(), + NetworkCommand::RequestDirectoryContent(_, _) => { + println!("[Network] RequestDirectoryContent() called"); + }, + NetworkCommand::RequestChunk(_, _) => { + println!("[Network] RequestChunk() called"); + }, } }