1use std::convert::TryFrom;
11use std::io::Write;
12
13use Callsign;
14use DecodeError;
15use DhmTimestamp;
16use EncodeError;
17use Timestamp;
18
19#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct AprsStatus {
21 pub to: Callsign,
22
23 timestamp: Option<Timestamp>,
24 comment: Vec<u8>,
25}
26
27impl AprsStatus {
28 pub fn new(to: Callsign, timestamp: Option<DhmTimestamp>, comment: Vec<u8>) -> Self {
29 let timestamp = timestamp.map(|t| t.into());
30 Self {
31 to,
32 timestamp,
33 comment,
34 }
35 }
36
37 pub fn new_noncompliant(to: Callsign, timestamp: Option<Timestamp>, comment: Vec<u8>) -> Self {
41 Self {
42 to,
43 timestamp,
44 comment,
45 }
46 }
47
48 pub fn is_timestamp_compliant(&self) -> bool {
49 self.timestamp
50 .as_ref()
51 .map(|t| matches!(t, Timestamp::DDHHMM(_, _, _)))
52 .unwrap_or(true)
53 }
54
55 pub fn timestamp(&self) -> Option<&Timestamp> {
56 self.timestamp.as_ref()
57 }
58
59 pub fn comment(&self) -> &[u8] {
60 &self.comment
61 }
62
63 pub fn decode(b: &[u8], to: Callsign) -> Result<Self, DecodeError> {
64 let timestamp = b.get(..7).and_then(|b| Timestamp::try_from(b).ok());
67 let comment = if timestamp.is_some() { &b[7..] } else { b };
68
69 Ok(AprsStatus {
70 to,
71 timestamp,
72 comment: comment.to_owned(),
73 })
74 }
75
76 pub fn encode<W: Write>(&self, buf: &mut W) -> Result<(), EncodeError> {
77 write!(buf, ">")?;
78
79 if let Some(ts) = &self.timestamp {
80 ts.encode(buf)?;
81 }
82
83 buf.write_all(&self.comment)?;
84
85 Ok(())
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 fn default_callsign() -> Callsign {
94 Callsign::new_no_ssid("VE9")
95 }
96
97 #[test]
98 fn parse_without_timestamp_or_comment() {
99 let result = AprsStatus::decode(&b""[..], default_callsign()).unwrap();
100
101 assert_eq!(result.to, default_callsign());
102 assert_eq!(result.timestamp, None);
103 assert_eq!(result.comment, []);
104 }
105
106 #[test]
107 fn parse_with_timestamp_without_comment() {
108 let result = AprsStatus::decode(r"312359z".as_bytes(), default_callsign()).unwrap();
109
110 assert_eq!(result.to, default_callsign());
111 assert_eq!(result.timestamp, Some(Timestamp::DDHHMM(31, 23, 59)));
112 assert_eq!(result.comment, b"");
113 }
114
115 #[test]
116 fn parse_without_timestamp_with_comment() {
117 let result = AprsStatus::decode(&b"Hi there!"[..], default_callsign()).unwrap();
118
119 assert_eq!(result.to, default_callsign());
120 assert_eq!(result.timestamp, None);
121 assert_eq!(result.comment, b"Hi there!");
122 }
123
124 #[test]
125 fn parse_with_timestamp_and_comment() {
126 let result =
127 AprsStatus::decode(r"235959hHi there!".as_bytes(), default_callsign()).unwrap();
128
129 assert_eq!(result.to, default_callsign());
130 assert_eq!(result.timestamp, Some(Timestamp::HHMMSS(23, 59, 59)));
131 assert_eq!(result.comment, b"Hi there!");
132 }
133
134 #[test]
135 fn compliant_time_is_compliant() {
136 let result = AprsStatus::decode(r"312359z".as_bytes(), default_callsign()).unwrap();
137
138 assert_eq!(result.to, default_callsign());
139 assert_eq!(result.timestamp, Some(Timestamp::DDHHMM(31, 23, 59)));
140 assert!(result.is_timestamp_compliant());
141 }
142
143 #[test]
144 fn uncompliant_time_is_not_compliant() {
145 let result =
146 AprsStatus::decode(r"235959hHi there!".as_bytes(), default_callsign()).unwrap();
147
148 assert_eq!(result.to, default_callsign());
149 assert_eq!(result.timestamp, Some(Timestamp::HHMMSS(23, 59, 59)));
150 assert!(!result.is_timestamp_compliant());
151 }
152
153 #[test]
154 fn missing_time_is_compliant() {
155 let result = AprsStatus::decode(&b"Hi there!"[..], default_callsign()).unwrap();
156
157 assert_eq!(result.to, default_callsign());
158 assert_eq!(result.timestamp, None);
159 assert!(result.is_timestamp_compliant());
160 }
161}