aprs_parser/
message.rs

1use std::io::Write;
2
3use Callsign;
4use DecodeError;
5use EncodeError;
6
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct AprsMessage {
9    pub to: Callsign,
10
11    pub addressee: Vec<u8>,
12    pub text: Vec<u8>,
13    pub id: Option<Vec<u8>>,
14}
15
16impl AprsMessage {
17    pub fn decode(b: &[u8], to: Callsign) -> Result<Self, DecodeError> {
18        let mut splitter = b.splitn(2, |x| *x == b':');
19
20        let mut addressee = match splitter.next() {
21            Some(x) => x.to_vec(),
22            None => {
23                return Err(DecodeError::InvalidMessageDestination(vec![]));
24            }
25        };
26
27        if addressee.len() != 9 {
28            return Err(DecodeError::InvalidMessageDestination(addressee.to_owned()));
29        }
30
31        trim_spaces_end(&mut addressee);
32
33        let text = splitter.next().unwrap_or(&[]);
34        let mut text_splitter = text.splitn(2, |x| *x == b'{');
35        let text = text_splitter.next().unwrap_or(&[]).to_vec();
36        let id = text_splitter.next().map(|x| x.to_vec());
37
38        Ok(Self {
39            to,
40
41            addressee,
42            text,
43            id,
44        })
45    }
46
47    pub fn encode<W: Write>(&self, buf: &mut W) -> Result<(), EncodeError> {
48        if self.addressee.len() > 9 {
49            return Err(EncodeError::InvalidMessageAddressee(
50                self.addressee.to_owned(),
51            ));
52        }
53
54        buf.write_all(b":")?;
55        buf.write_all(&self.addressee)?;
56        for _ in self.addressee.len()..9 {
57            buf.write_all(b" ")?;
58        }
59
60        buf.write_all(b":")?;
61        buf.write_all(&self.text)?;
62
63        if let Some(id) = &self.id {
64            buf.write_all(b"{")?;
65            buf.write_all(id)?;
66        }
67
68        Ok(())
69    }
70}
71
72fn trim_spaces_end(arr: &mut Vec<u8>) {
73    let space_count = arr.iter().rev().take_while(|&&b| b == b' ').count();
74
75    arr.truncate(arr.len() - space_count);
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    fn default_callsign() -> Callsign {
83        Callsign::new_no_ssid("VE9")
84    }
85
86    #[test]
87    fn parse_message_invalid_dest() {
88        // Dest must be padded with spaces to 9 characters long
89        let result = AprsMessage::decode(
90            &b"DEST  :Hello World! This msg has a : colon {32975"[..],
91            default_callsign(),
92        );
93
94        assert_eq!(
95            result,
96            Err(DecodeError::InvalidMessageDestination(b"DEST  ".to_vec()))
97        );
98    }
99
100    #[test]
101    fn parse_message_id() {
102        let result = AprsMessage::decode(
103            r"DESTINATI:Hello World! This msg has a : colon {329A7D5Z4".as_bytes(),
104            default_callsign(),
105        );
106
107        assert_eq!(
108            result,
109            Ok(AprsMessage {
110                to: default_callsign(),
111                addressee: b"DESTINATI".to_vec(),
112                id: Some(b"329A7D5Z4".to_vec()),
113                text: b"Hello World! This msg has a : colon ".to_vec()
114            })
115        );
116    }
117
118    #[test]
119    fn parse_message_empty_id() {
120        let result = AprsMessage::decode(
121            r"DESTINATI:Hello World! This msg has a : colon {".as_bytes(),
122            default_callsign(),
123        );
124
125        assert_eq!(
126            result,
127            Ok(AprsMessage {
128                to: default_callsign(),
129                addressee: b"DESTINATI".to_vec(),
130                id: Some(vec![]),
131                text: b"Hello World! This msg has a : colon ".to_vec()
132            })
133        );
134    }
135
136    #[test]
137    fn parse_message_no_id() {
138        let result = AprsMessage::decode(
139            r"DESTINATI:Hello World! This msg has a : colon ".as_bytes(),
140            default_callsign(),
141        );
142
143        assert_eq!(
144            result,
145            Ok(AprsMessage {
146                to: default_callsign(),
147                addressee: b"DESTINATI".to_vec(),
148                id: None,
149                text: b"Hello World! This msg has a : colon ".to_vec()
150            })
151        );
152    }
153}