use super::header::HEADER_SIZE;
use super::types::RecordType;
use super::wal_record::{WalRecord, WalRecordArgs};
use crate::align::{AlignedBuf, round_up};
use crate::error::{Result, WalError};
pub const MIN_PADDING_RECORD_SIZE: usize = HEADER_SIZE;
pub fn padding_span(len: usize, alignment: usize) -> Option<usize> {
let gap = round_up(len, alignment) - len;
if gap == 0 {
return None;
}
if gap < MIN_PADDING_RECORD_SIZE {
Some(gap + alignment)
} else {
Some(gap)
}
}
pub fn padding_record(total_size: usize) -> Result<WalRecord> {
let payload_len = total_size
.checked_sub(HEADER_SIZE)
.ok_or(WalError::AlignmentViolation {
context: "alignment padding is narrower than a WAL record header",
required: HEADER_SIZE,
actual: total_size,
})?;
WalRecord::new(WalRecordArgs {
record_type: RecordType::Noop as u32,
lsn: 0,
tenant_id: 0,
vshard_id: 0,
database_id: 0,
payload: vec![0u8; payload_len],
encryption_key: None,
preamble_bytes: None,
})
}
pub(crate) fn pad_buffer_to_alignment(
buffer: &mut AlignedBuf,
alignment: usize,
context: &'static str,
) -> Result<()> {
let Some(span) = padding_span(buffer.len(), alignment) else {
return Ok(());
};
if buffer.remaining() < span {
return Err(WalError::AlignmentViolation {
context,
required: span,
actual: buffer.remaining(),
});
}
let padding = padding_record(span)?;
buffer.write(&padding.header.to_bytes());
buffer.write(&padding.payload);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::align::DEFAULT_ALIGNMENT;
#[test]
fn aligned_length_needs_no_padding() {
assert_eq!(padding_span(0, DEFAULT_ALIGNMENT), None);
assert_eq!(padding_span(DEFAULT_ALIGNMENT, DEFAULT_ALIGNMENT), None);
assert_eq!(padding_span(2 * DEFAULT_ALIGNMENT, DEFAULT_ALIGNMENT), None);
}
#[test]
fn span_closes_the_gap_to_the_next_boundary() {
let span = padding_span(100, DEFAULT_ALIGNMENT).unwrap();
assert_eq!(span, DEFAULT_ALIGNMENT - 100);
assert_eq!((100 + span) % DEFAULT_ALIGNMENT, 0);
}
#[test]
fn narrow_gap_is_extended_by_a_whole_block() {
let len = DEFAULT_ALIGNMENT - 1;
let span = padding_span(len, DEFAULT_ALIGNMENT).unwrap();
assert!(span >= MIN_PADDING_RECORD_SIZE);
assert_eq!((len + span) % DEFAULT_ALIGNMENT, 0);
}
#[test]
fn every_unaligned_length_yields_a_framable_aligned_span() {
for len in 0..(2 * DEFAULT_ALIGNMENT) {
match padding_span(len, DEFAULT_ALIGNMENT) {
None => assert_eq!(len % DEFAULT_ALIGNMENT, 0),
Some(span) => {
assert!(
span >= MIN_PADDING_RECORD_SIZE,
"span {span} cannot be framed"
);
assert_eq!((len + span) % DEFAULT_ALIGNMENT, 0);
assert!(padding_record(span).is_ok());
}
}
}
}
#[test]
fn padding_record_occupies_exactly_its_span() {
let record = padding_record(512).unwrap();
assert_eq!(HEADER_SIZE + record.payload.len(), 512);
assert_eq!(record.header.lsn, 0);
assert_eq!(record.header.record_type, RecordType::Noop as u32);
assert!(record.verify_checksum().is_ok());
}
#[test]
fn span_below_a_header_is_rejected() {
assert!(matches!(
padding_record(HEADER_SIZE - 1),
Err(WalError::AlignmentViolation { .. })
));
}
}