Skip to content

Commit

Permalink
feat:add more function in cid
Browse files Browse the repository at this point in the history
Signed-off-by: Chen Kai <[email protected]>
  • Loading branch information
GrapeBaBa committed Dec 15, 2024
1 parent a83eaf4 commit bf968f4
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 123 deletions.
271 changes: 154 additions & 117 deletions src/cid.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const Allocator = std.mem.Allocator;
const Multicodec = @import("multicodec.zig").Multicodec;
const Multihash = @import("multihash.zig").Multihash;
const varint = @import("unsigned_varint.zig");
const MultiBaseCodec = @import("multibase.zig").MultiBaseCodec;

pub const Error = error{
UnknownCodec,
Expand Down Expand Up @@ -48,29 +49,31 @@ pub fn Cid(comptime S: usize) type {
hash: Multihash(S),
allocator: Allocator,

pub fn newV0(allocator: Allocator, hash: Multihash(32)) !Cid {
const Self = @This();

pub fn newV0(allocator: Allocator, hash: Multihash(32)) !Self {
if (hash.getCode() != Multicodec.SHA2_256 or hash.getSize() != 32) {
return Error.InvalidCidV0Multihash;
}

return Cid{
return Cid(32){
.version = .V0,
.codec = Multicodec.DAG_PB.getCode(),
.hash = hash,
.allocator = allocator,
};
}

pub fn newV1(allocator: Allocator, codec: u64, hash: Multihash(S)) !Cid {
return Cid{
pub fn newV1(allocator: Allocator, codec: u64, hash: Multihash(S)) !Self {
return Cid(S){
.version = .V1,
.codec = codec,
.hash = hash,
.allocator = allocator,
};
}

pub fn init(allocator: Allocator, version: CidVersion, codec: u64, hash: Multihash(S)) !@This() {
pub fn init(allocator: Allocator, version: CidVersion, codec: u64, hash: Multihash(S)) !Self {
switch (version) {
.V0 => {
if (codec != Multicodec.DAG_PB.getCode()) {
Expand All @@ -85,15 +88,43 @@ pub fn Cid(comptime S: usize) type {
}
}

pub fn readBytes(allocator: Allocator, reader: anytype) !Cid {
/// Checks if two CIDs are equal by comparing version, codec and hash
pub fn isEqual(self: *const Self, other: *const Self) bool {
return self.version == other.version and
self.codec == other.codec and
std.mem.eql(u8, self.hash.getDigest(), other.hash.getDigest());
}

pub fn writeBytesV1(self: *const Self, writer: anytype) !usize {
const version_written = try varint.encode_stream(writer, u64, self.version.toInt());
const codec_written = try varint.encode_stream(writer, u64, self.codec);

var written: usize = version_written + codec_written;
written += try self.hash.write(writer);
return written;
}

pub fn intoV1(self: Self) !Self {
return switch (self.version) {
.V0 => {
if (self.codec != @intFromEnum(Multicodec.DAG_PB)) {
return Error.InvalidCidV0Codec;
}
return newV1(self.allocator, self.codec, self.hash);
},
.V1 => self,
};
}

pub fn readBytes(allocator: Allocator, reader: anytype) !Self {
const version = try varint.decode_stream(reader, u64);
const codec = try varint.decode_stream(reader, u64);

// CIDv0 has the fixed `0x12 0x20` prefix
if (version == 0x12 and codec == 0x20) {
var digest: [32]u8 = undefined;
try reader.readNoEof(&digest);
const mh = try Multihash(32).wrap(version, &digest);
const version_codec = try Multicodec.fromCode(version);
const mh = try Multihash(32).wrap(version_codec, &digest);
return newV0(allocator, mh);
}

Expand All @@ -102,123 +133,129 @@ pub fn Cid(comptime S: usize) type {
.V0 => return Error.InvalidExplicitCidV0,
.V1 => {
const mh = try Multihash(32).read(reader);
return Cid.init(allocator, ver, codec, mh.getDigest());
return Self.init(allocator, ver, codec, mh);
},
}
}

fn writeBytesV1(self: *const Cid, writer: anytype) !usize {
const version_written=try varint.encode_stream(writer,u64,self.version.toInt());
const codec_written=try varint.encode_stream(writer,u64,self.codec);
pub fn writeBytes(self: *const Self, writer: anytype) !usize {
return switch (self.version) {
.V0 => try self.hash.write(writer),
.V1 => try self.writeBytesV1(writer),
};
}

const written: usize = version_written + codec_written;
pub fn encodedLen(self: Self) usize {
return switch (self.version) {
.V0 => self.hash.encodedLen(),
.V1 => {
var version_buf: [varint.bufferSize(u64)]u8 = undefined;
const version = varint.encode(u64, self.version.toInt(), &version_buf);

return written;
var codec_buf: [varint.bufferSize(u64)]u8 = undefined;
const codec = varint.encode(u64, self.codec, &codec_buf);

return version.len + codec.len + self.hash.encodedLen();
},
};
}

pub fn toBytes(self: *const Self) ![]u8 {
var bytes = std.ArrayList(u8).init(self.allocator);
errdefer bytes.deinit();

const written = try self.writeBytes(bytes.writer());
std.debug.assert(written == bytes.items.len);

return bytes.toOwnedSlice();
}

pub fn getHash(self: *const Self) []const u8 {
return self.hash.getDigest();
}

pub fn getCodec(self: *const Self) u64 {
return self.codec;
}

pub fn getVersion(self: *const Self) CidVersion {
return self.version;
}

fn toStringV0(self: *const Self) ![]const u8 {
const hash_bytes = try self.hash.toBytes();
var bytes = std.ArrayList(u8).init(self.allocator);
errdefer bytes.deinit();
return MultiBaseCodec.Base58Btc.encode(bytes.items, hash_bytes);
}

fn to_string_v1(self: *const Self) ![]u8 {
const bytes = try self.toBytes(self.allocator);
defer self.allocator.free(bytes);

const dest = std.ArrayList(u8).init(self.allocator);
return MultiBaseCodec.Base32Lower.encode(dest.items, bytes);
}
};
}

// test "CID basic operations" {
// const testing = std.testing;
// const allocator = testing.allocator;
//
// // Test CIDv0
// {
// var hash = [_]u8{ 0x12, 0x20 } ++ [_]u8{1} ** 32;
// var cid = try Cid.init(allocator, .V0, .DagPb, hash[2..]);
// defer cid.deinit();
//
// try testing.expect(cid.version == .V0);
// try testing.expect(cid.codec == .DagPb);
// try testing.expectEqualSlices(u8, hash[2..], cid.hash);
//
// // Test toBytes
// const bytes = try cid.toBytes();
// defer allocator.free(bytes);
// try testing.expectEqualSlices(u8, &hash, bytes);
// }
//
// // Test CIDv1
// {
// var hash = [_]u8{1} ** 32;
// var cid = try Cid.init(allocator, .V1, .DagCbor, &hash);
// defer cid.deinit();
//
// try testing.expect(cid.version == .V1);
// try testing.expect(cid.codec == .DagCbor);
// try testing.expectEqualSlices(u8, &hash, cid.hash);
// }
// }
//
// test "CID fromBytes" {
// const testing = std.testing;
// const allocator = testing.allocator;
//
// // Test CIDv0 parsing
// {
// var input = [_]u8{ 0x12, 0x20 } ++ [_]u8{1} ** 32;
// var cid = try Cid.fromBytes(allocator, &input);
// defer cid.deinit();
//
// try testing.expect(cid.version == .V0);
// try testing.expect(cid.codec == .DagPb);
// try testing.expectEqualSlices(u8, input[2..], cid.hash);
// }
//
// // Test CIDv1 parsing
// {
// var input = [_]u8{ 1, @intFromEnum(Codec.DagCbor) } ++ [_]u8{1} ** 32;
// var cid = try Cid.fromBytes(allocator, &input);
// defer cid.deinit();
//
// try testing.expect(cid.version == .V1);
// try testing.expect(cid.codec == .DagCbor);
// try testing.expectEqualSlices(u8, input[2..], cid.hash);
// }
// }
//
// test "CID error cases" {
// const testing = std.testing;
// const allocator = testing.allocator;
//
// // Test invalid V0 codec
// {
// var hash = [_]u8{1} ** 32;
// try testing.expectError(Error.InvalidCidV0Codec, Cid.init(allocator, .V0, .DagCbor, &hash));
// }
//
// // Test input too short
// {
// var input = [_]u8{1};
// try testing.expectError(Error.InputTooShort, Cid.fromBytes(allocator, &input));
// }
//
// // Test unknown codec
// {
// var input = [_]u8{ 1, 0xFF } ++ [_]u8{1} ** 32;
// try testing.expectError(Error.UnknownCodec, Cid.fromBytes(allocator, &input));
// }
// }
//
// test "CID version checks" {
// const testing = std.testing;
//
// // Test V0 string detection
// {
// const v0_str = "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n";
// try testing.expect(CidVersion.isV0Str(v0_str));
//
// const invalid_str = "invalid";
// try testing.expect(!CidVersion.isV0Str(invalid_str));
// }
//
// // Test V0 binary detection
// {
// var valid_bytes = [_]u8{ 0x12, 0x20 } ++ [_]u8{1} ** 32;
// try testing.expect(CidVersion.isV0Binary(&valid_bytes));
//
// var invalid_bytes = [_]u8{ 0x00, 0x00 } ++ [_]u8{1} ** 32;
// try testing.expect(!CidVersion.isV0Binary(&invalid_bytes));
// }
// }
test "Cid" {
const testing = std.testing;
const allocator = testing.allocator;

// Test CIDv0
{
const hash = try Multihash(32).wrap(Multicodec.SHA2_256, &[_]u8{0} ** 32);
const cid = try Cid(32).newV0(allocator, hash);
try testing.expectEqual(cid.version, .V0);
try testing.expectEqual(cid.codec, Multicodec.DAG_PB.getCode());
}

// Test CIDv1
{
const hash = try Multihash(64).wrap(Multicodec.SHA2_256, &[_]u8{0} ** 32);
const cid = try Cid(64).newV1(allocator, Multicodec.RAW.getCode(), hash);
try testing.expectEqual(cid.version, .V1);
try testing.expectEqual(cid.codec, Multicodec.RAW.getCode());
}

// Test encoding/decoding
{
const hash = try Multihash(32).wrap(Multicodec.SHA2_256, &[_]u8{0} ** 32);
const original = try Cid(32).newV1(allocator, Multicodec.RAW.getCode(), hash);

const bytes = try original.toBytes();
defer allocator.free(bytes);

var fbs = std.io.fixedBufferStream(bytes);
const decoded = try Cid(32).readBytes(allocator, fbs.reader());

try testing.expect(original.isEqual(&decoded));
}
}

test "Cid conversion and comparison" {
const testing = std.testing;
const allocator = testing.allocator;

// Test V0 to V1 conversion
{
const hash = try Multihash(32).wrap(Multicodec.SHA2_256, &[_]u8{0} ** 32);
const v0 = try Cid(32).newV0(allocator, hash);
const v1 = try v0.intoV1();

try testing.expectEqual(v1.version, .V1);
try testing.expectEqual(v1.codec, v0.codec);
try testing.expect(std.mem.eql(u8, v1.getHash(), v0.getHash()));
}

// Test encoded length
{
const hash = try Multihash(32).wrap(Multicodec.SHA2_256, &[_]u8{0} ** 32);
const cid = try Cid(32).newV1(allocator, Multicodec.RAW.getCode(), hash);
const bytes = try cid.toBytes();
defer allocator.free(bytes);

try testing.expectEqual(cid.encodedLen(), bytes.len);
}
}
13 changes: 7 additions & 6 deletions src/multihash.zig
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,21 @@ pub fn Multihash(comptime S: usize) type {
}

pub fn encodedLen(self: Self) usize {
var code_buf: [10]u8 = undefined;
var code_buf: [varint.bufferSize(u64)]u8 = undefined;
const code_encoded = varint.encode(u64, self.code.getCode(), &code_buf);

var size_buf: [1]u8 = undefined;
var size_buf: [varint.bufferSize(u8)]u8 = undefined;
const size_encoded = varint.encode(u8, self.size, &size_buf);

return code_encoded.len + size_encoded.len + self.size;
}

pub fn write(self: Self, writer: anytype) !usize {
var code_buf: [10]u8 = undefined;
var code_buf: [varint.bufferSize(u64)]u8 = undefined;
const code_encoded = varint.encode(u64, self.code.getCode(), &code_buf);
try writer.writeAll(code_encoded);

var size_buf: [1]u8 = undefined;
var size_buf: [varint.bufferSize(u8)]u8 = undefined;
const size_encoded = varint.encode(u8, self.size, &size_buf);
try writer.writeAll(size_encoded);

Expand Down Expand Up @@ -105,9 +105,10 @@ pub fn Multihash(comptime S: usize) type {
}

pub fn toBytes(self: Self, allocator: std.mem.Allocator) ![]u8 {
const bytes = try allocator.alloc(u8, self.encodedLen());
const bytes = try allocator.alloc(u8, self.size);
var stream = std.io.fixedBufferStream(bytes);
_ = try self.write(stream.writer());
const written = try self.write(stream.writer());
std.debug.assert(written == bytes.len);
return bytes;
}
};
Expand Down

0 comments on commit bf968f4

Please sign in to comment.