use core::fmt;
#[cfg(feature = "std")]
use std::error;
use crate::checksum::{label_checksum, label_verify, LabelChecksumError, LabelVerifyError, Sha256};
use crate::phys::{
BigLittleEndianDecoder, BigLittleEndianEncoder, BinaryDecodeError, BinaryDecoder,
BinaryEncodeError, BinaryEncoder, BlockPointer, BlockPointerDecodeError,
BlockPointerEncodeError, ChecksumTail, EndianOrder, LabelNvPairs, LittleEndianDecoder,
SpaVersion, SpaVersionError, SECTOR_SHIFT,
};
#[derive(Debug)]
pub struct UberBlock {
pub checkpoint_txg: u64,
pub order: EndianOrder,
pub guid_sum: u64,
pub mmp: Option<UberBlockMmp>,
pub ptr: BlockPointer,
pub software_version: Option<SpaVersion>,
pub timestamp: u64,
pub txg: u64,
pub version: SpaVersion,
}
impl UberBlock {
pub const TOTAL_SIZE: usize = 131072;
pub const TOTAL_SECTORS: u32 = (UberBlock::TOTAL_SIZE >> SECTOR_SHIFT) as u32;
pub const LABEL_OFFSET: u64 =
LabelNvPairs::LABEL_OFFSET + ((LabelNvPairs::SIZE >> SECTOR_SHIFT) as u64);
pub const MAGIC: u64 = 0x0000000000bab10c;
pub fn is_newer_than(&self, other: &UberBlock) -> bool {
self.txg > other.txg || (self.txg == other.txg && self.timestamp > other.timestamp)
}
pub fn get_shift_from_version_ashift(version: SpaVersion, ashift: u64) -> u32 {
let min_shift = 10;
let max_shift = match version {
SpaVersion::V1 => 10,
SpaVersion::V2
| SpaVersion::V3
| SpaVersion::V4
| SpaVersion::V5
| SpaVersion::V6
| SpaVersion::V7
| SpaVersion::V8
| SpaVersion::V9
| SpaVersion::V10
| SpaVersion::V11
| SpaVersion::V12
| SpaVersion::V13
| SpaVersion::V14
| SpaVersion::V15
| SpaVersion::V16
| SpaVersion::V17
| SpaVersion::V18
| SpaVersion::V19
| SpaVersion::V20
| SpaVersion::V21
| SpaVersion::V22
| SpaVersion::V23
| SpaVersion::V24
| SpaVersion::V25
| SpaVersion::V26
| SpaVersion::V27
| SpaVersion::V28 => 17,
SpaVersion::V5000 => 13,
};
ashift.clamp(min_shift, max_shift) as u32
}
fn bytes_are_empty(bytes: &[u8], exclude_checksum: bool) -> bool {
let mut decoder = LittleEndianDecoder::from_bytes(bytes);
if decoder.skip_zeros(8).is_ok() {
if decoder.skip(8).is_ok() {
let excluded_length = if exclude_checksum {
ChecksumTail::SIZE
} else {
0
};
if let Some(rest_size) = decoder.len().checked_sub(excluded_length) {
if decoder.skip_zeros(rest_size).is_ok() {
return true;
}
}
}
}
false
}
pub fn from_bytes(
bytes: &[u8],
offset: u64,
sha256: &mut Sha256,
) -> Result<Option<UberBlock>, UberBlockDecodeError> {
if let Err(err) = label_verify(bytes, offset, sha256) {
if UberBlock::bytes_are_empty(bytes, false) {
return Ok(None);
}
return Err(UberBlockDecodeError::LabelVerify { err });
}
let mut bele_decoder = match BigLittleEndianDecoder::from_u64_magic(bytes, UberBlock::MAGIC)
{
Ok(v) => v,
Err(
err @ BinaryDecodeError::InvalidU64Magic {
expected: _,
actual: _,
},
) => {
if UberBlock::bytes_are_empty(bytes, true) {
return Ok(None);
}
return Err(UberBlockDecodeError::Binary { err });
}
Err(err) => return Err(UberBlockDecodeError::Binary { err }),
};
let decoder = bele_decoder.decoder_as_mut();
let version = SpaVersion::try_from(decoder.get_u64()?)?;
let txg = decoder.get_u64()?;
let guid_sum = decoder.get_u64()?;
let timestamp = decoder.get_u64()?;
let block_ptr = match BlockPointer::from_decoder(decoder)? {
Some(ptr) => ptr,
None => return Err(UberBlockDecodeError::EmptyBlockPointer {}),
};
let software_version = match decoder.get_u64()? {
0 => None,
v => Some(SpaVersion::try_from(v)?),
};
let mmp = UberBlockMmp::from_decoder(decoder)?;
let checkpoint_txg = decoder.get_u64()?;
let rest_size = match decoder.len().checked_sub(ChecksumTail::SIZE) {
Some(v) => v,
None => {
return Err(UberBlockDecodeError::Binary {
err: BinaryDecodeError::EndOfInput {
offset: decoder.offset(),
max_offset: bytes.len(),
capacity: decoder.capacity(),
count: ChecksumTail::SIZE,
},
})
}
};
decoder.skip_zeros(rest_size)?;
Ok(Some(UberBlock {
checkpoint_txg,
order: decoder.order(),
guid_sum,
mmp,
ptr: block_ptr,
software_version,
timestamp,
txg,
version,
}))
}
pub fn to_bytes(
&self,
bytes: &mut [u8],
offset: u64,
sha256: &mut Sha256,
) -> Result<(), UberBlockEncodeError> {
let mut bl_encoder = BigLittleEndianEncoder::to_bytes(bytes, self.order);
let encoder = bl_encoder.encoder();
encoder.put_u64(UberBlock::MAGIC)?;
encoder.put_u64(self.version.into())?;
encoder.put_u64(self.txg)?;
encoder.put_u64(self.guid_sum)?;
encoder.put_u64(self.timestamp)?;
self.ptr.to_encoder(encoder)?;
match self.software_version {
Some(v) => encoder.put_u64(v.into())?,
None => encoder.put_u64(0)?,
};
match &self.mmp {
Some(mmp) => mmp.to_encoder(encoder)?,
None => {
encoder.put_zeros(UberBlockMmp::SIZE)?;
}
}
encoder.put_u64(self.checkpoint_txg)?;
let rest_size = match encoder.available().checked_sub(ChecksumTail::SIZE) {
Some(v) => v,
None => {
return Err(UberBlockEncodeError::Binary {
err: BinaryEncodeError::EndOfOutput {
offset: encoder.offset(),
capacity: encoder.capacity(),
count: ChecksumTail::SIZE,
},
})
}
};
encoder.put_zeros(rest_size)?;
label_checksum(bytes, offset, sha256, self.order)?;
Ok(())
}
}
#[derive(Debug)]
pub enum UberBlockDecodeError {
Binary {
err: BinaryDecodeError,
},
BlockPointer {
err: BlockPointerDecodeError,
},
EmptyBlockPointer {},
LabelVerify {
err: LabelVerifyError,
},
SpaVersion {
err: SpaVersionError,
},
UberBlockMmp {
err: UberBlockMmpDecodeError,
},
}
impl From<BinaryDecodeError> for UberBlockDecodeError {
fn from(err: BinaryDecodeError) -> Self {
UberBlockDecodeError::Binary { err }
}
}
impl From<BlockPointerDecodeError> for UberBlockDecodeError {
fn from(err: BlockPointerDecodeError) -> Self {
UberBlockDecodeError::BlockPointer { err }
}
}
impl From<LabelVerifyError> for UberBlockDecodeError {
fn from(err: LabelVerifyError) -> Self {
UberBlockDecodeError::LabelVerify { err }
}
}
impl From<SpaVersionError> for UberBlockDecodeError {
fn from(err: SpaVersionError) -> Self {
UberBlockDecodeError::SpaVersion { err }
}
}
impl From<UberBlockMmpDecodeError> for UberBlockDecodeError {
fn from(err: UberBlockMmpDecodeError) -> Self {
UberBlockDecodeError::UberBlockMmp { err }
}
}
impl fmt::Display for UberBlockDecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UberBlockDecodeError::Binary { err } => {
write!(f, "UberBlock decode error | {err}")
}
UberBlockDecodeError::BlockPointer { err } => {
write!(f, "UberBlock decode error | {err}")
}
UberBlockDecodeError::EmptyBlockPointer {} => {
write!(f, "UberBlock decode error, empty block pointer")
}
UberBlockDecodeError::LabelVerify { err } => {
write!(f, "UberBlock decode error | {err}")
}
UberBlockDecodeError::SpaVersion { err } => {
write!(f, "UberBlock decode error | {err}")
}
UberBlockDecodeError::UberBlockMmp { err } => {
write!(f, "UberBlock decode error | {err}")
}
}
}
}
#[cfg(feature = "std")]
impl error::Error for UberBlockDecodeError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
UberBlockDecodeError::Binary { err } => Some(err),
UberBlockDecodeError::BlockPointer { err } => Some(err),
UberBlockDecodeError::EmptyBlockPointer {} => None,
UberBlockDecodeError::LabelVerify { err } => Some(err),
UberBlockDecodeError::SpaVersion { err } => Some(err),
UberBlockDecodeError::UberBlockMmp { err } => Some(err),
}
}
}
#[derive(Debug)]
pub enum UberBlockEncodeError {
Binary {
err: BinaryEncodeError,
},
BlockPointer {
err: BlockPointerEncodeError,
},
LabelChecksum {
err: LabelChecksumError,
},
UberBlockMmp {
err: UberBlockMmpEncodeError,
},
}
impl From<BinaryEncodeError> for UberBlockEncodeError {
fn from(err: BinaryEncodeError) -> Self {
UberBlockEncodeError::Binary { err }
}
}
impl From<BlockPointerEncodeError> for UberBlockEncodeError {
fn from(err: BlockPointerEncodeError) -> Self {
UberBlockEncodeError::BlockPointer { err }
}
}
impl From<LabelChecksumError> for UberBlockEncodeError {
fn from(err: LabelChecksumError) -> Self {
UberBlockEncodeError::LabelChecksum { err }
}
}
impl From<UberBlockMmpEncodeError> for UberBlockEncodeError {
fn from(err: UberBlockMmpEncodeError) -> Self {
UberBlockEncodeError::UberBlockMmp { err }
}
}
impl fmt::Display for UberBlockEncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UberBlockEncodeError::Binary { err } => {
write!(f, "UberBlock encode error | {err}")
}
UberBlockEncodeError::BlockPointer { err } => {
write!(f, "UberBlock encode error | {err}")
}
UberBlockEncodeError::LabelChecksum { err } => {
write!(f, "UberBlock encode error | {err}")
}
UberBlockEncodeError::UberBlockMmp { err } => {
write!(f, "UberBlock encode error | {err}")
}
}
}
}
#[cfg(feature = "std")]
impl error::Error for UberBlockEncodeError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
UberBlockEncodeError::Binary { err } => Some(err),
UberBlockEncodeError::BlockPointer { err } => Some(err),
UberBlockEncodeError::LabelChecksum { err } => Some(err),
UberBlockEncodeError::UberBlockMmp { err } => Some(err),
}
}
}
#[derive(Debug)]
pub struct UberBlockMmp {
pub delay: u64,
pub fail_intervals: Option<u16>,
pub sequence: Option<u16>,
pub write_interval: Option<u32>,
}
impl UberBlockMmp {
pub const SIZE: usize = 24;
pub const MAGIC: u64 = 0x00000000a11cea11;
pub const WRITE_INTERVAL_MAX: u32 =
(UberBlockMmp::CONFIG_WRITE_INTERVAL_MASK_DOWN_SHIFTED as u32);
const CONFIG_WRITE_INTERVAL_BIT_FLAG: u64 = 1 << 0;
const CONFIG_SEQUENCE_BIT_FLAG: u64 = 1 << 1;
const CONFIG_FAIL_INTERVALS_BIT_FLAG: u64 = 1 << 2;
const CONFIG_RESERVED_MASK: u64 = 0xff
^ UberBlockMmp::CONFIG_FAIL_INTERVALS_BIT_FLAG
^ UberBlockMmp::CONFIG_SEQUENCE_BIT_FLAG
^ UberBlockMmp::CONFIG_WRITE_INTERVAL_BIT_FLAG;
const CONFIG_WRITE_INTERVAL_SHIFT: u64 = 8;
const CONFIG_WRITE_INTERVAL_MASK_DOWN_SHIFTED: u64 = (1 << 24) - 1;
const CONFIG_SEQUENCE_SHIFT: u64 = 32;
const CONFIG_FAIL_INTERVALS_SHIFT: u64 = 48;
pub fn from_decoder(
decoder: &mut dyn BinaryDecoder<'_>,
) -> Result<Option<UberBlockMmp>, UberBlockMmpDecodeError> {
let magic = decoder.get_u64()?;
let delay = decoder.get_u64()?;
let config = decoder.get_u64()?;
match magic {
0 => {
if delay != 0 || config != 0 {
return Err(UberBlockMmpDecodeError::NonZeroValues {
magic,
delay,
config,
});
}
Ok(None)
}
UberBlockMmp::MAGIC => {
if (config & UberBlockMmp::CONFIG_RESERVED_MASK) != 0 {
return Err(UberBlockMmpDecodeError::NonZeroReservedConfigBits { config });
}
let fail_intervals = (config >> UberBlockMmp::CONFIG_FAIL_INTERVALS_SHIFT) as u16;
let sequence = (config >> UberBlockMmp::CONFIG_SEQUENCE_SHIFT) as u16;
let write_interval = ((config >> UberBlockMmp::CONFIG_WRITE_INTERVAL_SHIFT)
& UberBlockMmp::CONFIG_WRITE_INTERVAL_MASK_DOWN_SHIFTED)
as u32;
let fail_intervals = if (config & UberBlockMmp::CONFIG_FAIL_INTERVALS_BIT_FLAG) != 0
{
Some(fail_intervals)
} else if fail_intervals != 0 {
return Err(UberBlockMmpDecodeError::NonZeroValues {
magic,
delay,
config,
});
} else {
None
};
let sequence = if (config & UberBlockMmp::CONFIG_SEQUENCE_BIT_FLAG) != 0 {
Some(sequence)
} else if sequence != 0 {
return Err(UberBlockMmpDecodeError::NonZeroValues {
magic,
delay,
config,
});
} else {
None
};
let write_interval = if (config & UberBlockMmp::CONFIG_WRITE_INTERVAL_BIT_FLAG) != 0
{
Some(write_interval)
} else if write_interval != 0 {
return Err(UberBlockMmpDecodeError::NonZeroValues {
magic,
delay,
config,
});
} else {
None
};
Ok(Some(UberBlockMmp {
delay,
fail_intervals,
sequence,
write_interval,
}))
}
_ => Err(UberBlockMmpDecodeError::InvalidMagic { magic }),
}
}
pub fn to_encoder(
&self,
encoder: &mut dyn BinaryEncoder<'_>,
) -> Result<(), UberBlockMmpEncodeError> {
encoder.put_u64(UberBlockMmp::MAGIC)?;
encoder.put_u64(self.delay)?;
let config: u64 = (match self.fail_intervals {
Some(v) => {
(u64::from(v) << UberBlockMmp::CONFIG_FAIL_INTERVALS_SHIFT)
| UberBlockMmp::CONFIG_FAIL_INTERVALS_BIT_FLAG
}
None => 0,
} | match self.sequence {
Some(v) => {
(u64::from(v) << UberBlockMmp::CONFIG_SEQUENCE_SHIFT)
| UberBlockMmp::CONFIG_SEQUENCE_BIT_FLAG
}
None => 0,
} | match self.write_interval {
Some(v) => {
if u64::from(v) > UberBlockMmp::CONFIG_WRITE_INTERVAL_MASK_DOWN_SHIFTED {
return Err(UberBlockMmpEncodeError::WriteIntervalTooLarge {
write_interval: v,
});
}
(u64::from(v) << UberBlockMmp::CONFIG_WRITE_INTERVAL_SHIFT)
| UberBlockMmp::CONFIG_WRITE_INTERVAL_BIT_FLAG
}
None => 0,
});
encoder.put_u64(config)?;
Ok(())
}
}
#[derive(Debug)]
pub enum UberBlockMmpDecodeError {
Endian {
err: BinaryDecodeError,
},
InvalidMagic {
magic: u64,
},
NonZeroReservedConfigBits {
config: u64,
},
NonZeroValues {
magic: u64,
delay: u64,
config: u64,
},
}
impl From<BinaryDecodeError> for UberBlockMmpDecodeError {
fn from(err: BinaryDecodeError) -> Self {
UberBlockMmpDecodeError::Endian { err }
}
}
impl fmt::Display for UberBlockMmpDecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UberBlockMmpDecodeError::Endian { err } => {
write!(f, "UberBlockMmp decode error | {err}")
}
UberBlockMmpDecodeError::InvalidMagic { magic } => {
write!(f, "UberBlockMmp decode error, invalid magic {magic:#016x}")
}
UberBlockMmpDecodeError::NonZeroReservedConfigBits { config } => {
write!(
f,
"UberBlockMmp decode error, non-zero reserved config bits config {config:#016x}"
)
}
UberBlockMmpDecodeError::NonZeroValues {
magic,
delay,
config,
} => {
write!(
f,
"UberBlockMmp decode error, non-zero values delay {delay:#016x} config {config:#016x} for magic {magic:#016x}"
)
}
}
}
}
#[cfg(feature = "std")]
impl error::Error for UberBlockMmpDecodeError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
UberBlockMmpDecodeError::Endian { err } => Some(err),
_ => None,
}
}
}
#[derive(Debug)]
pub enum UberBlockMmpEncodeError {
Binary {
err: BinaryEncodeError,
},
WriteIntervalTooLarge {
write_interval: u32,
},
}
impl From<BinaryEncodeError> for UberBlockMmpEncodeError {
fn from(err: BinaryEncodeError) -> Self {
UberBlockMmpEncodeError::Binary { err }
}
}
impl fmt::Display for UberBlockMmpEncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UberBlockMmpEncodeError::Binary { err } => {
write!(f, "UberBlockMmp encode error | {err}")
}
UberBlockMmpEncodeError::WriteIntervalTooLarge { write_interval } => {
write!(
f,
"UberBlockMmp encode error, write interval is too large {write_interval} > {}",
UberBlockMmp::CONFIG_WRITE_INTERVAL_MASK_DOWN_SHIFTED
)
}
}
}
}
#[cfg(feature = "std")]
impl error::Error for UberBlockMmpEncodeError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
UberBlockMmpEncodeError::Binary { err } => Some(err),
UberBlockMmpEncodeError::WriteIntervalTooLarge { write_interval: _ } => None,
}
}
}