flarmnet/xcsoar/
encode.rs

1use super::fields::*;
2use crate::{File, Record};
3use encoding_rs::mem::{encode_latin1_lossy, is_str_latin1};
4use std::io::{Cursor, Write};
5use thiserror::Error;
6
7#[derive(Error, Debug)]
8pub enum EncodeError {
9    #[error(transparent)]
10    Io(#[from] std::io::Error),
11    // the value could not be converted to valid latin1
12    #[error("invalid encoding: {0}")]
13    InvalidEncoding(String),
14}
15
16/// Encodes a FlarmNet file.
17///
18/// # Examples
19///
20/// ```
21/// # use flarmnet::Record;
22/// let file = flarmnet::File {
23///     version: 123,
24///     records: vec![
25///         Record {
26///             flarm_id: "3EE3C7".to_string(),
27///             pilot_name: "Tobias Bieniek".to_string(),
28///             airfield: "EDKA".to_string(),
29///             plane_type: "LS6a".to_string(),
30///             registration: "D-0816".to_string(),
31///             call_sign: "SG".to_string(),
32///             frequency: "130.530".to_string(),
33///         }
34///     ]
35/// };
36///
37/// let result = flarmnet::xcsoar::encode_file(&file).unwrap();
38/// assert_eq!(result, br#"00007b
39/// 334545334337546f62696173204269656e69656b2020202020202045444b4120202020202020202020202020202020204c5336612020202020202020202020202020202020442d30383136205347203133302e353330
40/// "#);
41/// ```
42pub fn encode_file(file: &File) -> Result<Vec<u8>, EncodeError> {
43    let mut writer = Writer::new(Cursor::new(Vec::new()));
44    writer.write(file)?;
45
46    let buffer = writer.into_inner().into_inner();
47
48    Ok(buffer)
49}
50
51#[derive(Clone)]
52pub struct Writer<W: Write> {
53    writer: W,
54}
55
56impl<W: Write> Writer<W> {
57    pub fn new(inner: W) -> Self {
58        Self { writer: inner }
59    }
60
61    pub fn write(&mut self, file: &File) -> Result<(), EncodeError> {
62        self.write_version(file.version)?;
63        for record in &file.records {
64            self.write_record(record)?;
65        }
66
67        Ok(())
68    }
69
70    fn write_version(&mut self, version: u32) -> Result<(), EncodeError> {
71        self.writer.write_fmt(format_args!("{:06x?}", version))?;
72        self.writer.write_all(b"\n")?;
73
74        Ok(())
75    }
76
77    fn write_record(&mut self, record: &Record) -> Result<(), EncodeError> {
78        self.write_str(&record.flarm_id, FLARM_ID_LENGTH)?;
79        self.write_str(&record.pilot_name, PILOT_NAME_LENGTH)?;
80        self.write_str(&record.airfield, AIRFIELD_LENGTH)?;
81        self.write_str(&record.plane_type, PLANE_TYPE_LENGTH)?;
82        self.write_str(&record.registration, REGISTRATION_LENGTH)?;
83        self.write_str(&record.call_sign, CALL_SIGN_LENGTH)?;
84        self.write_str(&record.frequency, FREQUENCY_LENGTH)?;
85        self.writer.write_all(b"\n")?;
86
87        Ok(())
88    }
89
90    pub(crate) fn write_str(&mut self, value: &str, length: usize) -> Result<(), EncodeError> {
91        if !is_str_latin1(value) {
92            return Err(EncodeError::InvalidEncoding(value.to_string()));
93        }
94
95        let bytes = encode_latin1_lossy(value);
96        for byte in bytes.iter().take(length) {
97            self.writer.write_fmt(format_args!("{:02x?}", byte))?;
98        }
99
100        let bytes_len = bytes.len();
101        if bytes_len < length {
102            let placeholders = b"20".repeat(length - bytes_len);
103            self.writer.write_all(&placeholders)?;
104        }
105
106        Ok(())
107    }
108
109    pub fn into_inner(self) -> W {
110        self.writer
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::{EncodeError, Writer};
117    use insta::assert_debug_snapshot;
118    use std::io::Cursor;
119
120    fn encode_str(value: &str, length: usize) -> Result<String, EncodeError> {
121        let mut writer = Writer::new(Cursor::new(Vec::new()));
122        writer.write_str(value, length)?;
123        let bytes = writer.into_inner().into_inner();
124        Ok(String::from_utf8(bytes).unwrap())
125    }
126
127    #[test]
128    fn encoding_works() {
129        assert_eq!(encode_str("D-4711", 7).unwrap(), "442d3437313120");
130        assert_eq!(encode_str("1234567890", 7).unwrap(), "31323334353637");
131        assert_eq!(encode_str("Müller", 10).unwrap(), "4dfc6c6c657220202020");
132    }
133
134    #[test]
135    fn encoding_fails_for_non_latin1() {
136        assert_debug_snapshot!(
137            encode_str("😅", 7).unwrap_err(),
138            @r###"
139            InvalidEncoding(
140                "😅",
141            )
142            "###);
143    }
144}