Files
waste-go/internal/ipc/ipc.go

590 lines
15 KiB
Go
Raw Permalink Normal View History

// Package ipc implements the local IPC server.
// The UI (or any local tool) connects to 127.0.0.1:17337 and speaks
// newline-delimited JSON: send IpcMessage commands, receive IpcMessage events.
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
//
// Backward compat: commands without network_id route to the first joined network.
package ipc
import (
"bufio"
Migrate to YAW/2 protocol: WebRTC transport, anchor signaling, nacl/box crypto Replace the original raw-TCP peer connections and Blowfish/RSA-era design with the YAW/2 protocol stack: Transport - internal/mesh/peer.go: raw TCP + manual ECDH replaced with pion/webrtc DataChannels (ICE, DTLS, SCTP). Peers negotiate via sealed offer/answer/ candidate exchange rather than direct dialling. - internal/nat: deleted — ICE subsumes everything this package was going to do. Signaling - cmd/anchor: new WebSocket signaling server (replaces cmd/relay). Verifies Ed25519 challenge/join signatures, routes opaque nacl/box blobs, broadcasts peer-join/peer-leave events. Never sees plaintext signaling content. - internal/anchor: new anchor client. Manages PeerConnection lifecycle, decides offerer by peer-id comparison, seals/opens signaling payloads with nacl/box, implements mesh.Anchor. Crypto (internal/crypto) - PeerID encoding: base64url → lowercase hex (64 chars, YAW/2 §2). - Sign/Verify: hex signatures throughout. - Added CurvePublicKey/CurvePrivateKey: X25519 keys derived from Ed25519 identity via Montgomery conversion (filippo.io/edwards25519), matching libsodium's crypto_sign_ed25519_*_to_curve25519. - Added SignalingBox/SignalingOpen: nacl/box (XSalsa20-Poly1305) seal/open for signaling payloads. Wire types (internal/proto) - ChatMessage gains Mid (random 16-byte hex) for mesh deduplication (§8). - FileChunk removed from PeerMessage — chunks go on a separate binary DataChannel labelled "f:<xid>"; FileDone added. - New types: SignalingPayload, AnchorMessage, HelloMessage, HelloBindString. - PeerID.Short() now returns first 16 hex chars grouped in 4s. - IPC: CmdConnect removed; CmdJoinNetwork/CmdLeaveNetwork added; EvtSessionReady added (fires when DataChannel opens and hello is received). IPC (internal/ipc) - join_network/leave_network replace connect (direct TCP dial). - Network joins are context-scoped: leave_network or client disconnect cancels the anchor connection cleanly. README updated to reflect new project layout, getting-started commands, IPC protocol, and roadmap state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 17:48:14 +02:00
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"strings"
"time"
"nhooyr.io/websocket"
"github.com/waste-go/internal/crypto"
"github.com/waste-go/internal/invite"
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
"github.com/waste-go/internal/netmgr"
"github.com/waste-go/internal/proto"
"github.com/waste-go/internal/shares"
)
// RunWS starts a WebSocket IPC server on 127.0.0.1:wsPort.
// Each WebSocket connection gets the same handleClient treatment as TCP.
// The OriginPatterns option allows connections from local dev servers.
func RunWS(mgr *netmgr.Manager, wsPort int) error {
addr := fmt.Sprintf("127.0.0.1:%d", wsPort)
log.Printf("ipc: WS listening on %s", addr)
return http.ListenAndServe(addr, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, err := websocket.Accept(w, r, &websocket.AcceptOptions{
OriginPatterns: []string{"*"},
})
if err != nil {
log.Printf("ipc: ws accept: %v", err)
return
}
log.Printf("ipc: WS client connected")
nc := websocket.NetConn(r.Context(), conn, websocket.MessageText)
handleClient(nc, mgr)
}))
}
// Run starts the IPC listener. Blocks until the listener fails.
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
func Run(mgr *netmgr.Manager, port int) error {
addr := fmt.Sprintf("127.0.0.1:%d", port)
ln, err := net.Listen("tcp", addr)
if err != nil {
return fmt.Errorf("ipc listen on %s: %w", addr, err)
}
log.Printf("ipc: listening on %s", addr)
for {
conn, err := ln.Accept()
if err != nil {
return fmt.Errorf("ipc accept: %w", err)
}
log.Printf("ipc: UI client connected from %s", conn.RemoteAddr())
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
go handleClient(conn, mgr)
}
}
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
func handleClient(conn net.Conn, mgr *netmgr.Manager) {
defer conn.Close()
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
events := mgr.Subscribe()
defer mgr.Unsubscribe(events)
writeCh := make(chan []byte, 128)
done := make(chan struct{})
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
writerDone := make(chan struct{})
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
// Writer goroutine.
go func() {
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
defer close(writerDone)
w := bufio.NewWriter(conn)
for line := range writeCh {
line = append(line, '\n')
if _, err := w.Write(line); err != nil {
return
}
w.Flush()
}
}()
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
// Event pusher — forwards Manager events to the UI client.
go func() {
defer func() { recover() }() //nolint:errcheck
for {
select {
case evt, ok := <-events:
if !ok {
return
}
line, err := json.Marshal(evt)
if err != nil {
continue
}
select {
case writeCh <- line:
case <-done:
return
}
case <-done:
return
}
}
}()
send := func(msg proto.IpcMessage) {
defer func() { recover() }() //nolint:errcheck
line, err := json.Marshal(msg)
if err != nil {
return
}
select {
case writeCh <- line:
case <-done:
}
}
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
// Send initial state snapshot.
send(stateSnapshot(mgr))
// Send stored history for each room so the UI is populated on connect.
sendStoredHistory(mgr, send)
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
var cmd proto.IpcMessage
if err := json.Unmarshal(scanner.Bytes(), &cmd); err != nil {
log.Printf("ipc: bad command: %v", err)
continue
}
Migrate to YAW/2 protocol: WebRTC transport, anchor signaling, nacl/box crypto Replace the original raw-TCP peer connections and Blowfish/RSA-era design with the YAW/2 protocol stack: Transport - internal/mesh/peer.go: raw TCP + manual ECDH replaced with pion/webrtc DataChannels (ICE, DTLS, SCTP). Peers negotiate via sealed offer/answer/ candidate exchange rather than direct dialling. - internal/nat: deleted — ICE subsumes everything this package was going to do. Signaling - cmd/anchor: new WebSocket signaling server (replaces cmd/relay). Verifies Ed25519 challenge/join signatures, routes opaque nacl/box blobs, broadcasts peer-join/peer-leave events. Never sees plaintext signaling content. - internal/anchor: new anchor client. Manages PeerConnection lifecycle, decides offerer by peer-id comparison, seals/opens signaling payloads with nacl/box, implements mesh.Anchor. Crypto (internal/crypto) - PeerID encoding: base64url → lowercase hex (64 chars, YAW/2 §2). - Sign/Verify: hex signatures throughout. - Added CurvePublicKey/CurvePrivateKey: X25519 keys derived from Ed25519 identity via Montgomery conversion (filippo.io/edwards25519), matching libsodium's crypto_sign_ed25519_*_to_curve25519. - Added SignalingBox/SignalingOpen: nacl/box (XSalsa20-Poly1305) seal/open for signaling payloads. Wire types (internal/proto) - ChatMessage gains Mid (random 16-byte hex) for mesh deduplication (§8). - FileChunk removed from PeerMessage — chunks go on a separate binary DataChannel labelled "f:<xid>"; FileDone added. - New types: SignalingPayload, AnchorMessage, HelloMessage, HelloBindString. - PeerID.Short() now returns first 16 hex chars grouped in 4s. - IPC: CmdConnect removed; CmdJoinNetwork/CmdLeaveNetwork added; EvtSessionReady added (fires when DataChannel opens and hello is received). IPC (internal/ipc) - join_network/leave_network replace connect (direct TCP dial). - Network joins are context-scoped: leave_network or client disconnect cancels the anchor connection cleanly. README updated to reflect new project layout, getting-started commands, IPC protocol, and roadmap state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 17:48:14 +02:00
switch cmd.Type {
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
case proto.CmdJoinNetwork:
var (
netID string
err error
)
switch {
case cmd.NetworkName != "":
netID, err = mgr.Join(cmd.NetworkName, cmd.ShareDir)
case len(cmd.NetworkHash) == 64:
netID, err = mgr.JoinByHash(cmd.NetworkHash, cmd.ShareDir)
default:
send(errMsg("join_network: network_name or network_hash (64 hex chars) required"))
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
continue
}
if err != nil {
send(errMsg(fmt.Sprintf("join_network: %v", err)))
continue
}
if n, ok := mgr.Get(netID); ok {
if cmd.RequireInvite {
n.Mesh.RequireInvite = true
}
if cmd.InviteString != "" {
n.Mesh.InviteString = cmd.InviteString
}
}
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
_ = netID
case proto.CmdLeaveNetwork:
if cmd.NetworkID != "" {
mgr.Leave(cmd.NetworkID)
} else {
// Backward compat: leave the first joined network.
if n := mgr.Default(); n != nil {
mgr.Leave(n.ID)
}
}
Migrate to YAW/2 protocol: WebRTC transport, anchor signaling, nacl/box crypto Replace the original raw-TCP peer connections and Blowfish/RSA-era design with the YAW/2 protocol stack: Transport - internal/mesh/peer.go: raw TCP + manual ECDH replaced with pion/webrtc DataChannels (ICE, DTLS, SCTP). Peers negotiate via sealed offer/answer/ candidate exchange rather than direct dialling. - internal/nat: deleted — ICE subsumes everything this package was going to do. Signaling - cmd/anchor: new WebSocket signaling server (replaces cmd/relay). Verifies Ed25519 challenge/join signatures, routes opaque nacl/box blobs, broadcasts peer-join/peer-leave events. Never sees plaintext signaling content. - internal/anchor: new anchor client. Manages PeerConnection lifecycle, decides offerer by peer-id comparison, seals/opens signaling payloads with nacl/box, implements mesh.Anchor. Crypto (internal/crypto) - PeerID encoding: base64url → lowercase hex (64 chars, YAW/2 §2). - Sign/Verify: hex signatures throughout. - Added CurvePublicKey/CurvePrivateKey: X25519 keys derived from Ed25519 identity via Montgomery conversion (filippo.io/edwards25519), matching libsodium's crypto_sign_ed25519_*_to_curve25519. - Added SignalingBox/SignalingOpen: nacl/box (XSalsa20-Poly1305) seal/open for signaling payloads. Wire types (internal/proto) - ChatMessage gains Mid (random 16-byte hex) for mesh deduplication (§8). - FileChunk removed from PeerMessage — chunks go on a separate binary DataChannel labelled "f:<xid>"; FileDone added. - New types: SignalingPayload, AnchorMessage, HelloMessage, HelloBindString. - PeerID.Short() now returns first 16 hex chars grouped in 4s. - IPC: CmdConnect removed; CmdJoinNetwork/CmdLeaveNetwork added; EvtSessionReady added (fires when DataChannel opens and hello is received). IPC (internal/ipc) - join_network/leave_network replace connect (direct TCP dial). - Network joins are context-scoped: leave_network or client disconnect cancels the anchor connection cleanly. README updated to reflect new project layout, getting-started commands, IPC protocol, and roadmap state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 17:48:14 +02:00
case proto.CmdSendMessage:
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
n := mgr.Resolve(cmd.NetworkID)
if n == nil {
send(errMsg("send_message: not joined to any network"))
continue
}
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
ts := time.Now().UnixMilli()
if cmd.To != nil {
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
// DM → spec "pm" type: flat {type, mid, text, ts} on the wire
mid := randomHex(16)
wire, err := json.Marshal(proto.PeerMessage{
Type: proto.MsgPm,
Mid: mid,
Text: cmd.Body,
Ts: ts,
})
if err != nil {
continue
}
n.Mesh.SendTo(*cmd.To, wire)
// Store locally with dm:<short-id> room convention
local := &proto.ChatMessage{
Mid: mid,
From: n.Identity.PeerID(),
To: cmd.To,
Room: "dm:" + (*cmd.To).Short(),
Text: cmd.Body,
Ts: ts,
}
n.Mesh.SaveMessage(local)
n.Mesh.Emit(proto.IpcMessage{
Type: proto.EvtMessageReceived,
NetworkID: n.ID,
Message: local,
})
} else {
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
// Group chat → spec "chat" type: flat {type, mid, room, text, ts}
mid := randomHex(16)
msgID := proto.ComputeMsgID(n.Identity.PeerID(), cmd.Room, ts, cmd.Body)
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
wire, err := json.Marshal(proto.PeerMessage{
Type: proto.MsgChat,
Mid: mid,
Room: cmd.Room,
Text: cmd.Body,
Ts: ts,
})
if err != nil {
continue
}
n.Mesh.Broadcast(wire)
local := &proto.ChatMessage{
Mid: mid,
MsgID: msgID,
From: n.Identity.PeerID(),
Room: cmd.Room,
Text: cmd.Body,
Ts: ts,
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
}
n.Mesh.SaveMessage(local)
n.Mesh.Emit(proto.IpcMessage{
Type: proto.EvtMessageReceived,
NetworkID: n.ID,
Message: local,
})
}
case proto.CmdSendReaction:
n := mgr.Resolve(cmd.NetworkID)
if n == nil {
send(errMsg("send_reaction: not joined to any network"))
continue
}
if cmd.ReactionMID == "" || cmd.ReactionEmoji == "" {
send(errMsg("send_reaction: reaction_mid and reaction_emoji are required"))
continue
}
wire, err := json.Marshal(proto.PeerMessage{
Type: proto.MsgReaction,
ReactionMID: cmd.ReactionMID,
ReactionEmoji: cmd.ReactionEmoji,
})
if err != nil {
continue
}
n.Mesh.Broadcast(wire)
n.Mesh.SaveReaction(cmd.ReactionMID, cmd.ReactionEmoji, string(n.Identity.PeerID()))
n.Mesh.Emit(proto.IpcMessage{
Type: proto.EvtReaction,
NetworkID: n.ID,
PeerID: ptr(n.Identity.PeerID()),
ReactionMID: cmd.ReactionMID,
ReactionEmoji: cmd.ReactionEmoji,
})
case proto.CmdCreateRoom:
n := mgr.Resolve(cmd.NetworkID)
if n == nil {
send(errMsg("create_room: not joined to any network"))
continue
}
name := strings.TrimSpace(cmd.Room)
if name == "" || name == "general" {
send(errMsg("create_room: room name is required and cannot be 'general'"))
continue
}
if err := n.Store.SaveRoom(name); err != nil {
send(errMsg(fmt.Sprintf("create_room: %v", err)))
continue
}
send(proto.IpcMessage{Type: proto.EvtRoomCreated, NetworkID: n.ID, Room: name})
Migrate to YAW/2 protocol: WebRTC transport, anchor signaling, nacl/box crypto Replace the original raw-TCP peer connections and Blowfish/RSA-era design with the YAW/2 protocol stack: Transport - internal/mesh/peer.go: raw TCP + manual ECDH replaced with pion/webrtc DataChannels (ICE, DTLS, SCTP). Peers negotiate via sealed offer/answer/ candidate exchange rather than direct dialling. - internal/nat: deleted — ICE subsumes everything this package was going to do. Signaling - cmd/anchor: new WebSocket signaling server (replaces cmd/relay). Verifies Ed25519 challenge/join signatures, routes opaque nacl/box blobs, broadcasts peer-join/peer-leave events. Never sees plaintext signaling content. - internal/anchor: new anchor client. Manages PeerConnection lifecycle, decides offerer by peer-id comparison, seals/opens signaling payloads with nacl/box, implements mesh.Anchor. Crypto (internal/crypto) - PeerID encoding: base64url → lowercase hex (64 chars, YAW/2 §2). - Sign/Verify: hex signatures throughout. - Added CurvePublicKey/CurvePrivateKey: X25519 keys derived from Ed25519 identity via Montgomery conversion (filippo.io/edwards25519), matching libsodium's crypto_sign_ed25519_*_to_curve25519. - Added SignalingBox/SignalingOpen: nacl/box (XSalsa20-Poly1305) seal/open for signaling payloads. Wire types (internal/proto) - ChatMessage gains Mid (random 16-byte hex) for mesh deduplication (§8). - FileChunk removed from PeerMessage — chunks go on a separate binary DataChannel labelled "f:<xid>"; FileDone added. - New types: SignalingPayload, AnchorMessage, HelloMessage, HelloBindString. - PeerID.Short() now returns first 16 hex chars grouped in 4s. - IPC: CmdConnect removed; CmdJoinNetwork/CmdLeaveNetwork added; EvtSessionReady added (fires when DataChannel opens and hello is received). IPC (internal/ipc) - join_network/leave_network replace connect (direct TCP dial). - Network joins are context-scoped: leave_network or client disconnect cancels the anchor connection cleanly. README updated to reflect new project layout, getting-started commands, IPC protocol, and roadmap state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 17:48:14 +02:00
case proto.CmdGetState:
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
send(stateSnapshot(mgr))
case proto.CmdGetFileList:
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
n := mgr.Resolve(cmd.NetworkID)
if n == nil {
send(errMsg("get_file_list: not joined to any network"))
continue
}
if cmd.PeerID == nil || *cmd.PeerID == n.Identity.PeerID() {
send(proto.IpcMessage{
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
Type: proto.EvtFileList,
NetworkID: n.ID,
PeerID: ptr(n.Identity.PeerID()),
Files: mgr.ScanAllShares(n.ID),
})
} else {
req, err := json.Marshal(proto.PeerMessage{Type: proto.MsgFileListReq})
if err != nil {
continue
}
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
if !n.Mesh.SendTo(*cmd.PeerID, req) {
send(errMsg(fmt.Sprintf("get_file_list: peer %s not connected", (*cmd.PeerID).Short())))
}
}
case proto.CmdAddShare:
if cmd.Path == "" {
send(errMsg("add_share: path is required"))
continue
}
networks := cmd.ShareNetworks
if len(networks) == 0 {
networks = []string{"*"}
}
if err := mgr.Shares.Add(shares.Share{Path: cmd.Path, Networks: networks}); err != nil {
send(errMsg(fmt.Sprintf("add_share: %v", err)))
continue
}
send(sharesListMsg(mgr))
case proto.CmdRemoveShare:
if cmd.Path == "" {
send(errMsg("remove_share: path is required"))
continue
}
if err := mgr.Shares.Remove(cmd.Path); err != nil {
send(errMsg(fmt.Sprintf("remove_share: %v", err)))
continue
}
send(sharesListMsg(mgr))
case proto.CmdListShares:
send(sharesListMsg(mgr))
case proto.CmdGenerateInvite:
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
n := mgr.Resolve(cmd.NetworkID)
if n == nil {
send(errMsg("generate_invite: not currently joined to a network"))
continue
}
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
if mgr.AnchorURL() == "" {
send(errMsg("generate_invite: daemon was started without -anchor flag"))
continue
}
inv, err := invite.EncodeSigned(mgr.AnchorURL(), n.Name, n.Identity)
if err != nil {
send(errMsg(fmt.Sprintf("generate_invite: %v", err)))
continue
}
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
send(proto.IpcMessage{
Type: proto.EvtInviteGenerated,
NetworkID: n.ID,
InviteGenerated: inv,
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
})
case proto.CmdSetShareDir:
n := mgr.Resolve(cmd.NetworkID)
if n == nil {
send(errMsg("set_share_dir: not joined to any network"))
continue
}
if !mgr.SetShareDir(n.ID, cmd.Path) {
send(errMsg("set_share_dir: network not found"))
continue
}
feat: download dirs, file transfer resume, Wails desktop app, PWA, CI Daemon: - Per-network download directories (-download-dir flag, set_download_dir IPC) - File transfer resume after disconnection: .tmp.meta sidecars survive interruption; resume_offset in file-accept lets sender seek and continue - set_download_dir IPC command; download_dir reported in state_snapshot Protocol: - PeerMessage.ResumeOffset (EXT-006) for file transfer resume - IpcMessage.ShareNetworks json tag changed from "networks" to "network_ids" to fix duplicate json tag collision with Networks []NetworkInfo Desktop app (cmd/app): - Wails v2 shell embedding daemon logic directly (no subprocess) - System tray on Linux/Windows via getlantern/systray; macOS hides to Dock - OS notifications for message_received and file_complete via Wails events - notray build tag for headless/CI builds without GTK tray headers - build-app.sh: builds web frontend, copies dist, runs wails build Web / PWA: - manifest.json + Apple touch icon meta tags for mobile "Add to Home Screen" - PNG icons (192px, 512px, 180px) generated from SVG - Wails EventsOn("notify") hook in App.tsx for native OS notifications CI: - .gitea/workflows/build.yml: server binaries cross-compiled for 5 platforms, desktop app for Linux amd64, release artifacts published on v* tags Docs: - README: download dir, file transfer resume, desktop app, PWA, CI sections - EXTENSIONS.md: EXT-004 daemon mode marked shipped; EXT-006 resume added - FUTURE.md: roadmap updated Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 21:38:28 +02:00
case proto.CmdSetDownloadDir:
n := mgr.Resolve(cmd.NetworkID)
if n == nil {
send(errMsg("set_download_dir: not joined to any network"))
continue
}
if cmd.Path == "" {
send(errMsg("set_download_dir: path is required"))
continue
}
if !mgr.SetDownloadDir(n.ID, cmd.Path) {
send(errMsg("set_download_dir: network not found"))
continue
}
send(stateSnapshot(mgr))
Migrate to YAW/2 protocol: WebRTC transport, anchor signaling, nacl/box crypto Replace the original raw-TCP peer connections and Blowfish/RSA-era design with the YAW/2 protocol stack: Transport - internal/mesh/peer.go: raw TCP + manual ECDH replaced with pion/webrtc DataChannels (ICE, DTLS, SCTP). Peers negotiate via sealed offer/answer/ candidate exchange rather than direct dialling. - internal/nat: deleted — ICE subsumes everything this package was going to do. Signaling - cmd/anchor: new WebSocket signaling server (replaces cmd/relay). Verifies Ed25519 challenge/join signatures, routes opaque nacl/box blobs, broadcasts peer-join/peer-leave events. Never sees plaintext signaling content. - internal/anchor: new anchor client. Manages PeerConnection lifecycle, decides offerer by peer-id comparison, seals/opens signaling payloads with nacl/box, implements mesh.Anchor. Crypto (internal/crypto) - PeerID encoding: base64url → lowercase hex (64 chars, YAW/2 §2). - Sign/Verify: hex signatures throughout. - Added CurvePublicKey/CurvePrivateKey: X25519 keys derived from Ed25519 identity via Montgomery conversion (filippo.io/edwards25519), matching libsodium's crypto_sign_ed25519_*_to_curve25519. - Added SignalingBox/SignalingOpen: nacl/box (XSalsa20-Poly1305) seal/open for signaling payloads. Wire types (internal/proto) - ChatMessage gains Mid (random 16-byte hex) for mesh deduplication (§8). - FileChunk removed from PeerMessage — chunks go on a separate binary DataChannel labelled "f:<xid>"; FileDone added. - New types: SignalingPayload, AnchorMessage, HelloMessage, HelloBindString. - PeerID.Short() now returns first 16 hex chars grouped in 4s. - IPC: CmdConnect removed; CmdJoinNetwork/CmdLeaveNetwork added; EvtSessionReady added (fires when DataChannel opens and hello is received). IPC (internal/ipc) - join_network/leave_network replace connect (direct TCP dial). - Network joins are context-scoped: leave_network or client disconnect cancels the anchor connection cleanly. README updated to reflect new project layout, getting-started commands, IPC protocol, and roadmap state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 17:48:14 +02:00
case proto.CmdSendFile:
n := mgr.Resolve(cmd.NetworkID)
if n == nil {
send(errMsg("send_file: not joined to any network"))
continue
}
if cmd.PeerID == nil {
send(errMsg("send_file: peer_id is required"))
continue
}
if cmd.Path == "" {
send(errMsg("send_file: path is required"))
continue
}
if err := n.Mesh.OfferFile(*cmd.PeerID, cmd.Path); err != nil {
send(errMsg(fmt.Sprintf("send_file: %v", err)))
}
Migrate to YAW/2 protocol: WebRTC transport, anchor signaling, nacl/box crypto Replace the original raw-TCP peer connections and Blowfish/RSA-era design with the YAW/2 protocol stack: Transport - internal/mesh/peer.go: raw TCP + manual ECDH replaced with pion/webrtc DataChannels (ICE, DTLS, SCTP). Peers negotiate via sealed offer/answer/ candidate exchange rather than direct dialling. - internal/nat: deleted — ICE subsumes everything this package was going to do. Signaling - cmd/anchor: new WebSocket signaling server (replaces cmd/relay). Verifies Ed25519 challenge/join signatures, routes opaque nacl/box blobs, broadcasts peer-join/peer-leave events. Never sees plaintext signaling content. - internal/anchor: new anchor client. Manages PeerConnection lifecycle, decides offerer by peer-id comparison, seals/opens signaling payloads with nacl/box, implements mesh.Anchor. Crypto (internal/crypto) - PeerID encoding: base64url → lowercase hex (64 chars, YAW/2 §2). - Sign/Verify: hex signatures throughout. - Added CurvePublicKey/CurvePrivateKey: X25519 keys derived from Ed25519 identity via Montgomery conversion (filippo.io/edwards25519), matching libsodium's crypto_sign_ed25519_*_to_curve25519. - Added SignalingBox/SignalingOpen: nacl/box (XSalsa20-Poly1305) seal/open for signaling payloads. Wire types (internal/proto) - ChatMessage gains Mid (random 16-byte hex) for mesh deduplication (§8). - FileChunk removed from PeerMessage — chunks go on a separate binary DataChannel labelled "f:<xid>"; FileDone added. - New types: SignalingPayload, AnchorMessage, HelloMessage, HelloBindString. - PeerID.Short() now returns first 16 hex chars grouped in 4s. - IPC: CmdConnect removed; CmdJoinNetwork/CmdLeaveNetwork added; EvtSessionReady added (fires when DataChannel opens and hello is received). IPC (internal/ipc) - join_network/leave_network replace connect (direct TCP dial). - Network joins are context-scoped: leave_network or client disconnect cancels the anchor connection cleanly. README updated to reflect new project layout, getting-started commands, IPC protocol, and roadmap state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 17:48:14 +02:00
case proto.CmdExportIdentity:
if cmd.Passphrase == "" {
send(errMsg("export_identity: passphrase is required"))
continue
}
blob, err := crypto.ExportIdentity(mgr.MasterIdentity(), cmd.Passphrase)
if err != nil {
send(errMsg(fmt.Sprintf("export_identity: %v", err)))
continue
}
send(proto.IpcMessage{
Type: proto.EvtIdentityExported,
Backup: string(blob),
})
case proto.CmdImportIdentity:
if cmd.Passphrase == "" || cmd.Backup == "" {
send(errMsg("import_identity: passphrase and backup are required"))
continue
}
_, err := crypto.ImportIdentity([]byte(cmd.Backup), cmd.Passphrase)
if err != nil {
send(errMsg(fmt.Sprintf("import_identity: %v", err)))
continue
}
// Import is intentionally read-only here: returns the decrypted identity
// for the caller to verify before committing. Actual on-disk replacement
// requires a daemon restart with --import flag (see cmd/daemon).
send(proto.IpcMessage{Type: proto.EvtIdentityImported})
Migrate to YAW/2 protocol: WebRTC transport, anchor signaling, nacl/box crypto Replace the original raw-TCP peer connections and Blowfish/RSA-era design with the YAW/2 protocol stack: Transport - internal/mesh/peer.go: raw TCP + manual ECDH replaced with pion/webrtc DataChannels (ICE, DTLS, SCTP). Peers negotiate via sealed offer/answer/ candidate exchange rather than direct dialling. - internal/nat: deleted — ICE subsumes everything this package was going to do. Signaling - cmd/anchor: new WebSocket signaling server (replaces cmd/relay). Verifies Ed25519 challenge/join signatures, routes opaque nacl/box blobs, broadcasts peer-join/peer-leave events. Never sees plaintext signaling content. - internal/anchor: new anchor client. Manages PeerConnection lifecycle, decides offerer by peer-id comparison, seals/opens signaling payloads with nacl/box, implements mesh.Anchor. Crypto (internal/crypto) - PeerID encoding: base64url → lowercase hex (64 chars, YAW/2 §2). - Sign/Verify: hex signatures throughout. - Added CurvePublicKey/CurvePrivateKey: X25519 keys derived from Ed25519 identity via Montgomery conversion (filippo.io/edwards25519), matching libsodium's crypto_sign_ed25519_*_to_curve25519. - Added SignalingBox/SignalingOpen: nacl/box (XSalsa20-Poly1305) seal/open for signaling payloads. Wire types (internal/proto) - ChatMessage gains Mid (random 16-byte hex) for mesh deduplication (§8). - FileChunk removed from PeerMessage — chunks go on a separate binary DataChannel labelled "f:<xid>"; FileDone added. - New types: SignalingPayload, AnchorMessage, HelloMessage, HelloBindString. - PeerID.Short() now returns first 16 hex chars grouped in 4s. - IPC: CmdConnect removed; CmdJoinNetwork/CmdLeaveNetwork added; EvtSessionReady added (fires when DataChannel opens and hello is received). IPC (internal/ipc) - join_network/leave_network replace connect (direct TCP dial). - Network joins are context-scoped: leave_network or client disconnect cancels the anchor connection cleanly. README updated to reflect new project layout, getting-started commands, IPC protocol, and roadmap state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 17:48:14 +02:00
default:
send(errMsg(fmt.Sprintf("unknown command: %s", cmd.Type)))
Migrate to YAW/2 protocol: WebRTC transport, anchor signaling, nacl/box crypto Replace the original raw-TCP peer connections and Blowfish/RSA-era design with the YAW/2 protocol stack: Transport - internal/mesh/peer.go: raw TCP + manual ECDH replaced with pion/webrtc DataChannels (ICE, DTLS, SCTP). Peers negotiate via sealed offer/answer/ candidate exchange rather than direct dialling. - internal/nat: deleted — ICE subsumes everything this package was going to do. Signaling - cmd/anchor: new WebSocket signaling server (replaces cmd/relay). Verifies Ed25519 challenge/join signatures, routes opaque nacl/box blobs, broadcasts peer-join/peer-leave events. Never sees plaintext signaling content. - internal/anchor: new anchor client. Manages PeerConnection lifecycle, decides offerer by peer-id comparison, seals/opens signaling payloads with nacl/box, implements mesh.Anchor. Crypto (internal/crypto) - PeerID encoding: base64url → lowercase hex (64 chars, YAW/2 §2). - Sign/Verify: hex signatures throughout. - Added CurvePublicKey/CurvePrivateKey: X25519 keys derived from Ed25519 identity via Montgomery conversion (filippo.io/edwards25519), matching libsodium's crypto_sign_ed25519_*_to_curve25519. - Added SignalingBox/SignalingOpen: nacl/box (XSalsa20-Poly1305) seal/open for signaling payloads. Wire types (internal/proto) - ChatMessage gains Mid (random 16-byte hex) for mesh deduplication (§8). - FileChunk removed from PeerMessage — chunks go on a separate binary DataChannel labelled "f:<xid>"; FileDone added. - New types: SignalingPayload, AnchorMessage, HelloMessage, HelloBindString. - PeerID.Short() now returns first 16 hex chars grouped in 4s. - IPC: CmdConnect removed; CmdJoinNetwork/CmdLeaveNetwork added; EvtSessionReady added (fires when DataChannel opens and hello is received). IPC (internal/ipc) - join_network/leave_network replace connect (direct TCP dial). - Network joins are context-scoped: leave_network or client disconnect cancels the anchor connection cleanly. README updated to reflect new project layout, getting-started commands, IPC protocol, and roadmap state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 17:48:14 +02:00
}
}
Migrate to YAW/2 protocol: WebRTC transport, anchor signaling, nacl/box crypto Replace the original raw-TCP peer connections and Blowfish/RSA-era design with the YAW/2 protocol stack: Transport - internal/mesh/peer.go: raw TCP + manual ECDH replaced with pion/webrtc DataChannels (ICE, DTLS, SCTP). Peers negotiate via sealed offer/answer/ candidate exchange rather than direct dialling. - internal/nat: deleted — ICE subsumes everything this package was going to do. Signaling - cmd/anchor: new WebSocket signaling server (replaces cmd/relay). Verifies Ed25519 challenge/join signatures, routes opaque nacl/box blobs, broadcasts peer-join/peer-leave events. Never sees plaintext signaling content. - internal/anchor: new anchor client. Manages PeerConnection lifecycle, decides offerer by peer-id comparison, seals/opens signaling payloads with nacl/box, implements mesh.Anchor. Crypto (internal/crypto) - PeerID encoding: base64url → lowercase hex (64 chars, YAW/2 §2). - Sign/Verify: hex signatures throughout. - Added CurvePublicKey/CurvePrivateKey: X25519 keys derived from Ed25519 identity via Montgomery conversion (filippo.io/edwards25519), matching libsodium's crypto_sign_ed25519_*_to_curve25519. - Added SignalingBox/SignalingOpen: nacl/box (XSalsa20-Poly1305) seal/open for signaling payloads. Wire types (internal/proto) - ChatMessage gains Mid (random 16-byte hex) for mesh deduplication (§8). - FileChunk removed from PeerMessage — chunks go on a separate binary DataChannel labelled "f:<xid>"; FileDone added. - New types: SignalingPayload, AnchorMessage, HelloMessage, HelloBindString. - PeerID.Short() now returns first 16 hex chars grouped in 4s. - IPC: CmdConnect removed; CmdJoinNetwork/CmdLeaveNetwork added; EvtSessionReady added (fires when DataChannel opens and hello is received). IPC (internal/ipc) - join_network/leave_network replace connect (direct TCP dial). - Network joins are context-scoped: leave_network or client disconnect cancels the anchor connection cleanly. README updated to reflect new project layout, getting-started commands, IPC protocol, and roadmap state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 17:48:14 +02:00
close(done)
Migrate to YAW/2 protocol: WebRTC transport, anchor signaling, nacl/box crypto Replace the original raw-TCP peer connections and Blowfish/RSA-era design with the YAW/2 protocol stack: Transport - internal/mesh/peer.go: raw TCP + manual ECDH replaced with pion/webrtc DataChannels (ICE, DTLS, SCTP). Peers negotiate via sealed offer/answer/ candidate exchange rather than direct dialling. - internal/nat: deleted — ICE subsumes everything this package was going to do. Signaling - cmd/anchor: new WebSocket signaling server (replaces cmd/relay). Verifies Ed25519 challenge/join signatures, routes opaque nacl/box blobs, broadcasts peer-join/peer-leave events. Never sees plaintext signaling content. - internal/anchor: new anchor client. Manages PeerConnection lifecycle, decides offerer by peer-id comparison, seals/opens signaling payloads with nacl/box, implements mesh.Anchor. Crypto (internal/crypto) - PeerID encoding: base64url → lowercase hex (64 chars, YAW/2 §2). - Sign/Verify: hex signatures throughout. - Added CurvePublicKey/CurvePrivateKey: X25519 keys derived from Ed25519 identity via Montgomery conversion (filippo.io/edwards25519), matching libsodium's crypto_sign_ed25519_*_to_curve25519. - Added SignalingBox/SignalingOpen: nacl/box (XSalsa20-Poly1305) seal/open for signaling payloads. Wire types (internal/proto) - ChatMessage gains Mid (random 16-byte hex) for mesh deduplication (§8). - FileChunk removed from PeerMessage — chunks go on a separate binary DataChannel labelled "f:<xid>"; FileDone added. - New types: SignalingPayload, AnchorMessage, HelloMessage, HelloBindString. - PeerID.Short() now returns first 16 hex chars grouped in 4s. - IPC: CmdConnect removed; CmdJoinNetwork/CmdLeaveNetwork added; EvtSessionReady added (fires when DataChannel opens and hello is received). IPC (internal/ipc) - join_network/leave_network replace connect (direct TCP dial). - Network joins are context-scoped: leave_network or client disconnect cancels the anchor connection cleanly. README updated to reflect new project layout, getting-started commands, IPC protocol, and roadmap state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 17:48:14 +02:00
close(writeCh)
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
<-writerDone // wait for writer to flush before conn.Close() fires
Migrate to YAW/2 protocol: WebRTC transport, anchor signaling, nacl/box crypto Replace the original raw-TCP peer connections and Blowfish/RSA-era design with the YAW/2 protocol stack: Transport - internal/mesh/peer.go: raw TCP + manual ECDH replaced with pion/webrtc DataChannels (ICE, DTLS, SCTP). Peers negotiate via sealed offer/answer/ candidate exchange rather than direct dialling. - internal/nat: deleted — ICE subsumes everything this package was going to do. Signaling - cmd/anchor: new WebSocket signaling server (replaces cmd/relay). Verifies Ed25519 challenge/join signatures, routes opaque nacl/box blobs, broadcasts peer-join/peer-leave events. Never sees plaintext signaling content. - internal/anchor: new anchor client. Manages PeerConnection lifecycle, decides offerer by peer-id comparison, seals/opens signaling payloads with nacl/box, implements mesh.Anchor. Crypto (internal/crypto) - PeerID encoding: base64url → lowercase hex (64 chars, YAW/2 §2). - Sign/Verify: hex signatures throughout. - Added CurvePublicKey/CurvePrivateKey: X25519 keys derived from Ed25519 identity via Montgomery conversion (filippo.io/edwards25519), matching libsodium's crypto_sign_ed25519_*_to_curve25519. - Added SignalingBox/SignalingOpen: nacl/box (XSalsa20-Poly1305) seal/open for signaling payloads. Wire types (internal/proto) - ChatMessage gains Mid (random 16-byte hex) for mesh deduplication (§8). - FileChunk removed from PeerMessage — chunks go on a separate binary DataChannel labelled "f:<xid>"; FileDone added. - New types: SignalingPayload, AnchorMessage, HelloMessage, HelloBindString. - PeerID.Short() now returns first 16 hex chars grouped in 4s. - IPC: CmdConnect removed; CmdJoinNetwork/CmdLeaveNetwork added; EvtSessionReady added (fires when DataChannel opens and hello is received). IPC (internal/ipc) - join_network/leave_network replace connect (direct TCP dial). - Network joins are context-scoped: leave_network or client disconnect cancels the anchor connection cleanly. README updated to reflect new project layout, getting-started commands, IPC protocol, and roadmap state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 17:48:14 +02:00
log.Printf("ipc: UI client disconnected")
}
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
// stateSnapshot builds a state_snapshot covering all joined networks.
// Backward compat: local_peer and connected_peers are populated from the first network.
func stateSnapshot(mgr *netmgr.Manager) proto.IpcMessage {
all := mgr.All()
master := mgr.MasterIdentity()
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
msg := proto.IpcMessage{
Type: proto.EvtStateSnapshot,
Rooms: []string{"general"},
MasterAlias: master.Alias,
MasterID: string(master.PeerID()),
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
}
var netInfos []proto.NetworkInfo
for _, n := range all {
pi := n.Identity.PeerInfo()
netInfos = append(netInfos, proto.NetworkInfo{
NetworkID: n.ID,
NetworkName: n.Name,
LocalPeer: &pi,
ShareDir: n.Mesh.ShareDir,
DownloadDir: n.Mesh.DownloadDir,
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
})
}
msg.Networks = netInfos
// Backward-compat fields — populated from the first network when one exists.
if len(all) > 0 {
pi := all[0].Identity.PeerInfo()
msg.LocalPeer = &pi
msg.ConnectedPeers = all[0].Mesh.ConnectedPeers()
if extra, err := all[0].Store.Rooms(); err == nil {
for _, r := range extra {
msg.Rooms = append(msg.Rooms, r)
}
}
// Include all historically-known peers so the UI can resolve aliases in history.
if known, err := all[0].Store.KnownPeers(); err == nil {
connected := map[proto.PeerID]bool{}
for _, p := range msg.ConnectedPeers {
connected[p.ID] = true
}
for id, alias := range known {
if connected[id] {
continue // already in ConnectedPeers
}
msg.KnownPeers = append(msg.KnownPeers, proto.PeerInfo{
ID: id,
Alias: alias,
})
}
}
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
}
return msg
}
// sendStoredHistory pushes recent messages for all known rooms to a newly-connected IPC client.
func sendStoredHistory(mgr *netmgr.Manager, send func(proto.IpcMessage)) {
all := mgr.All()
if len(all) == 0 {
return
}
n := all[0] // use first network; multi-network history follows same pattern
if n.Store == nil {
return
}
rooms := []string{"general"}
if extra, err := n.Store.Rooms(); err == nil {
rooms = append(rooms, extra...)
}
for _, room := range rooms {
msgs, err := n.Store.RecentMessagesSince(room, 0, 200)
if err != nil || len(msgs) == 0 {
continue
}
send(proto.IpcMessage{
Type: proto.EvtHistoryLoaded,
NetworkID: n.ID,
Room: room,
Messages: msgs,
})
// Send stored reactions for this room's messages.
rxns, err := n.Store.ReactionsForRoom(room)
if err != nil {
continue
}
for mid, byEmoji := range rxns {
for emoji, fromPeers := range byEmoji {
for _, fromPeer := range fromPeers {
pid := proto.PeerID(fromPeer)
send(proto.IpcMessage{
Type: proto.EvtReaction,
NetworkID: n.ID,
PeerID: &pid,
ReactionMID: mid,
ReactionEmoji: emoji,
})
}
}
}
}
}
func errMsg(s string) proto.IpcMessage {
return proto.IpcMessage{Type: proto.EvtError, ErrorMessage: s}
}
func sharesListMsg(mgr *netmgr.Manager) proto.IpcMessage {
all := mgr.Shares.All()
entries := make([]proto.ShareEntry, len(all))
for i, sh := range all {
entries[i] = proto.ShareEntry{Path: sh.Path, Networks: sh.Networks}
}
return proto.IpcMessage{Type: proto.EvtSharesList, Shares: entries}
}
// ensure shares import is used
var _ = shares.Share{}
func ptr[T any](v T) *T { return &v }
Migrate to YAW/2 protocol: WebRTC transport, anchor signaling, nacl/box crypto Replace the original raw-TCP peer connections and Blowfish/RSA-era design with the YAW/2 protocol stack: Transport - internal/mesh/peer.go: raw TCP + manual ECDH replaced with pion/webrtc DataChannels (ICE, DTLS, SCTP). Peers negotiate via sealed offer/answer/ candidate exchange rather than direct dialling. - internal/nat: deleted — ICE subsumes everything this package was going to do. Signaling - cmd/anchor: new WebSocket signaling server (replaces cmd/relay). Verifies Ed25519 challenge/join signatures, routes opaque nacl/box blobs, broadcasts peer-join/peer-leave events. Never sees plaintext signaling content. - internal/anchor: new anchor client. Manages PeerConnection lifecycle, decides offerer by peer-id comparison, seals/opens signaling payloads with nacl/box, implements mesh.Anchor. Crypto (internal/crypto) - PeerID encoding: base64url → lowercase hex (64 chars, YAW/2 §2). - Sign/Verify: hex signatures throughout. - Added CurvePublicKey/CurvePrivateKey: X25519 keys derived from Ed25519 identity via Montgomery conversion (filippo.io/edwards25519), matching libsodium's crypto_sign_ed25519_*_to_curve25519. - Added SignalingBox/SignalingOpen: nacl/box (XSalsa20-Poly1305) seal/open for signaling payloads. Wire types (internal/proto) - ChatMessage gains Mid (random 16-byte hex) for mesh deduplication (§8). - FileChunk removed from PeerMessage — chunks go on a separate binary DataChannel labelled "f:<xid>"; FileDone added. - New types: SignalingPayload, AnchorMessage, HelloMessage, HelloBindString. - PeerID.Short() now returns first 16 hex chars grouped in 4s. - IPC: CmdConnect removed; CmdJoinNetwork/CmdLeaveNetwork added; EvtSessionReady added (fires when DataChannel opens and hello is received). IPC (internal/ipc) - join_network/leave_network replace connect (direct TCP dial). - Network joins are context-scoped: leave_network or client disconnect cancels the anchor connection cleanly. README updated to reflect new project layout, getting-started commands, IPC protocol, and roadmap state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 17:48:14 +02:00
func randomHex(n int) string {
b := make([]byte, n)
rand.Read(b) //nolint:errcheck
return hex.EncodeToString(b)
}
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
// autoJoin is called by the daemon when --join is provided at startup.
// It joins the network before the IPC listener starts accepting clients.
func AutoJoin(ctx context.Context, mgr *netmgr.Manager, networkName string) {
_ = ctx // Manager owns the context internally
if _, err := mgr.Join(networkName, ""); err != nil {
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
log.Printf("ipc: auto-join %q failed: %v", networkName, err)
}
}