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 | #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 $@ | ||||
| @@ -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 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) | |||||
| @@ -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; | 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); | ||||