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:
10
proto/Cargo.toml
Normal file
10
proto/Cargo.toml
Normal 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
227
proto/src/lib.rs
Normal 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 },
|
||||
}
|
||||
Reference in New Issue
Block a user