use ozzy_proto::{Envelope, EnvelopeLimits, Opcode};
use ozzy_proto::{LinkSessionId, NodeId, RequestId};
use super::prepare::{DecodedBatch, decode_batch, metadata_length, validate_batch, write_batch};
use super::{
COMMON_BYTES, ControlEncoding, Operation, Operations, PeerBinding, PrepareEncoding, Reader,
WireError, WireLimits, Writer, validate_prefix, validate_scope,
};
use crate::{JournalGeneration, LogSource, Prefix, Scope};
const SOURCE_BYTES: usize = 16 + 16 + 40;
pub(super) const FETCH_BYTES: usize = COMMON_BYTES + SOURCE_BYTES + 40 + 4 + 4;
pub(super) const OPS_FIXED_BYTES: usize = COMMON_BYTES + SOURCE_BYTES + 40 + 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FetchOps {
pub scope: Scope,
pub request_id: RequestId,
pub source: LogSource,
pub predecessor: Prefix,
pub max_operations: u32,
pub max_body_bytes: u32,
}
impl FetchOps {
fn validate(self) -> Result<(), WireError> {
validate_scope(self.scope)?;
validate_source(self.source)?;
validate_prefix(self.predecessor)?;
if self.predecessor.op >= self.source.accepted.op {
return Err(WireError::History);
}
if self.max_operations == 0 || self.max_body_bytes == 0 {
return Err(WireError::Limit);
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OpsBatch<'a> {
request_id: RequestId,
source: LogSource,
batch: DecodedBatch<'a>,
}
impl<'a> OpsBatch<'a> {
pub const fn scope(self) -> Scope {
self.batch.scope
}
pub const fn request_id(self) -> RequestId {
self.request_id
}
pub const fn source(self) -> LogSource {
self.source
}
pub const fn predecessor(self) -> Prefix {
self.batch.predecessor
}
pub const fn end(self) -> Prefix {
self.batch.accepted
}
pub fn operations(self) -> Operations<'a> {
self.batch.operations()
}
pub fn validate_response(self, request: FetchOps) -> Result<(), WireError> {
request.validate()?;
if self.request_id != request.request_id
|| self.scope() != request.scope
|| self.source != request.source
|| self.predecessor() != request.predecessor
|| self.operations().len() > request.max_operations as usize
|| self.batch.payload.len() > request.max_body_bytes as usize
{
return Err(WireError::Transfer);
}
Ok(())
}
}
pub fn encode_ops(
sender: NodeId,
session: LinkSessionId,
request: FetchOps,
operations: &[Operation<'_>],
metadata: &mut [u8],
payload: &mut [u8],
limits: WireLimits,
) -> Result<PrepareEncoding, WireError> {
request.validate()?;
if sender != request.source.voter {
return Err(WireError::Peer);
}
let limits = WireLimits {
max_operations: limits.max_operations.min(request.max_operations as usize),
envelope: EnvelopeLimits {
max_metadata_bytes: limits.envelope.max_metadata_bytes,
max_payload_bytes: limits
.envelope
.max_payload_bytes
.min(request.max_body_bytes as usize),
},
};
let metadata_bytes = metadata_length(operations.len(), OPS_FIXED_BYTES, limits)?;
let payload_bytes =
validate_batch(request.scope, request.predecessor, None, operations, limits)?;
validate_tail(
request.source,
operations.last().expect("nonempty validated").prefix(),
)?;
let header = Envelope {
opcode: Opcode::Ops,
response: true,
request_id: Some(request.request_id),
sender,
session: Some(session),
}
.encode_header(metadata_bytes, payload_bytes, limits.envelope)?;
if metadata.len() < metadata_bytes || payload.len() < payload_bytes {
return Err(WireError::Capacity);
}
let mut writer = Writer::new(&mut metadata[..metadata_bytes]);
writer.scope(request.scope, sender);
write_source(&mut writer, request.source);
writer.prefix(request.predecessor);
write_batch(&mut writer, Some(payload), operations);
Ok(PrepareEncoding {
header,
metadata_bytes,
payload_bytes,
})
}
pub(super) fn decode_ops<'a>(
scope: Scope,
envelope: Envelope,
mut reader: Reader<'a>,
payload: &'a [u8],
limits: WireLimits,
) -> Result<OpsBatch<'a>, WireError> {
let request_id = envelope.request_id.ok_or(WireError::Correlation)?;
if !envelope.response {
return Err(WireError::Correlation);
}
let source = read_source(&mut reader)?;
if source.voter != envelope.sender {
return Err(WireError::Peer);
}
let predecessor = reader.prefix()?;
let batch = decode_batch(scope, predecessor, None, reader, payload, limits)?;
validate_tail(source, batch.accepted)?;
Ok(OpsBatch {
request_id,
source,
batch,
})
}
pub(super) fn validate_routing(
request: FetchOps,
envelope: Envelope,
mut reader: Reader<'_>,
payload_bytes: usize,
) -> Result<(), WireError> {
request.validate()?;
if !envelope.response || envelope.request_id != Some(request.request_id) {
return Err(WireError::Correlation);
}
if envelope.sender != request.source.voter
|| read_source(&mut reader)? != request.source
|| reader.prefix()? != request.predecessor
{
return Err(WireError::Transfer);
}
let operations = reader.u32()?;
if operations == 0
|| operations > request.max_operations
|| payload_bytes > request.max_body_bytes as usize
{
return Err(WireError::Limit);
}
Ok(())
}
fn validate_tail(source: LogSource, end: Prefix) -> Result<(), WireError> {
if end.op > source.accepted.op || (end.op == source.accepted.op && end != source.accepted) {
return Err(WireError::History);
}
Ok(())
}
pub fn encode_fetch(
sender: NodeId,
session: LinkSessionId,
request: FetchOps,
metadata: &mut [u8],
limits: WireLimits,
) -> Result<ControlEncoding, WireError> {
request.validate()?;
let header = Envelope {
opcode: Opcode::FetchOps,
response: false,
request_id: Some(request.request_id),
sender,
session: Some(session),
}
.encode_header(FETCH_BYTES, 0, limits.envelope)?;
if metadata.len() < FETCH_BYTES {
return Err(WireError::Capacity);
}
let mut writer = Writer::new(&mut metadata[..FETCH_BYTES]);
writer.scope(request.scope, sender);
write_source(&mut writer, request.source);
writer.prefix(request.predecessor);
writer.bytes(&request.max_operations.to_be_bytes());
writer.bytes(&request.max_body_bytes.to_be_bytes());
Ok(ControlEncoding {
header,
metadata_bytes: FETCH_BYTES,
})
}
pub(super) fn decode_fetch(
scope: Scope,
envelope: Envelope,
mut reader: Reader<'_>,
payload: &[u8],
binding: PeerBinding,
) -> Result<FetchOps, WireError> {
let request_id = envelope.request_id.ok_or(WireError::Correlation)?;
if envelope.response {
return Err(WireError::Correlation);
}
if !payload.is_empty() {
return Err(WireError::Payload);
}
let source = read_source(&mut reader)?;
binding.configuration.voter_index(source.voter)?;
let request = FetchOps {
scope,
request_id,
source,
predecessor: reader.prefix()?,
max_operations: reader.u32()?,
max_body_bytes: reader.u32()?,
};
reader.finish()?;
request.validate()?;
Ok(request)
}
pub(super) fn validate_source(source: LogSource) -> Result<(), WireError> {
validate_prefix(source.accepted)?;
if source.generation.0 == 0 || source.voter.as_bytes() == &[0; 16] {
return Err(WireError::History);
}
Ok(())
}
pub(super) fn write_source(writer: &mut Writer<'_>, source: LogSource) {
writer.bytes(source.voter.as_bytes());
writer.bytes(&source.generation.0.to_be_bytes());
writer.prefix(source.accepted);
}
pub(super) fn read_source(reader: &mut Reader<'_>) -> Result<LogSource, WireError> {
let source = LogSource {
voter: NodeId::from_bytes(reader.bytes()?),
generation: JournalGeneration(u128::from_be_bytes(reader.bytes()?)),
accepted: reader.prefix()?,
};
validate_source(source)?;
Ok(source)
}