1use std::fmt::Write;
2use std::str::FromStr;
3
4use serde::Serialize;
5
6use crate::AprsError;
7use crate::AprsMessage;
8use crate::AprsPosition;
9use crate::AprsStatus;
10use crate::Callsign;
11use crate::EncodeError;
12
13#[derive(PartialEq, Debug, Clone, Serialize)]
14pub struct AprsPacket {
15 pub from: Callsign,
16 pub to: Callsign,
17 pub via: Vec<Callsign>,
18 #[serde(flatten)]
19 pub data: AprsData,
20}
21
22impl FromStr for AprsPacket {
23 type Err = AprsError;
24
25 fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
26 if !s.is_ascii() {
27 return Err(AprsError::InvalidCoding(s.to_owned()));
28 }
29 let header_delimiter = s
30 .find(':')
31 .ok_or_else(|| AprsError::InvalidPacket(s.to_owned()))?;
32 let (header, rest) = s.split_at(header_delimiter);
33 let body = &rest[1..];
34
35 let from_delimiter = header
36 .find('>')
37 .ok_or_else(|| AprsError::InvalidPacket(s.to_owned()))?;
38 let (from, rest) = header.split_at(from_delimiter);
39 let from = Callsign::from_str(from)?;
40
41 let to_and_via = &rest[1..];
42 let to_and_via: Vec<_> = to_and_via.split(',').collect();
43
44 let to = to_and_via
45 .first()
46 .ok_or_else(|| AprsError::InvalidPacket(s.to_owned()))?;
47 let to = Callsign::from_str(to)?;
48
49 let mut via = vec![];
50 for v in to_and_via.iter().skip(1) {
51 via.push(Callsign::from_str(v)?);
52 }
53
54 let data = AprsData::from_str(body)?;
55
56 Ok(AprsPacket {
57 from,
58 to,
59 via,
60 data,
61 })
62 }
63}
64
65impl AprsPacket {
66 pub fn encode<W: Write>(&self, buf: &mut W) -> Result<(), EncodeError> {
67 write!(buf, "{}>{}", self.from, self.to)?;
68 for v in &self.via {
69 write!(buf, ",{v}").unwrap();
70 }
71 write!(buf, ":")?;
72 self.data.encode(buf)?;
73
74 Ok(())
75 }
76}
77
78#[derive(PartialEq, Debug, Clone, Serialize)]
79#[serde(rename_all = "snake_case")]
80pub enum AprsData {
81 Position(AprsPosition),
82 Message(AprsMessage),
83 Status(AprsStatus),
84 Unknown,
85}
86
87impl FromStr for AprsData {
88 type Err = AprsError;
89
90 fn from_str(s: &str) -> Result<Self, AprsError> {
91 Ok(match s.chars().next().unwrap_or(0 as char) {
92 ':' => AprsData::Message(AprsMessage::from_str(&s[1..])?),
93 '!' | '/' | '=' | '@' => AprsData::Position(AprsPosition::from_str(s)?),
94 '>' => AprsData::Status(AprsStatus::from_str(&s[1..])?),
95 _ => AprsData::Unknown,
96 })
97 }
98}
99
100impl AprsData {
101 fn encode<W: Write>(&self, buf: &mut W) -> Result<(), EncodeError> {
102 match self {
103 Self::Position(p) => {
104 p.encode(buf)?;
105 }
106 Self::Message(m) => {
107 write!(buf, "{m}")?;
108 }
109 Self::Status(s) => {
110 write!(buf, "{s}")?;
111 }
112 Self::Unknown => return Err(EncodeError::InvalidData),
113 }
114
115 Ok(())
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::Timestamp;
123
124 #[test]
125 fn parse() {
126 let result = r"ICA3D17F2>APRS,qAS,dl4mea:/074849h4821.61N\01224.49E^322/103/A=003054 !W46! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1".parse::<AprsPacket>().unwrap();
127 assert_eq!(result.from, Callsign::new("ICA3D17F2", None));
128 assert_eq!(result.to, Callsign::new("APRS", None));
129 assert_eq!(
130 result.via,
131 vec![Callsign::new("qAS", None), Callsign::new("dl4mea", None),]
132 );
133
134 match result.data {
135 AprsData::Position(position) => {
136 assert_eq!(position.timestamp, Some(Timestamp::HHMMSS(7, 48, 49)));
137 assert_relative_eq!(*position.latitude, 48.36023333333334);
138 assert_relative_eq!(*position.longitude, 12.408266666666666);
139 }
144 _ => panic!("Unexpected data type"),
145 }
146 }
147
148 #[test]
149 fn parse_error_no_ascii() {
150 let result =
151 r"ICA3D17F2>APRS,qAS,dl4mea:/074849h4821.61N\01224.49E^322/103/A=003054 Hochkönig"
152 .parse::<AprsPacket>();
153 assert_eq!(result.is_err(), true);
154 }
155
156 #[test]
157 fn parse_message() {
158 let result =
159 r"ICA3D17F2>Aprs,qAS,dl4mea::DEST :Hello World! This msg has a : colon {32975"
160 .parse::<AprsPacket>()
161 .unwrap();
162 assert_eq!(result.from, Callsign::new("ICA3D17F2", None));
163 assert_eq!(result.to, Callsign::new("Aprs", None));
164 assert_eq!(
165 result.via,
166 vec![Callsign::new("qAS", None), Callsign::new("dl4mea", None),]
167 );
168
169 match result.data {
170 AprsData::Message(msg) => {
171 assert_eq!(msg.addressee, "DEST");
172 assert_eq!(msg.text, "Hello World! This msg has a : colon ");
173 assert_eq!(msg.id, Some(32975));
174 }
175 _ => panic!("Unexpected data type"),
176 }
177 }
178
179 #[test]
180 fn parse_status() {
181 let result = r"ICA3D17F2>APRS,qAS,dl4mea:>312359zStatus seems okay!"
182 .parse::<AprsPacket>()
183 .unwrap();
184 assert_eq!(result.from, Callsign::new("ICA3D17F2", None));
185 assert_eq!(result.to, Callsign::new("APRS", None));
186 assert_eq!(
187 result.via,
188 vec![Callsign::new("qAS", None), Callsign::new("dl4mea", None),]
189 );
190
191 match result.data {
192 AprsData::Status(msg) => {
193 assert_eq!(msg.timestamp, Some(Timestamp::DDHHMM(31, 23, 59)));
194 assert_eq!(msg.comment.unparsed.unwrap(), "Status seems okay!");
195 }
196 _ => panic!("Unexpected data type"),
197 }
198 }
199
200 #[ignore = "status_comment and position_comment serialization not implemented"]
201 #[test]
202 fn e2e_serialize_deserialize() {
203 let valids = vec![
204 r"ICA3D17F2>APRS,qAS,dl4mea:/074849h4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
205 r"ICA3D17F2>APRS,qAS,dl4mea:@074849h4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
206 r"ICA3D17F2>APRS,qAS,dl4mea:!4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
207 r"ICA3D17F2>APRS,qAS,dl4mea:=4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
208 r"ICA3D17F2>Aprs,qAS,dl4mea::DEST :Hello World! This msg has a : colon {32975",
209 r"ICA3D17F2>Aprs,qAS,dl4mea::DESTINATI:Hello World! This msg has a : colon ",
210 r"ICA3D17F2>APRS,qAS,dl4mea:>312359zStatus seems okay!",
211 r"ICA3D17F2>APRS,qAS,dl4mea:>184050hAlso with HMS format...",
212 ];
213
214 for v in valids {
215 let mut buf = String::new();
216 v.parse::<AprsPacket>().unwrap().encode(&mut buf).unwrap();
217 assert_eq!(buf, v)
218 }
219 }
220}