diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 5c19d7a1..b5d70edc 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -154,7 +154,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56 file.read((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60 file.read((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61 - file.read((uint8_t *)&_prefs.reserved1, sizeof(_prefs.reserved1)); // 62 + file.read(pad, 1); // 62 file.read((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63 file.read((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64 file.read((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 @@ -163,7 +163,8 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env)); // 71 file.read((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 file.read((uint8_t *)&_prefs.advert_loc_policy, sizeof(_prefs.advert_loc_policy)); // 76 - file.read(pad, 3); // 77 + file.read((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77 + file.read(pad, 2); // 78 file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 file.close(); @@ -184,7 +185,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56 file.write((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60 file.write((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61 - file.write((uint8_t *)&_prefs.reserved1, sizeof(_prefs.reserved1)); // 62 + file.write(pad, 1); // 62 file.write((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63 file.write((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64 file.write((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 @@ -193,7 +194,8 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env)); // 71 file.write((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 file.write((uint8_t *)&_prefs.advert_loc_policy, sizeof(_prefs.advert_loc_policy)); // 76 - file.write(pad, 3); // 77 + file.write((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77 + file.write(pad, 2); // 78 file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 file.close(); diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 9331a50c..939c93de 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -210,6 +210,10 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const { return (int)((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time); } +uint8_t MyMesh::getExtraAckTransmitCount() const { + return _prefs.multi_acks; +} + void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) { if (_serial->isConnected() && len + 3 <= MAX_FRAME_SIZE) { int i = 0; @@ -719,7 +723,7 @@ void MyMesh::handleCmdFrame(size_t len) { i += 4; memcpy(&out_frame[i], &lon, 4); i += 4; - out_frame[i++] = 0; // reserved + out_frame[i++] = _prefs.multi_acks; // new v7+ out_frame[i++] = _prefs.advert_loc_policy; out_frame[i++] = (_prefs.telemetry_mode_env << 4) | (_prefs.telemetry_mode_loc << 2) | (_prefs.telemetry_mode_base); // v5+ @@ -1050,6 +1054,9 @@ void MyMesh::handleCmdFrame(size_t len) { if (len >= 4) { _prefs.advert_loc_policy = cmd_frame[3]; + if (len >= 5) { + _prefs.multi_acks = cmd_frame[4]; + } } } savePrefs(); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 81bf261e..cd4a72dc 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -97,6 +97,7 @@ protected: float getAirtimeBudgetFactor() const override; int getInterferenceThreshold() const override; int calcRxDelay(float score, uint32_t air_time) const override; + uint8_t getExtraAckTransmitCount() const override; void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override; bool isAutoAddEnabled() const override; diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index bf4739e4..bfde7218 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -14,7 +14,7 @@ struct NodePrefs { // persisted to file float freq; uint8_t sf; uint8_t cr; - uint8_t reserved1; + uint8_t multi_acks; uint8_t manual_add_contacts; float bw; uint8_t tx_power_dbm; diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index 1eb5be8e..a7f03a26 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -408,8 +408,12 @@ void UITask::handleButtonQuadruplePress() { if (strcmp(_sensors->getSettingName(i), "gps") == 0) { if (strcmp(_sensors->getSettingValue(i), "1") == 0) { _sensors->setSettingValue("gps", "0"); + soundBuzzer(UIEventType::ack); + sprintf(_alert, "GPS: Disabled"); } else { _sensors->setSettingValue("gps", "1"); + soundBuzzer(UIEventType::ack); + sprintf(_alert, "GPS: Enabled"); } break; } diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 2ec94c4b..cee3a008 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -339,6 +339,9 @@ protected: int getAGCResetInterval() const override { return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds } + uint8_t getExtraAckTransmitCount() const override { + return _prefs.multi_acks; + } void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override { if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage) diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index b8f74a0c..1ab08d9b 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -424,6 +424,9 @@ protected: int getAGCResetInterval() const override { return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds } + uint8_t getExtraAckTransmitCount() const override { + return _prefs.multi_acks; + } bool allowPacketForward(const mesh::Packet* packet) override { if (_prefs.disable_fwd) return false; @@ -578,15 +581,22 @@ protected: uint32_t delay_millis; if (send_ack) { - mesh::Packet* ack = createAck(ack_hash); - if (ack) { - if (client->out_path_len < 0) { - sendFlood(ack, TXT_ACK_DELAY); - } else { - sendDirect(ack, client->out_path, client->out_path_len, TXT_ACK_DELAY); + if (client->out_path_len < 0) { + mesh::Packet* ack = createAck(ack_hash); + if (ack) sendFlood(ack, TXT_ACK_DELAY); + delay_millis = TXT_ACK_DELAY + REPLY_DELAY_MILLIS; + } else { + uint32_t d = TXT_ACK_DELAY; + if (getExtraAckTransmitCount() > 0) { + mesh::Packet* a1 = createMultiAck(ack_hash, 1); + if (a1) sendDirect(a1, client->out_path, client->out_path_len, d); + d += 300; } + + mesh::Packet* a2 = createAck(ack_hash); + if (a2) sendDirect(a2, client->out_path, client->out_path_len, d); + delay_millis = d + REPLY_DELAY_MILLIS; } - delay_millis = TXT_ACK_DELAY + REPLY_DELAY_MILLIS; } else { delay_millis = 0; } diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 5bbe9f4d..abdc7182 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -243,7 +243,7 @@ static uint8_t putFloat(uint8_t * dest, float value, uint8_t size, uint32_t mult uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len) { memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag') - if (req_type == REQ_TYPE_GET_TELEMETRY_DATA && (perms & PERM_GET_TELEMETRY) != 0) { + if (req_type == REQ_TYPE_GET_TELEMETRY_DATA) { // allow all telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); // query other sensors -- target specific @@ -254,7 +254,7 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint memcpy(&reply_data[4], telemetry.getBuffer(), tlen); return 4 + tlen; // reply_len } - if (req_type == REQ_TYPE_GET_AVG_MIN_MAX && (perms & PERM_GET_OTHER_STATS) != 0) { + if (req_type == REQ_TYPE_GET_AVG_MIN_MAX && (perms & PERM_ACL_ROLE_MASK) >= PERM_ACL_READ_ONLY) { uint32_t start_secs_ago, end_secs_ago; memcpy(&start_secs_ago, &payload[0], 4); memcpy(&end_secs_ago, &payload[4], 4); @@ -288,13 +288,14 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint } return ofs; } - if (req_type == REQ_TYPE_GET_ACCESS_LIST && (perms & PERM_ACL_ROLE_MASK) == PERM_ACL_LEVEL3) { + if (req_type == REQ_TYPE_GET_ACCESS_LIST && (perms & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN) { uint8_t res1 = payload[0]; // reserved for future (extra query params) uint8_t res2 = payload[1]; if (res1 == 0 && res2 == 0) { uint8_t ofs = 4; for (int i = 0; i < num_contacts && ofs + 7 <= sizeof(reply_data) - 4; i++) { auto c = &contacts[i]; + if (c->permissions == 0) continue; // skip deleted entries memcpy(&reply_data[ofs], c->id.pub_key, 6); ofs += 6; // just 6-byte pub_key prefix reply_data[ofs++] = c->permissions; } @@ -315,7 +316,14 @@ mesh::Packet* SensorMesh::createSelfAdvert() { return createAdvert(self_id, app_data, app_data_len); } -ContactInfo* SensorMesh::putContact(const mesh::Identity& id) { +ContactInfo* SensorMesh::getContact(const uint8_t* pubkey, int key_len) { + for (int i = 0; i < num_contacts; i++) { + if (memcmp(pubkey, contacts[i].id.pub_key, key_len) == 0) return &contacts[i]; // already known + } + return NULL; // not found +} + +ContactInfo* SensorMesh::putContact(const mesh::Identity& id, uint8_t init_perms) { uint32_t min_time = 0xFFFFFFFF; ContactInfo* oldest = &contacts[MAX_CONTACTS - 1]; for (int i = 0; i < num_contacts; i++) { @@ -333,22 +341,35 @@ ContactInfo* SensorMesh::putContact(const mesh::Identity& id) { c = oldest; // evict least active contact } memset(c, 0, sizeof(*c)); + c->permissions = init_perms; c->id = id; c->out_path_len = -1; // initially out_path is unknown return c; } -void SensorMesh::applyContactPermissions(const uint8_t* pubkey, uint8_t perms) { - mesh::Identity id(pubkey); - auto c = putContact(id); - +bool SensorMesh::applyContactPermissions(const uint8_t* pubkey, int key_len, uint8_t perms) { + ContactInfo* c; if ((perms & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { // guest role is not persisted in contacts - memset(c, 0, sizeof(*c)); + c = getContact(pubkey, key_len); + if (c == NULL) return false; // partial pubkey not found + + num_contacts--; // delete from contacts[] + int i = c - contacts; + while (i < num_contacts) { + contacts[i] = contacts[i + 1]; + i++; + } } else { + if (key_len < PUB_KEY_SIZE) return false; // need complete pubkey when adding/modifying + + mesh::Identity id(pubkey); + c = putContact(id, 0); + c->permissions = perms; // update their permissions self_id.calcSharedSecret(c->shared_secret, pubkey); } dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // trigger saveContacts() + return true; } void SensorMesh::sendAlert(ContactInfo* c, Trigger* t) { @@ -434,32 +455,43 @@ int SensorMesh::getAGCResetInterval() const { } uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) { - if (strcmp((char *) data, _prefs.password) != 0) { // check for valid password - #if MESH_DEBUG - MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]); - #endif - return 0; + ContactInfo* client; + if (data[0] == 0) { // blank password, just check if sender is in ACL + client = getContact(sender.pub_key, PUB_KEY_SIZE); + if (client == NULL) { + #if MESH_DEBUG + MESH_DEBUG_PRINTLN("Login, sender not in ACL"); + #endif + return 0; + } + } else { + if (strcmp((char *) data, _prefs.password) != 0) { // check for valid admin password + #if MESH_DEBUG + MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]); + #endif + return 0; + } + + client = putContact(sender, PERM_RECV_ALERTS_HI | PERM_RECV_ALERTS_LO); // add to contacts (if not already known) + if (sender_timestamp <= client->last_timestamp) { + MESH_DEBUG_PRINTLN("Possible login replay attack!"); + return 0; // FATAL: client table is full -OR- replay attack + } + + MESH_DEBUG_PRINTLN("Login success!"); + client->last_timestamp = sender_timestamp; + client->last_activity = getRTCClock()->getCurrentTime(); + client->permissions |= PERM_ACL_ADMIN; + memcpy(client->shared_secret, secret, PUB_KEY_SIZE); + + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); } - auto client = putContact(sender); // add to contacts (if not already known) - if (sender_timestamp <= client->last_timestamp) { - MESH_DEBUG_PRINTLN("Possible login replay attack!"); - return 0; // FATAL: client table is full -OR- replay attack - } - - MESH_DEBUG_PRINTLN("Login success!"); - client->last_timestamp = sender_timestamp; - client->last_activity = getRTCClock()->getCurrentTime(); - client->permissions = PERM_ACL_LEVEL3 | PERM_RECV_ALERTS_HI | PERM_RECV_ALERTS_LO; // initially opt-in to receive alerts (can opt out) - memcpy(client->shared_secret, secret, PUB_KEY_SIZE); - - dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); - uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp reply_data[4] = RESP_SERVER_LOGIN_OK; reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16) - reply_data[6] = 1; // 1 = is admin + reply_data[6] = client->isAdmin() ? 1 : 0; reply_data[7] = client->permissions; getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness @@ -484,16 +516,20 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r if (memcmp(command, "setperm ", 8) == 0) { // format: setperm {pubkey-hex} {permissions-int8} char* hex = &command[8]; char* sp = strchr(hex, ' '); // look for separator char - if (sp == NULL || sp - hex != PUB_KEY_SIZE*2) { - strcpy(reply, "Err - bad pubkey len"); + if (sp == NULL) { + strcpy(reply, "Err - bad params"); } else { *sp++ = 0; // replace space with null terminator uint8_t pubkey[PUB_KEY_SIZE]; - if (mesh::Utils::fromHex(pubkey, PUB_KEY_SIZE, hex)) { + int hex_len = min(sp - hex, PUB_KEY_SIZE*2); + if (mesh::Utils::fromHex(pubkey, hex_len / 2, hex)) { uint8_t perms = atoi(sp); - applyContactPermissions(pubkey, perms); - strcpy(reply, "OK"); + if (applyContactPermissions(pubkey, hex_len / 2, perms)) { + strcpy(reply, "OK"); + } else { + strcpy(reply, "Err - invalid params"); + } } else { strcpy(reply, "Err - bad pubkey"); } @@ -502,6 +538,7 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r Serial.println("ACL:"); for (int i = 0; i < num_contacts; i++) { auto c = &contacts[i]; + if (c->permissions == 0) continue; // skip deleted entries Serial.printf("%02X ", c->permissions); mesh::Utils::printHex(Serial, c->id.pub_key, PUB_KEY_SIZE); @@ -569,7 +606,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i memcpy(×tamp, data, 4); if (timestamp > from.last_timestamp) { // prevent replay attacks - uint8_t reply_len = handleRequest(from.isAdmin() ? 0xFFFF : from.permissions, timestamp, data[4], &data[5], len - 5); + uint8_t reply_len = handleRequest(from.isAdmin() ? 0xFF : from.permissions, timestamp, data[4], &data[5], len - 5); if (reply_len == 0) return; // invalid command from.last_timestamp = timestamp; diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index a357506f..3f687c3f 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -25,14 +25,14 @@ #define PERM_ACL_ROLE_MASK 3 // lower 2 bits #define PERM_ACL_GUEST 0 -#define PERM_ACL_LEVEL1 1 -#define PERM_ACL_LEVEL2 2 -#define PERM_ACL_LEVEL3 3 // admin +#define PERM_ACL_READ_ONLY 1 +#define PERM_ACL_READ_WRITE 2 +#define PERM_ACL_ADMIN 3 -#define PERM_GET_TELEMETRY (1 << 2) -#define PERM_GET_OTHER_STATS (1 << 3) -#define PERM_RESERVED1 (1 << 4) -#define PERM_RESERVED2 (1 << 5) +#define PERM_RESERVED1 (1 << 2) +#define PERM_RESERVED2 (1 << 3) +#define PERM_RESERVED3 (1 << 4) +#define PERM_RESERVED4 (1 << 5) #define PERM_RECV_ALERTS_LO (1 << 6) // low priority alerts #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts @@ -45,7 +45,7 @@ struct ContactInfo { uint32_t last_timestamp; // by THEIR clock (transient) uint32_t last_activity; // by OUR clock (transient) - bool isAdmin() const { return (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_LEVEL3; } + bool isAdmin() const { return (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN; } }; #ifndef FIRMWARE_BUILD_DATE @@ -160,8 +160,9 @@ private: uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data); uint8_t handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); - ContactInfo* putContact(const mesh::Identity& id); - void applyContactPermissions(const uint8_t* pubkey, uint8_t perms); + ContactInfo* getContact(const uint8_t* pubkey, int key_len); + ContactInfo* putContact(const mesh::Identity& id, uint8_t init_perms); + bool applyContactPermissions(const uint8_t* pubkey, int key_len, uint8_t perms); void sendAlert(ContactInfo* c, Trigger* t); diff --git a/src/Mesh.cpp b/src/Mesh.cpp index f34f6f77..b055d811 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -22,6 +22,9 @@ uint32_t Mesh::getRetransmitDelay(const mesh::Packet* packet) { uint32_t Mesh::getDirectRetransmitDelay(const Packet* packet) { return 0; // by default, no delay } +uint8_t Mesh::getExtraAckTransmitCount() const { + return 0; +} uint32_t Mesh::getCADFailRetryDelay() const { return _rng->nextInt(1, 4)*120; @@ -67,22 +70,22 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) { if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) { - if (_tables->hasSeen(pkt)) return ACTION_RELEASE; // don't retransmit! - - // remove our hash from 'path', then re-broadcast - pkt->path_len -= PATH_HASH_SIZE; - #if 0 - memcpy(pkt->path, &pkt->path[PATH_HASH_SIZE], pkt->path_len); - #elif PATH_HASH_SIZE == 1 - for (int k = 0; k < pkt->path_len; k++) { // shuffle bytes by 1 - pkt->path[k] = pkt->path[k + 1]; + if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) { + return forwardMultipartDirect(pkt); + } else if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { + if (!_tables->hasSeen(pkt)) { // don't retransmit! + removeSelfFromPath(pkt); + routeDirectRecvAcks(pkt, 0); + } + return ACTION_RELEASE; } - #else - #error "need path remove impl" - #endif - uint32_t d = getDirectRetransmitDelay(pkt); - return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority + if (!_tables->hasSeen(pkt)) { + removeSelfFromPath(pkt); + + uint32_t d = getDirectRetransmitDelay(pkt); + return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority + } } return ACTION_RELEASE; // this node is NOT the next hop (OR this packet has already been forwarded), so discard. } @@ -261,6 +264,32 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { } break; } + case PAYLOAD_TYPE_MULTIPART: + if (pkt->payload_len > 2) { + uint8_t remaining = pkt->payload[0] >> 4; // num of packets in this multipart sequence still to be sent + uint8_t type = pkt->payload[0] & 0x0F; + + if (type == PAYLOAD_TYPE_ACK && pkt->payload_len >= 5) { // a multipart ACK + Packet tmp; + tmp.header = pkt->header; + tmp.path_len = pkt->path_len; + memcpy(tmp.path, pkt->path, pkt->path_len); + tmp.payload_len = pkt->payload_len - 1; + memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len); + + if (!_tables->hasSeen(&tmp)) { + uint32_t ack_crc; + memcpy(&ack_crc, tmp.payload, 4); + + onAckRecv(&tmp, ack_crc); + //action = routeRecvPacket(&tmp); // NOTE: currently not needed, as multipart ACKs not sent Flood + } + } else { + // FUTURE: other multipart types?? + } + } + break; + default: MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): unknown payload type, header: %d", getLogDateTime(), (int) pkt->header); // Don't flood route unknown packet types! action = routeRecvPacket(pkt); @@ -269,6 +298,20 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { return action; } +void Mesh::removeSelfFromPath(Packet* pkt) { + // remove our hash from 'path' + pkt->path_len -= PATH_HASH_SIZE; +#if 0 + memcpy(pkt->path, &pkt->path[PATH_HASH_SIZE], pkt->path_len); +#elif PATH_HASH_SIZE == 1 + for (int k = 0; k < pkt->path_len; k++) { // shuffle bytes by 1 + pkt->path[k] = pkt->path[k + 1]; + } +#else + #error "need path remove impl" +#endif +} + DispatcherAction Mesh::routeRecvPacket(Packet* packet) { if (packet->isRouteFlood() && !packet->isMarkedDoNotRetransmit() && packet->path_len + PATH_HASH_SIZE <= MAX_PATH_SIZE && allowPacketForward(packet)) { @@ -282,6 +325,54 @@ DispatcherAction Mesh::routeRecvPacket(Packet* packet) { return ACTION_RELEASE; } +DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt) { + uint8_t remaining = pkt->payload[0] >> 4; // num of packets in this multipart sequence still to be sent + uint8_t type = pkt->payload[0] & 0x0F; + + if (type == PAYLOAD_TYPE_ACK && pkt->payload_len >= 5) { // a multipart ACK + Packet tmp; + tmp.header = pkt->header; + tmp.path_len = pkt->path_len; + memcpy(tmp.path, pkt->path, pkt->path_len); + tmp.payload_len = pkt->payload_len - 1; + memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len); + + if (!_tables->hasSeen(&tmp)) { // don't retransmit! + removeSelfFromPath(&tmp); + routeDirectRecvAcks(&tmp, ((uint32_t)remaining + 1) * 300); // expect multipart ACKs 300ms apart (x2) + } + } + return ACTION_RELEASE; +} + +void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) { + if (!packet->isMarkedDoNotRetransmit()) { + uint32_t crc; + memcpy(&crc, packet->payload, 4); + + uint8_t extra = getExtraAckTransmitCount(); + while (extra > 0) { + delay_millis += getDirectRetransmitDelay(packet) + 300; + auto a1 = createMultiAck(crc, extra); + if (a1) { + memcpy(a1->path, packet->path, a1->path_len = packet->path_len); + a1->header &= ~PH_ROUTE_MASK; + a1->header |= ROUTE_TYPE_DIRECT; + sendPacket(a1, 0, delay_millis); + } + extra--; + } + + auto a2 = createAck(crc); + if (a2) { + memcpy(a2->path, packet->path, a2->path_len = packet->path_len); + a2->header &= ~PH_ROUTE_MASK; + a2->header |= ROUTE_TYPE_DIRECT; + sendPacket(a2, 0, delay_millis); + } + } +} + Packet* Mesh::createAdvert(const LocalIdentity& id, const uint8_t* app_data, size_t app_data_len) { if (app_data_len > MAX_ADVERT_DATA_SIZE) return NULL; @@ -449,6 +540,21 @@ Packet* Mesh::createAck(uint32_t ack_crc) { return packet; } +Packet* Mesh::createMultiAck(uint32_t ack_crc, uint8_t remaining) { + Packet* packet = obtainNewPacket(); + if (packet == NULL) { + MESH_DEBUG_PRINTLN("%s Mesh::createMultiAck(): error, packet pool empty", getLogDateTime()); + return NULL; + } + packet->header = (PAYLOAD_TYPE_MULTIPART << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later + + packet->payload[0] = (remaining << 4) | PAYLOAD_TYPE_ACK; + memcpy(&packet->payload[1], &ack_crc, 4); + packet->payload_len = 5; + + return packet; +} + Packet* Mesh::createRawData(const uint8_t* data, size_t len) { if (len > sizeof(Packet::payload)) return NULL; // invalid arg diff --git a/src/Mesh.h b/src/Mesh.h index b2047ac1..a8fdb2a4 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -28,6 +28,11 @@ class Mesh : public Dispatcher { RNG* _rng; MeshTables* _tables; + void removeSelfFromPath(Packet* packet); + void routeDirectRecvAcks(Packet* packet, uint32_t delay_millis); + //void routeRecvAcks(Packet* packet, uint32_t delay_millis); + DispatcherAction forwardMultipartDirect(Packet* pkt); + protected: DispatcherAction onRecvPacket(Packet* pkt) override; @@ -54,6 +59,11 @@ protected: */ virtual uint32_t getDirectRetransmitDelay(const Packet* packet); + /** + * \returns number of extra (Direct) ACK transmissions wanted. + */ + virtual uint8_t getExtraAckTransmitCount() const; + /** * \brief Perform search of local DB of peers/contacts. * \returns Number of peers with matching hash @@ -165,6 +175,7 @@ public: Packet* createAnonDatagram(uint8_t type, const LocalIdentity& sender, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len); Packet* createGroupDatagram(uint8_t type, const GroupChannel& channel, const uint8_t* data, size_t data_len); Packet* createAck(uint32_t ack_crc); + Packet* createMultiAck(uint32_t ack_crc, uint8_t remaining); Packet* createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len); Packet* createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len); Packet* createRawData(const uint8_t* data, size_t len); diff --git a/src/Packet.h b/src/Packet.h index 7c7df8e3..e52ab526 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -26,6 +26,7 @@ namespace mesh { #define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...) #define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra) #define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNI for each hop +#define PAYLOAD_TYPE_MULTIPART 0x0A // packet is one of a set of packets //... #define PAYLOAD_TYPE_RAW_CUSTOM 0x0F // custom packet as raw bytes, for applications with custom encryption, payloads, etc diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index d6ba17b9..476e6e8f 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -31,6 +31,23 @@ mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name, double lat, doubl return createAdvert(self_id, app_data, app_data_len); } +void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) { + if (dest.out_path_len < 0) { + mesh::Packet* ack = createAck(ack_hash); + if (ack) sendFlood(ack, TXT_ACK_DELAY); + } else { + uint32_t d = TXT_ACK_DELAY; + if (getExtraAckTransmitCount() > 0) { + mesh::Packet* a1 = createMultiAck(ack_hash, 1); + if (a1) sendDirect(a1, dest.out_path, dest.out_path_len, d); + d += 300; + } + + mesh::Packet* a2 = createAck(ack_hash); + if (a2) sendDirect(a2, dest.out_path, dest.out_path_len, d); + } +} + void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) { AdvertDataParser parser(app_data, app_data_len); if (!(parser.isValid() && parser.hasName())) { @@ -152,14 +169,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); if (path) sendFlood(path, TXT_ACK_DELAY); } else { - mesh::Packet* ack = createAck(ack_hash); - if (ack) { - if (from.out_path_len < 0) { - sendFlood(ack, TXT_ACK_DELAY); - } else { - sendDirect(ack, from.out_path, from.out_path_len, TXT_ACK_DELAY); - } - } + sendAckTo(from, ack_hash); } } else if (flags == TXT_TYPE_CLI_DATA) { onCommandDataRecv(from, packet, timestamp, (const char *) &data[5]); // let UI know @@ -185,14 +195,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); if (path) sendFlood(path, TXT_ACK_DELAY); } else { - mesh::Packet* ack = createAck(ack_hash); - if (ack) { - if (from.out_path_len < 0) { - sendFlood(ack, TXT_ACK_DELAY); - } else { - sendDirect(ack, from.out_path, from.out_path_len, TXT_ACK_DELAY); - } - } + sendAckTo(from, ack_hash); } } else { MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported message type: %u", (uint32_t) flags); diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 903b4ece..683af852 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -72,6 +72,7 @@ class BaseChatMesh : public mesh::Mesh { ConnectionInfo connections[MAX_CONNECTIONS]; mesh::Packet* composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack); + void sendAckTo(const ContactInfo& dest, uint32_t ack_hash); protected: BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index df510c28..afb7dfbc 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -51,7 +51,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *) &_prefs->sf, sizeof(_prefs->sf)); // 112 file.read((uint8_t *) &_prefs->cr, sizeof(_prefs->cr)); // 113 file.read((uint8_t *) &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114 - file.read((uint8_t *) &_prefs->reserved2, sizeof(_prefs->reserved2)); // 115 + file.read((uint8_t *) &_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115 file.read((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116 file.read((uint8_t *) &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120 file.read(pad, 3); // 121 @@ -69,6 +69,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->sf = constrain(_prefs->sf, 7, 12); _prefs->cr = constrain(_prefs->cr, 5, 8); _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30); + _prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1); file.close(); } @@ -106,7 +107,7 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *) &_prefs->sf, sizeof(_prefs->sf)); // 112 file.write((uint8_t *) &_prefs->cr, sizeof(_prefs->cr)); // 113 file.write((uint8_t *) &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114 - file.write((uint8_t *) &_prefs->reserved2, sizeof(_prefs->reserved2)); // 115 + file.write((uint8_t *) &_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115 file.write((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116 file.write((uint8_t *) &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120 file.write(pad, 3); // 121 @@ -180,6 +181,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold); } else if (memcmp(config, "agc.reset.interval", 18) == 0) { sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4); + } else if (memcmp(config, "multi.acks", 10) == 0) { + sprintf(reply, "> %d", (uint32_t) _prefs->multi_acks); } else if (memcmp(config, "allow.read.only", 15) == 0) { sprintf(reply, "> %s", _prefs->allow_read_only ? "on" : "off"); } else if (memcmp(config, "flood.advert.interval", 21) == 0) { @@ -235,6 +238,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->agc_reset_interval = atoi(&config[19]) / 4; savePrefs(); strcpy(reply, "OK"); + } else if (memcmp(config, "multi.acks ", 11) == 0) { + _prefs->multi_acks = atoi(&config[11]); + savePrefs(); + strcpy(reply, "OK"); } else if (memcmp(config, "allow.read.only ", 16) == 0) { _prefs->allow_read_only = memcmp(&config[16], "on", 2) == 0; savePrefs(); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index b50bf7f3..91a5bd3b 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -21,7 +21,7 @@ struct NodePrefs { // persisted to file uint8_t sf; uint8_t cr; uint8_t allow_read_only; - uint8_t reserved2; + uint8_t multi_acks; float bw; uint8_t flood_max; uint8_t interference_threshold; diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index ab5b459e..c54e45c2 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -162,7 +162,7 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen #if ENV_INCLUDE_BMP280 if (BMP280_initialized) { telemetry.addTemperature(TELEM_CHANNEL_SELF, BMP280.readTemperature()); - telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BMP280.readPressure()); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BMP280.readPressure()/100); telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA)); } #endif @@ -332,4 +332,4 @@ void EnvironmentSensorManager::loop() { next_gps_update = millis() + 1000; } } -#endif \ No newline at end of file +#endif diff --git a/variants/wio-tracker-l1/variant.h b/variants/wio-tracker-l1/variant.h index 7f51dcb8..9d6eb532 100644 --- a/variants/wio-tracker-l1/variant.h +++ b/variants/wio-tracker-l1/variant.h @@ -70,6 +70,8 @@ #define P_LORA_NSS (4) #define SX126X_RXEN (5) #define SX126X_TXEN RADIOLIB_NC +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE (1.8f) // Wire Interfaces #define WIRE_INTERFACES_COUNT (2)