package_vendor_build.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. #include <array>
  2. #include <cstddef>
  3. #include <cstdio>
  4. #include <cstring>
  5. #include <limits>
  6. #include <span>
  7. #include <vector>
  8. #include "../../../core/logging/log.h"
  9. #include "../../../middleware/content/packages/tables/definition_index_table.h"
  10. #include "../../../state/build_data/runtime.h"
  11. #include "../../../state/build_data/vendors/definition.h"
  12. #include "layout.h"
  13. #include "vendor_build.h"
  14. namespace sunrise::client::content::vendors {
  15. namespace {
  16. namespace reader = middleware::content::packages::reader;
  17. namespace tables = middleware::content::packages::tables;
  18. namespace domain = state::build_data::vendors;
  19. /** Every extracted row, kept off the caller stack. */
  20. struct Storage {
  21. std::vector<std::byte> blob{};
  22. std::array<domain::IndexEntry, domain::kIndexCapacity> index{};
  23. std::array<domain::Definition, domain::kDefinitionCapacity> definitions{};
  24. std::array<domain::SaleRow, domain::kSaleRowCapacity> saleRows{};
  25. std::array<domain::InstalledRow, domain::kInstalledRowCapacity> installedRows{};
  26. std::size_t indexCount{};
  27. std::size_t definitionCount{};
  28. std::size_t saleRowCount{};
  29. std::size_t installedRowCount{};
  30. };
  31. /** One array a definition or a sale row declares, reduced to what the catalog stores. */
  32. struct ArrayView {
  33. std::uint32_t base{};
  34. std::uint32_t classId{};
  35. std::uint16_t count{};
  36. };
  37. /** @param blob Source bytes. @param offset Field offset. @param value Receives the field. */
  38. template <typename Value>
  39. [[nodiscard]] bool
  40. read(std::span<const std::byte> blob, std::size_t offset, Value& value) noexcept {
  41. if (offset > blob.size() || blob.size() - offset < sizeof value) {
  42. return false;
  43. }
  44. std::memcpy(&value, blob.data() + offset, sizeof value);
  45. return true;
  46. }
  47. /**
  48. * Reads one array descriptor and bounds it against the blob holding it.
  49. * The raw count is read first, because the shared resolver reports absent and corrupt alike.
  50. * @param blob Whole blob owning the descriptor.
  51. * @param descriptor Descriptor offset.
  52. * @param stride One row's size.
  53. * @param output Receives the array, or an absent array.
  54. * @return True when the array is absent, or resolves and ends inside the blob.
  55. */
  56. [[nodiscard]] bool read_array(std::span<const std::byte> blob,
  57. std::size_t descriptor,
  58. std::size_t stride,
  59. ArrayView& output) noexcept {
  60. /** Row counts are stored as unsigned 16-bit values. */
  61. constexpr std::uint64_t kMaximumCount = (std::numeric_limits<std::uint16_t>::max)();
  62. output = {};
  63. std::uint64_t declared = 0;
  64. if (!read(blob, descriptor, declared)) {
  65. return false;
  66. }
  67. if (declared == 0) {
  68. return true;
  69. }
  70. tables::Array array{};
  71. if (!tables::find_array_at(blob, descriptor, array) || array.count > kMaximumCount) {
  72. return false;
  73. }
  74. const std::uint64_t end = array.dataOffset + (array.count * stride);
  75. if (end > blob.size()) {
  76. return false;
  77. }
  78. output = {static_cast<std::uint32_t>(array.dataOffset),
  79. array.elementClass,
  80. static_cast<std::uint16_t>(array.count)};
  81. return true;
  82. }
  83. /**
  84. * Reads what one sale row charges: every row of its price-override array, in order.
  85. * A row charging nothing declares no override, which is data rather than a malformed row. A row
  86. * declaring more overrides than the catalog can hold is refused, so a charge is never a subset of
  87. * what the package asks for.
  88. * @param blob Whole definition blob.
  89. * @param at Sale row offset inside the blob.
  90. * @param value Receives the cost rows, or none.
  91. * @return True when the array is absent, or resolves, fits, and ends inside the blob.
  92. */
  93. [[nodiscard]] bool
  94. read_sale_cost(std::span<const std::byte> blob, std::size_t at, domain::SaleRow& value) noexcept {
  95. value.costs = {};
  96. value.costCount = 0;
  97. ArrayView cost{};
  98. if (!read_array(blob, at + kSaleCostArrayDescriptor, domain::kSaleCostRowStride, cost)) {
  99. return false;
  100. }
  101. if (cost.count == 0) {
  102. return true;
  103. }
  104. if (cost.classId != domain::kSaleCostRowClass || cost.count > value.costs.size()) {
  105. return false;
  106. }
  107. for (std::size_t row = 0; row < cost.count; ++row) {
  108. const std::size_t rowAt = cost.base + (row * domain::kSaleCostRowStride);
  109. domain::SaleCost& entry = value.costs[row];
  110. if (!read(blob, rowAt + kSaleCostItemIndexOffset, entry.itemIndex)
  111. || !read(blob, rowAt + kSaleCostQuantityOffset, entry.quantity)) {
  112. return false;
  113. }
  114. }
  115. value.costCount = static_cast<std::uint8_t>(cost.count);
  116. return true;
  117. }
  118. /**
  119. * Reads the whole installed vendor index.
  120. * @param source Package directory and borrowed block keys.
  121. * @param scratch Lock-owned block storage.
  122. * @param storage Pass storage receiving the index rows.
  123. * @return True when the index blob reads and every row fits.
  124. */
  125. [[nodiscard]] bool
  126. read_index(const reader::Source& source, reader::Scratch& scratch, Storage& storage) noexcept {
  127. std::uint32_t classId = 0;
  128. tables::Array array{};
  129. if (!reader::read_tag(source, scratch, kIndexRootTag, storage.blob, classId)
  130. || classId != domain::kIndexWrapperClass) {
  131. return false;
  132. }
  133. const std::span<const std::byte> blob{storage.blob};
  134. if (!tables::find_array_at(blob, tables::kTableArrayDescriptor, array)
  135. || array.elementClass != domain::kIndexRowClass || array.count > domain::kIndexCapacity) {
  136. return false;
  137. }
  138. for (std::uint64_t row = 0; row < array.count; ++row) {
  139. tables::IndexRow entry{};
  140. if (!tables::index_row(blob, array, row, entry)) {
  141. return false;
  142. }
  143. storage.index[storage.indexCount] = {
  144. entry.definitionHash, entry.targetTag, static_cast<std::uint16_t>(row)};
  145. ++storage.indexCount;
  146. }
  147. return storage.indexCount != 0;
  148. }
  149. /**
  150. * Reads every sale row of one definition into the flat bank.
  151. * @param blob Whole definition blob.
  152. * @param definition Definition whose sale array was already resolved.
  153. * @param storage Pass storage receiving the rows.
  154. * @return True when every row is inside the blob and the bank holds them all.
  155. */
  156. [[nodiscard]] bool read_sale_rows(std::span<const std::byte> blob,
  157. const domain::Definition& definition,
  158. Storage& storage) noexcept {
  159. if (definition.saleCount > domain::kSaleRowCapacity - storage.saleRowCount) {
  160. return false;
  161. }
  162. for (std::size_t row = 0; row < definition.saleCount; ++row) {
  163. const std::size_t at = definition.saleRowBase + (row * domain::kSaleRowStride);
  164. domain::SaleRow& value = storage.saleRows[storage.saleRowCount + row];
  165. value = {};
  166. if (!read(blob, at + kSaleItemIndexOffset, value.itemIndex)
  167. || !read(blob, at + kSaleSecondaryItemOffset, value.secondaryItemIndex)
  168. || !read(blob, at + kSaleCategoryIndexOffset, value.categoryIndex)
  169. || !read_sale_cost(blob, at, value)) {
  170. return false;
  171. }
  172. }
  173. storage.saleRowCount += definition.saleCount;
  174. return true;
  175. }
  176. /**
  177. * Reads the definition hash of every category row of one definition into the flat bank.
  178. * @param blob Whole definition blob.
  179. * @param definition Definition whose installed array was already resolved.
  180. * @param storage Pass storage receiving the rows.
  181. * @return True when every row is inside the blob and the bank holds them all.
  182. */
  183. [[nodiscard]] bool read_installed_rows(std::span<const std::byte> blob,
  184. const domain::Definition& definition,
  185. Storage& storage) noexcept {
  186. if (definition.installedCount > domain::kInstalledRowCapacity - storage.installedRowCount) {
  187. return false;
  188. }
  189. for (std::size_t row = 0; row < definition.installedCount; ++row) {
  190. const std::size_t at = definition.installedRowBase + (row * domain::kInstalledRowStride);
  191. domain::InstalledRow& value = storage.installedRows[storage.installedRowCount + row];
  192. value = {};
  193. if (!read(blob, at + kInstalledRowHashOffset, value.definitionHash)) {
  194. return false;
  195. }
  196. }
  197. storage.installedRowCount += definition.installedCount;
  198. return true;
  199. }
  200. /**
  201. * Reads one vendor definition and both of its row arrays.
  202. * @param source Package directory and borrowed block keys.
  203. * @param scratch Lock-owned block storage.
  204. * @param entry Index row naming the definition.
  205. * @param storage Pass storage receiving the definition and its rows.
  206. * @return True when the definition blob reads and every array ends inside it.
  207. */
  208. [[nodiscard]] bool read_definition(const reader::Source& source,
  209. reader::Scratch& scratch,
  210. const domain::IndexEntry& entry,
  211. Storage& storage) noexcept {
  212. /** Definition sizes are stored as unsigned 32-bit values. */
  213. constexpr std::size_t kMaximumSize = (std::numeric_limits<std::uint32_t>::max)();
  214. std::uint32_t classId = 0;
  215. if (storage.definitionCount == domain::kDefinitionCapacity
  216. || !reader::read_tag(source, scratch, entry.definitionTag, storage.blob, classId)
  217. || classId != domain::kDefinitionClass || storage.blob.size() > kMaximumSize) {
  218. return false;
  219. }
  220. const std::span<const std::byte> blob{storage.blob};
  221. ArrayView installed{};
  222. ArrayView sale{};
  223. ArrayView third{};
  224. if (!read_array(blob, kInstalledArrayDescriptor, domain::kInstalledRowStride, installed)
  225. || !read_array(blob, kSaleArrayDescriptor, domain::kSaleRowStride, sale)
  226. || !read_array(blob, kThirdArrayDescriptor, domain::kThirdRowStride, third)) {
  227. return false;
  228. }
  229. domain::Definition definition{};
  230. definition.definitionHash = entry.definitionHash;
  231. definition.definitionTag = entry.definitionTag;
  232. definition.definitionClass = classId;
  233. definition.definitionSize = static_cast<std::uint32_t>(blob.size());
  234. definition.index = entry.index;
  235. definition.installedRowBase = installed.base;
  236. definition.installedRowClass = installed.classId;
  237. definition.installedCount = installed.count;
  238. definition.saleRowBase = sale.base;
  239. definition.saleRowClass = sale.classId;
  240. definition.saleCount = sale.count;
  241. definition.thirdRowBase = third.base;
  242. definition.thirdRowClass = third.classId;
  243. definition.thirdCount = third.count;
  244. definition.saleRowOffset = static_cast<std::uint32_t>(storage.saleRowCount);
  245. definition.installedRowOffset = static_cast<std::uint32_t>(storage.installedRowCount);
  246. // A skipped definition must leave both banks exactly as it found them; an orphan sale row
  247. // shifts the next definition's offset and `valid()` then rejects the whole set.
  248. const std::size_t saleRowsBefore = storage.saleRowCount;
  249. const std::size_t installedRowsBefore = storage.installedRowCount;
  250. if (!read(blob, kResetIntervalOffset, definition.resetIntervalRaw)
  251. || !read(blob, kResetPhaseOffset, definition.resetPhaseRaw)
  252. || !read_sale_rows(blob, definition, storage)
  253. || !read_installed_rows(blob, definition, storage)) {
  254. storage.saleRowCount = saleRowsBefore;
  255. storage.installedRowCount = installedRowsBefore;
  256. return false;
  257. }
  258. storage.definitions[storage.definitionCount] = definition;
  259. ++storage.definitionCount;
  260. return true;
  261. }
  262. /**
  263. * Reports the pass so a boot with no vendor catalog says which step lost the rows.
  264. * @param storage Pass storage holding every count.
  265. * @param skipped Requested definitions that could not be read or could not fit.
  266. * @param result Outcome text for the log line.
  267. */
  268. void report(const Storage& storage, std::size_t skipped, const char* result) noexcept {
  269. std::array<char, core::log::kLineCapacity> line{};
  270. const int written = std::snprintf(line.data(),
  271. line.size(),
  272. "ev=build_data stage=vendors index=%zu definitions=%zu "
  273. "sale=%zu installed=%zu skipped=%zu result=%s",
  274. storage.indexCount,
  275. storage.definitionCount,
  276. storage.saleRowCount,
  277. storage.installedRowCount,
  278. skipped,
  279. result);
  280. if (written > 0) {
  281. core::log::write(core::log::Channel::state,
  282. storage.indexCount != 0 && skipped == 0 ? core::log::Level::info
  283. : core::log::Level::warn,
  284. {line.data(), static_cast<std::size_t>(written)});
  285. }
  286. }
  287. } // namespace
  288. /** Extracts and publishes the vendor catalog from the installed packages. */
  289. bool build(const reader::Source& source, reader::Scratch& scratch) noexcept {
  290. if (state::build_data::vendor_catalog_ready()) {
  291. return true;
  292. }
  293. static Storage storage{};
  294. storage = {};
  295. if (!read_index(source, scratch, storage)) {
  296. report(storage, 0, "index");
  297. return false;
  298. }
  299. // Walk the index in order: the catalog requires ascending definition order. A definition that
  300. // will not read or will not fit costs that vendor alone, never the whole pass.
  301. std::size_t skipped = 0;
  302. for (std::size_t row = 0; row < storage.indexCount; ++row) {
  303. const domain::IndexEntry entry = storage.index[row];
  304. if (read_definition(source, scratch, entry, storage)) {
  305. continue;
  306. }
  307. ++skipped;
  308. core::log::writef(core::log::Channel::state,
  309. core::log::Level::warn,
  310. "ev=build_data stage=vendors result=skip hash=0x%08X row=%zu "
  311. "definitions=%zu sale=%zu",
  312. entry.definitionHash,
  313. row,
  314. storage.definitionCount,
  315. storage.saleRowCount);
  316. }
  317. const bool published = state::build_data::publish_vendor_catalog(
  318. std::span(storage.index).first(storage.indexCount),
  319. std::span(storage.definitions).first(storage.definitionCount),
  320. std::span(storage.saleRows).first(storage.saleRowCount),
  321. std::span(storage.installedRows).first(storage.installedRowCount));
  322. report(storage, skipped, published ? "ok" : "publish");
  323. return published;
  324. }
  325. } // namespace sunrise::client::content::vendors