Skip to main content

leodos_protocols/misc/sle/
isp1.rs

1//! ISP1 (Internet SLE Protocol 1) transport layer.
2//!
3//! Each SLE PDU is framed with a 4-byte big-endian length prefix
4//! for transmission over TCP. This module provides the framing
5//! types and authentication credential structures — actual TCP I/O
6//! is left to the caller.
7
8use super::types::SleError;
9
10/// ISP1 framing header size: 4-byte big-endian length prefix.
11pub const ISP1_HEADER_SIZE: usize = 4;
12
13/// An ISP1 length-prefixed frame.
14///
15/// The wire format is simply `[u32 BE length][payload]` where
16/// `length` is the number of payload bytes (not including itself).
17pub struct Isp1Frame;
18
19impl Isp1Frame {
20    /// Encodes an ISP1 frame by writing the 4-byte length prefix
21    /// followed by the payload into `buf`.
22    ///
23    /// Returns the total number of bytes written
24    /// (4 + payload.len()).
25    pub fn encode(
26        payload: &[u8],
27        buf: &mut [u8],
28    ) -> Result<usize, SleError> {
29        let total = ISP1_HEADER_SIZE + payload.len();
30        if buf.len() < total {
31            return Err(SleError::BufferTooSmall);
32        }
33        let len_bytes =
34            (payload.len() as u32).to_be_bytes();
35        buf[..4].copy_from_slice(&len_bytes);
36        buf[4..total].copy_from_slice(payload);
37        Ok(total)
38    }
39
40    /// Reads the 4-byte length prefix from `buf`, returning
41    /// the payload length. Does not validate or consume the
42    /// payload — the caller should read that many bytes next.
43    ///
44    /// Returns `(payload_length, bytes_consumed)` where
45    /// `bytes_consumed` is always 4.
46    pub fn decode_header(
47        buf: &[u8],
48    ) -> Result<(usize, usize), SleError> {
49        if buf.len() < ISP1_HEADER_SIZE {
50            return Err(SleError::Truncated);
51        }
52        let len = u32::from_be_bytes([
53            buf[0], buf[1], buf[2], buf[3],
54        ]) as usize;
55        Ok((len, ISP1_HEADER_SIZE))
56    }
57
58    /// Decodes a complete ISP1 frame (header + payload) from
59    /// `buf`, returning a slice of the payload.
60    pub fn decode(buf: &[u8]) -> Result<&[u8], SleError> {
61        let (payload_len, hdr) = Self::decode_header(buf)?;
62        let total = hdr + payload_len;
63        if buf.len() < total {
64            return Err(SleError::Truncated);
65        }
66        Ok(&buf[hdr..total])
67    }
68}
69
70/// ISP1 authentication credentials.
71///
72/// Used in Bind invocations to prove the caller's identity.
73/// The hash is SHA-1 over (time ++ random ++ password).
74#[derive(Copy, Clone, Debug, PartialEq, Eq)]
75pub struct Credentials {
76    /// CDS time at which the credentials were generated.
77    pub time: [u8; 8],
78    /// Random nonce to prevent replay attacks.
79    pub random: u32,
80    /// SHA-1 hash: H(time || random || password).
81    pub hash: [u8; 20],
82}
83
84impl Credentials {
85    /// Total encoded size: 8 (time) + 4 (random) + 20 (hash).
86    pub const SIZE: usize = 8 + 4 + 20;
87
88    /// Encodes credentials into the buffer.
89    /// Returns the number of bytes written (always 32).
90    pub fn encode(
91        &self,
92        buf: &mut [u8],
93    ) -> Result<usize, SleError> {
94        if buf.len() < Self::SIZE {
95            return Err(SleError::BufferTooSmall);
96        }
97        buf[..8].copy_from_slice(&self.time);
98        buf[8..12].copy_from_slice(&self.random.to_be_bytes());
99        buf[12..32].copy_from_slice(&self.hash);
100        Ok(Self::SIZE)
101    }
102
103    /// Decodes credentials from the buffer.
104    pub fn decode(buf: &[u8]) -> Result<(Self, usize), SleError> {
105        if buf.len() < Self::SIZE {
106            return Err(SleError::Truncated);
107        }
108        let mut time = [0u8; 8];
109        time.copy_from_slice(&buf[..8]);
110        let random = u32::from_be_bytes([
111            buf[8], buf[9], buf[10], buf[11],
112        ]);
113        let mut hash = [0u8; 20];
114        hash.copy_from_slice(&buf[12..32]);
115        Ok((Self { time, random, hash }, Self::SIZE))
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn isp1_frame_roundtrip() {
125        let payload = b"SLE RAF data";
126        let mut buf = [0u8; 64];
127        let n = Isp1Frame::encode(payload, &mut buf).unwrap();
128        assert_eq!(n, 4 + payload.len());
129
130        let decoded = Isp1Frame::decode(&buf[..n]).unwrap();
131        assert_eq!(decoded, payload);
132    }
133
134    #[test]
135    fn isp1_decode_header() {
136        let mut buf = [0u8; 4];
137        buf.copy_from_slice(&100u32.to_be_bytes());
138        let (len, consumed) =
139            Isp1Frame::decode_header(&buf).unwrap();
140        assert_eq!(len, 100);
141        assert_eq!(consumed, 4);
142    }
143
144    #[test]
145    fn credentials_roundtrip() {
146        let cred = Credentials {
147            time: [1, 2, 3, 4, 5, 6, 7, 8],
148            random: 0xDEADBEEF,
149            hash: [
150                0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
151                0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
152                0xB0, 0xB1, 0xB2, 0xB3,
153            ],
154        };
155        let mut buf = [0u8; 64];
156        let n = cred.encode(&mut buf).unwrap();
157        assert_eq!(n, Credentials::SIZE);
158
159        let (decoded, consumed) =
160            Credentials::decode(&buf[..n]).unwrap();
161        assert_eq!(consumed, n);
162        assert_eq!(decoded, cred);
163    }
164}