Skip to main content

sedsnet/
config.rs

1//! Runtime telemetry configuration and schema registry.
2//!
3//! v4 removes compile-time schema generation. `DataType` and `DataEndpoint`
4//! are stable runtime IDs, and metadata is looked up through the process-local
5//! registry. Applications may seed the registry from JSON at startup, or add
6//! endpoints/types as the network announces them.
7
8#[path = "schema_identity.rs"]
9mod schema_identity;
10
11use crate::{
12    E2eEncryptionPolicy, EndpointMeta, MessageClass, MessageDataType, MessageElement, MessageMeta,
13    ReliableMode, TelemetryError, TelemetryResult, parse_f64, parse_strings, parse_usize,
14};
15#[cfg(not(feature = "std"))]
16use alloc::boxed::Box;
17use alloc::sync::Arc;
18use alloc::{
19    string::{String, ToString},
20    vec::Vec,
21};
22use core::mem::size_of;
23#[cfg(not(feature = "std"))]
24use core::sync::atomic::AtomicPtr;
25use core::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
26
27#[cfg(feature = "std")]
28use std::sync::OnceLock;
29#[cfg(feature = "std")]
30use std::sync::RwLock;
31
32// -----------------------------------------------------------------------------
33// Device-/build-time constants
34// -----------------------------------------------------------------------------
35
36pub const DEVICE_IDENTIFIER: &str = match option_env!("DEVICE_IDENTIFIER") {
37    Some(val) => parse_strings(val),
38    None => "TEST_PLATFORM",
39};
40
41#[cfg(feature = "std")]
42static RUNTIME_DEVICE_IDENTIFIER: OnceLock<RwLock<String>> = OnceLock::new();
43
44pub fn runtime_device_identifier() -> String {
45    #[cfg(feature = "std")]
46    {
47        RUNTIME_DEVICE_IDENTIFIER
48            .get_or_init(|| RwLock::new(DEVICE_IDENTIFIER.to_string()))
49            .read()
50            .map(|value| value.clone())
51            .unwrap_or_else(|_| DEVICE_IDENTIFIER.to_string())
52    }
53    #[cfg(not(feature = "std"))]
54    {
55        DEVICE_IDENTIFIER.to_string()
56    }
57}
58
59pub fn set_runtime_device_identifier(value: &str) -> TelemetryResult<()> {
60    if value.is_empty() {
61        return Err(TelemetryError::BadArg);
62    }
63    #[cfg(feature = "std")]
64    {
65        let lock =
66            RUNTIME_DEVICE_IDENTIFIER.get_or_init(|| RwLock::new(DEVICE_IDENTIFIER.to_string()));
67        let mut guard = lock
68            .write()
69            .map_err(|_| TelemetryError::Io("device id lock"))?;
70        *guard = value.to_string();
71        Ok(())
72    }
73    #[cfg(not(feature = "std"))]
74    {
75        let _ = value;
76        Err(TelemetryError::BadArg)
77    }
78}
79
80pub const MAX_RECENT_RX_IDS: usize = match option_env!("MAX_RECENT_RX_IDS") {
81    Some(val) => parse_usize(val),
82    None => 128,
83};
84
85pub const STARTING_QUEUE_SIZE: usize = match option_env!("STARTING_QUEUE_SIZE") {
86    Some(val) => parse_usize(val),
87    None => 128,
88};
89
90pub const MAX_QUEUE_BUDGET: usize = match option_env!("MAX_QUEUE_BUDGET") {
91    Some(val) => parse_usize(val),
92    None => match option_env!("MAX_QUEUE_SIZE") {
93        Some(val) => parse_usize(val),
94        None => 1024 * 100,
95    },
96};
97
98/// Read buffer used by bounded JSON schema loading. Embedded `std` builds can
99/// lower or raise it with `SCHEMA_JSON_CHUNK_BYTES`; `no_std` builds do not use
100/// a file-read buffer.
101#[cfg(feature = "std")]
102pub const SCHEMA_JSON_CHUNK_BYTES: usize = match option_env!("SCHEMA_JSON_CHUNK_BYTES") {
103    Some(value) => {
104        let parsed = parse_usize(value);
105        if parsed == 0 { 1 } else { parsed }
106    }
107    None => 512,
108};
109
110/// Maximum JSON input size relative to the retained schema budget. This permits
111/// normal formatting overhead without allowing an attacker-controlled document
112/// to drive an unbounded parser allocation.
113#[cfg(feature = "std")]
114const SCHEMA_JSON_INPUT_MULTIPLIER: usize = 8;
115
116pub const RECENT_RX_QUEUE_BYTES: usize = {
117    let requested = MAX_RECENT_RX_IDS.saturating_mul(size_of::<u64>());
118    if requested < MAX_QUEUE_BUDGET {
119        requested
120    } else {
121        MAX_QUEUE_BUDGET
122    }
123};
124
125pub const QUEUE_GROW_STEP: f64 = match option_env!("QUEUE_GROW_STEP") {
126    Some(val) => parse_f64(val),
127    None => 3.2,
128};
129
130pub const PAYLOAD_COMPRESS_THRESHOLD: usize = match option_env!("PAYLOAD_COMPRESS_THRESHOLD") {
131    Some(val) => parse_usize(val),
132    None => 128,
133};
134
135pub const STATIC_STRING_LENGTH: usize = match option_env!("STATIC_STRING_LENGTH") {
136    Some(val) => parse_usize(val),
137    None => 1024,
138};
139
140pub const STATIC_HEX_LENGTH: usize = match option_env!("STATIC_HEX_LENGTH") {
141    Some(val) => parse_usize(val),
142    None => 1024,
143};
144
145pub const STRING_PRECISION: usize = match option_env!("STRING_PRECISION") {
146    Some(val) => parse_usize(val),
147    None => 8,
148};
149
150#[cfg(not(feature = "compact-packet-store"))]
151sedsnet_macros::define_stack_payload!(env = "MAX_STACK_PAYLOAD", default = 64);
152#[cfg(feature = "compact-packet-store")]
153#[allow(dead_code)]
154mod compact_inline_limit {
155    sedsnet_macros::define_stack_payload!(env = "MAX_STACK_PAYLOAD", default = 64);
156}
157#[cfg(feature = "compact-packet-store")]
158pub use compact_inline_limit::STACK_PAYLOAD_SIZE;
159#[cfg(feature = "compact-packet-store")]
160pub type StandardSmallPayload = crate::small_payload::SmallPayload<STACK_PAYLOAD_SIZE>;
161
162pub const MAX_HANDLER_RETRIES: usize = match option_env!("MAX_HANDLER_RETRIES") {
163    Some(val) => parse_usize(val),
164    None => 3,
165};
166
167pub const RELIABLE_RETRANSMIT_MS: u64 = match option_env!("RELIABLE_RETRANSMIT_MS") {
168    Some(val) => parse_usize(val) as u64,
169    None => 200,
170};
171
172pub const RELIABLE_MAX_RETRIES: u32 = match option_env!("RELIABLE_MAX_RETRIES") {
173    Some(val) => parse_usize(val) as u32,
174    None => 8,
175};
176
177pub const RELIABLE_MAX_PENDING: usize = match option_env!("RELIABLE_MAX_PENDING") {
178    Some(val) => parse_usize(val),
179    None => 32,
180};
181
182pub const RELIABLE_MAX_RETURN_ROUTES: usize = match option_env!("RELIABLE_MAX_RETURN_ROUTES") {
183    Some(val) => parse_usize(val),
184    // Return routes protect ACKs for packets that are still in the reliable
185    // pending window. Tying this to the unrelated RX de-duplication cache can
186    // evict a live ACK path when memory-constrained targets reduce
187    // MAX_RECENT_RX_IDS below RELIABLE_MAX_PENDING.
188    None => RELIABLE_MAX_PENDING,
189};
190
191pub const RELIABLE_MAX_END_TO_END_PENDING: usize =
192    match option_env!("RELIABLE_MAX_END_TO_END_PENDING") {
193        Some(val) => parse_usize(val),
194        None => RELIABLE_MAX_PENDING,
195    };
196
197pub const RELIABLE_MAX_END_TO_END_ACK_CACHE: usize =
198    match option_env!("RELIABLE_MAX_END_TO_END_ACK_CACHE") {
199        Some(val) => parse_usize(val),
200        None => MAX_RECENT_RX_IDS,
201    };
202
203static RUNTIME_PAYLOAD_COMPRESS_THRESHOLD: AtomicUsize =
204    AtomicUsize::new(PAYLOAD_COMPRESS_THRESHOLD);
205static RUNTIME_STATIC_STRING_LENGTH: AtomicUsize = AtomicUsize::new(STATIC_STRING_LENGTH);
206static RUNTIME_STATIC_HEX_LENGTH: AtomicUsize = AtomicUsize::new(STATIC_HEX_LENGTH);
207static RUNTIME_STRING_PRECISION: AtomicUsize = AtomicUsize::new(STRING_PRECISION);
208static RUNTIME_MAX_HANDLER_RETRIES: AtomicUsize = AtomicUsize::new(MAX_HANDLER_RETRIES);
209static RUNTIME_RELIABLE_RETRANSMIT_MS: AtomicU32 = AtomicU32::new(RELIABLE_RETRANSMIT_MS as u32);
210static RUNTIME_RELIABLE_MAX_RETRIES: AtomicU32 = AtomicU32::new(RELIABLE_MAX_RETRIES);
211static RUNTIME_RELIABLE_MAX_PENDING: AtomicUsize = AtomicUsize::new(RELIABLE_MAX_PENDING);
212static RUNTIME_RELIABLE_MAX_RETURN_ROUTES: AtomicUsize =
213    AtomicUsize::new(RELIABLE_MAX_RETURN_ROUTES);
214static RUNTIME_RELIABLE_MAX_END_TO_END_PENDING: AtomicUsize =
215    AtomicUsize::new(RELIABLE_MAX_END_TO_END_PENDING);
216static RUNTIME_RELIABLE_MAX_END_TO_END_ACK_CACHE: AtomicUsize =
217    AtomicUsize::new(RELIABLE_MAX_END_TO_END_ACK_CACHE);
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
220pub struct RuntimeTuningConfig {
221    pub payload_compress_threshold: usize,
222    pub static_string_length: usize,
223    pub static_hex_length: usize,
224    pub string_precision: usize,
225    pub max_handler_retries: usize,
226    pub reliable_retransmit_ms: u32,
227    pub reliable_max_retries: u32,
228    pub reliable_max_pending: usize,
229    pub reliable_max_return_routes: usize,
230    pub reliable_max_end_to_end_pending: usize,
231    pub reliable_max_end_to_end_ack_cache: usize,
232}
233
234impl Default for RuntimeTuningConfig {
235    fn default() -> Self {
236        Self {
237            payload_compress_threshold: PAYLOAD_COMPRESS_THRESHOLD,
238            static_string_length: STATIC_STRING_LENGTH,
239            static_hex_length: STATIC_HEX_LENGTH,
240            string_precision: STRING_PRECISION,
241            max_handler_retries: MAX_HANDLER_RETRIES,
242            reliable_retransmit_ms: RELIABLE_RETRANSMIT_MS as u32,
243            reliable_max_retries: RELIABLE_MAX_RETRIES,
244            reliable_max_pending: RELIABLE_MAX_PENDING,
245            reliable_max_return_routes: RELIABLE_MAX_RETURN_ROUTES,
246            reliable_max_end_to_end_pending: RELIABLE_MAX_END_TO_END_PENDING,
247            reliable_max_end_to_end_ack_cache: RELIABLE_MAX_END_TO_END_ACK_CACHE,
248        }
249    }
250}
251
252impl RuntimeTuningConfig {
253    pub fn validate(self) -> TelemetryResult<()> {
254        if self.static_string_length == 0
255            || self.static_hex_length == 0
256            || self.max_handler_retries == 0
257            || self.reliable_retransmit_ms == 0
258            || self.reliable_max_retries == 0
259            || self.reliable_max_pending == 0
260            || self.reliable_max_return_routes == 0
261            || self.reliable_max_end_to_end_pending == 0
262            || self.reliable_max_end_to_end_ack_cache == 0
263        {
264            return Err(TelemetryError::BadArg);
265        }
266        Ok(())
267    }
268}
269
270pub fn set_runtime_tuning_config(cfg: RuntimeTuningConfig) -> TelemetryResult<()> {
271    cfg.validate()?;
272    RUNTIME_PAYLOAD_COMPRESS_THRESHOLD.store(cfg.payload_compress_threshold, Ordering::Relaxed);
273    RUNTIME_STATIC_STRING_LENGTH.store(cfg.static_string_length, Ordering::Relaxed);
274    RUNTIME_STATIC_HEX_LENGTH.store(cfg.static_hex_length, Ordering::Relaxed);
275    RUNTIME_STRING_PRECISION.store(cfg.string_precision, Ordering::Relaxed);
276    RUNTIME_MAX_HANDLER_RETRIES.store(cfg.max_handler_retries, Ordering::Relaxed);
277    RUNTIME_RELIABLE_RETRANSMIT_MS.store(cfg.reliable_retransmit_ms, Ordering::Relaxed);
278    RUNTIME_RELIABLE_MAX_RETRIES.store(cfg.reliable_max_retries, Ordering::Relaxed);
279    RUNTIME_RELIABLE_MAX_PENDING.store(cfg.reliable_max_pending, Ordering::Relaxed);
280    RUNTIME_RELIABLE_MAX_RETURN_ROUTES.store(cfg.reliable_max_return_routes, Ordering::Relaxed);
281    RUNTIME_RELIABLE_MAX_END_TO_END_PENDING
282        .store(cfg.reliable_max_end_to_end_pending, Ordering::Relaxed);
283    RUNTIME_RELIABLE_MAX_END_TO_END_ACK_CACHE
284        .store(cfg.reliable_max_end_to_end_ack_cache, Ordering::Relaxed);
285    Ok(())
286}
287
288pub fn runtime_tuning_config() -> RuntimeTuningConfig {
289    RuntimeTuningConfig {
290        payload_compress_threshold: runtime_payload_compress_threshold(),
291        static_string_length: runtime_static_string_length(),
292        static_hex_length: runtime_static_hex_length(),
293        string_precision: runtime_string_precision(),
294        max_handler_retries: runtime_max_handler_retries(),
295        reliable_retransmit_ms: runtime_reliable_retransmit_ms() as u32,
296        reliable_max_retries: runtime_reliable_max_retries(),
297        reliable_max_pending: runtime_reliable_max_pending(),
298        reliable_max_return_routes: runtime_reliable_max_return_routes(),
299        reliable_max_end_to_end_pending: runtime_reliable_max_end_to_end_pending(),
300        reliable_max_end_to_end_ack_cache: runtime_reliable_max_end_to_end_ack_cache(),
301    }
302}
303
304#[inline]
305pub fn runtime_payload_compress_threshold() -> usize {
306    RUNTIME_PAYLOAD_COMPRESS_THRESHOLD.load(Ordering::Relaxed)
307}
308
309#[inline]
310pub fn runtime_static_string_length() -> usize {
311    RUNTIME_STATIC_STRING_LENGTH.load(Ordering::Relaxed)
312}
313
314#[inline]
315pub fn runtime_static_hex_length() -> usize {
316    RUNTIME_STATIC_HEX_LENGTH.load(Ordering::Relaxed)
317}
318
319#[inline]
320pub fn runtime_string_precision() -> usize {
321    RUNTIME_STRING_PRECISION.load(Ordering::Relaxed)
322}
323
324#[inline]
325pub fn runtime_max_handler_retries() -> usize {
326    RUNTIME_MAX_HANDLER_RETRIES.load(Ordering::Relaxed)
327}
328
329#[inline]
330pub fn runtime_reliable_retransmit_ms() -> u64 {
331    u64::from(RUNTIME_RELIABLE_RETRANSMIT_MS.load(Ordering::Relaxed))
332}
333
334#[inline]
335pub fn runtime_reliable_max_retries() -> u32 {
336    RUNTIME_RELIABLE_MAX_RETRIES.load(Ordering::Relaxed)
337}
338
339#[inline]
340pub fn runtime_reliable_max_pending() -> usize {
341    RUNTIME_RELIABLE_MAX_PENDING.load(Ordering::Relaxed)
342}
343
344#[inline]
345pub fn runtime_reliable_max_return_routes() -> usize {
346    RUNTIME_RELIABLE_MAX_RETURN_ROUTES.load(Ordering::Relaxed)
347}
348
349#[inline]
350pub fn runtime_reliable_max_end_to_end_pending() -> usize {
351    RUNTIME_RELIABLE_MAX_END_TO_END_PENDING.load(Ordering::Relaxed)
352}
353
354#[inline]
355pub fn runtime_reliable_max_end_to_end_ack_cache() -> usize {
356    RUNTIME_RELIABLE_MAX_END_TO_END_ACK_CACHE.load(Ordering::Relaxed)
357}
358
359/// Runtime memory limits for router/relay queue-backed state.
360///
361/// Compile-time environment values remain the defaults for embedded builds, but applications using
362/// prebuilt binaries can now choose per-instance budgets at construction time.
363#[derive(Debug, Clone, Copy, PartialEq)]
364pub struct RuntimeMemoryConfig {
365    pub max_queue_budget: usize,
366    pub max_recent_rx_ids: usize,
367    pub starting_queue_size: usize,
368    pub queue_grow_step: f64,
369}
370
371impl RuntimeMemoryConfig {
372    pub const fn default_const() -> Self {
373        Self {
374            max_queue_budget: MAX_QUEUE_BUDGET,
375            max_recent_rx_ids: MAX_RECENT_RX_IDS,
376            starting_queue_size: STARTING_QUEUE_SIZE,
377            queue_grow_step: QUEUE_GROW_STEP,
378        }
379    }
380
381    pub fn new(
382        max_queue_budget: usize,
383        max_recent_rx_ids: usize,
384        starting_queue_size: usize,
385        queue_grow_step: f64,
386    ) -> TelemetryResult<Self> {
387        let cfg = Self {
388            max_queue_budget,
389            max_recent_rx_ids,
390            starting_queue_size,
391            queue_grow_step,
392        };
393        cfg.validate()?;
394        Ok(cfg)
395    }
396
397    pub fn validate(self) -> TelemetryResult<()> {
398        if self.max_queue_budget == 0 {
399            return Err(TelemetryError::BadArg);
400        }
401        if self.max_recent_rx_ids == 0 {
402            return Err(TelemetryError::BadArg);
403        }
404        if self.starting_queue_size == 0 || self.starting_queue_size > self.max_queue_budget {
405            return Err(TelemetryError::BadArg);
406        }
407        if !self.queue_grow_step.is_finite() || self.queue_grow_step < 1.0 {
408            return Err(TelemetryError::BadArg);
409        }
410        Ok(())
411    }
412
413    pub fn recent_rx_queue_bytes(self) -> usize {
414        self.max_recent_rx_ids
415            .saturating_mul(size_of::<u64>())
416            .min(self.max_queue_budget)
417            .max(1)
418    }
419}
420
421impl Default for RuntimeMemoryConfig {
422    fn default() -> Self {
423        Self::default_const()
424    }
425}
426
427// -----------------------------------------------------------------------------
428// Runtime IDs
429// -----------------------------------------------------------------------------
430
431#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
432#[repr(transparent)]
433pub struct DataEndpoint(pub u32);
434
435impl DataEndpoint {
436    /// Stable identity for a name, including a definition supplied later by another node.
437    pub const fn identity(name: &str) -> Self {
438        Self(schema_identity::endpoint_id(name))
439    }
440
441    pub const TIME_SYNC: Self = Self(200);
442    pub const DISCOVERY: Self = Self(201);
443    pub const TELEMETRY_ERROR: Self = Self(202);
444
445    #[allow(non_upper_case_globals)]
446    pub const TelemetryError: Self = Self::TELEMETRY_ERROR;
447    #[allow(non_upper_case_globals)]
448    pub const TimeSync: Self = Self::TIME_SYNC;
449    #[allow(non_upper_case_globals)]
450    pub const Discovery: Self = Self::DISCOVERY;
451
452    #[inline]
453    pub const fn as_u32(self) -> u32 {
454        self.0
455    }
456
457    #[inline]
458    pub fn try_from_u32(x: u32) -> Option<Self> {
459        if endpoint_exists(Self(x)) {
460            Some(Self(x))
461        } else {
462            None
463        }
464    }
465
466    #[cfg_attr(feature = "std", inline)]
467    #[cfg_attr(not(feature = "std"), inline(never))]
468    pub fn try_named(name: &str) -> Option<Self> {
469        #[cfg(feature = "std")]
470        {
471            registry()
472                .lock()
473                .expect("schema registry poisoned")
474                .endpoints
475                .iter()
476                .find(|(_, meta)| meta.name.as_ref() == name)
477                .map(|(id, _)| *id)
478        }
479        #[cfg(not(feature = "std"))]
480        {
481            embedded_overlay_endpoint_by_name(name)
482                .or_else(|| {
483                    EMBEDDED_BUILTIN_ENDPOINTS
484                        .iter()
485                        .chain(EMBEDDED_SCHEMA_ENDPOINTS.iter())
486                        .find(|def| {
487                            def.name == name
488                                && endpoint_visible_at(
489                                    **def,
490                                    EMBEDDED_ENDPOINT_HEAD.load(Ordering::Acquire),
491                                )
492                        })
493                        .copied()
494                })
495                .map(|def| def.id)
496        }
497    }
498
499    #[inline]
500    pub fn named(name: &str) -> Self {
501        Self::try_named(name).unwrap_or_else(|| panic!("unknown data endpoint: {name}"))
502    }
503}
504
505impl core::fmt::Debug for DataEndpoint {
506    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
507        let name = match *self {
508            Self::TelemetryError => "SEDSNET_ERROR",
509            Self::TimeSync => "SEDSNET_TIME_SYNC",
510            Self::Discovery => "SEDSNET_DISCOVERY",
511            _ => {
512                let meta = get_endpoint_meta(*self);
513                let meta_name = meta.name_ref();
514                if meta_name != "UNKNOWN_ENDPOINT" {
515                    return f.write_str(meta_name);
516                }
517                return write!(f, "DataEndpoint({})", self.0);
518            }
519        };
520        f.write_str(name)
521    }
522}
523
524#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
525#[repr(transparent)]
526pub struct DataType(pub u32);
527
528impl DataType {
529    /// Stable identity for a name, including a definition supplied later by another node.
530    pub const fn identity(name: &str) -> Self {
531        Self(schema_identity::type_id(name))
532    }
533
534    pub const TELEMETRY_ERROR: Self = Self(0);
535    pub const RELIABLE_ACK: Self = Self(1);
536    pub const RELIABLE_PACKET_REQUEST: Self = Self(2);
537    pub const RELIABLE_PARTIAL_ACK: Self = Self(3);
538    pub const TIME_SYNC_ANNOUNCE: Self = Self(4);
539    pub const TIME_SYNC_REQUEST: Self = Self(5);
540    pub const TIME_SYNC_RESPONSE: Self = Self(6);
541    pub const DISCOVERY_ANNOUNCE: Self = Self(7);
542    pub const DISCOVERY_TIMESYNC_SOURCES: Self = Self(8);
543    pub const DISCOVERY_TOPOLOGY: Self = Self(9);
544    pub const DISCOVERY_SCHEMA: Self = Self(10);
545    pub const DISCOVERY_TOPOLOGY_REQUEST: Self = Self(11);
546    pub const DISCOVERY_SCHEMA_REQUEST: Self = Self(12);
547    pub const MANAGED_VARIABLE_REQUEST: Self = Self(13);
548    pub const MANAGED_VARIABLE_VALUE: Self = Self(14);
549    pub const DISCOVERY_LEAVE: Self = Self(15);
550    pub const DISCOVERY_LINK_CAPABILITIES: Self = Self(16);
551    pub const DISCOVERY_ADDRESS: Self = Self(17);
552    pub const P2P_MESSAGE: Self = Self(18);
553
554    #[allow(non_upper_case_globals)]
555    pub const TelemetryError: Self = Self::TELEMETRY_ERROR;
556    #[allow(non_upper_case_globals)]
557    pub const ReliableAck: Self = Self::RELIABLE_ACK;
558    #[allow(non_upper_case_globals)]
559    pub const ReliablePacketRequest: Self = Self::RELIABLE_PACKET_REQUEST;
560    #[allow(non_upper_case_globals)]
561    pub const ReliablePartialAck: Self = Self::RELIABLE_PARTIAL_ACK;
562    #[allow(non_upper_case_globals)]
563    pub const TimeSyncAnnounce: Self = Self::TIME_SYNC_ANNOUNCE;
564    #[allow(non_upper_case_globals)]
565    pub const TimeSyncRequest: Self = Self::TIME_SYNC_REQUEST;
566    #[allow(non_upper_case_globals)]
567    pub const TimeSyncResponse: Self = Self::TIME_SYNC_RESPONSE;
568    #[allow(non_upper_case_globals)]
569    pub const DiscoveryAnnounce: Self = Self::DISCOVERY_ANNOUNCE;
570    #[allow(non_upper_case_globals)]
571    pub const DiscoveryTimeSyncSources: Self = Self::DISCOVERY_TIMESYNC_SOURCES;
572    #[allow(non_upper_case_globals)]
573    pub const DiscoveryTopology: Self = Self::DISCOVERY_TOPOLOGY;
574    #[allow(non_upper_case_globals)]
575    pub const DiscoverySchema: Self = Self::DISCOVERY_SCHEMA;
576    #[allow(non_upper_case_globals)]
577    pub const DiscoveryTopologyRequest: Self = Self::DISCOVERY_TOPOLOGY_REQUEST;
578    #[allow(non_upper_case_globals)]
579    pub const DiscoverySchemaRequest: Self = Self::DISCOVERY_SCHEMA_REQUEST;
580    #[allow(non_upper_case_globals)]
581    pub const ManagedVariableRequest: Self = Self::MANAGED_VARIABLE_REQUEST;
582    #[allow(non_upper_case_globals)]
583    pub const ManagedVariableValue: Self = Self::MANAGED_VARIABLE_VALUE;
584    #[allow(non_upper_case_globals)]
585    pub const DiscoveryLeave: Self = Self::DISCOVERY_LEAVE;
586    #[allow(non_upper_case_globals)]
587    pub const DiscoveryLinkCapabilities: Self = Self::DISCOVERY_LINK_CAPABILITIES;
588    #[allow(non_upper_case_globals)]
589    pub const DiscoveryAddress: Self = Self::DISCOVERY_ADDRESS;
590    #[allow(non_upper_case_globals)]
591    pub const P2pMessage: Self = Self::P2P_MESSAGE;
592
593    #[inline]
594    pub const fn as_u32(self) -> u32 {
595        self.0
596    }
597
598    #[inline]
599    pub fn try_from_u32(x: u32) -> Option<Self> {
600        if data_type_exists(Self(x)) {
601            Some(Self(x))
602        } else {
603            None
604        }
605    }
606
607    #[cfg_attr(feature = "std", inline)]
608    #[cfg_attr(not(feature = "std"), inline(never))]
609    pub fn try_named(name: &str) -> Option<Self> {
610        #[cfg(feature = "std")]
611        {
612            registry()
613                .lock()
614                .expect("schema registry poisoned")
615                .types
616                .iter()
617                .find(|(_, meta)| meta.name.as_ref() == name)
618                .map(|(id, _)| *id)
619        }
620        #[cfg(not(feature = "std"))]
621        {
622            embedded_overlay_type_by_name(name)
623                .or_else(|| {
624                    EMBEDDED_BUILTIN_TYPES
625                        .iter()
626                        .chain(EMBEDDED_SCHEMA_TYPES.iter())
627                        .find(|def| {
628                            def.name == name
629                                && type_visible_at(def, EMBEDDED_TYPE_HEAD.load(Ordering::Acquire))
630                        })
631                        .copied()
632                })
633                .map(|def| def.id)
634        }
635    }
636
637    #[inline]
638    pub fn named(name: &str) -> Self {
639        Self::try_named(name).unwrap_or_else(|| panic!("unknown data type: {name}"))
640    }
641}
642
643impl core::fmt::Debug for DataType {
644    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
645        let name = match *self {
646            Self::TelemetryError => "SEDSNET_ERROR",
647            Self::ReliableAck => "ReliableAck",
648            Self::ReliablePacketRequest => "ReliablePacketRequest",
649            Self::ReliablePartialAck => "ReliablePartialAck",
650            Self::TimeSyncAnnounce => "SedsnetTimeSyncAnnounce",
651            Self::TimeSyncRequest => "SedsnetTimeSyncRequest",
652            Self::TimeSyncResponse => "SedsnetTimeSyncResponse",
653            Self::DiscoveryAnnounce => "SedsnetDiscoveryAnnounce",
654            Self::DiscoveryTimeSyncSources => "SedsnetDiscoveryTimeSyncSources",
655            Self::DiscoveryTopology => "SedsnetDiscoveryTopology",
656            Self::DiscoverySchema => "SedsnetDiscoverySchema",
657            Self::DiscoveryTopologyRequest => "SedsnetDiscoveryTopologyRequest",
658            Self::DiscoverySchemaRequest => "SedsnetDiscoverySchemaRequest",
659            Self::ManagedVariableRequest => "SedsnetManagedVariableRequest",
660            Self::ManagedVariableValue => "SedsnetManagedVariableValue",
661            Self::DiscoveryLeave => "SedsnetDiscoveryLeave",
662            Self::DiscoveryLinkCapabilities => "SedsnetDiscoveryLinkCapabilities",
663            Self::DiscoveryAddress => "SedsnetDiscoveryAddress",
664            Self::P2pMessage => "SedsnetP2pMessage",
665            _ => {
666                let meta = get_message_meta(*self);
667                let meta_name = meta.name_ref();
668                if meta_name != "UNKNOWN_TYPE" {
669                    return f.write_str(meta_name);
670                }
671                return write!(f, "DataType({})", self.0);
672            }
673        };
674        f.write_str(name)
675    }
676}
677
678// -----------------------------------------------------------------------------
679// Runtime registry
680// -----------------------------------------------------------------------------
681
682#[derive(Debug, Clone, Copy, PartialEq, Eq)]
683pub struct EndpointDefinition {
684    pub id: DataEndpoint,
685    pub name: &'static str,
686    pub description: &'static str,
687    pub link_local_only: bool,
688}
689
690#[derive(Debug, Clone, Copy, PartialEq, Eq)]
691pub struct DataTypeDefinition {
692    pub id: DataType,
693    pub name: &'static str,
694    pub description: &'static str,
695    pub element: MessageElement,
696    pub endpoints: &'static [DataEndpoint],
697    pub reliable: ReliableMode,
698    pub priority: u8,
699    pub e2e_encryption: E2eEncryptionPolicy,
700}
701
702#[cfg(not(feature = "std"))]
703include!(concat!(env!("OUT_DIR"), "/embedded_schema.rs"));
704
705#[derive(Debug, Clone)]
706pub struct RuntimeSchemaSnapshot {
707    pub endpoints: Vec<EndpointDefinition>,
708    pub types: Vec<DataTypeDefinition>,
709}
710
711#[derive(Debug, Clone, PartialEq, Eq)]
712pub struct OwnedEndpointDefinition {
713    pub id: DataEndpoint,
714    pub name: String,
715    pub description: String,
716    pub link_local_only: bool,
717}
718
719#[derive(Debug, Clone, PartialEq, Eq)]
720pub struct OwnedDataTypeDefinition {
721    pub id: DataType,
722    pub name: String,
723    pub description: String,
724    pub element: MessageElement,
725    pub endpoints: Vec<DataEndpoint>,
726    pub reliable: ReliableMode,
727    pub priority: u8,
728    pub e2e_encryption: E2eEncryptionPolicy,
729}
730
731#[derive(Debug, Clone)]
732pub struct OwnedRuntimeSchemaSnapshot {
733    pub endpoints: Vec<OwnedEndpointDefinition>,
734    pub types: Vec<OwnedDataTypeDefinition>,
735}
736
737// Schema catalogs are small, and embedded builds value flash more than the
738// asymptotic speed of Rust's generic unstable sort. These concrete insertion
739// sorts avoid pulling several independently monomorphized quicksort engines
740// into constrained firmware images.
741#[cfg(feature = "std")]
742pub(crate) fn sort_owned_schema(snapshot: &mut OwnedRuntimeSchemaSnapshot) {
743    for index in 1..snapshot.endpoints.len() {
744        let mut cursor = index;
745        while cursor > 0 && snapshot.endpoints[cursor - 1].id.0 > snapshot.endpoints[cursor].id.0 {
746            snapshot.endpoints.swap(cursor - 1, cursor);
747            cursor -= 1;
748        }
749    }
750    for index in 1..snapshot.types.len() {
751        let mut cursor = index;
752        while cursor > 0 && snapshot.types[cursor - 1].id.0 > snapshot.types[cursor].id.0 {
753            snapshot.types.swap(cursor - 1, cursor);
754            cursor -= 1;
755        }
756    }
757}
758
759impl PartialEq<EndpointDefinition> for OwnedEndpointDefinition {
760    fn eq(&self, other: &EndpointDefinition) -> bool {
761        self.id == other.id
762            && self.name == other.name
763            && self.description == other.description
764            && self.link_local_only == other.link_local_only
765    }
766}
767
768#[derive(Debug, Clone, Copy, PartialEq, Eq)]
769pub enum SchemaMergeDecision {
770    Added,
771    Unchanged,
772    ReplacedLocal,
773    KeptLocal,
774}
775
776#[derive(Debug, Clone, Copy, PartialEq, Eq)]
777pub struct SchemaMergeReport {
778    pub endpoints_added: usize,
779    pub endpoints_replaced: usize,
780    pub endpoints_kept: usize,
781    pub types_added: usize,
782    pub types_replaced: usize,
783    pub types_kept: usize,
784}
785
786impl SchemaMergeReport {
787    #[inline]
788    pub const fn changed(&self) -> bool {
789        self.endpoints_added != 0
790            || self.endpoints_replaced != 0
791            || self.types_added != 0
792            || self.types_replaced != 0
793    }
794}
795
796#[cfg(feature = "std")]
797#[derive(Debug, Clone)]
798struct Registry {
799    endpoints: Vec<(DataEndpoint, EndpointMeta)>,
800    types: Vec<(DataType, MessageMeta)>,
801    next_endpoint_id: u32,
802    next_type_id: u32,
803}
804
805#[cfg(feature = "std")]
806impl Registry {
807    fn new() -> Self {
808        let mut reg = Self {
809            endpoints: Vec::new(),
810            types: Vec::new(),
811            next_endpoint_id: 100,
812            next_type_id: 100,
813        };
814        reg.register_endpoint_definition(EndpointDefinition {
815            id: DataEndpoint::TelemetryError,
816            name: "SEDSNET_ERROR",
817            description: "",
818            link_local_only: false,
819        })
820        .expect("built-in endpoint");
821        reg.register_endpoint_definition(EndpointDefinition {
822            id: DataEndpoint::TimeSync,
823            name: "SEDSNET_TIME_SYNC",
824            description: "",
825            link_local_only: false,
826        })
827        .expect("built-in endpoint");
828        reg.register_endpoint_definition(EndpointDefinition {
829            id: DataEndpoint::Discovery,
830            name: "SEDSNET_DISCOVERY",
831            description: "",
832            link_local_only: false,
833        })
834        .expect("built-in endpoint");
835
836        reg.register_type_definition(DataTypeDefinition {
837            id: DataType::TelemetryError,
838            name: "SEDSNET_ERROR",
839            description: "",
840            element: MessageElement::Dynamic(MessageDataType::String, MessageClass::Error),
841            endpoints: &[DataEndpoint::TelemetryError],
842            reliable: ReliableMode::None,
843            priority: 255,
844            e2e_encryption: E2eEncryptionPolicy::PreferOff,
845        })
846        .expect("built-in type");
847        reg.register_type_definition(DataTypeDefinition {
848            id: DataType::ReliableAck,
849            name: "SEDSNET_RELIABLE_ACK",
850            description: "",
851            element: MessageElement::Static(2, MessageDataType::UInt32, MessageClass::Data),
852            endpoints: &[DataEndpoint::TelemetryError],
853            reliable: ReliableMode::None,
854            priority: 250,
855            e2e_encryption: E2eEncryptionPolicy::PreferOff,
856        })
857        .expect("built-in type");
858        reg.register_type_definition(DataTypeDefinition {
859            id: DataType::ReliablePacketRequest,
860            name: "SEDSNET_RELIABLE_PACKET_REQUEST",
861            description: "",
862            element: MessageElement::Static(2, MessageDataType::UInt32, MessageClass::Data),
863            endpoints: &[DataEndpoint::TelemetryError],
864            reliable: ReliableMode::None,
865            priority: 250,
866            e2e_encryption: E2eEncryptionPolicy::PreferOff,
867        })
868        .expect("built-in type");
869        reg.register_type_definition(DataTypeDefinition {
870            id: DataType::ReliablePartialAck,
871            name: "SEDSNET_RELIABLE_PARTIAL_ACK",
872            description: "",
873            element: MessageElement::Static(2, MessageDataType::UInt32, MessageClass::Data),
874            endpoints: &[DataEndpoint::TelemetryError],
875            reliable: ReliableMode::None,
876            priority: 250,
877            e2e_encryption: E2eEncryptionPolicy::PreferOff,
878        })
879        .expect("built-in type");
880        reg.register_type_definition(DataTypeDefinition {
881            id: DataType::TimeSyncAnnounce,
882            name: "SEDSNET_TIME_SYNC_ANNOUNCE",
883            description: "",
884            element: MessageElement::Static(2, MessageDataType::UInt64, MessageClass::Data),
885            endpoints: &[DataEndpoint::TimeSync],
886            reliable: ReliableMode::None,
887            priority: 245,
888            e2e_encryption: E2eEncryptionPolicy::PreferOff,
889        })
890        .expect("built-in type");
891        reg.register_type_definition(DataTypeDefinition {
892            id: DataType::TimeSyncRequest,
893            name: "SEDSNET_TIME_SYNC_REQUEST",
894            description: "",
895            element: MessageElement::Static(2, MessageDataType::UInt64, MessageClass::Data),
896            endpoints: &[DataEndpoint::TimeSync],
897            reliable: ReliableMode::None,
898            priority: 245,
899            e2e_encryption: E2eEncryptionPolicy::PreferOff,
900        })
901        .expect("built-in type");
902        reg.register_type_definition(DataTypeDefinition {
903            id: DataType::TimeSyncResponse,
904            name: "SEDSNET_TIME_SYNC_RESPONSE",
905            description: "",
906            element: MessageElement::Static(4, MessageDataType::UInt64, MessageClass::Data),
907            endpoints: &[DataEndpoint::TimeSync],
908            reliable: ReliableMode::None,
909            priority: 245,
910            e2e_encryption: E2eEncryptionPolicy::PreferOff,
911        })
912        .expect("built-in type");
913        reg.register_type_definition(DataTypeDefinition {
914            id: DataType::DiscoveryAnnounce,
915            name: "SEDSNET_DISCOVERY_ANNOUNCE",
916            description: "",
917            element: MessageElement::Dynamic(MessageDataType::UInt32, MessageClass::Data),
918            endpoints: &[DataEndpoint::Discovery],
919            reliable: ReliableMode::None,
920            priority: 240,
921            e2e_encryption: E2eEncryptionPolicy::PreferOff,
922        })
923        .expect("built-in type");
924        reg.register_type_definition(DataTypeDefinition {
925            id: DataType::DiscoveryTimeSyncSources,
926            name: "SEDSNET_DISCOVERY_TIMESYNC_SOURCES",
927            description: "",
928            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
929            endpoints: &[DataEndpoint::Discovery],
930            reliable: ReliableMode::None,
931            priority: 240,
932            e2e_encryption: E2eEncryptionPolicy::PreferOff,
933        })
934        .expect("built-in type");
935        reg.register_type_definition(DataTypeDefinition {
936            id: DataType::DiscoveryTopology,
937            name: "SEDSNET_DISCOVERY_TOPOLOGY",
938            description: "",
939            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
940            endpoints: &[DataEndpoint::Discovery],
941            reliable: ReliableMode::Ordered,
942            priority: 240,
943            e2e_encryption: E2eEncryptionPolicy::PreferOff,
944        })
945        .expect("built-in type");
946        reg.register_type_definition(DataTypeDefinition {
947            id: DataType::DiscoverySchema,
948            name: "SEDSNET_DISCOVERY_SCHEMA",
949            description: "",
950            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
951            endpoints: &[DataEndpoint::Discovery],
952            reliable: ReliableMode::Ordered,
953            priority: 241,
954            e2e_encryption: E2eEncryptionPolicy::PreferOff,
955        })
956        .expect("built-in type");
957        reg.register_type_definition(DataTypeDefinition {
958            id: DataType::DiscoveryTopologyRequest,
959            name: "SEDSNET_DISCOVERY_TOPOLOGY_REQUEST",
960            description: "",
961            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
962            endpoints: &[DataEndpoint::Discovery],
963            reliable: ReliableMode::Ordered,
964            priority: 242,
965            e2e_encryption: E2eEncryptionPolicy::PreferOff,
966        })
967        .expect("built-in type");
968        reg.register_type_definition(DataTypeDefinition {
969            id: DataType::DiscoverySchemaRequest,
970            name: "SEDSNET_DISCOVERY_SCHEMA_REQUEST",
971            description: "",
972            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
973            endpoints: &[DataEndpoint::Discovery],
974            reliable: ReliableMode::Ordered,
975            priority: 242,
976            e2e_encryption: E2eEncryptionPolicy::PreferOff,
977        })
978        .expect("built-in type");
979        reg.register_type_definition(DataTypeDefinition {
980            id: DataType::ManagedVariableRequest,
981            name: "SEDSNET_MANAGED_VARIABLE_REQUEST",
982            description: "",
983            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
984            endpoints: &[DataEndpoint::Discovery],
985            reliable: ReliableMode::Ordered,
986            priority: 243,
987            e2e_encryption: E2eEncryptionPolicy::PreferOff,
988        })
989        .expect("built-in type");
990        reg.register_type_definition(DataTypeDefinition {
991            id: DataType::ManagedVariableValue,
992            name: "SEDSNET_MANAGED_VARIABLE_VALUE",
993            description: "",
994            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
995            endpoints: &[DataEndpoint::Discovery],
996            reliable: ReliableMode::Ordered,
997            priority: 243,
998            e2e_encryption: E2eEncryptionPolicy::PreferOff,
999        })
1000        .expect("built-in type");
1001        reg.register_type_definition(DataTypeDefinition {
1002            id: DataType::DiscoveryLeave,
1003            name: "SEDSNET_DISCOVERY_LEAVE",
1004            description: "",
1005            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
1006            endpoints: &[DataEndpoint::Discovery],
1007            reliable: ReliableMode::None,
1008            priority: 244,
1009            e2e_encryption: E2eEncryptionPolicy::PreferOff,
1010        })
1011        .expect("built-in type");
1012        reg.register_type_definition(DataTypeDefinition {
1013            id: DataType::DiscoveryLinkCapabilities,
1014            name: "SEDSNET_DISCOVERY_LINK_CAPABILITIES",
1015            description: "",
1016            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
1017            endpoints: &[DataEndpoint::Discovery],
1018            reliable: ReliableMode::None,
1019            priority: 240,
1020            e2e_encryption: E2eEncryptionPolicy::PreferOff,
1021        })
1022        .expect("built-in type");
1023        reg.register_type_definition(DataTypeDefinition {
1024            id: DataType::DiscoveryAddress,
1025            name: "SEDSNET_DISCOVERY_ADDRESS",
1026            description: "",
1027            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
1028            endpoints: &[DataEndpoint::Discovery],
1029            reliable: ReliableMode::Ordered,
1030            priority: 244,
1031            e2e_encryption: E2eEncryptionPolicy::PreferOff,
1032        })
1033        .expect("built-in type");
1034        reg.register_type_definition(DataTypeDefinition {
1035            id: DataType::P2pMessage,
1036            name: "SEDSNET_P2P_MESSAGE",
1037            description: "",
1038            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
1039            endpoints: &[DataEndpoint::Discovery],
1040            reliable: ReliableMode::Ordered,
1041            priority: 246,
1042            e2e_encryption: E2eEncryptionPolicy::PreferOff,
1043        })
1044        .expect("built-in type");
1045        #[cfg(all(feature = "embedded", sedsnet_has_telemetry_config_json))]
1046        if let Ok(snapshot) = bundled_schema_snapshot() {
1047            let _ = register_owned_schema_snapshot_into(&mut reg, snapshot);
1048        }
1049        let _ = register_runtime_json_config(
1050            &mut reg,
1051            "SEDSNET_STATIC_SCHEMA_PATH",
1052            false,
1053            MAX_QUEUE_BUDGET,
1054        );
1055        let _ = register_runtime_json_config(
1056            &mut reg,
1057            "SEDSNET_STATIC_IPC_SCHEMA_PATH",
1058            true,
1059            MAX_QUEUE_BUDGET,
1060        );
1061        reg
1062    }
1063
1064    fn register_endpoint_definition(&mut self, def: EndpointDefinition) -> TelemetryResult<()> {
1065        self.register_owned_endpoint(OwnedEndpointDefinition {
1066            id: def.id,
1067            name: def.name.to_string(),
1068            description: def.description.to_string(),
1069            link_local_only: def.link_local_only,
1070        })
1071    }
1072
1073    fn register_owned_endpoint(&mut self, def: OwnedEndpointDefinition) -> TelemetryResult<()> {
1074        if let Some((_, existing)) = self.endpoints.iter().find(|(id, _)| *id == def.id) {
1075            if existing.name.as_ref() == def.name
1076                && existing.description.as_ref() == def.description
1077                && existing.link_local_only == def.link_local_only
1078            {
1079                return Ok(());
1080            }
1081            return Err(TelemetryError::BadArg);
1082        }
1083        if self
1084            .endpoints
1085            .iter()
1086            .any(|(_, meta)| meta.name.as_ref() == def.name)
1087        {
1088            return Err(TelemetryError::BadArg);
1089        }
1090        self.next_endpoint_id = self.next_endpoint_id.max(def.id.0.saturating_add(1));
1091        self.endpoints.push((
1092            def.id,
1093            EndpointMeta {
1094                name: Arc::from(def.name),
1095                description: Arc::from(def.description),
1096                link_local_only: def.link_local_only,
1097            },
1098        ));
1099        self.endpoints.sort_unstable_by_key(|(id, _)| id.0);
1100        Ok(())
1101    }
1102
1103    fn register_type_definition(&mut self, def: DataTypeDefinition) -> TelemetryResult<()> {
1104        self.register_owned_type(OwnedDataTypeDefinition {
1105            id: def.id,
1106            name: def.name.to_string(),
1107            description: def.description.to_string(),
1108            element: def.element,
1109            endpoints: def.endpoints.to_vec(),
1110            reliable: def.reliable,
1111            priority: def.priority,
1112            e2e_encryption: def.e2e_encryption,
1113        })
1114    }
1115
1116    fn register_owned_type(&mut self, def: OwnedDataTypeDefinition) -> TelemetryResult<()> {
1117        if let Some((_, existing)) = self.types.iter().find(|(id, _)| *id == def.id) {
1118            if existing.name.as_ref() == def.name
1119                && existing.description.as_ref() == def.description
1120                && existing.element == def.element
1121                && existing.endpoints.as_ref() == def.endpoints
1122                && existing.reliable == def.reliable
1123                && existing.priority == def.priority
1124                && existing.e2e_encryption == def.e2e_encryption
1125            {
1126                return Ok(());
1127            }
1128            return Err(TelemetryError::BadArg);
1129        }
1130        if self
1131            .types
1132            .iter()
1133            .any(|(_, meta)| meta.name.as_ref() == def.name)
1134        {
1135            return Err(TelemetryError::BadArg);
1136        }
1137        self.next_type_id = self.next_type_id.max(def.id.0.saturating_add(1));
1138        self.types.push((
1139            def.id,
1140            MessageMeta {
1141                name: Arc::from(def.name),
1142                description: Arc::from(def.description),
1143                element: def.element,
1144                endpoints: Arc::from(def.endpoints),
1145                reliable: def.reliable,
1146                priority: def.priority,
1147                e2e_encryption: def.e2e_encryption,
1148            },
1149        ));
1150        self.types.sort_unstable_by_key(|(id, _)| id.0);
1151        Ok(())
1152    }
1153
1154    fn schema_byte_cost(&self) -> usize {
1155        self.endpoints
1156            .iter()
1157            .map(|(_, meta)| endpoint_schema_byte_cost(meta.name.len(), meta.description.len()))
1158            .sum::<usize>()
1159            .saturating_add(
1160                self.types
1161                    .iter()
1162                    .map(|(_, meta)| {
1163                        type_schema_byte_cost(
1164                            meta.name.len(),
1165                            meta.description.len(),
1166                            meta.endpoints.len(),
1167                        )
1168                    })
1169                    .sum::<usize>(),
1170            )
1171    }
1172
1173    fn merge_endpoint_definition(&mut self, def: OwnedEndpointDefinition) -> SchemaMergeDecision {
1174        let id_match = self.endpoints.iter().position(|(id, _)| *id == def.id);
1175        let name_match = self
1176            .endpoints
1177            .iter()
1178            .position(|(_, meta)| meta.name.as_ref() == def.name);
1179        let conflict = match (id_match, name_match) {
1180            (Some(a), Some(b)) if a != b => Some(a.min(b)),
1181            (Some(a), _) | (_, Some(a)) => Some(a),
1182            (None, None) => None,
1183        };
1184
1185        let Some(idx) = conflict else {
1186            self.next_endpoint_id = self.next_endpoint_id.max(def.id.0.saturating_add(1));
1187            self.endpoints.push((
1188                def.id,
1189                EndpointMeta {
1190                    name: Arc::from(def.name),
1191                    description: Arc::from(def.description),
1192                    link_local_only: def.link_local_only,
1193                },
1194            ));
1195            self.endpoints.sort_unstable_by_key(|(id, _)| id.0);
1196            return SchemaMergeDecision::Added;
1197        };
1198
1199        let existing = self.endpoints[idx].clone();
1200        let existing_def = OwnedEndpointDefinition {
1201            id: existing.0,
1202            name: existing.1.name.to_string(),
1203            description: existing.1.description.to_string(),
1204            link_local_only: existing.1.link_local_only,
1205        };
1206        if endpoint_def_equivalent(&existing_def, &def) {
1207            return SchemaMergeDecision::Unchanged;
1208        }
1209        let def = merge_endpoint_metadata(&existing_def, &def);
1210        if !endpoint_def_equivalent(&existing_def, &def) {
1211            self.endpoints[idx] = (
1212                def.id,
1213                EndpointMeta {
1214                    name: Arc::from(def.name),
1215                    description: Arc::from(def.description),
1216                    link_local_only: def.link_local_only,
1217                },
1218            );
1219            self.endpoints.sort_unstable_by_key(|(id, _)| id.0);
1220            self.next_endpoint_id = self.next_endpoint_id.max(def.id.0.saturating_add(1));
1221            SchemaMergeDecision::ReplacedLocal
1222        } else {
1223            SchemaMergeDecision::KeptLocal
1224        }
1225    }
1226
1227    fn merge_type_definition(&mut self, def: OwnedDataTypeDefinition) -> SchemaMergeDecision {
1228        let id_match = self.types.iter().position(|(id, _)| *id == def.id);
1229        let name_match = self
1230            .types
1231            .iter()
1232            .position(|(_, meta)| meta.name.as_ref() == def.name);
1233        let conflict = match (id_match, name_match) {
1234            (Some(a), Some(b)) if a != b => Some(a.min(b)),
1235            (Some(a), _) | (_, Some(a)) => Some(a),
1236            (None, None) => None,
1237        };
1238
1239        let Some(idx) = conflict else {
1240            self.next_type_id = self.next_type_id.max(def.id.0.saturating_add(1));
1241            self.types.push((
1242                def.id,
1243                MessageMeta {
1244                    name: Arc::from(def.name),
1245                    description: Arc::from(def.description),
1246                    element: def.element,
1247                    endpoints: Arc::from(def.endpoints),
1248                    reliable: def.reliable,
1249                    priority: def.priority,
1250                    e2e_encryption: def.e2e_encryption,
1251                },
1252            ));
1253            self.types.sort_unstable_by_key(|(id, _)| id.0);
1254            return SchemaMergeDecision::Added;
1255        };
1256
1257        let existing = self.types[idx].clone();
1258        let existing_def = OwnedDataTypeDefinition {
1259            id: existing.0,
1260            name: existing.1.name.to_string(),
1261            description: existing.1.description.to_string(),
1262            element: existing.1.element,
1263            endpoints: existing.1.endpoints.to_vec(),
1264            reliable: existing.1.reliable,
1265            priority: existing.1.priority,
1266            e2e_encryption: existing.1.e2e_encryption,
1267        };
1268        if type_def_equivalent(&existing_def, &def) {
1269            return SchemaMergeDecision::Unchanged;
1270        }
1271        let def = merge_type_metadata(&existing_def, &def);
1272        if !type_def_equivalent(&existing_def, &def) {
1273            self.types[idx] = (
1274                def.id,
1275                MessageMeta {
1276                    name: Arc::from(def.name),
1277                    description: Arc::from(def.description),
1278                    element: def.element,
1279                    endpoints: Arc::from(def.endpoints),
1280                    reliable: def.reliable,
1281                    priority: def.priority,
1282                    e2e_encryption: def.e2e_encryption,
1283                },
1284            );
1285            self.types.sort_unstable_by_key(|(id, _)| id.0);
1286            self.next_type_id = self.next_type_id.max(def.id.0.saturating_add(1));
1287            SchemaMergeDecision::ReplacedLocal
1288        } else {
1289            SchemaMergeDecision::KeptLocal
1290        }
1291    }
1292}
1293
1294const fn endpoint_schema_byte_cost(name_len: usize, description_len: usize) -> usize {
1295    size_of::<(DataEndpoint, EndpointMeta)>()
1296        .saturating_add(name_len)
1297        .saturating_add(description_len)
1298}
1299
1300const fn type_schema_byte_cost(
1301    name_len: usize,
1302    description_len: usize,
1303    endpoint_count: usize,
1304) -> usize {
1305    size_of::<(DataType, MessageMeta)>()
1306        .saturating_add(name_len)
1307        .saturating_add(description_len)
1308        .saturating_add(endpoint_count.saturating_mul(size_of::<DataEndpoint>()))
1309}
1310
1311pub fn owned_schema_byte_cost(snapshot: &OwnedRuntimeSchemaSnapshot) -> usize {
1312    snapshot
1313        .endpoints
1314        .iter()
1315        .map(|def| endpoint_schema_byte_cost(def.name.len(), def.description.len()))
1316        .sum::<usize>()
1317        .saturating_add(
1318            snapshot
1319                .types
1320                .iter()
1321                .map(|def| {
1322                    type_schema_byte_cost(
1323                        def.name.len(),
1324                        def.description.len(),
1325                        def.endpoints.len(),
1326                    )
1327                })
1328                .sum::<usize>(),
1329        )
1330}
1331
1332#[cfg(feature = "std")]
1333static REGISTRY: OnceLock<std::sync::Mutex<Registry>> = OnceLock::new();
1334
1335#[cfg(feature = "std")]
1336fn registry() -> &'static std::sync::Mutex<Registry> {
1337    REGISTRY.get_or_init(|| std::sync::Mutex::new(Registry::new()))
1338}
1339
1340#[cfg(all(
1341    feature = "std",
1342    feature = "serde",
1343    feature = "embedded",
1344    sedsnet_has_telemetry_config_json
1345))]
1346fn bundled_schema_snapshot() -> TelemetryResult<OwnedRuntimeSchemaSnapshot> {
1347    schema_snapshot_from_json_bytes(include_bytes!(env!("SEDSNET_BUNDLED_SCHEMA_PATH")))
1348}
1349
1350#[cfg(feature = "std")]
1351fn register_runtime_json_config(
1352    reg: &mut Registry,
1353    env_key: &str,
1354    link_local_overlay: bool,
1355    max_schema_bytes: usize,
1356) -> TelemetryResult<()> {
1357    let path = std::env::var(env_key).map_err(|_| TelemetryError::Io("schema json path"))?;
1358    register_schema_json_file_into(
1359        reg,
1360        std::path::Path::new(&path),
1361        link_local_overlay,
1362        max_schema_bytes,
1363        SCHEMA_JSON_CHUNK_BYTES,
1364    )
1365}
1366
1367#[cfg(feature = "std")]
1368pub fn register_endpoint(name: &str, link_local_only: bool) -> TelemetryResult<DataEndpoint> {
1369    register_endpoint_with_description(name, "", link_local_only)
1370}
1371
1372#[cfg(feature = "std")]
1373pub fn register_endpoint_with_description(
1374    name: &str,
1375    description: &str,
1376    link_local_only: bool,
1377) -> TelemetryResult<DataEndpoint> {
1378    let mut reg = registry().lock().expect("schema registry poisoned");
1379    let id = DataEndpoint(schema_identity::endpoint_id(name));
1380    reg.register_owned_endpoint(OwnedEndpointDefinition {
1381        id,
1382        name: name.to_string(),
1383        description: description.to_string(),
1384        link_local_only,
1385    })?;
1386    Ok(id)
1387}
1388
1389#[cfg(feature = "std")]
1390pub fn register_endpoint_id(
1391    id: DataEndpoint,
1392    name: &str,
1393    link_local_only: bool,
1394) -> TelemetryResult<DataEndpoint> {
1395    register_endpoint_id_with_description(id, name, "", link_local_only)
1396}
1397
1398#[cfg(feature = "std")]
1399pub fn register_endpoint_id_with_description(
1400    id: DataEndpoint,
1401    name: &str,
1402    description: &str,
1403    link_local_only: bool,
1404) -> TelemetryResult<DataEndpoint> {
1405    registry()
1406        .lock()
1407        .expect("schema registry poisoned")
1408        .register_owned_endpoint(OwnedEndpointDefinition {
1409            id,
1410            name: name.to_string(),
1411            description: description.to_string(),
1412            link_local_only,
1413        })?;
1414    Ok(id)
1415}
1416
1417#[cfg(feature = "std")]
1418pub fn ensure_endpoint_id(
1419    id: DataEndpoint,
1420    link_local_only: bool,
1421) -> TelemetryResult<DataEndpoint> {
1422    if endpoint_exists(id) {
1423        return Ok(id);
1424    }
1425    register_endpoint_id(id, &format!("ENDPOINT_{}", id.0), link_local_only)
1426}
1427
1428#[cfg(feature = "std")]
1429pub fn register_endpoint_definition(def: EndpointDefinition) -> TelemetryResult<()> {
1430    registry()
1431        .lock()
1432        .expect("schema registry poisoned")
1433        .register_endpoint_definition(def)
1434}
1435
1436#[cfg(feature = "std")]
1437pub fn register_data_type(
1438    name: &str,
1439    element: MessageElement,
1440    endpoints: &[DataEndpoint],
1441    reliable: ReliableMode,
1442    priority: u8,
1443) -> TelemetryResult<DataType> {
1444    register_data_type_with_description(name, "", element, endpoints, reliable, priority)
1445}
1446
1447#[cfg(feature = "std")]
1448pub fn register_data_type_with_description(
1449    name: &str,
1450    description: &str,
1451    element: MessageElement,
1452    endpoints: &[DataEndpoint],
1453    reliable: ReliableMode,
1454    priority: u8,
1455) -> TelemetryResult<DataType> {
1456    register_data_type_with_description_and_e2e_encryption(
1457        name,
1458        description,
1459        element,
1460        endpoints,
1461        reliable,
1462        priority,
1463        E2eEncryptionPolicy::PreferOff,
1464    )
1465}
1466
1467#[cfg(feature = "std")]
1468#[allow(clippy::too_many_arguments)]
1469pub fn register_data_type_with_description_and_e2e_encryption(
1470    name: &str,
1471    description: &str,
1472    element: MessageElement,
1473    endpoints: &[DataEndpoint],
1474    reliable: ReliableMode,
1475    priority: u8,
1476    e2e_encryption: E2eEncryptionPolicy,
1477) -> TelemetryResult<DataType> {
1478    let mut reg = registry().lock().expect("schema registry poisoned");
1479    let id = DataType(schema_identity::type_id(name));
1480    reg.register_owned_type(OwnedDataTypeDefinition {
1481        id,
1482        name: name.to_string(),
1483        description: description.to_string(),
1484        element,
1485        endpoints: endpoints.to_vec(),
1486        reliable,
1487        priority,
1488        e2e_encryption,
1489    })?;
1490    Ok(id)
1491}
1492
1493#[cfg(feature = "std")]
1494pub fn register_data_type_definition(def: DataTypeDefinition) -> TelemetryResult<()> {
1495    registry()
1496        .lock()
1497        .expect("schema registry poisoned")
1498        .register_type_definition(def)
1499}
1500
1501#[cfg(feature = "std")]
1502pub fn set_data_type_e2e_encryption_policy(
1503    ty: DataType,
1504    policy: E2eEncryptionPolicy,
1505) -> TelemetryResult<()> {
1506    let mut reg = registry().lock().expect("schema registry poisoned");
1507    let Some((_, meta)) = reg.types.iter_mut().find(|(id, _)| *id == ty) else {
1508        return Err(TelemetryError::InvalidType);
1509    };
1510    meta.e2e_encryption = policy;
1511    Ok(())
1512}
1513
1514#[cfg(feature = "std")]
1515pub fn register_data_type_id(
1516    id: DataType,
1517    name: &str,
1518    element: MessageElement,
1519    endpoints: &[DataEndpoint],
1520    reliable: ReliableMode,
1521    priority: u8,
1522) -> TelemetryResult<DataType> {
1523    register_data_type_id_with_description(id, name, "", element, endpoints, reliable, priority)
1524}
1525
1526#[cfg(feature = "std")]
1527pub fn register_data_type_id_with_description(
1528    id: DataType,
1529    name: &str,
1530    description: &str,
1531    element: MessageElement,
1532    endpoints: &[DataEndpoint],
1533    reliable: ReliableMode,
1534    priority: u8,
1535) -> TelemetryResult<DataType> {
1536    register_data_type_id_with_description_and_e2e_encryption(
1537        id,
1538        name,
1539        description,
1540        element,
1541        endpoints,
1542        reliable,
1543        priority,
1544        E2eEncryptionPolicy::PreferOff,
1545    )
1546}
1547
1548#[cfg(feature = "std")]
1549#[allow(clippy::too_many_arguments)]
1550pub fn register_data_type_id_with_description_and_e2e_encryption(
1551    id: DataType,
1552    name: &str,
1553    description: &str,
1554    element: MessageElement,
1555    endpoints: &[DataEndpoint],
1556    reliable: ReliableMode,
1557    priority: u8,
1558    e2e_encryption: E2eEncryptionPolicy,
1559) -> TelemetryResult<DataType> {
1560    registry()
1561        .lock()
1562        .expect("schema registry poisoned")
1563        .register_owned_type(OwnedDataTypeDefinition {
1564            id,
1565            name: name.to_string(),
1566            description: description.to_string(),
1567            element,
1568            endpoints: endpoints.to_vec(),
1569            reliable,
1570            priority,
1571            e2e_encryption,
1572        })?;
1573    Ok(id)
1574}
1575
1576#[cfg(feature = "std")]
1577pub fn merge_schema_snapshot(snapshot: RuntimeSchemaSnapshot) -> SchemaMergeReport {
1578    merge_owned_schema_snapshot(OwnedRuntimeSchemaSnapshot {
1579        endpoints: snapshot
1580            .endpoints
1581            .into_iter()
1582            .map(|def| OwnedEndpointDefinition {
1583                id: def.id,
1584                name: def.name.to_string(),
1585                description: def.description.to_string(),
1586                link_local_only: def.link_local_only,
1587            })
1588            .collect(),
1589        types: snapshot
1590            .types
1591            .into_iter()
1592            .map(|def| OwnedDataTypeDefinition {
1593                id: def.id,
1594                name: def.name.to_string(),
1595                description: def.description.to_string(),
1596                element: def.element,
1597                endpoints: def.endpoints.to_vec(),
1598                reliable: def.reliable,
1599                priority: def.priority,
1600                e2e_encryption: def.e2e_encryption,
1601            })
1602            .collect(),
1603    })
1604}
1605
1606#[cfg(feature = "std")]
1607pub fn merge_owned_schema_snapshot(snapshot: OwnedRuntimeSchemaSnapshot) -> SchemaMergeReport {
1608    merge_owned_schema_snapshot_with_budget(snapshot, usize::MAX)
1609        .expect("unbounded schema merge should not fail budget")
1610}
1611
1612#[cfg(feature = "std")]
1613pub fn merge_owned_schema_snapshot_with_budget(
1614    mut snapshot: OwnedRuntimeSchemaSnapshot,
1615    max_schema_bytes: usize,
1616) -> TelemetryResult<SchemaMergeReport> {
1617    sort_owned_schema(&mut snapshot);
1618    snapshot.endpoints.dedup_by_key(|def| def.id.0);
1619    snapshot.types.dedup_by_key(|def| def.id.0);
1620
1621    let reg = registry().lock().expect("schema registry poisoned");
1622    if reg
1623        .schema_byte_cost()
1624        .saturating_add(owned_schema_byte_cost(&snapshot))
1625        > max_schema_bytes
1626    {
1627        return Err(TelemetryError::PacketTooLarge(
1628            "Schema exceeds maximum shared queue budget",
1629        ));
1630    }
1631    drop(reg);
1632
1633    let mut reg = registry().lock().expect("schema registry poisoned");
1634    let mut preview = reg.clone();
1635    let report = merge_owned_schema_snapshot_locked(&mut preview, snapshot);
1636    if preview.schema_byte_cost() > max_schema_bytes {
1637        return Err(TelemetryError::PacketTooLarge(
1638            "Schema exceeds maximum shared queue budget",
1639        ));
1640    }
1641    *reg = preview;
1642    Ok(report)
1643}
1644
1645#[cfg(feature = "std")]
1646fn merge_owned_schema_snapshot_locked(
1647    reg: &mut Registry,
1648    mut snapshot: OwnedRuntimeSchemaSnapshot,
1649) -> SchemaMergeReport {
1650    sort_owned_schema(&mut snapshot);
1651    snapshot.endpoints.dedup_by_key(|def| def.id.0);
1652    snapshot.types.dedup_by_key(|def| def.id.0);
1653
1654    let mut report = SchemaMergeReport {
1655        endpoints_added: 0,
1656        endpoints_replaced: 0,
1657        endpoints_kept: 0,
1658        types_added: 0,
1659        types_replaced: 0,
1660        types_kept: 0,
1661    };
1662    for endpoint in snapshot.endpoints {
1663        match reg.merge_endpoint_definition(endpoint) {
1664            SchemaMergeDecision::Added => report.endpoints_added += 1,
1665            SchemaMergeDecision::ReplacedLocal => report.endpoints_replaced += 1,
1666            SchemaMergeDecision::KeptLocal => report.endpoints_kept += 1,
1667            SchemaMergeDecision::Unchanged => {}
1668        }
1669    }
1670    for ty in snapshot.types {
1671        match reg.merge_type_definition(ty) {
1672            SchemaMergeDecision::Added => report.types_added += 1,
1673            SchemaMergeDecision::ReplacedLocal => report.types_replaced += 1,
1674            SchemaMergeDecision::KeptLocal => report.types_kept += 1,
1675            SchemaMergeDecision::Unchanged => {}
1676        }
1677    }
1678    report
1679}
1680
1681#[cfg(feature = "std")]
1682pub fn export_schema() -> OwnedRuntimeSchemaSnapshot {
1683    let reg = registry().lock().expect("schema registry poisoned");
1684    OwnedRuntimeSchemaSnapshot {
1685        endpoints: reg
1686            .endpoints
1687            .iter()
1688            .map(|(id, meta)| OwnedEndpointDefinition {
1689                id: *id,
1690                name: meta.name.to_string(),
1691                description: meta.description.to_string(),
1692                link_local_only: meta.link_local_only,
1693            })
1694            .collect(),
1695        types: reg
1696            .types
1697            .iter()
1698            .map(|(id, meta)| OwnedDataTypeDefinition {
1699                id: *id,
1700                name: meta.name.to_string(),
1701                description: meta.description.to_string(),
1702                element: meta.element,
1703                endpoints: meta.endpoints.to_vec(),
1704                reliable: meta.reliable,
1705                priority: meta.priority,
1706                e2e_encryption: meta.e2e_encryption,
1707            })
1708            .collect(),
1709    }
1710}
1711
1712#[cfg(feature = "std")]
1713pub fn known_endpoints() -> Vec<OwnedEndpointDefinition> {
1714    export_schema().endpoints
1715}
1716
1717#[cfg(feature = "std")]
1718pub fn known_data_types() -> Vec<OwnedDataTypeDefinition> {
1719    export_schema().types
1720}
1721
1722#[cfg(feature = "std")]
1723pub fn schema_fingerprint() -> u64 {
1724    let snapshot = export_schema();
1725    let mut h = 0x5E_D5_50_4F_52_49_4E_54u64;
1726    for ep in snapshot.endpoints {
1727        h = hash_u32(h, ep.id.0);
1728        h = hash_bytes(h, ep.name.as_bytes());
1729        h = hash_bytes(h, ep.description.as_bytes());
1730        h = hash_u8(h, ep.link_local_only as u8);
1731    }
1732    for ty in snapshot.types {
1733        h = hash_u32(h, ty.id.0);
1734        h = hash_bytes(h, ty.name.as_bytes());
1735        h = hash_bytes(h, ty.description.as_bytes());
1736        h = hash_message_element(h, ty.element);
1737        h = hash_u8(h, reliable_code(ty.reliable));
1738        h = hash_u8(h, ty.priority);
1739        for ep in ty.endpoints {
1740            h = hash_u32(h, ep.0);
1741        }
1742    }
1743    h
1744}
1745
1746#[cfg(feature = "std")]
1747pub fn schema_bytes_used() -> usize {
1748    // A memory query must not initialize/allocate the hosted registry. Until
1749    // the first schema operation, no registry heap storage is retained.
1750    REGISTRY.get().map_or(0, |registry| {
1751        registry
1752            .lock()
1753            .expect("schema registry poisoned")
1754            .schema_byte_cost()
1755    })
1756}
1757
1758#[cfg(feature = "std")]
1759pub fn endpoint_exists(ep: DataEndpoint) -> bool {
1760    #[cfg(all(test, feature = "std"))]
1761    seed_test_schema();
1762    registry()
1763        .lock()
1764        .expect("schema registry poisoned")
1765        .endpoints
1766        .iter()
1767        .any(|(id, _)| *id == ep)
1768}
1769
1770#[cfg(feature = "std")]
1771pub fn data_type_exists(ty: DataType) -> bool {
1772    #[cfg(all(test, feature = "std"))]
1773    seed_test_schema();
1774    registry()
1775        .lock()
1776        .expect("schema registry poisoned")
1777        .types
1778        .iter()
1779        .any(|(id, _)| *id == ty)
1780}
1781
1782#[cfg(feature = "std")]
1783pub fn get_endpoint_meta(endpoint_type: DataEndpoint) -> EndpointMeta {
1784    #[cfg(all(test, feature = "std"))]
1785    seed_test_schema();
1786    registry()
1787        .lock()
1788        .expect("schema registry poisoned")
1789        .endpoints
1790        .iter()
1791        .find(|(id, _)| *id == endpoint_type)
1792        .map(|(_, meta)| meta.clone())
1793        .unwrap_or_else(|| EndpointMeta {
1794            name: Arc::from("UNKNOWN_ENDPOINT"),
1795            description: Arc::from(""),
1796            link_local_only: false,
1797        })
1798}
1799
1800/// Scheduling only needs two small fields. Do not clone hosted metadata or
1801/// copy the entire embedded definition for every queue/reliability decision.
1802/// Look up current runtime definitions on every call; no stale schema cache.
1803pub(crate) fn message_delivery_meta(data_type: DataType) -> (ReliableMode, u8) {
1804    #[cfg(feature = "std")]
1805    {
1806        #[cfg(test)]
1807        seed_test_schema();
1808        registry()
1809            .lock()
1810            .expect("schema registry poisoned")
1811            .types
1812            .iter()
1813            .find(|(id, _)| *id == data_type)
1814            .map(|(_, meta)| (meta.reliable, meta.priority))
1815            .unwrap_or((ReliableMode::None, 0))
1816    }
1817    #[cfg(not(feature = "std"))]
1818    {
1819        embedded_overlay_type(data_type)
1820            .or_else(|| compiled_type_definition(data_type))
1821            .map(|def| (def.reliable, def.priority))
1822            .unwrap_or((ReliableMode::None, 0))
1823    }
1824}
1825
1826#[cfg(feature = "std")]
1827pub(crate) fn try_get_message_meta(data_type: DataType) -> Option<MessageMeta> {
1828    #[cfg(all(test, feature = "std"))]
1829    seed_test_schema();
1830    registry()
1831        .lock()
1832        .expect("schema registry poisoned")
1833        .types
1834        .iter()
1835        .find(|(id, _)| *id == data_type)
1836        .map(|(_, meta)| meta.clone())
1837}
1838
1839#[cfg(feature = "std")]
1840pub fn get_message_meta(data_type: DataType) -> MessageMeta {
1841    try_get_message_meta(data_type).unwrap_or_else(|| MessageMeta {
1842        name: Arc::from("UNKNOWN_TYPE"),
1843        description: Arc::from(""),
1844        element: MessageElement::Dynamic(MessageDataType::Binary, MessageClass::Data),
1845        endpoints: Arc::from([]),
1846        reliable: ReliableMode::None,
1847        priority: 0,
1848        e2e_encryption: E2eEncryptionPolicy::PreferOff,
1849    })
1850}
1851
1852#[cfg(feature = "std")]
1853pub fn max_endpoint_id() -> u32 {
1854    registry()
1855        .lock()
1856        .expect("schema registry poisoned")
1857        .endpoints
1858        .iter()
1859        .map(|(id, _)| id.0)
1860        .max()
1861        .unwrap_or(0)
1862}
1863
1864#[cfg(feature = "std")]
1865pub fn max_data_type_id() -> u32 {
1866    registry()
1867        .lock()
1868        .expect("schema registry poisoned")
1869        .types
1870        .iter()
1871        .map(|(id, _)| id.0)
1872        .max()
1873        .unwrap_or(0)
1874}
1875
1876fn hash_u8(h: u64, v: u8) -> u64 {
1877    hash_bytes(h, &[v])
1878}
1879
1880fn hash_u32(h: u64, v: u32) -> u64 {
1881    hash_bytes(h, &v.to_le_bytes())
1882}
1883
1884fn hash_usize(h: u64, v: usize) -> u64 {
1885    hash_bytes(h, &(v as u64).to_le_bytes())
1886}
1887
1888fn hash_bytes(mut h: u64, bytes: &[u8]) -> u64 {
1889    const PRIME: u64 = 0x0000_0100_0000_01B3;
1890    for &b in bytes {
1891        h ^= b as u64;
1892        h = h.wrapping_mul(PRIME);
1893    }
1894    h
1895}
1896
1897fn endpoint_fingerprint(def: &OwnedEndpointDefinition) -> u64 {
1898    let mut h = 0x4550_4445_4600_0001;
1899    h = hash_u32(h, def.id.0);
1900    h = hash_bytes(h, def.name.as_bytes());
1901    hash_u8(h, def.link_local_only as u8)
1902}
1903
1904fn type_fingerprint(def: &OwnedDataTypeDefinition) -> u64 {
1905    let mut h = 0x5459_4445_4600_0001;
1906    h = hash_u32(h, def.id.0);
1907    h = hash_bytes(h, def.name.as_bytes());
1908    h = hash_message_element(h, def.element);
1909    h = hash_u8(h, reliable_code(def.reliable));
1910    h = hash_u8(h, def.priority);
1911    h = hash_u8(h, e2e_encryption_policy_code(def.e2e_encryption));
1912    for ep in &def.endpoints {
1913        h = hash_u32(h, ep.0);
1914    }
1915    h
1916}
1917
1918fn hash_message_element(mut h: u64, element: MessageElement) -> u64 {
1919    match element {
1920        MessageElement::Static(count, data_type, class) => {
1921            h = hash_u8(h, 0);
1922            h = hash_usize(h, count);
1923            h = hash_u8(h, message_data_type_code(data_type));
1924            hash_u8(h, message_class_code(class))
1925        }
1926        MessageElement::Dynamic(data_type, class) => {
1927            h = hash_u8(h, 1);
1928            h = hash_u8(h, message_data_type_code(data_type));
1929            hash_u8(h, message_class_code(class))
1930        }
1931    }
1932}
1933
1934#[cfg(feature = "std")]
1935pub fn endpoint_definition(ep: DataEndpoint) -> Option<OwnedEndpointDefinition> {
1936    registry()
1937        .lock()
1938        .expect("schema registry poisoned")
1939        .endpoints
1940        .iter()
1941        .find(|(id, _)| *id == ep)
1942        .map(|(id, meta)| OwnedEndpointDefinition {
1943            id: *id,
1944            name: meta.name.to_string(),
1945            description: meta.description.to_string(),
1946            link_local_only: meta.link_local_only,
1947        })
1948}
1949
1950#[cfg(feature = "std")]
1951pub fn data_type_definition(ty: DataType) -> Option<OwnedDataTypeDefinition> {
1952    registry()
1953        .lock()
1954        .expect("schema registry poisoned")
1955        .types
1956        .iter()
1957        .find(|(id, _)| *id == ty)
1958        .map(|(id, meta)| OwnedDataTypeDefinition {
1959            id: *id,
1960            name: meta.name.to_string(),
1961            description: meta.description.to_string(),
1962            element: meta.element,
1963            endpoints: meta.endpoints.to_vec(),
1964            reliable: meta.reliable,
1965            priority: meta.priority,
1966            e2e_encryption: meta.e2e_encryption,
1967        })
1968}
1969
1970#[cfg(feature = "std")]
1971pub fn endpoint_definition_by_name(name: &str) -> Option<OwnedEndpointDefinition> {
1972    registry()
1973        .lock()
1974        .expect("schema registry poisoned")
1975        .endpoints
1976        .iter()
1977        .find(|(_, meta)| meta.name.as_ref() == name)
1978        .map(|(id, meta)| OwnedEndpointDefinition {
1979            id: *id,
1980            name: meta.name.to_string(),
1981            description: meta.description.to_string(),
1982            link_local_only: meta.link_local_only,
1983        })
1984}
1985
1986#[cfg(feature = "std")]
1987pub fn data_type_definition_by_name(name: &str) -> Option<OwnedDataTypeDefinition> {
1988    registry()
1989        .lock()
1990        .expect("schema registry poisoned")
1991        .types
1992        .iter()
1993        .find(|(_, meta)| meta.name.as_ref() == name)
1994        .map(|(id, meta)| OwnedDataTypeDefinition {
1995            id: *id,
1996            name: meta.name.to_string(),
1997            description: meta.description.to_string(),
1998            element: meta.element,
1999            endpoints: meta.endpoints.to_vec(),
2000            reliable: meta.reliable,
2001            priority: meta.priority,
2002            e2e_encryption: meta.e2e_encryption,
2003        })
2004}
2005
2006#[cfg(feature = "std")]
2007fn is_internal_endpoint(ep: DataEndpoint) -> bool {
2008    matches!(
2009        ep,
2010        DataEndpoint::TelemetryError | DataEndpoint::TimeSync | DataEndpoint::Discovery
2011    )
2012}
2013
2014#[cfg(feature = "std")]
2015fn is_internal_data_type(ty: DataType) -> bool {
2016    matches!(
2017        ty,
2018        DataType::TelemetryError
2019            | DataType::ReliableAck
2020            | DataType::ReliablePacketRequest
2021            | DataType::ReliablePartialAck
2022            | DataType::TimeSyncAnnounce
2023            | DataType::TimeSyncRequest
2024            | DataType::TimeSyncResponse
2025            | DataType::DiscoveryAnnounce
2026            | DataType::DiscoveryTimeSyncSources
2027            | DataType::DiscoveryTopology
2028            | DataType::DiscoverySchema
2029            | DataType::DiscoveryTopologyRequest
2030            | DataType::DiscoverySchemaRequest
2031            | DataType::ManagedVariableRequest
2032            | DataType::ManagedVariableValue
2033            | DataType::DiscoveryLeave
2034            | DataType::DiscoveryLinkCapabilities
2035            | DataType::DiscoveryAddress
2036            | DataType::P2pMessage
2037    )
2038}
2039
2040#[cfg(feature = "std")]
2041pub fn remove_endpoint(ep: DataEndpoint) -> TelemetryResult<bool> {
2042    if is_internal_endpoint(ep) {
2043        return Err(TelemetryError::BadArg);
2044    }
2045    let mut reg = registry().lock().expect("schema registry poisoned");
2046    let before = reg.endpoints.len();
2047    reg.endpoints.retain(|(id, _)| *id != ep);
2048    if reg.endpoints.len() == before {
2049        return Ok(false);
2050    }
2051    reg.types.retain(|(_, meta)| !meta.endpoints.contains(&ep));
2052    Ok(true)
2053}
2054
2055#[cfg(feature = "std")]
2056pub fn remove_endpoint_by_name(name: &str) -> TelemetryResult<bool> {
2057    if let Some(def) = endpoint_definition_by_name(name) {
2058        remove_endpoint(def.id)
2059    } else {
2060        Ok(false)
2061    }
2062}
2063
2064#[cfg(feature = "std")]
2065pub fn remove_data_type(ty: DataType) -> TelemetryResult<bool> {
2066    if is_internal_data_type(ty) {
2067        return Err(TelemetryError::BadArg);
2068    }
2069    let mut reg = registry().lock().expect("schema registry poisoned");
2070    let before = reg.types.len();
2071    reg.types.retain(|(id, _)| *id != ty);
2072    Ok(reg.types.len() != before)
2073}
2074
2075#[cfg(feature = "std")]
2076pub fn remove_data_type_by_name(name: &str) -> TelemetryResult<bool> {
2077    if let Some(def) = data_type_definition_by_name(name) {
2078        remove_data_type(def.id)
2079    } else {
2080        Ok(false)
2081    }
2082}
2083
2084fn endpoint_def_equivalent(a: &OwnedEndpointDefinition, b: &OwnedEndpointDefinition) -> bool {
2085    a.id == b.id
2086        && a.name == b.name
2087        && (a.description == b.description || a.description.is_empty() || b.description.is_empty())
2088        && a.link_local_only == b.link_local_only
2089}
2090
2091fn type_def_equivalent(a: &OwnedDataTypeDefinition, b: &OwnedDataTypeDefinition) -> bool {
2092    a.id == b.id
2093        && a.name == b.name
2094        && (a.description == b.description || a.description.is_empty() || b.description.is_empty())
2095        && a.element == b.element
2096        && a.endpoints == b.endpoints
2097        && a.reliable == b.reliable
2098        && a.priority == b.priority
2099        && a.e2e_encryption == b.e2e_encryption
2100}
2101
2102fn endpoint_winner(
2103    a: &OwnedEndpointDefinition,
2104    b: &OwnedEndpointDefinition,
2105) -> OwnedEndpointDefinition {
2106    let a_key = (endpoint_fingerprint(a), a.id.0, a.name.as_str());
2107    let b_key = (endpoint_fingerprint(b), b.id.0, b.name.as_str());
2108    if a_key <= b_key { a.clone() } else { b.clone() }
2109}
2110
2111fn type_winner(
2112    a: &OwnedDataTypeDefinition,
2113    b: &OwnedDataTypeDefinition,
2114) -> OwnedDataTypeDefinition {
2115    let a_key = (type_fingerprint(a), a.id.0, a.name.as_str());
2116    let b_key = (type_fingerprint(b), b.id.0, b.name.as_str());
2117    if a_key <= b_key { a.clone() } else { b.clone() }
2118}
2119
2120// Compatible partial catalogs contribute destinations rather than choosing
2121// one node's incomplete list. Wire layouts/reliability remain a single
2122// deterministic definition when incompatible declarations conflict.
2123fn merge_endpoint_metadata(
2124    a: &OwnedEndpointDefinition,
2125    b: &OwnedEndpointDefinition,
2126) -> OwnedEndpointDefinition {
2127    if a.id == b.id && a.name != b.name {
2128        let placeholder = |def: &OwnedEndpointDefinition| {
2129            def.name
2130                .strip_prefix("ENDPOINT_")
2131                .and_then(|suffix| suffix.parse::<u32>().ok())
2132                == Some(def.id.0)
2133        };
2134        if placeholder(a) && !placeholder(b) {
2135            return b.clone();
2136        }
2137        if placeholder(b) && !placeholder(a) {
2138            return a.clone();
2139        }
2140    }
2141    if a.id != b.id || a.name != b.name {
2142        return endpoint_winner(a, b);
2143    }
2144    let mut merged = a.clone();
2145    merged.link_local_only |= b.link_local_only;
2146    merged
2147}
2148fn merge_type_metadata(
2149    a: &OwnedDataTypeDefinition,
2150    b: &OwnedDataTypeDefinition,
2151) -> OwnedDataTypeDefinition {
2152    if a.id != b.id
2153        || a.name != b.name
2154        || a.element != b.element
2155        || a.reliable != b.reliable
2156        || a.e2e_encryption != b.e2e_encryption
2157    {
2158        return type_winner(a, b);
2159    }
2160    let mut merged = a.clone();
2161    for endpoint in &b.endpoints {
2162        if !merged.endpoints.contains(endpoint) {
2163            merged.endpoints.push(*endpoint);
2164        }
2165    }
2166    for index in 1..merged.endpoints.len() {
2167        let mut cursor = index;
2168        while cursor > 0 && merged.endpoints[cursor - 1] > merged.endpoints[cursor] {
2169            merged.endpoints.swap(cursor - 1, cursor);
2170            cursor -= 1;
2171        }
2172    }
2173    merged.endpoints.dedup();
2174    merged.priority = merged.priority.max(b.priority);
2175    merged
2176}
2177
2178pub(crate) fn message_data_type_code(dt: MessageDataType) -> u8 {
2179    match dt {
2180        MessageDataType::Float64 => 0,
2181        MessageDataType::Float32 => 1,
2182        MessageDataType::UInt8 => 2,
2183        MessageDataType::UInt16 => 3,
2184        MessageDataType::UInt32 => 4,
2185        MessageDataType::UInt64 => 5,
2186        MessageDataType::UInt128 => 6,
2187        MessageDataType::Int8 => 7,
2188        MessageDataType::Int16 => 8,
2189        MessageDataType::Int32 => 9,
2190        MessageDataType::Int64 => 10,
2191        MessageDataType::Int128 => 11,
2192        MessageDataType::Bool => 12,
2193        MessageDataType::String => 13,
2194        MessageDataType::Binary => 14,
2195        MessageDataType::NoData => 15,
2196    }
2197}
2198
2199pub(crate) fn message_data_type_from_code(code: u8) -> Option<MessageDataType> {
2200    match code {
2201        0 => Some(MessageDataType::Float64),
2202        1 => Some(MessageDataType::Float32),
2203        2 => Some(MessageDataType::UInt8),
2204        3 => Some(MessageDataType::UInt16),
2205        4 => Some(MessageDataType::UInt32),
2206        5 => Some(MessageDataType::UInt64),
2207        6 => Some(MessageDataType::UInt128),
2208        7 => Some(MessageDataType::Int8),
2209        8 => Some(MessageDataType::Int16),
2210        9 => Some(MessageDataType::Int32),
2211        10 => Some(MessageDataType::Int64),
2212        11 => Some(MessageDataType::Int128),
2213        12 => Some(MessageDataType::Bool),
2214        13 => Some(MessageDataType::String),
2215        14 => Some(MessageDataType::Binary),
2216        15 => Some(MessageDataType::NoData),
2217        _ => None,
2218    }
2219}
2220
2221pub(crate) fn message_class_code(class: MessageClass) -> u8 {
2222    match class {
2223        MessageClass::Data => 0,
2224        MessageClass::Error => 1,
2225        MessageClass::Warning => 2,
2226    }
2227}
2228
2229pub(crate) fn message_class_from_code(code: u8) -> Option<MessageClass> {
2230    match code {
2231        0 => Some(MessageClass::Data),
2232        1 => Some(MessageClass::Error),
2233        2 => Some(MessageClass::Warning),
2234        _ => None,
2235    }
2236}
2237
2238pub(crate) fn reliable_code(mode: ReliableMode) -> u8 {
2239    match mode {
2240        ReliableMode::None => 0,
2241        ReliableMode::Ordered => 1,
2242        ReliableMode::Unordered => 2,
2243    }
2244}
2245
2246pub(crate) fn reliable_from_code(code: u8) -> Option<ReliableMode> {
2247    match code {
2248        0 => Some(ReliableMode::None),
2249        1 => Some(ReliableMode::Ordered),
2250        2 => Some(ReliableMode::Unordered),
2251        _ => None,
2252    }
2253}
2254
2255pub(crate) fn e2e_encryption_policy_code(policy: E2eEncryptionPolicy) -> u8 {
2256    match policy {
2257        E2eEncryptionPolicy::PreferOff => 0,
2258        E2eEncryptionPolicy::PreferOn => 1,
2259        E2eEncryptionPolicy::RequireOn => 2,
2260    }
2261}
2262
2263pub(crate) fn e2e_encryption_policy_from_code(code: u8) -> Option<E2eEncryptionPolicy> {
2264    match code {
2265        0 => Some(E2eEncryptionPolicy::PreferOff),
2266        1 => Some(E2eEncryptionPolicy::PreferOn),
2267        2 => Some(E2eEncryptionPolicy::RequireOn),
2268        _ => None,
2269    }
2270}
2271
2272#[cfg(not(feature = "std"))]
2273pub fn register_endpoint(name: &str, link_local_only: bool) -> TelemetryResult<DataEndpoint> {
2274    register_endpoint_with_description(name, "", link_local_only)
2275}
2276
2277#[cfg(not(feature = "std"))]
2278pub fn register_data_type(
2279    name: &str,
2280    element: MessageElement,
2281    endpoints: &[DataEndpoint],
2282    reliable: ReliableMode,
2283    priority: u8,
2284) -> TelemetryResult<DataType> {
2285    register_data_type_with_description(name, "", element, endpoints, reliable, priority)
2286}
2287
2288#[cfg(not(feature = "std"))]
2289pub fn register_data_type_with_description(
2290    name: &str,
2291    description: &str,
2292    element: MessageElement,
2293    endpoints: &[DataEndpoint],
2294    reliable: ReliableMode,
2295    priority: u8,
2296) -> TelemetryResult<DataType> {
2297    register_data_type_with_description_and_e2e_encryption(
2298        name,
2299        description,
2300        element,
2301        endpoints,
2302        reliable,
2303        priority,
2304        E2eEncryptionPolicy::PreferOff,
2305    )
2306}
2307
2308#[cfg(not(feature = "std"))]
2309pub fn register_data_type_id(
2310    id: DataType,
2311    name: &str,
2312    element: MessageElement,
2313    endpoints: &[DataEndpoint],
2314    reliable: ReliableMode,
2315    priority: u8,
2316) -> TelemetryResult<DataType> {
2317    register_data_type_id_with_description(id, name, "", element, endpoints, reliable, priority)
2318}
2319
2320#[cfg(not(feature = "std"))]
2321pub fn register_data_type_id_with_description(
2322    id: DataType,
2323    name: &str,
2324    description: &str,
2325    element: MessageElement,
2326    endpoints: &[DataEndpoint],
2327    reliable: ReliableMode,
2328    priority: u8,
2329) -> TelemetryResult<DataType> {
2330    register_data_type_id_with_description_and_e2e_encryption(
2331        id,
2332        name,
2333        description,
2334        element,
2335        endpoints,
2336        reliable,
2337        priority,
2338        E2eEncryptionPolicy::PreferOff,
2339    )
2340}
2341
2342#[cfg(not(feature = "std"))]
2343pub fn register_endpoint_with_description(
2344    name: &str,
2345    description: &str,
2346    link_local_only: bool,
2347) -> TelemetryResult<DataEndpoint> {
2348    register_endpoint_id_with_description(
2349        DataEndpoint(schema_identity::endpoint_id(name)),
2350        name,
2351        description,
2352        link_local_only,
2353    )
2354}
2355#[cfg(not(feature = "std"))]
2356pub fn register_endpoint_id(
2357    id: DataEndpoint,
2358    name: &str,
2359    link_local_only: bool,
2360) -> TelemetryResult<DataEndpoint> {
2361    register_endpoint_id_with_description(id, name, "", link_local_only)
2362}
2363#[cfg(not(feature = "std"))]
2364pub fn register_endpoint_definition(def: EndpointDefinition) -> TelemetryResult<()> {
2365    register_endpoint_id_with_description(def.id, def.name, def.description, def.link_local_only)
2366        .map(|_| ())
2367}
2368#[cfg(not(feature = "std"))]
2369pub fn register_endpoint_id_with_description(
2370    id: DataEndpoint,
2371    name: &str,
2372    description: &str,
2373    link_local_only: bool,
2374) -> TelemetryResult<DataEndpoint> {
2375    let def = OwnedEndpointDefinition {
2376        id,
2377        name: name.into(),
2378        description: description.into(),
2379        link_local_only,
2380    };
2381    unsafe { telemetry_lock() };
2382    let result = (|| {
2383        if let Some(existing) = endpoint_definition(id) {
2384            return if existing == def {
2385                Ok(id)
2386            } else {
2387                Err(TelemetryError::BadArg)
2388            };
2389        }
2390        if embedded_endpoint_name_exists(name) {
2391            return Err(TelemetryError::BadArg);
2392        }
2393        let cost = embedded_endpoint_allocation_cost(&def);
2394        if schema_bytes_used().saturating_add(cost) > MAX_QUEUE_BUDGET {
2395            return Err(TelemetryError::PacketTooLarge(
2396                "Schema exceeds maximum shared queue budget",
2397            ));
2398        }
2399        crate::memory_admission::check(cost.saturating_add(512), cost)?;
2400        push_embedded_endpoint(def);
2401        EMBEDDED_SCHEMA_OWNED_BYTES.fetch_add(cost, Ordering::Relaxed);
2402        Ok(id)
2403    })();
2404    unsafe { telemetry_unlock() };
2405    result
2406}
2407#[cfg(not(feature = "std"))]
2408pub fn ensure_endpoint_id(
2409    id: DataEndpoint,
2410    link_local_only: bool,
2411) -> TelemetryResult<DataEndpoint> {
2412    if endpoint_exists(id) {
2413        Ok(id)
2414    } else {
2415        register_endpoint_id(id, &alloc::format!("ENDPOINT_{}", id.0), link_local_only)
2416    }
2417}
2418#[cfg(not(feature = "std"))]
2419#[allow(clippy::too_many_arguments)]
2420pub fn register_data_type_with_description_and_e2e_encryption(
2421    name: &str,
2422    description: &str,
2423    element: MessageElement,
2424    endpoints: &[DataEndpoint],
2425    reliable: ReliableMode,
2426    priority: u8,
2427    e2e_encryption: E2eEncryptionPolicy,
2428) -> TelemetryResult<DataType> {
2429    register_data_type_id_with_description_and_e2e_encryption(
2430        DataType(schema_identity::type_id(name)),
2431        name,
2432        description,
2433        element,
2434        endpoints,
2435        reliable,
2436        priority,
2437        e2e_encryption,
2438    )
2439}
2440#[cfg(not(feature = "std"))]
2441pub fn register_data_type_definition(def: DataTypeDefinition) -> TelemetryResult<()> {
2442    register_data_type_id_with_description_and_e2e_encryption(
2443        def.id,
2444        def.name,
2445        def.description,
2446        def.element,
2447        def.endpoints,
2448        def.reliable,
2449        def.priority,
2450        def.e2e_encryption,
2451    )
2452    .map(|_| ())
2453}
2454#[cfg(not(feature = "std"))]
2455pub fn set_data_type_e2e_encryption_policy(
2456    _ty: DataType,
2457    _policy: E2eEncryptionPolicy,
2458) -> TelemetryResult<()> {
2459    Err(TelemetryError::BadArg)
2460}
2461#[cfg(not(feature = "std"))]
2462#[allow(clippy::too_many_arguments)]
2463pub fn register_data_type_id_with_description_and_e2e_encryption(
2464    id: DataType,
2465    name: &str,
2466    description: &str,
2467    element: MessageElement,
2468    endpoints: &[DataEndpoint],
2469    reliable: ReliableMode,
2470    priority: u8,
2471    e2e_encryption: E2eEncryptionPolicy,
2472) -> TelemetryResult<DataType> {
2473    let def = OwnedDataTypeDefinition {
2474        id,
2475        name: name.into(),
2476        description: description.into(),
2477        element,
2478        endpoints: endpoints.to_vec(),
2479        reliable,
2480        priority,
2481        e2e_encryption,
2482    };
2483    unsafe { telemetry_lock() };
2484    let result = (|| {
2485        if let Some(existing) = data_type_definition(id) {
2486            return if existing == def {
2487                Ok(id)
2488            } else {
2489                Err(TelemetryError::BadArg)
2490            };
2491        }
2492        if embedded_type_name_exists(name) {
2493            return Err(TelemetryError::BadArg);
2494        }
2495        let cost = embedded_type_allocation_cost(&def);
2496        if schema_bytes_used().saturating_add(cost) > MAX_QUEUE_BUDGET {
2497            return Err(TelemetryError::PacketTooLarge(
2498                "Schema exceeds maximum shared queue budget",
2499            ));
2500        }
2501        crate::memory_admission::check(cost.saturating_add(512), cost)?;
2502        push_embedded_type(def);
2503        EMBEDDED_SCHEMA_OWNED_BYTES.fetch_add(cost, Ordering::Relaxed);
2504        Ok(id)
2505    })();
2506    unsafe { telemetry_unlock() };
2507    result
2508}
2509
2510#[cfg(not(feature = "std"))]
2511const EMBEDDED_BUILTIN_ENDPOINTS: &[EndpointDefinition] = &[
2512    EndpointDefinition {
2513        id: DataEndpoint::TelemetryError,
2514        name: "SEDSNET_ERROR",
2515        description: "",
2516        link_local_only: false,
2517    },
2518    EndpointDefinition {
2519        id: DataEndpoint::TimeSync,
2520        name: "SEDSNET_TIME_SYNC",
2521        description: "",
2522        link_local_only: false,
2523    },
2524    EndpointDefinition {
2525        id: DataEndpoint::Discovery,
2526        name: "SEDSNET_DISCOVERY",
2527        description: "",
2528        link_local_only: false,
2529    },
2530];
2531
2532#[cfg(not(feature = "std"))]
2533fn embedded_static_endpoints() -> Vec<EndpointDefinition> {
2534    let mut endpoints = EMBEDDED_BUILTIN_ENDPOINTS.to_vec();
2535    endpoints.extend_from_slice(EMBEDDED_SCHEMA_ENDPOINTS);
2536    endpoints
2537}
2538
2539#[cfg(not(feature = "std"))]
2540const EMBEDDED_BUILTIN_TYPES: &[DataTypeDefinition] = &[
2541    DataTypeDefinition {
2542        id: DataType::TelemetryError,
2543        name: "SEDSNET_ERROR",
2544        description: "",
2545        element: MessageElement::Dynamic(MessageDataType::String, MessageClass::Error),
2546        endpoints: &[DataEndpoint::TelemetryError],
2547        reliable: ReliableMode::None,
2548        priority: 255,
2549        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2550    },
2551    DataTypeDefinition {
2552        id: DataType::ReliableAck,
2553        name: "SEDSNET_RELIABLE_ACK",
2554        description: "",
2555        element: MessageElement::Static(2, MessageDataType::UInt32, MessageClass::Data),
2556        endpoints: &[DataEndpoint::TelemetryError],
2557        reliable: ReliableMode::None,
2558        priority: 250,
2559        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2560    },
2561    DataTypeDefinition {
2562        id: DataType::ReliablePacketRequest,
2563        name: "SEDSNET_RELIABLE_PACKET_REQUEST",
2564        description: "",
2565        element: MessageElement::Static(2, MessageDataType::UInt32, MessageClass::Data),
2566        endpoints: &[DataEndpoint::TelemetryError],
2567        reliable: ReliableMode::None,
2568        priority: 250,
2569        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2570    },
2571    DataTypeDefinition {
2572        id: DataType::ReliablePartialAck,
2573        name: "SEDSNET_RELIABLE_PARTIAL_ACK",
2574        description: "",
2575        element: MessageElement::Static(2, MessageDataType::UInt32, MessageClass::Data),
2576        endpoints: &[DataEndpoint::TelemetryError],
2577        reliable: ReliableMode::None,
2578        priority: 250,
2579        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2580    },
2581    DataTypeDefinition {
2582        id: DataType::TimeSyncAnnounce,
2583        name: "SEDSNET_TIME_SYNC_ANNOUNCE",
2584        description: "",
2585        element: MessageElement::Static(2, MessageDataType::UInt64, MessageClass::Data),
2586        endpoints: &[DataEndpoint::TimeSync],
2587        reliable: ReliableMode::None,
2588        priority: 245,
2589        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2590    },
2591    DataTypeDefinition {
2592        id: DataType::TimeSyncRequest,
2593        name: "SEDSNET_TIME_SYNC_REQUEST",
2594        description: "",
2595        element: MessageElement::Static(2, MessageDataType::UInt64, MessageClass::Data),
2596        endpoints: &[DataEndpoint::TimeSync],
2597        reliable: ReliableMode::None,
2598        priority: 245,
2599        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2600    },
2601    DataTypeDefinition {
2602        id: DataType::TimeSyncResponse,
2603        name: "SEDSNET_TIME_SYNC_RESPONSE",
2604        description: "",
2605        element: MessageElement::Static(4, MessageDataType::UInt64, MessageClass::Data),
2606        endpoints: &[DataEndpoint::TimeSync],
2607        reliable: ReliableMode::None,
2608        priority: 245,
2609        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2610    },
2611    DataTypeDefinition {
2612        id: DataType::DiscoveryAnnounce,
2613        name: "SEDSNET_DISCOVERY_ANNOUNCE",
2614        description: "",
2615        element: MessageElement::Dynamic(MessageDataType::UInt32, MessageClass::Data),
2616        endpoints: &[DataEndpoint::Discovery],
2617        reliable: ReliableMode::None,
2618        priority: 240,
2619        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2620    },
2621    DataTypeDefinition {
2622        id: DataType::DiscoveryTimeSyncSources,
2623        name: "SEDSNET_DISCOVERY_TIMESYNC_SOURCES",
2624        description: "",
2625        element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2626        endpoints: &[DataEndpoint::Discovery],
2627        reliable: ReliableMode::None,
2628        priority: 240,
2629        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2630    },
2631    DataTypeDefinition {
2632        id: DataType::DiscoveryTopology,
2633        name: "SEDSNET_DISCOVERY_TOPOLOGY",
2634        description: "",
2635        element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2636        endpoints: &[DataEndpoint::Discovery],
2637        reliable: ReliableMode::Ordered,
2638        priority: 240,
2639        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2640    },
2641    DataTypeDefinition {
2642        id: DataType::DiscoverySchema,
2643        name: "SEDSNET_DISCOVERY_SCHEMA",
2644        description: "",
2645        element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2646        endpoints: &[DataEndpoint::Discovery],
2647        reliable: ReliableMode::Ordered,
2648        priority: 241,
2649        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2650    },
2651    DataTypeDefinition {
2652        id: DataType::DiscoveryTopologyRequest,
2653        name: "SEDSNET_DISCOVERY_TOPOLOGY_REQUEST",
2654        description: "",
2655        element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2656        endpoints: &[DataEndpoint::Discovery],
2657        reliable: ReliableMode::Ordered,
2658        priority: 242,
2659        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2660    },
2661    DataTypeDefinition {
2662        id: DataType::DiscoverySchemaRequest,
2663        name: "SEDSNET_DISCOVERY_SCHEMA_REQUEST",
2664        description: "",
2665        element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2666        endpoints: &[DataEndpoint::Discovery],
2667        reliable: ReliableMode::Ordered,
2668        priority: 242,
2669        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2670    },
2671    DataTypeDefinition {
2672        id: DataType::ManagedVariableRequest,
2673        name: "SEDSNET_MANAGED_VARIABLE_REQUEST",
2674        description: "",
2675        element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2676        endpoints: &[DataEndpoint::Discovery],
2677        reliable: ReliableMode::Ordered,
2678        priority: 243,
2679        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2680    },
2681    DataTypeDefinition {
2682        id: DataType::ManagedVariableValue,
2683        name: "SEDSNET_MANAGED_VARIABLE_VALUE",
2684        description: "",
2685        element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2686        endpoints: &[DataEndpoint::Discovery],
2687        reliable: ReliableMode::Ordered,
2688        priority: 243,
2689        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2690    },
2691    DataTypeDefinition {
2692        id: DataType::DiscoveryLeave,
2693        name: "SEDSNET_DISCOVERY_LEAVE",
2694        description: "",
2695        element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2696        endpoints: &[DataEndpoint::Discovery],
2697        reliable: ReliableMode::None,
2698        priority: 244,
2699        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2700    },
2701    DataTypeDefinition {
2702        id: DataType::DiscoveryLinkCapabilities,
2703        name: "SEDSNET_DISCOVERY_LINK_CAPABILITIES",
2704        description: "",
2705        element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2706        endpoints: &[DataEndpoint::Discovery],
2707        reliable: ReliableMode::None,
2708        priority: 240,
2709        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2710    },
2711    DataTypeDefinition {
2712        id: DataType::DiscoveryAddress,
2713        name: "SEDSNET_DISCOVERY_ADDRESS",
2714        description: "",
2715        element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2716        endpoints: &[DataEndpoint::Discovery],
2717        reliable: ReliableMode::Ordered,
2718        priority: 244,
2719        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2720    },
2721    DataTypeDefinition {
2722        id: DataType::P2pMessage,
2723        name: "SEDSNET_P2P_MESSAGE",
2724        description: "",
2725        element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2726        endpoints: &[DataEndpoint::Discovery],
2727        reliable: ReliableMode::Ordered,
2728        priority: 246,
2729        e2e_encryption: E2eEncryptionPolicy::PreferOff,
2730    },
2731];
2732
2733#[cfg(not(feature = "std"))]
2734fn embedded_static_data_types() -> Vec<DataTypeDefinition> {
2735    let mut types = EMBEDDED_BUILTIN_TYPES.to_vec();
2736    types.extend_from_slice(EMBEDDED_SCHEMA_TYPES);
2737    types
2738}
2739
2740#[cfg(not(feature = "std"))]
2741struct EmbeddedEndpointNode {
2742    def: EndpointDefinition,
2743    next: *mut EmbeddedEndpointNode,
2744}
2745
2746#[cfg(not(feature = "std"))]
2747struct EmbeddedTypeNode {
2748    def: DataTypeDefinition,
2749    next: *mut EmbeddedTypeNode,
2750}
2751
2752// Learned definitions are immutable append-only nodes. Readers never lock and
2753// normal packet metadata lookup never allocates. Superseded definitions remain
2754// accounted against the schema budget, which makes both memory use and update
2755// count bounded even under repeated conflicting advertisements.
2756#[cfg(not(feature = "std"))]
2757static EMBEDDED_ENDPOINT_HEAD: AtomicPtr<EmbeddedEndpointNode> =
2758    AtomicPtr::new(core::ptr::null_mut());
2759#[cfg(not(feature = "std"))]
2760static EMBEDDED_TYPE_HEAD: AtomicPtr<EmbeddedTypeNode> = AtomicPtr::new(core::ptr::null_mut());
2761#[cfg(not(feature = "std"))]
2762static EMBEDDED_SCHEMA_OWNED_BYTES: AtomicUsize = AtomicUsize::new(0);
2763
2764#[cfg(not(feature = "std"))]
2765unsafe extern "C" {
2766    fn telemetry_lock();
2767    fn telemetry_unlock();
2768}
2769
2770#[cfg(not(feature = "std"))]
2771fn embedded_overlay_endpoint(id: DataEndpoint) -> Option<EndpointDefinition> {
2772    let mut node = EMBEDDED_ENDPOINT_HEAD.load(Ordering::Acquire);
2773    while !node.is_null() {
2774        // SAFETY: published nodes and their referenced fields are immutable
2775        // and intentionally live until reboot.
2776        let current = unsafe { &*node };
2777        if current.def.id == id
2778            && endpoint_visible_at(current.def, EMBEDDED_ENDPOINT_HEAD.load(Ordering::Acquire))
2779        {
2780            return Some(current.def);
2781        }
2782        node = current.next;
2783    }
2784    None
2785}
2786
2787#[cfg(not(feature = "std"))]
2788fn embedded_overlay_type(id: DataType) -> Option<&'static DataTypeDefinition> {
2789    let mut node = EMBEDDED_TYPE_HEAD.load(Ordering::Acquire);
2790    while !node.is_null() {
2791        // SAFETY: published nodes and their referenced fields are immutable
2792        // and intentionally live until reboot.
2793        let current = unsafe { &*node };
2794        if current.def.id == id
2795            && type_visible_at(&current.def, EMBEDDED_TYPE_HEAD.load(Ordering::Acquire))
2796        {
2797            return Some(&current.def);
2798        }
2799        node = current.next;
2800    }
2801    None
2802}
2803
2804#[cfg(not(feature = "std"))]
2805fn embedded_overlay_endpoint_by_name(name: &str) -> Option<EndpointDefinition> {
2806    let mut node = EMBEDDED_ENDPOINT_HEAD.load(Ordering::Acquire);
2807    while !node.is_null() {
2808        let current = unsafe { &*node };
2809        if current.def.name == name
2810            && endpoint_visible_at(current.def, EMBEDDED_ENDPOINT_HEAD.load(Ordering::Acquire))
2811        {
2812            return Some(current.def);
2813        }
2814        node = current.next;
2815    }
2816    None
2817}
2818
2819#[cfg(not(feature = "std"))]
2820fn embedded_overlay_type_by_name(name: &str) -> Option<DataTypeDefinition> {
2821    let mut node = EMBEDDED_TYPE_HEAD.load(Ordering::Acquire);
2822    while !node.is_null() {
2823        let current = unsafe { &*node };
2824        if current.def.name == name
2825            && type_visible_at(&current.def, EMBEDDED_TYPE_HEAD.load(Ordering::Acquire))
2826        {
2827            return Some(current.def);
2828        }
2829        node = current.next;
2830    }
2831    None
2832}
2833
2834#[cfg(not(feature = "std"))]
2835fn embedded_endpoint_name_exists(name: &str) -> bool {
2836    DataEndpoint::try_named(name).is_some()
2837}
2838
2839#[cfg(not(feature = "std"))]
2840fn embedded_type_name_exists(name: &str) -> bool {
2841    DataType::try_named(name).is_some()
2842}
2843
2844#[cfg(not(feature = "std"))]
2845fn endpoint_visible_at(def: EndpointDefinition, mut head: *mut EmbeddedEndpointNode) -> bool {
2846    while !head.is_null() {
2847        let current = unsafe { &*head };
2848        if current.def.id == def.id || current.def.name == def.name {
2849            return current.def == def;
2850        }
2851        head = current.next;
2852    }
2853    true
2854}
2855#[cfg(not(feature = "std"))]
2856fn type_visible_at(def: &DataTypeDefinition, mut head: *mut EmbeddedTypeNode) -> bool {
2857    while !head.is_null() {
2858        let current = unsafe { &*head };
2859        if current.def.id == def.id || current.def.name == def.name {
2860            return &current.def == def;
2861        }
2862        head = current.next;
2863    }
2864    true
2865}
2866
2867#[cfg(not(feature = "std"))]
2868fn own_embedded_endpoint(def: EndpointDefinition) -> OwnedEndpointDefinition {
2869    OwnedEndpointDefinition {
2870        id: def.id,
2871        name: def.name.to_string(),
2872        description: def.description.to_string(),
2873        link_local_only: def.link_local_only,
2874    }
2875}
2876
2877#[cfg(not(feature = "std"))]
2878fn own_embedded_type(def: DataTypeDefinition) -> OwnedDataTypeDefinition {
2879    OwnedDataTypeDefinition {
2880        id: def.id,
2881        name: def.name.to_string(),
2882        description: def.description.to_string(),
2883        element: def.element,
2884        endpoints: def.endpoints.to_vec(),
2885        reliable: def.reliable,
2886        priority: def.priority,
2887        e2e_encryption: def.e2e_encryption,
2888    }
2889}
2890
2891#[cfg(not(feature = "std"))]
2892fn effective_embedded_schema() -> OwnedRuntimeSchemaSnapshot {
2893    let endpoint_head = EMBEDDED_ENDPOINT_HEAD.load(Ordering::Acquire);
2894    let type_head = EMBEDDED_TYPE_HEAD.load(Ordering::Acquire);
2895    let mut endpoints = Vec::new();
2896    for def in embedded_static_endpoints() {
2897        if endpoint_visible_at(def, endpoint_head) {
2898            endpoints.push(own_embedded_endpoint(def));
2899        }
2900    }
2901    let mut node = endpoint_head;
2902    while !node.is_null() {
2903        let current = unsafe { &*node };
2904        if endpoint_visible_at(current.def, endpoint_head) {
2905            endpoints.push(own_embedded_endpoint(current.def));
2906        }
2907        node = current.next;
2908    }
2909    let mut types = Vec::new();
2910    for def in embedded_static_data_types() {
2911        if type_visible_at(&def, type_head) {
2912            types.push(own_embedded_type(def));
2913        }
2914    }
2915    let mut node = type_head;
2916    while !node.is_null() {
2917        let current = unsafe { &*node };
2918        if type_visible_at(&current.def, type_head) {
2919            types.push(own_embedded_type(current.def));
2920        }
2921        node = current.next;
2922    }
2923    OwnedRuntimeSchemaSnapshot { endpoints, types }
2924}
2925
2926#[cfg(not(feature = "std"))]
2927pub(crate) fn encode_embedded_schema_payload() -> TelemetryResult<Arc<[u8]>> {
2928    struct SchemaWriter<'a> {
2929        bytes: Option<&'a mut [core::mem::MaybeUninit<u8>]>,
2930        len: usize,
2931    }
2932    impl SchemaWriter<'_> {
2933        fn extend_from_slice(&mut self, values: &[u8]) {
2934            let end = self
2935                .len
2936                .checked_add(values.len())
2937                .expect("schema length overflow");
2938            if let Some(bytes) = &mut self.bytes {
2939                for (slot, value) in bytes[self.len..end].iter_mut().zip(values) {
2940                    slot.write(*value);
2941                }
2942            }
2943            self.len = end;
2944        }
2945        fn push(&mut self, value: u8) {
2946            self.extend_from_slice(&[value]);
2947        }
2948    }
2949    fn push_string(payload: &mut SchemaWriter<'_>, value: &str) {
2950        payload.extend_from_slice(&(value.len() as u32).to_le_bytes());
2951        payload.extend_from_slice(value.as_bytes());
2952    }
2953
2954    fn push_endpoint(payload: &mut SchemaWriter<'_>, def: EndpointDefinition) {
2955        payload.extend_from_slice(&def.id.0.to_le_bytes());
2956        payload.push(def.link_local_only as u8);
2957        push_string(payload, def.name);
2958        push_string(payload, "");
2959    }
2960
2961    fn push_type(payload: &mut SchemaWriter<'_>, def: DataTypeDefinition) {
2962        payload.extend_from_slice(&def.id.0.to_le_bytes());
2963        push_string(payload, def.name);
2964        push_string(payload, "");
2965        match def.element {
2966            MessageElement::Static(count, data_type, class) => {
2967                payload.push(0);
2968                payload.extend_from_slice(&(count as u32).to_le_bytes());
2969                payload.push(message_data_type_code(data_type));
2970                payload.push(message_class_code(class));
2971            }
2972            MessageElement::Dynamic(data_type, class) => {
2973                payload.push(1);
2974                payload.extend_from_slice(&0u32.to_le_bytes());
2975                payload.push(message_data_type_code(data_type));
2976                payload.push(message_class_code(class));
2977            }
2978        }
2979        payload.push(reliable_code(def.reliable));
2980        payload.push(def.priority);
2981        payload.push(e2e_encryption_policy_code(def.e2e_encryption));
2982        payload.extend_from_slice(&(def.endpoints.len() as u32).to_le_bytes());
2983        for endpoint in def.endpoints {
2984            payload.extend_from_slice(&endpoint.0.to_le_bytes());
2985        }
2986    }
2987
2988    let endpoint_head = EMBEDDED_ENDPOINT_HEAD.load(Ordering::Acquire);
2989    let type_head = EMBEDDED_TYPE_HEAD.load(Ordering::Acquire);
2990    let endpoints = |visit: &mut dyn FnMut(EndpointDefinition)| {
2991        for def in EMBEDDED_BUILTIN_ENDPOINTS
2992            .iter()
2993            .chain(EMBEDDED_SCHEMA_ENDPOINTS.iter())
2994        {
2995            if endpoint_visible_at(*def, endpoint_head) {
2996                visit(*def);
2997            }
2998        }
2999        let mut node = endpoint_head;
3000        while !node.is_null() {
3001            let current = unsafe { &*node };
3002            if endpoint_visible_at(current.def, endpoint_head) {
3003                visit(current.def);
3004            }
3005            node = current.next;
3006        }
3007    };
3008    let types = |visit: &mut dyn FnMut(DataTypeDefinition)| {
3009        for def in EMBEDDED_BUILTIN_TYPES
3010            .iter()
3011            .chain(EMBEDDED_SCHEMA_TYPES.iter())
3012        {
3013            if type_visible_at(def, type_head) {
3014                visit(*def);
3015            }
3016        }
3017        let mut node = type_head;
3018        while !node.is_null() {
3019            let current = unsafe { &*node };
3020            if type_visible_at(&current.def, type_head) {
3021                visit(current.def);
3022            }
3023            node = current.next;
3024        }
3025    };
3026    let mut endpoint_count = 0u32;
3027    endpoints(&mut |_| endpoint_count += 1);
3028    let mut type_count = 0u32;
3029    types(&mut |_| type_count += 1);
3030    let encode = |payload: &mut SchemaWriter<'_>| {
3031        payload.extend_from_slice(&3u32.to_le_bytes());
3032        payload.extend_from_slice(&endpoint_count.to_le_bytes());
3033        endpoints(&mut |def| push_endpoint(payload, def));
3034        payload.extend_from_slice(&type_count.to_le_bytes());
3035        types(&mut |def| push_type(payload, def));
3036    };
3037    let mut count = SchemaWriter {
3038        bytes: None,
3039        len: 0,
3040    };
3041    encode(&mut count);
3042    crate::memory_admission::check(count.len.saturating_add(512), count.len.saturating_add(32))?;
3043    let mut payload = Arc::<[u8]>::new_uninit_slice(count.len);
3044    let bytes = Arc::get_mut(&mut payload).unwrap();
3045    let mut writer = SchemaWriter {
3046        bytes: Some(bytes),
3047        len: 0,
3048    };
3049    encode(&mut writer);
3050    assert_eq!(writer.len, count.len);
3051    // SAFETY: the writer initialized every byte of the counted allocation.
3052    Ok(unsafe { payload.assume_init() })
3053}
3054
3055#[cfg(not(feature = "std"))]
3056fn materialize_embedded_endpoint(def: OwnedEndpointDefinition) -> EndpointDefinition {
3057    EndpointDefinition {
3058        id: def.id,
3059        name: Box::leak(def.name.into_boxed_str()),
3060        description: Box::leak(def.description.into_boxed_str()),
3061        link_local_only: def.link_local_only,
3062    }
3063}
3064
3065#[cfg(not(feature = "std"))]
3066fn materialize_embedded_type(def: OwnedDataTypeDefinition) -> DataTypeDefinition {
3067    DataTypeDefinition {
3068        id: def.id,
3069        name: Box::leak(def.name.into_boxed_str()),
3070        description: Box::leak(def.description.into_boxed_str()),
3071        element: def.element,
3072        endpoints: Box::leak(def.endpoints.into_boxed_slice()),
3073        reliable: def.reliable,
3074        priority: def.priority,
3075        e2e_encryption: def.e2e_encryption,
3076    }
3077}
3078
3079#[cfg(not(feature = "std"))]
3080fn embedded_endpoint_allocation_cost(def: &OwnedEndpointDefinition) -> usize {
3081    size_of::<EmbeddedEndpointNode>()
3082        .saturating_add(def.name.len())
3083        .saturating_add(def.description.len())
3084}
3085
3086#[cfg(not(feature = "std"))]
3087fn embedded_type_allocation_cost(def: &OwnedDataTypeDefinition) -> usize {
3088    size_of::<EmbeddedTypeNode>()
3089        .saturating_add(def.name.len())
3090        .saturating_add(def.description.len())
3091        .saturating_add(
3092            def.endpoints
3093                .len()
3094                .saturating_mul(size_of::<DataEndpoint>()),
3095        )
3096}
3097
3098// Reserved definitions are dense by protocol identity. Keep this invariant
3099// checked at compile time so normal application IDs never scan the control list.
3100#[cfg(not(feature = "std"))]
3101const _: () = {
3102    let mut i = 0;
3103    while i < EMBEDDED_BUILTIN_TYPES.len() {
3104        assert!(EMBEDDED_BUILTIN_TYPES[i].id.0 == i as u32);
3105        i += 1;
3106    }
3107};
3108
3109// Runtime and generated schemas use the same name-based wire identities.
3110// A schema subset or reordered file cannot change another definition's ID.
3111#[cfg(not(feature = "std"))]
3112#[inline(never)]
3113fn compiled_type_definition(id: DataType) -> Option<&'static DataTypeDefinition> {
3114    EMBEDDED_BUILTIN_TYPES
3115        .get(id.0 as usize)
3116        .or_else(|| {
3117            EMBEDDED_SCHEMA_TYPES
3118                .binary_search_by_key(&id.0, |def| def.id.0)
3119                .ok()
3120                .map(|index| &EMBEDDED_SCHEMA_TYPES[index])
3121        })
3122        .filter(|def| type_visible_at(def, EMBEDDED_TYPE_HEAD.load(Ordering::Acquire)))
3123}
3124#[cfg(not(feature = "std"))]
3125#[inline(never)]
3126fn compiled_endpoint_definition(id: DataEndpoint) -> Option<EndpointDefinition> {
3127    EMBEDDED_BUILTIN_ENDPOINTS
3128        .iter()
3129        .find(|def| def.id == id)
3130        .copied()
3131        .or_else(|| {
3132            EMBEDDED_SCHEMA_ENDPOINTS
3133                .binary_search_by_key(&id.0, |def| def.id.0)
3134                .ok()
3135                .map(|index| EMBEDDED_SCHEMA_ENDPOINTS[index])
3136        })
3137        .filter(|def| endpoint_visible_at(*def, EMBEDDED_ENDPOINT_HEAD.load(Ordering::Acquire)))
3138}
3139
3140#[cfg(not(feature = "std"))]
3141fn push_embedded_endpoint(def: OwnedEndpointDefinition) {
3142    let node = Box::new(EmbeddedEndpointNode {
3143        def: materialize_embedded_endpoint(def),
3144        next: EMBEDDED_ENDPOINT_HEAD.load(Ordering::Relaxed),
3145    });
3146    EMBEDDED_ENDPOINT_HEAD.store(Box::into_raw(node), Ordering::Release);
3147}
3148
3149#[cfg(not(feature = "std"))]
3150fn push_embedded_type(def: OwnedDataTypeDefinition) {
3151    let node = Box::new(EmbeddedTypeNode {
3152        def: materialize_embedded_type(def),
3153        next: EMBEDDED_TYPE_HEAD.load(Ordering::Relaxed),
3154    });
3155    EMBEDDED_TYPE_HEAD.store(Box::into_raw(node), Ordering::Release);
3156}
3157
3158#[cfg(not(feature = "std"))]
3159pub fn export_schema() -> OwnedRuntimeSchemaSnapshot {
3160    effective_embedded_schema()
3161}
3162
3163#[cfg(not(feature = "std"))]
3164pub fn known_endpoints() -> Vec<OwnedEndpointDefinition> {
3165    export_schema().endpoints
3166}
3167
3168#[cfg(not(feature = "std"))]
3169pub fn known_data_types() -> Vec<OwnedDataTypeDefinition> {
3170    export_schema().types
3171}
3172
3173#[cfg(not(feature = "std"))]
3174pub fn merge_schema_snapshot(snapshot: RuntimeSchemaSnapshot) -> SchemaMergeReport {
3175    merge_owned_schema_snapshot_with_budget(
3176        OwnedRuntimeSchemaSnapshot {
3177            endpoints: snapshot
3178                .endpoints
3179                .into_iter()
3180                .map(|def| OwnedEndpointDefinition {
3181                    id: def.id,
3182                    name: def.name.to_string(),
3183                    description: def.description.to_string(),
3184                    link_local_only: def.link_local_only,
3185                })
3186                .collect(),
3187            types: snapshot
3188                .types
3189                .into_iter()
3190                .map(|def| OwnedDataTypeDefinition {
3191                    id: def.id,
3192                    name: def.name.to_string(),
3193                    description: def.description.to_string(),
3194                    element: def.element,
3195                    endpoints: def.endpoints.to_vec(),
3196                    reliable: def.reliable,
3197                    priority: def.priority,
3198                    e2e_encryption: def.e2e_encryption,
3199                })
3200                .collect(),
3201        },
3202        usize::MAX,
3203    )
3204    .expect("unbounded embedded schema merge")
3205}
3206
3207#[cfg(not(feature = "std"))]
3208pub fn merge_owned_schema_snapshot_with_budget(
3209    snapshot: OwnedRuntimeSchemaSnapshot,
3210    max_schema_bytes: usize,
3211) -> TelemetryResult<SchemaMergeReport> {
3212    unsafe { telemetry_lock() };
3213    let result = (|| {
3214        let mut report = SchemaMergeReport {
3215            endpoints_added: 0,
3216            endpoints_replaced: 0,
3217            endpoints_kept: 0,
3218            types_added: 0,
3219            types_replaced: 0,
3220            types_kept: 0,
3221        };
3222        let mut endpoints = Vec::new();
3223        let mut types = Vec::new();
3224        let mut added_bytes = 0usize;
3225        for incoming in snapshot.endpoints {
3226            let existing = endpoint_definition(incoming.id)
3227                .or_else(|| endpoint_definition_by_name(&incoming.name));
3228            let merged = if let Some(existing) = &existing {
3229                merge_endpoint_metadata(existing, &incoming)
3230            } else {
3231                incoming
3232            };
3233            if existing
3234                .as_ref()
3235                .is_some_and(|existing| endpoint_def_equivalent(existing, &merged))
3236            {
3237                continue;
3238            }
3239            if existing.is_some() {
3240                report.endpoints_replaced += 1;
3241            } else {
3242                report.endpoints_added += 1;
3243            }
3244            added_bytes = added_bytes.saturating_add(embedded_endpoint_allocation_cost(&merged));
3245            endpoints.push(merged);
3246        }
3247        for incoming in snapshot.types {
3248            let existing = data_type_definition(incoming.id)
3249                .or_else(|| data_type_definition_by_name(&incoming.name));
3250            let merged = if let Some(existing) = &existing {
3251                merge_type_metadata(existing, &incoming)
3252            } else {
3253                incoming
3254            };
3255            if existing
3256                .as_ref()
3257                .is_some_and(|existing| type_def_equivalent(existing, &merged))
3258            {
3259                continue;
3260            }
3261            if existing.is_some() {
3262                report.types_replaced += 1;
3263            } else {
3264                report.types_added += 1;
3265            }
3266            added_bytes = added_bytes.saturating_add(embedded_type_allocation_cost(&merged));
3267            types.push(merged);
3268        }
3269        if schema_bytes_used().saturating_add(added_bytes) > max_schema_bytes {
3270            return Err(TelemetryError::PacketTooLarge(
3271                "Schema exceeds maximum shared queue budget",
3272            ));
3273        }
3274        for definition in endpoints {
3275            push_embedded_endpoint(definition);
3276        }
3277        for definition in types {
3278            push_embedded_type(definition);
3279        }
3280        EMBEDDED_SCHEMA_OWNED_BYTES.fetch_add(added_bytes, Ordering::Relaxed);
3281        Ok(report)
3282    })();
3283    unsafe { telemetry_unlock() };
3284    result
3285}
3286
3287#[cfg(not(feature = "std"))]
3288pub fn schema_fingerprint() -> u64 {
3289    let snapshot = export_schema();
3290    let mut h = 0x5E_D5_50_4F_52_49_4E_54u64;
3291    for ep in snapshot.endpoints {
3292        h = hash_u32(h, ep.id.0);
3293        h = hash_bytes(h, ep.name.as_bytes());
3294        h = hash_bytes(h, ep.description.as_bytes());
3295        h = hash_u8(h, ep.link_local_only as u8);
3296    }
3297    for ty in snapshot.types {
3298        h = hash_u32(h, ty.id.0);
3299        h = hash_bytes(h, ty.name.as_bytes());
3300        h = hash_bytes(h, ty.description.as_bytes());
3301        h = hash_message_element(h, ty.element);
3302        h = hash_u8(h, reliable_code(ty.reliable));
3303        h = hash_u8(h, ty.priority);
3304        h = hash_u8(h, e2e_encryption_policy_code(ty.e2e_encryption));
3305        for ep in ty.endpoints {
3306            h = hash_u32(h, ep.0);
3307        }
3308    }
3309    h
3310}
3311
3312#[cfg(not(feature = "std"))]
3313pub fn schema_bytes_used() -> usize {
3314    // Report RAM retained by learned definitions, excluding flash tables.
3315    EMBEDDED_SCHEMA_OWNED_BYTES.load(Ordering::Relaxed)
3316}
3317
3318#[cfg(not(feature = "std"))]
3319pub fn endpoint_exists(ep: DataEndpoint) -> bool {
3320    embedded_overlay_endpoint(ep).is_some() || compiled_endpoint_definition(ep).is_some()
3321}
3322
3323#[cfg(not(feature = "std"))]
3324pub fn data_type_exists(ty: DataType) -> bool {
3325    embedded_overlay_type(ty).is_some() || compiled_type_definition(ty).is_some()
3326}
3327
3328#[cfg(not(feature = "std"))]
3329pub fn endpoint_definition(ep: DataEndpoint) -> Option<OwnedEndpointDefinition> {
3330    embedded_overlay_endpoint(ep)
3331        .or_else(|| compiled_endpoint_definition(ep))
3332        .map(own_embedded_endpoint)
3333}
3334
3335#[cfg(not(feature = "std"))]
3336pub fn data_type_definition(ty: DataType) -> Option<OwnedDataTypeDefinition> {
3337    embedded_overlay_type(ty)
3338        .or_else(|| compiled_type_definition(ty))
3339        .map(|def| own_embedded_type(*def))
3340}
3341
3342#[cfg(not(feature = "std"))]
3343pub fn endpoint_definition_by_name(name: &str) -> Option<OwnedEndpointDefinition> {
3344    DataEndpoint::try_named(name).and_then(endpoint_definition)
3345}
3346
3347#[cfg(not(feature = "std"))]
3348pub fn data_type_definition_by_name(name: &str) -> Option<OwnedDataTypeDefinition> {
3349    DataType::try_named(name).and_then(data_type_definition)
3350}
3351
3352#[cfg(not(feature = "std"))]
3353pub fn remove_endpoint(_ep: DataEndpoint) -> TelemetryResult<bool> {
3354    Err(TelemetryError::BadArg)
3355}
3356
3357#[cfg(not(feature = "std"))]
3358pub fn remove_endpoint_by_name(_name: &str) -> TelemetryResult<bool> {
3359    Err(TelemetryError::BadArg)
3360}
3361
3362#[cfg(not(feature = "std"))]
3363pub fn remove_data_type(_ty: DataType) -> TelemetryResult<bool> {
3364    Err(TelemetryError::BadArg)
3365}
3366
3367#[cfg(not(feature = "std"))]
3368pub fn remove_data_type_by_name(_name: &str) -> TelemetryResult<bool> {
3369    Err(TelemetryError::BadArg)
3370}
3371
3372#[cfg(not(feature = "std"))]
3373#[inline(never)]
3374pub fn get_endpoint_meta(endpoint_type: DataEndpoint) -> EndpointMeta {
3375    embedded_overlay_endpoint(endpoint_type)
3376        .or_else(|| compiled_endpoint_definition(endpoint_type))
3377        .map(|def| EndpointMeta {
3378            name: def.name,
3379            description: def.description,
3380            link_local_only: def.link_local_only,
3381        })
3382        .unwrap_or_else(|| EndpointMeta {
3383            name: "UNKNOWN_ENDPOINT",
3384            description: "",
3385            link_local_only: false,
3386        })
3387}
3388
3389#[cfg(not(feature = "std"))]
3390#[inline(never)]
3391pub(crate) fn try_get_message_meta(data_type: DataType) -> Option<MessageMeta> {
3392    embedded_overlay_type(data_type)
3393        .or_else(|| compiled_type_definition(data_type))
3394        .map(|def| MessageMeta {
3395            name: def.name,
3396            description: def.description,
3397            element: def.element,
3398            endpoints: def.endpoints,
3399            reliable: def.reliable,
3400            priority: def.priority,
3401            e2e_encryption: def.e2e_encryption,
3402        })
3403}
3404
3405#[cfg(not(feature = "std"))]
3406#[inline(never)]
3407pub fn get_message_meta(data_type: DataType) -> MessageMeta {
3408    try_get_message_meta(data_type).unwrap_or(MessageMeta {
3409        name: "UNKNOWN_TYPE",
3410        description: "",
3411        element: MessageElement::Dynamic(MessageDataType::Binary, MessageClass::Data),
3412        endpoints: &[],
3413        reliable: ReliableMode::None,
3414        priority: 0,
3415        e2e_encryption: E2eEncryptionPolicy::PreferOff,
3416    })
3417}
3418
3419#[cfg(not(feature = "std"))]
3420pub fn max_endpoint_id() -> u32 {
3421    known_endpoints()
3422        .iter()
3423        .map(|def| def.id.as_u32())
3424        .max()
3425        .unwrap_or(DataEndpoint::TelemetryError.as_u32())
3426}
3427
3428#[cfg(not(feature = "std"))]
3429pub fn max_data_type_id() -> u32 {
3430    known_data_types()
3431        .iter()
3432        .map(|def| def.id.as_u32())
3433        .max()
3434        .unwrap_or(DataType::DiscoverySchema.as_u32())
3435}
3436
3437// -----------------------------------------------------------------------------
3438// Optional JSON seeding for std builds
3439// -----------------------------------------------------------------------------
3440
3441#[cfg(feature = "std")]
3442pub fn register_schema_json_str(json: &str) -> TelemetryResult<()> {
3443    register_schema_json_bytes(json.as_bytes())
3444}
3445
3446#[cfg(feature = "std")]
3447pub fn register_schema_json_bytes(json: &[u8]) -> TelemetryResult<()> {
3448    register_schema_json_bytes_with_budget(json, MAX_QUEUE_BUDGET)
3449}
3450
3451/// Stream a schema document from an existing byte slice while limiting the
3452/// total retained schema memory. The caller owns the input slice; this function
3453/// never creates a second full-document allocation.
3454#[cfg(feature = "std")]
3455pub fn register_schema_json_bytes_with_budget(
3456    json: &[u8],
3457    max_schema_bytes: usize,
3458) -> TelemetryResult<()> {
3459    let max_input_bytes = schema_json_max_input_bytes(max_schema_bytes);
3460    if json.len() > max_input_bytes {
3461        return Err(TelemetryError::PacketTooLarge(
3462            "Schema JSON exceeds bounded input size",
3463        ));
3464    }
3465    let mut reg = registry().lock().expect("schema registry poisoned");
3466    register_schema_json_reader_into(&mut reg, json, false, max_schema_bytes)
3467}
3468
3469#[cfg(feature = "std")]
3470pub fn register_schema_json_file(path: impl AsRef<std::path::Path>) -> TelemetryResult<()> {
3471    register_schema_json_file_with_budget(path, MAX_QUEUE_BUDGET)
3472}
3473
3474/// Load a JSON schema using a fixed-size read buffer and a hard retained-memory
3475/// budget. Entries are decoded and validated individually, and a failed load is
3476/// rolled back completely.
3477#[cfg(feature = "std")]
3478pub fn register_schema_json_file_with_budget(
3479    path: impl AsRef<std::path::Path>,
3480    max_schema_bytes: usize,
3481) -> TelemetryResult<()> {
3482    let mut reg = registry().lock().expect("schema registry poisoned");
3483    register_schema_json_file_into(
3484        &mut reg,
3485        path.as_ref(),
3486        false,
3487        max_schema_bytes,
3488        SCHEMA_JSON_CHUNK_BYTES,
3489    )
3490}
3491
3492#[cfg(feature = "std")]
3493pub fn register_schema_json_path(path: &str) -> TelemetryResult<()> {
3494    register_schema_json_file(path)
3495}
3496
3497#[cfg(not(feature = "std"))]
3498pub fn register_schema_json_bytes(_json: &[u8]) -> TelemetryResult<()> {
3499    Err(TelemetryError::BadArg)
3500}
3501
3502#[cfg(feature = "serde")]
3503#[derive(serde::Deserialize)]
3504struct JsonConfig {
3505    endpoints: Vec<JsonEndpoint>,
3506    types: Vec<JsonType>,
3507}
3508
3509#[cfg(feature = "serde")]
3510#[derive(serde::Deserialize)]
3511struct JsonEndpoint {
3512    rust: Option<String>,
3513    name: String,
3514    #[serde(default, alias = "doc")]
3515    description: Option<String>,
3516    #[serde(default, alias = "link_local_only")]
3517    link_local_only: Option<bool>,
3518    #[serde(default, alias = "broadcast_mode")]
3519    broadcast_mode: Option<String>,
3520}
3521
3522#[cfg(feature = "serde")]
3523#[derive(serde::Deserialize)]
3524#[serde(deny_unknown_fields)]
3525struct JsonType {
3526    #[serde(rename = "rust")]
3527    _rust: Option<String>,
3528    name: String,
3529    #[serde(default, alias = "doc")]
3530    description: Option<String>,
3531    class: String,
3532    element: JsonElement,
3533    endpoints: Vec<String>,
3534    reliable: ReliableMode,
3535    #[serde(default)]
3536    priority: Option<u8>,
3537    #[serde(default)]
3538    e2e_encryption: Option<String>,
3539}
3540
3541fn parse_e2e_encryption_policy(raw: Option<&str>) -> TelemetryResult<E2eEncryptionPolicy> {
3542    match raw.unwrap_or("PreferOff") {
3543        "PreferOff" | "prefer_off" | "off" | "false" => Ok(E2eEncryptionPolicy::PreferOff),
3544        "PreferOn" | "prefer_on" | "preferred" | "true" => Ok(E2eEncryptionPolicy::PreferOn),
3545        "RequireOn" | "require_on" | "required" => Ok(E2eEncryptionPolicy::RequireOn),
3546        _ => Err(TelemetryError::BadArg),
3547    }
3548}
3549
3550#[cfg(feature = "serde")]
3551#[derive(serde::Deserialize)]
3552#[serde(tag = "kind")]
3553enum JsonElement {
3554    Static {
3555        data_type: String,
3556        count: Option<usize>,
3557    },
3558    Dynamic {
3559        data_type: String,
3560    },
3561}
3562
3563#[cfg(feature = "std")]
3564fn schema_json_max_input_bytes(max_schema_bytes: usize) -> usize {
3565    max_schema_bytes
3566        .saturating_mul(SCHEMA_JSON_INPUT_MULTIPLIER)
3567        .max(SCHEMA_JSON_CHUNK_BYTES)
3568}
3569
3570#[cfg(feature = "std")]
3571struct StreamingSchemaState<'a> {
3572    reg: &'a mut Registry,
3573    aliases: Vec<(String, DataEndpoint)>,
3574    alias_bytes: usize,
3575    added_endpoints: Vec<DataEndpoint>,
3576    added_types: Vec<DataType>,
3577    initial_next_endpoint_id: u32,
3578    initial_next_type_id: u32,
3579    link_local_overlay: bool,
3580    max_schema_bytes: usize,
3581    saw_endpoints: bool,
3582    saw_types: bool,
3583    error: Option<TelemetryError>,
3584}
3585
3586#[cfg(feature = "std")]
3587impl StreamingSchemaState<'_> {
3588    fn ensure_budget(&self, additional: usize) -> TelemetryResult<()> {
3589        if self
3590            .reg
3591            .schema_byte_cost()
3592            .saturating_add(self.alias_bytes)
3593            .saturating_add(additional)
3594            > self.max_schema_bytes
3595        {
3596            Err(TelemetryError::PacketTooLarge(
3597                "Schema exceeds maximum shared queue budget",
3598            ))
3599        } else {
3600            Ok(())
3601        }
3602    }
3603
3604    fn add_endpoint(&mut self, ep: JsonEndpoint) -> TelemetryResult<()> {
3605        let rust_name = ep.rust.unwrap_or_else(|| ep.name.clone());
3606        let id = DataEndpoint(schema_identity::endpoint_id(&ep.name));
3607        let existed = self.reg.endpoints.iter().any(|(known, _)| *known == id);
3608        let description = ep.description.unwrap_or_default();
3609        let retained = if existed {
3610            0
3611        } else {
3612            endpoint_schema_byte_cost(ep.name.len(), description.len())
3613        };
3614        self.ensure_budget(retained.saturating_add(rust_name.len()))?;
3615        self.reg.register_owned_endpoint(OwnedEndpointDefinition {
3616            id,
3617            name: ep.name,
3618            description,
3619            link_local_only: self.link_local_overlay
3620                || ep.link_local_only.unwrap_or(false)
3621                || matches!(ep.broadcast_mode.as_deref(), Some("Never")),
3622        })?;
3623        if !existed {
3624            self.added_endpoints.push(id);
3625        }
3626        self.alias_bytes = self.alias_bytes.saturating_add(rust_name.len());
3627        self.aliases.push((rust_name, id));
3628        Ok(())
3629    }
3630
3631    fn add_type(&mut self, ty: JsonType) -> TelemetryResult<()> {
3632        let endpoints = ty
3633            .endpoints
3634            .iter()
3635            .map(|name| {
3636                self.aliases
3637                    .iter()
3638                    .find(|(alias, _)| alias == name)
3639                    .map(|(_, id)| *id)
3640                    .or_else(|| {
3641                        self.reg
3642                            .endpoints
3643                            .iter()
3644                            .find(|(_, meta)| meta.name.as_ref() == name.as_str())
3645                            .map(|(id, _)| *id)
3646                    })
3647                    .unwrap_or_else(|| DataEndpoint::identity(name))
3648            })
3649            .collect::<Vec<_>>();
3650        let id = DataType(schema_identity::type_id(&ty.name));
3651        let existed = self.reg.types.iter().any(|(known, _)| *known == id);
3652        let description = ty.description.unwrap_or_default();
3653        if !existed {
3654            self.ensure_budget(type_schema_byte_cost(
3655                ty.name.len(),
3656                description.len(),
3657                endpoints.len(),
3658            ))?;
3659        }
3660        let class = parse_message_class(&ty.class)?;
3661        let element = match ty.element {
3662            JsonElement::Static { data_type, count } => MessageElement::Static(
3663                count.unwrap_or(1),
3664                parse_message_data_type(&data_type)?,
3665                class,
3666            ),
3667            JsonElement::Dynamic { data_type } => {
3668                MessageElement::Dynamic(parse_message_data_type(&data_type)?, class)
3669            }
3670        };
3671        let reliable = ty.reliable;
3672        self.reg.register_owned_type(OwnedDataTypeDefinition {
3673            id,
3674            name: ty.name,
3675            description,
3676            element,
3677            endpoints,
3678            reliable,
3679            priority: ty.priority.unwrap_or(0),
3680            e2e_encryption: parse_e2e_encryption_policy(ty.e2e_encryption.as_deref())?,
3681        })?;
3682        if !existed {
3683            self.added_types.push(id);
3684        }
3685        Ok(())
3686    }
3687
3688    fn rollback(&mut self) {
3689        self.reg
3690            .types
3691            .retain(|(id, _)| !self.added_types.contains(id));
3692        self.reg
3693            .endpoints
3694            .retain(|(id, _)| !self.added_endpoints.contains(id));
3695        self.reg.next_endpoint_id = self.initial_next_endpoint_id;
3696        self.reg.next_type_id = self.initial_next_type_id;
3697    }
3698}
3699
3700#[cfg(feature = "std")]
3701struct StreamingSchemaSeed<'a, 'r>(&'a mut StreamingSchemaState<'r>);
3702
3703#[cfg(feature = "std")]
3704impl<'de> serde::de::DeserializeSeed<'de> for StreamingSchemaSeed<'_, '_> {
3705    type Value = ();
3706
3707    fn deserialize<D>(self, deserializer: D) -> Result<(), D::Error>
3708    where
3709        D: serde::Deserializer<'de>,
3710    {
3711        deserializer.deserialize_map(StreamingSchemaVisitor(self.0))
3712    }
3713}
3714
3715#[cfg(feature = "std")]
3716struct StreamingSchemaVisitor<'a, 'r>(&'a mut StreamingSchemaState<'r>);
3717
3718#[cfg(feature = "std")]
3719impl<'de> serde::de::Visitor<'de> for StreamingSchemaVisitor<'_, '_> {
3720    type Value = ();
3721
3722    fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3723        formatter.write_str("a schema object containing endpoints followed by types")
3724    }
3725
3726    fn visit_map<A>(self, mut map: A) -> Result<(), A::Error>
3727    where
3728        A: serde::de::MapAccess<'de>,
3729    {
3730        while let Some(key) = map.next_key::<String>()? {
3731            match key.as_str() {
3732                "endpoints" => {
3733                    self.0.saw_endpoints = true;
3734                    map.next_value_seed(JsonEndpointSequence(self.0))?;
3735                }
3736                "types" => {
3737                    if !self.0.saw_endpoints {
3738                        self.0.error = Some(TelemetryError::BadArg);
3739                        return Err(<A::Error as serde::de::Error>::custom(
3740                            "endpoints must precede types for bounded loading",
3741                        ));
3742                    }
3743                    self.0.saw_types = true;
3744                    map.next_value_seed(JsonTypeSequence(self.0))?;
3745                }
3746                _ => {
3747                    map.next_value::<serde::de::IgnoredAny>()?;
3748                }
3749            }
3750        }
3751        Ok(())
3752    }
3753}
3754
3755#[cfg(feature = "std")]
3756struct JsonEndpointSequence<'a, 'r>(&'a mut StreamingSchemaState<'r>);
3757
3758#[cfg(feature = "std")]
3759impl<'de> serde::de::DeserializeSeed<'de> for JsonEndpointSequence<'_, '_> {
3760    type Value = ();
3761
3762    fn deserialize<D>(self, deserializer: D) -> Result<(), D::Error>
3763    where
3764        D: serde::Deserializer<'de>,
3765    {
3766        struct Visitor<'a, 'r>(&'a mut StreamingSchemaState<'r>);
3767        impl<'de> serde::de::Visitor<'de> for Visitor<'_, '_> {
3768            type Value = ();
3769            fn expecting(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3770                f.write_str("an endpoint array")
3771            }
3772            fn visit_seq<A>(self, mut seq: A) -> Result<(), A::Error>
3773            where
3774                A: serde::de::SeqAccess<'de>,
3775            {
3776                while let Some(endpoint) = seq.next_element::<JsonEndpoint>()? {
3777                    if let Err(err) = self.0.add_endpoint(endpoint) {
3778                        self.0.error = Some(err);
3779                        return Err(<A::Error as serde::de::Error>::custom("schema endpoint"));
3780                    }
3781                }
3782                Ok(())
3783            }
3784        }
3785        deserializer.deserialize_seq(Visitor(self.0))
3786    }
3787}
3788
3789#[cfg(feature = "std")]
3790struct JsonTypeSequence<'a, 'r>(&'a mut StreamingSchemaState<'r>);
3791
3792#[cfg(feature = "std")]
3793impl<'de> serde::de::DeserializeSeed<'de> for JsonTypeSequence<'_, '_> {
3794    type Value = ();
3795
3796    fn deserialize<D>(self, deserializer: D) -> Result<(), D::Error>
3797    where
3798        D: serde::Deserializer<'de>,
3799    {
3800        struct Visitor<'a, 'r>(&'a mut StreamingSchemaState<'r>);
3801        impl<'de> serde::de::Visitor<'de> for Visitor<'_, '_> {
3802            type Value = ();
3803            fn expecting(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3804                f.write_str("a data type array")
3805            }
3806            fn visit_seq<A>(self, mut seq: A) -> Result<(), A::Error>
3807            where
3808                A: serde::de::SeqAccess<'de>,
3809            {
3810                while let Some(ty) = seq.next_element::<JsonType>()? {
3811                    if let Err(err) = self.0.add_type(ty) {
3812                        self.0.error = Some(err);
3813                        return Err(<A::Error as serde::de::Error>::custom("schema type"));
3814                    }
3815                }
3816                Ok(())
3817            }
3818        }
3819        deserializer.deserialize_seq(Visitor(self.0))
3820    }
3821}
3822
3823#[cfg(feature = "std")]
3824fn register_schema_json_reader_into<R: std::io::Read>(
3825    reg: &mut Registry,
3826    reader: R,
3827    link_local_overlay: bool,
3828    max_schema_bytes: usize,
3829) -> TelemetryResult<()> {
3830    let initial_next_endpoint_id = reg.next_endpoint_id;
3831    let initial_next_type_id = reg.next_type_id;
3832    let mut state = StreamingSchemaState {
3833        reg,
3834        aliases: Vec::new(),
3835        alias_bytes: 0,
3836        added_endpoints: Vec::new(),
3837        added_types: Vec::new(),
3838        initial_next_endpoint_id,
3839        initial_next_type_id,
3840        link_local_overlay,
3841        max_schema_bytes,
3842        saw_endpoints: false,
3843        saw_types: false,
3844        error: None,
3845    };
3846    let mut deserializer = serde_json::Deserializer::from_reader(reader);
3847    let parsed =
3848        serde::de::DeserializeSeed::deserialize(StreamingSchemaSeed(&mut state), &mut deserializer)
3849            .and_then(|()| deserializer.end());
3850    if parsed.is_err() || !state.saw_endpoints || !state.saw_types {
3851        let err = state
3852            .error
3853            .take()
3854            .unwrap_or(TelemetryError::Unpack("schema json"));
3855        state.rollback();
3856        return Err(err);
3857    }
3858    Ok(())
3859}
3860
3861#[cfg(feature = "std")]
3862fn register_schema_json_file_into(
3863    reg: &mut Registry,
3864    path: &std::path::Path,
3865    link_local_overlay: bool,
3866    max_schema_bytes: usize,
3867    chunk_bytes: usize,
3868) -> TelemetryResult<()> {
3869    let file = std::fs::File::open(path).map_err(|_| TelemetryError::Io("schema json file"))?;
3870    let max_input_bytes = schema_json_max_input_bytes(max_schema_bytes);
3871    if file
3872        .metadata()
3873        .map_err(|_| TelemetryError::Io("schema json metadata"))?
3874        .len()
3875        > max_input_bytes as u64
3876    {
3877        return Err(TelemetryError::PacketTooLarge(
3878            "Schema JSON exceeds bounded input size",
3879        ));
3880    }
3881    let bounded = std::io::Read::take(file, max_input_bytes.saturating_add(1) as u64);
3882    let reader = std::io::BufReader::with_capacity(chunk_bytes.max(1), bounded);
3883    register_schema_json_reader_into(reg, reader, link_local_overlay, max_schema_bytes)
3884}
3885
3886#[cfg(feature = "serde")]
3887fn json_config_to_snapshot(
3888    cfg: JsonConfig,
3889    link_local_overlay: bool,
3890    mut next_endpoint_id: u32,
3891    mut next_type_id: u32,
3892) -> TelemetryResult<OwnedRuntimeSchemaSnapshot> {
3893    let mut endpoint_ids: Vec<(String, DataEndpoint)> = Vec::new();
3894    let mut endpoints = Vec::with_capacity(cfg.endpoints.len());
3895    for ep in cfg.endpoints {
3896        let rust_name = ep.rust.clone().unwrap_or_else(|| ep.name.clone());
3897        let link_local = link_local_overlay
3898            || ep.link_local_only.unwrap_or(false)
3899            || matches!(ep.broadcast_mode.as_deref(), Some("Never"));
3900        let id = DataEndpoint(schema_identity::endpoint_id(&ep.name));
3901        next_endpoint_id = next_endpoint_id.max(id.0.saturating_add(1));
3902        endpoints.push(OwnedEndpointDefinition {
3903            id,
3904            name: ep.name,
3905            description: ep.description.unwrap_or_default(),
3906            link_local_only: link_local,
3907        });
3908        endpoint_ids.push((rust_name, id));
3909    }
3910
3911    let mut types = Vec::with_capacity(cfg.types.len());
3912    for ty in cfg.types {
3913        let endpoints_for_type: Vec<DataEndpoint> = ty
3914            .endpoints
3915            .iter()
3916            .map(|name| {
3917                endpoint_ids
3918                    .iter()
3919                    .find(|(ep_name, _)| ep_name == name)
3920                    .map(|(_, id)| *id)
3921                    .unwrap_or_else(|| DataEndpoint::identity(name))
3922            })
3923            .collect::<Vec<_>>();
3924        let id = DataType(schema_identity::type_id(&ty.name));
3925        next_type_id = next_type_id.max(id.0.saturating_add(1));
3926        let class = parse_message_class(&ty.class)?;
3927        let element = match ty.element {
3928            JsonElement::Static { data_type, count } => MessageElement::Static(
3929                count.unwrap_or(1),
3930                parse_message_data_type(&data_type)?,
3931                class,
3932            ),
3933            JsonElement::Dynamic { data_type } => {
3934                MessageElement::Dynamic(parse_message_data_type(&data_type)?, class)
3935            }
3936        };
3937        let reliable = ty.reliable;
3938        types.push(OwnedDataTypeDefinition {
3939            id,
3940            name: ty.name,
3941            description: ty.description.unwrap_or_default(),
3942            element,
3943            endpoints: endpoints_for_type,
3944            reliable,
3945            priority: ty.priority.unwrap_or(0),
3946            e2e_encryption: parse_e2e_encryption_policy(ty.e2e_encryption.as_deref())?,
3947        });
3948    }
3949    Ok(OwnedRuntimeSchemaSnapshot { endpoints, types })
3950}
3951
3952#[cfg(feature = "serde")]
3953pub fn schema_snapshot_from_json_bytes(json: &[u8]) -> TelemetryResult<OwnedRuntimeSchemaSnapshot> {
3954    let cfg: JsonConfig =
3955        serde_json::from_slice(json).map_err(|_| TelemetryError::Unpack("schema json"))?;
3956    json_config_to_snapshot(cfg, false, 100, 100)
3957}
3958
3959#[cfg(feature = "std")]
3960#[allow(dead_code)]
3961fn register_owned_schema_snapshot_into(
3962    reg: &mut Registry,
3963    snapshot: OwnedRuntimeSchemaSnapshot,
3964) -> TelemetryResult<()> {
3965    for endpoint in snapshot.endpoints {
3966        reg.register_owned_endpoint(endpoint)?;
3967    }
3968    for ty in snapshot.types {
3969        reg.register_owned_type(ty)?;
3970    }
3971    Ok(())
3972}
3973
3974#[cfg(feature = "serde")]
3975fn parse_message_class(s: &str) -> TelemetryResult<MessageClass> {
3976    match s {
3977        "Data" => Ok(MessageClass::Data),
3978        "Error" => Ok(MessageClass::Error),
3979        "Warning" => Ok(MessageClass::Warning),
3980        _ => Err(TelemetryError::BadArg),
3981    }
3982}
3983
3984#[cfg(feature = "serde")]
3985fn parse_message_data_type(s: &str) -> TelemetryResult<MessageDataType> {
3986    match s {
3987        "Float64" => Ok(MessageDataType::Float64),
3988        "Float32" => Ok(MessageDataType::Float32),
3989        "UInt8" => Ok(MessageDataType::UInt8),
3990        "UInt16" => Ok(MessageDataType::UInt16),
3991        "UInt32" => Ok(MessageDataType::UInt32),
3992        "UInt64" => Ok(MessageDataType::UInt64),
3993        "UInt128" => Ok(MessageDataType::UInt128),
3994        "Int8" => Ok(MessageDataType::Int8),
3995        "Int16" => Ok(MessageDataType::Int16),
3996        "Int32" => Ok(MessageDataType::Int32),
3997        "Int64" => Ok(MessageDataType::Int64),
3998        "Int128" => Ok(MessageDataType::Int128),
3999        "Bool" => Ok(MessageDataType::Bool),
4000        "String" => Ok(MessageDataType::String),
4001        "Binary" => Ok(MessageDataType::Binary),
4002        "NoData" => Ok(MessageDataType::NoData),
4003        _ => Err(TelemetryError::BadArg),
4004    }
4005}
4006
4007#[cfg(all(test, feature = "std"))]
4008pub(crate) fn seed_test_schema() {
4009    static SEEDED: OnceLock<()> = OnceLock::new();
4010    SEEDED.get_or_init(|| {
4011        let _ = register_schema_json_str(include_str!("../telemetry_config.test.json"));
4012        let ipc = include_str!("../telemetry_config.ipc.test.json");
4013        let mut reg = registry().lock().expect("schema registry poisoned");
4014        let _ = register_schema_json_reader_into(&mut reg, ipc.as_bytes(), true, MAX_QUEUE_BUDGET);
4015    });
4016}
4017
4018#[cfg(all(test, feature = "std"))]
4019mod memory_regression_tests {
4020    use super::*;
4021
4022    struct OneByteReader<'a> {
4023        remaining: &'a [u8],
4024    }
4025
4026    impl std::io::Read for OneByteReader<'_> {
4027        fn read(&mut self, out: &mut [u8]) -> std::io::Result<usize> {
4028            if out.is_empty() || self.remaining.is_empty() {
4029                return Ok(0);
4030            }
4031            out[0] = self.remaining[0];
4032            self.remaining = &self.remaining[1..];
4033            Ok(1)
4034        }
4035    }
4036
4037    #[test]
4038    fn runtime_schema_storage_is_owned_and_reclaimable() {
4039        let mut reg = Registry::new();
4040        let baseline_cost = reg.schema_byte_cost();
4041        let baseline_count = reg.endpoints.len();
4042
4043        for index in 0..256u32 {
4044            let id = DataEndpoint(50_000 + index);
4045            reg.register_owned_endpoint(OwnedEndpointDefinition {
4046                id,
4047                name: format!("MEMORY_RECLAIM_ENDPOINT_{index}"),
4048                description: "x".repeat(1024),
4049                link_local_only: false,
4050            })
4051            .unwrap();
4052            let name_handle = reg.endpoints.last().unwrap().1.name.clone();
4053            assert_eq!(Arc::strong_count(&name_handle), 2);
4054            drop(name_handle);
4055            reg.endpoints.retain(|(known, _)| *known != id);
4056        }
4057
4058        assert_eq!(reg.endpoints.len(), baseline_count);
4059        assert_eq!(reg.schema_byte_cost(), baseline_cost);
4060    }
4061
4062    #[test]
4063    fn streaming_schema_budget_failure_rolls_back_every_entry() {
4064        let mut reg = Registry::new();
4065        let baseline_cost = reg.schema_byte_cost();
4066        let baseline_endpoints = reg.endpoints.len();
4067        let baseline_types = reg.types.len();
4068        let next_endpoint_id = reg.next_endpoint_id;
4069        let next_type_id = reg.next_type_id;
4070        let json = br#"{
4071            "endpoints": [{
4072                "rust": "MemoryBudgetEndpoint",
4073                "name": "MEMORY_BUDGET_ENDPOINT",
4074                "description": "this entry must be rolled back"
4075            }],
4076            "types": []
4077        }"#;
4078
4079        let err = register_schema_json_reader_into(&mut reg, &json[..], false, baseline_cost)
4080            .expect_err("schema must exceed its unchanged baseline budget");
4081        assert!(matches!(err, TelemetryError::PacketTooLarge(_)));
4082        assert_eq!(reg.schema_byte_cost(), baseline_cost);
4083        assert_eq!(reg.endpoints.len(), baseline_endpoints);
4084        assert_eq!(reg.types.len(), baseline_types);
4085        assert_eq!(reg.next_endpoint_id, next_endpoint_id);
4086        assert_eq!(reg.next_type_id, next_type_id);
4087    }
4088
4089    #[test]
4090    fn streaming_schema_parse_failure_rolls_back_prior_chunks() {
4091        let mut reg = Registry::new();
4092        let baseline_cost = reg.schema_byte_cost();
4093        let baseline_endpoints = reg.endpoints.len();
4094        let next_endpoint_id = reg.next_endpoint_id;
4095        let json = br#"{
4096            "endpoints": [{
4097                "rust": "MemoryRollbackEndpoint",
4098                "name": "MEMORY_ROLLBACK_ENDPOINT"
4099            }],
4100            "types": [{
4101                "rust": "MemoryRollbackType",
4102                "name": "MEMORY_ROLLBACK_TYPE",
4103                "class": "NotAClass",
4104                "element": {"kind": "Dynamic", "data_type": "UInt8"},
4105                "endpoints": ["MemoryRollbackEndpoint"],
4106                "reliable": "Off"
4107            }]
4108        }"#;
4109
4110        assert!(register_schema_json_reader_into(
4111            &mut reg,
4112            &json[..],
4113            false,
4114            baseline_cost + 16 * 1024,
4115        )
4116        .is_err());
4117        assert_eq!(reg.schema_byte_cost(), baseline_cost);
4118        assert_eq!(reg.endpoints.len(), baseline_endpoints);
4119        assert_eq!(reg.next_endpoint_id, next_endpoint_id);
4120    }
4121
4122    #[test]
4123    fn streaming_schema_loads_through_single_byte_reads() {
4124        let mut reg = Registry::new();
4125        let baseline_cost = reg.schema_byte_cost();
4126        let json = br#"{
4127            "endpoints": [{
4128                "rust": "SingleByteReaderEndpoint",
4129                "name": "SINGLE_BYTE_READER_ENDPOINT",
4130                "description": "streamed one byte at a time"
4131            }],
4132            "types": [{
4133                "rust": "SingleByteReaderType",
4134                "name": "SINGLE_BYTE_READER_TYPE",
4135                "class": "Data",
4136                "element": {"kind": "Dynamic", "data_type": "Binary"},
4137                "endpoints": ["SingleByteReaderEndpoint"],
4138                "reliable": "Off"
4139            }]
4140        }"#;
4141
4142        register_schema_json_reader_into(
4143            &mut reg,
4144            OneByteReader { remaining: json },
4145            false,
4146            baseline_cost + 16 * 1024,
4147        )
4148        .unwrap();
4149
4150        assert!(
4151            reg.endpoints
4152                .iter()
4153                .any(|(_, meta)| meta.name.as_ref() == "SINGLE_BYTE_READER_ENDPOINT")
4154        );
4155        assert!(
4156            reg.types
4157                .iter()
4158                .any(|(_, meta)| meta.name.as_ref() == "SINGLE_BYTE_READER_TYPE")
4159        );
4160        assert!(reg.schema_byte_cost() > baseline_cost);
4161    }
4162}