mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-08-16 15:50:11 +00:00
678 lines
24 KiB
C++
678 lines
24 KiB
C++
#include "LocationTelemetryCodec.h"
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <limits>
|
|
|
|
namespace Telemetry {
|
|
namespace {
|
|
|
|
constexpr std::size_t MAX_MAP_ENTRIES = 32;
|
|
constexpr std::size_t MAX_LOCATION_ELEMENTS = 16;
|
|
constexpr std::size_t MAX_SKIP_DEPTH = 8;
|
|
constexpr std::size_t MAX_SKIP_ITEMS = 64;
|
|
constexpr std::size_t MAX_ENCODED_TELEMETRY = 96;
|
|
constexpr std::size_t MAX_CUSTOM_META_ENTRIES = 16;
|
|
constexpr std::size_t MAX_ENCODED_CUSTOM_META = 128;
|
|
|
|
class Cursor {
|
|
public:
|
|
Cursor(const uint8_t* data, std::size_t size) : data_(data), size_(size) {}
|
|
|
|
bool readByte(uint8_t& value) {
|
|
if (position_ >= size_) return false;
|
|
value = data_[position_++];
|
|
return true;
|
|
}
|
|
|
|
bool readBytes(std::size_t count, const uint8_t*& value) {
|
|
if (count > remaining()) return false;
|
|
value = data_ + position_;
|
|
position_ += count;
|
|
return true;
|
|
}
|
|
|
|
bool readUnsigned(uint64_t& value) {
|
|
uint8_t marker = 0;
|
|
if (!readByte(marker)) return false;
|
|
if (marker <= 0x7fU) {
|
|
value = marker;
|
|
return true;
|
|
}
|
|
|
|
std::size_t width = 0;
|
|
bool signed_value = false;
|
|
switch (marker) {
|
|
case 0xcc: width = 1; break;
|
|
case 0xcd: width = 2; break;
|
|
case 0xce: width = 4; break;
|
|
case 0xcf: width = 8; break;
|
|
case 0xd0: width = 1; signed_value = true; break;
|
|
case 0xd1: width = 2; signed_value = true; break;
|
|
case 0xd2: width = 4; signed_value = true; break;
|
|
case 0xd3: width = 8; signed_value = true; break;
|
|
default: return false;
|
|
}
|
|
|
|
const uint8_t* bytes = nullptr;
|
|
if (!readBytes(width, bytes)) return false;
|
|
uint64_t decoded = 0;
|
|
for (std::size_t index = 0; index < width; ++index) {
|
|
decoded = (decoded << 8U) | bytes[index];
|
|
}
|
|
if (signed_value && (bytes[0] & 0x80U) != 0) return false;
|
|
value = decoded;
|
|
return true;
|
|
}
|
|
|
|
bool readBoolean(bool& value) {
|
|
uint8_t marker = 0;
|
|
if (!readByte(marker)) return false;
|
|
if (marker == 0xc2U) {
|
|
value = false;
|
|
return true;
|
|
}
|
|
if (marker == 0xc3U) {
|
|
value = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool readString(BinaryView& value) {
|
|
uint8_t marker = 0;
|
|
if (!readByte(marker)) return false;
|
|
|
|
std::size_t length = 0;
|
|
if ((marker & 0xe0U) == 0xa0U) {
|
|
length = marker & 0x1fU;
|
|
} else if (marker == 0xd9U) {
|
|
uint8_t byte_length = 0;
|
|
if (!readByte(byte_length)) return false;
|
|
length = byte_length;
|
|
} else if (marker == 0xdaU) {
|
|
if (!readBigEndianSize(2, length)) return false;
|
|
} else if (marker == 0xdbU) {
|
|
if (!readBigEndianSize(4, length)) return false;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
const uint8_t* bytes = nullptr;
|
|
if (!readBytes(length, bytes)) return false;
|
|
value = BinaryView{bytes, length};
|
|
return true;
|
|
}
|
|
|
|
bool readMapSize(std::size_t& count) {
|
|
uint8_t marker = 0;
|
|
if (!readByte(marker)) return false;
|
|
if ((marker & 0xf0U) == 0x80U) {
|
|
count = marker & 0x0fU;
|
|
return true;
|
|
}
|
|
return readSizedContainer(marker, 0xdeU, 0xdfU, count);
|
|
}
|
|
|
|
bool readArraySize(std::size_t& count) {
|
|
uint8_t marker = 0;
|
|
if (!readByte(marker)) return false;
|
|
if ((marker & 0xf0U) == 0x90U) {
|
|
count = marker & 0x0fU;
|
|
return true;
|
|
}
|
|
return readSizedContainer(marker, 0xdcU, 0xddU, count);
|
|
}
|
|
|
|
bool readBinary(BinaryView& value) {
|
|
uint8_t marker = 0;
|
|
if (!readByte(marker)) return false;
|
|
|
|
std::size_t length = 0;
|
|
if (marker == 0xc4U) {
|
|
uint8_t byte_length = 0;
|
|
if (!readByte(byte_length)) return false;
|
|
length = byte_length;
|
|
} else if (marker == 0xc5U) {
|
|
if (!readBigEndianSize(2, length)) return false;
|
|
} else if (marker == 0xc6U) {
|
|
if (!readBigEndianSize(4, length)) return false;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
const uint8_t* bytes = nullptr;
|
|
if (!readBytes(length, bytes)) return false;
|
|
value = BinaryView{bytes, length};
|
|
return true;
|
|
}
|
|
|
|
bool skipValue(std::size_t depth, std::size_t& budget) {
|
|
if (depth > MAX_SKIP_DEPTH || budget == 0) return false;
|
|
--budget;
|
|
|
|
uint8_t marker = 0;
|
|
if (!peekByte(marker)) return false;
|
|
|
|
if (marker <= 0x7fU || marker >= 0xe0U || marker == 0xc0U ||
|
|
marker == 0xc2U || marker == 0xc3U) {
|
|
++position_;
|
|
return true;
|
|
}
|
|
if ((marker & 0xe0U) == 0xa0U) {
|
|
++position_;
|
|
return skipBytes(marker & 0x1fU);
|
|
}
|
|
if ((marker & 0xf0U) == 0x90U) {
|
|
++position_;
|
|
return skipChildren(marker & 0x0fU, depth, budget, false);
|
|
}
|
|
if ((marker & 0xf0U) == 0x80U) {
|
|
++position_;
|
|
return skipChildren(marker & 0x0fU, depth, budget, true);
|
|
}
|
|
|
|
++position_;
|
|
switch (marker) {
|
|
case 0xc4: return skipLengthPrefixed(1, 0);
|
|
case 0xc5: return skipLengthPrefixed(2, 0);
|
|
case 0xc6: return skipLengthPrefixed(4, 0);
|
|
case 0xca: return skipBytes(4);
|
|
case 0xcb: return skipBytes(8);
|
|
case 0xcc: case 0xd0: return skipBytes(1);
|
|
case 0xcd: case 0xd1: return skipBytes(2);
|
|
case 0xce: case 0xd2: return skipBytes(4);
|
|
case 0xcf: case 0xd3: return skipBytes(8);
|
|
case 0xd4: return skipBytes(2); // type + 1-byte payload
|
|
case 0xd5: return skipBytes(3); // type + 2-byte payload
|
|
case 0xd6: return skipBytes(5); // type + 4-byte payload
|
|
case 0xd7: return skipBytes(9); // type + 8-byte payload
|
|
case 0xd8: return skipBytes(17); // type + 16-byte payload
|
|
case 0xd9: return skipLengthPrefixed(1, 0);
|
|
case 0xda: return skipLengthPrefixed(2, 0);
|
|
case 0xdb: return skipLengthPrefixed(4, 0);
|
|
case 0xc7: return skipLengthPrefixed(1, 1);
|
|
case 0xc8: return skipLengthPrefixed(2, 1);
|
|
case 0xc9: return skipLengthPrefixed(4, 1);
|
|
case 0xdc:
|
|
case 0xdd: {
|
|
std::size_t count = 0;
|
|
if (!readBigEndianSize(marker == 0xdcU ? 2 : 4, count)) return false;
|
|
return skipChildren(count, depth, budget, false);
|
|
}
|
|
case 0xde:
|
|
case 0xdf: {
|
|
std::size_t count = 0;
|
|
if (!readBigEndianSize(marker == 0xdeU ? 2 : 4, count)) return false;
|
|
return skipChildren(count, depth, budget, true);
|
|
}
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
bool atEnd() const { return position_ == size_; }
|
|
|
|
private:
|
|
bool peekByte(uint8_t& value) const {
|
|
if (position_ >= size_) return false;
|
|
value = data_[position_];
|
|
return true;
|
|
}
|
|
|
|
std::size_t remaining() const { return size_ - position_; }
|
|
|
|
bool skipBytes(std::size_t count) {
|
|
if (count > remaining()) return false;
|
|
position_ += count;
|
|
return true;
|
|
}
|
|
|
|
bool readBigEndianSize(std::size_t width, std::size_t& value) {
|
|
const uint8_t* bytes = nullptr;
|
|
if (!readBytes(width, bytes)) return false;
|
|
uint64_t decoded = 0;
|
|
for (std::size_t index = 0; index < width; ++index) {
|
|
decoded = (decoded << 8U) | bytes[index];
|
|
}
|
|
if (decoded > std::numeric_limits<std::size_t>::max()) return false;
|
|
value = static_cast<std::size_t>(decoded);
|
|
return true;
|
|
}
|
|
|
|
bool readSizedContainer(uint8_t marker, uint8_t marker16, uint8_t marker32,
|
|
std::size_t& count) {
|
|
if (marker == marker16) return readBigEndianSize(2, count);
|
|
if (marker == marker32) return readBigEndianSize(4, count);
|
|
return false;
|
|
}
|
|
|
|
bool skipLengthPrefixed(std::size_t width, std::size_t suffix) {
|
|
std::size_t length = 0;
|
|
if (!readBigEndianSize(width, length)) return false;
|
|
if (length > std::numeric_limits<std::size_t>::max() - suffix) return false;
|
|
return skipBytes(length + suffix);
|
|
}
|
|
|
|
bool skipChildren(std::size_t count, std::size_t depth, std::size_t& budget,
|
|
bool map) {
|
|
if (map) {
|
|
if (count > MAX_SKIP_ITEMS / 2) return false;
|
|
count *= 2;
|
|
}
|
|
if (count > budget) return false;
|
|
for (std::size_t index = 0; index < count; ++index) {
|
|
if (!skipValue(depth + 1, budget)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const uint8_t* data_;
|
|
std::size_t size_;
|
|
std::size_t position_ = 0;
|
|
};
|
|
|
|
class Writer {
|
|
public:
|
|
Writer(uint8_t* data, std::size_t capacity) : data_(data), capacity_(capacity) {}
|
|
|
|
bool writeByte(uint8_t value) {
|
|
if (size_ >= capacity_) return false;
|
|
data_[size_++] = value;
|
|
return true;
|
|
}
|
|
|
|
bool writeBytes(const uint8_t* data, std::size_t size) {
|
|
if (size > capacity_ - size_) return false;
|
|
std::memcpy(data_ + size_, data, size);
|
|
size_ += size;
|
|
return true;
|
|
}
|
|
|
|
bool writeUnsigned(uint64_t value) {
|
|
if (value <= 0x7fU) return writeByte(static_cast<uint8_t>(value));
|
|
if (value <= 0xffU) {
|
|
return writeByte(0xccU) && writeBigEndian(value, 1);
|
|
}
|
|
if (value <= 0xffffU) {
|
|
return writeByte(0xcdU) && writeBigEndian(value, 2);
|
|
}
|
|
if (value <= 0xffffffffULL) {
|
|
return writeByte(0xceU) && writeBigEndian(value, 4);
|
|
}
|
|
return writeByte(0xcfU) && writeBigEndian(value, 8);
|
|
}
|
|
|
|
bool writeBoolean(bool value) {
|
|
return writeByte(value ? 0xc3U : 0xc2U);
|
|
}
|
|
|
|
bool writeString(const char* value, std::size_t length) {
|
|
if (value == nullptr) return false;
|
|
if (length <= 31U) {
|
|
return writeByte(static_cast<uint8_t>(0xa0U | length)) &&
|
|
writeBytes(reinterpret_cast<const uint8_t*>(value), length);
|
|
}
|
|
if (length <= 0xffU) {
|
|
return writeByte(0xd9U) && writeByte(static_cast<uint8_t>(length)) &&
|
|
writeBytes(reinterpret_cast<const uint8_t*>(value), length);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool writeBinary(const uint8_t* data, std::size_t size) {
|
|
if (size > 0xffU) return false;
|
|
return writeByte(0xc4U) && writeByte(static_cast<uint8_t>(size)) &&
|
|
writeBytes(data, size);
|
|
}
|
|
|
|
std::size_t size() const { return size_; }
|
|
|
|
private:
|
|
bool writeBigEndian(uint64_t value, std::size_t width) {
|
|
for (std::size_t index = width; index > 0; --index) {
|
|
if (!writeByte(static_cast<uint8_t>(value >> ((index - 1) * 8U)))) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
uint8_t* data_;
|
|
std::size_t capacity_;
|
|
std::size_t size_ = 0;
|
|
};
|
|
|
|
uint32_t decodeU32(const uint8_t* bytes) {
|
|
return (static_cast<uint32_t>(bytes[0]) << 24U) |
|
|
(static_cast<uint32_t>(bytes[1]) << 16U) |
|
|
(static_cast<uint32_t>(bytes[2]) << 8U) |
|
|
static_cast<uint32_t>(bytes[3]);
|
|
}
|
|
|
|
int32_t decodeI32(const uint8_t* bytes) {
|
|
const uint32_t raw = decodeU32(bytes);
|
|
if (raw <= static_cast<uint32_t>(std::numeric_limits<int32_t>::max())) {
|
|
return static_cast<int32_t>(raw);
|
|
}
|
|
// Convert two's-complement wire bits without relying on an
|
|
// implementation-defined uint32_t -> int32_t narrowing conversion.
|
|
const uint32_t distance_from_minus_one =
|
|
std::numeric_limits<uint32_t>::max() - raw;
|
|
return -1 - static_cast<int32_t>(distance_from_minus_one);
|
|
}
|
|
|
|
void encodeU32(uint32_t value, uint8_t bytes[4]) {
|
|
bytes[0] = static_cast<uint8_t>(value >> 24U);
|
|
bytes[1] = static_cast<uint8_t>(value >> 16U);
|
|
bytes[2] = static_cast<uint8_t>(value >> 8U);
|
|
bytes[3] = static_cast<uint8_t>(value);
|
|
}
|
|
|
|
bool readFixedBinary(Cursor& cursor, std::size_t expected, BinaryView& value) {
|
|
return cursor.readBinary(value) && value.size == expected;
|
|
}
|
|
|
|
bool readLocation(Cursor& cursor, LocationTelemetry& location) {
|
|
std::size_t count = 0;
|
|
if (!cursor.readArraySize(count) || count < 7 ||
|
|
count > MAX_LOCATION_ELEMENTS) {
|
|
return false;
|
|
}
|
|
|
|
BinaryView value{};
|
|
uint64_t timestamp = 0;
|
|
|
|
if (!readFixedBinary(cursor, 4, value)) return false;
|
|
location.latitude_e6 = decodeI32(value.data);
|
|
if (!readFixedBinary(cursor, 4, value)) return false;
|
|
location.longitude_e6 = decodeI32(value.data);
|
|
if (!readFixedBinary(cursor, 4, value)) return false;
|
|
location.altitude_cm = decodeI32(value.data);
|
|
if (!readFixedBinary(cursor, 4, value)) return false;
|
|
location.speed_centi_kmh = decodeU32(value.data);
|
|
if (!readFixedBinary(cursor, 4, value)) return false;
|
|
location.bearing_cdeg = decodeI32(value.data);
|
|
if (!readFixedBinary(cursor, 2, value)) return false;
|
|
location.accuracy_cm = static_cast<uint16_t>(
|
|
(static_cast<uint16_t>(value.data[0]) << 8U) | value.data[1]);
|
|
if (!cursor.readUnsigned(timestamp)) return false;
|
|
location.timestamp_seconds = timestamp;
|
|
|
|
std::size_t budget = MAX_SKIP_ITEMS;
|
|
for (std::size_t index = 7; index < count; ++index) {
|
|
if (!cursor.skipValue(0, budget)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool locationInRange(const LocationTelemetry& location) {
|
|
return location.latitude_e6 >= -90000000 &&
|
|
location.latitude_e6 <= 90000000 &&
|
|
location.longitude_e6 >= -180000000 &&
|
|
location.longitude_e6 <= 180000000;
|
|
}
|
|
|
|
bool stringEquals(const BinaryView& value, const char* expected,
|
|
std::size_t expected_size) {
|
|
return value.size == expected_size &&
|
|
std::memcmp(value.data, expected, expected_size) == 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
FieldValueResult unwrapLxmfBinaryFieldValue(
|
|
const uint8_t* raw_value,
|
|
std::size_t raw_size,
|
|
BinaryView& output) {
|
|
if (raw_value == nullptr || raw_size == 0) {
|
|
return FieldValueResult::INVALID_ARGUMENT;
|
|
}
|
|
|
|
const uint8_t marker = raw_value[0];
|
|
if (marker != 0xc4U && marker != 0xc5U && marker != 0xc6U) {
|
|
return FieldValueResult::NOT_BINARY;
|
|
}
|
|
|
|
Cursor cursor(raw_value, raw_size);
|
|
BinaryView candidate{};
|
|
if (!cursor.readBinary(candidate)) return FieldValueResult::MALFORMED;
|
|
if (!cursor.atEnd()) return FieldValueResult::MALFORMED;
|
|
output = candidate;
|
|
return FieldValueResult::OK;
|
|
}
|
|
|
|
FieldValueResult wrapLxmfBinaryFieldValue(
|
|
const uint8_t* payload,
|
|
std::size_t payload_size,
|
|
uint8_t* output,
|
|
std::size_t capacity,
|
|
std::size_t& written) {
|
|
if ((payload == nullptr && payload_size != 0) || output == nullptr) {
|
|
return FieldValueResult::INVALID_ARGUMENT;
|
|
}
|
|
|
|
std::size_t header_size = 0;
|
|
if (payload_size <= 0xffU) {
|
|
header_size = 2;
|
|
} else if (payload_size <= 0xffffU) {
|
|
header_size = 3;
|
|
} else if (payload_size <= 0xffffffffULL) {
|
|
header_size = 5;
|
|
} else {
|
|
return FieldValueResult::INVALID_ARGUMENT;
|
|
}
|
|
if (payload_size > std::numeric_limits<std::size_t>::max() - header_size ||
|
|
capacity < header_size + payload_size) {
|
|
return FieldValueResult::BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
if (header_size == 2) {
|
|
output[0] = 0xc4U;
|
|
output[1] = static_cast<uint8_t>(payload_size);
|
|
} else if (header_size == 3) {
|
|
output[0] = 0xc5U;
|
|
output[1] = static_cast<uint8_t>(payload_size >> 8U);
|
|
output[2] = static_cast<uint8_t>(payload_size);
|
|
} else {
|
|
output[0] = 0xc6U;
|
|
output[1] = static_cast<uint8_t>(payload_size >> 24U);
|
|
output[2] = static_cast<uint8_t>(payload_size >> 16U);
|
|
output[3] = static_cast<uint8_t>(payload_size >> 8U);
|
|
output[4] = static_cast<uint8_t>(payload_size);
|
|
}
|
|
if (payload_size != 0) std::memmove(output + header_size, payload, payload_size);
|
|
written = header_size + payload_size;
|
|
return FieldValueResult::OK;
|
|
}
|
|
|
|
DecodeResult decodeLocationTelemetry(
|
|
const uint8_t* data,
|
|
std::size_t size,
|
|
LocationTelemetry& output) {
|
|
if (data == nullptr || size == 0) return DecodeResult::INVALID_ARGUMENT;
|
|
|
|
Cursor cursor(data, size);
|
|
std::size_t map_size = 0;
|
|
if (!cursor.readMapSize(map_size) || map_size > MAX_MAP_ENTRIES) {
|
|
return DecodeResult::MALFORMED;
|
|
}
|
|
|
|
LocationTelemetry candidate{};
|
|
bool has_location = false;
|
|
std::size_t skip_budget = MAX_SKIP_ITEMS;
|
|
for (std::size_t index = 0; index < map_size; ++index) {
|
|
uint64_t key = 0;
|
|
if (!cursor.readUnsigned(key)) return DecodeResult::MALFORMED;
|
|
if (key == SID_TIME) {
|
|
if (!cursor.readUnsigned(candidate.sensor_timestamp_seconds)) {
|
|
return DecodeResult::MALFORMED;
|
|
}
|
|
} else if (key == SID_LOCATION) {
|
|
if (has_location || !readLocation(cursor, candidate)) {
|
|
return DecodeResult::MALFORMED;
|
|
}
|
|
has_location = true;
|
|
} else if (!cursor.skipValue(0, skip_budget)) {
|
|
return DecodeResult::MALFORMED;
|
|
}
|
|
}
|
|
|
|
if (!cursor.atEnd()) return DecodeResult::MALFORMED;
|
|
if (!has_location) return DecodeResult::MISSING_LOCATION;
|
|
if (!locationInRange(candidate)) return DecodeResult::OUT_OF_RANGE;
|
|
output = candidate;
|
|
return DecodeResult::OK;
|
|
}
|
|
|
|
EncodeResult encodeLocationTelemetry(
|
|
const LocationTelemetry& input,
|
|
uint8_t* output,
|
|
std::size_t capacity,
|
|
std::size_t& written) {
|
|
if (output == nullptr) return EncodeResult::INVALID_ARGUMENT;
|
|
if (!locationInRange(input)) return EncodeResult::OUT_OF_RANGE;
|
|
|
|
uint8_t temporary[MAX_ENCODED_TELEMETRY]{};
|
|
Writer writer(temporary, sizeof(temporary));
|
|
uint8_t word[4]{};
|
|
uint8_t half[2]{};
|
|
bool ok = writer.writeByte(0x82U) &&
|
|
writer.writeUnsigned(SID_TIME) &&
|
|
writer.writeUnsigned(input.sensor_timestamp_seconds) &&
|
|
writer.writeUnsigned(SID_LOCATION) &&
|
|
writer.writeByte(0x97U);
|
|
|
|
encodeU32(static_cast<uint32_t>(input.latitude_e6), word);
|
|
ok = ok && writer.writeBinary(word, sizeof(word));
|
|
encodeU32(static_cast<uint32_t>(input.longitude_e6), word);
|
|
ok = ok && writer.writeBinary(word, sizeof(word));
|
|
encodeU32(static_cast<uint32_t>(input.altitude_cm), word);
|
|
ok = ok && writer.writeBinary(word, sizeof(word));
|
|
encodeU32(input.speed_centi_kmh, word);
|
|
ok = ok && writer.writeBinary(word, sizeof(word));
|
|
encodeU32(static_cast<uint32_t>(input.bearing_cdeg), word);
|
|
ok = ok && writer.writeBinary(word, sizeof(word));
|
|
half[0] = static_cast<uint8_t>(input.accuracy_cm >> 8U);
|
|
half[1] = static_cast<uint8_t>(input.accuracy_cm);
|
|
ok = ok && writer.writeBinary(half, sizeof(half)) &&
|
|
writer.writeUnsigned(input.timestamp_seconds);
|
|
|
|
if (!ok) return EncodeResult::INVALID_ARGUMENT;
|
|
if (capacity < writer.size()) return EncodeResult::BUFFER_TOO_SMALL;
|
|
std::memcpy(output, temporary, writer.size());
|
|
written = writer.size();
|
|
return EncodeResult::OK;
|
|
}
|
|
|
|
CustomMetaResult decodeCustomLocationMeta(
|
|
const uint8_t* data,
|
|
std::size_t size,
|
|
CustomLocationMeta& output) {
|
|
if (data == nullptr || size == 0) return CustomMetaResult::INVALID_ARGUMENT;
|
|
|
|
Cursor cursor(data, size);
|
|
std::size_t map_size = 0;
|
|
if (!cursor.readMapSize(map_size) || map_size > MAX_CUSTOM_META_ENTRIES) {
|
|
return CustomMetaResult::MALFORMED;
|
|
}
|
|
|
|
CustomLocationMeta candidate{};
|
|
std::size_t skip_budget = MAX_SKIP_ITEMS;
|
|
for (std::size_t index = 0; index < map_size; ++index) {
|
|
BinaryView key{};
|
|
if (!cursor.readString(key)) return CustomMetaResult::MALFORMED;
|
|
|
|
if (stringEquals(key, "cease", 5)) {
|
|
if (candidate.has_cease || !cursor.readBoolean(candidate.cease)) {
|
|
return CustomMetaResult::MALFORMED;
|
|
}
|
|
candidate.has_cease = true;
|
|
} else if (stringEquals(key, "expires", 7)) {
|
|
uint64_t expires = 0;
|
|
if (candidate.has_expires || !cursor.readUnsigned(expires) ||
|
|
expires > static_cast<uint64_t>(
|
|
std::numeric_limits<int64_t>::max())) {
|
|
return CustomMetaResult::MALFORMED;
|
|
}
|
|
candidate.expires_millis = static_cast<int64_t>(expires);
|
|
candidate.has_expires = true;
|
|
} else if (stringEquals(key, "approxRadius", 12)) {
|
|
uint64_t radius = 0;
|
|
if (candidate.has_approx_radius || !cursor.readUnsigned(radius) ||
|
|
radius > static_cast<uint64_t>(
|
|
std::numeric_limits<int32_t>::max())) {
|
|
return CustomMetaResult::MALFORMED;
|
|
}
|
|
candidate.approx_radius_meters = static_cast<int32_t>(radius);
|
|
candidate.has_approx_radius = true;
|
|
} else if (stringEquals(key, "ts", 2)) {
|
|
uint64_t timestamp = 0;
|
|
if (candidate.has_timestamp || !cursor.readUnsigned(timestamp) ||
|
|
timestamp > static_cast<uint64_t>(
|
|
std::numeric_limits<int64_t>::max())) {
|
|
return CustomMetaResult::MALFORMED;
|
|
}
|
|
candidate.timestamp_millis = static_cast<int64_t>(timestamp);
|
|
candidate.has_timestamp = true;
|
|
} else if (!cursor.skipValue(0, skip_budget)) {
|
|
return CustomMetaResult::MALFORMED;
|
|
}
|
|
}
|
|
|
|
if (!cursor.atEnd()) return CustomMetaResult::MALFORMED;
|
|
output = candidate;
|
|
return CustomMetaResult::OK;
|
|
}
|
|
|
|
CustomMetaResult encodeCustomLocationMeta(
|
|
const CustomLocationMeta& input,
|
|
uint8_t* output,
|
|
std::size_t capacity,
|
|
std::size_t& written) {
|
|
const std::size_t field_count =
|
|
static_cast<std::size_t>(input.has_cease) +
|
|
static_cast<std::size_t>(input.has_expires) +
|
|
static_cast<std::size_t>(input.has_approx_radius) +
|
|
static_cast<std::size_t>(input.has_timestamp);
|
|
if (field_count == 0) {
|
|
written = 0;
|
|
return CustomMetaResult::EMPTY;
|
|
}
|
|
if (output == nullptr) return CustomMetaResult::INVALID_ARGUMENT;
|
|
if ((input.has_expires && input.expires_millis < 0) ||
|
|
(input.has_approx_radius && input.approx_radius_meters < 0) ||
|
|
(input.has_timestamp && input.timestamp_millis < 0)) {
|
|
return CustomMetaResult::INVALID_ARGUMENT;
|
|
}
|
|
|
|
uint8_t temporary[MAX_ENCODED_CUSTOM_META]{};
|
|
Writer writer(temporary, sizeof(temporary));
|
|
bool ok = writer.writeByte(static_cast<uint8_t>(0x80U | field_count));
|
|
if (input.has_cease) {
|
|
ok = ok && writer.writeString("cease", 5) &&
|
|
writer.writeBoolean(input.cease);
|
|
}
|
|
if (input.has_expires) {
|
|
ok = ok && writer.writeString("expires", 7) &&
|
|
writer.writeUnsigned(static_cast<uint64_t>(input.expires_millis));
|
|
}
|
|
if (input.has_approx_radius) {
|
|
ok = ok && writer.writeString("approxRadius", 12) &&
|
|
writer.writeUnsigned(
|
|
static_cast<uint64_t>(input.approx_radius_meters));
|
|
}
|
|
if (input.has_timestamp) {
|
|
ok = ok && writer.writeString("ts", 2) &&
|
|
writer.writeUnsigned(static_cast<uint64_t>(input.timestamp_millis));
|
|
}
|
|
|
|
if (!ok) return CustomMetaResult::INVALID_ARGUMENT;
|
|
if (capacity < writer.size()) return CustomMetaResult::BUFFER_TOO_SMALL;
|
|
std::memcpy(output, temporary, writer.size());
|
|
written = writer.size();
|
|
return CustomMetaResult::OK;
|
|
}
|
|
|
|
} // namespace Telemetry
|