Skip to main content

gsym/
endian.rs

1use crate::error::{Error, Result};
2
3/// Byte order used for fixed-width integers in a GSYM file.
4///
5/// A file records its own byte order, and both orders can be read on any host.
6///
7/// Conversion writes the byte order of the ELF image it read. Everything else
8/// defaults to [`Little`](Self::Little) and can be changed with
9/// [`GsymBuilder::endian`](crate::GsymBuilder::endian) or
10/// [`TranscodeOptions`](crate::TranscodeOptions).
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub enum Endian {
13    /// Least-significant byte first.
14    Little,
15    /// Most-significant byte first.
16    Big,
17}
18
19impl Endian {
20    /// Returns the byte order of the compilation target.
21    #[must_use]
22    pub const fn native() -> Self {
23        if cfg!(target_endian = "little") {
24            Self::Little
25        } else {
26            Self::Big
27        }
28    }
29}
30
31/// Bounds-checked cursor over untrusted binary input.
32#[derive(Clone, Debug)]
33pub(crate) struct Cursor<'data> {
34    bytes: &'data [u8],
35    position: usize,
36    endian: Endian,
37}
38
39impl<'data> Cursor<'data> {
40    pub(crate) const fn new(bytes: &'data [u8], endian: Endian) -> Self {
41        Self {
42            bytes,
43            position: 0,
44            endian,
45        }
46    }
47
48    pub(crate) const fn at(bytes: &'data [u8], endian: Endian, position: usize) -> Result<Self> {
49        if position > bytes.len() {
50            return Err(Error::InvalidOffset {
51                offset: position as u64,
52                input_len: bytes.len(),
53            });
54        }
55        Ok(Self {
56            bytes,
57            position,
58            endian,
59        })
60    }
61
62    pub(crate) const fn endian(&self) -> Endian {
63        self.endian
64    }
65
66    pub(crate) const fn position(&self) -> usize {
67        self.position
68    }
69
70    pub(crate) const fn remaining(&self) -> usize {
71        self.bytes.len().saturating_sub(self.position)
72    }
73
74    pub(crate) const fn is_empty(&self) -> bool {
75        self.remaining() == 0
76    }
77
78    pub(crate) fn take(&mut self, count: usize) -> Result<&'data [u8]> {
79        let end = self
80            .position
81            .checked_add(count)
82            .ok_or(Error::Overflow("cursor end offset"))?;
83        let Some(bytes) = self.bytes.get(self.position..end) else {
84            return Err(Error::UnexpectedEof {
85                offset: self.position,
86                needed: count,
87                remaining: self.remaining(),
88            });
89        };
90        self.position = end;
91        Ok(bytes)
92    }
93
94    pub(crate) fn take_array<const N: usize>(&mut self) -> Result<[u8; N]> {
95        let offset = self.position;
96        let remaining = self.remaining();
97        let bytes = self
98            .bytes
99            .get(offset..)
100            .and_then(<[u8]>::first_chunk::<N>)
101            .ok_or(Error::UnexpectedEof {
102                offset,
103                needed: N,
104                remaining,
105            })?;
106        self.position = offset.saturating_add(N);
107        Ok(*bytes)
108    }
109
110    pub(crate) fn read_u8(&mut self) -> Result<u8> {
111        let [byte] = self.take_array()?;
112        Ok(byte)
113    }
114
115    pub(crate) fn read_u16(&mut self) -> Result<u16> {
116        let bytes = self.take_array()?;
117        Ok(match self.endian {
118            Endian::Little => u16::from_le_bytes(bytes),
119            Endian::Big => u16::from_be_bytes(bytes),
120        })
121    }
122
123    pub(crate) fn read_u32(&mut self) -> Result<u32> {
124        let bytes = self.take_array()?;
125        Ok(match self.endian {
126            Endian::Little => u32::from_le_bytes(bytes),
127            Endian::Big => u32::from_be_bytes(bytes),
128        })
129    }
130
131    pub(crate) fn read_u64(&mut self) -> Result<u64> {
132        let bytes = self.take_array()?;
133        Ok(match self.endian {
134            Endian::Little => u64::from_le_bytes(bytes),
135            Endian::Big => u64::from_be_bytes(bytes),
136        })
137    }
138
139    pub(crate) fn read_uint(&mut self, width: u8) -> Result<u64> {
140        match width {
141            1 => return self.read_u8().map(u64::from),
142            2 => return self.read_u16().map(u64::from),
143            4 => return self.read_u32().map(u64::from),
144            8 => return self.read_u64(),
145            3 | 5..=7 => {}
146            _ => {
147                return Err(Error::OutOfRange {
148                    field: "integer width",
149                    value: u64::from(width),
150                    max: 8,
151                });
152            }
153        }
154        let count = usize::from(width);
155        let bytes = self.take(count)?;
156        let mut buffer = [0_u8; 8];
157        let window = match self.endian {
158            Endian::Little => buffer.get_mut(..count),
159            Endian::Big => buffer.get_mut(size_of::<u64>().saturating_sub(count)..),
160        };
161        window
162            .ok_or_else(|| Error::OutOfRange {
163                field: "integer width",
164                value: u64::from(width),
165                max: 8,
166            })?
167            .copy_from_slice(bytes);
168        Ok(match self.endian {
169            Endian::Little => u64::from_le_bytes(buffer),
170            Endian::Big => u64::from_be_bytes(buffer),
171        })
172    }
173}
174
175/// Checked binary output buffer with endian-aware fixed-width writes.
176#[derive(Clone, Debug)]
177pub(crate) struct Encoder {
178    bytes: Vec<u8>,
179    endian: Endian,
180}
181
182impl Encoder {
183    #[cfg(test)]
184    pub(crate) const fn new(endian: Endian) -> Self {
185        Self {
186            bytes: Vec::new(),
187            endian,
188        }
189    }
190
191    pub(crate) fn with_capacity(endian: Endian, capacity: usize) -> Self {
192        Self {
193            bytes: Vec::with_capacity(capacity),
194            endian,
195        }
196    }
197
198    pub(crate) const fn len(&self) -> usize {
199        self.bytes.len()
200    }
201
202    #[cfg(test)]
203    pub(crate) fn as_slice(&self) -> &[u8] {
204        &self.bytes
205    }
206
207    pub(crate) fn into_inner(self) -> Vec<u8> {
208        self.bytes
209    }
210
211    pub(crate) fn write_u8(&mut self, value: u8) {
212        self.bytes.push(value);
213    }
214
215    pub(crate) fn write_u16(&mut self, value: u16) {
216        self.bytes.extend_from_slice(&match self.endian {
217            Endian::Little => value.to_le_bytes(),
218            Endian::Big => value.to_be_bytes(),
219        });
220    }
221
222    pub(crate) fn write_u32(&mut self, value: u32) {
223        self.bytes.extend_from_slice(&match self.endian {
224            Endian::Little => value.to_le_bytes(),
225            Endian::Big => value.to_be_bytes(),
226        });
227    }
228
229    pub(crate) fn write_u64(&mut self, value: u64) {
230        self.bytes.extend_from_slice(&match self.endian {
231            Endian::Little => value.to_le_bytes(),
232            Endian::Big => value.to_be_bytes(),
233        });
234    }
235
236    pub(crate) fn write_uint(&mut self, value: u64, width: u8) -> Result<()> {
237        if !(1..=8).contains(&width) {
238            return Err(Error::OutOfRange {
239                field: "integer width",
240                value: u64::from(width),
241                max: 8,
242            });
243        }
244        let count = usize::from(width);
245        if width < 8 {
246            let max = u64::MAX
247                .checked_shr(u32::from(8_u8.saturating_sub(width)).saturating_mul(8))
248                .unwrap_or(u64::MAX);
249            if value > max {
250                return Err(Error::OutOfRange {
251                    field: "fixed-width integer",
252                    value,
253                    max,
254                });
255            }
256        }
257        let bytes = match self.endian {
258            Endian::Little => value.to_le_bytes(),
259            Endian::Big => value.to_be_bytes(),
260        };
261        let window = match self.endian {
262            Endian::Little => bytes.get(..count),
263            Endian::Big => bytes.get(size_of::<u64>().saturating_sub(count)..),
264        };
265        self.bytes
266            .extend_from_slice(window.ok_or_else(|| Error::OutOfRange {
267                field: "integer width",
268                value: u64::from(width),
269                max: 8,
270            })?);
271        Ok(())
272    }
273
274    pub(crate) fn write_bytes(&mut self, bytes: &[u8]) {
275        self.bytes.extend_from_slice(bytes);
276    }
277
278    pub(crate) fn align_to(&mut self, alignment: usize) -> Result<()> {
279        if alignment == 0 {
280            return Err(Error::InvalidAlignment(alignment));
281        }
282        let remainder = self.bytes.len().checked_rem(alignment).unwrap_or(0);
283        if remainder != 0 {
284            let padding = alignment.saturating_sub(remainder);
285            let new_len = self
286                .bytes
287                .len()
288                .checked_add(padding)
289                .ok_or(Error::Overflow("aligned output length"))?;
290            self.bytes.resize(new_len, 0);
291        }
292        Ok(())
293    }
294
295    pub(crate) fn patch_u32(&mut self, offset: usize, value: u32) -> Result<()> {
296        let bytes = match self.endian {
297            Endian::Little => value.to_le_bytes(),
298            Endian::Big => value.to_be_bytes(),
299        };
300        let end = offset
301            .checked_add(bytes.len())
302            .ok_or(Error::Overflow("patch end offset"))?;
303        let remaining = self.bytes.len().saturating_sub(offset);
304        let destination = self
305            .bytes
306            .get_mut(offset..end)
307            .ok_or(Error::UnexpectedEof {
308                offset,
309                needed: bytes.len(),
310                remaining,
311            })?;
312        destination.copy_from_slice(&bytes);
313        Ok(())
314    }
315
316    #[cfg(test)]
317    pub(crate) fn patch_uint(&mut self, offset: usize, value: u64, width: u8) -> Result<()> {
318        let mut encoded = Self::new(self.endian);
319        encoded.write_uint(value, width)?;
320        let end = offset
321            .checked_add(usize::from(width))
322            .ok_or(Error::Overflow("patch end offset"))?;
323        let remaining = self.bytes.len().saturating_sub(offset);
324        let destination = self
325            .bytes
326            .get_mut(offset..end)
327            .ok_or_else(|| Error::UnexpectedEof {
328                offset,
329                needed: usize::from(width),
330                remaining,
331            })?;
332        destination.copy_from_slice(encoded.as_slice());
333        Ok(())
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use super::{Cursor, Encoder, Endian};
340
341    #[test]
342    fn fixed_width_round_trip_both_endians() {
343        for endian in [Endian::Little, Endian::Big] {
344            let mut output = Encoder::new(endian);
345            output.write_u8(0x12);
346            output.write_u16(0x3456);
347            output.write_u32(0x789a_bcde);
348            output.write_u64(0x0123_4567_89ab_cdef);
349            output.write_uint(0xa1_b2_c3, 3).unwrap();
350
351            let mut input = Cursor::new(output.as_slice(), endian);
352            assert_eq!(input.read_u8().unwrap(), 0x12);
353            assert_eq!(input.read_u16().unwrap(), 0x3456);
354            assert_eq!(input.read_u32().unwrap(), 0x789a_bcde);
355            assert_eq!(input.read_u64().unwrap(), 0x0123_4567_89ab_cdef);
356            assert_eq!(input.read_uint(3).unwrap(), 0xa1_b2_c3);
357            assert!(input.is_empty());
358        }
359    }
360
361    #[test]
362    fn bounds_and_alignment_are_checked() {
363        let mut input = Cursor::new(&[1, 2], Endian::Little);
364        assert!(input.read_u32().is_err());
365
366        let mut output = Encoder::new(Endian::Little);
367        output.write_u8(1);
368        output.align_to(4).unwrap();
369        assert_eq!(output.as_slice(), &[1, 0, 0, 0]);
370        assert!(output.patch_u32(1, 7).is_err());
371    }
372}