|
| 1 | +//! Rynk wire-format message view. |
| 2 | +//! |
| 3 | +//! Fixed 5-byte header followed by a postcard-encoded payload: |
| 4 | +//! |
| 5 | +//! ```text |
| 6 | +//! ┌──────────────┬───────┬──────────────┐ |
| 7 | +//! │ CMD u16 LE │SEQ u8 │ LEN u16 LE │ ← 5-byte header |
| 8 | +//! ├──────────────┴───────┴──────────────┤ |
| 9 | +//! │ payload bytes ... │ |
| 10 | +//! └─────────────────────────────────────┘ |
| 11 | +//! ``` |
| 12 | +
|
| 13 | +use super::RynkError; |
| 14 | +use super::cmd::Cmd; |
| 15 | + |
| 16 | +/// Size in bytes of the fixed Rynk header. |
| 17 | +pub const RYNK_HEADER_SIZE: usize = 5; |
| 18 | + |
| 19 | +/// Message operations for Rynk |
| 20 | +pub trait RynkMessage { |
| 21 | + fn cmd(&self) -> Result<Cmd, RynkError>; |
| 22 | + fn seq(&self) -> Result<u8, RynkError>; |
| 23 | + fn payload_len(&self) -> Result<u16, RynkError>; |
| 24 | + fn payload(&self) -> Result<&[u8], RynkError>; |
| 25 | + fn payload_mut(&mut self) -> Result<&mut [u8], RynkError>; |
| 26 | + |
| 27 | + fn set_cmd(&mut self, cmd: Cmd) -> Result<(), RynkError>; |
| 28 | + fn set_seq(&mut self, seq: u8) -> Result<(), RynkError>; |
| 29 | + fn set_payload_len(&mut self, len: u16) -> Result<(), RynkError>; |
| 30 | +} |
| 31 | + |
| 32 | +impl RynkMessage for [u8] { |
| 33 | + fn cmd(&self) -> Result<Cmd, RynkError> { |
| 34 | + if self.len() < RYNK_HEADER_SIZE { |
| 35 | + return Err(RynkError::InvalidRequest); |
| 36 | + } |
| 37 | + Cmd::from_repr(u16::from_le_bytes([self[0], self[1]])).ok_or(RynkError::InvalidRequest) |
| 38 | + } |
| 39 | + |
| 40 | + fn seq(&self) -> Result<u8, RynkError> { |
| 41 | + if self.len() < RYNK_HEADER_SIZE { |
| 42 | + return Err(RynkError::InvalidRequest); |
| 43 | + } |
| 44 | + Ok(self[2]) |
| 45 | + } |
| 46 | + |
| 47 | + fn payload_len(&self) -> Result<u16, RynkError> { |
| 48 | + if self.len() < RYNK_HEADER_SIZE { |
| 49 | + return Err(RynkError::InvalidRequest); |
| 50 | + } |
| 51 | + Ok(u16::from_le_bytes([self[3], self[4]])) |
| 52 | + } |
| 53 | + |
| 54 | + fn payload(&self) -> Result<&[u8], RynkError> { |
| 55 | + if self.len() < RYNK_HEADER_SIZE { |
| 56 | + return Err(RynkError::InvalidRequest); |
| 57 | + } |
| 58 | + Ok(&self[RYNK_HEADER_SIZE..]) |
| 59 | + } |
| 60 | + |
| 61 | + fn payload_mut(&mut self) -> Result<&mut [u8], RynkError> { |
| 62 | + if self.len() < RYNK_HEADER_SIZE { |
| 63 | + return Err(RynkError::InvalidRequest); |
| 64 | + } |
| 65 | + Ok(&mut self[RYNK_HEADER_SIZE..]) |
| 66 | + } |
| 67 | + |
| 68 | + fn set_cmd(&mut self, cmd: Cmd) -> Result<(), RynkError> { |
| 69 | + if self.len() < RYNK_HEADER_SIZE { |
| 70 | + return Err(RynkError::InvalidRequest); |
| 71 | + } |
| 72 | + self[0..2].copy_from_slice(&(cmd as u16).to_le_bytes()); |
| 73 | + Ok(()) |
| 74 | + } |
| 75 | + |
| 76 | + fn set_seq(&mut self, seq: u8) -> Result<(), RynkError> { |
| 77 | + if self.len() < RYNK_HEADER_SIZE { |
| 78 | + return Err(RynkError::InvalidRequest); |
| 79 | + } |
| 80 | + self[2] = seq; |
| 81 | + Ok(()) |
| 82 | + } |
| 83 | + |
| 84 | + fn set_payload_len(&mut self, len: u16) -> Result<(), RynkError> { |
| 85 | + if self.len() < RYNK_HEADER_SIZE { |
| 86 | + return Err(RynkError::InvalidRequest); |
| 87 | + } |
| 88 | + self[3..5].copy_from_slice(&len.to_le_bytes()); |
| 89 | + Ok(()) |
| 90 | + } |
| 91 | +} |
| 92 | + |
| 93 | +#[cfg(test)] |
| 94 | +mod tests { |
| 95 | + use super::*; |
| 96 | + |
| 97 | + #[test] |
| 98 | + fn round_trip_header_fields() { |
| 99 | + let mut buf = [0u8; RYNK_HEADER_SIZE + 7]; |
| 100 | + buf.set_cmd(Cmd::GetVersion).unwrap(); |
| 101 | + buf.set_seq(0x42).unwrap(); |
| 102 | + buf.set_payload_len(7).unwrap(); |
| 103 | + buf.payload_mut().unwrap()[..7].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7]); |
| 104 | + |
| 105 | + assert_eq!(buf.cmd().unwrap(), Cmd::GetVersion); |
| 106 | + assert_eq!(buf.seq().unwrap(), 0x42); |
| 107 | + assert_eq!(buf.payload_len().unwrap(), 7); |
| 108 | + assert_eq!(buf.payload().unwrap(), &[1, 2, 3, 4, 5, 6, 7]); |
| 109 | + } |
| 110 | + |
| 111 | + #[test] |
| 112 | + fn cmd_rejects_short_buffer() { |
| 113 | + let buf = [0u8; RYNK_HEADER_SIZE - 1]; |
| 114 | + assert_eq!(buf.cmd(), Err(RynkError::InvalidRequest)); |
| 115 | + } |
| 116 | + |
| 117 | + #[test] |
| 118 | + fn cmd_rejects_unknown_discriminant() { |
| 119 | + let mut buf = [0u8; RYNK_HEADER_SIZE]; |
| 120 | + buf[0..2].copy_from_slice(&0xFFFFu16.to_le_bytes()); |
| 121 | + assert_eq!(buf.cmd(), Err(RynkError::InvalidRequest)); |
| 122 | + } |
| 123 | + |
| 124 | + #[test] |
| 125 | + fn every_accessor_rejects_short_buffer() { |
| 126 | + let mut buf = [0u8; RYNK_HEADER_SIZE - 1]; |
| 127 | + assert_eq!(buf.seq(), Err(RynkError::InvalidRequest)); |
| 128 | + assert_eq!(buf.payload_len(), Err(RynkError::InvalidRequest)); |
| 129 | + assert!(buf.payload().is_err()); |
| 130 | + assert!(buf.payload_mut().is_err()); |
| 131 | + assert_eq!(buf.set_cmd(Cmd::GetVersion), Err(RynkError::InvalidRequest)); |
| 132 | + assert_eq!(buf.set_seq(0), Err(RynkError::InvalidRequest)); |
| 133 | + assert_eq!(buf.set_payload_len(0), Err(RynkError::InvalidRequest)); |
| 134 | + } |
| 135 | +} |
0 commit comments