#![no_std]
#![warn(clippy::all)]
#![warn(missing_docs)]
extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
use core::{iter, num::NonZeroU16, num::NonZeroUsize};
use block::{
Block, DataBlock, GlobalBlock, SlotHeaderBlock, SlotState, deserialize_block, serialize_block,
};
#[cfg(test)]
mod test;
#[cfg(test)]
pub(crate) mod test_storage;
mod block;
mod sector_storage;
use sector_storage::{SectorError, SectorStorage};
#[derive(Debug, Clone, Copy)]
pub struct StorageInfo {
pub size: usize,
pub erase_size: Option<NonZeroUsize>,
pub write_size: NonZeroUsize,
}
pub trait StorageMedium {
type Error;
fn info(&self) -> StorageInfo;
fn read(&mut self, offset: usize, buf: &mut [u8]) -> Result<(), Self::Error>;
fn erase(&mut self, offset: usize, len: usize) -> Result<(), Self::Error>;
fn write(&mut self, offset: usize, data: &[u8]) -> Result<(), Self::Error>;
fn verify(&mut self, offset: usize, expected: &[u8]) -> Result<bool, Self::Error> {
let mut buf = vec![0u8; expected.len()];
self.read(offset, &mut buf)?;
Ok(buf == expected)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SlotStatus {
Empty,
Valid,
Corrupted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Slot<'a, Metadata> {
Empty,
Valid(&'a Metadata),
Corrupted,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SaveError<StorageError> {
Storage(StorageError),
SlotEmpty,
SlotCorrupted,
OutOfSpace,
Serialization(SerializationError),
VerificationFailed,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SerializationError(postcard::Error);
impl<StorageError> SaveError<StorageError> {
fn from_postcard_serialization(e: postcard::Error) -> Self {
Self::Serialization(SerializationError(e))
}
fn from_data_verify_error(e: DataVerifyError) -> Self {
match e {
DataVerifyError::Deserialization(e) => Self::Serialization(SerializationError(e)),
DataVerifyError::LengthMismatch | DataVerifyError::CrcMismatch => Self::SlotCorrupted,
}
}
}
#[derive(Debug)]
enum DataVerifyError {
LengthMismatch,
CrcMismatch,
Deserialization(postcard::Error),
}
fn verify_and_deserialize_data<T>(
data: &[u8],
expected_length: u32,
expected_crc32: u32,
) -> Result<T, DataVerifyError>
where
T: serde::de::DeserializeOwned,
{
let expected_len = expected_length as usize;
if data.len() != expected_len {
return Err(DataVerifyError::LengthMismatch);
}
let actual_crc = calc_crc32(data);
if actual_crc != expected_crc32 {
return Err(DataVerifyError::CrcMismatch);
}
postcard::from_bytes(data).map_err(DataVerifyError::Deserialization)
}
impl<T> From<T> for SaveError<T> {
fn from(value: T) -> Self {
Self::Storage(value)
}
}
impl<T> From<SectorError<T>> for SaveError<T> {
fn from(value: SectorError<T>) -> Self {
match value {
SectorError::Storage(e) => SaveError::Storage(e),
SectorError::VerificationFailed => SaveError::VerificationFailed,
}
}
}
pub struct SaveSlotManager<Storage: StorageMedium, Metadata> {
num_slots: usize,
storage: SectorStorage<Storage>,
magic: [u8; 32],
slot_info: Vec<SlotInfo<Metadata>>,
free_sector_list: Vec<u16>,
ghost_sector: u16,
}
struct SlotInfo<Metadata> {
status: SlotStatus,
metadata: Option<Metadata>,
generation: u32,
first_data_block: Option<NonZeroU16>,
first_metadata_block: Option<NonZeroU16>,
data_length: u32,
data_crc32: u32,
header_sector: u16,
}
impl<Metadata> SlotInfo<Metadata> {
fn empty(generation: u32, header_sector: u16) -> Self {
Self {
status: SlotStatus::Empty,
metadata: None,
generation,
first_data_block: None,
first_metadata_block: None,
data_length: 0,
data_crc32: 0,
header_sector,
}
}
fn corrupted(header_sector: u16) -> Self {
Self {
status: SlotStatus::Corrupted,
metadata: None,
generation: 0,
first_data_block: None,
first_metadata_block: None,
data_length: 0,
data_crc32: 0,
header_sector,
}
}
fn valid(
metadata: Metadata,
generation: u32,
first_data_block: Option<NonZeroU16>,
first_metadata_block: Option<NonZeroU16>,
data_length: u32,
data_crc32: u32,
header_sector: u16,
) -> Self {
Self {
status: SlotStatus::Valid,
metadata: Some(metadata),
generation,
first_data_block,
first_metadata_block,
data_length,
data_crc32,
header_sector,
}
}
}
struct GhostRecoveryInfo {
generation: u32,
first_data_block: Option<NonZeroU16>,
first_metadata_block: Option<NonZeroU16>,
data_length: u32,
data_crc32: u32,
metadata_bytes: Vec<u8>,
metadata_length: u32,
metadata_crc32: u32,
physical_sector: u16,
}
impl<Storage, Metadata> SaveSlotManager<Storage, Metadata>
where
Storage: StorageMedium,
Metadata: serde::Serialize + serde::de::DeserializeOwned + Clone,
{
pub fn new(
storage: Storage,
num_slots: usize,
magic: [u8; 32],
) -> Result<Self, SaveError<Storage::Error>> {
let mut manager = Self {
num_slots,
storage: SectorStorage::new(storage),
magic,
slot_info: Vec::new(),
free_sector_list: Vec::new(),
ghost_sector: 0, };
manager.initialise()?;
Ok(manager)
}
pub fn num_slots(&self) -> usize {
self.num_slots
}
pub fn slot(&self, slot: usize) -> Slot<'_, Metadata> {
let info = &self.slot_info[slot];
match info.status {
SlotStatus::Empty => Slot::Empty,
SlotStatus::Valid => Slot::Valid(info.metadata.as_ref().unwrap()),
SlotStatus::Corrupted => Slot::Corrupted,
}
}
pub fn metadata(&self, slot: usize) -> Option<&Metadata> {
self.slot_info[slot].metadata.as_ref()
}
pub fn read<T>(&mut self, slot: usize) -> Result<T, SaveError<Storage::Error>>
where
T: serde::de::DeserializeOwned,
{
match self.slot_info[slot].status {
SlotStatus::Empty => Err(SaveError::SlotEmpty),
SlotStatus::Corrupted => Err(SaveError::SlotCorrupted),
SlotStatus::Valid => self.read_slot_data(slot),
}
}
pub fn write<T>(
&mut self,
slot: usize,
data: &T,
metadata: &Metadata,
) -> Result<(), SaveError<Storage::Error>>
where
T: serde::Serialize,
{
assert!(slot < self.num_slots, "slot index {slot} out of bounds");
self.write_slot_data(slot, data, metadata)
}
pub fn erase(&mut self, slot: usize) -> Result<(), SaveError<Storage::Error>> {
assert!(slot < self.num_slots, "slot index {slot} out of bounds");
self.erase_slot(slot)
}
pub fn slots(&self) -> impl Iterator<Item = Slot<'_, Metadata>> {
self.slot_info.iter().map(|info| match info.status {
SlotStatus::Empty => Slot::Empty,
SlotStatus::Valid => Slot::Valid(info.metadata.as_ref().unwrap()),
SlotStatus::Corrupted => Slot::Corrupted,
})
}
#[cfg(test)]
pub(crate) fn into_storage(self) -> Storage {
self.storage.into_inner()
}
fn initialise(&mut self) -> Result<(), SaveError<Storage::Error>> {
let sector_size = self.storage.sector_size();
let mut buffer = vec![0u8; sector_size];
self.storage.read_sector(0, &mut buffer)?;
let needs_format = match deserialize_block(&buffer) {
Ok(Block::Global(global)) => {
global.game_identifier[..32] != self.magic
|| global.slot_count() as usize != self.num_slots
}
_ => true, };
if needs_format {
self.format_storage()?;
} else {
self.load_slot_headers()?;
}
Ok(())
}
fn format_storage(&mut self) -> Result<(), SaveError<Storage::Error>> {
let sector_size = self.storage.sector_size();
let mut buffer = vec![0u8; sector_size];
serialize_block(
Block::Global(GlobalBlock::new(self.num_slots as u16, &self.magic)),
&mut buffer,
);
self.storage.write_sector(0, &buffer)?;
let metadata_size = sector_size - SlotHeaderBlock::header_size();
let empty_metadata = vec![0u8; metadata_size];
for slot in 0..self.num_slots {
buffer.fill(0);
serialize_block(
Block::SlotHeader(SlotHeaderBlock::empty(slot as u8, &empty_metadata)),
&mut buffer,
);
self.storage.write_sector(slot + 1, &buffer)?;
}
buffer.fill(0);
serialize_block(
Block::SlotHeader(SlotHeaderBlock::ghost(0xFF, &empty_metadata)),
&mut buffer,
);
self.storage.write_sector(self.num_slots + 1, &buffer)?;
self.slot_info.clear();
for slot in 0..self.num_slots {
self.slot_info.push(SlotInfo::empty(0, (slot + 1) as u16));
}
self.ghost_sector = (self.num_slots + 1) as u16;
let first_data_sector = self.num_slots + 2;
let sector_count = self.storage.sector_count();
self.free_sector_list.clear();
for sector in first_data_sector..sector_count {
self.free_sector_list.push(sector as u16);
}
Ok(())
}
fn load_slot_headers(&mut self) -> Result<(), SaveError<Storage::Error>> {
self.initialize_slots_as_corrupted();
let ghost_recovery = self.scan_slot_headers()?;
self.recover_from_ghosts(ghost_recovery);
self.ghost_sector = self.find_unused_header_sector();
self.build_free_sector_list()
}
fn initialize_slots_as_corrupted(&mut self) {
self.slot_info.clear();
for slot in 0..self.num_slots {
self.slot_info.push(SlotInfo::corrupted((slot + 1) as u16));
}
}
fn scan_slot_headers(
&mut self,
) -> Result<Vec<Option<GhostRecoveryInfo>>, SaveError<Storage::Error>> {
let sector_size = self.storage.sector_size();
let mut buffer = vec![0u8; sector_size];
let mut ghost_recovery: Vec<Option<GhostRecoveryInfo>> =
(0..self.num_slots).map(|_| None).collect();
self.ghost_sector = (self.num_slots + 1) as u16;
let num_slot_header_sectors = self.num_slots + 1;
for sector in 0..num_slot_header_sectors {
let physical_sector = (sector + 1) as u16;
self.storage
.read_sector(physical_sector as usize, &mut buffer)?;
if let Ok(Block::SlotHeader(slot_block)) = deserialize_block(&buffer) {
self.process_slot_block(&slot_block, physical_sector, &mut ghost_recovery)?;
}
}
Ok(ghost_recovery)
}
fn process_slot_block(
&mut self,
slot_block: &SlotHeaderBlock,
physical_sector: u16,
ghost_recovery: &mut [Option<GhostRecoveryInfo>],
) -> Result<(), SaveError<Storage::Error>> {
if slot_block.state() == SlotState::Ghost {
self.ghost_sector = physical_sector;
let logical_id = slot_block.logical_slot_id() as usize;
if logical_id < self.num_slots {
ghost_recovery[logical_id] = Some(GhostRecoveryInfo {
generation: slot_block.generation(),
first_data_block: slot_block.first_data_block(),
first_metadata_block: slot_block.first_metadata_block(),
data_length: slot_block.length(),
data_crc32: slot_block.crc32(),
metadata_bytes: slot_block.metadata().to_vec(),
metadata_length: slot_block.metadata_length(),
metadata_crc32: slot_block.metadata_crc32(),
physical_sector,
});
}
return Ok(());
}
let logical_id = slot_block.logical_slot_id() as usize;
if logical_id >= self.num_slots {
return Ok(());
}
let existing = &self.slot_info[logical_id];
if existing.status == SlotStatus::Corrupted || slot_block.generation() > existing.generation
{
let (status, metadata) = match slot_block.state() {
SlotState::Empty => (SlotStatus::Empty, None),
SlotState::Valid => {
let metadata_len = slot_block.metadata_length();
let metadata_bytes = slot_block.metadata();
match self.read_complete_metadata(
metadata_bytes,
metadata_len,
slot_block.metadata_crc32(),
slot_block.first_metadata_block(),
)? {
Some(m) => (SlotStatus::Valid, Some(m)),
None => (SlotStatus::Corrupted, None),
}
}
SlotState::Ghost => unreachable!(), };
self.slot_info[logical_id] = SlotInfo {
status,
metadata,
generation: slot_block.generation(),
first_data_block: slot_block.first_data_block(),
first_metadata_block: slot_block.first_metadata_block(),
data_length: slot_block.length(),
data_crc32: slot_block.crc32(),
header_sector: physical_sector,
};
}
Ok(())
}
fn recover_from_ghosts(&mut self, ghost_recovery: Vec<Option<GhostRecoveryInfo>>) {
for (slot, ghost_info) in ghost_recovery.into_iter().enumerate() {
if self.slot_info[slot].status == SlotStatus::Corrupted
&& let Some(ghost) = ghost_info
{
if ghost.generation < self.slot_info[slot].generation {
continue;
}
if let Ok(Some(metadata)) = self.read_complete_metadata(
&ghost.metadata_bytes,
ghost.metadata_length,
ghost.metadata_crc32,
ghost.first_metadata_block,
) {
self.slot_info[slot] = SlotInfo::valid(
metadata,
ghost.generation,
ghost.first_data_block,
ghost.first_metadata_block,
ghost.data_length,
ghost.data_crc32,
ghost.physical_sector,
);
}
}
}
}
fn find_unused_header_sector(&self) -> u16 {
let used_header_sectors: Vec<u16> = self
.slot_info
.iter()
.map(|info| info.header_sector)
.collect();
for sector in 1..=(self.num_slots + 1) as u16 {
if !used_header_sectors.contains(§or) {
return sector;
}
}
(self.num_slots + 1) as u16
}
fn build_free_sector_list(&mut self) -> Result<(), SaveError<Storage::Error>> {
self.free_sector_list.clear();
let first_data_sector = self.num_slots + 2;
let sector_count = self.storage.sector_count();
let mut used_sectors = vec![false; sector_count];
for used in used_sectors.iter_mut().take(first_data_sector) {
*used = true;
}
for slot_index in 0..self.slot_info.len() {
let slot_info = &self.slot_info[slot_index];
if slot_info.status != SlotStatus::Valid {
continue;
}
let first_data_block = slot_info.first_data_block;
let first_metadata_block = slot_info.first_metadata_block;
let Some(data_sectors) = self.collect_chain_sectors(first_data_block)? else {
self.slot_info[slot_index].status = SlotStatus::Corrupted;
continue;
};
let Some(metadata_sectors) = self.collect_chain_sectors(first_metadata_block)? else {
self.slot_info[slot_index].status = SlotStatus::Corrupted;
continue;
};
for sector_idx in data_sectors {
used_sectors[sector_idx] = true;
}
for sector_idx in metadata_sectors {
used_sectors[sector_idx] = true;
}
}
self.free_sector_list.clear();
for (sector, &used) in used_sectors.iter().enumerate() {
if !used {
self.free_sector_list.push(sector as u16);
}
}
Ok(())
}
fn collect_chain_sectors(
&mut self,
start_sector: Option<NonZeroU16>,
) -> Result<Option<Vec<usize>>, SaveError<Storage::Error>> {
let sector_count = self.storage.sector_count();
let sector_size = self.storage.sector_size();
let mut buffer = vec![0u8; sector_size];
let mut sectors = Vec::new();
let mut current_sector = start_sector;
while let Some(sector) = current_sector {
let sector_idx = sector.get() as usize;
if sector_idx >= sector_count || sectors.contains(§or_idx) {
return Ok(None);
}
sectors.push(sector_idx);
self.storage.read_sector(sector_idx, &mut buffer)?;
match deserialize_block(&buffer) {
Ok(Block::Data(data_block)) => {
current_sector = data_block.next_block();
}
_ => {
return Ok(None);
}
}
}
Ok(Some(sectors))
}
fn write_data_blocks(
&mut self,
data: &[u8],
) -> Result<Option<NonZeroU16>, SaveError<Storage::Error>> {
if data.is_empty() {
return Ok(None);
}
let sector_size = self.storage.sector_size();
let payload_size = sector_size - DataBlock::header_size();
let sectors_needed = data.len().div_ceil(payload_size);
if self.free_sector_list.len() < sectors_needed {
return Err(SaveError::OutOfSpace);
}
let allocated_sectors: Vec<u16> = self
.free_sector_list
.drain(self.free_sector_list.len() - sectors_needed..)
.collect();
let mut buffer = vec![0u8; sector_size];
let mut padded_data = vec![0u8; payload_size];
let mut data_offset = 0;
for (i, §or) in allocated_sectors.iter().enumerate() {
let is_last = i == allocated_sectors.len() - 1;
let next_block = if is_last {
None
} else {
NonZeroU16::new(allocated_sectors[i + 1])
};
let chunk_size = (data.len() - data_offset).min(payload_size);
let chunk = &data[data_offset..data_offset + chunk_size];
padded_data[..chunk_size].copy_from_slice(chunk);
padded_data[chunk_size..].fill(0);
buffer.fill(0);
serialize_block(
Block::Data(DataBlock::new(next_block, &padded_data)),
&mut buffer,
);
self.storage.write_sector(sector as usize, &buffer)?;
data_offset += chunk_size;
}
Ok(NonZeroU16::new(allocated_sectors[0]))
}
fn read_block_chain(
&mut self,
start_block: Option<NonZeroU16>,
data: &mut Vec<u8>,
size: usize,
) -> Result<(), SaveError<Storage::Error>> {
let sector_size = self.storage.sector_size();
let payload_size = sector_size - DataBlock::header_size();
let mut buffer = vec![0u8; sector_size];
let mut current_block = start_block;
let max_blocks = self.storage.sector_count();
let mut blocks_read = 0;
while let Some(block) = current_block {
if data.len() >= size {
break;
}
blocks_read += 1;
if blocks_read > max_blocks {
return Err(SaveError::SlotCorrupted);
}
self.storage
.read_sector(block.get() as usize, &mut buffer)?;
match deserialize_block(&buffer) {
Ok(Block::Data(data_block)) => {
let remaining = size.saturating_sub(data.len());
let to_copy = remaining.min(payload_size);
data.extend_from_slice(&data_block.data[..to_copy]);
current_block = data_block.next_block();
}
_ => return Err(SaveError::SlotCorrupted),
}
}
Ok(())
}
fn read_complete_metadata(
&mut self,
inline_metadata: &[u8],
metadata_length: u32,
metadata_crc32: u32,
first_metadata_block: Option<NonZeroU16>,
) -> Result<Option<Metadata>, SaveError<Storage::Error>> {
let metadata_len = metadata_length as usize;
let mut metadata = Vec::with_capacity(metadata_len);
let inline_portion = metadata_len.min(inline_metadata.len());
metadata.extend_from_slice(&inline_metadata[..inline_portion]);
if first_metadata_block.is_some() && metadata.len() < metadata_len {
self.read_block_chain(first_metadata_block, &mut metadata, metadata_len)?;
}
match verify_and_deserialize_data(&metadata, metadata_length, metadata_crc32) {
Ok(m) => Ok(Some(m)),
Err(_) => Ok(None),
}
}
fn read_slot_data<T>(&mut self, slot: usize) -> Result<T, SaveError<Storage::Error>>
where
T: serde::de::DeserializeOwned,
{
let slot_info = &self.slot_info[slot];
let first_data_block = slot_info.first_data_block;
let data_length = slot_info.data_length;
let expected_crc32 = slot_info.data_crc32;
if first_data_block.is_none() {
if data_length == 0 {
return postcard::from_bytes(&[]).map_err(SaveError::from_postcard_serialization);
} else {
return Err(SaveError::SlotCorrupted);
}
}
let mut data = Vec::with_capacity(data_length as usize);
self.read_block_chain(first_data_block, &mut data, data_length as usize)?;
verify_and_deserialize_data(&data, data_length, expected_crc32)
.map_err(SaveError::from_data_verify_error)
}
fn write_slot_data<T>(
&mut self,
slot: usize,
data: &T,
metadata: &Metadata,
) -> Result<(), SaveError<Storage::Error>>
where
T: serde::Serialize,
{
let data_bytes =
postcard::to_allocvec(data).map_err(SaveError::from_postcard_serialization)?;
let data_crc32 = calc_crc32(&data_bytes);
let data_length = data_bytes.len() as u32;
let first_data_block = self.write_data_blocks(&data_bytes)?;
let sector_size = self.storage.sector_size();
let mut serialized_metadata =
postcard::to_allocvec(metadata).map_err(SaveError::from_postcard_serialization)?;
let metadata_length = serialized_metadata.len() as u32;
let metadata_crc32 = calc_crc32(&serialized_metadata);
let metadata_in_header_size = self.storage.sector_size() - SlotHeaderBlock::header_size();
let first_metadata_block = if serialized_metadata.len() > metadata_in_header_size {
self.write_data_blocks(&serialized_metadata[metadata_in_header_size..])?
} else {
None
};
if serialized_metadata.len() < metadata_in_header_size {
serialized_metadata.extend(iter::repeat_n(
0,
metadata_in_header_size - serialized_metadata.len(),
));
}
let new_generation = self.slot_info[slot].generation.wrapping_add(1);
let old_header_sector = self.slot_info[slot].header_sector;
let old_first_data_block = self.slot_info[slot].first_data_block;
let mut buffer = vec![0u8; sector_size];
serialize_block(
Block::SlotHeader(SlotHeaderBlock::valid(
slot as u8,
first_data_block,
first_metadata_block, new_generation,
data_crc32,
data_length,
metadata_length,
metadata_crc32,
&serialized_metadata[..metadata_in_header_size],
)),
&mut buffer,
);
self.storage
.write_sector(self.ghost_sector as usize, &buffer)?;
self.storage
.read_sector(old_header_sector as usize, &mut buffer)?;
if let Ok(Block::SlotHeader(old_block)) = deserialize_block(&buffer) {
let old_metadata = old_block.metadata().to_vec();
let ghost_block = SlotHeaderBlock::valid(
old_block.logical_slot_id(),
old_block.first_data_block(),
old_block.first_metadata_block(),
old_block.generation(),
old_block.crc32(),
old_block.length(),
old_block.metadata_length(),
old_block.metadata_crc32(),
&old_metadata,
)
.with_state(SlotState::Ghost);
serialize_block(Block::SlotHeader(ghost_block), &mut buffer);
self.storage
.write_sector(old_header_sector as usize, &buffer)?;
}
let new_header_sector = self.ghost_sector;
self.ghost_sector = old_header_sector;
self.free_data_chain(old_first_data_block, &mut buffer)?;
self.slot_info[slot] = SlotInfo::valid(
metadata.clone(),
new_generation,
first_data_block,
None, data_length,
data_crc32,
new_header_sector,
);
Ok(())
}
fn free_data_chain(
&mut self,
first_block: Option<NonZeroU16>,
buffer: &mut [u8],
) -> Result<(), SaveError<Storage::Error>> {
let mut current_block = first_block;
while let Some(block) = current_block {
self.storage.read_sector(block.get() as usize, buffer)?;
let next_block = match deserialize_block(buffer) {
Ok(Block::Data(data_block)) => data_block.next_block(),
_ => None, };
self.free_sector_list.push(block.get());
current_block = next_block;
}
Ok(())
}
fn erase_slot(&mut self, slot: usize) -> Result<(), SaveError<Storage::Error>> {
let sector_size = self.storage.sector_size();
let metadata_size = sector_size - SlotHeaderBlock::header_size();
let old_first_data_block = self.slot_info[slot].first_data_block;
let header_sector = self.slot_info[slot].header_sector;
let new_generation = self.slot_info[slot].generation.wrapping_add(1);
let mut buffer = vec![0u8; sector_size];
let empty_metadata = vec![0u8; metadata_size];
serialize_block(
Block::SlotHeader(SlotHeaderBlock::empty_with_generation(
slot as u8,
new_generation,
&empty_metadata,
)),
&mut buffer,
);
self.storage.write_sector(header_sector as usize, &buffer)?;
self.free_data_chain(old_first_data_block, &mut buffer)?;
self.slot_info[slot] = SlotInfo::empty(new_generation, header_sector);
Ok(())
}
}
fn calc_crc32(bytes: &[u8]) -> u32 {
let mut crc: u32 = 0xFFFF_FFFF;
for &b in bytes {
crc ^= b as u32;
for _ in 0..8 {
let mask = (crc & 1).wrapping_neg();
crc = (crc >> 1) ^ (0xEDB8_8320 & mask);
}
}
!crc
}