From 71b6e633b199cd4a2b469f6286970fca7a368e14 Mon Sep 17 00:00:00 2001 From: Elinor Berger Date: Fri, 6 Oct 2023 00:09:51 +0200 Subject: [PATCH] Re-introduce a token invalidation mechanism --- CHANGELOG.md | 4 + src/io.rs | 28 +++---- src/lib.rs | 2 + src/list.rs | 71 ++++++++++++++++ src/loop_logic.rs | 204 ++++++++++++++++++++++++---------------------- src/sys.rs | 91 ++++----------------- src/token.rs | 112 +++++++++++++++++++++++++ 7 files changed, 323 insertions(+), 189 deletions(-) create mode 100644 src/list.rs create mode 100644 src/token.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index dc6e702c..f11f1f19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +#### Additions + +- `Token` and `RegistrationToken` are now invalidated when the event source they represent is removed from the event loop. + #### Bugfixes - Fix an issue, where id-reuse could execute a PostAction on a newly registered event source diff --git a/src/io.rs b/src/io.rs index e8130069..5b37e331 100644 --- a/src/io.rs +++ b/src/io.rs @@ -19,9 +19,8 @@ use futures_io::{AsyncRead, AsyncWrite, IoSlice, IoSliceMut}; use crate::loop_logic::EventIterator; use crate::{ - loop_logic::{LoopInner, MAX_SOURCES_MASK}, - sources::EventDispatcher, - Interest, Mode, Poll, PostAction, Readiness, Token, TokenFactory, + loop_logic::LoopInner, sources::EventDispatcher, Interest, Mode, Poll, PostAction, Readiness, + Token, TokenFactory, }; use crate::{AdditionalLifecycleEventsSet, RegistrationToken}; @@ -62,8 +61,13 @@ impl<'l, F: AsFd> Async<'l, F> { interest: Interest::EMPTY, last_readiness: Readiness::EMPTY, })); - let key = inner.sources.borrow_mut().insert(Some(dispatcher.clone())); - dispatcher.borrow_mut().token = Some(Token { key }); + + { + let mut sources = inner.sources.borrow_mut(); + let slot = sources.vacant_entry(); + slot.source = Some(dispatcher.clone()); + dispatcher.borrow_mut().token = Some(Token { inner: slot.token }); + } // SAFETY: We are sure to deregister on drop. unsafe { @@ -198,17 +202,13 @@ impl<'l, Data> IoLoopInner for LoopInner<'l, Data> { } fn kill(&self, dispatcher: &RefCell) { - let key = dispatcher + let token = dispatcher .borrow() .token - .expect("No token for IO dispatcher") - .key - & MAX_SOURCES_MASK; - let _source = self - .sources - .borrow_mut() - .try_remove(key) - .expect("Attempting to remove a non-existent source?!"); + .expect("No token for IO dispatcher"); + if let Ok(slot) = self.sources.borrow_mut().get_mut(token.inner) { + slot.source = None; + } } } diff --git a/src/lib.rs b/src/lib.rs index 5e27cbec..ffb08a0a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -156,6 +156,8 @@ pub mod error; pub use error::{Error, InsertError, Result}; pub mod io; +mod list; mod loop_logic; mod macros; mod sources; +mod token; diff --git a/src/list.rs b/src/list.rs new file mode 100644 index 00000000..7efac873 --- /dev/null +++ b/src/list.rs @@ -0,0 +1,71 @@ +use std::rc::Rc; + +use crate::sources::EventDispatcher; +use crate::token::TokenInner; + +pub(crate) struct SourceEntry<'l, Data> { + pub(crate) token: TokenInner, + pub(crate) source: Option + 'l>>, +} + +pub(crate) struct SourceList<'l, Data> { + sources: Vec>, +} + +impl<'l, Data> SourceList<'l, Data> { + pub(crate) fn new() -> Self { + SourceList { + sources: Vec::new(), + } + } + + pub(crate) fn vacant_entry(&mut self) -> &mut SourceEntry<'l, Data> { + let opt_id = self.sources.iter().position(|slot| slot.source.is_none()); + match opt_id { + Some(id) => { + // we are reusing a slot + let slot = &mut self.sources[id]; + // increment the slot version + slot.token = slot.token.increment_version(); + slot + } + None => { + // we are inserting a new slot + let next_id = self.sources.len(); + self.sources.push(SourceEntry { + token: TokenInner::new(self.sources.len()) + .expect("Trying to insert too many sources in an event loop."), + source: None, + }); + &mut self.sources[next_id] + } + } + } + + pub(crate) fn get(&self, token: TokenInner) -> crate::Result<&SourceEntry<'l, Data>> { + let entry = self + .sources + .get(token.get_id()) + .ok_or(crate::Error::InvalidToken)?; + if entry.token.same_source_as(token) { + Ok(entry) + } else { + Err(crate::Error::InvalidToken) + } + } + + pub(crate) fn get_mut( + &mut self, + token: TokenInner, + ) -> crate::Result<&mut SourceEntry<'l, Data>> { + let entry = self + .sources + .get_mut(token.get_id()) + .ok_or(crate::Error::InvalidToken)?; + if entry.token.same_source_as(token) { + Ok(entry) + } else { + Err(crate::Error::InvalidToken) + } + } +} diff --git a/src/loop_logic.rs b/src/loop_logic.rs index 258848c5..1db8a534 100644 --- a/src/loop_logic.rs +++ b/src/loop_logic.rs @@ -10,43 +10,18 @@ use std::{io, slice}; #[cfg(feature = "block_on")] use std::future::Future; -use slab::Slab; - use log::trace; +use crate::list::{SourceEntry, SourceList}; use crate::sources::{Dispatcher, EventSource, Idle, IdleDispatcher}; use crate::sys::{Notifier, PollEvent}; +use crate::token::TokenInner; use crate::{ - AdditionalLifecycleEventsSet, EventDispatcher, InsertError, Poll, PostAction, Readiness, Token, - TokenFactory, + AdditionalLifecycleEventsSet, InsertError, Poll, PostAction, Readiness, Token, TokenFactory, }; type IdleCallback<'i, Data> = Rc + 'i>>; -// The number of bits used to store the source ID. -// -// This plus `MAX_SUBSOURCES` must equal the number of bits in `usize`. -#[cfg(target_pointer_width = "64")] -pub(crate) const MAX_SOURCES: u32 = 44; -#[cfg(target_pointer_width = "32")] -pub(crate) const MAX_SOURCES: u32 = 22; -#[cfg(target_pointer_width = "16")] -pub(crate) const MAX_SOURCES: u32 = 10; - -// The number of bits used to store the sub-source ID. -// -// This plus `MAX_SOURCES` must equal the number of bits in `usize`. -#[cfg(target_pointer_width = "64")] -pub(crate) const MAX_SUBSOURCES: u32 = 20; -#[cfg(target_pointer_width = "32")] -pub(crate) const MAX_SUBSOURCES: u32 = 10; -#[cfg(target_pointer_width = "16")] -pub(crate) const MAX_SUBSOURCES: u32 = 6; - -pub(crate) const MAX_SOURCES_TOTAL: usize = 1 << MAX_SOURCES; -pub(crate) const MAX_SUBSOURCES_TOTAL: usize = 1 << MAX_SUBSOURCES; -pub(crate) const MAX_SOURCES_MASK: usize = MAX_SOURCES_TOTAL - 1; - /// A token representing a registration in the [`EventLoop`]. /// /// This token is given to you by the [`EventLoop`] when an [`EventSource`] is inserted or @@ -55,15 +30,15 @@ pub(crate) const MAX_SOURCES_MASK: usize = MAX_SOURCES_TOTAL - 1; /// [remove](LoopHandle#method.remove) or [kill](LoopHandle#method.kill) it. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct RegistrationToken { - key: usize, + inner: TokenInner, } impl RegistrationToken { /// Create the RegistrationToken corresponding to the given raw key /// This is needed because some methods use `RegistrationToken`s as /// raw usizes within this crate - pub(crate) fn new(key: usize) -> Self { - Self { key } + pub(crate) fn new(inner: TokenInner) -> Self { + Self { inner } } } @@ -71,7 +46,7 @@ pub(crate) struct LoopInner<'l, Data> { pub(crate) poll: RefCell, // The `Option` is used to keep slots of the slab occipied, to prevent id reuse // while in-flight events might still referr to a recently destroyed event source. - pub(crate) sources: RefCell + 'l>>>>, + pub(crate) sources: RefCell>, pub(crate) sources_with_additional_lifecycle_events: RefCell, idles: RefCell>>, pending_action: Cell, @@ -143,31 +118,26 @@ impl<'l, Data> LoopHandle<'l, Data> { let mut sources = self.inner.sources.borrow_mut(); let mut poll = self.inner.poll.borrow_mut(); - // Make sure we won't overflow the token. - if sources.vacant_key() >= MAX_SOURCES_TOTAL { - return Err(crate::Error::IoError(std::io::Error::new( - std::io::ErrorKind::Other, - "Too many sources", - ))); - } + // Find an empty slot if any + let slot = sources.vacant_entry(); - let key = sources.insert(Some(dispatcher.clone_as_event_dispatcher())); - trace!("[calloop] Inserting new source #{}", key); - let ret = sources.get(key).unwrap().as_ref().unwrap().register( + slot.source = Some(dispatcher.clone_as_event_dispatcher()); + trace!("[calloop] Inserting new source #{}", slot.token.get_id()); + let ret = slot.source.as_ref().unwrap().register( &mut poll, &mut self .inner .sources_with_additional_lifecycle_events .borrow_mut(), - &mut TokenFactory::new(key), + &mut TokenFactory::new(slot.token), ); if let Err(error) = ret { - sources.try_remove(key).expect("Source was just inserted?!"); + slot.source = None; return Err(error); } - Ok(RegistrationToken { key }) + Ok(RegistrationToken { inner: slot.token }) } /// Inserts an idle callback. @@ -191,18 +161,23 @@ impl<'l, Data> LoopHandle<'l, Data> { /// /// **Note:** this cannot be done from within the source callback. pub fn enable(&self, token: &RegistrationToken) -> crate::Result<()> { - if let Some(Some(source)) = self.inner.sources.borrow().get(token.key) { - trace!("[calloop] Registering source #{}", token.key); + if let &SourceEntry { + token: entry_token, + source: Some(ref source), + } = self.inner.sources.borrow().get(token.inner)? + { + trace!("[calloop] Registering source #{}", entry_token.get_id()); source.register( &mut self.inner.poll.borrow_mut(), &mut self .inner .sources_with_additional_lifecycle_events .borrow_mut(), - &mut TokenFactory::new(token.key), - )?; + &mut TokenFactory::new(entry_token), + ) + } else { + Err(crate::Error::InvalidToken) } - Ok(()) } /// Makes this source update its registration. @@ -210,30 +185,47 @@ impl<'l, Data> LoopHandle<'l, Data> { /// If after accessing the source you changed its parameters in a way that requires /// updating its registration. pub fn update(&self, token: &RegistrationToken) -> crate::Result<()> { - if let Some(Some(source)) = self.inner.sources.borrow().get(token.key) { - trace!("[calloop] Updating registration of source #{}", token.key); + if let &SourceEntry { + token: entry_token, + source: Some(ref source), + } = self.inner.sources.borrow().get(token.inner)? + { + trace!( + "[calloop] Updating registration of source #{}", + entry_token.get_id() + ); if !source.reregister( &mut self.inner.poll.borrow_mut(), &mut self .inner .sources_with_additional_lifecycle_events .borrow_mut(), - &mut TokenFactory::new(token.key), + &mut TokenFactory::new(entry_token), )? { trace!("[calloop] Cannot do it now, storing for later."); // we are in a callback, store for later processing self.inner.pending_action.set(PostAction::Reregister); } + Ok(()) + } else { + Err(crate::Error::InvalidToken) } - Ok(()) } /// Disables this event source. /// /// The source remains in the event loop, but it'll no longer generate events pub fn disable(&self, token: &RegistrationToken) -> crate::Result<()> { - if let Some(Some(source)) = self.inner.sources.borrow().get(token.key) { - trace!("[calloop] Unregistering source #{}", token.key); + if let &SourceEntry { + token: entry_token, + source: Some(ref source), + } = self.inner.sources.borrow().get(token.inner)? + { + if !token.inner.same_source_as(entry_token) { + // The token provided by the user is no longer valid + return Err(crate::Error::InvalidToken); + } + trace!("[calloop] Unregistering source #{}", entry_token.get_id()); if !source.unregister( &mut self.inner.poll.borrow_mut(), &mut self @@ -246,18 +238,21 @@ impl<'l, Data> LoopHandle<'l, Data> { // we are in a callback, store for later processing self.inner.pending_action.set(PostAction::Disable); } + Ok(()) + } else { + Err(crate::Error::InvalidToken) } - Ok(()) } /// Removes this source from the event loop. pub fn remove(&self, token: RegistrationToken) { - if let Some(source) = self.inner.sources.borrow_mut().get_mut(token.key) { - // We intentionally leave `None` in-place, as this might be called from - // within an event source callback. It will get cleaned up on the end - // of the currently running or next `dispatch_events` call. + if let Ok(&mut SourceEntry { + token: entry_token, + ref mut source, + }) = self.inner.sources.borrow_mut().get_mut(token.inner) + { if let Some(source) = source.take() { - trace!("[calloop] Removing source #{}", token.key); + trace!("[calloop] Removing source #{}", entry_token.get_id()); if let Err(e) = source.unregister( &mut self.inner.poll.borrow_mut(), &mut self @@ -323,7 +318,7 @@ impl<'l, Data> EventLoop<'l, Data> { let handle = LoopHandle { inner: Rc::new(LoopInner { poll: RefCell::new(poll), - sources: RefCell::new(Slab::new()), + sources: RefCell::new(SourceList::new()), idles: RefCell::new(Vec::new()), pending_action: Cell::new(PostAction::Continue), sources_with_additional_lifecycle_events: Default::default(), @@ -360,7 +355,10 @@ impl<'l, Data> EventLoop<'l, Data> { .borrow_mut(); let sources = &self.handle.inner.sources.borrow(); for source in &mut *extra_lifecycle_sources.values { - if let Some(Some(disp)) = sources.get(source.key) { + if let Ok(SourceEntry { + source: Some(disp), .. + }) = sources.get(source.inner) + { if let Some((readiness, token)) = disp.before_sleep()? { // Wake up instantly after polling if we recieved an event timeout = Some(Duration::ZERO); @@ -401,7 +399,10 @@ impl<'l, Data> EventLoop<'l, Data> { .borrow_mut(); if !extra_lifecycle_sources.values.is_empty() { for source in &mut *extra_lifecycle_sources.values { - if let Some(Some(disp)) = self.handle.inner.sources.borrow().get(source.key) { + if let Ok(SourceEntry { + source: Some(disp), .. + }) = self.handle.inner.sources.borrow().get(source.inner) + { let iter = EventIterator { inner: events.iter(), registration_token: *source, @@ -416,20 +417,21 @@ impl<'l, Data> EventLoop<'l, Data> { for event in self.synthetic_events.drain(..).chain(events) { // Get the registration token associated with the event. - let registroken_token = event.token.key & MAX_SOURCES_MASK; + let reg_token = event.token.inner.forget_sub_id(); let opt_disp = self .handle .inner .sources .borrow() - .get(registroken_token) - .cloned(); + .get(reg_token) + .ok() + .and_then(|entry| entry.source.clone()); - if let Some(Some(disp)) = opt_disp { + if let Some(disp) = opt_disp { trace!( "[calloop] Dispatching events for source #{}", - registroken_token + reg_token.get_id() ); let mut ret = disp.process_events(event.readiness, event.token, data)?; @@ -447,7 +449,7 @@ impl<'l, Data> EventLoop<'l, Data> { PostAction::Reregister => { trace!( "[calloop] Postaction reregister for source #{}", - registroken_token + reg_token.get_id() ); disp.reregister( &mut self.handle.inner.poll.borrow_mut(), @@ -456,13 +458,13 @@ impl<'l, Data> EventLoop<'l, Data> { .inner .sources_with_additional_lifecycle_events .borrow_mut(), - &mut TokenFactory::new(registroken_token), + &mut TokenFactory::new(reg_token), )?; } PostAction::Disable => { trace!( "[calloop] Postaction unregister for source #{}", - registroken_token + reg_token.get_id() ); disp.unregister( &mut self.handle.inner.poll.borrow_mut(), @@ -471,25 +473,18 @@ impl<'l, Data> EventLoop<'l, Data> { .inner .sources_with_additional_lifecycle_events .borrow_mut(), - RegistrationToken::new(registroken_token), + RegistrationToken::new(reg_token), )?; } PostAction::Remove => { trace!( "[calloop] Postaction remove for source #{}", - registroken_token + reg_token.get_id() ); - // We intentionally leave `None` in-place, while there are still - // events being processed to prevent id reuse. - // Unregister will happen right after this match and cleanup at - // the end of the function. - self.handle - .inner - .sources - .borrow_mut() - .get_mut(registroken_token) - .unwrap_or(&mut None) - .take(); + if let Ok(entry) = self.handle.inner.sources.borrow_mut().get_mut(reg_token) + { + entry.source = None; + } } PostAction::Continue => {} } @@ -499,9 +494,10 @@ impl<'l, Data> EventLoop<'l, Data> { .inner .sources .borrow() - .get(registroken_token) - .unwrap_or(&None) - .is_none() + .get(reg_token) + .ok() + .map(|entry| entry.source.is_none()) + .unwrap_or(true) { // the source has been removed from within its callback, unregister it let mut poll = self.handle.inner.poll.borrow_mut(); @@ -512,7 +508,7 @@ impl<'l, Data> EventLoop<'l, Data> { .inner .sources_with_additional_lifecycle_events .borrow_mut(), - RegistrationToken::new(registroken_token), + RegistrationToken::new(reg_token), ) { log::warn!( "[calloop] Failed to unregister source from the polling system: {:?}", @@ -523,18 +519,11 @@ impl<'l, Data> EventLoop<'l, Data> { } else { log::warn!( "[calloop] Received an event for non-existence source: {:?}", - registroken_token + reg_token ); } } - // cleanup empty event source slots to free up ids again - self.handle - .inner - .sources - .borrow_mut() - .retain(|_, opt_disp| opt_disp.is_some()); - Ok(()) } @@ -688,7 +677,11 @@ impl<'a> Iterator for EventIterator<'a> { fn next(&mut self) -> Option { for next in self.inner.by_ref() { - if next.token.key & MAX_SOURCES_MASK == self.registration_token.key { + if next + .token + .inner + .same_source_as(self.registration_token.inner) + { return Some((next.readiness, next.token)); } } @@ -1118,6 +1111,19 @@ mod tests { assert!(ret.is_err()); } + #[test] + fn invalid_token() { + let (_ping, source) = crate::sources::ping::make_ping().unwrap(); + + let event_loop = EventLoop::<()>::try_new().unwrap(); + let handle = event_loop.handle(); + let reg_token = handle.insert_source(source, |_, _, _| {}).unwrap(); + handle.remove(reg_token); + + let ret = handle.enable(®_token); + assert!(ret.is_err()); + } + #[test] fn insert_source_no_interest() { use rustix::pipe::pipe; diff --git a/src/sys.rs b/src/sys.rs index 35e39fb3..d7a473c3 100644 --- a/src/sys.rs +++ b/src/sys.rs @@ -8,8 +8,8 @@ use std::os::windows::io::{AsRawSocket, AsSocket, BorrowedSocket as Borrowed, Ra use polling::{Event, Events, PollMode, Poller}; -use crate::loop_logic::{MAX_SOURCES, MAX_SUBSOURCES_TOTAL}; use crate::sources::timer::TimerWheel; +use crate::token::TokenInner; use crate::RegistrationToken; /// Possible modes for registering a file descriptor @@ -120,39 +120,26 @@ pub(crate) struct PollEvent { #[derive(Debug)] pub struct TokenFactory { - /// The key of the source this factory is associated with. - key: usize, - - /// The next sub-id to use. - sub_id: u32, + next_token: TokenInner, } impl TokenFactory { - pub(crate) fn new(key: usize) -> TokenFactory { - TokenFactory { key, sub_id: 0 } + pub(crate) fn new(token: TokenInner) -> TokenFactory { + TokenFactory { + next_token: token.forget_sub_id(), + } } /// Get the "raw" registration token of this TokenFactory pub(crate) fn registration_token(&self) -> RegistrationToken { - RegistrationToken::new(self.key) + RegistrationToken::new(self.next_token.forget_sub_id()) } /// Produce a new unique token pub fn token(&mut self) -> Token { - // Ensure we don't overflow the sub-id. - if self.sub_id >= MAX_SUBSOURCES_TOTAL as _ { - panic!("Too many sub-sources for this source"); - } - - // Compose the key and the sub-key together. - let mut key = self.key; - key |= (self.sub_id as usize) << MAX_SOURCES; - - let token = Token { key }; - - self.sub_id += 1; - - token + let token = self.next_token; + self.next_token = token.increment_sub_id(); + Token { inner: token } } } @@ -165,7 +152,7 @@ impl TokenFactory { /// You should forward it to the [`Poll`] when registering your file descriptors. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Token { - pub(crate) key: usize, + pub(crate) inner: TokenInner, } /// The polling system @@ -267,7 +254,9 @@ impl Poll { writable: ev.writable, error: false, }, - token: Token { key: ev.key }, + token: Token { + inner: TokenInner::from(ev.key), + }, }) }) .collect::>>()?; @@ -439,7 +428,7 @@ impl Notifier { } fn cvt_interest(interest: Interest, tok: Token) -> Event { - let mut event = Event::none(tok.key); + let mut event = Event::none(tok.inner.into()); event.readable = interest.readable; event.writable = interest.writable; event @@ -456,53 +445,3 @@ fn cvt_mode(mode: Mode, supports_other_modes: bool) -> PollMode { Mode::OneShot => PollMode::Oneshot, } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::{loop_logic::MAX_SOURCES_TOTAL, sources::ping::make_ping, EventSource}; - - #[should_panic] - #[test] - fn overflow_subid() { - let mut gen = TokenFactory { - key: 0, - sub_id: u32::MAX - 1, - }; - - let _ = gen.token(); - } - - #[test] - fn test_fallback_lt() { - let mut poll = Poll::new_inner(true).unwrap(); - let mut gen = TokenFactory { key: 0, sub_id: 0 }; - let (dst, mut src) = make_ping().unwrap(); - - src.register(&mut poll, &mut gen).unwrap(); - let mut key = 0; - - for _ in 0..2 { - // Send a ping. - dst.ping(); - - // The ping should arrive at this point. - let events = poll.poll(Some(Duration::from_secs(3))).unwrap(); - - assert_eq!(events.len(), 1); - assert_eq!(events[0].token, Token { key }); - - // Since we haven't read the ping, polling again should return the same result. - let events = poll.poll(Some(Duration::from_secs(3))).unwrap(); - assert_eq!(events.len(), 1); - assert_eq!(events[0].token, Token { key }); - - // Reregister and poll again. - src.reregister(&mut poll, &mut gen).unwrap(); - key += MAX_SOURCES_TOTAL; - } - - // Remove the source. - src.unregister(&mut poll).unwrap(); - } -} diff --git a/src/token.rs b/src/token.rs new file mode 100644 index 00000000..db55b427 --- /dev/null +++ b/src/token.rs @@ -0,0 +1,112 @@ +// Several implementations of the internals of `Token` depending on the size of `usize` + +use std::convert::TryInto; + +#[cfg(target_pointer_width = "64")] +const BITS_VERSION: usize = 16; +#[cfg(target_pointer_width = "64")] +const BITS_SUBID: usize = 16; + +#[cfg(target_pointer_width = "32")] +const BITS_VERSION: usize = 8; +#[cfg(target_pointer_width = "32")] +const BITS_SUBID: usize = 8; + +#[cfg(target_pointer_width = "16")] +const BITS_VERSION: usize = 4; +#[cfg(target_pointer_width = "16")] +const BITS_SUBID: usize = 4; + +const MASK_VERSION: usize = (1 << BITS_VERSION) - 1; +const MASK_SUBID: usize = (1 << BITS_SUBID) - 1; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(crate) struct TokenInner { + id: u32, + version: u16, + sub_id: u16, +} + +impl TokenInner { + pub(crate) fn new(id: usize) -> Result { + Ok(TokenInner { + id: id.try_into().map_err(|_| ())?, + version: 0, + sub_id: 0, + }) + } + + pub(crate) fn get_id(self) -> usize { + self.id as usize + } + + pub(crate) fn same_source_as(self, other: TokenInner) -> bool { + self.id == other.id && self.version == other.version + } + + pub(crate) fn increment_version(self) -> TokenInner { + TokenInner { + id: self.id, + version: self.version.wrapping_add(1) & (MASK_VERSION as u16), + sub_id: 0, + } + } + + pub(crate) fn increment_sub_id(self) -> TokenInner { + let sub_id = match self.sub_id.checked_add(1) { + Some(sid) if sid <= (MASK_SUBID as u16) => sid, + _ => panic!("Maximum number of sub-ids reached for source #{}", self.id), + }; + + TokenInner { + id: self.id, + version: self.version, + sub_id, + } + } + + pub(crate) fn forget_sub_id(self) -> TokenInner { + TokenInner { + id: self.id, + version: self.version, + sub_id: 0, + } + } +} + +impl From for TokenInner { + fn from(value: usize) -> Self { + let sub_id = (value & MASK_SUBID) as u16; + let version = ((value >> BITS_SUBID) & MASK_VERSION) as u16; + let id = (value >> (BITS_SUBID + BITS_VERSION)) as u32; + TokenInner { + id, + version, + sub_id, + } + } +} + +impl From for usize { + fn from(token: TokenInner) -> Self { + ((token.id as usize) << (BITS_SUBID + BITS_VERSION)) + + ((token.version as usize) << BITS_SUBID) + + (token.sub_id as usize) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[should_panic] + #[test] + fn overflow_subid() { + let token = TokenInner { + id: 0, + version: 0, + sub_id: MASK_SUBID as u16, + }; + token.increment_sub_id(); + } +}