1use crate::{Result, error::Error};
3
4pub const CPF_FILE_SIZE: usize = 40;
6
7#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct CpfFile {
10 pub font: u32,
12 pub shadow: u32,
14 pub colors: [u32; 4],
16 pub hex_lines: i32,
18 pub paper_disabled: u32,
20 pub background: u8,
22 pub screen_limit: i32,
24}
25
26pub fn parse_cpf_file(data: &[u8]) -> Result<CpfFile> {
28 if data.len() != CPF_FILE_SIZE {
29 return Err(Error::Parse(format!(
30 "CPF file must be exactly {CPF_FILE_SIZE} bytes, got {}",
31 data.len()
32 )));
33 }
34 if &data[..3] != b"2.0" {
35 return Err(Error::Parse("CPF signature must be 2.0".into()));
36 }
37 let word = |offset| {
38 u32::from_le_bytes([
39 data[offset],
40 data[offset + 1],
41 data[offset + 2],
42 data[offset + 3],
43 ])
44 };
45 Ok(CpfFile {
46 font: word(3),
47 shadow: word(7),
48 colors: [word(11), word(15), word(19), word(23)],
49 hex_lines: word(27) as i32,
50 paper_disabled: word(31),
51 background: data[35],
52 screen_limit: word(36) as i32,
53 })
54}
55
56#[must_use]
58pub fn encode_cpf_file(file: &CpfFile) -> [u8; CPF_FILE_SIZE] {
59 let mut bytes = [0; CPF_FILE_SIZE];
60 bytes[..3].copy_from_slice(b"2.0");
61 for (offset, value) in [
62 (3, file.font),
63 (7, file.shadow),
64 (11, file.colors[0]),
65 (15, file.colors[1]),
66 (19, file.colors[2]),
67 (23, file.colors[3]),
68 (27, file.hex_lines as u32),
69 (31, file.paper_disabled),
70 (36, file.screen_limit as u32),
71 ] {
72 bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
73 }
74 bytes[35] = file.background;
75 bytes
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 const BYTES: [u8; CPF_FILE_SIZE] = [
83 b'2', b'.', b'0', 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 15, 0, 0, 0,
84 255, 255, 255, 255, 1, 0, 0, 0, 199, 0, 44, 1, 0,
85 ];
86
87 #[test]
88 fn reads_and_writes_the_documented_layout_without_normalizing() {
89 let expected = CpfFile {
90 font: 1,
91 shadow: 2,
92 colors: [3, 13, 9, 15],
93 hex_lines: -1,
94 paper_disabled: 1,
95 background: 199,
96 screen_limit: 300 * 256,
97 };
98 assert_eq!(parse_cpf_file(&BYTES).unwrap(), expected);
99 assert_eq!(encode_cpf_file(&expected), BYTES);
100 }
101
102 #[test]
103 fn rejects_incomplete_trailing_and_wrong_version_data() {
104 for length in 0..CPF_FILE_SIZE {
105 assert!(parse_cpf_file(&BYTES[..length]).is_err());
106 }
107 let mut trailing = BYTES.to_vec();
108 trailing.push(0);
109 assert!(parse_cpf_file(&trailing).is_err());
110 let mut invalid = BYTES;
111 invalid[0] = b'3';
112 assert!(parse_cpf_file(&invalid).is_err());
113 }
114}