Java tutorial
/* * 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; } }