Browse Source

add the start of the C version for the uC...

Turns out there's a bit of code that isn't compatible w/ the Python version,
eliminate it...  This was to support encoding lengths (via negative length
parameters)...

Also, the default C version (which we want to use) is Keccak(800) and not
Keccak(1600), switch Python to 800, as it'll be faster on the 32-bit uC, and
still has plenty of security margin...
irr_shared
John-Mark Gurney 3 years ago
parent
commit
5ad4088bf8
6 changed files with 322 additions and 27 deletions
  1. +33
    -4
      Makefile
  2. +110
    -0
      comms.c
  3. +31
    -0
      comms.h
  4. +89
    -4
      lora.py
  5. +59
    -0
      lora_comms.py
  6. +0
    -19
      strobe/strobe.c

+ 33
- 4
Makefile View File

@@ -6,6 +6,14 @@ ARMTARGET?= -mcpu=cortex-m3 -mthumb -DSTROBE_SINGLE_THREAD=1
#ARMCC?=clang-mp-9.0 #ARMCC?=clang-mp-9.0
#ARMTARGET?= -nostdlib -ffreestanding -target arm-none-eabi -mcpu=cortex-m3 -mfloat-abi=soft -mthumb #ARMTARGET?= -nostdlib -ffreestanding -target arm-none-eabi -mcpu=cortex-m3 -mfloat-abi=soft -mthumb


PLATFORM != uname -s

.if $(PLATFORM) == "Darwin"
SOEXT=dylib
.else
.error Unsupported platform: $(PLATFORM)
.endif

PROG = lora.irr PROG = lora.irr
PROGEXT = .elf PROGEXT = .elf


@@ -86,12 +94,28 @@ CFLAGS+= -I$(STM32)/usb
OBJS = $(SRCS:C/.c$/.o/) OBJS = $(SRCS:C/.c$/.o/)
CFLAGS+= -Werror -Wall CFLAGS+= -Werror -Wall


LIBLORA_TEST_SRCS= comms.c strobe.c x25519.c
LIBLORA_TEST_OBJS= $(LIBLORA_TEST_SRCS:C/.c$/.no/)

LIBLORA_TEST = liblora_test.$(SOEXT)
$(LIBLORA_TEST): $(LIBLORA_TEST_OBJS)
$(CC) -shared -o $@ $(.ALLSRC)

.MAIN: all
.PHONY: all .PHONY: all
all: $(PROG)$(PROGEXT) $(PROG).list all: $(PROG)$(PROGEXT) $(PROG).list


.PHONY: depend .PHONY: depend
depend: $(SRCS)
$(ARMCC) $(ARMTARGET) $(CFLAGS) $(.ALLSRC) -MM > .depend || rm -f .depend
depend: .arm_deps .test_deps

.sinclude ".arm_deps"
.sinclude ".test_deps"

.arm_deps: $(SRCS)
$(ARMCC) $(ARMTARGET) $(CFLAGS) $(.ALLSRC) -MM > $@ || rm -f $@

.test_deps: $(LIBLORA_TEST_SRCS)
$(CC) $(CFLAGS) $(.ALLSRC) -MM | sed -e 's/\.o:/\.no:/' > $@ || rm -f $@


$(PROG)$(PROGEXT): $(OBJS) $(PROG)$(PROGEXT): $(OBJS)
$(ARMCC) $(ARMTARGET) -o $@ $(.ALLSRC) -T$(LINKER_SCRIPT) --specs=nosys.specs -Wl,--gc-sections -static --specs=nano.specs -Wl,--start-group -lc -lm -Wl,--end-group $(ARMCC) $(ARMTARGET) -o $@ $(.ALLSRC) -T$(LINKER_SCRIPT) --specs=nosys.specs -Wl,--gc-sections -static --specs=nano.specs -Wl,--start-group -lc -lm -Wl,--end-group
@@ -104,8 +128,13 @@ runbuild: $(SRCS)
for i in $(.MAKEFILE_LIST) $(.ALLSRC) $$(gsed ':x; /\\$$/ { N; s/\\\n//; tx }' < .depend | sed -e 's/^[^:]*://'); do if [ "$$i" != ".." ]; then echo $$i; fi; done | entr -d sh -c 'echo starting...; cd $(.CURDIR) && $(MAKE) $(.MAKEFLAGS) depend && $(MAKE) $(.MAKEFLAGS) all' for i in $(.MAKEFILE_LIST) $(.ALLSRC) $$(gsed ':x; /\\$$/ { N; s/\\\n//; tx }' < .depend | sed -e 's/^[^:]*://'); do if [ "$$i" != ".." ]; then echo $$i; fi; done | entr -d sh -c 'echo starting...; cd $(.CURDIR) && $(MAKE) $(.MAKEFLAGS) depend && $(MAKE) $(.MAKEFLAGS) all'


.PHONY: runtests .PHONY: runtests
runtests: lora.py
ls $(.ALLSRC) | entr sh -c 'PYTHONPATH="$(.CURDIR)" python -m coverage run -m unittest lora && coverage report --omit=p/\* -m -i'
runtests: Makefile lora_comms.py lora.py $(LIBLORA_TEST) $(LIBLORA_TEST_SRCS)
ls $(.ALLSRC) | entr sh -c '(cd $(.CURDIR) && $(MAKE) $(.MAKEFLAGS) $(LIBLORA_TEST)) && ((PYTHONPATH="$(.CURDIR)" python -m coverage run -m unittest lora && coverage report --omit=p/\* -m -i) 2>&1 | head -n 20)'

# native objects
.SUFFIXES: .no
.c.no:
$(CC) $(CFLAGS) -c $< -o $@


.c.o: .c.o:
$(ARMCC) $(ARMTARGET) $(CFLAGS) -c $< -o $@ $(ARMCC) $(ARMTARGET) $(CFLAGS) -c $< -o $@


+ 110
- 0
comms.c View File

@@ -0,0 +1,110 @@
#include <comms.h>
#include <strobe_rng_init.h>

static const size_t MAC_LEN = 8;
static const size_t CHALLENGE_LEN = 16;
static const uint8_t domain[] = "com.funkthat.lora.irrigation.shared.v0.0.1";

size_t
_strobe_state_size()
{

return sizeof(strobe_s);
}

void
comms_init(struct comms_state *cs, process_msgfunc_t pmf)
{

*cs = (struct comms_state){
.cs_comm_state = COMMS_WAIT_REQUEST,
.cs_procmsg = pmf,
};

strobe_init(&cs->cs_start, domain, sizeof domain - 1);

/* copy starting state over to initial state */
cs->cs_state = cs->cs_start;

}

#define CONFIRMED_STR_BASE "confirmed"
#define CONFIRMED_STR ((const uint8_t *)CONFIRMED_STR_BASE)
#define CONFIRMED_STR_LEN (sizeof(CONFIRMED_STR_BASE) - 1)

/*
* encrypted data to be processed is passed in via pbin.
*
* The pktbuf pointed to by pbout contains the buffer that a [encrypted]
* response will be written to. The length needs to be updated, where 0
* means no reply.
*/
void
comms_process(struct comms_state *cs, struct pktbuf pbin, struct pktbuf *pbout)
{
uint8_t buf[64] = {};
struct pktbuf pbmsg, pbrep;
ssize_t cnt, ret, msglen;

strobe_attach_buffer(&cs->cs_state, pbin.pkt, pbin.pktlen);

cnt = strobe_get(&cs->cs_state, APP_CIPHERTEXT, buf, pbin.pktlen -
MAC_LEN);
msglen = cnt;

cnt = strobe_get(&cs->cs_state, MAC, pbin.pkt +
(pbin.pktlen - MAC_LEN), MAC_LEN);

/* XXX - cnt != MAC_LEN test case */

/*
* if we have arrived here, MAC has been verified, and buf now
* contains the data to operate upon.
*/

/* attach the buffer for output */
strobe_attach_buffer(&cs->cs_state, pbout->pkt, pbout->pktlen);

ret = 0;
switch (cs->cs_comm_state) {
case COMMS_WAIT_REQUEST:
/* XXX - reqreset check */

bare_strobe_randomize(buf, CHALLENGE_LEN);
ret = strobe_put(&cs->cs_state, APP_CIPHERTEXT, buf,
CHALLENGE_LEN);
ret += strobe_put(&cs->cs_state, MAC, NULL, MAC_LEN);
cs->cs_comm_state = COMMS_WAIT_CONFIRM;
break;

case COMMS_WAIT_CONFIRM:
/* XXX - confirm check */
ret = strobe_put(&cs->cs_state, APP_CIPHERTEXT, CONFIRMED_STR,
CONFIRMED_STR_LEN);
ret += strobe_put(&cs->cs_state, MAC, NULL, MAC_LEN);
cs->cs_comm_state = COMMS_PROCESS_MSGS;
break;

case COMMS_PROCESS_MSGS: {
uint8_t repbuf[pbout->pktlen - MAC_LEN];

memset(repbuf, '\x00', sizeof repbuf);

pbmsg.pkt = buf;
pbmsg.pktlen = msglen;

pbrep.pkt = repbuf;
pbrep.pktlen = sizeof repbuf;

cs->cs_procmsg(pbmsg, &pbrep);

ret = strobe_put(&cs->cs_state, APP_CIPHERTEXT, repbuf,
pbrep.pktlen);
ret += strobe_put(&cs->cs_state, MAC, NULL, MAC_LEN);

break;
}
}

pbout->pktlen = ret;
}

+ 31
- 0
comms.h View File

@@ -0,0 +1,31 @@
#include <sys/types.h>
#include <stdint.h>

#include <strobe.h>

struct pktbuf {
uint8_t *pkt;
uint16_t pktlen;
};

/* first arg is input buffer, second arg is what will be sent as reply */
typedef void (*process_msgfunc_t)(struct pktbuf, struct pktbuf *);

enum comm_state {
COMMS_WAIT_REQUEST = 1,
COMMS_WAIT_CONFIRM,
COMMS_PROCESS_MSGS,
};

struct comms_state {
strobe_s cs_state;
enum comm_state cs_comm_state;
strobe_s cs_start; /* special starting state cache */

process_msgfunc_t cs_procmsg;
};

size_t _strobe_state_size();

void comms_init(struct comms_state *, process_msgfunc_t);
void comms_process(struct comms_state *, struct pktbuf, struct pktbuf *);

+ 89
- 4
lora.py View File

@@ -3,9 +3,12 @@ import functools
import os import os
import unittest import unittest


from Strobe.Strobe import Strobe
from Strobe.Strobe import Strobe, KeccakF
from Strobe.Strobe import AuthenticationFailed from Strobe.Strobe import AuthenticationFailed


import lora_comms
from lora_comms import make_pktbuf

domain = b'com.funkthat.lora.irrigation.shared.v0.0.1' domain = b'com.funkthat.lora.irrigation.shared.v0.0.1'


# Response to command will be the CMD and any arguments if needed. # Response to command will be the CMD and any arguments if needed.
@@ -21,7 +24,7 @@ class LORANode(object):


def __init__(self, syncdatagram): def __init__(self, syncdatagram):
self.sd = syncdatagram self.sd = syncdatagram
self.st = Strobe(domain)
self.st = Strobe(domain, F=KeccakF(800))


async def start(self): async def start(self):
msg = self.st.send_enc(os.urandom(16) + b'reqreset') + \ msg = self.st.send_enc(os.urandom(16) + b'reqreset') + \
@@ -174,7 +177,7 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase):
async def test_lora(self): async def test_lora(self):
class TestSD(MockSyncDatagram): class TestSD(MockSyncDatagram):
async def runner(self): async def runner(self):
l = Strobe(domain)
l = Strobe(domain, F=KeccakF(800))


# start handshake # start handshake
r = await self.get() r = await self.get()
@@ -243,4 +246,86 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase):
self.assertTrue(tsd.sendq.empty()) self.assertTrue(tsd.sendq.empty())
self.assertTrue(tsd.recvq.empty()) self.assertTrue(tsd.recvq.empty())


print('done')
@timeout(2)
async def test_ccode(self):
_self = self
from ctypes import pointer, sizeof, c_uint8

# seed the RNG
prngseed = b'abc123'
lora_comms.strobe_seed_prng((c_uint8 *
len(prngseed))(*prngseed), len(prngseed))

# Create the state for testing
commstate = lora_comms.CommsState()

# These are the expected messages and their arguments
exptmsgs = [
(CMD_WAITFOR, [ 30 ]),
(CMD_RUNFOR, [ 1, 50 ]),
(CMD_TERMINATE, [ ]),
]
def procmsg(msg, outbuf):
msgbuf = msg._from()
#print('procmsg:', repr(msg), repr(msgbuf), repr(outbuf))
cmd = msgbuf[0]
args = [ int.from_bytes(msgbuf[x:x + 4],
byteorder='little') for x in range(1, len(msgbuf),
4) ]

if exptmsgs[0] == (cmd, args):
exptmsgs.pop(0)
outbuf[0].pkt[0] = cmd
outbuf[0].pktlen = 1
else: #pragma: no cover
raise RuntimeError('cmd not found')

# wrap the callback function
cb = lora_comms.process_msgfunc_t(procmsg)

class CCodeSD(MockSyncDatagram):
async def runner(self):
for expectlen in [ 24, 17, 9, 9, 9 ]:
# get message
gb = await self.get()
r = make_pktbuf(gb)

outbytes = bytearray(64)
outbuf = make_pktbuf(outbytes)

# process the test message
lora_comms.comms_process(commstate, r,
outbuf)

# make sure the reply matches length
_self.assertEqual(expectlen,
outbuf.pktlen)

# pass the reply back
await self.put(outbytes[:outbuf.pktlen])

# Initialize everything
lora_comms.comms_init(commstate, cb)

# Create test fixture
tsd = CCodeSD()
l = LORANode(tsd)

# Send various messages
await l.start()

await l.waitfor(30)

await l.runfor(1, 50)

await l.terminate()

await tsd.drain()

# Make sure all messages have been processed
self.assertTrue(tsd.sendq.empty())
self.assertTrue(tsd.recvq.empty())

# Make sure all the expected messages have been
# processed.
self.assertFalse(exptmsgs)

+ 59
- 0
lora_comms.py View File

@@ -0,0 +1,59 @@
from ctypes import Structure, POINTER, CFUNCTYPE, pointer
from ctypes import c_uint8, c_uint16, c_ssize_t, c_size_t, c_uint64
from ctypes import CDLL

class PktBuf(Structure):
_fields_ = [
('pkt', POINTER(c_uint8)),
('pktlen', c_uint16),
]

def _from(self):
return bytes(self.pkt[:self.pktlen])

def __repr__(self):
return 'PktBuf(pkt=%s, pktlen=%s)' % (repr(self._from()),
self.pktlen)

def make_pktbuf(s):
pb = PktBuf()

if isinstance(s, bytearray):
obj = s
pb.pkt = pointer(c_uint8.from_buffer(s))
#print('mp:', repr(pb.pkt))
else:
obj = (c_uint8 * len(s))(*s)
pb.pkt = obj

pb.pktlen = len(s)

pb._make_pktbuf_ref = (obj, s)

return pb

process_msgfunc_t = CFUNCTYPE(None, PktBuf, POINTER(PktBuf))

_lib = CDLL('liblora_test.dylib')

_lib._strobe_state_size.restype = c_size_t
_lib._strobe_state_size.argtypes = ()
_strobe_state_u64_cnt = (_lib._strobe_state_size() + 7) // 8

class CommsState(Structure):
_fields_ = [
# The alignment of these may be off
('cs_state', c_uint64 * _strobe_state_u64_cnt),
('cs_start', c_uint64 * _strobe_state_u64_cnt),
('cs_procmsg', process_msgfunc_t),
]

for func, ret, args in [
('comms_init', None, (POINTER(CommsState), process_msgfunc_t)),
('comms_process', None, (POINTER(CommsState), PktBuf, POINTER(PktBuf))),
('strobe_seed_prng', None, (POINTER(c_uint8), c_ssize_t)),
]:
f = getattr(_lib, func)
f.restype = ret
f.argtypes = args
locals()[func] = f

+ 0
- 19
strobe/strobe.c View File

@@ -224,25 +224,6 @@ static ssize_t strobe_operate_0 (
return -1; return -1;
} }


/* Read/write the control word */
strobe_serialized_control_t str = {
GET_CONTROL_TAG(flags),
receiving_the_length ? 0 : eswap_htole_sl(len)
};
if (!more) {
TRY(strobe_duplex(strobe, cwf, (uint8_t *)&str, sizeof(str.control) + length_bytes));
}
str.len = eswap_letoh_sl(str.len);

// Check received control word and length
if ( str.control != GET_CONTROL_TAG(flags)
|| str.len > INT_MAX
|| ((ssize_t)(len + str.len) > 0 && (ssize_t)str.len != len)
) {
return -1;
}
len = str.len;

if (flags & FLAG_NO_DATA) return 0; if (flags & FLAG_NO_DATA) return 0;


return strobe_duplex(strobe, flags, inside, len); return strobe_duplex(strobe, flags, inside, len);


Loading…
Cancel
Save