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    WorkstreamCreate {
35        repo_name: String,
36        name: String,
37    },
38    WorkstreamList {
39        repo_name: String,
40    },
41    WorkstreamDelete {
42        repo_name: String,
43        name: String,
44    },
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct WorkstreamInfo {
49    pub name: String,
50    pub repo_name: String,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(tag = "type", content = "data")]
55pub enum Response {
56    Pong,
57    Ok,
58    DaemonStatus(DaemonStatus),
59    ClientInfo(ClientInfo),
60    /// Returned after PairCreate; contains the plaintext secret (one-time)
61    Pair(PairPayload),
62    PairedClient(PairedClient),
63    PairedClients(Vec<PairedClient>),
64    /// Returned by PairRevoke / PairRevokeAll, carrying the revoked count.
65    Revoked(u32),
66    Repo(RepoInfo),
67    Repos(Vec<RepoInfo>),
68    Workstream(WorkstreamInfo),
69    Workstreams(Vec<WorkstreamInfo>),
70    Error(VexProtoError),
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct DaemonStatus {
75    pub uptime_secs: u64,
76    pub connected_clients: u32,
77    pub version: String,
78}
79
80/// Returned by PairCreate — contains the plaintext secret for the new token.
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct PairPayload {
83    pub token_id: String,
84    pub token_secret: String,
85    /// Optional TCP host for encoding into a QR pairing string
86    pub host: Option<String>,
87}
88
89impl PairPayload {
90    /// Returns the pairing string in `<token_id>:<token_secret>` format.
91    pub fn pairing_string(&self) -> String {
92        format!("{}:{}", self.token_id, self.token_secret)
93    }
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct PairedClient {
98    pub token_id: String,
99    pub label: Option<String>,
100    pub created_at: String,
101    pub expires_at: Option<String>,
102    pub last_seen: Option<String>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct RepoInfo {
107    pub name: String,
108    pub path: String,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct ClientInfo {
113    pub token_id: Option<String>,
114    pub is_local: bool,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
118#[serde(rename_all = "snake_case")]
119pub enum Transport {
120    Unix,
121    Tcp,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125#[serde(tag = "code", content = "message")]
126pub enum VexProtoError {
127    Unauthorized,
128    LocalOnly,
129    NotFound,
130    Internal(String),
131}
132
133/// Sent by the client at the start of every TCP connection before any Command.
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct AuthToken {
136    pub token_id: String,
137    /// Plaintext hex-encoded 32-byte secret
138    pub token_secret: String,
139}
140
141// ── Framing ───────────────────────────────────────────────────────────────────
142
143pub mod framing {
144    use serde::{Deserialize, Serialize};
145    use std::io;
146    use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
147
148    #[derive(Debug)]
149    pub enum VexFrameError {
150        Io(io::Error),
151        Json(serde_json::Error),
152    }
153
154    impl std::fmt::Display for VexFrameError {
155        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156            match self {
157                VexFrameError::Io(e) => write!(f, "IO error: {e}"),
158                VexFrameError::Json(e) => write!(f, "JSON error: {e}"),
159            }
160        }
161    }
162
163    impl std::error::Error for VexFrameError {}
164
165    impl From<io::Error> for VexFrameError {
166        fn from(e: io::Error) -> Self {
167            VexFrameError::Io(e)
168        }
169    }
170
171    impl From<serde_json::Error> for VexFrameError {
172        fn from(e: serde_json::Error) -> Self {
173            VexFrameError::Json(e)
174        }
175    }
176
177    /// Write a length-prefixed JSON frame to `w`.
178    pub async fn send<W, T>(w: &mut W, msg: &T) -> Result<(), VexFrameError>
179    where
180        W: AsyncWrite + Unpin,
181        T: Serialize,
182    {
183        let body = serde_json::to_vec(msg)?;
184        w.write_u32(body.len() as u32).await?;
185        w.write_all(&body).await?;
186        Ok(())
187    }
188
189    /// Read a length-prefixed JSON frame from `r`.
190    pub async fn recv<R, T>(r: &mut R) -> Result<T, VexFrameError>
191    where
192        R: AsyncRead + Unpin,
193        T: for<'de> Deserialize<'de>,
194    {
195        let len = r.read_u32().await?;
196        let mut buf = vec![0u8; len as usize];
197        r.read_exact(&mut buf).await?;
198        Ok(serde_json::from_slice(&buf)?)
199    }
200}