213 lines
7.3 KiB
Go
213 lines
7.3 KiB
Go
|
|
// Package proto defines all wire types shared between the daemon and relay.
|
||
|
|
// Everything on the wire is newline-delimited JSON.
|
||
|
|
// Binary data (keys, signatures, ciphertext) is base64url encoded.
|
||
|
|
package proto
|
||
|
|
|
||
|
|
import "time"
|
||
|
|
|
||
|
|
// ── Identity ──────────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
// PeerID is a peer's stable identity: their Ed25519 public key, base64url encoded.
|
||
|
|
// This IS the peer — display names are advisory only and unauthenticated.
|
||
|
|
type PeerID string
|
||
|
|
|
||
|
|
// Short returns the first 8 characters, useful for display.
|
||
|
|
func (p PeerID) Short() string {
|
||
|
|
if len(p) < 8 {
|
||
|
|
return string(p)
|
||
|
|
}
|
||
|
|
return string(p)[:8]
|
||
|
|
}
|
||
|
|
|
||
|
|
// PeerInfo is a peer's self-description. Included in the Hello handshake.
|
||
|
|
type PeerInfo struct {
|
||
|
|
ID PeerID `json:"id"`
|
||
|
|
Alias string `json:"alias"` // advisory, not authenticated
|
||
|
|
PublicKey string `json:"public_key"` // Ed25519 pubkey, base64url
|
||
|
|
CreatedAt time.Time `json:"created_at"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Handshake ─────────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
// Hello is the first message sent on a new TCP connection.
|
||
|
|
type Hello struct {
|
||
|
|
Version int `json:"version"`
|
||
|
|
Peer PeerInfo `json:"peer"`
|
||
|
|
EphemeralKey string `json:"ephemeral_key"` // X25519 pubkey, base64url
|
||
|
|
Signature string `json:"signature"` // Ed25519 sig over ephemeral_key
|
||
|
|
}
|
||
|
|
|
||
|
|
// HelloAck is the response to Hello, completing the handshake.
|
||
|
|
type HelloAck struct {
|
||
|
|
Peer PeerInfo `json:"peer"`
|
||
|
|
EphemeralKey string `json:"ephemeral_key"`
|
||
|
|
Signature string `json:"signature"`
|
||
|
|
RelayCapable bool `json:"relay_capable"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Encrypted envelope ────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
// Envelope wraps all post-handshake messages.
|
||
|
|
// The payload is ChaCha20-Poly1305 encrypted.
|
||
|
|
type Envelope struct {
|
||
|
|
Nonce string `json:"nonce"` // 12 bytes, base64url
|
||
|
|
Payload string `json:"payload"` // ciphertext, base64url
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Peer-to-peer message types ────────────────────────────────────────────────
|
||
|
|
|
||
|
|
// MsgType identifies the kind of peer message inside an Envelope.
|
||
|
|
type MsgType string
|
||
|
|
|
||
|
|
const (
|
||
|
|
MsgChat MsgType = "chat"
|
||
|
|
MsgPeerGossip MsgType = "peer_gossip"
|
||
|
|
MsgFileOffer MsgType = "file_offer"
|
||
|
|
MsgFileResp MsgType = "file_response"
|
||
|
|
MsgFileChunk MsgType = "file_chunk"
|
||
|
|
MsgPing MsgType = "ping"
|
||
|
|
MsgPong MsgType = "pong"
|
||
|
|
)
|
||
|
|
|
||
|
|
// PeerMessage is the top-level container decoded from inside an Envelope.
|
||
|
|
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"`
|
||
|
|
FileChunk *FileChunk `json:"file_chunk,omitempty"`
|
||
|
|
Seq *uint64 `json:"seq,omitempty"` // for ping/pong
|
||
|
|
}
|
||
|
|
|
||
|
|
// ChatMessage is a message to a room or a DM.
|
||
|
|
type ChatMessage struct {
|
||
|
|
ID string `json:"id"`
|
||
|
|
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, may be behind NAT
|
||
|
|
LastSeen time.Time `json:"last_seen"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// FileOffer initiates a file transfer.
|
||
|
|
type FileOffer struct {
|
||
|
|
TransferID string `json:"transfer_id"`
|
||
|
|
Filename string `json:"filename"`
|
||
|
|
SizeBytes int64 `json:"size_bytes"`
|
||
|
|
SHA256 string `json:"sha256"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// FileResponse accepts or declines a FileOffer.
|
||
|
|
type FileResponse struct {
|
||
|
|
TransferID string `json:"transfer_id"`
|
||
|
|
Accepted bool `json:"accepted"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// FileChunk is one piece of a file transfer.
|
||
|
|
type FileChunk struct {
|
||
|
|
TransferID string `json:"transfer_id"`
|
||
|
|
Seq uint32 `json:"seq"`
|
||
|
|
Data string `json:"data"` // base64url
|
||
|
|
IsLast bool `json:"is_last"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Relay protocol ────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
// RelayMsgType identifies relay wire messages.
|
||
|
|
type RelayMsgType string
|
||
|
|
|
||
|
|
const (
|
||
|
|
RelayRegister RelayMsgType = "register"
|
||
|
|
RelayForward RelayMsgType = "forward"
|
||
|
|
RelayListPeers RelayMsgType = "list_peers"
|
||
|
|
RelayForwarded RelayMsgType = "forwarded"
|
||
|
|
RelayPeerList RelayMsgType = "peer_list"
|
||
|
|
RelayError RelayMsgType = "error"
|
||
|
|
)
|
||
|
|
|
||
|
|
// RelayMessage is used for both client→relay and relay→client.
|
||
|
|
type RelayMessage struct {
|
||
|
|
Type RelayMsgType `json:"type"`
|
||
|
|
|
||
|
|
// register
|
||
|
|
Peer *PeerInfo `json:"peer,omitempty"`
|
||
|
|
Signature string `json:"signature,omitempty"`
|
||
|
|
|
||
|
|
// forward / forwarded
|
||
|
|
To *PeerID `json:"to,omitempty"`
|
||
|
|
From *PeerID `json:"from,omitempty"`
|
||
|
|
Envelope *Envelope `json:"envelope,omitempty"`
|
||
|
|
|
||
|
|
// peer_list
|
||
|
|
Peers []GossipEntry `json:"peers,omitempty"`
|
||
|
|
|
||
|
|
// error
|
||
|
|
Message string `json:"message,omitempty"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── IPC protocol (daemon ↔ local UI) ─────────────────────────────────────────
|
||
|
|
|
||
|
|
// IpcMsgType identifies IPC messages.
|
||
|
|
type IpcMsgType string
|
||
|
|
|
||
|
|
const (
|
||
|
|
// Commands (UI → daemon)
|
||
|
|
CmdSendMessage IpcMsgType = "send_message"
|
||
|
|
CmdConnect IpcMsgType = "connect"
|
||
|
|
CmdGetState IpcMsgType = "get_state"
|
||
|
|
CmdSendFile IpcMsgType = "send_file"
|
||
|
|
|
||
|
|
// Events (daemon → UI)
|
||
|
|
EvtMessageReceived IpcMsgType = "message_received"
|
||
|
|
EvtPeerConnected IpcMsgType = "peer_connected"
|
||
|
|
EvtPeerDisconnected IpcMsgType = "peer_disconnected"
|
||
|
|
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"`
|
||
|
|
|
||
|
|
// connect
|
||
|
|
Addr string `json:"addr,omitempty"`
|
||
|
|
|
||
|
|
// send_file
|
||
|
|
Path string `json:"path,omitempty"`
|
||
|
|
|
||
|
|
// events
|
||
|
|
Peer *PeerInfo `json:"peer,omitempty"`
|
||
|
|
PeerID *PeerID `json:"peer_id,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"`
|
||
|
|
}
|