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