blob: 2d3850dec551a2a848ee85b7604f2f41ee4dfc3c [file] [log] [blame]
223011028fd30b82025-06-05 18:02:21 +08001package com.pt.utils;
2
3import java.io.ByteArrayOutputStream;
4import java.io.IOException;
5import java.io.OutputStream;
6import java.net.InetAddress;
7import java.nio.ByteBuffer;
8import java.nio.charset.StandardCharsets;
9import java.util.*;
10
11public class BencodeUtils {
12
13 // 通用bencode编码接口
14 public static void encode(Object obj, OutputStream out) throws IOException {
15 if (obj instanceof String) {
16 encodeString((String) obj, out);
17 } else if (obj instanceof Number) {
18 encodeInteger(((Number) obj).longValue(), out);
19 } else if (obj instanceof byte[]) {
20 encodeBytes((byte[]) obj, out);
21 } else if (obj instanceof List) {
22 encodeList((List<?>) obj, out);
23 } else if (obj instanceof Map) {
24 encodeMap((Map<String, Object>) obj, out);
25 } else {
26 throw new IllegalArgumentException("Unsupported type: " + obj.getClass());
27 }
28 }
29
30 public static byte[] encode(Object obj) {
31 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
32 encode(obj, baos);
33 return baos.toByteArray();
34 } catch (IOException e) {
35 throw new RuntimeException(e);
36 }
37 }
38
39 private static void encodeString(String s, OutputStream out) throws IOException {
40 byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
41 out.write(String.valueOf(bytes.length).getBytes(StandardCharsets.US_ASCII));
42 out.write(':');
43 out.write(bytes);
44 }
45
46 private static void encodeBytes(byte[] bytes, OutputStream out) throws IOException {
47 out.write(String.valueOf(bytes.length).getBytes(StandardCharsets.US_ASCII));
48 out.write(':');
49 out.write(bytes);
50 }
51
52 private static void encodeInteger(long value, OutputStream out) throws IOException {
53 out.write('i');
54 out.write(Long.toString(value).getBytes(StandardCharsets.US_ASCII));
55 out.write('e');
56 }
57
58 private static void encodeList(List<?> list, OutputStream out) throws IOException {
59 out.write('l');
60 for (Object item : list) {
61 encode(item, out);
62 }
63 out.write('e');
64 }
65
66 private static void encodeMap(Map<String, Object> map, OutputStream out) throws IOException {
67 out.write('d');
68 List<String> keys = new ArrayList<>(map.keySet());
69 Collections.sort(keys); // bencode字典必须按key排序
70 for (String key : keys) {
71 encodeString(key, out);
72 encode(map.get(key), out);
73 }
74 out.write('e');
75 }
76
77 // 构造单个compact peer的二进制格式 (4字节IP + 2字节端口)
78 public static byte[] buildCompactPeer(String ip, int port) {
79 try {
80 InetAddress addr = InetAddress.getByName(ip);
81 ByteBuffer buffer = ByteBuffer.allocate(6);
82 buffer.put(addr.getAddress());
83 buffer.putShort((short) port);
84 return buffer.array();
85 } catch (IOException e) {
86 throw new RuntimeException(e);
87 }
88 }
89
90 // 构造多个compact peer的二进制拼接
91 public static byte[] buildCompactPeers(List<String> ips, List<Integer> ports) {
92 if (ips.size() != ports.size()) throw new IllegalArgumentException("IPs and ports list size mismatch");
93 ByteArrayOutputStream out = new ByteArrayOutputStream();
94 for (int i = 0; i < ips.size(); i++) {
95 out.write(buildCompactPeer(ips.get(i), ports.get(i)), 0, 6);
96 }
97 return out.toByteArray();
98 }
99
100 // 构造tracker响应字典,至少包含interval和peers
101 public static byte[] buildTrackerResponse(int interval, byte[] peersCompact) {
102 Map<String, Object> dict = new LinkedHashMap<>();
103 dict.put("interval", interval);
104 dict.put("peers", peersCompact);
105 return encode(dict);
106 }
107}