mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-08-22 02:29:51 +00:00
feat: encode persistent location state
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
#include "LocationStateRecord.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
namespace Telemetry {
|
||||
namespace {
|
||||
|
||||
constexpr uint8_t MAGIC[4] = {'P', 'Y', 'L', 'S'};
|
||||
constexpr uint8_t SESSION_HAS_EXPIRY = 0x01;
|
||||
constexpr uint8_t SESSION_CEASE_PENDING = 0x02;
|
||||
constexpr uint8_t LOCATION_HAS_EXPIRY = 0x01;
|
||||
|
||||
uint16_t readU16(const uint8_t* data) {
|
||||
return static_cast<uint16_t>(
|
||||
(static_cast<uint16_t>(data[0]) << 8U) |
|
||||
static_cast<uint16_t>(data[1]));
|
||||
}
|
||||
|
||||
uint32_t readU32(const uint8_t* data) {
|
||||
return (static_cast<uint32_t>(data[0]) << 24U) |
|
||||
(static_cast<uint32_t>(data[1]) << 16U) |
|
||||
(static_cast<uint32_t>(data[2]) << 8U) |
|
||||
static_cast<uint32_t>(data[3]);
|
||||
}
|
||||
|
||||
uint64_t readU64(const uint8_t* data) {
|
||||
uint64_t value = 0;
|
||||
for (uint8_t index = 0; index < 8; ++index) {
|
||||
value = (value << 8U) | data[index];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
int32_t readI32(const uint8_t* data) {
|
||||
const uint32_t raw = readU32(data);
|
||||
if (raw <= static_cast<uint32_t>(std::numeric_limits<int32_t>::max())) {
|
||||
return static_cast<int32_t>(raw);
|
||||
}
|
||||
return -1 - static_cast<int32_t>(
|
||||
std::numeric_limits<uint32_t>::max() - raw);
|
||||
}
|
||||
|
||||
void writeU16(uint8_t* output, uint16_t value) {
|
||||
output[0] = static_cast<uint8_t>(value >> 8U);
|
||||
output[1] = static_cast<uint8_t>(value);
|
||||
}
|
||||
|
||||
void writeU32(uint8_t* output, uint32_t value) {
|
||||
output[0] = static_cast<uint8_t>(value >> 24U);
|
||||
output[1] = static_cast<uint8_t>(value >> 16U);
|
||||
output[2] = static_cast<uint8_t>(value >> 8U);
|
||||
output[3] = static_cast<uint8_t>(value);
|
||||
}
|
||||
|
||||
void writeI32(uint8_t* output, int32_t value) {
|
||||
writeU32(output, static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
void writeU64(uint8_t* output, uint64_t value) {
|
||||
for (int index = 7; index >= 0; --index) {
|
||||
output[index] = static_cast<uint8_t>(value);
|
||||
value >>= 8U;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t crc32(const uint8_t* data, std::size_t size) {
|
||||
uint32_t crc = 0xffffffffU;
|
||||
for (std::size_t index = 0; index < size; ++index) {
|
||||
crc ^= data[index];
|
||||
for (uint8_t bit = 0; bit < 8; ++bit) {
|
||||
crc = (crc & 1U) != 0
|
||||
? (crc >> 1U) ^ 0xedb88320U
|
||||
: crc >> 1U;
|
||||
}
|
||||
}
|
||||
return crc ^ 0xffffffffU;
|
||||
}
|
||||
|
||||
bool peerEquals(const PeerId& left, const PeerId& right) {
|
||||
return std::memcmp(left.bytes, right.bytes, PEER_ID_SIZE) == 0;
|
||||
}
|
||||
|
||||
bool validSession(const ShareRestoreEntry& entry) {
|
||||
const ShareRestoreRecord& record = entry.record;
|
||||
return record.cadence_millis >= MIN_SHARE_CADENCE_MILLIS &&
|
||||
record.cadence_millis <= MAX_SHARE_CADENCE_MILLIS &&
|
||||
record.approx_radius_meters >= 0 &&
|
||||
(record.has_expiry || record.expires_at_millis == 0) &&
|
||||
record.expires_at_millis <=
|
||||
static_cast<uint64_t>(std::numeric_limits<int64_t>::max());
|
||||
}
|
||||
|
||||
bool validLocation(const PeerLocationRecord& record) {
|
||||
return record.location.latitude_e6 >= -90000000 &&
|
||||
record.location.latitude_e6 <= 90000000 &&
|
||||
record.location.longitude_e6 >= -180000000 &&
|
||||
record.location.longitude_e6 <= 180000000 &&
|
||||
(record.has_expiry || record.expires_at_millis == 0) &&
|
||||
record.expires_at_millis <=
|
||||
static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) &&
|
||||
record.approx_radius_meters <=
|
||||
static_cast<uint32_t>(std::numeric_limits<int32_t>::max());
|
||||
}
|
||||
|
||||
bool duplicateSession(
|
||||
const LocationStateSnapshot& state,
|
||||
std::size_t index) {
|
||||
for (std::size_t prior = 0; prior < index; ++prior) {
|
||||
if (peerEquals(state.sessions[prior].peer, state.sessions[index].peer)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool duplicateLocation(
|
||||
const LocationStateSnapshot& state,
|
||||
std::size_t index) {
|
||||
for (std::size_t prior = 0; prior < index; ++prior) {
|
||||
if (peerEquals(state.locations[prior].peer, state.locations[index].peer)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void encodeSession(const ShareRestoreEntry& entry, uint8_t* output) {
|
||||
std::memcpy(output, entry.peer.bytes, PEER_ID_SIZE);
|
||||
output[16] = static_cast<uint8_t>(
|
||||
(entry.record.has_expiry ? SESSION_HAS_EXPIRY : 0U) |
|
||||
(entry.record.cease_pending ? SESSION_CEASE_PENDING : 0U));
|
||||
output[17] = 0;
|
||||
writeU32(output + 18, entry.record.cadence_millis);
|
||||
writeI32(output + 22, entry.record.approx_radius_meters);
|
||||
writeU64(output + 26, entry.record.expires_at_millis);
|
||||
}
|
||||
|
||||
ShareRestoreEntry decodeSession(const uint8_t* data) {
|
||||
ShareRestoreEntry entry{};
|
||||
std::memcpy(entry.peer.bytes, data, PEER_ID_SIZE);
|
||||
entry.record.has_expiry = (data[16] & SESSION_HAS_EXPIRY) != 0;
|
||||
entry.record.cease_pending = (data[16] & SESSION_CEASE_PENDING) != 0;
|
||||
entry.record.cadence_millis = readU32(data + 18);
|
||||
entry.record.approx_radius_meters = readI32(data + 22);
|
||||
entry.record.expires_at_millis = readU64(data + 26);
|
||||
return entry;
|
||||
}
|
||||
|
||||
void encodeLocation(const PeerLocationRecord& record, uint8_t* output) {
|
||||
std::memcpy(output, record.peer.bytes, PEER_ID_SIZE);
|
||||
output[16] = record.has_expiry ? LOCATION_HAS_EXPIRY : 0;
|
||||
output[17] = 0;
|
||||
writeI32(output + 18, record.location.latitude_e6);
|
||||
writeI32(output + 22, record.location.longitude_e6);
|
||||
writeI32(output + 26, record.location.altitude_cm);
|
||||
writeU32(output + 30, record.location.speed_centi_kmh);
|
||||
writeI32(output + 34, record.location.bearing_cdeg);
|
||||
writeU16(output + 38, record.location.accuracy_cm);
|
||||
writeU16(output + 40, 0);
|
||||
writeU64(output + 42, record.location.timestamp_seconds);
|
||||
writeU64(output + 50, record.location.sensor_timestamp_seconds);
|
||||
writeU64(output + 58, record.source_timestamp_millis);
|
||||
writeU64(output + 66, record.received_at_millis);
|
||||
writeU64(output + 74, record.expires_at_millis);
|
||||
writeU32(output + 82, record.approx_radius_meters);
|
||||
}
|
||||
|
||||
PeerLocationRecord decodeLocation(const uint8_t* data) {
|
||||
PeerLocationRecord record{};
|
||||
std::memcpy(record.peer.bytes, data, PEER_ID_SIZE);
|
||||
record.has_expiry = (data[16] & LOCATION_HAS_EXPIRY) != 0;
|
||||
record.location.latitude_e6 = readI32(data + 18);
|
||||
record.location.longitude_e6 = readI32(data + 22);
|
||||
record.location.altitude_cm = readI32(data + 26);
|
||||
record.location.speed_centi_kmh = readU32(data + 30);
|
||||
record.location.bearing_cdeg = readI32(data + 34);
|
||||
record.location.accuracy_cm = readU16(data + 38);
|
||||
record.location.timestamp_seconds = readU64(data + 42);
|
||||
record.location.sensor_timestamp_seconds = readU64(data + 50);
|
||||
record.source_timestamp_millis = readU64(data + 58);
|
||||
record.received_at_millis = readU64(data + 66);
|
||||
record.expires_at_millis = readU64(data + 74);
|
||||
record.approx_radius_meters = readU32(data + 82);
|
||||
return record;
|
||||
}
|
||||
|
||||
LocationStateRecordResult validateEncodedRecords(
|
||||
const uint8_t* payload,
|
||||
std::size_t session_count,
|
||||
std::size_t location_count) {
|
||||
for (std::size_t index = 0; index < session_count; ++index) {
|
||||
const uint8_t* current = payload + index * LOCATION_STATE_SESSION_BYTES;
|
||||
if ((current[16] & ~0x03U) != 0 || current[17] != 0) {
|
||||
return LocationStateRecordResult::MALFORMED;
|
||||
}
|
||||
const ShareRestoreEntry entry = decodeSession(current);
|
||||
if (!validSession(entry)) return LocationStateRecordResult::OUT_OF_RANGE;
|
||||
for (std::size_t prior = 0; prior < index; ++prior) {
|
||||
if (std::memcmp(payload + prior * LOCATION_STATE_SESSION_BYTES,
|
||||
current, PEER_ID_SIZE) == 0) {
|
||||
return LocationStateRecordResult::DUPLICATE_PEER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t* locations =
|
||||
payload + session_count * LOCATION_STATE_SESSION_BYTES;
|
||||
for (std::size_t index = 0; index < location_count; ++index) {
|
||||
const uint8_t* current = locations + index * LOCATION_STATE_LOCATION_BYTES;
|
||||
if ((current[16] & ~0x01U) != 0 || current[17] != 0 ||
|
||||
readU16(current + 40) != 0) {
|
||||
return LocationStateRecordResult::MALFORMED;
|
||||
}
|
||||
const PeerLocationRecord record = decodeLocation(current);
|
||||
if (!validLocation(record)) return LocationStateRecordResult::OUT_OF_RANGE;
|
||||
for (std::size_t prior = 0; prior < index; ++prior) {
|
||||
if (std::memcmp(locations + prior * LOCATION_STATE_LOCATION_BYTES,
|
||||
current, PEER_ID_SIZE) == 0) {
|
||||
return LocationStateRecordResult::DUPLICATE_PEER;
|
||||
}
|
||||
}
|
||||
}
|
||||
return LocationStateRecordResult::OK;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LocationStateRecordResult encodeLocationStateRecord(
|
||||
const LocationStateSnapshot& input,
|
||||
uint8_t* output,
|
||||
std::size_t capacity,
|
||||
std::size_t& written_or_required) {
|
||||
written_or_required = 0;
|
||||
if (input.session_count > MAX_SHARE_SESSIONS ||
|
||||
input.location_count > MAX_PEER_LOCATIONS) {
|
||||
return LocationStateRecordResult::OUT_OF_RANGE;
|
||||
}
|
||||
for (std::size_t index = 0; index < input.session_count; ++index) {
|
||||
if (!validSession(input.sessions[index])) {
|
||||
return LocationStateRecordResult::OUT_OF_RANGE;
|
||||
}
|
||||
if (duplicateSession(input, index)) {
|
||||
return LocationStateRecordResult::DUPLICATE_PEER;
|
||||
}
|
||||
}
|
||||
for (std::size_t index = 0; index < input.location_count; ++index) {
|
||||
if (!validLocation(input.locations[index])) {
|
||||
return LocationStateRecordResult::OUT_OF_RANGE;
|
||||
}
|
||||
if (duplicateLocation(input, index)) {
|
||||
return LocationStateRecordResult::DUPLICATE_PEER;
|
||||
}
|
||||
}
|
||||
|
||||
const std::size_t payload_size =
|
||||
input.session_count * LOCATION_STATE_SESSION_BYTES +
|
||||
input.location_count * LOCATION_STATE_LOCATION_BYTES;
|
||||
const std::size_t required =
|
||||
LOCATION_STATE_HEADER_BYTES + payload_size + LOCATION_STATE_CRC_BYTES;
|
||||
written_or_required = required;
|
||||
if (capacity < required) return LocationStateRecordResult::BUFFER_TOO_SMALL;
|
||||
if (output == nullptr) return LocationStateRecordResult::INVALID_ARGUMENT;
|
||||
|
||||
std::memcpy(output, MAGIC, sizeof(MAGIC));
|
||||
writeU16(output + 4, LOCATION_STATE_SCHEMA_VERSION);
|
||||
writeU16(output + 6, 0);
|
||||
writeU32(output + 8, static_cast<uint32_t>(payload_size));
|
||||
writeU16(output + 12, static_cast<uint16_t>(input.session_count));
|
||||
writeU16(output + 14, static_cast<uint16_t>(input.location_count));
|
||||
uint8_t* cursor = output + LOCATION_STATE_HEADER_BYTES;
|
||||
for (std::size_t index = 0; index < input.session_count; ++index) {
|
||||
encodeSession(input.sessions[index], cursor);
|
||||
cursor += LOCATION_STATE_SESSION_BYTES;
|
||||
}
|
||||
for (std::size_t index = 0; index < input.location_count; ++index) {
|
||||
encodeLocation(input.locations[index], cursor);
|
||||
cursor += LOCATION_STATE_LOCATION_BYTES;
|
||||
}
|
||||
writeU32(cursor, crc32(output, required - LOCATION_STATE_CRC_BYTES));
|
||||
return LocationStateRecordResult::OK;
|
||||
}
|
||||
|
||||
LocationStateRecordResult decodeLocationStateRecord(
|
||||
const uint8_t* data,
|
||||
std::size_t size,
|
||||
LocationStateSnapshot& output) {
|
||||
if (data == nullptr) return LocationStateRecordResult::INVALID_ARGUMENT;
|
||||
if (size < LOCATION_STATE_HEADER_BYTES + LOCATION_STATE_CRC_BYTES) {
|
||||
return LocationStateRecordResult::MALFORMED;
|
||||
}
|
||||
if (std::memcmp(data, MAGIC, sizeof(MAGIC)) != 0) {
|
||||
return LocationStateRecordResult::BAD_MAGIC;
|
||||
}
|
||||
if (readU16(data + 4) != LOCATION_STATE_SCHEMA_VERSION) {
|
||||
return LocationStateRecordResult::UNSUPPORTED_VERSION;
|
||||
}
|
||||
if (readU16(data + 6) != 0) return LocationStateRecordResult::MALFORMED;
|
||||
|
||||
const std::size_t payload_size = readU32(data + 8);
|
||||
const std::size_t session_count = readU16(data + 12);
|
||||
const std::size_t location_count = readU16(data + 14);
|
||||
if (session_count > MAX_SHARE_SESSIONS ||
|
||||
location_count > MAX_PEER_LOCATIONS) {
|
||||
return LocationStateRecordResult::OUT_OF_RANGE;
|
||||
}
|
||||
const std::size_t expected_payload =
|
||||
session_count * LOCATION_STATE_SESSION_BYTES +
|
||||
location_count * LOCATION_STATE_LOCATION_BYTES;
|
||||
if (payload_size != expected_payload) {
|
||||
return LocationStateRecordResult::MALFORMED;
|
||||
}
|
||||
const std::size_t expected_size =
|
||||
LOCATION_STATE_HEADER_BYTES + expected_payload + LOCATION_STATE_CRC_BYTES;
|
||||
if (size != expected_size) return LocationStateRecordResult::MALFORMED;
|
||||
if (readU32(data + size - LOCATION_STATE_CRC_BYTES) !=
|
||||
crc32(data, size - LOCATION_STATE_CRC_BYTES)) {
|
||||
return LocationStateRecordResult::CRC_MISMATCH;
|
||||
}
|
||||
|
||||
const uint8_t* payload = data + LOCATION_STATE_HEADER_BYTES;
|
||||
const LocationStateRecordResult validation =
|
||||
validateEncodedRecords(payload, session_count, location_count);
|
||||
if (validation != LocationStateRecordResult::OK) return validation;
|
||||
|
||||
output.session_count = 0;
|
||||
output.location_count = 0;
|
||||
for (std::size_t index = 0; index < MAX_SHARE_SESSIONS; ++index) {
|
||||
output.sessions[index] = ShareRestoreEntry{};
|
||||
}
|
||||
for (std::size_t index = 0; index < MAX_PEER_LOCATIONS; ++index) {
|
||||
output.locations[index] = PeerLocationRecord{};
|
||||
}
|
||||
output.session_count = session_count;
|
||||
output.location_count = location_count;
|
||||
const uint8_t* cursor = payload;
|
||||
for (std::size_t index = 0; index < session_count; ++index) {
|
||||
output.sessions[index] = decodeSession(cursor);
|
||||
cursor += LOCATION_STATE_SESSION_BYTES;
|
||||
}
|
||||
for (std::size_t index = 0; index < location_count; ++index) {
|
||||
output.locations[index] = decodeLocation(cursor);
|
||||
cursor += LOCATION_STATE_LOCATION_BYTES;
|
||||
}
|
||||
return LocationStateRecordResult::OK;
|
||||
}
|
||||
|
||||
} // namespace Telemetry
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef PYXIS_TELEMETRY_LOCATION_STATE_RECORD_H
|
||||
#define PYXIS_TELEMETRY_LOCATION_STATE_RECORD_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "LocationShareScheduler.h"
|
||||
#include "LocationShareState.h"
|
||||
|
||||
namespace Telemetry {
|
||||
|
||||
constexpr uint16_t LOCATION_STATE_SCHEMA_VERSION = 1;
|
||||
constexpr std::size_t LOCATION_STATE_HEADER_BYTES = 16;
|
||||
constexpr std::size_t LOCATION_STATE_SESSION_BYTES = 34;
|
||||
constexpr std::size_t LOCATION_STATE_LOCATION_BYTES = 86;
|
||||
constexpr std::size_t LOCATION_STATE_CRC_BYTES = 4;
|
||||
constexpr std::size_t MAX_LOCATION_STATE_RECORD_BYTES =
|
||||
LOCATION_STATE_HEADER_BYTES +
|
||||
MAX_SHARE_SESSIONS * LOCATION_STATE_SESSION_BYTES +
|
||||
MAX_PEER_LOCATIONS * LOCATION_STATE_LOCATION_BYTES +
|
||||
LOCATION_STATE_CRC_BYTES;
|
||||
static_assert(MAX_LOCATION_STATE_RECORD_BYTES <= 4096,
|
||||
"persistent location record must remain bounded to 4 KiB");
|
||||
|
||||
struct LocationStateSnapshot {
|
||||
std::size_t session_count = 0;
|
||||
ShareRestoreEntry sessions[MAX_SHARE_SESSIONS]{};
|
||||
std::size_t location_count = 0;
|
||||
PeerLocationRecord locations[MAX_PEER_LOCATIONS]{};
|
||||
};
|
||||
|
||||
enum class LocationStateRecordResult : uint8_t {
|
||||
OK,
|
||||
INVALID_ARGUMENT,
|
||||
BUFFER_TOO_SMALL,
|
||||
BAD_MAGIC,
|
||||
UNSUPPORTED_VERSION,
|
||||
MALFORMED,
|
||||
CRC_MISMATCH,
|
||||
OUT_OF_RANGE,
|
||||
DUPLICATE_PEER,
|
||||
};
|
||||
|
||||
LocationStateRecordResult encodeLocationStateRecord(
|
||||
const LocationStateSnapshot& input,
|
||||
uint8_t* output,
|
||||
std::size_t capacity,
|
||||
std::size_t& written_or_required);
|
||||
|
||||
LocationStateRecordResult decodeLocationStateRecord(
|
||||
const uint8_t* data,
|
||||
std::size_t size,
|
||||
LocationStateSnapshot& output);
|
||||
|
||||
} // namespace Telemetry
|
||||
|
||||
#endif // PYXIS_TELEMETRY_LOCATION_STATE_RECORD_H
|
||||
@@ -0,0 +1,296 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "Telemetry/LocationStateRecord.h"
|
||||
|
||||
namespace {
|
||||
|
||||
int passed = 0;
|
||||
int failures = 0;
|
||||
|
||||
#define CHECK(expr) \
|
||||
do { \
|
||||
if (expr) { \
|
||||
++passed; \
|
||||
} else { \
|
||||
++failures; \
|
||||
std::cerr << "FAIL line " << __LINE__ << ": " #expr << '\n'; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
Telemetry::PeerId peer(uint8_t seed) {
|
||||
Telemetry::PeerId id{};
|
||||
for (std::size_t i = 0; i < Telemetry::PEER_ID_SIZE; ++i) {
|
||||
id.bytes[i] = static_cast<uint8_t>(seed + i);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
Telemetry::LocationStateSnapshot sample() {
|
||||
Telemetry::LocationStateSnapshot state{};
|
||||
state.session_count = 1;
|
||||
state.sessions[0].peer = peer(0);
|
||||
auto& session = state.sessions[0].record;
|
||||
session.cadence_millis = 60000;
|
||||
session.approx_radius_meters = 250;
|
||||
session.has_expiry = true;
|
||||
session.expires_at_millis = 1700000900000ULL;
|
||||
session.cease_pending = true;
|
||||
|
||||
state.location_count = 1;
|
||||
auto& record = state.locations[0];
|
||||
record.peer = peer(16);
|
||||
record.location.latitude_e6 = 37774900;
|
||||
record.location.longitude_e6 = -122419400;
|
||||
record.location.altitude_cm = 12345;
|
||||
record.location.speed_centi_kmh = 1234;
|
||||
record.location.bearing_cdeg = 27000;
|
||||
record.location.accuracy_cm = 500;
|
||||
record.location.timestamp_seconds = 1700000000ULL;
|
||||
record.location.sensor_timestamp_seconds = 1699999999ULL;
|
||||
record.source_timestamp_millis = 1700000000123ULL;
|
||||
record.received_at_millis = 1700000001000ULL;
|
||||
record.has_expiry = true;
|
||||
record.expires_at_millis = 1700000900000ULL;
|
||||
record.approx_radius_meters = 250;
|
||||
return state;
|
||||
}
|
||||
|
||||
uint32_t crc32(const uint8_t* data, std::size_t size) {
|
||||
uint32_t crc = 0xffffffffU;
|
||||
for (std::size_t i = 0; i < size; ++i) {
|
||||
crc ^= data[i];
|
||||
for (uint8_t bit = 0; bit < 8; ++bit) {
|
||||
crc = (crc & 1U) != 0
|
||||
? (crc >> 1U) ^ 0xedb88320U
|
||||
: crc >> 1U;
|
||||
}
|
||||
}
|
||||
return crc ^ 0xffffffffU;
|
||||
}
|
||||
|
||||
void writeU16(uint8_t* output, uint16_t value) {
|
||||
output[0] = static_cast<uint8_t>(value >> 8U);
|
||||
output[1] = static_cast<uint8_t>(value);
|
||||
}
|
||||
|
||||
void writeU32(uint8_t* output, uint32_t value) {
|
||||
output[0] = static_cast<uint8_t>(value >> 24U);
|
||||
output[1] = static_cast<uint8_t>(value >> 16U);
|
||||
output[2] = static_cast<uint8_t>(value >> 8U);
|
||||
output[3] = static_cast<uint8_t>(value);
|
||||
}
|
||||
|
||||
void repairCrc(uint8_t* bytes, std::size_t size) {
|
||||
writeU32(bytes + size - 4, crc32(bytes, size - 4));
|
||||
}
|
||||
|
||||
bool sameState(
|
||||
const Telemetry::LocationStateSnapshot& left,
|
||||
const Telemetry::LocationStateSnapshot& right) {
|
||||
return std::memcmp(&left, &right, sizeof(left)) == 0;
|
||||
}
|
||||
|
||||
void matchesIndependentGoldenBytes() {
|
||||
static const uint8_t expected[] = {
|
||||
0x50,0x59,0x4c,0x53,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x78,0x00,0x01,0x00,0x01,
|
||||
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,
|
||||
0x03,0x00,0x00,0x00,0xea,0x60,0x00,0x00,0x00,0xfa,0x00,0x00,0x01,0x8b,0xcf,0xf3,
|
||||
0x23,0xa0,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,
|
||||
0x1e,0x1f,0x01,0x00,0x02,0x40,0x66,0x34,0xf8,0xb4,0x07,0x38,0x00,0x00,0x30,0x39,
|
||||
0x00,0x00,0x04,0xd2,0x00,0x00,0x69,0x78,0x01,0xf4,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x65,0x53,0xf1,0x00,0x00,0x00,0x00,0x00,0x65,0x53,0xf0,0xff,0x00,0x00,0x01,0x8b,
|
||||
0xcf,0xe5,0x68,0x7b,0x00,0x00,0x01,0x8b,0xcf,0xe5,0x6b,0xe8,0x00,0x00,0x01,0x8b,
|
||||
0xcf,0xf3,0x23,0xa0,0x00,0x00,0x00,0xfa,0xa4,0x54,0x8e,0x49,
|
||||
};
|
||||
uint8_t encoded[Telemetry::MAX_LOCATION_STATE_RECORD_BYTES]{};
|
||||
std::size_t written = 0;
|
||||
CHECK(Telemetry::encodeLocationStateRecord(
|
||||
sample(), encoded, sizeof(encoded), written) ==
|
||||
Telemetry::LocationStateRecordResult::OK);
|
||||
CHECK(written == sizeof(expected));
|
||||
CHECK(std::memcmp(encoded, expected, sizeof(expected)) == 0);
|
||||
|
||||
Telemetry::LocationStateSnapshot decoded{};
|
||||
CHECK(Telemetry::decodeLocationStateRecord(
|
||||
expected, sizeof(expected), decoded) ==
|
||||
Telemetry::LocationStateRecordResult::OK);
|
||||
CHECK(sameState(decoded, sample()));
|
||||
}
|
||||
|
||||
void rejectsEveryTruncationTransactionally() {
|
||||
uint8_t encoded[Telemetry::MAX_LOCATION_STATE_RECORD_BYTES]{};
|
||||
std::size_t written = 0;
|
||||
CHECK(Telemetry::encodeLocationStateRecord(
|
||||
sample(), encoded, sizeof(encoded), written) ==
|
||||
Telemetry::LocationStateRecordResult::OK);
|
||||
for (std::size_t size = 0; size < written; ++size) {
|
||||
Telemetry::LocationStateSnapshot output = sample();
|
||||
output.sessions[0].record.cadence_millis = 1234;
|
||||
const auto before = output;
|
||||
CHECK(Telemetry::decodeLocationStateRecord(encoded, size, output) !=
|
||||
Telemetry::LocationStateRecordResult::OK);
|
||||
CHECK(sameState(output, before));
|
||||
}
|
||||
}
|
||||
|
||||
void rejectsCorruptionAndUnsupportedHeadersTransactionally() {
|
||||
uint8_t encoded[Telemetry::MAX_LOCATION_STATE_RECORD_BYTES]{};
|
||||
std::size_t written = 0;
|
||||
CHECK(Telemetry::encodeLocationStateRecord(
|
||||
sample(), encoded, sizeof(encoded), written) ==
|
||||
Telemetry::LocationStateRecordResult::OK);
|
||||
Telemetry::LocationStateSnapshot sentinel = sample();
|
||||
sentinel.location_count = 0;
|
||||
const auto before = sentinel;
|
||||
|
||||
encoded[70] ^= 1U;
|
||||
CHECK(Telemetry::decodeLocationStateRecord(encoded, written, sentinel) ==
|
||||
Telemetry::LocationStateRecordResult::CRC_MISMATCH);
|
||||
CHECK(sameState(sentinel, before));
|
||||
encoded[70] ^= 1U;
|
||||
|
||||
encoded[0] = 'X';
|
||||
repairCrc(encoded, written);
|
||||
CHECK(Telemetry::decodeLocationStateRecord(encoded, written, sentinel) ==
|
||||
Telemetry::LocationStateRecordResult::BAD_MAGIC);
|
||||
CHECK(sameState(sentinel, before));
|
||||
encoded[0] = 'P';
|
||||
|
||||
writeU16(encoded + 4, 2);
|
||||
repairCrc(encoded, written);
|
||||
CHECK(Telemetry::decodeLocationStateRecord(encoded, written, sentinel) ==
|
||||
Telemetry::LocationStateRecordResult::UNSUPPORTED_VERSION);
|
||||
CHECK(sameState(sentinel, before));
|
||||
}
|
||||
|
||||
void rejectsCountOverflowDuplicatesAndInvalidCoordinates() {
|
||||
uint8_t encoded[Telemetry::MAX_LOCATION_STATE_RECORD_BYTES]{};
|
||||
std::size_t written = 0;
|
||||
CHECK(Telemetry::encodeLocationStateRecord(
|
||||
sample(), encoded, sizeof(encoded), written) ==
|
||||
Telemetry::LocationStateRecordResult::OK);
|
||||
Telemetry::LocationStateSnapshot output{};
|
||||
|
||||
writeU16(encoded + 12, 33);
|
||||
repairCrc(encoded, written);
|
||||
CHECK(Telemetry::decodeLocationStateRecord(encoded, written, output) ==
|
||||
Telemetry::LocationStateRecordResult::OUT_OF_RANGE);
|
||||
|
||||
auto two = sample();
|
||||
two.session_count = 2;
|
||||
two.sessions[1] = two.sessions[0];
|
||||
two.sessions[1].peer = peer(32);
|
||||
CHECK(Telemetry::encodeLocationStateRecord(
|
||||
two, encoded, sizeof(encoded), written) ==
|
||||
Telemetry::LocationStateRecordResult::OK);
|
||||
std::memcpy(encoded + Telemetry::LOCATION_STATE_HEADER_BYTES +
|
||||
Telemetry::LOCATION_STATE_SESSION_BYTES,
|
||||
encoded + Telemetry::LOCATION_STATE_HEADER_BYTES,
|
||||
Telemetry::PEER_ID_SIZE);
|
||||
repairCrc(encoded, written);
|
||||
CHECK(Telemetry::decodeLocationStateRecord(encoded, written, output) ==
|
||||
Telemetry::LocationStateRecordResult::DUPLICATE_PEER);
|
||||
|
||||
two = sample();
|
||||
two.location_count = 2;
|
||||
two.locations[1] = two.locations[0];
|
||||
two.locations[1].peer = peer(48);
|
||||
CHECK(Telemetry::encodeLocationStateRecord(
|
||||
two, encoded, sizeof(encoded), written) ==
|
||||
Telemetry::LocationStateRecordResult::OK);
|
||||
const std::size_t first_location =
|
||||
Telemetry::LOCATION_STATE_HEADER_BYTES +
|
||||
Telemetry::LOCATION_STATE_SESSION_BYTES;
|
||||
std::memcpy(encoded + first_location +
|
||||
Telemetry::LOCATION_STATE_LOCATION_BYTES,
|
||||
encoded + first_location, Telemetry::PEER_ID_SIZE);
|
||||
repairCrc(encoded, written);
|
||||
CHECK(Telemetry::decodeLocationStateRecord(encoded, written, output) ==
|
||||
Telemetry::LocationStateRecordResult::DUPLICATE_PEER);
|
||||
|
||||
CHECK(Telemetry::encodeLocationStateRecord(
|
||||
sample(), encoded, sizeof(encoded), written) ==
|
||||
Telemetry::LocationStateRecordResult::OK);
|
||||
writeU32(encoded + first_location + 18, 90000001U);
|
||||
repairCrc(encoded, written);
|
||||
CHECK(Telemetry::decodeLocationStateRecord(encoded, written, output) ==
|
||||
Telemetry::LocationStateRecordResult::OUT_OF_RANGE);
|
||||
|
||||
CHECK(Telemetry::encodeLocationStateRecord(
|
||||
sample(), encoded, sizeof(encoded), written) ==
|
||||
Telemetry::LocationStateRecordResult::OK);
|
||||
encoded[Telemetry::LOCATION_STATE_HEADER_BYTES + 16] |= 0x80U;
|
||||
repairCrc(encoded, written);
|
||||
CHECK(Telemetry::decodeLocationStateRecord(encoded, written, output) ==
|
||||
Telemetry::LocationStateRecordResult::MALFORMED);
|
||||
|
||||
auto duplicate = sample();
|
||||
duplicate.session_count = 2;
|
||||
duplicate.sessions[1] = duplicate.sessions[0];
|
||||
CHECK(Telemetry::encodeLocationStateRecord(
|
||||
duplicate, encoded, sizeof(encoded), written) ==
|
||||
Telemetry::LocationStateRecordResult::DUPLICATE_PEER);
|
||||
|
||||
duplicate = sample();
|
||||
duplicate.location_count = 2;
|
||||
duplicate.locations[1] = duplicate.locations[0];
|
||||
CHECK(Telemetry::encodeLocationStateRecord(
|
||||
duplicate, encoded, sizeof(encoded), written) ==
|
||||
Telemetry::LocationStateRecordResult::DUPLICATE_PEER);
|
||||
|
||||
auto invalid = sample();
|
||||
invalid.locations[0].location.latitude_e6 = 90000001;
|
||||
CHECK(Telemetry::encodeLocationStateRecord(
|
||||
invalid, encoded, sizeof(encoded), written) ==
|
||||
Telemetry::LocationStateRecordResult::OUT_OF_RANGE);
|
||||
}
|
||||
|
||||
void encodingIsAtomicAndBounded() {
|
||||
uint8_t encoded[Telemetry::MAX_LOCATION_STATE_RECORD_BYTES];
|
||||
std::memset(encoded, 0xa5, sizeof(encoded));
|
||||
std::size_t required = 0;
|
||||
for (std::size_t capacity = 0; capacity < 140; ++capacity) {
|
||||
std::memset(encoded, 0xa5, sizeof(encoded));
|
||||
CHECK(Telemetry::encodeLocationStateRecord(
|
||||
sample(), encoded, capacity, required) ==
|
||||
Telemetry::LocationStateRecordResult::BUFFER_TOO_SMALL);
|
||||
CHECK(required == 140);
|
||||
CHECK(encoded[0] == 0xa5);
|
||||
}
|
||||
|
||||
auto maximum = sample();
|
||||
maximum.session_count = Telemetry::MAX_SHARE_SESSIONS;
|
||||
maximum.location_count = Telemetry::MAX_PEER_LOCATIONS;
|
||||
for (std::size_t i = 0; i < maximum.session_count; ++i) {
|
||||
maximum.sessions[i] = maximum.sessions[0];
|
||||
maximum.sessions[i].peer = peer(static_cast<uint8_t>(i));
|
||||
}
|
||||
for (std::size_t i = 0; i < maximum.location_count; ++i) {
|
||||
maximum.locations[i] = maximum.locations[0];
|
||||
maximum.locations[i].peer = peer(static_cast<uint8_t>(64 + i));
|
||||
}
|
||||
CHECK(Telemetry::encodeLocationStateRecord(
|
||||
maximum, encoded, sizeof(encoded), required) ==
|
||||
Telemetry::LocationStateRecordResult::OK);
|
||||
CHECK(required <= Telemetry::MAX_LOCATION_STATE_RECORD_BYTES);
|
||||
Telemetry::LocationStateSnapshot decoded{};
|
||||
CHECK(Telemetry::decodeLocationStateRecord(encoded, required, decoded) ==
|
||||
Telemetry::LocationStateRecordResult::OK);
|
||||
CHECK(sameState(decoded, maximum));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
matchesIndependentGoldenBytes();
|
||||
rejectsEveryTruncationTransactionally();
|
||||
rejectsCorruptionAndUnsupportedHeadersTransactionally();
|
||||
rejectsCountOverflowDuplicatesAndInvalidCoordinates();
|
||||
encodingIsAtomicAndBounded();
|
||||
std::cout << "location state record: " << passed << " passed, "
|
||||
<< failures << " failed\n";
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Compile and execute versioned location-state record tests."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from native_test import compile_and_run
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
PYXIS_ROOT = HERE.parents[1]
|
||||
|
||||
|
||||
def test_location_state_record(tmp_path):
|
||||
ran = compile_and_run(
|
||||
tmp_path,
|
||||
name="test_location_state_record",
|
||||
sources=[
|
||||
HERE / "test_location_state_record.cpp",
|
||||
PYXIS_ROOT / "lib" / "tdeck_ui" / "Telemetry" / "LocationStateRecord.cpp",
|
||||
],
|
||||
include_dirs=[PYXIS_ROOT / "lib" / "tdeck_ui"],
|
||||
sanitize=True,
|
||||
timeout=60,
|
||||
)
|
||||
assert "0 failed" in ran.stdout
|
||||
Reference in New Issue
Block a user