Skip to main content

trustless_protocol/
message.rs

1use serde_with::serde_as;
2
3/// A protocol request message.
4///
5/// Internally tagged by `method`, with `id` repeated in each variant.
6#[derive(serde::Serialize, serde::Deserialize, Debug)]
7#[serde(tag = "method")]
8pub enum Request {
9    #[serde(rename = "initialize")]
10    Initialize { id: u64, params: InitializeParams },
11    #[serde(rename = "sign")]
12    Sign { id: u64, params: SignParams },
13}
14
15impl Request {
16    pub fn id(&self) -> u64 {
17        match self {
18            Request::Initialize { id, .. } => *id,
19            Request::Sign { id, .. } => *id,
20        }
21    }
22}
23
24/// Parameters for the `initialize` method. Currently empty.
25#[derive(serde::Serialize, serde::Deserialize, Debug)]
26pub struct InitializeParams {}
27
28/// Parameters for the `sign` method.
29#[serde_as]
30#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
31pub struct SignParams {
32    /// The certificate ID (from [`InitializeResult`]) identifying which key to use.
33    pub certificate_id: String,
34    /// The signature scheme name (e.g., `"ECDSA_NISTP256_SHA256"`).
35    pub scheme: String,
36    /// The data to sign. Base64-encoded on the wire.
37    #[serde_as(as = "serde_with::base64::Base64")]
38    pub blob: Vec<u8>,
39}
40
41/// A successful protocol response, internally tagged by `method`.
42#[derive(serde::Serialize, serde::Deserialize, Debug)]
43#[serde(tag = "method")]
44pub enum SuccessResponse {
45    #[serde(rename = "initialize")]
46    Initialize { id: u64, result: InitializeResult },
47    #[serde(rename = "sign")]
48    Sign { id: u64, result: SignResult },
49}
50
51/// An error response with no method tag.
52#[derive(serde::Serialize, serde::Deserialize, Debug)]
53pub struct ErrorResponse {
54    pub id: u64,
55    pub error: ErrorPayload,
56}
57
58/// A protocol response message — either a tagged success or an error.
59#[derive(serde::Serialize, serde::Deserialize, Debug)]
60#[serde(untagged)]
61pub enum Response {
62    Success(SuccessResponse),
63    Error(ErrorResponse),
64}
65
66impl From<SuccessResponse> for Response {
67    fn from(s: SuccessResponse) -> Self {
68        Response::Success(s)
69    }
70}
71
72impl From<ErrorResponse> for Response {
73    fn from(e: ErrorResponse) -> Self {
74        Response::Error(e)
75    }
76}
77
78impl Response {
79    pub fn id(&self) -> u64 {
80        match self {
81            Response::Success(SuccessResponse::Initialize { id, .. }) => *id,
82            Response::Success(SuccessResponse::Sign { id, .. }) => *id,
83            Response::Error(ErrorResponse { id, .. }) => *id,
84        }
85    }
86
87    pub fn initialize(id: u64, result: Result<InitializeResult, ErrorPayload>) -> Self {
88        match result {
89            Ok(result) => SuccessResponse::Initialize { id, result }.into(),
90            Err(error) => ErrorResponse { id, error }.into(),
91        }
92    }
93
94    pub fn sign(id: u64, result: Result<SignResult, ErrorPayload>) -> Self {
95        match result {
96            Ok(result) => SuccessResponse::Sign { id, result }.into(),
97            Err(error) => ErrorResponse { id, error }.into(),
98        }
99    }
100}
101
102/// An error payload with a numeric code and human-readable message.
103#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
104pub struct ErrorPayload {
105    /// Error code. Conventional values: 1 = cert not found, 2 = unsupported scheme, 3 = signing failed.
106    pub code: i64,
107    /// Human-readable error description.
108    pub message: String,
109}
110
111/// Result of the `initialize` method.
112#[derive(serde::Serialize, serde::Deserialize, Debug)]
113pub struct InitializeResult {
114    /// Certificate ID to use as the default when no SNI matches.
115    pub default: String,
116    /// All certificates available from this provider.
117    pub certificates: Vec<CertificateInfo>,
118}
119
120/// Metadata for a single certificate returned during initialization.
121#[derive(serde::Serialize, serde::Deserialize, Debug)]
122pub struct CertificateInfo {
123    /// Unique identifier for this certificate. Used in `sign` requests.
124    pub id: String,
125    /// DNS Subject Alternative Names the certificate covers (e.g., `["*.example.com"]`).
126    pub domains: Vec<String>,
127    /// Full certificate chain in PEM format (leaf first, then intermediates).
128    pub pem: String,
129    /// Supported signature scheme names (e.g., `["ECDSA_NISTP256_SHA256"]`).
130    /// Strongly recommended — certificates without valid schemes are skipped.
131    #[serde(default)]
132    pub schemes: Vec<String>,
133}
134
135/// Result of the `sign` method.
136#[serde_with::serde_as]
137#[derive(serde::Serialize, serde::Deserialize, Debug)]
138pub struct SignResult {
139    /// The signature bytes. Base64-encoded on the wire.
140    #[serde_as(as = "serde_with::base64::Base64")]
141    pub signature: Vec<u8>,
142}
143
144#[cfg(test)]
145mod tests {
146    #[derive(serde::Deserialize, Debug)]
147    struct WireRequest {
148        id: u64,
149        method: String,
150        #[allow(dead_code)]
151        params: serde_json::Value,
152    }
153
154    #[test]
155    fn serialize_initialize_request() {
156        let req = super::Request::Initialize {
157            id: 1,
158            params: super::InitializeParams {},
159        };
160        let json = serde_json::to_string(&req).unwrap();
161        let wire: WireRequest = serde_json::from_str(&json).unwrap();
162        assert_eq!(wire.id, 1);
163        assert_eq!(wire.method, "initialize");
164    }
165
166    #[test]
167    fn serialize_sign_request() {
168        let req = super::Request::Sign {
169            id: 42,
170            params: super::SignParams {
171                certificate_id: "cert/v1".to_owned(),
172                scheme: "ECDSA_NISTP256_SHA256".to_owned(),
173                blob: vec![0xde, 0xad, 0xbe, 0xef],
174            },
175        };
176        let json = serde_json::to_string(&req).unwrap();
177        let wire: WireRequest = serde_json::from_str(&json).unwrap();
178        assert_eq!(wire.id, 42);
179        assert_eq!(wire.method, "sign");
180        let params: super::SignParams = serde_json::from_value(wire.params).unwrap();
181        assert_eq!(params.certificate_id, "cert/v1");
182        assert_eq!(params.scheme, "ECDSA_NISTP256_SHA256");
183        assert_eq!(params.blob, vec![0xde, 0xad, 0xbe, 0xef]);
184    }
185
186    #[test]
187    fn deserialize_initialize_request() {
188        let json = r#"{"id":5,"method":"initialize","params":{}}"#;
189        let req: super::Request = serde_json::from_str(json).unwrap();
190        assert_eq!(req.id(), 5);
191        assert!(matches!(req, super::Request::Initialize { .. }));
192    }
193
194    #[test]
195    fn deserialize_sign_request() {
196        let json = r#"{"id":7,"method":"sign","params":{"certificate_id":"c1","scheme":"ED25519","blob":"AQID"}}"#;
197        let req: super::Request = serde_json::from_str(json).unwrap();
198        assert_eq!(req.id(), 7);
199        match req {
200            super::Request::Sign { params, .. } => {
201                assert_eq!(params.certificate_id, "c1");
202                assert_eq!(params.scheme, "ED25519");
203                assert_eq!(params.blob, vec![1, 2, 3]);
204            }
205            _ => panic!("expected Sign"),
206        }
207    }
208
209    #[test]
210    fn request_round_trip() {
211        let req = super::Request::Sign {
212            id: 10,
213            params: super::SignParams {
214                certificate_id: "cert/v1".to_owned(),
215                scheme: "ECDSA_NISTP256_SHA256".to_owned(),
216                blob: vec![0xde, 0xad],
217            },
218        };
219        let json = serde_json::to_string(&req).unwrap();
220        let decoded: super::Request = serde_json::from_str(&json).unwrap();
221        assert_eq!(decoded.id(), 10);
222        match decoded {
223            super::Request::Sign { params, .. } => {
224                assert_eq!(params.certificate_id, "cert/v1");
225                assert_eq!(params.blob, vec![0xde, 0xad]);
226            }
227            _ => panic!("expected Sign"),
228        }
229    }
230
231    #[test]
232    fn serialize_initialize_result_response() {
233        let resp = super::Response::Success(super::SuccessResponse::Initialize {
234            id: 1,
235            result: super::InitializeResult {
236                default: "cert1".to_owned(),
237                certificates: vec![super::CertificateInfo {
238                    id: "cert1".to_owned(),
239                    domains: vec!["*.example.com".to_owned()],
240                    pem: "PEM DATA".to_owned(),
241                    schemes: vec!["ECDSA_NISTP256_SHA256".to_owned()],
242                }],
243            },
244        });
245        let json = serde_json::to_string(&resp).unwrap();
246        assert!(json.contains("\"method\":\"initialize\""));
247        assert!(json.contains("\"result\""));
248        assert!(!json.contains("\"error\""));
249        let v: serde_json::Value = serde_json::from_str(&json).unwrap();
250        assert_eq!(v["id"], 1);
251        assert_eq!(v["method"], "initialize");
252        assert_eq!(v["result"]["default"], "cert1");
253    }
254
255    #[test]
256    fn serialize_sign_result_response() {
257        let resp = super::Response::Success(super::SuccessResponse::Sign {
258            id: 2,
259            result: super::SignResult {
260                signature: vec![0xff, 0x00, 0xab],
261            },
262        });
263        let json = serde_json::to_string(&resp).unwrap();
264        assert!(json.contains("\"method\":\"sign\""));
265        let v: serde_json::Value = serde_json::from_str(&json).unwrap();
266        assert_eq!(v["id"], 2);
267        assert_eq!(v["method"], "sign");
268    }
269
270    #[test]
271    fn serialize_error_response() {
272        let resp = super::Response::Error(super::ErrorResponse {
273            id: 3,
274            error: super::ErrorPayload {
275                code: 1,
276                message: "not found".to_owned(),
277            },
278        });
279        let json = serde_json::to_string(&resp).unwrap();
280        let v: serde_json::Value = serde_json::from_str(&json).unwrap();
281        assert_eq!(v["id"], 3);
282        assert_eq!(v["error"]["code"], 1);
283        assert_eq!(v["error"]["message"], "not found");
284        assert!(!json.contains("\"result\""));
285        assert!(!json.contains("\"method\""));
286    }
287
288    #[test]
289    fn deserialize_result_response() {
290        let json = r#"{"id":1,"method":"initialize","result":{"default":"c1","certificates":[{"id":"c1","domains":["*.test"],"pem":"---"}]}}"#;
291        let resp: super::Response = serde_json::from_str(json).unwrap();
292        assert_eq!(resp.id(), 1);
293        match resp {
294            super::Response::Success(super::SuccessResponse::Initialize { result, .. }) => {
295                assert_eq!(result.default, "c1");
296                assert_eq!(result.certificates.len(), 1);
297            }
298            _ => panic!("expected Initialize Result"),
299        }
300    }
301
302    #[test]
303    fn deserialize_error_response() {
304        let json = r#"{"id":2,"error":{"code":99,"message":"boom"}}"#;
305        let resp: super::Response = serde_json::from_str(json).unwrap();
306        assert_eq!(resp.id(), 2);
307        match resp {
308            super::Response::Error(super::ErrorResponse { error, .. }) => {
309                assert_eq!(error.code, 99);
310                assert_eq!(error.message, "boom");
311            }
312            _ => panic!("expected Error"),
313        }
314    }
315
316    #[test]
317    fn sign_params_blob_round_trip() {
318        let params = super::SignParams {
319            certificate_id: "x".to_owned(),
320            scheme: "RSA_PSS_SHA256".to_owned(),
321            blob: (0..=255).collect(),
322        };
323        let json = serde_json::to_string(&params).unwrap();
324        let decoded: super::SignParams = serde_json::from_str(&json).unwrap();
325        assert_eq!(decoded.blob, params.blob);
326    }
327}