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