use crate::{
ENVELOPE_BYTES, Envelope, EnvelopeLimits, GroupId, Opcode, OperationId, Packet,
PartitionIncarnation, ProducerId,
append::{Authority, Policy},
data::{CodecError, Cursor},
};
const GROUP_TAG: u8 = 1;
const OPEN_BYTES: usize = 90;
const OPENED_BYTES: usize = 90;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum Mode {
Resume = 0,
Fence = 1,
Create = 2,
}
impl TryFrom<u8> for Mode {
type Error = CodecError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Resume),
1 => Ok(Self::Fence),
2 => Ok(Self::Create),
_ => Err(CodecError::Profile),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Open {
pub authority: Authority,
pub partition: PartitionIncarnation,
pub producer: ProducerId,
pub mode: Mode,
pub expected_epoch: Option<u64>,
pub operation: OperationId,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Opened {
pub authority: Authority,
pub partition: PartitionIncarnation,
pub producer: ProducerId,
pub epoch: u64,
pub next_sequence: u64,
pub retry_floor: u64,
pub policy: Policy,
}
pub fn encode_open(
envelope: Envelope,
open: Open,
metadata: &mut Vec<u8>,
limits: EnvelopeLimits,
) -> Result<[u8; ENVELOPE_BYTES], CodecError> {
command(envelope, Opcode::OpenProducer)?;
validate_open(open)?;
let header = envelope.encode_header(OPEN_BYTES, 0, limits)?;
reserve(metadata, OPEN_BYTES)?;
common(metadata, open.authority, open.partition, open.producer);
metadata.push(open.mode as u8);
metadata.extend_from_slice(&open.expected_epoch.unwrap_or(0).to_be_bytes());
metadata.extend_from_slice(open.operation.as_bytes());
Ok(header)
}
pub fn decode_open(packet: Packet<'_>, limits: EnvelopeLimits) -> Result<Open, CodecError> {
check(packet, Opcode::OpenProducer, OPEN_BYTES, limits)?;
let mut cursor = Cursor(packet.metadata);
let (authority, partition, producer) = read_common(&mut cursor)?;
let mode = Mode::try_from(cursor.byte()?)?;
let old = cursor.u64()?;
let open = Open {
authority,
partition,
producer,
mode,
expected_epoch: (old != 0).then_some(old),
operation: OperationId::from_bytes(cursor.array()?),
};
validate_open(open)?;
Ok(open)
}
pub fn encode_opened(
envelope: Envelope,
opened: Opened,
metadata: &mut Vec<u8>,
limits: EnvelopeLimits,
) -> Result<[u8; ENVELOPE_BYTES], CodecError> {
command(envelope, Opcode::ProducerOpened)?;
validate_opened(opened)?;
let header = envelope.encode_header(OPENED_BYTES, 0, limits)?;
reserve(metadata, OPENED_BYTES)?;
common(
metadata,
opened.authority,
opened.partition,
opened.producer,
);
metadata.extend_from_slice(&opened.epoch.to_be_bytes());
metadata.extend_from_slice(&opened.next_sequence.to_be_bytes());
metadata.extend_from_slice(&opened.retry_floor.to_be_bytes());
metadata.push(opened.policy as u8);
Ok(header)
}
pub fn decode_opened(packet: Packet<'_>, limits: EnvelopeLimits) -> Result<Opened, CodecError> {
check(packet, Opcode::ProducerOpened, OPENED_BYTES, limits)?;
let mut cursor = Cursor(packet.metadata);
let (authority, partition, producer) = read_common(&mut cursor)?;
let opened = Opened {
authority,
partition,
producer,
epoch: cursor.u64()?,
next_sequence: cursor.u64()?,
retry_floor: cursor.u64()?,
policy: Policy::try_from(cursor.byte()?)?,
};
validate_opened(opened)?;
Ok(opened)
}
fn command(envelope: Envelope, opcode: Opcode) -> Result<(), CodecError> {
if envelope.opcode != opcode
|| envelope.response != (opcode == Opcode::ProducerOpened)
|| envelope.request_id.is_none()
|| envelope.session.is_none()
{
return Err(CodecError::Command);
}
Ok(())
}
fn check(
packet: Packet<'_>,
opcode: Opcode,
length: usize,
limits: EnvelopeLimits,
) -> Result<(), CodecError> {
command(packet.envelope, opcode)?;
packet
.envelope
.validate_frames(packet.metadata.len(), packet.payload.len(), limits)?;
if packet.metadata.len() != length || !packet.payload.is_empty() {
return Err(CodecError::Length);
}
Ok(())
}
fn reserve(metadata: &mut Vec<u8>, length: usize) -> Result<(), CodecError> {
if metadata.capacity() < length {
return Err(CodecError::Capacity);
}
metadata.clear();
Ok(())
}
fn common(
metadata: &mut Vec<u8>,
authority: Authority,
partition: PartitionIncarnation,
producer: ProducerId,
) {
metadata.push(GROUP_TAG);
metadata.extend_from_slice(authority.group_id.as_bytes());
metadata.extend_from_slice(&authority.config_epoch.to_be_bytes());
metadata.extend_from_slice(&authority.view.to_be_bytes());
metadata.extend_from_slice(partition.as_bytes());
metadata.extend_from_slice(producer.as_bytes());
}
fn read_common(
cursor: &mut Cursor<'_>,
) -> Result<(Authority, PartitionIncarnation, ProducerId), CodecError> {
if cursor.byte()? != GROUP_TAG {
return Err(CodecError::Profile);
}
let authority = Authority {
group_id: GroupId::from_bytes(cursor.array()?),
config_epoch: cursor.u64()?,
view: cursor.u64()?,
};
let partition = PartitionIncarnation::from_bytes(cursor.array()?);
let producer = ProducerId::from_bytes(cursor.array()?);
validate_common(authority, partition, producer)?;
Ok((authority, partition, producer))
}
fn validate_common(
authority: Authority,
partition: PartitionIncarnation,
producer: ProducerId,
) -> Result<(), CodecError> {
if authority.group_id.as_bytes() == &[0; 16]
|| authority.config_epoch == 0
|| partition.as_bytes() == &[0; 16]
|| producer.as_bytes() == &[0; 16]
{
return Err(CodecError::Identity);
}
Ok(())
}
fn validate_open(open: Open) -> Result<(), CodecError> {
validate_common(open.authority, open.partition, open.producer)?;
if open.operation.as_bytes() == &[0; 16]
|| open.expected_epoch == Some(0)
|| (open.mode == Mode::Fence && open.expected_epoch.is_none())
|| (open.mode == Mode::Create && open.expected_epoch.is_some())
{
return Err(CodecError::Identity);
}
Ok(())
}
fn validate_opened(opened: Opened) -> Result<(), CodecError> {
validate_common(opened.authority, opened.partition, opened.producer)?;
if opened.epoch == 0
|| opened.retry_floor > opened.next_sequence
|| !matches!(
opened.policy,
Policy::LocalDurable | Policy::QuorumDurable | Policy::QuorumReplicatedPersisting
)
{
return Err(CodecError::Profile);
}
Ok(())
}
#[cfg(test)]
mod tests;