const std = @import("std"); const mem = std.mem; const assert = std.debug.assert; const Allocator = mem.Allocator; const ArrayListAligned = std.ArrayListAligned; const BufSet = std.BufSet; const validate = @import("validate.zig"); const compress = @import("compress.zig"); const InvalidRecord = validate.InvalidRecord; const PackedGroup = @This(); pub const GroupStored = struct { gid: u32, name: []const u8, members_offset: u64, }; pub const alignment_bits = 3; const Inner = packed struct { gid: u32, padding: u3 = 0, groupname_len: u5, pub fn groupnameLen(self: *const Inner) usize { return @as(usize, self.groupname_len) + 1; } }; inner: *const Inner, groupdata: []const u8, members_offset: u64, pub const Entry = struct { group: PackedGroup, end: usize, }; pub fn fromBytes(bytes: []align(8) const u8) Entry { const inner = mem.bytesAsValue(Inner, bytes[0..@sizeOf(Inner)]); const start_blob = @sizeOf(Inner); const end_strings = @sizeOf(Inner) + inner.groupnameLen(); const members_offset = compress.uvarint(bytes[end_strings..]) catch |err| switch (err) { error.Overflow => unreachable, }; const end_blob = end_strings + members_offset.bytes_read; return Entry{ .group = PackedGroup{ .inner = inner, .groupdata = bytes[start_blob..end_strings], .members_offset = members_offset.value, }, .end = mem.alignForward(end_blob, 8), }; } fn validateUtf8(s: []const u8) InvalidRecord!void { if (!std.unicode.utf8ValidateSlice(s)) return error.InvalidRecord; } pub const Iterator = struct { section: []align(8) const u8, next_start: usize = 0, idx: u32 = 0, total: u32, advanced_by: usize = 0, pub fn next(it: *Iterator) ?PackedGroup { if (it.idx == it.total) return null; const entry = fromBytes(@alignCast(8, it.section[it.next_start..])); it.idx += 1; it.next_start += entry.end; it.advanced_by = entry.end; return entry.group; } pub fn rollback(it: *Iterator) void { assert(it.advanced_by > 0); it.idx -= 1; it.next_start -= it.advanced_by; it.advanced_by = 0; } }; pub fn iterator(section: []align(8) const u8, total: u32) Iterator { return Iterator{ .section = section, .total = total, }; } pub inline fn gid(self: *const PackedGroup) u32 { return self.inner.gid; } pub inline fn membersOffset(self: *const PackedGroup) u64 { return self.members_offset; } pub inline fn name(self: *const PackedGroup) []const u8 { return self.groupdata; } pub fn packTo( arr: *ArrayListAligned(u8, 8), group: GroupStored, ) error{ InvalidRecord, OutOfMemory }!void { std.debug.assert(arr.items.len & 7 == 0); try validate.utf8(group.name); const len = try validate.downCast(u5, group.name.len - 1); const inner = Inner{ .gid = group.gid, .groupname_len = len }; try arr.*.appendSlice(mem.asBytes(&inner)); try arr.*.appendSlice(group.name); try compress.appendUvarint(arr, group.members_offset); } const testing = std.testing; test "PackedGroup alignment" { try testing.expectEqual(@sizeOf(PackedGroup) * 8, @bitSizeOf(PackedGroup)); } test "PackedGroup construct" { var buf = ArrayListAligned(u8, 8).init(testing.allocator); defer buf.deinit(); const groups = [_]GroupStored{ GroupStored{ .gid = 1000, .name = "sudo", .members_offset = 1, }, GroupStored{ .gid = std.math.maxInt(u32), .name = "Name" ** 8, // 32 .members_offset = std.math.maxInt(u64), }, }; for (groups) |group| { try PackedGroup.packTo(&buf, group); try buf.appendNTimes(0, mem.alignForward(buf.items.len, 8) - buf.items.len); } var i: u29 = 0; var it = PackedGroup.iterator(buf.items, groups.len); while (it.next()) |group| : (i += 1) { try testing.expectEqual(groups[i].gid, group.gid()); try testing.expectEqualStrings(groups[i].name, group.name()); try testing.expectEqual(groups[i].members_offset, group.membersOffset()); } try testing.expectEqual(groups.len, i); }