1use 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
20pub 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#[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#[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#[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}