* Add explicit presence for MeshPacket.rx_time (arrival time) rx_time is now proto3 optional with a has_rx_time presence bit, matching the rx_rssi treatment. A node with no GPS and no phone connected yet has no time source at all, so a bare 0 was indistinguishable from a genuine 1970-01-01 reading; downstream consumers (replay packets, JSON serialization) now check has_rx_time instead of the value. * Dedupe rx_time stamping into a shared helper; trim a debug log string Extract the repeated haveTime/rx_time/has_rx_time stamp logic (5 call sites across Router.cpp, MeshBeaconModule.cpp, MeshService.cpp) into Router::computeRxTimeStamp()/stampRxTime(). Also shorten the new RTC.cpp LOG_DEBUG string. Saves 48 bytes of flash on rak4631 (measured), no behavior change. * Fix has_rx_rssi presence carried unconditionally through StoreForward replay preparePayload() set has_rx_rssi = true unconditionally on replay, regardless of whether the packet's rx_rssi at store time was a genuine measurement (e.g. MQTT-relayed packets carry no real RSSI). Store the presence bit alongside rx_rssi in PacketHistoryStruct and restore it on replay instead. Flagged by Copilot on #11271 (same root cause the has_rx_time explicit presence work fixes) but never addressed before that PR merged. * Trim comment blocks to the repo's 1-2 line guideline .github/copilot-instructions.md:338 caps code comments at 1-2 lines; several blocks added across the rx_time explicit-presence work ran well past that. Also consolidates Time.cpp's file-level doc comment into Time.h, where the rest of the Time:: API contract already lives. No behavior change. * Add rx_time explicit-presence test coverage - test_meshpacket_serializer: has_rx_time=false fixture plus tests asserting JsonSerialize/JsonSerializeEncrypted emit 0 rather than leaking the millis() placeholder, alongside the has_rx_time=true baseline. - test_stream_api: two tests driving a real PhoneAPI handshake (want_config_id through STATE_SEND_PACKETS) that simulate a phone time-giving transaction arriving before vs. after a queued packet is drained - covering both the reconciled and the ships-with-placeholder-absent paths of MeshService::reconcilePendingRxTimes(). * Fix three correctness issues flagged in review - Time.h: drop the reserved-identifier include guard (_MT_TIME_H); pragma once already covers it, matching convention elsewhere (e.g. RTC.h). - Time.cpp: rebase getMillis64()'s wrap accumulator when the test seam swaps clock sources, so a real<->injected clock jump isn't miscounted as a genuine 32-bit wrap. - NodeInfoModule: the 12h reply-suppression window is a local dedup duration, not a wall-clock reading - switch it to Time::getMillis64() so RTC-quality jumps and replayed packets' stale rx_time can't perturb it. - StoreForwardModule: has_rx_time was derived from *current* RTC quality at replay time rather than stored at capture time, so a history entry saved while time-blind could be misreported as a valid epoch once the clock later improved. Persist the presence bit in PacketHistoryStruct instead. * tryfix CI * post review fixes * more test fixes
127 lines
4.3 KiB
C++
127 lines
4.3 KiB
C++
#pragma once
|
|
|
|
#include "ProtobufModule.h"
|
|
#include "concurrency/OSThread.h"
|
|
#include "mesh/generated/meshtastic/storeforward.pb.h"
|
|
|
|
#include "configuration.h"
|
|
#include <Arduino.h>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
|
|
struct PacketHistoryStruct {
|
|
uint32_t time;
|
|
bool has_rx_time; // whether `time` was a trustworthy epoch when captured, not a getTime() boot-relative fallback
|
|
uint32_t to;
|
|
uint32_t from;
|
|
uint32_t id;
|
|
uint8_t channel;
|
|
uint32_t reply_id;
|
|
bool emoji;
|
|
uint8_t payload[meshtastic_Constants_DATA_PAYLOAD_LEN];
|
|
pb_size_t payload_size;
|
|
int32_t rx_rssi;
|
|
bool has_rx_rssi; // whether rx_rssi was a genuine measurement (e.g. not MQTT-relayed) when captured
|
|
float rx_snr;
|
|
uint8_t hop_start;
|
|
uint8_t hop_limit;
|
|
bool via_mqtt;
|
|
uint8_t transport_mechanism;
|
|
};
|
|
|
|
class StoreForwardModule : private concurrency::OSThread, public ProtobufModule<meshtastic_StoreAndForward>
|
|
{
|
|
// packetHistory is allocated with ps_calloc / calloc, so it must be released with free(),
|
|
// not delete[].
|
|
struct CFreeDeleter {
|
|
void operator()(PacketHistoryStruct *p) const noexcept { free(p); }
|
|
};
|
|
|
|
bool busy = 0;
|
|
uint32_t busyTo = 0;
|
|
char routerMessage[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0};
|
|
|
|
std::unique_ptr<PacketHistoryStruct[], CFreeDeleter> packetHistory;
|
|
uint32_t packetHistoryTotalCount = 0;
|
|
uint32_t last_time = 0;
|
|
uint32_t requestCount = 0;
|
|
|
|
uint32_t packetTimeMax = 5000; // Interval between sending history packets as a server.
|
|
|
|
bool is_client = false;
|
|
bool is_server = false;
|
|
|
|
// Unordered_map stores the last request for each nodeNum (`to` field)
|
|
std::unordered_map<NodeNum, uint32_t> lastRequest;
|
|
|
|
public:
|
|
StoreForwardModule();
|
|
|
|
unsigned long lastHeartbeat = 0;
|
|
uint32_t heartbeatInterval = 900;
|
|
|
|
/**
|
|
Update our local reference of when we last saw that node.
|
|
@return 0 if we have never seen that node before otherwise return the last time we saw the node.
|
|
*/
|
|
void historyAdd(const meshtastic_MeshPacket &mp);
|
|
void statsSend(uint32_t to);
|
|
void historySend(uint32_t secAgo, uint32_t to);
|
|
uint32_t getNumAvailablePackets(NodeNum dest, uint32_t last_time);
|
|
|
|
/**
|
|
* Send our payload into the mesh
|
|
*/
|
|
bool sendPayload(NodeNum dest = NODENUM_BROADCAST, uint32_t packetHistory_index = 0);
|
|
meshtastic_MeshPacket *preparePayload(NodeNum dest, uint32_t packetHistory_index, bool local = false);
|
|
void sendMessage(NodeNum dest, const meshtastic_StoreAndForward &payload);
|
|
void sendMessage(NodeNum dest, meshtastic_StoreAndForward_RequestResponse rr);
|
|
void sendErrorTextMessage(NodeNum dest, bool want_response);
|
|
meshtastic_MeshPacket *getForPhone();
|
|
// Returns true if we are configured as server AND we could allocate PSRAM.
|
|
bool isServer() { return is_server; }
|
|
|
|
/*
|
|
-Override the wantPacket method.
|
|
*/
|
|
virtual bool wantPacket(const meshtastic_MeshPacket *p) override
|
|
{
|
|
switch (p->decoded.portnum) {
|
|
case meshtastic_PortNum_TEXT_MESSAGE_APP:
|
|
case meshtastic_PortNum_STORE_FORWARD_APP:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private:
|
|
void populatePSRAM();
|
|
|
|
// S&F Defaults
|
|
uint32_t historyReturnMax = 25; // Return maximum of 25 records by default.
|
|
uint32_t historyReturnWindow = 240; // Return history of last 4 hours by default.
|
|
uint32_t records = 0; // Calculated
|
|
bool heartbeat = false; // No heartbeat.
|
|
|
|
// stats
|
|
uint32_t requests = 0; // Number of times any client sent a request to the S&F.
|
|
uint32_t requests_history = 0; // Number of times the history was requested.
|
|
|
|
uint32_t retry_delay = 0; // If server is busy, retry after this delay (in ms).
|
|
|
|
protected:
|
|
virtual int32_t runOnce() override;
|
|
|
|
/** Called to handle a particular incoming message
|
|
|
|
@return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for
|
|
it
|
|
*/
|
|
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
|
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_StoreAndForward *p);
|
|
};
|
|
|
|
extern StoreForwardModule *storeForwardModule;
|