Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion bee2bee.egg-info/PKG-INFO
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,15 @@ 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
```
*Usage:*
- 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.

---

Expand Down
1 change: 1 addition & 0 deletions bee2bee.egg-info/SOURCES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions bee2bee/node.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
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
import psutil
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,
Expand Down
104 changes: 98 additions & 6 deletions bee2bee/p2p_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ---

Expand Down
18 changes: 11 additions & 7 deletions tests/test_nat_optional.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())