// Package proto defines all wire types shared between the daemon and anchor. // Everything on the wire is newline-delimited JSON. // Binary data (keys, signatures) is hex-encoded; signaling boxes are base64. package proto import "time" // ── Identity ────────────────────────────────────────────────────────────────── // PeerID is a peer's stable identity: lowercase hex of the 32-byte Ed25519 public key (64 chars). // This IS the peer — display names are advisory only and unauthenticated. type PeerID string // Short returns the first 16 hex chars grouped in 4s: "a1b2 c3d4 e5f6 0718". func (p PeerID) Short() string { s := string(p) if len(s) < 16 { return s } return s[0:4] + " " + s[4:8] + " " + s[8:12] + " " + s[12:16] } // PeerInfo is a peer's self-description, included in the hello confirmation. type PeerInfo struct { ID PeerID `json:"id"` Alias string `json:"alias"` // advisory, not authenticated PublicKey string `json:"public_key"` // Ed25519 pubkey, hex CreatedAt time.Time `json:"created_at"` } // ── Peer-to-peer message types (over the "yaw" DataChannel) ────────────────── // MsgType identifies the kind of peer message. type MsgType string const ( MsgChat MsgType = "chat" MsgPeerGossip MsgType = "peer_gossip" MsgFileOffer MsgType = "file_offer" MsgFileResp MsgType = "file_response" MsgFileDone MsgType = "file_done" MsgPing MsgType = "ping" MsgPong MsgType = "pong" ) // PeerMessage is the top-level container sent over the "yaw" DataChannel. // File chunks go over a separate binary DataChannel labeled "f:". type PeerMessage struct { Type MsgType `json:"type"` // Only one of these will be set, depending on Type. Chat *ChatMessage `json:"chat,omitempty"` Gossip *PeerGossip `json:"gossip,omitempty"` FileOffer *FileOffer `json:"file_offer,omitempty"` FileResp *FileResponse `json:"file_response,omitempty"` FileDone *FileDone `json:"file_done,omitempty"` Seq *uint64 `json:"seq,omitempty"` // for ping/pong } // ChatMessage is a message to a room or a DM. type ChatMessage struct { Mid string `json:"mid"` // random 16-byte hex, for deduplication (YAW/2 §8) ID string `json:"id"` // internal uuid, kept for local use From PeerID `json:"from"` To *PeerID `json:"to,omitempty"` // nil = broadcast to room Room string `json:"room"` Body string `json:"body"` SentAt time.Time `json:"sent_at"` } // PeerGossip shares known peer addresses. type PeerGossip struct { Peers []GossipEntry `json:"peers"` } // GossipEntry is one peer hint shared via gossip. type GossipEntry struct { Peer PeerInfo `json:"peer"` AddrHint string `json:"addr_hint"` // IP:port hint, may be behind NAT LastSeen time.Time `json:"last_seen"` } // FileOffer initiates a file transfer. type FileOffer struct { Mid string `json:"mid"` // dedup id Xid string `json:"xid"` // transfer id, used as DataChannel label "f:" Filename string `json:"filename"` SizeBytes int64 `json:"size_bytes"` SHA256 string `json:"sha256"` // hex } // FileResponse accepts or declines a FileOffer. type FileResponse struct { Mid string `json:"mid"` Xid string `json:"xid"` Accepted bool `json:"accepted"` } // FileDone signals that all chunks have been sent. Receiver verifies SHA256. type FileDone struct { Mid string `json:"mid"` Xid string `json:"xid"` SHA256 string `json:"sha256"` // hex } // ── DataChannel hello (YAW/2 §6) ───────────────────────────────────────────── // HelloMessage is the first message sent on the "yaw" DataChannel. // The signature binds this identity to the specific DTLS session. type HelloMessage struct { Type string `json:"type"` // always "hello" ID string `json:"id"` // hex pubkey Nick string `json:"nick"` // alias Caps []string `json:"caps"` // capability list, e.g. ["chat","file"] Sig string `json:"sig"` // hex ed25519 sig over HelloBindString } // HelloBindString returns the bytes the hello signature covers: // "yaw/2 bind" || localDTLSFingerprint(32 bytes) || remoteDTLSFingerprint(32 bytes) func HelloBindString(localFP, remoteFP []byte) []byte { buf := []byte("yaw/2 bind") buf = append(buf, localFP...) buf = append(buf, remoteFP...) return buf } // ── Signaling payload (sealed inside nacl/box, exchanged via anchor) ────────── // SignalingKind identifies the kind of sealed signaling payload. type SignalingKind string const ( SigOffer SignalingKind = "offer" SigAnswer SignalingKind = "answer" SigCandidate SignalingKind = "candidate" SigBye SignalingKind = "bye" ) // SignalingPayload is the JSON plaintext sealed inside a crypto_box (YAW/2 §5). type SignalingPayload struct { Kind SignalingKind `json:"kind"` SDP string `json:"sdp,omitempty"` // offer / answer Cand string `json:"cand,omitempty"` // trickle ICE candidate line Mid string `json:"mid,omitempty"` // media stream id for candidate MLine int `json:"mline,omitempty"` // media line index } // ── Anchor WebSocket wire types (YAW/2 §5) ──────────────────────────────────── // AnchorMsgType identifies anchor WebSocket messages. type AnchorMsgType string const ( AnchorChallenge AnchorMsgType = "challenge" AnchorJoin AnchorMsgType = "join" AnchorJoined AnchorMsgType = "joined" AnchorPeerJoin AnchorMsgType = "peer-join" AnchorPeerLeave AnchorMsgType = "peer-leave" AnchorTo AnchorMsgType = "to" AnchorFrom AnchorMsgType = "from" AnchorNoPeer AnchorMsgType = "no-peer" ) // AnchorMessage covers all WebSocket frames to/from the anchor. type AnchorMessage struct { Type AnchorMsgType `json:"type"` Nonce string `json:"nonce,omitempty"` // challenge nonce, hex ID string `json:"id,omitempty"` // peer hex id Net string `json:"net,omitempty"` // hashed network name Sig string `json:"sig,omitempty"` // ed25519 sig over (nonce||net), hex Peers []string `json:"peers,omitempty"` // joined: list of peer hex ids in network To string `json:"to,omitempty"` // target peer hex id From string `json:"from,omitempty"` // sender peer hex id Box string `json:"box,omitempty"` // base64 nacl/box sealed payload } // ── IPC protocol (daemon ↔ local UI) ───────────────────────────────────────── // IpcMsgType identifies IPC messages. type IpcMsgType string const ( // Commands (UI → daemon) CmdSendMessage IpcMsgType = "send_message" CmdJoinNetwork IpcMsgType = "join_network" // fields: network_name (plaintext) CmdLeaveNetwork IpcMsgType = "leave_network" CmdGetState IpcMsgType = "get_state" CmdSendFile IpcMsgType = "send_file" // Events (daemon → UI) EvtMessageReceived IpcMsgType = "message_received" EvtPeerConnected IpcMsgType = "peer_connected" EvtPeerDisconnected IpcMsgType = "peer_disconnected" EvtSessionReady IpcMsgType = "session_ready" // DataChannel open + hello verified EvtIncomingFile IpcMsgType = "incoming_file" EvtFileProgress IpcMsgType = "file_progress" EvtStateSnapshot IpcMsgType = "state_snapshot" EvtError IpcMsgType = "error" ) // IpcMessage covers both commands and events. type IpcMessage struct { Type IpcMsgType `json:"type"` // send_message Room string `json:"room,omitempty"` To *PeerID `json:"to,omitempty"` Body string `json:"body,omitempty"` // join_network / leave_network NetworkName string `json:"network_name,omitempty"` // send_file Path string `json:"path,omitempty"` // events Peer *PeerInfo `json:"peer,omitempty"` PeerID *PeerID `json:"peer_id,omitempty"` Nick string `json:"nick,omitempty"` Message *ChatMessage `json:"message,omitempty"` Offer *FileOffer `json:"offer,omitempty"` TransferID string `json:"transfer_id,omitempty"` BytesReceived int64 `json:"bytes_received,omitempty"` TotalBytes int64 `json:"total_bytes,omitempty"` LocalPeer *PeerInfo `json:"local_peer,omitempty"` ConnectedPeers []PeerInfo `json:"connected_peers,omitempty"` Rooms []string `json:"rooms,omitempty"` ErrorMessage string `json:"error_message,omitempty"` }