diff --git a/bee2bee.egg-info/PKG-INFO b/bee2bee.egg-info/PKG-INFO index 88cdd09..16d9da8 100644 --- a/bee2bee.egg-info/PKG-INFO +++ b/bee2bee.egg-info/PKG-INFO @@ -146,7 +146,7 @@ A modern UI to visualize the network and chat with models. **Run Locally:** ```bash -cd desktop-app +cd electron-app npm install # First time only npm run dev ``` @@ -154,6 +154,7 @@ npm run dev - Open the App. - It connects to `http://localhost:4002` by default. - Go to "Chat" to talk to available providers. +- See [MANUAL_TESTING.md](MANUAL_TESTING.md) for detailed testing steps. --- diff --git a/bee2bee.egg-info/SOURCES.txt b/bee2bee.egg-info/SOURCES.txt index 3b5e27b..6a8c61a 100644 --- a/bee2bee.egg-info/SOURCES.txt +++ b/bee2bee.egg-info/SOURCES.txt @@ -17,6 +17,7 @@ bee2bee/p2p_runtime.py bee2bee/pieces.py bee2bee/protocol.py bee2bee/services.py +bee2bee/stun_client.py bee2bee/utils.py examples/api_demo.py examples/p2p_request_demo.py diff --git a/bee2bee/node.py b/bee2bee/node.py index f54f602..df450f2 100644 --- a/bee2bee/node.py +++ b/bee2bee/node.py @@ -1,4 +1,18 @@ from __future__ import annotations +""" +⚠️ DEPRECATED: This module uses a legacy coordinator-based architecture. + +For new deployments, use `p2p_runtime.py` which provides: +- True P2P networking (any node can be server/client) +- NAT traversal with STUN/UPnP +- Piece-based data transfer +- Service discovery without central coordinator + +This module is kept for backward compatibility with existing deployments +that use a central coordinator pattern. It will be removed in a future version. + +Migration: Replace `node.run_node(url)` with `p2p_runtime.run_p2p_node(bootstrap_link=url)` +""" import asyncio import json import platform @@ -6,6 +20,14 @@ import websockets from typing import Dict, Any, Optional from rich.console import Console +import warnings + +# Emit deprecation warning when module is imported +warnings.warn( + "bee2bee.node is deprecated. Use bee2bee.p2p_runtime instead.", + DeprecationWarning, + stacklevel=2 +) from .protocol import ( msg, diff --git a/bee2bee/p2p_runtime.py b/bee2bee/p2p_runtime.py index f6ebfc8..26a8f04 100644 --- a/bee2bee/p2p_runtime.py +++ b/bee2bee/p2p_runtime.py @@ -531,14 +531,106 @@ async def _handle_gen_result(self, ws, data): console.log(f"[yellow]Unknown request ID: {rid}[/yellow]") async def _handle_piece_request(self, ws, data): - # ... logic as before ... - console.log(f"[debug] Piece request received") - pass + """Handle a request for piece data by content hash and piece index.""" + content_hash = data.get("content_hash") + piece_index = data.get("piece_index") + console.log(f"[debug] Piece request received: hash={content_hash}, idx={piece_index}") + + if not content_hash or piece_index is None: + await self._send(ws, { + "type": "piece_data", + "content_hash": content_hash, + "piece_index": piece_index, + "error": "missing_params" + }) + return + + # Look up piece in local storage + piece_info = self.pieces.get(content_hash) + if not piece_info: + console.log(f"[yellow]Piece not found: {content_hash}[/yellow]") + await self._send(ws, { + "type": "piece_data", + "content_hash": content_hash, + "piece_index": piece_index, + "error": "not_found" + }) + return + + pieces_list = piece_info.get("pieces", []) + if piece_index < 0 or piece_index >= len(pieces_list): + console.log(f"[yellow]Invalid piece index: {piece_index}[/yellow]") + await self._send(ws, { + "type": "piece_data", + "content_hash": content_hash, + "piece_index": piece_index, + "error": "invalid_index" + }) + return + + # Send the piece data (base64 encoded for JSON transport) + piece_bytes = pieces_list[piece_index] + await self._send(ws, { + "type": "piece_data", + "content_hash": content_hash, + "piece_index": piece_index, + "data": base64.b64encode(piece_bytes).decode("utf-8"), + "hash": piece_info.get("hashes", [])[piece_index] if piece_index < len(piece_info.get("hashes", [])) else None + }) + console.log(f"[green]✅ Sent piece {piece_index} for {content_hash[:16]}...[/green]") async def _handle_piece_data(self, ws, data): - # ... logic as before ... - console.log(f"[debug] Piece data received") - pass + """Handle received piece data - verify and store it.""" + content_hash = data.get("content_hash") + piece_index = data.get("piece_index") + piece_data_b64 = data.get("data") + expected_hash = data.get("hash") + error = data.get("error") + + console.log(f"[debug] Piece data received: hash={content_hash}, idx={piece_index}") + + if error: + console.log(f"[red]Piece error from peer: {error}[/red]") + return + + if not content_hash or piece_index is None or not piece_data_b64: + console.log(f"[red]Invalid piece data message[/red]") + return + + # Decode the piece + try: + piece_bytes = base64.b64decode(piece_data_b64) + except Exception as e: + console.log(f"[red]Failed to decode piece data: {e}[/red]") + return + + # Verify hash if provided + if expected_hash: + from .p2p import sha256_hex_bytes + actual_hash = sha256_hex_bytes(piece_bytes) + if actual_hash != expected_hash: + console.log(f"[red]Piece hash mismatch! Expected {expected_hash[:16]}..., got {actual_hash[:16]}...[/red]") + return + console.log(f"[debug] Piece hash verified: {actual_hash[:16]}...") + + # Store the piece + async with self._lock: + if content_hash not in self.pieces: + self.pieces[content_hash] = {"pieces": [], "hashes": [], "complete": False} + + piece_info = self.pieces[content_hash] + + # Extend lists if needed + while len(piece_info["pieces"]) <= piece_index: + piece_info["pieces"].append(None) + while len(piece_info["hashes"]) <= piece_index: + piece_info["hashes"].append(None) + + piece_info["pieces"][piece_index] = piece_bytes + if expected_hash: + piece_info["hashes"][piece_index] = expected_hash + + console.log(f"[green]✅ Stored piece {piece_index} for {content_hash[:16]}...[/green]") # --- Public API --- diff --git a/tests/test_nat_optional.py b/tests/test_nat_optional.py index 95f89f7..7c50f9b 100644 --- a/tests/test_nat_optional.py +++ b/tests/test_nat_optional.py @@ -3,12 +3,16 @@ def test_run(): from bee2bee.nat import try_upnp_map, try_stun - ok, ip = try_upnp_map(5555) - assert ok in (True, False) - assert (ip is None) or isinstance(ip, str) - - async def _s(): + + async def _test(): + # Test UPnP - returns (success: bool, external_ip: Optional[str]) + ok, ip = await try_upnp_map(5555) + assert isinstance(ok, bool) + assert (ip is None) or isinstance(ip, str) + + # Test STUN - returns (ip, port) tuple or None out = await try_stun() - assert (out is None) or isinstance(out, str) - asyncio.run(_s()) + assert (out is None) or isinstance(out, tuple) + + asyncio.run(_test())