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
@@ -6,6 +6,14 @@ ARMTARGET?= -mcpu=cortex-m3 -mthumb -DSTROBE_SINGLE_THREAD=1 | |||
#ARMCC?=clang-mp-9.0 | |||
#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 | |||
PROGEXT = .elf | |||
@@ -86,12 +94,28 @@ CFLAGS+= -I$(STM32)/usb | |||
OBJS = $(SRCS:C/.c$/.o/) | |||
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 | |||
all: $(PROG)$(PROGEXT) $(PROG).list | |||
.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) | |||
$(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' | |||
.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: | |||
$(ARMCC) $(ARMTARGET) $(CFLAGS) -c $< -o $@ | |||
@@ -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; | |||
} |
@@ -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 *); |
@@ -3,9 +3,12 @@ import functools | |||
import os | |||
import unittest | |||
from Strobe.Strobe import Strobe | |||
from Strobe.Strobe import Strobe, KeccakF | |||
from Strobe.Strobe import AuthenticationFailed | |||
import lora_comms | |||
from lora_comms import make_pktbuf | |||
domain = b'com.funkthat.lora.irrigation.shared.v0.0.1' | |||
# Response to command will be the CMD and any arguments if needed. | |||
@@ -21,7 +24,7 @@ class LORANode(object): | |||
def __init__(self, syncdatagram): | |||
self.sd = syncdatagram | |||
self.st = Strobe(domain) | |||
self.st = Strobe(domain, F=KeccakF(800)) | |||
async def start(self): | |||
msg = self.st.send_enc(os.urandom(16) + b'reqreset') + \ | |||
@@ -174,7 +177,7 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||
async def test_lora(self): | |||
class TestSD(MockSyncDatagram): | |||
async def runner(self): | |||
l = Strobe(domain) | |||
l = Strobe(domain, F=KeccakF(800)) | |||
# start handshake | |||
r = await self.get() | |||
@@ -243,4 +246,86 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||
self.assertTrue(tsd.sendq.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) |
@@ -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 |
@@ -224,25 +224,6 @@ static ssize_t strobe_operate_0 ( | |||
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; | |||
return strobe_duplex(strobe, flags, inside, len); | |||