Guest User

ReTrackerLocal

a guest
Aug 8th, 2013
695
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  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. }
RAW Paste Data

Adblocker detected! Please consider disabling it...

We've detected AdBlock Plus or some other adblocking software preventing Pastebin.com from fully loading.

We don't have any obnoxious sound, or popup ads, we actively block these annoying types of ads!

Please add Pastebin.com to your ad blocker whitelist or disable your adblocking software.

×