const std = @import("std"); const MetaInfo = @import("metainfo.zig"); const bencode = @import("bencode.zig"); const AnyWriter = @import("anywriter.zig"); const peerproto = @import("peer_protocol.zig"); const prr = "00112233445566778899"; pub fn main() !void { var peer_id: [20]u8 = undefined; @memcpy(&peer_id, prr); 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", &peer_id); 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); for (tr.peers) |peer| { std.log.info("peer: {}", .{peer}); } // Handle peers... const p = tr.peers[0]; var ps = try std.net.tcpConnectToAddress(p); defer ps.close(); var pw = ps.writer(); var pr = ps.reader(); var hs: peerproto.Handshake = .{ .info_hash = info_hash, .peer_id = peer_id, }; try hs.write(pw); var phs = try peerproto.Handshake.read(pr); std.log.info("peer at {} peer_id {s}", .{ p, std.fmt.fmtSliceHexLower(&phs.peer_id) }); } } 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 { _ = bencode; _ = MetaInfo; }