Something went wrong. Try again.
A Zig library that reads git repositories without running git, with log, show and diff output byte-identical to git's.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! The end of index entries (`EOIE`) and index entry offset table//! (`IEOT`) extensions, read from their bytes and checked against the//! index file they came from.//!//! gitformat-index(5) describes both, which git writes when//! `index.recordEndOfIndexEntries` and `index.recordOffsetTable` say so//! (or `index.threads` asks for more than one thread): `EOIE`, always the//! last extension, is where the entries end and a hash over the//! signatures and sizes of the extensions before it; `IEOT` is a version,//! 1, and for each block of entries the offset of its first and how many//! there are. They are for reading an index faster, in parallel; this//! library reads the entries in one pass and uses them only to check.//!//! git passes over either without a word when it cannot read it.
const std = @import("std");
const object = @import("../object.zig");const Format = object.Format;const Id = object.Id;const file = @import("file.zig");
/// The end of index entries extension.pub const EndOfEntries = struct { /// Where the entries end, from the start of the file. offset: u32, /// The hash of each earlier extension's signature and size. hash: Id,};
/// One block of the index entry offset table.pub const Block = struct { /// Where the block's first entry is, from the start of the file. offset: u32, /// How many entries the block holds. count: u32,};
/// The index entry offset table.pub const OffsetTable = struct { version: u32, /// The table's contents, eight bytes a block; `block` reads one. raw: []const u8,
/// How many blocks there are. pub fn blockCount(t: *const OffsetTable) usize { return t.raw.len / 8; }
/// The `i`th block. pub fn block(t: *const OffsetTable, i: usize) Block { return .{ .offset = std.mem.readInt(u32, t.raw[i * 8 ..][0..4], .big), .count = std.mem.readInt(u32, t.raw[i * 8 + 4 ..][0..4], .big), }; }};
/// Why either extension could not be read or does not fit its file.pub const Error = error{ MalformedEndOfEntries, MalformedOffsetTable };
/// Reads the `EOIE` extension's contents, in `format`.pub fn parseEndOfEntries(format: Format, data: []const u8) Error!EndOfEntries { const n = format.byteLength(); if (data.len != 4 + n) return error.MalformedEndOfEntries; return .{ .offset = std.mem.readInt(u32, data[0..4], .big), .hash = .fromBytes(format, data[4..][0..n]), };}
/// Checks the end of index entries extension of `f`, read from its/// extension `eoie`: that it is the last, that the entries end where it/// says, and that its hash is right.pub fn checkEndOfEntries(format: Format, f: *const file.File, eoie: file.Extension, e: EndOfEntries) Error!void { const last = f.extensions[f.extensions.len - 1]; if (last.offset != eoie.offset) return error.MalformedEndOfEntries; if (e.offset != f.entries_end) return error.MalformedEndOfEntries; var h: object.Hasher = .init(format); for (f.extensions[0 .. f.extensions.len - 1]) |x| { h.update(&x.signature); var size: [4]u8 = undefined; std.mem.writeInt(u32, &size, @intCast(x.data.len), .big); h.update(&size); } if (!h.final().eql(e.hash)) return error.MalformedEndOfEntries;}
/// Reads the `IEOT` extension's contents.pub fn parseOffsetTable(data: []const u8) Error!OffsetTable { if (data.len < 4 or (data.len - 4) % 8 != 0) return error.MalformedOffsetTable; const version = std.mem.readInt(u32, data[0..4], .big); if (version != 1) return error.MalformedOffsetTable; return .{ .version = version, .raw = data[4..] };}
/// Checks the offset table `t` against the entries of `f`: that its/// blocks begin where entries begin, one after another, and cover them/// all.pub fn checkOffsetTable(f: *const file.File, t: OffsetTable) Error!void { var next: u64 = 0; for (0..t.blockCount()) |i| { const b = t.block(i); if (next >= f.entries.len and b.count != 0) return error.MalformedOffsetTable; if (next < f.entries.len and f.entry_offsets[@intCast(next)] != b.offset) return error.MalformedOffsetTable; next += b.count; if (next > f.entries.len) return error.MalformedOffsetTable; } if (next != f.entries.len) return error.MalformedOffsetTable;}
// -- tests -----------------------------------------------------------------------
const testing = std.testing;
const fuzz_input = @import("../testing/fuzz.zig");
test "fuzz: end of index entries and offset tables" { try fuzz_input.fuzz({}, struct { fn one(_: void, smith: *testing.Smith) anyerror!void { var buffer: [4096]u8 = undefined; const input = @import("../testing/fuzz.zig").bytes(smith, &buffer); var arena: std.heap.ArenaAllocator = .init(testing.allocator); defer arena.deinit(); for ([_]Format{ .sha1, .sha256 }) |format| { const f = file.parse(arena.allocator(), format, input, null) catch continue; for (f.extensions) |x| { if (std.mem.eql(u8, &x.signature, "EOIE")) { const e = parseEndOfEntries(format, x.data) catch continue; checkEndOfEntries(format, &f, x, e) catch {}; } else if (std.mem.eql(u8, &x.signature, "IEOT")) { const t = parseOffsetTable(x.data) catch continue; checkOffsetTable(&f, t) catch {}; } } } } }.one, .{ .corpus = &.{ @embedFile("../fuzz/index-eoie"), @embedFile("../fuzz/index-eoie-v4"), } });}