载入中...
搜索中...
未找到
Evpack.cpp
浏览该文件的文档.
1#include "asset/Evpack.h"
2
4
6
7#include "data/HashFunction.h"
8
9#include <algorithm>
10#include <cstring>
11#include <limits>
12#include <optional>
13#include <set>
14#include <tuple>
15
16namespace eve::asset {
17namespace {
18
19constexpr std::size_t kHeaderSize = 128;
20constexpr std::array<std::uint8_t, 8> kPackMagic = {'E', 'V', 'P', 'A', 'C', 'K', 0, 1};
21constexpr std::array<std::uint8_t, 8> kManifestMagic = {'E', 'V', 'M', 'A', 'N', 0, 1, 0};
22constexpr std::array<std::uint8_t, 8> kTocMagic = {'E', 'V', 'T', 'O', 'C', 0, 1, 0};
23constexpr std::array<std::uint8_t, 8> kSignatureMagic = {'E', 'V', 'S', 'I', 'G', 0, 1, 0};
24constexpr std::size_t kSignatureSize = 128;
25constexpr std::uint32_t kSignedFlag = 1;
26
27void put16(std::vector<std::uint8_t>& bytes, std::uint16_t value) {
28 bytes.push_back(static_cast<std::uint8_t>(value));
29 bytes.push_back(static_cast<std::uint8_t>(value >> 8));
30}
31void put32(std::vector<std::uint8_t>& bytes, std::uint32_t value) {
32 for (unsigned shift = 0; shift != 32; shift += 8) bytes.push_back(static_cast<std::uint8_t>(value >> shift));
33}
34void put64(std::vector<std::uint8_t>& bytes, std::uint64_t value) {
35 for (unsigned shift = 0; shift != 64; shift += 8) bytes.push_back(static_cast<std::uint8_t>(value >> shift));
36}
37void putId(std::vector<std::uint8_t>& bytes, const PersistentId& id) {
38 const auto value = id.bytes();
39 bytes.insert(bytes.end(), value.begin(), value.end());
40}
41void putString(std::vector<std::uint8_t>& bytes, std::string_view value) {
42 put32(bytes, static_cast<std::uint32_t>(value.size()));
43 bytes.insert(bytes.end(), value.begin(), value.end());
44}
45void putStrings(std::vector<std::uint8_t>& bytes, const std::vector<std::string>& values) {
46 put32(bytes, static_cast<std::uint32_t>(values.size()));
47 for (const auto& value : values) putString(bytes, value);
48}
49
50bool checkedAdd(std::uint64_t left, std::uint64_t right, std::uint64_t& result) {
51 if (right > std::numeric_limits<std::uint64_t>::max() - left) return false;
52 result = left + right;
53 return true;
54}
55
56bool alignUp(std::uint64_t value, std::uint32_t alignment, std::uint64_t& result) {
57 const std::uint64_t mask = alignment - 1;
58 if (value > std::numeric_limits<std::uint64_t>::max() - mask) return false;
59 result = (value + mask) & ~mask;
60 return true;
61}
62
63bool powerOfTwo(std::uint32_t value) { return value != 0 && (value & (value - 1)) == 0; }
64
65std::array<std::uint8_t, 32> sha256(std::span<const std::uint8_t> bytes) {
66 data::HashFunction::Value digest{};
68 ->hash("sha256", reinterpret_cast<const char*>(bytes.data()), bytes.size(), digest);
69 std::array<std::uint8_t, 32> result{};
70 std::copy_n(reinterpret_cast<const std::uint8_t*>(digest.data), result.size(), result.begin());
71 return result;
72}
73
74bool validText(std::string_view value, const EvpackLimits& limits) {
75 if (value.empty() || value.size() > limits.maximumStringBytes ||
77 return false;
78 for (unsigned char byte : value)
79 if (byte == 0 || byte < 0x20 || byte == 0x7f) return false;
80 return true;
81}
82
83bool validStrings(const std::vector<std::string>& values, const EvpackLimits& limits) {
84 if (values.size() > limits.maximumStringBytes) return false;
85 std::set<std::string> unique;
86 for (const auto& value : values)
87 if (!validText(value, limits) || !unique.emplace(value).second) return false;
88 return true;
89}
90
91class Reader {
92public:
93 Reader(std::span<const std::uint8_t> bytes, std::size_t begin, std::size_t end)
94 : bytes_(bytes), cursor_(begin), end_(end) {}
95
96 bool raw(std::size_t count, std::span<const std::uint8_t>& value) {
97 if (cursor_ > end_ || count > end_ - cursor_) return false;
98 value = bytes_.subspan(cursor_, count);
99 cursor_ += count;
100 return true;
101 }
102 bool u16(std::uint16_t& value) {
103 std::span<const std::uint8_t> data;
104 if (!raw(2, data)) return false;
105 value = std::uint16_t(data[0]) | (std::uint16_t(data[1]) << 8);
106 return true;
107 }
108 bool u32(std::uint32_t& value) {
109 std::span<const std::uint8_t> data;
110 if (!raw(4, data)) return false;
111 value = 0;
112 for (unsigned index = 0; index != 4; ++index) value |= std::uint32_t(data[index]) << (index * 8);
113 return true;
114 }
115 bool u64(std::uint64_t& value) {
116 std::span<const std::uint8_t> data;
117 if (!raw(8, data)) return false;
118 value = 0;
119 for (unsigned index = 0; index != 8; ++index) value |= std::uint64_t(data[index]) << (index * 8);
120 return true;
121 }
122 bool id(PersistentId& value) {
123 std::span<const std::uint8_t> data;
124 if (!raw(16, data)) return false;
125 auto parsed = PersistentId::fromBytes(data);
126 if (!parsed || parsed->isNil()) return false;
127 value = *parsed;
128 return true;
129 }
130 bool string(std::string& value, const EvpackLimits& limits) {
131 std::uint32_t size = 0;
132 std::span<const std::uint8_t> data;
133 if (!u32(size) || size > limits.maximumStringBytes || !raw(size, data)) return false;
134 value.assign(reinterpret_cast<const char*>(data.data()), data.size());
135 return validText(value, limits);
136 }
137 bool strings(std::vector<std::string>& values, const EvpackLimits& limits) {
138 std::uint32_t count = 0;
139 if (!u32(count) || count > limits.maximumStringBytes) return false;
140 values.reserve(count);
141 for (std::uint32_t index = 0; index < count; ++index) {
142 std::string value;
143 if (!string(value, limits)) return false;
144 values.push_back(std::move(value));
145 }
146 return validStrings(values, limits);
147 }
148 [[nodiscard]] std::size_t cursor() const { return cursor_; }
149 [[nodiscard]] bool finished() const { return cursor_ == end_; }
150
151private:
152 std::span<const std::uint8_t> bytes_;
153 std::size_t cursor_;
154 std::size_t end_;
155};
156
157std::vector<std::uint8_t> encodeManifest(const std::vector<EvpackVariant>& variants) {
158 std::vector<std::uint8_t> bytes(kManifestMagic.begin(), kManifestMagic.end());
159 put32(bytes, static_cast<std::uint32_t>(variants.size()));
160 put32(bytes, 0);
161 for (const auto& variant : variants) {
162 putString(bytes, variant.os); putString(bytes, variant.arch); putString(bytes, variant.graphics);
163 putStrings(bytes, variant.textureFamilies); putString(bytes, variant.shaderFormat);
164 putString(bytes, variant.quality); putStrings(bytes, variant.features);
165 }
166 return bytes;
167}
168
169struct BuildRecord {
170 EvpackChunkInput input;
171 std::vector<std::uint8_t> stored;
172 std::array<std::uint8_t, 32> hash{};
173 std::uint64_t offset = 0;
174};
175
176std::vector<std::uint8_t> encodeToc(const std::vector<BuildRecord>& records) {
177 std::vector<std::uint8_t> bytes(kTocMagic.begin(), kTocMagic.end());
178 put32(bytes, static_cast<std::uint32_t>(records.size())); put32(bytes, 0);
179 for (const auto& record : records) {
180 const auto& input = record.input;
181 putId(bytes, input.assetId); put64(bytes, input.schemaVersion.value());
182 put32(bytes, input.variantIndex); put16(bytes, static_cast<std::uint16_t>(input.kind));
183 put16(bytes, static_cast<std::uint16_t>(input.codec)); put32(bytes, input.chunkId);
185 put64(bytes, record.offset); put64(bytes, record.stored.size()); put64(bytes, input.bytes.size());
186 bytes.insert(bytes.end(), record.hash.begin(), record.hash.end());
187 put32(bytes, static_cast<std::uint32_t>(input.dependencies.size()));
188 putString(bytes, input.type);
189 for (const auto& dependency : input.dependencies) putId(bytes, dependency);
190 }
191 return bytes;
192}
193
194bool contains(const std::vector<std::string>& values, const std::string& value) {
195 return std::find(values.begin(), values.end(), value) != values.end();
196}
197
198bool variantMatches(const EvpackVariant& variant, const EvpackCapabilities& capabilities) {
199 if (variant.os != capabilities.os || variant.arch != capabilities.arch ||
200 variant.graphics != capabilities.graphics || !contains(capabilities.shaderFormats, variant.shaderFormat) ||
201 !contains(capabilities.qualities, variant.quality)) return false;
202 if (!variant.textureFamilies.empty() &&
203 std::none_of(variant.textureFamilies.begin(), variant.textureFamilies.end(),
204 [&](const auto& family) { return contains(capabilities.textureFamilies, family); })) return false;
205 return std::all_of(variant.features.begin(), variant.features.end(),
206 [&](const auto& feature) { return contains(capabilities.features, feature); });
207}
208
209Result<std::vector<std::uint8_t>> encodeSignature(const EvpackSignature& signature) {
210 if (signature.algorithm.empty() || signature.algorithm.size() > 15 ||
211 !validText(signature.algorithm, EvpackLimits{}))
212 return Result<std::vector<std::uint8_t>>::failure(Diagnostic::error(
213 DiagnosticCode::InvalidArgument, "signature algorithm identifier is invalid", {}, {}, "asset.evpack"));
214 std::vector<std::uint8_t> bytes(kSignatureSize, 0);
215 std::copy(kSignatureMagic.begin(), kSignatureMagic.end(), bytes.begin());
216 std::copy(signature.algorithm.begin(), signature.algorithm.end(), bytes.begin() + 8);
217 std::copy(signature.keyId.begin(), signature.keyId.end(), bytes.begin() + 24);
218 std::copy(signature.bytes.begin(), signature.bytes.end(), bytes.begin() + 56);
219 return Result<std::vector<std::uint8_t>>::success(std::move(bytes));
220}
221
222Result<EvpackSignature> decodeSignature(std::span<const std::uint8_t> bytes) {
223 if (bytes.size() != kSignatureSize ||
224 !std::equal(kSignatureMagic.begin(), kSignatureMagic.end(), bytes.begin()))
226 DiagnosticCode::ParseError, "runtime package signature block is invalid", {}, {}, "asset.evpack"));
227 const auto terminator = std::find(bytes.begin() + 8, bytes.begin() + 24, std::uint8_t(0));
228 if (terminator == bytes.begin() + 8)
230 DiagnosticCode::ParseError, "runtime package signature algorithm is empty", {}, {}, "asset.evpack"));
231 if (std::any_of(terminator, bytes.begin() + 24, [](std::uint8_t value) { return value != 0; }) ||
232 std::any_of(bytes.begin() + 120, bytes.end(), [](std::uint8_t value) { return value != 0; }))
234 DiagnosticCode::ParseError, "runtime package signature padding is not canonical", {}, {}, "asset.evpack"));
235 EvpackSignature signature;
236 signature.algorithm.assign(reinterpret_cast<const char*>(&bytes[8]),
237 static_cast<std::size_t>(terminator - (bytes.begin() + 8)));
238 if (!validText(signature.algorithm, EvpackLimits{}))
240 DiagnosticCode::ParseError, "runtime package signature algorithm is invalid", {}, {}, "asset.evpack"));
241 std::copy_n(bytes.begin() + 24, 32, signature.keyId.begin());
242 std::copy_n(bytes.begin() + 56, 64, signature.bytes.begin());
243 return Result<EvpackSignature>::success(std::move(signature));
244}
245
246} // namespace
247
249 if (build.packageId.isNil() || build.buildId.isNil())
251 DiagnosticCode::InvalidArgument, "packageId and buildId must be non-nil", {}, {}, "asset.evpack"));
252 if (build.variants.empty() || build.variants.size() > limits.maximumVariants ||
253 build.chunks.size() > limits.maximumChunks)
255 DiagnosticCode::InvalidArgument, "variant or chunk count is outside limits", {}, {}, "asset.evpack"));
256 for (const auto& variant : build.variants) {
257 if (!validText(variant.os, limits) || !validText(variant.arch, limits) ||
258 !validText(variant.graphics, limits) || !validText(variant.shaderFormat, limits) ||
259 !validText(variant.quality, limits) || !validStrings(variant.textureFamilies, limits) ||
260 !validStrings(variant.features, limits))
261 return Result<std::vector<std::uint8_t>>::failure(Diagnostic::error(
262 DiagnosticCode::InvalidArgument, "variant contains invalid capability text", {}, {}, "asset.evpack"));
263 }
264
265 std::vector<BuildRecord> records;
266 std::uint64_t decodedTotal = 0;
267 for (auto& input : build.chunks) {
268 if (input.assetId.isNil() || !validText(input.type, limits) || input.schemaVersion.value() == 0 ||
269 input.variantIndex >= build.variants.size() || !powerOfTwo(input.alignment) ||
270 input.alignment > 16 * 1024 * 1024 || input.dependencies.size() > limits.maximumDependencies ||
272 input.bytes.size() > limits.maximumChunkBytes ||
273 input.bytes.size() > limits.maximumDecodedBytes ||
274 decodedTotal > limits.maximumDecodedBytes - input.bytes.size())
275 return Result<std::vector<std::uint8_t>>::failure(
276 Diagnostic::error(DiagnosticCode::InvalidArgument, "chunk metadata or resource budget is invalid",
277 input.type, {}, "asset.evpack"));
278 for (const auto& dependency : input.dependencies)
279 if (dependency.isNil())
280 return Result<std::vector<std::uint8_t>>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,
281 "chunk dependency must be non-nil",
282 input.type, {}, "asset.evpack"));
283 std::sort(input.dependencies.begin(), input.dependencies.end());
284 if (std::adjacent_find(input.dependencies.begin(), input.dependencies.end()) != input.dependencies.end())
286 DiagnosticCode::Conflict, "chunk dependencies contain duplicates", input.type, {}, "asset.evpack"));
287 decodedTotal += input.bytes.size();
288 records.push_back({std::move(input), {}, {}, 0});
289 records.back().hash = sha256(records.back().input.bytes);
290 auto compressed = compressEvpackChunk(records.back().input.codec, records.back().input.bytes);
291 if (!compressed) return Result<std::vector<std::uint8_t>>::failure(compressed.status());
292 records.back().stored = std::move(compressed).takeValue();
293 if (records.back().stored.size() > limits.maximumChunkBytes)
294 return Result<std::vector<std::uint8_t>>::failure(
295 Diagnostic::error(DiagnosticCode::InvalidArgument, "stored chunk exceeds size limits",
296 records.back().input.type, {}, "asset.evpack"));
297 }
298 std::sort(records.begin(), records.end(), [](const BuildRecord& left, const BuildRecord& right) {
299 return std::tie(left.input.assetId, left.input.type, left.input.schemaVersion, left.input.variantIndex,
300 left.input.kind, left.input.chunkId) <
301 std::tie(right.input.assetId, right.input.type, right.input.schemaVersion, right.input.variantIndex,
302 right.input.kind, right.input.chunkId);
303 });
304 for (std::size_t index = 1; index < records.size(); ++index) {
305 const auto key = [](const BuildRecord& value) {
306 return std::tie(value.input.assetId, value.input.type, value.input.schemaVersion,
307 value.input.variantIndex, value.input.kind, value.input.chunkId);
308 };
309 if (key(records[index - 1]) == key(records[index]))
310 return Result<std::vector<std::uint8_t>>::failure(
311 Diagnostic::error(DiagnosticCode::Conflict, "duplicate runtime chunk key", records[index].input.type,
312 {}, "asset.evpack"));
313 }
314
315 auto manifest = encodeManifest(build.variants);
316 auto preliminaryToc = encodeToc(records);
317 if (manifest.size() > limits.maximumMetadataBytes || preliminaryToc.size() > limits.maximumMetadataBytes ||
318 manifest.size() > limits.maximumMetadataBytes - preliminaryToc.size())
319 return Result<std::vector<std::uint8_t>>::failure(Diagnostic::error(
320 DiagnosticCode::InvalidArgument, "runtime package metadata exceeds size limits", {}, {}, "asset.evpack"));
321 const std::uint64_t signatureSize = build.signer ? kSignatureSize : 0;
322 std::uint64_t cursor = kHeaderSize + manifest.size() + preliminaryToc.size() + signatureSize;
323 for (auto& record : records) {
324 if (!alignUp(cursor, record.input.alignment, record.offset) ||
325 !checkedAdd(record.offset, record.stored.size(), cursor) || cursor > limits.maximumPackageBytes)
326 return Result<std::vector<std::uint8_t>>::failure(Diagnostic::error(
327 DiagnosticCode::InvalidArgument, "encoded package exceeds size limits", {}, {}, "asset.evpack"));
328 }
329 auto toc = encodeToc(records);
330 std::vector<std::uint8_t> bytes(static_cast<std::size_t>(cursor), 0);
331 std::copy(manifest.begin(), manifest.end(), bytes.begin() + kHeaderSize);
332 const std::uint64_t tocOffset = kHeaderSize + manifest.size();
333 std::copy(toc.begin(), toc.end(), bytes.begin() + static_cast<std::size_t>(tocOffset));
334 for (const auto& record : records)
335 std::copy(record.stored.begin(), record.stored.end(),
336 bytes.begin() + static_cast<std::size_t>(record.offset));
337
338 std::vector<std::uint8_t> header(kPackMagic.begin(), kPackMagic.end());
339 put32(header, kHeaderSize); put32(header, build.signer ? kSignedFlag : 0);
340 putId(header, build.packageId); putId(header, build.buildId);
341 put64(header, tocOffset); put64(header, toc.size()); put64(header, kHeaderSize); put64(header, manifest.size());
342 put64(header, build.signer ? tocOffset + toc.size() : 0); put64(header, signatureSize);
343 header.resize(kHeaderSize, 0);
344 std::vector<std::uint8_t> metadataHashInput = header;
345 metadataHashInput.insert(metadataHashInput.end(), manifest.begin(), manifest.end());
346 metadataHashInput.insert(metadataHashInput.end(), toc.begin(), toc.end());
347 const auto headerHash = sha256(metadataHashInput);
348 std::copy(headerHash.begin(), headerHash.end(), header.begin() + 96);
349 std::copy(header.begin(), header.end(), bytes.begin());
350 if (build.signer) {
351 auto signature = build.signer->sign(headerHash);
352 if (!signature) return Result<std::vector<std::uint8_t>>::failure(signature.status());
353 auto encoded = encodeSignature(signature.value());
354 if (!encoded) return encoded;
355 std::copy(encoded.value().begin(), encoded.value().end(),
356 bytes.begin() + static_cast<std::size_t>(tocOffset + toc.size()));
357 }
358 return Result<std::vector<std::uint8_t>>::success(std::move(bytes));
359}
360
361Result<Evpack> parseEvpackMetadata(std::span<const std::uint8_t> bytes, std::uint64_t packageSize,
362 const EvpackLimits& limits, const EvpackTrust& trust) {
363 if (packageSize < kHeaderSize || packageSize > limits.maximumPackageBytes ||
364 bytes.size() < kHeaderSize || bytes.size() > packageSize)
366 Diagnostic::error(DiagnosticCode::ParseError, "package size is outside limits", {}, {}, "asset.evpack"));
367 const bool completePackage = bytes.size() == packageSize;
368 Reader header(bytes, 0, kHeaderSize);
369 std::span<const std::uint8_t> magic;
370 std::uint32_t headerSize = 0, flags = 0;
371 PersistentId packageId, buildId;
372 std::uint64_t tocOffset = 0, tocSize = 0, manifestOffset = 0, manifestSize = 0, signatureOffset = 0,
373 signatureSize = 0;
374 std::span<const std::uint8_t> storedHeaderHash;
375 if (!header.raw(8, magic) || !std::equal(magic.begin(), magic.end(), kPackMagic.begin()) ||
376 !header.u32(headerSize) || headerSize != kHeaderSize || !header.u32(flags) ||
377 (flags & ~kSignedFlag) != 0 ||
378 !header.id(packageId) || !header.id(buildId) || !header.u64(tocOffset) || !header.u64(tocSize) ||
379 !header.u64(manifestOffset) || !header.u64(manifestSize) || !header.u64(signatureOffset) ||
380 !header.u64(signatureSize) ||
381 !header.raw(32, storedHeaderHash) || !header.finished())
383 Diagnostic::error(DiagnosticCode::ParseError, "runtime package header is invalid", {}, {}, "asset.evpack"));
384 const auto validRange = [&](std::uint64_t offset, std::uint64_t size) {
385 return offset >= kHeaderSize && offset <= packageSize && size <= packageSize - offset;
386 };
387 if (!validRange(manifestOffset, manifestSize) || !validRange(tocOffset, tocSize) ||
388 manifestOffset != kHeaderSize || manifestOffset + manifestSize != tocOffset ||
389 manifestSize > limits.maximumMetadataBytes || tocSize > limits.maximumMetadataBytes ||
390 manifestSize > limits.maximumMetadataBytes - tocSize)
392 Diagnostic::error(DiagnosticCode::ParseError, "manifest or TOC range is invalid", {}, {}, "asset.evpack"));
393 const bool signedPackage = (flags & kSignedFlag) != 0;
394 if (signedPackage != (signatureSize != 0) ||
395 (signedPackage && (signatureSize != kSignatureSize || signatureOffset != tocOffset + tocSize ||
396 !validRange(signatureOffset, signatureSize))) ||
397 (!signedPackage && (signatureOffset != 0 || signatureSize != 0)))
399 Diagnostic::error(DiagnosticCode::ParseError, "signature range is invalid", {}, {}, "asset.evpack"));
400 if (tocOffset + tocSize > bytes.size())
401 return Result<Evpack>::failure(Diagnostic::error(
402 DiagnosticCode::ParseError, "metadata prefix does not contain the complete TOC", {}, {}, "asset.evpack"));
403 std::vector<std::uint8_t> metadataHashInput(bytes.begin(), bytes.begin() + kHeaderSize);
404 std::fill(metadataHashInput.begin() + 96, metadataHashInput.end(), 0);
405 metadataHashInput.insert(metadataHashInput.end(), bytes.begin() + static_cast<std::size_t>(manifestOffset),
406 bytes.begin() + static_cast<std::size_t>(manifestOffset + manifestSize));
407 metadataHashInput.insert(metadataHashInput.end(), bytes.begin() + static_cast<std::size_t>(tocOffset),
408 bytes.begin() + static_cast<std::size_t>(tocOffset + tocSize));
409 const auto computedMetadataHash = sha256(metadataHashInput);
410 if (!std::equal(storedHeaderHash.begin(), storedHeaderHash.end(), computedMetadataHash.begin()))
411 return Result<Evpack>::failure(Diagnostic::error(DiagnosticCode::HashMismatch,
412 "runtime package header and metadata hash does not match", {},
413 {}, "asset.evpack"));
414
415 Evpack pack;
416 pack.packageId_ = packageId;
417 pack.buildId_ = buildId;
418 if (signedPackage) {
419 if (signatureOffset + signatureSize > bytes.size())
420 return Result<Evpack>::failure(Diagnostic::error(DiagnosticCode::ParseError,
421 "metadata prefix does not contain the signature block", {},
422 {}, "asset.evpack"));
423 auto signature = decodeSignature(bytes.subspan(static_cast<std::size_t>(signatureOffset),
424 static_cast<std::size_t>(signatureSize)));
425 if (!signature) return Result<Evpack>::failure(signature.status());
426 if (!trust.verifier)
427 return Result<Evpack>::failure(Diagnostic::error(DiagnosticCode::PreconditionViolation,
428 "signed package requires a configured trust verifier", {},
429 {}, "asset.evpack"));
430 auto verified = trust.verifier->verify(computedMetadataHash, signature.value());
431 if (!verified) return Result<Evpack>::failure(verified.status());
432 pack.signature_ = signature.value();
433 pack.trustedSignature_ = true;
434 } else if (trust.policy == EvpackSignaturePolicy::RequireTrustedSignature) {
435 return Result<Evpack>::failure(Diagnostic::error(DiagnosticCode::PreconditionViolation,
436 "project policy requires a trusted package signature", {}, {},
437 "asset.evpack"));
438 }
439
440 Reader manifestReader(bytes, static_cast<std::size_t>(manifestOffset),
441 static_cast<std::size_t>(manifestOffset + manifestSize));
442 std::uint32_t variantCount = 0, reserved = 0;
443 if (!manifestReader.raw(8, magic) || !std::equal(magic.begin(), magic.end(), kManifestMagic.begin()) ||
444 !manifestReader.u32(variantCount) || variantCount == 0 || variantCount > limits.maximumVariants ||
445 !manifestReader.u32(reserved) || reserved != 0)
447 Diagnostic::error(DiagnosticCode::ParseError, "binary Cook manifest is invalid", {}, {}, "asset.evpack"));
448 pack.variants_.reserve(variantCount);
449 for (std::uint32_t index = 0; index < variantCount; ++index) {
451 if (!manifestReader.string(variant.os, limits) || !manifestReader.string(variant.arch, limits) ||
452 !manifestReader.string(variant.graphics, limits) ||
453 !manifestReader.strings(variant.textureFamilies, limits) ||
454 !manifestReader.string(variant.shaderFormat, limits) || !manifestReader.string(variant.quality, limits) ||
455 !manifestReader.strings(variant.features, limits))
457 Diagnostic::error(DiagnosticCode::ParseError, "variant record is invalid", {}, {}, "asset.evpack"));
458 pack.variants_.push_back(std::move(variant));
459 }
460 if (!manifestReader.finished())
461 return Result<Evpack>::failure(Diagnostic::error(
462 DiagnosticCode::ParseError, "binary Cook manifest has trailing bytes", {}, {}, "asset.evpack"));
463
464 Reader tocReader(bytes, static_cast<std::size_t>(tocOffset), static_cast<std::size_t>(tocOffset + tocSize));
465 std::uint32_t chunkCount = 0;
466 if (!tocReader.raw(8, magic) || !std::equal(magic.begin(), magic.end(), kTocMagic.begin()) ||
467 !tocReader.u32(chunkCount) || chunkCount > limits.maximumChunks ||
468 !tocReader.u32(reserved) || reserved != 0)
470 Diagnostic::error(DiagnosticCode::ParseError, "runtime package TOC is invalid", {}, {}, "asset.evpack"));
471 std::uint64_t decodedTotal = 0;
472 std::uint64_t previousEnd = signedPackage ? signatureOffset + signatureSize : tocOffset + tocSize;
473 using ChunkKey = std::tuple<PersistentId, std::string, std::uint64_t, std::uint32_t,
474 std::uint16_t, std::uint32_t>;
475 std::optional<ChunkKey> previousKey;
476 for (std::uint32_t index = 0; index < chunkCount; ++index) {
477 EvpackChunk chunk;
478 std::uint16_t kind = 0, codec = 0;
479 std::uint64_t schemaVersion = 0;
480 std::span<const std::uint8_t> hash;
481 std::uint32_t dependencyCount = 0;
482 if (!tocReader.id(chunk.assetId) || !tocReader.u64(schemaVersion) || schemaVersion == 0 ||
483 !tocReader.u32(chunk.variantIndex) ||
484 chunk.variantIndex >= variantCount || !tocReader.u16(kind) || !tocReader.u16(codec) ||
485 !tocReader.u32(chunk.chunkId) || !tocReader.u32(chunk.alignment) || !powerOfTwo(chunk.alignment) ||
486 chunk.alignment > 16 * 1024 * 1024 || !tocReader.u64(chunk.offset) ||
487 !tocReader.u64(chunk.storedSize) || !tocReader.u64(chunk.decodedSize) ||
488 !tocReader.raw(32, hash) || !tocReader.u32(dependencyCount) ||
489 dependencyCount > limits.maximumDependencies || !tocReader.string(chunk.type, limits))
490 return Result<Evpack>::failure(Diagnostic::error(
491 DiagnosticCode::ParseError, "runtime chunk record is invalid", {}, {}, "asset.evpack"));
492 chunk.schemaVersion = SchemaVersion(schemaVersion);
493 chunk.kind = static_cast<EvpackChunkKind>(kind);
494 chunk.codec = static_cast<EvpackCodec>(codec);
495 if (chunk.kind < EvpackChunkKind::Definition || chunk.kind > EvpackChunkKind::DebugName ||
496 (chunk.codec != EvpackCodec::None && chunk.codec != EvpackCodec::Zstd) ||
497 (chunk.codec == EvpackCodec::None && chunk.storedSize != chunk.decodedSize) ||
498 chunk.storedSize > limits.maximumChunkBytes || chunk.decodedSize > limits.maximumDecodedBytes ||
499 decodedTotal > limits.maximumDecodedBytes - chunk.decodedSize || chunk.offset % chunk.alignment != 0 ||
500 chunk.offset < previousEnd || chunk.offset > packageSize || chunk.storedSize > packageSize - chunk.offset)
501 return Result<Evpack>::failure(Diagnostic::error(
502 DiagnosticCode::Unsupported, "unsupported or unsafe runtime chunk", chunk.type, {}, "asset.evpack"));
503 ChunkKey key{chunk.assetId, chunk.type, chunk.schemaVersion.value(), chunk.variantIndex, kind, chunk.chunkId};
504 if (previousKey && !(*previousKey < key))
505 return Result<Evpack>::failure(Diagnostic::error(
506 DiagnosticCode::Conflict, "runtime chunk TOC is not canonical", chunk.type, {}, "asset.evpack"));
507 previousKey = std::move(key);
508 std::copy(hash.begin(), hash.end(), chunk.contentHash.begin());
509 chunk.dependencies.reserve(dependencyCount);
510 for (std::uint32_t dependency = 0; dependency < dependencyCount; ++dependency) {
512 if (!tocReader.id(id))
513 return Result<Evpack>::failure(Diagnostic::error(
514 DiagnosticCode::ParseError, "chunk dependency is invalid", chunk.type, {}, "asset.evpack"));
515 chunk.dependencies.push_back(id);
516 }
517 if (!std::is_sorted(chunk.dependencies.begin(), chunk.dependencies.end()) ||
518 std::adjacent_find(chunk.dependencies.begin(), chunk.dependencies.end()) != chunk.dependencies.end())
519 return Result<Evpack>::failure(Diagnostic::error(
520 DiagnosticCode::Conflict, "chunk dependencies are not canonical", chunk.type, {}, "asset.evpack"));
521 if (completePackage) {
522 const auto payload = bytes.subspan(static_cast<std::size_t>(chunk.offset),
523 static_cast<std::size_t>(chunk.storedSize));
524 auto decoded = decompressEvpackChunk(chunk.codec, payload, chunk.decodedSize,
525 limits.maximumDecodedBytes);
526 if (!decoded) return Result<Evpack>::failure(decoded.status());
527 if (sha256(decoded.value()) != chunk.contentHash)
528 return Result<Evpack>::failure(Diagnostic::error(
529 DiagnosticCode::HashMismatch, "runtime chunk hash does not match", chunk.type, {}, "asset.evpack"));
530 }
531 decodedTotal += chunk.decodedSize;
532 previousEnd = chunk.offset + chunk.storedSize;
533 pack.chunks_.push_back(std::move(chunk));
534 }
535 if (!tocReader.finished())
536 return Result<Evpack>::failure(Diagnostic::error(
537 DiagnosticCode::ParseError, "runtime package TOC has trailing bytes", {}, {}, "asset.evpack"));
538 if (completePackage) pack.bytes_.assign(bytes.begin(), bytes.end());
539 return Result<Evpack>::success(std::move(pack));
540}
541
542Result<Evpack> parseEvpack(std::span<const std::uint8_t> bytes, const EvpackLimits& limits,
543 const EvpackTrust& trust) {
544 return parseEvpackMetadata(bytes, bytes.size(), limits, trust);
545}
546
547Result<std::span<const std::uint8_t>> Evpack::chunkBytes(std::size_t index) const {
548 if (index >= chunks_.size())
549 return Result<std::span<const std::uint8_t>>::failure(Diagnostic::error(
550 DiagnosticCode::InvalidArgument, "chunk index is outside the TOC", {}, {}, "asset.evpack"));
551 if (bytes_.empty())
553 Diagnostic::error(DiagnosticCode::PreconditionViolation, "metadata-only package requires a range source",
554 {}, {}, "asset.evpack"));
555 const auto& chunk = chunks_[index];
556 if (chunk.codec != EvpackCodec::None)
558 Diagnostic::error(DiagnosticCode::PreconditionViolation, "compressed chunk requires decodeChunk()",
559 chunk.type, {}, "asset.evpack"));
561 std::span<const std::uint8_t>(bytes_).subspan(static_cast<std::size_t>(chunk.offset),
562 static_cast<std::size_t>(chunk.storedSize)));
563}
564
566 std::size_t index, std::uint64_t maximumDecodedBytes) const {
567 if (index >= chunks_.size())
568 return Result<std::vector<std::uint8_t>>::failure(Diagnostic::error(
569 DiagnosticCode::InvalidArgument, "chunk index is outside the TOC", {}, {}, "asset.evpack"));
570 if (bytes_.empty())
571 return Result<std::vector<std::uint8_t>>::failure(
572 Diagnostic::error(DiagnosticCode::PreconditionViolation, "metadata-only package requires a range source",
573 {}, {}, "asset.evpack"));
574 const auto& chunk = chunks_[index];
575 const auto stored = std::span<const std::uint8_t>(bytes_).subspan(
576 static_cast<std::size_t>(chunk.offset), static_cast<std::size_t>(chunk.storedSize));
577 auto decoded = decompressEvpackChunk(chunk.codec, stored, chunk.decodedSize, maximumDecodedBytes);
578 if (!decoded) return decoded;
579 if (sha256(decoded.value()) != chunk.contentHash)
580 return Result<std::vector<std::uint8_t>>::failure(Diagnostic::error(
581 DiagnosticCode::HashMismatch, "runtime chunk hash does not match", chunk.type, {}, "asset.evpack"));
582 return decoded;
583}
584
586 const EvpackCapabilities& capabilities) {
587 for (std::size_t index = 0; index < pack.variants().size(); ++index) {
588 if (variantMatches(pack.variants()[index], capabilities))
590 {static_cast<std::uint32_t>(index), index != 0});
591 }
593 Diagnostic::error(DiagnosticCode::Unsupported, "no runtime package variant satisfies device capabilities", {},
594 {}, "asset.evpack"));
595}
596
597} // namespace eve::asset
double value
Value::Object payload
std::string packageId
int mask
std::string variant
std::map< std::string, Var > values
Bounded deterministic compression used by the .evpack wire format.
std::uint64_t offset
Definition Evpack.cpp:173
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
std::vector< std::uint8_t > stored
Definition Evpack.cpp:171
EvpackChunkInput input
Definition Evpack.cpp:170
Versioned random-access runtime package produced by the asset cooker.
std::uint32_t key
HexVec3 left
HexVec3 right
std::int32_t second
TokenKind kind
size_t offset
std::uint64_t bytes
std::string id
Definition PlayHost.cpp:108
float begin
std::string digest
std::string string
std::uint32_t count
std::size_t cursor
float size
Definition TreeMesh.cpp:156
uint32_t index
const AssetImportLimits & limits
Strict allocation-free UTF-8 validation.
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Fully admitted owning runtime pack.
Definition Evpack.h:164
const std::vector< EvpackVariant > & variants() const noexcept
Ordered capability variants encoded in the binary manifest.
Definition Evpack.h:171
static HashFunction * getHashFunction(std::string function)
Get a HashFunction instance for the given function.
virtual void hash(std::string function, const char *input, uint64_t length, Value &output) const =0
Hash the input, producing an set of bytes as output.
Id128 public API.
Definition Identity.h:112
static std::optional< Id128 > fromBytes(std::span< const std::uint8_t > bytes) noexcept
Constructs an ID from exactly 16 bytes.
Definition Identity.h:161
constexpr bool isNil() const noexcept
Returns whether this value is the all-zero nil ID.
Definition Identity.h:176
constexpr std::uint64_t value() const noexcept
Returns the underlying value at an explicit protocol boundary.
std::string sha256(std::span< const std::uint8_t > bytes)
Sha 256.
Result< Evpack > parseEvpack(std::span< const std::uint8_t > bytes, const EvpackLimits &limits, const EvpackTrust &trust)
Parse, bound-check and hash-verify a complete untrusted .evpack.
Definition Evpack.cpp:542
Result< std::vector< std::uint8_t > > buildEvpack(EvpackBuild build, const EvpackLimits &limits)
Build deterministic little-endian .evpack bytes from validated Cook output.
Definition Evpack.cpp:248
EvpackCodec
Physical outer compression applied to one runtime chunk.
Definition Evpack.h:36
Result< std::vector< std::uint8_t > > compressEvpackChunk(EvpackCodec codec, std::span< const std::uint8_t > decoded)
Compress decoded chunk bytes using the selected stable wire codec.
Result< std::vector< std::uint8_t > > decompressEvpackChunk(EvpackCodec codec, std::span< const std::uint8_t > stored, std::uint64_t decodedSize, std::uint64_t maximumDecodedBytes)
Decode one chunk into its authenticated canonical bytes.
Result< EvpackVariantSelection > selectEvpackVariant(const Evpack &pack, const EvpackCapabilities &capabilities)
Select the first explicitly ordered variant satisfied by device capabilities.
Definition Evpack.cpp:585
Result< Evpack > parseEvpackMetadata(std::span< const std::uint8_t > bytes, std::uint64_t packageSize, const EvpackLimits &limits, const EvpackTrust &trust)
Admit only the contiguous header/manifest/TOC prefix of a range-readable package.
Definition Evpack.cpp:361
EvpackChunkKind
Semantic role of a runtime chunk.
Definition Evpack.h:26
void put16(std::vector< char > &b, uint16_t v)
以小端序写入 uint16 / uint32。
Definition zip_writer.h:51
void put32(std::vector< char > &b, uint32_t v)
Put 32.
Definition zip_writer.h:56
std::string header(int localX, int localY)
Header.
Definition KernelGen.cpp:13
StrongUuid< detail::PersistentIdTag > PersistentId
Stable instance identity for persistence, networking and process boundaries.
Definition Identity.h:254
bool isValidUtf8(std::string_view text, Utf8NullPolicy nullPolicy) noexcept
Validate shortest-form UTF-8, Unicode scalar range and optional NUL exclusion.
Complete input to deterministic .evpack assembly.
Definition Evpack.h:117
std::vector< EvpackVariant > variants
Definition Evpack.h:120
PersistentId packageId
Definition Evpack.h:118
PersistentId buildId
Definition Evpack.h:119
std::vector< EvpackChunkInput > chunks
Definition Evpack.h:121
Actual runtime device capabilities used for variant selection.
Definition Evpack.h:92
EvpackChunkKind kind
Definition Evpack.h:108
std::vector< std::uint8_t > bytes
Definition Evpack.h:113
SchemaVersion schemaVersion
Definition Evpack.h:106
std::uint32_t alignment
Definition Evpack.h:111
std::vector< PersistentId > dependencies
Definition Evpack.h:112
std::uint32_t variantIndex
Definition Evpack.h:107
Admitted immutable chunk metadata.
Definition Evpack.h:138
EvpackCodec codec
Definition Evpack.h:145
EvpackChunkKind kind
Definition Evpack.h:143
std::array< std::uint8_t, 32 > contentHash
Definition Evpack.h:150
SchemaVersion schemaVersion
Definition Evpack.h:141
std::vector< PersistentId > dependencies
Definition Evpack.h:151
std::uint64_t decodedSize
Definition Evpack.h:149
std::uint32_t alignment
Definition Evpack.h:146
std::uint64_t offset
Definition Evpack.h:147
std::uint32_t variantIndex
Definition Evpack.h:142
std::uint64_t storedSize
Definition Evpack.h:148
std::uint32_t chunkId
Definition Evpack.h:144
PersistentId assetId
Definition Evpack.h:139
Resource limits checked before package allocations and hash work.
Definition Evpack.h:126
Trust inputs kept separate from size/resource limits.
Definition Evpack.h:75
std::shared_ptr< const EvpackSignatureVerifier > verifier
Definition Evpack.h:77
EvpackSignaturePolicy policy
Definition Evpack.h:76
Capability-addressed runtime representation; list order is fallback order.
Definition Evpack.h:81