blob: 2117d43a4e10a68fba2d3155e45eb03e50616a97 [file] [log] [blame]
Edwardsamaxlf1bf7ad2025-06-03 23:52:16 +08001package com.pt.utils;
2
3import java.io.*;
4import java.net.InetAddress;
5import java.nio.ByteBuffer;
6import java.nio.charset.StandardCharsets;
7import java.util.*;
8
9public class BencodeCodec {
10
11 /* ------------- 编码部分 ------------- */
12
13 public static void encode(Object obj, OutputStream out) throws IOException {
14 if (obj instanceof String) {
15 encodeString((String) obj, out);
16 } else if (obj instanceof Number) {
17 encodeInteger(((Number) obj).longValue(), out);
18 } else if (obj instanceof byte[]) {
19 encodeBytes((byte[]) obj, out);
20 } else if (obj instanceof List) {
21 encodeList((List<?>) obj, out);
22 } else if (obj instanceof Map) {
23 encodeMap((Map<String, Object>) obj, out);
24 } else {
25 throw new IllegalArgumentException("Unsupported type: " + obj.getClass());
26 }
27 }
28
29 public static byte[] encode(Object obj) {
30 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
31 encode(obj, baos);
32 return baos.toByteArray();
33 } catch (IOException e) {
34 throw new RuntimeException(e);
35 }
36 }
37
38 private static void encodeString(String s, OutputStream out) throws IOException {
39 byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
40 out.write(String.valueOf(bytes.length).getBytes(StandardCharsets.US_ASCII));
41 out.write(':');
42 out.write(bytes);
43 }
44
45 private static void encodeBytes(byte[] bytes, OutputStream out) throws IOException {
46 out.write(String.valueOf(bytes.length).getBytes(StandardCharsets.US_ASCII));
47 out.write(':');
48 out.write(bytes);
49 }
50
51 private static void encodeInteger(long value, OutputStream out) throws IOException {
52 out.write('i');
53 out.write(Long.toString(value).getBytes(StandardCharsets.US_ASCII));
54 out.write('e');
55 }
56
57 private static void encodeList(List<?> list, OutputStream out) throws IOException {
58 out.write('l');
59 for (Object item : list) {
60 encode(item, out);
61 }
62 out.write('e');
63 }
64
65 private static void encodeMap(Map<String, Object> map, OutputStream out) throws IOException {
66 out.write('d');
67 List<String> keys = new ArrayList<>(map.keySet());
68 Collections.sort(keys); // bencode字典必须按key排序
69 for (String key : keys) {
70 encodeString(key, out);
71 encode(map.get(key), out);
72 }
73 out.write('e');
74 }
75
76 /* ------------- 解码部分 ------------- */
77
78 public static Object decode(byte[] data) throws IOException {
79 try (ByteArrayInputStream in = new ByteArrayInputStream(data)) {
80 in.mark(data.length);
81 return decodeNext(in);
82 }
83 }
84
85 private static Object decodeNext(InputStream in) throws IOException {
86 int prefix = in.read();
87 if (prefix == -1) {
88 throw new IOException("Unexpected end of stream");
89 }
90
91 in.mark(1024);
92
93 if (prefix >= '0' && prefix <= '9') {
94 in.reset();
95 return parseString(in);
96 } else if (prefix == 'i') {
97 return parseInteger(in);
98 } else if (prefix == 'l') {
99 return parseList(in);
100 } else if (prefix == 'd') {
101 return parseDict(in);
102 } else {
103 throw new IOException("Invalid bencode prefix: " + (char) prefix);
104 }
105 }
106
107 private static String parseString(InputStream in) throws IOException {
108 StringBuilder lenStr = new StringBuilder();
109 int b;
110 while ((b = in.read()) != -1 && b != ':') {
111 if (b < '0' || b > '9') {
112 throw new IOException("Invalid string length character: " + (char) b);
113 }
114 lenStr.append((char) b);
115 }
116 if (b == -1) {
117 throw new IOException("Unexpected end of stream reading string length");
118 }
119 int length = Integer.parseInt(lenStr.toString());
120
121 byte[] buf = new byte[length];
122 int offset = 0;
123 while (offset < length) {
124 int read = in.read(buf, offset, length - offset);
125 if (read == -1) {
126 throw new IOException("Unexpected end of stream reading string data");
127 }
128 offset += read;
129 }
130
131 return new String(buf, StandardCharsets.UTF_8);
132 }
133
134 private static long parseInteger(InputStream in) throws IOException {
135 StringBuilder intStr = new StringBuilder();
136 int b;
137 while ((b = in.read()) != -1 && b != 'e') {
138 intStr.append((char) b);
139 }
140 if (b == -1) {
141 throw new IOException("Unexpected end of stream reading integer");
142 }
143 return Long.parseLong(intStr.toString());
144 }
145
146 private static List<Object> parseList(InputStream in) throws IOException {
147 List<Object> list = new ArrayList<>();
148 int b;
149 while (true) {
150 in.mark(1);
151 b = in.read();
152 if (b == -1) {
153 throw new IOException("Unexpected end of stream reading list");
154 }
155 if (b == 'e') {
156 break;
157 }
158 in.reset();
159 list.add(decodeNext(in));
160 }
161 return list;
162 }
163
164 private static Map<String, Object> parseDict(InputStream in) throws IOException {
165 Map<String, Object> map = new LinkedHashMap<>();
166 int b;
167 while (true) {
168 in.mark(1);
169 b = in.read();
170 if (b == -1) {
171 throw new IOException("Unexpected end of stream reading dictionary");
172 }
173 if (b == 'e') {
174 break;
175 }
176 in.reset();
177 String key = (String) decodeNext(in);
178 Object value = decodeNext(in);
179 map.put(key, value);
180 }
181 return map;
182 }
183
184 /* ------------- 其他辅助方法 ------------- */
185
186 // 构造单个compact peer的二进制格式 (4字节IP + 2字节端口)
187 public static byte[] buildCompactPeer(String ip, int port) {
188 try {
189 InetAddress addr = InetAddress.getByName(ip);
190 ByteBuffer buffer = ByteBuffer.allocate(6);
191 buffer.put(addr.getAddress());
192 buffer.putShort((short) port);
193 return buffer.array();
194 } catch (IOException e) {
195 throw new RuntimeException(e);
196 }
197 }
198
199 // 构造多个compact peer的二进制拼接
200 public static byte[] buildCompactPeers(List<String> ips, List<Integer> ports) {
201 if (ips.size() != ports.size()) throw new IllegalArgumentException("IPs and ports list size mismatch");
202 ByteArrayOutputStream out = new ByteArrayOutputStream();
203 for (int i = 0; i < ips.size(); i++) {
204 out.write(buildCompactPeer(ips.get(i), ports.get(i)), 0, 6);
205 }
206 return out.toByteArray();
207 }
208
209 // 构造tracker响应字典,至少包含interval和peers
210 public static byte[] buildTrackerResponse(int interval, byte[] peersCompact) {
211 Map<String, Object> dict = new LinkedHashMap<>();
212 dict.put("interval", interval);
213 dict.put("peers", peersCompact);
214 return encode(dict);
215 }
216}