use std::{
collections::HashMap,
io::{ErrorKind, Read},
str::FromStr,
};
use log::warn;
use serde::de::DeserializeOwned;
use serde_json::from_slice;
use crate::{
AvroResult, Codec, Error,
decode::{decode, decode_internal},
error::Details,
schema::{Names, Schema, resolve_names, resolve_names_with_schemata},
serde::deser_schema::{Config, SchemaAwareDeserializer},
types::Value,
util,
};
#[derive(Debug, Clone)]
pub(super) struct Block<'r, R> {
reader: R,
buf: Vec<u8>,
buf_idx: usize,
message_count: usize,
marker: [u8; 16],
codec: Codec,
pub(super) writer_schema: Schema,
schemata: Vec<&'r Schema>,
pub(super) user_metadata: HashMap<String, Vec<u8>>,
names_refs: Names,
human_readable: bool,
}
impl<'r, R: Read> Block<'r, R> {
pub(super) fn new(
reader: R,
schemata: Vec<&'r Schema>,
human_readable: bool,
) -> AvroResult<Block<'r, R>> {
let mut block = Block {
reader,
codec: Codec::Null,
writer_schema: Schema::Null,
schemata,
buf: vec![],
buf_idx: 0,
message_count: 0,
marker: [0; 16],
user_metadata: Default::default(),
names_refs: Default::default(),
human_readable,
};
block.read_header()?;
Ok(block)
}
fn read_header(&mut self) -> AvroResult<()> {
let mut buf = [0u8; 4];
self.reader
.read_exact(&mut buf)
.map_err(Details::ReadHeader)?;
if buf != [b'O', b'b', b'j', 1u8] {
return Err(Details::HeaderMagic.into());
}
let meta_schema = Schema::map(Schema::Bytes).build();
match decode(&meta_schema, &mut self.reader)? {
Value::Map(metadata) => {
self.read_writer_schema(&metadata)?;
self.codec = read_codec(&metadata)?;
for (key, value) in metadata {
if key == "avro.schema"
|| key == "avro.codec"
|| key == "avro.codec.compression_level"
{
} else if key.starts_with("avro.") {
warn!("Ignoring unknown metadata key: {key}");
} else {
self.read_user_metadata(key, value);
}
}
}
_ => {
return Err(Details::GetHeaderMetadata.into());
}
}
self.reader
.read_exact(&mut self.marker)
.map_err(|e| Details::ReadMarker(e).into())
}
fn fill_buf(&mut self, n: usize) -> AvroResult<()> {
self.buf.resize(util::safe_len(n)?, 0);
self.reader
.read_exact(&mut self.buf)
.map_err(Details::ReadIntoBuf)?;
self.buf_idx = 0;
Ok(())
}
fn read_block_next(&mut self) -> AvroResult<()> {
assert!(self.is_empty(), "Expected self to be empty!");
match util::read_usize(&mut self.reader).map_err(Error::into_details) {
Ok(block_len) => {
self.message_count = block_len;
let block_bytes = util::read_usize(&mut self.reader)?;
self.fill_buf(block_bytes)?;
let mut marker = [0u8; 16];
self.reader
.read_exact(&mut marker)
.map_err(Details::ReadBlockMarker)?;
if marker != self.marker {
return Err(Details::GetBlockMarker.into());
}
self.codec.decompress(&mut self.buf)
}
Err(Details::ReadVariableIntegerBytes(io_err)) => {
if let ErrorKind::UnexpectedEof = io_err.kind() {
Ok(())
} else {
Err(Details::ReadVariableIntegerBytes(io_err).into())
}
}
Err(e) => Err(Error::new(e)),
}
}
fn len(&self) -> usize {
self.message_count
}
fn is_empty(&self) -> bool {
self.len() == 0
}
pub(super) fn read_next(&mut self, read_schema: Option<&Schema>) -> AvroResult<Option<Value>> {
if self.is_empty() {
self.read_block_next()?;
if self.is_empty() {
return Ok(None);
}
}
let mut block_bytes = &self.buf[self.buf_idx..];
let b_original = block_bytes.len();
let item = decode_internal(
&self.writer_schema,
&self.names_refs,
None,
&mut block_bytes,
)?;
let item = match read_schema {
Some(schema) => item.resolve(schema)?,
None => item,
};
if b_original != 0 && b_original == block_bytes.len() {
return Err(Details::ReadBlock.into());
}
self.buf_idx += b_original - block_bytes.len();
self.message_count -= 1;
Ok(Some(item))
}
pub(super) fn read_next_deser<T: DeserializeOwned>(
&mut self,
reader_schema: Option<&Schema>,
) -> AvroResult<Option<T>> {
if self.is_empty() {
self.read_block_next()?;
if self.is_empty() {
return Ok(None);
}
}
let mut block_bytes = &self.buf[self.buf_idx..];
let b_original = block_bytes.len();
let item = if reader_schema.is_some() {
panic!("Schema aware deserialisation does not resolve schemas yet");
} else {
let config = Config {
names: &self.names_refs,
human_readable: self.human_readable,
};
T::deserialize(SchemaAwareDeserializer::new(
&mut block_bytes,
&self.writer_schema,
config,
)?)?
};
if b_original != 0 && b_original == block_bytes.len() {
return Err(Details::ReadBlock.into());
}
self.buf_idx += b_original - block_bytes.len();
self.message_count -= 1;
Ok(Some(item))
}
fn read_writer_schema(&mut self, metadata: &HashMap<String, Value>) -> AvroResult<()> {
let json: serde_json::Value = metadata
.get("avro.schema")
.and_then(|bytes| {
if let Value::Bytes(ref bytes) = *bytes {
from_slice(bytes.as_ref()).ok()
} else {
None
}
})
.ok_or(Details::GetAvroSchemaFromMap)?;
if !self.schemata.is_empty() {
let mut names = HashMap::new();
resolve_names_with_schemata(
self.schemata.iter().copied(),
&mut names,
None,
&HashMap::new(),
)?;
self.names_refs = names.into_iter().map(|(n, s)| (n, s.clone())).collect();
self.writer_schema = Schema::parse_with_names(&json, self.names_refs.clone())?;
} else {
self.writer_schema = Schema::parse(&json)?;
let mut names = HashMap::new();
resolve_names(&self.writer_schema, &mut names, None, &HashMap::new())?;
self.names_refs = names.into_iter().map(|(n, s)| (n, s.clone())).collect();
}
Ok(())
}
fn read_user_metadata(&mut self, key: String, value: Value) {
match value {
Value::Bytes(ref vec) => {
self.user_metadata.insert(key, vec.clone());
}
wrong => {
warn!("User metadata values must be Value::Bytes, found {wrong:?}");
}
}
}
}
fn read_codec(metadata: &HashMap<String, Value>) -> AvroResult<Codec> {
let result = metadata
.get("avro.codec")
.map(|codec| {
if let Value::Bytes(ref bytes) = *codec {
match std::str::from_utf8(bytes.as_ref()) {
Ok(utf8) => Ok(utf8),
Err(utf8_error) => Err(Details::ConvertToUtf8Error(utf8_error).into()),
}
} else {
Err(Details::BadCodecMetadata.into())
}
})
.map(|codec_res| match codec_res {
Ok(codec) => match Codec::from_str(codec) {
Ok(codec) => match codec {
#[cfg(feature = "bzip")]
Codec::Bzip2(_) => {
use crate::Bzip2Settings;
if let Some(Value::Bytes(bytes)) =
metadata.get("avro.codec.compression_level")
{
Ok(Codec::Bzip2(Bzip2Settings::new(bytes[0])))
} else {
Ok(codec)
}
}
#[cfg(feature = "xz")]
Codec::Xz(_) => {
use crate::XzSettings;
if let Some(Value::Bytes(bytes)) =
metadata.get("avro.codec.compression_level")
{
Ok(Codec::Xz(XzSettings::new(bytes[0])))
} else {
Ok(codec)
}
}
#[cfg(feature = "zstandard")]
Codec::Zstandard(_) => {
use crate::ZstandardSettings;
if let Some(Value::Bytes(bytes)) =
metadata.get("avro.codec.compression_level")
{
Ok(Codec::Zstandard(ZstandardSettings::new(bytes[0])))
} else {
Ok(codec)
}
}
_ => Ok(codec),
},
Err(_) => Err(Details::CodecNotSupported(codec.to_owned()).into()),
},
Err(err) => Err(err),
});
result.unwrap_or(Ok(Codec::Null))
}
#[cfg(test)]
mod tests {
use super::Block;
use crate::error::Details;
use crate::{Codec, Schema};
#[test]
fn avro_rs_586_negative_block_size() {
let mut block = Block::<'_, &[u8]> {
reader: &[0x02, 0x01],
buf: vec![],
buf_idx: 0,
message_count: 0,
marker: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
codec: Codec::Null,
writer_schema: Schema::Null,
schemata: vec![],
user_metadata: Default::default(),
names_refs: Default::default(),
human_readable: false,
};
let err = block.read_block_next().unwrap_err().into_details();
assert!(matches!(err, Details::ConvertI64ToUsize(_, _)));
}
#[test]
fn avro_rs_586_negative_block_len() {
let mut block = Block::<'_, &[u8]> {
reader: &[0x01, 0x00],
buf: vec![],
buf_idx: 0,
message_count: 0,
marker: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
codec: Codec::Null,
writer_schema: Schema::Null,
schemata: vec![],
user_metadata: Default::default(),
names_refs: Default::default(),
human_readable: false,
};
let err = block.read_block_next().unwrap_err().into_details();
assert!(matches!(err, Details::ConvertI64ToUsize(_, _)));
}
}