Skip to main content

pb_mapper_auth/
ids.rs

1//! Identity types for temporary credentials.
2//!
3//! ```text
4//!  KeyId (u64) — what a client presents
5//! ┌──────────────────────────┬──────────────────────────┐
6//! │ Generation (high 32)     │ SlotIndex (low 32)       │
7//! └──────────────────────────┴──────────────────────────┘
8//!      which tenant of the row       which row of the table
9//! ```
10//!
11//! These were all bare integers, which made `make_key_id(generation, slot)`
12//! accept its arguments in either order and let a slot index be compared against
13//! a generation without complaint. Separate types make both a compile error, and
14//! keep a `KeyId` from being used as an array index by mistake — the only way to
15//! get one is [`KeyId::slot`], which is also the only place the truncation to a
16//! row number is expressed.
17//!
18//! All three are `#[serde(transparent)]`, so persisted snapshots and the admin
19//! wire protocol keep the plain-integer encoding they already had.
20
21use std::fmt;
22
23use serde::{Deserialize, Serialize};
24
25/// The identity a client presents: a [`SlotIndex`] paired with the
26/// [`Generation`] of the row it was issued from.
27#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
28#[serde(transparent)]
29pub struct KeyId(u64);
30
31/// Which tenant of a slot a credential belongs to. Bumped every time the row is
32/// reissued, and never reset, so a retired credential can never match the row
33/// that replaced it.
34#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
35#[serde(transparent)]
36pub struct Generation(u32);
37
38/// Which row of the slot table a credential lives in.
39#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
40#[serde(transparent)]
41pub struct SlotIndex(u32);
42
43/// The administrator, which owns no slot and never expires.
44pub const ADMIN_KEY_ID: KeyId = KeyId(0);
45
46impl KeyId {
47    pub const fn new(generation: Generation, slot: SlotIndex) -> Self {
48        Self(((generation.0 as u64) << 32) | slot.0 as u64)
49    }
50
51    pub const fn generation(self) -> Generation {
52        Generation((self.0 >> 32) as u32)
53    }
54
55    pub const fn slot(self) -> SlotIndex {
56        SlotIndex(self.0 as u32)
57    }
58
59    pub const fn is_admin(self) -> bool {
60        self.0 == ADMIN_KEY_ID.0
61    }
62
63    /// The bytes mixed into the credential's key derivation.
64    pub const fn to_be_bytes(self) -> [u8; 8] {
65        self.0.to_be_bytes()
66    }
67
68    pub const fn as_u64(self) -> u64 {
69        self.0
70    }
71
72    pub const fn from_u64(raw: u64) -> Self {
73        Self(raw)
74    }
75}
76
77impl Generation {
78    pub const FIRST: Self = Self(0);
79
80    /// The generation for a reissue of this row, or `None` once the row has been
81    /// cycled `u32::MAX` times and can no longer produce a fresh identity.
82    pub fn next(self) -> Option<Self> {
83        self.0.checked_add(1).map(Self)
84    }
85
86    pub const fn as_u32(self) -> u32 {
87        self.0
88    }
89
90    pub const fn from_u32(raw: u32) -> Self {
91        Self(raw)
92    }
93}
94
95impl SlotIndex {
96    pub const fn as_index(self) -> usize {
97        self.0 as usize
98    }
99
100    /// # Panics
101    ///
102    /// If `index` exceeds `u32::MAX`. `MAX_TEMP_KEY_CAPACITY` caps the table far
103    /// below that, so a real index cannot reach it; panicking keeps a future
104    /// capacity change from silently wrapping into another row's identity.
105    pub fn from_index(index: usize) -> Self {
106        match u32::try_from(index) {
107            Ok(index) => Self(index),
108            Err(_) => panic!("slot index exceeds the addressable slot table"),
109        }
110    }
111}
112
113impl fmt::Display for KeyId {
114    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
115        self.0.fmt(formatter)
116    }
117}
118
119impl fmt::Display for Generation {
120    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
121        self.0.fmt(formatter)
122    }
123}
124
125impl fmt::Display for SlotIndex {
126    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
127        self.0.fmt(formatter)
128    }
129}