use std::fmt;
use std::num::NonZeroU64;
use serde::{Deserialize, Deserializer, Serialize};
fn is_participant_token(value: &str) -> bool {
!value.is_empty()
&& value.chars().all(|character| {
character.is_ascii_lowercase()
|| character.is_ascii_digit()
|| matches!(character, '_' | '-')
})
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ParticipantId(String);
impl ParticipantId {
pub fn new(value: impl Into<String>) -> Result<Self, ParticipantIdError> {
let value = value.into();
if is_participant_token(&value) {
Ok(Self(value))
} else {
Err(ParticipantIdError(value))
}
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for ParticipantId {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(&self.0)
}
}
impl AsRef<str> for ParticipantId {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl std::str::FromStr for ParticipantId {
type Err = ParticipantIdError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
impl TryFrom<String> for ParticipantId {
type Error = ParticipantIdError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<ParticipantId> for String {
fn from(value: ParticipantId) -> Self {
value.0
}
}
impl Serialize for ParticipantId {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for ParticipantId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Self::new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
#[error("participant id must be a non-empty lowercase token, got '{0}'")]
pub struct ParticipantIdError(String);
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ParticipantArtifactId(String);
impl ParticipantArtifactId {
pub fn new(value: impl Into<String>) -> Result<Self, ParticipantArtifactIdError> {
let value = value.into();
if is_participant_token(&value) {
Ok(Self(value))
} else {
Err(ParticipantArtifactIdError(value))
}
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for ParticipantArtifactId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl AsRef<str> for ParticipantArtifactId {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl std::str::FromStr for ParticipantArtifactId {
type Err = ParticipantArtifactIdError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
impl TryFrom<String> for ParticipantArtifactId {
type Error = ParticipantArtifactIdError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<ParticipantArtifactId> for String {
fn from(value: ParticipantArtifactId) -> Self {
value.0
}
}
impl Serialize for ParticipantArtifactId {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for ParticipantArtifactId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Self::new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
#[error("participant artifact id must be a non-empty lowercase token, got '{0}'")]
pub struct ParticipantArtifactIdError(String);
const ZID_BYTES: usize = 16;
const ZID_HEX_LEN: usize = ZID_BYTES * 2;
const CANONICAL_TOP_NIBBLE: u128 = 1 << 124;
fn mint_canonical_value() -> u128 {
let mut bytes = [0_u8; ZID_BYTES];
#[expect(
clippy::expect_used,
reason = "a session identity is the root of bus provenance; a host without randomness cannot safely start one"
)]
getrandom::fill(&mut bytes).expect("the host must provide randomness");
let mut value = u128::from_be_bytes(bytes);
if value >> 124 == 0 {
value |= CANONICAL_TOP_NIBBLE;
}
value
}
fn canonical_hex(value: u128) -> String {
format!("{value:032x}")
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExecutionId(u128);
impl ExecutionId {
pub const LEN: usize = ZID_HEX_LEN;
pub fn mint() -> Self {
ExecutionId(mint_canonical_value())
}
pub fn parse(value: &str) -> Result<Self, IdentityError> {
if value.len() != ZID_HEX_LEN || !value.bytes().all(is_lowercase_hex) {
return Err(IdentityError(format!(
"an execution id is exactly {ZID_HEX_LEN} lowercase hexadecimal \
characters, got '{value}'"
)));
}
let value = u128::from_str_radix(value, 16)
.map_err(|error| IdentityError(format!("'{value}' is not hexadecimal: {error}")))?;
ExecutionId::try_from(value)
}
}
impl TryFrom<u128> for ExecutionId {
type Error = IdentityError;
fn try_from(value: u128) -> Result<Self, IdentityError> {
if value >> 124 == 0 {
return Err(IdentityError(format!(
"an execution id renders as {ZID_HEX_LEN} characters, so its most \
significant nibble is never zero"
)));
}
Ok(ExecutionId(value))
}
}
impl From<ExecutionId> for u128 {
fn from(execution: ExecutionId) -> Self {
execution.0
}
}
impl fmt::Display for ExecutionId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&canonical_hex(self.0))
}
}
impl fmt::Debug for ExecutionId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "ExecutionId({self})")
}
}
impl Serialize for ExecutionId {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for ExecutionId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = String::deserialize(deserializer)?;
ExecutionId::parse(&value).map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct ProducerId(u128);
impl ProducerId {
pub const LEN: usize = ZID_HEX_LEN;
pub fn parse(value: &str) -> Result<Self, IdentityError> {
if value.len() != ZID_HEX_LEN || !value.bytes().all(is_lowercase_hex) {
return Err(IdentityError(format!(
"a producer id is exactly {ZID_HEX_LEN} lowercase hexadecimal characters \
and is not zero, got '{value}'"
)));
}
let value = u128::from_str_radix(value, 16)
.map_err(|error| IdentityError(format!("'{value}' is not hexadecimal: {error}")))?;
ProducerId::try_from(value)
}
}
impl TryFrom<u128> for ProducerId {
type Error = IdentityError;
fn try_from(value: u128) -> Result<Self, IdentityError> {
if value >> 124 == 0 {
return Err(IdentityError(
"a producer id must have a non-zero leading nibble".to_string(),
));
}
Ok(ProducerId(value))
}
}
impl From<ProducerId> for u128 {
fn from(producer: ProducerId) -> Self {
producer.0
}
}
impl fmt::Display for ProducerId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&canonical_hex(self.0))
}
}
impl fmt::Debug for ProducerId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "ProducerId({self})")
}
}
impl Serialize for ProducerId {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_bytes(&self.0.to_le_bytes())
}
}
impl<'de> Deserialize<'de> for ProducerId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let bytes = serde_bytes::ByteBuf::deserialize(deserializer)?;
let bytes = <[u8; ZID_BYTES]>::try_from(bytes.as_ref()).map_err(|_| {
serde::de::Error::custom(format!(
"producer id must be {ZID_BYTES} bytes, got {}",
bytes.len()
))
})?;
ProducerId::try_from(u128::from_le_bytes(bytes)).map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct TimelineId(NonZeroU64);
impl TimelineId {
pub fn mint() -> Self {
let mut bytes = [0_u8; 8];
#[expect(
clippy::expect_used,
reason = "a timeline names one world history, so two histories separated by a \
predictable identity would be indistinguishable to every reader; a host \
whose randomness source is unavailable has no correct value to return"
)]
getrandom::fill(&mut bytes).expect("the host must provide randomness");
TimelineId(NonZeroU64::new(u64::from_le_bytes(bytes)).unwrap_or(NonZeroU64::MIN))
}
pub const fn from_raw(value: u64) -> Option<Self> {
match NonZeroU64::new(value) {
Some(value) => Some(TimelineId(value)),
None => None,
}
}
pub const fn get(self) -> u64 {
self.0.get()
}
}
impl fmt::Display for TimelineId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "t{:016x}", self.0.get())
}
}
impl fmt::Debug for TimelineId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "TimelineId({self})")
}
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[error("{0}")]
pub struct IdentityError(String);
const fn is_lowercase_hex(byte: u8) -> bool {
byte.is_ascii_digit() || byte.is_ascii_lowercase() && byte <= b'f'
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn participant_ids_are_typed_canonical_tokens() {
let id = ParticipantId::new("front_camera").expect("a canonical participant id");
assert_eq!(id.as_str(), "front_camera");
assert_eq!(id.to_string(), "front_camera");
assert_eq!(
serde_json::to_string(&id).expect("id serializes"),
"\"front_camera\""
);
assert_eq!(
serde_json::from_str::<ParticipantId>("\"front_camera\"").expect("id deserializes"),
id
);
}
#[test]
fn participant_ids_reject_noncanonical_and_path_tokens() {
for value in ["", "FrontCamera", "front camera", "../brain", "brain/extra"] {
assert!(ParticipantId::new(value).is_err(), "{value:?}");
assert!(
serde_json::from_str::<ParticipantId>(&format!("\"{value}\"")).is_err(),
"{value:?}"
);
}
}
#[test]
fn a_minted_execution_always_renders_at_the_canonical_width() {
let first = ExecutionId::mint();
let second = ExecutionId::mint();
assert_ne!(first, second);
let rendered = first.to_string();
assert_eq!(rendered.len(), ExecutionId::LEN);
assert!(!rendered.starts_with('0'));
assert!(rendered.bytes().all(is_lowercase_hex));
assert!(!rendered.contains('/') && !rendered.contains('*'));
assert_eq!(ExecutionId::parse(&rendered), Ok(first));
}
#[test]
fn minting_does_not_pin_the_leading_digit_to_half_the_alphabet() {
let saw_even_leading_digit = (0..64).any(|_| {
let leading = ExecutionId::mint().to_string().as_bytes()[0];
let digit = if leading.is_ascii_digit() {
leading - b'0'
} else {
leading - b'a' + 10
};
digit % 2 == 0
});
assert!(
saw_even_leading_digit,
"a minted execution covers the whole nonzero leading-digit range"
);
}
#[test]
fn only_the_canonical_execution_form_parses() {
let canonical = ExecutionId::mint().to_string();
assert!(ExecutionId::parse("").is_err());
assert!(ExecutionId::parse("deadbeef").is_err());
assert!(
ExecutionId::parse(&canonical.to_uppercase()).is_err(),
"uppercase renders back differently, so it is not the same identity"
);
assert!(
ExecutionId::parse(&format!("0{}", &canonical[1..])).is_err(),
"a leading zero would render back one character shorter"
);
assert!(
ExecutionId::parse(&format!("{canonical}0")).is_err(),
"an over-long run of digits is not a session identity"
);
assert!(ExecutionId::parse(&"z".repeat(ExecutionId::LEN)).is_err());
assert!(
ExecutionId::parse(&format!("x{canonical}")).is_err(),
"the key root is bare, so there is no prefix to strip"
);
}
#[test]
fn an_execution_round_trips_through_its_session_identity_value() {
let execution = ExecutionId::mint();
let value = u128::from(execution);
assert_eq!(ExecutionId::try_from(value), Ok(execution));
assert_eq!(format!("{value:x}"), execution.to_string());
assert!(
ExecutionId::try_from(u128::from(execution) >> 4).is_err(),
"a value that renders narrower than the canonical width is not an execution"
);
assert!(ExecutionId::try_from(0).is_err());
}
#[test]
fn a_producer_round_trips_in_the_canonical_transport_form() {
let minted = ProducerId::try_from((1_u128 << 124) | 0x0123_4567_89ab_cdef).unwrap();
assert_eq!(minted.to_string().len(), ProducerId::LEN);
assert_eq!(ProducerId::parse(&minted.to_string()), Ok(minted));
let wide = ProducerId::try_from(u128::MAX).unwrap();
assert_eq!(wide.to_string(), "f".repeat(ZID_HEX_LEN));
assert_eq!(ProducerId::parse(&wide.to_string()), Ok(wide));
assert!(ProducerId::try_from(0).is_err());
assert!(ProducerId::parse("").is_err());
assert!(ProducerId::parse("01").is_err());
assert!(ProducerId::parse("AB").is_err());
assert!(ProducerId::parse(&"f".repeat(ZID_HEX_LEN + 1)).is_err());
assert!(ProducerId::parse(&format!("0{}", "f".repeat(ZID_HEX_LEN - 1))).is_err());
}
#[test]
fn producer_ids_round_trip_through_the_wire_encoding() {
let producer = ProducerId::try_from((1_u128 << 124) | 0x0123_4567_89ab_cdef).unwrap();
let encoded = rmp_serde::to_vec_named(&producer).unwrap();
let decoded: ProducerId = rmp_serde::from_slice(&encoded).unwrap();
assert_eq!(decoded, producer);
assert_ne!(producer, ProducerId::try_from((1_u128 << 124) | 1).unwrap());
}
#[test]
fn timelines_have_no_zero_value_and_no_generation_order() {
assert_eq!(TimelineId::from_raw(0), None);
let timeline = TimelineId::mint();
assert_eq!(TimelineId::from_raw(timeline.get()), Some(timeline));
assert_ne!(timeline, TimelineId::mint());
}
}