Skip to main content

zerodds_opcua_pubsub/binary/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! OPC-UA binary encoding (OPC-UA Foundation **Part 6** §5.2).
4//!
5//! This is the wire primitive the DDS-OPCUA gateway previously delegated
6//! to an external stack (open62541 / opcua-rs). UADP DataSetMessages
7//! (Part 14) carry their payload in exactly this encoding, so it is the
8//! foundation for the whole Pub/Sub stack — and it simultaneously lets
9//! the Client/Server gateway encode natively.
10//!
11//! The encoders/decoders operate directly on the `zerodds-opcua-gateway`
12//! type model (`NodeId`, `Variant`, `DataValue`, …) — there is no
13//! parallel type hierarchy.
14//!
15//! # Encoding rules (Part 6 §5.2.2)
16//!
17//! * Integers/floats: little-endian, no alignment padding.
18//! * `String`/`ByteString`/`XmlElement`: `Int32` length prefix, `-1`
19//!   denotes the null form, `0` the empty form, followed by the bytes.
20//! * Arrays: `Int32` length prefix (`-1` = null) followed by the
21//!   elements back-to-back.
22
23mod builtin;
24mod io;
25
26use alloc::string::String;
27use alloc::vec::Vec;
28
29pub(crate) use builtin::{builtin_type_from_value, decode_builtin_value};
30pub use io::{UaReader, UaWriter};
31
32use crate::error::{DecodeError, EncodeError};
33
34/// A value that can be serialised into the OPC-UA binary encoding.
35pub trait UaEncode {
36    /// Appends the binary form of `self` to `w`.
37    ///
38    /// Returns [`EncodeError::LengthOverflow`] if a contained
39    /// `String`/`ByteString`/array exceeds the `Int32` length ceiling
40    /// mandated by Part 6 §5.2.2.4.
41    fn encode(&self, w: &mut UaWriter) -> Result<(), EncodeError>;
42}
43
44/// A value that can be parsed from the OPC-UA binary encoding.
45pub trait UaDecode: Sized {
46    /// Reads one value of `Self` from `r`, advancing the cursor.
47    fn decode(r: &mut UaReader<'_>) -> Result<Self, DecodeError>;
48}
49
50/// Converts a buffer length to the `Int32` the wire format requires,
51/// rejecting values above `i32::MAX` (Part 6 §5.2.2.4).
52pub(crate) fn len_i32(what: &'static str, len: usize) -> Result<i32, EncodeError> {
53    i32::try_from(len).map_err(|_| EncodeError::LengthOverflow { what, len })
54}
55
56/// Converts a count to the `UInt16` several UADP headers use (field
57/// counts, DataSetMessage counts), rejecting values above `u16::MAX`.
58pub(crate) fn len_u16(what: &'static str, len: usize) -> Result<u16, EncodeError> {
59    u16::try_from(len).map_err(|_| EncodeError::LengthOverflow { what, len })
60}
61
62/// Writes a non-nullable UTF-8 string (`Int32` byte length + bytes).
63pub(crate) fn write_string(w: &mut UaWriter, s: &str) -> Result<(), EncodeError> {
64    w.write_i32(len_i32("String", s.len())?);
65    w.write_bytes(s.as_bytes());
66    Ok(())
67}
68
69/// Writes a non-nullable byte string (`Int32` length + bytes).
70pub(crate) fn write_byte_string(w: &mut UaWriter, b: &[u8]) -> Result<(), EncodeError> {
71    w.write_i32(len_i32("ByteString", b.len())?);
72    w.write_bytes(b);
73    Ok(())
74}
75
76/// Reads a non-nullable UTF-8 string; the null form (`-1`) maps to the
77/// empty string, matching the gateway model which uses a plain `String`
78/// for these fields.
79pub(crate) fn read_string(r: &mut UaReader<'_>) -> Result<String, DecodeError> {
80    Ok(read_opt_string(r)?.unwrap_or_default())
81}
82
83/// Reads an optional UTF-8 string; the null form (`-1`) maps to `None`.
84pub(crate) fn read_opt_string(r: &mut UaReader<'_>) -> Result<Option<String>, DecodeError> {
85    let len = r.read_i32()?;
86    if len < 0 {
87        return Ok(None);
88    }
89    let bytes = r.read_bytes(len as usize)?;
90    let s = core::str::from_utf8(bytes).map_err(|_| DecodeError::InvalidUtf8)?;
91    Ok(Some(String::from(s)))
92}
93
94/// Reads a byte string; the null form (`-1`) maps to an empty vector.
95pub(crate) fn read_byte_string(r: &mut UaReader<'_>) -> Result<Vec<u8>, DecodeError> {
96    let len = r.read_i32()?;
97    if len < 0 {
98        return Ok(Vec::new());
99    }
100    Ok(r.read_bytes(len as usize)?.to_vec())
101}
102
103/// Writes an `Int32`-length-prefixed array of encodable elements
104/// (Part 6 §5.2.5).
105pub(crate) fn write_array<T: UaEncode>(
106    w: &mut UaWriter,
107    items: &[T],
108    what: &'static str,
109) -> Result<(), EncodeError> {
110    w.write_i32(len_i32(what, items.len())?);
111    for it in items {
112        it.encode(w)?;
113    }
114    Ok(())
115}
116
117/// Reads an `Int32`-length-prefixed array (`-1` = null → empty).
118pub(crate) fn read_array<T: UaDecode>(r: &mut UaReader<'_>) -> Result<Vec<T>, DecodeError> {
119    let len = r.read_i32()?;
120    if len < 0 {
121        return Ok(Vec::new());
122    }
123    let mut out = Vec::with_capacity(len as usize);
124    for _ in 0..len {
125        out.push(T::decode(r)?);
126    }
127    Ok(out)
128}
129
130/// Writes an `Int32`-length-prefixed `UInt16` array.
131pub(crate) fn write_u16_array(w: &mut UaWriter, items: &[u16]) -> Result<(), EncodeError> {
132    w.write_i32(len_i32("UInt16 array", items.len())?);
133    for x in items {
134        w.write_u16(*x);
135    }
136    Ok(())
137}
138
139/// Reads an `Int32`-length-prefixed `UInt16` array (`-1` = null → empty).
140pub(crate) fn read_u16_array(r: &mut UaReader<'_>) -> Result<Vec<u16>, DecodeError> {
141    let len = r.read_i32()?;
142    if len < 0 {
143        return Ok(Vec::new());
144    }
145    let mut out = Vec::with_capacity(len as usize);
146    for _ in 0..len {
147        out.push(r.read_u16()?);
148    }
149    Ok(out)
150}
151
152/// Writes an `Int32`-length-prefixed `UInt32` array.
153pub(crate) fn write_u32_array(w: &mut UaWriter, items: &[u32]) -> Result<(), EncodeError> {
154    w.write_i32(len_i32("UInt32 array", items.len())?);
155    for x in items {
156        w.write_u32(*x);
157    }
158    Ok(())
159}
160
161/// Reads an `Int32`-length-prefixed `UInt32` array (`-1` = null → empty).
162pub(crate) fn read_u32_array(r: &mut UaReader<'_>) -> Result<Vec<u32>, DecodeError> {
163    let len = r.read_i32()?;
164    if len < 0 {
165        return Ok(Vec::new());
166    }
167    let mut out = Vec::with_capacity(len as usize);
168    for _ in 0..len {
169        out.push(r.read_u32()?);
170    }
171    Ok(out)
172}
173
174/// Writes an `Int32`-length-prefixed array of non-nullable UTF-8 strings.
175pub(crate) fn write_string_array(w: &mut UaWriter, items: &[String]) -> Result<(), EncodeError> {
176    w.write_i32(len_i32("String array", items.len())?);
177    for s in items {
178        write_string(w, s)?;
179    }
180    Ok(())
181}
182
183/// Reads an `Int32`-length-prefixed string array (`-1` = null → empty).
184pub(crate) fn read_string_array(r: &mut UaReader<'_>) -> Result<Vec<String>, DecodeError> {
185    let len = r.read_i32()?;
186    if len < 0 {
187        return Ok(Vec::new());
188    }
189    let mut out = Vec::with_capacity(len as usize);
190    for _ in 0..len {
191        out.push(read_string(r)?);
192    }
193    Ok(out)
194}
195
196/// Convenience: encode any [`UaEncode`] value to a fresh `Vec<u8>`.
197pub fn to_binary<T: UaEncode>(value: &T) -> Result<Vec<u8>, EncodeError> {
198    let mut w = UaWriter::new();
199    value.encode(&mut w)?;
200    Ok(w.into_vec())
201}
202
203/// Convenience: decode a [`UaDecode`] value from a byte slice.
204pub fn from_binary<T: UaDecode>(bytes: &[u8]) -> Result<T, DecodeError> {
205    let mut r = UaReader::new(bytes);
206    T::decode(&mut r)
207}