Class: Dap::Filter::FilterDecodeBacnetRPMReply

Inherits:
Object
  • Object
show all
Includes:
BaseDecoder
Defined in:
lib/dap/filter/udp.rb

Overview

Decode a BACnet Read Property Multiple reply

Constant Summary collapse

TAG_TYPE_LENGTHS =
{
  2 => 2,
  10 => 4,
  11 => 4,
}

Instance Attribute Summary

Attributes included from Base

#name, #opts

Instance Method Summary collapse

Methods included from BaseDecoder

#process

Methods included from Base

#initialize, #process

Instance Method Details

#decode(sdata) ⇒ Object



379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
# File 'lib/dap/filter/udp.rb', line 379

def decode(sdata)
  info = {}
  return if sdata.length < 9

  data = sdata.dup

  bacnet_vlc_type, bacnet_vlc_function, bacnet_vlc_length = data.slice!(0,4).unpack("CCn")
  # if this isn't a BACnet/IP (0x81) original unicast NPDU (0x0a), abort
  if bacnet_vlc_type != 0x81 || bacnet_vlc_function != 0x0a
    return info
  else
    info['bacnet_vlc_type'] = bacnet_vlc_type
    info['bacnet_vlc_function'] = bacnet_vlc_function
    info['bacnet_vlc_length'] = bacnet_vlc_length
  end

  # we only know how to decode version 1, so abort if it is anything else
  # but store the version in the event that we want to parse these later
  bacnet_npdu_version, bacnet_npdu_control = data.slice!(0,2).unpack("CC")
  info['bacnet_npdu_version'] = bacnet_npdu_version
  info['bacnet_npdu_control'] = bacnet_npdu_control
  return info if bacnet_npdu_version != 1

  bacnet_apdu_type_flags, bacnet_apdu_invoke_id, bacnet_apdu_service_choice = data.slice!(0,3).unpack("CCC")
  bacnet_apdu_type = bacnet_apdu_type_flags >> 4
  bacnet_apdu_flags = bacnet_apdu_type_flags & 0b00001111
  info['bacnet_apdu_type'] = bacnet_apdu_type
  info['bacnet_apdu_flags'] = bacnet_apdu_flags
  info['bacnet_apdu_invoke_id'] = bacnet_apdu_invoke_id
  info['bacnet_apdu_service_choice'] = bacnet_apdu_service_choice
  return info unless (bacnet_apdu_type == 3 && bacnet_apdu_service_choice == 14)

  return info unless data.size > 5
  # XXX: don't know what to do with this right now
  bacnet_object_id = data.slice!(0,5)
  return info unless data.slice!(0,1).unpack('C').first == 0x1e
  props = {}
  # XXX: I think this is ASN.1, but still need to confirm
  while (true) do
    #puts "size is #{data.size}, data is #{data.each_byte.map { |b| b.to_s(16) }.join(' ')}"
    break if data.size < 4
    property_tag, property_id = data.slice!(0,2).unpack('CC')
    props[property_id] = true
    # slice off the opening tag
    otag = data.slice!(0,1).unpack('C').first
    if otag == 0x5e
      data.slice!(0,5)
      #puts "Property #{property_id} unknown"
      props[property_id] = nil
    else
      # it isn't clear if the length is one byte wide followed by one byte of
      # 0x00 for spacing or if it is two bytes little endian.  Looks like the later.
      # XXX?
      tag_flags = data.slice!(0,1).unpack('C').first
      tag_type = tag_flags >> 4
      if TAG_TYPE_LENGTHS.key?(tag_type)
        #puts "Know how to handle property #{property_id}'s tag type #{tag_type}"
        props[property_id] = data.slice!(0, TAG_TYPE_LENGTHS[tag_type])
      else
        if tag_type == 7
          property_length = data.slice!(0,2).unpack('v').first
          #puts "Handled property #{property_id}'s #{property_length}-byte tag type #{tag_type}"
            property_length -= 1
            # handle String
            props[property_id] = data.slice!(0, property_length)
        else
          #puts "Don't know how to handle property #{property_id}'s tag type #{tag_type}"
        end
      end

      ctag = data.slice!(0,1).unpack('C')
    end
    if data.size == 0
      #puts "done"
      break
    else
      #puts "going"
    end
  end

  props.each do |k,v|
    info[k] = v
  end


  info
end