Skip to main content

rustmod_core/frame/
tcp.rs

1use crate::encoding::{Reader, Writer};
2use crate::{DecodeError, EncodeError};
3
4pub const MBAP_HEADER_LEN: usize = 7;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct MbapHeader {
8    pub transaction_id: u16,
9    pub protocol_id: u16,
10    /// Length includes unit-id byte + PDU length.
11    pub length: u16,
12    pub unit_id: u8,
13}
14
15impl MbapHeader {
16    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
17        w.write_be_u16(self.transaction_id)?;
18        w.write_be_u16(self.protocol_id)?;
19        w.write_be_u16(self.length)?;
20        w.write_u8(self.unit_id)?;
21        Ok(())
22    }
23
24    pub fn decode(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
25        let transaction_id = r.read_be_u16()?;
26        let protocol_id = r.read_be_u16()?;
27        let length = r.read_be_u16()?;
28        let unit_id = r.read_u8()?;
29
30        if protocol_id != 0 {
31            return Err(DecodeError::InvalidValue);
32        }
33        if length < 1 {
34            return Err(DecodeError::InvalidLength);
35        }
36
37        Ok(Self {
38            transaction_id,
39            protocol_id,
40            length,
41            unit_id,
42        })
43    }
44}
45
46pub fn encode_frame(
47    w: &mut Writer<'_>,
48    transaction_id: u16,
49    unit_id: u8,
50    pdu: &[u8],
51) -> Result<(), EncodeError> {
52    let pdu_len_u16: u16 = pdu
53        .len()
54        .try_into()
55        .map_err(|_| EncodeError::ValueOutOfRange)?;
56    let length = pdu_len_u16
57        .checked_add(1)
58        .ok_or(EncodeError::ValueOutOfRange)?;
59
60    let header = MbapHeader {
61        transaction_id,
62        protocol_id: 0,
63        length,
64        unit_id,
65    };
66    header.encode(w)?;
67    w.write_all(pdu)?;
68    Ok(())
69}
70
71pub fn decode_frame<'a>(r: &mut Reader<'a>) -> Result<(MbapHeader, &'a [u8]), DecodeError> {
72    let header = MbapHeader::decode(r)?;
73    let pdu_len = usize::from(header.length - 1);
74    let pdu = r.read_exact(pdu_len)?;
75    Ok((header, pdu))
76}
77
78#[cfg(test)]
79mod tests {
80    use super::{decode_frame, encode_frame, MbapHeader};
81    use crate::encoding::{Reader, Writer};
82    use crate::DecodeError;
83
84    #[test]
85    fn mbap_roundtrip() {
86        let mut buf = [0u8; 32];
87        let mut w = Writer::new(&mut buf);
88        encode_frame(&mut w, 1, 2, &[0x03, 0x00, 0x6B, 0x00, 0x03]).unwrap();
89
90        let mut r = Reader::new(w.as_written());
91        let (header, pdu) = decode_frame(&mut r).unwrap();
92        assert_eq!(
93            header,
94            MbapHeader {
95                transaction_id: 1,
96                protocol_id: 0,
97                length: 6,
98                unit_id: 2,
99            }
100        );
101        assert_eq!(pdu, &[0x03, 0x00, 0x6B, 0x00, 0x03]);
102    }
103
104    #[test]
105    fn rejects_non_zero_protocol_id() {
106        let bytes = [0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x01, 0x03];
107        let mut r = Reader::new(&bytes);
108        assert_eq!(decode_frame(&mut r).unwrap_err(), DecodeError::InvalidValue);
109    }
110}