struct Cursor<'a> {
chunks: &'a [&'a [u8]],
ci: usize,
off: usize,
}
impl<'a> Cursor<'a> {
fn new(chunks: &'a [&'a [u8]]) -> Self {
Self {
chunks,
ci: 0,
off: 0,
}
}
fn u8(&mut self) -> Option<u8> {
while self.ci < self.chunks.len() {
let chunk = self.chunks[self.ci];
if self.off < chunk.len() {
let b = chunk[self.off];
self.off += 1;
return Some(b);
}
self.ci += 1;
self.off = 0;
}
None
}
fn u16(&mut self) -> Option<u16> {
Some(u16::from_le_bytes([self.u8()?, self.u8()?]))
}
fn u32(&mut self) -> Option<u32> {
Some(u32::from_le_bytes([
self.u8()?,
self.u8()?,
self.u8()?,
self.u8()?,
]))
}
fn skip(&mut self, mut n: usize) {
while n > 0 && self.u8().is_some() {
n -= 1;
}
}
fn read_units(&mut self, cch: usize, mut grbit: u8) -> Vec<u16> {
let mut units: Vec<u16> = Vec::with_capacity(cch.min(1 << 20));
let mut read = 0usize;
while read < cch {
while self.ci < self.chunks.len() && self.off >= self.chunks[self.ci].len() {
self.ci += 1;
self.off = 0;
if self.ci < self.chunks.len() && !self.chunks[self.ci].is_empty() {
grbit = self.chunks[self.ci][0];
self.off = 1;
}
}
if self.ci >= self.chunks.len() {
break;
}
if grbit & 0x01 != 0 {
let (Some(lo), Some(hi)) = (self.u8(), self.u8()) else {
break;
};
units.push(u16::from_le_bytes([lo, hi]));
} else {
match self.u8() {
Some(b) => units.push(u16::from(b)),
None => break,
}
}
read += 1;
}
units
}
fn read_string(&mut self) -> Option<String> {
let cch = self.u16()? as usize;
let grbit = self.u8()?;
let rich = if grbit & 0x08 != 0 {
self.u16()? as usize
} else {
0
};
let ext = if grbit & 0x04 != 0 {
self.u32()? as usize
} else {
0
};
let units = self.read_units(cch, grbit);
self.skip(rich.saturating_mul(4));
self.skip(ext);
Some(String::from_utf16_lossy(&units))
}
fn read_plain(&mut self) -> Option<String> {
let cch = self.u16()? as usize;
let grbit = self.u8()?;
Some(String::from_utf16_lossy(&self.read_units(cch, grbit)))
}
}
pub(crate) fn parse(chunks: &[&[u8]]) -> Vec<String> {
let mut cur = Cursor::new(chunks);
let _total = cur.u32();
let unique = cur.u32().unwrap_or(0) as usize;
let mut out = Vec::with_capacity(unique.min(1 << 20));
for _ in 0..unique {
match cur.read_string() {
Some(s) => out.push(s),
None => break,
}
}
out
}
pub(crate) fn read_continued_plain(chunks: &[&[u8]], skip: usize) -> Option<String> {
let mut cur = Cursor::new(chunks);
cur.skip(skip);
cur.read_plain()
}
pub(crate) fn read_continued_rich(chunks: &[&[u8]], skip: usize) -> Option<String> {
let mut cur = Cursor::new(chunks);
cur.skip(skip);
cur.read_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compressed_and_uncompressed_strings() {
let mut body = Vec::new();
body.extend_from_slice(&2u32.to_le_bytes());
body.extend_from_slice(&2u32.to_le_bytes());
body.extend_from_slice(&2u16.to_le_bytes());
body.push(0x00);
body.extend_from_slice(b"AB");
body.extend_from_slice(&1u16.to_le_bytes());
body.push(0x01);
body.extend_from_slice(&('가' as u16).to_le_bytes());
let strings = parse(&[&body]);
assert_eq!(strings, vec!["AB".to_string(), "가".to_string()]);
}
#[test]
fn string_split_across_continue_reuses_grbit() {
let mut head = Vec::new();
head.extend_from_slice(&1u32.to_le_bytes()); head.extend_from_slice(&1u32.to_le_bytes()); head.extend_from_slice(&4u16.to_le_bytes()); head.push(0x01); head.extend_from_slice(&('A' as u16).to_le_bytes());
head.extend_from_slice(&('B' as u16).to_le_bytes());
let mut cont = vec![0x01u8]; cont.extend_from_slice(&('C' as u16).to_le_bytes());
cont.extend_from_slice(&('D' as u16).to_le_bytes());
let strings = parse(&[&head, &cont]);
assert_eq!(strings, vec!["ABCD".to_string()]);
}
}