1use core::mem::{MaybeUninit, size_of};
6use core::slice;
7use core::sync::atomic::{AtomicU64, Ordering};
8use zx_status::Status;
9
10#[derive(Copy, Clone, Debug, PartialEq, Eq)]
12pub enum AllocateError {
13 InvalidHeader,
17 NonEmptyIndex,
20 OutOfMemory,
23}
24
25impl From<AllocateError> for Status {
26 fn from(err: AllocateError) -> Self {
27 match err {
28 AllocateError::OutOfMemory => Status::NO_MEMORY,
29 AllocateError::InvalidHeader | AllocateError::NonEmptyIndex => {
30 Status::IO_DATA_INTEGRITY
31 }
32 }
33 }
34}
35
36#[derive(Copy, Clone, Debug, PartialEq, Eq)]
38pub enum AllocateErrorWith<E> {
39 Error(AllocateError),
41 Copy(E),
43}
44
45impl<E> From<AllocateErrorWith<E>> for Status
46where
47 Status: From<E>,
48{
49 fn from(err: AllocateErrorWith<E>) -> Self {
50 match err {
51 AllocateErrorWith::Error(e) => e.into(),
52 AllocateErrorWith::Copy(e) => Status::from(e),
53 }
54 }
55}
56
57#[derive(Copy, Clone, Debug, PartialEq, Eq)]
59pub enum BlobError {
60 InvalidHeader,
64 UnallocatedId,
66 UncommittedIndex,
72 InvalidIndex,
75}
76
77impl From<BlobError> for Status {
78 fn from(err: BlobError) -> Self {
79 match err {
80 BlobError::UnallocatedId => Status::NOT_FOUND,
81 BlobError::UncommittedIndex => Status::SHOULD_WAIT,
82 BlobError::InvalidHeader | BlobError::InvalidIndex => Status::IO_DATA_INTEGRITY,
83 }
84 }
85}
86
87#[derive(Copy, Clone, Debug, PartialEq, Eq)]
89pub enum ZeroFill {
90 No,
91 Yes,
92}
93
94#[repr(C)]
96#[derive(Copy, Clone, Debug, PartialEq, Eq)]
97pub struct Header {
98 pub next_id: u32,
99 pub blob_head: u32,
100}
101
102const _: () = assert!(size_of::<Header>() == size_of::<u64>());
103
104impl Header {
105 pub const SIZE: usize = size_of::<Self>();
106
107 pub fn from_u64(val: u64) -> Self {
108 Self { next_id: val as u32, blob_head: (val >> 32) as u32 }
109 }
110
111 pub fn to_u64(self) -> u64 {
112 (self.next_id as u64) | ((self.blob_head as u64) << 32)
113 }
114
115 pub fn index_end(self) -> Option<u32> {
118 self.next_id.checked_mul(Index::SIZE as u32)?.checked_add(Header::SIZE as u32)
119 }
120
121 pub fn is_valid(self, length: usize) -> bool {
123 self.remaining_bytes(length).is_some()
124 }
125
126 pub fn remaining_bytes(self, length: usize) -> Option<usize> {
129 let end = self.index_end()?;
130 if end <= self.blob_head && (self.blob_head as usize) <= length {
131 Some((self.blob_head - end) as usize)
132 } else {
133 None
134 }
135 }
136}
137
138#[repr(C)]
140#[derive(Copy, Clone, Debug, PartialEq, Eq)]
141pub struct Index {
142 pub size: u32,
143 pub offset: u32,
144}
145
146const _: () = assert!(size_of::<Index>() == size_of::<u64>());
147
148impl Index {
149 pub const SIZE: usize = size_of::<Self>();
150
151 pub fn from_u64(val: u64) -> Self {
152 Self { size: val as u32, offset: (val >> 32) as u32 }
153 }
154
155 pub fn to_u64(self) -> u64 {
156 (self.size as u64) | ((self.offset as u64) << 32)
157 }
158
159 pub fn is_valid(self, blob_head: u32, length: usize) -> bool {
162 debug_assert!((blob_head as usize) <= length);
163 (blob_head <= self.offset)
164 && (self.offset as usize <= length)
165 && (self.size as usize <= length - (self.offset as usize))
166 }
167}
168
169#[derive(Clone, Copy, Debug)]
199pub struct BlobIdAllocator<'a> {
200 bytes: &'a [u8],
201}
202
203impl<'a> BlobIdAllocator<'a> {
204 pub fn from_slice(slice: &'a [u8]) -> Self {
208 debug_assert!(slice.as_ptr().cast::<Header>().is_aligned());
209 debug_assert!((Header::SIZE..=u32::MAX as usize).contains(&slice.len()));
210 Self { bytes: slice }
211 }
212
213 pub fn init_from_slice(slice: &'a mut [u8], zero_fill: ZeroFill) -> Self {
220 debug_assert!((Header::SIZE..=u32::MAX as usize).contains(&slice.len()));
221 if zero_fill == ZeroFill::Yes {
222 slice[Header::SIZE..].fill(0);
223 }
224 let allocator = Self::from_slice(slice);
225 allocator.store_header(Header { next_id: 0, blob_head: allocator.bytes.len() as u32 });
226 allocator
227 }
228
229 pub fn next_id(&self) -> u32 {
231 self.load_header().next_id
232 }
233
234 pub fn remaining_bytes(&self) -> Option<usize> {
238 self.load_header().remaining_bytes(self.bytes.len())
239 }
240
241 pub fn allocate(&self, blob: &[u8]) -> Result<u32, AllocateError> {
243 self.allocate_with(blob.len(), |dest| {
244 for (d, s) in dest.iter_mut().zip(blob) {
245 d.write(*s);
246 }
247 Ok::<(), core::convert::Infallible>(())
248 })
249 .map_err(|e| match e {
250 AllocateErrorWith::Error(err) => err,
251 AllocateErrorWith::Copy(infallible) => match infallible {},
252 })
253 }
254
255 pub fn allocate_with<E>(
262 &self,
263 blob_size: usize,
264 copy: impl FnOnce(&mut [MaybeUninit<u8>]) -> Result<(), E>,
265 ) -> Result<u32, AllocateErrorWith<E>> {
266 let header_atomic = self.header_atomic();
267 let mut raw_hdr = header_atomic.load(Ordering::Acquire);
268 let (id, offset) = loop {
269 let hdr = Header::from_u64(raw_hdr);
270 let remaining = hdr
271 .remaining_bytes(self.bytes.len())
272 .ok_or(AllocateErrorWith::Error(AllocateError::InvalidHeader))?;
273 if remaining < Index::SIZE || remaining - Index::SIZE < blob_size {
274 return Err(AllocateErrorWith::Error(AllocateError::OutOfMemory));
275 }
276 let offset = hdr.blob_head - (blob_size as u32);
277 let updated = Header { next_id: hdr.next_id + 1, blob_head: offset };
278 match header_atomic.compare_exchange_weak(
279 raw_hdr,
280 updated.to_u64(),
281 Ordering::AcqRel,
282 Ordering::Acquire,
283 ) {
284 Ok(_) => break (hdr.next_id, offset),
285 Err(actual) => raw_hdr = actual,
286 }
287 };
288
289 let dest_slice = unsafe {
297 let dest_ptr = self.bytes.as_ptr().add(offset as usize).cast_mut().cast();
298 slice::from_raw_parts_mut(dest_ptr, blob_size)
299 };
300 copy(dest_slice).map_err(AllocateErrorWith::Copy)?;
301
302 let new_index = Index { size: blob_size as u32, offset };
304 unsafe { self.index_atomic(id) }
307 .compare_exchange(0, new_index.to_u64(), Ordering::Release, Ordering::Relaxed)
308 .map_err(|_| AllocateErrorWith::Error(AllocateError::NonEmptyIndex))?;
309 Ok(id)
310 }
311
312 pub fn get_blob(&self, id: u32) -> Result<&'a [u8], BlobError> {
314 let hdr = self.load_header();
315 if !hdr.is_valid(self.bytes.len()) {
316 return Err(BlobError::InvalidHeader);
317 }
318 if id >= hdr.next_id {
319 return Err(BlobError::UnallocatedId);
320 }
321 let index_raw = unsafe { self.index_atomic(id) }.load(Ordering::Acquire);
329 if index_raw == 0 {
330 return Err(BlobError::UncommittedIndex);
331 }
332 let index = Index::from_u64(index_raw);
333 if !index.is_valid(hdr.blob_head, self.bytes.len()) {
334 return Err(BlobError::InvalidIndex);
335 }
336 let offset = index.offset as usize;
337 let size = index.size as usize;
338 Ok(&self.bytes[offset..offset + size])
339 }
340
341 pub fn iter(&self) -> Iter<'a> {
343 Iter { allocator: *self, next_id: 0 }
344 }
345
346 fn header_atomic(&self) -> &AtomicU64 {
347 unsafe { &*self.bytes.as_ptr().cast::<AtomicU64>() }
353 }
354
355 unsafe fn index_atomic(&self, id: u32) -> &AtomicU64 {
362 let index_offset = (id as usize + 1) * Index::SIZE;
363 unsafe { &*self.bytes.as_ptr().add(index_offset).cast::<AtomicU64>() }
369 }
370
371 fn load_header(&self) -> Header {
372 Header::from_u64(self.header_atomic().load(Ordering::Relaxed))
373 }
374
375 fn store_header(&self, header: Header) {
376 self.header_atomic().store(header.to_u64(), Ordering::Release);
377 }
378}
379
380pub struct Iter<'a> {
382 allocator: BlobIdAllocator<'a>,
383 next_id: u32,
384}
385
386impl<'a> Iterator for Iter<'a> {
387 type Item = Result<(u32, &'a [u8]), BlobError>;
388
389 fn next(&mut self) -> Option<Self::Item> {
390 let max_id = self.allocator.next_id();
391 if self.next_id >= max_id {
392 return None;
393 }
394 let id = self.next_id;
395 self.next_id += 1;
396 Some(self.allocator.get_blob(id).map(|blob| (id, blob)))
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403 use core::ptr;
404 use std::vec::Vec;
405 use std::{thread, vec};
406
407 #[test]
408 fn test_header_and_index() {
409 let hdr = Header { next_id: 42, blob_head: 4096 };
410 let raw = hdr.to_u64();
411 let decoded = Header::from_u64(raw);
412 assert_eq!(hdr, decoded);
413
414 assert_eq!(Header { next_id: 0, blob_head: 4096 }.index_end(), Some(8));
415 assert_eq!(Header { next_id: 1, blob_head: 4096 }.index_end(), Some(16));
416 assert_eq!(Header { next_id: 5, blob_head: 4096 }.index_end(), Some(48));
417
418 const MAX_NEXT_ID: u32 = (u32::MAX - 8) / 8;
419 assert_eq!(
420 Header { next_id: MAX_NEXT_ID, blob_head: 4096 }.index_end(),
421 Some(8 + MAX_NEXT_ID * 8)
422 );
423 assert_eq!(Header { next_id: MAX_NEXT_ID + 1, blob_head: 4096 }.index_end(), None);
424
425 assert_eq!(Header { next_id: 0, blob_head: 1024 }.remaining_bytes(1024), Some(1024 - 8));
426 assert_eq!(Header { next_id: 1, blob_head: 1000 }.remaining_bytes(1024), Some(1000 - 16));
427 assert_eq!(Header { next_id: 0, blob_head: 8 }.remaining_bytes(8), Some(0));
428 assert_eq!(Header { next_id: 1, blob_head: 15 }.remaining_bytes(1024), None);
429 assert_eq!(Header { next_id: 0, blob_head: 2000 }.remaining_bytes(1024), None);
430
431 let idx = Index { size: 100, offset: 500 };
432 let raw_idx = idx.to_u64();
433 assert_eq!(raw_idx, 100 | (500u64 << 32));
434 assert_eq!(Index::from_u64(raw_idx), idx);
435 assert!(idx.is_valid(400, 1024));
436 assert!(!idx.is_valid(600, 1024));
437 assert!(!idx.is_valid(400, 550));
438 }
439
440 #[test]
441 fn test_single_threaded() {
442 let blob_a = [b'a'; 51];
443 let blob_b = [b'b'; 17];
444 let blob_c = [b'c'; 1];
445
446 let mut buffer = [0u64; 100 / 8 + 1];
447 let slice = unsafe { slice::from_raw_parts_mut(buffer.as_mut_ptr().cast::<u8>(), 100) };
448 let allocator = BlobIdAllocator::init_from_slice(slice, ZeroFill::Yes);
449
450 assert_eq!(allocator.remaining_bytes(), Some(92)); let id_a = allocator.allocate(&blob_a).expect("allocate A");
453 assert_eq!(id_a, 0);
454 assert_eq!(allocator.remaining_bytes(), Some(33)); let res_a = allocator.get_blob(0).expect("get A");
457 assert_eq!(res_a, &blob_a[..]);
458
459 let id_b = allocator.allocate(&blob_b).expect("allocate B");
460 assert_eq!(id_b, 1);
461 assert_eq!(allocator.remaining_bytes(), Some(8)); let res_b = allocator.get_blob(1).expect("get B");
464 assert_eq!(res_b, &blob_b[..]);
465
466 assert_eq!(allocator.allocate(&blob_c), Err(AllocateError::OutOfMemory));
468
469 let items: Vec<(u32, &[u8])> = allocator.iter().map(Result::unwrap).collect();
470 assert_eq!(items.len(), 2);
471 assert_eq!(items[0], (0, &blob_a[..]));
472 assert_eq!(items[1], (1, &blob_b[..]));
473 }
474
475 #[test]
476 fn test_multi_threaded() {
477 const NUM_THREADS: usize = 100;
478 const BUF_SIZE: usize = 8 + NUM_THREADS * 8 + NUM_THREADS * 1;
479 let mut buffer = vec![0u64; BUF_SIZE / 8 + 1];
480 let slice =
481 unsafe { slice::from_raw_parts_mut(buffer.as_mut_ptr().cast::<u8>(), BUF_SIZE) };
482 let allocator = BlobIdAllocator::init_from_slice(slice, ZeroFill::Yes);
483
484 let mut ids: Vec<u32> = thread::scope(|s| {
485 let mut handles = Vec::with_capacity(NUM_THREADS);
486 for i in 0..NUM_THREADS {
487 handles.push(s.spawn(move || {
488 let byte = [i as u8];
489 allocator.allocate(&byte).expect("allocate in thread")
490 }));
491 }
492 handles.into_iter().map(|h| h.join().unwrap()).collect()
493 });
494 ids.sort();
495 for (expected, actual) in ids.iter().enumerate() {
496 assert_eq!(expected as u32, *actual);
497 }
498
499 let mut blob_values: Vec<u8> = allocator
500 .iter()
501 .map(|res| {
502 let (_id, blob) = res.unwrap();
503 assert_eq!(blob.len(), 1);
504 blob[0]
505 })
506 .collect();
507 assert_eq!(blob_values.len(), NUM_THREADS);
508 blob_values.sort();
509 for (i, val) in blob_values.iter().enumerate() {
510 assert_eq!(i as u8, *val);
511 }
512 }
513
514 #[test]
515 fn test_corruption_detection() {
516 let mut buffer = [0u64; 16];
517 let slice = unsafe { slice::from_raw_parts_mut(buffer.as_mut_ptr().cast::<u8>(), 128) };
518 let allocator = BlobIdAllocator::init_from_slice(slice, ZeroFill::No);
519
520 let corrupted_hdr = Header { next_id: 1, blob_head: 8 };
522 unsafe {
523 ptr::write_volatile(buffer.as_mut_ptr(), corrupted_hdr.to_u64());
524 }
525
526 assert_eq!(allocator.allocate(&[0u8; 4]), Err(AllocateError::InvalidHeader));
527 assert_eq!(allocator.get_blob(0), Err(BlobError::InvalidHeader));
528
529 let slice = unsafe { slice::from_raw_parts_mut(buffer.as_mut_ptr().cast::<u8>(), 128) };
531 let allocator = BlobIdAllocator::init_from_slice(slice, ZeroFill::No);
532 unsafe {
533 ptr::write_volatile(buffer.as_mut_ptr().add(1), 0x1234);
535 }
536 assert_eq!(allocator.allocate(&[0u8; 4]), Err(AllocateError::NonEmptyIndex));
537 }
538
539 #[test]
540 fn test_overflowing_next_id_is_invalid() {
541 let mut buffer = [0u64; 512];
542 let slice = unsafe { slice::from_raw_parts_mut(buffer.as_mut_ptr().cast::<u8>(), 4096) };
543 let allocator = BlobIdAllocator::init_from_slice(slice, ZeroFill::Yes);
544
545 unsafe {
548 let raw = buffer.as_mut_ptr() as *mut u32;
549 ptr::write_volatile(raw, 0x2000_0000);
550 }
551
552 assert_eq!(allocator.remaining_bytes(), None);
553 assert_eq!(allocator.allocate(&[0u8; 8]), Err(AllocateError::InvalidHeader));
554 assert_eq!(allocator.get_blob(0), Err(BlobError::InvalidHeader));
555 }
556}