1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
use std::str::FromStr;

use APRSError;
use Callsign;
use APRSPosition;

#[derive(PartialEq, Debug, Clone)]
pub struct APRSMessage {
    pub from: Callsign,
    pub to: Callsign,
    pub via: Vec<Callsign>,
    pub data: APRSData,
}

impl FromStr for APRSMessage {
    type Err = APRSError;

    fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
        let header_delimiter = s.find(':').ok_or_else(|| APRSError::InvalidMessage(s.to_owned()))?;
        let (header, rest) = s.split_at(header_delimiter);
        let body = &rest[1..];

        let from_delimiter = header.find('>').ok_or_else(|| APRSError::InvalidMessage(s.to_owned()))?;
        let (from, rest) = header.split_at(from_delimiter);
        let from = Callsign::from_str(from)?;

        let to_and_via = &rest[1..];
        let to_and_via: Vec<_> = to_and_via.split(',').collect();

        let to = to_and_via.first().ok_or_else(|| APRSError::InvalidMessage(s.to_owned()))?;
        let to = Callsign::from_str(to)?;

        let mut via = vec!();
        for v in to_and_via.iter().skip(1) {
            via.push(Callsign::from_str(v)?);
        }

        let data = APRSPosition::from_str(body).map(APRSData::Position)
            .unwrap_or(APRSData::Unknown);

        Ok(APRSMessage { from, to, via, data })
    }
}

#[derive(PartialEq, Debug, Clone)]
pub enum APRSData {
    Position(APRSPosition),
    Unknown,
}


#[cfg(test)]
mod tests {
    use super::*;
    use Timestamp;

    #[test]
    fn parse() {
        let result = r"ICA3D17F2>APRS,qAS,dl4mea:/074849h4821.61N\01224.49E^322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1".parse::<APRSMessage>().unwrap();
        assert_eq!(result.from, Callsign::new("ICA3D17F2", None));
        assert_eq!(result.to, Callsign::new("APRS", None));
        assert_eq!(result.via, vec![
            Callsign::new("qAS", None),
            Callsign::new("dl4mea", None),
        ]);

        match result.data {
            APRSData::Position(position) => {
                assert_eq!(position.timestamp, Some(Timestamp::HHMMSS(7, 48, 49)));
                assert_relative_eq!(position.latitude, 48.360166);
                assert_relative_eq!(position.longitude, 12.408166);
                assert_eq!(position.comment, "322/103/A=003054 !W09! id213D17F2 -039fpm +0.0rot 2.5dB 3e -0.0kHz gps1x1");
            },
            _ => panic!("Unexpected data type"),
        }
    }
}