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