lib/sql/src/history/segment/manifest.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const sql = @import("../../root.zig");
3 const segment = @import("root.zig");
4
5 const Hash = sql.Hash;
6 const Sha256 = std.crypto.hash.sha2.Sha256;
7
8 pub const root_bytes: usize = 128;
9 pub const page_bytes: usize = 4096;
10 pub const page_header_bytes: usize = 96;
11 pub const entry_bytes: usize = 208;
12 pub const page_trailer_bytes: usize = Sha256.digest_length;
13 pub const entries_per_page: usize =
14 (page_bytes - page_header_bytes - page_trailer_bytes) / entry_bytes;
15
16 pub const PageRange = struct {
17 pub const Count = u64;
18 pub const unit_precision_segments: Count = 1;
19 pub const maximum_segments_per_second: Count =
20 segment.SegmentRange.maximum_units_per_second;
21 pub const maximum_seconds_per_year: Count =
22 segment.SegmentRange.maximum_seconds_per_year;
23 pub const service_lifetime_years: Count =
24 segment.SegmentRange.service_lifetime_years;
25 pub const service_lifetime_seconds: Count =
26 segment.SegmentRange.service_lifetime_seconds;
27 pub const segments_per_page: Count = entries_per_page;
28 pub const maximum_increment_pages: Count = 1;
29 pub const budget_pages: Count = total(
30 maximum_segments_per_second,
31 service_lifetime_seconds,
32 ).?;
33
34 pub fn total(segments_per_second: Count, lifetime_seconds: Count) ?Count {
35 const segment_count = @as(u128, unit_precision_segments) *
36 @as(u128, segments_per_second) * @as(u128, lifetime_seconds);
37 const page_count = if (segment_count == 0) 0 else (segment_count - 1) / @as(u128, segments_per_page) + 1;
38 return std.math.cast(Count, page_count);
39 }
40
41 pub fn advance(current: Count, increment: Count) ?Count {
42 if (!containsCount(current) or increment > maximum_increment_pages) {
43 return null;
44 }
45 const next = std.math.add(Count, current, increment) catch return null;
46 return if (next <= budget_pages) next else null;
47 }
48
49 pub fn containsCount(count: Count) bool {
50 return count > 0 and count <= budget_pages;
51 }
52 };
53
54 comptime {
55 std.debug.assert(PageRange.budget_pages > 0);
56 std.debug.assert(
57 PageRange.budget_pages ==
58 (segment.SegmentRange.budget_segments - 1) /
59 PageRange.segments_per_page + 1,
60 );
61 std.debug.assert(
62 @as(u128, PageRange.budget_pages) +
63 @as(u128, PageRange.maximum_increment_pages) <=
64 std.math.maxInt(PageRange.Count),
65 );
66 }
67
68 const root_magic = "TINYMNRT".*;
69 const page_magic = "TINYMPAG".*;
70 const format_version: u32 = 1;
71
72 pub const Error = segment.Error || error{
73 InvalidManifest,
74 InvalidLifecycleTransition,
75 ManifestChanged,
76 };
77
78 pub const Tier = enum(u8) {
79 sealed = 1,
80 cold = 2,
81 };
82
83 pub const Entry = struct {
84 segment: Hash,
85 previous_segment: Hash,
86 stream_identity: Hash,
87 checkpoint_root: Hash,
88 cold_receipt: Hash = @splat(0),
89 epoch: u64,
90 ordinal: segment.SegmentRange.Count,
91 first_event: segment.EventRange.Count,
92 event_count: segment.EventRange.Count,
93 encoded_bytes: u64,
94 tier: Tier = .sealed,
95
96 pub fn fromVerified(value: segment.View) Entry {
97 return .{
98 .segment = value.digest,
99 .previous_segment = value.spec.previous_segment,
100 .stream_identity = value.spec.stream_identity,
101 .checkpoint_root = value.spec.checkpoint_root,
102 .epoch = value.spec.epoch,
103 .ordinal = value.spec.ordinal,
104 .first_event = value.spec.first_event,
105 .event_count = value.spec.event_count,
106 .encoded_bytes = value.bytes.len,
107 };
108 }
109
110 pub fn localReleaseAllowed(self: Entry) bool {
111 return self.tier == .cold and !isZero(self.cold_receipt);
112 }
113 };
114
115 pub const Root = struct {
116 digest: Hash,
117 tail_page: Hash,
118 last_segment: Hash,
119 segment_count: segment.SegmentRange.Count,
120 page_count: PageRange.Count,
121 tail_entries: u32,
122
123 pub fn demand(self: Root) Error!ReplayDemand {
124 return .{
125 .manifest_pages = self.page_count,
126 .segments = self.segment_count,
127 .io_operations = std.math.add(
128 u64,
129 self.page_count,
130 self.segment_count,
131 ) catch return error.InvalidManifest,
132 };
133 }
134 };
135
136 pub const ReplayDemand = struct {
137 manifest_pages: u64,
138 segments: u64,
139 sealed_segments: u64 = 0,
140 cold_segments: u64 = 0,
141 event_count: u64 = 0,
142 segment_bytes: u64 = 0,
143 max_segment_bytes: u64 = 0,
144 cold_fetch_count: u64 = 0,
145 cold_bytes: u64 = 0,
146 io_operations: u64,
147 };
148
149 pub const Page = struct {
150 bytes: []const u8,
151 digest: Hash,
152 previous_page: Hash,
153 first_ordinal: segment.SegmentRange.Count,
154 count: u32,
155
156 pub fn entry(self: Page, index: usize) Error!Entry {
157 if (index >= self.count) return error.InvalidManifest;
158 const start = page_header_bytes + index * entry_bytes;
159 return try readEntry(self.bytes[start..][0..entry_bytes]);
160 }
161
162 pub fn latest(self: Page) Error!Entry {
163 if (self.count == 0) return error.InvalidManifest;
164 return try self.entry(self.count - 1);
165 }
166 };
167
168 pub const Publication = struct {
169 root: Root,
170 page: Page,
171 };
172
173 pub const Reader = struct {
174 expected_page: Hash,
175 expected_last_segment: Hash,
176 pages_remaining: PageRange.Count,
177 segments_remaining: segment.SegmentRange.Count,
178 first: bool = true,
179 tail_entries: u32,
180 replay_demand: ReplayDemand,
181
182 pub fn init(root: Root) Reader {
183 std.debug.assert(root.page_count != 0);
184 std.debug.assert(root.segment_count != 0);
185 return .{
186 .expected_page = root.tail_page,
187 .expected_last_segment = root.last_segment,
188 .pages_remaining = root.page_count,
189 .segments_remaining = root.segment_count,
190 .tail_entries = root.tail_entries,
191 .replay_demand = .{
192 .manifest_pages = root.page_count,
193 .segments = 0,
194 .io_operations = root.page_count,
195 },
196 };
197 }
198
199 pub fn accept(self: *Reader, bytes: []const u8) Error!Page {
200 if (self.pages_remaining == 0) return error.InvalidManifest;
201 const page = try verifyPage(bytes);
202 if (!same(page.digest, self.expected_page)) return error.ManifestChanged;
203 if (self.first and page.count != self.tail_entries) {
204 return error.InvalidManifest;
205 }
206 if (page.count > self.segments_remaining) return error.InvalidManifest;
207 const latest = try page.latest();
208 if (!same(latest.segment, self.expected_last_segment)) {
209 return error.InvalidManifest;
210 }
211 const ordinal_count = segment.SegmentRange.advance(latest.ordinal) orelse
212 return error.InvalidManifest;
213 if (ordinal_count != self.segments_remaining) {
214 return error.InvalidManifest;
215 }
216 const oldest = try page.entry(0);
217 var next_demand = self.replay_demand;
218 for (0..page.count) |index| {
219 try addDemand(&next_demand, try page.entry(index));
220 }
221 self.segments_remaining -= page.count;
222 self.pages_remaining -= 1;
223 self.expected_page = page.previous_page;
224 self.expected_last_segment = oldest.previous_segment;
225 self.first = false;
226 self.replay_demand = next_demand;
227 if ((self.pages_remaining == 0) != isZero(self.expected_page)) {
228 return error.InvalidManifest;
229 }
230 if (self.pages_remaining == 0 and self.segments_remaining != 0) {
231 return error.InvalidManifest;
232 }
233 if (self.pages_remaining == 0 and
234 !isZero(self.expected_last_segment))
235 {
236 return error.InvalidManifest;
237 }
238 return page;
239 }
240
241 pub fn done(self: Reader) bool {
242 return self.pages_remaining == 0 and self.segments_remaining == 0;
243 }
244
245 pub fn demand(self: Reader) Error!ReplayDemand {
246 if (!self.done()) return error.InvalidManifest;
247 return self.replay_demand;
248 }
249 };
250
251 pub fn append(
252 current_root_bytes: ?[]const u8,
253 current_tail_bytes: ?[]const u8,
254 entry_value: Entry,
255 page_out: *[page_bytes]u8,
256 root_out: *[root_bytes]u8,
257 ) Error!Publication {
258 if (entry_value.tier != .sealed or !isZero(entry_value.cold_receipt)) {
259 return error.InvalidLifecycleTransition;
260 }
261 if (!entryWithinRanges(entry_value)) return error.InvalidManifest;
262 const current = try validateCurrent(current_root_bytes, current_tail_bytes);
263 if (current) |state| {
264 if (entry_value.ordinal != state.root.segment_count or
265 !same(entry_value.previous_segment, state.root.last_segment))
266 {
267 return error.InvalidManifest;
268 }
269 const page_count = PageRange.advance(
270 state.root.page_count,
271 @intFromBool(state.page.count == entries_per_page),
272 ) orelse return error.InvalidManifest;
273 const segment_count = segment.SegmentRange.advance(
274 state.root.segment_count,
275 ) orelse return error.InvalidManifest;
276 if (state.page.count < entries_per_page) {
277 copyPageAndAppend(state.page, entry_value, page_out);
278 } else {
279 writePage(page_out, state.page.digest, entry_value);
280 }
281 const page = try verifyPage(page_out);
282 writeRoot(root_out, .{
283 .tail_page = page.digest,
284 .last_segment = entry_value.segment,
285 .segment_count = segment_count,
286 .page_count = page_count,
287 .tail_entries = page.count,
288 });
289 return .{ .root = try verifyRoot(root_out), .page = page };
290 }
291 if (entry_value.ordinal != 0 or !isZero(entry_value.previous_segment)) {
292 return error.InvalidManifest;
293 }
294 writePage(page_out, @splat(0), entry_value);
295 const page = try verifyPage(page_out);
296 writeRoot(root_out, .{
297 .tail_page = page.digest,
298 .last_segment = entry_value.segment,
299 .segment_count = 1,
300 .page_count = 1,
301 .tail_entries = 1,
302 });
303 return .{ .root = try verifyRoot(root_out), .page = page };
304 }
305
306 pub fn promoteTailCold(
307 current_root_bytes: []const u8,
308 current_tail_bytes: []const u8,
309 copied_segment: []const u8,
310 page_out: *[page_bytes]u8,
311 root_out: *[root_bytes]u8,
312 ) Error!Publication {
313 const state = (try validateCurrent(
314 current_root_bytes,
315 current_tail_bytes,
316 )) orelse return error.InvalidManifest;
317 var latest = try state.page.latest();
318 if (latest.tier != .sealed or !isZero(latest.cold_receipt)) {
319 return error.InvalidLifecycleTransition;
320 }
321 const verified = try segment.verify(copied_segment);
322 if (!same(verified.digest, latest.segment) or
323 copied_segment.len != latest.encoded_bytes)
324 {
325 return error.InvalidLifecycleTransition;
326 }
327 latest.tier = .cold;
328 latest.cold_receipt = coldReceipt(latest.segment, copied_segment.len);
329 page_out.* = state.page.bytes[0..page_bytes].*;
330 const latest_index: usize = state.page.count - 1;
331 const start = page_header_bytes + latest_index * entry_bytes;
332 writeEntry(page_out[start..][0..entry_bytes], latest);
333 finishPage(page_out, state.page.count);
334 const page = try verifyPage(page_out);
335 writeRoot(root_out, .{
336 .tail_page = page.digest,
337 .last_segment = state.root.last_segment,
338 .segment_count = state.root.segment_count,
339 .page_count = state.root.page_count,
340 .tail_entries = state.root.tail_entries,
341 });
342 return .{ .root = try verifyRoot(root_out), .page = page };
343 }
344
345 pub fn verifyRoot(bytes: []const u8) Error!Root {
346 if (bytes.len != root_bytes or !std.mem.eql(u8, bytes[0..8], &root_magic)) {
347 return error.InvalidManifest;
348 }
349 if (std.mem.readInt(u32, bytes[8..12], .little) != format_version) {
350 return error.InvalidManifest;
351 }
352 const root_checksum = digest(bytes[0 .. root_bytes - Sha256.digest_length]);
353 if (!std.mem.eql(u8, &root_checksum, bytes[root_bytes - Sha256.digest_length ..])) {
354 return error.InvalidManifest;
355 }
356 const root_digest = digest(bytes);
357 const segment_count = std.mem.readInt(u64, bytes[16..24], .little);
358 const page_count = std.mem.readInt(u64, bytes[24..32], .little);
359 const tail_entries = std.mem.readInt(u32, bytes[12..16], .little);
360 if (!segment.SegmentRange.containsCount(segment_count) or
361 !PageRange.containsCount(page_count) or tail_entries == 0 or
362 tail_entries > entries_per_page or page_count > segment_count)
363 {
364 return error.InvalidManifest;
365 }
366 const page_width: u64 = entries_per_page;
367 const expected_page_count = (segment_count - 1) / page_width + 1;
368 const expected_tail_entries = (segment_count - 1) % page_width + 1;
369 if (page_count != expected_page_count or
370 @as(u64, tail_entries) != expected_tail_entries)
371 {
372 return error.InvalidManifest;
373 }
374 return .{
375 .digest = root_digest,
376 .tail_page = bytes[32..64].*,
377 .last_segment = bytes[64..96].*,
378 .segment_count = segment_count,
379 .page_count = page_count,
380 .tail_entries = tail_entries,
381 };
382 }
383
384 pub fn verifyPage(bytes: []const u8) Error!Page {
385 if (bytes.len != page_bytes or !std.mem.eql(u8, bytes[0..8], &page_magic)) {
386 return error.InvalidManifest;
387 }
388 if (std.mem.readInt(u32, bytes[8..12], .little) != format_version) {
389 return error.InvalidManifest;
390 }
391 const count = std.mem.readInt(u32, bytes[12..16], .little);
392 if (count == 0 or count > entries_per_page) return error.InvalidManifest;
393 const used_end = page_header_bytes + count * entry_bytes;
394 if (!allZero(bytes[used_end .. page_bytes - page_trailer_bytes])) {
395 return error.InvalidManifest;
396 }
397 if (!std.mem.eql(u8, bytes[64..96], &digest(bytes[page_header_bytes..used_end]))) {
398 return error.InvalidManifest;
399 }
400 const page_checksum = digest(bytes[0 .. page_bytes - page_trailer_bytes]);
401 if (!std.mem.eql(u8, &page_checksum, bytes[page_bytes - page_trailer_bytes ..])) {
402 return error.InvalidManifest;
403 }
404 const page_digest = digest(bytes);
405 const page = Page{
406 .bytes = bytes,
407 .digest = page_digest,
408 .previous_page = bytes[32..64].*,
409 .first_ordinal = std.mem.readInt(u64, bytes[16..24], .little),
410 .count = count,
411 };
412 try verifyEntries(page);
413 return page;
414 }
415
416 const Current = struct {
417 root: Root,
418 page: Page,
419 };
420
421 const RootFields = struct {
422 tail_page: Hash,
423 last_segment: Hash,
424 segment_count: segment.SegmentRange.Count,
425 page_count: PageRange.Count,
426 tail_entries: u32,
427 };
428
429 fn validateCurrent(
430 root_value: ?[]const u8,
431 page_value: ?[]const u8,
432 ) Error!?Current {
433 if ((root_value == null) != (page_value == null)) return error.InvalidManifest;
434 const root_bytes_value = root_value orelse return null;
435 const page_bytes_value = page_value.?;
436 const root = try verifyRoot(root_bytes_value);
437 const page = try verifyPage(page_bytes_value);
438 if (!same(root.tail_page, page.digest) or root.tail_entries != page.count) {
439 return error.ManifestChanged;
440 }
441 const latest = try page.latest();
442 const segment_count = segment.SegmentRange.advance(latest.ordinal) orelse
443 return error.InvalidManifest;
444 if (!same(root.last_segment, latest.segment) or
445 root.segment_count != segment_count)
446 {
447 return error.InvalidManifest;
448 }
449 return .{ .root = root, .page = page };
450 }
451
452 fn verifyEntries(page: Page) Error!void {
453 var previous: ?Entry = null;
454 for (0..page.count) |index| {
455 const entry_value = try page.entry(index);
456 if (!entryWithinRanges(entry_value)) return error.InvalidManifest;
457 const expected_ordinal = std.math.add(
458 segment.SegmentRange.Count,
459 page.first_ordinal,
460 @intCast(index),
461 ) catch return error.InvalidManifest;
462 if (entry_value.ordinal != expected_ordinal) {
463 return error.InvalidManifest;
464 }
465 if (entry_value.event_count == 0 or entry_value.encoded_bytes == 0 or
466 entry_value.encoded_bytes > segment.encoded_bytes_max or
467 isZero(entry_value.segment) or isZero(entry_value.stream_identity))
468 {
469 return error.InvalidManifest;
470 }
471 if (previous) |prior| {
472 if (!same(entry_value.previous_segment, prior.segment)) {
473 return error.InvalidManifest;
474 }
475 }
476 switch (entry_value.tier) {
477 .sealed => if (!isZero(entry_value.cold_receipt)) {
478 return error.InvalidManifest;
479 },
480 .cold => if (isZero(entry_value.cold_receipt)) {
481 return error.InvalidManifest;
482 },
483 }
484 previous = entry_value;
485 }
486 }
487
488 fn addDemand(demand: *ReplayDemand, entry_value: Entry) Error!void {
489 demand.segments = std.math.add(u64, demand.segments, 1) catch
490 return error.InvalidManifest;
491 demand.event_count = std.math.add(
492 u64,
493 demand.event_count,
494 entry_value.event_count,
495 ) catch return error.InvalidManifest;
496 demand.segment_bytes = std.math.add(
497 u64,
498 demand.segment_bytes,
499 entry_value.encoded_bytes,
500 ) catch return error.InvalidManifest;
501 demand.max_segment_bytes = @max(
502 demand.max_segment_bytes,
503 entry_value.encoded_bytes,
504 );
505 demand.io_operations = std.math.add(u64, demand.io_operations, 1) catch
506 return error.InvalidManifest;
507 switch (entry_value.tier) {
508 .sealed => demand.sealed_segments = std.math.add(
509 u64,
510 demand.sealed_segments,
511 1,
512 ) catch return error.InvalidManifest,
513 .cold => {
514 demand.cold_segments = std.math.add(
515 u64,
516 demand.cold_segments,
517 1,
518 ) catch return error.InvalidManifest;
519 demand.cold_fetch_count = std.math.add(
520 u64,
521 demand.cold_fetch_count,
522 1,
523 ) catch return error.InvalidManifest;
524 demand.cold_bytes = std.math.add(
525 u64,
526 demand.cold_bytes,
527 entry_value.encoded_bytes,
528 ) catch return error.InvalidManifest;
529 },
530 }
531 }
532
533 fn writeRoot(out: *[root_bytes]u8, fields: RootFields) void {
534 std.debug.assert(segment.SegmentRange.containsCount(fields.segment_count));
535 std.debug.assert(PageRange.containsCount(fields.page_count));
536 std.debug.assert(fields.tail_entries != 0);
537 @memset(out, 0);
538 @memcpy(out[0..8], &root_magic);
539 std.mem.writeInt(u32, out[8..12], format_version, .little);
540 std.mem.writeInt(u32, out[12..16], fields.tail_entries, .little);
541 std.mem.writeInt(u64, out[16..24], fields.segment_count, .little);
542 std.mem.writeInt(u64, out[24..32], fields.page_count, .little);
543 @memcpy(out[32..64], &fields.tail_page);
544 @memcpy(out[64..96], &fields.last_segment);
545 const root_checksum = digest(out[0 .. root_bytes - Sha256.digest_length]);
546 @memcpy(out[root_bytes - Sha256.digest_length ..], &root_checksum);
547 }
548
549 fn writePage(out: *[page_bytes]u8, previous_page: Hash, entry_value: Entry) void {
550 std.debug.assert(entryWithinRanges(entry_value));
551 @memset(out, 0);
552 @memcpy(out[0..8], &page_magic);
553 std.mem.writeInt(u32, out[8..12], format_version, .little);
554 std.mem.writeInt(u32, out[12..16], 1, .little);
555 std.mem.writeInt(u64, out[16..24], entry_value.ordinal, .little);
556 @memcpy(out[32..64], &previous_page);
557 writeEntry(out[page_header_bytes..][0..entry_bytes], entry_value);
558 finishPage(out, 1);
559 }
560
561 fn copyPageAndAppend(page: Page, entry_value: Entry, out: *[page_bytes]u8) void {
562 std.debug.assert(page.count < entries_per_page);
563 out.* = page.bytes[0..page_bytes].*;
564 const index: usize = page.count;
565 const start = page_header_bytes + index * entry_bytes;
566 writeEntry(out[start..][0..entry_bytes], entry_value);
567 const count = page.count + 1;
568 std.mem.writeInt(u32, out[12..16], count, .little);
569 finishPage(out, count);
570 }
571
572 fn finishPage(out: *[page_bytes]u8, count: u32) void {
573 const used_end = page_header_bytes + count * entry_bytes;
574 @memset(out[used_end .. page_bytes - page_trailer_bytes], 0);
575 const entries_digest = digest(out[page_header_bytes..used_end]);
576 @memcpy(out[64..96], &entries_digest);
577 const page_checksum = digest(out[0 .. page_bytes - page_trailer_bytes]);
578 @memcpy(out[page_bytes - page_trailer_bytes ..], &page_checksum);
579 }
580
581 fn writeEntry(out: []u8, value: Entry) void {
582 std.debug.assert(out.len == entry_bytes);
583 std.debug.assert(entryWithinRanges(value));
584 @memset(out, 0);
585 @memcpy(out[0..32], &value.segment);
586 @memcpy(out[32..64], &value.previous_segment);
587 @memcpy(out[64..96], &value.stream_identity);
588 @memcpy(out[96..128], &value.checkpoint_root);
589 @memcpy(out[128..160], &value.cold_receipt);
590 std.mem.writeInt(u64, out[160..168], value.epoch, .little);
591 std.mem.writeInt(u64, out[168..176], value.ordinal, .little);
592 std.mem.writeInt(u64, out[176..184], value.first_event, .little);
593 std.mem.writeInt(u64, out[184..192], value.event_count, .little);
594 std.mem.writeInt(u64, out[192..200], value.encoded_bytes, .little);
595 out[200] = @backingInt(value.tier);
596 }
597
598 fn readEntry(bytes: []const u8) Error!Entry {
599 if (bytes.len != entry_bytes or !allZero(bytes[201..208])) {
600 return error.InvalidManifest;
601 }
602 return .{
603 .segment = bytes[0..32].*,
604 .previous_segment = bytes[32..64].*,
605 .stream_identity = bytes[64..96].*,
606 .checkpoint_root = bytes[96..128].*,
607 .cold_receipt = bytes[128..160].*,
608 .epoch = std.mem.readInt(u64, bytes[160..168], .little),
609 .ordinal = std.mem.readInt(u64, bytes[168..176], .little),
610 .first_event = std.mem.readInt(u64, bytes[176..184], .little),
611 .event_count = std.mem.readInt(u64, bytes[184..192], .little),
612 .encoded_bytes = std.mem.readInt(u64, bytes[192..200], .little),
613 .tier = std.enums.fromInt(Tier, bytes[200]) orelse
614 return error.InvalidManifest,
615 };
616 }
617
618 fn entryWithinRanges(value: Entry) bool {
619 return segment.SegmentRange.containsOrdinal(value.ordinal) and
620 segment.EventRange.advance(value.first_event, value.event_count) != null;
621 }
622
623 fn coldReceipt(segment_digest: Hash, bytes: usize) Hash {
624 var hasher = Sha256.init(.{});
625 hasher.update("tiny.sql.history.cold-receipt/v1");
626 hasher.update(&segment_digest);
627 var frame: [8]u8 = undefined;
628 std.mem.writeInt(u64, &frame, bytes, .little);
629 hasher.update(&frame);
630 var value: Hash = undefined;
631 hasher.final(&value);
632 return value;
633 }
634
635 fn digest(bytes: []const u8) Hash {
636 var value: Hash = undefined;
637 Sha256.hash(bytes, &value, .{});
638 return value;
639 }
640
641 fn same(left: Hash, right: Hash) bool {
642 return std.mem.eql(u8, &left, &right);
643 }
644
645 fn isZero(value: Hash) bool {
646 return same(value, @splat(0));
647 }
648
649 fn allZero(bytes: []const u8) bool {
650 for (bytes) |byte| if (byte != 0) return false;
651 return true;
652 }