extern crate alloc;
use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use super::prefix::AllocatedExtranoncePrefix;
use super::{bitvector::BitVector, bytes_needed, MAX_EXTRANONCE_LEN};
pub struct ExtranonceAllocator {
upstream_prefix: Vec<u8>,
local_prefix_bytes: Vec<u8>,
total_extranonce_len: u8,
allocation_bitmap: Arc<BitVector>,
last_allocated_index: Option<usize>,
}
impl core::fmt::Debug for ExtranonceAllocator {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ExtranonceAllocator")
.field("upstream_prefix_len", &self.upstream_prefix_len())
.field("local_prefix_len", &self.local_prefix_len())
.field("local_index_len", &self.local_index_len())
.field("rollable_extranonce_size", &self.rollable_extranonce_size())
.field("total_extranonce_len", &self.total_extranonce_len)
.field("max_channels", &self.max_channels())
.field("allocated_count", &self.allocation_bitmap.count_ones())
.finish()
}
}
impl ExtranonceAllocator {
pub fn new(
local_prefix_bytes: Vec<u8>,
total_extranonce_len: u8,
max_channels: u32,
) -> Result<Self, ExtranonceAllocatorError> {
Self::validate_and_create(
Vec::new(),
local_prefix_bytes,
total_extranonce_len,
max_channels,
)
}
pub fn from_upstream_prefix(
upstream_prefix_bytes: Vec<u8>,
local_prefix_bytes: Vec<u8>,
total_extranonce_len: u8,
max_channels: u32,
) -> Result<Self, ExtranonceAllocatorError> {
Self::validate_and_create(
upstream_prefix_bytes,
local_prefix_bytes,
total_extranonce_len,
max_channels,
)
}
pub fn allocate_extended(
&mut self,
min_rollable_size: usize,
) -> Result<AllocatedExtranoncePrefix, ExtranonceAllocatorError> {
if min_rollable_size > self.rollable_extranonce_size() as usize {
return Err(ExtranonceAllocatorError::InvalidRollableSize);
}
let idx = self
.find_free_index()
.ok_or(ExtranonceAllocatorError::CapacityExhausted)?;
self.mark_allocated(idx);
Ok(AllocatedExtranoncePrefix::from_allocation(
idx as u32,
self.upstream_prefix_len(),
self.build_extended_prefix(idx),
Arc::downgrade(&self.allocation_bitmap),
))
}
pub fn allocate_standard(
&mut self,
) -> Result<AllocatedExtranoncePrefix, ExtranonceAllocatorError> {
let idx = self
.find_free_index()
.ok_or(ExtranonceAllocatorError::CapacityExhausted)?;
self.mark_allocated(idx);
Ok(AllocatedExtranoncePrefix::from_allocation(
idx as u32,
self.upstream_prefix_len(),
self.build_standard_prefix(idx),
Arc::downgrade(&self.allocation_bitmap),
))
}
#[inline]
pub fn upstream_prefix(&self) -> &[u8] {
&self.upstream_prefix
}
#[inline]
pub fn upstream_prefix_len(&self) -> u8 {
self.upstream_prefix.len() as u8
}
pub fn set_upstream_prefix(
&mut self,
upstream_prefix_bytes: Vec<u8>,
) -> Result<(), ExtranonceAllocatorError> {
let total_extranonce_len = upstream_prefix_bytes.len()
+ self.local_prefix_len() as usize
+ self.local_index_len() as usize
+ self.rollable_extranonce_size() as usize;
if total_extranonce_len > MAX_EXTRANONCE_LEN as usize {
return Err(ExtranonceAllocatorError::ExceedsMaxLength);
}
self.upstream_prefix = upstream_prefix_bytes;
self.total_extranonce_len = total_extranonce_len as u8;
Ok(())
}
#[inline]
pub fn local_prefix(&self) -> &[u8] {
&self.local_prefix_bytes
}
#[inline]
pub fn local_prefix_len(&self) -> u8 {
self.local_prefix_bytes.len() as u8
}
#[inline]
pub fn local_index_len(&self) -> u8 {
bytes_needed(self.max_channels())
}
#[inline]
pub fn rollable_extranonce_size(&self) -> u8 {
self.total_extranonce_len
- self.upstream_prefix_len()
- self.local_prefix_len()
- self.local_index_len()
}
#[inline]
pub fn full_prefix_len(&self) -> u8 {
self.upstream_prefix_len() + self.local_prefix_len() + self.local_index_len()
}
#[inline]
pub fn total_extranonce_len(&self) -> u8 {
self.total_extranonce_len
}
#[inline]
pub fn allocated_count(&self) -> u32 {
self.allocation_bitmap.count_ones()
}
#[inline]
pub fn max_channels(&self) -> u32 {
self.allocation_bitmap.capacity() as u32
}
fn validate_and_create(
upstream_prefix: Vec<u8>,
local_prefix_bytes: Vec<u8>,
total_extranonce_len: u8,
max_channels: u32,
) -> Result<Self, ExtranonceAllocatorError> {
if total_extranonce_len > MAX_EXTRANONCE_LEN {
return Err(ExtranonceAllocatorError::ExceedsMaxLength);
}
if max_channels == 0 {
return Err(ExtranonceAllocatorError::ZeroMaxChannels);
}
let local_index_len = bytes_needed(max_channels) as usize;
let needed = upstream_prefix.len() + local_prefix_bytes.len() + local_index_len;
if needed > total_extranonce_len as usize {
return Err(ExtranonceAllocatorError::PrefixExceedsTotalLength);
}
let allocation_bitmap = Arc::new(BitVector::new(max_channels as usize));
Ok(Self {
upstream_prefix,
local_prefix_bytes,
total_extranonce_len,
allocation_bitmap,
last_allocated_index: None,
})
}
#[inline]
fn mark_allocated(&mut self, index: usize) {
self.allocation_bitmap.set(index, true);
self.last_allocated_index = Some(index);
}
fn find_free_index(&self) -> Option<usize> {
let max = self.max_channels() as usize;
let start = self.last_allocated_index.map_or(0, |idx| (idx + 1) % max);
self.allocation_bitmap
.find_first_zero_in_range(start, max)
.or_else(|| {
if start > 0 {
self.allocation_bitmap.find_first_zero_in_range(0, start)
} else {
None
}
})
}
fn build_extended_prefix(&self, local_index: usize) -> Vec<u8> {
let mut prefix = Vec::with_capacity(self.full_prefix_len() as usize);
prefix.extend_from_slice(&self.upstream_prefix);
prefix.extend_from_slice(&self.local_prefix_bytes);
prefix.extend_from_slice(&Self::local_index_to_bytes(
local_index,
self.local_index_len() as usize,
));
prefix
}
fn build_standard_prefix(&self, local_index: usize) -> Vec<u8> {
let mut prefix = self.build_extended_prefix(local_index);
prefix.resize(self.total_extranonce_len as usize, 0);
prefix
}
fn local_index_to_bytes(local_index: usize, len: usize) -> Vec<u8> {
let mut result = vec![0u8; len];
let be_bytes = local_index.to_be_bytes();
let copy_len = be_bytes.len().min(len);
let src_start = be_bytes.len() - copy_len;
let dst_start = len - copy_len;
result[dst_start..].copy_from_slice(&be_bytes[src_start..]);
result
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExtranonceAllocatorError {
ExceedsMaxLength,
ZeroMaxChannels,
PrefixExceedsTotalLength,
CapacityExhausted,
InvalidRollableSize,
}
impl core::fmt::Display for ExtranonceAllocatorError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::ExceedsMaxLength => {
write!(f, "total_extranonce_len exceeds {MAX_EXTRANONCE_LEN} bytes")
}
Self::ZeroMaxChannels => write!(f, "max_channels must be greater than zero"),
Self::PrefixExceedsTotalLength => {
write!(
f,
"upstream_prefix + local_prefix + local_index exceeds total_extranonce_len"
)
}
Self::CapacityExhausted => write!(f, "all channels are allocated — no more capacity"),
Self::InvalidRollableSize => {
write!(f, "requested rollable size exceeds available space")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::extranonce_manager::ExtranoncePrefix;
use std::collections::HashSet;
#[test]
fn pool_basic_allocation() {
let mut alloc = ExtranonceAllocator::new(vec![0x00, 0x01], 20, 65_536).unwrap();
assert_eq!(alloc.total_extranonce_len(), 20);
assert_eq!(alloc.upstream_prefix_len(), 0);
assert_eq!(alloc.local_prefix(), &[0x00, 0x01]);
assert_eq!(alloc.local_prefix_len(), 2);
assert_eq!(alloc.local_index_len(), 2);
assert_eq!(alloc.full_prefix_len(), 4);
assert_eq!(alloc.rollable_extranonce_size(), 16);
assert_eq!(alloc.max_channels(), 65_536);
assert_eq!(alloc.allocated_count(), 0);
let ext = alloc.allocate_extended(16).unwrap();
assert_eq!(ext.len(), 4);
assert_eq!(&ext.as_bytes()[0..2], &[0x00, 0x01]);
assert_eq!(alloc.allocated_count(), 1);
let std = alloc.allocate_standard().unwrap();
assert_eq!(std.len(), 20);
assert_eq!(&std.as_bytes()[0..2], &[0x00, 0x01]);
assert_eq!(alloc.allocated_count(), 2);
}
#[test]
fn proxy_no_local_prefix() {
let upstream_prefix = vec![0xAA, 0xBB, 0xCC, 0xDD];
let mut alloc = ExtranonceAllocator::from_upstream_prefix(
upstream_prefix.clone(),
Vec::new(),
20,
65_536,
)
.unwrap();
assert_eq!(alloc.upstream_prefix(), &[0xAA, 0xBB, 0xCC, 0xDD]);
assert_eq!(alloc.upstream_prefix_len(), 4);
assert_eq!(alloc.local_prefix(), &[] as &[u8]);
assert_eq!(alloc.local_prefix_len(), 0);
assert_eq!(alloc.local_index_len(), 2);
assert_eq!(alloc.full_prefix_len(), 6);
assert_eq!(alloc.rollable_extranonce_size(), 14);
let ext = alloc.allocate_extended(14).unwrap();
assert_eq!(ext.len(), 6);
assert_eq!(&ext.as_bytes()[0..4], &[0xAA, 0xBB, 0xCC, 0xDD]);
}
#[test]
fn proxy_with_local_prefix_padding() {
let upstream_prefix = vec![0xAA, 0xBB, 0xCC, 0xDD];
let local_prefix_bytes = vec![0x42; 11];
let mut alloc = ExtranonceAllocator::from_upstream_prefix(
upstream_prefix.clone(),
local_prefix_bytes.clone(),
20,
256,
)
.unwrap();
assert_eq!(alloc.upstream_prefix_len(), 4);
assert_eq!(alloc.local_prefix_len(), 11);
assert_eq!(alloc.local_index_len(), 1);
assert_eq!(alloc.full_prefix_len(), 16);
assert_eq!(alloc.rollable_extranonce_size(), 4);
let ext = alloc.allocate_extended(4).unwrap();
assert_eq!(ext.len(), 16);
assert_eq!(&ext.as_bytes()[0..4], upstream_prefix.as_slice());
assert_eq!(&ext.as_bytes()[4..15], local_prefix_bytes.as_slice());
assert_eq!(ext.as_bytes()[15], 0);
}
#[test]
fn uniqueness() {
let mut alloc = ExtranonceAllocator::new(Vec::new(), 20, 256).unwrap();
let mut seen = HashSet::new();
let mut prefixes = Vec::with_capacity(256);
for _ in 0..256 {
let p = alloc.allocate_extended(19).unwrap();
assert!(seen.insert(p.as_bytes().to_vec()), "duplicate prefix");
prefixes.push(p);
}
assert_eq!(alloc.allocated_count(), 256);
drop(prefixes);
assert_eq!(alloc.allocated_count(), 0);
}
#[test]
fn exhaustion_and_reuse() {
let mut alloc = ExtranonceAllocator::new(Vec::new(), 6, 256).unwrap();
let mut prefixes = Vec::new();
for _ in 0..256 {
prefixes.push(alloc.allocate_extended(5).unwrap());
}
assert!(alloc.allocate_extended(5).is_err());
assert_eq!(alloc.allocated_count(), 256);
let freed_bytes = prefixes[42].as_bytes().to_vec();
drop(prefixes.remove(42));
assert_eq!(alloc.allocated_count(), 255);
let reused = alloc.allocate_extended(5).unwrap();
assert_eq!(reused.as_bytes(), freed_bytes.as_slice());
assert_eq!(alloc.allocated_count(), 256);
drop(prefixes);
drop(reused);
assert_eq!(alloc.allocated_count(), 0);
}
#[test]
fn drop_frees_and_allows_reuse() {
let mut alloc = ExtranonceAllocator::new(Vec::new(), 6, 4).unwrap();
let p0 = alloc.allocate_extended(5).unwrap();
let p1 = alloc.allocate_extended(5).unwrap();
let p2 = alloc.allocate_extended(5).unwrap();
let p3 = alloc.allocate_extended(5).unwrap();
assert_eq!(alloc.allocated_count(), 4);
assert!(alloc.allocate_extended(5).is_err());
let freed_bytes = p1.as_bytes().to_vec();
drop(p1);
assert_eq!(alloc.allocated_count(), 3);
let reused = alloc.allocate_extended(5).unwrap();
assert_eq!(reused.as_bytes(), freed_bytes.as_slice());
drop(p0);
drop(p2);
drop(p3);
drop(reused);
assert_eq!(alloc.allocated_count(), 0);
}
#[test]
fn drop_reuses_first_available_index() {
let mut alloc = ExtranonceAllocator::new(Vec::new(), 6, 256).unwrap();
let mut prefixes = Vec::new();
for _ in 0..256 {
prefixes.push(alloc.allocate_extended(5).unwrap());
}
let freed_bytes = prefixes[10].as_bytes().to_vec();
drop(prefixes.remove(20));
drop(prefixes.remove(10));
let reused = alloc.allocate_extended(5).unwrap();
assert_eq!(reused.as_bytes(), freed_bytes.as_slice());
drop(prefixes);
drop(reused);
assert_eq!(alloc.allocated_count(), 0);
}
#[test]
fn prefix_drop_frees_slot_on_scope_exit() {
let mut alloc = ExtranonceAllocator::new(Vec::new(), 6, 4).unwrap();
{
let _p = alloc.allocate_extended(5).unwrap();
assert_eq!(alloc.allocated_count(), 1);
}
assert_eq!(alloc.allocated_count(), 0);
}
#[test]
fn prefix_outliving_allocator_is_safe() {
let alloc = ExtranonceAllocator::new(Vec::new(), 6, 4)
.unwrap()
.allocate_extended(5)
.unwrap();
drop(alloc);
}
#[test]
fn standard_prefix_includes_rollable_zeros() {
let mut alloc = ExtranonceAllocator::new(vec![0x01], 20, 256).unwrap();
let std = alloc.allocate_standard().unwrap();
assert_eq!(std.len(), 20);
assert!(std.as_bytes()[2..].iter().all(|&b| b == 0));
}
#[test]
fn invalid_rollable_size() {
let mut alloc = ExtranonceAllocator::new(Vec::new(), 20, 256).unwrap();
let err = alloc.allocate_extended(20).unwrap_err();
assert_eq!(err, ExtranonceAllocatorError::InvalidRollableSize);
}
#[test]
fn validation_errors() {
assert_eq!(
ExtranonceAllocator::new(Vec::new(), 33, 256).unwrap_err(),
ExtranonceAllocatorError::ExceedsMaxLength
);
assert_eq!(
ExtranonceAllocator::new(Vec::new(), 20, 0).unwrap_err(),
ExtranonceAllocatorError::ZeroMaxChannels
);
assert_eq!(
ExtranonceAllocator::new(vec![0x01, 0x02], 2, 256).unwrap_err(),
ExtranonceAllocatorError::PrefixExceedsTotalLength
);
assert_eq!(
ExtranonceAllocator::from_upstream_prefix(vec![0; 20], Vec::new(), 20, 256)
.unwrap_err(),
ExtranonceAllocatorError::PrefixExceedsTotalLength
);
assert_eq!(
ExtranonceAllocator::from_upstream_prefix(vec![0; 4], vec![0; 16], 20, 256)
.unwrap_err(),
ExtranonceAllocatorError::PrefixExceedsTotalLength
);
}
#[test]
fn no_overlap_standard_and_extended() {
let mut alloc = ExtranonceAllocator::new(Vec::new(), 8, 256).unwrap();
let ext = alloc.allocate_extended(7).unwrap();
let std = alloc.allocate_standard().unwrap();
assert_ne!(&ext.as_bytes()[..1], &std.as_bytes()[..1]);
}
#[test]
fn upstream_prefix_update_preserves_layout_and_allocations() {
let mut alloc =
ExtranonceAllocator::from_upstream_prefix(vec![0xaa], vec![0xbb], 6, 256).unwrap();
let mut first: ExtranoncePrefix = alloc.allocate_extended(3).unwrap().into();
let mut second: ExtranoncePrefix = alloc.allocate_extended(3).unwrap().into();
alloc.set_upstream_prefix(vec![0xcc, 0xdd]).unwrap();
first.set_upstream_prefix(alloc.upstream_prefix()).unwrap();
second.set_upstream_prefix(alloc.upstream_prefix()).unwrap();
assert_eq!(alloc.upstream_prefix(), &[0xcc, 0xdd]);
assert_eq!(alloc.local_prefix(), &[0xbb]);
assert_eq!(alloc.local_index_len(), 1);
assert_eq!(alloc.rollable_extranonce_size(), 3);
assert_eq!(alloc.total_extranonce_len(), 7);
assert_eq!(alloc.allocated_count(), 2);
assert_eq!(first.as_bytes(), &[0xcc, 0xdd, 0xbb, 0x00]);
assert_eq!(second.as_bytes(), &[0xcc, 0xdd, 0xbb, 0x01]);
assert_eq!(first.upstream_prefix_len(), 2);
let third = alloc.allocate_extended(3).unwrap();
assert_eq!(third.as_bytes(), &[0xcc, 0xdd, 0xbb, 0x02]);
assert_eq!(alloc.allocated_count(), 3);
drop(first);
drop(second);
drop(third);
assert_eq!(alloc.allocated_count(), 0);
}
#[test]
fn invalid_upstream_prefix_update_leaves_allocator_unchanged() {
let mut alloc =
ExtranonceAllocator::from_upstream_prefix(vec![0xaa], vec![0xbb], 32, 256).unwrap();
assert_eq!(
alloc.set_upstream_prefix(vec![0xcc, 0xdd]),
Err(ExtranonceAllocatorError::ExceedsMaxLength)
);
assert_eq!(alloc.upstream_prefix(), &[0xaa]);
assert_eq!(alloc.total_extranonce_len(), 32);
assert_eq!(alloc.rollable_extranonce_size(), 29);
}
}