// // varint64 []const u8 variants // // Thanks to https://github.com/gsquire/zig-snappy/blob/master/snappy.zig and // golang's varint implementation. const std = @import("std"); const ArrayListAligned = std.ArrayListAligned; const Allocator = std.mem.Allocator; const assert = std.debug.assert; const math = std.math; // compresses a strictly incrementing sorted slice of integers using delta // compression. Compression is in-place. pub fn deltaCompress(comptime T: type, elems: []T) error{NotSorted}!void { if (elems.len <= 1) { return; } var prev: T = elems[0]; var i: usize = 1; while (i < elems.len) : (i += 1) { const cur = elems[i]; if (cur <= prev) { return error.NotSorted; } elems[i] = cur - prev - 1; prev = cur; } } // decompresses a slice compressed by deltaCompress. In-place. pub fn deltaDecompress(comptime T: type, elems: []T) error{Overflow}!void { if (elems.len <= 1) { return; } var i: usize = 1; while (i < elems.len) : (i += 1) { const x = try math.add(T, elems[i - 1], 1); elems[i] = try math.add(T, elems[i], x); } } // Represents a variable length integer that we read from a byte stream along // with how many bytes were read to decode it. pub const Varint = struct { value: u64, bytes_read: usize, }; pub const maxVarintLen64 = 10; // https://golang.org/pkg/encoding/binary/#Uvarint pub fn uvarint(buf: []const u8) error{Overflow}!Varint { var x: u64 = 0; var s: u6 = 0; for (buf, 0..) |b, i| { if (i == maxVarintLen64) // Catch byte reads past maxVarintLen64. // See issue https://golang.org/issues/41185 return error.Overflow; if (b < 0x80) { if (i == maxVarintLen64 - 1 and b > 1) { return error.Overflow; } return Varint{ .value = x | (@as(u64, b) << s), .bytes_read = i + 1, }; } x |= (@as(u64, b & 0x7f) << s); s = try math.add(u6, s, 7); } return Varint{ .value = 0, .bytes_read = 0, }; } // https://golang.org/pkg/encoding/binary/#PutUvarint pub fn putUvarint(buf: []u8, x: u64) usize { var i: usize = 0; var mutX = x; while (mutX >= 0x80) { buf[i] = @as(u8, @truncate(mutX)) | 0x80; mutX >>= 7; i += 1; } buf[i] = @truncate(mutX); return i + 1; } // varintSliceIterator iterates over varint-encoded slice. // The first element is the length of the slice, in decoded numbers. pub const VarintSliceIterator = struct { remaining: usize, arr: []const u8, idx: usize, pub fn next(self: *VarintSliceIterator) error{Overflow}!?u64 { if (self.remaining == 0) return null; const value = try uvarint(self.arr[self.idx..]); //std.debug.print("ptr={*} idx={d:<10} arr.ptr={*}\n", .{ self, self.idx, self.arr.ptr }); self.idx += value.bytes_read; self.remaining -= 1; return value.value; } // returns the number of remaining items. If called before the first // next(), returns the length of the slice. pub fn remaining(self: *const VarintSliceIterator) usize { return self.remaining; } }; pub fn varintSliceIterator(arr: []const u8) error{Overflow}!VarintSliceIterator { const firstnumber = try uvarint(arr); return VarintSliceIterator{ .remaining = firstnumber.value, .arr = arr, .idx = firstnumber.bytes_read, }; } pub const DeltaDecompressionIterator = struct { vit: *VarintSliceIterator, prev: u64, add_to_prev: u1, pub fn next(self: *DeltaDecompressionIterator) error{Overflow}!?u64 { const current = try self.vit.next(); if (current == null) return null; const prevExtra = try math.add(u64, self.prev, self.add_to_prev); const result = try math.add(u64, current.?, prevExtra); self.prev = result; self.add_to_prev = 1; return result; } // returns the number of remaining items. If called before the first // next(), returns the length of the slice. pub fn remaining(self: *const DeltaDecompressionIterator) usize { return self.vit.remaining; } }; pub fn deltaDecompressionIterator(vit: *VarintSliceIterator) DeltaDecompressionIterator { return DeltaDecompressionIterator{ .vit = vit, .prev = 0, .add_to_prev = 0, }; } pub fn appendUvarint(arr: *ArrayListAligned(u8, 8), x: u64) Allocator.Error!void { var buf: [maxVarintLen64]u8 = undefined; const n = putUvarint(&buf, x); try arr.appendSlice(buf[0..n]); } const testing = std.testing; const uvarint_tests = [_]u64{ 0, 1, 2, 10, 20, 63, 64, 65, 127, 128, 129, 255, 256, 257, 1 << 63 - 1, }; test "compress putUvarint/uvarint" { for (uvarint_tests) |x| { var buf: [maxVarintLen64]u8 = undefined; const n = putUvarint(buf[0..], x); const got = try uvarint(buf[0..n]); try testing.expectEqual(x, got.value); try testing.expectEqual(n, got.bytes_read); } } test "compress varintSliceIterator" { var buf = ArrayListAligned(u8, 8).init(testing.allocator); defer buf.deinit(); try appendUvarint(&buf, uvarint_tests.len); for (uvarint_tests) |x| try appendUvarint(&buf, x); var it = try varintSliceIterator(buf.items); var i: usize = 0; while (try it.next()) |got| : (i += 1) { try testing.expectEqual(uvarint_tests[i], got); } try testing.expectEqual(i, uvarint_tests.len); } test "compress delta compress/decompress" { const tests = [_]struct { input: []const u8, want: []const u8 }{ .{ .input = &[_]u8{}, .want = &[_]u8{} }, .{ .input = &[_]u8{0}, .want = &[_]u8{0} }, .{ .input = &[_]u8{10}, .want = &[_]u8{10} }, .{ .input = &[_]u8{ 0, 1, 2 }, .want = &[_]u8{ 0, 0, 0 } }, .{ .input = &[_]u8{ 10, 20, 30, 255 }, .want = &[_]u8{ 10, 9, 9, 224 } }, .{ .input = &[_]u8{ 0, 254, 255 }, .want = &[_]u8{ 0, 253, 0 } }, }; for (tests) |t| { var arr = try ArrayListAligned(u8, 8).initCapacity( testing.allocator, t.input.len, ); defer arr.deinit(); try arr.appendSlice(t.input); try deltaCompress(u8, arr.items); try testing.expectEqualSlices(u8, arr.items, t.want); try deltaDecompress(u8, arr.items); try testing.expectEqualSlices(u8, arr.items, t.input); } } test "compress delta compression with varint tests" { var scratch: [uvarint_tests.len]u64 = undefined; std.mem.copy(u64, scratch[0..], uvarint_tests[0..]); try deltaCompress(u64, scratch[0..]); try deltaDecompress(u64, scratch[0..]); try testing.expectEqualSlices(u64, uvarint_tests[0..], scratch[0..]); } test "compress delta compression negative tests" { for ([_][]const u8{ &[_]u8{ 0, 0 }, &[_]u8{ 0, 1, 1 }, &[_]u8{ 0, 1, 2, 1 }, }) |t| { var arr = try ArrayListAligned(u8, 8).initCapacity(testing.allocator, t.len); defer arr.deinit(); try arr.appendSlice(t); try testing.expectError(error.NotSorted, deltaCompress(u8, arr.items)); } } test "compress delta decompress overflow" { for ([_][]const u8{ &[_]u8{ 255, 0 }, &[_]u8{ 0, 128, 127 }, }) |t| { var arr = try ArrayListAligned(u8, 8).initCapacity(testing.allocator, t.len); defer arr.deinit(); try arr.appendSlice(t); try testing.expectError(error.Overflow, deltaDecompress(u8, arr.items)); } } test "compress delta decompression with an iterator" { var compressed: [uvarint_tests.len]u64 = undefined; std.mem.copy(u64, compressed[0..], uvarint_tests[0..]); try deltaCompress(u64, compressed[0..]); var buf = ArrayListAligned(u8, 8).init(testing.allocator); defer buf.deinit(); try appendUvarint(&buf, compressed.len); for (compressed) |x| try appendUvarint(&buf, x); var vit = try varintSliceIterator(buf.items); var it = deltaDecompressionIterator(&vit); var i: usize = 0; try testing.expectEqual(it.remaining(), uvarint_tests.len); while (try it.next()) |got| : (i += 1) { try testing.expectEqual(uvarint_tests[i], got); } try testing.expectEqual(i, uvarint_tests.len); } test "compress appendUvarint" { for (uvarint_tests) |x| { var buf = ArrayListAligned(u8, 8).init(testing.allocator); defer buf.deinit(); try appendUvarint(&buf, x); const got = try uvarint(buf.items); try testing.expectEqual(x, got.value); } } test "compress overflow" { for ([_][]const u8{ &[_]u8{ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x2 }, &[_]u8{ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x1, 0, 0 }, &[_]u8{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, }) |t| { try testing.expectError(error.Overflow, uvarint(t)); } } const compress = @This(); const GroupMembersIter = struct { vit: compress.VarintSliceIterator, it: compress.DeltaDecompressionIterator, total: usize, }; pub fn groupMembersIter(members_slice: []const u8) error{Overflow}!GroupMembersIter { var vit = try compress.varintSliceIterator(members_slice); var it = compress.deltaDecompressionIterator(&vit); return GroupMembersIter{ .vit = vit, .it = it, .total = vit.remaining, }; } test "compress: trying to repro pointer change of DB.groupMembersIter" { const members_slice = &[_]u8{ 4, 0, 60, 2, 2, 2, 64, 2 }; var members = try groupMembersIter(members_slice); var i: usize = 0; while (try members.it.next()) |member_offset| : (i += 1) { _ = member_offset; } }