diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index a607f202..34c284e1 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -143,11 +143,93 @@ struct ServerStats { uint16_t n_posted, n_post_push; }; -class MyMesh : public mesh::Mesh, public CommonCLICallbacks { +class MyMesh : public mesh::Mesh { + +protected: + // float getAirtimeBudgetFactor() const override { + // return _prefs.airtime_factor; + // } + + // void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override { + // #if MESH_PACKET_LOGGING + // Serial.print(getLogDateTime()); + // Serial.print(" RAW: "); + // mesh::Utils::printHex(Serial, raw, len); + // Serial.println(); + // #endif + // } + + // void logRx(mesh::Packet* pkt, int len, float score) override { + // if (_logging) { + // File f = openAppend(PACKET_LOG_FILE); + // if (f) { + // f.print(getLogDateTime()); + // f.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d", + // len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len, + // (int)_radio->getLastSNR(), (int)_radio->getLastRSSI(), (int)(score*1000)); + + // if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ + // || pkt->getPayloadType() == PAYLOAD_TYPE_RESPONSE || pkt->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { + // f.printf(" [%02X -> %02X]\n", (uint32_t)pkt->payload[1], (uint32_t)pkt->payload[0]); + // } else { + // f.printf("\n"); + // } + // f.close(); + // } + // } + // } + // void logTx(mesh::Packet* pkt, int len) override { + // if (_logging) { + // File f = openAppend(PACKET_LOG_FILE); + // if (f) { + // f.print(getLogDateTime()); + // f.printf(": TX, len=%d (type=%d, route=%s, payload_len=%d)", + // len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len); + + // if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ + // || pkt->getPayloadType() == PAYLOAD_TYPE_RESPONSE || pkt->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { + // f.printf(" [%02X -> %02X]\n", (uint32_t)pkt->payload[1], (uint32_t)pkt->payload[0]); + // } else { + // f.printf("\n"); + // } + // f.close(); + // } + // } + // } + // void logTxFail(mesh::Packet* pkt, int len) override { + // if (_logging) { + // File f = openAppend(PACKET_LOG_FILE); + // if (f) { + // f.print(getLogDateTime()); + // f.printf(": TX FAIL!, len=%d (type=%d, route=%s, payload_len=%d)\n", + // len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len); + // f.close(); + // } + // } + // } + + // int calcRxDelay(float score, uint32_t air_time) const override { + // if (_prefs.rx_delay_base <= 0.0f) return 0; + // return (int) ((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time); + // } + + // const char* getLogDateTime() override { + // static char tmp[32]; + // uint32_t now = getRTCClock()->getCurrentTime(); + // DateTime dt = DateTime(now); + // sprintf(tmp, "%02d:%02d:%02d - %d/%d/%d U", dt.hour(), dt.minute(), dt.second(), dt.day(), dt.month(), dt.year()); + // return tmp; + // } + +public: + MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables) + : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables) {} +}; + +class RoomNode : public mesh::MeshNode, public CommonCLICallbacks { FILESYSTEM* _fs; unsigned long next_local_advert, next_flood_advert; bool _logging; - NodePrefs _prefs; CommonCLI _cli; uint8_t reply_data[MAX_PACKET_PAYLOAD]; int num_clients; @@ -196,10 +278,10 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { posts[next_post_idx].author = client->id; // add to cyclic queue StrHelper::strncpy(posts[next_post_idx].text, postData, MAX_POST_TEXT_LEN); - posts[next_post_idx].post_timestamp = getRTCClock()->getCurrentTimeUnique(); + posts[next_post_idx].post_timestamp = mesh->getRTCClock()->getCurrentTimeUnique(); next_post_idx = (next_post_idx + 1) % MAX_UNSYNCED_POSTS; - next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); + next_push = mesh->futureMillis(PUSH_NOTIFY_DELAY_MILLIS); _num_posted++; // stats } @@ -218,14 +300,14 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { mesh::Utils::sha256((uint8_t *)&client->pending_ack, 4, reply_data, len, client->id.pub_key, PUB_KEY_SIZE); client->push_post_timestamp = post.post_timestamp; - auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->secret, reply_data, len); + auto reply = mesh->createDatagram(PAYLOAD_TYPE_TXT_MSG, self_id, client->id, client->secret, reply_data, len); if (reply) { if (client->out_path_len < 0) { - sendFlood(reply); - client->ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD); + mesh->sendFlood(reply); + client->ack_timeout = mesh->futureMillis(PUSH_ACK_TIMEOUT_FLOOD); } else { - sendDirect(reply, client->out_path, client->out_path_len); - client->ack_timeout = futureMillis(PUSH_TIMEOUT_BASE + PUSH_ACK_TIMEOUT_FACTOR * (client->out_path_len + 1)); + mesh->sendDirect(reply, client->out_path, client->out_path_len); + client->ack_timeout = mesh->futureMillis(PUSH_TIMEOUT_BASE + PUSH_ACK_TIMEOUT_FACTOR * (client->out_path_len + 1)); } _num_post_pushes++; // stats } else { @@ -266,7 +348,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { app_data_len = builder.encodeTo(app_data); } - return createAdvert(self_id, app_data, app_data_len); + return mesh->createAdvert(self_id, app_data, app_data_len); } File openAppend(const char* fname) { @@ -280,88 +362,13 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { } protected: - float getAirtimeBudgetFactor() const override { - return _prefs.airtime_factor; - } - - void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override { - #if MESH_PACKET_LOGGING - Serial.print(getLogDateTime()); - Serial.print(" RAW: "); - mesh::Utils::printHex(Serial, raw, len); - Serial.println(); - #endif - } - - void logRx(mesh::Packet* pkt, int len, float score) override { - if (_logging) { - File f = openAppend(PACKET_LOG_FILE); - if (f) { - f.print(getLogDateTime()); - f.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d", - len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len, - (int)_radio->getLastSNR(), (int)_radio->getLastRSSI(), (int)(score*1000)); - - if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ - || pkt->getPayloadType() == PAYLOAD_TYPE_RESPONSE || pkt->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { - f.printf(" [%02X -> %02X]\n", (uint32_t)pkt->payload[1], (uint32_t)pkt->payload[0]); - } else { - f.printf("\n"); - } - f.close(); - } - } - } - void logTx(mesh::Packet* pkt, int len) override { - if (_logging) { - File f = openAppend(PACKET_LOG_FILE); - if (f) { - f.print(getLogDateTime()); - f.printf(": TX, len=%d (type=%d, route=%s, payload_len=%d)", - len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len); - - if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ - || pkt->getPayloadType() == PAYLOAD_TYPE_RESPONSE || pkt->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { - f.printf(" [%02X -> %02X]\n", (uint32_t)pkt->payload[1], (uint32_t)pkt->payload[0]); - } else { - f.printf("\n"); - } - f.close(); - } - } - } - void logTxFail(mesh::Packet* pkt, int len) override { - if (_logging) { - File f = openAppend(PACKET_LOG_FILE); - if (f) { - f.print(getLogDateTime()); - f.printf(": TX FAIL!, len=%d (type=%d, route=%s, payload_len=%d)\n", - len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len); - f.close(); - } - } - } - - int calcRxDelay(float score, uint32_t air_time) const override { - if (_prefs.rx_delay_base <= 0.0f) return 0; - return (int) ((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time); - } - - const char* getLogDateTime() override { - static char tmp[32]; - uint32_t now = getRTCClock()->getCurrentTime(); - DateTime dt = DateTime(now); - sprintf(tmp, "%02d:%02d:%02d - %d/%d/%d U", dt.hour(), dt.minute(), dt.second(), dt.day(), dt.month(), dt.year()); - return tmp; - } - uint32_t getRetransmitDelay(const mesh::Packet* packet) override { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); - return getRNG()->nextInt(0, 6)*t; + uint32_t t = (mesh->getRadio()->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); + return mesh->getRNG()->nextInt(0, 6)*t; } uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); - return getRNG()->nextInt(0, 6)*t; + uint32_t t = (mesh->getRadio()->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); + return mesh->getRNG()->nextInt(0, 6)*t; } bool allowPacketForward(const mesh::Packet* packet) override { @@ -404,10 +411,10 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { client->pending_ack = 0; client->push_failures = 0; - uint32_t now = getRTCClock()->getCurrentTime(); + uint32_t now = mesh->getRTCClock()->getCurrentTime(); client->last_activity = now; - now = getRTCClock()->getCurrentTimeUnique(); + now = mesh->getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp // TODO: maybe reply with count of messages waiting to be synced for THIS client? reply_data[4] = RESP_SERVER_LOGIN_OK; @@ -416,20 +423,20 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { reply_data[7] = getUnsyncedCount(client); // NEW memcpy(&reply_data[8], "OK", 2); // REVISIT: not really needed - next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); // delay next push, give RESPONSE packet time to arrive first + next_push = mesh->futureMillis(PUSH_NOTIFY_DELAY_MILLIS); // delay next push, give RESPONSE packet time to arrive first 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->secret, packet->path, packet->path_len, + mesh::Packet* path = mesh->createPathReturn(self_id, sender, client->secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, 8 + 2); - if (path) sendFlood(path); + if (path) mesh->sendFlood(path); } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->secret, reply_data, 8 + 2); + mesh::Packet* reply = mesh->createDatagram(PAYLOAD_TYPE_RESPONSE, self_id, sender, client->secret, reply_data, 8 + 2); 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); + mesh->sendDirect(reply, client->out_path, client->out_path_len); } else { - sendFlood(reply); + mesh->sendFlood(reply); } } } @@ -476,7 +483,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { bool is_retry = (sender_timestamp == client->last_timestamp); client->last_timestamp = sender_timestamp; - uint32_t now = getRTCClock()->getCurrentTimeUnique(); + uint32_t now = mesh->getRTCClock()->getCurrentTimeUnique(); client->last_activity = now; client->push_failures = 0; // reset so push can resume (if prev failed) @@ -516,12 +523,12 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { uint32_t delay_millis; if (send_ack) { - mesh::Packet* ack = createAck(ack_hash); + mesh::Packet* ack = mesh->createAck(ack_hash); if (ack) { if (client->out_path_len < 0) { - sendFlood(ack); + mesh->sendFlood(ack); } else { - sendDirect(ack, client->out_path, client->out_path_len); + mesh->sendDirect(ack, client->out_path, client->out_path_len); } } delay_millis = REPLY_DELAY_MILLIS; @@ -540,12 +547,12 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { // calc expected ACK reply //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 = mesh->createDatagram(PAYLOAD_TYPE_TXT_MSG, self_id, client->id, secret, temp, 5 + text_len); if (reply) { if (client->out_path_len < 0) { - sendFlood(reply, delay_millis); + mesh->sendFlood(reply, delay_millis); } else { - sendDirect(reply, client->out_path, client->out_path_len, delay_millis); + mesh->sendDirect(reply, client->out_path, client->out_path_len, delay_millis); } } } @@ -560,7 +567,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { } else { client->last_timestamp = sender_timestamp; - uint32_t now = getRTCClock()->getCurrentTime(); + uint32_t now = mesh->getRTCClock()->getCurrentTime(); client->last_activity = now; // <-- THIS will keep client connection alive client->push_failures = 0; // reset so push can resume (if prev failed) @@ -585,50 +592,50 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { uint32_t ack_hash; // calc ACK to prove to sender that we got request mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 9, client->id.pub_key, PUB_KEY_SIZE); - auto reply = createAck(ack_hash); + auto reply = mesh->createAck(ack_hash); if (reply) { reply->payload[reply->payload_len++] = getUnsyncedCount(client); // NEW: add unsynced counter to end of ACK packet - sendDirect(reply, client->out_path, client->out_path_len); + mesh->sendDirect(reply, client->out_path, client->out_path_len); } } } else if (data[4] == REQ_TYPE_GET_STATUS) { ServerStats stats; stats.batt_milli_volts = board.getBattMilliVolts(); - stats.curr_tx_queue_len = _mgr->getOutboundCount(); - stats.curr_free_queue_len = _mgr->getFreeCount(); + stats.curr_tx_queue_len = mesh->getManager()->getOutboundCount(); + stats.curr_free_queue_len = mesh->getManager()->getFreeCount(); stats.last_rssi = (int16_t) radio_driver.getLastRSSI(); stats.n_packets_recv = radio_driver.getPacketsRecv(); stats.n_packets_sent = radio_driver.getPacketsSent(); - stats.total_air_time_secs = getTotalAirTime() / 1000; - stats.total_up_time_secs = _ms->getMillis() / 1000; - stats.n_sent_flood = getNumSentFlood(); - stats.n_sent_direct = getNumSentDirect(); - stats.n_recv_flood = getNumRecvFlood(); - stats.n_recv_direct = getNumRecvDirect(); - stats.n_full_events = getNumFullEvents(); + stats.total_air_time_secs = mesh->getTotalAirTime() / 1000; + stats.total_up_time_secs = mesh->getClock()->getMillis() / 1000; + stats.n_sent_flood = mesh->getNumSentFlood(); + stats.n_sent_direct = mesh->getNumSentDirect(); + stats.n_recv_flood = mesh->getNumRecvFlood(); + stats.n_recv_direct = mesh->getNumRecvDirect(); + stats.n_full_events = mesh->getNumFullEvents(); stats.last_snr = (int16_t)(radio_driver.getLastSNR() * 4); - stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups(); - stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups(); + stats.n_direct_dups = ((SimpleMeshTables *)mesh->getTables())->getNumDirectDups(); + stats.n_flood_dups = ((SimpleMeshTables *)mesh->getTables())->getNumFloodDups(); stats.n_posted = _num_posted; stats.n_post_push = _num_post_pushes; - now = getRTCClock()->getCurrentTimeUnique(); + now = mesh->getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp memcpy(&reply_data[4], &stats, sizeof(stats)); uint8_t reply_len = 4 + sizeof(stats); 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, + mesh::Packet* path = mesh->createPathReturn(self_id, client->id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); - if (path) sendFlood(path); + if (path) mesh->sendFlood(path); } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); + mesh::Packet* reply = mesh->createDatagram(PAYLOAD_TYPE_RESPONSE, self_id, client->id, secret, reply_data, reply_len); 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); + mesh->sendDirect(reply, client->out_path, client->out_path_len); } else { - sendFlood(reply); + mesh->sendFlood(reply); } } } @@ -665,9 +672,11 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { } public: - MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables) - : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, this, &_prefs, this) + NodePrefs _prefs; + + RoomNode(mesh::Mesh* mesh, int index) + : mesh::MeshNode(mesh) + , _cli(board, this, &_prefs, this, index) { next_local_advert = next_flood_advert = 0; _logging = false; @@ -690,9 +699,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { _prefs.advert_interval = 1; // default to 2 minutes for NEW installs _prefs.flood_advert_interval = 3; // 3 hours _prefs.flood_max = 64; - #ifdef ROOM_PASSWORD + #ifdef ROOM_PASSWORD StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password)); - #endif + #endif num_clients = 0; next_post_idx = 0; @@ -705,13 +714,12 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { CommonCLI* getCLI() { return &_cli; } void begin(FILESYSTEM* fs) { - mesh::Mesh::begin(); _fs = fs; // load persisted prefs - _cli.loadPrefs(_fs); + // _cli.loadPrefs(_fs); - radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); - radio_set_tx_power(_prefs.tx_power_dbm); + // radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); + // radio_set_tx_power(_prefs.tx_power_dbm); updateAdvertTimer(); updateFloodAdvertTimer(); @@ -720,10 +728,10 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { const char* getFirmwareVer() override { return FIRMWARE_VERSION; } const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; } const char* getRole() override { return FIRMWARE_ROLE; } - const char* getNodeName() { return _prefs.node_name; } + const char* getName() override { return _prefs.node_name; } void savePrefs() override { - _cli.savePrefs(_fs); + // _cli.savePrefs(_fs); } bool formatFileSystem() override { @@ -742,7 +750,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void sendSelfAdvertisement(int delay_millis) override { mesh::Packet* pkt = createSelfAdvert(); if (pkt) { - sendFlood(pkt, delay_millis); + mesh->sendFlood(pkt, delay_millis); } else { MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!"); } @@ -750,14 +758,14 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void updateAdvertTimer() override { if (_prefs.advert_interval > 0) { // schedule local advert timer - next_local_advert = futureMillis((uint32_t)_prefs.advert_interval * 2 * 60 * 1000); + next_local_advert = mesh->futureMillis((uint32_t)_prefs.advert_interval * 2 * 60 * 1000); } else { next_local_advert = 0; // stop the timer } } void updateFloodAdvertTimer() override { if (_prefs.flood_advert_interval > 0) { // schedule flood advert timer - next_flood_advert = futureMillis( ((uint32_t)_prefs.flood_advert_interval) * 60 * 60 * 1000); + next_flood_advert = mesh->futureMillis( ((uint32_t)_prefs.flood_advert_interval) * 60 * 60 * 1000); } else { next_flood_advert = 0; // stop the timer } @@ -789,14 +797,14 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { radio_set_tx_power(power_dbm); } - void loop() { - mesh::Mesh::loop(); + void loop() override { + mesh::MeshNode::loop(); - if (millisHasNowPassed(next_push) && num_clients > 0) { + if (mesh->millisHasNowPassed(next_push) && num_clients > 0) { // check for ACK timeouts for (int i = 0; i < num_clients; i++) { auto c = &known_clients[i]; - if (c->pending_ack && millisHasNowPassed(c->ack_timeout)) { + if (c->pending_ack && mesh->millisHasNowPassed(c->ack_timeout)) { c->push_failures++; c->pending_ack = 0; // reset (TODO: keep prev expected_ack's in a list, incase they arrive LATER, after we retry) MESH_DEBUG_PRINTLN("pending ACK timed out: push_failures: %d", (uint32_t)c->push_failures); @@ -824,22 +832,22 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { next_client_idx = (next_client_idx + 1) % num_clients; // round robin polling for each client if (did_push) { - next_push = futureMillis(SYNC_PUSH_INTERVAL); + next_push = mesh->futureMillis(SYNC_PUSH_INTERVAL); } else { // were no unsynced posts for curr client, so proccess next client much quicker! (in next loop()) - next_push = futureMillis(SYNC_PUSH_INTERVAL / 8); + next_push = mesh->futureMillis(SYNC_PUSH_INTERVAL / 8); } } - if (next_flood_advert && millisHasNowPassed(next_flood_advert)) { + if (next_flood_advert && mesh->millisHasNowPassed(next_flood_advert)) { mesh::Packet* pkt = createSelfAdvert(); - if (pkt) sendFlood(pkt); + if (pkt) mesh->sendFlood(pkt); updateFloodAdvertTimer(); // schedule next flood advert updateAdvertTimer(); // also schedule local advert (so they don't overlap) - } else if (next_local_advert && millisHasNowPassed(next_local_advert)) { + } else if (next_local_advert && mesh->millisHasNowPassed(next_local_advert)) { mesh::Packet* pkt = createSelfAdvert(); - if (pkt) sendZeroHop(pkt); + if (pkt) mesh->sendZeroHop(pkt); updateAdvertTimer(); // schedule next local advert } @@ -855,6 +863,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { StdRNG fast_rng; SimpleMeshTables tables; MyMesh the_mesh(board, radio_driver, *new ArduinoMillis(), fast_rng, rtc_clock, tables); +const int N_ROOMS = 3; +mesh::MeshNode* room_nodes[N_ROOMS]; void halt() { while (1) ; @@ -897,28 +907,67 @@ void setup() { #else #error "need to define filesystem" #endif - if (!store.load("_main", the_mesh.self_id)) { - the_mesh.self_id = radio_new_identity(); // create new random identity - int count = 0; - while (count < 10 && (the_mesh.self_id.pub_key[0] == 0x00 || the_mesh.self_id.pub_key[0] == 0xFF)) { // reserved id hashes - the_mesh.self_id = radio_new_identity(); count++; + + for (int i = 0; i < N_ROOMS; ++i) { + RoomNode* node = new RoomNode(&the_mesh, i); + + char identity_key[10]; + + char suffix[5] = "_"; + itoa(i, suffix + 1, 10); + + size_t p = strlen(suffix); + + strncpy(identity_key, "_main", 10); + strncpy(identity_key + 5, suffix, 10 - 5); + + if (!store.load(identity_key, node->self_id)) { + node->self_id = radio_new_identity(); // create new random identity + int count = 0; + while (count < 10 && (node->self_id.pub_key[0] == 0x00 || node->self_id.pub_key[0] == 0xFF)) { // reserved id hashes + node->self_id = radio_new_identity(); count++; + } + store.save(identity_key, node->self_id); } - store.save("_main", the_mesh.self_id); + + // node->self_id = radio_new_identity(); // create new random identity + + strncpy(node->_prefs.node_name, "Room", 32); + strncpy(node->_prefs.node_name + 4, suffix, 32 - 4); + + Serial.print("ROOM"); + Serial.print(" name: "); + Serial.print(node->_prefs.node_name); + Serial.print(" identity_key: "); + Serial.print(identity_key); + + Serial.print(" ID: "); + mesh::Utils::printHex(Serial, node->self_id.pub_key, PUB_KEY_SIZE); + + Serial.println(); + + node->begin(fs); + + room_nodes[i] = node; } - Serial.print("Room ID: "); - mesh::Utils::printHex(Serial, the_mesh.self_id.pub_key, PUB_KEY_SIZE); Serial.println(); + the_mesh.begin(); + radio_set_params(910.525, 250.0, 11, 5); + radio_set_tx_power(22); command[0] = 0; - the_mesh.begin(fs); - #ifdef DISPLAY_CLASS - ui_task.begin(the_mesh.getNodeName(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); + ui_task.begin(room_nodes[0]->getName(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); #endif - // send out initial Advertisement to the mesh - the_mesh.sendSelfAdvertisement(16000); + the_mesh._nodes = room_nodes; + the_mesh._node_count = N_ROOMS; + + for (int i = 0; i < N_ROOMS; ++i) { + // send out initial Advertisement to the mesh + ((RoomNode*)room_nodes[i])->sendSelfAdvertisement(16000); + } } void loop() { @@ -937,11 +986,11 @@ void loop() { if (len > 0 && command[len - 1] == '\r') { // received complete line command[len - 1] = 0; // replace newline with C string null terminator - char reply[160]; - the_mesh.getCLI()->handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial! - if (reply[0]) { - Serial.print(" -> "); Serial.println(reply); - } + // char reply[160]; + // the_mesh.getCLI()->handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial! + // if (reply[0]) { + // Serial.print(" -> "); Serial.println(reply); + // } command[0] = 0; // reset command buffer } diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 7a415f07..a65a2a9f 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -90,12 +90,13 @@ void Dispatcher::checkRecv() { if (pkt == NULL) { MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): WARNING: received data, no unused packets available!", getLogDateTime()); } else { + _mgr->take(pkt); int i = 0; #ifdef NODE_ID uint8_t sender_id = raw[i++]; if (sender_id == NODE_ID - 1 || sender_id == NODE_ID + 1) { // simulate that NODE_ID can only hear NODE_ID-1 or NODE_ID+1, eg. 3 can't hear 1 } else { - _mgr->free(pkt); // put back into pool + _mgr->release(pkt); // put back into pool return; } #endif @@ -111,7 +112,7 @@ void Dispatcher::checkRecv() { if (pkt->path_len > MAX_PATH_SIZE || i + pkt->path_len > len) { MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): partial or corrupt packet received, len=%d", getLogDateTime(), len); - _mgr->free(pkt); // put back into pool + _mgr->release(pkt); // put back into pool pkt = NULL; } else { memcpy(pkt->path, &raw[i], pkt->path_len); i += pkt->path_len; @@ -119,7 +120,7 @@ void Dispatcher::checkRecv() { pkt->payload_len = len - i; // payload is remainder if (pkt->payload_len > sizeof(pkt->payload)) { MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): packet payload too big, payload_len=%d", getLogDateTime(), (uint32_t)pkt->payload_len); - _mgr->free(pkt); // put back into pool + _mgr->release(pkt); // put back into pool pkt = NULL; } else { memcpy(pkt->payload, &raw[i], pkt->payload_len); @@ -179,7 +180,7 @@ void Dispatcher::checkRecv() { void Dispatcher::processRecvPacket(Packet* pkt) { DispatcherAction action = onRecvPacket(pkt); if (action == ACTION_RELEASE) { - _mgr->free(pkt); + _mgr->release(pkt); } else if (action == ACTION_MANUAL_HOLD) { // sub-class is wanting to manually hold Packet instance, and call releasePacket() at appropriate time } else { // ACTION_RETRANSMIT* @@ -227,7 +228,7 @@ void Dispatcher::checkSend() { if (len + outbound->payload_len > MAX_TRANS_UNIT) { MESH_DEBUG_PRINTLN("%s Dispatcher::checkSend(): FATAL: Invalid packet queued... too long, len=%d", getLogDateTime(), len + outbound->payload_len); - _mgr->free(outbound); + _mgr->release(outbound); outbound = NULL; } else { memcpy(&raw[len], outbound->payload, outbound->payload_len); len += outbound->payload_len; @@ -257,20 +258,25 @@ Packet* Dispatcher::obtainNewPacket() { if (pkt == NULL) { n_full_events++; } else { + takePacket(pkt); pkt->payload_len = pkt->path_len = 0; pkt->_snr = 0; } return pkt; } +void Dispatcher::takePacket(Packet* packet) { + _mgr->take(packet); +} + void Dispatcher::releasePacket(Packet* packet) { - _mgr->free(packet); + _mgr->release(packet); } void Dispatcher::sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis) { if (packet->path_len > MAX_PATH_SIZE || packet->payload_len > MAX_PACKET_PAYLOAD) { MESH_DEBUG_PRINTLN("%s Dispatcher::sendPacket(): ERROR: invalid packet... path_len=%d, payload_len=%d", getLogDateTime(), (uint32_t) packet->path_len, (uint32_t) packet->payload_len); - _mgr->free(packet); + _mgr->release(packet); } else { _mgr->queueOutbound(packet, priority, futureMillis(delay_millis)); } diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 393d0856..14f4bf75 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -71,7 +71,8 @@ class Radio { class PacketManager { public: virtual Packet* allocNew() = 0; - virtual void free(Packet* packet) = 0; + virtual void take(Packet* packet) = 0; + virtual void release(Packet* packet) = 0; virtual void queueOutbound(Packet* packet, uint8_t priority, uint32_t scheduled_for) = 0; virtual Packet* getNextOutbound(uint32_t now) = 0; // by priority @@ -132,10 +133,15 @@ class Dispatcher { virtual uint32_t getCADFailMaxDuration() const; public: + Radio* getRadio() { return _radio; } + PacketManager* getManager() { return _mgr; } + MillisecondClock* getClock() { return _ms; } + void begin(); void loop(); Packet* obtainNewPacket(); + void takePacket(Packet* packet); void releasePacket(Packet* packet); void sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis=0); diff --git a/src/Mesh.cpp b/src/Mesh.cpp index fe3b2473..59e5d363 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -9,268 +9,46 @@ void Mesh::begin() { void Mesh::loop() { Dispatcher::loop(); -} - -bool Mesh::allowPacketForward(const mesh::Packet* packet) { - return false; // by default, Transport NOT enabled -} -uint32_t Mesh::getRetransmitDelay(const mesh::Packet* packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->getRawLength()) * 52 / 50) / 2; - return _rng->nextInt(0, 5)*t; -} -uint32_t Mesh::getDirectRetransmitDelay(const Packet* packet) { - return 0; // by default, no delay + for (int i = 0; i < _node_count; ++i) { + mesh::MeshNode* node = _nodes[i]; + node->loop(); + } } uint32_t Mesh::getCADFailRetryDelay() const { return _rng->nextInt(1, 4)*120; } -int Mesh::searchPeersByHash(const uint8_t* hash) { - return 0; // not found -} - -int Mesh::searchChannelsByHash(const uint8_t* hash, GroupChannel channels[], int max_matches) { - return 0; // not found -} - DispatcherAction Mesh::onRecvPacket(Packet* pkt) { - if (pkt->getPayloadVer() > PAYLOAD_VER_1) { // not supported in this firmware version - MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): unsupported packet version", getLogDateTime()); - return ACTION_RELEASE; - } + bool has_seen = _tables->hasSeen(pkt); - if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_TRACE) { - if (pkt->path_len < MAX_PATH_SIZE) { - uint8_t i = 0; - uint32_t trace_tag; - memcpy(&trace_tag, &pkt->payload[i], 4); i += 4; - uint32_t auth_code; - memcpy(&auth_code, &pkt->payload[i], 4); i += 4; - uint8_t flags = pkt->payload[i++]; + bool any_hold = false; + bool any_retransmit = false; + uint32_t first_retransmit = 0; - uint8_t len = pkt->payload_len - i; - if (pkt->path_len >= len) { // TRACE has reached end of given path - onTraceRecv(pkt, trace_tag, auth_code, flags, pkt->path, &pkt->payload[i], len); - } else if (self_id.isHashMatch(&pkt->payload[i + pkt->path_len]) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) { - // append SNR (Not hash!) - pkt->path[pkt->path_len] = (int8_t) (pkt->getSNR()*4); - pkt->path_len += PATH_HASH_SIZE; + for (int i = 0; i < _node_count; ++i) { + mesh::MeshNode* node = _nodes[i]; + DispatcherAction node_action = node->onRecvPacket(pkt, has_seen); - uint32_t d = getDirectRetransmitDelay(pkt); - return ACTION_RETRANSMIT_DELAYED(5, d); // schedule with priority 5 (for now), maybe make configurable? + any_hold = any_hold || node_action == ACTION_MANUAL_HOLD; + + if (node_action != ACTION_RELEASE && node_action == ACTION_MANUAL_HOLD) { + if (!any_retransmit) { + any_retransmit = true; + first_retransmit = node_action; } } - return ACTION_RELEASE; } - 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; - memcpy(pkt->path, &pkt->path[PATH_HASH_SIZE], pkt->path_len); - - 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. + if (any_retransmit) { + return first_retransmit; } - DispatcherAction action = ACTION_RELEASE; - - switch (pkt->getPayloadType()) { - case PAYLOAD_TYPE_ACK: { - int i = 0; - uint32_t ack_crc; - memcpy(&ack_crc, &pkt->payload[i], 4); i += 4; - if (i > pkt->payload_len) { - MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete ACK packet", getLogDateTime()); - } else if (!_tables->hasSeen(pkt)) { - onAckRecv(pkt, ack_crc); - action = routeRecvPacket(pkt); - } - break; - } - case PAYLOAD_TYPE_PATH: - case PAYLOAD_TYPE_REQ: - case PAYLOAD_TYPE_RESPONSE: - case PAYLOAD_TYPE_TXT_MSG: { - int i = 0; - uint8_t dest_hash = pkt->payload[i++]; - uint8_t src_hash = pkt->payload[i++]; - - uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data - if (i + CIPHER_MAC_SIZE >= pkt->payload_len) { - MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime()); - } else if (!_tables->hasSeen(pkt)) { - // NOTE: this is a 'first packet wins' impl. When receiving from multiple paths, the first to arrive wins. - // For flood mode, the path may not be the 'best' in terms of hops. - // FUTURE: could send back multiple paths, using createPathReturn(), and let sender choose which to use(?) - - if (self_id.isHashMatch(&dest_hash)) { - // scan contacts DB, for all matching hashes of 'src_hash' (max 4 matches supported ATM) - int num = searchPeersByHash(&src_hash); - // for each matching contact, try to decrypt data - bool found = false; - for (int j = 0; j < num; j++) { - uint8_t secret[PUB_KEY_SIZE]; - getPeerSharedSecret(secret, j); - - // decrypt, checking MAC is valid - uint8_t data[MAX_PACKET_PAYLOAD]; - int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i); - if (len > 0) { // success! - if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) { - int k = 0; - uint8_t path_len = data[k++]; - uint8_t* path = &data[k]; k += path_len; - uint8_t extra_type = data[k++]; - uint8_t* extra = &data[k]; - uint8_t extra_len = len - k; // remainder of packet (may be padded with zeroes!) - if (onPeerPathRecv(pkt, j, secret, path, path_len, extra_type, extra, extra_len)) { - if (pkt->isRouteFlood()) { - // send a reciprocal return path to sender, but send DIRECTLY! - mesh::Packet* rpath = createPathReturn(&src_hash, secret, pkt->path, pkt->path_len, 0, NULL, 0); - if (rpath) sendDirect(rpath, path, path_len); - } - } - } else { - onPeerDataRecv(pkt, pkt->getPayloadType(), j, secret, data, len); - } - found = true; - break; - } - } - if (found) { - pkt->markDoNotRetransmit(); // packet was for this node, so don't retransmit - } else { - MESH_DEBUG_PRINTLN("%s recv matches no peers, src_hash=%02X", getLogDateTime(), (uint32_t)src_hash); - } - } - action = routeRecvPacket(pkt); - } - break; - } - case PAYLOAD_TYPE_ANON_REQ: { - int i = 0; - uint8_t dest_hash = pkt->payload[i++]; - uint8_t* sender_pub_key = &pkt->payload[i]; i += PUB_KEY_SIZE; - - uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data - if (i + 2 >= pkt->payload_len) { - MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime()); - } else if (!_tables->hasSeen(pkt)) { - if (self_id.isHashMatch(&dest_hash)) { - Identity sender(sender_pub_key); - - uint8_t secret[PUB_KEY_SIZE]; - self_id.calcSharedSecret(secret, sender); - - // decrypt, checking MAC is valid - uint8_t data[MAX_PACKET_PAYLOAD]; - int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i); - if (len > 0) { // success! - onAnonDataRecv(pkt, pkt->getPayloadType(), sender, data, len); - pkt->markDoNotRetransmit(); - } - } - action = routeRecvPacket(pkt); - } - break; - } - case PAYLOAD_TYPE_GRP_DATA: - case PAYLOAD_TYPE_GRP_TXT: { - int i = 0; - uint8_t channel_hash = pkt->payload[i++]; - - uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data - if (i + 2 >= pkt->payload_len) { - MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime()); - } else if (!_tables->hasSeen(pkt)) { - // scan channels DB, for all matching hashes of 'channel_hash' (max 2 matches supported ATM) - GroupChannel channels[2]; - int num = searchChannelsByHash(&channel_hash, channels, 2); - // for each matching channel, try to decrypt data - for (int j = 0; j < num; j++) { - // decrypt, checking MAC is valid - uint8_t data[MAX_PACKET_PAYLOAD]; - int len = Utils::MACThenDecrypt(channels[j].secret, data, macAndData, pkt->payload_len - i); - if (len > 0) { // success! - onGroupDataRecv(pkt, pkt->getPayloadType(), channels[j], data, len); - break; - } - } - action = routeRecvPacket(pkt); - } - break; - } - case PAYLOAD_TYPE_ADVERT: { - int i = 0; - Identity id; - memcpy(id.pub_key, &pkt->payload[i], PUB_KEY_SIZE); i += PUB_KEY_SIZE; - - uint32_t timestamp; - memcpy(×tamp, &pkt->payload[i], 4); i += 4; - const uint8_t* signature = &pkt->payload[i]; i += SIGNATURE_SIZE; - - if (i > pkt->payload_len) { - MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete advertisement packet", getLogDateTime()); - } else if (self_id.matches(id.pub_key)) { - MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): receiving SELF advert packet", getLogDateTime()); - } else if (!_tables->hasSeen(pkt)) { - uint8_t* app_data = &pkt->payload[i]; - int app_data_len = pkt->payload_len - i; - if (app_data_len > MAX_ADVERT_DATA_SIZE) { app_data_len = MAX_ADVERT_DATA_SIZE; } - - // check that signature is valid - bool is_ok; - { - uint8_t message[PUB_KEY_SIZE + 4 + MAX_ADVERT_DATA_SIZE]; - int msg_len = 0; - memcpy(&message[msg_len], id.pub_key, PUB_KEY_SIZE); msg_len += PUB_KEY_SIZE; - memcpy(&message[msg_len], ×tamp, 4); msg_len += 4; - memcpy(&message[msg_len], app_data, app_data_len); msg_len += app_data_len; - - is_ok = id.verify(signature, message, msg_len); - } - if (is_ok) { - MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): valid advertisement received!", getLogDateTime()); - onAdvertRecv(pkt, id, timestamp, app_data, app_data_len); - action = routeRecvPacket(pkt); - } else { - MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): received advertisement with forged signature! (app_data_len=%d)", getLogDateTime(), app_data_len); - } - } - break; - } - case PAYLOAD_TYPE_RAW_CUSTOM: { - if (pkt->isRouteDirect() && !_tables->hasSeen(pkt)) { - onRawDataRecv(pkt); - //action = routeRecvPacket(pkt); don't flood route these (yet) - } - 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); - break; + if (any_hold) { + return ACTION_MANUAL_HOLD; } - return action; -} - -DispatcherAction Mesh::routeRecvPacket(Packet* packet) { - if (packet->isRouteFlood() && !packet->isMarkedDoNotRetransmit() - && packet->path_len + PATH_HASH_SIZE <= MAX_PATH_SIZE && allowPacketForward(packet)) { - // append this node's hash to 'path' - packet->path_len += self_id.copyHashTo(&packet->path[packet->path_len]); - uint32_t d = getRetransmitDelay(packet); - // as this propagates outwards, give it lower and lower priority - return ACTION_RETRANSMIT_DELAYED(packet->path_len, d); // give priority to closer sources, than ones further away - } return ACTION_RELEASE; } @@ -312,13 +90,13 @@ Packet* Mesh::createAdvert(const LocalIdentity& id, const uint8_t* app_data, siz #define MAX_COMBINED_PATH (MAX_PACKET_PAYLOAD - 2 - CIPHER_BLOCK_SIZE) -Packet* Mesh::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* Mesh::createPathReturn(const LocalIdentity& source, 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) { uint8_t dest_hash[PATH_HASH_SIZE]; dest.copyHashTo(dest_hash); - return createPathReturn(dest_hash, secret, path, path_len, extra_type, extra, extra_len); + return createPathReturn(source, dest_hash, secret, path, path_len, extra_type, extra, extra_len); } -Packet* Mesh::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* Mesh::createPathReturn(const LocalIdentity& source, 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) { if (path_len + extra_len + 5 > MAX_COMBINED_PATH) return NULL; // too long!! Packet* packet = obtainNewPacket(); @@ -330,7 +108,7 @@ Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, int len = 0; memcpy(&packet->payload[len], dest_hash, PATH_HASH_SIZE); len += PATH_HASH_SIZE; // dest hash - len += self_id.copyHashTo(&packet->payload[len]); // src hash + len += source.copyHashTo(&packet->payload[len]); // src hash { int data_len = 0; @@ -355,7 +133,7 @@ Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, return packet; } -Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len) { +Packet* Mesh::createDatagram(uint8_t type, const LocalIdentity& source, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len) { if (type == PAYLOAD_TYPE_TXT_MSG || type == PAYLOAD_TYPE_REQ || type == PAYLOAD_TYPE_RESPONSE) { if (data_len + CIPHER_MAC_SIZE + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) return NULL; } else { @@ -371,7 +149,7 @@ Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t* int len = 0; len += dest.copyHashTo(&packet->payload[len]); // dest hash - len += self_id.copyHashTo(&packet->payload[len]); // src hash + len += source.copyHashTo(&packet->payload[len]); // src hash len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len); packet->payload_len = len; @@ -527,4 +305,278 @@ void Mesh::sendZeroHop(Packet* packet, uint32_t delay_millis) { sendPacket(packet, 0, delay_millis); } +MeshNode::MeshNode(Mesh* mesh) + : mesh(mesh) +{ + +} + +void MeshNode::begin() {} + +void MeshNode::loop() {} + +DispatcherAction MeshNode::onRecvPacket(Packet* pkt, bool has_seen) { + if (pkt->getPayloadVer() > PAYLOAD_VER_1) { // not supported in this firmware version + MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): unsupported packet version", getLogDateTime()); + return ACTION_RELEASE; + } + + if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_TRACE) { + if (pkt->path_len < MAX_PATH_SIZE) { + uint8_t i = 0; + uint32_t trace_tag; + memcpy(&trace_tag, &pkt->payload[i], 4); i += 4; + uint32_t auth_code; + memcpy(&auth_code, &pkt->payload[i], 4); i += 4; + uint8_t flags = pkt->payload[i++]; + + uint8_t len = pkt->payload_len - i; + if (pkt->path_len >= len) { // TRACE has reached end of given path + onTraceRecv(pkt, trace_tag, auth_code, flags, pkt->path, &pkt->payload[i], len); + } else if (self_id.isHashMatch(&pkt->payload[i + pkt->path_len]) && allowPacketForward(pkt) && !has_seen) { + // append SNR (Not hash!) + pkt->path[pkt->path_len] = (int8_t) (pkt->getSNR()*4); + pkt->path_len += PATH_HASH_SIZE; + + uint32_t d = getDirectRetransmitDelay(pkt); + return ACTION_RETRANSMIT_DELAYED(5, d); // schedule with priority 5 (for now), maybe make configurable? + } + } + return ACTION_RELEASE; + } + + if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) { + if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) { + if (has_seen) return ACTION_RELEASE; // don't retransmit! + + // remove our hash from 'path', then re-broadcast + pkt->path_len -= PATH_HASH_SIZE; + memcpy(pkt->path, &pkt->path[PATH_HASH_SIZE], pkt->path_len); + + 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. + } + + DispatcherAction action = ACTION_RELEASE; + + switch (pkt->getPayloadType()) { + case PAYLOAD_TYPE_ACK: { + int i = 0; + uint32_t ack_crc; + memcpy(&ack_crc, &pkt->payload[i], 4); i += 4; + if (i > pkt->payload_len) { + MESH_DEBUG_PRINTLN("%s MeshNode::onRecvPacket(): incomplete ACK packet", getLogDateTime()); + } else if (!has_seen) { + onAckRecv(pkt, ack_crc); + action = routeRecvPacket(pkt); + } + break; + } + case PAYLOAD_TYPE_PATH: + case PAYLOAD_TYPE_REQ: + case PAYLOAD_TYPE_RESPONSE: + case PAYLOAD_TYPE_TXT_MSG: { + int i = 0; + uint8_t dest_hash = pkt->payload[i++]; + uint8_t src_hash = pkt->payload[i++]; + + uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data + if (i + CIPHER_MAC_SIZE >= pkt->payload_len) { + MESH_DEBUG_PRINTLN("%s MeshNode::onRecvPacket(): incomplete data packet", getLogDateTime()); + } else if (!has_seen) { + // NOTE: this is a 'first packet wins' impl. When receiving from multiple paths, the first to arrive wins. + // For flood mode, the path may not be the 'best' in terms of hops. + // FUTURE: could send back multiple paths, using createPathReturn(), and let sender choose which to use(?) + + if (self_id.isHashMatch(&dest_hash)) { + // scan contacts DB, for all matching hashes of 'src_hash' (max 4 matches supported ATM) + int num = searchPeersByHash(&src_hash); + // for each matching contact, try to decrypt data + bool found = false; + for (int j = 0; j < num; j++) { + uint8_t secret[PUB_KEY_SIZE]; + getPeerSharedSecret(secret, j); + + // decrypt, checking MAC is valid + uint8_t data[MAX_PACKET_PAYLOAD]; + int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i); + if (len > 0) { // success! + if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) { + int k = 0; + uint8_t path_len = data[k++]; + uint8_t* path = &data[k]; k += path_len; + uint8_t extra_type = data[k++]; + uint8_t* extra = &data[k]; + uint8_t extra_len = len - k; // remainder of packet (may be padded with zeroes!) + if (onPeerPathRecv(pkt, j, secret, path, path_len, extra_type, extra, extra_len)) { + if (pkt->isRouteFlood()) { + // send a reciprocal return path to sender, but send DIRECTLY! + mesh::Packet* rpath = mesh->createPathReturn(self_id, &src_hash, secret, pkt->path, pkt->path_len, 0, NULL, 0); + if (rpath) mesh->sendDirect(rpath, path, path_len); + } + } + } else { + onPeerDataRecv(pkt, pkt->getPayloadType(), j, secret, data, len); + } + found = true; + break; + } + } + if (found) { + pkt->markDoNotRetransmit(); // packet was for this node, so don't retransmit + } else { + MESH_DEBUG_PRINTLN("%s recv matches no peers, src_hash=%02X", getLogDateTime(), (uint32_t)src_hash); + } + } + action = routeRecvPacket(pkt); + } + break; + } + case PAYLOAD_TYPE_ANON_REQ: { + int i = 0; + uint8_t dest_hash = pkt->payload[i++]; + uint8_t* sender_pub_key = &pkt->payload[i]; i += PUB_KEY_SIZE; + + uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data + if (i + 2 >= pkt->payload_len) { + MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime()); + } else if (!has_seen) { + if (self_id.isHashMatch(&dest_hash)) { + Identity sender(sender_pub_key); + + uint8_t secret[PUB_KEY_SIZE]; + self_id.calcSharedSecret(secret, sender); + + // decrypt, checking MAC is valid + uint8_t data[MAX_PACKET_PAYLOAD]; + int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i); + if (len > 0) { // success! + onAnonDataRecv(pkt, pkt->getPayloadType(), sender, data, len); + pkt->markDoNotRetransmit(); + } + } + action = routeRecvPacket(pkt); + } + break; + } + case PAYLOAD_TYPE_GRP_DATA: + case PAYLOAD_TYPE_GRP_TXT: { + int i = 0; + uint8_t channel_hash = pkt->payload[i++]; + + uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data + if (i + 2 >= pkt->payload_len) { + MESH_DEBUG_PRINTLN("%s MeshNode::onRecvPacket(): incomplete data packet", getLogDateTime()); + } else if (!has_seen) { + // scan channels DB, for all matching hashes of 'channel_hash' (max 2 matches supported ATM) + GroupChannel channels[2]; + int num = searchChannelsByHash(&channel_hash, channels, 2); + // for each matching channel, try to decrypt data + for (int j = 0; j < num; j++) { + // decrypt, checking MAC is valid + uint8_t data[MAX_PACKET_PAYLOAD]; + int len = Utils::MACThenDecrypt(channels[j].secret, data, macAndData, pkt->payload_len - i); + if (len > 0) { // success! + onGroupDataRecv(pkt, pkt->getPayloadType(), channels[j], data, len); + break; + } + } + action = routeRecvPacket(pkt); + } + break; + } + case PAYLOAD_TYPE_ADVERT: { + int i = 0; + Identity id; + memcpy(id.pub_key, &pkt->payload[i], PUB_KEY_SIZE); i += PUB_KEY_SIZE; + + uint32_t timestamp; + memcpy(×tamp, &pkt->payload[i], 4); i += 4; + const uint8_t* signature = &pkt->payload[i]; i += SIGNATURE_SIZE; + + if (i > pkt->payload_len) { + MESH_DEBUG_PRINTLN("%s MeshNode::onRecvPacket(): incomplete advertisement packet", getLogDateTime()); + } else if (self_id.matches(id.pub_key)) { + MESH_DEBUG_PRINTLN("%s MeshNode::onRecvPacket(): receiving SELF advert packet", getLogDateTime()); + } else if (!has_seen) { + uint8_t* app_data = &pkt->payload[i]; + int app_data_len = pkt->payload_len - i; + if (app_data_len > MAX_ADVERT_DATA_SIZE) { app_data_len = MAX_ADVERT_DATA_SIZE; } + + // check that signature is valid + bool is_ok; + { + uint8_t message[PUB_KEY_SIZE + 4 + MAX_ADVERT_DATA_SIZE]; + int msg_len = 0; + memcpy(&message[msg_len], id.pub_key, PUB_KEY_SIZE); msg_len += PUB_KEY_SIZE; + memcpy(&message[msg_len], ×tamp, 4); msg_len += 4; + memcpy(&message[msg_len], app_data, app_data_len); msg_len += app_data_len; + + is_ok = id.verify(signature, message, msg_len); + } + if (is_ok) { + MESH_DEBUG_PRINTLN("%s MeshNode::onRecvPacket(): valid advertisement received!", getLogDateTime()); + onAdvertRecv(pkt, id, timestamp, app_data, app_data_len); + action = routeRecvPacket(pkt); + } else { + MESH_DEBUG_PRINTLN("%s MeshNode::onRecvPacket(): received advertisement with forged signature! (app_data_len=%d)", getLogDateTime(), app_data_len); + } + } + break; + } + case PAYLOAD_TYPE_RAW_CUSTOM: { + if (pkt->isRouteDirect() && !has_seen) { + onRawDataRecv(pkt); + //action = routeRecvPacket(pkt); don't flood route these (yet) + } + 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); + break; + } + return action; +} + +DispatcherAction MeshNode::routeRecvPacket(Packet* packet) { + if (packet->isRouteFlood() && !packet->isMarkedDoNotRetransmit() + && packet->path_len + PATH_HASH_SIZE <= MAX_PATH_SIZE && allowPacketForward(packet)) { + // append this node's hash to 'path' + packet->path_len += self_id.copyHashTo(&packet->path[packet->path_len]); + + uint32_t d = getRetransmitDelay(packet); + // as this propagates outwards, give it lower and lower priority + return ACTION_RETRANSMIT_DELAYED(packet->path_len, d); // give priority to closer sources, than ones further away + } + return ACTION_RELEASE; +} + +int MeshNode::searchPeersByHash(const uint8_t* hash) { + return 0; // not found +} + +int MeshNode::searchChannelsByHash(const uint8_t* hash, GroupChannel channels[], int max_matches) { + return 0; // not found +} + +const char* MeshNode::getName() { + return ""; +} + +bool MeshNode::allowPacketForward(const mesh::Packet* packet) { + return false; // by default, Transport NOT enabled +} + +uint32_t MeshNode::getRetransmitDelay(const mesh::Packet* packet) { + uint32_t t = (mesh->getRadio()->getEstAirtimeFor(packet->getRawLength()) * 52 / 50) / 2; + + return mesh->getRNG()->nextInt(0, 5)*t; +} +uint32_t MeshNode::getDirectRetransmitDelay(const Packet* packet) { + return 0; // by default, no delay +} + } \ No newline at end of file diff --git a/src/Mesh.h b/src/Mesh.h index cb81f8de..bdb9eeb0 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -4,6 +4,8 @@ namespace mesh { +class Mesh; + class GroupChannel { public: uint8_t hash[PATH_HASH_SIZE]; @@ -18,20 +20,8 @@ class MeshTables { virtual bool hasSeen(const Packet* packet) = 0; }; -/** - * \brief The next layer in the basic Dispatcher task, Mesh recognises the particular Payload TYPES, - * and provides virtual methods for sub-classes on handling incoming, and also preparing outbound Packets. -*/ -class Mesh : public Dispatcher { - RTCClock* _rtc; - RNG* _rng; - MeshTables* _tables; - +class MeshNode { protected: - DispatcherAction onRecvPacket(Packet* pkt) override; - - virtual uint32_t getCADFailRetryDelay() const override; - /** * \brief Decide what to do with received packet, ie. discard, forward, or hold */ @@ -53,6 +43,19 @@ class Mesh : public Dispatcher { */ virtual uint32_t getDirectRetransmitDelay(const Packet* packet); +public: + MeshNode(Mesh* mesh); + + virtual void begin(); + virtual void loop(); + + virtual const char* getName(); + + LocalIdentity self_id; + Mesh* mesh; + + virtual DispatcherAction onRecvPacket(Packet* pkt, bool has_seen); + /** * \brief Perform search of local DB of peers/contacts. * \returns Number of peers with matching hash @@ -142,30 +145,45 @@ class Mesh : public Dispatcher { * NOTE: same ACK can be received multiple times, via different routes */ virtual void onAckRecv(Packet* packet, uint32_t ack_crc) { } +}; + +/** + * \brief The next layer in the basic Dispatcher task, Mesh recognises the particular Payload TYPES, + * and provides virtual methods for sub-classes on handling incoming, and also preparing outbound Packets. +*/ +class Mesh : public Dispatcher { + RTCClock* _rtc; + RNG* _rng; + MeshTables* _tables; + +protected: + virtual DispatcherAction onRecvPacket(Packet* pkt); + + virtual uint32_t getCADFailRetryDelay() const; Mesh(Radio& radio, MillisecondClock& ms, RNG& rng, RTCClock& rtc, PacketManager& mgr, MeshTables& tables) : Dispatcher(radio, ms, mgr), _rng(&rng), _rtc(&rtc), _tables(&tables) { } - MeshTables* getTables() const { return _tables; } - public: + MeshNode** _nodes = NULL; + uint8_t _node_count = 0; + void begin(); void loop(); - LocalIdentity self_id; - + MeshTables* getTables() const { return _tables; } RNG* getRNG() const { return _rng; } RTCClock* getRTCClock() const { return _rtc; } Packet* createAdvert(const LocalIdentity& id, const uint8_t* app_data=NULL, size_t app_data_len=0); - Packet* createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t len); + Packet* createDatagram(uint8_t type, const LocalIdentity& source, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t len); 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* 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* createPathReturn(const LocalIdentity& source, 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 LocalIdentity& source, 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); Packet* createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags = 0); diff --git a/src/Packet.h b/src/Packet.h index 7c7df8e3..62a834a0 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -47,6 +47,7 @@ class Packet { uint8_t path[MAX_PATH_SIZE]; uint8_t payload[MAX_PACKET_PAYLOAD]; int8_t _snr; + uint8_t _ref_count; /** * \brief calculate the hash of payload + type diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 97aefff4..e69de29b 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -1,697 +0,0 @@ -#include -#include - -mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name, double lat, double lon) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE]; - uint8_t app_data_len; - { - AdvertDataBuilder builder(ADV_TYPE_CHAT, name, lat, lon); - app_data_len = builder.encodeTo(app_data); - } - - return createAdvert(self_id, app_data, app_data_len); -} - -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())) { - MESH_DEBUG_PRINTLN("onAdvertRecv: invalid app_data, or name is missing: len=%d", app_data_len); - return; - } - - ContactInfo* from = NULL; - for (int i = 0; i < num_contacts; i++) { - if (id.matches(contacts[i].id)) { // is from one of our contacts - from = &contacts[i]; - if (timestamp <= from->last_advert_timestamp) { // check for replay attacks!! - MESH_DEBUG_PRINTLN("onAdvertRecv: Possible replay attack, name: %s", from->name); - return; - } - break; - } - } - - // save a copy of raw advert packet (to support "Share..." function) - int plen = packet->writeTo(temp_buf); - putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen); - - bool is_new = false; - if (from == NULL) { - if (!isAutoAddEnabled()) { - ContactInfo ci; - memset(&ci, 0, sizeof(ci)); - ci.id = id; - ci.out_path_len = -1; // initially out_path is unknown - StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name)); - ci.type = parser.getType(); - if (parser.hasLatLon()) { - ci.gps_lat = parser.getIntLat(); - ci.gps_lon = parser.getIntLon(); - } - ci.last_advert_timestamp = timestamp; - ci.lastmod = getRTCClock()->getCurrentTime(); - onDiscoveredContact(ci, true); // let UI know - return; - } - - is_new = true; - if (num_contacts < MAX_CONTACTS) { - from = &contacts[num_contacts++]; - from->id = id; - from->out_path_len = -1; // initially out_path is unknown - from->gps_lat = 0; // initially unknown GPS loc - from->gps_lon = 0; - from->sync_since = 0; - - // only need to calculate the shared_secret once, for better performance - self_id.calcSharedSecret(from->shared_secret, id); - } else { - MESH_DEBUG_PRINTLN("onAdvertRecv: contacts table is full!"); - return; - } - } - - // update - StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name)); - from->type = parser.getType(); - if (parser.hasLatLon()) { - from->gps_lat = parser.getIntLat(); - from->gps_lon = parser.getIntLon(); - } - from->last_advert_timestamp = timestamp; - from->lastmod = getRTCClock()->getCurrentTime(); - - onDiscoveredContact(*from, is_new); // let UI know -} - -int BaseChatMesh::searchPeersByHash(const uint8_t* hash) { - int n = 0; - for (int i = 0; i < num_contacts && n < MAX_SEARCH_RESULTS; i++) { - if (contacts[i].id.isHashMatch(hash)) { - matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods) - } - } - return n; -} - -void BaseChatMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { - int i = matching_peer_indexes[peer_idx]; - if (i >= 0 && i < num_contacts) { - // lookup pre-calculated shared_secret - memcpy(dest_secret, contacts[i].shared_secret, PUB_KEY_SIZE); - } else { - MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i); - } -} - -void BaseChatMesh::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]; - if (i < 0 || i >= num_contacts) { - MESH_DEBUG_PRINTLN("onPeerDataRecv: Invalid sender idx: %d", i); - return; - } - - ContactInfo& from = contacts[i]; - - if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { - uint32_t timestamp; - memcpy(×tamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = data[4] >> 2; // message attempt number, and other flags - - // len can be > original length, but 'text' will be padded with zeroes - data[len] = 0; // need to make a C string again, with null terminator - - if (flags == TXT_TYPE_PLAIN) { - onMessageRecv(from, packet, timestamp, (const char *) &data[5]); // let UI know - - uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it - mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 5 + strlen((char *)&data[5]), from.id.pub_key, PUB_KEY_SIZE); - - if (packet->isRouteFlood()) { - // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK - mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, - PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); - if (path) sendFlood(path); - } else { - mesh::Packet* ack = createAck(ack_hash); - if (ack) { - if (from.out_path_len < 0) { - sendFlood(ack); - } else { - sendDirect(ack, from.out_path, from.out_path_len); - } - } - } - } else if (flags == TXT_TYPE_CLI_DATA) { - onCommandDataRecv(from, packet, timestamp, (const char *) &data[5]); // let UI know - // NOTE: no ack expected for CLI_DATA replies - - if (packet->isRouteFlood()) { - // let this sender know path TO here, so they can use sendDirect() (NOTE: no ACK as extra) - mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, 0, NULL, 0); - if (path) sendFlood(path); - } - } else if (flags == TXT_TYPE_SIGNED_PLAIN) { - if (timestamp > from.sync_since) { // make sure 'sync_since' is up-to-date - from.sync_since = timestamp; - } - onSignedMessageRecv(from, packet, timestamp, &data[5], (const char *) &data[9]); // let UI know - - uint32_t ack_hash; // calc truncated hash of the message timestamp + text + OUR pub_key, to prove to sender that we got it - mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 9 + strlen((char *)&data[9]), self_id.pub_key, PUB_KEY_SIZE); - - if (packet->isRouteFlood()) { - // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK - mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, - PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); - if (path) sendFlood(path); - } else { - mesh::Packet* ack = createAck(ack_hash); - if (ack) { - if (from.out_path_len < 0) { - sendFlood(ack); - } else { - sendDirect(ack, from.out_path, from.out_path_len); - } - } - } - } else { - MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported message type: %u", (uint32_t) flags); - } - } else if (type == PAYLOAD_TYPE_RESPONSE && len > 0) { - onContactResponse(from, data, len); - } -} - -bool BaseChatMesh::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) { - int i = matching_peer_indexes[sender_idx]; - if (i < 0 || i >= num_contacts) { - MESH_DEBUG_PRINTLN("onPeerPathRecv: Invalid sender idx: %d", i); - return false; - } - - ContactInfo& from = contacts[i]; - - // NOTE: for this impl, we just replace the current 'out_path' regardless, whenever sender sends us a new out_path. - // FUTURE: could store multiple out_paths per contact, and try to find which is the 'best'(?) - memcpy(from.out_path, path, from.out_path_len = path_len); // store a copy of path, for sendDirect() - from.lastmod = getRTCClock()->getCurrentTime(); - - onContactPathUpdated(from); - - if (extra_type == PAYLOAD_TYPE_ACK && extra_len >= 4) { - // also got an encoded ACK! - if (processAck(extra)) { - txt_send_timeout = 0; // matched one we're waiting for, cancel timeout timer - } - } else if (extra_type == PAYLOAD_TYPE_RESPONSE && extra_len > 0) { - onContactResponse(from, extra, extra_len); - } - return true; // send reciprocal path if necessary -} - -void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { - if (processAck((uint8_t *)&ack_crc)) { - txt_send_timeout = 0; // matched one we're waiting for, cancel timeout timer - packet->markDoNotRetransmit(); // ACK was for this node, so don't retransmit - } -} - -#ifdef MAX_GROUP_CHANNELS -int BaseChatMesh::searchChannelsByHash(const uint8_t* hash, mesh::GroupChannel dest[], int max_matches) { - int n = 0; - for (int i = 0; i < MAX_GROUP_CHANNELS && n < max_matches; i++) { - if (channels[i].channel.hash[0] == hash[0]) { - dest[n++] = channels[i].channel; - } - } - return n; -} -#endif - -void BaseChatMesh::onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mesh::GroupChannel& channel, uint8_t* data, size_t len) { - uint8_t txt_type = data[4]; - if (type == PAYLOAD_TYPE_GRP_TXT && len > 5 && (txt_type >> 2) == 0) { // 0 = plain text msg - uint32_t timestamp; - memcpy(×tamp, data, 4); - - // len can be > original length, but 'text' will be padded with zeroes - data[len] = 0; // need to make a C string again, with null terminator - - // notify UI of this new message - onChannelMessageRecv(channel, packet, timestamp, (const char *) &data[5]); // let UI know - } -} - -mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack) { - int text_len = strlen(text); - if (text_len > MAX_TEXT_LEN) return NULL; - if (attempt > 3 && text_len > MAX_TEXT_LEN-2) return NULL; - - uint8_t temp[5+MAX_TEXT_LEN+1]; - memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique - temp[4] = (attempt & 3); - memcpy(&temp[5], text, text_len + 1); - - // calc expected ACK reply - mesh::Utils::sha256((uint8_t *)&expected_ack, 4, temp, 5 + text_len, self_id.pub_key, PUB_KEY_SIZE); - - int len = 5 + text_len; - if (attempt > 3) { - temp[len++] = 0; // null terminator - temp[len++] = attempt; // hide attempt number at tail end of payload - } - - return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, len); -} - -int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout) { - mesh::Packet* pkt = composeMsgPacket(recipient, timestamp, attempt, text, expected_ack); - if (pkt == NULL) return MSG_SEND_FAILED; - - uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); - - int rc; - if (recipient.out_path_len < 0) { - sendFlood(pkt); - txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t)); - rc = MSG_SEND_SENT_FLOOD; - } else { - sendDirect(pkt, recipient.out_path, recipient.out_path_len); - txt_send_timeout = futureMillis(est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len)); - rc = MSG_SEND_SENT_DIRECT; - } - return rc; -} - -int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& est_timeout) { - int text_len = strlen(text); - if (text_len > MAX_TEXT_LEN) return MSG_SEND_FAILED; - - uint8_t temp[5+MAX_TEXT_LEN+1]; - memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique - temp[4] = (attempt & 3) | (TXT_TYPE_CLI_DATA << 2); - memcpy(&temp[5], text, text_len + 1); - - auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, 5 + text_len); - if (pkt == NULL) return MSG_SEND_FAILED; - - uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); - int rc; - if (recipient.out_path_len < 0) { - sendFlood(pkt); - txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t)); - rc = MSG_SEND_SENT_FLOOD; - } else { - sendDirect(pkt, recipient.out_path, recipient.out_path_len); - txt_send_timeout = futureMillis(est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len)); - rc = MSG_SEND_SENT_DIRECT; - } - return rc; -} - -bool BaseChatMesh::sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& channel, const char* sender_name, const char* text, int text_len) { - uint8_t temp[5+MAX_TEXT_LEN+32]; - memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique - temp[4] = 0; // TXT_TYPE_PLAIN - - sprintf((char *) &temp[5], "%s: ", sender_name); // : - char *ep = strchr((char *) &temp[5], 0); - int prefix_len = ep - (char *) &temp[5]; - - if (text_len + prefix_len > MAX_TEXT_LEN) text_len = MAX_TEXT_LEN - prefix_len; - memcpy(ep, text, text_len); - ep[text_len] = 0; // null terminator - - auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, channel, temp, 5 + prefix_len + text_len); - if (pkt) { - sendFlood(pkt); - return true; - } - return false; -} - -bool BaseChatMesh::shareContactZeroHop(const ContactInfo& contact) { - int plen = getBlobByKey(contact.id.pub_key, PUB_KEY_SIZE, temp_buf); // retrieve last raw advert packet - if (plen == 0) return false; // not found - - auto packet = obtainNewPacket(); - if (packet == NULL) return false; // no Packets available - - packet->readFrom(temp_buf, plen); // restore Packet from 'blob' - sendZeroHop(packet); - return true; // success -} - -uint8_t BaseChatMesh::exportContact(const ContactInfo& contact, uint8_t dest_buf[]) { - return getBlobByKey(contact.id.pub_key, PUB_KEY_SIZE, dest_buf); // retrieve last raw advert packet -} - -bool BaseChatMesh::importContact(const uint8_t src_buf[], uint8_t len) { - auto pkt = obtainNewPacket(); - if (pkt) { - if (pkt->readFrom(src_buf, len) && pkt->getPayloadType() == PAYLOAD_TYPE_ADVERT) { - pkt->header |= ROUTE_TYPE_FLOOD; // simulate it being received flood-mode - _pendingLoopback = pkt; // loop-back, as if received over radio - return true; // success - } else { - releasePacket(pkt); // undo the obtainNewPacket() - } - } - return false; // error -} - -int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, uint32_t& est_timeout) { - int tlen; - uint8_t temp[24]; - uint32_t now = getRTCClock()->getCurrentTimeUnique(); - memcpy(temp, &now, 4); // mostly an extra blob to help make packet_hash unique - if (recipient.type == ADV_TYPE_ROOM) { - memcpy(&temp[4], &recipient.sync_since, 4); - int len = strlen(password); if (len > 15) len = 15; // max 15 chars currently - memcpy(&temp[8], password, len); - tlen = 8 + len; - } else { - int len = strlen(password); if (len > 15) len = 15; // max 15 chars currently - memcpy(&temp[4], password, len); - tlen = 4 + len; - } - - auto pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.shared_secret, temp, tlen); - if (pkt) { - uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); - if (recipient.out_path_len < 0) { - sendFlood(pkt); - est_timeout = calcFloodTimeoutMillisFor(t); - return MSG_SEND_SENT_FLOOD; - } else { - sendDirect(pkt, recipient.out_path, recipient.out_path_len); - est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len); - return MSG_SEND_SENT_DIRECT; - } - } - return MSG_SEND_FAILED; -} - -int BaseChatMesh::sendStatusRequest(const ContactInfo& recipient, uint32_t& est_timeout) { - uint8_t temp[13]; - uint32_t now = getRTCClock()->getCurrentTimeUnique(); - memcpy(temp, &now, 4); // mostly an extra blob to help make packet_hash unique - temp[4] = REQ_TYPE_GET_STATUS; - memset(&temp[5], 0, 4); // reserved (possibly for 'since' param) - getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique - - auto pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, sizeof(temp)); - if (pkt) { - uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); - if (recipient.out_path_len < 0) { - sendFlood(pkt); - est_timeout = calcFloodTimeoutMillisFor(t); - return MSG_SEND_SENT_FLOOD; - } else { - sendDirect(pkt, recipient.out_path, recipient.out_path_len); - est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len); - return MSG_SEND_SENT_DIRECT; - } - } - return MSG_SEND_FAILED; -} - -bool BaseChatMesh::startConnection(const ContactInfo& contact, uint16_t keep_alive_secs) { - int use_idx = -1; - for (int i = 0; i < MAX_CONNECTIONS; i++) { - if (connections[i].keep_alive_millis == 0) { // free slot? - use_idx = i; - } else if (connections[i].server_id.matches(contact.id)) { // already in table? - use_idx = i; - break; - } - } - if (use_idx < 0) { - return false; // table is full - } - connections[use_idx].server_id = contact.id; - uint32_t interval = connections[use_idx].keep_alive_millis = ((uint32_t)keep_alive_secs)*1000; - connections[use_idx].next_ping = futureMillis(interval); - connections[use_idx].expected_ack = 0; - connections[use_idx].last_activity = getRTCClock()->getCurrentTime(); - return true; // success -} - -void BaseChatMesh::stopConnection(const uint8_t* pub_key) { - for (int i = 0; i < MAX_CONNECTIONS; i++) { - if (connections[i].server_id.matches(pub_key)) { - connections[i].keep_alive_millis = 0; // mark slot as now free - connections[i].next_ping = 0; - connections[i].expected_ack = 0; - connections[i].last_activity = 0; - break; - } - } -} - -bool BaseChatMesh::hasConnectionTo(const uint8_t* pub_key) { - for (int i = 0; i < MAX_CONNECTIONS; i++) { - if (connections[i].keep_alive_millis > 0 && connections[i].server_id.matches(pub_key)) return true; - } - return false; -} - -void BaseChatMesh::markConnectionActive(const ContactInfo& contact) { - for (int i = 0; i < MAX_CONNECTIONS; i++) { - if (connections[i].keep_alive_millis > 0 && connections[i].server_id.matches(contact.id)) { - connections[i].last_activity = getRTCClock()->getCurrentTime(); - - // re-schedule next KEEP_ALIVE, now that we have heard from server - connections[i].next_ping = futureMillis(connections[i].keep_alive_millis); - break; - } - } -} - -bool BaseChatMesh::checkConnectionsAck(const uint8_t* data) { - for (int i = 0; i < MAX_CONNECTIONS; i++) { - if (connections[i].keep_alive_millis > 0 && memcmp(&connections[i].expected_ack, data, 4) == 0) { - // yes, got an ack for our keep_alive request! - connections[i].expected_ack = 0; - connections[i].last_activity = getRTCClock()->getCurrentTime(); - - // re-schedule next KEEP_ALIVE, now that we have heard from server - connections[i].next_ping = futureMillis(connections[i].keep_alive_millis); - return true; // yes, a match - } - } - return false; /// no match -} - -void BaseChatMesh::checkConnections() { - // scan connections[] table, send KEEP_ALIVE requests - for (int i = 0; i < MAX_CONNECTIONS; i++) { - if (connections[i].keep_alive_millis == 0) continue; // unused slot - - uint32_t now = getRTCClock()->getCurrentTime(); - uint32_t expire_secs = (connections[i].keep_alive_millis / 1000) * 5 / 2; // 2.5 x keep_alive interval - if (now >= connections[i].last_activity + expire_secs) { - // connection now lost - connections[i].keep_alive_millis = 0; - connections[i].next_ping = 0; - connections[i].expected_ack = 0; - connections[i].last_activity = 0; - continue; - } - - if (millisHasNowPassed(connections[i].next_ping)) { - auto contact = lookupContactByPubKey(connections[i].server_id.pub_key, PUB_KEY_SIZE); - if (contact == NULL) { - MESH_DEBUG_PRINTLN("checkConnections(): Keep_alive contact not found!"); - continue; - } - if (contact->out_path_len < 0) { - MESH_DEBUG_PRINTLN("checkConnections(): Keep_alive contact, no out_path!"); - continue; - } - - // send KEEP_ALIVE request - uint8_t data[9]; - uint32_t now = getRTCClock()->getCurrentTimeUnique(); - memcpy(data, &now, 4); - data[4] = REQ_TYPE_KEEP_ALIVE; - memcpy(&data[5], &contact->sync_since, 4); - - // calc expected ACK reply - mesh::Utils::sha256((uint8_t *)&connections[i].expected_ack, 4, data, 9, self_id.pub_key, PUB_KEY_SIZE); - - auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->shared_secret, data, 9); - if (pkt) { - sendDirect(pkt, contact->out_path, contact->out_path_len); - } - - // schedule next KEEP_ALIVE - connections[i].next_ping = futureMillis(connections[i].keep_alive_millis); - } - } -} - -void BaseChatMesh::resetPathTo(ContactInfo& recipient) { - recipient.out_path_len = -1; -} - -static ContactInfo* table; // pass via global :-( - -static int cmp_adv_timestamp(const void *a, const void *b) { - int a_idx = *((int *)a); - int b_idx = *((int *)b); - if (table[b_idx].last_advert_timestamp > table[a_idx].last_advert_timestamp) return 1; - if (table[b_idx].last_advert_timestamp < table[a_idx].last_advert_timestamp) return -1; - return 0; -} - -void BaseChatMesh::scanRecentContacts(int last_n, ContactVisitor* visitor) { - for (int i = 0; i < num_contacts; i++) { // sort the INDEXES into contacts[] - sort_array[i] = i; - } - table = contacts; // pass via global *sigh* :-( - qsort(sort_array, num_contacts, sizeof(sort_array[0]), cmp_adv_timestamp); - - if (last_n == 0) { - last_n = num_contacts; // scan ALL - } else { - if (last_n > num_contacts) last_n = num_contacts; - } - for (int i = 0; i < last_n; i++) { - visitor->onContactVisit(contacts[sort_array[i]]); - } -} - -ContactInfo* BaseChatMesh::searchContactsByPrefix(const char* name_prefix) { - int len = strlen(name_prefix); - for (int i = 0; i < num_contacts; i++) { - auto c = &contacts[i]; - if (memcmp(c->name, name_prefix, len) == 0) return c; - } - return NULL; // not found -} - -ContactInfo* BaseChatMesh::lookupContactByPubKey(const uint8_t* pub_key, int prefix_len) { - for (int i = 0; i < num_contacts; i++) { - auto c = &contacts[i]; - if (memcmp(c->id.pub_key, pub_key, prefix_len) == 0) return c; - } - return NULL; // not found -} - -bool BaseChatMesh::addContact(const ContactInfo& contact) { - if (num_contacts < MAX_CONTACTS) { - auto dest = &contacts[num_contacts++]; - *dest = contact; - - // calc the ECDH shared secret (just once for performance) - self_id.calcSharedSecret(dest->shared_secret, contact.id); - - return true; // success - } - return false; -} - -bool BaseChatMesh::removeContact(ContactInfo& contact) { - int idx = 0; - while (idx < num_contacts && !contacts[idx].id.matches(contact.id)) { - idx++; - } - if (idx >= num_contacts) return false; // not found - - // remove from contacts array - num_contacts--; - while (idx < num_contacts) { - contacts[idx] = contacts[idx + 1]; - idx++; - } - return true; // Success -} - -#ifdef MAX_GROUP_CHANNELS -#include - -ChannelDetails* BaseChatMesh::addChannel(const char* name, const char* psk_base64) { - if (num_channels < MAX_GROUP_CHANNELS) { - auto dest = &channels[num_channels]; - - memset(dest->channel.secret, 0, sizeof(dest->channel.secret)); - int len = decode_base64((unsigned char *) psk_base64, strlen(psk_base64), dest->channel.secret); - if (len == 32 || len == 16) { - mesh::Utils::sha256(dest->channel.hash, sizeof(dest->channel.hash), dest->channel.secret, len); - StrHelper::strncpy(dest->name, name, sizeof(dest->name)); - num_channels++; - return dest; - } - } - return NULL; -} -bool BaseChatMesh::getChannel(int idx, ChannelDetails& dest) { - if (idx >= 0 && idx < MAX_GROUP_CHANNELS) { - dest = channels[idx]; - return true; - } - return false; -} -bool BaseChatMesh::setChannel(int idx, const ChannelDetails& src) { - static uint8_t zeroes[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; - - if (idx >= 0 && idx < MAX_GROUP_CHANNELS) { - channels[idx] = src; - if (memcmp(&src.channel.secret[16], zeroes, 16) == 0) { - mesh::Utils::sha256(channels[idx].channel.hash, sizeof(channels[idx].channel.hash), src.channel.secret, 16); // 128-bit key - } else { - mesh::Utils::sha256(channels[idx].channel.hash, sizeof(channels[idx].channel.hash), src.channel.secret, 32); // 256-bit key - } - return true; - } - return false; -} -int BaseChatMesh::findChannelIdx(const mesh::GroupChannel& ch) { - for (int i = 0; i < MAX_GROUP_CHANNELS; i++) { - if (memcmp(ch.secret, channels[i].channel.secret, sizeof(ch.secret)) == 0) return i; - } - return -1; // not found -} -#else -ChannelDetails* BaseChatMesh::addChannel(const char* name, const char* psk_base64) { - return NULL; // not supported -} -bool BaseChatMesh::getChannel(int idx, ChannelDetails& dest) { - return false; -} -bool BaseChatMesh::setChannel(int idx, const ChannelDetails& src) { - return false; -} -int BaseChatMesh::findChannelIdx(const mesh::GroupChannel& ch) { - return -1; // not found -} -#endif - -ContactsIterator BaseChatMesh::startContactsIterator() { - return ContactsIterator(); -} - -bool ContactsIterator::hasNext(const BaseChatMesh* mesh, ContactInfo& dest) { - if (next_idx >= mesh->getNumContacts()) return false; - - dest = mesh->contacts[next_idx++]; - return true; -} - -void BaseChatMesh::loop() { - Mesh::loop(); - - if (txt_send_timeout && millisHasNowPassed(txt_send_timeout)) { - // failed to get an ACK - onSendTimeout(); - txt_send_timeout = 0; - } - - if (_pendingLoopback) { - onRecvPacket(_pendingLoopback); // loop-back, as if received over radio - releasePacket(_pendingLoopback); // undo the obtainNewPacket() - _pendingLoopback = NULL; - } -} diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index ed6e1c3a..e69de29b 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -1,167 +0,0 @@ -#pragma once - -#include // needed for PlatformIO -#include -#include -#include - -#define MAX_TEXT_LEN (10*CIPHER_BLOCK_SIZE) // must be LESS than (MAX_PACKET_PAYLOAD - 4 - CIPHER_MAC_SIZE - 1) - -struct ContactInfo { - mesh::Identity id; - char name[32]; - uint8_t type; // on of ADV_TYPE_* - uint8_t flags; - int8_t out_path_len; - uint8_t out_path[MAX_PATH_SIZE]; - uint32_t last_advert_timestamp; // by THEIR clock - uint8_t shared_secret[PUB_KEY_SIZE]; - uint32_t lastmod; // by OUR clock - int32_t gps_lat, gps_lon; // 6 dec places - uint32_t sync_since; -}; - -#define MAX_SEARCH_RESULTS 8 - -#define MSG_SEND_FAILED 0 -#define MSG_SEND_SENT_FLOOD 1 -#define MSG_SEND_SENT_DIRECT 2 - -#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS -#define REQ_TYPE_KEEP_ALIVE 0x02 - -#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ - -class ContactVisitor { -public: - virtual void onContactVisit(const ContactInfo& contact) = 0; -}; - -class BaseChatMesh; - -class ContactsIterator { - int next_idx = 0; -public: - bool hasNext(const BaseChatMesh* mesh, ContactInfo& dest); -}; - -#ifndef MAX_CONTACTS - #define MAX_CONTACTS 32 -#endif - -#ifndef MAX_CONNECTIONS - #define MAX_CONNECTIONS 16 -#endif - -struct ConnectionInfo { - mesh::Identity server_id; - unsigned long next_ping; - uint32_t last_activity; - uint32_t keep_alive_millis; - uint32_t expected_ack; -}; - -struct ChannelDetails { - mesh::GroupChannel channel; - char name[32]; -}; - -/** - * \brief abstract Mesh class for common 'chat' client - */ -class BaseChatMesh : public mesh::Mesh { - - friend class ContactsIterator; - - ContactInfo contacts[MAX_CONTACTS]; - int num_contacts; - int sort_array[MAX_CONTACTS]; - int matching_peer_indexes[MAX_SEARCH_RESULTS]; - unsigned long txt_send_timeout; -#ifdef MAX_GROUP_CHANNELS - ChannelDetails channels[MAX_GROUP_CHANNELS]; - int num_channels; // only for addChannel() -#endif - mesh::Packet* _pendingLoopback; - uint8_t temp_buf[MAX_TRANS_UNIT]; - ConnectionInfo connections[MAX_CONNECTIONS]; - - mesh::Packet* composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack); - -protected: - BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables) - : mesh::Mesh(radio, ms, rng, rtc, mgr, tables) - { - num_contacts = 0; - #ifdef MAX_GROUP_CHANNELS - memset(channels, 0, sizeof(channels)); - num_channels = 0; - #endif - txt_send_timeout = 0; - _pendingLoopback = NULL; - memset(connections, 0, sizeof(connections)); - } - - // 'UI' concepts, for sub-classes to implement - virtual bool isAutoAddEnabled() const { return true; } - virtual void onDiscoveredContact(ContactInfo& contact, bool is_new) = 0; - virtual bool processAck(const uint8_t *data) = 0; - virtual void onContactPathUpdated(const ContactInfo& contact) = 0; - virtual void onMessageRecv(const ContactInfo& contact, mesh::Packet* pkt, uint32_t sender_timestamp, const char *text) = 0; - virtual void onCommandDataRecv(const ContactInfo& contact, mesh::Packet* pkt, uint32_t sender_timestamp, const char *text) = 0; - virtual void onSignedMessageRecv(const ContactInfo& contact, mesh::Packet* pkt, uint32_t sender_timestamp, const uint8_t *sender_prefix, const char *text) = 0; - virtual uint32_t calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const = 0; - virtual uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const = 0; - virtual void onSendTimeout() = 0; - virtual void onChannelMessageRecv(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t timestamp, const char *text) = 0; - virtual void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0; - - // storage concepts, for sub-classes to override/implement - virtual int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { return 0; } // not implemented - virtual bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], int len) { return false; } - - // Mesh overrides - void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override; - int searchPeersByHash(const uint8_t* hash) override; - void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) 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; -#ifdef MAX_GROUP_CHANNELS - int searchChannelsByHash(const uint8_t* hash, mesh::GroupChannel channels[], int max_matches) override; -#endif - void onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mesh::GroupChannel& channel, uint8_t* data, size_t len) override; - - // Connections - bool startConnection(const ContactInfo& contact, uint16_t keep_alive_secs); - void stopConnection(const uint8_t* pub_key); - bool hasConnectionTo(const uint8_t* pub_key); - void markConnectionActive(const ContactInfo& contact); - bool checkConnectionsAck(const uint8_t* data); - void checkConnections(); - -public: - mesh::Packet* createSelfAdvert(const char* name, double lat=0.0, double lon=0.0); - int sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout); - int sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& est_timeout); - bool sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& channel, const char* sender_name, const char* text, int text_len); - int sendLogin(const ContactInfo& recipient, const char* password, uint32_t& est_timeout); - int sendStatusRequest(const ContactInfo& recipient, uint32_t& est_timeout); - bool shareContactZeroHop(const ContactInfo& contact); - uint8_t exportContact(const ContactInfo& contact, uint8_t dest_buf[]); - bool importContact(const uint8_t src_buf[], uint8_t len); - void resetPathTo(ContactInfo& recipient); - void scanRecentContacts(int last_n, ContactVisitor* visitor); - ContactInfo* searchContactsByPrefix(const char* name_prefix); - ContactInfo* lookupContactByPubKey(const uint8_t* pub_key, int prefix_len); - bool removeContact(ContactInfo& contact); - bool addContact(const ContactInfo& contact); - int getNumContacts() const { return num_contacts; } - ContactsIterator startContactsIterator(); - ChannelDetails* addChannel(const char* name, const char* psk_base64); - bool getChannel(int idx, ChannelDetails& dest); - bool setChannel(int idx, const ChannelDetails& src); - int findChannelIdx(const mesh::GroupChannel& ch); - - void loop(); -}; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index f3077afb..22692cc7 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -14,12 +14,26 @@ static uint32_t _atoi(const char* sp) { } void CommonCLI::loadPrefs(FILESYSTEM* fs) { - if (fs->exists("/com_prefs")) { - loadPrefsInt(fs, "/com_prefs"); // new filename - } else if (fs->exists("/node_prefs")) { - loadPrefsInt(fs, "/node_prefs"); + char filename[16] = "/com_prefs"; + char old_filename[16] = "/node_prefs"; + + char suffix[5] = ""; + if (_index > 0) { + suffix[0] = '_'; + itoa(_index, suffix + 1, 10); + } + + size_t p = strlen(suffix); + + strncpy(filename + 10, suffix, 16 - p); + strncpy(old_filename + 11, suffix, 16 - p); + + if (fs->exists(filename)) { + loadPrefsInt(fs, filename); // new filename + } else if (fs->exists(old_filename)) { + loadPrefsInt(fs, old_filename); savePrefs(fs); // save to new filename - fs->remove("/node_prefs"); // remove old + fs->remove(old_filename); // remove old } } @@ -73,13 +87,25 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { } void CommonCLI::savePrefs(FILESYSTEM* fs) { + char filename[16] = "/com_prefs"; + + char suffix[5] = ""; + if (_index > 0) { + suffix[0] = '_'; + itoa(_index, suffix + 1, 10); + } + + size_t p = strlen(suffix); + + strncpy(filename + 10, suffix, 16 - p); + #if defined(NRF52_PLATFORM) - File file = fs->open("/com_prefs", FILE_O_WRITE); + File file = fs->open(filename, FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) - File file = fs->open("/com_prefs", "w"); + File file = fs->open(filename, "w"); #else - File file = fs->open("/com_prefs", "w", true); + File file = fs->open(filename, "w", true); #endif if (file) { uint8_t pad[8]; @@ -206,7 +232,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "> %s", StrHelper::ftoa(_prefs->freq)); } else if (memcmp(config, "public.key", 10) == 0) { strcpy(reply, "> "); - mesh::Utils::toHex(&reply[2], _mesh->self_id.pub_key, PUB_KEY_SIZE); + // mesh::Utils::toHex(&reply[2], _node->self_id.pub_key, PUB_KEY_SIZE); } else if (memcmp(config, "role", 4) == 0) { sprintf(reply, "> %s", _callbacks->getRole()); } else { diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 50e5f8d6..333a7770 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -43,13 +43,14 @@ class CommonCLICallbacks { }; class CommonCLI { - mesh::Mesh* _mesh; + mesh::MeshNode* _node; NodePrefs* _prefs; CommonCLICallbacks* _callbacks; mesh::MainBoard* _board; + uint8_t _index; char tmp[80]; - mesh::RTCClock* getRTCClock() { return _mesh->getRTCClock(); } + mesh::RTCClock* getRTCClock() { return _node->mesh->getRTCClock(); } void savePrefs() { _callbacks->savePrefs(); } void checkAdvertInterval(); @@ -57,8 +58,8 @@ class CommonCLI { void loadPrefsInt(FILESYSTEM* _fs, const char* filename); public: - CommonCLI(mesh::MainBoard& board, mesh::Mesh* mesh, NodePrefs* prefs, CommonCLICallbacks* callbacks) - : _board(&board), _mesh(mesh), _prefs(prefs), _callbacks(callbacks) { } + CommonCLI(mesh::MainBoard& board, mesh::MeshNode* node, NodePrefs* prefs, CommonCLICallbacks* callbacks, uint8_t index=0) + : _board(&board), _node(node), _prefs(prefs), _callbacks(callbacks), _index(index) { } void loadPrefs(FILESYSTEM* _fs); void savePrefs(FILESYSTEM* _fs); diff --git a/src/helpers/StaticPoolPacketManager.cpp b/src/helpers/StaticPoolPacketManager.cpp index 07bc8f2e..a4b6592c 100644 --- a/src/helpers/StaticPoolPacketManager.cpp +++ b/src/helpers/StaticPoolPacketManager.cpp @@ -57,10 +57,15 @@ void PacketQueue::add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled _num++; } -StaticPoolPacketManager::StaticPoolPacketManager(int pool_size): unused(pool_size), send_queue(pool_size), rx_queue(pool_size) { +StaticPoolPacketManager::StaticPoolPacketManager(int pool_size): _size(pool_size), unused(pool_size), send_queue(pool_size), rx_queue(pool_size) { + _packets = new mesh::Packet*[pool_size]; + _reference_table = new uint8_t[pool_size]; // load up our unusued Packet pool for (int i = 0; i < pool_size; i++) { - unused.add(new mesh::Packet(), 0, 0); + mesh::Packet* packet = new mesh::Packet(); + _packets[i] = packet; + _reference_table[i] = 0; + unused.add(packet, 0, 0); } } @@ -68,8 +73,44 @@ mesh::Packet* StaticPoolPacketManager::allocNew() { return unused.removeByIdx(0); // just get first one (returns NULL if empty) } -void StaticPoolPacketManager::free(mesh::Packet* packet) { - unused.add(packet, 0, 0); +int StaticPoolPacketManager::getPacketIndex(mesh::Packet* packet) const { + int idx; + + for (idx = 0; idx < _size; idx++) { + if (packet == _packets[idx]) { + break; + } + } + + return idx; +} + +void StaticPoolPacketManager::take(mesh::Packet* packet) { + int idx = getPacketIndex(packet); + + if (idx >= _size) { + return; + } + + _reference_table[idx]++; +} + +void StaticPoolPacketManager::release(mesh::Packet* packet) { + int idx = getPacketIndex(packet); + + if (idx >= _size) { + return; + } + + if (_reference_table[idx] == 0) { + return; + } + + _reference_table[idx]--; + + if (_reference_table[idx] == 0) { + unused.add(packet, 0, 0); + } } void StaticPoolPacketManager::queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) { diff --git a/src/helpers/StaticPoolPacketManager.h b/src/helpers/StaticPoolPacketManager.h index 09c2fdec..a66ad340 100644 --- a/src/helpers/StaticPoolPacketManager.h +++ b/src/helpers/StaticPoolPacketManager.h @@ -19,12 +19,18 @@ class PacketQueue { class StaticPoolPacketManager : public mesh::PacketManager { PacketQueue unused, send_queue, rx_queue; + mesh::Packet** _packets; + uint8_t* _reference_table; + int _size; + + int getPacketIndex(mesh::Packet* packet) const; public: StaticPoolPacketManager(int pool_size); mesh::Packet* allocNew() override; - void free(mesh::Packet* packet) override; + void take(mesh::Packet* packet) override; + void release(mesh::Packet* packet) override; void queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) override; mesh::Packet* getNextOutbound(uint32_t now) override; int getOutboundCount() const override;