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