fr.letroll.ttorrentandroid.common.protocol.http.HTTPAnnounceRequestMessage.java Source code

Java tutorial

Introduction

Here is the source code for fr.letroll.ttorrentandroid.common.protocol.http.HTTPAnnounceRequestMessage.java

Source

/**
 * Copyright (C) 2012 Turn, Inc.
 *
 * 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 fr.letroll.ttorrentandroid.common.protocol.http;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.net.InetAddresses;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.annotation.Nonnull;

import fr.letroll.ttorrentandroid.common.Torrent;
import fr.letroll.ttorrentandroid.common.TorrentUtils;
import fr.letroll.ttorrentandroid.common.protocol.TrackerMessage.AnnounceRequestMessage;

/**
 * The announce request message for the HTTP tracker protocol.
 *
 * <p>
 * This class represents the announce request message in the HTTP tracker
 * protocol. It doesn't add any specific fields compared to the generic
 * announce request message, but it provides the means to parse such
 * messages and craft them.
 * </p>
 *
 * @author mpetazzoni
 */
public class HTTPAnnounceRequestMessage extends HTTPTrackerMessage implements AnnounceRequestMessage {

    private static final Logger LOG = LoggerFactory.getLogger(HTTPAnnounceRequestMessage.class);
    private final byte[] infoHash;
    private final byte[] peerId;
    private final List<InetSocketAddress> peerAddresses;
    private final long uploaded;
    private final long downloaded;
    private final long left;
    private final boolean compact;
    private final boolean noPeerId;
    private final AnnounceEvent event;
    private final int numWant;

    public HTTPAnnounceRequestMessage(byte[] infoHash, byte[] peerId, List<InetSocketAddress> peerAddresses,
            long uploaded, long downloaded, long left, boolean compact, boolean noPeerId, AnnounceEvent event,
            int numWant) {
        if (peerAddresses.isEmpty())
            throw new IllegalArgumentException(
                    "No PeerAddresses specified. Require at least an InetSocketAddress(port).");
        this.infoHash = infoHash;
        this.peerId = peerId;
        this.peerAddresses = peerAddresses;
        this.downloaded = downloaded;
        this.uploaded = uploaded;
        this.left = left;
        this.compact = compact;
        this.noPeerId = noPeerId;
        this.event = event;
        this.numWant = numWant;
    }

    @Override
    public byte[] getInfoHash() {
        return this.infoHash;
    }

    @Override
    public String getHexInfoHash() {
        return TorrentUtils.toHex(this.infoHash);
    }

    @Override
    public byte[] getPeerId() {
        return peerId;
    }

    @Nonnull
    public List<InetSocketAddress> getPeerAddresses() {
        return peerAddresses;
    }

    @Override
    public long getUploaded() {
        return this.uploaded;
    }

    @Override
    public long getDownloaded() {
        return this.downloaded;
    }

    @Override
    public long getLeft() {
        return this.left;
    }

    public boolean getCompact() {
        return this.compact;
    }

    public boolean getNoPeerIds() {
        return this.noPeerId;
    }

    @Override
    public AnnounceEvent getEvent() {
        return this.event;
    }

    @Override
    public int getNumWant() {
        return this.numWant;
    }

    @Nonnull
    @VisibleForTesting
    /* pp */ static String toUrlString(@Nonnull byte[] data) throws UnsupportedEncodingException {
        String text = new String(data, Torrent.BYTE_ENCODING);
        return URLEncoder.encode(text, Torrent.BYTE_ENCODING_NAME);
    }

    @Nonnull
    @VisibleForTesting
    /* pp */ static String toUrlString(@Nonnull InetAddress address, int port) throws UnsupportedEncodingException {
        String text = InetAddresses.toUriString(address);
        if (port != -1)
            text = text + ":" + port;
        return URLEncoder.encode(text, Torrent.BYTE_ENCODING_NAME);
    }

    /**
     * Build the announce request URL for the given tracker announce URL.
     *
     * @param trackerAnnounceURL The tracker's announce URL.
     * @return The URL object representing the announce request URL.
     */
    @Nonnull
    public URI toURI(@Nonnull URI trackerAnnounceURL) throws UnsupportedEncodingException, URISyntaxException {
        String base = trackerAnnounceURL.toString();
        StringBuilder url = new StringBuilder(base);
        url.append(base.contains("?") ? "&" : "?").append("info_hash=").append(toUrlString(getInfoHash()))
                .append("&peer_id=").append(toUrlString(getPeerId()))
                // .append("&port=").append(getPeerAddress().getPort())
                .append("&uploaded=").append(getUploaded()).append("&downloaded=").append(getDownloaded())
                .append("&left=").append(getLeft()).append("&compact=").append(getCompact() ? 1 : 0)
                .append("&no_peer_id=").append(getNoPeerIds() ? 1 : 0);

        if (getEvent() != null && !AnnounceEvent.NONE.equals(getEvent())) {
            url.append("&event=").append(getEvent().getEventName());
        }

        List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>();
        Iterables.addAll(addresses, getPeerAddresses());
        boolean port = false;
        for (InetSocketAddress sockaddr : addresses) {
            InetAddress inaddr = sockaddr.getAddress();
            if (!port) {
                url.append("&port=").append(sockaddr.getPort());
                if (inaddr instanceof Inet4Address)
                    url.append("&ip=").append(toUrlString(inaddr, -1));
                else if (inaddr instanceof Inet6Address)
                    url.append("&ipv6=").append(toUrlString(inaddr, -1));
                port = true;
                continue;
            }
            if (inaddr instanceof Inet4Address)
                url.append("&ipv4=").append(toUrlString(inaddr, sockaddr.getPort()));
            else if (inaddr instanceof Inet6Address)
                url.append("&ipv6=").append(toUrlString(inaddr, sockaddr.getPort()));
        }

        if (getNumWant() != AnnounceRequestMessage.DEFAULT_NUM_WANT)
            url.append("&numwant=").append(getNumWant());

        return new URI(url.toString());
    }

    @VisibleForTesting
    @Nonnull
    /* pp */ static InetSocketAddress toInetSocketAddress(@Nonnull String sockstr, int port) {
        if (sockstr.indexOf(':') == -1)
            return new InetSocketAddress(InetAddresses.forString(sockstr), port);
        if (sockstr.startsWith("[")) {
            int idx = sockstr.indexOf("]:");
            if (idx == -1) // Pure bracket-surrounded IPv6 address.
                return new InetSocketAddress(InetAddresses.forUriString(sockstr), port);
            int port6 = Integer.parseInt(sockstr.substring(idx + 2));
            return new InetSocketAddress(InetAddresses.forUriString(sockstr.substring(0, idx + 1)), port6);
        }
        int idx = sockstr.indexOf(':');
        if (idx != -1) { // IPv4 plus port
            int port4 = Integer.parseInt(sockstr.substring(idx + 1));
            return new InetSocketAddress(InetAddresses.forUriString(sockstr.substring(0, idx)), port4);
        }
        return new InetSocketAddress(InetAddresses.forUriString(sockstr.substring(0, idx)), port);
    }

    @Nonnull
    public static HTTPAnnounceRequestMessage fromParams(@Nonnull Multimap<String, String> params)
            throws MessageValidationException {

        byte[] infoHash = toBytes(params, "info_hash", ErrorMessage.FailureReason.MISSING_HASH);
        byte[] peerId = toBytes(params, "peer_id", ErrorMessage.FailureReason.MISSING_PEER_ID);

        // Default 'uploaded' and 'downloaded' to 0 if the client does
        // not provide it (although it should, according to the spec).
        long uploaded = toLong(params, "uploaded", 0, null);
        long downloaded = toLong(params, "downloaded", 0, null);
        // Default 'left' to -1 to avoid peers entering the COMPLETED
        // state when they don't provide the 'left' parameter.
        long left = toLong(params, "left", -1, null);

        boolean compact = toBoolean(params, "compact");
        boolean noPeerId = toBoolean(params, "no_peer_id");

        int numWant = toInt(params, "numwant", AnnounceRequestMessage.DEFAULT_NUM_WANT, null);

        AnnounceEvent event = AnnounceEvent.NONE;
        if (params.containsKey("event"))
            event = AnnounceEvent.getByName(toString(params, "event", null));

        List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>();
        int port = toInt(params, "port", -1, ErrorMessage.FailureReason.MISSING_PORT);

        MAIN: {
            String ip = toString(params, "ip", null);
            if (ip != null)
                addresses.add(new InetSocketAddress(InetAddresses.forString(ip), port));
        }

        IP4: {
            Collection<String> ips = params.get("ipv4");
            if (ips != null)
                for (String ip : ips)
                    addresses.add(toInetSocketAddress(ip, port));
        }

        IP6: {
            Collection<String> ips = params.get("ipv6");
            if (ips != null)
                for (String ip : ips)
                    addresses.add(toInetSocketAddress(ip, port));
        }

        DEFAULT: {
            if (addresses.isEmpty())
                addresses.add(new InetSocketAddress(port));
        }

        return new HTTPAnnounceRequestMessage(infoHash, peerId, addresses, uploaded, downloaded, left, compact,
                noPeerId, event, numWant);
    }

    @Override
    public String toString() {
        return Objects.toStringHelper(this).add("infoHash", getHexInfoHash())
                .add("peerId", TorrentUtils.toHex(peerId)).add("peerAddresses", peerAddresses)
                .add("uploaded", uploaded).add("downloaded", downloaded).add("left", left).add("compact", compact)
                .add("noPeerId", noPeerId).add("event", event).add("numWant", numWant).toString();
    }
}