flarmnet/xcsoar/
encode.rs1use 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 #[error("invalid encoding: {0}")]
13 InvalidEncoding(String),
14}
15
16pub 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}