1use std::fmt::{Display, Formatter};
2use std::io::{self, Write};
3
4use EncodeError;
5
6pub enum CallsignField {
7 Destination,
8 Source,
9 Via(bool),
10}
11
12#[derive(Eq, PartialEq, Debug, Clone, Hash)]
13pub struct Callsign {
14 call: String,
15 ssid: Option<String>,
16}
17
18impl Callsign {
19 pub fn new(s: impl AsRef<str>) -> Option<Self> {
22 match s.as_ref().split_once('-') {
23 Some((call, ssid)) => {
24 if call.is_empty() || ssid.is_empty() {
25 None
26 } else {
27 Some(Callsign::new_with_ssid(call.to_owned(), ssid))
28 }
29 }
30
31 None => Some(Callsign::new_no_ssid(s.as_ref())),
32 }
33 }
34
35 pub fn new_no_ssid(call: impl Into<String>) -> Callsign {
38 let call = call.into();
39 let ssid = None;
40 Callsign { call, ssid }
41 }
42
43 pub fn new_with_ssid(call: impl Into<String>, ssid: impl Into<String>) -> Callsign {
44 let call = call.into();
45 let ssid = ssid.into();
46
47 let ssid = if ssid == "0" { None } else { Some(ssid) };
48
49 Callsign { call, ssid }
50 }
51
52 pub fn call(&self) -> &str {
53 &self.call
54 }
55
56 pub fn ssid(&self) -> Option<&str> {
57 self.ssid.as_deref()
58 }
59
60 pub fn encode_textual<W: Write>(&self, heard: bool, w: &mut W) -> io::Result<()> {
61 write!(w, "{}", self)?;
62
63 if heard {
64 write!(w, "*")?;
65 }
66
67 Ok(())
68 }
69
70 pub fn decode_textual(bytes: &[u8]) -> Option<(Self, bool)> {
71 let (bytes, heard) = if bytes.last() == Some(&b'*') {
72 (&bytes[0..(bytes.len() - 1)], true)
73 } else {
74 (bytes, false)
75 };
76
77 let s = std::str::from_utf8(bytes).ok()?;
78
79 Self::new(s).map(|c| (c, heard))
80 }
81
82 pub fn encode_ax25<W: Write>(
83 &self,
84 buf: &mut W,
85 field: CallsignField,
86 has_more: bool,
87 ) -> Result<(), EncodeError> {
88 let call = self.call.as_bytes();
94 if call.len() > 6 {
95 return Err(EncodeError::InvalidCallsign(self.clone()));
96 }
97
98 let ssid: u8 = self
99 .ssid
100 .clone()
101 .map(|x| x.parse().ok())
102 .unwrap_or(Some(0))
103 .ok_or_else(|| EncodeError::InvalidCallsign(self.clone()))?;
104
105 if ssid > 15 {
106 return Err(EncodeError::InvalidCallsign(self.clone()));
107 }
108
109 let has_more = if has_more { 0 } else { 1 };
110
111 for c in call {
112 if !c.is_ascii_alphanumeric() {
113 return Err(EncodeError::InvalidCallsign(self.clone()));
114 }
115
116 buf.write_all(&[c.to_ascii_uppercase() << 1])?;
117 }
118
119 for _ in call.len()..6 {
120 buf.write_all(&[b' ' << 1])?;
121 }
122
123 match field {
124 CallsignField::Destination => {
125 buf.write_all(&[0b11100000 | (ssid << 1) | has_more])?;
126 }
127 CallsignField::Source => {
128 buf.write_all(&[0b01100000 | (ssid << 1) | has_more])?;
129 }
130 CallsignField::Via(heard) => {
131 let heard = if heard { 1 } else { 0 };
132
133 buf.write_all(&[0b01100000 | (ssid << 1) | (heard << 7) | has_more])?;
134 }
135 }
136
137 Ok(())
138 }
139
140 pub fn decode_ax25(data: &[u8]) -> Option<(Self, bool, bool)> {
142 if data.len() != 7 {
143 return None;
144 }
145
146 let mut call = String::new();
147 let mut found_space = false;
148 for d in &data[0..6] {
149 if (d & 0x01) != 0 {
151 return None;
152 }
153 let d = d >> 1;
154
155 if d == b' ' {
156 found_space = true;
157 continue;
158 }
159
160 if found_space {
162 return None;
163 }
164
165 if !d.is_ascii_alphanumeric() {
166 return None;
167 }
168
169 call.push(d.to_ascii_uppercase().into());
170 }
171
172 let s = data[6];
173 let heard = (s & 0x80) != 0;
174 let has_more = (s & 0x01) == 0;
175 let ssid = (s & 0x1E) >> 1;
176
177 let ssid = if ssid == 0 {
178 None
179 } else {
180 Some(format!("{}", ssid))
181 };
182
183 Some((Callsign { call, ssid }, heard, has_more))
184 }
185}
186
187impl Display for Callsign {
188 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
189 write!(f, "{}", self.call)?;
190
191 if let Some(ssid) = &self.ssid {
192 if !ssid.is_empty() {
193 write!(f, "-{}", ssid)?;
194 }
195 }
196
197 Ok(())
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn parse_callsign() {
207 assert_eq!(
208 Callsign::decode_textual(&b"ABCDEF"[..]),
209 Some((Callsign::new("ABCDEF").unwrap(), false))
210 );
211 }
212
213 #[test]
214 fn parse_heard_callsign() {
215 assert_eq!(
216 Callsign::decode_textual(&b"ABCDEF*"[..]),
217 Some((Callsign::new("ABCDEF").unwrap(), true))
218 );
219 }
220
221 #[test]
222 fn parse_with_ssid() {
223 assert_eq!(
224 Callsign::decode_textual(&b"ABCDEF-2"[..]),
225 Some((Callsign::new_with_ssid("ABCDEF", "2"), false))
226 );
227
228 assert_eq!(
229 Callsign::decode_textual(&b"ABCDEF-0"[..]),
230 Some((Callsign::new_no_ssid("ABCDEF"), false))
231 );
232 }
233
234 #[test]
235 fn omit_end_spaces() {
236 assert_eq!(
237 Callsign::decode_ax25(&[172, 138, 114, 64, 64, 64, 1]),
238 Some((Callsign::new_no_ssid("VE9"), false, false))
239 )
240 }
241
242 #[test]
243 fn spaces_in_middle() {
244 assert_eq!(Callsign::decode_ax25(&[172, 64, 114, 64, 64, 64, 1]), None)
245 }
246
247 #[test]
248 fn uppercase_callsign() {
249 assert_eq!(
250 Callsign::decode_ax25(&[236, 202, 114, 64, 64, 64, 1]),
251 Some((Callsign::new_no_ssid("VE9"), false, false))
252 );
253
254 let mut buf = vec![];
255 let c = Callsign::new_no_ssid("ve9");
256 c.encode_ax25(&mut buf, CallsignField::Destination, false)
257 .unwrap();
258 assert_eq!(&[172, 138, 114, 64, 64, 64, 225][..], buf);
259
260 let mut buf = vec![];
262 let c = Callsign::new_no_ssid("VE9");
263 c.encode_ax25(&mut buf, CallsignField::Destination, false)
264 .unwrap();
265 assert_eq!(&[172, 138, 114, 64, 64, 64, 225][..], buf);
266 }
267
268 #[test]
269 fn non_alphanumeric() {
270 let mut buf = vec![];
271 assert!(matches!(
272 Callsign::new_no_ssid("VE9---").encode_ax25(
273 &mut buf,
274 CallsignField::Destination,
275 false
276 ),
277 Err(EncodeError::InvalidCallsign(c)) if c == Callsign::new_no_ssid(
278 "VE9---"
279 )));
280 }
281
282 #[test]
283 fn callsign_too_long() {
284 let mut buf = vec![];
285 assert!(matches!(
286 Callsign::new_no_ssid("VE9ABCD").encode_ax25(
287 &mut buf,
288 CallsignField::Source,
289 false
290 ),
291 Err(EncodeError::InvalidCallsign(c)) if c == Callsign::new_no_ssid(
292 "VE9ABCD"
293 )
294 ));
295 }
296
297 #[test]
298 fn empty_callsign() {
299 assert_eq!(Callsign::decode_textual("-3".as_bytes()), None);
300 }
301
302 #[test]
303 fn empty_ssid() {
304 assert_eq!(Callsign::decode_textual("ABCDEF-".as_bytes()), None);
305 }
306
307 #[test]
308 fn non_utf8() {
309 assert_eq!(Callsign::decode_textual(&b"ABCDEF\xF0\xA4\xAD"[..]), None);
310 }
311
312 #[test]
313 fn textual_no_ssid() {
314 let c = Callsign::new_no_ssid("ABCDEF");
315
316 assert_eq!("ABCDEF", format!("{}", c));
317
318 let mut buf = vec![];
319 c.encode_textual(true, &mut buf).unwrap();
320 assert_eq!(&b"ABCDEF*"[..], buf);
321
322 buf.clear();
323 c.encode_textual(false, &mut buf).unwrap();
324 assert_eq!(&b"ABCDEF"[..], buf);
325 }
326
327 #[test]
328 fn textual_with_ssid() {
329 let c = Callsign::new_with_ssid("ABCDEF", "XF");
330
331 assert_eq!("ABCDEF-XF", format!("{}", c));
332
333 let mut buf = vec![];
334 c.encode_textual(true, &mut buf).unwrap();
335 assert_eq!(&b"ABCDEF-XF*"[..], buf);
336
337 buf.clear();
338 c.encode_textual(false, &mut buf).unwrap();
339 assert_eq!(&b"ABCDEF-XF"[..], buf);
340 }
341
342 #[test]
343 fn textual_non_ascii_characters() {
344 let c = Callsign::new_with_ssid("ABCDEF\x001\x002", "XF\x002\x003");
345
346 assert_eq!("ABCDEF\x001\x002-XF\x002\x003", format!("{}", c));
347
348 let mut buf = vec![];
349 c.encode_textual(true, &mut buf).unwrap();
350 assert_eq!(&b"ABCDEF\x001\x002-XF\x002\x003*"[..], buf);
351
352 buf.clear();
353 c.encode_textual(false, &mut buf).unwrap();
354 assert_eq!(&b"ABCDEF\x001\x002-XF\x002\x003"[..], buf);
355 }
356
357 #[test]
358 fn display_no_ssid() {
359 assert_eq!("ABCDEF", format!("{}", Callsign::new_no_ssid("ABCDEF")));
360 }
361
362 #[test]
363 fn display_with_ssid() {
364 assert_eq!(
365 "ABCDEF-12",
366 format!("{}", Callsign::new_with_ssid("ABCDEF", "12"))
367 );
368 }
369}