org.aotorrent.common.protocol.tracker.HTTPTrackerResponse.java Source code

Java tutorial

Introduction

Here is the source code for org.aotorrent.common.protocol.tracker.HTTPTrackerResponse.java

Source

/*
 * Copyright 2014 Napolov Dmitry
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.aotorrent.common.protocol.tracker;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.aotorrent.common.Torrent;
import org.aotorrent.common.bencode.BEncodeParser;
import org.aotorrent.common.bencode.BEncodeValue;
import org.aotorrent.common.bencode.BEncodeWriter;
import org.aotorrent.common.bencode.InvalidBEncodingException;

import javax.annotation.Nullable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.*;

/**
 * Project: AOTorrent
 * User:    dmitry
 * Date:    11/8/13
 */
public class HTTPTrackerResponse {
    public static final int DEFAULT_INTERVAL = 1800;
    private static final int SECOND_BYTE = 0x0000ff00;
    private static final int FIRST_BYTE = 0x000000ff;
    private static final String DEFAULT_TRACKER_ID = UUID.randomUUID().toString();
    //    failure reason: If present, then no other keys may be present. The value is a human-readable error message as to why the request failed (string).
    @Nullable
    private final String failureReason;
    //    warning message: (new, optional) Similar to failure reason, but the response still gets processed normally. The warning message is shown just like an error.
    @Nullable
    private final String warningMessage;
    //    interval: Interval in seconds that the client should wait between sending regular requests to the tracker
    private final int interval;
    //    min interval: (optional) Minimum announce interval. If present clients must not reannounce more frequently than this.
    private final int minInterval;
    //    tracker id: A string that the client should send back on its next announcements. If absent and a previous announce sent a tracker id, do not discard the old value; keep using it.
    @Nullable
    private final String trackerId;
    //    complete: number of peers with the entire file, i.e. seeders (integer)
    private final int complete;
    //    incomplete: number of non-seeder peers, aka "leechers" (integer)
    private final int incomplete;
    //    peers: (dictionary model) The value is a list of dictionaries, each with the following keys:
    private final Set<InetSocketAddress> peers = Sets.newHashSet();
    //    peer id: peer's self-selected ID, as described above for the tracker request (string)
    //    ip: peer's IP address either IPv6 (hexed) or IPv4 (dotted quad) or DNS name (string)
    //    port: peer's port number (integer)
    //    peers: (binary model) Instead of using the dictionary model described above, the peers value may be a string consisting of multiples of 6 bytes. First 4 bytes are the IP address and last 2 bytes are the port number. All in network (big endian) notation.

    public HTTPTrackerResponse(ByteBuf data) throws IOException, InvalidBEncodingException {
        Map<String, BEncodeValue> responseMap = BEncodeParser.parse(data);
        if (!responseMap.containsKey("failure reason")) {
            failureReason = null;
            warningMessage = (responseMap.containsKey("warning message"))
                    ? responseMap.get("warning message").getString()
                    : null;
            interval = (int) responseMap.get("interval").getLong();
            minInterval = (responseMap.containsKey("min interval"))
                    ? (int) responseMap.get("min interval").getLong()
                    : interval;
            trackerId = (responseMap.containsKey("tracker id")) ? responseMap.get("tracker id").getString() : null;
            complete = (responseMap.containsKey("complete")) ? (int) responseMap.get("complete").getLong() : 0;
            incomplete = (responseMap.containsKey("incomplete")) ? (int) responseMap.get("incomplete").getLong()
                    : 0;

            if ("String".equals(responseMap.get("peers").getValueClass())) {
                byte[] encodedPeers = responseMap.get("peers").getString()
                        .getBytes(Torrent.DEFAULT_TORRENT_ENCODING);

                if ((encodedPeers.length % 6) > 0) {
                    throw new IllegalStateException("peers has strange length"); //TODO make good exception
                }

                for (int i = 0; i < (encodedPeers.length / 6); i++) {
                    byte[] rawIP = Arrays.copyOfRange(encodedPeers, i * 6, i * 6 + 4);
                    InetAddress ip = InetAddress.getByAddress(rawIP);

                    byte[] rawPort = Arrays.copyOfRange(encodedPeers, i * 6 + 4, i * 6 + 6);
                    int port = ((rawPort[0] << 8) & SECOND_BYTE) | (rawPort[1] & FIRST_BYTE);
                    peers.add(new InetSocketAddress(ip, port));
                }
            }
        } else {
            failureReason = responseMap.get("failure reason").getString();
            warningMessage = null;
            interval = 0;
            minInterval = 0;
            trackerId = null;
            complete = 0;
            incomplete = 0;

        }
    }

    public HTTPTrackerResponse(@Nullable Collection<InetSocketAddress> peers) {
        this.failureReason = null;
        this.warningMessage = null;
        this.interval = DEFAULT_INTERVAL;
        this.minInterval = DEFAULT_INTERVAL;
        this.trackerId = DEFAULT_TRACKER_ID;
        this.complete = 0;
        this.incomplete = 0;
        if (peers != null) {
            this.peers.addAll(peers);
        }
    }

    public ByteBuf toTransmit() throws IOException, InvalidBEncodingException {
        Map<String, BEncodeValue> responseMap = Maps.newHashMap();

        responseMap.put("interval", new BEncodeValue(interval));
        responseMap.put("min_interval", new BEncodeValue(interval));
        responseMap.put("tracker id", new BEncodeValue(trackerId));
        responseMap.put("complete", new BEncodeValue(complete));
        responseMap.put("incomplete", new BEncodeValue(incomplete));

        ByteBuf peersEncoded = Unpooled.buffer(peers.size() * 6);
        for (InetSocketAddress peer : peers) {
            peersEncoded.writeBytes(peer.getAddress().getAddress());
            peersEncoded.writeShort(peer.getPort());
        }

        responseMap.put("peers", new BEncodeValue(
                new String(peersEncoded.array(), Charset.forName(Torrent.DEFAULT_TORRENT_ENCODING))));

        return BEncodeWriter.writeOut(responseMap);
    }

    public int getInterval() {
        return interval;
    }

    @Nullable
    public String getTrackerId() {
        return trackerId;
    }

    public int getComplete() {
        return complete;
    }

    public int getIncomplete() {
        return incomplete;
    }

    public Set<InetSocketAddress> getPeers() {
        return peers;
    }

    public boolean isFailed() {
        return failureReason != null;
    }
}