Skip to main content

sedsnet/
config.rs

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