diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 942659348..7852fc31f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,7 +47,7 @@ jobs: pio upgrade - name: Setup Node - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: 22 diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 8c981850d..5bec39ae2 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -4,24 +4,24 @@ cli: plugins: sources: - id: trunk - ref: v1.7.2 + ref: v1.7.3 uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.473 - - renovate@41.132.5 + - checkov@3.2.483 + - renovate@41.148.2 - prettier@3.6.2 - trufflehog@3.90.8 - yamllint@1.37.1 - bandit@1.8.6 - - trivy@0.67.0 + - trivy@0.67.2 - taplo@0.10.0 - - ruff@0.13.3 - - isort@6.1.0 + - ruff@0.14.0 + - isort@7.0.0 - markdownlint@0.45.0 - oxipng@9.1.5 - svgo@4.0.0 - - actionlint@1.7.7 + - actionlint@1.7.8 - flake8@7.3.0 - hadolint@2.14.0 - shfmt@3.6.0 diff --git a/platformio.ini b/platformio.ini index 5b7f5ddcf..376f6e5a8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -120,7 +120,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/3fb7c0e28e8e51fc0a7d56facacf3411f1d29fe0.zip + https://github.com/meshtastic/device-ui/archive/19b7855e9a1d9deff37391659ca7194e4ef57c43.zip ; Common libs for environmental measurements in telemetry module [environmental_base] diff --git a/src/configuration.h b/src/configuration.h index b6b1c1e5e..c6c8d673c 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -126,11 +126,6 @@ along with this program. If not, see . #define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7 #endif -#ifdef STATION_G2 -#define NUM_PA_POINTS 19 -#define TX_GAIN_LORA 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 19, 19, 18, 18 -#endif - // Default system gain to 0 if not defined #ifndef TX_GAIN_LORA #define TX_GAIN_LORA 0 diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index 0e23405e5..3831a384d 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -218,6 +218,7 @@ template void LR11x0Interface::addReceiveMetadata(meshtastic_Mes // LOG_DEBUG("PacketStatus %x", lora.getPacketStatus()); mp->rx_snr = lora.getSNR(); mp->rx_rssi = lround(lora.getRSSI()); + LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError()); } /** We override to turn on transmitter power as needed. diff --git a/src/mesh/PacketCache.cpp b/src/mesh/PacketCache.cpp new file mode 100644 index 000000000..0edf81741 --- /dev/null +++ b/src/mesh/PacketCache.cpp @@ -0,0 +1,253 @@ +#include "PacketCache.h" +#include "Router.h" + +PacketCache packetCache{}; + +/** + * Allocate a new cache entry and copy the packet header and payload into it + */ +PacketCacheEntry *PacketCache::cache(const meshtastic_MeshPacket *p, bool preserveMetadata) +{ + size_t payload_size = + (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) ? p->encrypted.size : p->decoded.payload.size; + PacketCacheEntry *e = (PacketCacheEntry *)malloc(sizeof(PacketCacheEntry) + payload_size + + (preserveMetadata ? sizeof(PacketCacheMetadata) : 0)); + if (!e) { + LOG_ERROR("Unable to allocate memory for packet cache entry"); + return NULL; + } + + *e = {}; + e->header.from = p->from; + e->header.to = p->to; + e->header.id = p->id; + e->header.channel = p->channel; + e->header.next_hop = p->next_hop; + e->header.relay_node = p->relay_node; + e->header.flags = (p->hop_limit & PACKET_FLAGS_HOP_LIMIT_MASK) | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) | + (p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0) | + ((p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK); + + PacketCacheMetadata m{}; + if (preserveMetadata) { + e->has_metadata = true; + m.rx_rssi = (uint8_t)(p->rx_rssi + 200); + m.rx_snr = (uint8_t)((p->rx_snr + 30.0f) / 0.25f); + m.rx_time = p->rx_time; + m.transport_mechanism = p->transport_mechanism; + m.priority = p->priority; + } + if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) { + e->encrypted = true; + e->payload_len = p->encrypted.size; + memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry), p->encrypted.bytes, p->encrypted.size); + } else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + e->encrypted = false; + if (preserveMetadata) { + m.portnum = p->decoded.portnum; + m.want_response = p->decoded.want_response; + m.emoji = p->decoded.emoji; + m.bitfield = p->decoded.bitfield; + if (p->decoded.reply_id) + m.reply_id = p->decoded.reply_id; + else if (p->decoded.request_id) + m.request_id = p->decoded.request_id; + } + e->payload_len = p->decoded.payload.size; + memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry), p->decoded.payload.bytes, p->decoded.payload.size); + } else { + LOG_ERROR("Unable to cache packet with unknown payload type %d", p->which_payload_variant); + free(e); + return NULL; + } + if (preserveMetadata) + memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry) + e->payload_len, &m, sizeof(m)); + + size += sizeof(PacketCacheEntry) + e->payload_len + (e->has_metadata ? sizeof(PacketCacheMetadata) : 0); + insert(e); + return e; +}; + +/** + * Dump a list of packets into the provided buffer + */ +void PacketCache::dump(void *dest, const PacketCacheEntry **entries, size_t num_entries) +{ + unsigned char *pos = (unsigned char *)dest; + for (size_t i = 0; i < num_entries; i++) { + size_t entry_len = + sizeof(PacketCacheEntry) + entries[i]->payload_len + (entries[i]->has_metadata ? sizeof(PacketCacheMetadata) : 0); + memcpy(pos, entries[i], entry_len); + pos += entry_len; + } +} + +/** + * Calculate the length of buffer needed to dump the specified entries + */ +size_t PacketCache::dumpSize(const PacketCacheEntry **entries, size_t num_entries) +{ + size_t total_size = 0; + for (size_t i = 0; i < num_entries; i++) { + total_size += sizeof(PacketCacheEntry) + entries[i]->payload_len; + if (entries[i]->has_metadata) + total_size += sizeof(PacketCacheMetadata); + } + return total_size; +} + +/** + * Find a packet in the cache + */ +PacketCacheEntry *PacketCache::find(NodeNum from, PacketId id) +{ + uint16_t h = PACKET_HASH(from, id); + PacketCacheEntry *e = buckets[PACKET_CACHE_BUCKET(h)]; + while (e) { + if (e->header.from == from && e->header.id == id) + return e; + e = e->next; + } + return NULL; +} + +/** + * Find a packet in the cache by its hash + */ +PacketCacheEntry *PacketCache::find(PacketHash h) +{ + PacketCacheEntry *e = buckets[PACKET_CACHE_BUCKET(h)]; + while (e) { + if (PACKET_HASH(e->header.from, e->header.id) == h) + return e; + e = e->next; + } + return NULL; +} + +/** + * Load a list of packets from the provided buffer + */ +bool PacketCache::load(void *src, PacketCacheEntry **entries, size_t num_entries) +{ + memset(entries, 0, sizeof(PacketCacheEntry *) * num_entries); + unsigned char *pos = (unsigned char *)src; + for (size_t i = 0; i < num_entries; i++) { + PacketCacheEntry e{}; + memcpy(&e, pos, sizeof(PacketCacheEntry)); + size_t entry_len = sizeof(PacketCacheEntry) + e.payload_len + (e.has_metadata ? sizeof(PacketCacheMetadata) : 0); + entries[i] = (PacketCacheEntry *)malloc(entry_len); + size += entry_len; + if (!entries[i]) { + LOG_ERROR("Unable to allocate memory for packet cache entry"); + for (size_t j = 0; j < i; j++) { + size -= sizeof(PacketCacheEntry) + entries[j]->payload_len + + (entries[j]->has_metadata ? sizeof(PacketCacheMetadata) : 0); + free(entries[j]); + entries[j] = NULL; + } + return false; + } + memcpy(entries[i], pos, entry_len); + pos += entry_len; + } + for (size_t i = 0; i < num_entries; i++) + insert(entries[i]); + return true; +} + +/** + * Copy the cached packet into the provided MeshPacket structure + */ +void PacketCache::rehydrate(const PacketCacheEntry *e, meshtastic_MeshPacket *p) +{ + if (!e || !p) + return; + + *p = {}; + p->from = e->header.from; + p->to = e->header.to; + p->id = e->header.id; + p->channel = e->header.channel; + p->next_hop = e->header.next_hop; + p->relay_node = e->header.relay_node; + p->hop_limit = e->header.flags & PACKET_FLAGS_HOP_LIMIT_MASK; + p->want_ack = !!(e->header.flags & PACKET_FLAGS_WANT_ACK_MASK); + p->via_mqtt = !!(e->header.flags & PACKET_FLAGS_VIA_MQTT_MASK); + p->hop_start = (e->header.flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT; + p->which_payload_variant = e->encrypted ? meshtastic_MeshPacket_encrypted_tag : meshtastic_MeshPacket_decoded_tag; + + unsigned char *payload = ((unsigned char *)e) + sizeof(PacketCacheEntry); + PacketCacheMetadata m{}; + if (e->has_metadata) { + memcpy(&m, (payload + e->payload_len), sizeof(m)); + p->rx_rssi = ((int)m.rx_rssi) - 200; + p->rx_snr = ((float)m.rx_snr * 0.25f) - 30.0f; + p->rx_time = m.rx_time; + p->transport_mechanism = (meshtastic_MeshPacket_TransportMechanism)m.transport_mechanism; + p->priority = (meshtastic_MeshPacket_Priority)m.priority; + } + if (e->encrypted) { + memcpy(p->encrypted.bytes, payload, e->payload_len); + p->encrypted.size = e->payload_len; + } else { + memcpy(p->decoded.payload.bytes, payload, e->payload_len); + p->decoded.payload.size = e->payload_len; + if (e->has_metadata) { + // Decrypted-only metadata + p->decoded.portnum = (meshtastic_PortNum)m.portnum; + p->decoded.want_response = m.want_response; + p->decoded.emoji = m.emoji; + p->decoded.bitfield = m.bitfield; + if (m.reply_id) + p->decoded.reply_id = m.reply_id; + else if (m.request_id) + p->decoded.request_id = m.request_id; + } + } +} + +/** + * Release a cache entry + */ +void PacketCache::release(PacketCacheEntry *e) +{ + if (!e) + return; + remove(e); + size -= sizeof(PacketCacheEntry) + e->payload_len + (e->has_metadata ? sizeof(PacketCacheMetadata) : 0); + free(e); +} + +/** + * Insert a new entry into the hash table + */ +void PacketCache::insert(PacketCacheEntry *e) +{ + assert(e); + PacketHash h = PACKET_HASH(e->header.from, e->header.id); + PacketCacheEntry **target = &buckets[PACKET_CACHE_BUCKET(h)]; + e->next = *target; + *target = e; + num_entries++; +} + +/** + * Remove an entry from the hash table + */ +void PacketCache::remove(PacketCacheEntry *e) +{ + assert(e); + PacketHash h = PACKET_HASH(e->header.from, e->header.id); + PacketCacheEntry **target = &buckets[PACKET_CACHE_BUCKET(h)]; + while (*target) { + if (*target == e) { + *target = e->next; + e->next = NULL; + num_entries--; + break; + } else { + target = &(*target)->next; + } + } +} \ No newline at end of file diff --git a/src/mesh/PacketCache.h b/src/mesh/PacketCache.h new file mode 100644 index 000000000..81ad455da --- /dev/null +++ b/src/mesh/PacketCache.h @@ -0,0 +1,75 @@ +#pragma once +#include "RadioInterface.h" + +#define PACKET_HASH(a, b) ((((a ^ b) >> 16) ^ (a ^ b)) & 0xFFFF) // 16 bit fold of packet (from, id) tuple +typedef uint16_t PacketHash; + +#define PACKET_CACHE_BUCKETS 64 // Number of hash table buckets +#define PACKET_CACHE_BUCKET(h) (((h >> 12) ^ (h >> 6) ^ h) & 0x3F) // Fold hash down to 6-bit bucket index + +typedef struct PacketCacheEntry { + PacketCacheEntry *next; + PacketHeader header; + uint16_t payload_len = 0; + union { + uint16_t bitfield; + struct { + uint8_t encrypted : 1; // Payload is encrypted + uint8_t has_metadata : 1; // Payload includes PacketCacheMetadata + uint8_t : 6; // Reserved for future use + uint16_t : 8; // Reserved for future use + }; + }; +} PacketCacheEntry; + +typedef struct PacketCacheMetadata { + PacketCacheMetadata() : _bitfield(0), reply_id(0), _bitfield2(0) {} + union { + uint32_t _bitfield; + struct { + uint16_t portnum : 9; // meshtastic_MeshPacket::decoded::portnum + uint16_t want_response : 1; // meshtastic_MeshPacket::decoded::want_response + uint16_t emoji : 1; // meshtastic_MeshPacket::decoded::emoji + uint16_t bitfield : 5; // meshtastic_MeshPacket::decoded::bitfield (truncated) + uint8_t rx_rssi : 8; // meshtastic_MeshPacket::rx_rssi (map via actual RSSI + 200) + uint8_t rx_snr : 8; // meshtastic_MeshPacket::rx_snr (map via (p->rx_snr + 30.0f) / 0.25f) + }; + }; + union { + uint32_t reply_id; // meshtastic_MeshPacket::decoded.reply_id + uint32_t request_id; // meshtastic_MeshPacket::decoded.request_id + }; + uint32_t rx_time = 0; // meshtastic_MeshPacket::rx_time + uint8_t transport_mechanism = 0; // meshtastic_MeshPacket::transport_mechanism + struct { + uint8_t _bitfield2; + union { + uint8_t priority : 7; // meshtastic_MeshPacket::priority + uint8_t reserved : 1; // Reserved for future use + }; + }; +} PacketCacheMetadata; + +class PacketCache +{ + public: + PacketCacheEntry *cache(const meshtastic_MeshPacket *p, bool preserveMetadata); + static void dump(void *dest, const PacketCacheEntry **entries, size_t num_entries); + size_t dumpSize(const PacketCacheEntry **entries, size_t num_entries); + PacketCacheEntry *find(NodeNum from, PacketId id); + PacketCacheEntry *find(PacketHash h); + bool load(void *src, PacketCacheEntry **entries, size_t num_entries); + size_t getNumEntries() { return num_entries; } + size_t getSize() { return size; } + void rehydrate(const PacketCacheEntry *e, meshtastic_MeshPacket *p); + void release(PacketCacheEntry *e); + + private: + PacketCacheEntry *buckets[PACKET_CACHE_BUCKETS]{}; + size_t num_entries = 0; + size_t size = 0; + void insert(PacketCacheEntry *e); + void remove(PacketCacheEntry *e); +}; + +extern PacketCache packetCache; \ No newline at end of file diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index c60ef3a80..e1f07a32b 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -266,6 +266,7 @@ template void SX126xInterface::addReceiveMetadata(meshtastic_Mes // LOG_DEBUG("PacketStatus %x", lora.getPacketStatus()); mp->rx_snr = lora.getSNR(); mp->rx_rssi = lround(lora.getRSSI()); + LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError()); } /** We override to turn on transmitter power as needed. diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index cbc98eeb1..80872af07 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -204,6 +204,7 @@ template void SX128xInterface::addReceiveMetadata(meshtastic_Mes // LOG_DEBUG("PacketStatus %x", lora.getPacketStatus()); mp->rx_snr = lora.getSNR(); mp->rx_rssi = lround(lora.getRSSI()); + LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError()); } /** We override to turn on transmitter power as needed.