mediatek_optical/receiver/
reference.rs1use super::{Mode, Receiver};
5use crate::{
6 cdb,
7 drive::{self, Data, Transport},
8 envelope::Envelope,
9 layout, INQUIRY_LEN,
10};
11use core::convert::Infallible;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum Provenance {
16 LiveCode,
18 CurrentReference,
21}
22
23#[derive(Debug)]
31#[non_exhaustive]
32pub enum Error<E> {
33 Analysis(super::Error<&'static str>),
35 Binding(super::Error<E>),
37}
38impl<E: core::fmt::Debug> core::fmt::Display for Error<E> {
39 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
40 match self {
41 Self::Analysis(e) => write!(f, "Current firmware receiver analysis failed: {e}"),
42 Self::Binding(e) => write!(
43 f,
44 "Current firmware does not match the running receiver: {e}"
45 ),
46 }
47 }
48}
49impl<E: core::fmt::Debug + 'static> std::error::Error for Error<E> {
50 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
51 match self {
52 Self::Analysis(source) => Some(source),
53 Self::Binding(source) => Some(source),
54 }
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub(super) struct Binding {
60 identity: [u8; 28],
61 banner: [u8; layout::BANNER.len],
62 descriptor: [u8; 0x58],
63 live_banner: Option<[u8; layout::BANNER.len]>,
64 image_hash: [u8; 32],
65}
66impl Binding {
67 fn read_banner<T: Transport>(
68 &self,
69 t: &mut T,
70 ) -> Result<[u8; layout::BANNER.len], super::Error<T::Error>> {
71 let mode = super::detect_mode(t)?;
72 if mode.mode() != Mode::Firmware || mode.inquiry()[8..36] != self.identity {
73 return Err(super::Error::Changed);
74 }
75 let mut banner = [0; layout::BANNER.len];
76 drive::read_memory(t, layout::BANNER.start as u32, &mut banner)
77 .map_err(super::Error::Read)?;
78 let mut descriptor = [0; layout::DESCRIPTOR.len];
79 drive::read_memory(t, layout::DESCRIPTOR.start as u32, &mut descriptor)
80 .map_err(super::Error::Read)?;
81 if banner[..16] != self.banner[..16]
82 || descriptor[..0x58] != self.descriptor
83 || self.live_banner.is_some_and(|expected| banner != expected)
84 {
85 return Err(super::Error::Changed);
86 }
87 Ok(banner)
88 }
89
90 pub(super) fn revalidate<T: Transport>(&self, t: &mut T) -> Result<(), super::Error<T::Error>> {
91 self.read_banner(t).map(|_| ())
92 }
93}
94
95impl Receiver {
96 pub fn from_firmware(envelope: &Envelope) -> Result<Self, Error<Infallible>> {
100 let image = envelope.image();
101 let descriptor = layout::DESCRIPTOR.of(image).expect("validated envelope");
102 let mut inquiry = [0; INQUIRY_LEN];
103 inquiry[0] = 5;
104 inquiry[4] = 31;
105 inquiry[8..36].copy_from_slice(&descriptor[..28]);
106 let mut source = ImageSource { image, inquiry };
107 let mode = super::detect_mode(&mut source).map_err(Error::Analysis)?;
108 if mode.mode() != Mode::Firmware {
109 return Err(Error::Analysis(super::Error::InvalidIdentity));
110 }
111 let mut receiver = super::detect(&mut source, &mode).map_err(Error::Analysis)?;
112 receiver.reference = Some(Binding {
113 identity: descriptor[..28].try_into().expect("descriptor identity"),
114 banner: layout::BANNER
115 .of(image)
116 .expect("validated banner")
117 .try_into()
118 .unwrap(),
119 descriptor: descriptor[..0x58].try_into().expect("descriptor metadata"),
120 live_banner: None,
121 image_hash: *envelope.image_sha256(),
122 });
123 Ok(receiver)
124 }
125
126 pub fn provenance(&self) -> Provenance {
128 if self.reference.is_some() {
129 Provenance::CurrentReference
130 } else {
131 Provenance::LiveCode
132 }
133 }
134
135 pub fn reference_sha256(&self) -> Option<&[u8; 32]> {
137 self.reference
138 .as_ref()
139 .map(|reference| &reference.image_hash)
140 }
141}
142
143pub fn detect<T: Transport>(t: &mut T, envelope: &Envelope) -> Result<Receiver, Error<T::Error>> {
147 let mut receiver = Receiver::from_firmware(envelope).map_err(|error| match error {
148 Error::Analysis(error) => Error::Analysis(error),
149 Error::Binding(_) => unreachable!("offline analysis performs no physical binding"),
150 })?;
151 let binding = receiver.reference.as_mut().expect("reference receiver");
152 binding.live_banner = Some(binding.read_banner(t).map_err(Error::Binding)?);
153 let identity = super::detect_mode(t).map_err(Error::Binding)?;
155 if identity.mode() != Mode::Firmware || identity.inquiry()[8..36] != binding.identity {
156 return Err(Error::Binding(super::Error::Changed));
157 }
158 receiver.identity = identity;
159 receiver.revalidate(t).map_err(Error::Binding)?;
160 Ok(receiver)
161}
162
163struct ImageSource<'a> {
166 image: &'a [u8],
167 inquiry: [u8; INQUIRY_LEN],
168}
169impl Transport for ImageSource<'_> {
170 type Error = &'static str;
171 fn sense(&self) -> Option<(u8, u8, u8)> {
172 None
173 }
174 fn exec(&mut self, command: &[u8], data: Data<'_>) -> Result<usize, Self::Error> {
175 let Data::In(bytes) = data else {
176 return Err("offline source cannot execute commands");
177 };
178 if command == cdb::inquiry(INQUIRY_LEN as u16) {
179 bytes.copy_from_slice(&self.inquiry);
180 } else if command.len() == 10 && command[..3] == cdb::read_memory(0, 0)[..3] {
181 let address =
182 ((command[3] as usize) << 16) | ((command[4] as usize) << 8) | command[5] as usize;
183 let source = self
184 .image
185 .get(address..address + bytes.len())
186 .ok_or("offline read outside image")?;
187 bytes.copy_from_slice(source);
188 } else {
189 return Err("unsupported offline analysis read");
190 }
191 Ok(bytes.len())
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 struct Restricted {
200 image: Vec<u8>,
201 reads: usize,
202 }
203 impl Transport for Restricted {
204 type Error = &'static str;
205 fn sense(&self) -> Option<(u8, u8, u8)> {
206 None
207 }
208 fn exec(&mut self, command: &[u8], data: Data<'_>) -> Result<usize, Self::Error> {
209 self.reads += 1;
210 let Data::In(bytes) = data else {
211 panic!("reference binding must not write")
212 };
213 if command == cdb::inquiry(INQUIRY_LEN as u16) {
214 bytes.fill(0);
215 bytes[0] = 5;
216 bytes[4] = 31;
217 bytes[8..36].copy_from_slice(
218 &self.image[layout::DESCRIPTOR.start..layout::DESCRIPTOR.start + 28],
219 );
220 } else {
221 let address = ((command[3] as usize) << 16)
222 | ((command[4] as usize) << 8)
223 | command[5] as usize;
224 if ![layout::BANNER, layout::DESCRIPTOR]
225 .iter()
226 .any(|range| address == range.start && bytes.len() == range.len)
227 {
228 return Err("physical code reads unavailable");
229 }
230 bytes.copy_from_slice(&self.image[address..address + bytes.len()]);
231 }
232 Ok(bytes.len())
233 }
234 }
235
236 fn fixture() -> (Envelope, Restricted) {
237 let mut image = crate::fixtures::image(&crate::fixtures::BU3, 7);
238 for (i, &byte) in super::super::PLAIN_APP.iter().enumerate() {
239 image[0x9a300 + i] = if byte == super::super::ANY {
240 0
241 } else {
242 byte as u8
243 };
244 }
245 let image = crate::fixtures::sign(image);
246 let envelope = Envelope::load(&image).unwrap();
247 (envelope, Restricted { image, reads: 0 })
248 }
249
250 #[test]
251 fn explicit_reference_binds_without_physical_code_reads() {
252 let (envelope, mut live) = fixture();
253 let receiver = detect(&mut live, &envelope).unwrap();
254 assert_eq!(receiver.provenance(), Provenance::CurrentReference);
255 assert_eq!(receiver.reference_sha256(), Some(envelope.image_sha256()));
256 assert!(receiver.prepare_command().is_some());
257 receiver.revalidate(&mut live).unwrap();
258 assert!(live.reads > 0);
259 }
260
261 #[test]
262 fn offline_stage_requires_actual_matching_application_at_handoff() {
263 let (envelope, mut live) = fixture();
264 let receiver = Receiver::from_firmware(&envelope).unwrap();
265 assert_eq!(live.reads, 0);
266 receiver.revalidate(&mut live).unwrap();
267 live.image[layout::DESCRIPTOR.start + 24..layout::DESCRIPTOR.start + 28]
268 .copy_from_slice(b"INTM");
269 assert!(matches!(
270 receiver.revalidate(&mut live),
271 Err(super::super::Error::Changed)
272 ));
273 live.image[layout::DESCRIPTOR.start + 24..layout::DESCRIPTOR.start + 28]
274 .copy_from_slice(b"BOOT");
275 assert!(detect(&mut live, &envelope).is_err());
276 }
277
278 #[test]
279 fn reference_binding_checks_build_metadata_and_preserves_live_boot_snapshot() {
280 let (envelope, mut live) = fixture();
281 live.image[layout::BANNER.start + 20] ^= 1;
282 let receiver = detect(&mut live, &envelope).unwrap();
283 receiver.revalidate(&mut live).unwrap();
284 live.image[layout::BANNER.start + 20] ^= 2;
285 assert!(receiver.revalidate(&mut live).is_err());
286 let receiver = Receiver::from_firmware(&envelope).unwrap();
287 receiver.revalidate(&mut live).unwrap();
288 live.image[layout::DESCRIPTOR.start + 0x40] ^= 1;
289 assert!(receiver.revalidate(&mut live).is_err());
290 }
291
292 #[test]
293 fn unknown_reference_code_does_not_gain_a_receiver_from_matching_metadata() {
294 let (envelope, mut live) = fixture();
295 let mut image = envelope.image().to_vec();
296 image[0x98000..0xb4000].fill(0);
297 let envelope = Envelope::load(&crate::fixtures::sign(image)).unwrap();
298 assert!(matches!(
299 detect(&mut live, &envelope),
300 Err(Error::Analysis(_))
301 ));
302 assert_eq!(live.reads, 0);
303 }
304
305 #[test]
306 fn reference_is_not_a_source_of_live_readback_bytes() {
307 let (envelope, mut live) = fixture();
308 let receiver = detect(&mut live, &envelope).unwrap();
309 receiver.revalidate(&mut live).unwrap();
310 let mut code = [0; 32];
311 assert!(drive::read_memory(&mut live, 0x9a300, &mut code).is_err());
312 }
313}