use super::{Command2, ConsensusError, Operation};
use bytemuck::{CheckedBitPattern, NoUninit};
use std::mem::offset_of;
pub const HEADER_SIZE: usize = 256;
pub const RESERVED_COMMAND_LEN: usize = 128;
pub const SIZE_FIELD_OFFSET: usize = 48;
#[inline]
#[must_use]
pub fn read_size_field(header: &[u8]) -> Option<u32> {
header
.get(SIZE_FIELD_OFFSET..SIZE_FIELD_OFFSET + 4)
.and_then(|s| s.try_into().ok())
.map(u32::from_le_bytes)
}
pub trait ConsensusHeader: Sized + CheckedBitPattern + NoUninit {
const COMMAND: Command2;
#[must_use]
fn accepts(command: Command2) -> bool {
command == Self::COMMAND
}
fn validate(&self) -> Result<(), ConsensusError>;
fn operation(&self) -> Operation;
fn command(&self) -> Command2;
fn size(&self) -> u32;
}
#[repr(C)]
#[derive(Debug, Clone, Copy, CheckedBitPattern, NoUninit)]
pub struct GenericHeader {
pub checksum: u128,
pub checksum_body: u128,
pub cluster: u128,
pub size: u32,
pub view: u32,
pub release: u32,
pub command: Command2,
pub replica: u8,
pub reserved_frame: [u8; 66],
pub reserved_command: [u8; RESERVED_COMMAND_LEN],
}
const _: () = {
assert!(size_of::<GenericHeader>() == HEADER_SIZE);
assert!(
offset_of!(GenericHeader, size) == SIZE_FIELD_OFFSET,
"GenericHeader.size offset drifted; transports decode wrong bytes",
);
assert!(
offset_of!(GenericHeader, reserved_command)
== offset_of!(GenericHeader, reserved_frame) + size_of::<[u8; 66]>()
);
assert!(
offset_of!(GenericHeader, reserved_command) + size_of::<[u8; RESERVED_COMMAND_LEN]>()
== HEADER_SIZE
);
};
impl ConsensusHeader for GenericHeader {
const COMMAND: Command2 = Command2::Reserved;
fn operation(&self) -> Operation {
Operation::Reserved
}
fn command(&self) -> Command2 {
self.command
}
fn validate(&self) -> Result<(), ConsensusError> {
Ok(())
}
fn size(&self) -> u32 {
self.size
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, CheckedBitPattern, NoUninit)]
pub struct RequestHeader {
pub checksum: u128,
pub checksum_body: u128,
pub cluster: u128,
pub size: u32,
pub view: u32,
pub release: u32,
pub command: Command2,
pub replica: u8,
pub reserved_frame: [u8; 66],
pub client: u128,
pub request_checksum: u128,
pub timestamp: u64,
pub request: u64,
pub operation: Operation,
pub operation_padding: [u8; 7],
pub namespace: u64,
pub session: u64,
pub user_id: u32,
pub reserved: [u8; 52],
}
const _: () = {
assert!(size_of::<RequestHeader>() == HEADER_SIZE);
assert!(
offset_of!(RequestHeader, client)
== offset_of!(RequestHeader, reserved_frame) + size_of::<[u8; 66]>()
);
assert!(
offset_of!(RequestHeader, user_id) == offset_of!(RequestHeader, session) + size_of::<u64>()
);
assert!(offset_of!(RequestHeader, reserved) + size_of::<[u8; 52]>() == HEADER_SIZE);
};
impl Default for RequestHeader {
fn default() -> Self {
Self {
checksum: 0,
checksum_body: 0,
cluster: 0,
size: 0,
view: 0,
release: 0,
command: Command2::Reserved,
replica: 0,
reserved_frame: [0; 66],
client: 0,
request_checksum: 0,
timestamp: 0,
request: 0,
operation: Operation::Reserved,
operation_padding: [0; 7],
namespace: 0,
session: 0,
user_id: 0,
reserved: [0; 52],
}
}
}
impl ConsensusHeader for RequestHeader {
const COMMAND: Command2 = Command2::Request;
fn operation(&self) -> Operation {
self.operation
}
fn command(&self) -> Command2 {
self.command
}
fn size(&self) -> u32 {
self.size
}
fn validate(&self) -> Result<(), ConsensusError> {
if self.command != Command2::Request {
return Err(ConsensusError::InvalidCommand {
expected: Command2::Request,
found: self.command,
});
}
if self.client == 0 {
return Err(ConsensusError::InvalidField(
"request: client must be != 0".to_string(),
));
}
if self.operation == Operation::Reserved {
return Err(ConsensusError::InvalidField(
"operation must not be Reserved".to_string(),
));
}
if self.operation == Operation::Register {
if self.session != 0 {
return Err(ConsensusError::InvalidField(
"register: session must be 0".to_string(),
));
}
if self.request != 0 {
return Err(ConsensusError::InvalidField(
"register: request must be 0".to_string(),
));
}
} else if self.operation != Operation::NonReplicated {
if self.session == 0 {
return Err(ConsensusError::InvalidField(
"non-register: session must be > 0".to_string(),
));
}
if self.request == 0 {
return Err(ConsensusError::InvalidField(
"non-register: request must be > 0".to_string(),
));
}
}
Ok(())
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, CheckedBitPattern, NoUninit)]
pub struct ReplyHeader {
pub checksum: u128,
pub checksum_body: u128,
pub cluster: u128,
pub size: u32,
pub view: u32,
pub release: u32,
pub command: Command2,
pub replica: u8,
pub reserved_frame: [u8; 66],
pub request_checksum: u128,
pub context: u128,
pub client: u128,
pub op: u64,
pub commit: u64,
pub timestamp: u64,
pub request: u64,
pub operation: Operation,
pub operation_padding: [u8; 7],
pub namespace: u64,
pub status: u32,
pub reserved: [u8; 28],
}
const _: () = {
assert!(size_of::<ReplyHeader>() == HEADER_SIZE);
assert!(
offset_of!(ReplyHeader, request_checksum)
== offset_of!(ReplyHeader, reserved_frame) + size_of::<[u8; 66]>()
);
assert!(
offset_of!(ReplyHeader, status) == offset_of!(ReplyHeader, namespace) + size_of::<u64>()
);
assert!(offset_of!(ReplyHeader, reserved) + size_of::<[u8; 28]>() == HEADER_SIZE);
};
impl Default for ReplyHeader {
fn default() -> Self {
Self {
checksum: 0,
checksum_body: 0,
cluster: 0,
size: 0,
view: 0,
release: 0,
command: Command2::Reserved,
replica: 0,
reserved_frame: [0; 66],
request_checksum: 0,
context: 0,
client: 0,
op: 0,
commit: 0,
timestamp: 0,
request: 0,
operation: Operation::Reserved,
operation_padding: [0; 7],
namespace: 0,
status: 0,
reserved: [0; 28],
}
}
}
impl ConsensusHeader for ReplyHeader {
const COMMAND: Command2 = Command2::Reply;
fn operation(&self) -> Operation {
self.operation
}
fn command(&self) -> Command2 {
self.command
}
fn size(&self) -> u32 {
self.size
}
fn validate(&self) -> Result<(), ConsensusError> {
if self.command != Command2::Reply {
return Err(ConsensusError::ReplyInvalidCommand2);
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, NoUninit, CheckedBitPattern)]
#[repr(u8)]
pub enum EvictionReason {
Reserved = 0,
NoSession = 1,
ClientReleaseTooLow = 2,
ClientReleaseTooHigh = 3,
InvalidRequestOperation = 4,
InvalidRequestBody = 5,
InvalidRequestBodySize = 6,
SessionTooLow = 7,
SessionReleaseMismatch = 8,
InvalidCredentials = 9,
InvalidToken = 10,
UserInactive = 11,
SessionError = 12,
StaleClient = 13,
IncompatibleProtocol = 14,
MalformedLogin = 15,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, CheckedBitPattern, NoUninit)]
pub struct EvictionHeader {
pub checksum: u128,
pub checksum_body: u128,
pub cluster: u128,
pub size: u32,
pub view: u32,
pub release: u32,
pub command: Command2,
pub replica: u8,
pub reserved_frame: [u8; 66],
pub client: u128,
pub server_protocol_version: u32,
pub server_protocol_version_min: u32,
pub reserved: [u8; 103],
pub reason: EvictionReason,
}
const _: () = {
assert!(size_of::<EvictionHeader>() == HEADER_SIZE);
assert!(
offset_of!(EvictionHeader, client)
== offset_of!(EvictionHeader, reserved_frame) + size_of::<[u8; 66]>()
);
assert!(offset_of!(EvictionHeader, server_protocol_version) == 144);
assert!(offset_of!(EvictionHeader, server_protocol_version_min) == 148);
assert!(offset_of!(EvictionHeader, reason) + size_of::<EvictionReason>() == HEADER_SIZE);
};
impl EvictionHeader {
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub const fn new(
cluster: u128,
view: u32,
replica: u8,
client: u128,
reason: EvictionReason,
) -> Self {
debug_assert!(
!matches!(
reason,
EvictionReason::ClientReleaseTooLow
| EvictionReason::ClientReleaseTooHigh
| EvictionReason::IncompatibleProtocol,
),
"EvictionHeader::new: ClientRelease*/IncompatibleProtocol need extra fields",
);
debug_assert!(
replica < 32,
"EvictionHeader::new: replica >= REPLICAS_MAX(32)",
);
Self {
checksum: 0,
checksum_body: 0,
cluster,
size: HEADER_SIZE as u32,
view,
release: 0,
command: Command2::Eviction,
replica,
reserved_frame: [0; 66],
client,
server_protocol_version: 0,
server_protocol_version_min: 0,
reserved: [0; 103],
reason,
}
}
#[must_use]
pub const fn incompatible_protocol(
cluster: u128,
view: u32,
replica: u8,
client: u128,
server_protocol_version: u32,
server_protocol_version_min: u32,
) -> Self {
let mut header = Self::new(cluster, view, replica, client, EvictionReason::NoSession);
header.reason = EvictionReason::IncompatibleProtocol;
header.server_protocol_version = server_protocol_version;
header.server_protocol_version_min = server_protocol_version_min;
header
}
}
impl ConsensusHeader for EvictionHeader {
const COMMAND: Command2 = Command2::Eviction;
fn operation(&self) -> Operation {
Operation::Reserved
}
fn command(&self) -> Command2 {
self.command
}
fn size(&self) -> u32 {
self.size
}
#[allow(clippy::cast_possible_truncation)]
fn validate(&self) -> Result<(), ConsensusError> {
if self.command != Command2::Eviction {
return Err(ConsensusError::InvalidCommand {
expected: Command2::Eviction,
found: self.command,
});
}
if self.size as usize != HEADER_SIZE {
return Err(ConsensusError::InvalidSize {
expected: HEADER_SIZE as u32,
found: self.size,
});
}
if self.client == 0 {
return Err(ConsensusError::InvalidField(
"eviction: client must be != 0".to_string(),
));
}
if self.reserved_frame.iter().any(|&b| b != 0) {
return Err(ConsensusError::InvalidField(
"eviction: reserved_frame bytes must be zero".to_string(),
));
}
if self.reserved.iter().any(|&b| b != 0) {
return Err(ConsensusError::InvalidField(
"eviction: reserved bytes must be zero".to_string(),
));
}
if self.reason == EvictionReason::Reserved {
return Err(ConsensusError::InvalidField(
"eviction: reason must not be Reserved".to_string(),
));
}
if self.reason == EvictionReason::IncompatibleProtocol {
if self.server_protocol_version_min == 0
|| self.server_protocol_version < self.server_protocol_version_min
{
return Err(ConsensusError::InvalidField(
"eviction: incompatible-protocol window must satisfy 1 <= min <= max"
.to_string(),
));
}
} else if self.server_protocol_version != 0 || self.server_protocol_version_min != 0 {
return Err(ConsensusError::InvalidField(
"eviction: protocol window bytes must be zero".to_string(),
));
}
Ok(())
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, CheckedBitPattern, NoUninit)]
pub struct PrepareHeader {
pub checksum: u128,
pub checksum_body: u128,
pub cluster: u128,
pub size: u32,
pub view: u32,
pub release: u32,
pub command: Command2,
pub replica: u8,
pub reserved_frame: [u8; 66],
pub client: u128,
pub parent: u128,
pub request_checksum: u128,
pub op: u64,
pub commit: u64,
pub timestamp: u64,
pub request: u64,
pub operation: Operation,
pub operation_padding: [u8; 7],
pub namespace: u64,
pub user_id: u32,
pub reserved: [u8; 28],
}
const _: () = {
assert!(size_of::<PrepareHeader>() == HEADER_SIZE);
assert!(
offset_of!(PrepareHeader, client)
== offset_of!(PrepareHeader, reserved_frame) + size_of::<[u8; 66]>()
);
assert!(
offset_of!(PrepareHeader, user_id)
== offset_of!(PrepareHeader, namespace) + size_of::<u64>()
);
assert!(offset_of!(PrepareHeader, reserved) + size_of::<[u8; 28]>() == HEADER_SIZE);
};
impl Default for PrepareHeader {
fn default() -> Self {
Self {
checksum: 0,
checksum_body: 0,
cluster: 0,
size: 0,
view: 0,
release: 0,
command: Command2::Reserved,
replica: 0,
reserved_frame: [0; 66],
client: 0,
parent: 0,
request_checksum: 0,
op: 0,
commit: 0,
timestamp: 0,
request: 0,
operation: Operation::Reserved,
operation_padding: [0; 7],
namespace: 0,
user_id: 0,
reserved: [0; 28],
}
}
}
impl ConsensusHeader for PrepareHeader {
const COMMAND: Command2 = Command2::Prepare;
fn operation(&self) -> Operation {
self.operation
}
fn command(&self) -> Command2 {
self.command
}
fn size(&self) -> u32 {
self.size
}
fn validate(&self) -> Result<(), ConsensusError> {
if self.command != Command2::Prepare {
return Err(ConsensusError::InvalidCommand {
expected: Command2::Prepare,
found: self.command,
});
}
Ok(())
}
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, CheckedBitPattern, NoUninit)]
pub struct RepairPrepareHeader(pub PrepareHeader);
impl ConsensusHeader for RepairPrepareHeader {
const COMMAND: Command2 = Command2::RepairPrepare;
fn operation(&self) -> Operation {
self.0.operation
}
fn command(&self) -> Command2 {
self.0.command
}
fn size(&self) -> u32 {
self.0.size
}
fn validate(&self) -> Result<(), ConsensusError> {
if self.0.command != Command2::RepairPrepare {
return Err(ConsensusError::InvalidCommand {
expected: Command2::RepairPrepare,
found: self.0.command,
});
}
Ok(())
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, CheckedBitPattern, NoUninit)]
pub struct PrepareOkHeader {
pub checksum: u128,
pub checksum_body: u128,
pub cluster: u128,
pub size: u32,
pub view: u32,
pub release: u32,
pub command: Command2,
pub replica: u8,
pub reserved_frame: [u8; 66],
pub parent: u128,
pub prepare_checksum: u128,
pub op: u64,
pub commit: u64,
pub timestamp: u64,
pub request: u64,
pub operation: Operation,
pub operation_padding: [u8; 7],
pub namespace: u64,
pub reserved: [u8; 48],
}
const _: () = {
assert!(size_of::<PrepareOkHeader>() == HEADER_SIZE);
assert!(
offset_of!(PrepareOkHeader, parent)
== offset_of!(PrepareOkHeader, reserved_frame) + size_of::<[u8; 66]>()
);
assert!(offset_of!(PrepareOkHeader, reserved) + size_of::<[u8; 48]>() == HEADER_SIZE);
};
impl Default for PrepareOkHeader {
fn default() -> Self {
Self {
checksum: 0,
checksum_body: 0,
cluster: 0,
size: 0,
view: 0,
release: 0,
command: Command2::Reserved,
replica: 0,
reserved_frame: [0; 66],
parent: 0,
prepare_checksum: 0,
op: 0,
commit: 0,
timestamp: 0,
request: 0,
operation: Operation::Reserved,
operation_padding: [0; 7],
namespace: 0,
reserved: [0; 48],
}
}
}
impl ConsensusHeader for PrepareOkHeader {
const COMMAND: Command2 = Command2::PrepareOk;
fn operation(&self) -> Operation {
self.operation
}
fn command(&self) -> Command2 {
self.command
}
fn size(&self) -> u32 {
self.size
}
fn validate(&self) -> Result<(), ConsensusError> {
if self.command != Command2::PrepareOk {
return Err(ConsensusError::InvalidCommand {
expected: Command2::PrepareOk,
found: self.command,
});
}
Ok(())
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, CheckedBitPattern, NoUninit)]
pub struct CommitHeader {
pub checksum: u128,
pub checksum_body: u128,
pub cluster: u128,
pub size: u32,
pub view: u32,
pub release: u32,
pub command: Command2,
pub replica: u8,
pub reserved_frame: [u8; 66],
pub commit_checksum: u128,
pub timestamp_monotonic: u64,
pub commit: u64,
pub checkpoint_op: u64,
pub namespace: u64,
pub reserved: [u8; 80],
}
const _: () = {
assert!(size_of::<CommitHeader>() == HEADER_SIZE);
assert!(
offset_of!(CommitHeader, commit_checksum)
== offset_of!(CommitHeader, reserved_frame) + size_of::<[u8; 66]>()
);
assert!(offset_of!(CommitHeader, reserved) + size_of::<[u8; 80]>() == HEADER_SIZE);
};
impl ConsensusHeader for CommitHeader {
const COMMAND: Command2 = Command2::Commit;
fn operation(&self) -> Operation {
Operation::Reserved
}
fn command(&self) -> Command2 {
self.command
}
fn size(&self) -> u32 {
self.size
}
fn validate(&self) -> Result<(), ConsensusError> {
if self.command != Command2::Commit {
return Err(ConsensusError::CommitInvalidCommand2);
}
if self.size != 256 {
return Err(ConsensusError::CommitInvalidSize(self.size));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, CheckedBitPattern, NoUninit)]
#[repr(C)]
pub struct StartViewChangeHeader {
pub checksum: u128,
pub checksum_body: u128,
pub cluster: u128,
pub size: u32,
pub view: u32,
pub release: u32,
pub command: Command2,
pub replica: u8,
pub reserved_frame: [u8; 66],
pub namespace: u64,
pub reserved: [u8; 120],
}
const _: () = {
assert!(size_of::<StartViewChangeHeader>() == HEADER_SIZE);
assert!(
offset_of!(StartViewChangeHeader, namespace)
== offset_of!(StartViewChangeHeader, reserved_frame) + size_of::<[u8; 66]>()
);
assert!(offset_of!(StartViewChangeHeader, reserved) + size_of::<[u8; 120]>() == HEADER_SIZE);
};
impl ConsensusHeader for StartViewChangeHeader {
const COMMAND: Command2 = Command2::StartViewChange;
fn operation(&self) -> Operation {
Operation::Reserved
}
fn command(&self) -> Command2 {
self.command
}
fn size(&self) -> u32 {
self.size
}
fn validate(&self) -> Result<(), ConsensusError> {
if self.command != Command2::StartViewChange {
return Err(ConsensusError::InvalidCommand {
expected: Command2::StartViewChange,
found: self.command,
});
}
if self.release != 0 {
return Err(ConsensusError::InvalidField("release != 0".to_string()));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, CheckedBitPattern, NoUninit)]
#[repr(C)]
pub struct DoViewChangeHeader {
pub checksum: u128,
pub checksum_body: u128,
pub cluster: u128,
pub size: u32,
pub view: u32,
pub release: u32,
pub command: Command2,
pub replica: u8,
pub reserved_frame: [u8; 66],
pub op: u64,
pub commit: u64,
pub namespace: u64,
pub log_view: u32,
pub reserved: [u8; 100],
}
const _: () = {
assert!(size_of::<DoViewChangeHeader>() == HEADER_SIZE);
assert!(
offset_of!(DoViewChangeHeader, op)
== offset_of!(DoViewChangeHeader, reserved_frame) + size_of::<[u8; 66]>()
);
assert!(offset_of!(DoViewChangeHeader, reserved) + size_of::<[u8; 100]>() == HEADER_SIZE);
};
impl ConsensusHeader for DoViewChangeHeader {
const COMMAND: Command2 = Command2::DoViewChange;
fn operation(&self) -> Operation {
Operation::Reserved
}
fn command(&self) -> Command2 {
self.command
}
fn size(&self) -> u32 {
self.size
}
fn validate(&self) -> Result<(), ConsensusError> {
if self.command != Command2::DoViewChange {
return Err(ConsensusError::InvalidCommand {
expected: Command2::DoViewChange,
found: self.command,
});
}
if self.release != 0 {
return Err(ConsensusError::InvalidField(
"release must be 0".to_string(),
));
}
if self.log_view > self.view {
return Err(ConsensusError::InvalidField(
"log_view cannot exceed view".to_string(),
));
}
if self.commit > self.op {
return Err(ConsensusError::InvalidField(
"commit cannot exceed op".to_string(),
));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, CheckedBitPattern, NoUninit)]
#[repr(C)]
pub struct StartViewHeader {
pub checksum: u128,
pub checksum_body: u128,
pub cluster: u128,
pub size: u32,
pub view: u32,
pub release: u32,
pub command: Command2,
pub replica: u8,
pub reserved_frame: [u8; 66],
pub op: u64,
pub commit: u64,
pub namespace: u64,
pub reserved: [u8; 104],
}
const _: () = {
assert!(size_of::<StartViewHeader>() == HEADER_SIZE);
assert!(
offset_of!(StartViewHeader, op)
== offset_of!(StartViewHeader, reserved_frame) + size_of::<[u8; 66]>()
);
assert!(offset_of!(StartViewHeader, reserved) + size_of::<[u8; 104]>() == HEADER_SIZE);
};
impl ConsensusHeader for StartViewHeader {
const COMMAND: Command2 = Command2::StartView;
fn operation(&self) -> Operation {
Operation::Reserved
}
fn command(&self) -> Command2 {
self.command
}
fn size(&self) -> u32 {
self.size
}
fn validate(&self) -> Result<(), ConsensusError> {
if self.command != Command2::StartView {
return Err(ConsensusError::InvalidCommand {
expected: Command2::StartView,
found: self.command,
});
}
if self.release != 0 {
return Err(ConsensusError::InvalidField(
"release must be 0".to_string(),
));
}
if self.commit > self.op {
return Err(ConsensusError::InvalidField(
"commit cannot exceed op".to_string(),
));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, CheckedBitPattern, NoUninit)]
#[repr(C)]
pub struct RequestStartViewHeader {
pub checksum: u128,
pub checksum_body: u128,
pub cluster: u128,
pub size: u32,
pub view: u32,
pub release: u32,
pub command: Command2,
pub replica: u8,
pub reserved_frame: [u8; 66],
pub namespace: u64,
pub reserved: [u8; 120],
}
const _: () = {
assert!(size_of::<RequestStartViewHeader>() == HEADER_SIZE);
assert!(
offset_of!(RequestStartViewHeader, namespace)
== offset_of!(RequestStartViewHeader, reserved_frame) + size_of::<[u8; 66]>()
);
assert!(offset_of!(RequestStartViewHeader, reserved) + size_of::<[u8; 120]>() == HEADER_SIZE);
};
impl ConsensusHeader for RequestStartViewHeader {
const COMMAND: Command2 = Command2::RequestStartView;
fn operation(&self) -> Operation {
Operation::Reserved
}
fn command(&self) -> Command2 {
self.command
}
fn size(&self) -> u32 {
self.size
}
fn validate(&self) -> Result<(), ConsensusError> {
if self.command != Command2::RequestStartView {
return Err(ConsensusError::InvalidCommand {
expected: Command2::RequestStartView,
found: self.command,
});
}
if self.release != 0 {
return Err(ConsensusError::InvalidField(
"release must be 0".to_string(),
));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, CheckedBitPattern, NoUninit)]
#[repr(C)]
pub struct RequestPreparesHeader {
pub checksum: u128,
pub checksum_body: u128,
pub cluster: u128,
pub size: u32,
pub view: u32,
pub release: u32,
pub command: Command2,
pub replica: u8,
pub reserved_frame: [u8; 66],
pub nonce: u128,
pub from_op: u64,
pub to_op: u64,
pub namespace: u64,
pub reserved: [u8; 88],
}
const _: () = {
assert!(size_of::<RequestPreparesHeader>() == HEADER_SIZE);
assert!(
offset_of!(RequestPreparesHeader, nonce)
== offset_of!(RequestPreparesHeader, reserved_frame) + size_of::<[u8; 66]>()
);
assert!(offset_of!(RequestPreparesHeader, reserved) + size_of::<[u8; 88]>() == HEADER_SIZE);
};
impl ConsensusHeader for RequestPreparesHeader {
const COMMAND: Command2 = Command2::RequestPrepares;
fn operation(&self) -> Operation {
Operation::Reserved
}
fn command(&self) -> Command2 {
self.command
}
fn size(&self) -> u32 {
self.size
}
fn validate(&self) -> Result<(), ConsensusError> {
if self.command != Command2::RequestPrepares {
return Err(ConsensusError::InvalidCommand {
expected: Command2::RequestPrepares,
found: self.command,
});
}
if self.from_op == 0 || self.from_op > self.to_op {
return Err(ConsensusError::InvalidField(
"repair range must be non-empty and 1-based".to_string(),
));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, CheckedBitPattern, NoUninit)]
#[repr(C)]
pub struct RepairRangeReplyHeader {
pub checksum: u128,
pub checksum_body: u128,
pub cluster: u128,
pub size: u32,
pub view: u32,
pub release: u32,
pub command: Command2,
pub replica: u8,
pub reserved_frame: [u8; 66],
pub nonce: u128,
pub op: u64,
pub namespace: u64,
pub reserved: [u8; 96],
}
const _: () = {
assert!(size_of::<RepairRangeReplyHeader>() == HEADER_SIZE);
assert!(
offset_of!(RepairRangeReplyHeader, nonce)
== offset_of!(RepairRangeReplyHeader, reserved_frame) + size_of::<[u8; 66]>()
);
assert!(offset_of!(RepairRangeReplyHeader, reserved) + size_of::<[u8; 96]>() == HEADER_SIZE);
};
impl ConsensusHeader for RepairRangeReplyHeader {
const COMMAND: Command2 = Command2::RepairDone;
fn accepts(command: Command2) -> bool {
command == Command2::RepairDone || command == Command2::RangeEvicted
}
fn operation(&self) -> Operation {
Operation::Reserved
}
fn command(&self) -> Command2 {
self.command
}
fn size(&self) -> u32 {
self.size
}
fn validate(&self) -> Result<(), ConsensusError> {
if self.command != Command2::RepairDone && self.command != Command2::RangeEvicted {
return Err(ConsensusError::InvalidCommand {
expected: Command2::RepairDone,
found: self.command,
});
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{
Command2, CommitHeader, ConsensusHeader, DoViewChangeHeader, EvictionHeader,
EvictionReason, GenericHeader, HEADER_SIZE, Operation, PrepareHeader, PrepareOkHeader,
ReplyHeader, RequestHeader, StartViewChangeHeader, StartViewHeader,
};
use aligned_vec::{AVec, ConstAlign};
fn aligned_zeroed(size: usize) -> AVec<u8, ConstAlign<16>> {
let mut v: AVec<u8, ConstAlign<16>> = AVec::new(16);
v.resize(size, 0);
v
}
#[test]
fn all_headers_are_256_bytes() {
assert_eq!(size_of::<GenericHeader>(), 256);
assert_eq!(size_of::<RequestHeader>(), 256);
assert_eq!(size_of::<ReplyHeader>(), 256);
assert_eq!(size_of::<PrepareHeader>(), 256);
assert_eq!(size_of::<PrepareOkHeader>(), 256);
assert_eq!(size_of::<CommitHeader>(), 256);
assert_eq!(size_of::<StartViewChangeHeader>(), 256);
assert_eq!(size_of::<DoViewChangeHeader>(), 256);
assert_eq!(size_of::<StartViewHeader>(), 256);
}
#[test]
fn generic_header_zero_copy() {
let buf = aligned_zeroed(256);
let header: &GenericHeader = bytemuck::checked::try_from_bytes(&buf).unwrap();
assert_eq!(header.command, Command2::Reserved);
assert_eq!(header.size, 0);
}
#[test]
fn request_header_zero_copy() {
let mut buf = aligned_zeroed(256);
buf[60] = Command2::Request as u8;
buf[128] = 1;
buf[std::mem::offset_of!(RequestHeader, operation)] = Operation::Register as u8;
let header: &RequestHeader = bytemuck::checked::try_from_bytes(&buf).unwrap();
assert_eq!(header.command, Command2::Request);
assert!(header.validate().is_ok());
}
#[test]
fn request_header_wrong_command_fails_validation() {
let buf = aligned_zeroed(256);
let header: &RequestHeader = bytemuck::checked::try_from_bytes(&buf).unwrap();
assert!(header.validate().is_err());
}
#[test]
fn request_reserved_operation_rejected() {
let header = RequestHeader {
command: Command2::Request,
client: 1,
operation: Operation::Reserved,
session: 1,
request: 1,
..RequestHeader::default()
};
assert!(header.validate().is_err());
}
#[test]
fn request_register_nonzero_session_rejected() {
let header = RequestHeader {
command: Command2::Request,
operation: Operation::Register,
session: 5,
request: 0,
..RequestHeader::default()
};
assert!(header.validate().is_err());
}
#[test]
fn request_register_nonzero_request_rejected() {
let header = RequestHeader {
command: Command2::Request,
operation: Operation::Register,
session: 0,
request: 1,
..RequestHeader::default()
};
assert!(header.validate().is_err());
}
#[test]
fn request_non_register_valid() {
let header = RequestHeader {
command: Command2::Request,
operation: Operation::SendMessages,
client: 0xCAFE,
session: 10,
request: 1,
..RequestHeader::default()
};
assert!(header.validate().is_ok());
}
#[test]
fn request_non_register_zero_session_rejected() {
let header = RequestHeader {
command: Command2::Request,
operation: Operation::SendMessages,
session: 0,
request: 1,
..RequestHeader::default()
};
assert!(header.validate().is_err());
}
#[test]
fn request_non_register_zero_request_rejected() {
let header = RequestHeader {
command: Command2::Request,
operation: Operation::SendMessages,
session: 10,
request: 0,
..RequestHeader::default()
};
assert!(header.validate().is_err());
}
#[test]
fn reply_header_zero_copy() {
let mut buf = aligned_zeroed(256);
buf[60] = Command2::Reply as u8;
let header: &ReplyHeader = bytemuck::checked::try_from_bytes(&buf).unwrap();
assert_eq!(header.command, Command2::Reply);
assert!(header.validate().is_ok());
}
#[test]
fn reply_header_status_offset_and_size_pinned() {
use std::mem::offset_of;
assert_eq!(size_of::<ReplyHeader>(), HEADER_SIZE);
assert_eq!(
offset_of!(ReplyHeader, status),
offset_of!(ReplyHeader, namespace) + size_of::<u64>()
);
assert_eq!(
offset_of!(ReplyHeader, reserved) + size_of::<[u8; 28]>(),
HEADER_SIZE
);
}
#[test]
fn reply_header_nonzero_status_still_validates() {
let header = ReplyHeader {
command: Command2::Reply,
status: 41,
..ReplyHeader::default()
};
assert!(header.validate().is_ok());
}
#[test]
fn eviction_reason_discriminants_pinned() {
assert_eq!(EvictionReason::Reserved as u8, 0);
assert_eq!(EvictionReason::NoSession as u8, 1);
assert_eq!(EvictionReason::ClientReleaseTooLow as u8, 2);
assert_eq!(EvictionReason::ClientReleaseTooHigh as u8, 3);
assert_eq!(EvictionReason::InvalidRequestOperation as u8, 4);
assert_eq!(EvictionReason::InvalidRequestBody as u8, 5);
assert_eq!(EvictionReason::InvalidRequestBodySize as u8, 6);
assert_eq!(EvictionReason::SessionTooLow as u8, 7);
assert_eq!(EvictionReason::SessionReleaseMismatch as u8, 8);
assert_eq!(EvictionReason::InvalidCredentials as u8, 9);
assert_eq!(EvictionReason::InvalidToken as u8, 10);
assert_eq!(EvictionReason::UserInactive as u8, 11);
assert_eq!(EvictionReason::SessionError as u8, 12);
assert_eq!(EvictionReason::StaleClient as u8, 13);
assert_eq!(EvictionReason::IncompatibleProtocol as u8, 14);
assert_eq!(EvictionReason::MalformedLogin as u8, 15);
}
#[test]
fn eviction_incompatible_protocol_accepts_valid_window() {
let header = EvictionHeader::incompatible_protocol(0, 0, 0, 0xCAFE, 2, 1);
assert!(header.validate().is_ok());
assert_eq!(header.reason, EvictionReason::IncompatibleProtocol);
assert_eq!(header.server_protocol_version, 2);
assert_eq!(header.server_protocol_version_min, 1);
}
#[test]
fn eviction_incompatible_protocol_rejects_inverted_window() {
let header = EvictionHeader::incompatible_protocol(0, 0, 0, 0xCAFE, 1, 2);
assert!(header.validate().is_err());
}
#[test]
fn eviction_incompatible_protocol_rejects_zero_min() {
let header = EvictionHeader::incompatible_protocol(0, 0, 0, 0xCAFE, 1, 0);
assert!(header.validate().is_err());
}
#[test]
fn eviction_validate_rejects_window_on_other_reason() {
let mut header = EvictionHeader::new(0, 0, 0, 0xCAFE, EvictionReason::NoSession);
header.server_protocol_version = 1;
assert!(header.validate().is_err());
}
#[test]
fn eviction_validate_rejects_zero_client() {
let header = EvictionHeader::new(0, 0, 0, 0, EvictionReason::NoSession);
assert!(header.validate().is_err());
}
#[test]
fn eviction_validate_rejects_reserved_reason() {
let header = EvictionHeader::new(0, 0, 0, 1, EvictionReason::Reserved);
assert!(header.validate().is_err());
}
#[test]
fn eviction_validate_rejects_nonzero_reserved() {
let mut header = EvictionHeader::new(0, 0, 0, 1, EvictionReason::NoSession);
header.reserved[0] = 1;
assert!(header.validate().is_err());
}
#[test]
fn eviction_validate_accepts_well_formed_frame() {
let header = EvictionHeader::new(0, 0, 0, 0xCAFE, EvictionReason::NoSession);
assert!(header.validate().is_ok());
}
#[test]
fn eviction_header_is_256_bytes() {
assert_eq!(size_of::<EvictionHeader>(), 256);
}
}