use serde::{Deserialize, Serialize};
use crate::{BlockPtr, SegmentId, SegmentOffset};
use std::io::{Error, ErrorKind, Result};
const BLOCK_PTR_DUMMY: u64 = 0x3FFF_FFFF_FFFF_FFFF;
const BLOCK_PTR_ZERO_BLOCK: u64 = 0x3FFF_FFFF_0000_0000;
const BLOCK_PTR_LOCATION_MASK: u64 = 0xC000_0000_0000_0000;
const BLOCK_PTR_SEGMENT_ID_MASK: u64 = 0x3FFF_FFFF_0000_0000;
const BLOCK_PTR_SEGMENT_FLAT_OFFSET_MASK: u64 = 0x0000_0000_FFFF_FFFF;
const BLOCK_PTR_SEGMENT_MG_BLOCK_INDEX_MASK: u64 = 0x0000_0000_FFFF_C000; const BLOCK_PTR_SEGMENT_MG_GROUP_INDEX_MASK: u64 = 0x0000_0000_0000_3FFF;
const BLOCK_PTR_PARTED_PART_MASK: u64 = 0x0000_0000_0000_3FFF;
const BLOCK_PTR_SEGMENT_ID_BIT_SHIFT: u32 = 32;
const BLOCK_PTR_SEGMENT_MG_BLOCK_INDEX_SHIFT: u32 = 14;
const BLOCK_PTR_SEGMENT_MG_GROUP_INDEX_SHIFT: u32 = 4; const BLOCK_PTR_SEGMENT_MG_OFFSET_ID_SHIFT: u32 = 12;
pub const BLOCK_PTR_SEGMENT_MG_GROUP_INDEX_BITS: u32 = BLOCK_PTR_SEGMENT_MG_BLOCK_INDEX_SHIFT;
pub const BLOCK_PTR_SEGMENT_MG_BLOCK_INDEX_BITS: u32 = 32 - BLOCK_PTR_SEGMENT_MG_GROUP_INDEX_BITS;
pub const BLOCK_PTR_MAX_CHECKPOINT: u64 = BLOCK_PTR_SEGMENT_ID_MASK >> BLOCK_PTR_SEGMENT_ID_BIT_SHIFT;
pub const BLOCK_PTR_PARTED_MAX_PARTS: usize = 1 << BLOCK_PTR_SEGMENT_MG_GROUP_INDEX_BITS;
pub const BLOCK_PTR_PARTED_MAX_PART_BYTES: usize =
(1 << BLOCK_PTR_SEGMENT_MG_BLOCK_INDEX_BITS) << BLOCK_PTR_SEGMENT_MG_OFFSET_ID_SHIFT;
const BLOCK_PTR_STAGING: u64 = 0x4000_0000_0000_0000;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[repr(u8)]
pub enum BlockPtrFormat {
Nop = 0,
Flat = 1,
MicroGroup = 2,
PartedSegment = 3,
StreamSegment = 4,
}
impl BlockPtrFormat {
#[inline]
pub fn try_from_u8(data: u8) -> Result<Self> {
match data {
0 => Ok(Self::Nop),
1 => Ok(Self::Flat),
2 => Ok(Self::MicroGroup),
3 => Ok(Self::PartedSegment),
4 => Ok(Self::StreamSegment),
n => Err(Error::new(ErrorKind::InvalidData,
format!("unknown block ptr format {}", n))),
}
}
#[inline]
pub fn from_u8(data: u8) -> Self {
Self::try_from_u8(data).expect("block ptr format")
}
#[inline]
pub fn new_zero_block() -> BlockPtr {
BLOCK_PTR_ZERO_BLOCK
}
#[inline]
pub fn is_zero_block(blk_ptr: &BlockPtr) -> bool {
*blk_ptr == BLOCK_PTR_ZERO_BLOCK
}
#[inline]
pub fn dummy_value() -> BlockPtr {
BLOCK_PTR_DUMMY
}
#[inline]
pub fn is_dummy_value(blk_ptr: &BlockPtr) -> bool {
*blk_ptr == BLOCK_PTR_DUMMY
}
#[inline]
pub fn invalid_value() -> BlockPtr {
BlockPtr::MIN
}
#[inline]
pub fn is_invalid_value(blk_ptr: &BlockPtr) -> bool {
*blk_ptr == BlockPtr::MIN
}
#[inline]
pub fn is_on_staging(blk_ptr: &BlockPtr) -> bool {
*blk_ptr & BLOCK_PTR_LOCATION_MASK == BLOCK_PTR_STAGING
}
pub fn encode(segid: SegmentId, offset: SegmentOffset, seq: usize, fmt: &BlockPtrFormat) -> BlockPtr {
let id = segid.seq_id() as u64;
match fmt {
Self::Nop => 0,
Self::Flat => Self::encode_flat(id, offset, seq),
Self::MicroGroup => Self::encode_micro_group(id, offset, seq),
Self::PartedSegment | Self::StreamSegment => Self::encode_parted_segment(
id, segid.part_id().unwrap_or(0), offset),
}
}
pub fn decode(blk_ptr: &BlockPtr, fmt: &BlockPtrFormat) -> (SegmentId, SegmentOffset) {
match fmt {
Self::Nop => (SegmentId::new(0), 0),
Self::Flat => { let (s, o) = Self::decode_flat(blk_ptr); (SegmentId::new(s as u32), o) },
Self::MicroGroup => { let (s, o) = Self::decode_micro_group(blk_ptr); (SegmentId::new(s as u32), o) },
Self::PartedSegment | Self::StreamSegment => Self::decode_parted_segment(blk_ptr),
}
}
#[inline]
pub fn is_parted(&self) -> bool {
matches!(self, Self::PartedSegment | Self::StreamSegment)
}
pub fn is_stream(&self) -> bool {
matches!(self, Self::StreamSegment)
}
pub fn max_object_bytes(&self) -> usize {
match self {
Self::Nop => usize::MAX,
Self::Flat => (BLOCK_PTR_SEGMENT_FLAT_OFFSET_MASK as usize) + 1,
Self::MicroGroup | Self::PartedSegment | Self::StreamSegment => BLOCK_PTR_PARTED_MAX_PART_BYTES,
}
}
#[inline]
fn encode_parted_segment(segid: u64, part: u16, offset: SegmentOffset) -> BlockPtr {
assert!((part as usize) < BLOCK_PTR_PARTED_MAX_PARTS,
"part {} past the {} a pointer can name", part, BLOCK_PTR_PARTED_MAX_PARTS);
assert!(offset < BLOCK_PTR_PARTED_MAX_PART_BYTES,
"offset {} past the {} bytes one part can hold", offset, BLOCK_PTR_PARTED_MAX_PART_BYTES);
BLOCK_PTR_STAGING |
(segid << BLOCK_PTR_SEGMENT_ID_BIT_SHIFT) & BLOCK_PTR_SEGMENT_ID_MASK |
(offset as u64 >> BLOCK_PTR_SEGMENT_MG_OFFSET_ID_SHIFT << BLOCK_PTR_SEGMENT_MG_BLOCK_INDEX_SHIFT) & BLOCK_PTR_SEGMENT_MG_BLOCK_INDEX_MASK |
(part as u64 & BLOCK_PTR_PARTED_PART_MASK)
}
#[inline]
fn decode_parted_segment(blk_ptr: &BlockPtr) -> (SegmentId, SegmentOffset) {
let p = blk_ptr & !BLOCK_PTR_LOCATION_MASK;
let part = (p & BLOCK_PTR_PARTED_PART_MASK) as u16;
let seg_offset_id = (p & BLOCK_PTR_SEGMENT_MG_BLOCK_INDEX_MASK) >> BLOCK_PTR_SEGMENT_MG_BLOCK_INDEX_SHIFT;
let segid = (p & BLOCK_PTR_SEGMENT_ID_MASK) >> BLOCK_PTR_SEGMENT_ID_BIT_SHIFT;
let offset = seg_offset_id << BLOCK_PTR_SEGMENT_MG_OFFSET_ID_SHIFT; (SegmentId::with_part(segid as u32, part), offset as SegmentOffset)
}
#[inline]
fn encode_flat(segid: u64, offset: SegmentOffset, _seq: usize) -> BlockPtr {
assert!(offset < BlockPtrFormat::Flat.max_object_bytes(),
"offset {} past what a flat pointer can name", offset);
BLOCK_PTR_STAGING |
(segid << BLOCK_PTR_SEGMENT_ID_BIT_SHIFT) & BLOCK_PTR_SEGMENT_ID_MASK |
(offset as u64 & BLOCK_PTR_SEGMENT_FLAT_OFFSET_MASK)
}
#[inline]
fn decode_flat(blk_ptr: &BlockPtr) -> (u64, SegmentOffset) {
let p = blk_ptr & !BLOCK_PTR_LOCATION_MASK;
let offset = p & BLOCK_PTR_SEGMENT_FLAT_OFFSET_MASK;
let segid = (p & BLOCK_PTR_SEGMENT_ID_MASK) >> BLOCK_PTR_SEGMENT_ID_BIT_SHIFT;
(segid, offset as SegmentOffset)
}
#[inline]
fn encode_micro_group(segid: u64, offset: SegmentOffset, seq: usize) -> BlockPtr {
assert!(offset < BLOCK_PTR_PARTED_MAX_PART_BYTES,
"offset {} past the {} bytes one segment can hold", offset, BLOCK_PTR_PARTED_MAX_PART_BYTES);
BLOCK_PTR_STAGING |
(segid << BLOCK_PTR_SEGMENT_ID_BIT_SHIFT) & BLOCK_PTR_SEGMENT_ID_MASK |
(offset as u64 >> BLOCK_PTR_SEGMENT_MG_OFFSET_ID_SHIFT << BLOCK_PTR_SEGMENT_MG_BLOCK_INDEX_SHIFT) & BLOCK_PTR_SEGMENT_MG_BLOCK_INDEX_MASK |
((seq as u64 >> BLOCK_PTR_SEGMENT_MG_GROUP_INDEX_SHIFT) & BLOCK_PTR_SEGMENT_MG_GROUP_INDEX_MASK) }
#[inline]
fn decode_micro_group(blk_ptr: &BlockPtr) -> (u64, SegmentOffset) {
let p = blk_ptr & !BLOCK_PTR_LOCATION_MASK;
let seg_offset_id = (p & BLOCK_PTR_SEGMENT_MG_BLOCK_INDEX_MASK) >> BLOCK_PTR_SEGMENT_MG_BLOCK_INDEX_SHIFT;
let segid = (p & BLOCK_PTR_SEGMENT_ID_MASK) >> BLOCK_PTR_SEGMENT_ID_BIT_SHIFT;
let offset = seg_offset_id << BLOCK_PTR_SEGMENT_MG_OFFSET_ID_SHIFT; (segid, offset as SegmentOffset)
}
#[inline]
pub fn decode_micro_group_id(blk_ptr: &BlockPtr) -> u64 {
blk_ptr & BLOCK_PTR_SEGMENT_MG_GROUP_INDEX_MASK
}
#[inline]
pub fn decode_segid(blk_ptr: &BlockPtr) -> SegmentId {
let p = blk_ptr & !BLOCK_PTR_LOCATION_MASK;
let segid = (p & BLOCK_PTR_SEGMENT_ID_MASK) >> BLOCK_PTR_SEGMENT_ID_BIT_SHIFT;
SegmentId::new(segid as u32)
}
}
#[cfg(test)]
mod tests {
#[test]
fn each_format_names_its_own_object_limit() {
assert_eq!(BlockPtrFormat::PartedSegment.max_object_bytes(), 1 << 30);
assert_eq!(BlockPtrFormat::MicroGroup.max_object_bytes(), 1 << 30);
assert_eq!(BlockPtrFormat::Flat.max_object_bytes(), 1 << 32);
for fmt in [BlockPtrFormat::PartedSegment, BlockPtrFormat::MicroGroup, BlockPtrFormat::Flat] {
let off = fmt.max_object_bytes() - 4096;
let p = BlockPtrFormat::encode(crate::SegmentId::new_from_cno(7).at_part(0), off, 0, &fmt);
assert_eq!(BlockPtrFormat::decode(&p, &fmt).1, off, "{:?}", fmt);
}
}
#[test]
#[should_panic(expected = "past the")]
fn a_parted_offset_past_one_part_is_refused() {
let _ = BlockPtrFormat::encode(crate::SegmentId::new_from_cno(1).at_part(0), 1 << 30, 0, &BlockPtrFormat::PartedSegment);
}
#[test]
#[should_panic(expected = "past the")]
fn a_micro_group_offset_past_one_segment_is_refused() {
let _ = BlockPtrFormat::encode(crate::SegmentId::new_from_cno(1), 1 << 30, 0, &BlockPtrFormat::MicroGroup);
}
use super::*;
#[test]
fn from_u8_known_variants() {
assert_eq!(BlockPtrFormat::from_u8(0), BlockPtrFormat::Nop);
assert_eq!(BlockPtrFormat::from_u8(1), BlockPtrFormat::Flat);
assert_eq!(BlockPtrFormat::from_u8(2), BlockPtrFormat::MicroGroup);
assert_eq!(BlockPtrFormat::from_u8(3), BlockPtrFormat::PartedSegment);
assert_eq!(BlockPtrFormat::from_u8(4), BlockPtrFormat::StreamSegment);
}
#[test]
fn stream_segment_is_addressed_as_parted() {
let f = BlockPtrFormat::StreamSegment;
assert!(f.is_parted() && f.is_stream() && !BlockPtrFormat::PartedSegment.is_stream());
let id = SegmentId::new_from_cno(9).at_part(5);
let p = BlockPtrFormat::encode(id, 3 * 4096, 0, &f);
assert_eq!(p, BlockPtrFormat::encode(id, 3 * 4096, 0, &BlockPtrFormat::PartedSegment));
assert_eq!(BlockPtrFormat::decode(&p, &f), (id, 3 * 4096));
assert_eq!(f.max_object_bytes(), BlockPtrFormat::PartedSegment.max_object_bytes());
}
#[test]
#[should_panic(expected = "unknown block ptr format")]
fn from_u8_invalid() {
BlockPtrFormat::from_u8(5);
}
#[test]
fn zero_block_sentinel() {
let z = BlockPtrFormat::new_zero_block();
assert!(BlockPtrFormat::is_zero_block(&z));
assert!(!BlockPtrFormat::is_zero_block(&0));
assert!(!BlockPtrFormat::is_dummy_value(&z));
}
#[test]
fn dummy_value_sentinel() {
let d = BlockPtrFormat::dummy_value();
assert!(BlockPtrFormat::is_dummy_value(&d));
assert!(!BlockPtrFormat::is_dummy_value(&0));
assert!(!BlockPtrFormat::is_zero_block(&d));
}
#[test]
fn invalid_value_sentinel() {
let iv = BlockPtrFormat::invalid_value();
assert!(BlockPtrFormat::is_invalid_value(&iv));
assert_eq!(iv, BlockPtr::MIN);
}
#[test]
fn flat_round_trip_basic() {
let segid = SegmentId::new(1);
let offset: SegmentOffset = 4096;
let ptr = BlockPtrFormat::encode(segid, offset, 0, &BlockPtrFormat::Flat);
assert!(BlockPtrFormat::is_on_staging(&ptr));
assert_eq!(BlockPtrFormat::decode(&ptr, &BlockPtrFormat::Flat), (segid, offset));
}
#[test]
fn flat_round_trip_large_segid() {
let segid = SegmentId::new(0x3FFF_FFFF); let offset: SegmentOffset = 0xFFFF_FFFF; let ptr = BlockPtrFormat::encode(segid, offset, 0, &BlockPtrFormat::Flat);
assert_eq!(BlockPtrFormat::decode(&ptr, &BlockPtrFormat::Flat), (segid, offset));
}
#[test]
fn flat_round_trip_zero() {
let ptr = BlockPtrFormat::encode(SegmentId::new(0), 0, 0, &BlockPtrFormat::Flat);
assert!(BlockPtrFormat::is_on_staging(&ptr));
assert_eq!(BlockPtrFormat::decode(&ptr, &BlockPtrFormat::Flat), (SegmentId::new(0), 0));
}
#[test]
fn parted_round_trip_carries_the_part() {
for part in [0u16, 1, 2, 255, 4095, (BLOCK_PTR_PARTED_MAX_PARTS - 1) as u16] {
let segid = SegmentId::with_part(12345, part);
let offset: SegmentOffset = 8192;
let ptr = BlockPtrFormat::encode(segid, offset, 0, &BlockPtrFormat::PartedSegment);
assert!(BlockPtrFormat::is_on_staging(&ptr));
let (got, o) = BlockPtrFormat::decode(&ptr, &BlockPtrFormat::PartedSegment);
assert_eq!(got, segid, "part {} did not survive", part);
assert_eq!(got.part_id(), Some(part));
assert_eq!(o, offset);
}
}
#[test]
fn parted_round_trip_at_the_field_edges() {
let segid = SegmentId::with_part(0x3FFF_FFFF, (BLOCK_PTR_PARTED_MAX_PARTS - 1) as u16);
let offset: SegmentOffset = BLOCK_PTR_PARTED_MAX_PART_BYTES - 4096;
let ptr = BlockPtrFormat::encode(segid, offset, 0, &BlockPtrFormat::PartedSegment);
assert_eq!(BlockPtrFormat::decode(&ptr, &BlockPtrFormat::PartedSegment), (segid, offset));
}
#[test]
fn parted_and_unparted_do_not_share_a_reading() {
let ptr = BlockPtrFormat::encode(
SegmentId::with_part(7, 3), 8192, 0, &BlockPtrFormat::PartedSegment);
let (parted, _) = BlockPtrFormat::decode(&ptr, &BlockPtrFormat::PartedSegment);
assert_eq!(parted.part_id(), Some(3));
let (plain, _) = BlockPtrFormat::decode(&ptr, &BlockPtrFormat::MicroGroup);
assert_eq!(plain.part_id(), None);
}
#[test]
fn only_the_parted_format_is_parted() {
assert!(BlockPtrFormat::PartedSegment.is_parted());
assert!(!BlockPtrFormat::MicroGroup.is_parted());
assert!(!BlockPtrFormat::Flat.is_parted());
assert!(!BlockPtrFormat::Nop.is_parted());
}
#[test]
fn summary_part_is_zero_and_data_starts_after_it() {
use crate::segment::Segment;
assert_eq!(Segment::SUMMARY_PART, 0);
assert_eq!(Segment::FIRST_DATA_PART, 1);
assert!(Segment::FIRST_DATA_PART > Segment::SUMMARY_PART);
}
#[test]
fn micro_group_round_trip_basic() {
let segid = SegmentId::new(5);
let offset: SegmentOffset = 8192; let seq = 32; let ptr = BlockPtrFormat::encode(segid, offset, seq, &BlockPtrFormat::MicroGroup);
assert!(BlockPtrFormat::is_on_staging(&ptr));
assert_eq!(BlockPtrFormat::decode(&ptr, &BlockPtrFormat::MicroGroup), (segid, offset));
}
#[test]
fn micro_group_round_trip_zero_offset() {
let ptr = BlockPtrFormat::encode(SegmentId::new(1), 0, 0, &BlockPtrFormat::MicroGroup);
assert_eq!(BlockPtrFormat::decode(&ptr, &BlockPtrFormat::MicroGroup), (SegmentId::new(1), 0));
}
#[test]
fn micro_group_group_id() {
let seq = 48; let ptr = BlockPtrFormat::encode(SegmentId::new(1), 4096, seq, &BlockPtrFormat::MicroGroup);
assert_eq!(BlockPtrFormat::decode_micro_group_id(&ptr), 3);
}
#[test]
fn decode_segid_consistent_across_formats() {
let segid = SegmentId::new(42);
let flat_ptr = BlockPtrFormat::encode(segid, 4096, 0, &BlockPtrFormat::Flat);
let mg_ptr = BlockPtrFormat::encode(segid, 4096, 0, &BlockPtrFormat::MicroGroup);
assert_eq!(BlockPtrFormat::decode_segid(&flat_ptr), segid);
assert_eq!(BlockPtrFormat::decode_segid(&mg_ptr), segid);
}
#[test]
fn nop_encode_decode() {
let ptr = BlockPtrFormat::encode(SegmentId::new(99), 1234, 0, &BlockPtrFormat::Nop);
assert_eq!(ptr, 0);
assert_eq!(BlockPtrFormat::decode(&ptr, &BlockPtrFormat::Nop), (SegmentId::new(0), 0));
}
}