1
0
mirror of https://github.com/JoelBender/bacpypes synced 2025-09-28 22:15:23 +08:00
bacpypes/py25/bacpypes/pdu.py

517 lines
16 KiB
Python
Executable File

#!/usr/bin/python
"""
PDU
"""
import re
import socket
import struct
from .debugging import ModuleLogger, bacpypes_debugging, btox, xtob
from .comm import PCI as _PCI, PDUData
# pack/unpack constants
_short_mask = 0xFFFFL
_long_mask = 0xFFFFFFFFL
# some debugging
_debug = 0
_log = ModuleLogger(globals())
#
# Address
#
ip_address_mask_port_re = re.compile(r'^(?:(\d+):)?(\d+\.\d+\.\d+\.\d+)(?:/(\d+))?(?::(\d+))?$')
ethernet_re = re.compile(r'^([0-9A-Fa-f][0-9A-Fa-f][:]){5}([0-9A-Fa-f][0-9A-Fa-f])$' )
class Address:
nullAddr = 0
localBroadcastAddr = 1
localStationAddr = 2
remoteBroadcastAddr = 3
remoteStationAddr = 4
globalBroadcastAddr = 5
def __init__(self, *args):
if _debug: Address._debug("__init__ %r", args)
self.addrType = Address.nullAddr
self.addrNet = None
self.addrLen = 0
self.addrAddr = ''
if len(args) == 1:
self.decode_address(args[0])
elif len(args) == 2:
self.decode_address(args[1])
if self.addrType == Address.localStationAddr:
self.addrType = Address.remoteStationAddr
self.addrNet = args[0]
elif self.addrType == Address.localBroadcastAddr:
self.addrType = Address.remoteBroadcastAddr
self.addrNet = args[0]
else:
raise ValueError("unrecognized address ctor form")
def decode_address(self, addr):
"""Initialize the address from a string. Lots of different forms are supported."""
if _debug: Address._debug("decode_address %r (%s)", addr, type(addr))
# start out assuming this is a local station
self.addrType = Address.localStationAddr
self.addrNet = None
if addr == "*":
if _debug: Address._debug(" - localBroadcast")
self.addrType = Address.localBroadcastAddr
self.addrNet = None
self.addrAddr = None
self.addrLen = None
elif addr == "*:*":
if _debug: Address._debug(" - globalBroadcast")
self.addrType = Address.globalBroadcastAddr
self.addrNet = None
self.addrAddr = None
self.addrLen = None
elif isinstance(addr, int):
if _debug: Address._debug(" - int")
if (addr < 0) or (addr >= 256):
raise ValueError("address out of range")
self.addrAddr = struct.pack('B', addr)
self.addrLen = 1
elif isinstance(addr, str):
if _debug: Address._debug(" - str")
m = ip_address_mask_port_re.match(addr)
if m:
if _debug: Address._debug(" - IP address")
net, addr, mask, port = m.groups()
if not mask: mask = '32'
if not port: port = '47808'
if _debug: Address._debug(" - net, addr, mask, port: %r, %r, %r, %r", net, addr, mask, port)
if net:
net = int(net)
if (net >= 65535):
raise ValueError("network out of range")
self.addrType = Address.remoteStationAddr
self.addrNet = net
self.addrPort = int(port)
self.addrTuple = (addr, self.addrPort)
addrstr = socket.inet_aton(addr)
self.addrIP = struct.unpack('!L', addrstr)[0]
self.addrMask = (_long_mask << (32 - int(mask))) & _long_mask
self.addrHost = (self.addrIP & ~self.addrMask)
self.addrSubnet = (self.addrIP & self.addrMask)
bcast = (self.addrSubnet | ~self.addrMask)
self.addrBroadcastTuple = (socket.inet_ntoa(struct.pack('!L', bcast & _long_mask)), self.addrPort)
self.addrAddr = addrstr + struct.pack('!H', self.addrPort & _short_mask)
self.addrLen = 6
elif ethernet_re.match(addr):
if _debug: Address._debug(" - ethernet")
self.addrAddr = xtob(addr, ':')
self.addrLen = len(self.addrAddr)
elif re.match(r"^\d+$", addr):
if _debug: Address._debug(" - int")
addr = int(addr)
if (addr > 255):
raise ValueError("address out of range")
self.addrAddr = struct.pack('B', addr)
self.addrLen = 1
elif re.match(r"^\d+:[*]$", addr):
if _debug: Address._debug(" - remote broadcast")
addr = int(addr[:-2])
if (addr >= 65535):
raise ValueError("network out of range")
self.addrType = Address.remoteBroadcastAddr
self.addrNet = addr
self.addrAddr = None
self.addrLen = None
elif re.match(r"^\d+:\d+$",addr):
if _debug: Address._debug(" - remote station")
net, addr = addr.split(':')
net = int(net)
addr = int(addr)
if (net >= 65535):
raise ValueError("network out of range")
if (addr > 255):
raise ValueError("address out of range")
self.addrType = Address.remoteStationAddr
self.addrNet = net
self.addrAddr = struct.pack('B', addr)
self.addrLen = 1
elif re.match(r"^0x([0-9A-Fa-f][0-9A-Fa-f])+$",addr):
if _debug: Address._debug(" - modern hex string")
self.addrAddr = xtob(addr[2:])
self.addrLen = len(self.addrAddr)
elif re.match(r"^X'([0-9A-Fa-f][0-9A-Fa-f])+'$",addr):
if _debug: Address._debug(" - old school hex string")
self.addrAddr = xtob(addr[2:-1])
self.addrLen = len(self.addrAddr)
elif re.match(r"^\d+:0x([0-9A-Fa-f][0-9A-Fa-f])+$",addr):
if _debug: Address._debug(" - remote station with modern hex string")
net, addr = addr.split(':')
net = int(net)
if (net >= 65535):
raise ValueError("network out of range")
self.addrType = Address.remoteStationAddr
self.addrNet = net
self.addrAddr = xtob(addr[2:])
self.addrLen = len(self.addrAddr)
elif re.match(r"^\d+:X'([0-9A-Fa-f][0-9A-Fa-f])+'$",addr):
if _debug: Address._debug(" - remote station with old school hex string")
net, addr = addr.split(':')
net = int(net)
if (net >= 65535):
raise ValueError("network out of range")
self.addrType = Address.remoteStationAddr
self.addrNet = net
self.addrAddr = xtob(addr[2:-1])
self.addrLen = len(self.addrAddr)
else:
raise ValueError("unrecognized format")
elif isinstance(addr, tuple):
addr, port = addr
self.addrPort = int(port)
if isinstance(addr, str):
if not addr:
# when ('', n) is passed it is the local host address, but that
# could be more than one on a multihomed machine, the empty string
# means "any".
addrstr = '\0\0\0\0'
else:
addrstr = socket.inet_aton(addr)
self.addrTuple = (addr, self.addrPort)
elif isinstance(addr, (int, long)):
addrstr = struct.pack('!L', addr & _long_mask)
self.addrTuple = (socket.inet_ntoa(addrstr), self.addrPort)
else:
raise TypeError("tuple must be (string, port) or (long, port)")
if _debug: Address._debug(" - addrstr: %r", addrstr)
self.addrIP = struct.unpack('!L', addrstr)[0]
self.addrMask = _long_mask
self.addrHost = None
self.addrSubnet = None
self.addrBroadcastTuple = self.addrTuple
self.addrAddr = addrstr + struct.pack('!H', self.addrPort & _short_mask)
self.addrLen = 6
else:
raise TypeError("integer, string or tuple required")
def __str__(self):
if self.addrType == Address.nullAddr:
return 'Null'
elif self.addrType == Address.localBroadcastAddr:
return '*'
elif self.addrType == Address.localStationAddr:
rslt = ''
if self.addrLen == 1:
rslt += str(ord(self.addrAddr))
else:
port = struct.unpack('!H', self.addrAddr[-2:])[0]
if (len(self.addrAddr) == 6) and (port >= 47808) and (port <= 47823):
rslt += '.'.join(["%d" % ord(x) for x in self.addrAddr[0:4]])
if port != 47808:
rslt += ':' + str(port)
else:
rslt += '0x' + btox(self.addrAddr)
return rslt
elif self.addrType == Address.remoteBroadcastAddr:
return '%d:*' % (self.addrNet,)
elif self.addrType == Address.remoteStationAddr:
rslt = '%d:' % (self.addrNet,)
if self.addrLen == 1:
rslt += str(ord(self.addrAddr[0]))
else:
port = struct.unpack('!H', self.addrAddr[-2:])[0]
if (len(self.addrAddr) == 6) and (port >= 47808) and (port <= 47823):
rslt += '.'.join(["%d" % ord(x) for x in self.addrAddr[0:4]])
if port != 47808:
rslt += ':' + str(port)
else:
rslt += '0x' + btox(self.addrAddr)
return rslt
elif self.addrType == Address.globalBroadcastAddr:
return '*:*'
else:
raise TypeError("unknown address type %d" % self.addrType)
def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, self.__str__())
def __hash__(self):
return hash( (self.addrType, self.addrNet, self.addrAddr) )
def __eq__(self,arg):
# try an coerce it into an address
if not isinstance(arg, Address):
arg = Address(arg)
# all of the components must match
return (self.addrType == arg.addrType) and (self.addrNet == arg.addrNet) and (self.addrAddr == arg.addrAddr)
def __ne__(self,arg):
return not self.__eq__(arg)
def dict_contents(self, use_dict=None, as_class=None):
"""Return the contents of an object as a dict."""
if _debug: _log.debug("dict_contents use_dict=%r as_class=%r", use_dict, as_class)
# exception to the rule of returning a dict
return str(self)
bacpypes_debugging(Address)
#
# pack_ip_addr, unpack_ip_addr
#
def pack_ip_addr(addr):
"""Given an IP address tuple like ('1.2.3.4', 47808) return the six-octet string
useful for a BACnet address."""
addr, port = addr
return socket.inet_aton(addr) + struct.pack('!H', port & _short_mask)
def unpack_ip_addr(addr):
"""Given a six-octet BACnet address, return an IP address tuple."""
return (socket.inet_ntoa(addr[0:4]), struct.unpack('!H', addr[4:6])[0])
#
# LocalStation
#
class LocalStation(Address):
def __init__(self, addr):
self.addrType = Address.localStationAddr
self.addrNet = None
if isinstance(addr, int):
if (addr < 0) or (addr >= 256):
raise ValueError("address out of range")
self.addrAddr = struct.pack('B', addr)
self.addrLen = 1
elif isinstance(addr, str):
if _debug: Address._debug(" - string (bytes)")
self.addrAddr = addr
self.addrLen = len(addr)
else:
raise TypeError("integer or string (bytes) required")
#
# RemoteStation
#
class RemoteStation(Address):
def __init__(self, net, addr):
if not isinstance(net, int):
raise TypeError("integer network required")
if (net < 0) or (net >= 65535):
raise ValueError("network out of range")
self.addrType = Address.remoteStationAddr
self.addrNet = net
if isinstance(addr, int):
if (addr < 0) or (addr >= 256):
raise ValueError("address out of range")
self.addrAddr = struct.pack('B', addr)
self.addrLen = 1
elif isinstance(addr, str):
if _debug: Address._debug(" - string (bytes)")
self.addrAddr = addr
self.addrLen = len(addr)
else:
raise TypeError("integer or string (bytes) required")
#
# LocalBroadcast
#
class LocalBroadcast(Address):
def __init__(self):
self.addrType = Address.localBroadcastAddr
self.addrNet = None
self.addrAddr = None
self.addrLen = None
#
# RemoteBroadcast
#
class RemoteBroadcast(Address):
def __init__(self, net):
if not isinstance(net, int):
raise TypeError("integer network required")
if (net < 0) or (net >= 65535):
raise ValueError("network out of range")
self.addrType = Address.remoteBroadcastAddr
self.addrNet = net
self.addrAddr = None
self.addrLen = None
#
# GlobalBroadcast
#
class GlobalBroadcast(Address):
def __init__(self):
self.addrType = Address.globalBroadcastAddr
self.addrNet = None
self.addrAddr = None
self.addrLen = None
#
# PCI
#
class PCI(_PCI):
_debug_contents = ('pduExpectingReply', 'pduNetworkPriority')
def __init__(self, *args, **kwargs):
if _debug: PCI._debug("__init__ %r %r", args, kwargs)
# split out the keyword arguments that belong to this class
my_kwargs = {}
other_kwargs = {}
for element in ('expectingReply', 'networkPriority'):
if element in kwargs:
my_kwargs[element] = kwargs[element]
for kw in kwargs:
if kw not in my_kwargs:
other_kwargs[kw] = kwargs[kw]
if _debug: PCI._debug(" - my_kwargs: %r", my_kwargs)
if _debug: PCI._debug(" - other_kwargs: %r", other_kwargs)
# call some superclass, if there is one
super(PCI, self).__init__(*args, **other_kwargs)
# set the attribute/property values for the ones provided
self.pduExpectingReply = my_kwargs.get('expectingReply', 0) # see 6.2.2 (1 or 0)
self.pduNetworkPriority = my_kwargs.get('networkPriority', 0) # see 6.2.2 (0..3)
def update(self, pci):
"""Copy the PCI fields."""
_PCI.update(self, pci)
# now do the BACnet PCI fields
self.pduExpectingReply = pci.pduExpectingReply
self.pduNetworkPriority = pci.pduNetworkPriority
def pci_contents(self, use_dict=None, as_class=dict):
"""Return the contents of an object as a dict."""
if _debug: PCI._debug("pci_contents use_dict=%r as_class=%r", use_dict, as_class)
# make/extend the dictionary of content
if use_dict is None:
use_dict = as_class()
# call the parent class
_PCI.pci_contents(self, use_dict=use_dict, as_class=as_class)
# save the values
use_dict.__setitem__('expectingReply', self.pduExpectingReply)
use_dict.__setitem__('networkPriority', self.pduNetworkPriority)
# return what we built/updated
return use_dict
def dict_contents(self, use_dict=None, as_class=dict):
"""Return the contents of an object as a dict."""
if _debug: PCI._debug("dict_contents use_dict=%r as_class=%r", use_dict, as_class)
return self.pci_contents(use_dict=use_dict, as_class=as_class)
bacpypes_debugging(PCI)
#
# PDU
#
class PDU(PCI, PDUData):
def __init__(self, *args, **kwargs):
if _debug: PDU._debug("__init__ %r %r", args, kwargs)
super(PDU, self).__init__(*args, **kwargs)
def __str__(self):
return '<%s %s -> %s : %s>' % (self.__class__.__name__, self.pduSource, self.pduDestination, btox(self.pduData,'.'))
def dict_contents(self, use_dict=None, as_class=dict):
"""Return the contents of an object as a dict."""
if _debug: PDU._debug("dict_contents use_dict=%r as_class=%r", use_dict, as_class)
# make/extend the dictionary of content
if use_dict is None:
use_dict = as_class()
# call into the two base classes
self.pci_contents(use_dict=use_dict, as_class=as_class)
self.pdudata_contents(use_dict=use_dict, as_class=as_class)
# return what we built/updated
return use_dict
bacpypes_debugging(PDU)