mirror of
https://github.com/JoelBender/bacpypes
synced 2025-09-28 22:15:23 +08:00
add a call() transition and tests, foreign device registration tests
This commit is contained in:
parent
209d619994
commit
4c1ecffba6
|
@ -493,7 +493,7 @@ class BIPForeign(BIPSAP, Client, Server, OneShotTask, DebugContents):
|
|||
# check for success
|
||||
if pdu.bvlciResultCode == 0:
|
||||
# schedule for a refresh
|
||||
self.install_task(_time() + self.bbmdTimeToLive)
|
||||
self.install_task(delta=self.bbmdTimeToLive)
|
||||
|
||||
return
|
||||
|
||||
|
@ -541,7 +541,7 @@ class BIPForeign(BIPSAP, Client, Server, OneShotTask, DebugContents):
|
|||
self.bbmdTimeToLive = ttl
|
||||
|
||||
# install this task to run when it gets a chance
|
||||
self.install_task(0)
|
||||
self.install_task(delta=0)
|
||||
|
||||
def unregister(self):
|
||||
"""Drop the registration with a BBMD."""
|
||||
|
|
|
@ -489,7 +489,7 @@ class BIPForeign(BIPSAP, Client, Server, OneShotTask, DebugContents):
|
|||
# check for success
|
||||
if pdu.bvlciResultCode == 0:
|
||||
# schedule for a refresh
|
||||
self.install_task(_time() + self.bbmdTimeToLive)
|
||||
self.install_task(delta=self.bbmdTimeToLive)
|
||||
|
||||
return
|
||||
|
||||
|
@ -537,7 +537,7 @@ class BIPForeign(BIPSAP, Client, Server, OneShotTask, DebugContents):
|
|||
self.bbmdTimeToLive = ttl
|
||||
|
||||
# install this task to run when it gets a chance
|
||||
self.install_task(0)
|
||||
self.install_task(delta=0)
|
||||
|
||||
def unregister(self):
|
||||
"""Drop the registration with a BBMD."""
|
||||
|
|
|
@ -488,7 +488,7 @@ class BIPForeign(BIPSAP, Client, Server, OneShotTask, DebugContents):
|
|||
# check for success
|
||||
if pdu.bvlciResultCode == 0:
|
||||
# schedule for a refresh
|
||||
self.install_task(_time() + self.bbmdTimeToLive)
|
||||
self.install_task(delta=self.bbmdTimeToLive)
|
||||
|
||||
return
|
||||
|
||||
|
@ -536,7 +536,7 @@ class BIPForeign(BIPSAP, Client, Server, OneShotTask, DebugContents):
|
|||
self.bbmdTimeToLive = ttl
|
||||
|
||||
# install this task to run when it gets a chance
|
||||
self.install_task(0)
|
||||
self.install_task(delta=0)
|
||||
|
||||
def unregister(self):
|
||||
"""Drop the registration with a BBMD."""
|
||||
|
|
|
@ -67,6 +67,15 @@ class TimeoutTransition(Transition):
|
|||
self.timeout = timeout
|
||||
|
||||
|
||||
class CallTransition(Transition):
|
||||
|
||||
def __init__(self, fnargs, next_state):
|
||||
Transition.__init__(self, next_state)
|
||||
|
||||
# a tuple of (fn, *args, *kwargs)
|
||||
self.fnargs = fnargs
|
||||
|
||||
|
||||
#
|
||||
# match_pdu
|
||||
#
|
||||
|
@ -134,6 +143,9 @@ class State(object):
|
|||
# timeout transition
|
||||
self.timeout_transition = None
|
||||
|
||||
# call transition
|
||||
self.call_transition = None
|
||||
|
||||
def reset(self):
|
||||
"""Override this method in a derived class if the state maintains
|
||||
counters or other information. Called when the associated state
|
||||
|
@ -307,7 +319,7 @@ class State(object):
|
|||
def unexpected_receive(self, pdu):
|
||||
"""Called with PDU that did not match. Unless this is trapped by the
|
||||
state, the default behaviour is to fail."""
|
||||
if _debug: State._debug("unexpected_receive %r", pdu)
|
||||
if _debug: State._debug("unexpected_receive(%s) %r", self.doc_string, pdu)
|
||||
|
||||
# pass along to the state machine
|
||||
self.state_machine.unexpected_receive(pdu)
|
||||
|
@ -393,6 +405,48 @@ class State(object):
|
|||
# return the next state
|
||||
return next_state
|
||||
|
||||
def call(self, fn, *args, **kwargs):
|
||||
"""Create a CallTransition from this state to another, possibly new,
|
||||
state. The next state is returned for method chaining.
|
||||
|
||||
:param criteria: PDU to match
|
||||
:param next_state: destination state after a successful match
|
||||
|
||||
Simulate the function as if it was defined in Python3.5+ like this:
|
||||
def receive(self, fn, *args, next_state=None, **kwargs)
|
||||
"""
|
||||
if _debug: State._debug("call(%s) %r %r %r", self.doc_string, fn, args, kwargs)
|
||||
|
||||
# only one call per state
|
||||
if self.call_transition:
|
||||
raise RuntimeError("only one 'call' per state")
|
||||
|
||||
# extract the next_state keyword argument
|
||||
if 'next_state' in kwargs:
|
||||
next_state = kwargs['next_state']
|
||||
if _debug: State._debug(" - next_state: %r", next_state)
|
||||
|
||||
del kwargs['next_state']
|
||||
else:
|
||||
next_state = None
|
||||
|
||||
# maybe build a new state
|
||||
if not next_state:
|
||||
next_state = self.state_machine.new_state()
|
||||
if _debug: State._debug(" - new next_state: %r", next_state)
|
||||
elif next_state not in self.state_machine.states:
|
||||
raise ValueError("off the rails")
|
||||
|
||||
# create a bundle of the match criteria
|
||||
fnargs = (fn, args, kwargs)
|
||||
if _debug: State._debug(" - fnargs: %r", fnargs)
|
||||
|
||||
# add this to the list of transitions
|
||||
self.call_transition = CallTransition(fnargs, next_state)
|
||||
|
||||
# return the next state
|
||||
return next_state
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s(%s) at %s>" % (
|
||||
self.__class__.__name__,
|
||||
|
@ -421,8 +475,12 @@ class StateMachine(object):
|
|||
unexpected_receive_state=None,
|
||||
machine_group=None,
|
||||
state_subclass=State,
|
||||
name='',
|
||||
):
|
||||
if _debug: StateMachine._debug("__init__")
|
||||
if _debug: StateMachine._debug("__init__(%s)", name)
|
||||
|
||||
# save the name for debugging
|
||||
self.name = name
|
||||
|
||||
# no states to starting out, not running
|
||||
self.states = []
|
||||
|
@ -476,7 +534,7 @@ class StateMachine(object):
|
|||
self.timeout_task = None
|
||||
|
||||
def new_state(self, doc="", state_subclass=None):
|
||||
if _debug: StateMachine._debug("new_state %r %r", doc, state_subclass)
|
||||
if _debug: StateMachine._debug("new_state(%s) %r %r", self.name, doc, state_subclass)
|
||||
|
||||
# check for proper subclass
|
||||
if state_subclass and not issubclass(state_subclass, State):
|
||||
|
@ -493,7 +551,7 @@ class StateMachine(object):
|
|||
return state
|
||||
|
||||
def reset(self):
|
||||
if _debug: StateMachine._debug("reset")
|
||||
if _debug: StateMachine._debug("reset(%s)", self.name)
|
||||
|
||||
# make sure we're not running
|
||||
if self.running:
|
||||
|
@ -511,7 +569,7 @@ class StateMachine(object):
|
|||
state.reset()
|
||||
|
||||
def run(self):
|
||||
if _debug: StateMachine._debug("run")
|
||||
if _debug: StateMachine._debug("run(%s)", self.name)
|
||||
|
||||
if self.running:
|
||||
raise RuntimeError("state machine running")
|
||||
|
@ -542,7 +600,7 @@ class StateMachine(object):
|
|||
|
||||
def halt(self):
|
||||
"""Called when the state machine should no longer be running."""
|
||||
if _debug: StateMachine._debug("halt")
|
||||
if _debug: StateMachine._debug("halt(%s)", self.name)
|
||||
|
||||
# make sure we're running
|
||||
if not self.running:
|
||||
|
@ -558,14 +616,14 @@ class StateMachine(object):
|
|||
|
||||
def success(self):
|
||||
"""Called when the state machine has successfully completed."""
|
||||
if _debug: StateMachine._debug("success")
|
||||
if _debug: StateMachine._debug("success(%s)", self.name)
|
||||
|
||||
def fail(self):
|
||||
"""Called when the state machine has failed."""
|
||||
if _debug: StateMachine._debug("fail")
|
||||
if _debug: StateMachine._debug("fail(%s)", self.name)
|
||||
|
||||
def goto_state(self, state):
|
||||
if _debug: StateMachine._debug("goto_state %r", state)
|
||||
if _debug: StateMachine._debug("goto_state(%s) %r", self.name, state)
|
||||
|
||||
# where do you think you're going?
|
||||
if state not in self.states:
|
||||
|
@ -586,6 +644,10 @@ class StateMachine(object):
|
|||
# here we are
|
||||
current_state = self.current_state = state
|
||||
|
||||
# let the state do something
|
||||
current_state.enter_state()
|
||||
if _debug: StateMachine._debug(" - state entered")
|
||||
|
||||
# events are managed by a state machine group
|
||||
if self.machine_group:
|
||||
# setting events
|
||||
|
@ -626,10 +688,6 @@ class StateMachine(object):
|
|||
|
||||
return
|
||||
|
||||
# let the state do something
|
||||
current_state.enter_state()
|
||||
if _debug: StateMachine._debug(" - state entered")
|
||||
|
||||
# assume we can stay
|
||||
next_state = None
|
||||
|
||||
|
@ -651,6 +709,21 @@ class StateMachine(object):
|
|||
else:
|
||||
if _debug: StateMachine._debug(" - not part of a group")
|
||||
|
||||
# call things that need to be called
|
||||
if current_state.call_transition:
|
||||
if _debug: StateMachine._debug(" - calling: %r", current_state.call_transition)
|
||||
|
||||
# pull apart the pieces and call it
|
||||
fn, args, kwargs = current_state.call_transition.fnargs
|
||||
fn( *args, **kwargs)
|
||||
if _debug: StateMachine._debug(" - called")
|
||||
|
||||
# check for a transition
|
||||
next_state = current_state.call_transition.next_state
|
||||
if _debug: StateMachine._debug(" - next_state: %r", next_state)
|
||||
else:
|
||||
if _debug: StateMachine._debug(" - no calls")
|
||||
|
||||
# send everything that needs to be sent
|
||||
if not next_state:
|
||||
for transition in current_state.send_transitions:
|
||||
|
@ -710,7 +783,7 @@ class StateMachine(object):
|
|||
self.transaction_log.append((">>>", pdu),)
|
||||
|
||||
def receive(self, pdu):
|
||||
if _debug: StateMachine._debug("receive %r", pdu)
|
||||
if _debug: StateMachine._debug("receive(%s) %r", self.name, pdu)
|
||||
|
||||
if not self.running:
|
||||
if _debug: StateMachine._debug(" - not running")
|
||||
|
@ -726,6 +799,7 @@ class StateMachine(object):
|
|||
if not self.current_state:
|
||||
raise RuntimeError("no current state")
|
||||
current_state = self.current_state
|
||||
if _debug: StateMachine._debug(" - current_state: %r", current_state)
|
||||
|
||||
# let the state know this was received
|
||||
current_state.before_receive(pdu)
|
||||
|
@ -766,7 +840,7 @@ class StateMachine(object):
|
|||
def unexpected_receive(self, pdu):
|
||||
"""Called with PDU that did not match. Unless this is trapped by the
|
||||
state, the default behaviour is to fail."""
|
||||
if _debug: StateMachine._debug("unexpected_receive %r", pdu)
|
||||
if _debug: StateMachine._debug("unexpected_receive(%s) %r", self.name, pdu)
|
||||
|
||||
# go to the unexpected receive state (failing)
|
||||
self.goto_state(self.unexpected_receive_state)
|
||||
|
@ -775,7 +849,7 @@ class StateMachine(object):
|
|||
"""Called by the state machine group when an event is set, the state
|
||||
machine checks to see if it's waiting for the event and makes the
|
||||
state transition if there is a match."""
|
||||
if _debug: StateMachine._debug("event_set %r", event_id)
|
||||
if _debug: StateMachine._debug("event_set(%s) %r", self.name, event_id)
|
||||
|
||||
if not self.running:
|
||||
if _debug: StateMachine._debug(" - not running")
|
||||
|
@ -815,7 +889,7 @@ class StateMachine(object):
|
|||
self.goto_state(next_state)
|
||||
|
||||
def state_timeout(self):
|
||||
if _debug: StateMachine._debug("state_timeout")
|
||||
if _debug: StateMachine._debug("state_timeout(%s)", self.name)
|
||||
|
||||
if not self.running:
|
||||
raise RuntimeError("state machine not running")
|
||||
|
@ -826,7 +900,7 @@ class StateMachine(object):
|
|||
self.goto_state(self.current_state.timeout_transition.next_state)
|
||||
|
||||
def state_machine_timeout(self):
|
||||
if _debug: StateMachine._debug("state_machine_timeout")
|
||||
if _debug: StateMachine._debug("state_machine_timeout(%s)", self.name)
|
||||
|
||||
if not self.running:
|
||||
raise RuntimeError("state machine not running")
|
||||
|
@ -835,7 +909,7 @@ class StateMachine(object):
|
|||
self.goto_state(self.timeout_state)
|
||||
|
||||
def match_pdu(self, pdu, criteria):
|
||||
if _debug: StateMachine._debug("match_pdu %r %r", pdu, criteria)
|
||||
if _debug: StateMachine._debug("match_pdu(%s) %r %r", self.name, pdu, criteria)
|
||||
|
||||
# separate the pdu_type and attributes to match
|
||||
pdu_type, pdu_attrs = criteria
|
||||
|
@ -1062,7 +1136,12 @@ class StateMachineGroup(object):
|
|||
def fail(self):
|
||||
"""Called when all of the machines in the group have halted and at
|
||||
at least one of them is in a 'fail' final state."""
|
||||
if _debug: StateMachineGroup._debug("fail")
|
||||
if _debug:
|
||||
StateMachineGroup._debug("fail")
|
||||
for state_machine in self.state_machines:
|
||||
StateMachineGroup._debug(" - machine: %r", state_machine)
|
||||
for direction, pdu in state_machine.transaction_log:
|
||||
StateMachineGroup._debug(" %s %s", direction, str(pdu))
|
||||
|
||||
self.is_running = False
|
||||
self.is_fail_state = True
|
||||
|
@ -1120,3 +1199,4 @@ class ServerStateMachine(Server, StateMachine):
|
|||
def indication(self, pdu):
|
||||
if _debug: ServerStateMachine._debug("indication %r", pdu)
|
||||
self.receive(pdu)
|
||||
|
||||
|
|
|
@ -112,7 +112,8 @@ class SnifferNode(_repr, ClientStateMachine):
|
|||
if _debug: SnifferNode._debug("__init__ %r %r", address, vlan)
|
||||
ClientStateMachine.__init__(self)
|
||||
|
||||
# save the address
|
||||
# save the name and address
|
||||
self.name = address
|
||||
self.address = Address(address)
|
||||
|
||||
# create a promiscuous node, added to the network
|
||||
|
@ -123,6 +124,31 @@ class SnifferNode(_repr, ClientStateMachine):
|
|||
bind(self, self.node)
|
||||
|
||||
|
||||
#
|
||||
# CodecNode
|
||||
#
|
||||
|
||||
@bacpypes_debugging
|
||||
class CodecNode(_repr, ClientStateMachine):
|
||||
|
||||
def __init__(self, address, vlan):
|
||||
if _debug: CodecNode._debug("__init__ %r %r", address, vlan)
|
||||
ClientStateMachine.__init__(self)
|
||||
|
||||
# save the name and address
|
||||
self.name = address
|
||||
self.address = Address(address)
|
||||
|
||||
# BACnet/IP interpreter
|
||||
self.annexj = AnnexJCodec()
|
||||
|
||||
# fake multiplexer has a VLAN node in it
|
||||
self.mux = FauxMultiplexer(self.address, vlan)
|
||||
|
||||
# bind the stack together
|
||||
bind(self, self.annexj, self.mux)
|
||||
|
||||
|
||||
#
|
||||
# SimpleNode
|
||||
#
|
||||
|
@ -134,7 +160,8 @@ class SimpleNode(_repr, ClientStateMachine):
|
|||
if _debug: SimpleNode._debug("__init__ %r %r", address, vlan)
|
||||
ClientStateMachine.__init__(self)
|
||||
|
||||
# save the address
|
||||
# save the name and address
|
||||
self.name = address
|
||||
self.address = Address(address)
|
||||
|
||||
# BACnet/IP interpreter
|
||||
|
@ -159,7 +186,8 @@ class ForeignNode(_repr, ClientStateMachine):
|
|||
if _debug: ForeignNode._debug("__init__ %r %r", address, vlan)
|
||||
ClientStateMachine.__init__(self)
|
||||
|
||||
# save the address
|
||||
# save the name and address
|
||||
self.name = address
|
||||
self.address = Address(address)
|
||||
|
||||
# BACnet/IP interpreter
|
||||
|
@ -183,17 +211,24 @@ class BBMDNode(_repr, ClientStateMachine):
|
|||
if _debug: BBMDNode._debug("__init__ %r %r", address, vlan)
|
||||
ClientStateMachine.__init__(self)
|
||||
|
||||
# save the address
|
||||
# save the name and address
|
||||
self.name = address
|
||||
self.address = Address(address)
|
||||
|
||||
# BACnet/IP interpreter
|
||||
self.bip = BIPBBMD(self.address)
|
||||
self.annexj = AnnexJCodec()
|
||||
|
||||
# build an address, full mask
|
||||
bdt_address = "%s/32:%d" % self.address.addrTuple
|
||||
if _debug: BBMDNode._debug(" - bdt_address: %r", bdt_address)
|
||||
|
||||
# add itself as the first entry in the BDT
|
||||
self.bip.add_peer(Address(bdt_address))
|
||||
|
||||
# fake multiplexer has a VLAN node in it
|
||||
self.mux = FauxMultiplexer(self.address, vlan)
|
||||
|
||||
# bind the stack together
|
||||
bind(self, self.bip, self.annexj, self.mux)
|
||||
|
||||
|
||||
|
|
|
@ -12,11 +12,12 @@ from bacpypes.debugging import bacpypes_debugging, ModuleLogger, xtob
|
|||
|
||||
from bacpypes.pdu import Address, PDU, LocalBroadcast
|
||||
from bacpypes.vlan import IPNetwork, IPRouter
|
||||
from bacpypes.bvll import ReadForeignDeviceTable, ReadForeignDeviceTableAck
|
||||
|
||||
from ..state_machine import match_pdu, StateMachineGroup
|
||||
from ..state_machine import StateMachineGroup
|
||||
from ..time_machine import reset_time_machine, run_time_machine
|
||||
|
||||
from .helpers import SnifferNode, SimpleNode, ForeignNode, BBMDNode
|
||||
from .helpers import SnifferNode, CodecNode, SimpleNode, ForeignNode, BBMDNode
|
||||
|
||||
# some debugging
|
||||
_debug = 0
|
||||
|
@ -49,26 +50,14 @@ class TNetwork(StateMachineGroup):
|
|||
self.home_vlan = IPNetwork()
|
||||
self.router.add_network(Address("192.168.5.1/24"), self.home_vlan)
|
||||
|
||||
# home sniffer node
|
||||
self.home_sniffer = SnifferNode("192.168.5.254/24", self.home_vlan)
|
||||
self.append(self.home_sniffer)
|
||||
|
||||
# make a remote LAN
|
||||
self.remote_vlan = IPNetwork()
|
||||
self.router.add_network(Address("192.168.6.1/24"), self.remote_vlan)
|
||||
|
||||
# remote sniffer node
|
||||
self.remote_sniffer = SnifferNode("192.168.6.254/24", self.remote_vlan)
|
||||
self.append(self.remote_sniffer)
|
||||
|
||||
# the foreign device
|
||||
self.fd = ForeignNode("192.168.6.2/24", self.remote_vlan)
|
||||
self.append(self.fd)
|
||||
|
||||
# intermediate test node
|
||||
self.tnode = SimpleNode("192.168.5.2/24", self.home_vlan)
|
||||
self.append(self.tnode)
|
||||
|
||||
# bbmd
|
||||
self.bbmd = BBMDNode("192.168.5.3/24", self.home_vlan)
|
||||
self.append(self.bbmd)
|
||||
|
@ -81,12 +70,7 @@ class TNetwork(StateMachineGroup):
|
|||
|
||||
# run it for some time
|
||||
run_time_machine(time_limit)
|
||||
if _debug:
|
||||
TNetwork._debug(" - time machine finished")
|
||||
for state_machine in self.state_machines:
|
||||
TNetwork._debug(" - machine: %r", state_machine)
|
||||
for direction, pdu in state_machine.transaction_log:
|
||||
TNetwork._debug(" %s %s", direction, str(pdu))
|
||||
if _debug: TNetwork._debug(" - time machine finished")
|
||||
|
||||
# check for success
|
||||
all_success, some_failed = super(TNetwork, self).check_for_success()
|
||||
|
@ -94,22 +78,227 @@ class TNetwork(StateMachineGroup):
|
|||
|
||||
|
||||
@bacpypes_debugging
|
||||
class TestSimple(unittest.TestCase):
|
||||
class TestForeign(unittest.TestCase):
|
||||
|
||||
def test_idle(self):
|
||||
"""Test an idle network, nothing happens is success."""
|
||||
if _debug: TestSimple._debug("test_idle")
|
||||
if _debug: TestForeign._debug("test_idle")
|
||||
|
||||
# create a network
|
||||
tnet = TNetwork()
|
||||
|
||||
# all start states are successful
|
||||
tnet.home_sniffer.start_state.success()
|
||||
tnet.remote_sniffer.start_state.success()
|
||||
tnet.fd.start_state.success()
|
||||
tnet.tnode.start_state.success()
|
||||
tnet.bbmd.start_state.success()
|
||||
|
||||
# run the group
|
||||
tnet.run()
|
||||
|
||||
def test_registration(self):
|
||||
"""Test foreign device registration."""
|
||||
if _debug: TestForeign._debug("test_registration")
|
||||
|
||||
# create a network
|
||||
tnet = TNetwork()
|
||||
|
||||
# add an addition codec node to the home vlan
|
||||
cnode = CodecNode("192.168.5.2/24", tnet.home_vlan)
|
||||
tnet.append(cnode)
|
||||
|
||||
# home sniffer node
|
||||
home_sniffer = SnifferNode("192.168.5.254/24", tnet.home_vlan)
|
||||
tnet.append(home_sniffer)
|
||||
|
||||
# remote sniffer node
|
||||
remote_sniffer = SnifferNode("192.168.6.254/24", tnet.remote_vlan)
|
||||
tnet.append(remote_sniffer)
|
||||
|
||||
# tell the B/IP layer of the foreign device to register
|
||||
tnet.fd.start_state \
|
||||
.call(tnet.fd.bip.register, tnet.bbmd.address, 30) \
|
||||
.success()
|
||||
|
||||
# sniffer pieces
|
||||
registration_request = xxtob('81.05.0006' # bvlci
|
||||
'001e' # time-to-live
|
||||
)
|
||||
registration_ack = xxtob('81.00.0006.0000') # simple ack
|
||||
|
||||
# remote sniffer sees registration
|
||||
remote_sniffer.start_state \
|
||||
.receive(PDU, pduData=registration_request).doc("--1-1") \
|
||||
.receive(PDU, pduData=registration_ack).doc("--1-2") \
|
||||
.set_event('fd-registered').doc("--1-3") \
|
||||
.success()
|
||||
|
||||
# the bbmd is idle
|
||||
tnet.bbmd.start_state.success()
|
||||
|
||||
# read the FDT
|
||||
cnode.start_state \
|
||||
.wait_event('fd-registered').doc("--1-4") \
|
||||
.send(ReadForeignDeviceTable(destination=tnet.bbmd.address)).doc("--1-5") \
|
||||
.receive(ReadForeignDeviceTableAck).doc("--1-6") \
|
||||
.success()
|
||||
|
||||
# the tnode reads the registration table
|
||||
read_fdt_request = xxtob('81.06.0004') # bvlci
|
||||
read_fdt_ack = xxtob('81.07.000e' # read-ack
|
||||
'c0.a8.06.02.ba.c0 001e 0023' # address, ttl, remaining
|
||||
)
|
||||
|
||||
# home sniffer sees registration
|
||||
home_sniffer.start_state \
|
||||
.receive(PDU, pduData=registration_request).doc("--1-7") \
|
||||
.receive(PDU, pduData=registration_ack).doc("--1-8") \
|
||||
.receive(PDU, pduData=read_fdt_request).doc("--1-9") \
|
||||
.receive(PDU, pduData=read_fdt_ack).doc("--1-A") \
|
||||
.success()
|
||||
|
||||
# run the group
|
||||
tnet.run()
|
||||
|
||||
def test_refresh_registration(self):
|
||||
"""Test refreshing foreign device registration."""
|
||||
if _debug: TestForeign._debug("test_refresh_registration")
|
||||
|
||||
# create a network
|
||||
tnet = TNetwork()
|
||||
|
||||
# tell the B/IP layer of the foreign device to register
|
||||
tnet.fd.start_state \
|
||||
.call(tnet.fd.bip.register, tnet.bbmd.address, 10) \
|
||||
.success()
|
||||
|
||||
# the bbmd is idle
|
||||
tnet.bbmd.start_state.success()
|
||||
|
||||
# remote sniffer node
|
||||
remote_sniffer = SnifferNode("192.168.6.254/24", tnet.remote_vlan)
|
||||
tnet.append(remote_sniffer)
|
||||
|
||||
# sniffer pieces
|
||||
registration_request = xxtob('81.05.0006' # bvlci
|
||||
'000a' # time-to-live
|
||||
)
|
||||
registration_ack = xxtob('81.00.0006.0000') # simple ack
|
||||
|
||||
# remote sniffer sees registration
|
||||
remote_sniffer.start_state \
|
||||
.receive(PDU, pduData=registration_request).doc("--2-1") \
|
||||
.receive(PDU, pduData=registration_ack).doc("--2-2") \
|
||||
.receive(PDU, pduData=registration_request).doc("--2-3") \
|
||||
.receive(PDU, pduData=registration_ack).doc("--2-4") \
|
||||
.success()
|
||||
|
||||
# run the group
|
||||
tnet.run()
|
||||
|
||||
def test_unicast(self):
|
||||
"""Test a unicast message from the foreign device to the bbmd."""
|
||||
if _debug: TestForeign._debug("test_unicast")
|
||||
|
||||
# create a network
|
||||
tnet = TNetwork()
|
||||
|
||||
# make a PDU from node 1 to node 2
|
||||
pdu_data = xxtob('dead.beef')
|
||||
pdu = PDU(pdu_data, source=tnet.fd.address, destination=tnet.bbmd.address)
|
||||
if _debug: TestForeign._debug(" - pdu: %r", pdu)
|
||||
|
||||
# register, wait for ack, send some beef
|
||||
tnet.fd.start_state \
|
||||
.call(tnet.fd.bip.register, tnet.bbmd.address, 60).doc("--3-1") \
|
||||
.wait_event('fd-registered').doc("--3-2") \
|
||||
.send(pdu).doc("--3-3") \
|
||||
.success()
|
||||
|
||||
# the bbmd is happy when it gets the pdu
|
||||
tnet.bbmd.start_state \
|
||||
.receive(PDU, pduSource=tnet.fd.address, pduData=pdu_data) \
|
||||
.success()
|
||||
|
||||
# remote sniffer node
|
||||
remote_sniffer = SnifferNode("192.168.6.254/24", tnet.remote_vlan)
|
||||
tnet.append(remote_sniffer)
|
||||
|
||||
# sniffer pieces
|
||||
registration_request = xxtob('81.05.0006' # bvlci
|
||||
'003c' # time-to-live (60)
|
||||
)
|
||||
registration_ack = xxtob('81.00.0006.0000') # simple ack
|
||||
unicast_pdu = xxtob('81.0a.0008' # original unicast bvlci
|
||||
'dead.beef' # PDU being unicast
|
||||
)
|
||||
|
||||
# remote sniffer sees registration
|
||||
remote_sniffer.start_state \
|
||||
.receive(PDU, pduData=registration_request).doc("--3-4") \
|
||||
.receive(PDU, pduData=registration_ack).doc("--3-5") \
|
||||
.set_event('fd-registered').doc("--3-6") \
|
||||
.receive(PDU, pduData=unicast_pdu).doc("--3-7") \
|
||||
.success()
|
||||
|
||||
# run the group
|
||||
tnet.run()
|
||||
|
||||
def test_broadcast(self):
|
||||
"""Test a broadcast message from the foreign device to the bbmd."""
|
||||
if _debug: TestForeign._debug("+test_broadcast")
|
||||
|
||||
# create a network
|
||||
tnet = TNetwork()
|
||||
|
||||
# make a broadcast pdu
|
||||
pdu_data = xxtob('dead.beef')
|
||||
pdu = PDU(pdu_data, destination=LocalBroadcast())
|
||||
if _debug: TestForeign._debug(" - pdu: %r", pdu)
|
||||
|
||||
# register, wait for ack, send some beef
|
||||
tnet.fd.start_state \
|
||||
.call(tnet.fd.bip.register, tnet.bbmd.address, 60).doc("--4-1") \
|
||||
.wait_event('4-registered').doc("--4-2") \
|
||||
.send(pdu).doc("--4-3") \
|
||||
.success()
|
||||
|
||||
# the bbmd is happy when it gets the pdu
|
||||
tnet.bbmd.start_state \
|
||||
.receive(PDU, pduSource=tnet.fd.address, pduData=pdu_data) \
|
||||
.success()
|
||||
|
||||
# home sniffer node
|
||||
home_node = SimpleNode("192.168.5.254/24", tnet.home_vlan)
|
||||
tnet.append(home_node)
|
||||
|
||||
# home node happy when getting the pdu, broadcast by the bbmd
|
||||
home_node.start_state.doc("--4-4") \
|
||||
.receive(PDU, pduSource=tnet.fd.address, pduData=pdu_data).doc("--4-5") \
|
||||
.success()
|
||||
|
||||
# remote sniffer node
|
||||
remote_sniffer = SnifferNode("192.168.6.254/24", tnet.remote_vlan)
|
||||
tnet.append(remote_sniffer)
|
||||
|
||||
# sniffer pieces
|
||||
registration_request = xxtob('81.05.0006' # bvlci
|
||||
'003c' # time-to-live (60)
|
||||
)
|
||||
registration_ack = xxtob('81.00.0006.0000') # simple ack
|
||||
distribute_pdu = xxtob('81.09.0008' # bvlci
|
||||
'deadbeef' # PDU to broadcast
|
||||
)
|
||||
|
||||
# remote sniffer sees registration
|
||||
remote_sniffer.start_state \
|
||||
.receive(PDU, pduData=registration_request).doc("--4-6") \
|
||||
.call(TestForeign._debug, "::--4-5").doc("--4-7") \
|
||||
.receive(PDU, pduData=registration_ack).doc("--4-8") \
|
||||
.set_event('4-registered') \
|
||||
.call(TestForeign._debug, "::--4-7").doc("--4-9") \
|
||||
.receive(PDU, pduData=distribute_pdu).doc("--4-10") \
|
||||
.success()
|
||||
|
||||
# run the group
|
||||
tnet.run(4.0)
|
||||
if _debug: TestForeign._debug("-test_broadcast")
|
||||
|
||||
|
|
|
@ -262,6 +262,26 @@ class TestStateMachine(unittest.TestCase):
|
|||
assert tsm.transaction_log[0][1] is bad_pdu
|
||||
if _debug: TestStateMachine._debug(" - passed")
|
||||
|
||||
def test_state_machine_call(self):
|
||||
if _debug: TestStateMachine._debug("test_state_machine_call")
|
||||
|
||||
# simple hook
|
||||
self._called = False
|
||||
|
||||
# create a trapped state machine
|
||||
tsm = TrappedStateMachine()
|
||||
|
||||
# make a send transition from start to success, run the machine
|
||||
tsm.start_state.call(setattr, self, '_called', True).success()
|
||||
tsm.run()
|
||||
|
||||
# check for success
|
||||
assert not tsm.running
|
||||
assert tsm.current_state.is_success_state
|
||||
|
||||
# check for the call
|
||||
assert self._called
|
||||
|
||||
def test_state_machine_loop_01(self):
|
||||
if _debug: TestStateMachine._debug("test_state_machine_loop_01")
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user