Browse Source

make sure that the initiator code can deal w/ bogus replies and they

are ignored, and it triggers a resent of the previous packet...
irr_shared
John-Mark Gurney 3 years ago
parent
commit
550f7392fe
1 changed files with 107 additions and 36 deletions
  1. +107
    -36
      lora.py

+ 107
- 36
lora.py View File

@@ -46,6 +46,8 @@ CMD_RUNFOR = 3 # arg: (chan, length): turns on chan for length seconds
class LORANode(object):
'''Implement a LORANode initiator.'''

MAC_LEN = 8

def __init__(self, syncdatagram, shared=None):
self.sd = syncdatagram
self.st = Strobe(domain, F=KeccakF(800))
@@ -53,25 +55,38 @@ class LORANode(object):
self.st.key(shared)

async def start(self):
msg = self.st.send_enc(os.urandom(16) + b'reqreset') + \
self.st.send_mac(8)

resp = await self.sd.sendtillrecv(msg, 1)

self.st.recv_enc(resp[:16])
self.st.recv_mac(resp[16:])
resp = await self.sendrecvvalid(os.urandom(16) + b'reqreset')

self.st.ratchet()

resp = await self.sd.sendtillrecv(
self.st.send_enc(b'confirm') + self.st.send_mac(8), 1)

pkt = self.st.recv_enc(resp[:9])
self.st.recv_mac(resp[9:])
pkt = await self.sendrecvvalid(b'confirm')

if pkt != b'confirmed':
raise RuntimeError

async def sendrecvvalid(self, msg):
msg = self.st.send_enc(msg) + self.st.send_mac(self.MAC_LEN)

origstate = self.st.copy()

while True:
resp = await self.sd.sendtillrecv(msg, 1)
#_debprint('got:', resp)

try:
decmsg = self.st.recv_enc(resp[:-self.MAC_LEN])
self.st.recv_mac(resp[-self.MAC_LEN:])
break
except AuthenticationFailed:
# didn't get a valid packet, restore
# state and retry

#_debprint('failed')
self.st.set_state_from(origstate)

#_debprint('got rep:', repr(resp), repr(decmsg))
return decmsg

@staticmethod
def _encodeargs(*args):
r = []
@@ -82,15 +97,12 @@ class LORANode(object):

async def _sendcmd(self, cmd, *args):
cmdbyte = cmd.to_bytes(1, byteorder='little')
pkt = await self.sd.sendtillrecv(
self.st.send_enc(cmdbyte +
self._encodeargs(*args)) + self.st.send_mac(8), 1)

resp = self.st.recv_enc(pkt[:-8])
self.st.recv_mac(pkt[-8:])
resp = await self.sendrecvvalid(cmdbyte + self._encodeargs(*args))

if resp[0:1] != cmdbyte:
raise RuntimeError('response does not match, got: %s, expected: %s' % (repr(resp[0:1]), repr(cmdbyte)))
raise RuntimeError(
'response does not match, got: %s, expected: %s' %
(repr(resp[0:1]), repr(cmdbyte)))

async def waitfor(self, length):
return await self._sendcmd(CMD_WAITFOR, length)
@@ -109,8 +121,8 @@ class SyncDatagram(object):

async def recv(self, timeout=None): #pragma: no cover
'''Receive a datagram. If timeout is not None, wait that many
seconds, and if nothing is received in that time, raise an TimeoutError
exception.'''
seconds, and if nothing is received in that time, raise an
TimeoutError exception.'''

raise NotImplementedError

@@ -125,6 +137,7 @@ class SyncDatagram(object):
then raise an TimeoutError exception.'''

while True:
#_debprint('sending:', repr(data))
await self.send(data)
try:
return await self.recv(freq)
@@ -200,12 +213,42 @@ def timeout(timeout):

return timeout_wrapper

def _debprint(*args): # pragma: no cover
import traceback, sys, os.path
st = traceback.extract_stack(limit=2)[0]

sep = ''
if args:
sep = ':'

print('%s:%d%s' % (os.path.basename(st.filename), st.lineno, sep),
*args)
sys.stdout.flush()

class TestLORANode(unittest.IsolatedAsyncioTestCase):
@timeout(2)
async def test_lora(self):
_self = self
shared_key = os.urandom(32)

class TestSD(MockSyncDatagram):
async def sendgettest(self, msg):
'''Send the message, but make sure that if a
bad message is sent afterward, that it replies
w/ the same previous message.
'''

await self.put(msg)
resp = await self.get()

await self.put(b'bogusmsg' * 5)

resp2 = await self.get()

_self.assertEqual(resp, resp2)

return resp

async def runner(self):
l = Strobe(domain, F=KeccakF(800))

@@ -219,42 +262,66 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase):

assert pkt.endswith(b'reqreset')

# make sure junk gets ignored
await self.put(b'sdlfkj')

# and that the packet remains the same
_self.assertEqual(r, await self.get())

# and a couple more times
await self.put(b'0' * 24)
_self.assertEqual(r, await self.get())
await self.put(b'0' * 32)
_self.assertEqual(r, await self.get())

# send the response
await self.put(l.send_enc(os.urandom(16)) +
l.send_mac(8))

# require no more back tracking at this point
l.ratchet()

# get the confirmation message
r = await self.get()

# test the resend capabilities
await self.put(b'0' * 24)
_self.assertEqual(r, await self.get())

# decode confirmation message
c = l.recv_enc(r[:-8])
l.recv_mac(r[-8:])

assert c == b'confirm'
# assert that we got it
_self.assertEqual(c, b'confirm')

await self.put(l.send_enc(b'confirmed') +
l.send_mac(8))
# send confirmed reply
r = await self.sendgettest(l.send_enc(
b'confirmed') + l.send_mac(8))

r = await self.get()
# test and decode remaining command messages
cmd = l.recv_enc(r[:-8])
l.recv_mac(r[-8:])

assert cmd[0] == CMD_WAITFOR
assert int.from_bytes(cmd[1:], byteorder='little') == 30
assert int.from_bytes(cmd[1:],
byteorder='little') == 30

await self.put(l.send_enc(cmd[0:1]) +
l.send_mac(8))
r = await self.sendgettest(l.send_enc(
cmd[0:1]) + l.send_mac(8))

r = await self.get()
cmd = l.recv_enc(r[:-8])
l.recv_mac(r[-8:])

assert cmd[0] == CMD_RUNFOR
assert int.from_bytes(cmd[1:5], byteorder='little') == 1
assert int.from_bytes(cmd[5:], byteorder='little') == 50
assert int.from_bytes(cmd[1:5],
byteorder='little') == 1
assert int.from_bytes(cmd[5:],
byteorder='little') == 50

await self.put(l.send_enc(cmd[0:1]) +
l.send_mac(8))
r = await self.sendgettest(l.send_enc(
cmd[0:1]) + l.send_mac(8))

r = await self.get()
cmd = l.recv_enc(r[:-8])
l.recv_mac(r[-8:])

@@ -279,6 +346,7 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase):
# Make sure all messages have been processed
self.assertTrue(tsd.sendq.empty())
self.assertTrue(tsd.recvq.empty())
#_debprint('done')

@timeout(2)
async def test_ccode(self):
@@ -345,8 +413,10 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase):
lora_comms.comms_process(commstate, r,
outbuf)

# make sure that the reply matches previous
_self.assertEqual(origmsg, outbuf._from())
# make sure that the reply matches
# the previous
_self.assertEqual(origmsg,
outbuf._from())

# pass the reply back
await self.put(outbytes[:outbuf.pktlen])
@@ -379,3 +449,4 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase):
# Make sure all the expected messages have been
# processed.
self.assertFalse(exptmsgs)
#_debprint('done')

Loading…
Cancel
Save