Skip to content
zit Artifact [1f5e773731]
Login

Artifact [1f5e773731]

Artifact 1f5e773731c0388eba6f0e7c3b86489873ddafa4285ff36dd61562a88b990ef9:


//! Artifact Format — draft-fossil-artifact-format-00.
//!
//! An artifact is an immutable byte sequence named by the lowercase-hex hash
//! of its content (Section 3). Structural artifacts are line-oriented "cards".
//! This module owns the canonical vocabulary for the whole suite: naming, the
//! card grammar, the structural types, and the two checksums (Z, R).
//!
//! Scope note: zit targets the version-control substrate — the check-in
//! (manifest), cluster, and tag/control artifact types. The collaboration
//! types (wiki, ticket, forum, attachment, technote) are carried opaquely.

const std = @import("std");

/// Card-type letters. Only the version-control cards are interpreted; others
/// are recognised syntactically and otherwise carried opaquely.
pub const CardType = enum(u8) {
    B = 'B', // baseline (delta manifest)
    C = 'C', // comment
    D = 'D', // date (mtime)
    F = 'F', // file
    M = 'M', // cluster member
    N = 'N', // mimetype
    P = 'P', // parent(s)
    Q = 'Q', // cherrypick
    R = 'R', // aggregate content checksum (MD5)
    T = 'T', // tag
    U = 'U', // user
    Z = 'Z', // artifact self-checksum (MD5), always last
};

/// Structural artifact types in scope for the version-control substrate.
pub const ArtifactType = enum {
    manifest, // a check-in
    cluster, // a set of artifact hashes (sync housekeeping)
    control, // a tag/branch mutation
    other, // recognised but carried opaquely (wiki/ticket/forum/...)
};

/// Hash algorithm that names an artifact (Section 3, Section 7 hash policy).
/// The canonical (writer) form is always lowercase hex.
pub const HashAlgo = enum { sha1, sha3_256 };

/// Compute the artifact name: the lowercase-hex hash of `content`.
/// Writes into `out` and returns the populated slice (40 hex for SHA-1,
/// 64 hex for SHA3-256).
pub fn artifactName(algo: HashAlgo, content: []const u8, out: *[64]u8) []u8 {
    switch (algo) {
        .sha1 => {
            var digest: [20]u8 = undefined;
            std.crypto.hash.Sha1.hash(content, &digest, .{});
            const hex = std.fmt.bytesToHex(digest, .lower);
            @memcpy(out[0..hex.len], &hex);
            return out[0..hex.len];
        },
        .sha3_256 => {
            var digest: [32]u8 = undefined;
            std.crypto.hash.sha3.Sha3_256.hash(content, &digest, .{});
            const hex = std.fmt.bytesToHex(digest, .lower);
            @memcpy(out[0..hex.len], &hex);
            return out[0..hex.len];
        },
    }
}

/// The Z-card value: lowercase-hex MD5 of all stripped-content bytes that
/// precede the `Z ` line (Section 6, Z card).
pub fn selfChecksum(stripped_prefix: []const u8, out: *[32]u8) []u8 {
    var digest: [16]u8 = undefined;
    std.crypto.hash.Md5.hash(stripped_prefix, &digest, .{});
    const hex = std.fmt.bytesToHex(digest, .lower);
    @memcpy(out, &hex);
    return out;
}

// ---------------------------------------------------------------------------
// Manifest parsing (Sections 4, 5) — TODO
// ---------------------------------------------------------------------------

pub const Manifest = struct {
    // Populated by parse(): cards, type, name. Skeleton for now.
    kind: ArtifactType = .other,
};

pub const ParseError = error{ SyntaxError, NotImplemented };

/// Parse a structural artifact from its bytes. See Section 4 (card syntax),
/// Section 5 (types + per-type validity). Not yet implemented.
pub fn parse(allocator: std.mem.Allocator, bytes: []const u8) ParseError!Manifest {
    _ = allocator;
    _ = bytes;
    return ParseError.NotImplemented;
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

test "artifact name = lowercase SHA3-256 (RFC example vector)" {
    var out: [64]u8 = undefined;
    // draft-fossil-artifact-format-00, R-card example: SHA3-256("hello world\n").
    const name = artifactName(.sha3_256, "hello world\n", &out);
    try std.testing.expectEqualStrings(
        "a8009a7a528d87778c356da3a55d964719e818666a04e4f960c9e2439e35f138",
        name,
    );
}

test "self-checksum = lowercase MD5 (empty input vector)" {
    var out: [32]u8 = undefined;
    const z = selfChecksum("", &out);
    try std.testing.expectEqualStrings("d41d8cd98f00b204e9800998ecf8427e", z);
}