Advertisement
Guest User

ReTrackerLocal

a guest
Aug 8th, 2013
790
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 30.21 KB | None | 0 0
  1. /*
  2. Общая мысль такая:
  3.  При массовом закрытии треккеров самое большое горе в потере/уходе пиров. Можно поднять новый треккер,
  4.  но это потребует обновить все торренты у раздающих.
  5.  Возник следующий вариант решения проблемы:
  6.   - написать специальный локальный (запускаемый на компьютере пользователя) прокси, который необходимо указать в торрент-клиенте.
  7.     такой прокси сможет
  8.         (а) - в случае чего туннелировать запросы к треккерам, обходя блокировки
  9.         (б) - дублировать аннонсы на сторонние открытые треккеры, дабы при убийстве одного аннонсера пиры никуда не делись
  10.     к сожалению с udp-аннонсерами не все так просто:
  11.         использование простого HTTP-прокси в торрент-клиенте блокирует udp-трекеры
  12.         написание перехватывающего SOCKS-прокси для меня счас слишком долго/сложно
  13.  Поэтому дабы с одной стороны не лишать возможности использовать UDP-треккеры и с другой стороны дублировать аннонсы
  14.  на свободных серверах я обратил свое внимание на retracker.local.
  15.  
  16.  Идея в следующем - на компьютере пользователя в HOSTS прописывается 127.0.0.1 retracker.local
  17.  и запускается данная софтинка. Таким образом перехватываются все обращения к ретрекеру.
  18.  Далее при анонсе на ретрекере происходит автоматическое анонсирование на куче свободных трекеров.
  19.  Победа!
  20.  
  21.  
  22.  */
  23.  
  24. import com.sun.net.httpserver.*;
  25.  
  26. import javax.xml.bind.JAXB;
  27. import javax.xml.bind.annotation.XmlAttribute;
  28. import java.io.*;
  29. import java.math.BigInteger;
  30. import java.net.*;
  31. import java.nio.ByteBuffer;
  32. import java.nio.charset.Charset;
  33. import java.nio.charset.StandardCharsets;
  34. import java.util.*;
  35.  
  36. /**
  37.  * Date: 8/7/13
  38.  * Time: 10:13 PM
  39.  */
  40. public final class ReTracker implements HttpHandler {
  41.     private final Config config;
  42.  
  43.     final CacheValue<List<String>> _trackers = new CacheValue<List<String>>(1000 * 60 * 20) {
  44.         @Override
  45.         List<String> get() throws IOException {
  46.             String trackers = HttpUtil.getString(String.format(config.dynamicTrackers, System.currentTimeMillis()), null, null);
  47.             if (config.staticTrackers != null)
  48.                 trackers = trackers + "\n" + config.staticTrackers;
  49.  
  50.             String[] split = trackers.split("\\s+");
  51.             HashSet<String> stringHashSet = new HashSet<String>();
  52.             ArrayList<String> unique = new ArrayList<String>();
  53.             for (String s : split) {
  54.                 if (stringHashSet.add(s.split("/announce")[0]))
  55.                     unique.add(s);
  56.             }
  57.             return unique;
  58.         }
  59.     };
  60.  
  61.     List<String> trackers() throws IOException {
  62.         return this._trackers.value();
  63.     }
  64.  
  65.     final CacheValue<String> myIP = new CacheValue<String>(1000 * 60 * 20) {
  66.         @Override
  67.         String get() throws IOException {
  68.             return HttpUtil.getString(String.format(config.whatIsMyIP, System.currentTimeMillis()), null, null);
  69.         }
  70.     };
  71.  
  72.  
  73.     public ReTracker(Config config) {
  74.         this.config = config;
  75.     }
  76.  
  77.     public static void main(String[] args) {
  78.         try {
  79.             startServer(args);
  80.         } catch (Exception e) {
  81.             e.printStackTrace();
  82.         }
  83.     }
  84.  
  85.     private static void startServer(String[] arg) throws IOException {
  86.         Config config = new Config();
  87.         if (arg.length != 0)
  88.             config = JAXB.unmarshal(new File(arg[0]), Config.class);
  89.         System.out.println(config);
  90.         HttpServer httpServer = HttpServer.create(new InetSocketAddress(config.listenPort), 1024);
  91.         httpServer.createContext("/", new ReTracker(config));
  92.         httpServer.setExecutor(null);
  93.         httpServer.start();
  94.     }
  95.  
  96.     @Override
  97.     public void handle(HttpExchange httpExchange) throws IOException {
  98.         //String method = httpExchange.getRequestMethod();
  99.         String requestURI = httpExchange.getRequestURI().getRawQuery();
  100.         requestURI = requestURI.replaceAll("&ip=[^&]*", "") + "&ip=" + myIP.value();
  101.         System.out.println(requestURI);
  102.  
  103.         List<HttpDownloader> downloaders = new ArrayList<HttpDownloader>();
  104.         for (String torrentTracker : trackers()) {
  105.             downloaders.add(HttpDownloader.start(String.format("%s?%s", torrentTracker, requestURI), null));
  106.         }
  107.         Collections.shuffle(downloaders);
  108.         PeerList peerList = new PeerList();
  109.         for (HttpDownloader downloader : downloaders) {
  110.             System.err.println("WAITING: " + downloader.url);
  111.             try {
  112.                 downloader.join();
  113.                 System.err.println("DONE: " + downloader.url);
  114.             } catch (InterruptedException e) {
  115.                 System.err.println("INTERRUPTED: " + downloader.url);
  116.                 e.printStackTrace();
  117.                 continue;
  118.             }
  119.             if (downloader.error != null) continue;
  120.             System.err.println("PARSING: " + downloader.url);
  121.             try {
  122.                 peerList.update(downloader.result);
  123.                 System.err.println("PARSED: " + downloader.url);
  124.             } catch (Throwable ee) {
  125.                 System.err.println("CAN'T PARSE:" + downloader.url);
  126.                 ee.printStackTrace();
  127.             }
  128.         }
  129.         byte[] result = peerList.dump(requestURI.contains("compact=1"));
  130.  
  131.         System.err.println(new String(result));
  132.  
  133.         httpExchange.sendResponseHeaders(200, result.length);
  134.         try (OutputStream responseBody = httpExchange.getResponseBody()) {
  135.             responseBody.write(result);
  136.             responseBody.flush();
  137.         }
  138.     }
  139. }
  140.  
  141. abstract class CacheValue<T> {
  142.     long next = 0;
  143.     final int ttl;
  144.     T val;
  145.  
  146.     public CacheValue(int ttl) {
  147.         this.ttl = ttl;
  148.     }
  149.  
  150.     abstract T get() throws IOException;
  151.  
  152.     public synchronized T value() throws IOException {
  153.         if (System.currentTimeMillis() > next) {
  154.             val = get();
  155.             next = System.currentTimeMillis() + next;
  156.         }
  157.         return val;
  158.     }
  159. }
  160.  
  161. class Peer {
  162.     final InetSocketAddress socketAddress;
  163.     final boolean ip6;
  164.     String id;
  165.  
  166.  
  167.     Peer(InetSocketAddress socketAddress) {
  168.         this.socketAddress = socketAddress;
  169.         ip6 = !(socketAddress.getAddress() instanceof Inet4Address);
  170.     }
  171.  
  172.     @Override
  173.     public boolean equals(Object o) {
  174.         if (this == o) return true;
  175.         if (!(o instanceof Peer)) return false;
  176.  
  177.         Peer peer = (Peer) o;
  178.  
  179.         if (!socketAddress.equals(peer.socketAddress)) return false;
  180.  
  181.         return true;
  182.     }
  183.  
  184.     @Override
  185.     public int hashCode() {
  186.         return socketAddress.hashCode();
  187.     }
  188. }
  189.  
  190.  
  191. class PeerList {
  192.     HashSet<Peer> peers = new HashSet<Peer>();
  193.     long complete = 0;
  194.     long incomplete = 0;
  195.     long maxInterval = 5;
  196.     long min_maxInterval = 5;
  197.     boolean has6 = false;
  198.  
  199.     public void update(byte[] bytes) {
  200.         try {
  201.             Map<String, BEValue> map = BDecoder.bdecode(bytes).getMap();
  202.             BEValue beValue = map.get("interval");
  203.             if (beValue != null) maxInterval = Math.max(maxInterval, beValue.getLong());
  204.             beValue = map.get("min_interval");
  205.             if (beValue != null) min_maxInterval = Math.max(min_maxInterval, beValue.getLong());
  206.             min_maxInterval = Math.min(maxInterval, min_maxInterval);
  207.  
  208.             beValue = map.get("complete");
  209.             if (beValue != null) complete += beValue.getLong();
  210.  
  211.             beValue = map.get("incomplete");
  212.             if (beValue != null) incomplete += beValue.getLong();
  213.  
  214.             BEValue peersRaw = map.get("peers");
  215.             if (peersRaw != null) {
  216.                 try {
  217.                     // First attempt to decode a compact response, since we asked
  218.                     // for it.
  219.                     byte[] data = peersRaw.getBytes();
  220.                     if (data.length % 6 == 0) {
  221.                         ByteBuffer peers = ByteBuffer.wrap(data);
  222.  
  223.                         for (int i = 0; i < data.length / 6; i++) {
  224.                             byte[] ipBytes = new byte[4];
  225.                             peers.get(ipBytes);
  226.                             InetAddress ip = InetAddress.getByAddress(ipBytes);
  227.                             int port =
  228.                                     (0xFF & (int) peers.get()) << 8 |
  229.                                             (0xFF & (int) peers.get());
  230.  
  231.                             addPeer(new Peer(new InetSocketAddress(ip, port)));
  232.                         }
  233.                     }
  234.                 } catch (InvalidBEncodingException ibee) {
  235.                     // Fall back to peer list, non-compact response, in case the
  236.                     // tracker did not support compact responses.
  237.                     List<BEValue> peersRawList = peersRaw.getList();
  238.                     for (BEValue peerRaw : peersRawList) {
  239.                         Map<String, BEValue> peerInfo = peerRaw.getMap();
  240.                         Peer pv4 = new Peer(
  241.                                 new InetSocketAddress(peerInfo.get("ip").getString(StandardCharsets.ISO_8859_1), peerInfo.get("port").getInt())
  242.                         );
  243.                         if (!addPeer(pv4)) continue;
  244.                         BEValue id = peerInfo.get("id");
  245.                         if (id != null) pv4.id = id.getString(StandardCharsets.ISO_8859_1);
  246.                     }
  247.                 }
  248.             }
  249.  
  250.         } catch (Exception e) {
  251.             e.printStackTrace();
  252.         }
  253.     }
  254.  
  255.     boolean addPeer(Peer peer) {
  256.         has6 = has6 || peer.ip6;
  257.         return peers.add(peer);
  258.     }
  259.  
  260.     public byte[] dump(boolean compactForm) throws IOException {
  261.         Map<String, BEValue> responseMap = new HashMap<String, BEValue>();
  262.         if (complete > 0) responseMap.put("complete", new BEValue(this.complete));
  263.         if (incomplete > 0) responseMap.put("incomplete", new BEValue(this.incomplete));
  264.         responseMap.put("interval", new BEValue(this.maxInterval));
  265.         responseMap.put("min_interval", new BEValue(this.min_maxInterval));
  266.  
  267.         // Т.к. ретрекер локальный компактную форму можно проигнорировать ;)
  268.         if (compactForm && has6) {
  269.             writeFullList(responseMap);
  270.         } else {
  271.             writeFullList(responseMap);
  272.         }
  273.         return BEncoder.bencode(responseMap).array();
  274.     }
  275.  
  276.     private void writeFullList(Map<String, BEValue> responseMap) {
  277.         List<BEValue> peers = new ArrayList<BEValue>();
  278.         for (Peer peer : this.peers) {
  279.             if (peer.ip6) continue;
  280.             Map<String, BEValue> peerMap = new HashMap<String, BEValue>();
  281.             if (peer.id != null) peerMap.put("id", new BEValue(peer.id, StandardCharsets.ISO_8859_1));
  282.             peerMap.put("ip", new BEValue(peer.socketAddress.getAddress().getHostAddress(), StandardCharsets.ISO_8859_1));
  283.             peerMap.put("port", new BEValue(peer.socketAddress.getPort()));
  284.             peers.add(new BEValue(peerMap));
  285.         }
  286.         responseMap.put("peers", new BEValue(peers));
  287.     }
  288. }
  289.  
  290. class HttpDownloader extends Thread {
  291.     final String url;
  292.     final Proxy proxy;
  293.     public byte[] result;
  294.     public Exception error;
  295.  
  296.     public static HttpDownloader start(String url, Proxy proxy) {
  297.         HttpDownloader a = new HttpDownloader(url, proxy);
  298.         a.start();
  299.         return a;
  300.     }
  301.  
  302.     HttpDownloader(String url, Proxy proxy) {
  303.         super(url);
  304.         this.url = url;
  305.         this.proxy = proxy;
  306.     }
  307.  
  308.     @Override
  309.     public void run() {
  310.         try {
  311.             // System.err.println("STARTING: " + url);
  312.             result = HttpUtil.getBytes(url, proxy);
  313.             // System.err.println("COMPLETE: " + url);
  314.         } catch (IOException e) {
  315.             System.err.println("ERROR: " + url);
  316.             e.printStackTrace();
  317.             error = e;
  318.         }
  319.     }
  320. }
  321.  
  322. class HttpUtil {
  323.     public static String getString(String url, Proxy proxy, Charset charset) throws IOException {
  324.         return new String(getBytes(url, proxy), charset == null ? StandardCharsets.UTF_8 : charset);
  325.     }
  326.  
  327.     public static byte[] getBytes(String url, Proxy proxy) throws IOException {
  328.         URL u = new URL(url);
  329.         HttpURLConnection cn = (HttpURLConnection) u.openConnection(proxy == null ? Proxy.NO_PROXY : proxy);
  330.         cn.setUseCaches(false);
  331.         cn.setAllowUserInteraction(false);
  332.         cn.setReadTimeout(60 * 1000);
  333.         cn.connect();
  334.         try {
  335.             byte[] b = new byte[1024];
  336.             int r;
  337.             try (InputStream i = cn.getInputStream()) {
  338.                 try (ByteArrayOutputStream o = new ByteArrayOutputStream()) {
  339.                     while ((r = i.read(b)) > -1)
  340.                         o.write(b, 0, r);
  341.                     return o.toByteArray();
  342.                 }
  343.             }
  344.         } finally {
  345.             cn.disconnect();
  346.         }
  347.     }
  348. }
  349.  
  350.  
  351. final class Config {
  352.     @XmlAttribute(name = "listen-port")
  353.     public int listenPort = 80;
  354.     @XmlAttribute(name = "udp-trackers")
  355.     public String udpTrackers = "udp://open.demonii.com:1337/announce " +
  356.             "udp://tracker.publicbt.com:80/announce";
  357.     @XmlAttribute(name = "static-trackers")
  358.     public String staticTrackers = "http://announce.torrentsmd.com:8080/announce " +
  359.             "http://www.h33t.com:3310/announce " +
  360.             "http://bt.eutorrents.com/announce.php " +
  361.             "http://announce.opensharing.org:2710/announce";
  362.     @XmlAttribute(name = "dynamic-trackers")
  363.     public String dynamicTrackers = "http://www.trackon.org/api/live?%d";
  364.     @XmlAttribute(name = "what-is-my-ip")
  365.     public String whatIsMyIP = "http://bot.whatismyipaddress.com/?%d";
  366.  
  367.  
  368.     @Override
  369.     public String toString() {
  370.         return "Config{" +
  371.                 "listenPort=" + listenPort +
  372.                 ", staticTrackers='" + staticTrackers + '\'' +
  373.                 ", dynamicTrackers='" + dynamicTrackers + '\'' +
  374.                 ", whatIsMyIP='" + whatIsMyIP + '\'' +
  375.                 '}';
  376.     }
  377. }
  378.  
  379. class InvalidBEncodingException extends IOException {
  380.  
  381.     public static final long serialVersionUID = -1;
  382.  
  383.     public InvalidBEncodingException(String message) {
  384.         super(message);
  385.     }
  386. }
  387.  
  388. class BEValue {
  389.  
  390.     /**
  391.      * The B-encoded value can be a byte array, a Number, a List or a Map.
  392.      * Lists and Maps contains BEValues too.
  393.      */
  394.     private final Object value;
  395.  
  396.     public BEValue(byte[] value) {
  397.         this.value = value;
  398.     }
  399.  
  400.     public BEValue(String value) throws UnsupportedEncodingException {
  401.         this.value = value.getBytes(StandardCharsets.UTF_8);
  402.     }
  403.  
  404.     public BEValue(String value, Charset enc) {
  405.         this.value = value.getBytes(enc);
  406.     }
  407.  
  408.     public BEValue(int value) {
  409.         this.value = value;
  410.     }
  411.  
  412.     public BEValue(long value) {
  413.         this.value = value;
  414.     }
  415.  
  416.     public BEValue(Number value) {
  417.         this.value = value;
  418.     }
  419.  
  420.     public BEValue(List<BEValue> value) {
  421.         this.value = value;
  422.     }
  423.  
  424.     public BEValue(Map<String, BEValue> value) {
  425.         this.value = value;
  426.     }
  427.  
  428.     public Object getValue() {
  429.         return this.value;
  430.     }
  431.  
  432.     /**
  433.      * Returns this BEValue as a String, interpreted as UTF-8.
  434.      *
  435.      * @throws InvalidBEncodingException If the value is not a byte[].
  436.      */
  437.     public String getString() throws InvalidBEncodingException {
  438.         return this.getString(StandardCharsets.UTF_8);
  439.     }
  440.  
  441.     /**
  442.      * Returns this BEValue as a String, interpreted with the specified
  443.      * encoding.
  444.      *
  445.      * @param encoding The encoding to interpret the bytes as when converting
  446.      *                 them into a {@link String}.
  447.      * @throws InvalidBEncodingException If the value is not a byte[].
  448.      */
  449.     public String getString(Charset encoding) throws InvalidBEncodingException {
  450.         try {
  451.             return new String(this.getBytes(), encoding);
  452.         } catch (ClassCastException cce) {
  453.             throw new InvalidBEncodingException(cce.toString());
  454.         }
  455.     }
  456.  
  457.     /**
  458.      * Returns this BEValue as a byte[].
  459.      *
  460.      * @throws InvalidBEncodingException If the value is not a byte[].
  461.      */
  462.     public byte[] getBytes() throws InvalidBEncodingException {
  463.         try {
  464.             return (byte[]) this.value;
  465.         } catch (ClassCastException cce) {
  466.             throw new InvalidBEncodingException(cce.toString());
  467.         }
  468.     }
  469.  
  470.     /**
  471.      * Returns this BEValue as a Number.
  472.      *
  473.      * @throws InvalidBEncodingException If the value is not a {@link Number}.
  474.      */
  475.     public Number getNumber() throws InvalidBEncodingException {
  476.         try {
  477.             return (Number) this.value;
  478.         } catch (ClassCastException cce) {
  479.             throw new InvalidBEncodingException(cce.toString());
  480.         }
  481.     }
  482.  
  483.     /**
  484.      * Returns this BEValue as short.
  485.      *
  486.      * @throws InvalidBEncodingException If the value is not a {@link Number}.
  487.      */
  488.     public short getShort() throws InvalidBEncodingException {
  489.         return this.getNumber().shortValue();
  490.     }
  491.  
  492.     /**
  493.      * Returns this BEValue as int.
  494.      *
  495.      * @throws InvalidBEncodingException If the value is not a {@link Number}.
  496.      */
  497.     public int getInt() throws InvalidBEncodingException {
  498.         return this.getNumber().intValue();
  499.     }
  500.  
  501.     /**
  502.      * Returns this BEValue as long.
  503.      *
  504.      * @throws InvalidBEncodingException If the value is not a {@link Number}.
  505.      */
  506.     public long getLong() throws InvalidBEncodingException {
  507.         return this.getNumber().longValue();
  508.     }
  509.  
  510.     /**
  511.      * Returns this BEValue as a List of BEValues.
  512.      *
  513.      * @throws InvalidBEncodingException If the value is not an
  514.      *                                   {@link ArrayList}.
  515.      */
  516.     @SuppressWarnings("unchecked")
  517.     public List<BEValue> getList() throws InvalidBEncodingException {
  518.         if (this.value instanceof ArrayList) {
  519.             return (ArrayList<BEValue>) this.value;
  520.         } else {
  521.             throw new InvalidBEncodingException("Excepted List<BEvalue> !");
  522.         }
  523.     }
  524.  
  525.     /**
  526.      * Returns this BEValue as a Map of String keys and BEValue values.
  527.      *
  528.      * @throws InvalidBEncodingException If the value is not a {@link HashMap}.
  529.      */
  530.     @SuppressWarnings("unchecked")
  531.     public Map<String, BEValue> getMap() throws InvalidBEncodingException {
  532.         if (this.value instanceof HashMap) {
  533.             return (Map<String, BEValue>) this.value;
  534.         } else {
  535.             throw new InvalidBEncodingException("Expected Map<String, BEValue> !");
  536.         }
  537.     }
  538. }
  539.  
  540. class BDecoder {
  541.  
  542.     // The InputStream to BDecode.
  543.     private final InputStream in;
  544.  
  545.     // The last indicator read.
  546.     // Zero if unknown.
  547.     // '0'..'9' indicates a byte[].
  548.     // 'i' indicates an Number.
  549.     // 'l' indicates a List.
  550.     // 'd' indicates a Map.
  551.     // 'e' indicates end of Number, List or Map (only used internally).
  552.     // -1 indicates end of stream.
  553.     // Call getNextIndicator to get the current value (will never return zero).
  554.     private int indicator = 0;
  555.  
  556.     /**
  557.      * Initializes a new BDecoder.
  558.      * <p/>
  559.      * <p>
  560.      * Nothing is read from the given <code>InputStream</code> yet.
  561.      * </p>
  562.      *
  563.      * @param in The input stream to read from.
  564.      */
  565.     public BDecoder(InputStream in) {
  566.         this.in = in;
  567.     }
  568.  
  569.     /**
  570.      * Decode a B-encoded stream.
  571.      * <p/>
  572.      * <p>
  573.      * Automatically instantiates a new BDecoder for the provided input stream
  574.      * and decodes its root member.
  575.      * </p>
  576.      *
  577.      * @param in The input stream to read from.
  578.      */
  579.     public static BEValue bdecode(InputStream in) throws IOException {
  580.         return new BDecoder(in).bdecode();
  581.     }
  582.  
  583.     /**
  584.      * Decode a B-encoded byte buffer.
  585.      * <p/>
  586.      * <p>
  587.      * Automatically instantiates a new BDecoder for the provided buffer and
  588.      * decodes its root member.
  589.      * </p>
  590.      *
  591.      * @param data The {@link ByteBuffer} to read from.
  592.      */
  593.     public static BEValue bdecode(ByteBuffer data) throws IOException {
  594.         return BDecoder.bdecode(data.array());
  595.     }
  596.  
  597.     public static BEValue bdecode(byte[] data) throws IOException {
  598.         return BDecoder.bdecode(new ByteArrayInputStream(data));
  599.     }
  600.  
  601.     /**
  602.      * Returns what the next b-encoded object will be on the stream or -1
  603.      * when the end of stream has been reached.
  604.      * <p/>
  605.      * <p>
  606.      * Can return something unexpected (not '0' .. '9', 'i', 'l' or 'd') when
  607.      * the stream isn't b-encoded.
  608.      * </p>
  609.      * <p/>
  610.      * This might or might not read one extra byte from the stream.
  611.      */
  612.     private int getNextIndicator() throws IOException {
  613.         if (this.indicator == 0) {
  614.             this.indicator = in.read();
  615.         }
  616.         return this.indicator;
  617.     }
  618.  
  619.     /**
  620.      * Gets the next indicator and returns either null when the stream
  621.      * has ended or b-decodes the rest of the stream and returns the
  622.      * appropriate BEValue encoded object.
  623.      */
  624.     public BEValue bdecode() throws IOException {
  625.         if (this.getNextIndicator() == -1)
  626.             return null;
  627.  
  628.         if (this.indicator >= '0' && this.indicator <= '9')
  629.             return this.bdecodeBytes();
  630.         else if (this.indicator == 'i')
  631.             return this.bdecodeNumber();
  632.         else if (this.indicator == 'l')
  633.             return this.bdecodeList();
  634.         else if (this.indicator == 'd')
  635.             return this.bdecodeMap();
  636.         else
  637.             throw new InvalidBEncodingException
  638.                     ("Unknown indicator '" + this.indicator + "'");
  639.     }
  640.  
  641.     /**
  642.      * Returns the next b-encoded value on the stream and makes sure it is a
  643.      * byte array.
  644.      *
  645.      * @throws InvalidBEncodingException If it is not a b-encoded byte array.
  646.      */
  647.     public BEValue bdecodeBytes() throws IOException {
  648.         int c = this.getNextIndicator();
  649.         int num = c - '0';
  650.         if (num < 0 || num > 9)
  651.             throw new InvalidBEncodingException("Number expected, not '"
  652.                     + (char) c + "'");
  653.         this.indicator = 0;
  654.  
  655.         c = this.read();
  656.         int i = c - '0';
  657.         while (i >= 0 && i <= 9) {
  658.             // This can overflow!
  659.             num = num * 10 + i;
  660.             c = this.read();
  661.             i = c - '0';
  662.         }
  663.  
  664.         if (c != ':') {
  665.             throw new InvalidBEncodingException("Colon expected, not '" +
  666.                     (char) c + "'");
  667.         }
  668.  
  669.         return new BEValue(read(num));
  670.     }
  671.  
  672.     /**
  673.      * Returns the next b-encoded value on the stream and makes sure it is a
  674.      * number.
  675.      *
  676.      * @throws InvalidBEncodingException If it is not a number.
  677.      */
  678.     public BEValue bdecodeNumber() throws IOException {
  679.         int c = this.getNextIndicator();
  680.         if (c != 'i') {
  681.             throw new InvalidBEncodingException("Expected 'i', not '" +
  682.                     (char) c + "'");
  683.         }
  684.         this.indicator = 0;
  685.  
  686.         c = this.read();
  687.         if (c == '0') {
  688.             c = this.read();
  689.             if (c == 'e')
  690.                 return new BEValue(BigInteger.ZERO);
  691.             else
  692.                 throw new InvalidBEncodingException("'e' expected after zero," +
  693.                         " not '" + (char) c + "'");
  694.         }
  695.  
  696.         // We don't support more the 255 char big integers
  697.         char[] chars = new char[256];
  698.         int off = 0;
  699.  
  700.         if (c == '-') {
  701.             c = this.read();
  702.             if (c == '0')
  703.                 throw new InvalidBEncodingException("Negative zero not allowed");
  704.             chars[off] = '-';
  705.             off++;
  706.         }
  707.  
  708.         if (c < '1' || c > '9')
  709.             throw new InvalidBEncodingException("Invalid Integer start '"
  710.                     + (char) c + "'");
  711.         chars[off] = (char) c;
  712.         off++;
  713.  
  714.         c = this.read();
  715.         int i = c - '0';
  716.         while (i >= 0 && i <= 9) {
  717.             chars[off] = (char) c;
  718.             off++;
  719.             c = read();
  720.             i = c - '0';
  721.         }
  722.  
  723.         if (c != 'e')
  724.             throw new InvalidBEncodingException("Integer should end with 'e'");
  725.  
  726.         String s = new String(chars, 0, off);
  727.         return new BEValue(new BigInteger(s));
  728.     }
  729.  
  730.     /**
  731.      * Returns the next b-encoded value on the stream and makes sure it is a
  732.      * list.
  733.      *
  734.      * @throws InvalidBEncodingException If it is not a list.
  735.      */
  736.     public BEValue bdecodeList() throws IOException {
  737.         int c = this.getNextIndicator();
  738.         if (c != 'l') {
  739.             throw new InvalidBEncodingException("Expected 'l', not '" +
  740.                     (char) c + "'");
  741.         }
  742.         this.indicator = 0;
  743.  
  744.         List<BEValue> result = new ArrayList<BEValue>();
  745.         c = this.getNextIndicator();
  746.         while (c != 'e') {
  747.             result.add(this.bdecode());
  748.             c = this.getNextIndicator();
  749.         }
  750.         this.indicator = 0;
  751.  
  752.         return new BEValue(result);
  753.     }
  754.  
  755.     /**
  756.      * Returns the next b-encoded value on the stream and makes sure it is a
  757.      * map (dictionary).
  758.      *
  759.      * @throws InvalidBEncodingException If it is not a map.
  760.      */
  761.     public BEValue bdecodeMap() throws IOException {
  762.         int c = this.getNextIndicator();
  763.         if (c != 'd') {
  764.             throw new InvalidBEncodingException("Expected 'd', not '" +
  765.                     (char) c + "'");
  766.         }
  767.         this.indicator = 0;
  768.  
  769.         Map<String, BEValue> result = new HashMap<String, BEValue>();
  770.         c = this.getNextIndicator();
  771.         while (c != 'e') {
  772.             // Dictionary keys are always strings.
  773.             String key = this.bdecode().getString();
  774.  
  775.             BEValue value = this.bdecode();
  776.             result.put(key, value);
  777.  
  778.             c = this.getNextIndicator();
  779.         }
  780.         this.indicator = 0;
  781.  
  782.         return new BEValue(result);
  783.     }
  784.  
  785.     /**
  786.      * Returns the next byte read from the InputStream (as int).
  787.      *
  788.      * @throws EOFException If InputStream.read() returned -1.
  789.      */
  790.     private int read() throws IOException {
  791.         int c = this.in.read();
  792.         if (c == -1)
  793.             throw new EOFException();
  794.         return c;
  795.     }
  796.  
  797.     /**
  798.      * Returns a byte[] containing length valid bytes starting at offset zero.
  799.      *
  800.      * @throws EOFException If InputStream.read() returned -1 before all
  801.      *                      requested bytes could be read.  Note that the byte[] returned might be
  802.      *                      bigger then requested but will only contain length valid bytes.  The
  803.      *                      returned byte[] will be reused when this method is called again.
  804.      */
  805.     private byte[] read(int length) throws IOException {
  806.         byte[] result = new byte[length];
  807.  
  808.         int read = 0;
  809.         while (read < length) {
  810.             int i = this.in.read(result, read, length - read);
  811.             if (i == -1)
  812.                 throw new EOFException();
  813.             read += i;
  814.         }
  815.  
  816.         return result;
  817.     }
  818. }
  819.  
  820. class BEncoder {
  821.  
  822.     @SuppressWarnings("unchecked")
  823.     public static void bencode(Object o, OutputStream out)
  824.             throws IOException, IllegalArgumentException {
  825.         if (o instanceof BEValue) {
  826.             o = ((BEValue) o).getValue();
  827.         }
  828.  
  829.         if (o instanceof String) {
  830.             bencode((String) o, out);
  831.         } else if (o instanceof byte[]) {
  832.             bencode((byte[]) o, out);
  833.         } else if (o instanceof Number) {
  834.             bencode((Number) o, out);
  835.         } else if (o instanceof List) {
  836.             bencode((List<BEValue>) o, out);
  837.         } else if (o instanceof Map) {
  838.             bencode((Map<String, BEValue>) o, out);
  839.         } else {
  840.             throw new IllegalArgumentException("Cannot bencode: " +
  841.                     o.getClass());
  842.         }
  843.     }
  844.  
  845.     public static void bencode(String s, OutputStream out) throws IOException {
  846.         byte[] bs = s.getBytes("UTF-8");
  847.         bencode(bs, out);
  848.     }
  849.  
  850.     public static void bencode(Number n, OutputStream out) throws IOException {
  851.         out.write('i');
  852.         String s = n.toString();
  853.         out.write(s.getBytes("UTF-8"));
  854.         out.write('e');
  855.     }
  856.  
  857.     public static void bencode(List<BEValue> l, OutputStream out)
  858.             throws IOException {
  859.         out.write('l');
  860.         for (BEValue value : l) {
  861.             bencode(value, out);
  862.         }
  863.         out.write('e');
  864.     }
  865.  
  866.     public static void bencode(byte[] bs, OutputStream out) throws IOException {
  867.         String l = Integer.toString(bs.length);
  868.         out.write(l.getBytes("UTF-8"));
  869.         out.write(':');
  870.         out.write(bs);
  871.     }
  872.  
  873.     public static void bencode(Map<String, BEValue> m, OutputStream out)
  874.             throws IOException {
  875.         out.write('d');
  876.  
  877.         // Keys must be sorted.
  878.         Set<String> s = m.keySet();
  879.         List<String> l = new ArrayList<String>(s);
  880.         Collections.sort(l);
  881.  
  882.         for (String key : l) {
  883.             Object value = m.get(key);
  884.             bencode(key, out);
  885.             bencode(value, out);
  886.         }
  887.  
  888.         out.write('e');
  889.     }
  890.  
  891.     public static ByteBuffer bencode(Map<String, BEValue> m)
  892.             throws IOException {
  893.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  894.         BEncoder.bencode(m, baos);
  895.         baos.close();
  896.         return ByteBuffer.wrap(baos.toByteArray());
  897.     }
  898. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement