Initial commit: waste-rs mesh daemon, relay, and proto crates

Full Rust implementation with Ed25519 identity, X25519 ECDH handshake,
ChaCha20-Poly1305 encrypted peer connections, IPC server, and relay server.
Builds on Windows (MSVC), Linux, and macOS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Fredrik Johansson
2026-06-21 16:32:36 +02:00
commit d2c06680f7
21 changed files with 2797 additions and 0 deletions

10
proto/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "proto"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
uuid = { workspace = true }
chrono = { workspace = true }

227
proto/src/lib.rs Normal file
View File

@@ -0,0 +1,227 @@
/// Shared protocol types between daemon and relay.
///
/// Everything on the wire is JSON-framed over TCP (length-prefixed u32 BE).
/// All binary blobs (keys, signatures, ciphertext) are base64url encoded.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
// ── Identity ─────────────────────────────────────────────────────────────────
/// A peer's stable identity: their Ed25519 public key, base64url encoded.
/// This IS the peer — display names are advisory only.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PeerId(pub String);
impl std::fmt::Display for PeerId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Show first 8 chars as a short fingerprint in UIs
write!(f, "{}", &self.0[..8.min(self.0.len())])
}
}
/// A peer's self-description, signed with their Ed25519 key.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeerInfo {
pub id: PeerId,
/// Human-readable alias, NOT authenticated — treat as a hint only.
pub alias: String,
/// Ed25519 public key bytes, base64url.
pub public_key: String,
/// When this record was created (used to prefer newer gossip entries).
pub created_at: DateTime<Utc>,
}
// ── Handshake ─────────────────────────────────────────────────────────────────
/// First message sent on a new TCP connection (either direction).
#[derive(Debug, Serialize, Deserialize)]
pub struct Hello {
pub version: u8,
pub peer: PeerInfo,
/// X25519 ephemeral public key for this session, base64url.
pub ephemeral_key: String,
/// Ed25519 signature over (ephemeral_key || peer.id), base64url.
pub signature: String,
}
/// Response to Hello — completes the handshake.
#[derive(Debug, Serialize, Deserialize)]
pub struct HelloAck {
pub peer: PeerInfo,
pub ephemeral_key: String,
pub signature: String,
/// Whether this peer is willing to relay traffic for others.
pub relay_capable: bool,
}
// ── Encrypted envelope ────────────────────────────────────────────────────────
/// All post-handshake messages are wrapped in this envelope.
/// The inner `payload` is ChaCha20-Poly1305 encrypted.
#[derive(Debug, Serialize, Deserialize)]
pub struct Envelope {
/// Random nonce for this message, base64url (12 bytes).
pub nonce: String,
/// Ciphertext, base64url.
pub payload: String,
}
// ── Peer-to-peer messages (inside Envelope) ───────────────────────────────────
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum PeerMessage {
/// Plain chat message to a public room or DM.
Chat(ChatMessage),
/// Gossip: share known peer addresses with a connected peer.
PeerGossip(PeerGossip),
/// File transfer initiation.
FileOffer(FileOffer),
/// Accept or decline a file offer.
FileResponse(FileResponse),
/// A chunk of file data.
FileChunk(FileChunk),
/// Keepalive ping.
Ping { seq: u64 },
/// Keepalive pong.
Pong { seq: u64 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
pub id: Uuid,
pub from: PeerId,
/// None = public room broadcast, Some = DM to that peer.
pub to: Option<PeerId>,
pub room: String,
pub body: String,
pub sent_at: DateTime<Utc>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PeerGossip {
/// Peers this node knows about and their last-seen addresses.
pub peers: Vec<GossipEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GossipEntry {
pub peer: PeerInfo,
/// IP:port hint (may be behind NAT — use as a hole-punch target).
pub addr_hint: String,
pub last_seen: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileOffer {
pub transfer_id: Uuid,
pub filename: String,
pub size_bytes: u64,
pub sha256: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FileResponse {
pub transfer_id: Uuid,
pub accepted: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FileChunk {
pub transfer_id: Uuid,
pub seq: u32,
pub data: String, // base64url
pub is_last: bool,
}
// ── Relay protocol (relay server speaks this) ─────────────────────────────────
/// Messages sent TO the relay server.
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum RelayClientMessage {
/// Register with the relay so other peers can find you.
Register {
peer: PeerInfo,
signature: String,
},
/// Ask the relay to forward an envelope to another peer.
Forward {
to: PeerId,
envelope: Envelope,
},
/// Ask for the relay's current peer list (for bootstrapping).
ListPeers,
}
/// Messages sent FROM the relay server.
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum RelayServerMessage {
/// Forwarded envelope from another peer.
Forwarded {
from: PeerId,
envelope: Envelope,
},
/// Response to ListPeers.
PeerList {
peers: Vec<GossipEntry>,
},
/// General error.
Error { message: String },
}
// ── Local IPC (daemon ↔ UI) ───────────────────────────────────────────────────
/// Commands sent from the UI to the daemon over localhost TCP (port 17337).
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum IpcCommand {
/// Send a chat message.
SendMessage {
room: String,
to: Option<PeerId>,
body: String,
},
/// Offer a file to a peer.
SendFile {
to: PeerId,
path: String,
},
/// Connect to a peer by address hint.
Connect { addr: String },
/// Request current state snapshot.
GetState,
}
/// Events pushed from the daemon to the UI.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum IpcEvent {
/// A chat message arrived.
MessageReceived(ChatMessage),
/// A peer connected.
PeerConnected { peer: PeerInfo },
/// A peer disconnected.
PeerDisconnected { peer_id: PeerId },
/// File offer incoming.
IncomingFileOffer {
from: PeerId,
offer: FileOffer,
},
/// File transfer progress update.
FileProgress {
transfer_id: Uuid,
bytes_received: u64,
total_bytes: u64,
},
/// Full state snapshot (response to GetState).
StateSnapshot {
local_peer: PeerInfo,
connected_peers: Vec<PeerInfo>,
rooms: Vec<String>,
},
/// Generic error event.
Error { message: String },
}