1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
//! https://wiki.theory.org/BitTorrentSpecification#Metainfo_File_Structure
const std = @import("std");
const bencode = @import("bencode.zig");
const MetaInfo = @This();
pub const Error = (error{Malformatted} || std.mem.Allocator.Error);
pub const Info = struct {
pub const File = struct {
name: []const u8,
length: u64,
path: []const u8,
};
piece_length: u64,
pieces: []const u8,
files: []File,
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;
const pp = d.get("pieces") orelse return error.Malformatted;
var files = std.ArrayList(File).init(a);
defer files.deinit();
if (d.get("files")) |f| {
// multi-file mode
const l = f.asList() catch return error.Malformatted;
for (l.items) |fi| {
const fd = fi.asDict() catch return error.Malformatted;
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;
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,
});
}
} else {
// single-file mode
const fin = d.get("name") orelse return error.Malformatted;
const fl = d.get("length") orelse 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
});
}
return .{
.piece_length = pl.asInt(u64) catch return error.Malformatted,
.pieces = pp.asString() catch return error.Malformatted,
.files = try files.toOwnedSlice(),
};
}
pub fn deinit(self: *Info, a: std.mem.Allocator) void {
a.free(self.files);
}
};
info: Info,
announce: []const u8,
pub fn parse(a: std.mem.Allocator, b: bencode.BValue) Error!MetaInfo {
// TODO diagnostics
var d = b.asDict() catch return error.Malformatted;
const i = d.get("info") orelse return error.Malformatted;
const an = d.get("announce") orelse return error.Malformatted;
return .{
.info = try Info.parse(a, i),
.announce = an.asString() catch return error.Malformatted,
};
}
pub fn deinit(self: *MetaInfo, a: std.mem.Allocator) void {
self.info.deinit(a);
}
test "sample" {
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);
try std.testing.expectEqualStrings("http://bittorrent-test-tracker.codecrafters.io/announce", mi.announce);
try std.testing.expectEqual(@as(usize, 1), mi.info.files.len);
try std.testing.expectEqualStrings("sample.txt", mi.info.files[0].name);
}
|