aboutsummaryrefslogtreecommitdiff
path: root/src/metainfo.zig
diff options
context:
space:
mode:
authorMartin Ashby <martin@ashbysoft.com>2023-11-11 20:47:46 +0000
committerMartin Ashby <martin@ashbysoft.com>2023-11-11 20:47:46 +0000
commite92459156c2f74de648566ca7acde3833de33425 (patch)
tree59d0bb42d23db0d295a0ef0a97e31e71b8f592cd /src/metainfo.zig
parentdff4234bf2d957a0328ff4f3dc4f9bba1fbeffd4 (diff)
downloadzbt-e92459156c2f74de648566ca7acde3833de33425.tar.gz
zbt-e92459156c2f74de648566ca7acde3833de33425.tar.bz2
zbt-e92459156c2f74de648566ca7acde3833de33425.tar.xz
zbt-e92459156c2f74de648566ca7acde3833de33425.zip
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
Diffstat (limited to 'src/metainfo.zig')
-rw-r--r--src/metainfo.zig79
1 files changed, 79 insertions, 0 deletions
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);
+}