Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 20 additions & 9 deletions examples/companion_radio/DataStore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,14 +274,17 @@ File file = openRead(_getContactsChannelsFS(), "/contacts3");
bool full = false;
while (!full) {
ContactInfo c;
memset(&c, 0, sizeof(c)); // Zero-initialize to prevent garbage in new fields
c.shared_secret_valid = false;
c.supports_ascon = false;
uint8_t pub_key[32];
uint8_t unused;
uint8_t crypto_flags; // was 'unused'; bit 0 = supports_ascon

bool success = (file.read(pub_key, 32) == 32);
success = success && (file.read((uint8_t *)&c.name, 32) == 32);
success = success && (file.read(&c.type, 1) == 1);
success = success && (file.read(&c.flags, 1) == 1);
success = success && (file.read(&unused, 1) == 1);
success = success && (file.read(&crypto_flags, 1) == 1);
success = success && (file.read((uint8_t *)&c.sync_since, 4) == 4); // was 'reserved'
success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1);
success = success && (file.read((uint8_t *)&c.last_advert_timestamp, 4) == 4);
Expand All @@ -292,6 +295,7 @@ File file = openRead(_getContactsChannelsFS(), "/contacts3");

if (!success) break; // EOF

c.supports_ascon = (crypto_flags & 0x01) != 0; // bit 0; old files have 0 here (backwards compatible)
c.id = mesh::Identity(pub_key);
if (!host->onContactLoaded(c)) full = true;
}
Expand All @@ -304,14 +308,14 @@ void DataStore::saveContacts(DataStoreHost* host) {
if (file) {
uint32_t idx = 0;
ContactInfo c;
uint8_t unused = 0;

while (host->getContactForSave(idx, c)) {
uint8_t crypto_flags = c.supports_ascon ? 0x01 : 0x00; // bit 0 = supports_ascon
bool success = (file.write(c.id.pub_key, 32) == 32);
success = success && (file.write((uint8_t *)&c.name, 32) == 32);
success = success && (file.write(&c.type, 1) == 1);
success = success && (file.write(&c.flags, 1) == 1);
success = success && (file.write(&unused, 1) == 1);
success = success && (file.write(&crypto_flags, 1) == 1);
success = success && (file.write((uint8_t *)&c.sync_since, 4) == 4);
success = success && (file.write((uint8_t *)&c.out_path_len, 1) == 1);
success = success && (file.write((uint8_t *)&c.last_advert_timestamp, 4) == 4);
Expand All @@ -335,14 +339,18 @@ void DataStore::loadChannels(DataStoreHost* host) {
uint8_t channel_idx = 0;
while (!full) {
ChannelDetails ch;
uint8_t unused[4];
uint8_t flags_and_unused[4];

bool success = (file.read(unused, 4) == 4);
bool success = (file.read(flags_and_unused, 4) == 4);
success = success && (file.read((uint8_t *)ch.name, 32) == 32);
success = success && (file.read((uint8_t *)ch.channel.secret, 32) == 32);

if (!success) break; // EOF

// First byte stores channel flags (v2 support, etc), rest unused
// Old files have all zeros here, which means v1 (backward compatible)
ch.channel.flags = flags_and_unused[0];

if (host->onChannelLoaded(channel_idx, ch)) {
channel_idx++;
} else {
Expand All @@ -358,11 +366,14 @@ void DataStore::saveChannels(DataStoreHost* host) {
if (file) {
uint8_t channel_idx = 0;
ChannelDetails ch;
uint8_t unused[4];
memset(unused, 0, 4);
uint8_t flags_and_unused[4];

while (host->getChannelForSave(channel_idx, ch)) {
bool success = (file.write(unused, 4) == 4);
// First byte stores channel flags (v2 support, etc), rest unused
memset(flags_and_unused, 0, 4);
flags_and_unused[0] = ch.channel.flags;

bool success = (file.write(flags_and_unused, 4) == 4);
success = success && (file.write((uint8_t *)ch.name, 32) == 32);
success = success && (file.write((uint8_t *)ch.channel.secret, 32) == 32);

Expand Down
27 changes: 25 additions & 2 deletions examples/companion_radio/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#define CMD_SEND_ANON_REQ 57
#define CMD_SET_AUTOADD_CONFIG 58
#define CMD_GET_AUTOADD_CONFIG 59
#define CMD_GET_CHANNEL_V2 60 // returns 51 bytes (includes flags)

// Stats sub-types for CMD_GET_STATS
#define STATS_TYPE_CORE 0
Expand Down Expand Up @@ -88,6 +89,7 @@
#define RESP_CODE_TUNING_PARAMS 23
#define RESP_CODE_STATS 24 // v8+, second byte is stats type
#define RESP_CODE_AUTOADD_CONFIG 25
#define RESP_CODE_CHANNEL_INFO_V2 26 // reply to CMD_GET_CHANNEL_V2 (51 bytes)

#define SEND_TIMEOUT_BASE_MILLIS 500
#define FLOOD_SEND_TIMEOUT_FACTOR 16.0f
Expand Down Expand Up @@ -1109,7 +1111,7 @@ void MyMesh::handleCmdFrame(size_t len) {
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
writeOKFrame();
} else {
ContactInfo contact;
ContactInfo contact = {}; // Zero-initialize to prevent garbage in supports_ascon etc.
updateContactFromFrame(contact, last_mod, cmd_frame, len);
contact.lastmod = last_mod;
contact.sync_since = 0;
Expand Down Expand Up @@ -1500,6 +1502,23 @@ void MyMesh::handleCmdFrame(size_t len) {
i += 32;
memcpy(&out_frame[i], channel.channel.secret, 16);
i += 16; // NOTE: only 128-bit supported
// flags byte NOT included - old apps expect exactly 50 bytes
_serial->writeFrame(out_frame, i);
} else {
writeErrFrame(ERR_CODE_NOT_FOUND);
}
} else if (cmd_frame[0] == CMD_GET_CHANNEL_V2 && len >= 2) {
uint8_t channel_idx = cmd_frame[1];
ChannelDetails channel;
if (getChannel(channel_idx, channel)) {
int i = 0;
out_frame[i++] = RESP_CODE_CHANNEL_INFO_V2;
out_frame[i++] = channel_idx;
strcpy((char *)&out_frame[i], channel.name);
i += 32;
memcpy(&out_frame[i], channel.channel.secret, 16);
i += 16; // NOTE: only 128-bit supported
out_frame[i++] = channel.channel.flags; // V2: includes flags byte (51 bytes total)
_serial->writeFrame(out_frame, i);
} else {
writeErrFrame(ERR_CODE_NOT_FOUND);
Expand All @@ -1509,9 +1528,13 @@ void MyMesh::handleCmdFrame(size_t len) {
} else if (cmd_frame[0] == CMD_SET_CHANNEL && len >= 2 + 32 + 16) {
uint8_t channel_idx = cmd_frame[1];
ChannelDetails channel;
memset(&channel, 0, sizeof(channel));
StrHelper::strncpy(channel.name, (char *)&cmd_frame[2], 32);
memset(channel.channel.secret, 0, sizeof(channel.channel.secret));
memcpy(channel.channel.secret, &cmd_frame[2 + 32], 16); // NOTE: only 128-bit supported
// Optional flags byte: old apps don't send it (flags defaults to 0 = AES)
if (len >= 2 + 32 + 16 + 1) {
channel.channel.flags = cmd_frame[2 + 32 + 16];
}
if (setChannel(channel_idx, channel)) {
saveChannels();
writeOKFrame();
Expand Down
44 changes: 32 additions & 12 deletions examples/simple_repeater/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
}

void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender,
uint8_t *data, size_t len) {
uint8_t *data, size_t len, bool was_ascon) {
if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin
// client (unknown at this stage)
uint32_t timestamp;
Expand All @@ -530,16 +530,17 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m

if (reply_len == 0) return; // invalid request

// Reply with same encryption the sender used
if (packet->isRouteFlood()) {
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len, was_ascon);
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
} else if (reply_path_len < 0) {
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len);
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len, was_ascon);
if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY);
} else {
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len);
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len, was_ascon);
if (reply) sendDirect(reply, reply_path, reply_path_len, SERVER_RESPONSE_DELAY);
}
}
Expand All @@ -565,6 +566,17 @@ void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) {
}
}

void MyMesh::onPeerAsconCapabilityDetected(int peer_idx, bool supports_ascon) {
int i = matching_peer_indexes[peer_idx];
if (i >= 0 && i < acl.getNumClients()) {
auto client = acl.getClientByIdx(i);
if (supports_ascon && !client->supports_ascon) {
client->supports_ascon = true;
MESH_DEBUG_PRINTLN("Auto-detected Ascon capability for client %d", i);
}
}
}

static bool isShare(const mesh::Packet *packet) {
if (packet->hasTransportCodes()) {
return packet->transport_codes[0] == 0 && packet->transport_codes[1] == 0; // codes { 0, 0 } means 'send to nowhere'
Expand All @@ -576,11 +588,19 @@ void MyMesh::onAdvertRecv(mesh::Packet *packet, const mesh::Identity &id, uint32
const uint8_t *app_data, size_t app_data_len) {
mesh::Mesh::onAdvertRecv(packet, id, timestamp, app_data, app_data_len); // chain to super impl

// if this a zero hop advert (and not via 'Share'), add it to neighbours
if (packet->path_len == 0 && !isShare(packet)) {
AdvertDataParser parser(app_data, app_data_len);
if (parser.isValid() && parser.getType() == ADV_TYPE_REPEATER) { // just keep neigbouring Repeaters
putNeighbour(id, timestamp, packet->getSNR());
AdvertDataParser parser(app_data, app_data_len);
if (parser.isValid()) {
// Update Ascon encryption capability for known clients (chat nodes that are in our ACL)
ClientInfo* client = acl.getClient(id.pub_key, PUB_KEY_SIZE);
if (client) {
client->supports_ascon = (parser.getFeat1() & ADV_FEAT1_ASCON_CAPABLE) != 0;
}

// if this a zero hop advert (and not via 'Share'), add it to neighbours
if (packet->path_len == 0 && !isShare(packet)) {
if (parser.getType() == ADV_TYPE_REPEATER) { // just keep neigbouring Repeaters
putNeighbour(id, timestamp, packet->getSNR());
}
}
}
}
Expand Down Expand Up @@ -608,11 +628,11 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
if (packet->isRouteFlood()) {
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len, client->supports_ascon);
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
} else {
mesh::Packet *reply =
createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len);
createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len, client->supports_ascon);
if (reply) {
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY);
Expand Down Expand Up @@ -673,7 +693,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
memcpy(temp, &timestamp, 4); // mostly an extra blob to help make packet_hash unique
temp[4] = (TXT_TYPE_CLI_DATA << 2); // NOTE: legacy was: TXT_TYPE_PLAIN

auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len);
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len, client->supports_ascon);
if (reply) {
if (client->out_path_len < 0) {
sendFlood(reply, CLI_REPLY_DELAY_MILLIS);
Expand Down
5 changes: 3 additions & 2 deletions examples/simple_repeater/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,11 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {

bool filterRecvFloodPacket(mesh::Packet* pkt) override;

void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len, bool was_ascon) override;
int searchPeersByHash(const uint8_t* hash) override;
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override;
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len);
void onPeerAsconCapabilityDetected(int peer_idx, bool supports_ascon) override;
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override;
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
void onControlDataRecv(mesh::Packet* packet) override;
Expand Down
40 changes: 33 additions & 7 deletions examples/simple_room_server/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) {
mesh::Utils::sha256((uint8_t *)&client->extra.room.pending_ack, 4, reply_data, len, client->id.pub_key, PUB_KEY_SIZE);
client->extra.room.push_post_timestamp = post.post_timestamp;

auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->shared_secret, reply_data, len);
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->shared_secret, reply_data, len, client->supports_ascon);
if (reply) {
if (client->out_path_len < 0) {
sendFlood(reply);
Expand Down Expand Up @@ -279,7 +279,7 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
}

void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender,
uint8_t *data, size_t len) {
uint8_t *data, size_t len, bool was_ascon) {
if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin
// client (unknown at this stage)
uint32_t sender_timestamp, sender_sync_since;
Expand Down Expand Up @@ -349,13 +349,14 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m

next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); // delay next push, give RESPONSE packet time to arrive first

// Reply with same encryption the sender used
if (packet->isRouteFlood()) {
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
mesh::Packet *path = createPathReturn(sender, client->shared_secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, 13);
PAYLOAD_TYPE_RESPONSE, reply_data, 13, was_ascon);
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
} else {
mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->shared_secret, reply_data, 13);
mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->shared_secret, reply_data, 13, was_ascon);
if (reply) {
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY);
Expand Down Expand Up @@ -387,6 +388,31 @@ void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) {
}
}

void MyMesh::onPeerAsconCapabilityDetected(int peer_idx, bool supports_ascon) {
int i = matching_peer_indexes[peer_idx];
if (i >= 0 && i < acl.getNumClients()) {
auto client = acl.getClientByIdx(i);
if (supports_ascon && !client->supports_ascon) {
client->supports_ascon = true;
MESH_DEBUG_PRINTLN("Auto-detected Ascon capability for client %d", i);
}
}
}

void MyMesh::onAdvertRecv(mesh::Packet *packet, const mesh::Identity &id, uint32_t timestamp,
const uint8_t *app_data, size_t app_data_len) {
mesh::Mesh::onAdvertRecv(packet, id, timestamp, app_data, app_data_len); // chain to super impl

// Update Ascon encryption capability for known clients
AdvertDataParser parser(app_data, app_data_len);
if (parser.isValid()) {
ClientInfo* client = acl.getClient(id.pub_key, PUB_KEY_SIZE);
if (client) {
client->supports_ascon = (parser.getFeat1() & ADV_FEAT1_ASCON_CAPABLE) != 0;
}
}
}

void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, const uint8_t *secret,
uint8_t *data, size_t len) {
int i = matching_peer_indexes[sender_idx];
Expand Down Expand Up @@ -480,7 +506,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
// mesh::Utils::sha256((uint8_t *)&expected_ack_crc, 4, temp, 5 + text_len, self_id.pub_key,
// PUB_KEY_SIZE);

auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len);
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len, client->supports_ascon);
if (reply) {
if (client->out_path_len < 0) {
sendFlood(reply, delay_millis + SERVER_RESPONSE_DELAY);
Expand Down Expand Up @@ -537,10 +563,10 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
if (packet->isRouteFlood()) {
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len, client->supports_ascon);
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
} else {
mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len);
mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len, client->supports_ascon);
if (reply) {
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY);
Expand Down
4 changes: 3 additions & 1 deletion examples/simple_room_server/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,11 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
}

bool allowPacketForward(const mesh::Packet* packet) override;
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len, bool was_ascon) override;
int searchPeersByHash(const uint8_t* hash) override ;
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override;
void onPeerAsconCapabilityDetected(int peer_idx, bool supports_ascon) override;
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override;
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override;
Expand Down
Loading