1use revault_lockbox_api::{LockboxId, SecretVec};
2use std::io;
3
4const MAGIC: &[u8; 4] = b"LBX1";
5const HEADER_LEN: usize = 9;
6const MAX_MESSAGE_BYTES: usize = 128 * 1024;
7
8const REQ_GET: u8 = 0x01;
9const REQ_PUT: u8 = 0x02;
10const REQ_FORGET: u8 = 0x03;
11const REQ_FORGET_ALL: u8 = 0x04;
12const REQ_STOP: u8 = 0x05;
13const REQ_LIST: u8 = 0x06;
14const REQ_INFO: u8 = 0x07;
15const REQ_REGISTER_SECRET_ACTIVITY: u8 = 0x10;
16const REQ_UNREGISTER_SECRET_ACTIVITY: u8 = 0x11;
17
18const RESP_REGISTERED: u8 = 0x80;
19const RESP_OK: u8 = 0x81;
20const RESP_MISS: u8 = 0x82;
21const RESP_KEY: u8 = 0x83;
22const RESP_LIST: u8 = 0x84;
23const RESP_INFO: u8 = 0x85;
24const RESP_ERR: u8 = 0xff;
25
26pub(crate) const DEFAULT_TTL_SECONDS: u64 = 15 * 60;
27pub(crate) const AGENT_PROTOCOL_VERSION: u32 = 1;
28pub(crate) const AGENT_IMPLEMENTATION_VERSION: &str = env!("CARGO_PKG_VERSION");
29
30#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct CachedLockbox {
32 pub id: String,
33 pub path: Option<String>,
34}
35
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub enum SecretActivityKind {
38 Open,
39 Close,
40 Variables,
41 Form,
42 Recovery,
43 Vault,
44}
45
46impl SecretActivityKind {
47 pub fn as_str(self) -> &'static str {
48 match self {
49 Self::Open => "open",
50 Self::Close => "close",
51 Self::Variables => "variables",
52 Self::Form => "form",
53 Self::Recovery => "recovery",
54 Self::Vault => "vault",
55 }
56 }
57
58 fn to_wire(self) -> u8 {
59 match self {
60 Self::Open => 1,
61 Self::Close => 2,
62 Self::Variables => 3,
63 Self::Form => 4,
64 Self::Recovery => 5,
65 Self::Vault => 6,
66 }
67 }
68}
69
70pub(crate) enum AgentRequest {
71 Get(String),
72 Put(String, SecretVec, Option<String>, Option<u64>),
73 Forget(String),
74 ForgetAll,
75 Stop,
76 List,
77 Info,
78}
79
80pub(crate) enum AgentResponse {
81 Ok,
82 Miss,
83 Key(SecretVec),
84 List(Vec<CachedLockbox>),
85 Info(u32, String),
86 Err(String),
87}
88
89pub(crate) enum ControlRequest {
90 RegisterSecretActivity(u32, SecretActivityKind),
91 UnregisterSecretActivity(u32, u64),
92}
93
94pub(crate) enum ControlResponse {
95 Ok,
96 Registered(u64),
97 Err(String),
98}
99
100#[allow(dead_code)]
101pub(crate) fn encode_get(lockbox_id: LockboxId) -> io::Result<SecretVec> {
102 encode_get_identifier(&lockbox_id.to_string())
103}
104
105pub(crate) fn encode_get_identifier(identifier: &str) -> io::Result<SecretVec> {
106 encode_frame(REQ_GET, identifier.as_bytes())
107}
108
109#[allow(dead_code)]
110pub(crate) fn encode_put(
111 lockbox_id: LockboxId,
112 key: &SecretVec,
113 path: Option<&str>,
114 ttl_seconds: Option<u64>,
115) -> io::Result<SecretVec> {
116 let lockbox_id = lockbox_id.to_string();
117 encode_put_identifier(&lockbox_id, key, path, ttl_seconds)
118}
119
120pub(crate) fn encode_put_identifier(
121 identifier: &str,
122 key: &SecretVec,
123 path: Option<&str>,
124 ttl_seconds: Option<u64>,
125) -> io::Result<SecretVec> {
126 let path = path.unwrap_or("");
127 let ttl_seconds = ttl_seconds.unwrap_or(DEFAULT_TTL_SECONDS);
128 let mut payload = SecretVec::new();
129 push_string(&mut payload, identifier)?;
130 push_u32(&mut payload, key.len() as u32)?;
131 push_u32(&mut payload, path.len() as u32)?;
132 push_u64(&mut payload, ttl_seconds)?;
133 payload
134 .try_extend_from_secure(key)
135 .map_err(io::Error::other)?;
136 payload
137 .try_extend_from_slice(path.as_bytes())
138 .map_err(io::Error::other)?;
139 encode_frame_secure(REQ_PUT, &payload)
140}
141
142#[allow(dead_code)]
143pub(crate) fn encode_forget(lockbox_id: LockboxId) -> io::Result<SecretVec> {
144 encode_forget_identifier(&lockbox_id.to_string())
145}
146
147pub(crate) fn encode_forget_identifier(identifier: &str) -> io::Result<SecretVec> {
148 encode_frame(REQ_FORGET, identifier.as_bytes())
149}
150
151pub(crate) fn encode_forget_all() -> io::Result<SecretVec> {
152 encode_frame(REQ_FORGET_ALL, &[])
153}
154
155pub(crate) fn encode_stop() -> io::Result<SecretVec> {
156 encode_frame(REQ_STOP, &[])
157}
158
159pub(crate) fn encode_list() -> io::Result<SecretVec> {
160 encode_frame(REQ_LIST, &[])
161}
162
163pub(crate) fn encode_info() -> io::Result<SecretVec> {
164 encode_frame(REQ_INFO, &[])
165}
166
167pub(crate) fn encode_register_secret_activity(
168 pid: u32,
169 kind: SecretActivityKind,
170) -> io::Result<Vec<u8>> {
171 let mut payload = Vec::with_capacity(5);
172 payload.extend_from_slice(&pid.to_le_bytes());
173 payload.push(kind.to_wire());
174 encode_plain_frame(REQ_REGISTER_SECRET_ACTIVITY, &payload)
175}
176
177pub(crate) fn encode_unregister_secret_activity(pid: u32, token: u64) -> io::Result<Vec<u8>> {
178 let mut payload = Vec::with_capacity(12);
179 payload.extend_from_slice(&pid.to_le_bytes());
180 payload.extend_from_slice(&token.to_le_bytes());
181 encode_plain_frame(REQ_UNREGISTER_SECRET_ACTIVITY, &payload)
182}
183
184pub(crate) fn encode_key_response(key: &SecretVec) -> io::Result<SecretVec> {
185 encode_frame_secure(RESP_KEY, key)
186}
187
188pub(crate) fn encode_ok_response() -> io::Result<SecretVec> {
189 encode_frame(RESP_OK, &[])
190}
191
192pub(crate) fn encode_miss_response() -> io::Result<SecretVec> {
193 encode_frame(RESP_MISS, &[])
194}
195
196pub(crate) fn encode_err_response(message: &str) -> io::Result<SecretVec> {
197 encode_frame(RESP_ERR, message.as_bytes())
198}
199
200pub(crate) fn encode_control_ok_response() -> io::Result<Vec<u8>> {
201 encode_plain_frame(RESP_OK, &[])
202}
203
204pub(crate) fn encode_control_err_response(message: &str) -> io::Result<Vec<u8>> {
205 encode_plain_frame(RESP_ERR, message.as_bytes())
206}
207
208pub(crate) fn encode_registered_response(token: u64) -> io::Result<Vec<u8>> {
209 encode_plain_frame(RESP_REGISTERED, &token.to_le_bytes())
210}
211
212pub(crate) fn encode_list_response(
213 lockboxes: impl Iterator<Item = CachedLockbox>,
214) -> io::Result<SecretVec> {
215 let lockboxes = lockboxes.collect::<Vec<_>>();
216 let mut payload = SecretVec::new();
217 push_u32(&mut payload, lockboxes.len() as u32)?;
218 for lockbox in lockboxes {
219 push_string(&mut payload, &lockbox.id)?;
220 match lockbox.path {
221 Some(path) => push_bytes_u32(&mut payload, path.as_bytes())?,
222 None => push_u32(&mut payload, 0)?,
223 }
224 }
225 encode_frame_secure(RESP_LIST, &payload)
226}
227
228pub(crate) fn encode_info_response() -> io::Result<SecretVec> {
229 let mut payload = SecretVec::new();
230 push_u32(&mut payload, AGENT_PROTOCOL_VERSION)?;
231 push_string(&mut payload, AGENT_IMPLEMENTATION_VERSION)?;
232 encode_frame_secure(RESP_INFO, &payload)
233}
234
235pub(crate) fn parse_request(request: &SecretVec) -> io::Result<AgentRequest> {
236 let parsed = request
237 .with_bytes(parse_request_bytes)
238 .map_err(io::Error::other)??;
239 match parsed {
240 ParsedRequest::Ready(request) => Ok(request),
241 ParsedRequest::Put {
242 lockbox_id,
243 key_offset,
244 key_len,
245 path,
246 ttl_seconds,
247 } => {
248 let key = request
249 .try_clone_range(key_offset, key_len)
250 .map_err(io::Error::other)?;
251 Ok(AgentRequest::Put(lockbox_id, key, path, Some(ttl_seconds)))
252 }
253 }
254}
255
256enum ParsedRequest {
257 Ready(AgentRequest),
258 Put {
259 lockbox_id: String,
260 key_offset: usize,
261 key_len: usize,
262 path: Option<String>,
263 ttl_seconds: u64,
264 },
265}
266
267fn parse_request_bytes(bytes: &[u8]) -> io::Result<ParsedRequest> {
268 let frame = parse_frame(bytes)?;
269 match frame.message_type {
270 REQ_GET => Ok(ParsedRequest::Ready(AgentRequest::Get(
271 read_utf8(frame.payload)?.to_string(),
272 ))),
273 REQ_PUT => parse_put_request(frame.payload),
274 REQ_FORGET => Ok(ParsedRequest::Ready(AgentRequest::Forget(
275 read_utf8(frame.payload)?.to_string(),
276 ))),
277 REQ_FORGET_ALL if frame.payload.is_empty() => {
278 Ok(ParsedRequest::Ready(AgentRequest::ForgetAll))
279 }
280 REQ_STOP if frame.payload.is_empty() => Ok(ParsedRequest::Ready(AgentRequest::Stop)),
281 REQ_LIST if frame.payload.is_empty() => Ok(ParsedRequest::Ready(AgentRequest::List)),
282 REQ_INFO if frame.payload.is_empty() => Ok(ParsedRequest::Ready(AgentRequest::Info)),
283 _ => invalid_data("invalid binary agent request"),
284 }
285}
286
287pub(crate) fn parse_response(response: SecretVec) -> io::Result<AgentResponse> {
288 let parsed = response
289 .with_bytes(parse_response_bytes)
290 .map_err(io::Error::other)??;
291 match parsed {
292 ParsedResponse::Ready(response) => Ok(response),
293 ParsedResponse::Key { offset, len } => {
294 let key = response
295 .try_clone_range(offset, len)
296 .map_err(io::Error::other)?;
297 Ok(AgentResponse::Key(key))
298 }
299 }
300}
301
302enum ParsedResponse {
303 Ready(AgentResponse),
304 Key { offset: usize, len: usize },
305}
306
307fn parse_response_bytes(bytes: &[u8]) -> io::Result<ParsedResponse> {
308 let frame = parse_frame(bytes)?;
309 match frame.message_type {
310 RESP_OK if frame.payload.is_empty() => Ok(ParsedResponse::Ready(AgentResponse::Ok)),
311 RESP_MISS if frame.payload.is_empty() => Ok(ParsedResponse::Ready(AgentResponse::Miss)),
312 RESP_KEY => Ok(ParsedResponse::Key {
313 offset: HEADER_LEN,
314 len: frame.payload.len(),
315 }),
316 RESP_LIST => parse_list_response(frame.payload),
317 RESP_INFO => parse_info_response(frame.payload),
318 RESP_ERR => Ok(ParsedResponse::Ready(AgentResponse::Err(
319 read_utf8(frame.payload)?.to_string(),
320 ))),
321 _ => invalid_data("invalid binary agent response"),
322 }
323}
324
325pub(crate) fn parse_control_request(request: &[u8]) -> io::Result<ControlRequest> {
326 let frame = parse_frame(request)?;
327 match frame.message_type {
328 REQ_REGISTER_SECRET_ACTIVITY => parse_register_secret_activity(frame.payload),
329 REQ_UNREGISTER_SECRET_ACTIVITY => parse_unregister_secret_activity(frame.payload),
330 _ => invalid_data("invalid binary agent control request"),
331 }
332}
333
334pub(crate) fn parse_control_response(response: &[u8]) -> io::Result<ControlResponse> {
335 let frame = parse_frame(response)?;
336 match frame.message_type {
337 RESP_OK if frame.payload.is_empty() => Ok(ControlResponse::Ok),
338 RESP_REGISTERED if frame.payload.len() == 8 => {
339 Ok(ControlResponse::Registered(read_u64(frame.payload)?))
340 }
341 RESP_ERR => Ok(ControlResponse::Err(read_utf8(frame.payload)?.to_string())),
342 _ => invalid_data("invalid binary agent control response"),
343 }
344}
345
346pub(crate) fn max_message_bytes() -> usize {
347 MAX_MESSAGE_BYTES
348}
349
350pub(crate) fn frame_header_len() -> usize {
351 HEADER_LEN
352}
353
354pub(crate) fn frame_payload_len(header: &[u8]) -> io::Result<usize> {
355 if header.len() != HEADER_LEN || &header[..4] != MAGIC {
356 return invalid_data("invalid binary agent frame header");
357 }
358 let len = read_u32_raw(&header[5..9])? as usize;
359 let total = HEADER_LEN
360 .checked_add(len)
361 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "message too large"))?;
362 if total > MAX_MESSAGE_BYTES {
363 return invalid_data("message too large");
364 }
365 Ok(len)
366}
367
368pub(crate) fn frame_message_type(header: &[u8]) -> io::Result<u8> {
369 if header.len() != HEADER_LEN || &header[..4] != MAGIC {
370 return invalid_data("invalid binary agent frame header");
371 }
372 Ok(header[4])
373}
374
375pub(crate) fn is_control_message_type(message_type: u8) -> bool {
376 matches!(
377 message_type,
378 REQ_REGISTER_SECRET_ACTIVITY | REQ_UNREGISTER_SECRET_ACTIVITY
379 )
380}
381
382fn encode_frame(message_type: u8, payload: &[u8]) -> io::Result<SecretVec> {
383 let payload = SecretVec::try_from_slice(payload).map_err(io::Error::other)?;
384 encode_frame_secure(message_type, &payload)
385}
386
387fn encode_plain_frame(message_type: u8, payload: &[u8]) -> io::Result<Vec<u8>> {
388 let total = HEADER_LEN
389 .checked_add(payload.len())
390 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "message too large"))?;
391 if total > MAX_MESSAGE_BYTES {
392 return invalid_data("message too large");
393 }
394 let mut message = Vec::with_capacity(total);
395 message.extend_from_slice(MAGIC);
396 message.push(message_type);
397 message.extend_from_slice(&(payload.len() as u32).to_le_bytes());
398 message.extend_from_slice(payload);
399 Ok(message)
400}
401
402fn encode_frame_secure(message_type: u8, payload: &SecretVec) -> io::Result<SecretVec> {
403 let total = HEADER_LEN
404 .checked_add(payload.len())
405 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "message too large"))?;
406 if total > MAX_MESSAGE_BYTES {
407 return invalid_data("message too large");
408 }
409 let mut message = SecretVec::new();
410 message
411 .try_extend_from_slice(MAGIC)
412 .map_err(io::Error::other)?;
413 message.try_push(message_type).map_err(io::Error::other)?;
414 push_u32(&mut message, payload.len() as u32)?;
415 if !payload.is_empty() {
416 message
417 .try_extend_from_secure(payload)
418 .map_err(io::Error::other)?;
419 }
420 Ok(message)
421}
422
423struct Frame<'a> {
424 message_type: u8,
425 payload: &'a [u8],
426}
427
428fn parse_frame(bytes: &[u8]) -> io::Result<Frame<'_>> {
429 if bytes.len() > MAX_MESSAGE_BYTES {
430 return invalid_data("agent message too large");
431 }
432 if bytes.len() < HEADER_LEN || &bytes[..4] != MAGIC {
433 return invalid_data("invalid binary agent frame");
434 }
435 let payload_len = read_u32_raw(&bytes[5..9])? as usize;
436 if HEADER_LEN + payload_len != bytes.len() {
437 return invalid_data("binary agent frame length mismatch");
438 }
439 Ok(Frame {
440 message_type: bytes[4],
441 payload: &bytes[HEADER_LEN..],
442 })
443}
444
445fn parse_put_request(payload: &[u8]) -> io::Result<ParsedRequest> {
446 let mut cursor = Cursor::new(payload);
447 let lockbox_id = cursor.read_string()?.to_string();
448 let key_len = cursor.read_u32()? as usize;
449 let path_len = cursor.read_u32()? as usize;
450 let ttl_seconds = cursor.read_u64()?;
451 if ttl_seconds == 0 {
452 return invalid_data("ttl must be positive");
453 }
454 let key_offset = cursor.position();
455 let path_offset = key_offset
456 .checked_add(key_len)
457 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "key length overflow"))?;
458 let end = path_offset
459 .checked_add(path_len)
460 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "path length overflow"))?;
461 if end != payload.len() {
462 return invalid_data("put payload length mismatch");
463 }
464 let path = if path_len == 0 {
465 None
466 } else {
467 Some(read_utf8(&payload[path_offset..end])?.to_string())
468 };
469 Ok(ParsedRequest::Put {
470 lockbox_id,
471 key_offset: HEADER_LEN + key_offset,
472 key_len,
473 path,
474 ttl_seconds,
475 })
476}
477
478fn parse_register_secret_activity(payload: &[u8]) -> io::Result<ControlRequest> {
479 if payload.len() != 5 {
480 return invalid_data("invalid secret activity registration length");
481 }
482 Ok(ControlRequest::RegisterSecretActivity(
483 validate_pid(read_u32_raw(&payload[..4])?)?,
484 kind_from_wire(payload[4])?,
485 ))
486}
487
488fn parse_unregister_secret_activity(payload: &[u8]) -> io::Result<ControlRequest> {
489 if payload.len() != 12 {
490 return invalid_data("invalid secret activity unregister length");
491 }
492 Ok(ControlRequest::UnregisterSecretActivity(
493 validate_pid(read_u32_raw(&payload[..4])?)?,
494 read_u64(&payload[4..12])?,
495 ))
496}
497
498fn parse_list_response(payload: &[u8]) -> io::Result<ParsedResponse> {
499 let mut cursor = Cursor::new(payload);
500 let count = cursor.read_u32()? as usize;
501 let mut lockboxes = Vec::with_capacity(count);
502 for _ in 0..count {
503 let id = cursor.read_string()?.to_string();
504 let path = cursor.read_bytes_u32()?;
505 let path = if path.is_empty() {
506 None
507 } else {
508 Some(read_utf8(path)?.to_string())
509 };
510 lockboxes.push(CachedLockbox { id, path });
511 }
512 if !cursor.is_finished() {
513 return invalid_data("list response has trailing bytes");
514 }
515 Ok(ParsedResponse::Ready(AgentResponse::List(lockboxes)))
516}
517
518fn parse_info_response(payload: &[u8]) -> io::Result<ParsedResponse> {
519 let mut cursor = Cursor::new(payload);
520 let protocol = cursor.read_u32()?;
521 let implementation = cursor.read_string()?.to_string();
522 if !cursor.is_finished() {
523 return invalid_data("agent info response has trailing bytes");
524 }
525 Ok(ParsedResponse::Ready(AgentResponse::Info(
526 protocol,
527 implementation,
528 )))
529}
530
531struct Cursor<'a> {
532 bytes: &'a [u8],
533 position: usize,
534}
535
536impl<'a> Cursor<'a> {
537 fn new(bytes: &'a [u8]) -> Self {
538 Self { bytes, position: 0 }
539 }
540
541 fn position(&self) -> usize {
542 self.position
543 }
544
545 fn is_finished(&self) -> bool {
546 self.position == self.bytes.len()
547 }
548
549 fn read_u16(&mut self) -> io::Result<u16> {
550 let bytes = self.read_exact(2)?;
551 Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
552 }
553
554 fn read_u32(&mut self) -> io::Result<u32> {
555 read_u32_raw(self.read_exact(4)?)
556 }
557
558 fn read_u64(&mut self) -> io::Result<u64> {
559 read_u64(self.read_exact(8)?)
560 }
561
562 fn read_string(&mut self) -> io::Result<&'a str> {
563 let len = self.read_u16()? as usize;
564 read_utf8(self.read_exact(len)?)
565 }
566
567 fn read_bytes_u32(&mut self) -> io::Result<&'a [u8]> {
568 let len = self.read_u32()? as usize;
569 self.read_exact(len)
570 }
571
572 fn read_exact(&mut self, len: usize) -> io::Result<&'a [u8]> {
573 let end = self
574 .position
575 .checked_add(len)
576 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "payload length overflow"))?;
577 let bytes = self
578 .bytes
579 .get(self.position..end)
580 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "payload too short"))?;
581 self.position = end;
582 Ok(bytes)
583 }
584}
585
586fn push_string(out: &mut SecretVec, value: &str) -> io::Result<()> {
587 if value.len() > u16::MAX as usize {
588 return invalid_data("string too long");
589 }
590 out.try_extend_from_slice(&(value.len() as u16).to_le_bytes())
591 .map_err(io::Error::other)?;
592 out.try_extend_from_slice(value.as_bytes())
593 .map_err(io::Error::other)
594}
595
596fn push_bytes_u32(out: &mut SecretVec, value: &[u8]) -> io::Result<()> {
597 push_u32(out, value.len() as u32)?;
598 out.try_extend_from_slice(value).map_err(io::Error::other)
599}
600
601fn push_u32(out: &mut SecretVec, value: u32) -> io::Result<()> {
602 out.try_extend_from_slice(&value.to_le_bytes())
603 .map_err(io::Error::other)
604}
605
606fn push_u64(out: &mut SecretVec, value: u64) -> io::Result<()> {
607 out.try_extend_from_slice(&value.to_le_bytes())
608 .map_err(io::Error::other)
609}
610
611fn read_utf8(bytes: &[u8]) -> io::Result<&str> {
612 std::str::from_utf8(bytes)
613 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "payload is not UTF-8"))
614}
615
616fn read_u32_raw(bytes: &[u8]) -> io::Result<u32> {
617 let bytes: [u8; 4] = bytes
618 .try_into()
619 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid u32 length"))?;
620 Ok(u32::from_le_bytes(bytes))
621}
622
623fn read_u64(bytes: &[u8]) -> io::Result<u64> {
624 let bytes: [u8; 8] = bytes
625 .try_into()
626 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid u64 length"))?;
627 Ok(u64::from_le_bytes(bytes))
628}
629
630fn validate_pid(pid: u32) -> io::Result<u32> {
631 if pid == 0 {
632 return invalid_data("pid must be positive");
633 }
634 Ok(pid)
635}
636
637fn kind_from_wire(value: u8) -> io::Result<SecretActivityKind> {
638 match value {
639 1 => Ok(SecretActivityKind::Open),
640 2 => Ok(SecretActivityKind::Close),
641 3 => Ok(SecretActivityKind::Variables),
642 4 => Ok(SecretActivityKind::Form),
643 5 => Ok(SecretActivityKind::Recovery),
644 6 => Ok(SecretActivityKind::Vault),
645 _ => invalid_data("invalid secret activity kind"),
646 }
647}
648
649fn invalid_data<T>(message: &str) -> io::Result<T> {
650 Err(io::Error::new(io::ErrorKind::InvalidData, message))
651}
652
653#[cfg(test)]
654mod tests {
655 use super::*;
656
657 #[test]
658 fn protocol_rejects_line_based_and_oversized_requests() {
659 let request = SecretVec::try_from_slice(b"GET vault\n").unwrap();
660 assert!(parse_request(&request).is_err());
661 let request = SecretVec::try_from_slice(&vec![b'x'; MAX_MESSAGE_BYTES + 1]).unwrap();
662 assert!(parse_request(&request).is_err());
663 }
664
665 #[test]
666 fn protocol_parses_put_and_cache_commands() {
667 let lockbox_id = LockboxId::from_bytes([1; 16]);
668 let key = SecretVec::try_from_slice(b"abc").unwrap();
669 let request = encode_put(lockbox_id, &key, Some("/tmp/a.lbox"), Some(30)).unwrap();
670 request
671 .with_bytes(|request| {
672 assert!(!request
673 .windows(b"616263".len())
674 .any(|window| window == b"616263"));
675 })
676 .unwrap();
677 match parse_request(&request).unwrap() {
678 AgentRequest::Put(id, parsed_key, path, ttl_seconds) => {
679 assert_eq!(id, lockbox_id.to_string());
680 parsed_key
681 .with_bytes(|key| assert_eq!(key, b"abc"))
682 .unwrap();
683 assert_eq!(path.as_deref(), Some("/tmp/a.lbox"));
684 assert_eq!(ttl_seconds, Some(30));
685 }
686 _ => panic!("expected PUT"),
687 }
688
689 assert!(matches!(
690 parse_request(&encode_forget_all().unwrap()).unwrap(),
691 AgentRequest::ForgetAll
692 ));
693 assert!(matches!(
694 parse_request(&encode_stop().unwrap()).unwrap(),
695 AgentRequest::Stop
696 ));
697 assert!(matches!(
698 parse_request(&encode_list().unwrap()).unwrap(),
699 AgentRequest::List
700 ));
701 assert!(matches!(
702 parse_request(&encode_info().unwrap()).unwrap(),
703 AgentRequest::Info
704 ));
705 }
706
707 #[test]
708 fn protocol_parses_list_and_activity_responses() {
709 let response = encode_list_response(
710 [
711 CachedLockbox {
712 id: "a".to_string(),
713 path: Some("/tmp/a.lbox".to_string()),
714 },
715 CachedLockbox {
716 id: "b".to_string(),
717 path: None,
718 },
719 ]
720 .into_iter(),
721 )
722 .unwrap();
723 match parse_response(response).unwrap() {
724 AgentResponse::List(ids) => {
725 assert_eq!(ids[0].id, "a");
726 assert_eq!(ids[0].path.as_deref(), Some("/tmp/a.lbox"));
727 assert_eq!(ids[1].id, "b");
728 assert_eq!(ids[1].path, None);
729 }
730 _ => panic!("expected LIST"),
731 }
732
733 let request = encode_register_secret_activity(42, SecretActivityKind::Open).unwrap();
734 match parse_control_request(&request).unwrap() {
735 ControlRequest::RegisterSecretActivity(42, SecretActivityKind::Open) => {}
736 _ => panic!("expected activity registration"),
737 }
738 let response = encode_registered_response(123).unwrap();
739 assert!(matches!(
740 parse_control_response(&response).unwrap(),
741 ControlResponse::Registered(123)
742 ));
743 }
744
745 #[test]
746 fn protocol_reports_agent_compatibility() {
747 match parse_response(encode_info_response().unwrap()).unwrap() {
748 AgentResponse::Info(protocol, implementation) => {
749 assert_eq!(protocol, AGENT_PROTOCOL_VERSION);
750 assert_eq!(implementation, AGENT_IMPLEMENTATION_VERSION);
751 }
752 _ => panic!("expected agent info response"),
753 }
754 }
755}