mod encoder;
mod start;
mod subscription;
pub use start::{IdPolicy, Start};
pub use encoder::RecordsEncoder;
pub use subscription::{
Target, decode_unsubscribe, decode_unsubscribed, encode_unsubscribe, encode_unsubscribed,
};
use crate::data::{
self, Authority, CodecError, Cursor, DataLimits, OwnedRecords, Record, Records, capacity,
};
use crate::{
ENVELOPE_BYTES, Envelope, EnvelopeLimits, GroupId, Opcode, Packet, PartitionId,
PartitionIncarnation, ProducerId, SubscriptionId, Topic,
};
use bytes::{Bytes, BytesMut};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Source {
Local {
producer: ProducerId,
partition: PartitionId,
},
Group {
authority: Authority,
partition: PartitionIncarnation,
owner_epoch: u64,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Subscription {
pub id: SubscriptionId,
pub generation: u128,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Subscribe {
pub subscription: Subscription,
pub target: Target,
pub start: Start,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecodedSubscribe {
pub subscription: Subscription,
pub target: Target,
pub start: Start,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Subscribed {
pub subscription: Subscription,
pub source: Source,
pub resolved_offset: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RecordHeader {
pub subscription: Subscription,
pub source: Source,
pub first_offset: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PublicationHeader {
pub source: Source,
pub first_offset: u64,
}
#[derive(Debug, Clone, Copy)]
pub struct Publication<'a> {
pub header: PublicationHeader,
pub payload_encoding: crate::append::PayloadEncoding,
pub records: Records<'a>,
}
#[derive(Debug, Clone, Copy)]
pub struct Delivery<'a> {
pub header: RecordHeader,
pub payload_encoding: crate::append::PayloadEncoding,
pub records: Records<'a>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Ack {
pub subscription: Subscription,
pub source: Source,
pub received: Option<u64>,
pub processed: Option<u64>,
}
pub fn route(packet: Packet<'_>, limits: EnvelopeLimits) -> Result<Source, CodecError> {
route_subscription(packet, limits).map(|(_, source)| source)
}
pub fn route_subscription(
packet: Packet<'_>,
limits: EnvelopeLimits,
) -> Result<(Subscription, Source), CodecError> {
let response = match packet.envelope.opcode {
Opcode::Subscribed | Opcode::Unsubscribed => true,
Opcode::Subscribe | Opcode::Records | Opcode::Unsubscribe => false,
Opcode::Ack => packet.envelope.response,
_ => return Err(CodecError::Command),
};
command(packet.envelope, packet.envelope.opcode, response)?;
packet
.envelope
.validate_frames(packet.metadata.len(), packet.payload.len(), limits)?;
if packet.envelope.opcode != Opcode::Records && !packet.payload.is_empty() {
return Err(CodecError::Length);
}
let mut cursor = Cursor(packet.metadata);
let subscription = Subscription::decode(&mut cursor)?;
if cursor.0.first() != Some(&1) {
return Err(CodecError::Profile);
}
Source::decode(&mut cursor).map(|source| (subscription, source))
}
pub fn route_publication(packet: Packet<'_>, limits: EnvelopeLimits) -> Result<Source, CodecError> {
command(packet.envelope, Opcode::RecordsPub, false)?;
packet
.envelope
.validate_frames(packet.metadata.len(), packet.payload.len(), limits)?;
let source = Source::decode(&mut Cursor(packet.metadata))?;
publication_topic(source)?;
Ok(source)
}
pub fn encode_subscribe(
e: Envelope,
v: &Subscribe,
out: &mut Vec<u8>,
limits: EnvelopeLimits,
) -> Result<[u8; ENVELOPE_BYTES], CodecError> {
v.subscription.validate()?;
v.target.validate()?;
v.start.validate()?;
let size = 32 + v.target.size() + v.start.size();
let header = prepare(e, Opcode::Subscribe, false, size, out, limits)?;
v.subscription.encode(out);
v.target.encode(out);
v.start.encode(out);
Ok(header)
}
pub fn decode_subscribe(
p: Packet<'_>,
limits: EnvelopeLimits,
) -> Result<DecodedSubscribe, CodecError> {
let mut c = control(p, Opcode::Subscribe, false, limits)?;
let subscription = Subscription::decode(&mut c)?;
let target = Target::decode(&mut c)?;
let start = Start::decode(&mut c)?;
end(c)?;
Ok(DecodedSubscribe {
subscription,
target,
start,
})
}
pub fn encode_subscribed(
e: Envelope,
v: Subscribed,
out: &mut Vec<u8>,
limits: EnvelopeLimits,
) -> Result<[u8; ENVELOPE_BYTES], CodecError> {
v.subscription.validate()?;
v.source.validate()?;
let header = prepare(
e,
Opcode::Subscribed,
true,
40 + v.source.size(),
out,
limits,
)?;
v.subscription.encode(out);
v.source.encode(out);
out.extend_from_slice(&v.resolved_offset.to_be_bytes());
Ok(header)
}
pub fn decode_subscribed(p: Packet<'_>, limits: EnvelopeLimits) -> Result<Subscribed, CodecError> {
let mut c = control(p, Opcode::Subscribed, true, limits)?;
let v = Subscribed {
subscription: Subscription::decode(&mut c)?,
source: Source::decode(&mut c)?,
resolved_offset: c.u64()?,
};
end(c)?;
Ok(v)
}
pub fn encode_records(
e: Envelope,
v: RecordHeader,
records: &[Record<'_>],
metadata: &mut Vec<u8>,
payload: &mut Vec<u8>,
limits: DataLimits,
) -> Result<[u8; ENVELOPE_BYTES], CodecError> {
encode_records_from(
e,
v,
records
.iter()
.map(|r| (r.message_id, r.encoding, r.parts.iter().copied())),
metadata,
payload,
limits,
)
}
pub fn encode_records_from<'a, I, P>(
e: Envelope,
v: RecordHeader,
records: I,
metadata: &mut Vec<u8>,
payload: &mut Vec<u8>,
limits: DataLimits,
) -> Result<[u8; ENVELOPE_BYTES], CodecError>
where
I: ExactSizeIterator<Item = (crate::MessageId, data::Encoding, P)> + Clone,
P: Iterator<Item = &'a [u8]>,
{
command(e, Opcode::Records, false)?;
v.subscription.validate()?;
v.source.validate()?;
let (descriptors, bytes) = data::record_sizes(records.clone(), v.first_offset, limits)?;
let size = data::add(45 + v.source.size(), descriptors)?;
let header = e.encode_header(size, bytes, limits.envelope)?;
capacity(metadata, size)?;
capacity(payload, bytes)?;
metadata.clear();
payload.clear();
v.subscription.encode(metadata);
v.source.encode(metadata);
metadata.extend_from_slice(&v.first_offset.to_be_bytes());
crate::append::encode_payload_encoding(metadata, crate::append::PayloadEncoding::Raw, bytes)?;
data::write_records(records, metadata, payload);
Ok(header)
}
pub fn decode_records(p: Packet<'_>, limits: DataLimits) -> Result<Delivery<'_>, CodecError> {
command(p.envelope, Opcode::Records, false)?;
p.envelope
.validate_frames(p.metadata.len(), p.payload.len(), limits.envelope)?;
let mut c = Cursor(p.metadata);
let header = RecordHeader {
subscription: Subscription::decode(&mut c)?,
source: Source::decode(&mut c)?,
first_offset: c.u64()?,
};
let payload_encoding = crate::append::PayloadEncoding::try_from(c.byte()?)?;
let decoded_bytes = c.u32()? as usize;
crate::append::validate_payload_encoding(
payload_encoding,
decoded_bytes,
p.payload.len(),
limits,
)?;
if payload_encoding != crate::append::PayloadEncoding::Raw {
return Err(CodecError::Profile);
}
let records = data::decode_records(c.0, p.payload, header.first_offset, limits)?;
Ok(Delivery {
header,
payload_encoding,
records,
})
}
#[derive(Debug, Clone)]
pub struct OwnedDelivery<H> {
pub header: H,
pub payload_encoding: crate::append::PayloadEncoding,
pub records: OwnedRecords,
}
fn owned_delivery<H>(
mut c: Cursor<'_>,
first_offset: u64,
header: H,
metadata: &Bytes,
payload: &Bytes,
limits: DataLimits,
buffer: &mut BytesMut,
) -> Result<OwnedDelivery<H>, CodecError> {
let (payload_encoding, payload) =
crate::append::decode_owned_payload(&mut c, payload, buffer, limits)?;
let records = data::decode_records(c.0, &payload, first_offset, limits)?;
Ok(OwnedDelivery {
header,
payload_encoding,
records: records.to_owned(metadata, &payload),
})
}
pub fn decode_owned_records(
p: Packet<'_>,
metadata: &Bytes,
payload: &Bytes,
limits: DataLimits,
buffer: &mut BytesMut,
) -> Result<OwnedDelivery<RecordHeader>, CodecError> {
command(p.envelope, Opcode::Records, false)?;
p.envelope
.validate_frames(p.metadata.len(), p.payload.len(), limits.envelope)?;
let mut c = Cursor(p.metadata);
let header = RecordHeader {
subscription: Subscription::decode(&mut c)?,
source: Source::decode(&mut c)?,
first_offset: c.u64()?,
};
owned_delivery(
c,
header.first_offset,
header,
metadata,
payload,
limits,
buffer,
)
}
pub fn decode_publication(
p: Packet<'_>,
limits: DataLimits,
) -> Result<Publication<'_>, CodecError> {
command(p.envelope, Opcode::RecordsPub, false)?;
p.envelope
.validate_frames(p.metadata.len(), p.payload.len(), limits.envelope)?;
let mut c = Cursor(p.metadata);
let header = PublicationHeader {
source: Source::decode(&mut c)?,
first_offset: c.u64()?,
};
publication_topic(header.source)?;
let payload_encoding = crate::append::PayloadEncoding::try_from(c.byte()?)?;
let decoded_bytes = c.u32()? as usize;
crate::append::validate_payload_encoding(
payload_encoding,
decoded_bytes,
p.payload.len(),
limits,
)?;
if payload_encoding != crate::append::PayloadEncoding::Raw {
return Err(CodecError::Profile);
}
let records = data::decode_records(c.0, p.payload, header.first_offset, limits)?;
Ok(Publication {
header,
payload_encoding,
records,
})
}
pub fn decode_owned_publication(
p: Packet<'_>,
metadata: &Bytes,
payload: &Bytes,
limits: DataLimits,
buffer: &mut BytesMut,
) -> Result<OwnedDelivery<PublicationHeader>, CodecError> {
command(p.envelope, Opcode::RecordsPub, false)?;
p.envelope
.validate_frames(p.metadata.len(), p.payload.len(), limits.envelope)?;
let mut c = Cursor(p.metadata);
let header = PublicationHeader {
source: Source::decode(&mut c)?,
first_offset: c.u64()?,
};
publication_topic(header.source)?;
owned_delivery(
c,
header.first_offset,
header,
metadata,
payload,
limits,
buffer,
)
}
pub fn publication_topic(source: Source) -> Result<[u8; 32], CodecError> {
let Source::Group {
authority,
partition,
..
} = source
else {
return Err(CodecError::Profile);
};
let mut topic = [0; 32];
topic[..16].copy_from_slice(authority.group_id.as_bytes());
topic[16..].copy_from_slice(partition.as_bytes());
Ok(topic)
}
pub fn encode_ack(
e: Envelope,
v: Ack,
out: &mut Vec<u8>,
limits: EnvelopeLimits,
) -> Result<[u8; ENVELOPE_BYTES], CodecError> {
v.validate()?;
let size = 34
+ v.source.size()
+ 8 * (usize::from(v.received.is_some()) + usize::from(v.processed.is_some()));
let header = prepare(e, Opcode::Ack, e.response, size, out, limits)?;
v.subscription.encode(out);
v.source.encode(out);
position(out, v.received);
position(out, v.processed);
Ok(header)
}
pub fn decode_ack(p: Packet<'_>, limits: EnvelopeLimits) -> Result<Ack, CodecError> {
let mut c = control(p, Opcode::Ack, p.envelope.response, limits)?;
let v = Ack {
subscription: Subscription::decode(&mut c)?,
source: Source::decode(&mut c)?,
received: read_position(&mut c)?,
processed: read_position(&mut c)?,
};
end(c)?;
v.validate()?;
Ok(v)
}
impl Ack {
fn validate(self) -> Result<(), CodecError> {
self.subscription.validate()?;
self.source.validate()?;
if self
.processed
.is_some_and(|p| self.received.is_none_or(|r| p > r))
{
return Err(CodecError::Position);
}
Ok(())
}
}
impl Subscription {
fn validate(self) -> Result<(), CodecError> {
nonzero(self.id.as_bytes())?;
nonzero(&self.generation.to_be_bytes())
}
fn encode(self, out: &mut Vec<u8>) {
out.extend_from_slice(self.id.as_bytes());
out.extend_from_slice(&self.generation.to_be_bytes());
}
fn decode(c: &mut Cursor<'_>) -> Result<Self, CodecError> {
let v = Self {
id: SubscriptionId::from_bytes(c.array()?),
generation: u128::from_be_bytes(c.array()?),
};
v.validate()?;
Ok(v)
}
}
impl Source {
fn size(self) -> usize {
match self {
Self::Local { .. } => 21,
Self::Group { .. } => 57,
}
}
fn validate(self) -> Result<(), CodecError> {
match self {
Self::Local { producer, .. } => nonzero(producer.as_bytes()),
Self::Group {
authority,
partition,
owner_epoch,
} => {
nonzero(authority.group_id.as_bytes())?;
nonzero(partition.as_bytes())?;
if authority.config_epoch == 0 || owner_epoch == 0 {
return Err(CodecError::Identity);
}
Ok(())
}
}
}
fn encode(self, out: &mut Vec<u8>) {
match self {
Self::Local {
producer,
partition,
} => {
out.push(0);
out.extend_from_slice(producer.as_bytes());
out.extend_from_slice(&partition.get().to_be_bytes());
}
Self::Group {
authority,
partition,
owner_epoch,
} => {
out.push(1);
out.extend_from_slice(authority.group_id.as_bytes());
out.extend_from_slice(&authority.config_epoch.to_be_bytes());
out.extend_from_slice(&authority.view.to_be_bytes());
out.extend_from_slice(partition.as_bytes());
out.extend_from_slice(&owner_epoch.to_be_bytes());
}
}
}
fn decode(c: &mut Cursor<'_>) -> Result<Self, CodecError> {
let v = match c.byte()? {
0 => Self::Local {
producer: ProducerId::from_bytes(c.array()?),
partition: PartitionId::new(c.u32()?),
},
1 => Self::Group {
authority: Authority {
group_id: GroupId::from_bytes(c.array()?),
config_epoch: c.u64()?,
view: c.u64()?,
},
partition: PartitionIncarnation::from_bytes(c.array()?),
owner_epoch: c.u64()?,
},
_ => return Err(CodecError::Profile),
};
v.validate()?;
Ok(v)
}
}
fn nonzero(id: &[u8; 16]) -> Result<(), CodecError> {
if id == &[0; 16] {
return Err(CodecError::Identity);
}
Ok(())
}
fn text(out: &mut Vec<u8>, v: &str) {
out.extend_from_slice(&(v.len() as u32).to_be_bytes());
out.extend_from_slice(v.as_bytes());
}
fn read_text<'a>(c: &mut Cursor<'a>) -> Result<&'a str, CodecError> {
let n = c.u32()? as usize;
if n == 0 || n > 255 {
return Err(CodecError::Profile);
}
std::str::from_utf8(c.take(n)?).map_err(|_| CodecError::Profile)
}
fn position(out: &mut Vec<u8>, v: Option<u64>) {
out.push(u8::from(v.is_some()));
if let Some(v) = v {
out.extend_from_slice(&v.to_be_bytes());
}
}
fn read_position(c: &mut Cursor<'_>) -> Result<Option<u64>, CodecError> {
match c.byte()? {
0 => Ok(None),
1 => Ok(Some(c.u64()?)),
_ => Err(CodecError::Profile),
}
}
fn command(e: Envelope, opcode: Opcode, response: bool) -> Result<(), CodecError> {
if e.opcode != opcode
|| e.response != response
|| (!matches!(opcode, Opcode::Records | Opcode::RecordsPub | Opcode::Ack)
&& e.request_id.is_none())
|| (opcode == Opcode::RecordsPub && e.request_id.is_some())
|| (opcode == Opcode::Ack && e.response && e.request_id.is_none())
{
return Err(CodecError::Command);
}
Ok(())
}
fn prepare(
e: Envelope,
opcode: Opcode,
response: bool,
size: usize,
out: &mut Vec<u8>,
limits: EnvelopeLimits,
) -> Result<[u8; ENVELOPE_BYTES], CodecError> {
command(e, opcode, response)?;
let header = e.encode_header(size, 0, limits)?;
capacity(out, size)?;
out.clear();
Ok(header)
}
fn control(
p: Packet<'_>,
opcode: Opcode,
response: bool,
limits: EnvelopeLimits,
) -> Result<Cursor<'_>, CodecError> {
command(p.envelope, opcode, response)?;
p.envelope
.validate_frames(p.metadata.len(), p.payload.len(), limits)?;
if !p.payload.is_empty() {
return Err(CodecError::Length);
}
Ok(Cursor(p.metadata))
}
fn end(c: Cursor<'_>) -> Result<(), CodecError> {
if c.0.is_empty() {
Ok(())
} else {
Err(CodecError::Length)
}
}