Pixie
Loading...
Searching...
No Matches
file_archive.h
Go to the documentation of this file.
1#pragma once
2
7
9#include <pixie/detail/serialization.h>
10#include <pixie/storage/read_only_view.h>
11#include <pixie/wavelet_tree/index.h>
12
13#include <algorithm>
14#include <array>
15#include <cstddef>
16#include <cstdint>
17#include <limits>
18#include <optional>
19#include <span>
20#include <stdexcept>
21#include <string>
22#include <string_view>
23#include <utility>
24#include <vector>
25
26namespace pixie {
27
29enum class FileArchiveEntryType : std::uint8_t {
30 kRegular = 0,
31 kSymlink = 1,
32};
33
36 std::string path;
37 std::vector<std::byte> content;
38 FileArchiveEntryType type = FileArchiveEntryType::kRegular;
39 std::uint32_t mode = 0;
40};
41
44 std::string path;
45 FileArchiveEntryType type = FileArchiveEntryType::kRegular;
46 std::uint32_t mode = 0;
47};
48
51 std::string_view path;
52 std::size_t content_offset = 0;
53 std::size_t content_size = 0;
54 std::size_t newline_rank_base = 0;
55 std::size_t line_count = 0;
56 std::uint32_t mode = 0;
57 FileArchiveEntryType type = FileArchiveEntryType::kRegular;
58 bool is_text = false;
59};
60
61namespace file_archive_detail {
62
63inline constexpr std::array<std::uint8_t, 8> kMagic = {'P', 'I', 'X', 'A',
64 'R', 'C', 'H', '1'};
65inline constexpr std::uint32_t kVersion = 5;
66inline constexpr std::size_t kHeaderBytes = 3 * sizeof(std::uint64_t);
67inline constexpr std::size_t kMetadataHeaderBytes =
68 kHeaderBytes + 2 * sizeof(std::uint32_t) + 5 * sizeof(std::uint64_t);
69inline constexpr std::size_t kRecordBytes =
70 6 * sizeof(std::uint64_t) + sizeof(std::uint32_t) + 4;
71inline constexpr std::size_t kByteAlphabetSize = 256;
72
73struct FileRecord {
74 std::size_t path_offset = 0;
75 std::size_t path_size = 0;
76 std::size_t content_offset = 0;
77 std::size_t content_size = 0;
78 std::size_t newline_rank_base = 0;
79 std::size_t line_count = 0;
80 std::uint32_t mode = 0;
81 FileArchiveEntryType type = FileArchiveEntryType::kRegular;
82 bool is_text = false;
83};
84
87 public:
89 explicit Utf8Validator(bool reject_nul) : reject_nul_(reject_nul) {}
90
92 void Consume(std::span<const std::byte> bytes) {
93 if (!valid_) {
94 return;
95 }
96 for (const std::byte raw : bytes) {
97 const std::uint8_t byte = std::to_integer<std::uint8_t>(raw);
98 if (remaining_ == 0) {
99 if (byte == 0 && reject_nul_) {
100 valid_ = false;
101 return;
102 }
103 if (byte <= 0x7fU) {
104 continue;
105 }
106 if (byte >= 0xc2U && byte <= 0xdfU) {
107 code_point_ = byte & 0x1fU;
108 minimum_ = 0x80U;
109 remaining_ = 1;
110 } else if (byte >= 0xe0U && byte <= 0xefU) {
111 code_point_ = byte & 0x0fU;
112 minimum_ = 0x800U;
113 remaining_ = 2;
114 } else if (byte >= 0xf0U && byte <= 0xf4U) {
115 code_point_ = byte & 0x07U;
116 minimum_ = 0x10000U;
117 remaining_ = 3;
118 } else {
119 valid_ = false;
120 return;
121 }
122 continue;
123 }
124 if ((byte & 0xc0U) != 0x80U) {
125 valid_ = false;
126 return;
127 }
128 code_point_ = (code_point_ << 6U) | (byte & 0x3fU);
129 --remaining_;
130 if (remaining_ == 0 &&
131 (code_point_ < minimum_ || code_point_ > 0x10ffffU ||
132 (code_point_ >= 0xd800U && code_point_ <= 0xdfffU))) {
133 valid_ = false;
134 return;
135 }
136 }
137 }
138
140 bool valid() const { return valid_ && remaining_ == 0; }
141
142 private:
143 bool reject_nul_;
144 bool valid_ = true;
145 std::uint32_t code_point_ = 0;
146 std::uint32_t minimum_ = 0;
147 std::uint8_t remaining_ = 0;
148};
149
150inline bool IsUtf8(std::span<const std::byte> bytes, bool reject_nul) {
151 Utf8Validator validator(reject_nul);
152 validator.Consume(bytes);
153 return validator.valid();
154}
155
156inline bool IsUtf8(std::string_view value, bool reject_nul) {
157 return IsUtf8(std::as_bytes(std::span(value.data(), value.size())),
158 reject_nul);
159}
160
161inline std::size_t CheckedSize(std::uint64_t value) {
162 if (value > std::numeric_limits<std::size_t>::max()) {
163 throw std::length_error("File-archive size does not fit in size_t");
164 }
165 return static_cast<std::size_t>(value);
166}
167
168inline void RequireZeroBytes(std::span<const std::byte> bytes,
169 std::size_t offset) {
170 for (const std::byte byte : bytes) {
171 if (byte != std::byte{0}) {
172 throw SerializationError("Non-zero file-archive padding", offset);
173 }
174 ++offset;
175 }
176}
177
178} // namespace file_archive_detail
179
190template <class Impl>
192 public:
194 static constexpr std::uint32_t format_version() {
195 return file_archive_detail::kVersion;
196 }
197
199 std::size_t size() const { return impl().records_impl().size(); }
200
202 bool empty() const { return size() == 0; }
203
205 std::size_t logical_size_bytes() const { return impl().logical_size_impl(); }
206
208 WaveletTreeBuildType build_type() const { return impl().build_type_impl(); }
209
211 std::size_t file_table_bytes() const {
212 return size() * file_archive_detail::kRecordBytes;
213 }
214
216 std::size_t path_storage_bytes() const {
217 return impl().path_storage_size_impl();
218 }
219
221 std::size_t metadata_bytes() const {
222 const std::size_t unaligned = file_archive_detail::kMetadataHeaderBytes +
224 return unaligned +
225 (alignof(std::uint64_t) - unaligned % alignof(std::uint64_t)) %
226 alignof(std::uint64_t);
227 }
228
233 std::optional<std::size_t> find(std::string_view path) const {
234 const auto records = impl().records_impl();
235 const auto position =
236 std::lower_bound(records.begin(), records.end(), path,
237 [this](const file_archive_detail::FileRecord& record,
238 std::string_view wanted) {
239 return impl().path_impl(record) < wanted;
240 });
241 if (position == records.end() || impl().path_impl(*position) != path) {
242 return std::nullopt;
243 }
244 return static_cast<std::size_t>(position - records.begin());
245 }
246
251 FileArchiveEntry entry(std::size_t index) const {
252 const auto records = impl().records_impl();
253 if (index >= records.size()) {
254 throw std::out_of_range("File-archive entry index is out of range");
255 }
256 const auto& record = records[index];
257 return {.path = impl().path_impl(record),
258 .content_offset = record.content_offset,
259 .content_size = record.content_size,
260 .newline_rank_base = record.newline_rank_base,
261 .line_count = record.line_count,
262 .mode = record.mode,
263 .type = record.type,
264 .is_text = record.is_text};
265 }
266
271 std::vector<std::byte> extract(std::size_t index) const {
272 const FileArchiveEntry metadata = entry(index);
273 return extract_range(metadata.content_offset,
274 metadata.content_offset + metadata.content_size);
275 }
276
284 std::vector<std::byte> extract_lines(std::size_t index,
285 std::size_t left,
286 std::size_t right) const {
287 const FileArchiveEntry metadata = entry(index);
288 if (metadata.type != FileArchiveEntryType::kRegular) {
289 throw std::invalid_argument("Line extraction requires a regular file");
290 }
291 if (!metadata.is_text) {
292 throw std::invalid_argument("Line extraction requires a text file");
293 }
294 if (left > right || right > metadata.line_count) {
295 throw std::out_of_range("File-archive line range is out of range");
296 }
297 const auto& tree = impl().tree_impl();
298 const auto boundary = [&](std::size_t line) {
299 if (line == 0) {
300 return metadata.content_offset;
301 }
302 if (line == metadata.line_count) {
303 return metadata.content_offset + metadata.content_size;
304 }
305 return tree.select('\n', metadata.newline_rank_base + line) + 1;
306 };
307 const std::size_t begin = boundary(left);
308 const std::size_t end = left == right ? begin : boundary(right);
309 return extract_range(begin, end);
310 }
311
312 private:
313 const Impl& impl() const { return static_cast<const Impl&>(*this); }
314
315 std::vector<std::byte> extract_range(std::size_t begin,
316 std::size_t end) const {
317 const std::vector<std::uint8_t> symbols =
318 impl().tree_impl().get_segment(begin, end);
319 std::vector<std::byte> bytes;
320 bytes.reserve(symbols.size());
321 for (const std::uint8_t symbol : symbols) {
322 bytes.push_back(static_cast<std::byte>(symbol));
323 }
324 return bytes;
325 }
326};
327
332template <StorageImplementation Storage>
333class FileArchiveIndex : public FileArchiveBase<FileArchiveIndex<Storage>>,
334 public SerializationBase<FileArchiveIndex<Storage>> {
335 public:
336 FileArchiveIndex()
337 requires(std::same_as<Storage, ReadOnlyStorageView>)
338 = default;
339
346 FileArchiveIndex(const FileArchiveIndex&)
347 requires(std::same_as<Storage, ReadOnlyStorageView>)
348 = default;
349 FileArchiveIndex& operator=(const FileArchiveIndex&)
350 requires(std::same_as<Storage, ReadOnlyStorageView>)
351 = default;
352 FileArchiveIndex(const FileArchiveIndex&)
353 requires(std::same_as<Storage, AlignedStorage>)
354 = delete;
355 FileArchiveIndex& operator=(const FileArchiveIndex&)
356 requires(std::same_as<Storage, AlignedStorage>)
357 = delete;
358 FileArchiveIndex(FileArchiveIndex&&) noexcept = default;
359 FileArchiveIndex& operator=(FileArchiveIndex&&) noexcept = default;
360
366 explicit FileArchiveIndex(
367 std::vector<FileArchiveSource> sources,
369 requires(std::same_as<Storage, AlignedStorage>)
370 : build_type_(build_type) {
371 build_sources(sources, [](const FileArchiveSource& source, auto&& consume) {
372 consume(std::span<const std::byte>(source.content));
373 });
374 }
375
387 template <class ReadSource>
389 std::vector<FileArchiveSourceMetadata> sources,
390 ReadSource&& read_source,
391 WaveletTreeBuildType build_type = WaveletTreeBuildType::Huffman)
392 requires(std::same_as<Storage, AlignedStorage>)
393 : build_type_(build_type) {
394 build_sources(sources, read_source);
395 }
396
398 void serialize_impl(BinaryWriter& writer) const {
399 if (writer.size_bytes() % alignof(std::uint64_t) != 0) {
400 throw std::invalid_argument(
401 "File-archive serialization requires an aligned writer offset");
402 }
403 const std::size_t begin = writer.size_bytes();
404 detail::write_magic(writer, file_archive_detail::kMagic);
405 writer.write_u32(file_archive_detail::kVersion);
406 writer.write_u32(0);
407 const std::size_t size_position = writer.write_u64_placeholder();
408 writer.write_u32(static_cast<std::uint32_t>(build_type_));
409 writer.write_u32(0);
410 writer.write_size(records_.size());
411 writer.write_size(logical_size_);
412 writer.write_size(records_.size() * file_archive_detail::kRecordBytes);
413 writer.write_size(paths_.size_bytes());
414 writer.write_size(0);
415 for (const auto& record : records_) {
416 writer.write_size(record.path_offset);
417 writer.write_size(record.path_size);
418 writer.write_size(record.content_offset);
419 writer.write_size(record.content_size);
420 writer.write_size(record.newline_rank_base);
421 writer.write_size(record.line_count);
422 writer.write_u32(record.mode);
423 writer.write_u8(static_cast<std::uint8_t>(record.type));
424 writer.write_u8(static_cast<std::uint8_t>(record.is_text));
425 writer.write_u16(0);
426 }
427 writer.write_bytes(paths_.as_bytes());
428 writer.align_to(alignof(std::uint64_t));
429 if (!tree_.has_value()) {
430 throw std::logic_error("Cannot serialize an uninitialized file archive");
431 }
432 tree_->serialize(writer);
433 writer.align_to(alignof(std::uint64_t));
434 writer.patch_u64(size_position,
435 static_cast<std::uint64_t>(writer.size_bytes() - begin));
436 }
437
443 static FileArchiveIndex deserialize_impl(
444 BinaryReader& reader,
446 requires(std::same_as<Storage, AlignedStorage> ||
447 std::same_as<Storage, ReadOnlyStorageView>)
448 {
449 if constexpr (std::same_as<Storage, ReadOnlyStorageView>) {
450 if (reinterpret_cast<std::uintptr_t>(reader.remaining_bytes().data()) %
451 alignof(std::uint64_t) !=
452 0) {
453 throw std::invalid_argument(
454 "Serialized file archive is not word aligned");
455 }
456 }
457 const std::size_t available = reader.remaining();
458 detail::require_magic(reader, file_archive_detail::kMagic);
459 if (reader.read_u32() != file_archive_detail::kVersion ||
460 reader.read_u32() != 0) {
461 throw std::invalid_argument("Incompatible serialized file archive");
462 }
463 const std::size_t artifact_size = detail::checked_artifact_size(
464 reader.read_u64(), file_archive_detail::kHeaderBytes, available);
465 BinaryReader payload = reader.read_subreader(
466 artifact_size - file_archive_detail::kHeaderBytes);
467
468 FileArchiveIndex result(LoadTag{});
469 const std::uint32_t build_type = payload.read_u32();
470 if (build_type >
471 static_cast<std::uint32_t>(WaveletTreeBuildType::Huffman)) {
472 throw std::invalid_argument("Invalid file-archive build type");
473 }
474 result.build_type_ = static_cast<WaveletTreeBuildType>(build_type);
475 if (payload.read_u32() != 0) {
476 throw std::invalid_argument("Invalid file-archive reserved field");
477 }
478 const std::size_t count = payload.read_size();
479 result.logical_size_ = payload.read_size();
480 const std::size_t records_bytes = payload.read_size();
481 const std::size_t paths_bytes = payload.read_size();
482 if (payload.read_size() != 0 ||
483 count > std::numeric_limits<std::size_t>::max() /
484 file_archive_detail::kRecordBytes ||
485 records_bytes != count * file_archive_detail::kRecordBytes) {
486 throw std::invalid_argument("Invalid file-archive section sizes");
487 }
488
489 BinaryReader records_reader = payload.read_subreader(records_bytes);
490 result.records_.reserve(count);
491 for (std::size_t index = 0; index < count; ++index) {
493 record.path_offset = records_reader.read_size();
494 record.path_size = records_reader.read_size();
495 record.content_offset = records_reader.read_size();
496 record.content_size = records_reader.read_size();
497 record.newline_rank_base = records_reader.read_size();
498 record.line_count = records_reader.read_size();
499 record.mode = records_reader.read_u32();
500 const std::uint8_t type = records_reader.read_u8();
501 const std::uint8_t is_text = records_reader.read_u8();
502 if (type > static_cast<std::uint8_t>(FileArchiveEntryType::kSymlink) ||
503 is_text > 1 || records_reader.read_u16() != 0) {
504 throw std::invalid_argument("Invalid file-archive entry flags");
505 }
506 record.type = static_cast<FileArchiveEntryType>(type);
507 record.is_text = is_text != 0;
508 result.records_.push_back(record);
509 }
510 if (!records_reader.empty()) {
511 throw std::invalid_argument("Invalid file-archive record section");
512 }
513 result.paths_ = MakeStorage(payload.read_bytes(paths_bytes));
514 const std::size_t padding =
515 (alignof(std::uint64_t) -
516 payload.byte_offset() % alignof(std::uint64_t)) %
517 alignof(std::uint64_t);
518 const std::size_t padding_offset = payload.byte_offset();
519 file_archive_detail::RequireZeroBytes(payload.read_bytes(padding),
520 padding_offset);
522 payload, validation));
523 payload.require_zero_padding(alignof(std::uint64_t) - 1);
524 result.validate(validation == DeserializationValidation::kFull);
525 return result;
526 }
527
528 private:
529 struct LoadTag {};
530
531 explicit FileArchiveIndex(LoadTag) {}
532
533 static Storage MakeStorage(std::span<const std::byte> bytes) {
534 if constexpr (std::same_as<Storage, ReadOnlyStorageView>) {
535 return ReadOnlyStorageView(bytes);
536 } else {
537 if (bytes.size() > std::numeric_limits<std::size_t>::max() / 8) {
538 throw std::length_error("File-archive path storage is too large");
539 }
540 AlignedStorage result(bytes.size() * 8);
541 std::ranges::copy(bytes, result.writable_bytes().begin());
542 return result;
543 }
544 }
545
546 template <class Source, class ReadSource>
547 void build_sources(std::vector<Source>& sources, ReadSource&& read_source) {
548 std::sort(sources.begin(), sources.end(),
549 [](const auto& left, const auto& right) {
550 return left.path < right.path;
551 });
552 detail::ByteHistogram histogram;
553 std::vector<std::uint64_t> content_hashes;
554 content_hashes.reserve(sources.size());
555 std::string paths;
556 std::size_t newline_rank = 0;
557 for (std::size_t source_index = 0; source_index < sources.size();
558 ++source_index) {
559 const Source& source = sources[source_index];
560 if (source.path.empty() ||
561 !file_archive_detail::IsUtf8(source.path, true)) {
562 throw std::invalid_argument(
563 "File-archive paths must be non-empty UTF-8 strings");
564 }
565 if (source_index != 0 && sources[source_index - 1].path == source.path) {
566 throw std::invalid_argument("File-archive paths must be unique");
567 }
568 if (source.type != FileArchiveEntryType::kRegular &&
569 source.type != FileArchiveEntryType::kSymlink) {
570 throw std::invalid_argument("Invalid file-archive entry type");
571 }
572
574 record.path_offset = paths.size();
575 record.path_size = source.path.size();
576 record.content_offset = logical_size_;
577 record.newline_rank_base = newline_rank;
578 record.mode = source.mode;
579 record.type = source.type;
580 paths.append(source.path);
581
582 file_archive_detail::Utf8Validator utf8(/*reject_nul=*/true);
583 std::size_t newlines = 0;
584 bool has_content = false;
585 std::uint8_t last_byte = 0;
586 std::uint64_t content_hash = 14695981039346656037ULL;
587 read_source(source, [&](std::span<const std::byte> chunk) {
588 if (chunk.size() >
589 std::numeric_limits<std::size_t>::max() - logical_size_) {
590 throw std::length_error("File-archive content is too large");
591 }
592 logical_size_ += chunk.size();
593 utf8.Consume(chunk);
594 histogram.add(chunk);
595 for (const std::byte byte : chunk) {
596 const std::uint8_t value = std::to_integer<std::uint8_t>(byte);
597 newlines += value == '\n' ? 1U : 0U;
598 has_content = true;
599 last_byte = value;
600 content_hash ^= value;
601 content_hash *= 1099511628211ULL;
602 }
603 });
604 record.content_size = logical_size_ - record.content_offset;
605 record.is_text = utf8.valid();
606 if (source.type == FileArchiveEntryType::kRegular && has_content) {
607 record.line_count =
608 newlines + static_cast<std::size_t>(last_byte != '\n');
609 }
610 newline_rank += newlines;
611 records_.push_back(record);
612 content_hashes.push_back(content_hash);
613 }
614
615 paths_ = MakeStorage(
616 std::as_bytes(std::span<const char>(paths.data(), paths.size())));
617
618 const auto symbol_counts = histogram.counts();
619
620 tree_.emplace(
621 file_archive_detail::kByteAlphabetSize, symbol_counts,
622 [&](auto&& emit) {
623 for (std::size_t index = 0; index < sources.size(); ++index) {
624 std::size_t content_size = 0;
625 std::uint64_t content_hash = 14695981039346656037ULL;
626 read_source(sources[index], [&](std::span<const std::byte> chunk) {
627 if (content_size > records_[index].content_size ||
628 chunk.size() > records_[index].content_size - content_size) {
629 throw std::invalid_argument(
630 "File-archive source changed between build passes");
631 }
632 content_size += chunk.size();
633 const auto symbols = std::span<const std::uint8_t>(
634 reinterpret_cast<const std::uint8_t*>(chunk.data()),
635 chunk.size());
636 for (const std::byte byte : chunk) {
637 const std::uint8_t value = std::to_integer<std::uint8_t>(byte);
638 content_hash ^= value;
639 content_hash *= 1099511628211ULL;
640 }
641 emit(symbols);
642 });
643 if (content_size != records_[index].content_size ||
644 content_hash != content_hashes[index]) {
645 throw std::invalid_argument(
646 "File-archive source changed between build passes");
647 }
648 }
649 },
650 build_type_);
651 }
652
654 friend class FileArchiveBase<FileArchiveIndex>;
655
656 void validate(bool full) const {
657 if (tree_->size() != logical_size_) {
658 throw std::invalid_argument("Invalid file-archive content size");
659 }
660 const std::size_t newline_count = tree_->rank('\n', logical_size_);
661 std::size_t expected_content_offset = 0;
662 std::size_t expected_newline_rank = 0;
663 std::string_view previous_path;
664 for (std::size_t index = 0; index < records_.size(); ++index) {
665 const auto& record = records_[index];
666 if (record.path_offset > paths_.size_bytes() ||
667 record.path_size > paths_.size_bytes() - record.path_offset ||
668 record.content_offset > logical_size_ ||
669 record.content_size > logical_size_ - record.content_offset ||
670 record.newline_rank_base > newline_count ||
671 record.line_count > newline_count - record.newline_rank_base + 1 ||
672 (record.type == FileArchiveEntryType::kSymlink &&
673 record.line_count != 0)) {
674 throw std::invalid_argument("Invalid file-archive entry bounds");
675 }
676 if (!full) {
677 continue;
678 }
679 const std::string_view path = path_impl(record);
680 if (path.empty() || !file_archive_detail::IsUtf8(path, true) ||
681 (index != 0 && previous_path >= path) ||
682 record.content_offset != expected_content_offset ||
683 record.newline_rank_base != expected_newline_rank) {
684 throw std::invalid_argument("Invalid file-archive entry metadata");
685 }
686 const std::vector<std::byte> content = this->extract(index);
687 const bool is_text = file_archive_detail::IsUtf8(content, true);
688 std::size_t newlines = 0;
689 for (const std::byte byte : content) {
690 newlines += byte == std::byte{'\n'} ? 1U : 0U;
691 }
692 std::size_t line_count = 0;
693 if (record.type == FileArchiveEntryType::kRegular && !content.empty()) {
694 line_count = newlines + static_cast<std::size_t>(content.back() !=
695 std::byte{'\n'});
696 }
697 if (record.is_text != is_text || record.line_count != line_count) {
698 throw std::invalid_argument("Invalid file-archive derived metadata");
699 }
700 previous_path = path;
701 expected_content_offset += content.size();
702 expected_newline_rank += newlines;
703 }
704 if (full && expected_content_offset != logical_size_) {
705 throw std::invalid_argument("Invalid file-archive content layout");
706 }
707 }
708
710 std::span<const file_archive_detail::FileRecord> records_impl() const {
711 return records_;
712 }
714 std::string_view path_impl(
715 const file_archive_detail::FileRecord& record) const {
716 const std::span<const std::byte> bytes = paths_.as_bytes();
717 const std::string_view paths =
718 bytes.empty()
719 ? std::string_view{}
720 : std::string_view(reinterpret_cast<const char*>(bytes.data()),
721 bytes.size());
722 return paths.substr(record.path_offset, record.path_size);
723 }
725 const WaveletTreeIndex<std::uint8_t, Storage>& tree_impl() const {
726 return *tree_;
727 }
729 std::size_t logical_size_impl() const { return logical_size_; }
731 WaveletTreeBuildType build_type_impl() const { return build_type_; }
733 std::size_t path_storage_size_impl() const { return paths_.size_bytes(); }
734
735 std::vector<file_archive_detail::FileRecord> records_;
736 Storage paths_;
737 std::optional<WaveletTreeIndex<std::uint8_t, Storage>> tree_;
738 std::size_t logical_size_ = 0;
739 WaveletTreeBuildType build_type_ = WaveletTreeBuildType::Huffman;
740};
741
744
747
748} // namespace pixie
Dependency-reduced histogram construction for byte streams.
Owning storage with a logical byte size and 64-byte-aligned backing.
Definition aligned.h:41
Bounds-checked reader for canonical little-endian binary data.
Definition serialization.h:526
std::uint16_t read_u16()
Read an unsigned little-endian 16-bit integer.
Definition serialization.h:555
std::uint8_t read_u8()
Read an unsigned eight-bit integer.
Definition serialization.h:552
BinaryReader read_subreader(std::size_t count)
Read a bounded region as an independent child reader.
Definition serialization.h:615
std::span< const std::byte > read_bytes(std::size_t count)
Read exactly count uninterpreted bytes.
Definition serialization.h:604
void require_zero_padding(std::size_t maximum)
Consume at most maximum trailing zero-padding bytes.
Definition serialization.h:633
std::uint32_t read_u32()
Read an unsigned little-endian 32-bit integer.
Definition serialization.h:558
bool empty() const noexcept
Return whether all input bytes have been consumed.
Definition serialization.h:532
std::size_t byte_offset() const noexcept
Return the current byte offset in the outermost input.
Definition serialization.h:541
std::size_t read_size()
Read an unsigned 64-bit size and convert it to size_t.
Definition serialization.h:587
Bounded-buffer writer for canonical little-endian binary data.
Definition serialization.h:198
void write_bytes(std::span< const std::byte > bytes)
Append bytes without interpretation.
Definition serialization.h:297
void write_u32(std::uint32_t value)
Write an unsigned 32-bit integer in little-endian order.
Definition serialization.h:258
void write_u8(std::uint8_t value)
Write an unsigned eight-bit integer.
Definition serialization.h:252
std::size_t size_bytes() const noexcept
Return the logical number of bytes written.
Definition serialization.h:244
void align_to(std::size_t alignment)
Pad with zero bytes to the next multiple of alignment.
Definition serialization.h:347
void write_u16(std::uint16_t value)
Write an unsigned 16-bit integer in little-endian order.
Definition serialization.h:255
void patch_u64(std::size_t position, std::uint64_t value)
Replace an existing 64-bit field with value.
Definition serialization.h:373
std::size_t write_u64_placeholder()
Write a zero 64-bit field and return its byte position.
Definition serialization.h:363
void write_size(std::size_t value)
Write a platform size as an unsigned 64-bit integer.
Definition serialization.h:287
CRTP facade for file-archive lookup and extraction.
Definition file_archive.h:191
std::size_t size() const
Return the number of archived entries.
Definition file_archive.h:199
std::size_t path_storage_bytes() const
Return serialized path-blob bytes.
Definition file_archive.h:216
std::optional< std::size_t > find(std::string_view path) const
Find an exact archive-relative path.
Definition file_archive.h:233
WaveletTreeBuildType build_type() const
Return the wavelet-tree construction strategy.
Definition file_archive.h:208
std::size_t metadata_bytes() const
Return framing, file-table, path, and alignment bytes.
Definition file_archive.h:221
FileArchiveEntry entry(std::size_t index) const
Return metadata for an entry.
Definition file_archive.h:251
std::vector< std::byte > extract_lines(std::size_t index, std::size_t left, std::size_t right) const
Reconstruct zero-based half-open line range [left, right).
Definition file_archive.h:284
std::size_t logical_size_bytes() const
Return the number of logical content bytes, excluding framing.
Definition file_archive.h:205
std::size_t file_table_bytes() const
Return serialized fixed-record bytes.
Definition file_archive.h:211
bool empty() const
Return whether the archive has no entries.
Definition file_archive.h:202
std::vector< std::byte > extract(std::size_t index) const
Reconstruct the complete byte content of one entry.
Definition file_archive.h:271
static constexpr std::uint32_t format_version()
Return the serialized file-archive format version.
Definition file_archive.h:194
Storage-backed byte-oriented file archive.
Definition file_archive.h:334
FileArchiveIndex(std::vector< FileArchiveSourceMetadata > sources, ReadSource &&read_source, WaveletTreeBuildType build_type=WaveletTreeBuildType::Huffman)
Construct from metadata and a replayable chunk reader.
Definition file_archive.h:388
FileArchiveIndex(const FileArchiveIndex &)=default
Owning archives are move-only; archive views are copyable.
static FileArchiveIndex deserialize_impl(BinaryReader &reader, DeserializationValidation validation=DeserializationValidation::kQuick)
Deserialize one framed archive using this specialization's storage.
Definition file_archive.h:443
void serialize_impl(BinaryWriter &writer) const
Serialize one native, framed Pixie file archive.
Definition file_archive.h:398
CRTP facade for optional binary serialization and deserialization.
Definition serialization.h:693
static WaveletTreeIndex< Symbol, AlignedStorage > deserialize(BinaryReader &reader, Context &&... context)
Definition serialization.h:710
Incremental 256-bin byte histogram with four update lanes.
Definition byte_histogram.h:16
std::array< std::size_t, 256 > counts() const
Return the combined symbol counts.
Definition byte_histogram.h:34
void add(std::span< const std::byte > bytes)
Add one contiguous chunk to the histogram.
Definition byte_histogram.h:19
Incrementally validate a byte stream as UTF-8.
Definition file_archive.h:86
void Consume(std::span< const std::byte > bytes)
Consume the next contiguous chunk of the byte stream.
Definition file_archive.h:92
bool valid() const
Return whether all consumed bytes form complete valid UTF-8.
Definition file_archive.h:140
Utf8Validator(bool reject_nul)
Construct a validator, optionally rejecting embedded NUL bytes.
Definition file_archive.h:89
FileArchiveEntryType
Kind of filesystem entry stored in a file archive.
Definition file_archive.h:29
FileArchiveIndex< ReadOnlyStorageView > FileArchiveView
Read-only file archive retaining serialized storage views.
Definition file_archive.h:746
FileArchiveIndex< AlignedStorage > FileArchive
Owning aligned-storage file archive.
Definition file_archive.h:743
DeserializationValidation
Validation strength used while restoring serialized indexes.
Definition serialization.h:28
@ kQuick
Check framing, dimensions, references, and other conditions needed for memory-safe terminating querie...
Definition serialization.h:33
@ kFull
Additionally authenticate all source-derived metadata against the supplied source contents.
Definition serialization.h:39
Public metadata for one archive entry.
Definition file_archive.h:50
Metadata for one replayable archive-construction source.
Definition file_archive.h:43
Source entry used to construct an owning file archive.
Definition file_archive.h:35
Definition file_archive.h:73
WaveletTreeBuildType
Construction strategy for a wavelet-tree implementation.
Definition wavelet_tree.h:19