Skip to main content

ddc_dex/
reader.rs

1//! Little-endian cursor: u1..u8 reads, LEB128, MUTF-8.
2
3#[derive(Debug, Clone, Copy)]
4pub struct Cursor<'a> {
5    pub data: &'a [u8],
6    pub pos: usize,
7}
8
9impl<'a> Cursor<'a> {
10    pub fn new(data: &'a [u8]) -> Self {
11        Cursor { data, pos: 0 }
12    }
13
14    pub fn at(data: &'a [u8], pos: usize) -> Self {
15        Cursor { data, pos }
16    }
17
18    pub fn remaining(&self) -> usize {
19        self.data.len().saturating_sub(self.pos)
20    }
21
22    fn take(&mut self, n: usize) -> Option<&'a [u8]> {
23        if self.remaining() < n {
24            return None;
25        }
26        let s = &self.data[self.pos..self.pos + n];
27        self.pos += n;
28        Some(s)
29    }
30
31    pub fn u1(&mut self) -> Option<u8> {
32        self.take(1).map(|s| s[0])
33    }
34
35    pub fn u2(&mut self) -> Option<u16> {
36        self.take(2).map(|s| u16::from_le_bytes([s[0], s[1]]))
37    }
38
39    pub fn u4(&mut self) -> Option<u32> {
40        self.take(4)
41            .map(|s| u32::from_le_bytes([s[0], s[1], s[2], s[3]]))
42    }
43
44    pub fn s4(&mut self) -> Option<i32> {
45        self.u4().map(|v| v as i32)
46    }
47
48    pub fn u8v(&mut self) -> Option<u64> {
49        self.take(8).map(|s| {
50            let mut b = [0u8; 8];
51            b.copy_from_slice(s);
52            u64::from_le_bytes(b)
53        })
54    }
55
56    /// Bytes from the current position, without consuming.
57    pub fn peek_rest(&self) -> &'a [u8] {
58        &self.data[self.pos.min(self.data.len())..]
59    }
60
61    pub fn skip(&mut self, n: usize) {
62        self.pos = (self.pos + n).min(self.data.len());
63    }
64
65    pub fn read_uleb128(&mut self) -> Option<u64> {
66        let mut result: u64 = 0;
67        let mut shift = 0u32;
68        loop {
69            let b = self.u1()?;
70            result |= ((b & 0x7f) as u64) << shift;
71            if b & 0x80 == 0 {
72                return Some(result);
73            }
74            shift += 7;
75            if shift > 63 {
76                return None;
77            }
78        }
79    }
80
81    pub fn read_sleb128(&mut self) -> Option<i64> {
82        let mut result: i64 = 0;
83        let mut shift = 0u32;
84        loop {
85            let b = self.u1()?;
86            result |= ((b & 0x7f) as i64) << shift;
87            shift += 7;
88            if b & 0x80 == 0 {
89                if shift < 64 && (b & 0x40) != 0 {
90                    result |= -1i64 << shift;
91                }
92                return Some(result);
93            }
94            if shift > 63 {
95                return None;
96            }
97        }
98    }
99
100    /// uleb128 whose encoding adds +1 to the stored value (NO_INDEX = 0 → -1).
101    pub fn read_uleb128p1(&mut self) -> Option<i64> {
102        self.read_uleb128().map(|v| v as i64 - 1)
103    }
104
105    /// Read a MUTF-8 string of `utf16_len` code units starting at `pos`
106    /// (does not consume the cursor).
107    pub fn read_mutf8(&self, pos: usize, utf16_len: u64) -> Option<String> {
108        // Fast path: pure-ASCII (and terminating NUL) strings — the vast
109        // majority of a dex's string table — decode straight through
110        // `str::from_utf8`. The per-byte state machine below stays for
111        // the multi-byte/edge cases.
112        if let Some(nul) = self.data[pos..].iter().position(|&b| b == 0) {
113            let ascii = &self.data[pos..pos + nul];
114            if !ascii.is_empty() && ascii.is_ascii() {
115                // ASCII bytes are valid UTF-8 by construction.
116                return Some(unsafe { std::str::from_utf8_unchecked(ascii).to_string() });
117            }
118        }
119        decode_mutf8_units(&self.data[pos..], Some(utf16_len))
120    }
121}
122
123/// MUTF-8 (CESU-8) decoder shared by the length-aware reader and the
124/// delimiter-bounded query path. `max_units == Some(n)` stops after `n`
125/// UTF-16 code units and returns None if the bytes run out early (dex
126/// tables are authoritative); `None` decodes to the end of the slice or
127/// the first bare NUL, which is what a raw string-table caller has.
128fn decode_mutf8_units(data: &[u8], max_units: Option<u64>) -> Option<String> {
129    let mut out = String::new();
130    let mut i = 0usize;
131    let mut units = 0u64;
132    loop {
133        if let Some(m) = max_units {
134            if units >= m {
135                break;
136            }
137        }
138        if i >= data.len() {
139            return match max_units {
140                Some(_) => None,
141                None => Some(out),
142            };
143        }
144        let b0 = data[i];
145        match b0 {
146            0x00 => break,
147            0x01..=0x7f => {
148                out.push(b0 as char);
149                i += 1;
150            }
151            0xc0..=0xdf => {
152                if i + 1 >= data.len() || (data[i + 1] & 0xc0) != 0x80 {
153                    out.push('\u{fffd}');
154                    i += 1;
155                } else {
156                    let c = (((b0 & 0x1f) as u32) << 6) | ((data[i + 1] & 0x3f) as u32);
157                    out.push(char::from_u32(c).unwrap_or('\u{fffd}'));
158                    i += 2;
159                }
160            }
161            0xe0..=0xef => {
162                if i + 2 >= data.len()
163                    || (data[i + 1] & 0xc0) != 0x80
164                    || (data[i + 2] & 0xc0) != 0x80
165                {
166                    out.push('\u{fffd}');
167                    i += 1;
168                } else {
169                    let c = (((b0 & 0x0f) as u32) << 12)
170                        | (((data[i + 1] & 0x3f) as u32) << 6)
171                        | ((data[i + 2] & 0x3f) as u32);
172                    // Supplementary characters arrive as surrogate pairs.
173                    if (0xd800..0xdc00).contains(&c) {
174                        if i + 5 < data.len()
175                            && (data[i + 3] & 0xf0) == 0xe0
176                            && (data[i + 4] & 0xc0) == 0x80
177                            && (data[i + 5] & 0xc0) == 0x80
178                        {
179                            let lo = (((data[i + 3] & 0x0f) as u32) << 12)
180                                | (((data[i + 4] & 0x3f) as u32) << 6)
181                                | ((data[i + 5] & 0x3f) as u32);
182                            let cp = 0x10000 + ((c - 0xd800) << 10) + (lo - 0xdc00);
183                            out.push(char::from_u32(cp).unwrap_or('\u{fffd}'));
184                            i += 6;
185                            units += 2;
186                            continue;
187                        }
188                        out.push('\u{fffd}');
189                        i += 3;
190                    } else {
191                        out.push(char::from_u32(c).unwrap_or('\u{fffd}'));
192                        i += 3;
193                    }
194                }
195            }
196            _ => {
197                out.push('\u{fffd}');
198                i += 1;
199            }
200        }
201        units += 1;
202    }
203    Some(out)
204}
205
206/// Encode a Rust string as MUTF-8 (CESU-8): BMP characters use their
207/// normal UTF-8 bytes, supplementary characters become surrogate pairs,
208/// and U+0000 becomes 0xC0 0x80. Byte-level string matching (filters,
209/// findrefs queries) needs this form to reach names/literals above the
210/// BMP — a UTF-8 needle can never match their dex encoding.
211pub fn mutf8_encode(s: &str) -> Vec<u8> {
212    let mut out = Vec::with_capacity(s.len());
213    for ch in s.chars() {
214        let mut cp = ch as u32;
215        if cp == 0 {
216            out.extend_from_slice(&[0xc0, 0x80]);
217            continue;
218        }
219        if cp > 0xffff {
220            // Split into a UTF-16 surrogate pair, then encode each half.
221            cp -= 0x10000;
222            let hi = 0xd800 + (cp >> 10);
223            let lo = 0xdc00 + (cp & 0x3ff);
224            for unit in [hi, lo] {
225                out.push(0xe0 | ((unit >> 12) as u8));
226                out.push(0x80 | (((unit >> 6) & 0x3f) as u8));
227                out.push(0x80 | ((unit & 0x3f) as u8));
228            }
229            continue;
230        }
231        if cp <= 0x7f {
232            out.push(cp as u8);
233        } else if cp <= 0x7ff {
234            out.push(0xc0 | ((cp >> 6) as u8));
235            out.push(0x80 | ((cp & 0x3f) as u8));
236        } else {
237            out.push(0xe0 | ((cp >> 12) as u8));
238            out.push(0x80 | (((cp >> 6) & 0x3f) as u8));
239            out.push(0x80 | ((cp & 0x3f) as u8));
240        }
241    }
242    out
243}
244
245/// Decode MUTF-8 bytes that are already delimited (no uleb length):
246/// stops at the slice end or a bare NUL. Corrupt sequences degrade to
247/// U+FFFD. Used by the metadata query path, where the string table
248/// entry has already been sliced.
249pub fn mutf8_decode_lossy(data: &[u8]) -> String {
250    decode_mutf8_units(data, None).unwrap_or_default()
251}
252
253/// Format a MUTF-8 string like the DEX spec's uleb-terminated layout:
254/// `utf16_len` then bytes then 0x00.
255pub fn mutf8_decode(data: &[u8], utf16_len: u64) -> Option<String> {
256    Cursor::new(data).read_mutf8(0, utf16_len)
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn uleb() {
265        let mut c = Cursor::new(&[0x00]);
266        assert_eq!(c.read_uleb128(), Some(0));
267        let mut c = Cursor::new(&[0x81, 0x01]);
268        assert_eq!(c.read_uleb128(), Some(129));
269        let mut c = Cursor::new(&[0xff, 0xff, 0xff, 0xff, 0x0f]);
270        assert_eq!(c.read_uleb128(), Some(0xffff_ffff));
271    }
272
273    #[test]
274    fn sleb() {
275        let mut c = Cursor::new(&[0x00]);
276        assert_eq!(c.read_sleb128(), Some(0));
277        let mut c = Cursor::new(&[0x7f]);
278        assert_eq!(c.read_sleb128(), Some(-1));
279        let mut c = Cursor::new(&[0x81, 0x7f]);
280        assert_eq!(c.read_sleb128(), Some(-127));
281        let mut c = Cursor::new(&[0xc0, 0xbb, 0x78]);
282        assert_eq!(c.read_sleb128(), Some(-123456));
283    }
284
285    #[test]
286    fn mutf8_ascii() {
287        assert_eq!(mutf8_decode(b"hello\0", 5).as_deref(), Some("hello"));
288    }
289
290    #[test]
291    fn mutf8_embedded_nul() {
292        assert_eq!(mutf8_decode(&[0xc0, 0x80], 1).as_deref(), Some("\0"));
293    }
294
295    #[test]
296    fn mutf8_supplementary() {
297        // U+1F600 as a surrogate pair in MUTF-8.
298        let bytes = [0xed, 0xa0, 0xbd, 0xed, 0xb8, 0x80];
299        assert_eq!(mutf8_decode(&bytes, 2).as_deref(), Some("\u{1f600}"));
300    }
301
302    #[test]
303    fn mutf8_encode_roundtrip() {
304        // ASCII, BMP, NUL and a supplementary character.
305        for s in ["hello", "中文", "\u{0}", "\u{1f600}", "a\u{1f600}b"] {
306            let enc = mutf8_encode(s);
307            assert_eq!(mutf8_decode_lossy(&enc), s, "roundtrip {s:?}");
308        }
309        assert_eq!(mutf8_encode("\u{1f600}"), vec![0xed, 0xa0, 0xbd, 0xed, 0xb8, 0x80]);
310        assert_eq!(mutf8_encode("\u{0}"), vec![0xc0, 0x80]);
311    }
312
313    #[test]
314    fn mutf8_lossy_delimited() {
315        // Same surrogate pair without a length prefix, then a NUL terminator.
316        let bytes = [0xed, 0xa0, 0xbd, 0xed, 0xb8, 0x80, 0x00, 0x41];
317        assert_eq!(mutf8_decode_lossy(&bytes), "\u{1f600}");
318        // Encoded NUL (0xC0 0x80) is a U+0000, not a terminator.
319        assert_eq!(mutf8_decode_lossy(&[0xc0, 0x80]), "\u{0}");
320        // A lone high surrogate degrades to U+FFFD.
321        assert_eq!(
322            mutf8_decode_lossy(&[0xed, 0xa0, 0xbd, 0x41]),
323            "\u{fffd}A"
324        );
325    }
326}