1#![cfg_attr(not(feature = "std"), no_std)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4#[cfg(all(not(feature = "std"), feature = "serde"))]
5extern crate alloc;
6
7#[cfg(all(not(feature = "std"), feature = "serde"))]
8use alloc::{string::String, vec::Vec};
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13pub const TRUST_RAW: u8 = 0x00;
15pub const TRUST_VERIFIED: u8 = 0x01;
16pub const TRUST_ANOMALY: u8 = 0x02;
17pub const TRUST_ENTERPRISE: u8 = 0xFF;
18
19pub const PREDICATE_NORMAL: u32 = 0x00000001;
20pub const PREDICATE_WARNING: u32 = 0x00000002;
21pub const PREDICATE_CRITICAL: u32 = 0x00000003;
22pub const PREDICATE_CONSISTENCY: u32 = 0x00000004;
23pub const PREDICATE_TRIANGULATED: u32 = 0x00000005;
24pub const PREDICATE_ANOMALY: u32 = 0x00000006;
25pub const PREDICATE_TREND_STABLE: u32 = 0x00000007;
26pub const PREDICATE_TREND_RISING_FAST: u32 = 0x00000008;
27pub const PREDICATE_SENSOR_STUCK_WARNING: u32 = 0x00000009;
28pub const PREDICATE_API_DATA_INCONSISTENCY: u32 = 0x0000000A;
29pub const PREDICATE_POTENTIAL_DATA_ANOMALY: u32 = 0x0000000B;
30
31pub const TELEMETRY_WATER_LEVEL_MM: u16 = 0x0001;
32pub const TELEMETRY_TEMPERATURE_C: u16 = 0x0002;
33pub const TELEMETRY_HUMIDITY_PERCENT: u16 = 0x0003;
34pub const TELEMETRY_PRESSURE_PA: u16 = 0x0004;
35pub const TELEMETRY_PRECIPITATION_MM: u16 = 0x0005;
36
37pub const HEADER_VERIFIED: u16 = 0x8000;
38pub const HEADER_SIGNED: u16 = 0x4000;
39pub const HEADER_ENCRYPTED: u16 = 0x2000;
40pub const HEADER_RAW_DATA: u16 = 0x0001;
41
42pub const TAG_HYDROLOGICAL: &str = "hydrological";
43pub const TAG_METEOROLOGICAL: &str = "meteorological";
44
45pub const ENTITY_EA_STATION: u32 = 0x00000001;
47pub const ENTITY_YR_NO_PRECIPITATION: u32 = 0x00000002;
48pub const ENTITY_OWM_WEATHER: u32 = 0x00000003;
49pub const VIRTUAL_STATION_ARMLEY: u32 = 0x00000004;
50pub const VIRTUAL_STATION_CROWN_POINT: u32 = 0x00000005;
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
54#[repr(C)]
55pub struct SemanticAtom {
56 pub entity_id: u32,
57 pub telemetry_info: u32,
58 pub predicate_id: u32,
59 pub value_mm: u32,
60 pub timestamp_us: u64,
61 pub tag_trust: u32,
62 pub pqc_anchor: u32,
63}
64
65impl SemanticAtom {
66 #[inline]
67 pub fn new(
68 entity_id: u32,
69 telemetry_type: u16,
70 predicate_id: u32,
71 value_mm: u32,
72 timestamp_us: u64,
73 tag_id: u32,
74 trust_level: u8,
75 ) -> Self {
76 let telemetry_info = ((telemetry_type as u32) << 16);
77 let tag_trust = ((tag_id & 0x00FFFFFF) << 8) | (trust_level as u32);
78
79 Self {
80 entity_id,
81 telemetry_info,
82 predicate_id,
83 value_mm,
84 timestamp_us,
85 tag_trust,
86 pqc_anchor: 0,
87 }
88 }
89
90 #[inline] pub fn telemetry_type(&self) -> u16 { (self.telemetry_info >> 16) as u16 }
91 #[inline] pub fn tag_id(&self) -> u32 { self.tag_trust >> 8 }
92 #[inline] pub fn trust_level(&self) -> u8 { (self.tag_trust & 0xFF) as u8 }
93 #[inline] pub fn set_pqc_anchor(&mut self, anchor: u32) { self.pqc_anchor = anchor; }
94
95 #[inline]
96 pub fn is_critical_or_warning(&self) -> bool {
97 matches!(self.predicate_id,
98 PREDICATE_WARNING | PREDICATE_CRITICAL |
99 PREDICATE_API_DATA_INCONSISTENCY | PREDICATE_POTENTIAL_DATA_ANOMALY
100 )
101 }
102
103 #[inline] pub fn get_value(&self) -> f64 { self.value_mm as f64 / 100.0 }
104
105 #[inline]
106 pub fn to_bytes(&self) -> [u8; 32] {
107 let mut bytes = [0u8; 32];
108 bytes[0..4].copy_from_slice(&self.entity_id.to_le_bytes());
109 bytes[4..8].copy_from_slice(&self.telemetry_info.to_le_bytes());
110 bytes[8..12].copy_from_slice(&self.predicate_id.to_le_bytes());
111 bytes[12..16].copy_from_slice(&self.value_mm.to_le_bytes());
112 bytes[16..24].copy_from_slice(&self.timestamp_us.to_le_bytes());
113 bytes[24..28].copy_from_slice(&self.tag_trust.to_le_bytes());
114 bytes[28..32].copy_from_slice(&self.pqc_anchor.to_le_bytes());
115 bytes
116 }
117
118 pub fn from_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
119 if bytes.len() != 32 { return Err("Invalid length"); }
120 Ok(Self {
121 entity_id: u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
122 telemetry_info: u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
123 predicate_id: u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
124 value_mm: u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
125 timestamp_us: u64::from_le_bytes([bytes[16], bytes[17], bytes[18], bytes[19], bytes[20], bytes[21], bytes[22], bytes[23]]),
126 tag_trust: u32::from_le_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]),
127 pqc_anchor: u32::from_le_bytes([bytes[28], bytes[29], bytes[30], bytes[31]]),
128 })
129 }
130
131 #[cfg(feature = "pqc")]
132 pub fn compute_hash(&self) -> [u8; 32] {
133 use sha2::{Sha256, Digest};
134 let mut hasher = Sha256::new();
135 hasher.update(self.to_bytes());
136 hasher.finalize().into()
137 }
138}
139
140#[derive(Debug, Clone, PartialEq)]
143#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
144pub enum AtomError {
145 InvalidAtom(String),
146 InvalidEntity(u32),
147 InvalidPredicate(u32),
148 PhysicsViolation(String),
149 InvalidTimestamp,
150 CryptographicError(String),
151 InvalidLength(usize),
152}
153
154impl core::fmt::Display for AtomError {
155 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
156 match self {
157 AtomError::InvalidAtom(msg) => write!(f, "Invalid atom: {}", msg),
158 AtomError::InvalidEntity(id) => write!(f, "Invalid entity ID: 0x{:08X}", id),
159 AtomError::InvalidPredicate(id) => write!(f, "Invalid predicate ID: 0x{:08X}", id),
160 AtomError::PhysicsViolation(msg) => write!(f, "Physics violation: {}", msg),
161 AtomError::InvalidTimestamp => write!(f, "Invalid timestamp"),
162 AtomError::CryptographicError(msg) => write!(f, "Cryptographic error: {}", msg),
163 AtomError::InvalidLength(len) => write!(f, "Invalid length: {} (expected 32)", len),
164 }
165 }
166}
167
168#[cfg(feature = "std")]
169impl std::error::Error for AtomError {}
170
171pub type BsaResult<T> = Result<T, AtomError>;
172
173#[cfg(feature = "serde")]
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct Entity {
178 pub id: u32,
179 pub name: String,
180 pub description: String,
181 pub source: Option<String>,
182}
183
184#[cfg(feature = "serde")]
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct Predicate {
187 pub id: u32,
188 pub name: String,
189 pub description: String,
190 pub tag: String,
191}
192
193#[cfg(feature = "serde")]
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct Config {
196 pub entities: Vec<Entity>,
197 pub predicates: Vec<Predicate>,
198}