use std::cell::RefCell;
use std::io;
use flate2::Compress;
use flate2::Compression as FlateCompression;
use flate2::FlushCompress;
use flate2::Status;
use protohoggr::{encode_bytes_field, encode_int32_field};
use crate::write::metrics::WRITER_METRICS;
use std::sync::atomic::Ordering::Relaxed;
use super::compression::Compression;
use super::pipeline::{elapsed_ns_u64, FramedBlobParts};
pub(super) struct FrameScratch {
pub(super) blob_buf: Vec<u8>,
pub(super) header_buf: Vec<u8>,
compress_buf: Vec<u8>,
zlib_compressor: Option<(u32, Compress)>,
zstd_compressor: Option<(i32, zstd::bulk::Compressor<'static>)>,
}
impl FrameScratch {
pub(super) const fn new() -> Self {
Self {
blob_buf: Vec::new(),
header_buf: Vec::new(),
compress_buf: Vec::new(),
zlib_compressor: None,
zstd_compressor: None,
}
}
}
thread_local! {
pub(super) static PIPELINE_SCRATCH: RefCell<FrameScratch> = const { RefCell::new(FrameScratch::new()) };
}
pub(crate) fn frame_blob(
blob_type: &str,
uncompressed: &[u8],
compression: &Compression,
indexdata: Option<&[u8]>,
) -> io::Result<Vec<u8>> {
let mut scratch = FrameScratch::new();
Ok(frame_blob_into(
blob_type,
uncompressed,
compression,
indexdata,
None,
&mut scratch,
)?
.into_vec())
}
pub(crate) fn frame_blob_pipelined(
uncompressed: &[u8],
compression: &Compression,
indexdata: Option<&[u8]>,
tagdata: Option<&[u8]>,
) -> io::Result<FramedBlobParts> {
PIPELINE_SCRATCH.with_borrow_mut(|scratch| {
frame_blob_into("OSMData", uncompressed, compression, indexdata, tagdata, scratch)
})
}
#[hotpath::measure]
pub(super) fn frame_blob_into(
blob_type: &str,
uncompressed: &[u8],
compression: &Compression,
indexdata: Option<&[u8]>,
tagdata: Option<&[u8]>,
scratch: &mut FrameScratch,
) -> io::Result<FramedBlobParts> {
let t_compress = std::time::Instant::now();
encode_blob_body(uncompressed, compression, scratch)?;
WRITER_METRICS
.compress_ns
.fetch_add(elapsed_ns_u64(t_compress), Relaxed);
let t_frame = std::time::Instant::now();
let datasize = i32::try_from(scratch.blob_buf.len()).map_err(|_| {
io::Error::other(format!("blob datasize overflow: {} bytes", scratch.blob_buf.len()))
})?;
encode_blob_header_into(blob_type, datasize, indexdata, tagdata, &mut scratch.header_buf);
let header_len = u32::try_from(scratch.header_buf.len())
.map_err(|_| io::Error::other(format!("header too large: {} bytes", scratch.header_buf.len())))?;
let total_len = 4 + scratch.header_buf.len() + scratch.blob_buf.len();
WRITER_METRICS
.frame_ns
.fetch_add(elapsed_ns_u64(t_frame), Relaxed);
WRITER_METRICS
.bytes_framed
.fetch_add(total_len as u64, Relaxed);
Ok(FramedBlobParts {
prefix: header_len.to_be_bytes(),
header: scratch.header_buf.clone(),
blob: scratch.blob_buf.clone(),
})
}
pub(super) fn encode_blob_body(
uncompressed: &[u8],
compression: &Compression,
scratch: &mut FrameScratch,
) -> io::Result<()> {
scratch.blob_buf.clear();
match compression {
Compression::None => {
encode_bytes_field(&mut scratch.blob_buf, 1, uncompressed);
}
Compression::Zlib(level) => {
compress_zlib(uncompressed, *level, scratch)?;
}
Compression::Zstd(level) => {
match &scratch.zstd_compressor {
Some((cached_level, _)) if *cached_level == *level => {}
_ => {
scratch.zstd_compressor = Some((
*level,
zstd::bulk::Compressor::new(*level).map_err(io::Error::other)?,
));
}
}
let (_, compressor) = scratch.zstd_compressor.as_mut().expect("just initialized");
let bound = zstd::zstd_safe::compress_bound(uncompressed.len());
scratch.compress_buf.clear();
scratch.compress_buf.reserve(bound);
compressor.compress_to_buffer(uncompressed, &mut scratch.compress_buf).map_err(io::Error::other)?;
let raw_size = i32::try_from(uncompressed.len()).map_err(|_| {
io::Error::other(format!("blob raw_size overflow: {} bytes", uncompressed.len()))
})?;
encode_int32_field(&mut scratch.blob_buf, 2, raw_size);
encode_bytes_field(&mut scratch.blob_buf, 7, &scratch.compress_buf);
}
}
Ok(())
}
fn compress_zlib(
uncompressed: &[u8],
level: u32,
scratch: &mut FrameScratch,
) -> io::Result<()> {
let needs_new = match &scratch.zlib_compressor {
Some((cached_level, _)) => *cached_level != level,
None => true,
};
if needs_new {
scratch.zlib_compressor = Some((level, Compress::new(FlateCompression::new(level), true)));
}
let (_, compressor) = scratch.zlib_compressor.as_mut().expect("just initialized");
scratch.compress_buf.clear();
scratch.compress_buf.reserve(uncompressed.len() + (uncompressed.len() >> 10) + 64);
let status = compressor
.compress_vec(uncompressed, &mut scratch.compress_buf, FlushCompress::Finish)
.map_err(|e| io::Error::other(format!("zlib compress error: {e}")))?;
if !matches!(status, Status::StreamEnd) {
return Err(io::Error::other("zlib compress did not complete in one call"));
}
compressor.reset();
let raw_size = i32::try_from(uncompressed.len()).map_err(|_| {
io::Error::other(format!("blob raw_size overflow: {} bytes", uncompressed.len()))
})?;
encode_int32_field(&mut scratch.blob_buf, 2, raw_size);
encode_bytes_field(&mut scratch.blob_buf, 3, &scratch.compress_buf);
Ok(())
}
pub(crate) fn encode_blob_header_into(
blob_type: &str,
datasize: i32,
indexdata: Option<&[u8]>,
tagdata: Option<&[u8]>,
buf: &mut Vec<u8>,
) {
buf.clear();
encode_bytes_field(buf, 1, blob_type.as_bytes());
if let Some(data) = indexdata {
encode_bytes_field(buf, 2, data);
}
encode_int32_field(buf, 3, datasize);
if let Some(data) = tagdata {
encode_bytes_field(buf, 4, data);
}
}
pub(crate) fn reframe_raw_with_index(
blob_bytes: &[u8],
indexdata: &[u8],
tagdata: Option<&[u8]>,
) -> io::Result<Vec<u8>> {
let mut header_buf = Vec::new();
reframe_raw_with_index_scratch(blob_bytes, indexdata, tagdata, &mut header_buf)
}
pub(crate) fn reframe_raw_with_index_scratch(
blob_bytes: &[u8],
indexdata: &[u8],
tagdata: Option<&[u8]>,
header_buf: &mut Vec<u8>,
) -> io::Result<Vec<u8>> {
let datasize = i32::try_from(blob_bytes.len()).map_err(|_| {
io::Error::other(format!("blob datasize overflow: {} bytes", blob_bytes.len()))
})?;
header_buf.clear();
encode_blob_header_into("OSMData", datasize, Some(indexdata), tagdata, header_buf);
let header_len = u32::try_from(header_buf.len())
.map_err(|_| io::Error::other(format!("header too large: {} bytes", header_buf.len())))?;
let total_len = 4 + header_buf.len() + blob_bytes.len();
let mut out = Vec::with_capacity(total_len);
out.extend_from_slice(&header_len.to_be_bytes());
out.extend_from_slice(header_buf);
out.extend_from_slice(blob_bytes);
Ok(out)
}