mediatek_optical/receiver/
decoding.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Value {
11 Unknown,
13 Constant(u32),
15 Staging(u32),
17 Stack(u32),
19 Loaded {
22 cell: u32,
24 offset: u32,
26 },
27}
28
29impl Value {
30 fn add(self, other: Self) -> Option<Self> {
31 match (self, other) {
32 (Self::Constant(a), Self::Constant(b)) => Some(Self::Constant(a.checked_add(b)?)),
33 (Self::Staging(a), Self::Constant(b)) | (Self::Constant(b), Self::Staging(a)) => {
34 Some(Self::Staging(a.checked_add(b)?))
35 }
36 (Self::Stack(a), Self::Constant(b)) | (Self::Constant(b), Self::Stack(a)) => {
37 Some(Self::Stack(a.checked_add(b)?))
38 }
39 (Self::Loaded { cell, offset }, Self::Constant(b))
40 | (Self::Constant(b), Self::Loaded { cell, offset }) => Some(Self::Loaded {
41 cell,
42 offset: offset.checked_add(b)?,
43 }),
44 _ => None,
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub struct Call {
52 pub address: u32,
54 pub target: u32,
56 pub source: Value,
58 pub destination: Value,
60 pub length: u32,
62 pub fourth_argument: Value,
64}
65
66fn word(bytes: &[u8], base: u32, address: u32) -> Option<u16> {
67 let offset = usize::try_from(address.checked_sub(base)?).ok()?;
68 Some(u16::from_le_bytes(
69 bytes.get(offset..offset.checked_add(2)?)?.try_into().ok()?,
70 ))
71}
72
73pub fn branch_link(bytes: &[u8], base: u32, address: u32) -> Option<u32> {
76 let first = word(bytes, base, address)?;
77 let second = word(bytes, base, address.checked_add(2)?)?;
78 if first & 0xf800 != 0xf000 || second & 0xf800 != 0xf800 {
79 return None;
80 }
81 let encoded = (u32::from(first & 2047) << 12) | (u32::from(second & 2047) << 1);
82 let delta = ((encoded as i32) << 9) >> 9;
83 u32::try_from(i64::from(address) + 4 + i64::from(delta)).ok()
84}
85
86pub fn trace_call(
90 bytes: &[u8],
91 base: u32,
92 start: u32,
93 mut registers: [Value; 8],
94 mut load: impl FnMut(Value) -> Option<Value>,
95) -> Option<Call> {
96 if base & 1 != 0 || start & 1 != 0 {
97 return None;
98 }
99 let mut address = start;
100 let mut visited = [u32::MAX; 32];
101 for step in 0..32 {
102 if visited[..step].contains(&address) {
103 return None;
104 }
105 visited[step] = address;
106 let op = word(bytes, base, address)?;
107 if let Some(target) = branch_link(bytes, base, address) {
108 let Value::Constant(length) = registers[2] else {
109 return None;
110 };
111 if length == 0 || length % 16 != 0 || registers[..2].contains(&Value::Unknown) {
112 return None;
113 }
114 return Some(Call {
115 address,
116 target,
117 source: registers[0],
118 destination: registers[1],
119 length,
120 fourth_argument: registers[3],
121 });
122 }
123 let d = usize::from(op & 7);
124 let s = usize::from((op >> 3) & 7);
125 if op & 0xf800 == 0xe000 {
126 let delta = ((i32::from(op & 0x7ff) << 21) >> 20) as i64;
127 address = u32::try_from(i64::from(address) + 4 + delta).ok()?;
128 continue;
129 } else if op & 0xf800 == 0x4800 {
130 let literal = (address.checked_add(4)? & !3).checked_add(u32::from(op & 255) * 4)?;
131 let low = u32::from(word(bytes, base, literal)?);
132 let high = u32::from(word(bytes, base, literal.checked_add(2)?)?);
133 registers[usize::from((op >> 8) & 7)] = Value::Constant(low | high << 16);
134 } else if op & 0xf800 == 0x2000 {
135 registers[usize::from((op >> 8) & 7)] = Value::Constant(u32::from(op & 255));
136 } else if op & 0xf800 == 0x0000 {
137 let shift = u32::from((op >> 6) & 31);
138 registers[d] = if shift == 0 {
139 registers[s]
140 } else if let Value::Constant(value) = registers[s] {
141 Value::Constant(u32::try_from(u64::from(value) << shift).ok()?)
142 } else {
143 return None;
144 };
145 } else if op & 0xfe00 == 0x1800 {
146 registers[d] = registers[s].add(registers[usize::from((op >> 6) & 7)])?;
147 } else if op & 0xfe00 == 0x1c00 {
148 registers[d] = registers[s].add(Value::Constant(u32::from((op >> 6) & 7)))?;
149 } else if op & 0xf800 == 0x3000 {
150 let d = usize::from((op >> 8) & 7);
151 registers[d] = registers[d].add(Value::Constant(u32::from(op & 255)))?;
152 } else if op & 0xf800 == 0x6800 {
153 let pointer = registers[s].add(Value::Constant(u32::from((op >> 6) & 31) * 4))?;
154 registers[d] = load(pointer)?;
155 } else if op & 0xf800 == 0xa800 {
156 registers[usize::from((op >> 8) & 7)] = Value::Stack(u32::from(op & 255) * 4);
157 } else {
158 return None;
159 }
160 address = address.checked_add(2)?;
161 }
162 None
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 fn header() -> Vec<u8> {
171 let mut bytes = vec![0; 0x370];
172 bytes[..18].copy_from_slice(&[
173 0xda, 0x48, 0x03, 0x23, 0x00, 0x68, 0x1b, 0x03, 0x10, 0x22, 0xc0, 0x18, 0x05, 0xa9,
174 0xfd, 0xf7, 0xc9, 0xfd,
175 ]);
176 bytes[0x36c..0x370].copy_from_slice(&0x0200_0c34u32.to_le_bytes());
177 bytes
178 }
179
180 fn inspect(bytes: &[u8]) -> Option<Call> {
181 trace_call(bytes, 0x9d480, 0x9d480, [Value::Unknown; 8], |address| {
182 (address == Value::Constant(0x0200_0c34)).then_some(Value::Staging(0))
183 })
184 }
185
186 #[test]
187 fn actual_header_arguments_are_scratch_not_in_place() {
188 let call = inspect(&header()).unwrap();
189 assert_eq!(
190 call,
191 Call {
192 address: 0x9d48e,
193 target: 0x9b024,
194 source: Value::Staging(0x3000),
195 destination: Value::Stack(0x14),
196 length: 16,
197 fourth_argument: Value::Constant(0x3000),
198 }
199 );
200 }
201
202 #[test]
203 fn operand_changes_cannot_retain_the_same_proof() {
204 let bytes = header();
205 let original = inspect(&bytes).unwrap();
206 for at in [0, 2, 4, 6, 8, 10, 12, 14, 16, 0x36c] {
207 let mut changed = bytes.clone();
208 changed[at] ^= 1;
209 assert_ne!(inspect(&changed), Some(original), "mutation at {at:#x}");
210 }
211 }
212
213 #[test]
214 fn truncated_literals_unknown_memory_and_control_flow_fail_closed() {
215 let bytes = header();
216 assert!(inspect(&bytes[..0x36f]).is_none());
217 assert!(trace_call(&bytes, 0x9d480, 0x9d480, [Value::Unknown; 8], |_| None).is_none());
218 let mut branch = bytes;
219 branch[2..4].copy_from_slice(&0xe000u16.to_le_bytes());
220 assert!(inspect(&branch).is_none());
221 branch[..2].copy_from_slice(&0xe7feu16.to_le_bytes());
222 assert!(inspect(&branch).is_none());
223 assert!(trace_call(
224 &branch,
225 u32::MAX - 1,
226 u32::MAX - 1,
227 [Value::Unknown; 8],
228 |_| None
229 )
230 .is_none());
231 }
232}