binarygcode/components/
serialiser.rs

1use alloc::{boxed::Box, vec::Vec};
2use embedded_heatshrink::{
3    HSEFinishRes, HSEPollRes, HSESinkRes, HeatshrinkEncoder,
4};
5use miniz_oxide::deflate::compress_to_vec_zlib;
6
7use crate::components::common::{
8    crc32, BinaryGcodeError, BlockKind, Checksum, CompressionAlgorithm,
9    Encoding, MAGIC,
10};
11
12pub fn serialise_file_header(
13    version: u32,
14    checksum: Checksum,
15) -> Box<[u8]> {
16    let mut header = Vec::with_capacity(10);
17    header.extend(MAGIC.to_le_bytes());
18    header.extend(version.to_le_bytes());
19    header.extend(checksum.to_le_bytes());
20    header.into_boxed_slice()
21}
22
23/// Serialise a gcode block.
24pub fn serialise_block(
25    kind: BlockKind,
26    compression: CompressionAlgorithm,
27    encoding: Encoding,
28    checksum: Checksum,
29    additional_parameters: &[u8],
30    data: &[u8],
31) -> Result<Box<[u8]>, BinaryGcodeError> {
32    // Create the block header
33    let mut block: Vec<u8> = Vec::new();
34    block.extend(kind.to_le_bytes());
35    block.extend(compression.to_le_bytes());
36    let uncompressed_len = data.len() as u32;
37    block.extend(uncompressed_len.to_le_bytes());
38
39    // Additional parameters beyond encoding
40    let mut parameters: Vec<u8> = Vec::with_capacity(0);
41    parameters.extend(encoding.to_le_bytes());
42    parameters.extend(additional_parameters);
43    // We do not append it to the block here as we need to check
44    // if the data is going to be compressed.
45
46    // Compression
47    match compression {
48        CompressionAlgorithm::None => {
49            block.extend(parameters);
50            block.extend(data);
51        }
52        CompressionAlgorithm::Deflate => {
53            let compressed = compress_to_vec_zlib(data, 10);
54            let compressed_len = compressed.len() as u32;
55            block.extend(compressed_len.to_le_bytes());
56            block.extend(parameters);
57            block.extend(compressed);
58        }
59        CompressionAlgorithm::Heatshrink11_4 => {
60            let compressed = shrink(11, 4, data)?;
61            let compressed_len = compressed.len() as u32;
62            block.extend(compressed_len.to_le_bytes());
63            block.extend(parameters);
64            block.extend(compressed);
65        }
66        CompressionAlgorithm::Heatshrink12_4 => {
67            let compressed = shrink(12, 4, data)?;
68            let compressed_len = compressed.len() as u32;
69            block.extend(compressed_len.to_le_bytes());
70            block.extend(parameters);
71            block.extend(compressed);
72        }
73    }
74
75    // CRC
76    if checksum == Checksum::Crc32 {
77        let crc = crc32(&block);
78        block.extend(crc.to_le_bytes());
79    }
80
81    Ok(block.into_boxed_slice())
82}
83
84/// A wrapper around the heatshrink algorithm that can be
85/// used to compress gcode.
86/// TODO: add a check to limit the size of the input slice.
87/// Ask the bgcode spec makers if there is a limit.
88fn shrink(
89    window: u8,
90    lookahead: u8,
91    input: &[u8],
92) -> Result<Box<[u8]>, BinaryGcodeError> {
93    let mut encoder = HeatshrinkEncoder::new(window, lookahead).unwrap();
94    let mut sunk: usize = 0;
95    let mut polled: usize = 0;
96
97    // Should never be as big as the input
98    let mut output = vec![0u8; input.len()];
99
100    // Keep looping until we have sunk all the input data
101    while sunk < input.len() {
102        // Sink the next
103        match encoder.sink(&input[sunk..]) {
104            HSESinkRes::Ok(sz) => {
105                sunk += sz;
106            }
107            _ => return Err(BinaryGcodeError::SerialiseError("heatshrink_01")),
108        }
109        // Loop to get the data out of the encoder.
110        loop {
111            match encoder.poll(&mut output[polled..]) {
112                // Through my trials. Only this is ever called
113                // in our scenario.
114                HSEPollRes::Empty(sz) => {
115                    polled += sz;
116                    if sz == 0 {
117                        break;
118                    }
119                }
120                _ => {
121                    return Err(BinaryGcodeError::SerialiseError(
122                        "heatshrink_02",
123                    ))
124                }
125            }
126        }
127    }
128
129    // Loop the check if there is any data remaining.
130    loop {
131        match encoder.finish() {
132            HSEFinishRes::Done => break,
133            HSEFinishRes::More => match encoder.poll(&mut output[polled..]) {
134                // Through my trials. Only this was ever called
135                // in our scenario.
136                HSEPollRes::Empty(sz) => polled += sz,
137                _ => {
138                    return Err(BinaryGcodeError::SerialiseError(
139                        "heatshrink_03",
140                    ))
141                }
142            },
143            _ => return Err(BinaryGcodeError::SerialiseError("heatshrink_04")),
144        }
145    }
146
147    // Resize so we don't pass any null bytes back out.
148    output.resize(polled, 0u8);
149    Ok(output.into_boxed_slice())
150}
151
152#[cfg(test)]
153mod test {
154    use super::*;
155    use crate::{
156        Checksum, {DeserialisedResult, Deserialiser},
157    };
158
159    #[test]
160    pub fn serde_gcode_none() {
161        let header = serialise_file_header(1, Checksum::Crc32);
162        let gcode = "M73 P0 R30";
163        let block = serialise_block(
164            BlockKind::GCode,
165            CompressionAlgorithm::None,
166            Encoding::Ascii,
167            Checksum::Crc32,
168            &[],
169            gcode.as_bytes(),
170        )
171        .unwrap();
172        let mut deserialiser = Deserialiser::default();
173        deserialiser.digest(&header);
174        deserialiser.digest(&block);
175        loop {
176            let r = deserialiser.deserialise().unwrap();
177            match r {
178                DeserialisedResult::FileHeader(_) => {}
179                DeserialisedResult::Block(_) => {}
180                DeserialisedResult::MoreBytesRequired(_) => {
181                    break;
182                }
183            }
184        }
185    }
186
187    #[test]
188    pub fn serde_gcode_deflate() {
189        let header = serialise_file_header(1, Checksum::Crc32);
190        let gcode = "M73 P0 R30";
191        let block = serialise_block(
192            BlockKind::GCode,
193            CompressionAlgorithm::Deflate,
194            Encoding::Ascii,
195            Checksum::Crc32,
196            &[],
197            gcode.as_bytes(),
198        )
199        .unwrap();
200        let mut deserialiser = Deserialiser::default();
201        deserialiser.digest(&header);
202        deserialiser.digest(&block);
203        loop {
204            let r = deserialiser.deserialise().unwrap();
205            match r {
206                DeserialisedResult::FileHeader(_) => {}
207                DeserialisedResult::Block(_) => {}
208                DeserialisedResult::MoreBytesRequired(_) => {
209                    break;
210                }
211            }
212        }
213    }
214
215    #[test]
216    pub fn serde_gcode_deflate_no_crc() {
217        let header = serialise_file_header(1, Checksum::None);
218        let gcode = "M73 P0 R30";
219        let block = serialise_block(
220            BlockKind::GCode,
221            CompressionAlgorithm::Deflate,
222            Encoding::Ascii,
223            Checksum::None,
224            &[],
225            gcode.as_bytes(),
226        )
227        .unwrap();
228        let mut deserialiser = Deserialiser::default();
229        deserialiser.digest(&header);
230        deserialiser.digest(&block);
231        loop {
232            let r = deserialiser.deserialise().unwrap();
233            match r {
234                DeserialisedResult::FileHeader(_) => {}
235                DeserialisedResult::Block(_) => {}
236                DeserialisedResult::MoreBytesRequired(_) => {
237                    break;
238                }
239            }
240        }
241    }
242}