pub const FORMAT_ONE: &[u8] = b"Bazaar pack format 1 (introduced in 0.18)";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PackError {
InvalidName(Vec<u8>),
UnknownContainerFormat(Vec<u8>),
UnknownRecordType(u8),
InvalidRecord(String),
}
impl std::fmt::Display for PackError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PackError::InvalidName(n) => write!(f, "{:?} is not a valid name.", n),
PackError::UnknownContainerFormat(line) => {
write!(f, "unrecognised container format: {:?}", line)
}
PackError::UnknownRecordType(b) => {
write!(f, "unknown record type: {:?}", &[*b])
}
PackError::InvalidRecord(reason) => write!(f, "invalid record: {}", reason),
}
}
}
impl std::error::Error for PackError {}
#[inline]
fn is_whitespace(byte: u8) -> bool {
matches!(byte, b'\t' | b'\n' | 0x0b | 0x0c | b'\r' | b' ')
}
pub fn check_name(name: &[u8]) -> Result<(), PackError> {
if name.iter().any(|&b| is_whitespace(b)) {
return Err(PackError::InvalidName(name.to_vec()));
}
Ok(())
}
pub fn begin() -> Vec<u8> {
let mut out = Vec::with_capacity(FORMAT_ONE.len() + 1);
out.extend_from_slice(FORMAT_ONE);
out.push(b'\n');
out
}
pub fn end() -> &'static [u8] {
b"E"
}
pub fn bytes_header(length: usize, names: &[Vec<Vec<u8>>]) -> Result<Vec<u8>, PackError> {
for name_tuple in names {
for part in name_tuple {
check_name(part)?;
}
}
let mut out = Vec::new();
out.push(b'B');
out.extend_from_slice(format!("{}\n", length).as_bytes());
for name_tuple in names {
for (i, part) in name_tuple.iter().enumerate() {
if i > 0 {
out.push(0);
}
out.extend_from_slice(part);
}
out.push(b'\n');
}
out.push(b'\n');
Ok(out)
}
pub fn bytes_record(body: &[u8], names: &[Vec<Vec<u8>>]) -> Result<Vec<u8>, PackError> {
let header = bytes_header(body.len(), names)?;
let mut out = Vec::with_capacity(header.len() + body.len());
out.extend_from_slice(&header);
out.extend_from_slice(body);
Ok(out)
}
pub type Record = (Vec<Vec<Vec<u8>>>, Vec<u8>);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(clippy::enum_variant_names)]
enum State {
ExpectingFormatLine,
ExpectingRecordType,
ExpectingLength,
ExpectingName,
ExpectingBody,
ExpectingNothing,
}
#[derive(Debug)]
pub struct ContainerPushParser {
buffer: Vec<u8>,
state: State,
parsed_records: Vec<Record>,
current_record_length: Option<usize>,
current_record_names: Vec<Vec<Vec<u8>>>,
finished: bool,
}
impl Default for ContainerPushParser {
fn default() -> Self {
Self::new()
}
}
impl ContainerPushParser {
pub fn new() -> Self {
Self {
buffer: Vec::new(),
state: State::ExpectingFormatLine,
parsed_records: Vec::new(),
current_record_length: None,
current_record_names: Vec::new(),
finished: false,
}
}
pub fn finished(&self) -> bool {
self.finished
}
pub fn accept_bytes(&mut self, bytes: &[u8]) -> Result<(), PackError> {
self.buffer.extend_from_slice(bytes);
let mut last_len = None;
let mut last_state = None;
while last_len != Some(self.buffer.len()) || last_state != Some(self.state) {
last_len = Some(self.buffer.len());
last_state = Some(self.state);
self.step()?;
}
Ok(())
}
pub fn read_pending_records(&mut self, max: Option<usize>) -> Vec<Record> {
match max {
Some(n) if n < self.parsed_records.len() => self.parsed_records.drain(..n).collect(),
_ => std::mem::take(&mut self.parsed_records),
}
}
pub fn read_size_hint(&self) -> usize {
let hint = 16384;
if self.state == State::ExpectingBody {
let need = self
.current_record_length
.expect("length set before body state")
.saturating_sub(self.buffer.len());
hint.max(need)
} else {
hint
}
}
fn consume_line(&mut self) -> Option<Vec<u8>> {
let pos = self.buffer.iter().position(|&b| b == b'\n')?;
let line: Vec<u8> = self.buffer.drain(..=pos).take(pos).collect();
Some(line)
}
fn step(&mut self) -> Result<(), PackError> {
match self.state {
State::ExpectingFormatLine => {
if let Some(line) = self.consume_line() {
if line != FORMAT_ONE {
return Err(PackError::UnknownContainerFormat(line));
}
self.state = State::ExpectingRecordType;
}
}
State::ExpectingRecordType => {
if let Some(&b) = self.buffer.first() {
self.buffer.drain(..1);
match b {
b'B' => self.state = State::ExpectingLength,
b'E' => {
self.finished = true;
self.state = State::ExpectingNothing;
}
other => return Err(PackError::UnknownRecordType(other)),
}
}
}
State::ExpectingLength => {
if let Some(line) = self.consume_line() {
let s = std::str::from_utf8(&line).map_err(|_| {
PackError::InvalidRecord(format!("{:?} is not a valid length.", line))
})?;
let n: usize = s.parse().map_err(|_| {
PackError::InvalidRecord(format!("{:?} is not a valid length.", line))
})?;
self.current_record_length = Some(n);
self.state = State::ExpectingName;
}
}
State::ExpectingName => {
if let Some(line) = self.consume_line() {
if line.is_empty() {
self.state = State::ExpectingBody;
} else {
let parts: Vec<Vec<u8>> =
line.split(|&b| b == 0).map(|s| s.to_vec()).collect();
for part in &parts {
check_name(part)?;
}
self.current_record_names.push(parts);
}
}
}
State::ExpectingBody => {
let need = self.current_record_length.expect("length set before body");
if self.buffer.len() >= need {
let body: Vec<u8> = self.buffer.drain(..need).collect();
let names = std::mem::take(&mut self.current_record_names);
self.parsed_records.push((names, body));
self.current_record_length = None;
self.state = State::ExpectingRecordType;
}
}
State::ExpectingNothing => {}
}
Ok(())
}
}
pub fn check_name_encoding(name: &[u8]) -> Result<(), PackError> {
std::str::from_utf8(name)
.map(|_| ())
.map_err(|e| PackError::InvalidRecord(e.to_string()))
}
#[derive(Debug)]
pub enum ReadError {
Pack(PackError),
Io(std::io::Error),
UnexpectedEof,
ExcessData(Vec<u8>),
DuplicateName(Vec<Vec<u8>>),
}
impl std::fmt::Display for ReadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ReadError::Pack(e) => write!(f, "{}", e),
ReadError::Io(e) => write!(f, "{}", e),
ReadError::UnexpectedEof => write!(f, "unexpected end of container stream"),
ReadError::ExcessData(d) => {
write!(f, "container has data after end marker: {:?}", d)
}
ReadError::DuplicateName(n) => {
write!(
f,
"container has multiple records with the same name: {:?}",
n
)
}
}
}
}
impl std::error::Error for ReadError {}
impl From<PackError> for ReadError {
fn from(e: PackError) -> Self {
ReadError::Pack(e)
}
}
impl From<std::io::Error> for ReadError {
fn from(e: std::io::Error) -> Self {
ReadError::Io(e)
}
}
pub const DEFAULT_JOIN_WRITES_THRESHOLD: usize = 100_000;
pub struct ContainerWriter<W: std::io::Write> {
out: W,
pub join_writes_threshold: usize,
pub current_offset: u64,
pub records_written: u64,
}
impl<W: std::io::Write> ContainerWriter<W> {
pub fn new(out: W) -> Self {
Self {
out,
join_writes_threshold: DEFAULT_JOIN_WRITES_THRESHOLD,
current_offset: 0,
records_written: 0,
}
}
fn write(&mut self, bytes: &[u8]) -> std::io::Result<()> {
self.out.write_all(bytes)?;
self.current_offset += bytes.len() as u64;
Ok(())
}
pub fn begin(&mut self) -> std::io::Result<()> {
self.write(&begin())
}
pub fn end(&mut self) -> std::io::Result<()> {
self.write(end())
}
pub fn add_bytes_record(
&mut self,
chunks: &[&[u8]],
length: usize,
names: &[Vec<Vec<u8>>],
) -> Result<(u64, u64), ReadError> {
let start = self.current_offset;
let header = bytes_header(length, names)?;
if length < self.join_writes_threshold {
let mut buf = Vec::with_capacity(header.len() + length);
buf.extend_from_slice(&header);
for chunk in chunks {
buf.extend_from_slice(chunk);
}
self.write(&buf)?;
} else {
self.write(&header)?;
for chunk in chunks {
self.write(chunk)?;
}
}
self.records_written += 1;
Ok((start, self.current_offset - start))
}
pub fn into_inner(self) -> W {
self.out
}
}
fn read_line<R: std::io::BufRead>(reader: &mut R) -> Result<Option<Vec<u8>>, ReadError> {
let mut buf = Vec::new();
let n = reader.read_until(b'\n', &mut buf)?;
if n == 0 {
return Ok(None);
}
if buf.last() != Some(&b'\n') {
return Err(ReadError::UnexpectedEof);
}
buf.pop();
Ok(Some(buf))
}
fn read_byte<R: std::io::Read>(reader: &mut R) -> Result<Option<u8>, ReadError> {
let mut buf = [0u8; 1];
let n = reader.read(&mut buf)?;
if n == 0 {
Ok(None)
} else {
Ok(Some(buf[0]))
}
}
pub struct BytesRecordReader<'a, R: std::io::BufRead> {
source: &'a mut R,
names: Vec<Vec<Vec<u8>>>,
remaining: usize,
}
impl<'a, R: std::io::BufRead> BytesRecordReader<'a, R> {
pub fn read_prelude(source: &'a mut R) -> Result<Self, ReadError> {
let line = read_line(source)?.ok_or(ReadError::UnexpectedEof)?;
let s = std::str::from_utf8(&line)
.map_err(|_| PackError::InvalidRecord(format!("{:?} is not a valid length.", line)))?;
let length: usize = s
.parse()
.map_err(|_| PackError::InvalidRecord(format!("{:?} is not a valid length.", line)))?;
let mut names = Vec::new();
loop {
let name_line = read_line(source)?.ok_or(ReadError::UnexpectedEof)?;
if name_line.is_empty() {
break;
}
let parts: Vec<Vec<u8>> = name_line.split(|&b| b == 0).map(|p| p.to_vec()).collect();
for part in &parts {
check_name(part)?;
}
names.push(parts);
}
Ok(Self {
source,
names,
remaining: length,
})
}
pub fn names(&self) -> &[Vec<Vec<u8>>] {
&self.names
}
pub fn remaining(&self) -> usize {
self.remaining
}
pub fn read_content(&mut self, max: Option<usize>) -> Result<Vec<u8>, ReadError> {
let want = match max {
Some(n) => n.min(self.remaining),
None => self.remaining,
};
let mut buf = vec![0u8; want];
self.source.read_exact(&mut buf).map_err(|e| {
if e.kind() == std::io::ErrorKind::UnexpectedEof {
ReadError::UnexpectedEof
} else {
ReadError::Io(e)
}
})?;
self.remaining -= want;
Ok(buf)
}
pub fn drain(&mut self) -> Result<(), ReadError> {
let _ = self.read_content(None)?;
Ok(())
}
pub fn validate(&mut self) -> Result<(), ReadError> {
for name_tuple in &self.names {
for name in name_tuple {
check_name_encoding(name)?;
}
}
self.drain()
}
}
pub enum RecordKind<'a, R: std::io::BufRead> {
Bytes(BytesRecordReader<'a, R>),
End,
}
pub struct ContainerReader<R: std::io::BufRead> {
source: R,
format_read: bool,
}
impl<R: std::io::BufRead> ContainerReader<R> {
pub fn new(source: R) -> Self {
Self {
source,
format_read: false,
}
}
pub fn read_format(&mut self) -> Result<(), ReadError> {
let line = read_line(&mut self.source)?.ok_or(ReadError::UnexpectedEof)?;
if line != FORMAT_ONE {
return Err(PackError::UnknownContainerFormat(line).into());
}
self.format_read = true;
Ok(())
}
pub fn next_record(&mut self) -> Result<RecordKind<'_, R>, ReadError> {
if !self.format_read {
self.read_format()?;
}
match read_byte(&mut self.source)? {
None => Err(ReadError::UnexpectedEof),
Some(b'B') => {
let r = BytesRecordReader::read_prelude(&mut self.source)?;
Ok(RecordKind::Bytes(r))
}
Some(b'E') => Ok(RecordKind::End),
Some(other) => Err(PackError::UnknownRecordType(other).into()),
}
}
pub fn validate(&mut self) -> Result<(), ReadError> {
let mut seen: std::collections::HashSet<Vec<Vec<u8>>> = std::collections::HashSet::new();
loop {
match self.next_record()? {
RecordKind::End => break,
RecordKind::Bytes(mut r) => {
for name_tuple in r.names() {
for name in name_tuple {
check_name_encoding(name)?;
}
if !seen.insert(name_tuple.clone()) {
return Err(ReadError::DuplicateName(name_tuple.clone()));
}
}
r.drain()?;
}
}
}
let mut tail = [0u8; 1];
match self.source.read(&mut tail)? {
0 => Ok(()),
_ => Err(ReadError::ExcessData(tail.to_vec())),
}
}
pub fn read_all(&mut self) -> Result<Vec<Record>, ReadError> {
let mut out = Vec::new();
loop {
match self.next_record()? {
RecordKind::End => return Ok(out),
RecordKind::Bytes(mut r) => {
let names = r.names().to_vec();
let body = r.read_content(None)?;
out.push((names, body));
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn name(parts: &[&[u8]]) -> Vec<Vec<u8>> {
parts.iter().map(|p| p.to_vec()).collect()
}
#[test]
fn check_name_accepts_plain_bytes() {
assert_eq!(check_name(b"abc"), Ok(()));
assert_eq!(check_name(b""), Ok(()));
assert_eq!(check_name(b"\x00\xff"), Ok(()));
}
#[test]
fn check_name_rejects_every_whitespace_byte() {
for &b in &[b'\t', b'\n', 0x0b, 0x0c, b'\r', b' '] {
let input = vec![b'a', b, b'b'];
assert_eq!(
check_name(&input),
Err(PackError::InvalidName(input.clone())),
"byte {:#x} should be rejected",
b
);
}
}
#[test]
fn begin_matches_python() {
assert_eq!(
begin(),
b"Bazaar pack format 1 (introduced in 0.18)\n".to_vec()
);
}
#[test]
fn end_marker() {
assert_eq!(end(), b"E");
}
#[test]
fn bytes_header_no_names() {
assert_eq!(bytes_header(0, &[]).unwrap(), b"B0\n\n".to_vec());
}
#[test]
fn bytes_header_one_name_one_part() {
let names = vec![name(&[b"name"])];
assert_eq!(bytes_header(0, &names).unwrap(), b"B0\nname\n\n".to_vec());
}
#[test]
fn bytes_header_one_name_two_parts() {
let names = vec![name(&[b"part1", b"part2"])];
assert_eq!(
bytes_header(0, &names).unwrap(),
b"B0\npart1\x00part2\n\n".to_vec()
);
}
#[test]
fn bytes_header_two_names() {
let names = vec![name(&[b"name1"]), name(&[b"name2"])];
assert_eq!(
bytes_header(0, &names).unwrap(),
b"B0\nname1\nname2\n\n".to_vec()
);
}
#[test]
fn bytes_record_concatenates_header_and_body() {
let body = b"body bytes";
let names = vec![name(&[b"foo"])];
let got = bytes_record(body, &names).unwrap();
let mut expected = format!("B{}\nfoo\n\n", body.len()).into_bytes();
expected.extend_from_slice(body);
assert_eq!(got, expected);
}
#[test]
fn bytes_header_rejects_whitespace_in_name() {
let names = vec![name(&[b"bad name"])];
assert_eq!(
bytes_header(0, &names),
Err(PackError::InvalidName(b"bad name".to_vec()))
);
}
#[test]
fn bytes_header_reports_correct_length() {
let names = vec![name(&[b"foo"])];
assert_eq!(bytes_header(42, &names).unwrap(), b"B42\nfoo\n\n".to_vec());
}
fn make_container(records: &[(&[&[&[u8]]], &[u8])]) -> Vec<u8> {
let mut out = begin();
for (names, body) in records {
let name_tuples: Vec<Vec<Vec<u8>>> = names
.iter()
.map(|nt| nt.iter().map(|p| p.to_vec()).collect())
.collect();
out.extend_from_slice(&bytes_record(body, &name_tuples).unwrap());
}
out.extend_from_slice(end());
out
}
#[test]
fn parser_empty_container() {
let data = make_container(&[]);
let mut p = ContainerPushParser::new();
p.accept_bytes(&data).unwrap();
assert!(p.finished());
assert!(p.read_pending_records(None).is_empty());
}
#[test]
fn parser_one_record() {
let data = make_container(&[(&[&[b"name"]], b"body")]);
let mut p = ContainerPushParser::new();
p.accept_bytes(&data).unwrap();
let records = p.read_pending_records(None);
assert_eq!(records.len(), 1);
assert_eq!(records[0].0, vec![vec![b"name".to_vec()]]);
assert_eq!(records[0].1, b"body");
assert!(p.finished());
}
#[test]
fn parser_multi_name_record() {
let data = make_container(&[(&[&[b"a", b"b"], &[b"c"]], b"xy")]);
let mut p = ContainerPushParser::new();
p.accept_bytes(&data).unwrap();
let records = p.read_pending_records(None);
assert_eq!(records.len(), 1);
assert_eq!(
records[0].0,
vec![vec![b"a".to_vec(), b"b".to_vec()], vec![b"c".to_vec()]]
);
assert_eq!(records[0].1, b"xy");
}
#[test]
fn parser_streams_byte_by_byte() {
let data = make_container(&[(&[&[b"first"]], b"one"), (&[&[b"second"]], b"two-body")]);
let mut p = ContainerPushParser::new();
for chunk in data.chunks(1) {
p.accept_bytes(chunk).unwrap();
}
let records = p.read_pending_records(None);
assert_eq!(records.len(), 2);
assert_eq!(records[0].1, b"one");
assert_eq!(records[1].1, b"two-body");
assert!(p.finished());
}
#[test]
fn parser_read_pending_records_max() {
let data = make_container(&[(&[&[b"a"]], b"1"), (&[&[b"b"]], b"2"), (&[&[b"c"]], b"3")]);
let mut p = ContainerPushParser::new();
p.accept_bytes(&data).unwrap();
let first = p.read_pending_records(Some(2));
assert_eq!(first.len(), 2);
let rest = p.read_pending_records(None);
assert_eq!(rest.len(), 1);
assert_eq!(rest[0].1, b"3");
}
#[test]
fn parser_unknown_format() {
let mut p = ContainerPushParser::new();
let err = p.accept_bytes(b"garbage\n").unwrap_err();
assert!(matches!(err, PackError::UnknownContainerFormat(_)));
}
#[test]
fn parser_unknown_record_type() {
let mut data = begin();
data.push(b'X');
let mut p = ContainerPushParser::new();
let err = p.accept_bytes(&data).unwrap_err();
assert_eq!(err, PackError::UnknownRecordType(b'X'));
}
#[test]
fn parser_invalid_length() {
let mut data = begin();
data.extend_from_slice(b"Bnotanumber\n");
let mut p = ContainerPushParser::new();
let err = p.accept_bytes(&data).unwrap_err();
assert!(matches!(err, PackError::InvalidRecord(_)));
}
#[test]
fn parser_read_size_hint_defaults_to_16k() {
let p = ContainerPushParser::new();
assert_eq!(p.read_size_hint(), 16384);
}
#[test]
fn parser_record_with_no_name() {
let data = make_container(&[(&[], b"aaaaa")]);
let mut p = ContainerPushParser::new();
p.accept_bytes(&data).unwrap();
let records = p.read_pending_records(None);
assert_eq!(records.len(), 1);
let (names, body) = &records[0];
assert!(names.is_empty());
assert_eq!(body, b"aaaaa");
}
#[test]
fn parser_two_separate_names() {
let data = make_container(&[(&[&[b"name1"]], b"body1"), (&[&[b"name2"]], b"body2")]);
let mut p = ContainerPushParser::new();
p.accept_bytes(&data).unwrap();
let records = p.read_pending_records(None);
assert_eq!(records.len(), 2);
assert_eq!(records[0].0, vec![vec![b"name1".to_vec()]]);
assert_eq!(records[1].0, vec![vec![b"name2".to_vec()]]);
}
#[test]
fn parser_multiple_names_on_one_record() {
let data = make_container(&[(&[&[b"n1"], &[b"n2"]], b"xy")]);
let mut p = ContainerPushParser::new();
p.accept_bytes(&data).unwrap();
let records = p.read_pending_records(None);
assert_eq!(records.len(), 1);
assert_eq!(
records[0].0,
vec![vec![b"n1".to_vec()], vec![b"n2".to_vec()]]
);
}
#[test]
fn parser_incomplete_record_drains_nothing() {
let mut data = begin();
data.extend_from_slice(b"B5\nname\n\n");
let mut p = ContainerPushParser::new();
p.accept_bytes(&data).unwrap();
assert!(p.read_pending_records(None).is_empty());
}
#[test]
fn parser_multiple_empty_records_at_once() {
let data = make_container(&[(&[&[b"name1"]], b""), (&[&[b"name2"]], b"")]);
let mut p = ContainerPushParser::new();
p.accept_bytes(&data).unwrap();
let records = p.read_pending_records(None);
assert_eq!(records.len(), 2);
assert_eq!(records[0].1, b"");
assert_eq!(records[1].1, b"");
assert_eq!(records[0].0, vec![vec![b"name1".to_vec()]]);
assert_eq!(records[1].0, vec![vec![b"name2".to_vec()]]);
}
#[test]
fn parser_accept_empty_bytes_is_a_noop() {
let mut p = ContainerPushParser::new();
p.accept_bytes(b"").unwrap();
assert!(p.read_pending_records(None).is_empty());
assert!(!p.finished());
}
#[test]
fn parser_rejects_whitespace_in_name() {
let mut data = begin();
data.extend_from_slice(b"B5\nbad name\n\nhello");
let mut p = ContainerPushParser::new();
let err = p.accept_bytes(&data).unwrap_err();
assert!(matches!(err, PackError::InvalidName(_)));
}
#[test]
fn parser_read_size_hint_covers_large_body() {
let body = vec![0u8; 100_000];
let data = make_container(&[(&[&[b"big"]], &body)]);
let header_len = data.len() - body.len() - end().len();
let mut p = ContainerPushParser::new();
p.accept_bytes(&data[..header_len + 10]).unwrap();
assert!(p.read_size_hint() >= body.len() - 10);
}
#[test]
fn check_name_encoding_accepts_ascii_and_utf8() {
assert!(check_name_encoding(b"abc").is_ok());
assert!(check_name_encoding("\u{e9}clair".as_bytes()).is_ok());
}
#[test]
fn check_name_encoding_rejects_invalid_utf8() {
assert!(check_name_encoding(b"\xcc").is_err());
}
#[test]
fn writer_emits_format_header_on_begin() {
let mut buf = Vec::new();
let mut w = ContainerWriter::new(&mut buf);
w.begin().unwrap();
assert_eq!(buf, b"Bazaar pack format 1 (introduced in 0.18)\n");
}
#[test]
fn writer_records_offsets_and_increments_count() {
let mut buf = Vec::new();
let mut w = ContainerWriter::new(&mut buf);
w.begin().unwrap();
let memo = w
.add_bytes_record(&[b"abc"], 3, &[name(&[b"name1"])])
.unwrap();
assert_eq!(memo, (42, 13));
assert_eq!(w.records_written, 1);
let memo2 = w.add_bytes_record(&[b"abc"], 3, &[]).unwrap();
assert_eq!(memo2.0, 42 + 13);
}
#[test]
fn writer_split_writes_when_above_threshold() {
struct Chunked(Vec<Vec<u8>>);
impl std::io::Write for Chunked {
fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
self.0.push(b.to_vec());
Ok(b.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let mut sink = Chunked(Vec::new());
{
let mut w = ContainerWriter::new(&mut sink);
w.join_writes_threshold = 2;
w.begin().unwrap();
w.add_bytes_record(&[b"abcabc"], 6, &[name(&[b"name1"])])
.unwrap();
}
assert_eq!(sink.0.len(), 3);
assert_eq!(sink.0[0], b"Bazaar pack format 1 (introduced in 0.18)\n");
assert_eq!(sink.0[1], b"B6\nname1\n\n");
assert_eq!(sink.0[2], b"abcabc");
}
#[test]
fn writer_rejects_invalid_name() {
let mut buf = Vec::new();
let mut w = ContainerWriter::new(&mut buf);
w.begin().unwrap();
let err = w
.add_bytes_record(&[b"abc"], 3, &[name(&[b"bad name"])])
.unwrap_err();
match err {
ReadError::Pack(PackError::InvalidName(_)) => {}
other => panic!("expected InvalidName, got {:?}", other),
}
}
#[test]
fn reader_empty_container_validates() {
let data = make_container(&[]);
let mut r = ContainerReader::new(std::io::Cursor::new(data));
r.validate().unwrap();
}
#[test]
fn reader_single_record_round_trips() {
let data = make_container(&[(&[&[b"name"]], b"body")]);
let mut r = ContainerReader::new(std::io::Cursor::new(data));
let records = r.read_all().unwrap();
assert_eq!(records.len(), 1);
assert_eq!(records[0].0, vec![vec![b"name".to_vec()]]);
assert_eq!(records[0].1, b"body");
}
#[test]
fn reader_validate_rejects_duplicate_names() {
let data = make_container(&[(&[&[b"n"]], b""), (&[&[b"n"]], b"")]);
let mut r = ContainerReader::new(std::io::Cursor::new(data));
match r.validate() {
Err(ReadError::DuplicateName(_)) => {}
other => panic!("expected DuplicateName, got {:?}", other),
}
}
#[test]
fn reader_validate_rejects_excess_data() {
let mut data = make_container(&[]);
data.extend_from_slice(b"crud");
let mut r = ContainerReader::new(std::io::Cursor::new(data));
match r.validate() {
Err(ReadError::ExcessData(_)) => {}
other => panic!("expected ExcessData, got {:?}", other),
}
}
#[test]
fn reader_validate_rejects_bad_format() {
let mut r = ContainerReader::new(std::io::Cursor::new(b"unknown format\n".to_vec()));
match r.validate() {
Err(ReadError::Pack(PackError::UnknownContainerFormat(_))) => {}
other => panic!("expected UnknownContainerFormat, got {:?}", other),
}
}
#[test]
fn reader_validate_rejects_undecodable_name() {
let data = b"Bazaar pack format 1 (introduced in 0.18)\nB0\n\xcc\n\nE".to_vec();
let mut r = ContainerReader::new(std::io::Cursor::new(data));
match r.validate() {
Err(ReadError::Pack(PackError::InvalidRecord(_))) => {}
other => panic!("expected InvalidRecord, got {:?}", other),
}
}
#[test]
fn bytes_record_reader_max_length() {
let mut data: &[u8] = b"6\n\nabcdef";
let mut r = BytesRecordReader::read_prelude(&mut data).unwrap();
assert_eq!(r.read_content(Some(3)).unwrap(), b"abc");
assert_eq!(r.read_content(Some(3)).unwrap(), b"def");
assert_eq!(r.read_content(Some(99)).unwrap(), b"");
}
#[test]
fn bytes_record_reader_invalid_length_errors() {
let mut data: &[u8] = b"not a number\n";
match BytesRecordReader::read_prelude(&mut data) {
Err(ReadError::Pack(PackError::InvalidRecord(_))) => {}
Err(other) => panic!("expected InvalidRecord, got {:?}", other),
Ok(_) => panic!("expected error"),
}
}
#[test]
fn bytes_record_reader_eof_during_name() {
let mut data: &[u8] = b"123\nname";
match BytesRecordReader::read_prelude(&mut data) {
Err(ReadError::UnexpectedEof) => {}
Err(other) => panic!("expected UnexpectedEof, got {:?}", other),
Ok(_) => panic!("expected error"),
}
}
}