unarm 2.1.0

Parses ARM instructions
Documentation
use crate::*;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ParseMode {
    #[cfg(feature = "arm")]
    Arm,
    #[cfg(feature = "thumb")]
    Thumb,
    Data,
}

impl ParseMode {
    pub fn from_mapping_symbol(symbol: &str) -> Option<Self> {
        match symbol {
            #[cfg(feature = "arm")]
            "$a" => Some(ParseMode::Arm),
            #[cfg(feature = "thumb")]
            "$t" => Some(ParseMode::Thumb),
            "$d" => Some(ParseMode::Data),
            _ => None,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ParseEndian {
    Little,
    Big,
}

pub struct Parser<'a> {
    bytes: &'a [u8],
    options: Options,
    mode: ParseMode,
    endian: ParseEndian,
    pc: u32,
    offset: usize,
}

impl<'a> Parser<'a> {
    pub const fn new(
        bytes: &'a [u8],
        mode: ParseMode,
        endian: ParseEndian,
        options: Options,
    ) -> Self {
        Self { bytes, options, mode, endian, pc: 0, offset: 0 }
    }

    pub fn mode(&self) -> ParseMode {
        self.mode
    }

    pub fn set_mode(&mut self, mode: ParseMode) {
        self.mode = mode;
    }

    pub fn pc(&self) -> u32 {
        self.pc
    }

    pub fn set_pc(&mut self, pc: u32) {
        self.pc = pc;
    }

    pub fn offset(&self) -> usize {
        self.offset
    }

    pub fn set_offset(&mut self, offset: usize) {
        self.offset = offset.min(self.bytes.len());
    }

    pub fn endian(&self) -> ParseEndian {
        self.endian
    }

    pub fn set_endianness(&mut self, endianness: ParseEndian) {
        self.endian = endianness;
    }

    pub fn goto_offset(&mut self, offset: usize) {
        let new_offset = offset.min(self.bytes.len());
        let delta = new_offset.saturating_sub(self.offset);
        self.offset = new_offset;
        self.pc = self.pc.wrapping_add(delta as u32);
    }

    pub fn jump(&mut self, delta: isize) {
        let delta = delta as usize;
        self.offset = self.offset.wrapping_add(delta);
        self.pc = self.pc.wrapping_add(delta as u32);
    }
}

impl<'a> Iterator for Parser<'a> {
    type Item = Ins;

    fn next(&mut self) -> Option<Self::Item> {
        match self.mode {
            #[cfg(feature = "arm")]
            ParseMode::Arm => {
                let start = self.offset;
                if start + 4 > self.bytes.len() {
                    self.goto_offset(self.bytes.len());
                    return None;
                }
                let code = bytes_to_u32(self.bytes, start, self.endian())?;

                let ins = parse_arm(code, self.pc, &self.options);
                self.jump(4);

                Some(ins)
            }
            #[cfg(feature = "thumb")]
            ParseMode::Thumb => {
                let start = self.offset;
                if start + 2 > self.bytes.len() {
                    self.goto_offset(self.bytes.len());
                    return None;
                }
                let first = bytes_to_u16(self.bytes, start, self.endian())? as u32;
                let second = bytes_to_u16(self.bytes, start + 2, self.endian()).unwrap_or(0) as u32;
                let code = first | (second << 16);

                let (ins, size) = parse_thumb(code, self.pc, &self.options);
                self.jump(size as isize);

                Some(ins)
            }
            ParseMode::Data => {
                let start = self.offset;
                if (self.offset & 3) == 0 && self.offset + 4 <= self.bytes.len() {
                    let value = bytes_to_u32(self.bytes, start, self.endian())?;
                    self.jump(4);
                    Some(Ins::Word(value))
                } else if (self.offset & 1) == 0 && self.offset + 2 <= self.bytes.len() {
                    let value = bytes_to_u16(self.bytes, start, self.endian())?;
                    self.jump(2);
                    Some(Ins::HalfWord(value))
                } else if self.offset < self.bytes.len() {
                    let value = self.bytes[start];
                    self.jump(1);
                    Some(Ins::Byte(value))
                } else {
                    self.goto_offset(self.bytes.len());
                    None
                }
            }
        }
    }
}

fn bytes_to_u32(bytes: &[u8], offset: usize, endian: ParseEndian) -> Option<u32> {
    if bytes.len() < offset + 4 {
        return None;
    }
    Some(match endian {
        ParseEndian::Little => u32::from_le_bytes([
            bytes[offset],
            bytes[offset + 1],
            bytes[offset + 2],
            bytes[offset + 3],
        ]),
        ParseEndian::Big => u32::from_be_bytes([
            bytes[offset],
            bytes[offset + 1],
            bytes[offset + 2],
            bytes[offset + 3],
        ]),
    })
}

fn bytes_to_u16(bytes: &[u8], offset: usize, endian: ParseEndian) -> Option<u16> {
    if bytes.len() < offset + 2 {
        return None;
    }
    Some(match endian {
        ParseEndian::Little => u16::from_le_bytes([bytes[offset], bytes[offset + 1]]),
        ParseEndian::Big => u16::from_be_bytes([bytes[offset], bytes[offset + 1]]),
    })
}