use crate::{
formats::BinaryContext,
structures::{moduledata::Moduledata, util::read_uintptr},
};
const MIN_LEN: usize = 2;
const MAX_LEN: usize = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GoString<'a> {
pub va: u64,
pub len: usize,
pub bytes: &'a [u8],
}
impl<'a> GoString<'a> {
pub fn as_bytes(&self) -> &'a [u8] {
self.bytes
}
pub fn try_as_str(&self) -> Result<&'a str, std::str::Utf8Error> {
std::str::from_utf8(self.bytes)
}
pub fn as_str(&self) -> Option<&'a str> {
std::str::from_utf8(self.bytes).ok()
}
}
pub struct GoStringIter<'a> {
ctx: &'a BinaryContext<'a>,
pos: usize,
ps: usize,
text_start: u64,
text_end: u64,
}
impl<'a> GoStringIter<'a> {
fn empty(ctx: &'a BinaryContext<'a>) -> Self {
Self {
ctx,
pos: 0,
ps: 0,
text_start: 0,
text_end: 0,
}
}
}
impl<'a> Iterator for GoStringIter<'a> {
type Item = GoString<'a>;
fn next(&mut self) -> Option<GoString<'a>> {
let ps = self.ps;
if ps == 0 {
return None;
}
let ps_u8 = u8::try_from(ps).ok()?;
let data = self.ctx.structure_search_data();
loop {
let header_end = self.pos.checked_add(ps.checked_mul(2)?)?;
if header_end > data.len() {
return None;
}
let header_pos = self.pos;
self.pos = self.pos.checked_add(ps)?;
let va = match read_uintptr(data, header_pos, ps_u8) {
Some(v) if v != 0 => v,
_ => continue,
};
if va >= self.text_start && va < self.text_end {
continue;
}
let len_u64 = match read_uintptr(data, header_pos.checked_add(ps)?, ps_u8) {
Some(l) => l,
None => continue,
};
let len = match usize::try_from(len_u64) {
Ok(l) if (MIN_LEN..=MAX_LEN).contains(&l) => l,
_ => continue,
};
let bytes = match self.ctx.slice_at_va(va).and_then(|s| s.get(..len)) {
Some(b) => b,
None => continue,
};
return Some(GoString { va, len, bytes });
}
}
}
pub fn extract_iter<'a>(
ctx: &'a BinaryContext<'a>,
moduledata: Option<&Moduledata>,
ptr_size: u8,
) -> GoStringIter<'a> {
let ps = ptr_size as usize;
if ps == 0 || !ctx.has_va_mapping() {
return GoStringIter::empty(ctx);
}
let (text_start, text_end) = match moduledata {
Some(m) => (m.text, m.etext),
None => (0, 0),
};
GoStringIter {
ctx,
pos: 0,
ps,
text_start,
text_end,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn min_max_len_constants() {
assert_eq!(MIN_LEN, 2);
assert_eq!(MAX_LEN, 4096);
}
#[test]
fn as_str_round_trips_utf8() {
let s = GoString {
va: 0x1000,
len: 5,
bytes: b"hello",
};
assert_eq!(s.as_str(), Some("hello"));
}
#[test]
fn as_str_rejects_invalid_utf8() {
let s = GoString {
va: 0,
len: 2,
bytes: &[0xff, 0xfe],
};
assert_eq!(s.as_str(), None);
}
}