really still wip

This commit is contained in:
2025-12-28 12:23:11 +01:00
parent a48ef9d5e0
commit 31dda62474
33 changed files with 1721 additions and 589 deletions

View File

@@ -6,6 +6,7 @@
#include "meshcore/packets/control.h"
#include "meshcore/packets/encrypted.h"
#include "meshcore/packets/group.h"
#include "meshcore/stats.h"
#include "task.h"
#include "lib/base64.h"
#include "lib/cifra/aes.h"
@@ -13,52 +14,54 @@
#include "lib/cifra/hmac.h"
#include "lib/config.h"
#include "meshframing.h"
#include "meshcore/packetstructs.h"
#define TAG "MeshCore"
// requires at least a 256 byte data
void processFrame (FrameStruct frame) {
void processFrame (const FrameStruct *frame) {
printFrameHeader (frame);
if (frame.header & PAYLOAD_VERSION_3) { // more than the version 0
ESP_LOGW (TAG, "Frame too new, got version %d instead of 0", (frame.header & PAYLOAD_VERSION_3) >> 6);
if (frame->header & PAYLOAD_VERSION_3) { // more than the version 0
MESH_LOGW (TAG, "Frame too new, got version %d instead of 0", (frame->header & PAYLOAD_VERSION_3) >> 6);
}
unsigned char checkSumBuf[10];
AdvertisementPayload advert;
GroupTextMessage msg;
unsigned char frameType = frame.header & PAYLOAD_TYPE_MASK;
unsigned char frameType = frame->header & PAYLOAD_TYPE_MASK;
unsigned char index = 0;
stats.packetsReceivedCount++;
if ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_FLOOD ||
(frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) {
stats.receivedFloodCount++;
}
if ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_DIRECT ||
(frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_DIRECT) {
stats.receivedDirectCount++;
}
if (frameType == PAYLOAD_TYPE_ANON_REQ) {
decodeAnonReq (frame);
} else if (frameType == PAYLOAD_TYPE_PATH || frameType == PAYLOAD_TYPE_REQ || frameType == PAYLOAD_TYPE_RESPONSE || frameType == PAYLOAD_TYPE_TXT_MSG) {
printf (" Typexd: 0x%02X\n", frameType);
EncryptedPayloadStruct enc = decodeEncryptedPayload (frame);
printf (" Typexdd: 0x%02X\n", enc.type);
if (enc.payloadLen > 0) {
parseEncryptedPayload (enc);
}
decodeEncryptedPayload (frame);
} else if (frameType == PAYLOAD_TYPE_ACK) {
memset (checkSumBuf, 0, sizeof (checkSumBuf));
base64_encode (frame.payload, 4, checkSumBuf);
printf ("Checksum: %s\n", checkSumBuf);
} else if (frameType == PAYLOAD_TYPE_ACK) {
uint32_t checkSum = frame.payload[index++];
checkSum |= frame.payload[index++] << 8;
checkSum |= frame.payload[index++] << 16;
checkSum |= frame.payload[index++] << 24;
uint32_t checkSum = frame->payload[index++];
checkSum |= frame->payload[index++] << 8;
checkSum |= frame->payload[index++] << 16;
checkSum |= frame->payload[index++] << 24;
// TODO add checking
} else if (frameType == PAYLOAD_TYPE_ADVERT) {
advert = decodeAdvertisement (frame);
printAdvertisement (advert);
decodeAdvertisement (frame);
} else if (frameType == PAYLOAD_TYPE_GRP_TXT || frameType == PAYLOAD_TYPE_GRP_DATA) {
msg = decodeGroupMessage (frame);
printGroupMessage (msg);
decodeGroupMessage (frame);
} else if (frameType == PAYLOAD_TYPE_TRACE) {
} else if (frameType == PAYLOAD_TYPE_MULTIPART) {
@@ -68,7 +71,8 @@ void processFrame (FrameStruct frame) {
} else if (frameType == PAYLOAD_TYPE_RAW_CUSTOM) {
// not implemented
} else {
stats.packetsReceivedCount--;
}
retransmitFrame (frame);
MESH_LOGD(TAG, "Processed frame");
}

View File

@@ -11,6 +11,6 @@
#include <ctype.h>
#include "stdio.h"
void processFrame (FrameStruct frame);
void processFrame (const FrameStruct * frame);
#endif

View File

@@ -1,12 +1,110 @@
#include "meshframing.h"
#include "ch32v30x_gpio.h"
#include "lib/config.h"
#include "meshcore/stats.h"
#include "string.h"
#include "stdio.h"
#include "sx1262.h"
#include "util/hexdump.h"
#include "util/log.h"
#include "FreeRTOS.h"
#include "task.h"
#include "lib/cifra/sha2.h"
#define TAG "Meshframing"
int ReadFrame (FrameStruct *frame, int8_t *rssiPacket, int8_t *snrPacket, int8_t *rawSnr) {
uint16_t irqRegs = GetIrqStatus();
// uint8_t status = GetStatus();
if (irqRegs & SX126X_IRQ_RX_DONE) {
// ClearIrqStatus(SX126X_IRQ_RX_DONE);
ClearIrqStatus (SX126X_IRQ_ALL);
uint8_t offset = 0;
uint8_t payloadLength = 0;
GetRxBufferStatus (&payloadLength, &offset);
if (payloadLength == 0) {
return 0;
}
GetPacketStatus (rssiPacket, snrPacket, rawSnr);
memset (frame, 0, sizeof (FrameStruct));
WaitForIdle (BUSY_WAIT, "start ReadBuffer", 1);
uint8_t cmd[3] = {SX126X_CMD_READ_BUFFER, offset, SX126X_CMD_NOP};
GPIO_WriteBit (GPIOA, GPIO_Pin_4, 0);
uint16_t curPayloadIndex = 0;
uint8_t pathIndex = 0;
uint8_t transportIndex = 0;
uint8_t state = 0; // 0=header, 1=transport, 2=pathLen, 3=path, 4=payload
for (uint16_t i = 0; i < payloadLength + sizeof (cmd); i++) {
uint8_t out = (i < sizeof (cmd)) ? cmd[i] : 0xFF;
// Wait TX ready
while (!SPI_I2S_GetFlagStatus (SPI1, SPI_I2S_FLAG_TXE));
SPI_I2S_SendData (SPI1, out);
// Wait RX ready
while (!SPI_I2S_GetFlagStatus (SPI1, SPI_I2S_FLAG_RXNE));
uint8_t in = (uint8_t)SPI_I2S_ReceiveData (SPI1);
// Only process payload bytes
if (i >= sizeof (cmd)) {
switch (state) {
case 0: // header
frame->header = in;
state = ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_DIRECT ||
(frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD)
? 1
: 2;
break;
case 1: // transportCodes[4]
frame->transportCodes[transportIndex++] = in;
if (transportIndex >= 4)
state = 2;
break;
case 2: // pathLen
frame->path.pathLen = in;
if (frame->path.pathLen > 64) {
frame->path.pathLen = 64;
}
pathIndex = 0;
state = (frame->path.pathLen > 0) ? 3 : 4;
break;
case 3: // path
frame->path.path[pathIndex++] = in;
if (pathIndex >= frame->path.pathLen)
state = 4;
break;
case 4: // payload
frame->payload[curPayloadIndex++] = in;
break;
}
}
}
frame->payloadLen = curPayloadIndex;
GPIO_WriteBit (GPIOA, GPIO_Pin_4, 1);
WaitForIdle (BUSY_WAIT, "end ReadBuffer", 0);
return payloadLength;
}
return 0;
}
/*
FrameStruct decodeFrame (unsigned char *data, unsigned char dataLen) {
hexdump ("RxDump", data, dataLen);
FrameStruct frame;
memset (&frame, 0, sizeof (frame));
unsigned char index = 0;
@@ -25,9 +123,9 @@ FrameStruct decodeFrame (unsigned char *data, unsigned char dataLen) {
return frame;
}
void printFrameHeader (FrameStruct frame) {
switch (frame.header & ROUTE_TYPE_MASK) {
*/
void printFrameHeader (const FrameStruct *frame) {
switch (frame->header & ROUTE_TYPE_MASK) {
case ROUTE_TYPE_TRANSPORT_FLOOD:
printf ("transport flood");
break;
@@ -47,7 +145,7 @@ void printFrameHeader (FrameStruct frame) {
printf (", payload type is ");
switch (frame.header & PAYLOAD_TYPE_MASK) {
switch (frame->header & PAYLOAD_TYPE_MASK) {
case PAYLOAD_TYPE_REQ:
printf ("request");
break;
@@ -101,67 +199,204 @@ void printFrameHeader (FrameStruct frame) {
break;
}
char version[2];
version[0] = (frame.header >> 6) + '0';
version[0] = (frame->header >> 6) + '0';
version[1] = 0;
printf (", payload version is %s ", version);
if ((frame.header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_DIRECT ||
(frame.header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) {
printf ("Transport codes: %d %d\n", *((uint16_t *)frame.transportCodes),
*((uint16_t *)&(frame.transportCodes[2])));
if ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_DIRECT ||
(frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) {
printf ("Transport codes: %d %d\n", *((uint16_t *)frame->transportCodes),
*((uint16_t *)&(frame->transportCodes[2])));
}
printf ("Path is %d nodes long", frame.path.pathLen);
printf ("Path is %d nodes long", frame->path.pathLen);
for (uint8_t pathIndex = 0; pathIndex < frame.path.pathLen; pathIndex++) {
printf ("node %d - %02X, ", pathIndex, frame.path.path[pathIndex]);
for (uint8_t pathIndex = 0; pathIndex < frame->path.pathLen; pathIndex++) {
printf ("node %d - %02X, ", pathIndex, frame->path.path[pathIndex]);
}
putchar ('\n');
}
void sendFrame (FrameStruct frame) {
uint8_t txBuf[256];
size_t offset = 0;
void LoRaTransmit (const FrameStruct *frame) {
uint8_t len = 2; // header + path_len
txBuf[offset++] = frame.header;
if ((frame.header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_DIRECT ||
(frame.header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) {
memcpy (txBuf + offset, frame.transportCodes, 4);
offset += 4;
if ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_DIRECT ||
(frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) {
len += 4;
}
if (frame.path.pathLen > 64) {
frame.path.pathLen = 64;
}
len += frame->path.pathLen;
len += frame->payloadLen;
txBuf[offset++] = frame.path.pathLen;
uint16_t irqStatus;
char rv = 0;
memcpy (txBuf + offset, frame.path.path, frame.path.pathLen);
offset += frame.path.pathLen;
if (txActive == 0) {
txActive = 1;
uint16_t maxPayloadLen = 256 - offset;
if (PacketParams[2] == 0x00) { // explicit header, variable length
PacketParams[3] = len;
}
uint16_t payloadLen = frame.payloadLen > maxPayloadLen ? maxPayloadLen : frame.payloadLen;
WriteCommand (SX126X_CMD_SET_PACKET_PARAMS, PacketParams, 6);
ClearIrqStatus (SX126X_IRQ_ALL);
memcpy (txBuf + offset, frame.payload, payloadLen);
offset += payloadLen;
WaitForIdle (BUSY_WAIT, "start WriteBuffer", 1);
hexdump ("TxDump", txBuf, offset);
LoRaSend (txBuf, offset, SX126x_TXMODE_SYNC);
}
uint8_t cmdBuf[2] = {SX126X_CMD_WRITE_BUFFER, 0x00};
uint8_t cmdLen = sizeof (cmdBuf);
void retransmitFrame (FrameStruct frame) {
if (frame.header & ROUTE_TYPE_FLOOD || frame.header & ROUTE_TYPE_TRANSPORT_FLOOD) {
if (frame.header != DONT_RETRANSMIT_HEADER && frame.path.pathLen + 1 < MAX_FLOOD_TTL) {
frame.path.path[frame.path.pathLen++] = persistent.pubkey[0];
GPIO_WriteBit (GPIOA, GPIO_Pin_4, 0);
uint16_t payloadIndex = 0;
uint8_t state = 0;
uint8_t pathIndex = 0;
uint8_t transportIndex = 0;
for (uint16_t i = 0; i < len + cmdLen; i++) {
uint8_t out = 0xFF;
if (i < cmdLen) {
out = cmdBuf[i];
} else {
switch (state) {
case 0: // header
out = frame->header;
state = ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_DIRECT ||
(frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD)
? 1
: 2;
break;
case 1: // transport codes
out = frame->transportCodes[transportIndex++];
if (transportIndex >= 4)
state = 2;
break;
case 2: // path length
out = frame->path.pathLen;
pathIndex = 0;
state = (frame->path.pathLen > 0) ? 3 : 4;
break;
case 3: // path
out = frame->path.path[pathIndex++];
if (pathIndex >= frame->path.pathLen)
state = 4;
break;
case 4: // payload
out = frame->payload[payloadIndex++];
break;
}
}
while (SPI_I2S_GetFlagStatus (SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData (SPI1, out);
while (SPI_I2S_GetFlagStatus (SPI1, SPI_I2S_FLAG_RXNE) == RESET);
(void)SPI_I2S_ReceiveData (SPI1); // discard
}
GPIO_WriteBit (GPIOA, GPIO_Pin_4, 1);
WaitForIdle (BUSY_WAIT, "end WriteBuffer", 0);
SetTx (5000);
irqStatus = GetIrqStatus();
while (!(irqStatus & (SX126X_IRQ_TX_DONE | SX126X_IRQ_TIMEOUT))) {
vTaskDelay (1);
irqStatus = GetIrqStatus();
}
txActive = 0;
SetRx (0xFFFFFF);
if (irqStatus & SX126X_IRQ_TX_DONE) {
rv = 1;
}
}
if (frame.header & ROUTE_TYPE_DIRECT || frame.header & ROUTE_TYPE_TRANSPORT_DIRECT) {
if (rv == 0) {
txLost++;
}
}
/*
void sendFrame (const FrameStruct *frame) {
uint8_t txBuf[256];
size_t offset = 0;
txBuf[offset++] = frame->header;
if ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_DIRECT ||
(frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) {
memcpy (txBuf + offset, frame->transportCodes, 4);
offset += 4;
}
if ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_FLOOD ||
(frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) {
stats.sentFloodCount++;
}
if ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_DIRECT ||
(frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_DIRECT) {
stats.sentDirectCount++;
}
stats.packetsSentCount++;
uint8_t pathLen = frame->path.pathLen;
if (pathLen > 64) {
pathLen = 64;
}
txBuf[offset++] = pathLen;
memcpy (txBuf + offset, frame->path.path, pathLen);
hexdump ("TxDump Path", frame->path.path, frame->path.pathLen);
offset += pathLen;
uint16_t maxPayloadLen = 256 - offset;
uint16_t payloadLen = frame->payloadLen > maxPayloadLen ? maxPayloadLen : frame->payloadLen;
memcpy (txBuf + offset, frame->payload, payloadLen);
offset += payloadLen;
hexdump ("TxDump", txBuf, offset);
hexdump ("TxDumpPayload", frame->payload, frame->payloadLen);
TickType_t start, end;
start = xTaskGetTickCount();
LoRaSend (txBuf, offset, SX126x_TXMODE_SYNC);
end = xTaskGetTickCount();
tickAirtime += end - start;
}
*/
void retransmitFrame (FrameStruct *frame) {
MESH_LOGD (TAG, "Going to check ReTx");
if (frame->header & ROUTE_TYPE_FLOOD || frame->header & ROUTE_TYPE_TRANSPORT_FLOOD) {
MESH_LOGD (TAG, "Header is flood");
if (frame->header != DONT_RETRANSMIT_HEADER && frame->path.pathLen + 1 < MAX_FLOOD_TTL) {
MESH_LOGD (TAG, "Writing pubkey");
frame->path.path[frame->path.pathLen++] = persistent.pubkey[0];
MESH_LOGD (TAG, "Flooding");
vTaskDelay (10);
LoRaTransmit (frame); // TODO check if correct
MESH_LOGD (TAG, "Flooded");
}
}
if (frame->header & ROUTE_TYPE_DIRECT || frame->header & ROUTE_TYPE_TRANSPORT_DIRECT) {
}
}
// Verify MAC + Decrypt
@@ -173,8 +408,8 @@ int encrypt_then_mac (const uint8_t *aes_key, const uint8_t keySize, const uint8
// prepare padded buffer
uint8_t padded[padded_len];
memset(padded, 0, padded_len); // zero padding
memcpy(padded, plaintext, plen); // copy plaintext
memset (padded, 0, padded_len); // zero padding
memcpy (padded, plaintext, plen); // copy plaintext
// ciphertext will go right after HMAC
uint8_t *ciphertext = output + HMAC_SIZE;
@@ -202,7 +437,8 @@ int mac_then_decrypt (const uint8_t *aes_key, const uint8_t keySize, const uint8
const uint8_t *ciphertext = input + HMAC_SIZE;
size_t clen = ilen - HMAC_SIZE;
if (clen % 16 != 0) return -2; // must be multiple of block size
if (clen % 16 != 0)
return -2; // must be multiple of block size
uint8_t calc_mac[32]; // full SHA-256
hmac_sha256 (aes_key, keySize, ciphertext, clen, calc_mac);
@@ -211,4 +447,26 @@ int mac_then_decrypt (const uint8_t *aes_key, const uint8_t keySize, const uint8
return -2;
return aes_decrypt_ecb (aes_key, 16, ciphertext, clen, plaintext);
}
uint16_t getTransportCode (const FrameStruct *frame) {
uint16_t code;
/*
// compute HMAC over ciphertext
uint8_t mac[32]; // full SHA-256
cf_hmac_ctx ctx;
cf_hmac_init (&ctx, &cf_sha256, key, sizeof(key));
cf_hmac_update (&ctx, &(frame->header), sizeof (frame->header));
cf_hmac_update (&ctx, frame->payload, frame->payloadLen);
cf_hmac_finish (&ctx, mac);
// copy only HMAC_SIZE bytes of MAC
memcpy (&code, mac, HMAC_SIZE);
*/
if (code == 0) { // reserve codes 0000 and FFFF
code++;
} else if (code == 0xFFFF) {
code--;
}
return code;
}

View File

@@ -12,13 +12,20 @@
#define HMAC_SIZE 2 // meshcore size
#define MAX_FLOOD_TTL 64
int ReadFrame(FrameStruct *frame, int8_t *rssiPacket, int8_t *snrPacket, int8_t *rawSnr);
void LoRaTransmit (const FrameStruct *frame);
/*
FrameStruct decodeFrame (unsigned char *data, unsigned char dataLen);
void printFrameHeader (FrameStruct frame) ;
void sendFrame (const FrameStruct * frame);
*/
void sendFrame (FrameStruct frame);
void printFrameHeader (const FrameStruct * frame) ;
void retransmitFrame (FrameStruct frame) ;
//CALL LAST, PATH GETS MODIFIED
void retransmitFrame (FrameStruct * frame) ;
// Verify MAC + Decrypt
@@ -26,4 +33,5 @@ int encrypt_then_mac (const uint8_t *aes_key, const uint8_t keySize, const uint8
int mac_then_decrypt (const uint8_t *aes_key, const uint8_t keySize, const uint8_t *input, size_t ilen, uint8_t *plaintext);
#endif

View File

@@ -8,6 +8,7 @@
void sendDiscreteAck (uint8_t *data, const uint8_t len, uint8_t *senderPubKey) {
FrameStruct frame;
frame.path.pathLen = 0;
memset (&frame, 0, sizeof (frame));
// 1. Header
@@ -38,5 +39,5 @@ void sendDiscreteAck (uint8_t *data, const uint8_t len, uint8_t *senderPubKey) {
frame.payloadLen = 4;
sendFrame (frame);
LoRaTransmit (&frame);
}

View File

@@ -7,186 +7,202 @@
#include "advert.h"
#include "util/hexdump.h"
#include "lib/base64.h"
#include "util/log.h"
#include "FreeRTOS.h"
#include "task.h"
#define TAG "Advert"
void sendAdvert() {
AdvertisementPayload ad;
memcpy (ad.pubKey, persistent.pubkey, sizeof (ad.pubKey));
ad.dataFlags = ADVERTISEMENT_FLAG_HAS_NAME;
if (persistent.nodeType == NODE_TYPE_CHAT_NODE) {
ad.dataFlags |= ADVERTISEMENT_FLAG_IS_CHAT_NODE;
} else if (persistent.nodeType == NODE_TYPE_REPEATER) {
ad.dataFlags |= ADVERTISEMENT_FLAG_IS_REAPEATER;
} else if (persistent.nodeType == NODE_TYPE_ROOM_SERVER) {
ad.dataFlags |= ADVERTISEMENT_FLAG_IS_ROOM_SERVER;
} else if (persistent.nodeType == NODE_TYPE_SENSOR) {
ad.dataFlags |= ADVERTISEMENT_FLAG_IS_SENSOR;
}
strcpy (ad.nodeName, persistent.nodeName);
ad.timestamp = RTC_GetCounter();
uint8_t app_data[40];
size_t app_len = 0;
app_data[app_len++] = ad.dataFlags;
if (ad.dataFlags & ADVERTISEMENT_FLAG_HAS_LOCATION) {
memcpy (app_data + app_len, &ad.latitude, sizeof (ad.latitude));
app_len += sizeof (ad.latitude);
memcpy (app_data + app_len, &ad.longitude, sizeof (ad.longitude));
app_len += sizeof (ad.longitude);
}
if (ad.dataFlags & ADVERTISEMENT_FLAG_RFU1) {
memcpy (app_data + app_len, &ad.rfu1, sizeof (ad.rfu1));
app_len += sizeof (ad.rfu1);
}
if (ad.dataFlags & ADVERTISEMENT_FLAG_RFU2) {
memcpy (app_data + app_len, &ad.rfu2, sizeof (ad.rfu2));
app_len += sizeof (ad.rfu2);
}
if (ad.dataFlags & ADVERTISEMENT_FLAG_HAS_NAME) {
size_t nodenameLen = strlen (ad.nodeName);
memcpy (app_data + app_len, ad.nodeName, nodenameLen);
app_len += nodenameLen;
}
// 2. Reserve frame and build payload header
void sendAdvert (uint8_t shouldFlood) {
printf ("High-water mark before preparing advert: %u\n", uxTaskGetStackHighWaterMark (NULL));
FrameStruct frame;
frame.header = ROUTE_TYPE_FLOOD | PAYLOAD_TYPE_ADVERT | PAYLOAD_VERSION_0;
size_t offset = 0;
memcpy (frame.payload + offset, ad.pubKey, sizeof (ad.pubKey));
offset += sizeof (ad.pubKey);
memcpy (frame.payload + offset, &ad.timestamp, sizeof (ad.timestamp));
offset += sizeof (ad.timestamp);
frame.header = (shouldFlood ? ROUTE_TYPE_FLOOD : ROUTE_TYPE_DIRECT) | PAYLOAD_TYPE_ADVERT | PAYLOAD_VERSION_0;
// reserve signature space
/* ---- public key ---- */
memcpy (frame.payload + offset, persistent.pubkey, 32);
offset += 32;
/* ---- timestamp ---- */
uint32_t timestamp = RTC_GetCounter();
memcpy (frame.payload + offset, &timestamp, sizeof (timestamp));
offset += sizeof (timestamp);
/* ---- reserve signature ---- */
uint8_t *signature_pos = frame.payload + offset;
offset += 64;
// append app_data after signature
memcpy (frame.payload + offset, app_data, app_len);
offset += app_len;
/* ---- build app data directly into payload ---- */
size_t app_start = offset;
// 3. Sign pubKey + timestamp + app_data
uint8_t message[76];
size_t msg_len = 0;
memcpy (message + msg_len, ad.pubKey, sizeof (ad.pubKey));
msg_len += sizeof (ad.pubKey);
memcpy (message + msg_len, &ad.timestamp, sizeof (ad.timestamp));
msg_len += sizeof (ad.timestamp);
memcpy (message + msg_len, app_data, app_len);
msg_len += app_len;
uint8_t dataFlags = ADVERTISEMENT_FLAG_HAS_NAME;
if (persistent.nodeType == NODE_TYPE_CHAT_NODE)
dataFlags |= ADVERTISEMENT_FLAG_IS_CHAT_NODE;
else if (persistent.nodeType == NODE_TYPE_REPEATER)
dataFlags |= ADVERTISEMENT_FLAG_IS_REAPEATER;
else if (persistent.nodeType == NODE_TYPE_ROOM_SERVER)
dataFlags |= ADVERTISEMENT_FLAG_IS_ROOM_SERVER;
else if (persistent.nodeType == NODE_TYPE_SENSOR)
dataFlags |= ADVERTISEMENT_FLAG_IS_SENSOR;
ed25519_sign (signature_pos, message, msg_len, persistent.pubkey, persistent.privkey);
frame.payload[offset++] = dataFlags;
hexdump ("Complete advert", frame.payload, offset);
if (dataFlags & ADVERTISEMENT_FLAG_HAS_LOCATION) {
memcpy (frame.payload + offset, &persistent.latitude, sizeof (persistent.latitude));
offset += sizeof (persistent.latitude);
hexdump ("Public key", ad.pubKey, 32);
memcpy (frame.payload + offset, &persistent.longitude, sizeof (persistent.longitude));
offset += sizeof (persistent.longitude);
}
/*
if (dataFlags & ADVERTISEMENT_FLAG_RFU1) {
memcpy(frame.payload + offset, &persistent.rfu1, sizeof(persistent.rfu1));
offset += sizeof(persistent.rfu1);
}
if (dataFlags & ADVERTISEMENT_FLAG_RFU2) {
memcpy(frame.payload + offset, &persistent.rfu2, sizeof(persistent.rfu2));
offset += sizeof(persistent.rfu2);
}
*/
if (dataFlags & ADVERTISEMENT_FLAG_HAS_NAME) {
size_t nameLen = strlen (persistent.nodeName);
memcpy (frame.payload + offset, persistent.nodeName, nameLen);
offset += nameLen;
}
size_t app_len = offset - app_start;
frame.payloadLen = offset;
/* ---- sign directly over payload ---- */
printf ("High-water mark before signing: %u\n", uxTaskGetStackHighWaterMark (NULL));
ed25519_sign_ad (&frame);
printf ("High-water mark after signing: %u\n", uxTaskGetStackHighWaterMark (NULL));
/* ---- debug ---- */
hexdump ("Public key", frame.payload, 32);
hexdump ("Signature", signature_pos, 64);
hexdump ("Appdata", frame.payload + app_start, app_len);
printf ("Timestamp is %lu\n", timestamp);
printf ("NodeName %s\n", persistent.nodeName);
printf ("Timestamp is %d\n", ad.timestamp);
printf ("NodeName %s\n", ad.nodeName);
hexdump ("Appdata", app_data, app_len);
// 5. Set payload length and send
/* ---- send ---- */
frame.payloadLen = offset;
frame.path.pathLen = 0;
sendFrame (frame);
LoRaTransmit (&frame);
}
AdvertisementPayload decodeAdvertisement (FrameStruct frame) {
void decodeAdvertisement (const FrameStruct *frame) {
AdvertisementPayload advert;
memset (&advert, 0, sizeof (advert));
if ((frame.header & PAYLOAD_TYPE_MASK) != PAYLOAD_TYPE_ADVERT) {
return advert;
advert.valid = 0;
if (frame->payloadLen < 101) {
MESH_LOGW (TAG, "Advertisement frame too short (%d < 101)", frame->payloadLen);
return;
}
if (ed25519_verify_ad (frame) != 1) {
MESH_LOGW (TAG, "Incorrect signature");
return;
}
advert.valid = 1;
unsigned char index = 0;
memcpy (advert.pubKey, frame.payload + index, 32);
memcpy (advert.pubKey, frame->payload + index, 32);
index += 32;
memcpy (&advert.timestamp, frame.payload + index, 4);
memcpy (&advert.timestamp, frame->payload + index, 4);
index += 4;
memcpy (advert.signature, frame.payload + index, 64);
memcpy (advert.signature, frame->payload + index, 64);
index += 64;
advert.dataFlags = frame.payload[index++];
advert.dataFlags = frame->payload[index++];
uint8_t expectedLen = 101;
if (advert.dataFlags & ADVERTISEMENT_FLAG_HAS_LOCATION) {
memcpy (&advert.latitude, frame.payload + index, 4);
expectedLen += 8;
}
if (advert.dataFlags & ADVERTISEMENT_FLAG_RFU1) {
expectedLen += 2;
}
if (advert.dataFlags & ADVERTISEMENT_FLAG_RFU2) {
expectedLen += 2;
}
if (frame->payloadLen < expectedLen) {
MESH_LOGW (TAG, "Advertisement frame with data too short (%d < %d)", frame->payloadLen, expectedLen);
return;
}
if (advert.dataFlags & ADVERTISEMENT_FLAG_HAS_LOCATION) {
memcpy (&advert.latitude, frame->payload + index, 4);
index += 4;
memcpy (&advert.longitude, frame.payload + index, 4);
memcpy (&advert.longitude, frame->payload + index, 4);
index += 4;
}
if (advert.dataFlags & ADVERTISEMENT_FLAG_RFU1) {
memcpy (&advert.rfu1, frame.payload + index, 2);
memcpy (&advert.rfu1, frame->payload + index, 2);
index += 2;
}
if (advert.dataFlags & ADVERTISEMENT_FLAG_RFU2) {
memcpy (&advert.rfu2, frame.payload + index, 2);
memcpy (&advert.rfu2, frame->payload + index, 2);
index += 2;
}
unsigned char nameLen = frame.payloadLen - index;
if (nameLen > 32) {
nameLen = 32;
unsigned char nameLen = frame->payloadLen - index;
if (nameLen > 31) {
nameLen = 31; // leave space for null
}
memcpy (advert.nodeName, frame.payload + index, nameLen);
advert.nodeName[frame.payloadLen - index] = 0;
memcpy (advert.nodeName, frame->payload + index, nameLen);
advert.nodeName[nameLen] = 0;
NodeEntry *node = getNode (advert.pubKey[0]);
printAdvertisement (&advert);
saveAdvert (&advert);
}
void saveAdvert (const AdvertisementPayload *advert) {
NodeEntry *node = getNode (advert->pubKey[0]);
if (node == NULL) {
node = getNextNode();
memset (node, 0, sizeof (NodeEntry));
}
memcpy (node->name, advert.nodeName, sizeof (node->name));
memcpy (node->pubKey, advert.pubKey, sizeof (node->pubKey));
ed25519_key_exchange ((unsigned char *)node->secret, advert.pubKey, persistent.privkey);
node->gps_latitude = advert.latitude;
node->gps_longitude = advert.longitude;
memcpy (node->name, advert->nodeName, sizeof (node->name));
memcpy (node->pubKey, advert->pubKey, sizeof (node->pubKey));
ed25519_key_exchange ((unsigned char *)node->secret, advert->pubKey, persistent.privkey);
node->gps_latitude = advert->latitude;
node->gps_longitude = advert->longitude;
// ADD PATH
node->type = advert.dataFlags & 0x0F;
node->type = advert->dataFlags & 0x0F;
node->last_seen_lt = RTC_GetCounter();
node->last_seen_rt = advert.timestamp;
return advert;
node->last_seen_rt = advert->timestamp;
}
void printAdvertisement (AdvertisementPayload advert) {
unsigned char keyBuf[50];
unsigned char sigBuf[90];
memset (keyBuf, 0, sizeof (keyBuf));
memset (sigBuf, 0, sizeof (sigBuf));
base64_encode (advert.pubKey, 32, keyBuf);
base64_encode (advert.signature, 64, sigBuf);
void printAdvertisement (const AdvertisementPayload *advert) {
printf (
"%s on %ld with type %s on %s location %ld %ld\n",
advert->dataFlags & ADVERTISEMENT_FLAG_HAS_NAME
? advert->nodeName
: "nameless node",
advert->timestamp,
(advert->dataFlags & 0x07) == 0x04 ? "sensor"
: (advert->dataFlags & 0x07) == 0x03 ? "room server"
: (advert->dataFlags & 0x07) == 0x02 ? "repeater"
: "chat node",
advert->dataFlags & 0x80 ? "known" : "unknown",
advert->latitude,
advert->longitude);
printf ("%s on %ld with type %s on %s location %ld %ld, public key %s and "
"signature %s\n",
advert.dataFlags & ADVERTISEMENT_FLAG_HAS_NAME ? advert.nodeName
: "nameless node",
advert.timestamp,
(advert.dataFlags & 0x07) == 0x04
? "sensor"
: ((advert.dataFlags & 0x07) == 0x03
? "room server"
: ((advert.dataFlags & 0x07) == 0x02 ? "repeater"
: "chat node")),
advert.dataFlags & 0x80 ? "known" : "unknown", advert.latitude,
advert.longitude, keyBuf, sigBuf);
}
hexdump ("Public key", advert->pubKey, 32);
hexdump ("Signature", advert->signature, 64);
}

View File

@@ -3,10 +3,12 @@
#include "meshcore/packetstructs.h"
void sendAdvert();
void sendAdvert (uint8_t shouldFlood);
AdvertisementPayload decodeAdvertisement (FrameStruct frame);
void decodeAdvertisement (const FrameStruct * frame);
void printAdvertisement (AdvertisementPayload advert);
void printAdvertisement (const AdvertisementPayload * advert);
void saveAdvert(const AdvertisementPayload * advert);
#endif

View File

@@ -10,9 +10,10 @@
#define TAG "Anonymous"
void sendAnonymousRequest (NodeEntry *targetNode, const uint8_t *password, uint32_t sync) {
void sendAnonymousRequest (const NodeEntry *targetNode, const uint8_t *password, uint32_t sync) {
uint8_t passwordLen = strlen ((const char *)password);
FrameStruct frame;
frame.path.pathLen = 0;
uint8_t offset = 0;
// 1. Frame header
@@ -61,36 +62,103 @@ void sendAnonymousRequest (NodeEntry *targetNode, const uint8_t *password, uint3
// 5. Finalize and send
frame.payloadLen = offset;
memcpy(&(frame.path), &(targetNode->path), sizeof(frame.path));
memcpy (&(frame.path), &(targetNode->path), sizeof (frame.path));
hexdump ("Anon payload", frame.payload, frame.payloadLen);
sendFrame (frame);
LoRaTransmit (&frame);
}
AnonymousRequestPayload decodeAnonReq (FrameStruct frame) {
void printAnonRequest (const AnonymousRequestPayload *req, int isRoomServer) {
if (!req)
return;
printf ("AnonymousRequestPayload at %p\n", (void *)req);
printf (" destination hash: 0x%02X\n", req->destinationHash);
printf (" sender pubKey: ");
for (int i = 0; i < sizeof (req->pubKey); i++) {
printf ("%02X", req->pubKey[i]);
}
printf ("\n");
printf (" cipher MAC: 0x%04X\n", req->cipherMAC);
printf (" decrypted payload (%u bytes):\n", req->payloadLen);
uint8_t index = 0;
// timestamp (first 4 bytes)
if (req->payloadLen >= 4) {
uint32_t timestamp = req->payload[index++];
timestamp |= req->payload[index++] << 8;
timestamp |= req->payload[index++] << 16;
timestamp |= req->payload[index++] << 24;
printf (" timestamp: %u\n", timestamp);
}
// room server sync timestamp
if (isRoomServer && req->payloadLen >= index + 4) {
uint32_t syncTimestamp = req->payload[index++];
syncTimestamp |= req->payload[index++] << 8;
syncTimestamp |= req->payload[index++] << 16;
syncTimestamp |= req->payload[index++] << 24;
printf (" sync timestamp: %u\n", syncTimestamp);
}
// remaining bytes = password
if (index < req->payloadLen) {
uint8_t passwordLen = req->payloadLen - index;
if (passwordLen > 16)
passwordLen = 16;
passwordLen = strnlen (&(req->payload[index]), passwordLen);
printf (" password: ");
for (uint8_t i = 0; i < passwordLen; i++) {
printf ("%c", req->payload[i + index]);
}
printf ("\n");
}
}
size_t strnlen (const char *s, size_t maxLen) {
size_t len = 0;
while (len < maxLen && s[len] != '\0') {
len++;
}
return len;
}
void decodeAnonReq (const FrameStruct *frame) {
uint8_t index = 0;
AnonymousRequestPayload anonReq;
anonReq.destinationHash = frame.payload[index++];
memcpy (anonReq.pubKey, &(frame.payload[index]), sizeof (anonReq.pubKey));
anonReq.destinationHash = frame->payload[index++];
memcpy (anonReq.pubKey, &(frame->payload[index]), sizeof (anonReq.pubKey));
index += sizeof (anonReq.pubKey);
anonReq.cipherMAC = frame.payload[index];
anonReq.cipherMAC |= frame.payload[index + 1] << 8;
anonReq.cipherMAC = frame->payload[index];
anonReq.cipherMAC |= frame->payload[index + 1] << 8;
NodeEntry *foundNode = getNode (anonReq.pubKey[0]);
if (foundNode == NULL) {
foundNode = getNextNode();
strcpy (foundNode->name, "Anonymous node");
memcpy (foundNode->pubKey, anonReq.pubKey, sizeof (foundNode->pubKey));
ed25519_key_exchange ((unsigned char *)foundNode->secret, anonReq.pubKey, persistent.privkey);
foundNode->gps_latitude = 0;
foundNode->gps_longitude = 0;
// ADD PATH
foundNode->type = 0;
foundNode->last_seen_lt = RTC_GetCounter();
MESH_LOGI (TAG, "New anonymous node created: %s", foundNode->name);
} else {
MESH_LOGD (TAG, "Existing node found for pubKey[0]=0x%02X", anonReq.pubKey[0]);
}
mac_then_decrypt (foundNode->secret, 32, &(frame.payload[index]), frame.payloadLen - index, anonReq.payload);
anonReq.payloadLen = frame.payloadLen - index - 2;
mac_then_decrypt (foundNode->secret, 32, &(frame->payload[index]), frame->payloadLen - index, anonReq.payload);
anonReq.payloadLen = frame->payloadLen - index - 2;
hexdump ("AnonReq payload", anonReq.payload, anonReq.payloadLen);
uint8_t index2 = 0;
foundNode->last_seen_rt = anonReq.payload[index2++];
foundNode->last_seen_rt |= anonReq.payload[index2++] << 8;
@@ -104,30 +172,39 @@ AnonymousRequestPayload decodeAnonReq (FrameStruct frame) {
foundNode->sync_timestamp |= anonReq.payload[index2++] << 24;
}
printAnonRequest (&anonReq, persistent.nodeType == NODE_TYPE_ROOM_SERVER);
uint8_t passwordLen = anonReq.payloadLen - index2;
if (passwordLen > 16) {
if (passwordLen > 16)
passwordLen = 16;
}
passwordLen = strnlen (&(anonReq.payload[index2]), passwordLen);
MESH_LOGI (TAG, "Password len is %d.", passwordLen);
uint8_t passwordBuf[16];
memcpy (passwordBuf, &(anonReq.payload[index2]), passwordLen);
if (memcmp (passwordBuf, persistent.password, sizeof (persistent.password)) == 0) {
if (memcmp (passwordBuf, persistent.password, passwordLen) == 0) {
foundNode->authenticated = 1;
Response resp;
resp.tag = RTC_GetCounter();
uint8_t index3 = 0;
uint32_t randOut = rand();
resp.data[index3++] = RESP_SERVER_LOGIN_OK;
resp.data[index3++] = 0;//legacy
resp.data[index3++] = 1;//isadmin
resp.data[index3++] = PERM_ACL_ADMIN;//permissions
resp.data[index3++] = randOut & 0xFF;//rng
resp.data[index3++] = (randOut >> 8) & 0xFF;
resp.data[index3++] = (randOut >> 16) & 0xFF;
resp.data[index3++] = (randOut >> 24) & 0xFF;
resp.data[index3++] = FIRMWARE_VER_LEVEL;
resp.dataLen = index3;
sendEncryptedResponse(foundNode, &resp);
MESH_LOGI (TAG, "Password correct, node %s authenticated.", foundNode->name);
MESH_LOGI (TAG, "Login response sent to node %s.", foundNode->name);
} else {
MESH_LOGW (TAG, "Password incorrect for node %s.", foundNode->name);
}
return anonReq;
}
Response resp;
resp.tag = RTC_GetCounter();
uint8_t index3 = 0;
uint32_t randOut = rand();
resp.data[index3++] = RESP_SERVER_LOGIN_OK;
resp.data[index3++] = 0; // legacy
resp.data[index3++] = foundNode->authenticated; // isadmin
resp.data[index3++] = foundNode->authenticated ? PERM_ACL_ADMIN : PERM_ACL_GUEST; // permissions
resp.data[index3++] = randOut & 0xFF;
resp.data[index3++] = (randOut >> 8) & 0xFF;
resp.data[index3++] = (randOut >> 16) & 0xFF;
resp.data[index3++] = (randOut >> 24) & 0xFF;
resp.data[index3++] = FIRMWARE_VER_LEVEL;
resp.dataLen = index3;
sendEncryptedResponse (foundNode, &resp);
}

View File

@@ -4,8 +4,8 @@
#include "meshcore/packetstructs.h"
#include "lib/config.h"
AnonymousRequestPayload decodeAnonReq (FrameStruct frame);
void decodeAnonReq (const FrameStruct * frame);
void sendAnonymousRequest (NodeEntry *targetNode, const uint8_t *password, uint32_t sync);
void sendAnonymousRequest (const NodeEntry *targetNode, const uint8_t *password, uint32_t sync);
#endif

View File

@@ -1,13 +1,19 @@
#include "lib/config.h"
#include "meshcore/meshframing.h"
#include "meshcore/packetstructs.h"
#include "control.h"
#include "meshcore/stats.h"
#include "string.h"
#include "util/hexdump.h"
#include "util/log.h"
#include <stdio.h>
#define TAG "Control"
void sendDiscoverRequest(const DiscoverRequestPayload *discReq) {
void sendDiscoverRequest (const DiscoverRequestPayload *discReq) {
FrameStruct frame;
frame.path.pathLen = 0;
uint8_t offset = 0;
// Build payload
@@ -21,7 +27,7 @@ void sendDiscoverRequest(const DiscoverRequestPayload *discReq) {
frame.payload[offset++] = (discReq->tag >> 24) & 0xFF;
// optional `since`
if (discReq->since != 0) { // or another condition if you want to always include
if (discReq->since != 0) { // or another condition if you want to always include
frame.payload[offset++] = (discReq->since >> 0) & 0xFF;
frame.payload[offset++] = (discReq->since >> 8) & 0xFF;
frame.payload[offset++] = (discReq->since >> 16) & 0xFF;
@@ -30,47 +36,110 @@ void sendDiscoverRequest(const DiscoverRequestPayload *discReq) {
frame.payloadLen = offset;
sendFrame(frame);
LoRaTransmit (&frame);
}
void sendDiscoverResponse (const DiscoverResponsePayload *discResp) {
FrameStruct frame;
frame.path.pathLen = 0;
uint8_t offset = 0;
void decodeControlFrame(FrameStruct frame) {
/* Control type + node type (lower nibble) */
frame.payload[offset++] =
(discResp->nodeType & 0x0F) |
CONTROL_DATA_FLAG_DISCOVER_RESP;
/* SNR */
frame.payload[offset++] = (uint8_t)discResp->snr;
/* Tag (LE) */
frame.payload[offset++] = (discResp->tag >> 0) & 0xFF;
frame.payload[offset++] = (discResp->tag >> 8) & 0xFF;
frame.payload[offset++] = (discResp->tag >> 16) & 0xFF;
frame.payload[offset++] = (discResp->tag >> 24) & 0xFF;
/* Pubkey */
if (discResp->pubkeyLen > 0) {
memcpy (&frame.payload[offset],
discResp->pubkey,
discResp->pubkeyLen);
offset += discResp->pubkeyLen;
}
frame.payloadLen = offset;
LoRaTransmit (&frame);
}
void printDiscoverRequest (const DiscoverRequestPayload *p) {
printf ("=== Discover Request ===\n");
printf ("prefixOnly : %u\n", p->prefixOnly);
printf ("typeFilter : 0x%02X\n", p->typeFilter);
printf ("tag : 0x%08lX\n", (unsigned long)p->tag);
printf ("since : 0x%08lX\n", (unsigned long)p->since);
}
void printDiscoverResponse (const DiscoverResponsePayload *p) {
printf ("=== Discover Response ===\n");
printf ("nodeType : %u\n", p->nodeType);
printf ("snr : %u\n", p->snr);
printf ("tag : 0x%08lX\n", (unsigned long)p->tag);
hexdump ("pubkey : ", p->pubkey, p->pubkeyLen);
printf ("\n");
}
void decodeControlFrame (const FrameStruct * frame) {
uint8_t index = 0;
uint8_t type = frame.payload[index] & 0xF0;
if (type == CONTROL_DATA_FLAG_TYPE_NODE_DISCOVER_REQ) {
DiscoverRequestPayload discReq;
discReq.prefixOnly = frame.payload[index++] & 0x01;
uint8_t type = frame->payload[index] & 0xF0;
if (type == CONTROL_DATA_FLAG_TYPE_NODE_DISCOVER_REQ) {
DiscoverRequestPayload discReq;
discReq.prefixOnly = frame->payload[index++] & 0x01;
discReq.typeFilter = frame.payload[index++];
discReq.typeFilter = frame->payload[index++];
discReq.tag = frame.payload[index++];
discReq.tag |= frame.payload[index++] << 8;
discReq.tag |= frame.payload[index++] << 16;
discReq.tag |= frame.payload[index++] << 24;
if (index < frame.payloadLen) {
discReq.since = frame.payload[index++];
discReq.since |= frame.payload[index++] << 8;
discReq.since |= frame.payload[index++] << 16;
discReq.since |= frame.payload[index++] << 24;
}
} else if (type == CONTROL_DATA_FLAG_DISCOVER_RESP) {
DiscoverResponsePayload discResp;
discResp.nodeType = frame.payload[index++] & 0x0F;
discResp.snr = frame.payload[index++];
discResp.tag = frame.payload[index++];
discResp.tag |= frame.payload[index++] << 8;
discResp.tag |= frame.payload[index++] << 16;
discResp.tag |= frame.payload[index++] << 24;
uint8_t remainingLen = frame.payloadLen - index;
uint8_t pubKeyLen = (remainingLen > 8) ? sizeof(discResp.pubkey) : 8;
memcpy(discResp.pubkey, &(frame.payload[index]), pubKeyLen);
index += pubKeyLen;
discReq.tag = frame->payload[index++];
discReq.tag |= frame->payload[index++] << 8;
discReq.tag |= frame->payload[index++] << 16;
discReq.tag |= frame->payload[index++] << 24;
if (index < frame->payloadLen) {
discReq.since = frame->payload[index++];
discReq.since |= frame->payload[index++] << 8;
discReq.since |= frame->payload[index++] << 16;
discReq.since |= frame->payload[index++] << 24;
}
printDiscoverRequest (&discReq);
if ((discReq.typeFilter >> 2) & persistent.nodeType) {
DiscoverResponsePayload discResp;
discResp.tag = discReq.tag;
discResp.nodeType = persistent.nodeType;
discResp.pubkeyLen = sizeof (persistent.pubkey);
memcpy (discResp.pubkey, persistent.pubkey, discResp.pubkeyLen);
discResp.snr = stats.lastSNR; // hopefully the correct one
sendDiscoverResponse (&discResp);
MESH_LOGD(TAG, "Replying to a discover request with tag %d", discResp.tag);
}
} else if (type == CONTROL_DATA_FLAG_DISCOVER_RESP) {
DiscoverResponsePayload discResp;
discResp.nodeType = frame->payload[index++] & 0x0F;
discResp.snr = frame->payload[index++];
discResp.tag = frame->payload[index++];
discResp.tag |= frame->payload[index++] << 8;
discResp.tag |= frame->payload[index++] << 16;
discResp.tag |= frame->payload[index++] << 24;
uint8_t remainingLen = frame->payloadLen - index;
uint8_t pubKeyLen = (remainingLen > 8) ? sizeof (discResp.pubkey) : 8;
discResp.pubkeyLen = pubKeyLen;
memcpy (discResp.pubkey, &(frame->payload[index]), discResp.pubkeyLen);
index += pubKeyLen;
printDiscoverResponse (&discResp);
}
}

View File

@@ -3,6 +3,14 @@
#include "meshcore/packetstructs.h"
void decodeControlFrame(FrameStruct frame);
void sendDiscoverRequest(const DiscoverRequestPayload *discReq);
void sendDiscoverResponse(const DiscoverResponsePayload *discResp);
void printDiscoverRequest(const DiscoverRequestPayload *p);
void printDiscoverResponse(const DiscoverResponsePayload *p);
void decodeControlFrame(const FrameStruct * frame);
#endif

View File

@@ -1,4 +1,5 @@
#include "lib/config.h"
#include "lib/telemetry/telemetry.h"
#include "meshcore/meshframing.h"
#include "meshcore/packets/ack.h"
#include "meshcore/packetstructs.h"
@@ -10,6 +11,9 @@
#include "encrypted.h"
#include "FreeRTOS.h"
#include "task.h"
#include "lib/adc/temperature.h"
#define TICKS_TO_MS(xTicks) (((uint32_t)(xTicks)*1000U) / (uint32_t)configTICK_RATE_HZ)
#define TAG "EncryptedMessage"
@@ -20,7 +24,7 @@ void sendEncryptedFrame (NodeEntry *targetNode, uint8_t payloadType, const uint8
// 1. Header
frame.header =
ROUTE_TYPE_FLOOD | //currently flood
(targetNode->path.pathLen > 0 ? ROUTE_TYPE_DIRECT : ROUTE_TYPE_FLOOD) | // currently flood
payloadType |
PAYLOAD_VERSION_0;
@@ -45,22 +49,22 @@ void sendEncryptedFrame (NodeEntry *targetNode, uint8_t payloadType, const uint8
memcpy (&frame.path, &targetNode->path, sizeof (frame.path));
hexdump ("Encrypted frame", frame.payload, frame.payloadLen);
sendFrame (frame);
LoRaTransmit (&frame);
}
void sendEncryptedTextMessage (NodeEntry *targetNode, const PlainTextMessagePayload *msg) {
if (targetNode == NULL) {
ESP_LOGW(TAG, "Node is null");
MESH_LOGW (TAG, "Node is null");
return;
}
if (targetNode->last_seen_lt == 0) {
ESP_LOGW(TAG, "Node is not populated");
MESH_LOGW (TAG, "Node is not populated");
return;
}
uint8_t buf[256];
uint8_t index = 0;
uint8_t msgLen = strlen(msg->message) + 1;
uint8_t msgLen = strlen (msg->message) + 1;
buf[index++] = msg->timestamp;
buf[index++] = msg->timestamp >> 8;
buf[index++] = msg->timestamp >> 16;
@@ -80,12 +84,13 @@ void sendEncryptedResponse (NodeEntry *targetNode, const Response *resp) {
uint8_t buf[256];
uint8_t index = 0;
buf[index++] = resp->tag;
buf[index++] = resp->tag >> 8;
buf[index++] = resp->tag >> 16;
buf[index++] = resp->tag >> 24;
buf[index++] = (resp->tag) & 0xFF;
buf[index++] = (resp->tag >> 8) & 0xFF;
buf[index++] = (resp->tag >> 16) & 0xFF;
buf[index++] = (resp->tag >> 24) & 0xFF;
memcpy (&buf[index], resp->data, resp->dataLen);
memcpy (&(buf[index]), resp->data, resp->dataLen);
index += resp->dataLen;
sendEncryptedFrame (
targetNode,
@@ -104,7 +109,8 @@ void sendEncryptedRequest (NodeEntry *targetNode, const Request *req) {
buf[index++] = req->timestamp >> 24;
buf[index++] = req->requestType;
memcpy (&buf[index], req->data, req->dataLen);
memcpy (&(buf[index]), req->data, req->dataLen);
index += req->dataLen;
sendEncryptedFrame (
targetNode,
@@ -123,7 +129,7 @@ void sendEncryptedPathPayload (NodeEntry *targetNode, const ReturnedPathPayload
buf[index++] = path->extra.type;
memcpy (&buf[index], path->extra.data,
sizeof (path->extra.data));
path->extra.dataLen);
sendEncryptedFrame (
targetNode,
@@ -137,20 +143,14 @@ void printRequest (const Request *req) {
printf (" Timestamp: %u\n", req->timestamp);
printf (" Type: 0x%02X\n", req->requestType);
printf (" Data: ");
for (int i = 0; i < req->dataLen; i++) {
printf ("%02X ", req->data[i]);
}
printf ("\n");
hexdump (" Data", req->data, req->dataLen);
}
void printResponse (const Response *resp) {
printf ("Response:\n");
printf (" Tag: %u\n", resp->tag);
printf (" Data: ");
for (int i = 0; i < resp->dataLen; i++) {
printf ("%02X ", resp->data[i]);
}
printf ("\n");
hexdump (" Data", resp->data, resp->dataLen);
}
void printPlainTextMessage (const PlainTextMessagePayload *msg) {
@@ -165,16 +165,10 @@ void printReturnedPathPayload (const ReturnedPathPayload *path) {
printf ("ReturnedPathPayload:\n");
printf (" Path Length: %u\n", path->path.pathLen);
printf (" Path: ");
for (int i = 0; i < path->path.pathLen; i++) {
printf ("%02X ", path->path.path[i]);
}
printf ("\n");
hexdump (" Path:", path->path.path, path->path.pathLen);
printf (" Extra Type: %u\n", path->extra.type);
printf (" Extra Data: ");
for (int i = 0; i < sizeof (path->extra.data); i++) {
printf ("%02X ", path->extra.data[i]);
}
printf ("\n");
hexdump (" Extra data:", path->extra.data, path->extra.dataLen);
}
void printEncryptedPayload (const EncryptedPayloadStruct *enc) {
@@ -191,22 +185,22 @@ void printEncryptedPayload (const EncryptedPayloadStruct *enc) {
printf ("\n");
}
EncryptedPayloadStruct decodeEncryptedPayload (FrameStruct frame) {
void decodeEncryptedPayload (const FrameStruct *frame) {
EncryptedPayloadStruct enc;
memset (&enc, 0, sizeof (enc));
enc.type = frame.header & PAYLOAD_TYPE_MASK;
enc.type = frame->header & PAYLOAD_TYPE_MASK;
unsigned char index = 0;
enc.destinationHash = frame.payload[index++];
enc.sourceHash = frame.payload[index++];
enc.cipherMAC = frame.payload[index];
enc.cipherMAC |= frame.payload[index + 1] << 8;
enc.destinationHash = frame->payload[index++];
enc.sourceHash = frame->payload[index++];
enc.cipherMAC = frame->payload[index];
enc.cipherMAC |= frame->payload[index + 1] << 8;
if (enc.destinationHash != persistent.pubkey[0]) {
return enc;
return;
}
ESP_LOGI(TAG, "Finding remote node, sourceHash is %d", enc.sourceHash);
MESH_LOGI (TAG, "Finding remote node, sourceHash is %d", enc.sourceHash);
NodeEntry *remNode = getNode (enc.sourceHash);
@@ -214,96 +208,152 @@ EncryptedPayloadStruct decodeEncryptedPayload (FrameStruct frame) {
if (remNode == NULL) {
ESP_LOGW(TAG, "Node not in DB");
return enc;
MESH_LOGW (TAG, "Node not in DB");
return;
}
ESP_LOGI(TAG, "Found node with index %d", remNode - persistent.contacts);
MESH_LOGI (TAG, "Found node with index %d", remNode - persistent.contacts);
if (mac_then_decrypt (remNode->secret, 32, &(frame.payload[index]), frame.payloadLen - index, enc.payload) != 0) {
ESP_LOGW (TAG, "HMAC failed on encrypted message %s", remNode->name);
if (mac_then_decrypt (remNode->secret, 32, &(frame->payload[index]), frame->payloadLen - index, enc.payload) != 0) {
MESH_LOGW (TAG, "HMAC failed on encrypted message %s", remNode->name);
} else {
enc.payloadLen = frame.payloadLen - HMAC_SIZE;
ESP_LOGI(TAG, "HMAC success from %s, %u bytes long", remNode->name, enc.payloadLen);
sendDiscreteAck(enc.payload, 5 + strlen((char *)&enc.payload[5]), remNode->pubKey);
enc.payloadLen = frame->payloadLen - HMAC_SIZE;
MESH_LOGI (TAG, "HMAC success from %s, %u bytes long", remNode->name, enc.payloadLen);
sendDiscreteAck (enc.payload, 5 + strlen ((char *)&enc.payload[5]), remNode->pubKey);
}
return enc;
printf (" Typexdd: 0x%02X\n", enc.type);
if (enc.payloadLen > 0) {
parseEncryptedPayload (&enc);
}
}
void parseEncryptedPayload (EncryptedPayloadStruct enc) {
void parseEncryptedPayload (const EncryptedPayloadStruct *enc) {
// printEncryptedPayload(&enc);
printf ("EncryptedPayload:\n");
printf (" Type: 0x%02X\n", enc.type);
printf (" DestinationHash: 0x%02X\n", enc.destinationHash);
printf (" SourceHash: 0x%02X\n", enc.sourceHash);
printf (" CipherMAC: 0x%04X\n", enc.cipherMAC);
printf (" PayloadLen: %u\n", enc.payloadLen);
printf (" Payload: ");
hexdump("Full payload buffer", enc.payload, sizeof(enc.payload));
printf (" Type: 0x%02X\n", enc->type);
printf (" DestinationHash: 0x%02X\n", enc->destinationHash);
printf (" SourceHash: 0x%02X\n", enc->sourceHash);
printf (" CipherMAC: 0x%04X\n", enc->cipherMAC);
printf (" PayloadLen: %u\n", enc->payloadLen);
hexdump (" Payload: ", enc->payload, enc->payloadLen);
printf ("\n");
uint8_t index = 0;
if (enc.type == PAYLOAD_TYPE_PATH) {
if (enc->type == PAYLOAD_TYPE_PATH) {
ReturnedPathPayload retPath;
retPath.path.pathLen = enc.payload[index++];
retPath.path.pathLen = enc->payload[index++];
if (retPath.path.pathLen > 64) {
ESP_LOGW (TAG, "Path too long\n");
MESH_LOGW (TAG, "Path too long\n");
return;
}
memcpy (retPath.path.path, &(enc.payload[index]), retPath.path.pathLen);
memcpy (retPath.path.path, &(enc->payload[index]), retPath.path.pathLen);
index += retPath.path.pathLen;
retPath.extra.type = enc.payload[index++];
memcpy (retPath.extra.data, &(enc.payload[index]), enc.payloadLen - index);
retPath.extra.type = enc->payload[index++];
retPath.extra.dataLen = enc->payloadLen - index;
memcpy (retPath.extra.data, &(enc->payload[index]), retPath.extra.dataLen);
} else if (enc.type == PAYLOAD_TYPE_REQ) {
} else if (enc->type == PAYLOAD_TYPE_REQ) {
Request req;
req.timestamp = enc.payload[index++];
req.timestamp |= enc.payload[index++] << 8;
req.timestamp |= enc.payload[index++] << 16;
req.timestamp |= enc.payload[index++] << 24;
req.requestType = enc.payload[index++];
req.dataLen = enc.payloadLen - index;
memcpy (req.data, &(enc.payload[index]), req.dataLen);
req.timestamp = enc->payload[index++];
req.timestamp |= enc->payload[index++] << 8;
req.timestamp |= enc->payload[index++] << 16;
req.timestamp |= enc->payload[index++] << 24;
req.requestType = enc->payload[index++];
req.dataLen = enc->payloadLen - index;
memcpy (req.data, &(enc->payload[index]), req.dataLen);
printRequest (&req);
switch (req.requestType) {
case REQUEST_GET_STATS: {
Response resp;
resp.tag = RTC_GetCounter();
memcpy(resp.data, &stats, sizeof(stats));
resp.dataLen = sizeof(stats);
sendEncryptedResponse(enc.remNode, &resp);
stats.totalUpTimeSeconds = RTC_GetCounter() - startupTime;
stats.totalAirTimeSeconds = TICKS_TO_MS (tickAirtime / 1000);
memcpy (resp.data, &stats, sizeof (stats));
resp.dataLen = sizeof (stats);
sendEncryptedResponse (enc->remNode, &resp);
break;
}
case REQUEST_KEEPALIVE:
break;
case REQUEST_GET_TELEMETRY_DATA:
case REQUEST_GET_TELEMETRY_DATA: {
Response resp;
resp.tag = req.timestamp;
uint8_t index2 = 0;
resp.data[index2++] = TELEM_CHANNEL_SELF;
resp.data[index2++] = LPP_TEMPERATURE;
int16_t dataTemp = getDeciTemperature();
printf ("The temperature is %d decicelsius\n", dataTemp);
resp.data[index2++] = (dataTemp >> 8) & 0xFF;
resp.data[index2++] = dataTemp & 0xFF;
resp.data[index2++] = TELEM_CHANNEL_SELF;
resp.data[index2++] = LPP_VOLTAGE;
int16_t dataVolt = stats.millivolts / 10;
resp.data[index2++] = (dataVolt >> 8) & 0xFF;
resp.data[index2++] = dataVolt & 0xFF;
if (enc->remNode->authenticated) {
encode_gps (TELEM_CHANNEL_SELF, persistent.latitude / 1000000.0f, persistent.longitude / 1000000.0f, persistent.altitude / 100.0f, &(resp.data[index2]));
// encode_gps(TELEM_CHANNEL_SELF, 48.1909f, 17.0303f, 234.0f, &(resp.data[index2]));
index2 += LPP_GPS_SIZE;
}
if (enc->remNode->authenticated) {
resp.data[index2++] = 2;
resp.data[index2++] = LPP_TEMPERATURE;
int16_t jokeTemp = 6942;
resp.data[index2++] = (jokeTemp >> 8) & 0xFF;
resp.data[index2++] = jokeTemp & 0xFF;
resp.dataLen = index2;
}
sendEncryptedResponse (enc->remNode, &resp);
break;
}
case REQUEST_GET_MIN_MAX_AVG:
break;
case REQUEST_GET_ACCESS_LIST:
break;
}
} else if (enc.type == PAYLOAD_TYPE_RESPONSE) {
} else if (enc->type == PAYLOAD_TYPE_RESPONSE) {
Response resp;
resp.tag = enc.payload[index++];
resp.tag |= enc.payload[index++] << 8;
resp.tag |= enc.payload[index++] << 16;
resp.tag |= enc.payload[index++] << 24;
resp.dataLen = enc.payloadLen - index;
memcpy (resp.data, &(enc.payload[index]), resp.dataLen);
resp.tag = enc->payload[index++];
resp.tag |= enc->payload[index++] << 8;
resp.tag |= enc->payload[index++] << 16;
resp.tag |= enc->payload[index++] << 24;
resp.dataLen = enc->payloadLen - index;
memcpy (resp.data, &(enc->payload[index]), resp.dataLen);
printResponse (&resp);
} else if (enc.type == PAYLOAD_TYPE_TXT_MSG) {
} else if (enc->type == PAYLOAD_TYPE_TXT_MSG) {
PlainTextMessagePayload plaintext;
plaintext.timestamp = enc.payload[index++];
plaintext.timestamp |= enc.payload[index++] << 8;
plaintext.timestamp |= enc.payload[index++] << 16;
plaintext.timestamp |= enc.payload[index++] << 24;
plaintext.attempt = enc.payload[index] & 0x03;
plaintext.textType = enc.payload[index++] >> 2;
memcpy (plaintext.message, &(enc.payload[index]), enc.payloadLen - index);
plaintext.timestamp = enc->payload[index++];
plaintext.timestamp |= enc->payload[index++] << 8;
plaintext.timestamp |= enc->payload[index++] << 16;
plaintext.timestamp |= enc->payload[index++] << 24;
plaintext.attempt = enc->payload[index] & 0x03;
plaintext.textType = enc->payload[index++] >> 2;
memcpy (plaintext.message, &(enc->payload[index]), enc->payloadLen - index);
printPlainTextMessage (&plaintext);
}
}

View File

@@ -18,8 +18,8 @@ void sendEncryptedRequest (NodeEntry *targetNode, const Request *req);
void sendEncryptedPathPayload (NodeEntry *targetNode, const ReturnedPathPayload *path);
EncryptedPayloadStruct decodeEncryptedPayload (FrameStruct frame);
void decodeEncryptedPayload (const FrameStruct *frame);
void parseEncryptedPayload (EncryptedPayloadStruct enc);
void parseEncryptedPayload (const EncryptedPayloadStruct * enc);
#endif

View File

@@ -9,61 +9,64 @@
#define TAG "GroupMessage"
void sendGroupMessage (GroupTextMessage msg) {
msg.channelHash = persistent.aesKeys[msg.keyIndex][0];
msg.flags = 0;
msg.timestamp = RTC_GetCounter();
void sendGroupMessage (const GroupTextMessage *msg) {
// Prepare values locally instead of modifying msg
uint8_t channelHash = persistent.aesKeys[msg->keyIndex][0];
uint8_t flags = 0;
int32_t timestamp = RTC_GetCounter();
FrameStruct frame;
frame.header = ROUTE_TYPE_FLOOD | PAYLOAD_TYPE_GRP_TXT | PAYLOAD_VERSION_0;
frame.path.pathLen = 0;
size_t offset = 0;
memset (frame.payload, 0, sizeof (frame.payload));
frame.payload[offset++] = msg.channelHash;
frame.payload[offset++] = channelHash;
uint8_t cipherBuf[176];
size_t offset2 = 0;
memcpy (cipherBuf, (const void *)&(msg.timestamp), 4);
offset2 += 4;
cipherBuf[offset2++] = msg.flags;
size_t textSize = offset2 + strlen ((const char *)msg.text);
if (textSize > 175) {
textSize = 175;
// Build encryption buffer directly on stack (no extra large buffer)
uint8_t buf[180]; // enough for timestamp + flags + text
size_t buf_offset = 0;
memcpy (buf + buf_offset, &timestamp, sizeof (timestamp));
buf_offset += sizeof (timestamp);
buf[buf_offset++] = flags;
size_t textLen = strlen ((const char *)msg->text);
if (buf_offset + textLen > sizeof (buf)) {
textLen = sizeof (buf) - buf_offset;
}
memcpy (cipherBuf + offset2, msg.text, textSize);
offset2 += textSize;
memcpy (buf + buf_offset, msg->text, textLen);
buf_offset += textLen;
hexdump ("TxDumpDec", buf, buf_offset);
// Encrypt and MAC directly into frame payload after channelHash
size_t olen = 0;
hexdump ("TxDumpDec", cipherBuf, offset2);
encrypt_then_mac (&(persistent.aesKeys[msg.keyIndex][1]), 16, cipherBuf, offset2, &(frame.payload[offset]), &olen);
encrypt_then_mac (&persistent.aesKeys[msg->keyIndex][1], 16, buf, buf_offset, &frame.payload[offset], &olen);
frame.payloadLen = olen + 1;
frame.payloadLen = olen + 1; // +1 for channelHash
sendFrame (frame);
return;
LoRaTransmit (&frame);
}
void makeSendGroupMessage (char *txt, uint8_t keyIndex) {
GroupTextMessage msg;
strcpy((char *) msg.text, persistent.nodeName);
strcpy ((char *)msg.text, persistent.nodeName);
strcat ((char *)msg.text, ": ");
strcat ((char *)msg.text, txt);
msg.keyIndex = keyIndex;
sendGroupMessage (msg);
sendGroupMessage (&msg);
return;
}
GroupTextMessage decodeGroupMessage (FrameStruct frame) {
void decodeGroupMessage (const FrameStruct *frame) {
GroupTextMessage msg;
memset (&msg, 0, sizeof (msg));
if ((frame.header & PAYLOAD_TYPE_MASK) != PAYLOAD_TYPE_GRP_TXT) {
ESP_LOGW (TAG, "Not a group text");
return msg;
if ((frame->header & PAYLOAD_TYPE_MASK) != PAYLOAD_TYPE_GRP_TXT) {
MESH_LOGW (TAG, "Not a group text");
return;
}
unsigned char index = 0;
msg.channelHash = frame.payload[index++];
msg.channelHash = frame->payload[index++];
unsigned char tmp[184];
@@ -71,27 +74,27 @@ GroupTextMessage decodeGroupMessage (FrameStruct frame) {
for (unsigned char i = 0; i < AESKeyCount; i++) {
if (msg.channelHash != persistent.aesKeys[i][0]) {
ESP_LOGW (TAG, "Hash %d does not equal %d", persistent.aesKeys[i][0], msg.channelHash);
MESH_LOGW (TAG, "Hash %d does not equal %d", persistent.aesKeys[i][0], msg.channelHash);
continue;
}
ESP_LOGW (TAG, "Hash does equal %d", msg.channelHash);
MESH_LOGW (TAG, "Hash does equal %d", msg.channelHash);
if (mac_then_decrypt (persistent.aesKeys[i] + 1, 16, frame.payload + index, frame.payloadLen - index, tmp) != 0) {
ESP_LOGW (TAG, "HMAC failed on grouphash key %d not matching %d", persistent.aesKeys[i][0], msg.channelHash);
if (mac_then_decrypt (persistent.aesKeys[i] + 1, 16, frame->payload + index, frame->payloadLen - index, tmp) != 0) {
MESH_LOGW (TAG, "HMAC failed on grouphash key %d not matching %d", persistent.aesKeys[i][0], msg.channelHash);
continue;
}
hexdump ("RxDumpDec", tmp, frame.payloadLen - index);
hexdump ("RxDumpDec", tmp, frame->payloadLen - index);
decrypted = 1;
break;
}
if (!decrypted) {
return msg;
return;
}
unsigned char plaintextLen = frame.payloadLen - index;
unsigned char plaintextLen = frame->payloadLen - index;
index = 0;
memcpy (&msg.timestamp, tmp + index, 4);
@@ -99,10 +102,11 @@ GroupTextMessage decodeGroupMessage (FrameStruct frame) {
msg.flags = tmp[index++];
memcpy (msg.text, tmp + index, plaintextLen - index);
return msg;
printGroupMessage (&msg);
}
void printGroupMessage (GroupTextMessage msg) {
printf ("Message with channel hash %d, flags %d: %s\n", msg.channelHash,
msg.flags, msg.text);
void printGroupMessage (const GroupTextMessage *msg) {
printf ("Message with channel hash %d, flags %d: %s\n", msg->channelHash,
msg->flags, msg->text);
}

View File

@@ -4,12 +4,12 @@
#include "stdint.h"
#include "meshcore/packetstructs.h"
void sendGroupMessage (GroupTextMessage msg);
void sendGroupMessage (const GroupTextMessage * msg);
void makeSendGroupMessage (char *txt, uint8_t keyIndex);
GroupTextMessage decodeGroupMessage (FrameStruct frame);
void decodeGroupMessage (const FrameStruct * frame);
void printGroupMessage (GroupTextMessage msg);
void printGroupMessage (const GroupTextMessage * msg);
#endif

View File

@@ -56,6 +56,7 @@ typedef struct DiscoverResponsePayload {
int8_t snr;
uint32_t tag;
uint8_t pubkey[32];
uint8_t pubkeyLen;
} DiscoverResponsePayload;
typedef struct AnonymousRequestPayload {
@@ -116,6 +117,27 @@ typedef struct Node {
char flags;
} Node;
typedef struct {
char name[32];
unsigned char pubKey[32];
unsigned char secret[32];
int32_t gps_latitude;
int32_t gps_longitude;
Path path;
uint8_t flags;
uint8_t type;
uint8_t authenticated;
uint32_t last_seen_rt; //remote timestamp
uint32_t last_seen_lt; //local timestamp
uint32_t sync_timestamp;
} NodeEntry;
typedef struct EncryptedPayloadStruct {
uint8_t destinationHash;
uint8_t sourceHash;
@@ -153,6 +175,7 @@ typedef struct AdvertisementPayload {
uint8_t pubKey[32];
int32_t timestamp;
uint8_t signature[64];
uint8_t valid;
uint8_t dataFlags;
int32_t latitude;
int32_t longitude;
@@ -164,6 +187,7 @@ typedef struct AdvertisementPayload {
typedef struct Extra {
uint8_t type;
uint8_t dataLen;
uint8_t data[180]; // hopefully long enough
} Extra;
@@ -204,4 +228,8 @@ typedef struct RepeaterStats {
uint32_t total_rx_air_time_secs;
} RepeaterStats;
typedef struct Channel {
} Channel;
#endif

View File

@@ -1,3 +1,6 @@
#include "stats.h"
RepeaterStats stats;
RepeaterStats stats;
uint32_t startupTime = 0;
uint32_t tickAirtime = 0;

View File

@@ -4,4 +4,8 @@
extern RepeaterStats stats;
extern uint32_t startupTime;
extern uint32_t tickAirtime;
#endif