всем привет, пишу на Rust небольшой декодер для клиентского формата логов запросов (байт count, затем столько пар ключ/значение, каждая из байта длины и самих байтов). байты приходят из сокета произвольными кусками, поэтому в main лежит Vec<u8>, я добавляю в него каждый чанк, вызываю next_record, пока не получу Incomplete, и потом дренирую то, что парсер сообщил как использованное.
если скормить одним куском, все три записи печатаются нормально. если передать первым аргументом размер чанка 7 или 10, ломается двумя способами: в записи иногда появляются лишние поля (ключ из записи, которая так и не дочиталась, оказывается внутри следующей), а в других случаях я получаю malformed at offset N на байтах, которые прогон одним куском принимает. похоже, что эти два симптома проявляются не на одних и тех же размерах чанка, так что я не могу понять, это один баг или два.
я грешил на drain, но когда я его меняю, меняется только то, какой мусор я получаю. ещё думал, не проблема ли в том, что я держу &buf через весь цикл (но оно компилируется, так что вряд ли). Incomplete я воспринимаю как «не ошибка, просто ждём», так цикл и написан, поэтому не понимаю, чего не хватает.
fn take<'a>(&mut self, buf: &'a [u8], n: usize) -> Result<&'a [u8], ParseError> {
if self.pos + n > buf.len() {
return Err(ParseError::Incomplete);
}
let s = &buf[self.pos..self.pos + n];
self.pos += n;
Ok(s)
}
fn take_str(&mut self, buf: &[u8]) -> Result<String, ParseError> {
let len = self.take(buf, 1)?[0] as usize;
let raw = self.take(buf, len)?;
std::str::from_utf8(raw)
.map(|s| s.to_string())
.map_err(|_| ParseError::Malformed { offset: self.pos - len })
}
pub fn next_record(&mut self, buf: &[u8]) -> Result<Record, ParseError> {
let count = self.take(buf, 1)?[0] as usize;
if count == 0 || count > self.max_fields {
return Err(ParseError::Malformed { offset: self.pos - 1 });
}
for _ in 0..count {
let k = self.take_str(buf)?;
let v = self.take_str(buf)?;
self.scratch.push((k, v));
}
Ok(Record { fields: std::mem::take(&mut self.scratch) })
}
и цикл в main (Parser хранит pos, scratch: Vec<(String, String)> и max_fields):
for piece in data.chunks(chunk) {
buf.extend_from_slice(piece);
loop {
match parser.next_record(&buf) {
Ok(rec) => { seen += 1; println!("record {}: {:?}", seen, rec.fields); }
Err(ParseError::Incomplete) => break,
Err(ParseError::Malformed { offset }) => {
eprintln!("malformed at offset {}", offset);
return;
}
}
}
buf.drain(..parser.pos());
parser.reset_pos();
}
что я упускаю?