Merge pull request #132 from geeksville/crypto

Crypto
pull/135/head^2
Kevin Hester 2020-05-09 19:14:01 -07:00 zatwierdzone przez GitHub
commit 1bf9d052fc
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
20 zmienionych plików z 419 dodań i 236 usunięć

Wyświetl plik

@ -4,7 +4,7 @@ This project is still pretty young but moving at a pretty good pace. Not all fea
Most of these problems should be solved by the beta release (within three months):
- We don't make these devices and they haven't been tested by UL or the FCC. If you use them you are experimenting and we can't promise they won't burn your house down ;-)
- Encryption is turned off for now
- The encryption [implementation](software/crypto.md) has not been reviewed by an expert. (Are you an expert? Please help us)
- A number of (straightforward) software work items have to be completed before battery life matches our measurements, currently battery life is about three days. Join us on chat if you want the spreadsheet of power measurements/calculations.
- The Android API needs to be documented better
- No one has written an iOS app yet. But some good souls [are talking about it](https://github.com/meshtastic/Meshtastic-esp32/issues/14) ;-)

Wyświetl plik

@ -29,7 +29,6 @@ fetches the fresh nodedb.
- rx signal measurements -3 marginal, -9 bad, 10 great, -10 means almost unusable. So scale this into % signal strength. preferably as a graph, with an X indicating loss of comms.
- assign every "channel" a random shared 8 bit sync word (per 4.2.13.6 of datasheet) - use that word to filter packets before even checking CRC. This will ensure our CPU will only wake for packets on our "channel"
- Note: we do not do address filtering at the chip level, because we might need to route for the mesh
- add basic crypto - https://github.com/chegewara/esp32-mbedtls-aes-test/blob/master/main/main.c https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation - use ECB at first (though it is shit) because it doesn't require us to send 16 bytes of IV with each packet. Then OFB per example. Possibly do this crypto at the data payload level only, so that all of the packet routing metadata
is in cleartext (so that nodes will route for other radios that are cryptoed with a key we don't know)
- add frequency hopping, dependent on the gps time, make the switch moment far from the time anyone is going to be transmitting
- share channel settings over Signal (or qr code) by embedding an an URL which is handled by the MeshUtil app.

Wyświetl plik

@ -0,0 +1,38 @@
# Encryption in Meshtastic
Cryptography is tricky, so we've tried to 'simply' apply standard crypto solutions to our implementation. However,
the project developers are not cryptography experts. Therefore we ask two things:
- If you are a cryptography expert, please review these notes and our questions below. Can you help us by reviewing our
notes below and offering advice? We will happily give as much or as little credit as you wish as our thanks ;-).
- Consider our existing solution 'alpha' and probably fairly secure against an not very aggressive adversary. But until
it is reviewed by someone smarter than us, assume it might have flaws.
## Notes on implementation
- We do all crypto at the SubPacket (payload) level only, so that all meshtastic nodes will route for others - even those channels which are encrypted with a different key.
- Mostly based on reading [Wikipedia](<https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)>) and using the modes the ESP32 provides support for in hardware.
- We use AES256-CTR as a stream cypher (with zero padding on the last BLOCK) because it is well supported with hardware acceleration.
Parameters for our CTR implementation:
- Our AES key is 256 bits, shared as part of the 'Channel' specification.
- Each SubPacket will be sent as a series of 16 byte BLOCKS.
- The node number concatenated with the packet number is used as the NONCE. This counter will be stored in flash in the device and should essentially never repeat. If the user makes a new 'Channel' (i.e. picking a new random 256 bit key), the packet number will start at zero. The packet number is sent
in cleartext with each packet. The node number can be derived from the "from" field of each packet.
- Each BLOCK for a packet has an incrementing COUNTER. COUNTER starts at zero for the first block of each packet.
- The IV for each block is constructed by concatenating the NONCE as the upper 96 bits of the IV and the COUNTER as the bottom 32 bits. Note: since our packets are small counter will really never be higher than 32 (five bits).
```
You can encrypt separate messages by dividing the nonce_counter buffer in two areas: the first one used for a per-message nonce, handled by yourself, and the second one updated by this function internally.
For example, you might reserve the first 12 bytes for the per-message nonce, and the last 4 bytes for internal use. In that case, before calling this function on a new message you need to set the first 12 bytes of nonce_counter to your chosen nonce value, the last 4 to 0, and nc_off to 0 (which will cause stream_block to be ignored). That way, you can encrypt at most 2**96 messages of up to 2**32 blocks each with the same key.
The per-message nonce (or information sufficient to reconstruct it) needs to be communicated with the ciphertext and must be unique. The recommended way to ensure uniqueness is to use a message counter. An alternative is to generate random nonces, but this limits the number of messages that can be securely encrypted: for example, with 96-bit random nonces, you should not encrypt more than 2**32 messages with the same key.
Note that for both stategies, sizes are measured in blocks and that an AES block is 16 bytes.
```
## Remaining todo
- Make the packet numbers 32 bit
- Implement for NRF52 [NRF52](https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.0.0/lib_crypto_aes.html#sub_aes_ctr)

Wyświetl plik

@ -62,6 +62,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At
Nice ideas worth considering someday...
- use the Jumper simulator to run meshes of simulated hardware: https://docs.jumper.io/docs/install.html
- make/find a multithread safe debug logging class (include remote logging and timestamps and levels). make each log event atomic.
- turn on freertos stack size checking
- Currently we use Nordic's vendor ID, which is apparently okay: https://devzone.nordicsemi.com/f/nordic-q-a/44014/using-nordic-vid-and-pid-for-nrf52840 and I just picked a PID of 0x4403

2
proto

@ -1 +1 @@
Subproject commit b35e7fb17e80a9761145d69a288a9e87af862cab
Subproject commit 5e2df6c9986cd75f0af4eab1ba0d2aacf258aaab

Wyświetl plik

@ -0,0 +1,83 @@
#include "CryptoEngine.h"
#include "configuration.h"
#include "crypto/includes.h"
#include "crypto/common.h"
// #include "esp_system.h"
#include "crypto/aes.h"
#include "crypto/aes_wrap.h"
#include "mbedtls/aes.h"
#define MAX_BLOCKSIZE 256
class ESP32CryptoEngine : public CryptoEngine
{
mbedtls_aes_context aes;
/// How many bytes in our key
uint8_t keySize = 0;
public:
ESP32CryptoEngine() { mbedtls_aes_init(&aes); }
~ESP32CryptoEngine() { mbedtls_aes_free(&aes); }
/**
* Set the key used for encrypt, decrypt.
*
* As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext.
*
* @param numBytes must be 16 (AES128), 32 (AES256) or 0 (no crypt)
* @param bytes a _static_ buffer that will remain valid for the life of this crypto instance (i.e. this class will cache the
* provided pointer)
*/
virtual void setKey(size_t numBytes, uint8_t *bytes)
{
keySize = numBytes;
DEBUG_MSG("Installing AES%d key!\n", numBytes * 8);
if (numBytes != 0) {
auto res = mbedtls_aes_setkey_enc(&aes, bytes, numBytes * 8);
assert(!res);
}
}
/**
* Encrypt a packet
*
* @param bytes is updated in place
*/
virtual void encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes)
{
if (keySize != 0) {
uint8_t stream_block[16];
static uint8_t scratch[MAX_BLOCKSIZE];
size_t nc_off = 0;
// DEBUG_MSG("ESP32 encrypt!\n");
initNonce(fromNode, packetNum);
assert(numBytes <= MAX_BLOCKSIZE);
memcpy(scratch, bytes, numBytes);
memset(scratch + numBytes, 0,
sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it)
auto res = mbedtls_aes_crypt_ctr(&aes, numBytes, &nc_off, nonce, stream_block, scratch, bytes);
assert(!res);
}
}
virtual void decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes)
{
// DEBUG_MSG("ESP32 decrypt!\n");
// For CTR, the implementation is the same
encrypt(fromNode, packetNum, numBytes, bytes);
}
private:
};
CryptoEngine *crypto = new ESP32CryptoEngine();

Wyświetl plik

@ -104,8 +104,6 @@ const char *getDeviceName()
return name;
}
static MeshRadio *radio = NULL;
static uint32_t ledBlinker()
{
static bool ledOn;
@ -231,10 +229,10 @@ void setup()
#else
new SimRadio();
#endif
radio = new MeshRadio(rIf);
router.addInterface(&radio->radioIf);
if (radio && !radio->init())
router.addInterface(rIf);
if (!rIf->init())
recordCriticalError(ErrNoRadio);
// This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values

Wyświetl plik

@ -0,0 +1,32 @@
#include "CryptoEngine.h"
#include "configuration.h"
void CryptoEngine::setKey(size_t numBytes, uint8_t *bytes)
{
DEBUG_MSG("WARNING: Using stub crypto - all crypto is sent in plaintext!\n");
}
/**
* Encrypt a packet
*
* @param bytes is updated in place
*/
void CryptoEngine::encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes)
{
DEBUG_MSG("WARNING: noop encryption!\n");
}
void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes)
{
DEBUG_MSG("WARNING: noop decryption!\n");
}
/**
* Init our 128 bit nonce for a new packet
*/
void CryptoEngine::initNonce(uint32_t fromNode, uint64_t packetNum)
{
memset(nonce, 0, sizeof(nonce));
*((uint64_t *)&nonce[0]) = packetNum;
*((uint32_t *)&nonce[8]) = fromNode;
}

Wyświetl plik

@ -0,0 +1,50 @@
#pragma once
#include <Arduino.h>
/**
* see docs/software/crypto.md for details.
*
*/
class CryptoEngine
{
protected:
/** Our per packet nonce */
uint8_t nonce[16];
public:
virtual ~CryptoEngine() {}
/**
* Set the key used for encrypt, decrypt.
*
* As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext.
*
* @param numBytes must be 16 (AES128), 32 (AES256) or 0 (no crypt)
* @param bytes a _static_ buffer that will remain valid for the life of this crypto instance (i.e. this class will cache the
* provided pointer)
*/
virtual void setKey(size_t numBytes, uint8_t *bytes);
/**
* Encrypt a packet
*
* @param bytes is updated in place
*/
virtual void encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes);
virtual void decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes);
protected:
/**
* Init our 128 bit nonce for a new packet
*
* The NONCE is constructed by concatenating (from MSB to LSB):
* a 64 bit packet number (stored in little endian order)
* a 32 bit sending node number (stored in little endian order)
* a 32 bit block counter (starts at zero)
*/
void initNonce(uint32_t fromNode, uint64_t packetNum);
};
extern CryptoEngine *crypto;

Wyświetl plik

@ -1,113 +0,0 @@
#include "error.h"
#include <SPI.h>
#include <assert.h>
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "configuration.h"
#include "sleep.h"
#include <pb_decode.h>
#include <pb_encode.h>
/**
* ## LoRaWAN for North America
LoRaWAN defines 64, 125 kHz channels from 902.3 to 914.9 MHz increments.
The maximum output power for North America is +30 dBM.
The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are
separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency.
*/
/// Sometimes while debugging it is useful to set this false, to disable rf95 accesses
bool useHardware = true;
MeshRadio::MeshRadio(RadioInterface *rIf) : radioIf(*rIf) // , manager(radioIf)
{
myNodeInfo.num_channels = NUM_CHANNELS;
// Can't print strings this early - serial not setup yet
// DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name);
}
bool MeshRadio::init()
{
if (!useHardware)
return true;
DEBUG_MSG("Starting meshradio init...\n");
configChangedObserver.observe(&service.configChanged);
preflightSleepObserver.observe(&preflightSleep);
notifyDeepSleepObserver.observe(&notifyDeepSleep);
#ifdef RESET_GPIO
pinMode(RESET_GPIO, OUTPUT); // Deassert reset
digitalWrite(RESET_GPIO, HIGH);
// pulse reset
digitalWrite(RESET_GPIO, LOW);
delay(10);
digitalWrite(RESET_GPIO, HIGH);
delay(10);
#endif
// we now expect interfaces to operate in promiscous mode
// radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor
// time.
applySettings();
if (!radioIf.init()) {
DEBUG_MSG("LoRa radio init failed\n");
return false;
}
// No need to call this now, init is supposed to do same. reloadConfig();
return true;
}
/** hash a string into an integer
*
* djb2 by Dan Bernstein.
* http://www.cse.yorku.ca/~oz/hash.html
*/
unsigned long hash(char *str)
{
unsigned long hash = 5381;
int c;
while ((c = *str++) != 0)
hash = ((hash << 5) + hash) + (unsigned char)c; /* hash * 33 + c */
return hash;
}
/**
* Pull our channel settings etc... from protobufs to the dumb interface settings
*/
void MeshRadio::applySettings()
{
// Set up default configuration
// No Sync Words in LORA mode.
radioIf.modemConfig = (ModemConfigChoice)channelSettings.modem_config;
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
int channel_num = hash(channelSettings.name) % NUM_CHANNELS;
radioIf.freq = CH0 + CH_SPACING * channel_num;
radioIf.power = channelSettings.tx_power;
DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, txpower=%d\n", channelSettings.name, channelSettings.modem_config,
channel_num, channelSettings.tx_power);
}
int MeshRadio::reloadConfig(void *unused)
{
applySettings();
radioIf.reconfigure();
return 0;
}

Wyświetl plik

@ -2,9 +2,7 @@
#include "MemoryPool.h"
#include "MeshTypes.h"
#include "Observer.h"
#include "PointerQueue.h"
#include "RadioInterface.h"
#include "configuration.h"
#include "mesh.pb.h"
@ -61,48 +59,3 @@
#define NUM_CHANNELS NUM_CHANNELS_US
#endif
/**
* A raw low level interface to our mesh. Only understands nodenums and bytes (not protobufs or node ids)
* FIXME - REMOVE THIS CLASS
*/
class MeshRadio
{
public:
// Kinda ugly way of selecting different radio implementations, but soon this MeshRadio class will be going away
// entirely. At that point we can make things pretty.
RadioInterface &radioIf;
/** pool is the pool we will alloc our rx packets from
* rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool
*/
MeshRadio(RadioInterface *rIf);
bool init();
private:
CallbackObserver<MeshRadio, void *> configChangedObserver =
CallbackObserver<MeshRadio, void *>(this, &MeshRadio::reloadConfig);
CallbackObserver<MeshRadio, void *> preflightSleepObserver =
CallbackObserver<MeshRadio, void *>(this, &MeshRadio::preflightSleepCb);
CallbackObserver<MeshRadio, void *> notifyDeepSleepObserver =
CallbackObserver<MeshRadio, void *>(this, &MeshRadio::notifyDeepSleepDb);
/// The radioConfig object just changed, call this to force the hw to change to the new settings
int reloadConfig(void *unused = NULL);
/// Return 0 if sleep is okay
int preflightSleepCb(void *unused = NULL) { return radioIf.canSleep() ? 0 : 1; }
int notifyDeepSleepDb(void *unused = NULL)
{
radioIf.sleep();
return 0;
}
/**
* Pull our channel settings etc... from protobufs to the dumb interface settings
*/
void applySettings();
};

Wyświetl plik

@ -92,9 +92,9 @@ void MeshService::sendOurOwner(NodeNum dest, bool wantReplies)
{
MeshPacket *p = allocForSending();
p->to = dest;
p->payload.want_response = wantReplies;
p->payload.has_user = true;
User &u = p->payload.user;
p->decoded.want_response = wantReplies;
p->decoded.has_user = true;
User &u = p->decoded.user;
u = owner;
DEBUG_MSG("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name);
@ -108,7 +108,7 @@ const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp)
bool isCollision = mp->from == myNodeInfo.my_node_num;
// we win if we have a lower macaddr
bool weWin = memcmp(&owner.macaddr, &mp->payload.user.macaddr, sizeof(owner.macaddr)) < 0;
bool weWin = memcmp(&owner.macaddr, &mp->decoded.user.macaddr, sizeof(owner.macaddr)) < 0;
if (isCollision) {
if (weWin) {
@ -134,7 +134,7 @@ const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp)
sendOurOwner(mp->from);
String lcd = String("Joined: ") + mp->payload.user.long_name + "\n";
String lcd = String("Joined: ") + mp->decoded.user.long_name + "\n";
screen.print(lcd.c_str());
}
@ -143,12 +143,12 @@ const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp)
void MeshService::handleIncomingPosition(const MeshPacket *mp)
{
if (mp->has_payload && mp->payload.has_position) {
DEBUG_MSG("handled incoming position time=%u\n", mp->payload.position.time);
if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.has_position) {
DEBUG_MSG("handled incoming position time=%u\n", mp->decoded.position.time);
if (mp->payload.position.time) {
if (mp->decoded.position.time) {
struct timeval tv;
uint32_t secs = mp->payload.position.time;
uint32_t secs = mp->decoded.position.time;
tv.tv_sec = secs;
tv.tv_usec = 0;
@ -171,7 +171,7 @@ int MeshService::handleFromRadio(const MeshPacket *mp)
DEBUG_MSG("Ignoring incoming time, because we have a GPS\n");
}
if (mp->has_payload && mp->payload.has_user) {
if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.has_user) {
mp = handleFromRadioUser(mp);
}
@ -192,7 +192,7 @@ int MeshService::handleFromRadio(const MeshPacket *mp)
MeshPacket *copied = packetPool.allocCopy(*mp);
assert(toPhoneQueue.enqueue(copied, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages
if (mp->payload.want_response)
if (mp->decoded.want_response)
sendNetworkPing(mp->from);
} else {
DEBUG_MSG("Not delivering vetoed User message\n");
@ -257,12 +257,12 @@ void MeshService::sendToMesh(MeshPacket *p)
// Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other
// nodes shouldn't trust it anyways) Note: for now, we allow a device with a local GPS to include the time, so that gpsless
// devices can get time.
if (p->has_payload && p->payload.has_position) {
if (p->which_payload == MeshPacket_decoded_tag && p->decoded.has_position) {
if (!gps->isConnected) {
DEBUG_MSG("Stripping time %u from position send\n", p->payload.position.time);
p->payload.position.time = 0;
DEBUG_MSG("Stripping time %u from position send\n", p->decoded.position.time);
p->decoded.position.time = 0;
} else
DEBUG_MSG("Providing time to mesh %u\n", p->payload.position.time);
DEBUG_MSG("Providing time to mesh %u\n", p->decoded.position.time);
}
// If the phone sent a packet just to us, don't send it out into the network
@ -282,7 +282,7 @@ MeshPacket *MeshService::allocForSending()
{
MeshPacket *p = packetPool.allocZeroed();
p->has_payload = true;
p->which_payload = MeshPacket_decoded_tag; // Assume payload is decoded at start.
p->from = nodeDB.getNodeNum();
p->to = NODENUM_BROADCAST;
p->id = generatePacketId();
@ -312,10 +312,10 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies)
// Update our local node info with our position (even if we don't decide to update anyone else)
MeshPacket *p = allocForSending();
p->to = dest;
p->payload.has_position = true;
p->payload.position = node->position;
p->payload.want_response = wantReplies;
p->payload.position.time = getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid.
p->decoded.has_position = true;
p->decoded.position = node->position;
p->decoded.want_response = wantReplies;
p->decoded.position.time = getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid.
sendToMesh(p);
}
@ -325,9 +325,9 @@ int MeshService::onGPSChanged(void *unused)
// Update our local node info with our position (even if we don't decide to update anyone else)
MeshPacket *p = allocForSending();
p->payload.has_position = true;
p->decoded.has_position = true;
Position &pos = p->payload.position;
Position &pos = p->decoded.position;
// !zero or !zero lat/long means valid
if (gps->latitude != 0 || gps->longitude != 0) {
if (gps->altitude != 0)

Wyświetl plik

@ -5,6 +5,7 @@
#include "FS.h"
#include "SPIFFS.h"
#include "CryptoEngine.h"
#include "GPS.h"
#include "NodeDB.h"
#include "PowerFSM.h"
@ -51,7 +52,7 @@ NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_cou
void NodeDB::resetRadioConfig()
{
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128)
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf};
@ -75,10 +76,14 @@ void NodeDB::resetRadioConfig()
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range
channelSettings.tx_power = 23;
memcpy(&channelSettings.psk, &defaultpsk, sizeof(channelSettings.psk));
memcpy(&channelSettings.psk.bytes, &defaultpsk, sizeof(channelSettings.psk));
channelSettings.psk.size = sizeof(defaultpsk);
strcpy(channelSettings.name, "Default");
}
// Tell our crypto engine about the psk
crypto->setKey(channelSettings.psk.size, channelSettings.psk.bytes);
// temp hack for quicker testing
/*
radioConfig.preferences.screen_on_secs = 30;
@ -274,8 +279,8 @@ size_t NodeDB::getNumOnlineNodes()
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
void NodeDB::updateFrom(const MeshPacket &mp)
{
if (mp.has_payload) {
const SubPacket &p = mp.payload;
if (mp.which_payload == MeshPacket_decoded_tag) {
const SubPacket &p = mp.decoded;
DEBUG_MSG("Update DB node 0x%x, rx_time=%u\n", mp.from, mp.rx_time);
int oldNumNodes = *numNodes;

Wyświetl plik

@ -1,28 +1,91 @@
#include "RadioInterface.h"
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "assert.h"
#include "configuration.h"
#include "sleep.h"
#include <assert.h>
#include <pb_decode.h>
#include <pb_encode.h>
/**
* ## LoRaWAN for North America
LoRaWAN defines 64, 125 kHz channels from 902.3 to 914.9 MHz increments.
The maximum output power for North America is +30 dBM.
The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are
separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency.
*/
// 1kb was too small
#define RADIO_STACK_SIZE 4096
RadioInterface::RadioInterface() : txQueue(MAX_TX_QUEUE)
{
assert(sizeof(PacketHeader) == 4); // make sure the compiler did what we expected
myNodeInfo.num_channels = NUM_CHANNELS;
// Can't print strings this early - serial not setup yet
// DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name);
}
bool RadioInterface::init()
{
DEBUG_MSG("Starting meshradio init...\n");
configChangedObserver.observe(&service.configChanged);
preflightSleepObserver.observe(&preflightSleep);
notifyDeepSleepObserver.observe(&notifyDeepSleep);
// we now expect interfaces to operate in promiscous mode
// radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor
// time.
// we want this thread to run at very high priority, because it is effectively running as a user space ISR
start("radio", RADIO_STACK_SIZE, configMAX_PRIORITIES - 1); // Start our worker thread
return true;
}
/** hash a string into an integer
*
* djb2 by Dan Bernstein.
* http://www.cse.yorku.ca/~oz/hash.html
*/
unsigned long hash(char *str)
{
unsigned long hash = 5381;
int c;
while ((c = *str++) != 0)
hash = ((hash << 5) + hash) + (unsigned char)c; /* hash * 33 + c */
return hash;
}
/**
* Pull our channel settings etc... from protobufs to the dumb interface settings
*/
void RadioInterface::applyModemConfig()
{
// Set up default configuration
// No Sync Words in LORA mode.
modemConfig = (ModemConfigChoice)channelSettings.modem_config;
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
int channel_num = hash(channelSettings.name) % NUM_CHANNELS;
freq = CH0 + CH_SPACING * channel_num;
power = channelSettings.tx_power;
DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, power=%d\n", channelSettings.name, channelSettings.modem_config, channel_num,
channelSettings.tx_power);
}
ErrorCode SimRadio::send(MeshPacket *p)
{
DEBUG_MSG("SimRadio.send\n");
@ -44,7 +107,7 @@ size_t RadioInterface::beginSending(MeshPacket *p)
assert(!sendingPacket);
// DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad());
assert(p->has_payload);
assert(p->which_payload == MeshPacket_encrypted_tag); // It should have already been encoded by now
lastTxStart = millis();
@ -58,11 +121,8 @@ size_t RadioInterface::beginSending(MeshPacket *p)
// if the sender nodenum is zero, that means uninitialized
assert(h->from);
size_t numbytes = pb_encode_to_bytes(radiobuf + sizeof(PacketHeader), sizeof(radiobuf), SubPacket_fields, &p->payload) +
sizeof(PacketHeader);
assert(numbytes <= MAX_RHPACKETLEN);
memcpy(radiobuf + sizeof(PacketHeader), p->encrypted.bytes, p->encrypted.size);
sendingPacket = p;
return numbytes;
return p->encrypted.size + sizeof(PacketHeader);
}

Wyświetl plik

@ -2,6 +2,7 @@
#include "MemoryPool.h"
#include "MeshTypes.h"
#include "Observer.h"
#include "PointerQueue.h"
#include "WorkerThread.h"
#include "mesh.pb.h"
@ -35,6 +36,15 @@ class RadioInterface : protected NotifiedWorkerThread
friend class MeshRadio; // for debugging we let that class touch pool
PointerQueue<MeshPacket> *rxDest = NULL;
CallbackObserver<RadioInterface, void *> configChangedObserver =
CallbackObserver<RadioInterface, void *>(this, &RadioInterface::reloadConfig);
CallbackObserver<RadioInterface, void *> preflightSleepObserver =
CallbackObserver<RadioInterface, void *>(this, &RadioInterface::preflightSleepCb);
CallbackObserver<RadioInterface, void *> notifyDeepSleepObserver =
CallbackObserver<RadioInterface, void *>(this, &RadioInterface::notifyDeepSleepDb);
protected:
MeshPacket *sendingPacket = NULL; // The packet we are currently sending
PointerQueue<MeshPacket> txQueue;
@ -104,6 +114,29 @@ class RadioInterface : protected NotifiedWorkerThread
size_t beginSending(MeshPacket *p);
virtual void loop() {} // Idle processing
/**
* Convert our modemConfig enum into wf, sf, etc...
*
* These paramaters will be pull from the channelSettings global
*/
virtual void applyModemConfig();
private:
/// Return 0 if sleep is okay
int preflightSleepCb(void *unused = NULL) { return canSleep() ? 0 : 1; }
int notifyDeepSleepDb(void *unused = NULL)
{
sleep();
return 0;
}
int reloadConfig(void *unused)
{
reconfigure();
return 0;
}
};
class SimRadio : public RadioInterface

Wyświetl plik

@ -58,6 +58,8 @@ RadioLibInterface *RadioLibInterface::instance;
*/
void RadioLibInterface::applyModemConfig()
{
RadioInterface::applyModemConfig();
switch (modemConfig) {
case Bw125Cr45Sf128: ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range
bw = 125;
@ -287,24 +289,19 @@ void RadioLibInterface::handleReceiveInterrupt()
} else {
MeshPacket *mp = packetPool.allocZeroed();
SubPacket *p = &mp->payload;
mp->from = h->from;
mp->to = h->to;
mp->id = h->id;
addReceiveMetadata(mp);
if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) {
DEBUG_MSG("Invalid protobufs in received mesh packet, discarding.\n");
packetPool.release(mp);
// rxBad++; not really a hw error
} else {
// parsing was successful, queue for our recipient
mp->has_payload = true;
DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id);
mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point
assert(payloadLen <= sizeof(mp->encrypted.bytes));
memcpy(mp->encrypted.bytes, payload, payloadLen);
mp->encrypted.size = payloadLen;
deliverToReceiver(mp);
}
DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id);
deliverToReceiver(mp);
}
}
}

Wyświetl plik

@ -99,8 +99,10 @@ class RadioLibInterface : public RadioInterface
protected:
/**
* Convert our modemConfig enum into wf, sf, etc...
*
* These paramaters will be pull from the channelSettings global
*/
void applyModemConfig();
virtual void applyModemConfig();
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
virtual bool canSendImmediately();

Wyświetl plik

@ -1,4 +1,6 @@
#include "Router.h"
#include "CryptoEngine.h"
#include "GPS.h"
#include "configuration.h"
#include "mesh-pb-constants.h"
@ -48,6 +50,26 @@ void Router::loop()
*/
ErrorCode Router::send(MeshPacket *p)
{
// If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it)
assert(p->which_payload == MeshPacket_encrypted_tag ||
p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now
// First convert from protobufs to raw bytes
if (p->which_payload == MeshPacket_decoded_tag) {
static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded);
assert(numbytes <= MAX_RHPACKETLEN);
crypto->encrypt(p->from, p->id, numbytes, bytes);
// Copy back into the packet and set the variant type
memcpy(p->encrypted.bytes, bytes, numbytes);
p->encrypted.size = numbytes;
p->which_payload = MeshPacket_encrypted_tag;
}
if (iface) {
// DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
return iface->send(p);
@ -58,8 +80,6 @@ ErrorCode Router::send(MeshPacket *p)
}
}
#include "GPS.h"
/**
* Handle any packet that is received by an interface on this node.
* Note: some packets may merely being passed through this node and will be forwarded elsewhere.
@ -70,7 +90,25 @@ void Router::handleReceived(MeshPacket *p)
// Also, we should set the time from the ISR and it should have msec level resolution
p->rx_time = getValidTime(); // store the arrival timestamp for the phone
DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
notifyPacketReceived.notifyObservers(p);
assert(p->which_payload ==
MeshPacket_encrypted_tag); // I _think_ the only thing that pushes to us is raw devices that just received packets
// Try to decrypt the packet if we can
static uint8_t bytes[MAX_RHPACKETLEN];
memcpy(bytes, p->encrypted.bytes,
p->encrypted.size); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf
crypto->decrypt(p->from, p->id, p->encrypted.size, bytes);
// Take those raw bytes and convert them back into a well structured protobuf we can understand
if (!pb_decode_from_bytes(bytes, p->encrypted.size, SubPacket_fields, &p->decoded)) {
DEBUG_MSG("Invalid protobufs in received mesh packet, discarding.\n");
} else {
// parsing was successful, queue for our recipient
p->which_payload = MeshPacket_decoded_tag;
DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
notifyPacketReceived.notifyObservers(p);
}
packetPool.release(p);
}

Wyświetl plik

@ -36,10 +36,11 @@ typedef struct _RouteDiscovery {
pb_callback_t route;
} RouteDiscovery;
typedef PB_BYTES_ARRAY_T(32) ChannelSettings_psk_t;
typedef struct _ChannelSettings {
int32_t tx_power;
ChannelSettings_ModemConfig modem_config;
pb_byte_t psk[16];
ChannelSettings_psk_t psk;
char name[12];
} ChannelSettings;
@ -122,11 +123,15 @@ typedef struct _SubPacket {
bool want_response;
} SubPacket;
typedef PB_BYTES_ARRAY_T(256) MeshPacket_encrypted_t;
typedef struct _MeshPacket {
int32_t from;
int32_t to;
bool has_payload;
SubPacket payload;
pb_size_t which_payload;
union {
SubPacket decoded;
MeshPacket_encrypted_t encrypted;
};
uint32_t rx_time;
uint32_t id;
float rx_snr;
@ -193,8 +198,8 @@ typedef struct _ToRadio {
#define User_init_default {"", "", "", {0}}
#define RouteDiscovery_init_default {{{NULL}, NULL}}
#define SubPacket_init_default {false, Position_init_default, false, Data_init_default, false, User_init_default, 0}
#define MeshPacket_init_default {0, 0, false, SubPacket_init_default, 0, 0, 0}
#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0}, ""}
#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0}
#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""}
#define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default}
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0}
@ -208,8 +213,8 @@ typedef struct _ToRadio {
#define User_init_zero {"", "", "", {0}}
#define RouteDiscovery_init_zero {{{NULL}, NULL}}
#define SubPacket_init_zero {false, Position_init_zero, false, Data_init_zero, false, User_init_zero, 0}
#define MeshPacket_init_zero {0, 0, false, SubPacket_init_zero, 0, 0, 0}
#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0}, ""}
#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0}
#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""}
#define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero}
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0}
@ -269,9 +274,10 @@ typedef struct _ToRadio {
#define SubPacket_data_tag 3
#define SubPacket_user_tag 4
#define SubPacket_want_response_tag 5
#define MeshPacket_decoded_tag 3
#define MeshPacket_encrypted_tag 8
#define MeshPacket_from_tag 1
#define MeshPacket_to_tag 2
#define MeshPacket_payload_tag 3
#define MeshPacket_rx_time_tag 4
#define MeshPacket_id_tag 6
#define MeshPacket_rx_snr_tag 7
@ -338,18 +344,19 @@ X(a, STATIC, SINGULAR, BOOL, want_response, 5)
#define MeshPacket_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, INT32, from, 1) \
X(a, STATIC, SINGULAR, INT32, to, 2) \
X(a, STATIC, OPTIONAL, MESSAGE, payload, 3) \
X(a, STATIC, ONEOF, MESSAGE, (payload,decoded,decoded), 3) \
X(a, STATIC, ONEOF, BYTES, (payload,encrypted,encrypted), 8) \
X(a, STATIC, SINGULAR, UINT32, rx_time, 4) \
X(a, STATIC, SINGULAR, UINT32, id, 6) \
X(a, STATIC, SINGULAR, FLOAT, rx_snr, 7)
#define MeshPacket_CALLBACK NULL
#define MeshPacket_DEFAULT NULL
#define MeshPacket_payload_MSGTYPE SubPacket
#define MeshPacket_payload_decoded_MSGTYPE SubPacket
#define ChannelSettings_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, INT32, tx_power, 1) \
X(a, STATIC, SINGULAR, UENUM, modem_config, 3) \
X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, psk, 4) \
X(a, STATIC, SINGULAR, BYTES, psk, 4) \
X(a, STATIC, SINGULAR, STRING, name, 5)
#define ChannelSettings_CALLBACK NULL
#define ChannelSettings_DEFAULT NULL
@ -492,12 +499,12 @@ extern const pb_msgdesc_t ToRadio_msg;
/* RouteDiscovery_size depends on runtime parameters */
#define SubPacket_size 377
#define MeshPacket_size 419
#define ChannelSettings_size 44
#define RadioConfig_size 120
#define ChannelSettings_size 60
#define RadioConfig_size 136
#define RadioConfig_UserPreferences_size 72
#define NodeInfo_size 132
#define MyNodeInfo_size 85
#define DeviceState_size 18535
#define DeviceState_size 18552
#define DebugString_size 258
#define FromRadio_size 428
#define ToRadio_size 422

Wyświetl plik

@ -81,7 +81,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
MeshPacket &mp = devicestate.rx_text_message;
NodeInfo *node = nodeDB.getNode(mp.from);
// DEBUG_MSG("drawing text message from 0x%x: %s\n", mp.from,
// mp.payload.variant.data.payload.bytes);
// mp.decoded.variant.data.decoded.bytes);
// Demo for drawStringMaxWidth:
// with the third parameter you can define the width after which words will
@ -94,8 +94,8 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
// the max length of this buffer is much longer than we can possibly print
static char tempBuf[96];
assert(mp.payload.has_data);
snprintf(tempBuf, sizeof(tempBuf), " %s", mp.payload.data.payload.bytes);
assert(mp.decoded.has_data);
snprintf(tempBuf, sizeof(tempBuf), " %s", mp.decoded.data.payload.bytes);
display->drawStringMaxWidth(4 + x, 10 + y, 128, tempBuf);
}