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