aprs_parser/
mic_e.rs

1use std::convert::TryInto;
2use std::io::Write;
3
4use Callsign;
5use DecodeError;
6use EncodeError;
7use Latitude;
8use Precision;
9
10use crate::Longitude;
11
12#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
13pub enum Message {
14    M0,
15    M1,
16    M2,
17    M3,
18    M4,
19    M5,
20    M6,
21    C0,
22    C1,
23    C2,
24    C3,
25    C4,
26    C5,
27    C6,
28    Emergency,
29    Unknown,
30}
31
32impl Message {
33    fn decode(a: MessageBit, b: MessageBit, c: MessageBit) -> Self {
34        use self::Message::*;
35        use self::MessageBit::{CustomOne, StandardOne, Zero};
36
37        match (a, b, c) {
38            (StandardOne, StandardOne, StandardOne) => M0,
39            (CustomOne, CustomOne, CustomOne) => C0,
40
41            (StandardOne, StandardOne, Zero) => M1,
42            (CustomOne, CustomOne, Zero) => C1,
43
44            (StandardOne, Zero, StandardOne) => M2,
45            (CustomOne, Zero, CustomOne) => C2,
46
47            (StandardOne, Zero, Zero) => M3,
48            (CustomOne, Zero, Zero) => C3,
49
50            (Zero, StandardOne, StandardOne) => M4,
51            (Zero, CustomOne, CustomOne) => C4,
52
53            (Zero, StandardOne, Zero) => M5,
54            (Zero, CustomOne, Zero) => C5,
55
56            (Zero, Zero, StandardOne) => M6,
57            (Zero, Zero, CustomOne) => C6,
58
59            (Zero, Zero, Zero) => Self::Emergency,
60            _ => Self::Unknown,
61        }
62    }
63
64    fn encode(&self) -> (MessageBit, MessageBit, MessageBit) {
65        use self::Message::*;
66        use self::MessageBit::{CustomOne, StandardOne, Zero};
67
68        match self {
69            M0 => (StandardOne, StandardOne, StandardOne),
70            C0 => (CustomOne, CustomOne, CustomOne),
71
72            M1 => (StandardOne, StandardOne, Zero),
73            C1 => (CustomOne, CustomOne, Zero),
74
75            M2 => (StandardOne, Zero, StandardOne),
76            C2 => (CustomOne, Zero, CustomOne),
77
78            M3 => (StandardOne, Zero, Zero),
79            C3 => (CustomOne, Zero, Zero),
80
81            M4 => (Zero, StandardOne, StandardOne),
82            C4 => (Zero, CustomOne, CustomOne),
83
84            M5 => (Zero, StandardOne, Zero),
85            C5 => (Zero, CustomOne, Zero),
86
87            M6 => (Zero, Zero, StandardOne),
88            C6 => (Zero, Zero, CustomOne),
89
90            Message::Emergency => (Zero, Zero, Zero),
91            // any combination of standard and custom ones would work here
92            Message::Unknown => (StandardOne, CustomOne, StandardOne),
93        }
94    }
95}
96
97/// A speed. Valid values range from 0 to 799 knots.
98#[derive(Copy, Clone, Debug, PartialEq, Eq)]
99pub struct Speed(u32);
100
101impl Speed {
102    /// Creates a new `Speed` from knots.
103    pub fn new(knots: u32) -> Option<Self> {
104        if knots > 799 {
105            return None;
106        }
107
108        Some(Self(knots))
109    }
110
111    pub fn knots(&self) -> u32 {
112        self.0
113    }
114}
115
116/// A course. Valid values range from 0 to 360 degrees.
117/// 0 degrees represents an unknown course.
118/// 360 degrees represents north.
119#[derive(Copy, Clone, Debug, PartialEq, Eq)]
120pub struct Course(u32);
121
122impl Course {
123    pub const UNKNOWN: Self = Self(0);
124
125    /// Creates a new `Course` from degrees.
126    pub fn new(degrees: u32) -> Option<Self> {
127        if degrees > 360 {
128            return None;
129        }
130
131        Some(Self(degrees))
132    }
133
134    pub fn degrees(&self) -> u32 {
135        self.0
136    }
137}
138
139#[derive(PartialEq, Debug, Clone)]
140pub struct AprsMicE {
141    pub latitude: Latitude,
142    pub longitude: Longitude,
143    pub precision: Precision,
144
145    pub message: Message,
146    pub speed: Speed,
147    pub course: Course,
148    pub symbol_table: u8,
149    pub symbol_code: u8,
150    pub comment: Vec<u8>,
151
152    pub current: bool,
153}
154
155impl AprsMicE {
156    pub fn decode(b: &[u8], to: Callsign, current: bool) -> Result<Self, DecodeError> {
157        let (latitude, precision, message, long_offset, long_dir) =
158            decode_callsign(&to).ok_or(DecodeError::InvalidMicEDestination(to))?;
159
160        let info = b
161            .get(0..8)
162            .ok_or_else(|| DecodeError::InvalidMicEInformation(b.to_vec()))?;
163        let comment = b.get(8..).unwrap_or(&[]).to_vec();
164
165        let longitude = decode_longitude(&info[0..3], long_offset, long_dir)
166            .ok_or_else(|| DecodeError::InvalidMicEInformation(b.to_vec()))?;
167        let (speed, course) = decode_speed_and_course(&info[3..6])
168            .ok_or_else(|| DecodeError::InvalidMicEInformation(b.to_vec()))?;
169        let symbol_code = info[6];
170        let symbol_table = info[7];
171
172        Ok(Self {
173            latitude,
174            longitude,
175            precision,
176
177            message,
178            speed,
179            course,
180            symbol_table,
181            symbol_code,
182            comment,
183
184            current,
185        })
186    }
187
188    pub fn encode<W: Write>(&self, buf: &mut W) -> Result<(), EncodeError> {
189        if self.current {
190            buf.write_all(&[b'`'])?;
191        } else {
192            buf.write_all(&[b'\''])?;
193        }
194
195        self.encode_longitude(buf)?;
196        self.encode_speed_and_course(buf)?;
197
198        buf.write_all(&[self.symbol_code, self.symbol_table])?;
199        buf.write_all(&self.comment)?;
200
201        Ok(())
202    }
203
204    pub fn encode_destination(&self) -> Callsign {
205        let mut encoded_lat = vec![];
206        // safe to do
207        // can only fail from a write error
208        // which is impossible because we're writing to an array
209        self.latitude
210            .encode_uncompressed(&mut encoded_lat, self.precision)
211            .unwrap();
212        assert_eq!(8, encoded_lat.len());
213
214        let lat_dir = if *self.latitude >= 0.0 {
215            LatDir::North
216        } else {
217            LatDir::South
218        };
219
220        let (long_deg, _, _, is_east) = self.longitude.dmh();
221
222        let long_dir = if is_east {
223            LongDir::East
224        } else {
225            LongDir::West
226        };
227
228        let long_offset = if (0..=9).contains(&long_deg) || long_deg >= 100 {
229            LongOffset::Hundred
230        } else {
231            LongOffset::Zero
232        };
233
234        let (a, b, c) = self.message.encode();
235
236        let bytes = vec![
237            encode_bits_012(encoded_lat[0], a),
238            encode_bits_012(encoded_lat[1], b),
239            encode_bits_012(encoded_lat[2], c),
240            encode_bit_3(encoded_lat[3], lat_dir),
241            encode_bit_4(encoded_lat[5], long_offset),
242            encode_bit_5(encoded_lat[6], long_dir),
243        ];
244
245        // Safe to unwrap because we know all bytes are valid ASCII
246        Callsign::new_no_ssid(String::from_utf8(bytes).unwrap())
247    }
248
249    fn encode_longitude<W: Write>(&self, w: &mut W) -> Result<(), EncodeError> {
250        let (d, m, h, _) = self.longitude.dmh();
251
252        // safe to unwrap - all values must be less than 255
253        let d: u8 = d.try_into().unwrap();
254        let m: u8 = m.try_into().unwrap();
255        let h: u8 = h.try_into().unwrap();
256
257        let d = match d {
258            0..=9 => d + 90,
259            10..=99 => d,
260            100..=109 => d - 20,
261            _ => d - 100,
262        };
263
264        let m = match m {
265            0..=9 => m + 60,
266            _ => m,
267        };
268
269        w.write_all(&[d + 28, m + 28, h + 28])?;
270
271        Ok(())
272    }
273
274    fn encode_speed_and_course<W: Write>(&self, w: &mut W) -> Result<(), EncodeError> {
275        let tens_knots: u8 = (self.speed.knots() / 10).try_into().unwrap();
276        let units_knots = self.speed.knots() % 10;
277
278        let hundreds_course = self.course.degrees() / 100;
279        let units_course = self.course.degrees() % 100;
280
281        let sp: u8 = match tens_knots {
282            0..=19 => tens_knots + 80,
283            _ => tens_knots,
284        };
285        let dc: u8 = (units_knots * 10 + hundreds_course + 4).try_into().unwrap();
286        let se: u8 = (units_course).try_into().unwrap();
287
288        w.write_all(&[sp + 28, dc + 28, se + 28])?;
289
290        Ok(())
291    }
292}
293
294enum MessageBit {
295    Zero,
296    CustomOne,
297    StandardOne,
298}
299
300impl MessageBit {
301    fn decode(c: u8) -> Option<Self> {
302        match c {
303            b'0'..=b'9' | b'L' => Some(MessageBit::Zero),
304            b'A'..=b'K' => Some(MessageBit::CustomOne),
305            b'P'..=b'Z' => Some(MessageBit::StandardOne),
306            _ => None,
307        }
308    }
309}
310
311enum LatDir {
312    North,
313    South,
314}
315
316impl LatDir {
317    fn decode(c: u8) -> Option<Self> {
318        match c {
319            b'0'..=b'9' | b'L' => Some(LatDir::South),
320            b'P'..=b'Z' => Some(LatDir::North),
321            _ => None,
322        }
323    }
324
325    fn byte(self) -> u8 {
326        match self {
327            Self::North => b'N',
328            Self::South => b'S',
329        }
330    }
331}
332
333#[derive(Debug, PartialEq, Eq)]
334enum LongOffset {
335    Zero,
336    Hundred,
337}
338
339impl LongOffset {
340    fn decode(c: u8) -> Option<Self> {
341        match c {
342            b'0'..=b'9' | b'L' => Some(LongOffset::Zero),
343            b'P'..=b'Z' => Some(LongOffset::Hundred),
344            _ => None,
345        }
346    }
347}
348
349#[derive(Debug, PartialEq, Eq)]
350enum LongDir {
351    East,
352    West,
353}
354
355impl LongDir {
356    fn decode(c: u8) -> Option<Self> {
357        match c {
358            b'0'..=b'9' | b'L' => Some(Self::East),
359            b'P'..=b'Z' => Some(Self::West),
360            _ => None,
361        }
362    }
363}
364
365// returns the ASCII value, since we can have spaces
366fn decode_latitude_digit(c: u8) -> Option<u8> {
367    match c {
368        b'0'..=b'9' => Some(c),
369        b'A'..=b'J' => Some(c - 17),
370        b'K' | b'L' | b'Z' => Some(b' '),
371        b'P'..=b'Y' => Some(c - 32),
372        _ => None,
373    }
374}
375
376fn decode_callsign(c: &Callsign) -> Option<(Latitude, Precision, Message, LongOffset, LongDir)> {
377    let data = c.call().as_bytes();
378    if data.len() != 6 {
379        return None;
380    }
381
382    let lat_bytes = [
383        decode_latitude_digit(data[0])?,
384        decode_latitude_digit(data[1])?,
385        decode_latitude_digit(data[2])?,
386        decode_latitude_digit(data[3])?,
387        b'.',
388        decode_latitude_digit(data[4])?,
389        decode_latitude_digit(data[5])?,
390        LatDir::decode(data[3])?.byte(),
391    ];
392
393    let (lat, precision) = Latitude::parse_uncompressed(&lat_bytes).ok()?;
394
395    let a = MessageBit::decode(data[0])?;
396    let b = MessageBit::decode(data[1])?;
397    let c = MessageBit::decode(data[2])?;
398
399    let msg = Message::decode(a, b, c);
400
401    let long_offset = LongOffset::decode(data[4])?;
402
403    let long_dir = LongDir::decode(data[5])?;
404
405    Some((lat, precision, msg, long_offset, long_dir))
406}
407
408fn decode_longitude(b: &[u8], offset: LongOffset, dir: LongDir) -> Option<Longitude> {
409    if b.len() != 3 {
410        return None;
411    }
412
413    let mut d = b[0].checked_sub(28)?;
414
415    if offset == LongOffset::Hundred {
416        d = d.checked_add(100)?;
417    }
418
419    if d >= 180 && d <= 189 {
420        d -= 80;
421    } else if d >= 190 && d <= 199 {
422        d -= 190;
423    }
424
425    let mut m = b[1].checked_sub(28)?;
426
427    if m >= 60 {
428        m -= 60;
429    }
430
431    let h = b[2].checked_sub(28)?;
432
433    Longitude::from_dmh(d.into(), m.into(), h.into(), dir == LongDir::East)
434}
435
436fn decode_speed_and_course(b: &[u8]) -> Option<(Speed, Course)> {
437    let sp = u32::from(b[0].checked_sub(28)?);
438
439    let tens_knots = sp * 10;
440
441    let dc = u32::from(b[1].checked_sub(28)?);
442
443    let units_knots = dc / 10;
444    let hundreds_course = (dc % 10) * 100;
445
446    let units_course = u32::from(b[2].checked_sub(28)?);
447
448    let mut speed_knots = tens_knots + units_knots;
449    if speed_knots >= 800 {
450        speed_knots -= 800;
451    }
452
453    let mut course_degrees = hundreds_course + units_course;
454    if course_degrees >= 400 {
455        course_degrees -= 400;
456    }
457
458    let speed = Speed::new(speed_knots)?;
459    let course = Course::new(course_degrees)?;
460
461    Some((speed, course))
462}
463
464// lat_digit must be an ASCII char to allow for spaces
465fn encode_bits_012(lat_digit: u8, message_bit: MessageBit) -> u8 {
466    match (message_bit, lat_digit == b' ') {
467        (MessageBit::Zero, false) => lat_digit,
468        (MessageBit::Zero, true) => b'L',
469
470        (MessageBit::CustomOne, false) => lat_digit + 17,
471        (MessageBit::CustomOne, true) => b'K',
472
473        (MessageBit::StandardOne, false) => lat_digit + 32,
474        (MessageBit::StandardOne, true) => b'Z',
475    }
476}
477
478fn encode_bit_3(lat_digit: u8, lat_dir: LatDir) -> u8 {
479    match (lat_dir, lat_digit == b' ') {
480        (LatDir::North, false) => lat_digit + 32,
481        (LatDir::North, true) => b'Z',
482
483        (LatDir::South, false) => lat_digit,
484        (LatDir::South, true) => b'L',
485    }
486}
487
488fn encode_bit_4(lat_digit: u8, long_offset: LongOffset) -> u8 {
489    match (long_offset, lat_digit == b' ') {
490        (LongOffset::Zero, false) => lat_digit,
491        (LongOffset::Zero, true) => b'L',
492
493        (LongOffset::Hundred, false) => lat_digit + 32,
494        (LongOffset::Hundred, true) => b'Z',
495    }
496}
497
498fn encode_bit_5(lat_digit: u8, long_dir: LongDir) -> u8 {
499    match (long_dir, lat_digit == b' ') {
500        (LongDir::East, false) => lat_digit,
501        (LongDir::East, true) => b'L',
502
503        (LongDir::West, false) => lat_digit + 32,
504        (LongDir::West, true) => b'Z',
505    }
506}
507
508#[cfg(test)]
509mod tests {
510    use super::*;
511
512    #[test]
513    fn course_from_u32() {
514        let course = Course::new(123).unwrap();
515        assert_eq!(123, course.degrees());
516
517        let course = Course::new(360).unwrap();
518        assert_eq!(360, course.degrees());
519    }
520
521    #[test]
522    fn course_from_u32_smallest() {
523        let course = Course::new(0).unwrap();
524        assert_eq!(0, course.degrees());
525    }
526
527    #[test]
528    fn course_from_u32_too_big() {
529        assert_eq!(None, Course::new(361));
530    }
531
532    #[test]
533    fn speed_from_u32() {
534        let speed = Speed::new(545).unwrap();
535        assert_eq!(545, speed.knots());
536
537        let speed = Speed::new(799).unwrap();
538        assert_eq!(799, speed.knots());
539    }
540
541    #[test]
542    fn speed_from_u32_smallest() {
543        let speed = Speed::new(0).unwrap();
544        assert_eq!(0, speed.knots());
545    }
546
547    #[test]
548    fn speed_from_u32_too_big() {
549        assert_eq!(None, Speed::new(800));
550        assert_eq!(None, Speed::new(801));
551        assert_eq!(None, Speed::new(5237));
552    }
553
554    #[test]
555    fn decode_dest_test() {
556        let (latitude, precision, message, offset, dir) =
557            decode_callsign(&Callsign::new_no_ssid("S32U6T")).unwrap();
558
559        assert_eq!(Latitude::new(33.42733333333333).unwrap(), latitude);
560        assert_eq!(Precision::HundredthMinute, precision);
561        assert_eq!(Message::M3, message);
562        assert_eq!(LongOffset::Zero, offset);
563        assert_eq!(LongDir::West, dir);
564    }
565
566    #[test]
567    fn decode_test() {
568        // example from the APRS spec doc
569        let information = &br#"(_fn"Oj/Hello world!"#[..];
570        let to = Callsign::new_no_ssid("PPPPPP");
571
572        let data = AprsMicE::decode(information, to.clone(), true).unwrap();
573
574        assert_eq!(
575            AprsMicE {
576                latitude: Latitude::new(0.0).unwrap(),
577                longitude: Longitude::new(-112.12899999999999).unwrap(),
578                precision: Precision::HundredthMinute,
579                message: Message::M0,
580                speed: Speed::new(20).unwrap(),
581                course: Course::new(251).unwrap(),
582                symbol_table: b'/',
583                symbol_code: b'j',
584                comment: b"Hello world!".to_vec(),
585                current: true
586            },
587            data
588        );
589
590        let mut re_encoded = vec![];
591        data.encode(&mut re_encoded).unwrap();
592
593        // skip first byte as it's the backtick
594        assert_eq!(information, &re_encoded[1..]);
595        assert_eq!(to, data.encode_destination());
596    }
597
598    #[test]
599    fn encode_destination_test() {
600        let information = &br#"(_fn"Oj/Hello world!"#[..];
601        let to = Callsign::new_no_ssid("S5PPW4");
602
603        let data = AprsMicE::decode(information, to.clone(), true).unwrap();
604
605        assert_eq!(to, data.encode_destination());
606    }
607}