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