Files
MeshCore/src/helpers/TrackerProtocol.h
T
mikecarper 1f1a499482 Add opt-in low power Sensor dog tracking
Acquire fresh GPS and validated UTC, check an authenticated owner Companion, and apply saved lost-status cadence with bounded awake windows. Persist request tags, routes, and six-hour flood reservations; preserve the four-day cutoff across sleep and restart.

Keep Dispatcher recovery active after a failed radio wake, honor denied flood retries after lost TxDone, and qualify GPS ownership and clock corrections. Add executable regression tests and nRF52/ESP32 Sensor build jobs.

Validation: six firmware builds, 1829 native test cases, sanitized tracker/storage/GPS/sleep/recovery tests, and strict documentation build. Bluetooth Find My advertising and automatic Room mailbox polling remain separate work.
2026-10-10 20:08:58 -07:00

121 lines
4.2 KiB
C++

#pragma once
#include <stddef.h>
#include <stdint.h>
namespace mesh {
namespace tracker {
// Encrypted PAYLOAD_TYPE_REQ/RESPONSE packets carry the usual four-byte tag.
// Only the configured full peer identity may answer a client's pending tag.
static constexpr uint8_t RequestType = 0x0c;
static constexpr uint8_t Version = 1;
static constexpr size_t TagLength = 4;
static constexpr size_t RequestLength = 13;
static constexpr size_t ResponseLength = 9;
static constexpr size_t CipherBlockLength = 16;
enum Status : uint8_t { Unknown = 0, Safe = 1, Lost = 2 };
enum RequestFlags : uint8_t { FreshGps = 1, GpsAttemptFailed = 2 };
struct StatusRequest {
uint8_t flags = 0;
int32_t latitude = 0; // degrees * 1e7
int32_t longitude = 0; // degrees * 1e7
uint16_t battery_mv = 0;
};
inline uint32_t get32(const uint8_t* bytes) {
uint32_t value = 0;
for (size_t i = 0; i < 4; ++i) value |= uint32_t(bytes[i]) << (8 * i);
return value;
}
inline void put32(uint8_t* bytes, uint32_t value) {
for (size_t i = 0; i < 4; ++i) bytes[i] = uint8_t(value >> (8 * i));
}
inline int32_t getSigned32(const uint8_t* bytes) {
const uint32_t value = get32(bytes);
// Avoid implementation-defined conversion when the sign bit is set.
return value <= UINT32_C(0x7fffffff) ? int32_t(value)
: -1 - int32_t(UINT32_MAX - value);
}
inline bool validStatusRequest(const StatusRequest& request) {
if (request.flags > GpsAttemptFailed) return false;
if (!(request.flags & FreshGps)) {
return request.latitude == 0 && request.longitude == 0;
}
return request.latitude >= -900000000 && request.latitude <= 900000000
&& request.longitude >= -1800000000 && request.longitude <= 1800000000;
}
inline bool zeroPadded(const uint8_t* bytes, size_t length, size_t logical,
size_t padded) {
if (bytes == NULL || (length != logical && length != padded)) return false;
for (size_t i = logical; i < length; ++i) {
if (bytes[i] != 0) return false;
}
return true;
}
// The callback has removed the tag. The seventeen-byte plaintext occupies two
// cipher blocks, leaving a twenty-eight-byte body including zero padding.
inline bool parseStatusRequest(const uint8_t* bytes, size_t length,
StatusRequest& request) {
if (!zeroPadded(bytes, length, RequestLength,
2 * CipherBlockLength - TagLength)
|| bytes[0] != RequestType || bytes[1] != Version) return false;
StatusRequest value;
value.flags = bytes[2];
value.latitude = getSigned32(bytes + 3);
value.longitude = getSigned32(bytes + 7);
value.battery_mv = uint16_t(bytes[11]) | (uint16_t(bytes[12]) << 8);
if (!validStatusRequest(value)) return false;
request = value;
return true;
}
inline size_t makeStatusRequest(const StatusRequest& request, uint8_t* bytes,
size_t capacity) {
if (bytes == NULL || capacity < RequestLength || !validStatusRequest(request))
return 0;
bytes[0] = RequestType;
bytes[1] = Version;
bytes[2] = request.flags;
put32(bytes + 3, uint32_t(request.latitude));
put32(bytes + 7, uint32_t(request.longitude));
bytes[11] = uint8_t(request.battery_mv);
bytes[12] = uint8_t(request.battery_mv >> 8);
return RequestLength;
}
inline size_t makeStatusResponse(uint32_t tag, uint8_t status,
uint16_t stayawake_seconds, uint8_t* bytes,
size_t capacity) {
if (bytes == NULL || capacity < ResponseLength || status > Lost) return 0;
put32(bytes, tag);
bytes[4] = RequestType;
bytes[5] = Version;
bytes[6] = status;
bytes[7] = uint8_t(stayawake_seconds);
bytes[8] = uint8_t(stayawake_seconds >> 8);
return ResponseLength;
}
inline bool parseStatusResponse(const uint8_t* bytes, size_t length,
uint32_t expected_tag, uint8_t& status,
uint16_t& stayawake_seconds) {
if (!zeroPadded(bytes, length, ResponseLength, CipherBlockLength)
|| bytes[4] != RequestType || bytes[5] != Version || bytes[6] > Lost)
return false;
if (get32(bytes) != expected_tag) return false;
status = bytes[6];
stayawake_seconds = uint16_t(bytes[7]) | (uint16_t(bytes[8]) << 8);
return true;
}
} // namespace tracker
} // namespace mesh