#[cfg(feature = "encryption")]
use crate::encryption::EncryptionKey;
use crate::{
Case, NameStr, Tnid, TnidName, TnidVariant, UuidLike, data_encoding, name_encoding, utils, v0,
v1,
};
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct DynamicTnid(u128);
impl DynamicTnid {
#[cfg(all(feature = "time", feature = "rand"))]
pub fn new_v0(name: NameStr) -> Self {
Self::new_v0_with_time(name, time::OffsetDateTime::now_utc())
}
#[cfg(all(feature = "time", feature = "rand"))]
pub fn new_time_ordered(name: NameStr) -> Self {
Self::new_v0(name)
}
#[cfg(all(feature = "time", feature = "rand"))]
pub fn new_v0_with_time(name: NameStr, time: time::OffsetDateTime) -> Self {
let epoch_millis = (time.unix_timestamp_nanos() / 1000 / 1000) as u64;
let random_bits: u64 = rand::random();
Self(v0::make_from_parts(name, epoch_millis, random_bits))
}
pub fn new_v0_with_parts(name: NameStr, epoch_millis: u64, random: u64) -> Self {
Self(v0::make_from_parts(name, epoch_millis, random))
}
#[cfg(feature = "rand")]
pub fn new_v1(name: NameStr) -> Self {
Self::new_v1_with_random(name, rand::random())
}
#[cfg(feature = "rand")]
pub fn new_high_entropy(name: NameStr) -> Self {
Self::new_v1(name)
}
pub fn new_v1_with_random(name: NameStr, random_bits: u128) -> Self {
Self(v1::make_from_parts(name, random_bits))
}
pub fn from_u128(id: u128) -> Result<Self, crate::ParseTnidError> {
if (id & utils::UUID_V8_MASK) != utils::UUID_V8_MASK {
return Err(crate::ParseTnidError::InvalidUuidBits);
}
if name_encoding::validate_name_bits(id) != name_encoding::NameBitsValidation::Valid {
return Err(crate::ParseTnidError::InvalidNameBits);
}
Ok(Self(id))
}
pub fn parse_tnid_string(s: &str) -> Result<Self, crate::ParseTnidError> {
const MIN_LEN: usize =
name_encoding::NAME_MIN_CHARS + 1 + data_encoding::DATA_CHAR_ENCODING_LEN as usize;
const MAX_LEN: usize =
name_encoding::NAME_MAX_CHARS + 1 + data_encoding::DATA_CHAR_ENCODING_LEN as usize;
if s.len() < MIN_LEN || s.len() > MAX_LEN {
return Err(crate::ParseTnidError::InvalidLength(s.len()));
}
let (name_str, data_str) = s
.split_once('.')
.ok_or(crate::ParseTnidError::MissingSeparator)?;
let name = NameStr::new(name_str).map_err(crate::ParseTnidError::InvalidName)?;
let compact_data = data_encoding::string_to_id_data(data_str)
.map_err(crate::ParseTnidError::InvalidDataEncoding)?;
let data_bits = data_encoding::expand_data_bits(compact_data);
let name_bits = name_encoding::name_mask(name);
let id = name_bits | utils::UUID_V8_MASK | data_bits;
Ok(Self(id))
}
pub fn parse_uuid_string(s: &str) -> Result<Self, crate::ParseTnidError> {
let id = crate::UuidLike::parse_uuid_string(s)
.map_err(crate::ParseTnidError::InvalidUuidFormat)?
.as_u128();
Self::from_u128(id)
}
pub fn name(&self) -> String {
name_encoding::extract_name_string(self.0).expect("DynamicTnid must have valid name")
}
pub fn name_hex(&self) -> String {
name_encoding::name_bits_to_hex(self.0)
}
pub fn data_string(&self) -> String {
data_encoding::id_data_to_string(self.0)
}
pub fn as_u128(&self) -> u128 {
self.0
}
pub fn variant(&self) -> TnidVariant {
TnidVariant::from_id(self.0)
}
pub fn to_tnid_string(&self) -> String {
format!(
"{}.{}",
self.name(),
data_encoding::id_data_to_string(self.0)
)
}
pub fn to_uuid_string(&self, case: Case) -> String {
utils::u128_to_uuid_string(self.0, case)
}
pub fn to_bytes(&self) -> [u8; 16] {
self.0.to_be_bytes()
}
pub fn from_bytes(bytes: [u8; 16]) -> Result<Self, crate::ParseTnidError> {
Self::from_u128(u128::from_be_bytes(bytes))
}
#[cfg(feature = "encryption")]
pub fn encrypt_v0_to_v1(
&self,
key: &EncryptionKey,
) -> Result<Self, crate::encryption::EncryptionError> {
let id = crate::encryption::encrypt_id_v0_to_v1(self.0, key)?;
Ok(Self(id))
}
#[cfg(feature = "encryption")]
pub fn decrypt_v1_to_v0(
&self,
key: &EncryptionKey,
) -> Result<Self, crate::encryption::EncryptionError> {
let id = crate::encryption::decrypt_id_v1_to_v0(self.0, key)?;
Ok(Self(id))
}
#[cfg(feature = "filter")]
pub fn new_v0_filtered(
name: NameStr,
blocklist: &crate::filter::Blocklist,
) -> Result<Self, crate::filter::FilterError> {
let mut timestamp = blocklist.get_starting_timestamp();
let max_iterations = blocklist.limits().max_v0_iterations;
for _ in 0..max_iterations {
let random: u64 = rand::random();
let id = Self::new_v0_with_parts(name, timestamp, random);
let data = id.data_string();
match crate::filter::find_first_match(blocklist, &data) {
None => {
blocklist.record_safe_timestamp(timestamp);
return Ok(id);
}
Some((start, len)) => {
if !crate::filter::match_touches_random_portion(start, len) {
let rightmost_char = start + len - 1;
timestamp += crate::filter::timestamp_bump_for_char(rightmost_char);
}
}
}
}
Err(crate::filter::FilterError::MaxIterationsExceeded {
iterations: max_iterations,
})
}
#[cfg(feature = "filter")]
pub fn new_v1_filtered(
name: NameStr,
blocklist: &crate::filter::Blocklist,
) -> Result<Self, crate::filter::FilterError> {
let max_iterations = blocklist.limits().max_v1_iterations;
for _ in 0..max_iterations {
let id = Self::new_v1(name);
let data = id.data_string();
if !blocklist.contains_match(&data) {
return Ok(id);
}
}
Err(crate::filter::FilterError::MaxIterationsExceeded {
iterations: max_iterations,
})
}
#[cfg(all(feature = "filter", feature = "encryption"))]
pub fn new_v0_filtered_for_encryption(
name: NameStr,
key: &EncryptionKey,
blocklist: &crate::filter::Blocklist,
) -> Result<Self, crate::filter::FilterError> {
let mut timestamp = blocklist.get_starting_timestamp();
let max_iterations = blocklist.limits().max_encryption_iterations;
for _ in 0..max_iterations {
let random: u64 = rand::random();
let v0 = Self::new_v0_with_parts(name, timestamp, random);
let v0_data = v0.data_string();
if let Some((start, len)) = crate::filter::find_first_match(blocklist, &v0_data) {
if !crate::filter::match_touches_random_portion(start, len) {
let rightmost_char = start + len - 1;
timestamp += crate::filter::timestamp_bump_for_char(rightmost_char);
}
continue;
}
let v1 = v0.encrypt_v0_to_v1(key)?;
let v1_data = v1.data_string();
if !blocklist.contains_match(&v1_data) {
blocklist.record_safe_timestamp(timestamp);
return Ok(v0);
}
}
Err(crate::filter::FilterError::MaxIterationsExceeded {
iterations: max_iterations,
})
}
}
impl<Name: TnidName> From<Tnid<Name>> for DynamicTnid {
fn from(tnid: Tnid<Name>) -> Self {
Self(tnid.as_u128())
}
}
impl TryFrom<UuidLike> for DynamicTnid {
type Error = crate::ParseTnidError;
fn try_from(uuid: UuidLike) -> Result<Self, Self::Error> {
Self::from_u128(uuid.as_u128())
}
}
impl<Name: TnidName> TryFrom<DynamicTnid> for Tnid<Name> {
type Error = crate::ParseTnidError;
fn try_from(dynamic: DynamicTnid) -> Result<Self, Self::Error> {
Tnid::<Name>::from_u128(dynamic.0)
}
}
impl From<DynamicTnid> for UuidLike {
fn from(dynamic: DynamicTnid) -> Self {
UuidLike::new(dynamic.0)
}
}
impl core::fmt::Display for DynamicTnid {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.to_tnid_string())
}
}
impl core::fmt::Debug for DynamicTnid {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.to_tnid_string())
}
}
impl core::str::FromStr for DynamicTnid {
type Err = crate::ParseTnidError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
const MIN_TNID_LEN: usize =
name_encoding::NAME_MIN_CHARS + 1 + data_encoding::DATA_CHAR_ENCODING_LEN as usize;
const MAX_TNID_LEN: usize =
name_encoding::NAME_MAX_CHARS + 1 + data_encoding::DATA_CHAR_ENCODING_LEN as usize;
const UUID_LEN: usize = 36;
match s.len() {
MIN_TNID_LEN..=MAX_TNID_LEN if s.contains('.') => Self::parse_tnid_string(s),
MIN_TNID_LEN..=MAX_TNID_LEN => Err(crate::ParseTnidError::MissingSeparator),
UUID_LEN => Self::parse_uuid_string(s),
len => Err(crate::ParseTnidError::InvalidLength(len)),
}
}
}