use crate::{
ConsumerGroupId, ConsumerMemberId, ENVELOPE_BYTES, Envelope, EnvelopeLimits, Offset, Opcode,
Packet, PartitionIncarnation,
append::{Authority, Policy},
data::CodecError,
layout::Reader,
wire::{check_exact, command, decode_authority, encode_authority, reserve},
};
pub const REQUEST_BYTES: usize = 99;
pub const CONFIRMED_BYTES: usize = 114;
const SOURCE_TAG: u8 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HistoryExpired {
pub cursor: Offset,
pub retained_from: Offset,
}
impl HistoryExpired {
pub fn encode(self) -> Result<[u8; 16], CodecError> {
self.validate()?;
let mut bytes = [0; 16];
bytes[..8].copy_from_slice(&self.cursor.get().to_be_bytes());
bytes[8..].copy_from_slice(&self.retained_from.get().to_be_bytes());
Ok(bytes)
}
pub fn decode(bytes: &[u8]) -> Result<Self, CodecError> {
if bytes.len() != 16 {
return Err(CodecError::Length);
}
let mut cursor = Reader::new(bytes);
let expired = Self {
cursor: Offset::new(cursor.u64()?),
retained_from: Offset::new(cursor.u64()?),
};
expired.validate()?;
Ok(expired)
}
fn validate(self) -> Result<(), CodecError> {
if self.cursor >= self.retained_from {
return Err(CodecError::Profile);
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Assignment {
pub group: ConsumerGroupId,
pub partition: PartitionIncarnation,
pub member: ConsumerMemberId,
pub epoch: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Request {
pub authority: Authority,
pub assignment: Assignment,
pub policy: Policy,
pub cursor: Option<Offset>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Confirmed {
pub authority: Authority,
pub assignment: Assignment,
pub policy: Policy,
pub cursor: Offset,
pub retained_from: Offset,
pub end: Offset,
}
pub fn encode_request(
envelope: Envelope,
request: Request,
metadata: &mut Vec<u8>,
limits: EnvelopeLimits,
) -> Result<[u8; ENVELOPE_BYTES], CodecError> {
command(envelope, Opcode::ProgressCommit, false)?;
validate(request.authority, request.assignment)?;
let header = envelope.encode_header(REQUEST_BYTES, 0, limits)?;
reserve(metadata, REQUEST_BYTES)?;
encode_common(
metadata,
request.authority,
request.assignment,
request.policy,
);
metadata.push(u8::from(request.cursor.is_some()));
metadata.extend_from_slice(&request.cursor.unwrap_or(Offset::ZERO).get().to_be_bytes());
Ok(header)
}
pub fn decode_request(packet: Packet<'_>, limits: EnvelopeLimits) -> Result<Request, CodecError> {
check_exact(packet, Opcode::ProgressCommit, false, REQUEST_BYTES, limits)?;
let mut cursor = Reader::new(packet.metadata);
let (authority, assignment, policy) = decode_common(&mut cursor)?;
let tag = cursor.u8()?;
let value = cursor.u64()?;
let offset = match (tag, value) {
(0, 0) => None,
(1, value) => Some(Offset::new(value)),
_ => return Err(CodecError::Profile),
};
Ok(Request {
authority,
assignment,
policy,
cursor: offset,
})
}
pub fn encode_confirmed(
envelope: Envelope,
confirmed: Confirmed,
metadata: &mut Vec<u8>,
limits: EnvelopeLimits,
) -> Result<[u8; ENVELOPE_BYTES], CodecError> {
command(envelope, Opcode::ProgressCommitted, true)?;
validate_confirmed(confirmed)?;
let header = envelope.encode_header(CONFIRMED_BYTES, 0, limits)?;
reserve(metadata, CONFIRMED_BYTES)?;
encode_common(
metadata,
confirmed.authority,
confirmed.assignment,
confirmed.policy,
);
for offset in [confirmed.cursor, confirmed.retained_from, confirmed.end] {
metadata.extend_from_slice(&offset.get().to_be_bytes());
}
Ok(header)
}
pub fn decode_confirmed(
packet: Packet<'_>,
limits: EnvelopeLimits,
) -> Result<Confirmed, CodecError> {
check_exact(
packet,
Opcode::ProgressCommitted,
true,
CONFIRMED_BYTES,
limits,
)?;
let mut cursor = Reader::new(packet.metadata);
let (authority, assignment, policy) = decode_common(&mut cursor)?;
let confirmed = Confirmed {
authority,
assignment,
policy,
cursor: Offset::new(cursor.u64()?),
retained_from: Offset::new(cursor.u64()?),
end: Offset::new(cursor.u64()?),
};
validate_confirmed(confirmed)?;
Ok(confirmed)
}
fn encode_common(
metadata: &mut Vec<u8>,
authority: Authority,
assignment: Assignment,
policy: Policy,
) {
metadata.push(SOURCE_TAG);
encode_authority(metadata, authority);
metadata.extend_from_slice(assignment.partition.as_bytes());
metadata.extend_from_slice(assignment.group.as_bytes());
metadata.extend_from_slice(assignment.member.as_bytes());
metadata.extend_from_slice(&assignment.epoch.to_be_bytes());
metadata.push(policy as u8);
}
fn decode_common(cursor: &mut Reader<'_>) -> Result<(Authority, Assignment, Policy), CodecError> {
if cursor.u8()? != SOURCE_TAG {
return Err(CodecError::Profile);
}
let authority = decode_authority(cursor)?;
let assignment = Assignment {
partition: PartitionIncarnation::from_bytes(cursor.array()?),
group: ConsumerGroupId::from_bytes(cursor.array()?),
member: ConsumerMemberId::from_bytes(cursor.array()?),
epoch: cursor.u64()?,
};
let policy = Policy::try_from(cursor.u8()?)?;
validate(authority, assignment)?;
Ok((authority, assignment, policy))
}
fn validate(authority: Authority, assignment: Assignment) -> Result<(), CodecError> {
if authority.group_id.as_bytes() == &[0; 16]
|| authority.config_epoch == 0
|| assignment.group.as_bytes() == &[0; 16]
|| assignment.partition.as_bytes() == &[0; 16]
|| assignment.member.as_bytes() == &[0; 16]
|| assignment.epoch == 0
{
return Err(CodecError::Identity);
}
Ok(())
}
fn validate_confirmed(confirmed: Confirmed) -> Result<(), CodecError> {
validate(confirmed.authority, confirmed.assignment)?;
if confirmed.retained_from > confirmed.end || confirmed.cursor > confirmed.end {
return Err(CodecError::Profile);
}
Ok(())
}
#[cfg(test)]
mod tests;