use super::{ModuleTls, TlsImageSource, TlsInfo, TlsModuleId, TlsTpOffset};
use crate::{Result, TlsError};
use alloc::vec::Vec;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TlsStorage {
Dynamic,
Static(TlsTpOffset),
}
impl TlsStorage {
#[inline]
fn module(self, mod_id: TlsModuleId) -> ModuleTls {
match self {
Self::Dynamic => ModuleTls::Dynamic { mod_id },
Self::Static(tp_offset) => ModuleTls::Static { mod_id, tp_offset },
}
}
}
#[derive(Clone, Debug)]
pub struct TlsModuleSnapshot {
pub info: TlsInfo,
pub tls: ModuleTls,
pub source: Option<TlsImageSource>,
}
#[derive(Clone, Debug)]
pub struct TlsSlotSnapshot {
pub generation: usize,
pub module: Option<TlsModuleSnapshot>,
}
#[derive(Clone, Debug)]
pub struct TlsRegistrySnapshot {
pub generation: usize,
pub slots: Vec<TlsSlotSnapshot>,
}
#[derive(Debug)]
struct TlsModuleRecord<D> {
info: TlsInfo,
tls: ModuleTls,
source: Option<TlsImageSource>,
data: D,
}
impl<D> TlsModuleRecord<D> {
fn snapshot(&self) -> TlsModuleSnapshot {
TlsModuleSnapshot {
info: self.info,
tls: self.tls,
source: self.source.clone(),
}
}
}
#[derive(Debug)]
struct TlsSlot<D> {
generation: usize,
module: Option<TlsModuleRecord<D>>,
}
#[derive(Debug)]
pub struct TlsRegistry<D = ()> {
slots: Vec<TlsSlot<D>>,
generation: usize,
}
impl<D> TlsRegistry<D> {
pub const fn new() -> Self {
Self {
slots: Vec::new(),
generation: 0,
}
}
#[inline]
pub fn generation(&self) -> usize {
self.generation
}
pub fn register(&mut self, info: TlsInfo, storage: TlsStorage, data: D) -> Result<ModuleTls> {
info.validate()?;
if self.slots.is_empty() {
self.slots.push(TlsSlot {
generation: 0,
module: None,
});
}
self.generation += 1;
let index = self
.slots
.iter()
.enumerate()
.skip(1)
.find(|(_, slot)| slot.module.is_none())
.map(|(index, _)| index)
.unwrap_or(self.slots.len());
let module = storage.module(TlsModuleId::new(index));
let slot = TlsSlot {
generation: self.generation,
module: Some(TlsModuleRecord {
info,
tls: module,
source: None,
data,
}),
};
if index == self.slots.len() {
self.slots.push(slot);
} else {
self.slots[index] = slot;
}
Ok(module)
}
pub fn publish(&mut self, source: TlsImageSource, mod_id: TlsModuleId) -> Result<()> {
let slot = self
.slots
.get_mut(mod_id.get())
.ok_or(TlsError::InvalidModuleId { mod_id })?;
let record = slot
.module
.as_mut()
.ok_or(TlsError::InvalidModuleId { mod_id })?;
if record.source.is_some() {
return Err(TlsError::AlreadyPublished { mod_id }.into());
}
self.generation += 1;
record.source = Some(source);
slot.generation = self.generation;
Ok(())
}
pub fn unregister(&mut self, mod_id: TlsModuleId) -> bool {
let Some(slot) = self.slots.get_mut(mod_id.get()) else {
return false;
};
if slot.module.is_none() {
return false;
}
self.generation += 1;
slot.generation = self.generation;
slot.module = None;
true
}
pub fn module(&self, mod_id: TlsModuleId) -> Option<TlsModuleSnapshot> {
self.slots
.get(mod_id.get())?
.module
.as_ref()
.map(TlsModuleRecord::snapshot)
}
pub fn info(&self, mod_id: TlsModuleId) -> Option<TlsInfo> {
self.slots
.get(mod_id.get())?
.module
.as_ref()
.map(|module| module.info)
}
pub fn data(&self, mod_id: TlsModuleId) -> Option<&D> {
self.slots
.get(mod_id.get())?
.module
.as_ref()
.map(|module| &module.data)
}
pub fn data_mut(&mut self, mod_id: TlsModuleId) -> Option<&mut D> {
self.slots
.get_mut(mod_id.get())?
.module
.as_mut()
.map(|module| &mut module.data)
}
pub fn snapshot_since(&self, generation: usize) -> Option<TlsRegistrySnapshot> {
if self.generation == generation {
return None;
}
Some(self.snapshot())
}
pub fn snapshot(&self) -> TlsRegistrySnapshot {
TlsRegistrySnapshot {
generation: self.generation,
slots: self
.slots
.iter()
.map(|slot| TlsSlotSnapshot {
generation: slot.generation,
module: slot.module.as_ref().map(TlsModuleRecord::snapshot),
})
.collect(),
}
}
}
impl<D> Default for TlsRegistry<D> {
#[inline]
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
sync::{Arc, arc_unsize},
tls::TlsImageProvider,
};
struct TestImage;
impl TlsImageProvider for TestImage {
fn with_tls_image(&self, f: &mut dyn FnMut(&[u8]) -> Result<()>) -> Result<()> {
f(&[0; 4])
}
}
fn info() -> TlsInfo {
TlsInfo {
vaddr: 0,
filesz: 4,
memsz: 8,
align: 4,
}
}
#[test]
fn unregister_changes_generation_and_reuses_slot() {
let mut registry = TlsRegistry::new();
let first = registry.register(info(), TlsStorage::Dynamic, ()).unwrap();
let first_id = first.mod_id();
assert_eq!(first_id.get(), 1);
let generation = registry.generation();
assert!(registry.snapshot_since(generation).is_none());
assert!(registry.unregister(first_id));
assert!(registry.generation() > generation);
assert!(registry.module(first_id).is_none());
let offset = TlsTpOffset::new(-8);
let second = registry
.register(info(), TlsStorage::Static(offset), ())
.unwrap();
assert_eq!(second.mod_id(), first_id);
assert_eq!(second.tp_offset(), Some(offset));
}
#[test]
fn rejects_invalid_tls_info() {
let mut registry = TlsRegistry::new();
let invalid = TlsInfo { align: 3, ..info() };
assert!(registry.register(invalid, TlsStorage::Dynamic, ()).is_err());
assert_eq!(registry.generation(), 0);
}
#[test]
fn rejects_repeated_publication() {
let mut registry = TlsRegistry::new();
let module = registry.register(info(), TlsStorage::Dynamic, ()).unwrap();
let provider = arc_unsize!(Arc::new(TestImage) => dyn TlsImageProvider);
let source = TlsImageSource::new(Arc::downgrade(&provider));
registry.publish(source.clone(), module.mod_id()).unwrap();
let generation = registry.generation();
assert!(registry.publish(source, module.mod_id()).is_err());
assert_eq!(registry.generation(), generation);
}
}