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