rakata_formats/ltr/
reader.rs1use std::io::Read;
4
5use crate::binary;
6
7use super::{
8 Ltr, LtrBinaryError, LtrProbabilityBlock, EXPECTED_FILE_SIZE, FILE_HEADER_SIZE,
9 FLOAT_SIZE_BYTES, LTR_CHARACTER_COUNT, LTR_MAGIC, LTR_VERSION_V10,
10};
11
12#[cfg_attr(
21 feature = "tracing",
22 tracing::instrument(level = "debug", skip(reader))
23)]
24pub fn read_ltr<R: Read>(reader: &mut R) -> Result<Ltr, LtrBinaryError> {
25 let mut bytes = Vec::new();
26 reader.read_to_end(&mut bytes)?;
27 read_ltr_from_bytes(&bytes)
28}
29
30#[cfg_attr(
40 feature = "tracing",
41 tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
42)]
43pub fn read_ltr_from_bytes(bytes: &[u8]) -> Result<Ltr, LtrBinaryError> {
44 if bytes.len() < FILE_HEADER_SIZE {
45 return Err(LtrBinaryError::InvalidHeader(
46 "file smaller than LTR header".into(),
47 ));
48 }
49
50 let magic = read_fourcc(bytes, 0)?;
51 if magic != LTR_MAGIC {
52 return Err(LtrBinaryError::InvalidMagic(magic));
53 }
54
55 let version = read_fourcc(bytes, 4)?;
56 if version != LTR_VERSION_V10 {
57 return Err(LtrBinaryError::InvalidVersion(version));
58 }
59
60 let letter_count = bytes[8];
61 if usize::from(letter_count) != LTR_CHARACTER_COUNT {
62 return Err(LtrBinaryError::UnsupportedLetterCount(letter_count));
63 }
64
65 if bytes.len() < EXPECTED_FILE_SIZE {
66 return Err(LtrBinaryError::InvalidHeader(format!(
67 "file smaller than expected LTR payload: expected at least {EXPECTED_FILE_SIZE} bytes, got {}",
68 bytes.len()
69 )));
70 }
71
72 let mut offset = FILE_HEADER_SIZE;
73 let mut ltr = Ltr::new();
74
75 ltr.singles = read_block(bytes, &mut offset)?;
76 for block in ltr.doubles.iter_mut() {
77 *block = read_block(bytes, &mut offset)?;
78 }
79 for row in ltr.triples.iter_mut() {
80 for block in row.iter_mut() {
81 *block = read_block(bytes, &mut offset)?;
82 }
83 }
84
85 Ok(ltr)
86}
87
88fn read_fourcc(bytes: &[u8], offset: usize) -> Result<[u8; 4], LtrBinaryError> {
89 binary::read_fourcc(bytes, offset).map_err(|err| LtrBinaryError::InvalidHeader(err.to_string()))
90}
91
92fn read_next_f32(bytes: &[u8], offset: &mut usize) -> Result<f32, LtrBinaryError> {
93 let value = binary::read_f32(bytes, *offset)
94 .map_err(|err| LtrBinaryError::InvalidData(err.to_string()))?;
95 *offset = offset
96 .checked_add(FLOAT_SIZE_BYTES)
97 .ok_or_else(|| LtrBinaryError::InvalidData("float offset overflow".into()))?;
98 Ok(value)
99}
100
101fn read_block(bytes: &[u8], offset: &mut usize) -> Result<LtrProbabilityBlock, LtrBinaryError> {
102 let mut block = LtrProbabilityBlock::new();
103
104 for chance in &mut block.start {
105 *chance = read_next_f32(bytes, offset)?;
106 }
107 for chance in &mut block.middle {
108 *chance = read_next_f32(bytes, offset)?;
109 }
110 for chance in &mut block.end {
111 *chance = read_next_f32(bytes, offset)?;
112 }
113
114 Ok(block)
115}
116
117#[cfg(test)]
118mod tests {
119 use std::io::Cursor;
120
121 use super::*;
122 use crate::ltr::{write_ltr, write_ltr_to_vec};
123
124 fn sample_ltr() -> Ltr {
126 let mut ltr = Ltr::new();
127 ltr.singles.start[0] = 0.95;
128 ltr.singles.middle[1] = 0.80;
129 ltr.singles.end[2] = 0.40;
130 ltr.doubles[3].start[4] = 0.65;
131 ltr.doubles[5].middle[6] = 0.50;
132 ltr.doubles[7].end[8] = 0.35;
133 ltr.triples[9][10].start[11] = 0.70;
134 ltr.triples[12][13].middle[14] = 0.55;
135 ltr.triples[15][16].end[17] = 0.45;
136 ltr
137 }
138
139 #[test]
140 fn roundtrip_synthetic_ltr() {
141 let ltr = sample_ltr();
142 let bytes = write_ltr_to_vec(<r).expect("write should succeed");
143 let parsed = read_ltr_from_bytes(&bytes).expect("read should succeed");
144 assert_eq!(parsed, ltr);
145 }
146
147 #[test]
148 fn writer_is_deterministic_for_synthetic_ltr() {
149 let ltr = sample_ltr();
150 let first = write_ltr_to_vec(<r).expect("first write should succeed");
151 let second = write_ltr_to_vec(<r).expect("second write should succeed");
152 assert_eq!(first, second);
153 }
154
155 #[test]
156 fn read_write_roundtrip_via_io_traits() {
157 let ltr = sample_ltr();
158 let mut out = Vec::new();
159 write_ltr(&mut out, <r).expect("write should succeed");
160 let parsed = read_ltr(&mut Cursor::new(out)).expect("read should succeed");
161 assert_eq!(parsed, ltr);
162 }
163
164 #[test]
165 fn writer_emits_expected_ltr_size() {
166 let ltr = Ltr::new();
167 let bytes = write_ltr_to_vec(<r).expect("write should succeed");
168 assert_eq!(bytes.len(), EXPECTED_FILE_SIZE);
169 }
170
171 #[test]
172 fn rejects_invalid_magic() {
173 let mut bytes = write_ltr_to_vec(&Ltr::new()).expect("write should succeed");
174 bytes[0] = b'X';
175 let err = read_ltr_from_bytes(&bytes).expect_err("must fail");
176 assert!(matches!(err, LtrBinaryError::InvalidMagic(_)));
177 }
178
179 #[test]
180 fn rejects_invalid_version() {
181 let mut bytes = write_ltr_to_vec(&Ltr::new()).expect("write should succeed");
182 bytes[4] = b'X';
183 let err = read_ltr_from_bytes(&bytes).expect_err("must fail");
184 assert!(matches!(err, LtrBinaryError::InvalidVersion(_)));
185 }
186
187 #[test]
188 fn rejects_unsupported_letter_count() {
189 let mut bytes = write_ltr_to_vec(&Ltr::new()).expect("write should succeed");
190 bytes[8] = 26;
191 let err = read_ltr_from_bytes(&bytes).expect_err("must fail");
192 assert!(matches!(err, LtrBinaryError::UnsupportedLetterCount(26)));
193 }
194
195 #[test]
196 fn rejects_truncated_payload() {
197 let mut bytes = write_ltr_to_vec(&Ltr::new()).expect("write should succeed");
198 bytes.truncate(bytes.len() - 1);
199 let err = read_ltr_from_bytes(&bytes).expect_err("must fail");
200 assert!(matches!(err, LtrBinaryError::InvalidHeader(_)));
201 }
202
203 #[test]
204 fn rejects_truncated_header() {
205 let bytes = vec![0_u8; FILE_HEADER_SIZE - 1];
206 let err = read_ltr_from_bytes(&bytes).expect_err("must fail");
207 assert!(matches!(err, LtrBinaryError::InvalidHeader(_)));
208 }
209}