From e92459156c2f74de648566ca7acde3833de33425 Mon Sep 17 00:00:00 2001 From: Martin Ashby Date: Sat, 11 Nov 2023 20:47:46 +0000 Subject: Include ownership info on BValue. It's a bit cumbersome, but it caters for the two situations: a. parsing from a stream where strings must be copied into the structure and owned, and b. constructing from application code where strings must _not_ be freed. Either way, the base structures (array list, hash map) are always owned and must be freed. Add method and test for info hash --- src/metainfo.zig | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) (limited to 'src/metainfo.zig') diff --git a/src/metainfo.zig b/src/metainfo.zig index a202b0e..31b14b7 100644 --- a/src/metainfo.zig +++ b/src/metainfo.zig @@ -9,10 +9,14 @@ pub const Info = struct { name: []const u8, length: u64, path: []const u8, + md5sum: ?[]const u8 = null, }; + piece_length: u64, pieces: []const u8, files: []File, + private: ?bool, + pub fn parse(a: std.mem.Allocator, b: bencode.BValue) Error!Info { var d = b.asDict() catch return error.Malformatted; const pl = d.get("piece length") orelse return error.Malformatted; @@ -27,33 +31,89 @@ pub const Info = struct { const fin = fd.get("name") orelse return error.Malformatted; const fl = fd.get("length") orelse return error.Malformatted; const fp = fd.get("path") orelse return error.Malformatted; + var fm: ?[]const u8 = null; + if (fd.get("md5sum")) |md5| { + fm = md5.asString() catch return error.Malformatted; + } try files.append(.{ .name = fin.asString() catch return error.Malformatted, .length = fl.asInt(u64) catch return error.Malformatted, .path = fp.asString() catch return error.Malformatted, + .md5sum = fm, }); } } else { // single-file mode const fin = d.get("name") orelse return error.Malformatted; const fl = d.get("length") orelse return error.Malformatted; + var fm: ?[]const u8 = null; + if (d.get("md5sum")) |md5| { + fm = md5.asString() catch return error.Malformatted; + } try files.append(.{ .name = fin.asString() catch return error.Malformatted, .length = fl.asInt(u64) catch return error.Malformatted, .path = fin.asString() catch return error.Malformatted, // just use the file name as path + .md5sum = fm, }); } + var priv: ?bool = null; + if (d.get("private")) |pr| { + const pri = pr.asInt(u1) catch return error.Malformatted; + priv = pri == 1; + } return .{ .piece_length = pl.asInt(u64) catch return error.Malformatted, .pieces = pp.asString() catch return error.Malformatted, .files = try files.toOwnedSlice(), + .private = priv, }; } + pub fn encode(self: Info, a: std.mem.Allocator) Error!bencode.BValue { + var r: bencode.BValue = .{ .dict = .{ .dict = std.StringArrayHashMap(bencode.BValue).init(a) } }; + errdefer r.deinit(a); + if (self.files.len > 1) { + @panic("TODO"); + } else if (self.files.len == 1) { + const f = self.files[0]; + const l = std.math.cast(i64, f.length) orelse return error.Malformatted; + try r.dict.dict.put("length", .{ .int = l }); + try r.dict.dict.put("name", .{ .string = .{ .string = f.name } }); + if (f.md5sum) |fm| { + try r.dict.dict.put("md5sum", .{ .string = .{ .string = fm } }); + } + } else { + return error.Malformatted; + } + + try r.dict.dict.put("pieces", .{ .string = .{ .string = self.pieces } }); + const pl = std.math.cast(i64, self.piece_length) orelse return error.Malformatted; + try r.dict.dict.put("piece length", .{ .int = pl }); + if (self.private) |pr| { + const pri: i64 = if (pr) 1 else 0; + try r.dict.dict.put("private", .{ .int = pri }); + } + + return r; + } + pub fn deinit(self: *Info, a: std.mem.Allocator) void { a.free(self.files); } + + const info_hash_len = 40; + pub fn hash(self: Info, a: std.mem.Allocator) ![info_hash_len]u8 { + var b = try self.encode(a); + defer b.deinit(a); + var sha1 = std.crypto.hash.Sha1.init(.{}); + var w = sha1.writer(); + try b.bencode(w); + var buf = [_]u8{0} ** info_hash_len; + _ = std.fmt.bufPrint(&buf, "{}", .{std.fmt.fmtSliceHexLower(&sha1.finalResult())}) catch unreachable; + return buf; + } }; info: Info, @@ -70,6 +130,14 @@ pub fn parse(a: std.mem.Allocator, b: bencode.BValue) Error!MetaInfo { }; } +pub fn encode(self: MetaInfo, a: std.mem.Allocator) !bencode.BValue { + var d: bencode.BValue = .{ .dict = .{ .dict = std.StringArrayHashMap(bencode.BValue).init(a) } }; + errdefer d.deinit(a); + try d.put("announce", .{ .string = .{ .string = self.announce } }); + try d.put("info", try self.info.encode(a)); + return d; +} + pub fn deinit(self: *MetaInfo, a: std.mem.Allocator) void { self.info.deinit(a); } @@ -85,3 +153,14 @@ test "sample" { try std.testing.expectEqual(@as(usize, 1), mi.info.files.len); try std.testing.expectEqualStrings("sample.txt", mi.info.files[0].name); } + +test "info hash" { + const a = std.testing.allocator; + const sample_str = @embedFile("sample.torrent"); + var b = try bencode.bdecodeBuf(a, sample_str); + defer b.deinit(a); + var mi = try MetaInfo.parse(a, b); + defer mi.deinit(a); + const hash = try mi.info.hash(a); + try std.testing.expectEqualStrings("d69f91e6b2ae4c542468d1073a71d4ea13879a7f", &hash); +} -- cgit v1.2.3-ZIG