use super::{
Anchoring, GENERATOR, GeneratorIdentity, HumanProjection, Identity, OwnerIdentity, Profile,
Provenance, Role, ShapeVersion, Subject, Transcript, Version,
};
use crate::bounded::{Bounded, Overflow};
use core::fmt;
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;
impl<S: Subject> Identity<S> {
#[must_use]
pub fn derived(transcript: Transcript<'_>) -> Self {
let context = transcript.profile().context_for::<S>(transcript.role());
Self(
blake3::derive_key(&context, &transcript.encoded::<S>()),
PhantomData,
)
}
#[must_use]
pub fn derived_with_provenance(transcript: Transcript<'_>) -> (Self, Provenance) {
(Self::derived(transcript), transcript.provenance::<S>())
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
impl<S: Subject> PartialEq for Identity<S> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<S: Subject> Eq for Identity<S> {}
impl<S: Subject> Hash for Identity<S> {
fn hash<H: Hasher>(&self, into: &mut H) {
self.0.hash(into);
}
}
impl<S: Subject> fmt::Debug for Identity<S> {
fn fmt(&self, into: &mut fmt::Formatter<'_>) -> fmt::Result {
into.debug_tuple(S::NAME).field(&self.0).finish()
}
}
impl Version {
#[must_use]
pub const fn declared(position: u32) -> Self {
Self(position)
}
#[must_use]
pub const fn position(self) -> u32 {
self.0
}
}
impl Profile {
#[must_use]
pub const fn declared(stem: &'static str, name: &'static str, version: Version) -> Self {
Self {
stem,
name,
version,
}
}
#[must_use]
pub const fn stem(self) -> &'static str {
self.stem
}
#[must_use]
pub const fn name(self) -> &'static str {
self.name
}
#[must_use]
pub const fn version(self) -> Version {
self.version
}
#[must_use]
pub fn context_for<S: Subject>(self, role: Role) -> String {
self.context_over(S::STEM, S::NAME, role)
}
pub(crate) fn context_over(self, subject_stem: &str, subject: &str, role: Role) -> String {
let Self {
stem,
name,
version,
} = self;
let position = version.position();
let seat = role.name();
format!("{stem}/{name}/v{position}/{subject_stem}/{subject}/{seat}")
}
}
impl ShapeVersion {
#[must_use]
pub const fn declared(position: u32) -> Self {
Self(position)
}
#[must_use]
pub const fn position(self) -> u32 {
self.0
}
}
impl GeneratorIdentity {
#[must_use]
pub const fn declared(name: &'static str, shape: ShapeVersion, package: &'static str) -> Self {
Self {
name,
shape,
package,
}
}
#[must_use]
pub const fn name(self) -> &'static str {
self.name
}
#[must_use]
pub const fn shape(self) -> ShapeVersion {
self.shape
}
#[must_use]
pub const fn package_version(self) -> &'static str {
self.package
}
#[must_use]
pub fn same_shape(self, other: Self) -> bool {
self.name == other.name && self.shape == other.shape
}
}
impl<'material> Transcript<'material> {
#[must_use]
pub const fn under_profile(
profile: Profile,
role: Role,
anchoring: Anchoring,
material: &'material [u8],
position: u32,
) -> Self {
Self {
profile,
generator: GENERATOR,
role,
anchoring,
material,
position,
}
}
#[must_use]
pub const fn under(
role: Role,
anchoring: Anchoring,
material: &'material [u8],
position: u32,
) -> Self {
Self::under_profile(role.profile(), role, anchoring, material, position)
}
#[must_use]
pub const fn rooted(role: Role, material: &'material [u8], position: u32) -> Self {
Self::under(role, Anchoring::Rooted, material, position)
}
#[must_use]
pub const fn under_owner(
role: Role,
anchor: &OwnerIdentity,
material: &'material [u8],
position: u32,
) -> Self {
Self::under(
role,
Anchoring::UnderOwner(anchor.bytes),
material,
position,
)
}
#[must_use]
pub fn under_projection<S: Subject>(
role: Role,
anchor: &Identity<S>,
material: &'material [u8],
position: u32,
) -> Self {
Self::under(
role,
Anchoring::UnderProjection(*anchor.as_bytes()),
material,
position,
)
}
#[must_use]
pub const fn profile(&self) -> Profile {
self.profile
}
#[must_use]
pub const fn generator(&self) -> GeneratorIdentity {
self.generator
}
#[must_use]
pub const fn role(&self) -> Role {
self.role
}
#[must_use]
pub const fn anchoring(&self) -> Anchoring {
self.anchoring
}
#[must_use]
pub const fn material(&self) -> &'material [u8] {
self.material
}
#[must_use]
pub const fn position(&self) -> u32 {
self.position
}
#[must_use]
pub fn provenance<S: Subject>(&self) -> Provenance {
Provenance {
subject_stem: S::STEM,
subject: S::NAME,
role: self.role,
profile: self.profile,
generator: self.generator,
anchoring: self.anchoring,
material_length: u64::try_from(self.material.len()).unwrap_or(u64::MAX),
position: self.position,
}
}
}
impl Provenance {
#[must_use]
pub const fn subject_stem(&self) -> &'static str {
self.subject_stem
}
#[must_use]
pub const fn subject(&self) -> &'static str {
self.subject
}
#[must_use]
pub const fn role(&self) -> Role {
self.role
}
#[must_use]
pub const fn profile(&self) -> Profile {
self.profile
}
#[must_use]
pub const fn generator(&self) -> GeneratorIdentity {
self.generator
}
#[must_use]
pub const fn anchoring(&self) -> Anchoring {
self.anchoring
}
#[must_use]
pub const fn material_length(&self) -> u64 {
self.material_length
}
#[must_use]
pub const fn position(&self) -> u32 {
self.position
}
#[must_use]
pub fn context(&self) -> String {
self.profile
.context_over(self.subject_stem, self.subject, self.role)
}
#[must_use]
pub fn under_current_shape(&self) -> bool {
self.generator.same_shape(GENERATOR)
}
}
impl HumanProjection {
pub fn projected(text: &str) -> Result<Self, Overflow> {
Bounded::new(text.as_bytes().to_vec()).map(Self)
}
#[must_use]
pub(crate) fn proven<const N: usize>(rendered: [u8; N]) -> Self {
Self(Bounded::from_array(rendered))
}
#[must_use]
pub fn empty() -> Self {
Self(Bounded::empty())
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub fn shown(&self) -> String {
String::from_utf8_lossy(self.0.as_slice()).into_owned()
}
}
#[must_use]
pub(crate) const fn static_bytes<const N: usize>(text: &str) -> [u8; N] {
assert!(
text.len() == N,
"a declared width that is not the rendering's own length"
);
let mut rendered = [0u8; N];
let mut source = text.as_bytes();
let mut sink: &mut [u8] = &mut rendered;
while let Some((seat, open)) = sink.split_first_mut() {
let Some((byte, remaining)) = source.split_first() else {
break;
};
*seat = *byte;
sink = open;
source = remaining;
}
rendered
}
macro_rules! human_projection {
($text:literal) => {{
const RENDERED: [u8; $text.len()] = $crate::identity::static_bytes($text);
$crate::identity::HumanProjection::proven(RENDERED)
}};
}
pub(crate) use human_projection;
#[must_use]
pub const fn names_are_separating(names: &[&str]) -> bool {
match names.split_first() {
None => true,
Some((first, rest)) => {
name_is_grammatical(first) && !names_contain(rest, first) && names_are_separating(rest)
}
}
}
#[derive(Clone, Copy)]
enum Segment {
Opening,
Inside,
}
const fn name_is_grammatical(name: &str) -> bool {
grammatical(name.as_bytes(), Segment::Opening)
}
const fn grammatical(bytes: &[u8], at: Segment) -> bool {
match bytes.split_first() {
None => matches!(at, Segment::Inside),
Some((byte, rest)) => {
if *byte == b'-' {
matches!(at, Segment::Inside) && grammatical(rest, Segment::Opening)
} else if byte.is_ascii_lowercase() || byte.is_ascii_digit() {
grammatical(rest, Segment::Inside)
} else {
false
}
}
}
}
const fn names_contain(names: &[&str], name: &str) -> bool {
match names.split_first() {
None => false,
Some((first, rest)) => {
same_bytes(first.as_bytes(), name.as_bytes()) || names_contain(rest, name)
}
}
}
const fn same_bytes(left: &[u8], right: &[u8]) -> bool {
match (left.split_first(), right.split_first()) {
(None, None) => true,
(None, Some(_)) | (Some(_), None) => false,
(Some((here, left_rest)), Some((there, right_rest))) => {
*here == *there && same_bytes(left_rest, right_rest)
}
}
}