mabi_opcua/codec/
data_value.rs1use bytes::{BufMut, Bytes, BytesMut};
8use chrono::{DateTime, Utc};
9
10use crate::codec::encoder::BinaryEncodable;
11use crate::codec::decoder::BinaryDecodable;
12use crate::error::OpcUaResult;
13use crate::types::{DataValue, StatusCode, Variant};
14
15const HAS_VALUE: u8 = 0x01;
17const HAS_STATUS: u8 = 0x02;
18const HAS_SOURCE_TIMESTAMP: u8 = 0x04;
19const HAS_SERVER_TIMESTAMP: u8 = 0x08;
20const HAS_SOURCE_PICOSECONDS: u8 = 0x10;
21const HAS_SERVER_PICOSECONDS: u8 = 0x20;
22
23impl BinaryEncodable for DataValue {
24 fn encode(&self, buf: &mut BytesMut) -> OpcUaResult<()> {
25 let mut mask: u8 = 0;
26 if self.value().is_some() { mask |= HAS_VALUE; }
27 if self.status().raw() != 0 { mask |= HAS_STATUS; }
28 if self.source_timestamp().is_some() { mask |= HAS_SOURCE_TIMESTAMP; }
29 if self.server_timestamp().is_some() { mask |= HAS_SERVER_TIMESTAMP; }
30 if self.source_picoseconds() != 0 { mask |= HAS_SOURCE_PICOSECONDS; }
31 if self.server_picoseconds() != 0 { mask |= HAS_SERVER_PICOSECONDS; }
32
33 buf.put_u8(mask);
34
35 if let Some(v) = self.value() {
36 v.encode(buf)?;
37 }
38 if (mask & HAS_STATUS) != 0 {
39 buf.put_u32_le(self.status().raw());
40 }
41 if let Some(ts) = self.source_timestamp() {
42 ts.encode(buf)?;
43 }
44 if (mask & HAS_SOURCE_PICOSECONDS) != 0 {
45 buf.put_u16_le(self.source_picoseconds());
46 }
47 if let Some(ts) = self.server_timestamp() {
48 ts.encode(buf)?;
49 }
50 if (mask & HAS_SERVER_PICOSECONDS) != 0 {
51 buf.put_u16_le(self.server_picoseconds());
52 }
53
54 Ok(())
55 }
56
57 fn encoded_size(&self) -> usize {
58 let mut size = 1; if let Some(v) = self.value() { size += v.encoded_size(); }
60 if self.status().raw() != 0 { size += 4; }
61 if self.source_timestamp().is_some() { size += 8; }
62 if self.source_picoseconds() != 0 { size += 2; }
63 if self.server_timestamp().is_some() { size += 8; }
64 if self.server_picoseconds() != 0 { size += 2; }
65 size
66 }
67}
68
69impl BinaryDecodable for DataValue {
70 fn decode(buf: &mut Bytes) -> OpcUaResult<Self> {
71 let mask = u8::decode(buf)?;
72
73 let value = if (mask & HAS_VALUE) != 0 {
74 Some(Variant::decode(buf)?)
75 } else {
76 None
77 };
78
79 let status = if (mask & HAS_STATUS) != 0 {
80 StatusCode::from_raw(u32::decode(buf)?)
81 } else {
82 StatusCode::GOOD
83 };
84
85 let source_timestamp = if (mask & HAS_SOURCE_TIMESTAMP) != 0 {
86 Some(DateTime::<Utc>::decode(buf)?)
87 } else {
88 None
89 };
90
91 let source_picoseconds = if (mask & HAS_SOURCE_PICOSECONDS) != 0 {
92 u16::decode(buf)?
93 } else {
94 0
95 };
96
97 let server_timestamp = if (mask & HAS_SERVER_TIMESTAMP) != 0 {
98 Some(DateTime::<Utc>::decode(buf)?)
99 } else {
100 None
101 };
102
103 let server_picoseconds = if (mask & HAS_SERVER_PICOSECONDS) != 0 {
104 u16::decode(buf)?
105 } else {
106 0
107 };
108
109 let mut dv = if let Some(value) = value {
110 DataValue::with_status(value, status)
111 } else {
112 let mut dv = DataValue::null();
113 dv.set_status(status);
114 dv
115 };
116
117 if let Some(ts) = source_timestamp {
119 dv.set_source_timestamp(ts);
120 }
121 if source_picoseconds != 0 {
122 dv.set_source_picoseconds(source_picoseconds);
123 }
124 if let Some(ts) = server_timestamp {
125 dv.set_server_timestamp(ts);
126 }
127 if server_picoseconds != 0 {
128 dv.set_server_picoseconds(server_picoseconds);
129 }
130
131 Ok(dv)
132 }
133}
134
135use crate::nodes::{QualifiedName, LocalizedText};
140
141impl BinaryEncodable for QualifiedName {
142 fn encode(&self, buf: &mut BytesMut) -> OpcUaResult<()> {
143 self.namespace_index.encode(buf)?;
144 self.name.encode(buf)?;
145 Ok(())
146 }
147 fn encoded_size(&self) -> usize { 2 + 4 + self.name.len() }
148}
149
150impl BinaryDecodable for QualifiedName {
151 fn decode(buf: &mut Bytes) -> OpcUaResult<Self> {
152 let namespace_index = u16::decode(buf)?;
153 let name = String::decode(buf)?;
154 Ok(QualifiedName::new(namespace_index, name))
155 }
156}
157
158impl BinaryEncodable for LocalizedText {
159 fn encode(&self, buf: &mut BytesMut) -> OpcUaResult<()> {
160 let mut mask: u8 = 0;
161 if !self.locale.is_empty() { mask |= 0x01; }
162 if !self.text.is_empty() { mask |= 0x02; }
163 buf.put_u8(mask);
164 if (mask & 0x01) != 0 { self.locale.encode(buf)?; }
165 if (mask & 0x02) != 0 { self.text.encode(buf)?; }
166 Ok(())
167 }
168 fn encoded_size(&self) -> usize {
169 let mut size = 1; if !self.locale.is_empty() { size += 4 + self.locale.len(); }
171 if !self.text.is_empty() { size += 4 + self.text.len(); }
172 size
173 }
174}
175
176impl BinaryDecodable for LocalizedText {
177 fn decode(buf: &mut Bytes) -> OpcUaResult<Self> {
178 let mask = u8::decode(buf)?;
179 let locale = if (mask & 0x01) != 0 { String::decode(buf)? } else { String::new() };
180 let text = if (mask & 0x02) != 0 { String::decode(buf)? } else { String::new() };
181 Ok(LocalizedText::new(locale, text))
182 }
183}
184
185impl BinaryEncodable for StatusCode {
190 fn encode(&self, buf: &mut BytesMut) -> OpcUaResult<()> {
191 buf.put_u32_le(self.raw());
192 Ok(())
193 }
194 fn encoded_size(&self) -> usize { 4 }
195}
196
197impl BinaryDecodable for StatusCode {
198 fn decode(buf: &mut Bytes) -> OpcUaResult<Self> {
199 Ok(StatusCode::from_raw(u32::decode(buf)?))
200 }
201}
202
203#[derive(Debug, Clone)]
214pub struct ExtensionObject {
215 pub type_id: crate::types::NodeId,
217 pub body: Option<Vec<u8>>,
219}
220
221impl BinaryEncodable for ExtensionObject {
222 fn encode(&self, buf: &mut BytesMut) -> OpcUaResult<()> {
223 self.type_id.encode(buf)?;
224 match &self.body {
225 Some(body) => {
226 buf.put_u8(0x01); buf.put_i32_le(body.len() as i32);
228 buf.put_slice(body);
229 }
230 None => {
231 buf.put_u8(0x00); }
233 }
234 Ok(())
235 }
236 fn encoded_size(&self) -> usize {
237 self.type_id.encoded_size() + 1 + match &self.body {
238 Some(body) => 4 + body.len(),
239 None => 0,
240 }
241 }
242}
243
244impl BinaryDecodable for ExtensionObject {
245 fn decode(buf: &mut Bytes) -> OpcUaResult<Self> {
246 let type_id = crate::types::NodeId::decode(buf)?;
247 let encoding = u8::decode(buf)?;
248 let body = match encoding {
249 0x00 => None,
250 0x01 => {
251 let body_bytes = Vec::<u8>::decode(buf)?;
252 Some(body_bytes)
253 }
254 0x02 => {
255 let xml = String::decode(buf)?;
257 Some(xml.into_bytes())
258 }
259 _ => {
260 return Err(crate::error::OpcUaError::Codec(
261 format!("Unknown ExtensionObject encoding: 0x{:02X}", encoding),
262 ));
263 }
264 };
265 Ok(ExtensionObject { type_id, body })
266 }
267}
268
269#[derive(Debug, Clone, Default)]
275pub struct DiagnosticInfo {
276 pub symbolic_id: Option<i32>,
277 pub namespace_uri: Option<i32>,
278 pub locale: Option<i32>,
279 pub localized_text: Option<i32>,
280 pub additional_info: Option<String>,
281 pub inner_status_code: Option<StatusCode>,
282 pub inner_diagnostic_info: Option<Box<DiagnosticInfo>>,
283}
284
285impl BinaryEncodable for DiagnosticInfo {
286 fn encode(&self, buf: &mut BytesMut) -> OpcUaResult<()> {
287 let mut mask: u8 = 0;
288 if self.symbolic_id.is_some() { mask |= 0x01; }
289 if self.namespace_uri.is_some() { mask |= 0x02; }
290 if self.localized_text.is_some() { mask |= 0x04; }
291 if self.locale.is_some() { mask |= 0x08; }
292 if self.additional_info.is_some() { mask |= 0x10; }
293 if self.inner_status_code.is_some() { mask |= 0x20; }
294 if self.inner_diagnostic_info.is_some() { mask |= 0x40; }
295
296 buf.put_u8(mask);
297 if let Some(v) = self.symbolic_id { v.encode(buf)?; }
298 if let Some(v) = self.namespace_uri { v.encode(buf)?; }
299 if let Some(v) = self.localized_text { v.encode(buf)?; }
300 if let Some(v) = self.locale { v.encode(buf)?; }
301 if let Some(v) = &self.additional_info { v.encode(buf)?; }
302 if let Some(v) = &self.inner_status_code { v.encode(buf)?; }
303 if let Some(v) = &self.inner_diagnostic_info { v.encode(buf)?; }
304 Ok(())
305 }
306 fn encoded_size(&self) -> usize {
307 let mut size = 1;
308 if self.symbolic_id.is_some() { size += 4; }
309 if self.namespace_uri.is_some() { size += 4; }
310 if self.localized_text.is_some() { size += 4; }
311 if self.locale.is_some() { size += 4; }
312 if let Some(s) = &self.additional_info { size += 4 + s.len(); }
313 if self.inner_status_code.is_some() { size += 4; }
314 if let Some(d) = &self.inner_diagnostic_info { size += d.encoded_size(); }
315 size
316 }
317}
318
319impl BinaryDecodable for DiagnosticInfo {
320 fn decode(buf: &mut Bytes) -> OpcUaResult<Self> {
321 let mask = u8::decode(buf)?;
322 Ok(DiagnosticInfo {
323 symbolic_id: if (mask & 0x01) != 0 { Some(i32::decode(buf)?) } else { None },
324 namespace_uri: if (mask & 0x02) != 0 { Some(i32::decode(buf)?) } else { None },
325 localized_text: if (mask & 0x04) != 0 { Some(i32::decode(buf)?) } else { None },
326 locale: if (mask & 0x08) != 0 { Some(i32::decode(buf)?) } else { None },
327 additional_info: if (mask & 0x10) != 0 { Some(String::decode(buf)?) } else { None },
328 inner_status_code: if (mask & 0x20) != 0 { Some(StatusCode::decode(buf)?) } else { None },
329 inner_diagnostic_info: if (mask & 0x40) != 0 { Some(Box::new(DiagnosticInfo::decode(buf)?)) } else { None },
330 })
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337
338 fn roundtrip_dv(dv: &DataValue) -> DataValue {
339 let mut buf = BytesMut::new();
340 dv.encode(&mut buf).unwrap();
341 let mut data = buf.freeze();
342 DataValue::decode(&mut data).unwrap()
343 }
344
345 #[test]
346 fn test_null_datavalue() {
347 let dv = DataValue::null();
348 let result = roundtrip_dv(&dv);
349 assert!(result.value().is_none());
350 }
351
352 #[test]
353 fn test_datavalue_with_value() {
354 let dv = DataValue::new(Variant::Double(42.0));
355 let result = roundtrip_dv(&dv);
356 assert_eq!(result.value(), Some(&Variant::Double(42.0)));
357 }
358
359 #[test]
360 fn test_extension_object_roundtrip() {
361 let eo = ExtensionObject {
362 type_id: crate::types::NodeId::numeric(0, 461),
363 body: Some(vec![1, 2, 3, 4]),
364 };
365 let mut buf = BytesMut::new();
366 eo.encode(&mut buf).unwrap();
367 let mut data = buf.freeze();
368 let result = ExtensionObject::decode(&mut data).unwrap();
369 assert_eq!(result.type_id, eo.type_id);
370 assert_eq!(result.body, eo.body);
371 }
372
373 #[test]
374 fn test_qualified_name_roundtrip() {
375 let qn = QualifiedName::new(2, "Temperature");
376 let mut buf = BytesMut::new();
377 qn.encode(&mut buf).unwrap();
378 let mut data = buf.freeze();
379 let result = QualifiedName::decode(&mut data).unwrap();
380 assert_eq!(result.namespace_index, 2);
381 assert_eq!(result.name, "Temperature");
382 }
383
384 #[test]
385 fn test_localized_text_roundtrip() {
386 let lt = LocalizedText::new("en", "Hello");
387 let mut buf = BytesMut::new();
388 lt.encode(&mut buf).unwrap();
389 let mut data = buf.freeze();
390 let result = LocalizedText::decode(&mut data).unwrap();
391 assert_eq!(result.locale, "en");
392 assert_eq!(result.text, "Hello");
393 }
394
395 #[test]
396 fn test_diagnostic_info_roundtrip() {
397 let di = DiagnosticInfo {
398 symbolic_id: Some(1),
399 additional_info: Some("test".into()),
400 ..Default::default()
401 };
402 let mut buf = BytesMut::new();
403 di.encode(&mut buf).unwrap();
404 let mut data = buf.freeze();
405 let result = DiagnosticInfo::decode(&mut data).unwrap();
406 assert_eq!(result.symbolic_id, Some(1));
407 assert_eq!(result.additional_info.as_deref(), Some("test"));
408 }
409}