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};
12use alloc::{
13    boxed::Box,
14    string::{String, ToString},
15    vec,
16    vec::Vec,
17};
18use core::mem::size_of;
19use core::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
20
21#[cfg(feature = "std")]
22use std::sync::OnceLock;
23#[cfg(feature = "std")]
24use std::sync::RwLock;
25
26// -----------------------------------------------------------------------------
27// Device-/build-time constants
28// -----------------------------------------------------------------------------
29
30pub const DEVICE_IDENTIFIER: &str = match option_env!("DEVICE_IDENTIFIER") {
31    Some(val) => parse_strings(val),
32    None => "TEST_PLATFORM",
33};
34
35#[cfg(feature = "std")]
36static RUNTIME_DEVICE_IDENTIFIER: OnceLock<RwLock<String>> = OnceLock::new();
37
38pub fn runtime_device_identifier() -> String {
39    #[cfg(feature = "std")]
40    {
41        RUNTIME_DEVICE_IDENTIFIER
42            .get_or_init(|| RwLock::new(DEVICE_IDENTIFIER.to_string()))
43            .read()
44            .map(|value| value.clone())
45            .unwrap_or_else(|_| DEVICE_IDENTIFIER.to_string())
46    }
47    #[cfg(not(feature = "std"))]
48    {
49        DEVICE_IDENTIFIER.to_string()
50    }
51}
52
53pub fn set_runtime_device_identifier(value: &str) -> TelemetryResult<()> {
54    if value.is_empty() {
55        return Err(TelemetryError::BadArg);
56    }
57    #[cfg(feature = "std")]
58    {
59        let lock =
60            RUNTIME_DEVICE_IDENTIFIER.get_or_init(|| RwLock::new(DEVICE_IDENTIFIER.to_string()));
61        let mut guard = lock
62            .write()
63            .map_err(|_| TelemetryError::Io("device id lock"))?;
64        *guard = value.to_string();
65        Ok(())
66    }
67    #[cfg(not(feature = "std"))]
68    {
69        let _ = value;
70        Err(TelemetryError::BadArg)
71    }
72}
73
74pub const MAX_RECENT_RX_IDS: usize = match option_env!("MAX_RECENT_RX_IDS") {
75    Some(val) => parse_usize(val),
76    None => 128,
77};
78
79pub const STARTING_QUEUE_SIZE: usize = match option_env!("STARTING_QUEUE_SIZE") {
80    Some(val) => parse_usize(val),
81    None => 128,
82};
83
84pub const MAX_QUEUE_BUDGET: usize = match option_env!("MAX_QUEUE_BUDGET") {
85    Some(val) => parse_usize(val),
86    None => match option_env!("MAX_QUEUE_SIZE") {
87        Some(val) => parse_usize(val),
88        None => 1024 * 100,
89    },
90};
91
92pub const RECENT_RX_QUEUE_BYTES: usize = {
93    let requested = MAX_RECENT_RX_IDS.saturating_mul(size_of::<u64>());
94    if requested < MAX_QUEUE_BUDGET {
95        requested
96    } else {
97        MAX_QUEUE_BUDGET
98    }
99};
100
101pub const QUEUE_GROW_STEP: f64 = match option_env!("QUEUE_GROW_STEP") {
102    Some(val) => parse_f64(val),
103    None => 3.2,
104};
105
106pub const PAYLOAD_COMPRESS_THRESHOLD: usize = match option_env!("PAYLOAD_COMPRESS_THRESHOLD") {
107    Some(val) => parse_usize(val),
108    None => 128,
109};
110
111pub const STATIC_STRING_LENGTH: usize = match option_env!("STATIC_STRING_LENGTH") {
112    Some(val) => parse_usize(val),
113    None => 1024,
114};
115
116pub const STATIC_HEX_LENGTH: usize = match option_env!("STATIC_HEX_LENGTH") {
117    Some(val) => parse_usize(val),
118    None => 1024,
119};
120
121pub const STRING_PRECISION: usize = match option_env!("STRING_PRECISION") {
122    Some(val) => parse_usize(val),
123    None => 8,
124};
125
126sedsnet_macros::define_stack_payload!(env = "MAX_STACK_PAYLOAD", default = 64);
127
128pub const MAX_HANDLER_RETRIES: usize = match option_env!("MAX_HANDLER_RETRIES") {
129    Some(val) => parse_usize(val),
130    None => 3,
131};
132
133pub const RELIABLE_RETRANSMIT_MS: u64 = match option_env!("RELIABLE_RETRANSMIT_MS") {
134    Some(val) => parse_usize(val) as u64,
135    None => 200,
136};
137
138pub const RELIABLE_MAX_RETRIES: u32 = match option_env!("RELIABLE_MAX_RETRIES") {
139    Some(val) => parse_usize(val) as u32,
140    None => 8,
141};
142
143pub const RELIABLE_MAX_PENDING: usize = match option_env!("RELIABLE_MAX_PENDING") {
144    Some(val) => parse_usize(val),
145    None => 32,
146};
147
148pub const RELIABLE_MAX_RETURN_ROUTES: usize = match option_env!("RELIABLE_MAX_RETURN_ROUTES") {
149    Some(val) => parse_usize(val),
150    None => MAX_RECENT_RX_IDS,
151};
152
153pub const RELIABLE_MAX_END_TO_END_PENDING: usize =
154    match option_env!("RELIABLE_MAX_END_TO_END_PENDING") {
155        Some(val) => parse_usize(val),
156        None => RELIABLE_MAX_PENDING,
157    };
158
159pub const RELIABLE_MAX_END_TO_END_ACK_CACHE: usize =
160    match option_env!("RELIABLE_MAX_END_TO_END_ACK_CACHE") {
161        Some(val) => parse_usize(val),
162        None => MAX_RECENT_RX_IDS,
163    };
164
165static RUNTIME_PAYLOAD_COMPRESS_THRESHOLD: AtomicUsize =
166    AtomicUsize::new(PAYLOAD_COMPRESS_THRESHOLD);
167static RUNTIME_STATIC_STRING_LENGTH: AtomicUsize = AtomicUsize::new(STATIC_STRING_LENGTH);
168static RUNTIME_STATIC_HEX_LENGTH: AtomicUsize = AtomicUsize::new(STATIC_HEX_LENGTH);
169static RUNTIME_STRING_PRECISION: AtomicUsize = AtomicUsize::new(STRING_PRECISION);
170static RUNTIME_MAX_HANDLER_RETRIES: AtomicUsize = AtomicUsize::new(MAX_HANDLER_RETRIES);
171static RUNTIME_RELIABLE_RETRANSMIT_MS: AtomicU32 = AtomicU32::new(RELIABLE_RETRANSMIT_MS as u32);
172static RUNTIME_RELIABLE_MAX_RETRIES: AtomicU32 = AtomicU32::new(RELIABLE_MAX_RETRIES);
173static RUNTIME_RELIABLE_MAX_PENDING: AtomicUsize = AtomicUsize::new(RELIABLE_MAX_PENDING);
174static RUNTIME_RELIABLE_MAX_RETURN_ROUTES: AtomicUsize =
175    AtomicUsize::new(RELIABLE_MAX_RETURN_ROUTES);
176static RUNTIME_RELIABLE_MAX_END_TO_END_PENDING: AtomicUsize =
177    AtomicUsize::new(RELIABLE_MAX_END_TO_END_PENDING);
178static RUNTIME_RELIABLE_MAX_END_TO_END_ACK_CACHE: AtomicUsize =
179    AtomicUsize::new(RELIABLE_MAX_END_TO_END_ACK_CACHE);
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub struct RuntimeTuningConfig {
183    pub payload_compress_threshold: usize,
184    pub static_string_length: usize,
185    pub static_hex_length: usize,
186    pub string_precision: usize,
187    pub max_handler_retries: usize,
188    pub reliable_retransmit_ms: u32,
189    pub reliable_max_retries: u32,
190    pub reliable_max_pending: usize,
191    pub reliable_max_return_routes: usize,
192    pub reliable_max_end_to_end_pending: usize,
193    pub reliable_max_end_to_end_ack_cache: usize,
194}
195
196impl Default for RuntimeTuningConfig {
197    fn default() -> Self {
198        Self {
199            payload_compress_threshold: PAYLOAD_COMPRESS_THRESHOLD,
200            static_string_length: STATIC_STRING_LENGTH,
201            static_hex_length: STATIC_HEX_LENGTH,
202            string_precision: STRING_PRECISION,
203            max_handler_retries: MAX_HANDLER_RETRIES,
204            reliable_retransmit_ms: RELIABLE_RETRANSMIT_MS as u32,
205            reliable_max_retries: RELIABLE_MAX_RETRIES,
206            reliable_max_pending: RELIABLE_MAX_PENDING,
207            reliable_max_return_routes: RELIABLE_MAX_RETURN_ROUTES,
208            reliable_max_end_to_end_pending: RELIABLE_MAX_END_TO_END_PENDING,
209            reliable_max_end_to_end_ack_cache: RELIABLE_MAX_END_TO_END_ACK_CACHE,
210        }
211    }
212}
213
214impl RuntimeTuningConfig {
215    pub fn validate(self) -> TelemetryResult<()> {
216        if self.static_string_length == 0
217            || self.static_hex_length == 0
218            || self.max_handler_retries == 0
219            || self.reliable_retransmit_ms == 0
220            || self.reliable_max_retries == 0
221            || self.reliable_max_pending == 0
222            || self.reliable_max_return_routes == 0
223            || self.reliable_max_end_to_end_pending == 0
224            || self.reliable_max_end_to_end_ack_cache == 0
225        {
226            return Err(TelemetryError::BadArg);
227        }
228        Ok(())
229    }
230}
231
232pub fn set_runtime_tuning_config(cfg: RuntimeTuningConfig) -> TelemetryResult<()> {
233    cfg.validate()?;
234    RUNTIME_PAYLOAD_COMPRESS_THRESHOLD.store(cfg.payload_compress_threshold, Ordering::Relaxed);
235    RUNTIME_STATIC_STRING_LENGTH.store(cfg.static_string_length, Ordering::Relaxed);
236    RUNTIME_STATIC_HEX_LENGTH.store(cfg.static_hex_length, Ordering::Relaxed);
237    RUNTIME_STRING_PRECISION.store(cfg.string_precision, Ordering::Relaxed);
238    RUNTIME_MAX_HANDLER_RETRIES.store(cfg.max_handler_retries, Ordering::Relaxed);
239    RUNTIME_RELIABLE_RETRANSMIT_MS.store(cfg.reliable_retransmit_ms, Ordering::Relaxed);
240    RUNTIME_RELIABLE_MAX_RETRIES.store(cfg.reliable_max_retries, Ordering::Relaxed);
241    RUNTIME_RELIABLE_MAX_PENDING.store(cfg.reliable_max_pending, Ordering::Relaxed);
242    RUNTIME_RELIABLE_MAX_RETURN_ROUTES.store(cfg.reliable_max_return_routes, Ordering::Relaxed);
243    RUNTIME_RELIABLE_MAX_END_TO_END_PENDING
244        .store(cfg.reliable_max_end_to_end_pending, Ordering::Relaxed);
245    RUNTIME_RELIABLE_MAX_END_TO_END_ACK_CACHE
246        .store(cfg.reliable_max_end_to_end_ack_cache, Ordering::Relaxed);
247    Ok(())
248}
249
250pub fn runtime_tuning_config() -> RuntimeTuningConfig {
251    RuntimeTuningConfig {
252        payload_compress_threshold: runtime_payload_compress_threshold(),
253        static_string_length: runtime_static_string_length(),
254        static_hex_length: runtime_static_hex_length(),
255        string_precision: runtime_string_precision(),
256        max_handler_retries: runtime_max_handler_retries(),
257        reliable_retransmit_ms: runtime_reliable_retransmit_ms() as u32,
258        reliable_max_retries: runtime_reliable_max_retries(),
259        reliable_max_pending: runtime_reliable_max_pending(),
260        reliable_max_return_routes: runtime_reliable_max_return_routes(),
261        reliable_max_end_to_end_pending: runtime_reliable_max_end_to_end_pending(),
262        reliable_max_end_to_end_ack_cache: runtime_reliable_max_end_to_end_ack_cache(),
263    }
264}
265
266#[inline]
267pub fn runtime_payload_compress_threshold() -> usize {
268    RUNTIME_PAYLOAD_COMPRESS_THRESHOLD.load(Ordering::Relaxed)
269}
270
271#[inline]
272pub fn runtime_static_string_length() -> usize {
273    RUNTIME_STATIC_STRING_LENGTH.load(Ordering::Relaxed)
274}
275
276#[inline]
277pub fn runtime_static_hex_length() -> usize {
278    RUNTIME_STATIC_HEX_LENGTH.load(Ordering::Relaxed)
279}
280
281#[inline]
282pub fn runtime_string_precision() -> usize {
283    RUNTIME_STRING_PRECISION.load(Ordering::Relaxed)
284}
285
286#[inline]
287pub fn runtime_max_handler_retries() -> usize {
288    RUNTIME_MAX_HANDLER_RETRIES.load(Ordering::Relaxed)
289}
290
291#[inline]
292pub fn runtime_reliable_retransmit_ms() -> u64 {
293    u64::from(RUNTIME_RELIABLE_RETRANSMIT_MS.load(Ordering::Relaxed))
294}
295
296#[inline]
297pub fn runtime_reliable_max_retries() -> u32 {
298    RUNTIME_RELIABLE_MAX_RETRIES.load(Ordering::Relaxed)
299}
300
301#[inline]
302pub fn runtime_reliable_max_pending() -> usize {
303    RUNTIME_RELIABLE_MAX_PENDING.load(Ordering::Relaxed)
304}
305
306#[inline]
307pub fn runtime_reliable_max_return_routes() -> usize {
308    RUNTIME_RELIABLE_MAX_RETURN_ROUTES.load(Ordering::Relaxed)
309}
310
311#[inline]
312pub fn runtime_reliable_max_end_to_end_pending() -> usize {
313    RUNTIME_RELIABLE_MAX_END_TO_END_PENDING.load(Ordering::Relaxed)
314}
315
316#[inline]
317pub fn runtime_reliable_max_end_to_end_ack_cache() -> usize {
318    RUNTIME_RELIABLE_MAX_END_TO_END_ACK_CACHE.load(Ordering::Relaxed)
319}
320
321/// Runtime memory limits for router/relay queue-backed state.
322///
323/// Compile-time environment values remain the defaults for embedded builds, but applications using
324/// prebuilt binaries can now choose per-instance budgets at construction time.
325#[derive(Debug, Clone, Copy, PartialEq)]
326pub struct RuntimeMemoryConfig {
327    pub max_queue_budget: usize,
328    pub max_recent_rx_ids: usize,
329    pub starting_queue_size: usize,
330    pub queue_grow_step: f64,
331}
332
333impl RuntimeMemoryConfig {
334    pub const fn default_const() -> Self {
335        Self {
336            max_queue_budget: MAX_QUEUE_BUDGET,
337            max_recent_rx_ids: MAX_RECENT_RX_IDS,
338            starting_queue_size: STARTING_QUEUE_SIZE,
339            queue_grow_step: QUEUE_GROW_STEP,
340        }
341    }
342
343    pub fn new(
344        max_queue_budget: usize,
345        max_recent_rx_ids: usize,
346        starting_queue_size: usize,
347        queue_grow_step: f64,
348    ) -> TelemetryResult<Self> {
349        let cfg = Self {
350            max_queue_budget,
351            max_recent_rx_ids,
352            starting_queue_size,
353            queue_grow_step,
354        };
355        cfg.validate()?;
356        Ok(cfg)
357    }
358
359    pub fn validate(self) -> TelemetryResult<()> {
360        if self.max_queue_budget == 0 {
361            return Err(TelemetryError::BadArg);
362        }
363        if self.max_recent_rx_ids == 0 {
364            return Err(TelemetryError::BadArg);
365        }
366        if self.starting_queue_size == 0 || self.starting_queue_size > self.max_queue_budget {
367            return Err(TelemetryError::BadArg);
368        }
369        if !self.queue_grow_step.is_finite() || self.queue_grow_step <= 1.0 {
370            return Err(TelemetryError::BadArg);
371        }
372        Ok(())
373    }
374
375    pub fn recent_rx_queue_bytes(self) -> usize {
376        self.max_recent_rx_ids
377            .saturating_mul(size_of::<u64>())
378            .min(self.max_queue_budget)
379            .max(1)
380    }
381}
382
383impl Default for RuntimeMemoryConfig {
384    fn default() -> Self {
385        Self::default_const()
386    }
387}
388
389// -----------------------------------------------------------------------------
390// Runtime IDs
391// -----------------------------------------------------------------------------
392
393#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
394#[repr(transparent)]
395pub struct DataEndpoint(pub u32);
396
397impl DataEndpoint {
398    pub const TIME_SYNC: Self = Self(200);
399    pub const DISCOVERY: Self = Self(201);
400    pub const TELEMETRY_ERROR: Self = Self(202);
401
402    #[allow(non_upper_case_globals)]
403    pub const TelemetryError: Self = Self::TELEMETRY_ERROR;
404    #[allow(non_upper_case_globals)]
405    pub const TimeSync: Self = Self::TIME_SYNC;
406    #[allow(non_upper_case_globals)]
407    pub const Discovery: Self = Self::DISCOVERY;
408
409    #[inline]
410    pub const fn as_u32(self) -> u32 {
411        self.0
412    }
413
414    #[inline]
415    pub fn try_from_u32(x: u32) -> Option<Self> {
416        if endpoint_exists(Self(x)) {
417            Some(Self(x))
418        } else {
419            None
420        }
421    }
422
423    #[inline]
424    pub fn try_named(name: &str) -> Option<Self> {
425        endpoint_definition_by_name(name).map(|def| def.id)
426    }
427
428    #[inline]
429    pub fn named(name: &str) -> Self {
430        Self::try_named(name).unwrap_or_else(|| panic!("unknown data endpoint: {name}"))
431    }
432}
433
434impl core::fmt::Debug for DataEndpoint {
435    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
436        let name = match *self {
437            Self::TelemetryError => "SEDSNET_ERROR",
438            Self::TimeSync => "SEDSNET_TIME_SYNC",
439            Self::Discovery => "SEDSNET_DISCOVERY",
440            _ => {
441                let meta = get_endpoint_meta(*self);
442                if meta.name != "UNKNOWN_ENDPOINT" {
443                    return f.write_str(meta.name);
444                }
445                return write!(f, "DataEndpoint({})", self.0);
446            }
447        };
448        f.write_str(name)
449    }
450}
451
452#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
453#[repr(transparent)]
454pub struct DataType(pub u32);
455
456impl DataType {
457    pub const TELEMETRY_ERROR: Self = Self(0);
458    pub const RELIABLE_ACK: Self = Self(1);
459    pub const RELIABLE_PACKET_REQUEST: Self = Self(2);
460    pub const RELIABLE_PARTIAL_ACK: Self = Self(3);
461    pub const TIME_SYNC_ANNOUNCE: Self = Self(4);
462    pub const TIME_SYNC_REQUEST: Self = Self(5);
463    pub const TIME_SYNC_RESPONSE: Self = Self(6);
464    pub const DISCOVERY_ANNOUNCE: Self = Self(7);
465    pub const DISCOVERY_TIMESYNC_SOURCES: Self = Self(8);
466    pub const DISCOVERY_TOPOLOGY: Self = Self(9);
467    pub const DISCOVERY_SCHEMA: Self = Self(10);
468    pub const DISCOVERY_TOPOLOGY_REQUEST: Self = Self(11);
469    pub const DISCOVERY_SCHEMA_REQUEST: Self = Self(12);
470    pub const MANAGED_VARIABLE_REQUEST: Self = Self(13);
471    pub const MANAGED_VARIABLE_VALUE: Self = Self(14);
472    pub const DISCOVERY_LEAVE: Self = Self(15);
473    pub const DISCOVERY_LINK_CAPABILITIES: Self = Self(16);
474    pub const DISCOVERY_ADDRESS: Self = Self(17);
475    pub const P2P_MESSAGE: Self = Self(18);
476
477    #[allow(non_upper_case_globals)]
478    pub const TelemetryError: Self = Self::TELEMETRY_ERROR;
479    #[allow(non_upper_case_globals)]
480    pub const ReliableAck: Self = Self::RELIABLE_ACK;
481    #[allow(non_upper_case_globals)]
482    pub const ReliablePacketRequest: Self = Self::RELIABLE_PACKET_REQUEST;
483    #[allow(non_upper_case_globals)]
484    pub const ReliablePartialAck: Self = Self::RELIABLE_PARTIAL_ACK;
485    #[allow(non_upper_case_globals)]
486    pub const TimeSyncAnnounce: Self = Self::TIME_SYNC_ANNOUNCE;
487    #[allow(non_upper_case_globals)]
488    pub const TimeSyncRequest: Self = Self::TIME_SYNC_REQUEST;
489    #[allow(non_upper_case_globals)]
490    pub const TimeSyncResponse: Self = Self::TIME_SYNC_RESPONSE;
491    #[allow(non_upper_case_globals)]
492    pub const DiscoveryAnnounce: Self = Self::DISCOVERY_ANNOUNCE;
493    #[allow(non_upper_case_globals)]
494    pub const DiscoveryTimeSyncSources: Self = Self::DISCOVERY_TIMESYNC_SOURCES;
495    #[allow(non_upper_case_globals)]
496    pub const DiscoveryTopology: Self = Self::DISCOVERY_TOPOLOGY;
497    #[allow(non_upper_case_globals)]
498    pub const DiscoverySchema: Self = Self::DISCOVERY_SCHEMA;
499    #[allow(non_upper_case_globals)]
500    pub const DiscoveryTopologyRequest: Self = Self::DISCOVERY_TOPOLOGY_REQUEST;
501    #[allow(non_upper_case_globals)]
502    pub const DiscoverySchemaRequest: Self = Self::DISCOVERY_SCHEMA_REQUEST;
503    #[allow(non_upper_case_globals)]
504    pub const ManagedVariableRequest: Self = Self::MANAGED_VARIABLE_REQUEST;
505    #[allow(non_upper_case_globals)]
506    pub const ManagedVariableValue: Self = Self::MANAGED_VARIABLE_VALUE;
507    #[allow(non_upper_case_globals)]
508    pub const DiscoveryLeave: Self = Self::DISCOVERY_LEAVE;
509    #[allow(non_upper_case_globals)]
510    pub const DiscoveryLinkCapabilities: Self = Self::DISCOVERY_LINK_CAPABILITIES;
511    #[allow(non_upper_case_globals)]
512    pub const DiscoveryAddress: Self = Self::DISCOVERY_ADDRESS;
513    #[allow(non_upper_case_globals)]
514    pub const P2pMessage: Self = Self::P2P_MESSAGE;
515
516    #[inline]
517    pub const fn as_u32(self) -> u32 {
518        self.0
519    }
520
521    #[inline]
522    pub fn try_from_u32(x: u32) -> Option<Self> {
523        if data_type_exists(Self(x)) {
524            Some(Self(x))
525        } else {
526            None
527        }
528    }
529
530    #[inline]
531    pub fn try_named(name: &str) -> Option<Self> {
532        data_type_definition_by_name(name).map(|def| def.id)
533    }
534
535    #[inline]
536    pub fn named(name: &str) -> Self {
537        Self::try_named(name).unwrap_or_else(|| panic!("unknown data type: {name}"))
538    }
539}
540
541impl core::fmt::Debug for DataType {
542    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
543        let name = match *self {
544            Self::TelemetryError => "SEDSNET_ERROR",
545            Self::ReliableAck => "ReliableAck",
546            Self::ReliablePacketRequest => "ReliablePacketRequest",
547            Self::ReliablePartialAck => "ReliablePartialAck",
548            Self::TimeSyncAnnounce => "SedsnetTimeSyncAnnounce",
549            Self::TimeSyncRequest => "SedsnetTimeSyncRequest",
550            Self::TimeSyncResponse => "SedsnetTimeSyncResponse",
551            Self::DiscoveryAnnounce => "SedsnetDiscoveryAnnounce",
552            Self::DiscoveryTimeSyncSources => "SedsnetDiscoveryTimeSyncSources",
553            Self::DiscoveryTopology => "SedsnetDiscoveryTopology",
554            Self::DiscoverySchema => "SedsnetDiscoverySchema",
555            Self::DiscoveryTopologyRequest => "SedsnetDiscoveryTopologyRequest",
556            Self::DiscoverySchemaRequest => "SedsnetDiscoverySchemaRequest",
557            Self::ManagedVariableRequest => "SedsnetManagedVariableRequest",
558            Self::ManagedVariableValue => "SedsnetManagedVariableValue",
559            Self::DiscoveryLeave => "SedsnetDiscoveryLeave",
560            Self::DiscoveryLinkCapabilities => "SedsnetDiscoveryLinkCapabilities",
561            Self::DiscoveryAddress => "SedsnetDiscoveryAddress",
562            Self::P2pMessage => "SedsnetP2pMessage",
563            _ => {
564                let meta = get_message_meta(*self);
565                if meta.name != "UNKNOWN_TYPE" {
566                    return f.write_str(meta.name);
567                }
568                return write!(f, "DataType({})", self.0);
569            }
570        };
571        f.write_str(name)
572    }
573}
574
575// -----------------------------------------------------------------------------
576// Runtime registry
577// -----------------------------------------------------------------------------
578
579#[derive(Debug, Clone, Copy, PartialEq, Eq)]
580pub struct EndpointDefinition {
581    pub id: DataEndpoint,
582    pub name: &'static str,
583    pub description: &'static str,
584    pub link_local_only: bool,
585}
586
587#[derive(Debug, Clone, Copy, PartialEq, Eq)]
588pub struct DataTypeDefinition {
589    pub id: DataType,
590    pub name: &'static str,
591    pub description: &'static str,
592    pub element: MessageElement,
593    pub endpoints: &'static [DataEndpoint],
594    pub reliable: ReliableMode,
595    pub priority: u8,
596    pub e2e_encryption: E2eEncryptionPolicy,
597}
598
599#[derive(Debug, Clone)]
600pub struct RuntimeSchemaSnapshot {
601    pub endpoints: Vec<EndpointDefinition>,
602    pub types: Vec<DataTypeDefinition>,
603}
604
605#[derive(Debug, Clone)]
606pub struct OwnedEndpointDefinition {
607    pub id: DataEndpoint,
608    pub name: String,
609    pub description: String,
610    pub link_local_only: bool,
611}
612
613#[derive(Debug, Clone)]
614pub struct OwnedDataTypeDefinition {
615    pub id: DataType,
616    pub name: String,
617    pub description: String,
618    pub element: MessageElement,
619    pub endpoints: Vec<DataEndpoint>,
620    pub reliable: ReliableMode,
621    pub priority: u8,
622    pub e2e_encryption: E2eEncryptionPolicy,
623}
624
625#[derive(Debug, Clone)]
626pub struct OwnedRuntimeSchemaSnapshot {
627    pub endpoints: Vec<OwnedEndpointDefinition>,
628    pub types: Vec<OwnedDataTypeDefinition>,
629}
630
631#[derive(Debug, Clone, Copy, PartialEq, Eq)]
632pub enum SchemaMergeDecision {
633    Added,
634    Unchanged,
635    ReplacedLocal,
636    KeptLocal,
637}
638
639#[derive(Debug, Clone, Copy, PartialEq, Eq)]
640pub struct SchemaMergeReport {
641    pub endpoints_added: usize,
642    pub endpoints_replaced: usize,
643    pub endpoints_kept: usize,
644    pub types_added: usize,
645    pub types_replaced: usize,
646    pub types_kept: usize,
647}
648
649impl SchemaMergeReport {
650    #[inline]
651    pub const fn changed(&self) -> bool {
652        self.endpoints_added != 0
653            || self.endpoints_replaced != 0
654            || self.types_added != 0
655            || self.types_replaced != 0
656    }
657}
658
659#[cfg(feature = "std")]
660#[derive(Debug, Clone)]
661struct Registry {
662    endpoints: Vec<(DataEndpoint, EndpointMeta)>,
663    types: Vec<(DataType, MessageMeta)>,
664    next_endpoint_id: u32,
665    next_type_id: u32,
666}
667
668#[cfg(feature = "std")]
669impl Registry {
670    fn new() -> Self {
671        let mut reg = Self {
672            endpoints: Vec::new(),
673            types: Vec::new(),
674            next_endpoint_id: 100,
675            next_type_id: 100,
676        };
677        reg.register_endpoint_definition(EndpointDefinition {
678            id: DataEndpoint::TelemetryError,
679            name: "SEDSNET_ERROR",
680            description: "",
681            link_local_only: false,
682        })
683        .expect("built-in endpoint");
684        reg.register_endpoint_definition(EndpointDefinition {
685            id: DataEndpoint::TimeSync,
686            name: "SEDSNET_TIME_SYNC",
687            description: "",
688            link_local_only: false,
689        })
690        .expect("built-in endpoint");
691        reg.register_endpoint_definition(EndpointDefinition {
692            id: DataEndpoint::Discovery,
693            name: "SEDSNET_DISCOVERY",
694            description: "",
695            link_local_only: false,
696        })
697        .expect("built-in endpoint");
698
699        reg.register_type_definition(DataTypeDefinition {
700            id: DataType::TelemetryError,
701            name: "SEDSNET_ERROR",
702            description: "",
703            element: MessageElement::Dynamic(MessageDataType::String, MessageClass::Error),
704            endpoints: leak_endpoints(vec![DataEndpoint::TelemetryError]),
705            reliable: ReliableMode::None,
706            priority: 255,
707            e2e_encryption: E2eEncryptionPolicy::PreferOff,
708        })
709        .expect("built-in type");
710        reg.register_type_definition(DataTypeDefinition {
711            id: DataType::ReliableAck,
712            name: "SEDSNET_RELIABLE_ACK",
713            description: "",
714            element: MessageElement::Static(2, MessageDataType::UInt32, MessageClass::Data),
715            endpoints: leak_endpoints(vec![DataEndpoint::TelemetryError]),
716            reliable: ReliableMode::None,
717            priority: 250,
718            e2e_encryption: E2eEncryptionPolicy::PreferOff,
719        })
720        .expect("built-in type");
721        reg.register_type_definition(DataTypeDefinition {
722            id: DataType::ReliablePacketRequest,
723            name: "SEDSNET_RELIABLE_PACKET_REQUEST",
724            description: "",
725            element: MessageElement::Static(2, MessageDataType::UInt32, MessageClass::Data),
726            endpoints: leak_endpoints(vec![DataEndpoint::TelemetryError]),
727            reliable: ReliableMode::None,
728            priority: 250,
729            e2e_encryption: E2eEncryptionPolicy::PreferOff,
730        })
731        .expect("built-in type");
732        reg.register_type_definition(DataTypeDefinition {
733            id: DataType::ReliablePartialAck,
734            name: "SEDSNET_RELIABLE_PARTIAL_ACK",
735            description: "",
736            element: MessageElement::Static(2, MessageDataType::UInt32, MessageClass::Data),
737            endpoints: leak_endpoints(vec![DataEndpoint::TelemetryError]),
738            reliable: ReliableMode::None,
739            priority: 250,
740            e2e_encryption: E2eEncryptionPolicy::PreferOff,
741        })
742        .expect("built-in type");
743        reg.register_type_definition(DataTypeDefinition {
744            id: DataType::TimeSyncAnnounce,
745            name: "SEDSNET_TIME_SYNC_ANNOUNCE",
746            description: "",
747            element: MessageElement::Static(2, MessageDataType::UInt64, MessageClass::Data),
748            endpoints: leak_endpoints(vec![DataEndpoint::TimeSync]),
749            reliable: ReliableMode::None,
750            priority: 245,
751            e2e_encryption: E2eEncryptionPolicy::PreferOff,
752        })
753        .expect("built-in type");
754        reg.register_type_definition(DataTypeDefinition {
755            id: DataType::TimeSyncRequest,
756            name: "SEDSNET_TIME_SYNC_REQUEST",
757            description: "",
758            element: MessageElement::Static(2, MessageDataType::UInt64, MessageClass::Data),
759            endpoints: leak_endpoints(vec![DataEndpoint::TimeSync]),
760            reliable: ReliableMode::None,
761            priority: 245,
762            e2e_encryption: E2eEncryptionPolicy::PreferOff,
763        })
764        .expect("built-in type");
765        reg.register_type_definition(DataTypeDefinition {
766            id: DataType::TimeSyncResponse,
767            name: "SEDSNET_TIME_SYNC_RESPONSE",
768            description: "",
769            element: MessageElement::Static(4, MessageDataType::UInt64, MessageClass::Data),
770            endpoints: leak_endpoints(vec![DataEndpoint::TimeSync]),
771            reliable: ReliableMode::None,
772            priority: 245,
773            e2e_encryption: E2eEncryptionPolicy::PreferOff,
774        })
775        .expect("built-in type");
776        reg.register_type_definition(DataTypeDefinition {
777            id: DataType::DiscoveryAnnounce,
778            name: "SEDSNET_DISCOVERY_ANNOUNCE",
779            description: "",
780            element: MessageElement::Dynamic(MessageDataType::UInt32, MessageClass::Data),
781            endpoints: leak_endpoints(vec![DataEndpoint::Discovery]),
782            reliable: ReliableMode::None,
783            priority: 240,
784            e2e_encryption: E2eEncryptionPolicy::PreferOff,
785        })
786        .expect("built-in type");
787        reg.register_type_definition(DataTypeDefinition {
788            id: DataType::DiscoveryTimeSyncSources,
789            name: "SEDSNET_DISCOVERY_TIMESYNC_SOURCES",
790            description: "",
791            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
792            endpoints: leak_endpoints(vec![DataEndpoint::Discovery]),
793            reliable: ReliableMode::None,
794            priority: 240,
795            e2e_encryption: E2eEncryptionPolicy::PreferOff,
796        })
797        .expect("built-in type");
798        reg.register_type_definition(DataTypeDefinition {
799            id: DataType::DiscoveryTopology,
800            name: "SEDSNET_DISCOVERY_TOPOLOGY",
801            description: "",
802            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
803            endpoints: leak_endpoints(vec![DataEndpoint::Discovery]),
804            reliable: ReliableMode::Ordered,
805            priority: 240,
806            e2e_encryption: E2eEncryptionPolicy::PreferOff,
807        })
808        .expect("built-in type");
809        reg.register_type_definition(DataTypeDefinition {
810            id: DataType::DiscoverySchema,
811            name: "SEDSNET_DISCOVERY_SCHEMA",
812            description: "",
813            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
814            endpoints: leak_endpoints(vec![DataEndpoint::Discovery]),
815            reliable: ReliableMode::Ordered,
816            priority: 241,
817            e2e_encryption: E2eEncryptionPolicy::PreferOff,
818        })
819        .expect("built-in type");
820        reg.register_type_definition(DataTypeDefinition {
821            id: DataType::DiscoveryTopologyRequest,
822            name: "SEDSNET_DISCOVERY_TOPOLOGY_REQUEST",
823            description: "",
824            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
825            endpoints: leak_endpoints(vec![DataEndpoint::Discovery]),
826            reliable: ReliableMode::Ordered,
827            priority: 242,
828            e2e_encryption: E2eEncryptionPolicy::PreferOff,
829        })
830        .expect("built-in type");
831        reg.register_type_definition(DataTypeDefinition {
832            id: DataType::DiscoverySchemaRequest,
833            name: "SEDSNET_DISCOVERY_SCHEMA_REQUEST",
834            description: "",
835            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
836            endpoints: leak_endpoints(vec![DataEndpoint::Discovery]),
837            reliable: ReliableMode::Ordered,
838            priority: 242,
839            e2e_encryption: E2eEncryptionPolicy::PreferOff,
840        })
841        .expect("built-in type");
842        reg.register_type_definition(DataTypeDefinition {
843            id: DataType::ManagedVariableRequest,
844            name: "SEDSNET_MANAGED_VARIABLE_REQUEST",
845            description: "",
846            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
847            endpoints: leak_endpoints(vec![DataEndpoint::Discovery]),
848            reliable: ReliableMode::Ordered,
849            priority: 243,
850            e2e_encryption: E2eEncryptionPolicy::PreferOff,
851        })
852        .expect("built-in type");
853        reg.register_type_definition(DataTypeDefinition {
854            id: DataType::ManagedVariableValue,
855            name: "SEDSNET_MANAGED_VARIABLE_VALUE",
856            description: "",
857            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
858            endpoints: leak_endpoints(vec![DataEndpoint::Discovery]),
859            reliable: ReliableMode::Ordered,
860            priority: 243,
861            e2e_encryption: E2eEncryptionPolicy::PreferOff,
862        })
863        .expect("built-in type");
864        reg.register_type_definition(DataTypeDefinition {
865            id: DataType::DiscoveryLeave,
866            name: "SEDSNET_DISCOVERY_LEAVE",
867            description: "",
868            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
869            endpoints: leak_endpoints(vec![DataEndpoint::Discovery]),
870            reliable: ReliableMode::None,
871            priority: 244,
872            e2e_encryption: E2eEncryptionPolicy::PreferOff,
873        })
874        .expect("built-in type");
875        reg.register_type_definition(DataTypeDefinition {
876            id: DataType::DiscoveryLinkCapabilities,
877            name: "SEDSNET_DISCOVERY_LINK_CAPABILITIES",
878            description: "",
879            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
880            endpoints: leak_endpoints(vec![DataEndpoint::Discovery]),
881            reliable: ReliableMode::None,
882            priority: 240,
883            e2e_encryption: E2eEncryptionPolicy::PreferOff,
884        })
885        .expect("built-in type");
886        reg.register_type_definition(DataTypeDefinition {
887            id: DataType::DiscoveryAddress,
888            name: "SEDSNET_DISCOVERY_ADDRESS",
889            description: "",
890            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
891            endpoints: leak_endpoints(vec![DataEndpoint::Discovery]),
892            reliable: ReliableMode::Ordered,
893            priority: 244,
894            e2e_encryption: E2eEncryptionPolicy::PreferOff,
895        })
896        .expect("built-in type");
897        reg.register_type_definition(DataTypeDefinition {
898            id: DataType::P2pMessage,
899            name: "SEDSNET_P2P_MESSAGE",
900            description: "",
901            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
902            endpoints: leak_endpoints(vec![DataEndpoint::Discovery]),
903            reliable: ReliableMode::Ordered,
904            priority: 246,
905            e2e_encryption: E2eEncryptionPolicy::PreferOff,
906        })
907        .expect("built-in type");
908        #[cfg(all(feature = "embedded", sedsnet_has_telemetry_config_json))]
909        if let Ok(snapshot) = bundled_schema_snapshot() {
910            let _ = register_schema_snapshot_into(&mut reg, snapshot);
911        }
912        if let Some(cfg) = read_runtime_json_config("SEDSNET_STATIC_SCHEMA_PATH", &[]) {
913            let _ = register_json_config_into(&mut reg, cfg, false);
914        }
915        if let Some(cfg) = read_runtime_json_config("SEDSNET_STATIC_IPC_SCHEMA_PATH", &[]) {
916            let _ = register_json_config_into(&mut reg, cfg, true);
917        }
918        reg
919    }
920
921    fn register_endpoint_definition(&mut self, def: EndpointDefinition) -> TelemetryResult<()> {
922        if let Some((_, existing)) = self.endpoints.iter().find(|(id, _)| *id == def.id) {
923            if existing.name == def.name
924                && existing.description == def.description
925                && existing.link_local_only == def.link_local_only
926            {
927                return Ok(());
928            }
929            return Err(TelemetryError::BadArg);
930        }
931        if self.endpoints.iter().any(|(_, meta)| meta.name == def.name) {
932            return Err(TelemetryError::BadArg);
933        }
934        self.next_endpoint_id = self.next_endpoint_id.max(def.id.0.saturating_add(1));
935        self.endpoints.push((
936            def.id,
937            EndpointMeta {
938                name: def.name,
939                description: def.description,
940                link_local_only: def.link_local_only,
941            },
942        ));
943        self.endpoints.sort_unstable_by_key(|(id, _)| id.0);
944        Ok(())
945    }
946
947    fn register_type_definition(&mut self, def: DataTypeDefinition) -> TelemetryResult<()> {
948        if let Some((_, existing)) = self.types.iter().find(|(id, _)| *id == def.id) {
949            if existing.name == def.name
950                && existing.description == def.description
951                && existing.element == def.element
952                && existing.endpoints == def.endpoints
953                && existing.reliable == def.reliable
954                && existing.priority == def.priority
955                && existing.e2e_encryption == def.e2e_encryption
956            {
957                return Ok(());
958            }
959            return Err(TelemetryError::BadArg);
960        }
961        if self.types.iter().any(|(_, meta)| meta.name == def.name) {
962            return Err(TelemetryError::BadArg);
963        }
964        for ep in def.endpoints {
965            if !self.endpoints.iter().any(|(id, _)| id == ep) {
966                return Err(TelemetryError::BadArg);
967            }
968        }
969        self.next_type_id = self.next_type_id.max(def.id.0.saturating_add(1));
970        self.types.push((
971            def.id,
972            MessageMeta {
973                name: def.name,
974                description: def.description,
975                element: def.element,
976                endpoints: def.endpoints,
977                reliable: def.reliable,
978                priority: def.priority,
979                e2e_encryption: def.e2e_encryption,
980            },
981        ));
982        self.types.sort_unstable_by_key(|(id, _)| id.0);
983        Ok(())
984    }
985
986    fn schema_byte_cost(&self) -> usize {
987        self.endpoints
988            .iter()
989            .map(|(_, meta)| endpoint_schema_byte_cost(meta.name.len(), meta.description.len()))
990            .sum::<usize>()
991            .saturating_add(
992                self.types
993                    .iter()
994                    .map(|(_, meta)| {
995                        type_schema_byte_cost(
996                            meta.name.len(),
997                            meta.description.len(),
998                            meta.endpoints.len(),
999                        )
1000                    })
1001                    .sum::<usize>(),
1002            )
1003    }
1004
1005    fn merge_endpoint_definition(&mut self, def: EndpointDefinition) -> SchemaMergeDecision {
1006        let id_match = self.endpoints.iter().position(|(id, _)| *id == def.id);
1007        let name_match = self
1008            .endpoints
1009            .iter()
1010            .position(|(_, meta)| meta.name == def.name);
1011        let conflict = match (id_match, name_match) {
1012            (Some(a), Some(b)) if a != b => Some(a.min(b)),
1013            (Some(a), _) | (_, Some(a)) => Some(a),
1014            (None, None) => None,
1015        };
1016
1017        let Some(idx) = conflict else {
1018            self.next_endpoint_id = self.next_endpoint_id.max(def.id.0.saturating_add(1));
1019            self.endpoints.push((
1020                def.id,
1021                EndpointMeta {
1022                    name: def.name,
1023                    description: def.description,
1024                    link_local_only: def.link_local_only,
1025                },
1026            ));
1027            self.endpoints.sort_unstable_by_key(|(id, _)| id.0);
1028            return SchemaMergeDecision::Added;
1029        };
1030
1031        let existing = self.endpoints[idx];
1032        let existing_def = EndpointDefinition {
1033            id: existing.0,
1034            name: existing.1.name,
1035            description: existing.1.description,
1036            link_local_only: existing.1.link_local_only,
1037        };
1038        if endpoint_def_equivalent(&existing_def, &def) {
1039            return SchemaMergeDecision::Unchanged;
1040        }
1041        if endpoint_winner(&existing_def, &def) == def {
1042            self.endpoints[idx] = (
1043                def.id,
1044                EndpointMeta {
1045                    name: def.name,
1046                    description: def.description,
1047                    link_local_only: def.link_local_only,
1048                },
1049            );
1050            self.endpoints.sort_unstable_by_key(|(id, _)| id.0);
1051            self.next_endpoint_id = self.next_endpoint_id.max(def.id.0.saturating_add(1));
1052            SchemaMergeDecision::ReplacedLocal
1053        } else {
1054            SchemaMergeDecision::KeptLocal
1055        }
1056    }
1057
1058    fn merge_type_definition(&mut self, def: DataTypeDefinition) -> SchemaMergeDecision {
1059        let id_match = self.types.iter().position(|(id, _)| *id == def.id);
1060        let name_match = self
1061            .types
1062            .iter()
1063            .position(|(_, meta)| meta.name == def.name);
1064        let conflict = match (id_match, name_match) {
1065            (Some(a), Some(b)) if a != b => Some(a.min(b)),
1066            (Some(a), _) | (_, Some(a)) => Some(a),
1067            (None, None) => None,
1068        };
1069
1070        let Some(idx) = conflict else {
1071            self.next_type_id = self.next_type_id.max(def.id.0.saturating_add(1));
1072            self.types.push((
1073                def.id,
1074                MessageMeta {
1075                    name: def.name,
1076                    description: def.description,
1077                    element: def.element,
1078                    endpoints: def.endpoints,
1079                    reliable: def.reliable,
1080                    priority: def.priority,
1081                    e2e_encryption: def.e2e_encryption,
1082                },
1083            ));
1084            self.types.sort_unstable_by_key(|(id, _)| id.0);
1085            return SchemaMergeDecision::Added;
1086        };
1087
1088        let existing = self.types[idx];
1089        let existing_def = DataTypeDefinition {
1090            id: existing.0,
1091            name: existing.1.name,
1092            description: existing.1.description,
1093            element: existing.1.element,
1094            endpoints: existing.1.endpoints,
1095            reliable: existing.1.reliable,
1096            priority: existing.1.priority,
1097            e2e_encryption: existing.1.e2e_encryption,
1098        };
1099        if type_def_equivalent(&existing_def, &def) {
1100            return SchemaMergeDecision::Unchanged;
1101        }
1102        if type_winner(&existing_def, &def) == def {
1103            self.types[idx] = (
1104                def.id,
1105                MessageMeta {
1106                    name: def.name,
1107                    description: def.description,
1108                    element: def.element,
1109                    endpoints: def.endpoints,
1110                    reliable: def.reliable,
1111                    priority: def.priority,
1112                    e2e_encryption: def.e2e_encryption,
1113                },
1114            );
1115            self.types.sort_unstable_by_key(|(id, _)| id.0);
1116            self.next_type_id = self.next_type_id.max(def.id.0.saturating_add(1));
1117            SchemaMergeDecision::ReplacedLocal
1118        } else {
1119            SchemaMergeDecision::KeptLocal
1120        }
1121    }
1122}
1123
1124fn endpoint_schema_byte_cost(name_len: usize, description_len: usize) -> usize {
1125    size_of::<(DataEndpoint, EndpointMeta)>()
1126        .saturating_add(name_len)
1127        .saturating_add(description_len)
1128}
1129
1130fn type_schema_byte_cost(name_len: usize, description_len: usize, endpoint_count: usize) -> usize {
1131    size_of::<(DataType, MessageMeta)>()
1132        .saturating_add(name_len)
1133        .saturating_add(description_len)
1134        .saturating_add(endpoint_count.saturating_mul(size_of::<DataEndpoint>()))
1135}
1136
1137pub fn owned_schema_byte_cost(snapshot: &OwnedRuntimeSchemaSnapshot) -> usize {
1138    snapshot
1139        .endpoints
1140        .iter()
1141        .map(|def| endpoint_schema_byte_cost(def.name.len(), def.description.len()))
1142        .sum::<usize>()
1143        .saturating_add(
1144            snapshot
1145                .types
1146                .iter()
1147                .map(|def| {
1148                    type_schema_byte_cost(
1149                        def.name.len(),
1150                        def.description.len(),
1151                        def.endpoints.len(),
1152                    )
1153                })
1154                .sum::<usize>(),
1155        )
1156}
1157
1158#[cfg(feature = "std")]
1159static REGISTRY: OnceLock<std::sync::Mutex<Registry>> = OnceLock::new();
1160
1161#[cfg(feature = "std")]
1162fn registry() -> &'static std::sync::Mutex<Registry> {
1163    REGISTRY.get_or_init(|| std::sync::Mutex::new(Registry::new()))
1164}
1165
1166#[cfg(all(
1167    feature = "serde",
1168    feature = "embedded",
1169    sedsnet_has_telemetry_config_json
1170))]
1171fn bundled_schema_snapshot() -> TelemetryResult<RuntimeSchemaSnapshot> {
1172    schema_snapshot_from_json_bytes(include_bytes!("../telemetry_config.json"))
1173}
1174
1175fn leak_str(s: String) -> &'static str {
1176    Box::leak(s.into_boxed_str())
1177}
1178
1179fn leak_endpoints(eps: Vec<DataEndpoint>) -> &'static [DataEndpoint] {
1180    Box::leak(eps.into_boxed_slice())
1181}
1182
1183#[cfg(feature = "std")]
1184fn read_runtime_json_config(env_key: &str, fallback_paths: &[&str]) -> Option<JsonConfig> {
1185    if let Ok(path) = std::env::var(env_key)
1186        && let Ok(json) = std::fs::read_to_string(path)
1187        && let Ok(cfg) = serde_json::from_str::<JsonConfig>(&json)
1188    {
1189        return Some(cfg);
1190    }
1191    for path in fallback_paths {
1192        if let Ok(json) = std::fs::read_to_string(path)
1193            && let Ok(cfg) = serde_json::from_str::<JsonConfig>(&json)
1194        {
1195            return Some(cfg);
1196        }
1197    }
1198    None
1199}
1200
1201#[cfg(feature = "std")]
1202pub fn register_endpoint(name: &str, link_local_only: bool) -> TelemetryResult<DataEndpoint> {
1203    register_endpoint_with_description(name, "", link_local_only)
1204}
1205
1206#[cfg(feature = "std")]
1207pub fn register_endpoint_with_description(
1208    name: &str,
1209    description: &str,
1210    link_local_only: bool,
1211) -> TelemetryResult<DataEndpoint> {
1212    let mut reg = registry().lock().expect("schema registry poisoned");
1213    let id = DataEndpoint(reg.next_endpoint_id);
1214    reg.register_endpoint_definition(EndpointDefinition {
1215        id,
1216        name: leak_str(name.to_string()),
1217        description: leak_str(description.to_string()),
1218        link_local_only,
1219    })?;
1220    Ok(id)
1221}
1222
1223#[cfg(feature = "std")]
1224pub fn register_endpoint_id(
1225    id: DataEndpoint,
1226    name: &str,
1227    link_local_only: bool,
1228) -> TelemetryResult<DataEndpoint> {
1229    register_endpoint_id_with_description(id, name, "", link_local_only)
1230}
1231
1232#[cfg(feature = "std")]
1233pub fn register_endpoint_id_with_description(
1234    id: DataEndpoint,
1235    name: &str,
1236    description: &str,
1237    link_local_only: bool,
1238) -> TelemetryResult<DataEndpoint> {
1239    registry()
1240        .lock()
1241        .expect("schema registry poisoned")
1242        .register_endpoint_definition(EndpointDefinition {
1243            id,
1244            name: leak_str(name.to_string()),
1245            description: leak_str(description.to_string()),
1246            link_local_only,
1247        })?;
1248    Ok(id)
1249}
1250
1251#[cfg(feature = "std")]
1252pub fn ensure_endpoint_id(
1253    id: DataEndpoint,
1254    link_local_only: bool,
1255) -> TelemetryResult<DataEndpoint> {
1256    if endpoint_exists(id) {
1257        return Ok(id);
1258    }
1259    register_endpoint_id(id, &format!("ENDPOINT_{}", id.0), link_local_only)
1260}
1261
1262#[cfg(feature = "std")]
1263pub fn register_endpoint_definition(def: EndpointDefinition) -> TelemetryResult<()> {
1264    registry()
1265        .lock()
1266        .expect("schema registry poisoned")
1267        .register_endpoint_definition(def)
1268}
1269
1270#[cfg(feature = "std")]
1271pub fn register_data_type(
1272    name: &str,
1273    element: MessageElement,
1274    endpoints: &[DataEndpoint],
1275    reliable: ReliableMode,
1276    priority: u8,
1277) -> TelemetryResult<DataType> {
1278    register_data_type_with_description(name, "", element, endpoints, reliable, priority)
1279}
1280
1281#[cfg(feature = "std")]
1282pub fn register_data_type_with_description(
1283    name: &str,
1284    description: &str,
1285    element: MessageElement,
1286    endpoints: &[DataEndpoint],
1287    reliable: ReliableMode,
1288    priority: u8,
1289) -> TelemetryResult<DataType> {
1290    register_data_type_with_description_and_e2e_encryption(
1291        name,
1292        description,
1293        element,
1294        endpoints,
1295        reliable,
1296        priority,
1297        E2eEncryptionPolicy::PreferOff,
1298    )
1299}
1300
1301#[cfg(feature = "std")]
1302#[allow(clippy::too_many_arguments)]
1303pub fn register_data_type_with_description_and_e2e_encryption(
1304    name: &str,
1305    description: &str,
1306    element: MessageElement,
1307    endpoints: &[DataEndpoint],
1308    reliable: ReliableMode,
1309    priority: u8,
1310    e2e_encryption: E2eEncryptionPolicy,
1311) -> TelemetryResult<DataType> {
1312    let mut reg = registry().lock().expect("schema registry poisoned");
1313    let id = DataType(reg.next_type_id);
1314    reg.register_type_definition(DataTypeDefinition {
1315        id,
1316        name: leak_str(name.to_string()),
1317        description: leak_str(description.to_string()),
1318        element,
1319        endpoints: leak_endpoints(endpoints.to_vec()),
1320        reliable,
1321        priority,
1322        e2e_encryption,
1323    })?;
1324    Ok(id)
1325}
1326
1327#[cfg(feature = "std")]
1328pub fn register_data_type_definition(def: DataTypeDefinition) -> TelemetryResult<()> {
1329    registry()
1330        .lock()
1331        .expect("schema registry poisoned")
1332        .register_type_definition(def)
1333}
1334
1335#[cfg(feature = "std")]
1336pub fn set_data_type_e2e_encryption_policy(
1337    ty: DataType,
1338    policy: E2eEncryptionPolicy,
1339) -> TelemetryResult<()> {
1340    let mut reg = registry().lock().expect("schema registry poisoned");
1341    let Some((_, meta)) = reg.types.iter_mut().find(|(id, _)| *id == ty) else {
1342        return Err(TelemetryError::InvalidType);
1343    };
1344    meta.e2e_encryption = policy;
1345    Ok(())
1346}
1347
1348#[cfg(feature = "std")]
1349pub fn register_data_type_id(
1350    id: DataType,
1351    name: &str,
1352    element: MessageElement,
1353    endpoints: &[DataEndpoint],
1354    reliable: ReliableMode,
1355    priority: u8,
1356) -> TelemetryResult<DataType> {
1357    register_data_type_id_with_description(id, name, "", element, endpoints, reliable, priority)
1358}
1359
1360#[cfg(feature = "std")]
1361pub fn register_data_type_id_with_description(
1362    id: DataType,
1363    name: &str,
1364    description: &str,
1365    element: MessageElement,
1366    endpoints: &[DataEndpoint],
1367    reliable: ReliableMode,
1368    priority: u8,
1369) -> TelemetryResult<DataType> {
1370    register_data_type_id_with_description_and_e2e_encryption(
1371        id,
1372        name,
1373        description,
1374        element,
1375        endpoints,
1376        reliable,
1377        priority,
1378        E2eEncryptionPolicy::PreferOff,
1379    )
1380}
1381
1382#[cfg(feature = "std")]
1383#[allow(clippy::too_many_arguments)]
1384pub fn register_data_type_id_with_description_and_e2e_encryption(
1385    id: DataType,
1386    name: &str,
1387    description: &str,
1388    element: MessageElement,
1389    endpoints: &[DataEndpoint],
1390    reliable: ReliableMode,
1391    priority: u8,
1392    e2e_encryption: E2eEncryptionPolicy,
1393) -> TelemetryResult<DataType> {
1394    registry()
1395        .lock()
1396        .expect("schema registry poisoned")
1397        .register_type_definition(DataTypeDefinition {
1398            id,
1399            name: leak_str(name.to_string()),
1400            description: leak_str(description.to_string()),
1401            element,
1402            endpoints: leak_endpoints(endpoints.to_vec()),
1403            reliable,
1404            priority,
1405            e2e_encryption,
1406        })?;
1407    Ok(id)
1408}
1409
1410#[cfg(feature = "std")]
1411pub fn merge_schema_snapshot(snapshot: RuntimeSchemaSnapshot) -> SchemaMergeReport {
1412    let mut reg = registry().lock().expect("schema registry poisoned");
1413    merge_schema_snapshot_locked(&mut reg, snapshot)
1414}
1415
1416#[cfg(feature = "std")]
1417pub fn merge_owned_schema_snapshot(snapshot: OwnedRuntimeSchemaSnapshot) -> SchemaMergeReport {
1418    merge_owned_schema_snapshot_with_budget(snapshot, usize::MAX)
1419        .expect("unbounded schema merge should not fail budget")
1420}
1421
1422#[cfg(feature = "std")]
1423pub fn merge_owned_schema_snapshot_with_budget(
1424    mut snapshot: OwnedRuntimeSchemaSnapshot,
1425    max_schema_bytes: usize,
1426) -> TelemetryResult<SchemaMergeReport> {
1427    snapshot.endpoints.sort_unstable_by_key(|def| def.id.0);
1428    snapshot.endpoints.dedup_by_key(|def| def.id.0);
1429    snapshot.types.sort_unstable_by_key(|def| def.id.0);
1430    snapshot.types.dedup_by_key(|def| def.id.0);
1431
1432    let reg = registry().lock().expect("schema registry poisoned");
1433    if reg
1434        .schema_byte_cost()
1435        .saturating_add(owned_schema_byte_cost(&snapshot))
1436        > max_schema_bytes
1437    {
1438        return Err(TelemetryError::PacketTooLarge(
1439            "Schema exceeds maximum shared queue budget",
1440        ));
1441    }
1442    drop(reg);
1443
1444    let mut converted = RuntimeSchemaSnapshot {
1445        endpoints: Vec::with_capacity(snapshot.endpoints.len()),
1446        types: Vec::with_capacity(snapshot.types.len()),
1447    };
1448    for endpoint in snapshot.endpoints {
1449        converted.endpoints.push(EndpointDefinition {
1450            id: endpoint.id,
1451            name: leak_str(endpoint.name),
1452            description: leak_str(endpoint.description),
1453            link_local_only: endpoint.link_local_only,
1454        });
1455    }
1456    for ty in snapshot.types {
1457        converted.types.push(DataTypeDefinition {
1458            id: ty.id,
1459            name: leak_str(ty.name),
1460            description: leak_str(ty.description),
1461            element: ty.element,
1462            endpoints: leak_endpoints(ty.endpoints),
1463            reliable: ty.reliable,
1464            priority: ty.priority,
1465            e2e_encryption: ty.e2e_encryption,
1466        });
1467    }
1468
1469    let mut reg = registry().lock().expect("schema registry poisoned");
1470    let mut preview = reg.clone();
1471    let report = merge_schema_snapshot_locked(&mut preview, converted.clone());
1472    if preview.schema_byte_cost() > max_schema_bytes {
1473        return Err(TelemetryError::PacketTooLarge(
1474            "Schema exceeds maximum shared queue budget",
1475        ));
1476    }
1477    *reg = preview;
1478    Ok(report)
1479}
1480
1481#[cfg(feature = "std")]
1482fn merge_schema_snapshot_locked(
1483    reg: &mut Registry,
1484    mut snapshot: RuntimeSchemaSnapshot,
1485) -> SchemaMergeReport {
1486    snapshot.endpoints.sort_unstable_by_key(|def| def.id.0);
1487    snapshot.endpoints.dedup_by_key(|def| def.id.0);
1488    snapshot.types.sort_unstable_by_key(|def| def.id.0);
1489    snapshot.types.dedup_by_key(|def| def.id.0);
1490
1491    let mut report = SchemaMergeReport {
1492        endpoints_added: 0,
1493        endpoints_replaced: 0,
1494        endpoints_kept: 0,
1495        types_added: 0,
1496        types_replaced: 0,
1497        types_kept: 0,
1498    };
1499    for endpoint in snapshot.endpoints {
1500        match reg.merge_endpoint_definition(endpoint) {
1501            SchemaMergeDecision::Added => report.endpoints_added += 1,
1502            SchemaMergeDecision::ReplacedLocal => report.endpoints_replaced += 1,
1503            SchemaMergeDecision::KeptLocal => report.endpoints_kept += 1,
1504            SchemaMergeDecision::Unchanged => {}
1505        }
1506    }
1507    for ty in snapshot.types {
1508        if ty
1509            .endpoints
1510            .iter()
1511            .all(|ep| reg.endpoints.iter().any(|(known_ep, _)| known_ep == ep))
1512        {
1513            match reg.merge_type_definition(ty) {
1514                SchemaMergeDecision::Added => report.types_added += 1,
1515                SchemaMergeDecision::ReplacedLocal => report.types_replaced += 1,
1516                SchemaMergeDecision::KeptLocal => report.types_kept += 1,
1517                SchemaMergeDecision::Unchanged => {}
1518            }
1519        } else {
1520            report.types_kept += 1;
1521        }
1522    }
1523    report
1524}
1525
1526#[cfg(feature = "std")]
1527pub fn export_schema() -> RuntimeSchemaSnapshot {
1528    let reg = registry().lock().expect("schema registry poisoned");
1529    RuntimeSchemaSnapshot {
1530        endpoints: reg
1531            .endpoints
1532            .iter()
1533            .map(|(id, meta)| EndpointDefinition {
1534                id: *id,
1535                name: meta.name,
1536                description: meta.description,
1537                link_local_only: meta.link_local_only,
1538            })
1539            .collect(),
1540        types: reg
1541            .types
1542            .iter()
1543            .map(|(id, meta)| DataTypeDefinition {
1544                id: *id,
1545                name: meta.name,
1546                description: meta.description,
1547                element: meta.element,
1548                endpoints: meta.endpoints,
1549                reliable: meta.reliable,
1550                priority: meta.priority,
1551                e2e_encryption: meta.e2e_encryption,
1552            })
1553            .collect(),
1554    }
1555}
1556
1557#[cfg(feature = "std")]
1558pub fn known_endpoints() -> Vec<EndpointDefinition> {
1559    export_schema().endpoints
1560}
1561
1562#[cfg(feature = "std")]
1563pub fn known_data_types() -> Vec<DataTypeDefinition> {
1564    export_schema().types
1565}
1566
1567#[cfg(feature = "std")]
1568pub fn schema_fingerprint() -> u64 {
1569    let snapshot = export_schema();
1570    let mut h = 0x5E_D5_50_4F_52_49_4E_54u64;
1571    for ep in snapshot.endpoints {
1572        h = hash_u32(h, ep.id.0);
1573        h = hash_bytes(h, ep.name.as_bytes());
1574        h = hash_bytes(h, ep.description.as_bytes());
1575        h = hash_u8(h, ep.link_local_only as u8);
1576    }
1577    for ty in snapshot.types {
1578        h = hash_u32(h, ty.id.0);
1579        h = hash_bytes(h, ty.name.as_bytes());
1580        h = hash_bytes(h, ty.description.as_bytes());
1581        h = hash_message_element(h, ty.element);
1582        h = hash_u8(h, reliable_code(ty.reliable));
1583        h = hash_u8(h, ty.priority);
1584        for ep in ty.endpoints {
1585            h = hash_u32(h, ep.0);
1586        }
1587    }
1588    h
1589}
1590
1591#[cfg(feature = "std")]
1592pub fn schema_bytes_used() -> usize {
1593    registry()
1594        .lock()
1595        .expect("schema registry poisoned")
1596        .schema_byte_cost()
1597}
1598
1599#[cfg(feature = "std")]
1600pub fn endpoint_exists(ep: DataEndpoint) -> bool {
1601    #[cfg(all(test, feature = "std"))]
1602    seed_test_schema();
1603    registry()
1604        .lock()
1605        .expect("schema registry poisoned")
1606        .endpoints
1607        .iter()
1608        .any(|(id, _)| *id == ep)
1609}
1610
1611#[cfg(feature = "std")]
1612pub fn data_type_exists(ty: DataType) -> bool {
1613    #[cfg(all(test, feature = "std"))]
1614    seed_test_schema();
1615    registry()
1616        .lock()
1617        .expect("schema registry poisoned")
1618        .types
1619        .iter()
1620        .any(|(id, _)| *id == ty)
1621}
1622
1623#[cfg(feature = "std")]
1624pub fn get_endpoint_meta(endpoint_type: DataEndpoint) -> EndpointMeta {
1625    #[cfg(all(test, feature = "std"))]
1626    seed_test_schema();
1627    registry()
1628        .lock()
1629        .expect("schema registry poisoned")
1630        .endpoints
1631        .iter()
1632        .find(|(id, _)| *id == endpoint_type)
1633        .map(|(_, meta)| *meta)
1634        .unwrap_or(EndpointMeta {
1635            name: "UNKNOWN_ENDPOINT",
1636            description: "",
1637            link_local_only: false,
1638        })
1639}
1640
1641#[cfg(feature = "std")]
1642pub fn get_message_meta(data_type: DataType) -> MessageMeta {
1643    #[cfg(all(test, feature = "std"))]
1644    seed_test_schema();
1645    registry()
1646        .lock()
1647        .expect("schema registry poisoned")
1648        .types
1649        .iter()
1650        .find(|(id, _)| *id == data_type)
1651        .map(|(_, meta)| *meta)
1652        .unwrap_or(MessageMeta {
1653            name: "UNKNOWN_TYPE",
1654            description: "",
1655            element: MessageElement::Dynamic(MessageDataType::Binary, MessageClass::Data),
1656            endpoints: &[],
1657            reliable: ReliableMode::None,
1658            priority: 0,
1659            e2e_encryption: E2eEncryptionPolicy::PreferOff,
1660        })
1661}
1662
1663#[cfg(feature = "std")]
1664pub fn max_endpoint_id() -> u32 {
1665    registry()
1666        .lock()
1667        .expect("schema registry poisoned")
1668        .endpoints
1669        .iter()
1670        .map(|(id, _)| id.0)
1671        .max()
1672        .unwrap_or(0)
1673}
1674
1675#[cfg(feature = "std")]
1676pub fn max_data_type_id() -> u32 {
1677    registry()
1678        .lock()
1679        .expect("schema registry poisoned")
1680        .types
1681        .iter()
1682        .map(|(id, _)| id.0)
1683        .max()
1684        .unwrap_or(0)
1685}
1686
1687#[cfg(feature = "std")]
1688fn hash_u8(h: u64, v: u8) -> u64 {
1689    hash_bytes(h, &[v])
1690}
1691
1692#[cfg(feature = "std")]
1693fn hash_u32(h: u64, v: u32) -> u64 {
1694    hash_bytes(h, &v.to_le_bytes())
1695}
1696
1697#[cfg(feature = "std")]
1698fn hash_usize(h: u64, v: usize) -> u64 {
1699    hash_bytes(h, &(v as u64).to_le_bytes())
1700}
1701
1702#[cfg(feature = "std")]
1703fn hash_bytes(mut h: u64, bytes: &[u8]) -> u64 {
1704    const PRIME: u64 = 0x0000_0100_0000_01B3;
1705    for &b in bytes {
1706        h ^= b as u64;
1707        h = h.wrapping_mul(PRIME);
1708    }
1709    h
1710}
1711
1712#[cfg(feature = "std")]
1713fn endpoint_fingerprint(def: EndpointDefinition) -> u64 {
1714    let mut h = 0x4550_4445_4600_0001;
1715    h = hash_u32(h, def.id.0);
1716    h = hash_bytes(h, def.name.as_bytes());
1717    h = hash_bytes(h, def.description.as_bytes());
1718    hash_u8(h, def.link_local_only as u8)
1719}
1720
1721#[cfg(feature = "std")]
1722fn type_fingerprint(def: DataTypeDefinition) -> u64 {
1723    let mut h = 0x5459_4445_4600_0001;
1724    h = hash_u32(h, def.id.0);
1725    h = hash_bytes(h, def.name.as_bytes());
1726    h = hash_bytes(h, def.description.as_bytes());
1727    h = hash_message_element(h, def.element);
1728    h = hash_u8(h, reliable_code(def.reliable));
1729    h = hash_u8(h, def.priority);
1730    h = hash_u8(h, e2e_encryption_policy_code(def.e2e_encryption));
1731    for ep in def.endpoints {
1732        h = hash_u32(h, ep.0);
1733    }
1734    h
1735}
1736
1737#[cfg(feature = "std")]
1738fn hash_message_element(mut h: u64, element: MessageElement) -> u64 {
1739    match element {
1740        MessageElement::Static(count, data_type, class) => {
1741            h = hash_u8(h, 0);
1742            h = hash_usize(h, count);
1743            h = hash_u8(h, message_data_type_code(data_type));
1744            hash_u8(h, message_class_code(class))
1745        }
1746        MessageElement::Dynamic(data_type, class) => {
1747            h = hash_u8(h, 1);
1748            h = hash_u8(h, message_data_type_code(data_type));
1749            hash_u8(h, message_class_code(class))
1750        }
1751    }
1752}
1753
1754#[cfg(feature = "std")]
1755pub fn endpoint_definition(ep: DataEndpoint) -> Option<EndpointDefinition> {
1756    registry()
1757        .lock()
1758        .expect("schema registry poisoned")
1759        .endpoints
1760        .iter()
1761        .find(|(id, _)| *id == ep)
1762        .map(|(id, meta)| EndpointDefinition {
1763            id: *id,
1764            name: meta.name,
1765            description: meta.description,
1766            link_local_only: meta.link_local_only,
1767        })
1768}
1769
1770#[cfg(feature = "std")]
1771pub fn data_type_definition(ty: DataType) -> Option<DataTypeDefinition> {
1772    registry()
1773        .lock()
1774        .expect("schema registry poisoned")
1775        .types
1776        .iter()
1777        .find(|(id, _)| *id == ty)
1778        .map(|(id, meta)| DataTypeDefinition {
1779            id: *id,
1780            name: meta.name,
1781            description: meta.description,
1782            element: meta.element,
1783            endpoints: meta.endpoints,
1784            reliable: meta.reliable,
1785            priority: meta.priority,
1786            e2e_encryption: meta.e2e_encryption,
1787        })
1788}
1789
1790#[cfg(feature = "std")]
1791pub fn endpoint_definition_by_name(name: &str) -> Option<EndpointDefinition> {
1792    registry()
1793        .lock()
1794        .expect("schema registry poisoned")
1795        .endpoints
1796        .iter()
1797        .find(|(_, meta)| meta.name == name)
1798        .map(|(id, meta)| EndpointDefinition {
1799            id: *id,
1800            name: meta.name,
1801            description: meta.description,
1802            link_local_only: meta.link_local_only,
1803        })
1804}
1805
1806#[cfg(feature = "std")]
1807pub fn data_type_definition_by_name(name: &str) -> Option<DataTypeDefinition> {
1808    registry()
1809        .lock()
1810        .expect("schema registry poisoned")
1811        .types
1812        .iter()
1813        .find(|(_, meta)| meta.name == name)
1814        .map(|(id, meta)| DataTypeDefinition {
1815            id: *id,
1816            name: meta.name,
1817            description: meta.description,
1818            element: meta.element,
1819            endpoints: meta.endpoints,
1820            reliable: meta.reliable,
1821            priority: meta.priority,
1822            e2e_encryption: meta.e2e_encryption,
1823        })
1824}
1825
1826#[cfg(feature = "std")]
1827fn is_internal_endpoint(ep: DataEndpoint) -> bool {
1828    matches!(
1829        ep,
1830        DataEndpoint::TelemetryError | DataEndpoint::TimeSync | DataEndpoint::Discovery
1831    )
1832}
1833
1834#[cfg(feature = "std")]
1835fn is_internal_data_type(ty: DataType) -> bool {
1836    matches!(
1837        ty,
1838        DataType::TelemetryError
1839            | DataType::ReliableAck
1840            | DataType::ReliablePacketRequest
1841            | DataType::ReliablePartialAck
1842            | DataType::TimeSyncAnnounce
1843            | DataType::TimeSyncRequest
1844            | DataType::TimeSyncResponse
1845            | DataType::DiscoveryAnnounce
1846            | DataType::DiscoveryTimeSyncSources
1847            | DataType::DiscoveryTopology
1848            | DataType::DiscoverySchema
1849            | DataType::DiscoveryTopologyRequest
1850            | DataType::DiscoverySchemaRequest
1851            | DataType::ManagedVariableRequest
1852            | DataType::ManagedVariableValue
1853            | DataType::DiscoveryLeave
1854            | DataType::DiscoveryLinkCapabilities
1855            | DataType::DiscoveryAddress
1856            | DataType::P2pMessage
1857    )
1858}
1859
1860#[cfg(feature = "std")]
1861pub fn remove_endpoint(ep: DataEndpoint) -> TelemetryResult<bool> {
1862    if is_internal_endpoint(ep) {
1863        return Err(TelemetryError::BadArg);
1864    }
1865    let mut reg = registry().lock().expect("schema registry poisoned");
1866    let before = reg.endpoints.len();
1867    reg.endpoints.retain(|(id, _)| *id != ep);
1868    if reg.endpoints.len() == before {
1869        return Ok(false);
1870    }
1871    reg.types.retain(|(_, meta)| !meta.endpoints.contains(&ep));
1872    Ok(true)
1873}
1874
1875#[cfg(feature = "std")]
1876pub fn remove_endpoint_by_name(name: &str) -> TelemetryResult<bool> {
1877    if let Some(def) = endpoint_definition_by_name(name) {
1878        remove_endpoint(def.id)
1879    } else {
1880        Ok(false)
1881    }
1882}
1883
1884#[cfg(feature = "std")]
1885pub fn remove_data_type(ty: DataType) -> TelemetryResult<bool> {
1886    if is_internal_data_type(ty) {
1887        return Err(TelemetryError::BadArg);
1888    }
1889    let mut reg = registry().lock().expect("schema registry poisoned");
1890    let before = reg.types.len();
1891    reg.types.retain(|(id, _)| *id != ty);
1892    Ok(reg.types.len() != before)
1893}
1894
1895#[cfg(feature = "std")]
1896pub fn remove_data_type_by_name(name: &str) -> TelemetryResult<bool> {
1897    if let Some(def) = data_type_definition_by_name(name) {
1898        remove_data_type(def.id)
1899    } else {
1900        Ok(false)
1901    }
1902}
1903
1904#[cfg(feature = "std")]
1905fn endpoint_def_equivalent(a: &EndpointDefinition, b: &EndpointDefinition) -> bool {
1906    a.id == b.id
1907        && a.name == b.name
1908        && a.description == b.description
1909        && a.link_local_only == b.link_local_only
1910}
1911
1912#[cfg(feature = "std")]
1913fn type_def_equivalent(a: &DataTypeDefinition, b: &DataTypeDefinition) -> bool {
1914    a.id == b.id
1915        && a.name == b.name
1916        && a.description == b.description
1917        && a.element == b.element
1918        && a.endpoints == b.endpoints
1919        && a.reliable == b.reliable
1920        && a.priority == b.priority
1921}
1922
1923#[cfg(feature = "std")]
1924fn endpoint_winner(a: &EndpointDefinition, b: &EndpointDefinition) -> EndpointDefinition {
1925    let a_key = (endpoint_fingerprint(*a), a.id.0, a.name);
1926    let b_key = (endpoint_fingerprint(*b), b.id.0, b.name);
1927    if a_key <= b_key { *a } else { *b }
1928}
1929
1930#[cfg(feature = "std")]
1931fn type_winner(a: &DataTypeDefinition, b: &DataTypeDefinition) -> DataTypeDefinition {
1932    let a_key = (type_fingerprint(*a), a.id.0, a.name);
1933    let b_key = (type_fingerprint(*b), b.id.0, b.name);
1934    if a_key <= b_key { *a } else { *b }
1935}
1936
1937pub(crate) fn message_data_type_code(dt: MessageDataType) -> u8 {
1938    match dt {
1939        MessageDataType::Float64 => 0,
1940        MessageDataType::Float32 => 1,
1941        MessageDataType::UInt8 => 2,
1942        MessageDataType::UInt16 => 3,
1943        MessageDataType::UInt32 => 4,
1944        MessageDataType::UInt64 => 5,
1945        MessageDataType::UInt128 => 6,
1946        MessageDataType::Int8 => 7,
1947        MessageDataType::Int16 => 8,
1948        MessageDataType::Int32 => 9,
1949        MessageDataType::Int64 => 10,
1950        MessageDataType::Int128 => 11,
1951        MessageDataType::Bool => 12,
1952        MessageDataType::String => 13,
1953        MessageDataType::Binary => 14,
1954        MessageDataType::NoData => 15,
1955    }
1956}
1957
1958pub(crate) fn message_data_type_from_code(code: u8) -> Option<MessageDataType> {
1959    match code {
1960        0 => Some(MessageDataType::Float64),
1961        1 => Some(MessageDataType::Float32),
1962        2 => Some(MessageDataType::UInt8),
1963        3 => Some(MessageDataType::UInt16),
1964        4 => Some(MessageDataType::UInt32),
1965        5 => Some(MessageDataType::UInt64),
1966        6 => Some(MessageDataType::UInt128),
1967        7 => Some(MessageDataType::Int8),
1968        8 => Some(MessageDataType::Int16),
1969        9 => Some(MessageDataType::Int32),
1970        10 => Some(MessageDataType::Int64),
1971        11 => Some(MessageDataType::Int128),
1972        12 => Some(MessageDataType::Bool),
1973        13 => Some(MessageDataType::String),
1974        14 => Some(MessageDataType::Binary),
1975        15 => Some(MessageDataType::NoData),
1976        _ => None,
1977    }
1978}
1979
1980pub(crate) fn message_class_code(class: MessageClass) -> u8 {
1981    match class {
1982        MessageClass::Data => 0,
1983        MessageClass::Error => 1,
1984        MessageClass::Warning => 2,
1985    }
1986}
1987
1988pub(crate) fn message_class_from_code(code: u8) -> Option<MessageClass> {
1989    match code {
1990        0 => Some(MessageClass::Data),
1991        1 => Some(MessageClass::Error),
1992        2 => Some(MessageClass::Warning),
1993        _ => None,
1994    }
1995}
1996
1997pub(crate) fn reliable_code(mode: ReliableMode) -> u8 {
1998    match mode {
1999        ReliableMode::None => 0,
2000        ReliableMode::Ordered => 1,
2001        ReliableMode::Unordered => 2,
2002    }
2003}
2004
2005pub(crate) fn reliable_from_code(code: u8) -> Option<ReliableMode> {
2006    match code {
2007        0 => Some(ReliableMode::None),
2008        1 => Some(ReliableMode::Ordered),
2009        2 => Some(ReliableMode::Unordered),
2010        _ => None,
2011    }
2012}
2013
2014pub(crate) fn e2e_encryption_policy_code(policy: E2eEncryptionPolicy) -> u8 {
2015    match policy {
2016        E2eEncryptionPolicy::PreferOff => 0,
2017        E2eEncryptionPolicy::PreferOn => 1,
2018        E2eEncryptionPolicy::RequireOn => 2,
2019    }
2020}
2021
2022pub(crate) fn e2e_encryption_policy_from_code(code: u8) -> Option<E2eEncryptionPolicy> {
2023    match code {
2024        0 => Some(E2eEncryptionPolicy::PreferOff),
2025        1 => Some(E2eEncryptionPolicy::PreferOn),
2026        2 => Some(E2eEncryptionPolicy::RequireOn),
2027        _ => None,
2028    }
2029}
2030
2031#[cfg(not(feature = "std"))]
2032pub fn register_endpoint(_name: &str, _link_local_only: bool) -> TelemetryResult<DataEndpoint> {
2033    Err(TelemetryError::BadArg)
2034}
2035
2036#[cfg(not(feature = "std"))]
2037pub fn register_endpoint_with_description(
2038    _name: &str,
2039    _description: &str,
2040    _link_local_only: bool,
2041) -> TelemetryResult<DataEndpoint> {
2042    Err(TelemetryError::BadArg)
2043}
2044
2045#[cfg(not(feature = "std"))]
2046pub fn register_endpoint_definition(_def: EndpointDefinition) -> TelemetryResult<()> {
2047    Err(TelemetryError::BadArg)
2048}
2049
2050#[cfg(not(feature = "std"))]
2051pub fn register_endpoint_id(
2052    _id: DataEndpoint,
2053    _name: &str,
2054    _link_local_only: bool,
2055) -> TelemetryResult<DataEndpoint> {
2056    Err(TelemetryError::BadArg)
2057}
2058
2059#[cfg(not(feature = "std"))]
2060pub fn register_endpoint_id_with_description(
2061    _id: DataEndpoint,
2062    _name: &str,
2063    _description: &str,
2064    _link_local_only: bool,
2065) -> TelemetryResult<DataEndpoint> {
2066    Err(TelemetryError::BadArg)
2067}
2068
2069#[cfg(not(feature = "std"))]
2070pub fn ensure_endpoint_id(
2071    id: DataEndpoint,
2072    _link_local_only: bool,
2073) -> TelemetryResult<DataEndpoint> {
2074    if endpoint_exists(id) {
2075        Ok(id)
2076    } else {
2077        Err(TelemetryError::BadArg)
2078    }
2079}
2080
2081#[cfg(not(feature = "std"))]
2082pub fn register_data_type(
2083    _name: &str,
2084    _element: MessageElement,
2085    _endpoints: &[DataEndpoint],
2086    _reliable: ReliableMode,
2087    _priority: u8,
2088) -> TelemetryResult<DataType> {
2089    Err(TelemetryError::BadArg)
2090}
2091
2092#[cfg(not(feature = "std"))]
2093pub fn register_data_type_with_description(
2094    _name: &str,
2095    _description: &str,
2096    _element: MessageElement,
2097    _endpoints: &[DataEndpoint],
2098    _reliable: ReliableMode,
2099    _priority: u8,
2100) -> TelemetryResult<DataType> {
2101    Err(TelemetryError::BadArg)
2102}
2103
2104#[cfg(not(feature = "std"))]
2105pub fn register_data_type_definition(_def: DataTypeDefinition) -> TelemetryResult<()> {
2106    Err(TelemetryError::BadArg)
2107}
2108
2109#[cfg(not(feature = "std"))]
2110pub fn set_data_type_e2e_encryption_policy(
2111    _ty: DataType,
2112    _policy: E2eEncryptionPolicy,
2113) -> TelemetryResult<()> {
2114    Err(TelemetryError::BadArg)
2115}
2116
2117#[cfg(not(feature = "std"))]
2118pub fn register_data_type_id(
2119    _id: DataType,
2120    _name: &str,
2121    _element: MessageElement,
2122    _endpoints: &[DataEndpoint],
2123    _reliable: ReliableMode,
2124    _priority: u8,
2125) -> TelemetryResult<DataType> {
2126    Err(TelemetryError::BadArg)
2127}
2128
2129#[cfg(not(feature = "std"))]
2130pub fn register_data_type_id_with_description(
2131    _id: DataType,
2132    _name: &str,
2133    _description: &str,
2134    _element: MessageElement,
2135    _endpoints: &[DataEndpoint],
2136    _reliable: ReliableMode,
2137    _priority: u8,
2138) -> TelemetryResult<DataType> {
2139    Err(TelemetryError::BadArg)
2140}
2141
2142#[cfg(not(feature = "std"))]
2143#[allow(clippy::too_many_arguments)]
2144pub fn register_data_type_id_with_description_and_e2e_encryption(
2145    _id: DataType,
2146    _name: &str,
2147    _description: &str,
2148    _element: MessageElement,
2149    _endpoints: &[DataEndpoint],
2150    _reliable: ReliableMode,
2151    _priority: u8,
2152    _e2e_encryption: E2eEncryptionPolicy,
2153) -> TelemetryResult<DataType> {
2154    Err(TelemetryError::BadArg)
2155}
2156
2157#[cfg(not(feature = "std"))]
2158pub fn export_schema() -> RuntimeSchemaSnapshot {
2159    RuntimeSchemaSnapshot {
2160        endpoints: known_endpoints(),
2161        types: known_data_types(),
2162    }
2163}
2164
2165#[cfg(not(feature = "std"))]
2166pub fn known_endpoints() -> Vec<EndpointDefinition> {
2167    #[cfg_attr(
2168        not(all(feature = "serde", sedsnet_has_telemetry_config_json)),
2169        allow(unused_mut)
2170    )]
2171    let mut endpoints = vec![
2172        EndpointDefinition {
2173            id: DataEndpoint::TelemetryError,
2174            name: "SEDSNET_ERROR",
2175            description: "",
2176            link_local_only: false,
2177        },
2178        EndpointDefinition {
2179            id: DataEndpoint::TimeSync,
2180            name: "SEDSNET_TIME_SYNC",
2181            description: "",
2182            link_local_only: false,
2183        },
2184        EndpointDefinition {
2185            id: DataEndpoint::Discovery,
2186            name: "SEDSNET_DISCOVERY",
2187            description: "",
2188            link_local_only: false,
2189        },
2190    ];
2191    #[cfg(all(feature = "serde", sedsnet_has_telemetry_config_json))]
2192    if let Ok(snapshot) = bundled_schema_snapshot() {
2193        for endpoint in snapshot.endpoints {
2194            if !endpoints
2195                .iter()
2196                .any(|known| known.id == endpoint.id || known.name == endpoint.name)
2197            {
2198                endpoints.push(endpoint);
2199            }
2200        }
2201    }
2202    endpoints
2203}
2204
2205#[cfg(not(feature = "std"))]
2206pub fn known_data_types() -> Vec<DataTypeDefinition> {
2207    #[cfg_attr(
2208        not(all(feature = "serde", sedsnet_has_telemetry_config_json)),
2209        allow(unused_mut)
2210    )]
2211    let mut types = vec![
2212        DataTypeDefinition {
2213            id: DataType::TelemetryError,
2214            name: "SEDSNET_ERROR",
2215            description: "",
2216            element: MessageElement::Dynamic(MessageDataType::String, MessageClass::Error),
2217            endpoints: &[DataEndpoint::TelemetryError],
2218            reliable: ReliableMode::None,
2219            priority: 255,
2220            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2221        },
2222        DataTypeDefinition {
2223            id: DataType::ReliableAck,
2224            name: "SEDSNET_RELIABLE_ACK",
2225            description: "",
2226            element: MessageElement::Static(2, MessageDataType::UInt32, MessageClass::Data),
2227            endpoints: &[DataEndpoint::TelemetryError],
2228            reliable: ReliableMode::None,
2229            priority: 250,
2230            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2231        },
2232        DataTypeDefinition {
2233            id: DataType::ReliablePacketRequest,
2234            name: "SEDSNET_RELIABLE_PACKET_REQUEST",
2235            description: "",
2236            element: MessageElement::Static(2, MessageDataType::UInt32, MessageClass::Data),
2237            endpoints: &[DataEndpoint::TelemetryError],
2238            reliable: ReliableMode::None,
2239            priority: 250,
2240            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2241        },
2242        DataTypeDefinition {
2243            id: DataType::ReliablePartialAck,
2244            name: "SEDSNET_RELIABLE_PARTIAL_ACK",
2245            description: "",
2246            element: MessageElement::Static(2, MessageDataType::UInt32, MessageClass::Data),
2247            endpoints: &[DataEndpoint::TelemetryError],
2248            reliable: ReliableMode::None,
2249            priority: 250,
2250            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2251        },
2252        DataTypeDefinition {
2253            id: DataType::TimeSyncAnnounce,
2254            name: "SEDSNET_TIME_SYNC_ANNOUNCE",
2255            description: "",
2256            element: MessageElement::Static(2, MessageDataType::UInt64, MessageClass::Data),
2257            endpoints: &[DataEndpoint::TimeSync],
2258            reliable: ReliableMode::None,
2259            priority: 245,
2260            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2261        },
2262        DataTypeDefinition {
2263            id: DataType::TimeSyncRequest,
2264            name: "SEDSNET_TIME_SYNC_REQUEST",
2265            description: "",
2266            element: MessageElement::Static(2, MessageDataType::UInt64, MessageClass::Data),
2267            endpoints: &[DataEndpoint::TimeSync],
2268            reliable: ReliableMode::None,
2269            priority: 245,
2270            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2271        },
2272        DataTypeDefinition {
2273            id: DataType::TimeSyncResponse,
2274            name: "SEDSNET_TIME_SYNC_RESPONSE",
2275            description: "",
2276            element: MessageElement::Static(4, MessageDataType::UInt64, MessageClass::Data),
2277            endpoints: &[DataEndpoint::TimeSync],
2278            reliable: ReliableMode::None,
2279            priority: 245,
2280            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2281        },
2282        DataTypeDefinition {
2283            id: DataType::DiscoveryAnnounce,
2284            name: "SEDSNET_DISCOVERY_ANNOUNCE",
2285            description: "",
2286            element: MessageElement::Dynamic(MessageDataType::UInt32, MessageClass::Data),
2287            endpoints: &[DataEndpoint::Discovery],
2288            reliable: ReliableMode::None,
2289            priority: 240,
2290            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2291        },
2292        DataTypeDefinition {
2293            id: DataType::DiscoveryTimeSyncSources,
2294            name: "SEDSNET_DISCOVERY_TIMESYNC_SOURCES",
2295            description: "",
2296            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2297            endpoints: &[DataEndpoint::Discovery],
2298            reliable: ReliableMode::None,
2299            priority: 240,
2300            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2301        },
2302        DataTypeDefinition {
2303            id: DataType::DiscoveryTopology,
2304            name: "SEDSNET_DISCOVERY_TOPOLOGY",
2305            description: "",
2306            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2307            endpoints: &[DataEndpoint::Discovery],
2308            reliable: ReliableMode::Ordered,
2309            priority: 240,
2310            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2311        },
2312        DataTypeDefinition {
2313            id: DataType::DiscoverySchema,
2314            name: "SEDSNET_DISCOVERY_SCHEMA",
2315            description: "",
2316            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2317            endpoints: &[DataEndpoint::Discovery],
2318            reliable: ReliableMode::Ordered,
2319            priority: 241,
2320            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2321        },
2322        DataTypeDefinition {
2323            id: DataType::DiscoveryTopologyRequest,
2324            name: "SEDSNET_DISCOVERY_TOPOLOGY_REQUEST",
2325            description: "",
2326            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2327            endpoints: &[DataEndpoint::Discovery],
2328            reliable: ReliableMode::Ordered,
2329            priority: 242,
2330            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2331        },
2332        DataTypeDefinition {
2333            id: DataType::DiscoverySchemaRequest,
2334            name: "SEDSNET_DISCOVERY_SCHEMA_REQUEST",
2335            description: "",
2336            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2337            endpoints: &[DataEndpoint::Discovery],
2338            reliable: ReliableMode::Ordered,
2339            priority: 242,
2340            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2341        },
2342        DataTypeDefinition {
2343            id: DataType::ManagedVariableRequest,
2344            name: "SEDSNET_MANAGED_VARIABLE_REQUEST",
2345            description: "",
2346            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2347            endpoints: &[DataEndpoint::Discovery],
2348            reliable: ReliableMode::Ordered,
2349            priority: 243,
2350            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2351        },
2352        DataTypeDefinition {
2353            id: DataType::ManagedVariableValue,
2354            name: "SEDSNET_MANAGED_VARIABLE_VALUE",
2355            description: "",
2356            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2357            endpoints: &[DataEndpoint::Discovery],
2358            reliable: ReliableMode::Ordered,
2359            priority: 243,
2360            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2361        },
2362        DataTypeDefinition {
2363            id: DataType::DiscoveryLeave,
2364            name: "SEDSNET_DISCOVERY_LEAVE",
2365            description: "",
2366            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2367            endpoints: &[DataEndpoint::Discovery],
2368            reliable: ReliableMode::None,
2369            priority: 244,
2370            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2371        },
2372        DataTypeDefinition {
2373            id: DataType::DiscoveryLinkCapabilities,
2374            name: "SEDSNET_DISCOVERY_LINK_CAPABILITIES",
2375            description: "",
2376            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2377            endpoints: &[DataEndpoint::Discovery],
2378            reliable: ReliableMode::None,
2379            priority: 240,
2380            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2381        },
2382        DataTypeDefinition {
2383            id: DataType::DiscoveryAddress,
2384            name: "SEDSNET_DISCOVERY_ADDRESS",
2385            description: "",
2386            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2387            endpoints: &[DataEndpoint::Discovery],
2388            reliable: ReliableMode::Ordered,
2389            priority: 244,
2390            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2391        },
2392        DataTypeDefinition {
2393            id: DataType::P2pMessage,
2394            name: "SEDSNET_P2P_MESSAGE",
2395            description: "",
2396            element: MessageElement::Dynamic(MessageDataType::UInt8, MessageClass::Data),
2397            endpoints: &[DataEndpoint::Discovery],
2398            reliable: ReliableMode::Ordered,
2399            priority: 246,
2400            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2401        },
2402    ];
2403    #[cfg(all(feature = "serde", sedsnet_has_telemetry_config_json))]
2404    if let Ok(snapshot) = bundled_schema_snapshot() {
2405        for ty in snapshot.types {
2406            if !types
2407                .iter()
2408                .any(|known| known.id == ty.id || known.name == ty.name)
2409            {
2410                types.push(ty);
2411            }
2412        }
2413    }
2414    types
2415}
2416
2417#[cfg(not(feature = "std"))]
2418pub fn merge_schema_snapshot(_snapshot: RuntimeSchemaSnapshot) -> SchemaMergeReport {
2419    SchemaMergeReport {
2420        endpoints_added: 0,
2421        endpoints_replaced: 0,
2422        endpoints_kept: 0,
2423        types_added: 0,
2424        types_replaced: 0,
2425        types_kept: 0,
2426    }
2427}
2428
2429#[cfg(not(feature = "std"))]
2430pub fn merge_owned_schema_snapshot_with_budget(
2431    _snapshot: OwnedRuntimeSchemaSnapshot,
2432    _max_schema_bytes: usize,
2433) -> TelemetryResult<SchemaMergeReport> {
2434    Ok(SchemaMergeReport {
2435        endpoints_added: 0,
2436        endpoints_replaced: 0,
2437        endpoints_kept: 0,
2438        types_added: 0,
2439        types_replaced: 0,
2440        types_kept: 0,
2441    })
2442}
2443
2444#[cfg(not(feature = "std"))]
2445pub fn schema_fingerprint() -> u64 {
2446    0
2447}
2448
2449#[cfg(not(feature = "std"))]
2450pub fn schema_bytes_used() -> usize {
2451    known_endpoints()
2452        .iter()
2453        .map(|def| {
2454            size_of::<EndpointDefinition>()
2455                .saturating_add(def.name.len())
2456                .saturating_add(def.description.len())
2457        })
2458        .sum::<usize>()
2459        .saturating_add(
2460            known_data_types()
2461                .iter()
2462                .map(|def| {
2463                    size_of::<DataTypeDefinition>()
2464                        .saturating_add(def.name.len())
2465                        .saturating_add(def.description.len())
2466                        .saturating_add(
2467                            def.endpoints
2468                                .len()
2469                                .saturating_mul(size_of::<DataEndpoint>()),
2470                        )
2471                })
2472                .sum::<usize>(),
2473        )
2474}
2475
2476#[cfg(not(feature = "std"))]
2477pub fn endpoint_exists(ep: DataEndpoint) -> bool {
2478    known_endpoints().iter().any(|def| def.id == ep)
2479}
2480
2481#[cfg(not(feature = "std"))]
2482pub fn data_type_exists(ty: DataType) -> bool {
2483    known_data_types().iter().any(|def| def.id == ty)
2484}
2485
2486#[cfg(not(feature = "std"))]
2487pub fn endpoint_definition(ep: DataEndpoint) -> Option<EndpointDefinition> {
2488    known_endpoints().into_iter().find(|def| def.id == ep)
2489}
2490
2491#[cfg(not(feature = "std"))]
2492pub fn data_type_definition(ty: DataType) -> Option<DataTypeDefinition> {
2493    known_data_types().into_iter().find(|def| def.id == ty)
2494}
2495
2496#[cfg(not(feature = "std"))]
2497pub fn endpoint_definition_by_name(name: &str) -> Option<EndpointDefinition> {
2498    known_endpoints().into_iter().find(|def| def.name == name)
2499}
2500
2501#[cfg(not(feature = "std"))]
2502pub fn data_type_definition_by_name(name: &str) -> Option<DataTypeDefinition> {
2503    known_data_types().into_iter().find(|def| def.name == name)
2504}
2505
2506#[cfg(not(feature = "std"))]
2507pub fn remove_endpoint(_ep: DataEndpoint) -> TelemetryResult<bool> {
2508    Err(TelemetryError::BadArg)
2509}
2510
2511#[cfg(not(feature = "std"))]
2512pub fn remove_endpoint_by_name(_name: &str) -> TelemetryResult<bool> {
2513    Err(TelemetryError::BadArg)
2514}
2515
2516#[cfg(not(feature = "std"))]
2517pub fn remove_data_type(_ty: DataType) -> TelemetryResult<bool> {
2518    Err(TelemetryError::BadArg)
2519}
2520
2521#[cfg(not(feature = "std"))]
2522pub fn remove_data_type_by_name(_name: &str) -> TelemetryResult<bool> {
2523    Err(TelemetryError::BadArg)
2524}
2525
2526#[cfg(not(feature = "std"))]
2527pub fn get_endpoint_meta(endpoint_type: DataEndpoint) -> EndpointMeta {
2528    known_endpoints()
2529        .iter()
2530        .find(|def| def.id == endpoint_type)
2531        .map(|def| EndpointMeta {
2532            name: def.name,
2533            description: def.description,
2534            link_local_only: def.link_local_only,
2535        })
2536        .unwrap_or(EndpointMeta {
2537            name: "UNKNOWN_ENDPOINT",
2538            description: "",
2539            link_local_only: false,
2540        })
2541}
2542
2543#[cfg(not(feature = "std"))]
2544pub fn get_message_meta(data_type: DataType) -> MessageMeta {
2545    known_data_types()
2546        .iter()
2547        .find(|def| def.id == data_type)
2548        .map(|def| MessageMeta {
2549            name: def.name,
2550            description: def.description,
2551            element: def.element,
2552            endpoints: def.endpoints,
2553            reliable: def.reliable,
2554            priority: def.priority,
2555            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2556        })
2557        .unwrap_or(MessageMeta {
2558            name: "UNKNOWN_TYPE",
2559            description: "",
2560            element: MessageElement::Dynamic(MessageDataType::Binary, MessageClass::Data),
2561            endpoints: &[],
2562            reliable: ReliableMode::None,
2563            priority: 0,
2564            e2e_encryption: E2eEncryptionPolicy::PreferOff,
2565        })
2566}
2567
2568#[cfg(not(feature = "std"))]
2569pub fn max_endpoint_id() -> u32 {
2570    known_endpoints()
2571        .iter()
2572        .map(|def| def.id.as_u32())
2573        .max()
2574        .unwrap_or(DataEndpoint::TelemetryError.as_u32())
2575}
2576
2577#[cfg(not(feature = "std"))]
2578pub fn max_data_type_id() -> u32 {
2579    known_data_types()
2580        .iter()
2581        .map(|def| def.id.as_u32())
2582        .max()
2583        .unwrap_or(DataType::DiscoverySchema.as_u32())
2584}
2585
2586// -----------------------------------------------------------------------------
2587// Optional JSON seeding for std builds
2588// -----------------------------------------------------------------------------
2589
2590#[cfg(feature = "std")]
2591pub fn register_schema_json_str(json: &str) -> TelemetryResult<()> {
2592    register_schema_json_bytes(json.as_bytes())
2593}
2594
2595#[cfg(feature = "std")]
2596pub fn register_schema_json_bytes(json: &[u8]) -> TelemetryResult<()> {
2597    let cfg: JsonConfig =
2598        serde_json::from_slice(json).map_err(|_| TelemetryError::Unpack("schema json"))?;
2599    register_json_config(cfg, false)
2600}
2601
2602#[cfg(feature = "std")]
2603pub fn register_schema_json_file(path: impl AsRef<std::path::Path>) -> TelemetryResult<()> {
2604    let json = std::fs::read_to_string(path).map_err(|_| TelemetryError::Io("schema json file"))?;
2605    register_schema_json_str(&json)
2606}
2607
2608#[cfg(feature = "std")]
2609pub fn register_schema_json_path(path: &str) -> TelemetryResult<()> {
2610    register_schema_json_file(path)
2611}
2612
2613#[cfg(not(feature = "std"))]
2614pub fn register_schema_json_bytes(_json: &[u8]) -> TelemetryResult<()> {
2615    Err(TelemetryError::BadArg)
2616}
2617
2618#[cfg(feature = "serde")]
2619#[derive(serde::Deserialize)]
2620struct JsonConfig {
2621    endpoints: Vec<JsonEndpoint>,
2622    types: Vec<JsonType>,
2623}
2624
2625#[cfg(feature = "serde")]
2626#[derive(serde::Deserialize)]
2627struct JsonEndpoint {
2628    rust: Option<String>,
2629    name: String,
2630    #[serde(default, alias = "doc")]
2631    description: Option<String>,
2632    #[serde(default, alias = "link_local_only")]
2633    link_local_only: Option<bool>,
2634    #[serde(default, alias = "broadcast_mode")]
2635    broadcast_mode: Option<String>,
2636}
2637
2638#[cfg(feature = "serde")]
2639#[derive(serde::Deserialize)]
2640struct JsonType {
2641    rust: Option<String>,
2642    name: String,
2643    #[serde(default, alias = "doc")]
2644    description: Option<String>,
2645    class: String,
2646    element: JsonElement,
2647    endpoints: Vec<String>,
2648    #[serde(default)]
2649    reliable: Option<bool>,
2650    #[serde(default)]
2651    reliable_mode: Option<String>,
2652    #[serde(default)]
2653    priority: Option<u8>,
2654    #[serde(default)]
2655    e2e_encryption: Option<String>,
2656}
2657
2658fn parse_e2e_encryption_policy(raw: Option<&str>) -> TelemetryResult<E2eEncryptionPolicy> {
2659    match raw.unwrap_or("PreferOff") {
2660        "PreferOff" | "prefer_off" | "off" | "false" => Ok(E2eEncryptionPolicy::PreferOff),
2661        "PreferOn" | "prefer_on" | "preferred" | "true" => Ok(E2eEncryptionPolicy::PreferOn),
2662        "RequireOn" | "require_on" | "required" => Ok(E2eEncryptionPolicy::RequireOn),
2663        _ => Err(TelemetryError::BadArg),
2664    }
2665}
2666
2667#[cfg(feature = "serde")]
2668#[derive(serde::Deserialize)]
2669#[serde(tag = "kind")]
2670enum JsonElement {
2671    Static {
2672        data_type: String,
2673        count: Option<usize>,
2674    },
2675    Dynamic {
2676        data_type: String,
2677    },
2678}
2679
2680#[cfg(feature = "serde")]
2681fn json_config_to_snapshot(
2682    cfg: JsonConfig,
2683    link_local_overlay: bool,
2684    mut next_endpoint_id: u32,
2685    mut next_type_id: u32,
2686) -> TelemetryResult<RuntimeSchemaSnapshot> {
2687    let mut endpoint_ids: Vec<(String, DataEndpoint)> = Vec::new();
2688    let mut endpoints = Vec::with_capacity(cfg.endpoints.len());
2689    for ep in cfg.endpoints {
2690        let rust_name = ep.rust.clone().unwrap_or_else(|| ep.name.clone());
2691        let link_local = link_local_overlay
2692            || ep.link_local_only.unwrap_or(false)
2693            || matches!(ep.broadcast_mode.as_deref(), Some("Never"));
2694        let id = known_endpoint_compat_id(&rust_name).unwrap_or_else(|| {
2695            let id = DataEndpoint(next_endpoint_id);
2696            next_endpoint_id = next_endpoint_id.saturating_add(1);
2697            id
2698        });
2699        next_endpoint_id = next_endpoint_id.max(id.0.saturating_add(1));
2700        endpoints.push(EndpointDefinition {
2701            id,
2702            name: leak_str(ep.name),
2703            description: leak_str(ep.description.unwrap_or_default()),
2704            link_local_only: link_local,
2705        });
2706        endpoint_ids.push((rust_name, id));
2707    }
2708
2709    let mut types = Vec::with_capacity(cfg.types.len());
2710    for ty in cfg.types {
2711        let rust_name = ty.rust.clone().unwrap_or_else(|| ty.name.clone());
2712        let endpoints_for_type: Vec<DataEndpoint> = ty
2713            .endpoints
2714            .iter()
2715            .map(|name| {
2716                endpoint_ids
2717                    .iter()
2718                    .find(|(ep_name, _)| ep_name == name)
2719                    .map(|(_, id)| *id)
2720                    .ok_or(TelemetryError::BadArg)
2721            })
2722            .collect::<TelemetryResult<Vec<_>>>()?;
2723        let id = known_type_compat_id(&rust_name).unwrap_or_else(|| {
2724            let id = DataType(next_type_id);
2725            next_type_id = next_type_id.saturating_add(1);
2726            id
2727        });
2728        next_type_id = next_type_id.max(id.0.saturating_add(1));
2729        let class = parse_message_class(&ty.class)?;
2730        let element = match ty.element {
2731            JsonElement::Static { data_type, count } => MessageElement::Static(
2732                count.unwrap_or(1),
2733                parse_message_data_type(&data_type)?,
2734                class,
2735            ),
2736            JsonElement::Dynamic { data_type } => {
2737                MessageElement::Dynamic(parse_message_data_type(&data_type)?, class)
2738            }
2739        };
2740        let reliable = match ty.reliable_mode.as_deref() {
2741            Some("Ordered") => ReliableMode::Ordered,
2742            Some("Unordered") => ReliableMode::Unordered,
2743            Some("None") | None => {
2744                if ty.reliable.unwrap_or(false) {
2745                    ReliableMode::Ordered
2746                } else {
2747                    ReliableMode::None
2748                }
2749            }
2750            _ => return Err(TelemetryError::BadArg),
2751        };
2752        types.push(DataTypeDefinition {
2753            id,
2754            name: leak_str(ty.name),
2755            description: leak_str(ty.description.unwrap_or_default()),
2756            element,
2757            endpoints: leak_endpoints(endpoints_for_type),
2758            reliable,
2759            priority: ty.priority.unwrap_or(0),
2760            e2e_encryption: parse_e2e_encryption_policy(ty.e2e_encryption.as_deref())?,
2761        });
2762    }
2763    Ok(RuntimeSchemaSnapshot { endpoints, types })
2764}
2765
2766#[cfg(feature = "serde")]
2767pub fn schema_snapshot_from_json_bytes(json: &[u8]) -> TelemetryResult<RuntimeSchemaSnapshot> {
2768    let cfg: JsonConfig =
2769        serde_json::from_slice(json).map_err(|_| TelemetryError::Unpack("schema json"))?;
2770    json_config_to_snapshot(cfg, false, 100, 100)
2771}
2772
2773#[cfg(feature = "std")]
2774fn register_json_config(cfg: JsonConfig, link_local_overlay: bool) -> TelemetryResult<()> {
2775    let mut reg = registry().lock().expect("schema registry poisoned");
2776    register_json_config_into(&mut reg, cfg, link_local_overlay)
2777}
2778
2779#[cfg(feature = "std")]
2780fn register_json_config_into(
2781    reg: &mut Registry,
2782    cfg: JsonConfig,
2783    link_local_overlay: bool,
2784) -> TelemetryResult<()> {
2785    let snapshot = json_config_to_snapshot(
2786        cfg,
2787        link_local_overlay,
2788        reg.next_endpoint_id,
2789        reg.next_type_id,
2790    )?;
2791    register_schema_snapshot_into(reg, snapshot)
2792}
2793
2794#[cfg(feature = "std")]
2795fn register_schema_snapshot_into(
2796    reg: &mut Registry,
2797    snapshot: RuntimeSchemaSnapshot,
2798) -> TelemetryResult<()> {
2799    for endpoint in snapshot.endpoints {
2800        reg.register_endpoint_definition(endpoint)?;
2801    }
2802    for ty in snapshot.types {
2803        reg.register_type_definition(ty)?;
2804    }
2805    Ok(())
2806}
2807
2808#[cfg(feature = "serde")]
2809fn known_endpoint_compat_id(name: &str) -> Option<DataEndpoint> {
2810    match name {
2811        "SdCard" => Some(DataEndpoint(100)),
2812        "Radio" => Some(DataEndpoint(101)),
2813        "SoftwareBus" => Some(DataEndpoint(102)),
2814        _ => None,
2815    }
2816}
2817
2818#[cfg(feature = "serde")]
2819fn known_type_compat_id(name: &str) -> Option<DataType> {
2820    match name {
2821        "GpsData" => Some(DataType(100)),
2822        "ImuData" => Some(DataType(101)),
2823        "BatteryStatus" => Some(DataType(102)),
2824        "SystemStatus" => Some(DataType(103)),
2825        "BarometerData" => Some(DataType(104)),
2826        "MessageData" => Some(DataType(105)),
2827        "Heartbeat" => Some(DataType(106)),
2828        "IpcMessage" => Some(DataType(107)),
2829        _ => None,
2830    }
2831}
2832
2833#[cfg(feature = "serde")]
2834fn parse_message_class(s: &str) -> TelemetryResult<MessageClass> {
2835    match s {
2836        "Data" => Ok(MessageClass::Data),
2837        "Error" => Ok(MessageClass::Error),
2838        "Warning" => Ok(MessageClass::Warning),
2839        _ => Err(TelemetryError::BadArg),
2840    }
2841}
2842
2843#[cfg(feature = "serde")]
2844fn parse_message_data_type(s: &str) -> TelemetryResult<MessageDataType> {
2845    match s {
2846        "Float64" => Ok(MessageDataType::Float64),
2847        "Float32" => Ok(MessageDataType::Float32),
2848        "UInt8" => Ok(MessageDataType::UInt8),
2849        "UInt16" => Ok(MessageDataType::UInt16),
2850        "UInt32" => Ok(MessageDataType::UInt32),
2851        "UInt64" => Ok(MessageDataType::UInt64),
2852        "UInt128" => Ok(MessageDataType::UInt128),
2853        "Int8" => Ok(MessageDataType::Int8),
2854        "Int16" => Ok(MessageDataType::Int16),
2855        "Int32" => Ok(MessageDataType::Int32),
2856        "Int64" => Ok(MessageDataType::Int64),
2857        "Int128" => Ok(MessageDataType::Int128),
2858        "Bool" => Ok(MessageDataType::Bool),
2859        "String" => Ok(MessageDataType::String),
2860        "Binary" => Ok(MessageDataType::Binary),
2861        "NoData" => Ok(MessageDataType::NoData),
2862        _ => Err(TelemetryError::BadArg),
2863    }
2864}
2865
2866#[cfg(all(test, feature = "std"))]
2867pub(crate) fn seed_test_schema() {
2868    static SEEDED: OnceLock<()> = OnceLock::new();
2869    SEEDED.get_or_init(|| {
2870        let _ = register_schema_json_str(include_str!("../telemetry_config.test.json"));
2871        let ipc = include_str!("../telemetry_config.ipc.test.json");
2872        let cfg: JsonConfig = serde_json::from_str(ipc).expect("test ipc schema json");
2873        let _ = register_json_config(cfg, true);
2874    });
2875}