use crate::arrow::message::org::apache::arrow::flatbuf as fb;
use crate::arrow::message::org::apache::arrow::flatbuf::{BodyCompression, Buffer};
use crate::models::decoders::limits::DecodeLimits;
use crate::traits::stream_buffer::StreamBuffer;
use flatbuffers::Vector;
use minarrow::Vec64;
use std::io;
fn decompress_buffer_data(data: &[u8], codec: fb::CompressionType) -> io::Result<Vec<u8>> {
if codec == fb::CompressionType::ZSTD {
return super::decompress(data, super::Compression::Zstd)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()));
}
Err(io::Error::new(
io::ErrorKind::Unsupported,
format!("Unsupported IPC compression codec: {:?}", codec),
))
}
#[allow(clippy::type_complexity)]
pub fn decompress_ipc_body<B: StreamBuffer>(
body: &[u8],
buffers: &Vector<'_, Buffer>,
compression: &BodyCompression,
limits: DecodeLimits,
) -> io::Result<(Vec64<u8>, Vec<(usize, usize)>)> {
const PREFIX_LEN: usize = 8;
let codec = compression.codec();
let mut total_size = 0usize;
let mut corrections = Vec::with_capacity(buffers.len());
for i in 0..buffers.len() {
let buf = buffers.get(i);
let offset_i = buf.offset();
let length_i = buf.length();
if offset_i < 0 || length_i < 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Compressed buffer {} descriptor negative", i),
));
}
let offset = offset_i as usize;
let length = length_i as usize;
if length == 0 {
corrections.push((total_size, 0));
continue;
}
let end = offset.checked_add(length).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"compressed buffer offset+length overflow",
)
})?;
if end > body.len() {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
format!("Compressed buffer {} out of bounds", i),
));
}
if length < PREFIX_LEN {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Compressed buffer {} too small for length prefix", i),
));
}
let prefix = i64::from_le_bytes(body[offset..offset + PREFIX_LEN].try_into().unwrap());
let uncompressed_len = if prefix == -1 {
length - PREFIX_LEN
} else if prefix < 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Invalid uncompressed length prefix for buffer {}: {}",
i, prefix
),
));
} else {
prefix as usize
};
limits.check(
uncompressed_len,
limits.max_decompressed_bytes,
"decompressed buffer length",
)?;
corrections.push((total_size, uncompressed_len));
total_size = total_size.checked_add(uncompressed_len).ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidData, "decompressed total overflow")
})?;
limits.check(
total_size,
limits.max_decompressed_bytes,
"decompressed body total",
)?;
let pad = total_size % B::ALIGN;
if pad != 0 {
total_size = total_size.checked_add(B::ALIGN - pad).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"decompressed alignment overflow",
)
})?;
limits.check(
total_size,
limits.max_decompressed_bytes,
"decompressed body total (aligned)",
)?;
}
}
let mut decompressed = Vec64::<u8>::with_capacity(total_size);
decompressed.resize(total_size, 0u8);
for (i, &(dec_offset, dec_len)) in corrections.iter().enumerate() {
let buf = buffers.get(i);
let offset = buf.offset() as usize;
let length = buf.length() as usize;
if length == 0 || dec_len == 0 {
continue;
}
let prefix = i64::from_le_bytes(body[offset..offset + PREFIX_LEN].try_into().unwrap());
let data_start = offset + PREFIX_LEN;
let data_len = length - PREFIX_LEN;
if prefix == -1 {
decompressed[dec_offset..dec_offset + dec_len]
.copy_from_slice(&body[data_start..data_start + data_len]);
} else {
let compressed_data = &body[data_start..data_start + data_len];
let result = decompress_buffer_data(compressed_data, codec)?;
if result.len() != dec_len {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Decompressed size mismatch for buffer {}: expected {}, got {}",
i,
dec_len,
result.len()
),
));
}
decompressed[dec_offset..dec_offset + dec_len].copy_from_slice(&result);
}
}
Ok((decompressed, corrections))
}