mirror of
https://github.com/cesanta/mongoose.git
synced 2026-11-06 15:51:25 +00:00
617 lines
23 KiB
C
617 lines
23 KiB
C
// Copyright (c) 2026 Cesanta Software Limited
|
|
// All rights reserved
|
|
|
|
#include "l2_mesh.h"
|
|
#include "log.h"
|
|
#include "printf.h"
|
|
#include "util.h"
|
|
|
|
#if MG_ENABLE_TCPIP
|
|
|
|
#define MGI_ETH_MAX 1514
|
|
#define MGI_TX_DST_LEN 2
|
|
|
|
// Mesh header, after the radio header: type, then burst: the fragment's
|
|
// index << 4 | the last fragment's index. Common to every radio
|
|
#define MG_MESH_HDR_LEN 2
|
|
|
|
#define MG_MESH_FRAME_HDR_LEN (MG_802154_HEADER_LEN + MG_MESH_HDR_LEN)
|
|
|
|
// MAC header, 9 bytes: frame control 0x8841, a data frame, PAN ID
|
|
// compression, short destination and source addresses; sequence number, 0,
|
|
// as nothing uses it yet; PAN ID; destination; source. Little endian, as
|
|
// the standard says
|
|
static void mgi_802154_print_hdr(const struct mg_radio *radio, uint8_t *buf,
|
|
uint16_t dst, uint16_t src) {
|
|
buf[0] = 0x41, buf[1] = 0x88, buf[2] = 0;
|
|
buf[3] = (uint8_t) MG_802154_PAN_ID;
|
|
buf[4] = (uint8_t) (MG_802154_PAN_ID >> 8);
|
|
buf[5] = (uint8_t) dst, buf[6] = (uint8_t) (dst >> 8);
|
|
buf[7] = (uint8_t) src, buf[8] = (uint8_t) (src >> 8);
|
|
(void) radio;
|
|
}
|
|
|
|
static size_t mgi_802154_parse_hdr(const struct mg_radio *radio,
|
|
const uint8_t *buf, size_t len,
|
|
uint16_t *dst, uint16_t *src) {
|
|
(void) radio;
|
|
if (len < MG_802154_HEADER_LEN || buf[0] != 0x41 || buf[1] != 0x88 ||
|
|
buf[3] != (uint8_t) MG_802154_PAN_ID ||
|
|
buf[4] != (uint8_t) (MG_802154_PAN_ID >> 8)) {
|
|
return 0;
|
|
}
|
|
*dst = (uint16_t) (buf[5] | buf[6] << 8);
|
|
*src = (uint16_t) (buf[7] | buf[8] << 8);
|
|
return MG_802154_HEADER_LEN;
|
|
}
|
|
|
|
const struct mg_radio mg_radio_802154 = {
|
|
"802.15.4", // name
|
|
125, // max_frame_size: 127 minus FCS
|
|
250000, // bitrate
|
|
8, // overhead
|
|
320, // slot_us
|
|
128, // cca_us
|
|
100, // turnaround_us
|
|
640, // ifs_us
|
|
3, // backoff_min
|
|
5, // backoff_max
|
|
true, // csma
|
|
MG_802154_HEADER_LEN, // header_len
|
|
mgi_802154_print_hdr, // print_hdr
|
|
mgi_802154_parse_hdr // parse_hdr
|
|
};
|
|
|
|
static uint64_t mgi_mesh_b_airtime(size_t len) {
|
|
const struct mg_radio *radio = &mg_radio_802154;
|
|
return ((uint64_t) (len + radio->overhead) * 8000000 + radio->bitrate - 1) /
|
|
radio->bitrate;
|
|
}
|
|
|
|
// Each queue record holds a destination and a complete burst.
|
|
static size_t mgi_mesh_b_queue(struct mg_mesh_node *node, struct mg_queue *q,
|
|
const void *buf, size_t len, uint16_t dst,
|
|
uint8_t type) {
|
|
const struct mg_radio *radio = &mg_radio_802154;
|
|
const uint8_t *eth = (const uint8_t *) buf;
|
|
size_t hdr = MG_MESH_FRAME_HDR_LEN, cap = radio->max_frame_size - hdr;
|
|
size_t frames, size, i;
|
|
char *record;
|
|
uint8_t *out;
|
|
if (len < 14 || len > MGI_ETH_MAX) return 0;
|
|
frames = (len + cap - 1) / cap;
|
|
size = MGI_TX_DST_LEN + len + frames * (1 + hdr);
|
|
if (mg_queue_next(q, NULL) == 0) mg_queue_init(q, q->buf, q->size);
|
|
if (mg_queue_book(q, &record, size) < size) return 0;
|
|
out = (uint8_t *) record;
|
|
*out++ = (uint8_t) (dst >> 8);
|
|
*out++ = (uint8_t) dst;
|
|
for (i = 0; i < frames; i++) {
|
|
size_t chunk = len - i * cap;
|
|
if (chunk > cap) chunk = cap;
|
|
*out++ = (uint8_t) (hdr + chunk);
|
|
radio->print_hdr(radio, out, dst, node->radio_address);
|
|
out[radio->header_len] = type;
|
|
out[radio->header_len + 1] = (uint8_t) (i << 4 | (frames - 1));
|
|
memcpy(out + hdr, eth + i * cap, chunk);
|
|
out += hdr + chunk;
|
|
}
|
|
mg_queue_add(q, size);
|
|
return len;
|
|
}
|
|
|
|
// Copyright (c) 2026 Cesanta Software Limited
|
|
// All rights reserved
|
|
|
|
#define MGI_JOIN_WINDOW_US 3000
|
|
#define MGI_JOIN_TURN_US 192
|
|
#define MGI_JOIN_JITTER_US 640
|
|
#define MGI_DATA_QUEUE_SIZE 2048
|
|
#define MGI_TX_RETRY_US 32
|
|
// Our frames: the radio header, with the radio addresses, then the mesh
|
|
// header MG_MESH_HDR_LEN, then the payload
|
|
#define MGI_JOIN_UP_LEN \
|
|
(MG_MESH_FRAME_HDR_LEN + 6) // MGI_JOIN_UP's payload: MAC addr
|
|
#define MGI_MAX_FRAGMENTS 14
|
|
|
|
enum { MGI_JOIN_DOWN = 1, MGI_JOIN_UP, MGI_DATA_DOWN, MGI_DATA_UP };
|
|
|
|
enum { MGI_TURN_IDLE, MGI_TURN_JOIN, MGI_TURN_DATA };
|
|
|
|
struct mgi_mesh_b_data {
|
|
uint8_t rx_buf[MGI_ETH_MAX]; // Ethernet frame being reassembled
|
|
uint8_t rx_expected; // Expected index | last index, 0xff if idle
|
|
struct mg_mesh_node *node; // Owner of the TCP/IP interface
|
|
struct mg_queue tx; // One record per burst of RF fragments
|
|
size_t tx_offset; // Next queued fragment; zero for an empty frame
|
|
uint64_t tx_at; // Next fragment or JOIN transmission time
|
|
uint64_t deadline; // End of the current JOIN or DATA turn
|
|
struct mgi_mesh_b_peer *fanout; // Next broadcast recipient
|
|
char tx_buf[MGI_DATA_QUEUE_SIZE]; // Transmit queue storage
|
|
uint32_t random; // PRNG state for join timing
|
|
struct mgi_mesh_b_peer *members; // Bridge membership list
|
|
struct mgi_mesh_b_peer *current; // Member whose turn is active or next
|
|
int8_t missed_rounds; // -1 after a grant; otherwise completed rounds missed
|
|
uint16_t coordinator; // Coordinator radio address
|
|
uint8_t phase; // MGI_TURN_IDLE, MGI_TURN_JOIN, or MGI_TURN_DATA
|
|
};
|
|
|
|
struct mgi_mesh_b_peer {
|
|
struct mgi_mesh_b_peer *next; // Next member in round-robin order
|
|
uint16_t radio_address; // Source address supplied in MGI_JOIN_UP
|
|
uint8_t mac[6]; // Ethernet MAC supplied in MGI_JOIN_UP
|
|
uint8_t misses; // Turns in a row it did not answer
|
|
};
|
|
|
|
static uint32_t mgi_mesh_b_random(struct mg_mesh_node *node,
|
|
struct mgi_mesh_b_data *data) {
|
|
uint32_t *random = &data->random;
|
|
if (*random == 0) {
|
|
size_t i;
|
|
*random = (uint32_t) mg_now();
|
|
for (i = 0; i < 6; i++) *random = *random * 33u + node->radio->mac[i];
|
|
if (*random == 0) *random = 1;
|
|
}
|
|
*random ^= *random << 13;
|
|
*random ^= *random >> 17;
|
|
*random ^= *random << 5;
|
|
return *random;
|
|
}
|
|
|
|
static const struct mg_radio *const s_mesh_default_radio = &mg_radio_802154;
|
|
|
|
// Read a frame's radio addresses to dst and src, see mg_radio::parse_hdr(), and
|
|
// return its type, from the mesh header. 0: not a frame of ours
|
|
static unsigned mgi_mesh_b_frame_parse(const uint8_t *frame, size_t len,
|
|
uint16_t *dst, uint16_t *src) {
|
|
if (len < MG_MESH_FRAME_HDR_LEN ||
|
|
s_mesh_default_radio->parse_hdr(s_mesh_default_radio, frame, len, dst,
|
|
src) !=
|
|
s_mesh_default_radio->header_len) {
|
|
return 0;
|
|
}
|
|
return frame[s_mesh_default_radio->header_len];
|
|
}
|
|
|
|
// Write a frame's headers: the radio's, to dst from src, then ours: type,
|
|
// and burst: high nibble is the index, low nibble is the last index
|
|
static void mgi_mesh_b_frame_header(uint8_t *frame, uint16_t dst, uint16_t src,
|
|
uint8_t type, uint8_t burst) {
|
|
size_t h = s_mesh_default_radio->header_len;
|
|
s_mesh_default_radio->print_hdr(s_mesh_default_radio, frame, dst, src);
|
|
frame[h] = type, frame[h + 1] = burst;
|
|
}
|
|
|
|
static bool mgi_mesh_b_control_send(struct mg_mesh_node *node, uint16_t dst,
|
|
uint8_t command) {
|
|
struct mg_tcpip_if *ifp = node->radio;
|
|
uint8_t frame[MGI_JOIN_UP_LEN] = {0};
|
|
size_t len = MG_MESH_FRAME_HDR_LEN;
|
|
mgi_mesh_b_frame_header(frame, dst, node->radio_address, command, 0);
|
|
if (command == MGI_JOIN_UP) {
|
|
memcpy(frame + MG_MESH_FRAME_HDR_LEN, node->radio->mac, 6);
|
|
len = MGI_JOIN_UP_LEN;
|
|
}
|
|
return node->now >= node->tx_at && node->tx(ifp, frame, len) == len;
|
|
}
|
|
|
|
static size_t mgi_mesh_b_fragment_capacity(void) {
|
|
return s_mesh_default_radio->max_frame_size - MG_MESH_FRAME_HDR_LEN;
|
|
}
|
|
|
|
static uint64_t mgi_mesh_b_burst_time(size_t len) {
|
|
size_t cap = mgi_mesh_b_fragment_capacity();
|
|
size_t frames = len == 0 ? 1 : (len + cap - 1) / cap;
|
|
return (frames - 1) *
|
|
(mgi_mesh_b_airtime(s_mesh_default_radio->max_frame_size) +
|
|
s_mesh_default_radio->ifs_us) +
|
|
mgi_mesh_b_airtime(MG_MESH_FRAME_HDR_LEN + len - (frames - 1) * cap);
|
|
}
|
|
|
|
static void mgi_mesh_b_tx_finish(struct mgi_mesh_b_data *data) {
|
|
if (data->tx_offset != 0) {
|
|
if (data->fanout != NULL) data->fanout = data->fanout->next;
|
|
if (data->fanout == NULL) {
|
|
size_t len = mg_queue_next(&data->tx, NULL);
|
|
mg_queue_del(&data->tx, len);
|
|
}
|
|
}
|
|
data->tx_offset = 0;
|
|
data->tx_at = (uint64_t) -1;
|
|
}
|
|
|
|
static void mgi_mesh_b_tx_step(struct mgi_mesh_b_data *data, uint64_t now) {
|
|
struct mg_mesh_node *node = data->node;
|
|
struct mg_tcpip_if *radio = node->radio;
|
|
uint8_t empty[MG_MESH_FRAME_HDR_LEN], *frame = empty;
|
|
size_t len = MG_MESH_FRAME_HDR_LEN, record_len = 0;
|
|
uint16_t dst;
|
|
uint8_t command;
|
|
if (now < data->tx_at) return;
|
|
if (now >= data->deadline) {
|
|
mgi_mesh_b_tx_finish(data);
|
|
return;
|
|
}
|
|
dst = node->lan == NULL ? data->coordinator : data->current->radio_address;
|
|
command = node->lan == NULL ? MGI_DATA_UP : MGI_DATA_DOWN;
|
|
if (data->tx_offset != 0) {
|
|
char *record;
|
|
uint8_t *part;
|
|
record_len = mg_queue_next(&data->tx, &record);
|
|
part = (uint8_t *) record + data->tx_offset;
|
|
len = part[0];
|
|
frame = part + 1;
|
|
s_mesh_default_radio->print_hdr(s_mesh_default_radio, frame, dst,
|
|
node->radio_address);
|
|
} else {
|
|
mgi_mesh_b_frame_header(frame, dst, node->radio_address, command, 0);
|
|
}
|
|
if (now < data->node->tx_at || data->node->tx(radio, frame, len) != len) {
|
|
data->tx_at = now + MGI_TX_RETRY_US;
|
|
return;
|
|
}
|
|
if (data->tx_offset == 0 || (data->tx_offset += len + 1) == record_len) {
|
|
mgi_mesh_b_tx_finish(data);
|
|
} else {
|
|
data->tx_at = now + mgi_mesh_b_airtime(len) + s_mesh_default_radio->ifs_us;
|
|
}
|
|
}
|
|
|
|
static void mgi_mesh_b_tx_begin(struct mgi_mesh_b_data *data, bool burst,
|
|
uint64_t now, uint64_t deadline) {
|
|
data->phase = MGI_TURN_DATA;
|
|
data->tx_offset = burst ? MGI_TX_DST_LEN : 0;
|
|
data->tx_at = now;
|
|
data->deadline = deadline;
|
|
}
|
|
|
|
// Return the assembled length, zero for an empty frame, or -1 if incomplete
|
|
// or invalid. Full non-final fragments make the write offset derivable.
|
|
static int mgi_mesh_b_data_receive(struct mgi_mesh_b_data *data,
|
|
const uint8_t *frame, size_t len,
|
|
uint64_t now) {
|
|
unsigned burst = frame[MG_MESH_FRAME_HDR_LEN - 1], index = burst >> 4,
|
|
last = burst & 15;
|
|
size_t chunk = len - MG_MESH_FRAME_HDR_LEN,
|
|
cap = mgi_mesh_b_fragment_capacity();
|
|
size_t offset = index * cap;
|
|
if (index == 0) {
|
|
data->rx_expected = (uint8_t) burst;
|
|
if (data->node->lan == NULL)
|
|
data->deadline = now + mgi_mesh_b_burst_time(MGI_ETH_MAX) +
|
|
2 * s_mesh_default_radio->ifs_us + MGI_JOIN_TURN_US;
|
|
}
|
|
if (last >= MGI_MAX_FRAGMENTS || index > last || burst != data->rx_expected ||
|
|
chunk > cap || now > data->deadline || (index < last && chunk != cap) ||
|
|
(last != 0 && chunk == 0) || offset + chunk > sizeof(data->rx_buf)) {
|
|
data->rx_expected = 0xff;
|
|
return -1;
|
|
}
|
|
memcpy(data->rx_buf + offset, frame + MG_MESH_FRAME_HDR_LEN, chunk);
|
|
if (index < last) {
|
|
data->rx_expected = (uint8_t) (burst + 0x10);
|
|
return -1;
|
|
}
|
|
data->rx_expected = 0xff;
|
|
return offset + chunk == 0 || offset + chunk >= 14 ? (int) (offset + chunk)
|
|
: -1;
|
|
}
|
|
|
|
// Bridge scheduling and Ethernet forwarding.
|
|
|
|
#define MGI_MAX_MISSES 3 // Turns in a row without an answer: remove the member
|
|
|
|
static uint16_t mgi_mesh_b_join_field(const uint8_t *p) {
|
|
return (uint16_t) ((unsigned) p[0] * 256 + p[1]);
|
|
}
|
|
|
|
// Return chaddr for an Ethernet DHCP server reply, even when broadcast.
|
|
static const uint8_t *mgi_mesh_b_dhcp_client(const uint8_t *frame, size_t len) {
|
|
size_t ip_len, ihl, udp_len;
|
|
const uint8_t *udp, *dhcp;
|
|
if (len < 34 || mgi_mesh_b_join_field(frame + 12) != 0x0800 ||
|
|
(frame[14] >> 4) != 4 || frame[23] != 17 ||
|
|
(mgi_mesh_b_join_field(frame + 20) & 0x3fff) != 0)
|
|
return NULL;
|
|
ip_len = mgi_mesh_b_join_field(frame + 16);
|
|
ihl = (size_t) (frame[14] & 15) * 4;
|
|
if (ihl < 20 || ip_len > len - 14 || ip_len < ihl + 8 + 240) return NULL;
|
|
udp = frame + 14 + ihl;
|
|
udp_len = mgi_mesh_b_join_field(udp + 4);
|
|
if (mgi_mesh_b_join_field(udp) != 67 ||
|
|
mgi_mesh_b_join_field(udp + 2) != 68 || udp_len < 8 + 240 ||
|
|
udp_len > ip_len - ihl)
|
|
return NULL;
|
|
dhcp = udp + 8;
|
|
if (dhcp[0] != 2 || dhcp[1] != 1 || dhcp[2] != 6 ||
|
|
memcmp(dhcp + 236, "\x63\x82\x53\x63", 4) != 0)
|
|
return NULL;
|
|
return dhcp + 28;
|
|
}
|
|
|
|
static size_t mgi_mesh_b_enqueue(struct mg_mesh_node *node, const void *buf,
|
|
size_t len) {
|
|
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) node->data;
|
|
const uint8_t *frame = (const uint8_t *) buf;
|
|
if (node->lan == NULL)
|
|
return mgi_mesh_b_queue(node, &data->tx, buf, len, data->coordinator,
|
|
MGI_DATA_UP);
|
|
if (len >= 14 && len <= MGI_ETH_MAX) {
|
|
const uint8_t *client = mgi_mesh_b_dhcp_client(frame, len);
|
|
const uint8_t *dst = client == NULL ? frame : client;
|
|
bool multicast = client == NULL && (frame[0] & 1) != 0;
|
|
struct mgi_mesh_b_peer *peer;
|
|
for (peer = data->members; peer != NULL; peer = peer->next) {
|
|
if (multicast || memcmp(dst, peer->mac, 6) == 0) {
|
|
uint16_t addr = multicast ? 0xffffu : peer->radio_address;
|
|
return mgi_mesh_b_queue(node, &data->tx, buf, len, addr, MGI_DATA_DOWN);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void mgi_mesh_b_bridge_queue_lan(struct mg_mesh_node *bridge) {
|
|
struct mg_queue *rx = &bridge->lan->recv_queue;
|
|
char *buf;
|
|
size_t len;
|
|
while ((len = mg_queue_next(rx, &buf)) > 0) {
|
|
bridge->strategy->tx(bridge, buf, len);
|
|
mg_queue_del(rx, len);
|
|
}
|
|
}
|
|
|
|
static void mgi_mesh_b_bridge_rx(uint64_t now, struct mg_mesh_node *b,
|
|
const uint8_t *frame, size_t len) {
|
|
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) b->data;
|
|
struct mgi_mesh_b_peer *peer;
|
|
uint16_t dst = 0, src = 0;
|
|
unsigned command = mgi_mesh_b_frame_parse(frame, len, &dst, &src);
|
|
if (command == MGI_DATA_UP && dst == b->radio_address &&
|
|
data->phase == MGI_TURN_DATA && data->current != NULL &&
|
|
src == data->current->radio_address && now <= data->deadline) {
|
|
int received = mgi_mesh_b_data_receive(data, frame, len, now);
|
|
if (received < 0) return;
|
|
if (received != 0) {
|
|
b->lan->driver->tx(data->rx_buf, (size_t) received, b->lan);
|
|
}
|
|
data->current->misses = 0;
|
|
data->current = data->current->next;
|
|
data->phase = MGI_TURN_IDLE;
|
|
data->deadline = now;
|
|
return;
|
|
}
|
|
if (len != MGI_JOIN_UP_LEN || command != MGI_JOIN_UP ||
|
|
dst != b->radio_address || data->phase != MGI_TURN_JOIN ||
|
|
now >= data->deadline) {
|
|
return;
|
|
}
|
|
if (src == 0 || src == 0xffffu) return;
|
|
for (peer = data->members; peer != NULL; peer = peer->next) {
|
|
if (peer->radio_address == src) break;
|
|
}
|
|
if (peer == NULL) {
|
|
peer = (struct mgi_mesh_b_peer *) calloc(1, sizeof(*peer));
|
|
if (peer == NULL) return;
|
|
peer->radio_address = src;
|
|
peer->next = data->members;
|
|
data->members = peer;
|
|
}
|
|
memcpy(peer->mac, frame + MG_MESH_FRAME_HDR_LEN, sizeof(peer->mac));
|
|
}
|
|
|
|
// Member peer did not answer its turn. After MGI_MAX_MISSES in a row, it is
|
|
// gone: remove it, and its queued downlink frames
|
|
static void mgi_mesh_b_member_missed(struct mgi_mesh_b_data *data,
|
|
struct mgi_mesh_b_peer *peer) {
|
|
if (++peer->misses < MGI_MAX_MISSES) return;
|
|
MG_INFO(("Member %M removed: no answer %d times", mg_print_mac, peer->mac,
|
|
MGI_MAX_MISSES));
|
|
if (data->fanout == peer) {
|
|
data->fanout = peer->next;
|
|
if (data->fanout == NULL) {
|
|
size_t len = mg_queue_next(&data->tx, NULL);
|
|
mg_queue_del(&data->tx, len);
|
|
}
|
|
}
|
|
LIST_DELETE(struct mgi_mesh_b_peer, &data->members, peer);
|
|
free(peer);
|
|
}
|
|
|
|
// A broadcast occupies one record and is sent once to each member in list
|
|
// order.
|
|
static size_t mgi_mesh_b_bridge_burst(struct mgi_mesh_b_data *data,
|
|
struct mgi_mesh_b_peer *peer) {
|
|
char *record;
|
|
size_t len;
|
|
while ((len = mg_queue_next(&data->tx, &record)) != 0) {
|
|
uint16_t dst = mgi_mesh_b_join_field((const uint8_t *) record);
|
|
struct mgi_mesh_b_peer *member;
|
|
if (dst == 0xffffu) {
|
|
if (data->fanout == NULL) data->fanout = data->members;
|
|
return data->fanout == peer ? len : 0;
|
|
}
|
|
if (dst == peer->radio_address) return len;
|
|
for (member = data->members; member != NULL; member = member->next) {
|
|
if (member->radio_address == dst) return 0;
|
|
}
|
|
mg_queue_del(&data->tx, len); // Its destination has left.
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static uint64_t mgi_mesh_b_queued_burst_time(struct mgi_mesh_b_data *data,
|
|
size_t len) {
|
|
char *record;
|
|
size_t frames;
|
|
if (len == 0) return mgi_mesh_b_burst_time(0);
|
|
mg_queue_next(&data->tx, &record);
|
|
// Fragment zero carries the final index. Count overhead and IFS per frame.
|
|
frames =
|
|
((uint8_t) record[MGI_TX_DST_LEN + MG_MESH_FRAME_HDR_LEN] & 15u) + 1u;
|
|
return mgi_mesh_b_airtime(len - MGI_TX_DST_LEN - frames +
|
|
(frames - 1) * s_mesh_default_radio->overhead) +
|
|
(frames - 1) * s_mesh_default_radio->ifs_us;
|
|
}
|
|
|
|
static void mgi_mesh_b_bridge_io(uint64_t now, struct mg_mesh_node *b) {
|
|
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) b->data;
|
|
mgi_mesh_b_bridge_queue_lan(b);
|
|
if (now >= data->deadline) {
|
|
if (data->tx_at != (uint64_t) -1) mgi_mesh_b_tx_finish(data);
|
|
if (data->phase == MGI_TURN_DATA && data->current != NULL) {
|
|
struct mgi_mesh_b_peer *peer = data->current;
|
|
data->current = peer->next;
|
|
mgi_mesh_b_member_missed(data, peer);
|
|
} else if (data->phase == MGI_TURN_JOIN) {
|
|
data->current = data->members;
|
|
}
|
|
data->phase = MGI_TURN_IDLE;
|
|
if (data->current != NULL) {
|
|
size_t burst = mgi_mesh_b_bridge_burst(data, data->current);
|
|
uint64_t down = mgi_mesh_b_queued_burst_time(data, burst);
|
|
data->rx_expected = 0xff;
|
|
mgi_mesh_b_tx_begin(data, burst != 0, now,
|
|
now + down + mgi_mesh_b_burst_time(MGI_ETH_MAX) +
|
|
2 * s_mesh_default_radio->ifs_us +
|
|
MGI_JOIN_TURN_US);
|
|
} else if (mgi_mesh_b_control_send(b, 0xffffu, MGI_JOIN_DOWN)) {
|
|
data->phase = MGI_TURN_JOIN;
|
|
data->deadline = now + MGI_JOIN_WINDOW_US;
|
|
}
|
|
if (data->deadline <= now) data->deadline = now + MGI_JOIN_TURN_US;
|
|
}
|
|
}
|
|
|
|
static void mgi_mesh_b_bridge_free(struct mgi_mesh_b_data *data) {
|
|
while (data->members != NULL) {
|
|
struct mgi_mesh_b_peer *peer = data->members;
|
|
data->members = peer->next;
|
|
free(peer);
|
|
}
|
|
}
|
|
|
|
static void mgi_mesh_b_node_rx(struct mg_mesh_node *n, uint64_t now,
|
|
uint8_t *frame, size_t len) {
|
|
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) n->data;
|
|
uint16_t dst = 0, src = 0;
|
|
unsigned command = mgi_mesh_b_frame_parse(frame, len, &dst, &src);
|
|
if (command == MGI_JOIN_DOWN && len == MG_MESH_FRAME_HDR_LEN &&
|
|
dst == 0xffffu) {
|
|
if (n->joined && src == data->coordinator &&
|
|
++data->missed_rounds >= MGI_MAX_MISSES) {
|
|
n->joined = false;
|
|
data->phase = MGI_TURN_IDLE;
|
|
data->tx_at = (uint64_t) -1;
|
|
data->tx_offset = 0;
|
|
data->rx_expected = 0xff;
|
|
}
|
|
if (!n->joined && (mgi_mesh_b_random(n, data) % 64) == 0) {
|
|
// Spread contenders across windows as well as within the reply window.
|
|
data->coordinator = src;
|
|
data->phase = MGI_TURN_JOIN;
|
|
data->tx_at = now + MGI_JOIN_TURN_US +
|
|
mgi_mesh_b_random(n, data) % MGI_JOIN_JITTER_US;
|
|
}
|
|
} else if (command == MGI_DATA_DOWN && data->coordinator != 0 &&
|
|
dst == n->radio_address && src == data->coordinator) {
|
|
int received = mgi_mesh_b_data_receive(data, frame, len, now);
|
|
if (received < 0) return;
|
|
n->joined = true;
|
|
data->missed_rounds =
|
|
-1; // This round had a grant; do not count it as missed.
|
|
if (received != 0) {
|
|
struct mg_tcpip_if *ifp = n->radio;
|
|
if (n->rx_len != 0 || (size_t) received > ifp->recv_queue.size) {
|
|
ifp->ndrop++;
|
|
} else {
|
|
memcpy(ifp->recv_queue.buf, data->rx_buf, (size_t) received);
|
|
n->rx_len = (size_t) received;
|
|
}
|
|
}
|
|
if (data->tx_at != (uint64_t) -1) mgi_mesh_b_tx_finish(data);
|
|
mgi_mesh_b_tx_begin(data, mg_queue_next(&data->tx, NULL) != 0, now,
|
|
now + mgi_mesh_b_burst_time(MGI_ETH_MAX) +
|
|
2 * s_mesh_default_radio->ifs_us +
|
|
MGI_JOIN_TURN_US);
|
|
}
|
|
}
|
|
|
|
static void mgi_mesh_b_input(struct mg_mesh_node *node, void *buf, size_t len) {
|
|
uint64_t ready = node->now + node->strategy->radio->turnaround_us;
|
|
if (node->tx_at < ready) node->tx_at = ready;
|
|
node->next_tick = node->now;
|
|
if (node->lan != NULL)
|
|
mgi_mesh_b_bridge_rx(node->now, node, (const uint8_t *) buf, len);
|
|
else
|
|
mgi_mesh_b_node_rx(node, node->now, (uint8_t *) buf, len);
|
|
}
|
|
|
|
static void mgi_mesh_b_tick(struct mg_mesh_node *node, uint64_t now) {
|
|
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) node->data;
|
|
node->now = now;
|
|
if (node->lan != NULL) {
|
|
mgi_mesh_b_bridge_io(now, node);
|
|
mgi_mesh_b_tx_step(data, now);
|
|
node->next_tick = data->deadline;
|
|
if (data->tx_at < node->next_tick) node->next_tick = data->tx_at;
|
|
return;
|
|
}
|
|
if (data->phase == MGI_TURN_JOIN) {
|
|
if (data->tx_at <= now) {
|
|
if (now - data->tx_at <= MGI_JOIN_TURN_US && !node->joined)
|
|
mgi_mesh_b_control_send(node, data->coordinator, MGI_JOIN_UP);
|
|
data->tx_at = (uint64_t) -1;
|
|
data->phase = MGI_TURN_IDLE;
|
|
}
|
|
} else {
|
|
mgi_mesh_b_tx_step(data, now);
|
|
}
|
|
node->next_tick = data->tx_at;
|
|
}
|
|
|
|
static bool mgi_mesh_b_init(struct mg_mesh_node *node,
|
|
struct mg_tcpip_if *radio, struct mg_tcpip_if *lan,
|
|
uint16_t radio_address) {
|
|
struct mgi_mesh_b_data *data =
|
|
(struct mgi_mesh_b_data *) calloc(1, sizeof(*data));
|
|
memset(node, 0, sizeof(*node));
|
|
node->radio = radio;
|
|
node->lan = lan;
|
|
node->radio_address = radio_address;
|
|
node->next_tick = (uint64_t) -1;
|
|
node->ram_used = node->ram_peak = sizeof(*node);
|
|
if (data == NULL) return false;
|
|
node->ram_used += sizeof(*data);
|
|
node->ram_peak = node->ram_used;
|
|
data->rx_expected = 0xff;
|
|
node->data = data;
|
|
data->node = node;
|
|
data->tx_at = (uint64_t) -1;
|
|
mg_queue_init(&data->tx, data->tx_buf, sizeof(data->tx_buf));
|
|
if (lan != NULL) node->next_tick = 0;
|
|
return true;
|
|
}
|
|
|
|
static void mgi_mesh_b_free(struct mg_mesh_node *node) {
|
|
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) node->data;
|
|
if (data == NULL) return;
|
|
mgi_mesh_b_bridge_free(data);
|
|
free(data);
|
|
node->data = NULL;
|
|
}
|
|
|
|
const struct mg_mesh mg_mesh_default = {
|
|
"default",
|
|
mgi_mesh_b_init,
|
|
mgi_mesh_b_tick,
|
|
mgi_mesh_b_free,
|
|
mgi_mesh_b_enqueue,
|
|
mgi_mesh_b_input,
|
|
true,
|
|
true,
|
|
&mg_radio_802154,
|
|
};
|
|
|
|
#endif
|