From f78a8c9d670027913c9a6b3121f93550874b2c61 Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Sun, 3 Jul 2016 00:08:07 -0400 Subject: [PATCH] sync py34 with py27 --- py34/bacpypes/apdu.py | 12 +- py34/bacpypes/app.py | 170 ++++++++++++++++++++++++-- py34/bacpypes/appservice.py | 231 +++++++++++++++++------------------- 3 files changed, 284 insertions(+), 129 deletions(-) diff --git a/py34/bacpypes/apdu.py b/py34/bacpypes/apdu.py index 523dd59..5637732 100755 --- a/py34/bacpypes/apdu.py +++ b/py34/bacpypes/apdu.py @@ -68,11 +68,19 @@ def decode_max_apdu_segments(arg): # encode_max_apdu_response/decode_max_apdu_response # +_max_apdu_response_encoding = {0:50, 1:128, 2:206, 3:480, 4:1024, 5:1476} + def encode_max_apdu_response(arg): - return {50:0, 128:1, 206:2, 480:3, 1024:4, 1476:5}.get(arg) + encodings = _max_apdu_response_encoding.items() + encodings.sort(lambda x, y: y[1] - x[1]) + for i, v in encodings: + if (v <= arg): + return i + + raise ValueError("invalid max APDU response encoding: {0}".format(arg)) def decode_max_apdu_response(arg): - return {0:50, 1:128, 2:206, 3:480, 4:1024, 5:1476}.get(arg) + return _max_apdu_response_encoding.get(arg) # # APCI diff --git a/py34/bacpypes/app.py b/py34/bacpypes/app.py index 0d2e211..a748fee 100755 --- a/py34/bacpypes/app.py +++ b/py34/bacpypes/app.py @@ -4,10 +4,10 @@ Application Module """ -from .debugging import bacpypes_debugging, ModuleLogger +from .debugging import bacpypes_debugging, DebugContents, ModuleLogger from .comm import ApplicationServiceElement, bind -from .pdu import Address +from .pdu import Address, LocalStation, RemoteStation from .primitivedata import Atomic, Date, Null, ObjectIdentifier, Time, Unsigned from .constructeddata import Any, Array, ArrayOf @@ -38,6 +38,152 @@ from .apdu import \ _debug = 0 _log = ModuleLogger(globals()) +# +# DeviceInfo +# + +@bacpypes_debugging +class DeviceInfo(DebugContents): + + _debug_contents = ( + 'deviceIdentifier', + 'address', + 'maxApduLengthAccepted', + 'segmentationSupported', + 'vendorID', + 'maxNpduLength', + 'maxSegmentsAccepted', + ) + + def __init__(self): + # this information is from an IAmRequest + self.deviceIdentifier = None # device identifier + self.address = None # LocalStation or RemoteStation + self.maxApduLengthAccepted = 1024 # maximum APDU device will accept + self.segmentationSupported = 'noSegmentation' # normally no segmentation + self.vendorID = None # vendor identifier + + self.maxNpduLength = 1497 # maximum we can send in transit + self.maxSegmentsAccepted = None # value for proposed/actual window size + +# +# DeviceInfoCache +# + +@bacpypes_debugging +class DeviceInfoCache: + + def __init__(self): + if _debug: DeviceInfoCache._debug("__init__") + + # empty cache + self.cache = {} + + def has_device_info(self, key): + """Return true iff cache has information about the device.""" + if _debug: DeviceInfoCache._debug("has_device_info %r", key) + + return key in self.cache + + def add_device_info(self, apdu): + """Create a device information record based on the contents of an + IAmRequest and put it in the cache.""" + if _debug: DeviceInfoCache._debug("add_device_info %r", apdu) + + # get the existing cache record by identifier + info = self.get_device_info(apdu.iAmDeviceIdentifier[1]) + if _debug: DeviceInfoCache._debug(" - info: %r", info) + + # update existing record + if info: + if (info.address == apdu.pduSource): + return + + info.address = apdu.pduSource + else: + # get the existing record by address (creates a new record) + info = self.get_device_info(apdu.pduSource) + if _debug: DeviceInfoCache._debug(" - info: %r", info) + + info.deviceIdentifier = apdu.iAmDeviceIdentifier[1] + + # update the rest of the values + info.maxApduLengthAccepted = apdu.maxApduLengthAccepted + info.segmentationSupported = apdu.segmentationSupported + info.vendorID = apdu.vendorID + + # say this is an updated record + self.update_device_info(info) + + def get_device_info(self, key): + """Return the known information about the device. If the key is the + address of an unknown device, build a generic device information record + add put it in the cache.""" + if _debug: DeviceInfoCache._debug("get_device_info %r", key) + + if isinstance(key, int): + current_info = self.cache.get(key, None) + + elif not isinstance(key, Address): + raise TypeError("key must be integer or an address") + + elif key.addrType not in (Address.localStationAddr, Address.remoteStationAddr): + raise TypeError("address must be a local or remote station") + + else: + current_info = self.cache.get(key, None) + if not current_info: + current_info = DeviceInfo() + current_info.address = key + current_info._cache_keys = (None, key) + + self.cache[key] = current_info + + if _debug: DeviceInfoCache._debug(" - current_info: %r", current_info) + + return current_info + + def update_device_info(self, info): + """The application has updated one or more fields in the device + information record and the cache needs to be updated to reflect the + changes. If this is a cached version of a persistent record then this + is the opportunity to update the database.""" + if _debug: DeviceInfoCache._debug("update_device_info %r", info) + + cache_id, cache_address = info._cache_keys + + if (cache_id is not None) and (info.deviceIdentifier != cache_id): + if _debug: DeviceInfoCache._debug(" - device identifier updated") + + # remove the old reference, add the new one + del self.cache[cache_id] + self.cache[info.deviceIdentifier] = info + + cache_id = info.deviceIdentifier + + if (cache_address is not None) and (info.address != cache_address): + if _debug: DeviceInfoCache._debug(" - device address updated") + + # remove the old reference, add the new one + del self.cache[cache_address] + self.cache[info.address] = info + + cache_address = info.address + + # update the keys + info._cache_keys = (cache_id, cache_address) + + def release_device_info(self, info): + """This function is called by the segmentation state machine when it + has finished with the device information.""" + if _debug: DeviceInfoCache._debug("release_device_info %r", info) + + cache_id, cache_address = info._cache_keys + if cache_id is not None: + del self.cache[cache_id] + if cache_address is not None: + del self.cache[cache_address] + # # CurrentDateProperty # @@ -146,13 +292,19 @@ class LocalDeviceObject(DeviceObject): @bacpypes_debugging class Application(ApplicationServiceElement): - def __init__(self, localDevice, localAddress, aseID=None): - if _debug: Application._debug("__init__ %r %r aseID=%r", localDevice, localAddress, aseID) + def __init__(self, localDevice, localAddress, deviceInfoCache=None, aseID=None): + if _debug: Application._debug("__init__ %r %r deviceInfoCache=%r aseID=%r", localDevice, localAddress, deviceInfoCache, aseID) ApplicationServiceElement.__init__(self, aseID) # keep track of the local device self.localDevice = localDevice + # use the provided cache or make a default one + if deviceInfoCache: + self.deviceInfoCache = deviceInfoCache + else: + self.deviceInfoCache = DeviceInfoCache() + # bind the device object to this application localDevice._app = self @@ -610,9 +762,9 @@ class Application(ApplicationServiceElement): @bacpypes_debugging class BIPSimpleApplication(Application): - def __init__(self, localDevice, localAddress, aseID=None): - if _debug: BIPSimpleApplication._debug("__init__ %r %r aseID=%r", localDevice, localAddress, aseID) - Application.__init__(self, localDevice, localAddress, aseID) + def __init__(self, localDevice, localAddress, deviceInfoCache=None, aseID=None): + if _debug: BIPSimpleApplication._debug("__init__ %r %r deviceInfoCache=%r aseID=%r", localDevice, localAddress, deviceInfoCache, aseID) + Application.__init__(self, localDevice, localAddress, deviceInfoCache, aseID) # include a application decoder self.asap = ApplicationServiceAccessPoint() @@ -621,6 +773,10 @@ class BIPSimpleApplication(Application): # can know if it should support segmentation self.smap = StateMachineAccessPoint(localDevice) + # the segmentation state machines need access to the same device + # information cache as the application + self.smap.deviceInfoCache = self.deviceInfoCache + # a network service access point will be needed self.nsap = NetworkServiceAccessPoint() diff --git a/py34/bacpypes/appservice.py b/py34/bacpypes/appservice.py index fb7af5e..959ca41 100755 --- a/py34/bacpypes/appservice.py +++ b/py34/bacpypes/appservice.py @@ -22,38 +22,6 @@ from .apdu import AbortPDU, AbortReason, ComplexAckPDU, \ _debug = 0 _log = ModuleLogger(globals()) -# -# DeviceInfo -# - -class DeviceInfo(DebugContents): - - _debug_contents = ( - 'address', - 'maxNpduLength', - 'segmentationSupported', - 'maxApduLengthAccepted', - 'maxSegmentsAccepted', - ) - - def __init__( - self, address=None, - maxNpduLength=1024, - segmentationSupported='noSegmentation', - maxApduLengthAccepted=1024, - maxSegmentsAccepted=None, - ): - if not isinstance(address, (None.__class__, Address, LocalStation, RemoteStation)): - raise TypeError("address") - - self.address = address # LocalStation or RemoteStation - self.maxNpduLength = maxNpduLength # maximum we can send in transit - self.segmentationSupported = segmentationSupported # normally no segmentation - self.maxApduLengthAccepted = maxApduLengthAccepted # how big to divide up apdu's - self.maxSegmentsAccepted = maxSegmentsAccepted # limit on how many segments to recieve - -#---------------------------------------------------------------------- - # # SSM - Segmentation State Machine # @@ -76,26 +44,27 @@ class SSM(OneShotTask, DebugContents): , 'SEGMENTED_RESPONSE', 'SEGMENTED_CONFIRMATION', 'COMPLETED', 'ABORTED' ] - _debug_contents = ('ssmSAP', 'remoteDevice', 'invokeID' + _debug_contents = ('ssmSAP', 'localDevice', 'remoteDevice', 'invokeID' , 'state', 'segmentAPDU', 'segmentSize', 'segmentCount', 'maxSegmentsAccepted' , 'retryCount', 'segmentRetryCount', 'sentAllSegments', 'lastSequenceNumber' , 'initialSequenceNumber', 'actualWindowSize', 'proposedWindowSize' ) - def __init__(self, sap): + def __init__(self, sap, localDevice, remoteDevice): """Common parts for client and server segmentation.""" - if _debug: SSM._debug("__init__ %r", sap) + if _debug: SSM._debug("__init__ %r %r %r", sap, localDevice, remoteDevice) OneShotTask.__init__(self) - self.ssmSAP = sap # reference to the service access point - self.remoteDevice = None # remote device + self.ssmSAP = sap # service access point + self.localDevice = localDevice # local device information, DeviceObject + self.remoteDevice = remoteDevice # remote device information, a DeviceInfo instance self.invokeID = None # invoke ID self.state = IDLE # initial state self.segmentAPDU = None # refers to request or response self.segmentSize = None # how big the pieces are self.segmentCount = None - self.maxSegmentsAccepted = None # maximum number of segments client will accept + self.maxSegmentsAccepted = None # maximum number of segments self.retryCount = None self.segmentRetryCount = None @@ -105,6 +74,14 @@ class SSM(OneShotTask, DebugContents): self.actualWindowSize = None self.proposedWindowSize = None + # look for our segmentation parameters from our device object + if localDevice: + self.maxApduLengthAccepted = localDevice.maxApduLengthAccepted + self.segmentationSupported = localDevice.segmentationSupported + else: + self.maxApduLengthAccepted = 1024 + self.segmentationSupported = 'noSegmentation' + def start_timer(self, msecs): if _debug: SSM._debug("start_timer %r", msecs) @@ -179,15 +156,12 @@ class SSM(OneShotTask, DebugContents): segAPDU = ConfirmedRequestPDU(self.segmentAPDU.apduService) segAPDU.apduMaxSegs = self.maxSegmentsAccepted - segAPDU.apduMaxResp = self.ssmSAP.maxApduLengthAccepted + segAPDU.apduMaxResp = self.maxApduLengthAccepted segAPDU.apduInvokeID = self.invokeID; # segmented response accepted? - segAPDU.apduSA = ((self.ssmSAP.segmentationSupported == 'segmentedBoth') \ - or (self.ssmSAP.segmentationSupported == 'segmentedReceive')) - if _debug: - SSM._debug(" - segmented response accepted: %r", segAPDU.apduSA) - SSM._debug(" - self.ssmSAP.segmentationSupported: %r", self.ssmSAP.segmentationSupported) + segAPDU.apduSA = self.segmentationSupported in ('segmentedReceive', 'segmentedBoth') + if _debug: SSM._debug(" - segmented response accepted: %r", segAPDU.apduSA) elif self.segmentAPDU.apduType == ComplexAckPDU.pduType: if _debug: SSM._debug(" - complex ack context") @@ -262,9 +236,9 @@ class SSM(OneShotTask, DebugContents): @bacpypes_debugging class ClientSSM(SSM): - def __init__(self, sap): - if _debug: ClientSSM._debug("__init__ %s", sap) - SSM.__init__(self, sap) + def __init__(self, sap, localDevice, remoteDevice): + if _debug: ClientSSM._debug("__init__ %s %r %r", sap, localDevice, remoteDevice) + SSM.__init__(self, sap, localDevice, remoteDevice) # initialize the retry count self.retryCount = 0 @@ -278,8 +252,15 @@ class ClientSSM(SSM): # completed or aborted, remove tracking if (newState == COMPLETED) or (newState == ABORTED): + if _debug: ClientSSM._debug(" - remove from active transactions") self.ssmSAP.clientTransactions.remove(self) + if _debug: ClientSSM._debug(" - release device information") + self.ssmSAP.deviceInfoCache.release_device_info(self.remoteDevice) + + # help along the garbage collector + self.ssmSAP = self.localDevice = self.remoteDevice = None + def request(self, apdu): """This function is called by client transaction functions when it wants to send a message to the device.""" @@ -304,24 +285,20 @@ class ClientSSM(SSM): # save the request and set the segmentation context self.set_segmentation_context(apdu) - # get information about the device - self.remoteDevice = self.ssmSAP.get_device_info(apdu.pduDestination) - if _debug: ClientSSM._debug(" - remoteDevice: %r", self.remoteDevice) - - # the segment size is the minimum of the maximum size I can transmit - # (assumed to have no local buffer limitations), the maximum conveyable - # by the internetwork to the remote device, and the maximum APDU size - # accepted by the remote device. - self.segmentSize = min(self.remoteDevice.maxNpduLength, self.remoteDevice.maxApduLengthAccepted) + # the segment size is the minimum of the maximum size I can transmit, + # the maximum conveyable by the internetwork to the remote device, and + # the maximum APDU size accepted by the remote device. + self.segmentSize = min( + self.maxApduLengthAccepted, + self.remoteDevice.maxNpduLength, + self.remoteDevice.maxApduLengthAccepted, + ) if _debug: ClientSSM._debug(" - segment size: %r", self.segmentSize) - # save the maximum number of segments acceptable in the reply + # the maximum number of segments acceptable in the reply if apdu.apduMaxSegs is not None: # this request overrides the default self.maxSegmentsAccepted = apdu.apduMaxSegs - else: - # use the default in the device definition - self.maxSegmentsAccepted = self.ssmSAP.maxSegmentsAccepted # save the invoke ID self.invokeID = apdu.apduInvokeID @@ -340,12 +317,12 @@ class ClientSSM(SSM): # make sure we support segmented transmit if we need to if self.segmentCount > 1: - if (self.ssmSAP.segmentationSupported != 'segmentedTransmit') and (self.ssmSAP.segmentationSupported != 'segmentedBoth'): + if self.segmentationSupported not in ('segmentedTransmit', 'segmentedBoth'): if _debug: ClientSSM._debug(" - local device can't send segmented requests") abort = self.abort(AbortReason.segmentationNotSupported) self.response(abort) return - if (self.remoteDevice.segmentationSupported != 'segmentedReceive') and (self.remoteDevice.segmentationSupported != 'segmentedBoth'): + if self.remoteDevice.segmentationSupported not in ('segmentedReceive', 'segmentedBoth'): if _debug: ClientSSM._debug(" - remote device can't receive segmented requests") abort = self.abort(AbortReason.segmentationNotSupported) self.response(abort) @@ -544,7 +521,7 @@ class ClientSSM(SSM): self.set_state(COMPLETED) self.response(apdu) - elif (self.ssmSAP.segmentationSupported != 'segmentedReceive') and (self.ssmSAP.segmentationSupported != 'segmentedBoth'): + elif self.segmentationSupported not in ('segmentedReceive', 'segmentedBoth'): if _debug: ClientSSM._debug(" - local device can't receive segmented messages") abort = self.abort(AbortReason.segmentationNotSupported) self.response(abort) @@ -671,9 +648,9 @@ class ClientSSM(SSM): @bacpypes_debugging class ServerSSM(SSM): - def __init__(self, sap): - if _debug: ServerSSM._debug("__init__ %s", sap) - SSM.__init__(self, sap) + def __init__(self, sap, localDevice, remoteDevice): + if _debug: ServerSSM._debug("__init__ %s %r %r", sap, localDevice, remoteDevice) + SSM.__init__(self, sap, localDevice, remoteDevice) def set_state(self, newState, timer=0): """This function is called when the client wants to change state.""" @@ -684,8 +661,15 @@ class ServerSSM(SSM): # completed or aborted, remove tracking if (newState == COMPLETED) or (newState == ABORTED): + if _debug: ServerSSM._debug(" - remove from active transactions") self.ssmSAP.serverTransactions.remove(self) + if _debug: ServerSSM._debug(" - release device information") + self.ssmSAP.deviceInfoCache.release_device_info(self.remoteDevice) + + # help along the garbage collector + self.ssmSAP = self.localDevice = self.remoteDevice = None + def request(self, apdu): """This function is called by transaction functions to send to the application.""" @@ -704,7 +688,7 @@ class ServerSSM(SSM): if _debug: ServerSSM._debug("indication %r", apdu) if self.state == IDLE: - self.Idle(apdu) + self.idle(apdu) elif self.state == SEGMENTED_REQUEST: self.segmented_request(apdu) elif self.state == AWAIT_RESPONSE: @@ -783,19 +767,23 @@ class ServerSSM(SSM): # make sure we support segmented transmit if we need to if self.segmentCount > 1: - if _debug: ServerSSM._debug(" - segmentation required, %d segemnts", self.segmentCount) + if _debug: ServerSSM._debug(" - segmentation required, %d segments", self.segmentCount) - if (self.ssmSAP.segmentationSupported != 'segmentedTransmit') and (self.ssmSAP.segmentationSupported != 'segmentedBoth'): + # make sure we support segmented transmit + if self.segmentationSupported not in ('segmentedTransmit', 'segmentedBoth'): + if _debug: ServerSSM._debug(" - server can't send segmented responses") abort = self.abort(AbortReason.segmentationNotSupported) - self.request(abort) - return - if (self.remoteDevice.segmentationSupported != 'segmentedReceive') and (self.remoteDevice.segmentationSupported != 'segmentedBoth'): - abort = self.abort(AbortReason.segmentationNotSupported) - self.request(abort) + self.reponse(abort) return - ### check to make sure the client can receive that many - ### look at apduMaxSegs + # make sure client supports segmented receive + if self.remoteDevice.segmentationSupported not in ('segmentedReceive', 'segmentedBoth'): + if _debug: ServerSSM._debug(" - client can't receive segmented responses") + abort = self.abort(AbortReason.segmentationNotSupported) + self.response(abort) + return + + ### check for APDUTooLong? # initialize the state self.segmentRetryCount = 0 @@ -846,8 +834,8 @@ class ServerSSM(SSM): # return an abort APDU return AbortPDU(True, self.invokeID, reason) - def Idle(self, apdu): - if _debug: ServerSSM._debug("Idle %r", apdu) + def idle(self, apdu): + if _debug: ServerSSM._debug("idle %r", apdu) # make sure we're getting confirmed requests if not isinstance(apdu, ConfirmedRequestPDU): @@ -857,36 +845,29 @@ class ServerSSM(SSM): self.invokeID = apdu.apduInvokeID if _debug: ServerSSM._debug(" - invoke ID: %r", self.invokeID) - # get information about the client - self.remoteDevice = remote_device = self.ssmSAP.get_device_info(apdu.pduSource) - if _debug: ServerSSM._debug(" - remoteDevice: %r", self.remoteDevice) - # make sure the device information is synced with the request if apdu.apduSA: - if remote_device.segmentationSupported == 'noSegmentation': - if _debug: ServerSSM._debug(" - client supports segmented receive") - remote_device.segmentationSupported = 'segmentedReceive' + if self.remoteDevice.segmentationSupported == 'noSegmentation': + if _debug: ServerSSM._debug(" - client actually supports segmented receive") + self.remoteDevice.segmentationSupported = 'segmentedReceive' - elif remote_device.segmentationSupported == 'segmentedTransmit': - if _debug: ServerSSM._debug(" - client supports both segmented transmit and receive") - remote_device.segmentationSupported = 'segmentedBoth' + elif self.remoteDevice.segmentationSupported == 'segmentedTransmit': + if _debug: ServerSSM._debug(" - client actually supports both segmented transmit and receive") + self.remoteDevice.segmentationSupported = 'segmentedBoth' - elif remote_device.segmentationSupported == 'segmentedReceive': + elif self.remoteDevice.segmentationSupported == 'segmentedReceive': pass - elif remote_device.segmentationSupported == 'segmentedBoth': + elif self.remoteDevice.segmentationSupported == 'segmentedBoth': pass else: raise RuntimeError("invalid segmentation supported in device info") - if apdu.apduMaxSegs != remote_device.maxSegmentsAccepted: - if _debug: ServerSSM._debug(" - updating maximum segments accepted") - remote_device.maxSegmentsAccepted = apdu.apduMaxSegs - - if apdu.apduMaxResp != remote_device.maxApduLengthAccepted: - if _debug: ServerSSM._debug(" - updating maximum max APDU length accepted") - remote_device.maxApduLengthAccepted = apdu.apduMaxResp + if apdu.apduMaxSegs != self.remoteDevice.maxSegmentsAccepted: + if _debug: ServerSSM._debug(" - update maximum segments accepted?") + if apdu.apduMaxResp != self.remoteDevice.maxApduLengthAccepted: + if _debug: ServerSSM._debug(" - update maximum max APDU length accepted?") # save the number of segments the client is willing to accept in the ack self.maxSegmentsAccepted = apdu.apduMaxSegs @@ -898,7 +879,7 @@ class ServerSSM(SSM): return # make sure we support segmented requests - if (self.ssmSAP.segmentationSupported != 'segmentedReceive') and (self.ssmSAP.segmentationSupported != 'segmentedBoth'): + if self.segmentationSupported not in ('segmentedReceive', 'segmentedBoth'): abort = self.abort(AbortReason.segmentationNotSupported) self.response(abort) return @@ -1073,31 +1054,34 @@ class ServerSSM(SSM): # @bacpypes_debugging -class StateMachineAccessPoint(DeviceInfo, Client, ServiceAccessPoint): +class StateMachineAccessPoint(Client, ServiceAccessPoint): - def __init__(self, device, sap=None, cid=None): - if _debug: StateMachineAccessPoint._debug("__init__ %r sap=%r cid=%r", device, sap, cid) + def __init__(self, localDevice=None, deviceInfoCache=None, sap=None, cid=None): + if _debug: StateMachineAccessPoint._debug("__init__ localDevice=%r deviceInfoCache=%r sap=%r cid=%r", localDevice, deviceInfoCache, sap, cid) # basic initialization - DeviceInfo.__init__(self) Client.__init__(self, cid) ServiceAccessPoint.__init__(self, sap) - # device information from the device object - self.segmentationSupported = device.segmentationSupported # normally no segmentation - self.segmentTimeout = device.apduSegmentTimeout # how long to wait for a segAck - self.maxApduLengthAccepted = device.maxApduLengthAccepted # how big to divide up apdu's - self.maxSegmentsAccepted = device.maxSegmentsAccepted # limit on how many segments to recieve + # save a reference to the local device object and the cache + self.localDevice = localDevice + self.deviceInfoCache = deviceInfoCache # client settings - self.clientTransactions = [] - self.retryCount = device.numberOfApduRetries # how many times to repeat the request - self.retryTimeout = device.apduTimeout # how long between retrying the request self.nextInvokeID = 1 + self.clientTransactions = [] # server settings self.serverTransactions = [] - self.applicationTimeout = device.apduTimeout # how long the application has to respond + + # segmentation settings + self.retryTimeout = 3000 + self.segmentTimeout = 1500 + self.maxSegmentsAccepted = 8 + + # how long the state machine is willing to wait for the application + # layer to form a response and send it + self.applicationTimeout = 3000 def get_next_invoke_id(self, addr): """Called by clients to get an unused invoke ID.""" @@ -1120,13 +1104,6 @@ class StateMachineAccessPoint(DeviceInfo, Client, ServiceAccessPoint): return invokeID - def get_device_info(self, addr): - """get the segmentation supported and max APDU length accepted for a device.""" - if _debug: StateMachineAccessPoint._debug("get_device_info %r", addr) - - # return a generic info object - return DeviceInfo(addr) - def confirmation(self, pdu): """Packets coming up the stack are APDU's.""" if _debug: StateMachineAccessPoint._debug("confirmation %r", pdu) @@ -1148,8 +1125,11 @@ class StateMachineAccessPoint(DeviceInfo, Client, ServiceAccessPoint): if (apdu.pduSource == tr.remoteDevice.address) and (apdu.apduInvokeID == tr.invokeID): break else: + # find the remote device information + remoteDevice = self.deviceInfoCache.get_device_info(apdu.pduSource) + # build a server transaction - tr = ServerSSM(self) + tr = ServerSSM(self, self.localDevice, remoteDevice) # add it to our transactions to track it self.serverTransactions.append(tr) @@ -1244,8 +1224,13 @@ class StateMachineAccessPoint(DeviceInfo, Client, ServiceAccessPoint): if (apdu.pduDestination.addrType != Address.localStationAddr) and (apdu.pduDestination.addrType != Address.remoteStationAddr): StateMachineAccessPoint._warning("%s is not a local or remote station", apdu.pduDestination) + # find the remote device information + remoteDevice = self.deviceInfoCache.get_device_info(apdu.pduDestination) + if _debug: StateMachineAccessPoint._debug(" - remoteDevice: %r", remoteDevice) + # create a client transaction state machine - tr = ClientSSM(self) + tr = ClientSSM(self, self.localDevice, remoteDevice) + if _debug: StateMachineAccessPoint._debug(" - client segmentation state machine: %r", tr) # add it to our transactions to track it self.clientTransactions.append(tr) @@ -1359,6 +1344,12 @@ class ApplicationServiceAccessPoint(ApplicationServiceElement, ServiceAccessPoin # forward the encoded packet self.request(xpdu) + # if the upper layers of the application did not assign an invoke ID, + # copy the one that was assigned on its way down the stack + if isinstance(apdu, ConfirmedRequestPDU) and apdu.apduInvokeID is None: + if _debug: ApplicationServiceAccessPoint._debug(" - pass invoke ID upstream %r", xpdu.apduInvokeID) + apdu.apduInvokeID = xpdu.apduInvokeID + def confirmation(self, apdu): if _debug: ApplicationServiceAccessPoint._debug("confirmation %r", apdu)