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