pub const SVID_EPOCH: i64 = 1767225600;
#[cfg(not(any(feature = "bits-long-life", feature = "bits-balanced", feature = "bits-high-rand")))]
compile_error!(
"svid: enable exactly one bit-layout feature: bits-long-life | bits-balanced | bits-high-rand"
);
#[cfg(any(
all(feature = "bits-long-life", feature = "bits-balanced"),
all(feature = "bits-long-life", feature = "bits-high-rand"),
all(feature = "bits-balanced", feature = "bits-high-rand"),
))]
compile_error!("svid: exactly one bit-layout feature may be enabled at a time");
#[cfg(feature = "bits-long-life")]
const _PROFILE: (u8, u8) = (31, 24);
#[cfg(feature = "bits-balanced")]
const _PROFILE: (u8, u8) = (29, 26);
#[cfg(feature = "bits-high-rand")]
const _PROFILE: (u8, u8) = (28, 27);
pub const TIMESTAMP_BITS: u8 = _PROFILE.0;
pub const RANDOM_BITS: u8 = _PROFILE.1;
pub const SOURCE_BITS: u8 = 1;
pub const IDTYPE_BITS: u8 = 7;
const _: () = assert!(
1 + TIMESTAMP_BITS as u32 + RANDOM_BITS as u32 + SOURCE_BITS as u32 + IDTYPE_BITS as u32 == 64,
"svid: bit-layout profile must sum to 64 bits"
);
pub const IDTYPE_MASK: i64 = (1 << IDTYPE_BITS) - 1;
pub const RANDOM_MASK: i64 = (1 << RANDOM_BITS) - 1;
pub const TIMESTAMP_MASK: i64 = (1 << TIMESTAMP_BITS) - 1;
pub const RANDOM_ID_TAG: u8 = 127;
pub const IDTYPE_SHIFT: u8 = 0;
pub const SOURCE_SHIFT: u8 = IDTYPE_BITS;
pub const RANDOM_SHIFT: u8 = SOURCE_SHIFT + SOURCE_BITS;
pub const TIMESTAMP_SHIFT: u8 = RANDOM_SHIFT + RANDOM_BITS;
pub const HUMAN_READABLE_LEN: usize = 11;
pub trait SvidExt {
fn tag(&self) -> u8;
fn timestamp_bits(&self) -> u32;
fn is_client(&self) -> bool;
fn random_bits(&self) -> u32;
fn unix_timestamp(&self) -> i64;
}
impl SvidExt for i64 {
#[inline]
fn tag(&self) -> u8 {
((*self >> IDTYPE_SHIFT) & IDTYPE_MASK) as u8
}
#[inline]
fn timestamp_bits(&self) -> u32 {
((*self >> TIMESTAMP_SHIFT) & TIMESTAMP_MASK) as u32
}
#[inline]
fn is_client(&self) -> bool {
((*self >> SOURCE_SHIFT) & 1) == 1
}
#[inline]
fn random_bits(&self) -> u32 {
((*self >> RANDOM_SHIFT) & RANDOM_MASK) as u32
}
#[inline]
fn unix_timestamp(&self) -> i64 {
SVID_EPOCH + self.timestamp_bits() as i64
}
}
#[inline]
pub fn encode_svid(timestamp: u32, is_client: bool, tag: u8, random: u32) -> i64 {
((timestamp as i64 & TIMESTAMP_MASK) << TIMESTAMP_SHIFT)
| ((random as i64 & RANDOM_MASK) << RANDOM_SHIFT)
| ((if is_client { 1i64 } else { 0i64 }) << SOURCE_SHIFT)
| ((tag as i64 & IDTYPE_MASK) << IDTYPE_SHIFT)
}
pub fn id_to_human_readable(id: i64) -> String {
let s = bs58::encode(id.to_be_bytes()).into_string();
debug_assert!(s.len() <= HUMAN_READABLE_LEN);
if s.len() == HUMAN_READABLE_LEN {
s
} else {
let pad = HUMAN_READABLE_LEN - s.len();
let mut out = "1".repeat(pad);
out.push_str(&s);
out
}
}
pub fn decode_i64_base58(s: &str) -> Result<i64, String> {
let bytes = bs58::decode(s).into_vec().map_err(|e| e.to_string())?;
let trimmed: &[u8] = if bytes.len() > 8 {
let excess = bytes.len() - 8;
if bytes[..excess].iter().all(|&b| b == 0) {
&bytes[excess..]
} else {
return Err(format!(
"invalid base58 SVID: decoded {} bytes, expected <= 8",
bytes.len()
));
}
} else {
&bytes
};
let mut arr = [0u8; 8];
arr[8 - trimmed.len()..].copy_from_slice(trimmed);
Ok(i64::from_be_bytes(arr))
}
pub fn human_readable_to_id(s: &str) -> Result<i64, String> {
decode_i64_base58(s)
}
pub fn human_readable_to_id_expecting(s: &str, expected_tag: u8) -> Result<i64, String> {
if s.len() != HUMAN_READABLE_LEN {
return Err(format!(
"Invalid human-readable SVID: expected {} chars, got {}",
HUMAN_READABLE_LEN,
s.len()
));
}
let id = human_readable_to_id(s)?;
let got = id.tag();
if got != expected_tag {
return Err(format!(
"Invalid SVID tag: expected {}, got {}",
expected_tag, got
));
}
Ok(id)
}