mediatek_optical/receiver/
layout.rs1use core::ops::Range;
7
8const BLOCK: usize = 16;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum Operation {
13 CheckDecrypted(Range<usize>),
16 DecryptInPlace(Range<usize>),
18 CheckPlaintext(Range<usize>),
20 Program(Range<usize>),
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
32#[non_exhaustive]
33pub enum Error {
34 Alignment,
36 Range,
38 Conflict {
40 block: usize,
42 },
43 MultipleDecryptions {
45 block: usize,
47 },
48 NoProgram,
50 ImageSize,
52}
53
54impl core::fmt::Display for Error {
55 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
56 match self {
57 Self::Alignment => f.write_str("receiver encoding requires complete AES blocks"),
58 Self::Range => f.write_str("receiver operation has an empty or out-of-bounds range"),
59 Self::Conflict { block } => write!(f, "conflicting wire encodings at {block:#x}"),
60 Self::MultipleDecryptions { block } => write!(f, "repeated AES decoding at {block:#x}"),
61 Self::NoProgram => f.write_str("receiver layout contains no programming operation"),
62 Self::ImageSize => f.write_str("image size differs from the receiver layout"),
63 }
64 }
65}
66
67impl std::error::Error for Error {}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct Layout {
73 size: usize,
74 encrypted: Vec<Range<usize>>,
75 programmed: Vec<Range<usize>>,
76}
77
78impl Layout {
79 pub fn encrypted_ranges(&self) -> &[Range<usize>] {
81 &self.encrypted
82 }
83
84 pub fn programmed_ranges(&self) -> &[Range<usize>] {
86 &self.programmed
87 }
88
89 pub fn encode(&self, image: &[u8]) -> Result<Vec<u8>, Error> {
92 if image.len() != self.size {
93 return Err(Error::ImageSize);
94 }
95 let mut wire = image.to_vec();
96 for range in &self.encrypted {
97 crate::enc::encrypt(&mut wire[range.clone()]).map_err(|_| Error::Alignment)?;
98 }
99 Ok(wire)
100 }
101}
102
103fn blocks(range: &Range<usize>, size: usize, aligned: bool) -> Result<Range<usize>, Error> {
104 if range.start >= range.end || range.end > size {
105 return Err(Error::Range);
106 }
107 if aligned && (range.start % BLOCK != 0 || range.end % BLOCK != 0) {
108 return Err(Error::Alignment);
109 }
110 Ok(range.start / BLOCK..range.end.div_ceil(BLOCK))
111}
112
113pub fn analyze(size: usize, operations: &[Operation]) -> Result<Layout, Error> {
117 if size == 0 || size % BLOCK != 0 {
118 return Err(Error::Alignment);
119 }
120 let mut decoded = vec![false; size / BLOCK];
121 let mut required = vec![None; size / BLOCK];
122 let mut programmed = Vec::new();
123 for operation in operations {
124 let (range, aligned) = match operation {
125 Operation::CheckDecrypted(r) | Operation::DecryptInPlace(r) => (r, true),
126 Operation::CheckPlaintext(r) | Operation::Program(r) => (r, false),
127 };
128 for block in blocks(range, size, aligned)? {
129 if matches!(operation, Operation::DecryptInPlace(_)) {
130 if decoded[block] {
131 return Err(Error::MultipleDecryptions {
132 block: block * BLOCK,
133 });
134 }
135 decoded[block] = true;
136 continue;
137 }
138 let scratch = matches!(operation, Operation::CheckDecrypted(_));
139 if scratch && decoded[block] {
140 return Err(Error::MultipleDecryptions {
141 block: block * BLOCK,
142 });
143 }
144 let encrypted = scratch || decoded[block];
145 if required[block].is_some_and(|previous| previous != encrypted) {
146 return Err(Error::Conflict {
147 block: block * BLOCK,
148 });
149 }
150 required[block] = Some(encrypted);
151 }
152 if let Operation::Program(range) = operation {
153 programmed.push(range.clone());
154 }
155 }
156 if programmed.is_empty() {
157 return Err(Error::NoProgram);
158 }
159 let mut encrypted: Vec<Range<usize>> = Vec::new();
160 for (block, value) in required.into_iter().enumerate() {
161 if value != Some(true) {
162 continue;
163 }
164 let start = block * BLOCK;
165 match encrypted.last_mut() {
166 Some(previous) if previous.end == start => previous.end += BLOCK,
167 _ => encrypted.push(start..start + BLOCK),
168 }
169 }
170 Ok(Layout {
171 size,
172 encrypted,
173 programmed,
174 })
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 const SIZE: usize = 0x20_0000;
182 const HEADER: Range<usize> = 0x3000..0x3010;
183
184 #[test]
185 fn scratch_decode_does_not_make_programmed_header_plaintext() {
186 let operations = [
187 Operation::CheckDecrypted(HEADER),
188 Operation::Program(0..SIZE),
189 ];
190 assert_eq!(
191 analyze(SIZE, &operations),
192 Err(Error::Conflict { block: 0x3000 })
193 );
194 }
195
196 #[test]
197 fn boot_integrity_check_also_prevents_header_only_encoding() {
198 let operations = [
199 Operation::CheckDecrypted(HEADER),
200 Operation::CheckPlaintext(0x1000..0xfff0),
201 Operation::Program(0x10000..SIZE),
202 ];
203 assert_eq!(
204 analyze(SIZE, &operations),
205 Err(Error::Conflict { block: 0x3000 })
206 );
207 }
208
209 #[test]
210 fn preserved_unchecked_boot_allows_header_only_encoding() {
211 let operations = [
212 Operation::CheckDecrypted(HEADER),
213 Operation::CheckPlaintext(0x10600..0x10608),
214 Operation::Program(0x10000..SIZE),
215 ];
216 let plan = analyze(SIZE, &operations).unwrap();
217 assert_eq!(plan.encrypted_ranges(), &[HEADER]);
218 let image = vec![0x5a; SIZE];
219 let wire = plan.encode(&image).unwrap();
220 assert_eq!(wire[..HEADER.start], image[..HEADER.start]);
221 assert_ne!(wire[HEADER.clone()], image[HEADER.clone()]);
222 assert_eq!(wire[HEADER.end..], image[HEADER.end..]);
223 let mut header = wire[HEADER.clone()].to_vec();
224 crate::enc::decrypt(&mut header).unwrap();
225 assert_eq!(header, image[HEADER]);
226 }
227
228 #[test]
229 fn full_decode_before_checks_and_programming_requires_full_encryption() {
230 let operations = [
231 Operation::CheckDecrypted(HEADER),
232 Operation::DecryptInPlace(0..SIZE),
233 Operation::CheckPlaintext(0x10600..0x10608),
234 Operation::Program(0..SIZE),
235 ];
236 let plan = analyze(SIZE, &operations).unwrap();
237 assert_eq!(plan.encrypted_ranges().len(), 1);
238 assert_eq!(plan.encrypted_ranges()[0], 0..SIZE);
239 let mut reordered = operations.to_vec();
240 reordered.swap(1, 2);
241 assert_eq!(
242 analyze(SIZE, &reordered),
243 Err(Error::Conflict { block: 0x10600 })
244 );
245 }
246
247 #[test]
248 fn plaintext_and_body_only_are_distinct_constraints() {
249 let plain = analyze(SIZE, &[Operation::Program(0..SIZE)]).unwrap();
250 assert!(plain.encrypted_ranges().is_empty());
251 let body = analyze(
252 SIZE,
253 &[
254 Operation::CheckPlaintext(HEADER),
255 Operation::DecryptInPlace(0x10000..SIZE),
256 Operation::Program(0x10000..SIZE),
257 ],
258 )
259 .unwrap();
260 assert_eq!(body.encrypted_ranges().len(), 1);
261 assert_eq!(body.encrypted_ranges()[0], 0x10000..SIZE);
262 }
263
264 #[test]
265 fn reject_unbounded_unaligned_repeated_and_incomplete_operations() {
266 assert_eq!(
267 analyze(SIZE, &[Operation::Program(0..SIZE + 1)]),
268 Err(Error::Range)
269 );
270 assert_eq!(
271 analyze(SIZE, &[Operation::CheckDecrypted(1..17)]),
272 Err(Error::Alignment)
273 );
274 assert_eq!(
275 analyze(SIZE, &[Operation::CheckPlaintext(HEADER)]),
276 Err(Error::NoProgram)
277 );
278 assert_eq!(
279 analyze(
280 SIZE,
281 &[
282 Operation::DecryptInPlace(HEADER),
283 Operation::CheckDecrypted(HEADER),
284 ]
285 ),
286 Err(Error::MultipleDecryptions { block: 0x3000 })
287 );
288 }
289}