|
@@ -0,0 +1,571 @@
|
|
|
|
|
+#include <string.h>
|
|
|
|
|
+
|
|
|
|
|
+#include <zephyr/device.h>
|
|
|
|
|
+#include <zephyr/drivers/lora.h>
|
|
|
|
|
+#include <zephyr/drivers/lora/sx126x.h>
|
|
|
|
|
+#include <zephyr/fs/fs.h>
|
|
|
|
|
+#include <zephyr/kernel.h>
|
|
|
|
|
+#include <zephyr/logging/log.h>
|
|
|
|
|
+#include <zephyr/sys/atomic.h>
|
|
|
|
|
+#include <zephyr/sys/byteorder.h>
|
|
|
|
|
+#include <zephyr/sys/util.h>
|
|
|
|
|
+
|
|
|
|
|
+#include "beacon/beacon_ctx.h"
|
|
|
|
|
+#include "frame/beacon_frame.h"
|
|
|
|
|
+#include "beacon/beacon_rx.h"
|
|
|
|
|
+#include "beacon/beacon_work.h"
|
|
|
|
|
+#include "relay/cad_rx.h"
|
|
|
|
|
+#include "relay/cbf.h"
|
|
|
|
|
+#include "rx/discovery_rx.h"
|
|
|
|
|
+#include "frame/frame.h"
|
|
|
|
|
+#include "crypto/group_mac.h"
|
|
|
|
|
+#include "crypto/ident.h"
|
|
|
|
|
+#include "crypto/ident_privkey.h"
|
|
|
|
|
+#include "log_ctx.h"
|
|
|
|
|
+#include "log_levels.h"
|
|
|
|
|
+#include "frame/msg_frame.h"
|
|
|
|
|
+#include "rx/payload_rx.h"
|
|
|
|
|
+#include "rx/payload_work.h"
|
|
|
|
|
+#include "net/peer_stats.h"
|
|
|
|
|
+#include "frame/pkt_log.h"
|
|
|
|
|
+#include "crypto/pubkey_table.h"
|
|
|
|
|
+#include "relay/relay_work.h"
|
|
|
|
|
+#include "frame/rx_ctx.h"
|
|
|
|
|
+#include "frame/rx_dedup.h"
|
|
|
|
|
+#include "net/time_ctx.h"
|
|
|
|
|
+#include "relay/topology.h"
|
|
|
|
|
+#include "relay/tx_queue.h"
|
|
|
|
|
+
|
|
|
|
|
+#ifdef CONFIG_APP_BASE_STATION
|
|
|
|
|
+#include "net/net_base.h"
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+LOG_MODULE_REGISTER(relay_zephyr, LOG_LEVEL_MAIN);
|
|
|
|
|
+
|
|
|
|
|
+#define LORA_NODE DT_ALIAS(lora0)
|
|
|
|
|
+BUILD_ASSERT(DT_NODE_HAS_STATUS_OKAY(LORA_NODE), "lora0 alias missing/disabled in devicetree");
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+ * Radio bench/field toggle: flip LORA_BENCH_TEST to 0 before range testing.
|
|
|
|
|
+ * Bench (1): min SX1262 PA power and fastest SF, to minimize on-air time
|
|
|
|
|
+ * and radiated power while units sit wired together on the desk.
|
|
|
|
|
+ * Field (0): full PA power and a slower SF for range.
|
|
|
|
|
+ */
|
|
|
|
|
+#define LORA_BENCH_TEST 1
|
|
|
|
|
+
|
|
|
|
|
+#if LORA_BENCH_TEST
|
|
|
|
|
+#define LORA_TX_POWER_DBM (-9)
|
|
|
|
|
+#define LORA_SF SF_7
|
|
|
|
|
+#else
|
|
|
|
|
+#define LORA_TX_POWER_DBM 8
|
|
|
|
|
+#define LORA_SF SF_10
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+ * RX mode toggle: 0 (default) keeps the shipped always-RX baseline
|
|
|
|
|
+ * (continuous lora_recv_async()). 1 swaps in cad_rx.c's CAD-poll/capture
|
|
|
|
|
+ * cycle instead - much lower average RX power on bench power, since the
|
|
|
|
|
+ * radio only opens a full RX window after CAD actually sees something,
|
|
|
|
|
+ * but not yet validated in the field (false-negative rate at range,
|
|
|
|
|
+ * CAD's own power draw vs continuous RX's, capture-window drops under
|
|
|
|
|
+ * back-to-back traffic).
|
|
|
|
|
+ */
|
|
|
|
|
+#define RX_MODE_CAD_TRIGGER 0
|
|
|
|
|
+
|
|
|
|
|
+/* Override with `west build ... -- -DEXTRA_CFLAGS=-DNODE_ID=1` per node. */
|
|
|
|
|
+#ifndef NODE_ID
|
|
|
|
|
+#define NODE_ID 0
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+ * Bench-test aid: pretend this node can't hear a given TX id, even though
|
|
|
|
|
+ * the radios are direct-connected and would otherwise hear everything.
|
|
|
|
|
+ * Override with `-DEXTRA_CFLAGS=-DSIM_DROP_TX_ID=<id>` per node. -1 never
|
|
|
|
|
+ * matches a real (uint8_t) tx id, so the filter is a no-op by default.
|
|
|
|
|
+ */
|
|
|
|
|
+#ifndef SIM_DROP_TX_ID
|
|
|
|
|
+#define SIM_DROP_TX_ID -1
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+static const struct device *lora_dev;
|
|
|
|
|
+
|
|
|
|
|
+static uint16_t tx_msg_seq;
|
|
|
|
|
+
|
|
|
|
|
+#define TEST_CURRENT_TABLE_ID 0x001
|
|
|
|
|
+
|
|
|
|
|
+static uint32_t alt_table_ids[BEACON_MAX_KV_PAIRS];
|
|
|
|
|
+static size_t alt_table_id_count;
|
|
|
|
|
+
|
|
|
|
|
+static uint8_t central_privkey[IDENT_PRIVKEY_LEN];
|
|
|
|
|
+static bool central_privkey_ok;
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+ * on_rx() itself runs synchronously inside the (unpatched) sx126x
|
|
|
|
|
+ * driver's irq_work, on the system workqueue - so it has to stay
|
|
|
|
|
+ * fast and shallow.
|
|
|
|
|
+ */
|
|
|
|
|
+static struct {
|
|
|
|
|
+ struct k_work work;
|
|
|
|
|
+ uint8_t buf[FRAME_MAX_LEN];
|
|
|
|
|
+ uint16_t len;
|
|
|
|
|
+ int16_t rssi;
|
|
|
|
|
+ int8_t snr;
|
|
|
|
|
+ uint64_t rx_done_ns;
|
|
|
|
|
+ bool rx_done_ns_valid;
|
|
|
|
|
+ atomic_t busy;
|
|
|
|
|
+} rx_work;
|
|
|
|
|
+
|
|
|
|
|
+static void rx_work_handler(struct k_work *work)
|
|
|
|
|
+{
|
|
|
|
|
+ ARG_UNUSED(work);
|
|
|
|
|
+
|
|
|
|
|
+ /* Log every frame heard on the channel, regardless of what the
|
|
|
|
|
+ * decode/MAC/CBF checks below decide to do with it.
|
|
|
|
|
+ */
|
|
|
|
|
+ pkt_log_raw("RX raw", rx_work.buf, rx_work.len);
|
|
|
|
|
+
|
|
|
|
|
+ struct frame_hdr hdr;
|
|
|
|
|
+ const uint8_t *payload;
|
|
|
|
|
+ size_t payload_len;
|
|
|
|
|
+
|
|
|
|
|
+ int ret = frame_decode_hdr(rx_work.buf, rx_work.len, &hdr, &payload, &payload_len);
|
|
|
|
|
+
|
|
|
|
|
+ if (ret < 0) {
|
|
|
|
|
+ LOG_WRN("RX frame rejected (malformed, err=%d, len=%u) rssi=%d snr=%d",
|
|
|
|
|
+ ret, rx_work.len, rx_work.rssi, rx_work.snr);
|
|
|
|
|
+ atomic_set(&rx_work.busy, 0);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ bool mac_ok = frame_group_mac_ok(rx_work.buf, rx_work.len);
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Every PTYPE except Discovery is rejected outright on a bad group
|
|
|
|
|
+ * MAC
|
|
|
|
|
+ */
|
|
|
|
|
+ if (!mac_ok && hdr.ptype != PTYPE_DISCOVERY) {
|
|
|
|
|
+ LOG_WRN("RX frame rejected (bad group MAC, ptype=%u, len=%u) rssi=%d snr=%d",
|
|
|
|
|
+ hdr.ptype, rx_work.len, rx_work.rssi, rx_work.snr);
|
|
|
|
|
+ atomic_set(&rx_work.busy, 0);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ LOG_INF("RX frame: ver=%u dir=%u relay=%u ptype=%u TX=%u SRC=%u DST=%u SEQ=%u payload_len=%zu",
|
|
|
|
|
+ hdr.ver, hdr.dir, hdr.relay, hdr.ptype, hdr.tx, hdr.src, hdr.dst, hdr.seq,
|
|
|
|
|
+ payload_len);
|
|
|
|
|
+
|
|
|
|
|
+ if ((int)hdr.tx == SIM_DROP_TX_ID) {
|
|
|
|
|
+ LOG_INF("RX frame from TX=%u dropped (SIM_DROP_TX_ID)", hdr.tx);
|
|
|
|
|
+ atomic_set(&rx_work.busy, 0);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* Radio-link stats for whichever node this hop's TX is
|
|
|
|
|
+ */
|
|
|
|
|
+ peer_stats_record(hdr.tx, rx_work.rssi, rx_work.snr, rx_work.rx_done_ns,
|
|
|
|
|
+ rx_work.rx_done_ns_valid);
|
|
|
|
|
+
|
|
|
|
|
+ /* Someone relaying a message we originated - the overhear-the-relay ack
|
|
|
|
|
+ */
|
|
|
|
|
+ if (hdr.src == NODE_ID && hdr.tx != NODE_ID) {
|
|
|
|
|
+ peer_stats_record_relayed_for_us(hdr.tx, rx_work.rx_done_ns,
|
|
|
|
|
+ rx_work.rx_done_ns_valid);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Gate RX-handler dispatch only
|
|
|
|
|
+ */
|
|
|
|
|
+ bool is_self_src = (hdr.src == NODE_ID);
|
|
|
|
|
+ bool is_dup = is_self_src || rx_dedup_check_and_add(hdr.src, hdr.seq);
|
|
|
|
|
+
|
|
|
|
|
+ LOG_CTX_DBG("rx/dispatch_gate", "src=%u seq=%u is_self_src=%d is_dup=%d", hdr.src, hdr.seq,
|
|
|
|
|
+ is_self_src, is_dup);
|
|
|
|
|
+
|
|
|
|
|
+ if (is_dup) {
|
|
|
|
|
+ /* Already captured above
|
|
|
|
|
+ */
|
|
|
|
|
+ } else {
|
|
|
|
|
+ /* Dispatch by PTYPE to its own queue
|
|
|
|
|
+ */
|
|
|
|
|
+ LOG_CTX_DBG("rx/dispatch", "ptype=%u payload_len=%zu", hdr.ptype, payload_len);
|
|
|
|
|
+
|
|
|
|
|
+ /* Traffic counters keyed by hdr.src
|
|
|
|
|
+ */
|
|
|
|
|
+ if (hdr.dst == NODE_ID) {
|
|
|
|
|
+ peer_stats_record_rx_unicast(hdr.src, rx_work.rx_done_ns,
|
|
|
|
|
+ rx_work.rx_done_ns_valid);
|
|
|
|
|
+ } else if (hdr.dst == FRAME_DST_ALL) {
|
|
|
|
|
+ peer_stats_record_rx_broadcast(hdr.src, rx_work.rx_done_ns,
|
|
|
|
|
+ rx_work.rx_done_ns_valid);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * One rx_ctx per dispatched packet, bundling
|
|
|
|
|
+ * everything a submit_*() call might want
|
|
|
|
|
+ */
|
|
|
|
|
+ struct rx_ctx ctx = {
|
|
|
|
|
+ .hdr = &hdr,
|
|
|
|
|
+ .payload = payload,
|
|
|
|
|
+ .payload_len = payload_len,
|
|
|
|
|
+ .mac_ok = mac_ok,
|
|
|
|
|
+ /* sx126x_irq_timestamp_get()'s ns is already an
|
|
|
|
|
+ * absolute, k_uptime_get()-comparable value (resolved
|
|
|
|
|
+ * in the driver itself - see zephyr-patches/0001's
|
|
|
|
|
+ * rewritten commit message), so this is a plain unit
|
|
|
|
|
+ * conversion, not a reconciliation.
|
|
|
|
|
+ */
|
|
|
|
|
+ .rx_done_uptime_ms = rx_work.rx_done_ns_valid
|
|
|
|
|
+ ? (int64_t)(rx_work.rx_done_ns / 1000000ULL)
|
|
|
|
|
+ : k_uptime_get(),
|
|
|
|
|
+ .rx_done_valid = rx_work.rx_done_ns_valid,
|
|
|
|
|
+ .bctx = beacon_ctx_acquire(),
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ switch (hdr.ptype) {
|
|
|
|
|
+ case PTYPE_BEACON:
|
|
|
|
|
+ beacon_rx_submit(&ctx);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case PTYPE_DISCOVERY:
|
|
|
|
|
+ discovery_rx_submit(&ctx);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case PTYPE_MSG:
|
|
|
|
|
+ payload_rx_submit_msg(&ctx);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case PTYPE_DATA:
|
|
|
|
|
+ payload_rx_submit_data(&ctx);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case PTYPE_SECRET:
|
|
|
|
|
+ payload_rx_submit_secret(&ctx);
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ LOG_WRN("RX frame: unrecognized ptype=%u", hdr.ptype);
|
|
|
|
|
+ beacon_ctx_release(ctx.bctx);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ cbf_on_rx(&hdr, rx_work.buf, rx_work.len);
|
|
|
|
|
+
|
|
|
|
|
+ atomic_set(&rx_work.busy, 0);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void on_rx(const struct device *dev, uint8_t *data, uint16_t size,
|
|
|
|
|
+ int16_t rssi, int8_t snr, void *user_data)
|
|
|
|
|
+{
|
|
|
|
|
+ ARG_UNUSED(user_data);
|
|
|
|
|
+
|
|
|
|
|
+ if (size > sizeof(rx_work.buf)) {
|
|
|
|
|
+ LOG_ERR("RX frame too large to defer (%u > %zu)", size, sizeof(rx_work.buf));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!atomic_cas(&rx_work.busy, 0, 1)) {
|
|
|
|
|
+ LOG_WRN("RX deferred work still busy, dropping frame (%u bytes)", size);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Peek the RX_DONE IRQ timestamp
|
|
|
|
|
+ */
|
|
|
|
|
+ rx_work.rx_done_ns_valid =
|
|
|
|
|
+ sx126x_irq_timestamp_get(dev, SX126X_IRQ_TS_RX_DONE, &rx_work.rx_done_ns);
|
|
|
|
|
+
|
|
|
|
|
+ memcpy(rx_work.buf, data, size);
|
|
|
|
|
+ rx_work.len = size;
|
|
|
|
|
+ rx_work.rssi = rssi;
|
|
|
|
|
+ rx_work.snr = snr;
|
|
|
|
|
+
|
|
|
|
|
+ k_work_submit_to_queue(&relay_work_q, &rx_work.work);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#if !RX_MODE_CAD_TRIGGER
|
|
|
|
|
+/* tx_queue's rx_pause/rx_resume for the continuous-RX baseline
|
|
|
|
|
+ */
|
|
|
|
|
+static void rx_pause(void)
|
|
|
|
|
+{
|
|
|
|
|
+ lora_recv_async(lora_dev, NULL, NULL);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void rx_resume(void)
|
|
|
|
|
+{
|
|
|
|
|
+ int ret = lora_recv_async(lora_dev, on_rx, NULL);
|
|
|
|
|
+
|
|
|
|
|
+ if (ret < 0) {
|
|
|
|
|
+ LOG_ERR("lora_recv_async restart failed: %d", ret);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+static uint64_t beacon_timestamp_ms(void)
|
|
|
|
|
+{
|
|
|
|
|
+ if (time_ctx_source() != TIME_SOURCE_NONE) {
|
|
|
|
|
+ return (uint64_t)time_ctx_now_ms() - BEACON_EPOCH_UNIX_MS;
|
|
|
|
|
+ }
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void send_beacon(void)
|
|
|
|
|
+{
|
|
|
|
|
+ uint64_t timestamp_ms = beacon_timestamp_ms();
|
|
|
|
|
+
|
|
|
|
|
+ /* current_table_id comes from beacon_ctx
|
|
|
|
|
+ */
|
|
|
|
|
+ uint32_t current_table_id = beacon_ctx_table_id();
|
|
|
|
|
+ uint16_t kv_keys[BEACON_MAX_KV_PAIRS];
|
|
|
|
|
+ uint32_t kv_values[BEACON_MAX_KV_PAIRS];
|
|
|
|
|
+ size_t kv_count = 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (current_table_id != PUBKEY_TABLE_ID_NONE) {
|
|
|
|
|
+ kv_keys[kv_count] = BEACON_KEY_TABLE_CURRENT;
|
|
|
|
|
+ kv_values[kv_count] = current_table_id;
|
|
|
|
|
+ kv_count++;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (size_t i = 0; i < alt_table_id_count; i++) {
|
|
|
|
|
+ kv_keys[kv_count] = BEACON_KEY_TABLE_ALT;
|
|
|
|
|
+ kv_values[kv_count] = alt_table_ids[i];
|
|
|
|
|
+ kv_count++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ LOG_CTX_DBG("send_beacon", "timestamp_ms=%llu current_table_id=0x%03x kv_count=%zu",
|
|
|
|
|
+ timestamp_ms, current_table_id, kv_count);
|
|
|
|
|
+
|
|
|
|
|
+ if (!central_privkey_ok) {
|
|
|
|
|
+ LOG_ERR("No central privkey loaded - skipping Beacon (no unsigned fallback)");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ uint8_t payload[BEACON_PAYLOAD_MAX_LEN];
|
|
|
|
|
+ size_t signed_len = beacon_frame_encode(payload, BEACON_CADENCE_MS, timestamp_ms,
|
|
|
|
|
+ kv_keys, kv_values, kv_count);
|
|
|
|
|
+
|
|
|
|
|
+ if (ident_sign(payload, signed_len, central_privkey, &payload[signed_len]) < 0) {
|
|
|
|
|
+ LOG_ERR("ident_sign failed - skipping Beacon");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ size_t payload_len = signed_len + BEACON_SIG_LEN;
|
|
|
|
|
+
|
|
|
|
|
+ struct frame_hdr hdr = {
|
|
|
|
|
+ .ver = 0,
|
|
|
|
|
+ .dir = FRAME_DIR_DOWNSTREAM,
|
|
|
|
|
+ .relay = FRAME_RELAY_REQUESTED,
|
|
|
|
|
+ .ptype = PTYPE_BEACON,
|
|
|
|
|
+ .tx = NODE_ID,
|
|
|
|
|
+ .src = NODE_ID,
|
|
|
|
|
+ .dst = FRAME_DST_ALL,
|
|
|
|
|
+ .seq = tx_msg_seq++,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ uint8_t buf[FRAME_HDR_LEN + sizeof(payload) + GROUP_MAC_LEN];
|
|
|
|
|
+ int len = frame_encode(buf, sizeof(buf), &hdr, payload, payload_len);
|
|
|
|
|
+
|
|
|
|
|
+ if (len < 0) {
|
|
|
|
|
+ LOG_ERR("Beacon encode failed: %d", len);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int ret = tx_queue_submit(buf, (size_t)len, NULL, NULL);
|
|
|
|
|
+
|
|
|
|
|
+ if (ret < 0) {
|
|
|
|
|
+ LOG_ERR("Beacon tx_queue_submit failed: %d", ret);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void beacon_work_handler(struct k_work *work)
|
|
|
|
|
+{
|
|
|
|
|
+ struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
|
|
|
|
+
|
|
|
|
|
+ send_beacon();
|
|
|
|
|
+ k_work_reschedule_for_queue(&relay_work_q, dwork, K_MSEC(BEACON_CADENCE_MS));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static K_WORK_DELAYABLE_DEFINE(beacon_work, beacon_work_handler);
|
|
|
|
|
+
|
|
|
|
|
+/* Test traffic
|
|
|
|
|
+ */
|
|
|
|
|
+#define MSG_CADENCE_MS 30000
|
|
|
|
|
+#define PING_TEST_DST 1 /* node 2 (the only scheduled sender) pings node 1 */
|
|
|
|
|
+
|
|
|
|
|
+static uint16_t ping_nonce;
|
|
|
|
|
+
|
|
|
|
|
+static void send_ping(uint8_t dst)
|
|
|
|
|
+{
|
|
|
|
|
+ uint8_t nonce[MSG_PING_DATA_LEN];
|
|
|
|
|
+
|
|
|
|
|
+ sys_put_be16(ping_nonce++, nonce);
|
|
|
|
|
+
|
|
|
|
|
+ uint8_t payload[MSG_SUBTYPE_HDR_LEN + MSG_PING_DATA_LEN];
|
|
|
|
|
+ size_t payload_len = msg_frame_encode(payload, MSG_SUBTYPE_PING, nonce, sizeof(nonce));
|
|
|
|
|
+
|
|
|
|
|
+ struct frame_hdr hdr = {
|
|
|
|
|
+ .ver = 0,
|
|
|
|
|
+ .dir = topology_dir(NODE_ID, dst),
|
|
|
|
|
+ .relay = FRAME_RELAY_REQUESTED,
|
|
|
|
|
+ .ptype = PTYPE_MSG,
|
|
|
|
|
+ .tx = NODE_ID,
|
|
|
|
|
+ .src = NODE_ID,
|
|
|
|
|
+ .dst = dst,
|
|
|
|
|
+ .seq = tx_msg_seq++,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ uint8_t buf[FRAME_HDR_LEN + sizeof(payload) + GROUP_MAC_LEN];
|
|
|
|
|
+ int len = frame_encode(buf, sizeof(buf), &hdr, payload, payload_len);
|
|
|
|
|
+
|
|
|
|
|
+ if (len < 0) {
|
|
|
|
|
+ LOG_ERR("Ping encode failed: %d", len);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int ret = tx_queue_submit(buf, (size_t)len, NULL, NULL);
|
|
|
|
|
+
|
|
|
|
|
+ if (ret < 0) {
|
|
|
|
|
+ LOG_ERR("Ping tx_queue_submit failed: %d", ret);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void msg_work_handler(struct k_work *work)
|
|
|
|
|
+{
|
|
|
|
|
+ struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
|
|
|
|
+
|
|
|
|
|
+ send_ping(PING_TEST_DST);
|
|
|
|
|
+ k_work_reschedule_for_queue(&relay_work_q, dwork, K_MSEC(MSG_CADENCE_MS));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static K_WORK_DELAYABLE_DEFINE(msg_work, msg_work_handler);
|
|
|
|
|
+
|
|
|
|
|
+int main(void)
|
|
|
|
|
+{
|
|
|
|
|
+ LOG_INF("Build: %s (NODE_ID=%d)", APP_BUILD_ID, NODE_ID);
|
|
|
|
|
+
|
|
|
|
|
+ /* Check lfs mount
|
|
|
|
|
+ */
|
|
|
|
|
+ struct fs_statvfs lfs_stat;
|
|
|
|
|
+ int lfs_ret = fs_statvfs(PUBKEY_TABLE_MOUNT_POINT, &lfs_stat);
|
|
|
|
|
+
|
|
|
|
|
+ if (lfs_ret < 0) {
|
|
|
|
|
+ LOG_ERR("littlefs not mounted at %s: %d", PUBKEY_TABLE_MOUNT_POINT, lfs_ret);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ LOG_INF("littlefs mounted at %s", PUBKEY_TABLE_MOUNT_POINT);
|
|
|
|
|
+ LOG_CTX_DBG("littlefs", "mount_point=%s blocks_free=%lu blocks_total=%lu "
|
|
|
|
|
+ "block_size=%lu", PUBKEY_TABLE_MOUNT_POINT,
|
|
|
|
|
+ (unsigned long)lfs_stat.f_bfree, (unsigned long)lfs_stat.f_blocks,
|
|
|
|
|
+ (unsigned long)lfs_stat.f_frsize);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* Needed before pubkey_table_load(), which calls ident_verify()
|
|
|
|
|
+ * against base_ident_pubkey (see ident.h/pubkey_table.h).
|
|
|
|
|
+ */
|
|
|
|
|
+ if (ident_init() < 0) {
|
|
|
|
|
+ LOG_ERR("ident_init failed");
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* Only central signs Beacon (send_beacon() checks central_privkey_ok,
|
|
|
|
|
+ * never sends unsigned) - no other node needs its own privkey loaded
|
|
|
|
|
+ * yet, even though every node is seeded with one.
|
|
|
|
|
+ */
|
|
|
|
|
+ if (NODE_ID == FRAME_SRC_CENTRAL) {
|
|
|
|
|
+ int pk_ret = ident_privkey_load(central_privkey);
|
|
|
|
|
+
|
|
|
|
|
+ if (pk_ret < 0) {
|
|
|
|
|
+ LOG_ERR("ident_privkey_load failed: %d - Beacon will not be sent", pk_ret);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ central_privkey_ok = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* Before net_base_start(): NTP sync (one of time_ctx's callers) can
|
|
|
|
|
+ * fire as soon as WiFi comes up.
|
|
|
|
|
+ */
|
|
|
|
|
+ time_ctx_init();
|
|
|
|
|
+
|
|
|
|
|
+#ifdef CONFIG_APP_BASE_STATION
|
|
|
|
|
+ net_base_start();
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+ relay_work_q_start();
|
|
|
|
|
+ k_work_init(&rx_work.work, rx_work_handler);
|
|
|
|
|
+ rx_dedup_init();
|
|
|
|
|
+ peer_stats_init();
|
|
|
|
|
+
|
|
|
|
|
+ beacon_work_q_start();
|
|
|
|
|
+ beacon_ctx_init();
|
|
|
|
|
+ /* Test-only, see TEST_CURRENT_TABLE_ID above - the one table this
|
|
|
|
|
+ * node trusts until the first beacon it verifies against it
|
|
|
|
|
+ * advertises a different one (beacon_ctx_commit(), beacon_rx.c).
|
|
|
|
|
+ * A real beacon state machine/loader still doesn't exist.
|
|
|
|
|
+ */
|
|
|
|
|
+ beacon_ctx_bootstrap_table(TEST_CURRENT_TABLE_ID);
|
|
|
|
|
+ beacon_rx_init();
|
|
|
|
|
+ discovery_rx_init();
|
|
|
|
|
+
|
|
|
|
|
+ payload_work_q_start();
|
|
|
|
|
+ payload_rx_init();
|
|
|
|
|
+
|
|
|
|
|
+ lora_dev = DEVICE_DT_GET(LORA_NODE);
|
|
|
|
|
+
|
|
|
|
|
+ if (!device_is_ready(lora_dev)) {
|
|
|
|
|
+ LOG_ERR("%s not ready", lora_dev->name);
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (group_mac_init() < 0) {
|
|
|
|
|
+ LOG_ERR("group_mac_init failed");
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* Matches common/lora-radio.yaml: 915 MHz, BW500/CR4.6. PA power and
|
|
|
|
|
+ * SF are set by the LORA_BENCH_TEST block above.
|
|
|
|
|
+ */
|
|
|
|
|
+ struct lora_modem_config config = {
|
|
|
|
|
+ .frequency = 915000000,
|
|
|
|
|
+ .bandwidth = BW_500_KHZ,
|
|
|
|
|
+ .datarate = LORA_SF,
|
|
|
|
|
+ .coding_rate = CR_4_6,
|
|
|
|
|
+ .preamble_len = 8,
|
|
|
|
|
+ .sync_word = 0x2C,
|
|
|
|
|
+ .tx_power = LORA_TX_POWER_DBM,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ int ret = lora_config(lora_dev, &config);
|
|
|
|
|
+
|
|
|
|
|
+ if (ret < 0) {
|
|
|
|
|
+ LOG_ERR("lora_config failed: %d", ret);
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ cbf_init(NODE_ID);
|
|
|
|
|
+
|
|
|
|
|
+#if RX_MODE_CAD_TRIGGER
|
|
|
|
|
+ cad_rx_start(lora_dev, on_rx, NULL);
|
|
|
|
|
+#else
|
|
|
|
|
+ ret = lora_recv_async(lora_dev, on_rx, NULL);
|
|
|
|
|
+ if (ret < 0) {
|
|
|
|
|
+ LOG_ERR("lora_recv_async failed: %d", ret);
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+ /* After the first RX arm above - tx_queue owns pausing/resuming RX
|
|
|
|
|
+ * around every TX from here on (see tx_queue.h), through whichever
|
|
|
|
|
+ * mode's pause/resume pair was just armed.
|
|
|
|
|
+ */
|
|
|
|
|
+#if RX_MODE_CAD_TRIGGER
|
|
|
|
|
+ tx_queue_start(lora_dev, cad_rx_pause, cad_rx_resume);
|
|
|
|
|
+#else
|
|
|
|
|
+ tx_queue_start(lora_dev, rx_pause, rx_resume);
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+ LOG_INF("Node %d up (ordinal=%u, max_ordinal=%u)", NODE_ID, topology_ordinal(NODE_ID),
|
|
|
|
|
+ TOPOLOGY_MAX_ORDINAL);
|
|
|
|
|
+
|
|
|
|
|
+ if (NODE_ID == FRAME_SRC_CENTRAL) {
|
|
|
|
|
+ k_work_schedule_for_queue(&relay_work_q, &beacon_work, K_NO_WAIT);
|
|
|
|
|
+ } else if (NODE_ID == 2) {
|
|
|
|
|
+ /* Only node 2 originates scheduled Msg traffic
|
|
|
|
|
+ */
|
|
|
|
|
+ k_work_schedule_for_queue(&relay_work_q, &msg_work, K_NO_WAIT);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|