Skip to main content

vex_cli/proto/
mod.rs

1use serde::{Deserialize, Serialize};
2
3/// Default port vexd listens on for TLS TCP connections.
4pub const DEFAULT_TCP_PORT: u16 = 7422;
5
6/// Default port vexd listens on for HTTPS (HTTP API) connections.
7pub const DEFAULT_HTTP_PORT: u16 = 7423;
8
9// ── Wire types ────────────────────────────────────────────────────────────────
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(tag = "type", content = "data")]
13pub enum Command {
14    Status,
15    Whoami,
16    PairCreate {
17        label: Option<String>,
18        /// Expiry in seconds from now
19        expire_secs: Option<u64>,
20    },
21    PairList,
22    PairRevoke {
23        id: String,
24    },
25    PairRevokeAll,
26    RepoRegister {
27        name: String,
28        path: String,
29    },
30    RepoUnregister {
31        name: String,
32    },
33    RepoList,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37#[serde(tag = "type", content = "data")]
38pub enum Response {
39    Pong,
40    Ok,
41    DaemonStatus(DaemonStatus),
42    ClientInfo(ClientInfo),
43    /// Returned after PairCreate; contains the plaintext secret (one-time)
44    Pair(PairPayload),
45    PairedClient(PairedClient),
46    PairedClients(Vec<PairedClient>),
47    /// Returned by PairRevoke / PairRevokeAll, carrying the revoked count.
48    Revoked(u32),
49    Repo(RepoInfo),
50    Repos(Vec<RepoInfo>),
51    Error(VexProtoError),
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct DaemonStatus {
56    pub uptime_secs: u64,
57    pub connected_clients: u32,
58    pub version: String,
59}
60
61/// Returned by PairCreate — contains the plaintext secret for the new token.
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct PairPayload {
64    pub token_id: String,
65    pub token_secret: String,
66    /// Optional TCP host for encoding into a QR pairing string
67    pub host: Option<String>,
68}
69
70impl PairPayload {
71    /// Returns the pairing string in `<token_id>:<token_secret>` format.
72    pub fn pairing_string(&self) -> String {
73        format!("{}:{}", self.token_id, self.token_secret)
74    }
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct PairedClient {
79    pub token_id: String,
80    pub label: Option<String>,
81    pub created_at: String,
82    pub expires_at: Option<String>,
83    pub last_seen: Option<String>,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct RepoInfo {
88    pub name: String,
89    pub path: String,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct ClientInfo {
94    pub token_id: Option<String>,
95    pub is_local: bool,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
99#[serde(rename_all = "snake_case")]
100pub enum Transport {
101    Unix,
102    Tcp,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106#[serde(tag = "code", content = "message")]
107pub enum VexProtoError {
108    Unauthorized,
109    LocalOnly,
110    NotFound,
111    Internal(String),
112}
113
114/// Sent by the client at the start of every TCP connection before any Command.
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct AuthToken {
117    pub token_id: String,
118    /// Plaintext hex-encoded 32-byte secret
119    pub token_secret: String,
120}
121
122// ── Framing ───────────────────────────────────────────────────────────────────
123
124pub mod framing {
125    use serde::{Deserialize, Serialize};
126    use std::io;
127    use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
128
129    #[derive(Debug)]
130    pub enum VexFrameError {
131        Io(io::Error),
132        Json(serde_json::Error),
133    }
134
135    impl std::fmt::Display for VexFrameError {
136        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137            match self {
138                VexFrameError::Io(e) => write!(f, "IO error: {e}"),
139                VexFrameError::Json(e) => write!(f, "JSON error: {e}"),
140            }
141        }
142    }
143
144    impl std::error::Error for VexFrameError {}
145
146    impl From<io::Error> for VexFrameError {
147        fn from(e: io::Error) -> Self {
148            VexFrameError::Io(e)
149        }
150    }
151
152    impl From<serde_json::Error> for VexFrameError {
153        fn from(e: serde_json::Error) -> Self {
154            VexFrameError::Json(e)
155        }
156    }
157
158    /// Write a length-prefixed JSON frame to `w`.
159    pub async fn send<W, T>(w: &mut W, msg: &T) -> Result<(), VexFrameError>
160    where
161        W: AsyncWrite + Unpin,
162        T: Serialize,
163    {
164        let body = serde_json::to_vec(msg)?;
165        w.write_u32(body.len() as u32).await?;
166        w.write_all(&body).await?;
167        Ok(())
168    }
169
170    /// Read a length-prefixed JSON frame from `r`.
171    pub async fn recv<R, T>(r: &mut R) -> Result<T, VexFrameError>
172    where
173        R: AsyncRead + Unpin,
174        T: for<'de> Deserialize<'de>,
175    {
176        let len = r.read_u32().await?;
177        let mut buf = vec![0u8; len as usize];
178        r.read_exact(&mut buf).await?;
179        Ok(serde_json::from_slice(&buf)?)
180    }
181}