opcua_types/
byte_string.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2024 Adam Lock
4
5//! Contains the implementation of `ByteString`.
6
7use std::{
8    convert::TryFrom,
9    io::{Read, Write},
10};
11
12use base64::{engine::general_purpose::STANDARD, Engine};
13
14use crate::{
15    encoding::{process_decode_io_result, process_encode_io_result, write_i32, EncodingResult},
16    read_i32, DecodingOptions, Error, Guid, OutOfRange, SimpleBinaryDecodable,
17    SimpleBinaryEncodable, UaNullable,
18};
19
20/// A sequence of octets.
21#[derive(Eq, PartialEq, Debug, Clone, Hash)]
22pub struct ByteString {
23    /// Raw inner byte string values as an array of bytes.
24    pub value: Option<Vec<u8>>,
25}
26
27impl AsRef<[u8]> for ByteString {
28    fn as_ref(&self) -> &[u8] {
29        if self.value.is_none() {
30            &[]
31        } else {
32            self.value.as_ref().unwrap()
33        }
34    }
35}
36
37impl UaNullable for ByteString {
38    fn is_ua_null(&self) -> bool {
39        self.is_null()
40    }
41}
42
43#[cfg(feature = "json")]
44mod json {
45    use std::io::{Read, Write};
46
47    use crate::{json::*, Error};
48
49    use super::ByteString;
50
51    impl JsonEncodable for ByteString {
52        fn encode(
53            &self,
54            stream: &mut JsonStreamWriter<&mut dyn Write>,
55            _ctx: &crate::json::Context<'_>,
56        ) -> crate::EncodingResult<()> {
57            if self.value.is_some() {
58                stream.string_value(&self.as_base64())?;
59            } else {
60                stream.null_value()?;
61            }
62            Ok(())
63        }
64    }
65
66    impl JsonDecodable for ByteString {
67        fn decode(
68            stream: &mut JsonStreamReader<&mut dyn Read>,
69            _ctx: &Context<'_>,
70        ) -> crate::EncodingResult<Self> {
71            match stream.peek()? {
72                ValueType::String => Ok(Self::from_base64_ignore_whitespace(stream.next_string()?)
73                    .ok_or_else(|| Error::decoding("Cannot decode base64 bytestring"))?),
74                _ => {
75                    stream.next_null()?;
76                    Ok(Self::null())
77                }
78            }
79        }
80    }
81}
82
83#[cfg(feature = "xml")]
84mod xml {
85    use crate::xml::*;
86    use std::io::{Read, Write};
87
88    use super::ByteString;
89
90    impl XmlType for ByteString {
91        const TAG: &'static str = "ByteString";
92    }
93
94    impl XmlEncodable for ByteString {
95        fn encode(
96            &self,
97            writer: &mut XmlStreamWriter<&mut dyn Write>,
98            _context: &Context<'_>,
99        ) -> EncodingResult<()> {
100            if self.value.is_some() {
101                writer.write_text(&self.as_base64())?;
102            }
103            Ok(())
104        }
105    }
106
107    impl XmlDecodable for ByteString {
108        fn decode(
109            read: &mut XmlStreamReader<&mut dyn Read>,
110            _context: &Context<'_>,
111        ) -> Result<Self, Error> {
112            let s = read.consume_as_text()?;
113            if s.is_empty() {
114                Ok(ByteString::null())
115            } else {
116                Ok(ByteString::from_base64_ignore_whitespace(s)
117                    .ok_or_else(|| Error::decoding("Cannot decode base64 bytestring"))?)
118            }
119        }
120    }
121}
122
123impl SimpleBinaryEncodable for ByteString {
124    fn byte_len(&self) -> usize {
125        // Length plus the actual length of bytes (if not null)
126        4 + match &self.value {
127            Some(v) => v.len(),
128            None => 0,
129        }
130    }
131
132    fn encode<S: Write + ?Sized>(&self, stream: &mut S) -> EncodingResult<()> {
133        // Strings are uncoded as UTF8 chars preceded by an Int32 length. A -1 indicates a null string
134        if self.value.is_none() {
135            write_i32(stream, -1)
136        } else {
137            let value = self.value.as_ref().unwrap();
138            write_i32(stream, value.len() as i32)?;
139            process_encode_io_result(stream.write_all(value))
140        }
141    }
142}
143
144impl SimpleBinaryDecodable for ByteString {
145    fn decode<S: Read + ?Sized>(
146        stream: &mut S,
147        decoding_options: &DecodingOptions,
148    ) -> EncodingResult<Self> {
149        let len = read_i32(stream)?;
150        // Null string?
151        if len == -1 {
152            Ok(ByteString::null())
153        } else if len < -1 {
154            Err(Error::decoding(format!(
155                "ByteString buf length is a negative number {len}"
156            )))
157        } else if len as usize > decoding_options.max_byte_string_length {
158            Err(Error::decoding(format!(
159                "ByteString length {} exceeds decoding limit {}",
160                len, decoding_options.max_byte_string_length
161            )))
162        } else {
163            // Create a buffer filled with zeroes and read the byte string over the top
164            let mut buf: Vec<u8> = vec![0u8; len as usize];
165            process_decode_io_result(stream.read_exact(&mut buf))?;
166            Ok(ByteString { value: Some(buf) })
167        }
168    }
169}
170
171impl<'a, T> From<&'a T> for ByteString
172where
173    T: AsRef<[u8]> + ?Sized,
174{
175    fn from(value: &'a T) -> Self {
176        Self::from(value.as_ref().to_vec())
177    }
178}
179
180impl From<Vec<u8>> for ByteString {
181    fn from(value: Vec<u8>) -> Self {
182        // Empty bytes will be treated as Some([])
183        ByteString { value: Some(value) }
184    }
185}
186
187impl From<Guid> for ByteString {
188    fn from(value: Guid) -> Self {
189        ByteString::from(value.as_bytes().to_vec())
190    }
191}
192
193impl TryFrom<&ByteString> for Guid {
194    type Error = ();
195
196    fn try_from(value: &ByteString) -> Result<Self, Self::Error> {
197        if value.is_null_or_empty() {
198            Err(())
199        } else {
200            let bytes = value.as_ref();
201            if bytes.len() != 16 {
202                Err(())
203            } else {
204                let mut guid = [0u8; 16];
205                guid.copy_from_slice(bytes);
206                Ok(Guid::from_bytes(guid))
207            }
208        }
209    }
210}
211
212impl From<ByteString> for String {
213    fn from(value: ByteString) -> Self {
214        value.as_base64()
215    }
216}
217
218impl Default for ByteString {
219    fn default() -> Self {
220        ByteString::null()
221    }
222}
223
224impl ByteString {
225    /// Create a null string (not the same as an empty string)
226    pub fn null() -> ByteString {
227        ByteString { value: None }
228    }
229
230    /// Test if the string is null
231    pub fn is_null(&self) -> bool {
232        self.value.is_none()
233    }
234
235    /// Test if the bytestring has an empty value (not the same as null)
236    pub fn is_empty(&self) -> bool {
237        if let Some(v) = &self.value {
238            v.is_empty()
239        } else {
240            false
241        }
242    }
243
244    /// Test if the string is null or empty
245    pub fn is_null_or_empty(&self) -> bool {
246        self.is_null() || self.is_empty()
247    }
248
249    /// Creates a byte string from a base64 encoded string
250    pub fn from_base64(data: &str) -> Option<ByteString> {
251        if let Ok(bytes) = STANDARD.decode(data) {
252            Some(Self::from(bytes))
253        } else {
254            None
255        }
256    }
257
258    /// Creates a byte string from a base64 encoded string, ignoring whitespace.
259    pub fn from_base64_ignore_whitespace(mut data: String) -> Option<ByteString> {
260        data.retain(|c| !c.is_whitespace());
261        if let Ok(bytes) = STANDARD.decode(&data) {
262            Some(Self::from(bytes))
263        } else {
264            None
265        }
266    }
267
268    /// Encodes the bytestring as a base64 encoded string
269    pub fn as_base64(&self) -> String {
270        // Base64 encodes the byte string so it can be represented as a string
271        if let Some(ref value) = self.value {
272            STANDARD.encode(value)
273        } else {
274            STANDARD.encode("")
275        }
276    }
277
278    /// This function is meant for use with NumericRange. It creates a substring from this string
279    /// from min up to and inclusive of max. Note that min must have an index within the string
280    /// but max is allowed to be beyond the end in which case the remainder of the string is
281    /// returned (see docs for NumericRange).
282    pub fn substring(&self, min: usize, max: usize) -> Result<ByteString, OutOfRange> {
283        if let Some(ref v) = self.value {
284            if min >= v.len() {
285                Err(OutOfRange)
286            } else {
287                let max = if max >= v.len() { v.len() - 1 } else { max };
288                let v = v[min..=max].to_vec();
289                Ok(ByteString::from(v))
290            }
291        } else {
292            Err(OutOfRange)
293        }
294    }
295}
296
297#[test]
298fn bytestring_null() {
299    let v = ByteString::null();
300    assert!(v.is_null());
301}
302
303#[test]
304fn bytestring_empty() {
305    let v = ByteString::from(&[]);
306    assert!(!v.is_null());
307    assert!(v.is_null_or_empty());
308    assert!(v.is_empty());
309}
310
311#[test]
312fn bytestring_bytes() {
313    let a = [0x1u8, 0x2u8, 0x3u8, 0x4u8];
314    let v = ByteString::from(&a);
315    assert!(!v.is_null());
316    assert!(!v.is_empty());
317    assert_eq!(v.value.as_ref().unwrap(), &a);
318}
319
320#[test]
321fn bytestring_substring() {
322    let a = [0x1u8, 0x2u8, 0x3u8, 0x4u8];
323    let v = ByteString::from(&a);
324    let v2 = v.substring(2, 10000).unwrap();
325    let a2 = v2.value.as_ref().unwrap().as_slice();
326    assert_eq!(a2, &a[2..]);
327
328    let v2 = v.substring(2, 2).unwrap();
329    let a2 = v2.value.as_ref().unwrap().as_slice();
330    assert_eq!(a2, &a[2..3]);
331
332    let v2 = v.substring(0, 2000).unwrap();
333    assert_eq!(v, v2);
334    assert_eq!(v2.value.as_ref().unwrap(), &a);
335
336    assert!(v.substring(4, 10000).is_err());
337    assert!(ByteString::null().substring(0, 0).is_err());
338}