Skip to main content

mabi_opcua/codec/
data_value.rs

1//! OPC UA DataValue binary encoding/decoding.
2//!
3//! OPC UA Part 6, Section 5.2.2.17 — DataValue encoding:
4//! - Encoding mask byte with flags for which fields are present
5//! - Followed by present fields in order
6
7use 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
15// DataValue encoding mask bits
16const 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; // mask byte
59        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        // Override timestamps if present in the wire data
118        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
135// =========================================================================
136// QualifiedName and LocalizedText encoding
137// =========================================================================
138
139use 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; // mask
170        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
185// =========================================================================
186// StatusCode encoding (it's a newtype around u32)
187// =========================================================================
188
189impl 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// =========================================================================
204// ExtensionObject encoding — used for service request/response wrapping
205// =========================================================================
206
207/// OPC UA ExtensionObject — wraps a service request or response body.
208///
209/// Encoding: NodeId (type_id) + encoding byte + body
210/// - encoding 0x00: no body
211/// - encoding 0x01: ByteString body (binary encoded)
212/// - encoding 0x02: XmlElement body
213#[derive(Debug, Clone)]
214pub struct ExtensionObject {
215    /// The NodeId of the type being wrapped.
216    pub type_id: crate::types::NodeId,
217    /// The raw encoded body (binary).
218    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); // Binary body
227                buf.put_i32_le(body.len() as i32);
228                buf.put_slice(body);
229            }
230            None => {
231                buf.put_u8(0x00); // No body
232            }
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                // XML body — decode as string, store as bytes
256                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// =========================================================================
270// DiagnosticInfo encoding
271// =========================================================================
272
273/// OPC UA DiagnosticInfo.
274#[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}