aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Ashby <martin@ashbysoft.com>2023-11-11 22:17:31 +0000
committerMartin Ashby <martin@ashbysoft.com>2023-11-11 22:17:31 +0000
commite3c5be0a33e66b124ff261479cffa9f95ce53dc0 (patch)
tree9d07b95a511d878862f380aa822ff3aff2a4fe95
parente51c15cc478b6c89e9100017eebfdf88aff95cca (diff)
downloadzbt-e3c5be0a33e66b124ff261479cffa9f95ce53dc0.tar.gz
zbt-e3c5be0a33e66b124ff261479cffa9f95ce53dc0.tar.bz2
zbt-e3c5be0a33e66b124ff261479cffa9f95ce53dc0.tar.xz
zbt-e3c5be0a33e66b124ff261479cffa9f95ce53dc0.zip
Make request to tracker, get and parse response to find peers list
-rw-r--r--src/main.zig97
-rw-r--r--src/metainfo.zig11
2 files changed, 104 insertions, 4 deletions
diff --git a/src/main.zig b/src/main.zig
index 14ab3c3..9966a55 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -1,10 +1,101 @@
const std = @import("std");
+const MetaInfo = @import("metainfo.zig");
+const bencode = @import("bencode.zig");
pub fn main() !void {
- std.log.info("Hello, World", .{});
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+ const a = gpa.allocator();
+
+ // open & parse the torrent file
+ const f = try std.fs.cwd().openFile("src/sample.torrent", .{});
+ defer f.close();
+ var fr = f.reader();
+ var mib = try bencode.bdecode(a, fr);
+ defer mib.deinit(a);
+ var mi = try MetaInfo.parse(a, mib);
+ defer mi.deinit(a);
+
+ // call the tracker...
+ var c = std.http.Client{
+ .allocator = a,
+ };
+ defer c.deinit();
+
+ var q = std.StringHashMap([]const u8).init(a);
+ defer q.deinit();
+ var info_hash = try mi.info.hash(a);
+ var buf_left = [_]u8{0} ** 1024;
+ try q.put("info_hash", &info_hash);
+ try q.put("peer_id", "00112233445566778899");
+ try q.put("port", "6881");
+ try q.put("uploaded", "0");
+ try q.put("downloaded", "0");
+ try q.put("left", try std.fmt.bufPrint(&buf_left, "{}", .{mi.info.files[0].length}));
+ try q.put("compact", "1");
+ var qs = try toqs(a, q);
+ defer a.free(qs);
+ var url = try std.fmt.allocPrint(a, "{s}?{s}", .{ mi.announce, qs });
+ defer a.free(url);
+ var res = try c.fetch(a, .{ .location = .{ .url = url } });
+ defer res.deinit();
+ if (res.status == .ok) {
+ var trb = try bencode.bdecodeBuf(a, res.body.?);
+ defer trb.deinit(a);
+ var tr = try TrackerResp.parse(a, trb);
+ defer tr.deinit(a);
+ var w = std.io.getStdOut().writer();
+ for (tr.peers) |peer| {
+ try std.fmt.format(w, "peer: {}\n", .{peer});
+ }
+ }
+}
+
+fn toqs(a: std.mem.Allocator, hm: std.StringHashMap([]const u8)) ![]const u8 {
+ var al = std.ArrayList(u8).init(a);
+ defer al.deinit();
+ var w = al.writer();
+ var it = hm.iterator();
+ var first = true;
+ while (it.next()) |entry| {
+ if (!first) try w.writeByte('&');
+ try std.Uri.writeEscapedQuery(w, entry.key_ptr.*);
+ try w.writeByte('=');
+ try std.Uri.writeEscapedQuery(w, entry.value_ptr.*);
+ first = false;
+ }
+ return try al.toOwnedSlice();
}
+const TrackerResp = struct {
+ // interval: u64,
+ peers: []std.net.Address,
+
+ pub fn parse(a: std.mem.Allocator, b: bencode.BValue) !TrackerResp {
+ var ipl = std.ArrayList(std.net.Address).init(a);
+ defer ipl.deinit();
+ var d = b.asDict() catch return error.Malformatted;
+ var pb = d.get("peers") orelse return error.Malformatted;
+ var ps = pb.asString() catch return error.Malformatted;
+ if ((ps.len % 6) != 0) return error.Malformatted;
+ for (0..ps.len / 6) |ix| {
+ const start = ix * 6;
+ const port = std.mem.readInt(u16, ps[start + 4 .. start + 6][0..2], .big);
+ var ip = [_]u8{0} ** 4;
+ @memcpy(&ip, ps[start .. start + 4]);
+ try ipl.append(std.net.Address.initIp4(ip, port));
+ }
+ return .{
+ .peers = try ipl.toOwnedSlice(),
+ };
+ }
+
+ pub fn deinit(self: *TrackerResp, a: std.mem.Allocator) void {
+ a.free(self.peers);
+ }
+};
+
test {
- _ = @import("bencode.zig");
- _ = @import("metainfo.zig");
+ _ = bencode;
+ _ = MetaInfo;
}
diff --git a/src/metainfo.zig b/src/metainfo.zig
index 29cc655..cd00438 100644
--- a/src/metainfo.zig
+++ b/src/metainfo.zig
@@ -63,9 +63,13 @@ pub const Info = struct {
const pri = pr.asInt(u1) catch return error.Malformatted;
priv = pri == 1;
}
+ // Validation....
+ const ps = pp.asString() catch return error.Malformatted;
+ if (ps.len % 20 != 0) return error.Malformatted;
+
return .{
.piece_length = pl.asInt(u64) catch return error.Malformatted,
- .pieces = pp.asString() catch return error.Malformatted,
+ .pieces = ps,
.files = try files.toOwnedSlice(),
.private = priv,
};
@@ -112,6 +116,11 @@ pub const Info = struct {
try b.bencode(w);
return sha1.finalResult();
}
+ pub fn pieceHash(self: Info, ix: usize) ?[]const u8 {
+ const start = 20 * ix;
+ if (start >= self.pieces.len) return null;
+ return self.pieces[start .. start + 20];
+ }
};
info: Info,