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