Files
pyxis/lib/tdeck_ui/Hardware/TDeck/MapTilePack.cpp
T

820 lines
38 KiB
C++

// Copyright (c) 2026 Pyxis contributors
// SPDX-License-Identifier: MIT
#include "Hardware/TDeck/MapTilePack.h"
#include <cstdio>
#include <cstring>
#if defined(ARDUINO_ARCH_ESP32)
#include <esp_heap_caps.h>
#endif
namespace Hardware {
namespace TDeck {
namespace {
std::uint16_t selectionU16(const std::uint8_t* input) {
return static_cast<std::uint16_t>(input[0]) |
static_cast<std::uint16_t>(static_cast<std::uint16_t>(input[1]) << 8U);
}
std::uint32_t selectionU32(const std::uint8_t* input) {
return static_cast<std::uint32_t>(input[0]) |
(static_cast<std::uint32_t>(input[1]) << 8U) |
(static_cast<std::uint32_t>(input[2]) << 16U) |
(static_cast<std::uint32_t>(input[3]) << 24U);
}
std::uint32_t selectionCrc32(const std::uint8_t* input, std::size_t length) {
std::uint32_t crc = UINT32_C(0xffffffff);
for (std::size_t index = 0U; index < length; ++index) {
crc ^= input[index];
for (std::uint8_t bit = 0U; bit < 8U; ++bit) {
crc = (crc >> 1U) ^ ((crc & 1U) != 0U ? UINT32_C(0xedb88320) : 0U);
}
}
return ~crc;
}
bool decodeSelection(const std::uint8_t* input, std::size_t length,
char* pack_id, std::uint32_t& generation) {
static const std::uint8_t magic[4] = {'P', 'M', 'A', 'S'};
if (input == NULL || pack_id == NULL || length != MapTilePack::LEGACY_ACTIVE_SELECTION_SIZE ||
std::memcmp(input, magic, sizeof(magic)) != 0 || input[4] != 1U || input[5] != 0U ||
selectionU16(input + 6U) != MapTilePack::LEGACY_ACTIVE_SELECTION_SIZE ||
selectionU32(input + 44U) != selectionCrc32(input, 44U)) {
return false;
}
generation = selectionU32(input + 8U);
const std::size_t id_length = input[12];
if (generation == 0U || id_length == 0U ||
id_length >= Pyxis::MapPackManifest::PACK_ID_CAPACITY) return false;
for (std::size_t index = 13U + id_length; index < 44U; ++index) {
if (input[index] != 0U) return false;
}
std::memcpy(pack_id, input + 13U, id_length);
pack_id[id_length] = '\0';
return MapTilePack::isValidPackId(pack_id);
}
struct StylePolicy {
const char* id;
const char* attribution;
const char* source;
const char* license;
};
const StylePolicy STYLE_POLICIES[] = {
{"osm-bright", "(c) OpenMapTiles (c) OpenStreetMap contributors",
"Oxed's Map Tile Downloader (OSM Bright)",
"OSM ODbL; style CC-BY-4.0/BSD-3-Clause"},
{"dark-matter", "(c) OpenMapTiles (c) OpenStreetMap contributors; style (c) CARTO",
"Oxed's Map Tile Downloader (Dark Matter)",
"OSM ODbL; style CC-BY-4.0/BSD-3-Clause (CARTO CC-BY-3.0)"},
{"positron", "(c) OpenMapTiles (c) OpenStreetMap contributors; style (c) CARTO",
"Oxed's Map Tile Downloader (Positron)",
"OSM ODbL; style CC-BY-4.0/BSD-3-Clause (CARTO CC-BY-3.0)"},
{"toner", "(c) MapTiler (c) OpenStreetMap contributors",
"Oxed's Map Tile Downloader (Toner)",
"OSM ODbL; style CC-BY-4.0/BSD-3-Clause (Stamen ISC)"},
};
const StylePolicy* findStylePolicy(const char* id) {
for (std::size_t index = 0U; index < sizeof(STYLE_POLICIES) / sizeof(STYLE_POLICIES[0]);
++index) {
if (std::strcmp(id, STYLE_POLICIES[index].id) == 0) return &STYLE_POLICIES[index];
}
return NULL;
}
} // namespace
const char MapTilePack::ACTIVE_PACK_PATH[] = "/pyxis-map/active-pack";
const char MapTilePack::ACTIVE_PACK_SLOT_0_PATH[] = "/pyxis-map/active-pack.0";
const char MapTilePack::ACTIVE_PACK_SLOT_1_PATH[] = "/pyxis-map/active-pack.1";
MapTilePack::MapTilePack(MapTileStorage& storage)
: storage_(storage), manifest_(), active_packs_(), active_pack_count_(0U),
map_set_active_(false), map_set_indexless_(false), resolution_cache_(),
selection_generation_(0U),
status_(MapTilePackStatus::UNINITIALIZED),
stream_open_(false), stream_remaining_(0U), selection_buffers_(),
active_selection_buffer_(0U) {
#if defined(ARDUINO_ARCH_ESP32)
selection_buffers_ = static_cast<std::uint8_t*>(heap_caps_malloc(
2U * ACTIVE_SELECTION_SIZE + MANIFEST_BUFFER_CAPACITY,
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
#endif
clearResolutionCache();
}
MapTilePack::~MapTilePack() {
endGet();
#if defined(ARDUINO_ARCH_ESP32)
if (selection_buffers_ != NULL) heap_caps_free(selection_buffers_);
selection_buffers_ = NULL;
#endif
}
bool MapTilePack::isValidPackId(const char* pack_id) {
if (pack_id == NULL) return false;
std::size_t length = 0U;
while (length < Pyxis::MapPackManifest::PACK_ID_CAPACITY && pack_id[length] != '\0') {
const char character = pack_id[length];
if (!((character >= 'a' && character <= 'z') ||
(character >= '0' && character <= '9') || character == '_' || character == '-')) {
return false;
}
++length;
}
return length > 0U && length < Pyxis::MapPackManifest::PACK_ID_CAPACITY;
}
bool MapTilePack::selectionRecordsEqual(const std::uint8_t* first, std::size_t first_length,
const std::uint8_t* second, std::size_t second_length) {
return first != NULL && second != NULL && first_length == second_length &&
first_length <= ACTIVE_SELECTION_SIZE && std::memcmp(first, second, first_length) == 0;
}
bool MapTilePack::isValidKey(const TileKey& key) {
if (key.zoom > Pyxis::MapPackManifest::MAX_ZOOM) return false;
const std::uint32_t edge = UINT32_C(1) << key.zoom;
return key.x < edge && key.y < edge;
}
std::uint8_t* MapTilePack::selectionBuffer(std::uint8_t index) {
if (index > 1U) return NULL;
#if defined(ARDUINO_ARCH_ESP32)
return selection_buffers_ == NULL ? NULL : selection_buffers_ +
static_cast<std::size_t>(index) * ACTIVE_SELECTION_SIZE;
#else
return selection_buffers_[index];
#endif
}
std::uint8_t* MapTilePack::manifestBuffer() {
#if defined(ARDUINO_ARCH_ESP32)
return selection_buffers_ == NULL ? NULL : selection_buffers_ + 2U * ACTIVE_SELECTION_SIZE;
#else
return manifest_buffer_;
#endif
}
bool MapTilePack::fileMatches(const char* path, const std::uint8_t* expected,
std::size_t length) {
if (expected == NULL || length > ACTIVE_SELECTION_SIZE) return false;
std::uint32_t declared_size = 0U;
if (storage_.beginRead(path, declared_size) != TileStoreResult::OK) return false;
if (declared_size != length) { storage_.endRead(); return false; }
std::size_t offset = 0U;
std::uint8_t chunk[64];
while (offset < length) {
const std::size_t wanted = (length - offset) < sizeof(chunk)
? length - offset : sizeof(chunk);
std::size_t count = 0U;
const TileStoreResult result = storage_.readChunk(chunk, wanted, count);
if (result != TileStoreResult::OK || count == 0U || count > wanted ||
!selectionRecordsEqual(chunk, count, expected + offset, count)) {
storage_.endRead(); return false;
}
offset += count;
}
std::uint8_t trailing = 0U; std::size_t trailing_count = 0U;
const TileStoreResult eof = storage_.readChunk(&trailing, 1U, trailing_count);
storage_.endRead();
return eof == TileStoreResult::OK && trailing_count == 0U;
}
MapTilePackResult MapTilePack::validateMapSet(MapTileStorage& storage,
const ActiveMapSetView& view,
std::uint8_t* scratch,
std::size_t scratch_capacity) {
const StylePolicy* policy = findStylePolicy(view.map_set_id);
if (policy == NULL || scratch == NULL || scratch_capacity < MANIFEST_BUFFER_CAPACITY) {
return policy == NULL ? MapTilePackResult::INVALID_MANIFEST
: MapTilePackResult::INSUFFICIENT_MEMORY;
}
const bool legacy_bright = std::strcmp(view.map_set_id, "osm-bright") == 0 &&
std::strcmp(view.attribution, "Map data (c) OpenStreetMap contributors") == 0;
const bool canonical = std::strcmp(view.attribution, policy->attribution) == 0;
if (!legacy_bright && !canonical) return MapTilePackResult::INVALID_MANIFEST;
for (std::uint8_t index = 0U; index < view.pack_count; ++index) {
char path[PATH_CAPACITY];
MapTilePackResult result = manifestPath(view.packs[index].pack_id, path, sizeof(path));
if (result != MapTilePackResult::OK) return result;
std::uint32_t declared_size = 0U;
const TileStoreResult begin = storage.beginRead(path, declared_size);
if (begin == TileStoreResult::MISS) return MapTilePackResult::MANIFEST_MISSING;
if (begin == TileStoreResult::STORAGE_UNAVAILABLE)
return MapTilePackResult::STORAGE_UNAVAILABLE;
if (begin == TileStoreResult::BUSY) return MapTilePackResult::BUSY;
if (begin != TileStoreResult::OK) return MapTilePackResult::IO_ERROR;
if (declared_size > scratch_capacity) {
storage.endRead(); return MapTilePackResult::MANIFEST_TOO_LARGE;
}
std::size_t length = 0U;
while (length < declared_size) {
std::size_t count = 0U;
const TileStoreResult read = storage.readChunk(
scratch + length, static_cast<std::size_t>(declared_size) - length, count);
if (read != TileStoreResult::OK || count == 0U ||
count > static_cast<std::size_t>(declared_size) - length) {
storage.endRead();
return read == TileStoreResult::BUSY ? MapTilePackResult::BUSY
: MapTilePackResult::IO_ERROR;
}
length += count;
}
std::uint8_t trailing = 0U; std::size_t trailing_count = 0U;
const TileStoreResult eof = storage.readChunk(&trailing, 1U, trailing_count);
storage.endRead();
if (eof != TileStoreResult::OK || trailing_count != 0U) {
return eof == TileStoreResult::BUSY ? MapTilePackResult::BUSY
: MapTilePackResult::IO_ERROR;
}
Pyxis::MapPackManifest manifest = {};
const bool indexless =
view.format_version == ActiveMapSetCodec::INDEXLESS_FORMAT_VERSION;
const Pyxis::ManifestResult parsed =
Pyxis::MapPackManifest::parse(scratch, length, manifest);
const bool compatible_manifest_version = indexless
? (manifest.format_version == Pyxis::MapPackManifest::FORMAT_VERSION ||
manifest.format_version == Pyxis::MapPackManifest::INDEXLESS_FORMAT_VERSION)
: manifest.format_version == Pyxis::MapPackManifest::FORMAT_VERSION;
if (parsed != Pyxis::ManifestResult::OK ||
!compatible_manifest_version ||
std::strcmp(manifest.pack_id, view.packs[index].pack_id) != 0 ||
std::strcmp(manifest.attribution, view.attribution) != 0 ||
(!indexless &&
(manifest.row_span_count != view.packs[index].span_count ||
std::memcmp(manifest.row_span_bytes, view.packs[index].span_bytes,
static_cast<std::size_t>(manifest.row_span_count) *
ActiveMapSetCodec::ROW_SPAN_SIZE) != 0))) {
return MapTilePackResult::INVALID_MANIFEST;
}
if (legacy_bright) {
if (std::strcmp(manifest.source, "Coalition MUI OSM Bright user download") != 0 ||
std::strcmp(manifest.license, "ODbL-1.0") != 0) {
return MapTilePackResult::INVALID_MANIFEST;
}
} else if (std::strcmp(manifest.source, policy->source) != 0 ||
std::strcmp(manifest.license, policy->license) != 0) {
return MapTilePackResult::INVALID_MANIFEST;
}
}
return MapTilePackResult::OK;
}
bool MapTilePack::parseMapSetSelection(const std::uint8_t* input, std::size_t length,
std::uint32_t& generation,
Pyxis::MapPackManifest& metadata,
ActivePackView* packs, std::uint8_t& pack_count,
bool& indexless) {
if (packs == NULL) return false;
ActiveMapSetView view = {};
if (!ActiveMapSetCodec::decode(input, length, view)) return false;
generation = view.generation;
indexless = view.format_version == ActiveMapSetCodec::INDEXLESS_FORMAT_VERSION;
metadata = Pyxis::MapPackManifest();
std::strcpy(metadata.pack_id, view.map_set_id);
std::strncpy(metadata.name, metadata.pack_id, sizeof(metadata.name) - 1U);
std::strcpy(metadata.attribution, view.attribution);
std::strncpy(metadata.source, "active map set", sizeof(metadata.source) - 1U);
pack_count = view.pack_count;
for (std::uint8_t pack_index = 0U; pack_index < pack_count; ++pack_index) {
ActivePackView& pack = packs[pack_index];
std::strcpy(pack.pack_id, view.packs[pack_index].pack_id);
pack.span_count = view.packs[pack_index].span_count;
pack.span_bytes = view.packs[pack_index].span_bytes;
}
return true;
}
bool MapTilePack::spanCovers(const ActivePackView& pack, const TileKey& key) {
for (std::uint16_t index = 0U; index < pack.span_count; ++index) {
const std::uint8_t* span = pack.span_bytes + static_cast<std::size_t>(index) * 13U;
const std::uint8_t zoom = span[0];
if (zoom > key.zoom) return false;
if (zoom != key.zoom) continue;
const std::uint32_t y = selectionU32(span + 1U);
if (y > key.y) return false;
if (y == key.y && key.x >= selectionU32(span + 5U) && key.x <= selectionU32(span + 9U)) {
return true;
}
}
return false;
}
bool MapTilePack::sameKey(const TileKey& left, const TileKey& right) {
return left.zoom == right.zoom && left.x == right.x && left.y == right.y;
}
void MapTilePack::clearResolutionCache() {
for (std::size_t index = 0U; index < RESOLUTION_CACHE_CAPACITY; ++index) {
resolution_cache_[index].valid = false;
resolution_cache_[index].pack_index = 0xffU;
resolution_cache_[index].rank = 0U;
resolution_cache_[index].key = TileKey{0U, 0U, 0U};
}
}
int MapTilePack::findResolution(const TileKey& key) {
for (std::size_t index = 0U; index < RESOLUTION_CACHE_CAPACITY; ++index) {
if (!resolution_cache_[index].valid ||
!sameKey(resolution_cache_[index].key, key)) continue;
const std::uint8_t previous_rank = resolution_cache_[index].rank;
for (std::size_t other = 0U; other < RESOLUTION_CACHE_CAPACITY; ++other) {
if (other != index && resolution_cache_[other].valid &&
resolution_cache_[other].rank < previous_rank) {
++resolution_cache_[other].rank;
}
}
resolution_cache_[index].rank = 0U;
return static_cast<int>(index);
}
return -1;
}
void MapTilePack::rememberResolution(const TileKey& key, std::uint8_t pack_index) {
std::size_t target = 0U;
bool found = false;
std::uint8_t oldest = 0U;
for (std::size_t index = 0U; index < RESOLUTION_CACHE_CAPACITY; ++index) {
if (!resolution_cache_[index].valid) {
target = index; found = true; break;
}
if (resolution_cache_[index].rank >= oldest) {
oldest = resolution_cache_[index].rank; target = index;
}
}
(void)found;
for (std::size_t index = 0U; index < RESOLUTION_CACHE_CAPACITY; ++index) {
if (index != target && resolution_cache_[index].valid &&
resolution_cache_[index].rank < 0xffU) {
++resolution_cache_[index].rank;
}
}
resolution_cache_[target].key = key;
resolution_cache_[target].pack_index = pack_index;
resolution_cache_[target].rank = 0U;
resolution_cache_[target].valid = true;
}
MapTilePackResult MapTilePack::beginIndexless(const TileKey& key, std::uint32_t& size) {
const int cached = findResolution(key);
if (cached >= 0) {
const std::uint8_t pack_index =
resolution_cache_[static_cast<std::size_t>(cached)].pack_index;
if (pack_index == 0xffU) return MapTilePackResult::UNCOVERED;
if (pack_index >= active_pack_count_) {
resolution_cache_[static_cast<std::size_t>(cached)].valid = false;
} else {
char path[PATH_CAPACITY];
MapTilePackResult result = tilePath(active_packs_[pack_index].pack_id,
key, path, sizeof(path));
if (result != MapTilePackResult::OK) return result;
std::uint32_t candidate_size = 0U;
const TileStoreResult begin = storage_.beginRead(path, candidate_size);
if (begin == TileStoreResult::OK) {
stream_open_ = true; stream_remaining_ = candidate_size; size = candidate_size;
return MapTilePackResult::OK;
}
if (begin != TileStoreResult::MISS) {
if (begin == TileStoreResult::STORAGE_UNAVAILABLE)
return MapTilePackResult::STORAGE_UNAVAILABLE;
if (begin == TileStoreResult::BUSY) return MapTilePackResult::BUSY;
return MapTilePackResult::IO_ERROR;
}
resolution_cache_[static_cast<std::size_t>(cached)].valid = false;
}
}
for (std::uint8_t index = 0U; index < active_pack_count_; ++index) {
char path[PATH_CAPACITY];
MapTilePackResult result = tilePath(active_packs_[index].pack_id,
key, path, sizeof(path));
if (result != MapTilePackResult::OK) return result;
std::uint32_t candidate_size = 0U;
const TileStoreResult begin = storage_.beginRead(path, candidate_size);
if (begin == TileStoreResult::MISS) continue;
if (begin == TileStoreResult::STORAGE_UNAVAILABLE)
return MapTilePackResult::STORAGE_UNAVAILABLE;
if (begin == TileStoreResult::BUSY) return MapTilePackResult::BUSY;
if (begin != TileStoreResult::OK) return MapTilePackResult::IO_ERROR;
rememberResolution(key, index);
stream_open_ = true; stream_remaining_ = candidate_size; size = candidate_size;
return MapTilePackResult::OK;
}
rememberResolution(key, 0xffU);
return MapTilePackResult::UNCOVERED;
}
MapTilePackResult MapTilePack::makePath(const char* pack_id, const TileKey* key,
bool manifest, char* output, std::size_t capacity) {
if (!isValidPackId(pack_id)) return MapTilePackResult::INVALID_PACK_ID;
if (output == NULL || capacity == 0U) return MapTilePackResult::INVALID_ARGUMENT;
if (!manifest && (key == NULL || !isValidKey(*key))) return MapTilePackResult::INVALID_KEY;
char temporary[PATH_CAPACITY];
int count = 0;
if (manifest) {
count = std::snprintf(temporary, sizeof(temporary), "/pyxis-map/packs/%s/manifest.pmp", pack_id);
} else {
count = std::snprintf(temporary, sizeof(temporary),
"/pyxis-map/packs/%s/tiles/%u/%lu/%lu.png", pack_id,
static_cast<unsigned>(key->zoom),
static_cast<unsigned long>(key->x),
static_cast<unsigned long>(key->y));
}
if (count < 0 || static_cast<std::size_t>(count) >= sizeof(temporary) ||
static_cast<std::size_t>(count) + 1U > capacity) {
return MapTilePackResult::PATH_TOO_LONG;
}
std::memcpy(output, temporary, static_cast<std::size_t>(count) + 1U);
return MapTilePackResult::OK;
}
MapTilePackResult MapTilePack::manifestPath(const char* pack_id, char* output,
std::size_t capacity) {
return makePath(pack_id, NULL, true, output, capacity);
}
MapTilePackResult MapTilePack::tilePath(const char* pack_id, const TileKey& key,
char* output, std::size_t capacity) {
return makePath(pack_id, &key, false, output, capacity);
}
MapTilePackResult MapTilePack::loadFile(const char* path, std::uint8_t* output,
std::size_t capacity, std::size_t& length,
MapTilePackResult missing_result,
MapTilePackResult oversized_result) {
std::uint32_t declared_size = 0U;
const TileStoreResult begin = storage_.beginRead(path, declared_size);
if (begin == TileStoreResult::MISS) return missing_result;
if (begin == TileStoreResult::STORAGE_UNAVAILABLE) return MapTilePackResult::STORAGE_UNAVAILABLE;
if (begin == TileStoreResult::BUSY) return MapTilePackResult::BUSY;
if (begin != TileStoreResult::OK) return MapTilePackResult::IO_ERROR;
if (declared_size > capacity) {
storage_.endRead();
return oversized_result;
}
std::size_t total = 0U;
while (total < declared_size) {
std::size_t count = 0U;
const TileStoreResult read = storage_.readChunk(output + total,
static_cast<std::size_t>(declared_size) - total, count);
if (read != TileStoreResult::OK || count == 0U ||
count > static_cast<std::size_t>(declared_size) - total) {
storage_.endRead();
return MapTilePackResult::IO_ERROR;
}
total += count;
}
std::uint8_t trailing = 0U;
std::size_t trailing_count = 0U;
const TileStoreResult eof = storage_.readChunk(&trailing, 1U, trailing_count);
storage_.endRead();
if (eof != TileStoreResult::OK || trailing_count != 0U) {
return MapTilePackResult::IO_ERROR;
}
length = total;
return MapTilePackResult::OK;
}
MapTilePackResult MapTilePack::failInitialize(MapTilePackResult result,
MapTilePackStatus initial_status,
bool had_selection) {
if (!had_selection) status_ = initial_status;
return result;
}
MapTilePackResult MapTilePack::initialize() {
endGet();
const bool had_selection = hasSelection();
if (selectionBuffer(0U) == NULL) {
return failInitialize(MapTilePackResult::INSUFFICIENT_MEMORY,
MapTilePackStatus::INSUFFICIENT_MEMORY, had_selection);
}
if (!storage_.isAvailable()) {
return failInitialize(MapTilePackResult::STORAGE_UNAVAILABLE,
MapTilePackStatus::STORAGE_UNAVAILABLE, had_selection);
}
const std::uint8_t candidate_buffer = static_cast<std::uint8_t>(active_selection_buffer_ ^ 1U);
const char* slot_paths[2] = {ACTIVE_PACK_SLOT_0_PATH, ACTIVE_PACK_SLOT_1_PATH};
std::uint32_t slot_generations[2] = {0U, 0U};
std::size_t slot_lengths[2] = {0U, 0U};
bool slot_valid[2] = {false, false};
bool redundant_record_present = false;
MapTilePackResult result = MapTilePackResult::NO_SELECTION;
for (std::size_t slot = 0U; slot < 2U; ++slot) {
std::size_t record_length = 0U;
result = loadFile(slot_paths[slot], selectionBuffer(candidate_buffer), ACTIVE_SELECTION_SIZE,
record_length, MapTilePackResult::NO_SELECTION,
MapTilePackResult::INVALID_PACK_ID);
if (result == MapTilePackResult::OK) {
redundant_record_present = true;
char legacy_id[Pyxis::MapPackManifest::PACK_ID_CAPACITY] = {};
Pyxis::MapPackManifest map_set_metadata = {};
ActivePackView map_set_packs[MAX_ACTIVE_PACKS] = {};
std::uint8_t map_set_pack_count = 0U;
bool map_set_indexless = false;
if (decodeSelection(selectionBuffer(candidate_buffer), record_length,
legacy_id, slot_generations[slot]) ||
parseMapSetSelection(selectionBuffer(candidate_buffer), record_length,
slot_generations[slot], map_set_metadata,
map_set_packs, map_set_pack_count,
map_set_indexless)) {
slot_valid[slot] = true;
slot_lengths[slot] = record_length;
}
} else if (result == MapTilePackResult::INVALID_PACK_ID) {
redundant_record_present = true;
} else if (result != MapTilePackResult::NO_SELECTION) {
const MapTilePackStatus state = result == MapTilePackResult::STORAGE_UNAVAILABLE
? MapTilePackStatus::STORAGE_UNAVAILABLE : MapTilePackStatus::INVALID_SELECTION;
return failInitialize(result, state, had_selection);
}
}
if (slot_valid[0] && slot_valid[1] && slot_generations[0] == slot_generations[1]) {
std::size_t first_length = 0U;
result = loadFile(slot_paths[0], selectionBuffer(candidate_buffer), ACTIVE_SELECTION_SIZE,
first_length, MapTilePackResult::NO_SELECTION,
MapTilePackResult::INVALID_PACK_ID);
if (result != MapTilePackResult::OK || first_length != slot_lengths[0] ||
slot_lengths[0] != slot_lengths[1] ||
!fileMatches(slot_paths[1], selectionBuffer(candidate_buffer), first_length)) {
return failInitialize(MapTilePackResult::INVALID_PACK_ID,
MapTilePackStatus::INVALID_SELECTION, had_selection);
}
}
if (slot_valid[0] || slot_valid[1]) {
std::size_t order[2] = {0U, 1U};
if (!slot_valid[0] || (slot_valid[1] && slot_generations[1] > slot_generations[0])) {
order[0] = 1U; order[1] = 0U;
}
MapTilePackResult deterministic_failure = MapTilePackResult::INVALID_PACK_ID;
for (std::size_t attempt = 0U; attempt < 2U; ++attempt) {
const std::size_t chosen = order[attempt];
if (!slot_valid[chosen]) continue;
std::size_t record_length = 0U;
result = loadFile(slot_paths[chosen], selectionBuffer(candidate_buffer), ACTIVE_SELECTION_SIZE,
record_length, MapTilePackResult::NO_SELECTION,
MapTilePackResult::INVALID_PACK_ID);
if (result != MapTilePackResult::OK) {
const MapTilePackStatus state = result == MapTilePackResult::STORAGE_UNAVAILABLE
? MapTilePackStatus::STORAGE_UNAVAILABLE : MapTilePackStatus::INVALID_SELECTION;
return failInitialize(result, state, had_selection);
}
std::uint32_t generation = 0U;
Pyxis::MapPackManifest map_set_metadata = {};
ActivePackView map_set_packs[MAX_ACTIVE_PACKS] = {};
std::uint8_t map_set_pack_count = 0U;
bool map_set_indexless = false;
ActiveMapSetView map_set_view = {};
if (ActiveMapSetCodec::decode(selectionBuffer(candidate_buffer), record_length,
map_set_view) &&
parseMapSetSelection(selectionBuffer(candidate_buffer), record_length, generation,
map_set_metadata, map_set_packs, map_set_pack_count,
map_set_indexless)) {
result = validateMapSet(storage_, map_set_view, manifestBuffer(),
MANIFEST_BUFFER_CAPACITY);
if (result == MapTilePackResult::OK) {
manifest_ = map_set_metadata;
std::memcpy(active_packs_, map_set_packs,
static_cast<std::size_t>(map_set_pack_count) * sizeof(ActivePackView));
active_pack_count_ = map_set_pack_count;
map_set_active_ = true;
map_set_indexless_ = map_set_indexless;
clearResolutionCache();
selection_generation_ = generation;
active_selection_buffer_ = candidate_buffer;
status_ = MapTilePackStatus::READY;
return MapTilePackResult::OK;
}
} else {
char pack_id[Pyxis::MapPackManifest::PACK_ID_CAPACITY] = {};
if (!decodeSelection(selectionBuffer(candidate_buffer), record_length,
pack_id, generation)) {
result = MapTilePackResult::INVALID_PACK_ID;
} else {
char path[PATH_CAPACITY];
result = manifestPath(pack_id, path, sizeof(path));
std::size_t manifest_length = 0U;
if (result == MapTilePackResult::OK) {
result = loadFile(path, manifestBuffer(), MANIFEST_BUFFER_CAPACITY,
manifest_length, MapTilePackResult::MANIFEST_MISSING,
MapTilePackResult::MANIFEST_TOO_LARGE);
}
Pyxis::MapPackManifest candidate = {};
if (result == MapTilePackResult::OK &&
Pyxis::MapPackManifest::parse(manifestBuffer(), manifest_length, candidate) !=
Pyxis::ManifestResult::OK) {
result = MapTilePackResult::INVALID_MANIFEST;
}
if (result == MapTilePackResult::OK &&
std::strcmp(pack_id, candidate.pack_id) != 0) {
result = MapTilePackResult::PACK_ID_MISMATCH;
}
if (result == MapTilePackResult::OK) {
manifest_ = candidate;
active_pack_count_ = 0U;
map_set_active_ = false;
map_set_indexless_ = false;
clearResolutionCache();
selection_generation_ = generation;
active_selection_buffer_ = candidate_buffer;
status_ = MapTilePackStatus::READY;
return MapTilePackResult::OK;
}
}
}
if (result == MapTilePackResult::STORAGE_UNAVAILABLE ||
result == MapTilePackResult::IO_ERROR || result == MapTilePackResult::BUSY ||
result == MapTilePackResult::INSUFFICIENT_MEMORY) {
const MapTilePackStatus state = result == MapTilePackResult::STORAGE_UNAVAILABLE
? MapTilePackStatus::STORAGE_UNAVAILABLE : MapTilePackStatus::INVALID_SELECTION;
return failInitialize(result, state, had_selection);
}
deterministic_failure = result;
}
return failInitialize(deterministic_failure, MapTilePackStatus::INVALID_SELECTION,
had_selection);
}
if (redundant_record_present) {
return failInitialize(MapTilePackResult::INVALID_PACK_ID,
MapTilePackStatus::INVALID_SELECTION, had_selection);
}
char selected_pack_id[Pyxis::MapPackManifest::PACK_ID_CAPACITY] = {};
std::uint32_t selected_generation = 0U;
std::uint8_t marker[Pyxis::MapPackManifest::PACK_ID_CAPACITY];
std::size_t marker_length = 0U;
result = loadFile(ACTIVE_PACK_PATH, marker, sizeof(marker) - 1U,
marker_length, MapTilePackResult::NO_SELECTION,
MapTilePackResult::INVALID_PACK_ID);
if (result == MapTilePackResult::NO_SELECTION ||
(result == MapTilePackResult::OK && marker_length == 0U)) {
manifest_ = Pyxis::MapPackManifest();
active_pack_count_ = 0U;
map_set_active_ = false;
map_set_indexless_ = false;
clearResolutionCache();
selection_generation_ = 0U;
status_ = MapTilePackStatus::NO_SELECTION;
return MapTilePackResult::NO_SELECTION;
}
if (result != MapTilePackResult::OK) {
const MapTilePackStatus state = result == MapTilePackResult::STORAGE_UNAVAILABLE
? MapTilePackStatus::STORAGE_UNAVAILABLE : MapTilePackStatus::INVALID_SELECTION;
return failInitialize(result, state, had_selection);
}
marker[marker_length] = 0U;
if (std::memchr(marker, 0, marker_length) != NULL ||
!isValidPackId(reinterpret_cast<const char*>(marker))) {
return failInitialize(MapTilePackResult::INVALID_PACK_ID,
MapTilePackStatus::INVALID_SELECTION, had_selection);
}
std::strcpy(selected_pack_id, reinterpret_cast<const char*>(marker));
char manifest_path[PATH_CAPACITY];
result = manifestPath(selected_pack_id, manifest_path, sizeof(manifest_path));
if (result != MapTilePackResult::OK) {
return failInitialize(result, MapTilePackStatus::INVALID_SELECTION, had_selection);
}
std::size_t manifest_length = 0U;
result = loadFile(manifest_path, selectionBuffer(candidate_buffer), MANIFEST_BUFFER_CAPACITY,
manifest_length, MapTilePackResult::MANIFEST_MISSING,
MapTilePackResult::MANIFEST_TOO_LARGE);
if (result != MapTilePackResult::OK) {
const MapTilePackStatus state = result == MapTilePackResult::STORAGE_UNAVAILABLE
? MapTilePackStatus::STORAGE_UNAVAILABLE : MapTilePackStatus::INVALID_SELECTION;
return failInitialize(result, state, had_selection);
}
Pyxis::MapPackManifest candidate = {};
if (Pyxis::MapPackManifest::parse(selectionBuffer(candidate_buffer), manifest_length, candidate) !=
Pyxis::ManifestResult::OK) {
return failInitialize(MapTilePackResult::INVALID_MANIFEST,
MapTilePackStatus::INVALID_SELECTION, had_selection);
}
if (std::strcmp(selected_pack_id, candidate.pack_id) != 0) {
return failInitialize(MapTilePackResult::PACK_ID_MISMATCH,
MapTilePackStatus::INVALID_SELECTION, had_selection);
}
manifest_ = candidate;
active_pack_count_ = 0U;
map_set_active_ = false;
map_set_indexless_ = false;
clearResolutionCache();
selection_generation_ = selected_generation;
active_selection_buffer_ = candidate_buffer;
status_ = MapTilePackStatus::READY;
return MapTilePackResult::OK;
}
MapTilePackResult MapTilePack::beginGet(const TileKey& key, std::uint32_t& size) {
if (stream_open_) return MapTilePackResult::BUSY;
if (!hasSelection()) return MapTilePackResult::NOT_INITIALIZED;
if (!isValidKey(key)) return MapTilePackResult::INVALID_KEY;
if (!storage_.isAvailable()) return MapTilePackResult::STORAGE_UNAVAILABLE;
if (map_set_active_) {
if (map_set_indexless_) return beginIndexless(key, size);
bool covered = false;
for (std::uint8_t index = 0U; index < active_pack_count_; ++index) {
if (!spanCovers(active_packs_[index], key)) continue;
covered = true;
char path[PATH_CAPACITY];
MapTilePackResult result = tilePath(active_packs_[index].pack_id, key, path, sizeof(path));
if (result != MapTilePackResult::OK) return result;
std::uint32_t candidate_size = 0U;
const TileStoreResult begin = storage_.beginRead(path, candidate_size);
if (begin == TileStoreResult::MISS) continue;
if (begin == TileStoreResult::STORAGE_UNAVAILABLE) return MapTilePackResult::STORAGE_UNAVAILABLE;
if (begin == TileStoreResult::BUSY) return MapTilePackResult::BUSY;
if (begin != TileStoreResult::OK) return MapTilePackResult::IO_ERROR;
stream_open_ = true;
stream_remaining_ = candidate_size;
size = candidate_size;
return MapTilePackResult::OK;
}
return covered ? MapTilePackResult::TILE_MISSING : MapTilePackResult::UNCOVERED;
}
if (!manifest_.covers(key)) return MapTilePackResult::UNCOVERED;
char path[PATH_CAPACITY];
MapTilePackResult result = tilePath(manifest_.pack_id, key, path, sizeof(path));
if (result != MapTilePackResult::OK) return result;
std::uint32_t candidate_size = 0U;
const TileStoreResult begin = storage_.beginRead(path, candidate_size);
if (begin == TileStoreResult::MISS) return MapTilePackResult::TILE_MISSING;
if (begin == TileStoreResult::STORAGE_UNAVAILABLE) return MapTilePackResult::STORAGE_UNAVAILABLE;
if (begin == TileStoreResult::BUSY) return MapTilePackResult::BUSY;
if (begin != TileStoreResult::OK) return MapTilePackResult::IO_ERROR;
stream_open_ = true;
stream_remaining_ = candidate_size;
size = candidate_size;
return MapTilePackResult::OK;
}
MapTilePackResult MapTilePack::readGetChunk(std::uint8_t* output, std::size_t capacity,
std::size_t& count) {
count = 0U;
if (output == NULL || capacity == 0U) {
endGet();
return MapTilePackResult::INVALID_ARGUMENT;
}
if (!stream_open_) return MapTilePackResult::NOT_STREAMING;
if (stream_remaining_ == 0U) {
std::uint8_t trailing = 0U;
std::size_t trailing_count = 0U;
const TileStoreResult eof = storage_.readChunk(&trailing, 1U, trailing_count);
endGet();
return (eof == TileStoreResult::OK && trailing_count == 0U)
? MapTilePackResult::OK : MapTilePackResult::IO_ERROR;
}
std::size_t read_count = 0U;
const std::size_t allowed = capacity < stream_remaining_ ? capacity : stream_remaining_;
const TileStoreResult read = storage_.readChunk(output, allowed, read_count);
if (read != TileStoreResult::OK || read_count > allowed ||
(read_count == 0U && stream_remaining_ != 0U)) {
endGet();
return MapTilePackResult::IO_ERROR;
}
if (read_count == 0U) {
endGet();
return MapTilePackResult::OK;
}
stream_remaining_ -= static_cast<std::uint32_t>(read_count);
if (stream_remaining_ == 0U) {
std::uint8_t trailing = 0U;
std::size_t trailing_count = 0U;
const TileStoreResult eof = storage_.readChunk(&trailing, 1U, trailing_count);
if (eof != TileStoreResult::OK || trailing_count != 0U) {
endGet();
return MapTilePackResult::IO_ERROR;
}
endGet();
}
count = read_count;
return MapTilePackResult::OK;
}
void MapTilePack::endGet() {
if (stream_open_) storage_.endRead();
stream_open_ = false;
stream_remaining_ = 0U;
}
} // namespace TDeck
} // namespace Hardware