bb_ir/ids.rs
1//! Wire-format and compiler-bound identifier types.
2//!
3//! IDs tied to the IR or wire envelope live here ([`PeerId`],
4//! [`RequestId`], [`OpsetId`], [`ComponentTag`]). Engine-internal
5//! dispatch IDs (`NodeSiteId`, `OpRef`, `ExecId`, `CommandId`,
6//! `ComponentRef`) live in `bb_runtime::ids`.
7
8use std::fmt;
9
10// --- Macro helpers ----------------------------------------------
11
12macro_rules! u64_id {
13 ($(#[$attr:meta])* $name:ident) => {
14 $(#[$attr])*
15 #[derive(
16 Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord,
17 serde::Serialize, serde::Deserialize,
18 )]
19 #[repr(transparent)]
20 pub struct $name(u64);
21
22 impl $name {
23 /// Construct from an explicit value.
24 pub const fn new(inner: u64) -> Self { Self(inner) }
25
26 /// Inner value accessor.
27 pub const fn as_u64(self) -> u64 { self.0 }
28 }
29
30 impl fmt::Display for $name {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 write!(f, "{}({})", stringify!($name), self.0)
33 }
34 }
35
36 impl From<u64> for $name {
37 fn from(inner: u64) -> Self { Self(inner) }
38 }
39 };
40}
41
42// --- Wire-format / compiler-bound IDs ---------------------------
43
44u64_id! {
45 /// Per-request correlation token. Issued by `SendReqBatched`
46 /// senders; echoed by receivers in `SendResp`.
47 RequestId
48}
49
50// --- Symbolic IDs -----------------------------------------------
51
52/// Typed bus-subscription tag - a static `&str` label naming the
53/// kind of event a component receives.
54#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
55pub struct ComponentTag(pub &'static str);
56
57impl ComponentTag {
58 /// Construct from an explicit static label.
59 pub const fn new(tag: &'static str) -> Self {
60 Self(tag)
61 }
62
63 /// Inner label accessor.
64 pub const fn as_str(self) -> &'static str {
65 self.0
66 }
67}
68
69impl fmt::Display for ComponentTag {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 write!(f, "ComponentTag({:?})", self.0)
72 }
73}
74
75/// Opset identifier. `(domain, version)` pair routing wire-level
76/// dispatch. See `docs/IR_AND_DSL.md` §5 for the canonical opset
77/// catalog.
78#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
79pub struct OpsetId {
80 /// Reverse-DNS-ish domain (`ai.onnx`, `bb.wire`, `bb.gossip`,
81 /// `user.kademlia`, ...).
82 pub domain: &'static str,
83
84 /// Major version. Minor/patch live in the component's own
85 /// versioning surface; this field gates wire-level
86 /// compatibility.
87 pub version: i64,
88}
89
90impl OpsetId {
91 /// Construct from explicit values.
92 pub const fn new(domain: &'static str, version: i64) -> Self {
93 Self { domain, version }
94 }
95}
96
97impl fmt::Display for OpsetId {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 write!(f, "{} v{}", self.domain, self.version)
100 }
101}
102
103// --- PeerId - libp2p-compatible multihash -----------------------
104
105/// Peer identity. Wraps a fixed-capacity `Multihash<64>` so
106/// overlays pick their own digest algorithm. Same wire shape as
107/// `libp2p_identity::PeerId`; bytes round-trip without translation.
108#[derive(
109 Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
110)]
111#[repr(transparent)]
112pub struct PeerId(multihash::Multihash<64>);
113
114impl PeerId {
115 /// Multihash code for `identity` (raw bytes, no hashing).
116 pub const IDENTITY: u64 = 0x00;
117
118 /// Multihash code for `sha2-256`.
119 pub const SHA2_256: u64 = 0x12;
120
121 /// Construct from a multihash directly.
122 pub const fn from_multihash(mh: multihash::Multihash<64>) -> Self {
123 Self(mh)
124 }
125
126 /// Parse the canonical multihash byte form
127 /// (`varint(code) ++ varint(size) ++ digest`). Wire-format
128 /// compatible with libp2p.
129 pub fn from_bytes(bytes: &[u8]) -> Result<Self, multihash::Error> {
130 multihash::Multihash::from_bytes(bytes).map(Self)
131 }
132
133 /// Canonical multihash byte form, matches libp2p's
134 /// `PeerId::to_bytes`.
135 pub fn to_bytes(&self) -> Vec<u8> {
136 self.0.to_bytes()
137 }
138
139 /// Multihash algorithm code (e.g. `0x00` identity, `0x12`
140 /// sha2-256).
141 pub fn code(&self) -> u64 {
142 self.0.code()
143 }
144
145 /// Raw digest bytes (after the multihash code + size prefix).
146 pub fn digest(&self) -> &[u8] {
147 self.0.digest()
148 }
149
150 /// Borrow the inner multihash.
151 pub fn as_multihash(&self) -> &multihash::Multihash<64> {
152 &self.0
153 }
154
155 /// Recover the inner u64 of a `PeerId::from(u64)`. Returns
156 /// `None` for non-identity-coded or non-8-byte digests.
157 pub fn as_identity_u64(&self) -> Option<u64> {
158 if self.0.code() == Self::IDENTITY {
159 let d = self.0.digest();
160 if d.len() == 8 {
161 return Some(u64::from_be_bytes(d.try_into().expect("checked len == 8")));
162 }
163 }
164 None
165 }
166}
167
168impl From<u64> for PeerId {
169 /// Test convenience: wraps the u64 as an 8-byte identity-coded
170 /// multihash. **Not for production identity** — use
171 /// `PeerId::from_multihash` / `from_bytes` for keyed identities.
172 fn from(value: u64) -> Self {
173 let bytes = value.to_be_bytes();
174 let mh = multihash::Multihash::<64>::wrap(Self::IDENTITY, &bytes)
175 .expect("identity hash of 8 bytes always fits in 64");
176 Self(mh)
177 }
178}
179
180impl fmt::Display for PeerId {
181 /// Base58btc-encoded multihash; matches libp2p's `PeerId::to_base58`.
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 f.write_str(&bs58::encode(self.to_bytes()).into_string())
184 }
185}
186