1use std::ops::Range;
15
16use bacnet_encoding::apdu::{Apdu, decode_apdu};
17use bacnet_encoding::primitives::decode_application_value;
18use bacnet_encoding::tags::{MAX_CONTEXT_NESTING_DEPTH, TagClass, decode_tag};
19use bacnet_npdu::{NetworkPriority, Npdu as NpduView};
20use bacnet_types::primitives::PropertyValue;
21use bytes::Bytes;
22
23use super::frame::{Framing, Link, Npdu, kebab, link};
24
25#[path = "service.rs"]
26mod service;
27
28use service::Params;
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct Field {
33 pub label: String,
35 pub range: Range<usize>,
38 pub children: Vec<Field>,
40 pub device_text: bool,
43}
44
45impl Field {
46 fn leaf(label: impl Into<String>, range: Range<usize>) -> Self {
47 Self {
48 label: label.into(),
49 range,
50 children: Vec::new(),
51 device_text: false,
52 }
53 }
54
55 fn node(label: impl Into<String>, range: Range<usize>, children: Vec<Self>) -> Self {
56 Self {
57 label: label.into(),
58 range,
59 children,
60 device_text: false,
61 }
62 }
63
64 fn relocate(&mut self, range: &Range<usize>) {
67 self.range = range.clone();
68 for child in &mut self.children {
69 child.relocate(range);
70 }
71 }
72}
73
74fn hex(octets: &[u8]) -> String {
75 octets.iter().map(|b| format!("{b:02X}")).collect()
76}
77
78pub fn decode_tree(framing: Framing, frame: &[u8]) -> Vec<Field> {
80 let link = link(framing, frame);
81 let npdu_start = match &link.npdu {
82 Some(Npdu::At(range)) => range.start,
83 Some(Npdu::Encoded { wire, .. }) => wire.start,
84 None => frame.len(),
85 };
86 let mut fields = vec![match framing {
87 Framing::Bip => bvll(frame, npdu_start, &link),
88 Framing::Bip6 => bvll6(frame, npdu_start, &link),
89 Framing::Sc => sc(frame, npdu_start, &link),
90 Framing::Mstp => mstp(frame, &link),
91 }];
92 match link.npdu {
93 Some(Npdu::At(range)) => fields.extend(npdu(&frame[range.clone()], range.start)),
94 Some(Npdu::Encoded { wire, data }) => {
95 let mut decoded = npdu(&data, 0);
96 for field in &mut decoded {
97 field.relocate(&wire);
98 }
99 fields.extend(decoded);
100 }
101 None => {}
102 }
103 fields
104}
105
106pub fn decode_npdu_tree(npdu_octets: &[u8]) -> Vec<Field> {
109 npdu(npdu_octets, 0)
110}
111
112pub fn decode_apdu_tree(apdu_octets: &[u8]) -> Field {
116 apdu(apdu_octets, 0)
117}
118
119fn header(name: String, range: Range<usize>, mut kids: Vec<Field>, link: &Link) -> Field {
121 if let Some(problem) = &link.problem {
122 kids.push(Field::leaf(problem.clone(), range.clone()));
123 }
124 Field::node(name, range, kids)
125}
126
127fn bvll(frame: &[u8], npdu_start: usize, link: &Link) -> Field {
128 let mut kids = Vec::new();
129 if frame.len() >= 4 {
130 kids.push(Field::leaf(
131 format!("Type: 0x{:02X} (B/IP)", frame[0]),
132 0..1,
133 ));
134 kids.push(Field::leaf(
135 format!("Function: {} (0x{:02X})", link.name, frame[1]),
136 1..2,
137 ));
138 let length = u16::from_be_bytes([frame[2], frame[3]]);
139 kids.push(Field::leaf(format!("Length: {length}"), 2..4));
140 let rest = &frame[4..npdu_start.max(4)];
141 match (frame[1], rest) {
142 (0x04, [a, b, c, d, hi, lo, ..]) => kids.push(Field::leaf(
143 format!(
144 "Original source: {a}.{b}.{c}.{d}:{}",
145 u16::from_be_bytes([*hi, *lo])
146 ),
147 4..10,
148 )),
149 (0x00, [hi, lo]) => {
150 let code = u16::from_be_bytes([*hi, *lo]);
151 let name = bacnet_transport::bvll::BvlcResultCode::from_raw(code);
152 kids.push(Field::leaf(
153 format!("Result code: {} (0x{code:04X})", kebab(name)),
154 4..6,
155 ));
156 return Field::node(format!("BVLL: {}", link.name), 0..npdu_start, kids);
158 }
159 (0x05, [hi, lo]) => kids.push(Field::leaf(
160 format!("Time-to-live: {} s", u16::from_be_bytes([*hi, *lo])),
161 4..6,
162 )),
163 (_, []) => {}
164 (_, rest) => kids.push(Field::leaf(
165 format!("Payload: {} octets", rest.len()),
166 4..4 + rest.len(),
167 )),
168 }
169 }
170 header(format!("BVLL: {}", link.name), 0..npdu_start, kids, link)
171}
172
173fn bvll6(frame: &[u8], npdu_start: usize, link: &Link) -> Field {
174 let mut kids = Vec::new();
175 if frame.len() >= 4 {
176 kids.push(Field::leaf(
177 format!("Type: 0x{:02X} (B/IPv6)", frame[0]),
178 0..1,
179 ));
180 kids.push(Field::leaf(
181 format!("Function: {} (0x{:02X})", link.name, frame[1]),
182 1..2,
183 ));
184 let length = u16::from_be_bytes([frame[2], frame[3]]);
185 kids.push(Field::leaf(format!("Length: {length}"), 2..4));
186 }
187 if let (Some(source), true) = (&link.source, frame.len() >= 7) {
188 kids.push(Field::leaf(format!("Source VMAC: {source}"), 4..7));
189 }
190 if let (Some(destination), true) = (&link.destination, frame.len() >= 10) {
191 kids.push(Field::leaf(
192 format!("Destination VMAC: {destination}"),
193 7..10,
194 ));
195 }
196 let used = kids.last().map_or(4, |field| field.range.end);
197 if npdu_start > used {
198 kids.push(Field::leaf(
199 format!("Function data: {} octets", npdu_start - used),
200 used..npdu_start,
201 ));
202 }
203 header(format!("BVLL6: {}", link.name), 0..npdu_start, kids, link)
204}
205
206fn sc(frame: &[u8], npdu_start: usize, link: &Link) -> Field {
207 let mut kids = Vec::new();
208 if frame.len() >= 4 {
209 kids.push(Field::leaf(
210 format!("Function: {} (0x{:02X})", link.name, frame[0]),
211 0..1,
212 ));
213 let control = frame[1];
214 let flags: Vec<&str> = [
215 (0x08, "originating VMAC"),
216 (0x04, "destination VMAC"),
217 (0x02, "destination options"),
218 (0x01, "data options"),
219 ]
220 .iter()
221 .filter(|(bit, _)| control & bit != 0)
222 .map(|(_, name)| *name)
223 .collect();
224 kids.push(Field::leaf(
225 format!("Control: 0x{control:02X} ({})", flags.join(", ")),
226 1..2,
227 ));
228 let id = u16::from_be_bytes([frame[2], frame[3]]);
229 kids.push(Field::leaf(format!("Message ID: {id}"), 2..4));
230 let mut at = 4;
231 for (bit, name, vmac) in [
232 (0x08, "Originating VMAC", &link.source),
233 (0x04, "Destination VMAC", &link.destination),
234 ] {
235 if let (true, Some(vmac)) = (control & bit != 0, vmac) {
236 kids.push(Field::leaf(format!("{name}: {vmac}"), at..at + 6));
237 at += 6;
238 }
239 }
240 let payload = if link.npdu.is_some() {
241 npdu_start
242 } else {
243 frame.len()
244 };
245 if control & 0x03 != 0 && link.npdu.is_some() && npdu_start > at {
246 kids.push(Field::leaf("Header options", at..npdu_start));
247 } else if payload > at {
248 kids.push(Field::leaf(
249 format!("Options and payload: {} octets", payload - at),
250 at..payload,
251 ));
252 }
253 }
254 header(format!("BVLC-SC: {}", link.name), 0..npdu_start, kids, link)
255}
256
257fn mstp(frame: &[u8], link: &Link) -> Field {
258 let mut kids = Vec::new();
259 if frame.len() >= 8 {
260 let length = usize::from(u16::from_be_bytes([frame[5], frame[6]]));
261 kids.push(Field::leaf("Preamble: 55 FF", 0..2));
262 kids.push(Field::leaf(
263 format!("Frame type: {} ({})", link.name, frame[2]),
264 2..3,
265 ));
266 kids.push(Field::leaf(format!("Destination: {}", frame[3]), 3..4));
267 kids.push(Field::leaf(format!("Source: {}", frame[4]), 4..5));
268 kids.push(Field::leaf(format!("Length: {length}"), 5..7));
269 kids.push(Field::leaf(format!("Header CRC: {:02X}", frame[7]), 7..8));
270 let tail = if frame[2] >= 32 && frame[2] < 128 {
271 5
272 } else {
273 2
274 };
275 if length > 0 && frame.len() >= 8 + tail {
276 let end = frame.len() - tail;
277 kids.push(Field::leaf(format!("Data: {} octets", end - 8), 8..end));
278 let crc = if tail == 5 {
279 "Encoded CRC-32K"
280 } else {
281 "Data CRC"
282 };
283 kids.push(Field::leaf(
284 format!("{crc}: {}", hex(&frame[end..])),
285 end..frame.len(),
286 ));
287 }
288 }
289 header(format!("MS/TP: {}", link.name), 0..frame.len(), kids, link)
290}
291
292fn npdu(npdu: &[u8], base: usize) -> Vec<Field> {
294 let at = |range: Range<usize>| base + range.start..base + range.end;
295 let view = match NpduView::decode(npdu) {
296 Ok(view) => view,
297 Err(error) => {
298 return vec![Field::leaf(
299 format!("NPDU: malformed ({error})"),
300 at(0..npdu.len()),
301 )];
302 }
303 };
304 let control = npdu.get(1).copied().unwrap_or_default();
305 let mut flags = Vec::new();
306 for (bit, name) in [
307 (0x80, "network message"),
308 (0x20, "DNET"),
309 (0x08, "SNET"),
310 (0x04, "expecting reply"),
311 ] {
312 if control & bit != 0 {
313 flags.push(name.to_string());
314 }
315 }
316 let priority = match view.priority {
317 NetworkPriority::Normal => "normal",
318 NetworkPriority::Urgent => "urgent",
319 NetworkPriority::CriticalEquipment => "critical-equipment",
320 NetworkPriority::LifeSafety => "life-safety",
321 };
322 flags.push(format!("priority {priority}"));
323 let mut kids = vec![
324 Field::leaf(format!("Version: {}", npdu[0]), at(0..1)),
325 Field::leaf(
326 format!("Control: 0x{control:02X} ({})", flags.join(", ")),
327 at(1..2),
328 ),
329 ];
330 let mut pos = 2;
331 for (name, address) in [("D", view.destination), ("S", view.source)] {
332 if let Some(address) = address {
333 let len = address.mac.len();
334 kids.push(Field::leaf(
335 format!("{name}NET: {}", address.network),
336 at(pos..pos + 2),
337 ));
338 kids.push(Field::leaf(
339 format!("{name}LEN: {len}"),
340 at(pos + 2..pos + 3),
341 ));
342 if len > 0 {
343 kids.push(Field::leaf(
344 format!("{name}ADR: {}", hex(address.mac)),
345 at(pos + 3..pos + 3 + len),
346 ));
347 }
348 pos += 3 + len;
349 }
350 }
351 if view.destination.is_some() {
352 kids.push(Field::leaf(
353 format!("Hop count: {}", view.hop_count),
354 at(pos..pos + 1),
355 ));
356 pos += 1;
357 }
358 if let Some(kind) = view.message_type {
359 kids.push(Field::leaf(
360 format!("Message type: {}", kebab(kind)),
361 at(pos..pos + 1),
362 ));
363 pos += 1;
364 if let Some(vendor) = view.vendor_id {
365 kids.push(Field::leaf(
366 format!("Vendor ID: {vendor}"),
367 at(pos..pos + 2),
368 ));
369 }
370 }
371 let payload = view.payload_offset;
372 let mut fields = vec![Field::node("NPDU", at(0..payload), kids)];
373 let rest = at(payload..npdu.len());
374 match view.message_type {
375 Some(kind) if !view.payload.is_empty() => fields.push(Field::leaf(
376 format!("{}: {} octets", kebab(kind), view.payload.len()),
377 rest,
378 )),
379 Some(_) => {}
380 None if view.payload.is_empty() => {}
381 None => fields.push(apdu(view.payload, rest.start)),
382 }
383 fields
384}
385
386const PDU_NAMES: [&str; 8] = [
387 "Confirmed-Request",
388 "Unconfirmed-Request",
389 "Simple-ACK",
390 "Complex-ACK",
391 "Segment-ACK",
392 "Error",
393 "Reject",
394 "Abort",
395];
396
397fn apdu(apdu: &[u8], base: usize) -> Field {
400 let at = |range: Range<usize>| base + range.start..base + range.end;
401 let whole = at(0..apdu.len());
402 let decoded = match decode_apdu(Bytes::copy_from_slice(apdu)) {
403 Ok(decoded) => decoded,
404 Err(error) => return Field::leaf(format!("APDU: malformed ({error})"), whole),
405 };
406 let first = apdu[0];
407 let kind = PDU_NAMES[usize::from(first >> 4)];
408 let mut kids = vec![Field::leaf(
409 format!("PDU type: {kind} (flags 0x{:X})", first & 0x0F),
410 at(0..1),
411 )];
412 let mut leaf = |label: String, range: Range<usize>| kids.push(Field::leaf(label, at(range)));
413 let segmented = first & 0x08 != 0;
414 let (service, params, extra, named) = match &decoded {
415 Apdu::ConfirmedRequest(request) => {
416 let segments = request
417 .max_segments
418 .map_or_else(|| "unspecified".to_string(), |n| n.to_string());
419 leaf(
420 format!(
421 "Max segments: {segments}, max APDU: {}",
422 request.max_apdu_length
423 ),
424 1..2,
425 );
426 leaf(format!("Invoke ID: {}", request.invoke_id), 2..3);
427 let mut pos = 3;
428 if segmented {
429 leaf(format!("Sequence number: {}", apdu[3]), 3..4);
430 leaf(format!("Proposed window size: {}", apdu[4]), 4..5);
431 pos = 5;
432 }
433 leaf(
434 format!("Service: {}", kebab(request.service_choice)),
435 pos..pos + 1,
436 );
437 let named = Params::Request(request.service_choice);
438 (kebab(request.service_choice), pos + 1, None, Some(named))
439 }
440 Apdu::UnconfirmedRequest(request) => {
441 leaf(format!("Service: {}", kebab(request.service_choice)), 1..2);
442 let named = Params::Unconfirmed(request.service_choice);
443 (kebab(request.service_choice), 2, None, Some(named))
444 }
445 Apdu::SimpleAck(ack) => {
446 leaf(format!("Invoke ID: {}", ack.invoke_id), 1..2);
447 leaf(format!("Service: {}", kebab(ack.service_choice)), 2..3);
448 (kebab(ack.service_choice), 3, None, None)
449 }
450 Apdu::ComplexAck(ack) => {
451 leaf(format!("Invoke ID: {}", ack.invoke_id), 1..2);
452 let mut pos = 2;
453 if segmented {
454 leaf(format!("Sequence number: {}", apdu[2]), 2..3);
455 leaf(format!("Proposed window size: {}", apdu[3]), 3..4);
456 pos = 4;
457 }
458 leaf(
459 format!("Service: {}", kebab(ack.service_choice)),
460 pos..pos + 1,
461 );
462 let named = Params::Ack(ack.service_choice);
463 (kebab(ack.service_choice), pos + 1, None, Some(named))
464 }
465 Apdu::SegmentAck(ack) => {
466 leaf(format!("Invoke ID: {}", ack.invoke_id), 1..2);
467 leaf(format!("Sequence number: {}", ack.sequence_number), 2..3);
468 leaf(
469 format!("Actual window size: {}", ack.actual_window_size),
470 3..4,
471 );
472 (String::new(), 4, None, None)
473 }
474 Apdu::Error(error) => {
475 leaf(format!("Invoke ID: {}", error.invoke_id), 1..2);
476 leaf(format!("Service: {}", kebab(error.service_choice)), 2..3);
477 let problem = format!(
478 "Error: {}:{}",
479 kebab(error.error_class),
480 kebab(error.error_code)
481 );
482 (kebab(error.service_choice), 3, Some(problem), None)
483 }
484 Apdu::Reject(reject) => {
485 leaf(format!("Invoke ID: {}", reject.invoke_id), 1..2);
486 leaf(
487 format!("Reject reason: {}", kebab(reject.reject_reason)),
488 2..3,
489 );
490 (String::new(), 3, None, None)
491 }
492 Apdu::Abort(abort) => {
493 leaf(format!("Invoke ID: {}", abort.invoke_id), 1..2);
494 leaf(format!("Abort reason: {}", kebab(abort.abort_reason)), 2..3);
495 (String::new(), 3, None, None)
496 }
497 };
498 if params < apdu.len() && service.is_empty() && extra.is_none() {
499 kids.push(Field::leaf(
501 format!("Trailing octets: {}", apdu.len() - params),
502 at(params..apdu.len()),
503 ));
504 } else if params < apdu.len() {
505 let label = extra.unwrap_or_else(|| format!("{service} parameters"));
506 let segment = match &decoded {
509 Apdu::ConfirmedRequest(request) => request.sequence_number,
510 Apdu::ComplexAck(ack) => ack.sequence_number,
511 _ => None,
512 }
513 .filter(|_| segmented);
514 let fields = service::parameters(named, segment, apdu, base, params);
515 kids.push(Field::node(label, at(params..apdu.len()), fields));
516 }
517 Field::node(format!("APDU: {kind}"), whole, kids)
518}
519
520const APPLICATION_TAGS: [&str; 13] = [
521 "Null",
522 "Boolean",
523 "Unsigned",
524 "Signed",
525 "Real",
526 "Double",
527 "Octet string",
528 "Character string",
529 "Bit string",
530 "Enumerated",
531 "Date",
532 "Time",
533 "Object identifier",
534];
535
536fn value_text(value: &PropertyValue) -> String {
539 match value {
540 PropertyValue::Enumerated(n) => n.to_string(),
541 PropertyValue::ObjectIdentifier(object) => {
542 format!(
543 "{} {}",
544 kebab(object.object_type()),
545 object.instance_number()
546 )
547 }
548 value => crate::format::format_property_value(value),
550 }
551}
552
553fn tags(data: &[u8], base: usize, start: usize, end: usize, depth: usize) -> Vec<Field> {
558 let mut fields = walk(data, base, start, end, depth).0;
559 let covered = fields
560 .last()
561 .map_or(start, |field| field.range.end.saturating_sub(base));
562 if covered < end {
563 fields.push(Field::leaf(
564 format!("Trailing octets: {}", end - covered),
565 base + covered..base + end,
566 ));
567 }
568 fields
569}
570
571fn walk(data: &[u8], base: usize, start: usize, end: usize, depth: usize) -> (Vec<Field>, usize) {
575 let mut fields = Vec::new();
576 let mut pos = start;
577 while pos < end {
578 let (tag, content) = match decode_tag(&data[..end], pos) {
579 Ok(read) if depth < MAX_CONTEXT_NESTING_DEPTH => read,
580 Ok(_) => {
581 fields.push(Field::leaf("Nested too deeply", base + pos..base + end));
582 return (fields, end);
583 }
584 Err(error) => {
585 fields.push(Field::leaf(
586 format!("Malformed tag: {error}"),
587 base + pos..base + end,
588 ));
589 return (fields, end);
590 }
591 };
592 if tag.is_closing {
593 return (fields, content);
594 }
595 if tag.is_opening {
596 let (inner, after) = walk(data, base, content, end, depth + 1);
597 fields.push(Field::node(
598 format!("[{}] opening", tag.number),
599 base + pos..base + after,
600 inner,
601 ));
602 pos = after;
603 continue;
604 }
605 let decoded = (tag.class == TagClass::Application)
608 .then(|| decode_application_value(&data[..end], pos));
609 let after = match &decoded {
610 Some(Ok((_, after))) => *after,
611 _ => content.saturating_add(tag.length as usize),
612 }
613 .min(end)
614 .max(content);
615 let device_text = matches!(&decoded, Some(Ok((PropertyValue::CharacterString(_), _))));
616 let label = match decoded {
617 Some(Ok((value, _))) => format!(
618 "{}: {}",
619 APPLICATION_TAGS
620 .get(usize::from(tag.number))
621 .unwrap_or(&"Application"),
622 value_text(&value)
623 ),
624 Some(Err(error)) => format!("Application tag {}: {error}", tag.number),
625 None => {
626 let octets = &data[content..after];
627 let number = (octets.len() <= 4)
628 .then(|| octets.iter().fold(0u32, |n, b| (n << 8) | u32::from(*b)));
629 match number {
630 Some(n) if !octets.is_empty() => {
631 format!("[{}] {} ({n})", tag.number, hex(octets))
632 }
633 _ => format!("[{}] {}", tag.number, hex(octets)),
634 }
635 }
636 };
637 let mut field = Field::leaf(label, base + pos..base + after);
638 field.device_text = device_text;
639 fields.push(field);
640 pos = after;
641 }
642 (fields, end)
643}
644
645#[cfg(test)]
646#[path = "tree_tests.rs"]
647mod tests;