bacnet_encoding/primitives/
view.rs1use bacnet_types::error::{DecodeError, DecodingKind, Error};
5use bacnet_types::primitives::{Date, ObjectIdentifier, Time};
6
7use super::{
8 charset, decode_bit_string_ref, decode_double, decode_real, decode_signed, decode_unsigned,
9 fixed_length_label, invalid_utf8, unknown_charset,
10};
11use crate::tags::{self, Tag, TagClass, app_tag};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub struct CharacterStringRef<'a> {
36 contents: &'a [u8],
39}
40
41impl<'a> CharacterStringRef<'a> {
42 pub fn decode(contents: &'a [u8]) -> Result<Self, Error> {
47 check_character_string(contents)?;
48 Ok(Self { contents })
49 }
50
51 pub(super) fn unchecked(contents: &'a [u8]) -> Self {
53 Self { contents }
54 }
55
56 pub fn charset(&self) -> u8 {
58 self.contents.first().copied().unwrap_or(charset::UTF8)
59 }
60
61 pub fn bytes(&self) -> &'a [u8] {
64 self.contents.get(1..).unwrap_or_default()
65 }
66
67 pub fn contents(&self) -> &'a [u8] {
69 self.contents
70 }
71
72 pub fn as_str(&self) -> Option<&'a str> {
74 if self.charset() != charset::UTF8 {
75 return None;
76 }
77 core::str::from_utf8(self.bytes()).ok()
78 }
79}
80
81pub(super) fn check_character_string(contents: &[u8]) -> Result<(), Error> {
94 let Some((&charset_id, payload)) = contents.split_first() else {
95 return Err(Error::decoding(
96 0,
97 "CharacterString requires at least 1 byte for charset",
98 ));
99 };
100 match charset_id {
101 charset::UTF8 => {
102 core::str::from_utf8(payload).map_err(invalid_utf8)?;
103 }
104 charset::IBM_MICROSOFT_DBCS if payload.len() < 2 => {
105 return Err(Error::decoding(
106 1,
107 "DBCS CharacterString requires a two-octet code page",
108 ));
109 }
110 charset::UCS2 => {
111 let (characters, rest) = payload.as_chunks::<2>();
112 if !rest.is_empty() {
113 return Err(Error::decoding(1, "UCS-2 data must have even length"));
114 }
115 let not_scalar = |c: &[u8; 2]| char::from_u32(u16::from_be_bytes(*c).into()).is_none();
116 if let Some(index) = characters.iter().position(not_scalar) {
117 return Err(Error::decoding(1 + index * 2, "invalid UCS-2 code point"));
118 }
119 }
120 charset::UCS4 => {
121 let (characters, rest) = payload.as_chunks::<4>();
122 if !rest.is_empty() {
123 return Err(Error::decoding(
124 1,
125 "UCS-4 CharacterString length must be a multiple of four",
126 ));
127 }
128 let not_scalar = |c: &[u8; 4]| char::from_u32(u32::from_be_bytes(*c)).is_none();
129 if let Some(index) = characters.iter().position(not_scalar) {
130 return Err(Error::decoding(1 + index * 4, "invalid UCS-4 code point"));
131 }
132 }
133 charset::IBM_MICROSOFT_DBCS | charset::JIS_X_0208 | charset::ISO_8859_1 => {}
134 other => return Err(unknown_charset(other)),
135 }
136 Ok(())
137}
138
139#[derive(Debug, Clone, Copy, PartialEq)]
145pub enum ApplicationValueRef<'a> {
146 Null,
148 Boolean(bool),
150 Unsigned(u64),
152 Signed(i32),
154 Real(f32),
156 Double(f64),
158 OctetString(&'a [u8]),
160 CharacterString(CharacterStringRef<'a>),
162 BitString {
164 unused_bits: u8,
166 data: &'a [u8],
168 },
169 Enumerated(u32),
171 Date(Date),
173 Time(Time),
175 ObjectIdentifier(ObjectIdentifier),
177}
178
179pub fn decode_application_value_ref(
206 data: &[u8],
207 offset: usize,
208) -> Result<(ApplicationValueRef<'_>, usize), Error> {
209 let (tag, content_start) = tags::decode_tag(data, offset)?;
210 if tag.class == TagClass::Context {
211 return Err(Error::invalid_tag(
212 offset,
213 "expected an application-tagged value, found a context tag",
214 ));
215 }
216 let (value, end) = decode_application_primitive::<true>(data, offset, tag, content_start)?;
217 if let ApplicationValueRef::CharacterString(text) = value {
218 let checked =
219 CharacterStringRef::decode(text.contents()).map_err(rebased(content_start))?;
220 return Ok((ApplicationValueRef::CharacterString(checked), end));
221 }
222 Ok((value, end))
223}
224
225pub(super) fn rebased(base: usize) -> impl Fn(Error) -> Error {
228 move |error| match error {
229 Error::Decoding(mut refused) => {
230 refused.offset = refused.offset.saturating_add(base);
231 refused.into()
232 }
233 other => other,
234 }
235}
236
237pub(super) fn decode_application_primitive<const IN_DATA: bool>(
244 data: &[u8],
245 offset: usize,
246 tag: Tag,
247 content_start: usize,
248) -> Result<(ApplicationValueRef<'_>, usize), Error> {
249 if tag.is_opening || tag.is_closing {
250 return Err(Error::invalid_tag(offset, "unexpected opening/closing tag"));
251 }
252 if tag.number == app_tag::NULL && tag.length != 0 {
253 return Err(Error::decoding(
254 offset,
255 "application NULL must have no contents",
256 ));
257 }
258 if tag.number == app_tag::BOOLEAN && tag.length > 1 {
259 return Err(Error::decoding(
260 offset,
261 "application BOOLEAN L/V/T must be 0 or 1",
262 ));
263 }
264 let content_end = content_start
265 .checked_add(tag.length as usize)
266 .ok_or_else(|| Error::decoding(content_start, "length overflow"))?;
267 if tag.number == app_tag::BOOLEAN {
268 return Ok((ApplicationValueRef::Boolean(tag.length != 0), content_start));
269 }
270 if let Some((label, octets)) = fixed_length_label(tag.number)
273 && tag.length != octets
274 {
275 return Err(
276 DecodeError::new(DecodingKind::InvalidEncoding, offset, label)
277 .with_value(tag.length.into())
278 .into(),
279 );
280 }
281 if data.len() < content_end {
282 return Err(Error::buffer_too_short(content_end, data.len()));
283 }
284 let content = &data[content_start..content_end];
285 let at = |error| {
286 if IN_DATA {
287 rebased(content_start)(error)
288 } else {
289 error
290 }
291 };
292 let value = match tag.number {
293 app_tag::NULL => ApplicationValueRef::Null,
294 app_tag::UNSIGNED => ApplicationValueRef::Unsigned(decode_unsigned(content).map_err(at)?),
295 app_tag::SIGNED => ApplicationValueRef::Signed(decode_signed(content).map_err(at)?),
296 app_tag::REAL => ApplicationValueRef::Real(decode_real(content).map_err(at)?),
297 app_tag::DOUBLE => ApplicationValueRef::Double(decode_double(content).map_err(at)?),
298 app_tag::OCTET_STRING => ApplicationValueRef::OctetString(content),
299 app_tag::CHARACTER_STRING => {
300 ApplicationValueRef::CharacterString(CharacterStringRef::unchecked(content))
301 }
302 app_tag::BIT_STRING => {
303 let (unused_bits, data) = decode_bit_string_ref(content).map_err(at)?;
304 ApplicationValueRef::BitString { unused_bits, data }
305 }
306 app_tag::ENUMERATED => {
307 let value = u32::try_from(decode_unsigned(content).map_err(at)?)
308 .map_err(|_| Error::out_of_range(content_start, "ENUMERATED exceeds u32"))?;
309 ApplicationValueRef::Enumerated(value)
310 }
311 app_tag::DATE => ApplicationValueRef::Date(Date::decode(content).map_err(at)?),
312 app_tag::TIME => ApplicationValueRef::Time(Time::decode(content).map_err(at)?),
313 app_tag::OBJECT_IDENTIFIER => {
314 ApplicationValueRef::ObjectIdentifier(ObjectIdentifier::decode(content).map_err(at)?)
315 }
316 other => {
317 return Err(DecodeError::new(
318 DecodingKind::InvalidTag,
319 offset,
320 "unknown application tag number {}",
321 )
322 .with_value(other.into())
323 .into());
324 }
325 };
326 Ok((value, content_end))
327}
328
329#[cfg(test)]
330mod tests;