// Copyright (c) 2026 Pyxis contributors // SPDX-License-Identifier: MIT #include "Hardware/TDeck/MapTilePack.h" #include #include #if defined(ARDUINO_ARCH_ESP32) #include #endif namespace Hardware { namespace TDeck { namespace { std::uint16_t selectionU16(const std::uint8_t* input) { return static_cast(input[0]) | static_cast(static_cast(input[1]) << 8U); } std::uint32_t selectionU32(const std::uint8_t* input) { return static_cast(input[0]) | (static_cast(input[1]) << 8U) | (static_cast(input[2]) << 16U) | (static_cast(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(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(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(declared_size) - length, count); if (read != TileStoreResult::OK || count == 0U || count > static_cast(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(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(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(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(cached)].pack_index; if (pack_index == 0xffU) return MapTilePackResult::UNCOVERED; if (pack_index >= active_pack_count_) { resolution_cache_[static_cast(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(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(key->zoom), static_cast(key->x), static_cast(key->y)); } if (count < 0 || static_cast(count) >= sizeof(temporary) || static_cast(count) + 1U > capacity) { return MapTilePackResult::PATH_TOO_LONG; } std::memcpy(output, temporary, static_cast(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(declared_size) - total, count); if (read != TileStoreResult::OK || count == 0U || count > static_cast(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(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(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(marker))) { return failInitialize(MapTilePackResult::INVALID_PACK_ID, MapTilePackStatus::INVALID_SELECTION, had_selection); } std::strcpy(selected_pack_id, reinterpret_cast(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(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