hi all, writing a small decoder in Rust for a client's request-log format (count byte, then that many key/value pairs, each a length byte plus bytes). bytes arrive from a socket in arbitrary chunks, so main keeps a Vec<u8>, appends each chunk, calls next_record until it gets Incomplete, then drains what the parser says it used.
fed as one chunk, all three records print fine. with a chunk size of 7 or 10 as the first arg, it goes wrong in two ways: a record sometimes shows up with extra fields (a key from a record that never finished appears inside the next one), and other times I get malformed at offset N on bytes the one-chunk run accepts. the two symptoms don't seem to show up at the same chunk sizes, so I can't tell if it's one bug or two.
I've been blaming the drain, but changing it only changes which garbage I get. I also wondered about holding &buf across the loop (it compiles, so I doubt it). I treat Incomplete as "not an error, just wait", which is what the loop does, so I don't see what's missing.
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) })
}
and the loop in main (Parser holds pos, scratch: Vec<(String, String)> and 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();
}
what am I missing?