1use std::borrow::Cow;
2use std::io::Write;
3
4use callsign::CallsignField;
5use AprsMessage;
6use AprsMicE;
7use AprsPosition;
8use AprsStatus;
9use Callsign;
10use DecodeError;
11use EncodeError;
12use Via;
13
14#[derive(PartialEq, Debug, Clone)]
15pub struct AprsPacket {
16 pub from: Callsign,
17 pub via: Vec<Via>,
18 pub data: AprsData,
19}
20
21impl AprsPacket {
22 pub fn decode_textual(s: &[u8]) -> Result<Self, DecodeError> {
23 let header_delimiter = s
24 .iter()
25 .position(|x| *x == b':')
26 .ok_or_else(|| DecodeError::InvalidPacket(s.to_owned()))?;
27 let (header, rest) = s.split_at(header_delimiter);
28 let body = &rest[1..];
29
30 let from_delimiter = header
31 .iter()
32 .position(|x| *x == b'>')
33 .ok_or_else(|| DecodeError::InvalidPacket(s.to_owned()))?;
34 let (from, rest) = header.split_at(from_delimiter);
35 let (from, _) = Callsign::decode_textual(from)
36 .ok_or_else(|| DecodeError::InvalidCallsign(from.to_owned()))?;
37
38 let to_and_via = &rest[1..];
39 let mut to_and_via = to_and_via.split(|x| *x == b',');
40
41 let to = to_and_via
42 .next()
43 .ok_or_else(|| DecodeError::InvalidPacket(s.to_owned()))?;
44 let (to, _) = Callsign::decode_textual(to)
45 .ok_or_else(|| DecodeError::InvalidCallsign(to.to_owned()))?;
46
47 let mut via = vec![];
48 for v in to_and_via {
49 via.push(Via::decode_textual(v).ok_or_else(|| DecodeError::InvalidVia(v.to_owned()))?);
50 }
51
52 let mut heard = false;
56 for v in via.iter_mut().rev() {
57 if let Some((_, c_heard)) = v.callsign_mut() {
58 if !heard {
59 heard = *c_heard;
60 }
61 *c_heard = heard;
62 }
63 }
64
65 let data = AprsData::decode(body, to)?;
66
67 Ok(AprsPacket { from, via, data })
68 }
69
70 pub fn to(&self) -> Option<&Callsign> {
71 self.data.to()
72 }
73
74 pub fn encode_textual<W: Write>(&self, buf: &mut W) -> Result<(), EncodeError> {
76 let mut via = self.via.clone();
78 let mut heard = false;
79 for v in via.iter_mut().rev() {
80 if let Some((_, c_heard)) = v.callsign_mut() {
81 if !heard {
82 heard = *c_heard;
83 } else {
84 *c_heard = false;
85 }
86 }
87 }
88
89 self.from.encode_textual(false, buf)?;
90 write!(buf, ">")?;
91 self.data.dest_field().encode_textual(false, buf)?;
92 for v in &via {
93 write!(buf, ",")?;
94 v.encode_textual(buf)?;
95 }
96 write!(buf, ":")?;
97 self.data.encode(buf)?;
98
99 Ok(())
100 }
101
102 pub fn decode_ax25(data: &[u8]) -> Result<Self, DecodeError> {
104 let dest_bytes = data
105 .get(0..7)
106 .ok_or_else(|| DecodeError::InvalidPacket(data.to_owned()))?;
107 let (to, _, has_more) = Callsign::decode_ax25(dest_bytes)
108 .ok_or_else(|| DecodeError::InvalidCallsign(dest_bytes.to_owned()))?;
109
110 if !has_more {
111 return Err(DecodeError::InvalidPacket(data.to_owned()));
112 }
113
114 let src_bytes = data
115 .get(7..14)
116 .ok_or_else(|| DecodeError::InvalidPacket(data.to_owned()))?;
117 let (from, _, mut has_more) = Callsign::decode_ax25(src_bytes)
118 .ok_or_else(|| DecodeError::InvalidCallsign(src_bytes.to_owned()))?;
119
120 let mut i = 14;
121 let mut via = vec![];
122 while has_more {
123 let v_bytes = data
124 .get(i..(i + 7))
125 .ok_or_else(|| DecodeError::InvalidPacket(data.to_owned()))?;
126
127 let (v, heard, more) = Callsign::decode_ax25(v_bytes)
130 .ok_or_else(|| DecodeError::InvalidCallsign(v_bytes.to_owned()))?;
131
132 via.push(Via::Callsign(v, heard));
133 has_more = more;
134 i += 7;
135 }
136
137 if data.get(i..(i + 2)) != Some(&[0x03, 0xf0]) {
139 return Err(DecodeError::InvalidPacket(data.to_owned()));
140 }
141 i += 2;
142
143 let data = AprsData::decode(data.get(i..).unwrap_or(&[]), to)?;
145
146 Ok(Self { data, from, via })
147 }
148
149 pub fn encode_ax25<W: Write>(&self, buf: &mut W) -> Result<(), EncodeError> {
151 self.data
153 .dest_field()
154 .encode_ax25(buf, CallsignField::Destination, true)?;
155
156 let via_calls: Vec<_> = self.via.iter().filter_map(|v| v.callsign()).collect();
157
158 let has_more = !via_calls.is_empty();
160 self.from
161 .encode_ax25(buf, CallsignField::Source, has_more)?;
162
163 if let Some(((last_v, last_heard), vs)) = via_calls.split_last() {
165 for (v, heard) in vs {
166 v.encode_ax25(buf, CallsignField::Via(*heard), true)?;
167 }
168
169 last_v.encode_ax25(buf, CallsignField::Via(*last_heard), false)?;
170 }
171
172 buf.write_all(&[0x03, 0xf0])?;
175
176 self.data.encode(buf)?;
178
179 Ok(())
180 }
181}
182
183#[derive(PartialEq, Debug, Clone)]
184pub enum AprsData {
185 Position(AprsPosition),
186 Message(AprsMessage),
187 Status(AprsStatus),
188 MicE(AprsMicE),
189 Unknown(Callsign),
190}
191
192impl AprsData {
193 pub fn to(&self) -> Option<&Callsign> {
194 match self {
195 AprsData::Position(p) => Some(&p.to),
196 AprsData::Message(m) => Some(&m.to),
197 AprsData::Status(s) => Some(&s.to),
198 AprsData::MicE(_) => None,
199 AprsData::Unknown(to) => Some(to),
200 }
201 }
202
203 fn dest_field(&self) -> Cow<Callsign> {
204 match self {
205 AprsData::Position(p) => Cow::Borrowed(&p.to),
206 AprsData::Message(m) => Cow::Borrowed(&m.to),
207 AprsData::Status(s) => Cow::Borrowed(&s.to),
208 AprsData::MicE(m) => Cow::Owned(m.encode_destination()),
209 AprsData::Unknown(to) => Cow::Borrowed(to),
210 }
211 }
212
213 fn decode(s: &[u8], to: Callsign) -> Result<Self, DecodeError> {
214 Ok(match *s.first().unwrap_or(&0) {
215 b':' => AprsData::Message(AprsMessage::decode(&s[1..], to)?),
216 b'!' | b'/' | b'=' | b'@' => AprsData::Position(AprsPosition::decode(s, to)?),
217 b'>' => AprsData::Status(AprsStatus::decode(&s[1..], to)?),
218 0x1c | b'`' => AprsData::MicE(AprsMicE::decode(&s[1..], to, true)?),
219 0x1d | b'\'' => AprsData::MicE(AprsMicE::decode(&s[1..], to, false)?),
220 _ => AprsData::Unknown(to),
221 })
222 }
223
224 fn encode<W: Write>(&self, buf: &mut W) -> Result<(), EncodeError> {
225 match self {
226 Self::Position(p) => {
227 p.encode(buf)?;
228 }
229 Self::Message(m) => {
230 m.encode(buf)?;
231 }
232 Self::Status(st) => {
233 st.encode(buf)?;
234 }
235 Self::MicE(m) => {
236 m.encode(buf)?;
237 }
238 Self::Unknown(_) => return Err(EncodeError::InvalidData),
239 }
240
241 Ok(())
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use mic_e::{Course, Message, Speed};
249 use AprsCst;
250 use Latitude;
251 use Longitude;
252 use Precision;
253 use QConstruct;
254 use Timestamp;
255
256 #[test]
257 fn parse() {
258 let result = AprsPacket::decode_textual(r"ID17F2>APRS,qAS,dl4mea:/074849h4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1".as_bytes()).unwrap();
259 assert_eq!(result.from, Callsign::new_no_ssid("ID17F2"));
260 assert_eq!(result.to(), Some(&Callsign::new_no_ssid("APRS")));
261 assert_eq!(
262 result.via,
263 vec![
264 Via::QConstruct(QConstruct::AS),
265 Via::Callsign(Callsign::new_no_ssid("dl4mea"), false),
266 ]
267 );
268
269 match result.data {
270 AprsData::Position(position) => {
271 assert_eq!(position.timestamp, Some(Timestamp::HHMMSS(7, 48, 49)));
272 assert_eq!(position.latitude.value(), 48.36016666666667);
273 assert_eq!(position.longitude.value(), 12.408166666666666);
274 assert_eq!(
275 position.comment,
276 b"322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1"
277 );
278 }
279 _ => panic!("Unexpected data type"),
280 }
281 }
282
283 #[test]
284 fn parse_message() {
285 let result = AprsPacket::decode_textual(
286 &b"IC17F2>Aprs,qAX,dl4mea::DEST :Hello World! This msg has a : colon {3a2B975"[..],
287 )
288 .unwrap();
289 assert_eq!(result.from, Callsign::new_no_ssid("IC17F2"));
290 assert_eq!(result.to(), Some(&Callsign::new_no_ssid("Aprs")));
291 assert_eq!(
292 result.via,
293 vec![
294 Via::QConstruct(QConstruct::AX),
295 Via::Callsign(Callsign::new_no_ssid("dl4mea"), false),
296 ]
297 );
298
299 match result.data {
300 AprsData::Message(msg) => {
301 assert_eq!(msg.addressee, b"DEST");
302 assert_eq!(msg.text, b"Hello World! This msg has a : colon ");
303 assert_eq!(msg.id, Some(b"3a2B975".to_vec()));
304 }
305 _ => panic!("Unexpected data type"),
306 }
307 }
308
309 #[test]
310 fn parse_status() {
311 let result =
312 AprsPacket::decode_textual(&b"3D17F2>APRS,qAU,dl4mea:>312359zStatus seems okay!"[..])
313 .unwrap();
314 assert_eq!(result.from, Callsign::new_no_ssid("3D17F2"));
315 assert_eq!(result.to(), Some(&Callsign::new_no_ssid("APRS")));
316 assert_eq!(
317 result.via,
318 vec![
319 Via::QConstruct(QConstruct::AU),
320 Via::Callsign(Callsign::new_no_ssid("dl4mea"), false),
321 ]
322 );
323
324 match result.data {
325 AprsData::Status(msg) => {
326 assert_eq!(msg.timestamp(), Some(&Timestamp::DDHHMM(31, 23, 59)));
327 assert_eq!(msg.comment(), b"Status seems okay!");
328 }
329 _ => panic!("Unexpected data type"),
330 }
331 }
332
333 #[test]
334 fn encode_ax25_basic() {
335 let encoded_ax25 = vec![
336 0x82, 0xa0, 0x9c, 0xaa, 0x62, 0x72, 0xe0, 0xac, 0x8a, 0x72, 0x84, 0x86, 0xa2, 0x60,
337 0xac, 0x8a, 0x72, 0x88, 0x8e, 0xa0, 0xe0, 0xac, 0x8a, 0x72, 0x8e, 0x8c, 0x92, 0xe4,
338 0xac, 0x8a, 0x72, 0x8c, 0xa0, 0x8e, 0xe0, 0xae, 0x92, 0x88, 0x8a, 0x66, 0x40, 0x61,
339 0x03, 0xf0, 0x21, 0x34, 0x36, 0x32, 0x37, 0x2e, 0x32, 0x30, 0x4e, 0x53, 0x30, 0x36,
340 0x36, 0x33, 0x31, 0x2e, 0x31, 0x39, 0x57, 0x23, 0x50, 0x48, 0x47, 0x35, 0x34, 0x36,
341 0x30, 0x2f, 0x57, 0x33, 0x20, 0x4d, 0x41, 0x52, 0x43, 0x41, 0x4e, 0x20, 0x55, 0x49,
342 0x44, 0x49, 0x47, 0x49, 0x20, 0x42, 0x4f, 0x49, 0x45, 0x53, 0x54, 0x4f, 0x57, 0x4e,
343 0x2c, 0x20, 0x4e, 0x42,
344 ];
345
346 let encoded_ascii = b"VE9BCQ>APNU19,VE9DGP,VE9GFI-2,VE9FPG*,WIDE3:!4627.20NS06631.19W#PHG5460/W3 MARCAN UIDIGI BOIESTOWN, NB";
347
348 let decoded_from_ascii = AprsPacket::decode_textual(&encoded_ascii[..]).unwrap();
350 let mut actual_ax25 = vec![];
351 decoded_from_ascii.encode_ax25(&mut actual_ax25).unwrap();
352 assert_eq!(encoded_ax25, actual_ax25);
353
354 let decoded_from_ax25 = AprsPacket::decode_ax25(&encoded_ax25).unwrap();
356 let mut actual_ascii = vec![];
357 decoded_from_ax25.encode_textual(&mut actual_ascii).unwrap();
358 assert_eq!(encoded_ascii[..], actual_ascii);
359
360 assert_eq!(decoded_from_ascii, decoded_from_ax25);
362 }
363
364 #[test]
365 fn parse_packet_mic_e() {
366 let result = AprsPacket::decode_textual(
367 &br#"DF1CHB-9>UQ0RT6,ARISS,APRSAT,WIDE1-1,qAU,DB0KOE-12:`|9g"H?>/>"4z}="#[..],
368 )
369 .unwrap();
370
371 assert_eq!(
372 AprsPacket {
373 from: Callsign::new_with_ssid("DF1CHB", "9"),
374 via: vec![
375 Via::Callsign(Callsign::new_no_ssid("ARISS"), false),
376 Via::Callsign(Callsign::new_no_ssid("APRSAT"), false),
377 Via::Callsign(Callsign::new_with_ssid("WIDE1", "1"), false),
378 Via::QConstruct(QConstruct::AU),
379 Via::Callsign(Callsign::new_with_ssid("DB0KOE", "12"), false)
380 ],
381 data: AprsData::MicE(AprsMicE {
382 latitude: Latitude::new(51.041).unwrap(),
383 longitude: Longitude::new(6.495833333333334).unwrap(),
384 precision: Precision::HundredthMinute,
385 message: Message::M1,
386 speed: Speed::new(64).unwrap(),
387 course: Course::new(35).unwrap(),
388 symbol_table: b'/',
389 symbol_code: b'>',
390 comment: br#">"4z}="#.to_vec(),
391 current: true
392 })
393 },
394 result
395 );
396 }
397
398 #[test]
399 fn encode_edge_case() {
400 let packet = AprsPacket {
401 from: Callsign::new_no_ssid("D9KS3"),
402 via: vec![],
403 data: AprsData::Position(AprsPosition {
404 to: Callsign::new_no_ssid("NOBODY"),
405 timestamp: None,
406 messaging_supported: true,
407 latitude: Latitude::new(33.999999999999).unwrap(),
408 longitude: Longitude::new(33.999999999999).unwrap(),
409 precision: Precision::HundredthMinute,
410 symbol_table: '/',
411 symbol_code: 'c',
412 comment: b"Hello world".to_vec(),
413 cst: AprsCst::Uncompressed,
414 }),
415 };
416
417 let mut buf = vec![];
418 packet.encode_textual(&mut buf).unwrap();
419 assert_eq!(
420 "D9KS3>NOBODY:=3400.00N/03400.00EcHello world",
421 String::from_utf8(buf).unwrap()
422 );
423 }
424
425 #[test]
426 fn encode_with_ssid_0() {
427 let packet = AprsPacket {
428 from: Callsign::new_with_ssid("D9KS3", "0"),
429 via: vec![Via::Callsign(Callsign::new("D9KS7-0").unwrap(), false)],
430 data: AprsData::Position(AprsPosition {
431 to: Callsign::new_with_ssid("NOBODY", "0"),
432 timestamp: None,
433 messaging_supported: true,
434 latitude: Latitude::new(3.95).unwrap(),
435 longitude: Longitude::new(-4.58).unwrap(),
436 precision: Precision::HundredthMinute,
437 symbol_table: '/',
438 symbol_code: 'c',
439 comment: b"Hello world".to_vec(),
440 cst: AprsCst::Uncompressed,
441 }),
442 };
443
444 let mut buf = vec![];
445 packet.encode_textual(&mut buf).unwrap();
446 assert_eq!(
447 "D9KS3>NOBODY,D9KS7:=0357.00N/00434.80WcHello world",
448 String::from_utf8(buf).unwrap()
449 );
450 }
451
452 #[test]
453 fn e2e_serialize_deserialize() {
454 let valids = vec![
455 r"3D17F2>APRS,qAS,DL4MEA:/074849h4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
456 r"3D17F2>APRS,qAS,DL4MEA:@074849h4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
457 r"ID17F2>APRS,qAS,DL4MEA:!4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
458 r"3D17F2>APRS,qAS,DL4MEA:!48 . N\01200.00E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
459 r"3D17F2>APRS,qAS,DL4MEA:=4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
460 r"ID17F2>APRS,qAS,DL4MEA::DEST :Hello World! This msg has a : colon {32975",
461 r"IC17F2>APRS,qAS,DL4MEA::DESTINATI:Hello World! This msg has a : colon ",
462 r"ICA7F2>APRS,qAS,DL4MEA:>312359zStatus seems okay!",
463 r"ICA3F2>APRS,qAS,DL4MEA:>184050hAlso with HMS format...",
464 "VE9MP-12>T5RX8P,VE9GFI-2,WIDE1*,WIDE2-1,qAR,VE9QLE-10:`]Q\x1cl|ok/'\"4<}Nick - Monitoring IRG|!\"&7'M|!wTD!|3",
465 r#"DF1CHB-9>UQ0RT6,ARISS,APRSAT,WIDE1-1,qAU,DB0KOE-1:`|9g\"H?>/>\"4z}="#,
466 ];
467
468 for v in valids {
469 let mut buf = vec![];
470 let packet = AprsPacket::decode_textual(v.as_bytes()).unwrap();
471 packet.encode_textual(&mut buf).unwrap();
472 assert_eq!(buf, v.as_bytes(), "\n{}\n{}", buf.escape_ascii(), v);
473 }
474 }
475
476 #[test]
477 fn e2e_serialize_deserialize_ax25() {
478 let originals = vec![
479 r"3D17F2>APRS,qAS,DL4MEA*:/074849h4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
480 r"3D17F2>APRS,qAS,DL4MEA:@074849h4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
481 r"ID17F2>APRS,qAS,dl4mea:!4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
482 r"3D17F2>APRS,qAS,DL4MEA:!48 . N\01200.00E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
483 r"3D17F2>APRS,qAS,DL4MEA:=4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
484 r"ID17F2>APRS,qAS,DL4MEA::DEST :Hello World! This msg has a : colon {32975",
485 r"IC17F2>APRS,qAS,DL4MEA::DESTINATI:Hello World! This msg has a : colon ",
486 r"ICA7F2>APRS,qAS,DL4MEA:>312359zStatus seems okay!",
487 r"ICA3F2>APRS,qAS,DL4MEA:>184050hAlso with HMS format...",
488 "VE9MP-12>T5RX8P,VE9GFI-2,WIDE1*,WIDE2-1,qAR,VE9QLE-10:`]Q\x1cl|ok/'\"4<}Nick - Monitoring IRG|!\"&7'M|!wTD!|3",
489 r#"DF1CHB-9>UQ0RT6,ARISS,APRSAT,WIDE1-1,qAU,DB0KOE-1:`|9g\"H?>/>\"4z}="#,
490 r"ICA3F2>APRS:>184050hAlso with HMS format...",
492 r"ICA3F2>APRS,qAS:>184050hAlso with HMS format...",
493 r"ICA3F2>APRS,ABC,qAS:>184050hAlso with HMS format...",
494 r"ICA3F2>APRS,ABC,DEF,qAS:>184050hAlso with HMS format...",
495 r"ICA3F2>APRS,ABC,DEF,HIJ,qAS:>184050hAlso with HMS format...",
496 r"ICA3F2>APRS,ABC,DEF,HIJ,KLM,qAS:>184050hAlso with HMS format...",
497 r"ICA3F2>APRS,ABC,DEF,NIJ,KLM,QRZ,qAS:>184050hAlso with HMS format...",
498 r"ICA3F2>APRS,ABC,DEF,HIK,ASD,NADL,ASKJ,qAS:>184050hAlso with HMS format...",
499 r"ICA3F2>APRS,ABC,DEF,HIK,ASD,NADL,ASKJ,SDKKA,qAS:>184050hAlso with HMS format...",
500 r"ICA3F2>APRS,ABC,DEF,HIK,ASD,NADL,ASKJ,SDKKA,ABC,qAS:>184050hAlso with HMS format...",
501 ];
502
503 let expected = vec![
505 r"3D17F2>APRS,DL4MEA*:/074849h4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
506 r"3D17F2>APRS,DL4MEA:@074849h4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
507 r"ID17F2>APRS,DL4MEA:!4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
508 r"3D17F2>APRS,DL4MEA:!48 . N\01200.00E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
509 r"3D17F2>APRS,DL4MEA:=4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1",
510 r"ID17F2>APRS,DL4MEA::DEST :Hello World! This msg has a : colon {32975",
511 r"IC17F2>APRS,DL4MEA::DESTINATI:Hello World! This msg has a : colon ",
512 r"ICA7F2>APRS,DL4MEA:>312359zStatus seems okay!",
513 r"ICA3F2>APRS,DL4MEA:>184050hAlso with HMS format...",
514 "VE9MP-12>T5RX8P,VE9GFI-2,WIDE1*,WIDE2-1,VE9QLE-10:`]Q\x1cl|ok/'\"4<}Nick - Monitoring IRG|!\"&7'M|!wTD!|3",
515 r#"DF1CHB-9>UQ0RT6,ARISS,APRSAT,WIDE1-1,DB0KOE-1:`|9g\"H?>/>\"4z}="#,
516 r"ICA3F2>APRS:>184050hAlso with HMS format...",
518 r"ICA3F2>APRS:>184050hAlso with HMS format...",
519 r"ICA3F2>APRS,ABC:>184050hAlso with HMS format...",
520 r"ICA3F2>APRS,ABC,DEF:>184050hAlso with HMS format...",
521 r"ICA3F2>APRS,ABC,DEF,HIJ:>184050hAlso with HMS format...",
522 r"ICA3F2>APRS,ABC,DEF,HIJ,KLM:>184050hAlso with HMS format...",
523 r"ICA3F2>APRS,ABC,DEF,NIJ,KLM,QRZ:>184050hAlso with HMS format...",
524 r"ICA3F2>APRS,ABC,DEF,HIK,ASD,NADL,ASKJ:>184050hAlso with HMS format...",
525 r"ICA3F2>APRS,ABC,DEF,HIK,ASD,NADL,ASKJ,SDKKA:>184050hAlso with HMS format...",
526 r"ICA3F2>APRS,ABC,DEF,HIK,ASD,NADL,ASKJ,SDKKA,ABC:>184050hAlso with HMS format...",
527 ];
528
529 for (o, e) in originals.iter().zip(expected.iter()) {
530 let o_packet = AprsPacket::decode_textual(o.as_bytes()).unwrap();
531
532 let mut o_ax25 = vec![];
533 o_packet.encode_ax25(&mut o_ax25).unwrap();
534 let o_pkt_from_ax25 = AprsPacket::decode_ax25(&o_ax25).unwrap();
535
536 let mut o_re_encoded = vec![];
537 o_pkt_from_ax25.encode_textual(&mut o_re_encoded).unwrap();
538
539 assert_eq!(
541 e.as_bytes(),
542 o_re_encoded,
543 "\n{}\n{}",
544 e,
545 String::from_utf8_lossy(&o_re_encoded)
546 );
547
548 let e_packet = AprsPacket::decode_textual(e.as_bytes()).unwrap();
552 let mut e_ax25 = vec![];
553 e_packet.encode_ax25(&mut e_ax25).unwrap();
554
555 assert_eq!(e_ax25, o_ax25);
556 }
557 }
558
559 #[test]
560 fn e2e_invalid_string_msg() {
561 let original = b"ICA7F2>Aprs,qAS,dl4mea::DEST :Hello World! This msg has raw bytes that are invalid utf8! \xc3\x28 {32975";
562
563 let mut buf = vec![];
564 let decoded = AprsPacket::decode_textual(&original[..]).unwrap();
565 decoded.encode_textual(&mut buf).unwrap();
566 assert_eq!(buf, original);
567 }
568}