blob: 968270f014f2b82515a20d0a2ea24a6c24a8f26b [file]
use ::lending_iterator::prelude::*;
use bytes::{Bytes, BytesMut};
use serde_with::serde_as;
use std::{io::Write, marker::PhantomData, ops::Range};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::{
binary::messages,
system::get_stats,
utils::{byte_size::IggyByteSize, timestamp::IggyTimestamp, varint::IggyVarInt},
};
pub const IGGY_BATCH_OVERHEAD: u64 = 16 + 16 + 16 + 16 + 8 + 8 + 8 + 8 + 4 + 4 + 4 + 4 + 1;
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct IggyHeader {
// TODO: replace timestamp with `IggyTimestamp`
// and maybe impl `IggyOffset` and other wrapper structs for things like checksums, attributes etc...
// TODO Fix me: Reorganize those fields, such that the most frequently accessed fields
// are at the top in the same cache line.
pub payload_type: u8,
pub last_offset_delta: u32,
pub last_timestamp_delta: u32,
pub release: u32,
pub attributes: u32,
pub base_offset: u64,
pub batch_length: u64,
pub origin_timestamp: u64,
pub base_timestamp: u64,
pub reserved_nonce: u128,
pub parent: u128,
pub checksum_body: u128,
pub checksum: u128,
}
impl IggyHeader {
pub fn new(
payload_type: u8,
last_offset_delta: u32,
last_timestamp_delta: u32,
release: u32,
attributes: u32,
base_offset: u64,
batch_length: u64,
origin_timestamp: u64,
base_timestamp: u64,
reserved_nonce: u128,
parent: u128,
checksum_body: u128,
checksum: u128,
) -> Self {
Self {
payload_type,
last_offset_delta,
last_timestamp_delta,
release,
attributes,
base_offset,
batch_length,
origin_timestamp,
base_timestamp,
reserved_nonce,
parent,
checksum_body,
checksum,
}
}
pub async fn read_header<R: AsyncReadExt + Unpin>(reader: &mut R) -> IggyHeader {
let payload_type = reader.read_u8().await.unwrap();
let last_offset_delta = reader.read_u32_le().await.unwrap();
let last_timestamp_delta = reader.read_u32_le().await.unwrap();
let release = reader.read_u32_le().await.unwrap();
let attributes = reader.read_u32_le().await.unwrap();
let base_offset = reader.read_u64_le().await.unwrap();
let batch_length = reader.read_u64_le().await.unwrap();
let origin_timestamp = reader.read_u64_le().await.unwrap();
let base_timestamp = reader.read_u64_le().await.unwrap();
let reserved_nonce = reader.read_u128_le().await.unwrap();
let parent = reader.read_u128_le().await.unwrap();
let checksum_body = reader.read_u128_le().await.unwrap();
let checksum = reader.read_u128_le().await.unwrap();
IggyHeader::new(
payload_type,
last_offset_delta,
last_timestamp_delta,
release,
attributes,
base_offset,
batch_length,
origin_timestamp,
base_timestamp,
reserved_nonce,
parent,
checksum_body,
checksum,
)
}
fn write_header<const N: usize>(&self, writer: &mut HeaderWriter<N>) {
writer.write_all(&self.payload_type.to_le_bytes()).unwrap();
writer
.write_all(&self.last_offset_delta.to_le_bytes())
.unwrap();
writer
.write_all(&self.last_timestamp_delta.to_le_bytes())
.unwrap();
writer.write_all(&self.release.to_le_bytes()).unwrap();
writer.write_all(&self.attributes.to_le_bytes()).unwrap();
writer.write_all(&self.base_offset.to_le_bytes()).unwrap();
writer.write_all(&self.batch_length.to_le_bytes()).unwrap();
writer
.write_all(&self.origin_timestamp.to_le_bytes())
.unwrap();
writer
.write_all(&self.base_timestamp.to_le_bytes())
.unwrap();
writer
.write_all(&self.reserved_nonce.to_le_bytes())
.unwrap();
writer.write_all(&self.parent.to_le_bytes()).unwrap();
writer.write_all(&self.checksum_body.to_le_bytes()).unwrap();
writer.write_all(&self.checksum.to_le_bytes()).unwrap();
}
pub fn from_bytes(bytes: &[u8]) -> Self {
let mut offset = 0;
let payload_type = bytes[offset];
offset += 1;
let last_offset_delta = u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap());
offset += 4;
let last_timestamp_delta =
u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap());
offset += 4;
let release = u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap());
offset += 4;
let attributes = u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap());
offset += 4;
let base_offset = u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap());
offset += 8;
let batch_length = u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap());
offset += 8;
let origin_timestamp = u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap());
offset += 8;
let base_timestamp = u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap());
offset += 8;
let reserved_nonce = u128::from_le_bytes(bytes[offset..offset + 16].try_into().unwrap());
offset += 16;
let parent = u128::from_le_bytes(bytes[offset..offset + 16].try_into().unwrap());
offset += 16;
let checksum_body = u128::from_le_bytes(bytes[offset..offset + 16].try_into().unwrap());
offset += 16;
let checksum = u128::from_le_bytes(bytes[offset..offset + 16].try_into().unwrap());
Self {
payload_type,
last_offset_delta,
last_timestamp_delta,
release,
attributes,
base_offset,
batch_length,
origin_timestamp,
base_timestamp,
reserved_nonce,
parent,
checksum_body,
checksum,
}
}
pub fn as_bytes(&self) -> [u8; IGGY_BATCH_OVERHEAD as usize] {
let header = [0u8; IGGY_BATCH_OVERHEAD as usize];
let mut writer = HeaderWriter::new(header);
self.write_header(&mut writer);
writer.header()
}
}
//TODO create a trait for this that will expose two methods async and not async.
pub struct HeaderWriter<const N: usize> {
buffer: [u8; N],
position: usize,
}
impl<const N: usize> HeaderWriter<N> {
pub fn new(buffer: [u8; N]) -> Self {
Self {
buffer,
position: 0,
}
}
pub fn header(self) -> [u8; N] {
self.buffer
}
}
impl<const N: usize> From<[u8; N]> for HeaderWriter<N> {
fn from(buffer: [u8; N]) -> Self {
Self {
buffer,
position: 0,
}
}
}
impl<const N: usize> std::io::Write for HeaderWriter<N> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let available = N.saturating_sub(self.position);
let len = buf.len().min(available);
self.buffer[self.position..self.position + len].copy_from_slice(&buf[..len]);
self.position += len;
Ok(len)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
// TODO: THIS IS SHIIIIIIIIIT, fuck Bytes
#[serde_as]
#[derive(Debug, Default, PartialEq)]
pub struct IggyBatch {
pub header: IggyHeader,
//TODO: Once the dependency on `Bytes` is dropped, change it to Vec<u8>.
pub batch: Bytes,
}
#[serde_as]
#[derive(Debug, Default, PartialEq)]
pub struct IggyMutableBatch {
pub header: IggyHeader,
//TODO: Once the dependency on `Bytes` is dropped, change it to Vec<u8>.
pub batch: BytesMut,
}
impl IggyMutableBatch {
pub fn new(header: IggyHeader, batch: BytesMut) -> Self {
Self { header, batch }
}
pub fn update_header_and_messages_offsets_and_timestamps(
&mut self,
current_offset: u64,
header: &mut IggyHeader,
) -> u32 {
#[inline(always)]
fn write_value_at<const N: usize>(slice: &mut [u8], value: [u8; N], position: usize) {
let slice = &mut slice[position..position + N];
let ptr = slice.as_mut_ptr();
// Use copy_nonoverlapping to avoid bounds checking.
unsafe {
std::ptr::copy_nonoverlapping(value.as_ptr(), ptr, N);
}
}
self.header.base_offset = header.base_offset;
self.header.base_timestamp = header.base_timestamp;
let base_timestamp = header.base_timestamp;
let base_offset = header.base_offset;
let relative_offset = (current_offset - base_offset) as u32;
let (messages_count, last_offset_delta, last_timestamp_delta) =
self.iter_mut()
.fold((0u32, 0u32, 0u32), |(count, _, _), msg| {
let offset_delta = relative_offset + count;
write_value_at(msg.view, offset_delta.to_le_bytes(), 0);
let timestamp_delta =
(IggyTimestamp::now().as_micros() - base_timestamp) as u32;
write_value_at(msg.view, timestamp_delta.to_le_bytes(), 4);
(count + 1, offset_delta, timestamp_delta)
});
let batch_size = self.get_size().as_bytes_u64();
self.header.last_offset_delta = last_offset_delta;
self.header.last_timestamp_delta = last_timestamp_delta;
self.header.batch_length = batch_size;
header.last_offset_delta = last_offset_delta;
header.last_timestamp_delta = last_timestamp_delta;
header.batch_length += batch_size;
messages_count
}
pub fn get_size(&self) -> IggyByteSize {
let batch_size = IggyByteSize::from(self.batch.len() as u64);
batch_size
}
pub fn get_size_w_header(&self) -> IggyByteSize {
let header_size = IggyByteSize::from(IGGY_BATCH_OVERHEAD);
let batch_size = IggyByteSize::from(self.batch.len() as u64);
header_size + batch_size
}
}
pub struct IggyMessageViewMut<'msg> {
view: &'msg mut [u8],
}
impl<'msg> IggyMessageViewMut<'msg> {
pub fn new(view: &'msg mut [u8]) -> Self {
Self { view }
}
}
pub struct IggyMessageMutIterator<'batch> {
batch: &'batch mut IggyMutableBatch,
position: usize,
}
impl<'batch> IggyMutableBatch {
pub fn iter_mut(&'batch mut self) -> IggyMessageMutIterator<'batch> {
IggyMessageMutIterator {
batch: self,
position: 0,
}
}
}
#[gat]
impl LendingIterator for IggyMessageMutIterator<'_> {
type Item<'next> = IggyMessageViewMut<'next>;
fn next(&mut self) -> Option<IggyMessageViewMut<'_>> {
if self.position >= self.batch.batch.len() {
return None;
}
let data = self.batch.batch.as_mut();
let start_post = self.position;
let mut pos = self.position;
// Offset delta + timestamp delta + id
pos += 4 + 4 + 16;
let (read, payload_length) = IggyVarInt::decode(&data[pos..]);
pos += read;
let (read, headers_length) = IggyVarInt::decode(&data[pos..]);
pos += read;
pos += payload_length as usize;
pos += headers_length as usize;
let view = &mut data[start_post..pos];
self.position = pos;
let msg = IggyMessageViewMut::new(view);
Some(msg)
}
}
impl IggyBatch {
pub fn new(header: IggyHeader, batch: Bytes) -> Self {
Self { header, batch }
}
pub async fn write_messages<W: AsyncWriteExt + Unpin>(&self, writer: &mut W) {
writer.write_all(&self.batch).await.unwrap();
}
pub fn get_size(&self) -> IggyByteSize {
let batch_size = IggyByteSize::from(self.batch.len() as u64);
batch_size
}
pub fn get_size_w_header(&self) -> IggyByteSize {
let header_size = IggyByteSize::from(IGGY_BATCH_OVERHEAD);
let batch_size = IggyByteSize::from(self.batch.len() as u64);
header_size + batch_size
}
}
#[derive(Debug, PartialEq)]
pub struct IggyMessageView<'msg> {
pub id: u128,
pub offset: u64,
pub timestamp: IggyTimestamp,
pub payload: &'msg [u8],
pub headers: &'msg [u8],
}
impl<'msg> IggyMessageView<'msg> {
pub fn new(
id: u128,
offset: u64,
timestamp: IggyTimestamp,
payload: &'msg [u8],
headers: &'msg [u8],
) -> Self {
Self {
id,
offset,
timestamp,
payload,
headers,
}
}
}
impl<'batch> IggyBatch {
pub fn iter(&'batch self) -> IggyMessageIterator<'batch> {
IggyMessageIterator {
batch: self,
position: 0,
}
}
pub fn messages_iter(&'batch self) -> IggyMessageViewIterator<'batch> {
IggyMessageViewIterator { inner: self.iter() }
}
}
pub struct IggyMessageViewIterator<'batch> {
inner: IggyMessageIterator<'batch>,
}
impl<'batch> Iterator for IggyMessageViewIterator<'batch> {
type Item = IggyMessageView<'batch>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(_, msg)| msg)
}
}
pub struct IggyMessageIterator<'batch> {
batch: &'batch IggyBatch,
position: usize,
}
impl<'msg> Iterator for IggyMessageIterator<'msg> {
type Item = (Range<usize>, IggyMessageView<'msg>);
fn next(&mut self) -> Option<Self::Item> {
if self.position >= self.batch.batch.len() {
return None;
}
let data = self.batch.batch.as_ref();
let start_pos = self.position;
let mut pos = self.position;
//TODO: replace all of those oks with an Faillable iterator I guess ?
let offset_delta = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?);
pos += 4;
let timestamp_delta = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?);
pos += 4;
let id = u128::from_le_bytes(data[pos..pos + 16].try_into().ok()?);
pos += 16;
let (read, payload_length) = IggyVarInt::decode(&data[pos..]);
pos += read;
let (read, headers_length) = IggyVarInt::decode(&data[pos..]);
pos += read;
let payload = &data[pos..pos + payload_length as usize];
pos += payload_length as usize;
let headers = &data[pos..pos + headers_length as usize];
pos += headers_length as usize;
self.position = pos;
let offset = self.batch.header.base_offset + offset_delta as u64;
let timestamp = self.batch.header.base_timestamp + timestamp_delta as u64;
let msg = IggyMessageView::new(id, offset, timestamp.into(), payload, headers);
let range = start_pos..pos;
Some((range, msg))
}
}