Skip to main content

netget/server/bacnet/
codec.rs

1//! Unsegmented BACnet/IP services. Typed application values, no object storage.
2use crate::server::ics_support::number;
3use anyhow::{bail, ensure, Context, Result};
4use serde_json::{json, Value};
5pub const MAX_FRAME: usize = 480;
6pub const DEFAULT_DEVICE_ID: u32 = 1234;
7pub fn wrap(a: &[u8]) -> Vec<u8> {
8    let mut b = vec![0x81, 0x0a, 0, 0, 1, 0];
9    b.extend(a);
10    let n = b.len() as u16;
11    b[2..4].copy_from_slice(&n.to_be_bytes());
12    b
13}
14pub fn unwrap(b: &[u8]) -> Result<&[u8]> {
15    ensure!(
16        b.len() >= 7
17            && b.len() <= MAX_FRAME
18            && b[0] == 0x81
19            && matches!(b[1], 10 | 11)
20            && u16::from_be_bytes([b[2], b[3]]) as usize == b.len(),
21        "BVLC framing"
22    );
23    ensure!(
24        b[4] == 1 && matches!(b[5], 0 | 4),
25        "only local BACnet/IP NPDU supported"
26    );
27    Ok(&b[6..])
28}
29fn uint(n: u64) -> Vec<u8> {
30    let b = n.to_be_bytes();
31    b[b.iter().position(|v| *v != 0).unwrap_or(7)..].to_vec()
32}
33fn tag(n: u8, context: bool, data: &[u8]) -> Result<Vec<u8>> {
34    ensure!(n < 15 && data.len() <= 255, "tag bound");
35    let mut b = vec![
36        (n << 4) | if context { 8 } else { 0 } | if data.len() <= 4 { data.len() as u8 } else { 5 },
37    ];
38    if data.len() > 4 {
39        b.push(data.len() as u8);
40    }
41    b.extend(data);
42    Ok(b)
43}
44fn context(n: u8, value: u64) -> Result<Vec<u8>> {
45    tag(n, true, &uint(value))
46}
47struct Reader<'a> {
48    b: &'a [u8],
49    p: usize,
50}
51impl<'a> Reader<'a> {
52    fn value(&mut self, n: u8, ctx: bool) -> Result<&'a [u8]> {
53        ensure!(self.p < self.b.len(), "missing tag");
54        let t = self.b[self.p];
55        self.p += 1;
56        ensure!(
57            t >> 4 == n && (t & 8 != 0) == ctx && t & 7 <= 5,
58            "unexpected tag"
59        );
60        let len = if t & 7 == 5 {
61            ensure!(self.p < self.b.len(), "missing extended length");
62            let l = self.b[self.p] as usize;
63            self.p += 1;
64            l
65        } else {
66            (t & 7) as usize
67        };
68        ensure!(self.p + len <= self.b.len(), "truncated tag");
69        let b = &self.b[self.p..self.p + len];
70        self.p += len;
71        Ok(b)
72    }
73    fn num(&mut self, n: u8, ctx: bool) -> Result<u64> {
74        let b = self.value(n, ctx)?;
75        ensure!(!b.is_empty() && b.len() <= 4, "integer width");
76        Ok(b.iter().fold(0, |v, b| (v << 8) | *b as u64))
77    }
78    fn marker(&mut self, b: u8) -> Result<()> {
79        ensure!(self.b.get(self.p) == Some(&b), "opening/closing tag");
80        self.p += 1;
81        Ok(())
82    }
83}
84pub fn encode_value(v: &Value) -> Result<Vec<u8>> {
85    match v["value_type"].as_str() {
86        Some("null") => Ok(vec![0]),
87        Some("boolean") => Ok(vec![
88            0x10 | u8::from(v["value"].as_bool().context("boolean")?),
89        ]),
90        Some("unsigned") => tag(2, false, &uint(number(v, "value", u32::MAX as u64)?)),
91        Some("enumerated") => tag(9, false, &uint(number(v, "value", u32::MAX as u64)?)),
92        Some("signed") => {
93            let n = v["value"].as_i64().context("signed integer")?;
94            ensure!(i32::try_from(n).is_ok(), "signed range");
95            tag(3, false, &(n as i32).to_be_bytes())
96        }
97        Some("real") => {
98            let n = v["value"].as_f64().context("real")?;
99            ensure!(n.is_finite() && (n as f32).is_finite(), "real range");
100            tag(4, false, &(n as f32).to_be_bytes())
101        }
102        Some("string") => {
103            let s = v["value"].as_str().context("string")?;
104            ensure!(s.len() <= 200, "string bound");
105            let mut b = vec![0];
106            b.extend(s.as_bytes());
107            tag(7, false, &b)
108        }
109        Some("object_identifier") => {
110            let t = number(&v["value"], "object_type", 1023)?;
111            let i = number(&v["value"], "instance", 4194303)?;
112            tag(12, false, &(((t << 22) | i) as u32).to_be_bytes())
113        }
114        _ => bail!("unsupported application value"),
115    }
116}
117fn decode_value(r: &mut Reader) -> Result<Value> {
118    let t = *r.b.get(r.p).context("missing application tag")?;
119    ensure!(t & 8 == 0, "expected application value");
120    if t >> 4 == 1 {
121        ensure!(t & 7 <= 1, "boolean tag");
122        r.p += 1;
123        return Ok(json!({"value_type":"boolean","value":t&1!=0}));
124    }
125    let b = r.value(t >> 4, false)?;
126    let n = || -> Result<u32> {
127        ensure!(!b.is_empty() && b.len() <= 4, "integer width");
128        Ok(b.iter().fold(0, |v, b| (v << 8) | *b as u32))
129    };
130    let (kind, value) = match t >> 4 {
131        0 => {
132            ensure!(b.is_empty(), "null length");
133            ("null", Value::Null)
134        }
135        2 => ("unsigned", json!(n()?)),
136        9 => ("enumerated", json!(n()?)),
137        3 => {
138            let mut out = if b.first().is_some_and(|n| n & 128 != 0) {
139                [255; 4]
140            } else {
141                [0; 4]
142            };
143            ensure!(!b.is_empty() && b.len() <= 4, "signed width");
144            out[4 - b.len()..].copy_from_slice(b);
145            ("signed", json!(i32::from_be_bytes(out)))
146        }
147        4 => {
148            ensure!(b.len() == 4, "real width");
149            let n = f32::from_be_bytes(b.try_into()?);
150            ensure!(n.is_finite(), "nonfinite real");
151            ("real", json!(n))
152        }
153        7 => {
154            ensure!(b.first() == Some(&0), "UTF8 character set required");
155            ("string", json!(std::str::from_utf8(&b[1..])?))
156        }
157        12 => {
158            ensure!(b.len() == 4, "object id width");
159            let v = n()?;
160            (
161                "object_identifier",
162                json!({"object_type":v>>22,"instance":v&4194303}),
163            )
164        }
165        _ => bail!("unsupported application tag"),
166    };
167    Ok(json!({"value_type":kind,"value":value}))
168}
169fn fields(r: &mut Reader) -> Result<Value> {
170    let o = r.num(0, true)? as u32;
171    let property = r.num(1, true)?;
172    let mut v = json!({"object_type":o>>22,"instance":o&4194303,"property":property});
173    if r.b.get(r.p).is_some_and(|v| v >> 4 == 2) {
174        v["array_index"] = json!(r.num(2, true)?);
175    }
176    Ok(v)
177}
178fn encode_fields(v: &Value) -> Result<Vec<u8>> {
179    let t = number(v, "object_type", 1023)?;
180    let i = number(v, "instance", 4194303)?;
181    let mut b = tag(0, true, &(((t << 22) | i) as u32).to_be_bytes())?;
182    b.extend(context(1, number(v, "property", 4194303)?)?);
183    if v.get("array_index").is_some() {
184        b.extend(context(2, number(v, "array_index", u32::MAX as u64)?)?);
185    }
186    Ok(b)
187}
188pub fn receive(a: &[u8], device: u32) -> Result<(Vec<u8>, Option<Value>)> {
189    ensure!(!a.is_empty(), "APDU empty");
190    if a[0] == 0x10 {
191        ensure!(a.len() >= 2, "unconfirmed service");
192        if a[1] != 8 {
193            return Ok((vec![], None));
194        }
195        let mut r = Reader { b: &a[2..], p: 0 };
196        if !r.b.is_empty() {
197            let low = r.num(0, true)?;
198            let high = r.num(1, true)?;
199            ensure!(r.p == r.b.len() && low <= high, "WhoIs range");
200            if !(low..=high).contains(&(device as u64)) {
201                return Ok((vec![], None));
202            }
203        }
204        let mut b = vec![0x10, 0];
205        b.extend(tag(12, false, &((8u32 << 22) | device).to_be_bytes())?);
206        b.extend(tag(2, false, &uint(MAX_FRAME as u64))?);
207        b.extend(tag(9, false, &[3])?);
208        b.extend(tag(2, false, &[0])?);
209        return Ok((wrap(&b), None));
210    }
211    ensure!(a[0] >> 4 == 0 && a.len() >= 4, "confirmed request framing");
212    let invoke = a[2];
213    if a[0] & 8 != 0 {
214        return Ok((wrap(&[0x71, invoke, 4]), None));
215    }
216    let service = a[3];
217    if !matches!(service, 12 | 15) {
218        return Ok((wrap(&[0x60, invoke, 9]), None));
219    }
220    let mut r = Reader { b: &a[4..], p: 0 };
221    let mut v = fields(&mut r)?;
222    v["operation"] = json!(if service == 12 { "read" } else { "write" });
223    v["invoke_id"] = json!(invoke);
224    v["service"] = json!(service);
225    if service == 15 {
226        r.marker(0x3e)?;
227        let value = decode_value(&mut r)?;
228        r.marker(0x3f)?;
229        v["value_type"] = value["value_type"].clone();
230        v["value"] = value["value"].clone();
231        if r.p < r.b.len() {
232            v["priority"] = json!(r.num(4, true)?);
233            ensure!(
234                v["priority"]
235                    .as_u64()
236                    .is_some_and(|n| (1..=16).contains(&n)),
237                "priority range"
238            );
239        }
240    }
241    ensure!(r.p == r.b.len(), "trailing request data");
242    Ok((vec![], Some(v)))
243}
244pub fn answer(r: &Value, a: Option<&Value>) -> Result<Vec<u8>> {
245    let invoke = number(r, "invoke_id", 255)? as u8;
246    let service = number(r, "service", 255)? as u8;
247    let Some(a) = a.filter(|a| a["type"] == "bacnet_reply") else {
248        return error(invoke, service, 2, 40);
249    };
250    if a.get("error_code").is_some() {
251        return error(
252            invoke,
253            service,
254            number(a, "error_class", 7)?,
255            number(a, "error_code", 65535)?,
256        );
257    }
258    if service == 15 {
259        if a["accepted"] == true {
260            return Ok(wrap(&[0x20, invoke, service]));
261        }
262        return error(invoke, service, 2, 40);
263    }
264    let mut b = vec![0x30, invoke, service];
265    b.extend(encode_fields(r)?);
266    b.push(0x3e);
267    b.extend(encode_value(a)?);
268    b.push(0x3f);
269    ensure!(b.len() + 6 <= MAX_FRAME, "response bound");
270    Ok(wrap(&b))
271}
272fn error(invoke: u8, service: u8, class: u64, code: u64) -> Result<Vec<u8>> {
273    let mut b = vec![0x50, invoke, service];
274    b.extend(tag(9, false, &uint(class))?);
275    b.extend(tag(9, false, &uint(code))?);
276    Ok(wrap(&b))
277}
278pub fn validate(v: &Value) -> Result<()> {
279    match v["type"].as_str() {
280        Some("bacnet_discover") => {}
281        Some("bacnet_read") => {
282            encode_fields(v)?;
283        }
284        Some("bacnet_write") => {
285            encode_fields(v)?;
286            encode_value(v)?;
287            if v.get("priority").is_some() {
288                ensure!(
289                    (1..=16).contains(&number(v, "priority", 16)?),
290                    "priority range"
291                );
292            }
293        }
294        _ => bail!("unknown BACnet action"),
295    };
296    Ok(())
297}
298pub fn request(v: &Value, invoke: u8) -> Result<Vec<u8>> {
299    validate(v)?;
300    if v["type"] == "bacnet_discover" {
301        return Ok(wrap(&[0x10, 8]));
302    }
303    let service = if v["type"] == "bacnet_read" { 12 } else { 15 };
304    let mut b = vec![0, 5, invoke, service];
305    b.extend(encode_fields(v)?);
306    if service == 15 {
307        b.push(0x3e);
308        b.extend(encode_value(v)?);
309        b.push(0x3f);
310        if v.get("priority").is_some() {
311            b.extend(context(4, number(v, "priority", 16)?)?);
312        }
313    }
314    Ok(wrap(&b))
315}
316pub fn response(a: &[u8], v: &Value, invoke: u8) -> Result<Value> {
317    if v["type"] == "bacnet_discover" {
318        ensure!(a.len() >= 2 && a[..2] == [0x10, 0], "expected IAm");
319        let mut r = Reader { b: &a[2..], p: 0 };
320        let o = r.num(12, false)?;
321        ensure!(o >> 22 == 8, "IAm device id");
322        let max = r.num(2, false)?;
323        let seg = r.num(9, false)?;
324        let vendor = r.num(2, false)?;
325        ensure!(r.p == r.b.len(), "IAm trailing data");
326        return Ok(
327            json!({"success":true,"device_id":o&4194303,"max_apdu":max,"segmentation":seg,"vendor_id":vendor}),
328        );
329    }
330    ensure!(a.len() >= 3 && a[1] == invoke, "invoke correlation");
331    let service = if v["type"] == "bacnet_read" { 12 } else { 15 };
332    match a[0] >> 4 {
333        2 => {
334            ensure!(
335                a.len() == 3 && a[2] == service && service == 15,
336                "simple ack mismatch"
337            );
338            Ok(json!({"success":true}))
339        }
340        3 => {
341            ensure!(
342                a[0] & 8 == 0 && a[2] == 12 && service == 12,
343                "complex ack mismatch"
344            );
345            let mut r = Reader { b: &a[3..], p: 0 };
346            let f = fields(&mut r)?;
347            for k in ["object_type", "instance", "property", "array_index"] {
348                ensure!(f[k] == v[k], "property correlation");
349            }
350            r.marker(0x3e)?;
351            let mut out = decode_value(&mut r)?;
352            r.marker(0x3f)?;
353            ensure!(r.p == r.b.len(), "ack trailing data");
354            out["success"] = json!(true);
355            Ok(out)
356        }
357        5 => {
358            ensure!(a[2] == service, "error service correlation");
359            let mut r = Reader { b: &a[3..], p: 0 };
360            let class = r.num(9, false)?;
361            let code = r.num(9, false)?;
362            ensure!(r.p == r.b.len(), "error trailing data");
363            Ok(json!({"success":false,"error_class":class,"error_code":code}))
364        }
365        6 | 7 => Ok(json!({"success":false,"reason":a[2]})),
366        _ => bail!("unexpected APDU"),
367    }
368}