Java tutorial
/* * Copyright 2009 Thomas Bocek * * 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 net.tomp2p.peers; import io.netty.buffer.ByteBuf; import java.io.Serializable; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collection; import java.util.Collections; import net.tomp2p.utils.Utils; /** * A PeerAddress contains the node ID and how to contact this node using both TCP and UDP. This class is thread safe (or * it does not matter if its not). The format looks as follows: * * <pre> * 20 bytes - Number160 * 2 bytes - Header * - 1 byte options: IPv6, firewalled UDP, firewalled TCP, is relayed * - 1 byte relays: * - first 3 bits: number of relays (max 5.) * - second 5 bits: if the 5 relays are IPv6 (bit set) or not (no bit set) * 2 bytes - TCP port * 2 bytes - UDP port * 4 or 16 bytes - Inet Address * 0-5 relays: * - 2 bytes - TCP port * - 2 bytes - UDP port * - 4 or 16 bytes - Inet Address * </pre> * * @author Thomas Bocek */ public final class PeerAddress implements Comparable<PeerAddress>, Serializable { public static final int MAX_SIZE = 142; public static final int MIN_SIZE = 30; private static final long serialVersionUID = -1316622724169272306L; private static final int NET6 = 1; // 0001 private static final int FIREWALL_UDP = 2; // 0010 private static final int FIREWALL_TCP = 4; // 0100 // indicates that a relay is used. // 1000 private static final int RELAYED = 8; // indicates that the peer is slow (because relayed) private static final int SLOW = 16; // indicates the peer forwarded the ports either manually or with UPNP private static final int PORT_FORWARDING = 32; // network information private final Number160 peerId; private final PeerSocketAddress peerSocketAddress; // connection information private final boolean net6; private final boolean firewalledUDP; private final boolean firewalledTCP; private final boolean relayed; private final boolean slow; private final boolean portForwarding; // we can make the hash final as it never changes, and this class is used // multiple times in maps. A new peer is always added to the peermap, so // making this final saves CPU time. The maps used are removedPeerCache, // that is always checked, peerMap that is either added or checked if // already present. Also peers from the neighbor list sent over the wire are // added to the peermap. private final int hashCode; // if deserialized from a byte array using the constructor, then we need to // report how many data we processed. private final int offset; private final int size; private final int relaySize; private final BitSet relayType; private static final BitSet EMPTY_RELAY_TYPE = new BitSet(0); //relays private final Collection<PeerSocketAddress> peerSocketAddresses; public static final Collection<PeerSocketAddress> EMPTY_PEER_SOCKET_ADDRESSES = Collections.emptySet(); private static final int TYPE_BIT_SIZE = 5; private static final int HEADER_SIZE = 2; // count both ports, UDP and TCP private static final int PORTS_SIZE = 4; //TODO: make integrate this private PeerSocketAddress internalPeerSocketAddress; // used for the relay bit shifting private static final int MASK_1F = 0x1f; // 0001 1111 private static final int MASK_07 = 0x7; // 0000 0111 /** * Creates a peer address, where the byte array has to be in the rigth format and in the right size. The new * offset can be accessed with offset(). * * @param me * The serialized array */ public PeerAddress(final byte[] me) { this(me, 0); } /** * Creates a PeerAddress from a continuous byte array. This is useful if you don't know the size beforehand. The new * offset can be accessed with offset(). * * @param me * The serialized array * @param initialOffset * the offset, where to start */ public PeerAddress(final byte[] me, final int initialOffset) { // get the peer ID, this is independent of the type int offset = initialOffset; // get the type final int options = me[offset++] & Utils.MASK_FF; this.net6 = (options & NET6) > 0; this.firewalledUDP = (options & FIREWALL_UDP) > 0; this.firewalledTCP = (options & FIREWALL_TCP) > 0; this.relayed = (options & RELAYED) > 0; this.slow = (options & SLOW) > 0; this.portForwarding = (options & PORT_FORWARDING) > 0; final int relays = me[offset++] & Utils.MASK_FF; // first: three bits are the size 1,2,4 // 000 means no relays // 001 means 1 relay // 010 means 2 relays // 011 means 3 relays // 100 means 4 relays // 101 means 5 relays // 110 is not used // 111 is not used // second: five bits indicate if IPv6 or IPv4 -> in total we can save 5 addresses this.relaySize = (relays >>> TYPE_BIT_SIZE) & MASK_07; final byte b = (byte) (relays & MASK_1F); this.relayType = Utils.createBitSet(b); // now comes the ID final byte[] tmp = new byte[Number160.BYTE_ARRAY_SIZE]; System.arraycopy(me, offset, tmp, 0, Number160.BYTE_ARRAY_SIZE); this.peerId = new Number160(tmp); offset += Number160.BYTE_ARRAY_SIZE; this.peerSocketAddress = PeerSocketAddress.create(me, isIPv4(), offset); offset = this.peerSocketAddress.offset(); if (relaySize > 0) { this.peerSocketAddresses = new ArrayList<PeerSocketAddress>(relaySize); for (int i = 0; i < relaySize; i++) { PeerSocketAddress psa = PeerSocketAddress.create(me, !relayType.get(i), offset); peerSocketAddresses.add(psa); offset = psa.offset(); } } else { this.peerSocketAddresses = EMPTY_PEER_SOCKET_ADDRESSES; } this.size = offset - initialOffset; this.offset = offset; this.hashCode = peerId.hashCode(); } /** * Creates a PeerAddress from a Netty ByteBuf. * * @param channelBuffer * The channel buffer to read from */ public PeerAddress(final ByteBuf channelBuffer) { // get the peer ID, this is independent of the type int readerIndex = channelBuffer.readerIndex(); // get the type final int options = channelBuffer.readUnsignedByte(); this.net6 = (options & NET6) > 0; this.firewalledUDP = (options & FIREWALL_UDP) > 0; this.firewalledTCP = (options & FIREWALL_TCP) > 0; this.relayed = (options & RELAYED) > 0; this.slow = (options & SLOW) > 0; this.portForwarding = (options & PORT_FORWARDING) > 0; final int relays = channelBuffer.readUnsignedByte(); // first: three bits are the size 1,2,4 // 000 means no relays // 001 means 1 relay // 010 means 2 relays // 011 means 3 relays // 100 means 4 relays // 101 means 5 relays // 110 is not used // 111 is not used // second: five bits indicate if IPv6 or IPv4 -> in total we can save 5 addresses this.relaySize = (relays >>> TYPE_BIT_SIZE) & MASK_07; final byte b = (byte) (relays & MASK_1F); this.relayType = Utils.createBitSet(b); // now comes the ID byte[] me = new byte[Number160.BYTE_ARRAY_SIZE]; channelBuffer.readBytes(me); this.peerId = new Number160(me); this.peerSocketAddress = PeerSocketAddress.create(channelBuffer, isIPv4()); if (relaySize > 0) { this.peerSocketAddresses = new ArrayList<PeerSocketAddress>(relaySize); for (int i = 0; i < relaySize; i++) { this.peerSocketAddresses.add(PeerSocketAddress.create(channelBuffer, !relayType.get(i))); } } else { this.peerSocketAddresses = EMPTY_PEER_SOCKET_ADDRESSES; } this.size = channelBuffer.readerIndex() - readerIndex; // not used here this.offset = -1; this.hashCode = peerId.hashCode(); } /** * If you only need to know the id. * * @param id * The id of the peer */ public PeerAddress(final Number160 id) { this(id, (InetAddress) null, -1, -1); } /** * If you only need to know the id and InetAddress. * * @param id * The id of the peer * @param address * The InetAddress of the peer */ public PeerAddress(final Number160 id, final InetAddress address) { this(id, address, -1, -1); } /** * Creates a PeerAddress if all the values are known. * * @param id * The id of the peer * @param peerSocketAddress * The peer socket address including both ports UDP and TCP * @param firewalledUDP * Indicates if peer is not reachable via UDP * @param firewalledTCP * Indicates if peer is not reachable via TCP * @param isRelay * Indicates if peer is used as a relay * @param isSlow * Indicates if a peer is slow * @param peerSocketAddresses * the relay peers */ public PeerAddress(final Number160 id, final PeerSocketAddress peerSocketAddress, final boolean firewalledTCP, final boolean firewalledUDP, final boolean isRelayed, boolean isSlow, boolean portForwarding, final Collection<PeerSocketAddress> peerSocketAddresses) { this.peerId = id; int size = Number160.BYTE_ARRAY_SIZE; this.peerSocketAddress = peerSocketAddress; this.hashCode = id.hashCode(); this.net6 = peerSocketAddress.inetAddress() instanceof Inet6Address; this.firewalledUDP = firewalledUDP; this.firewalledTCP = firewalledTCP; this.relayed = isRelayed; this.slow = isSlow; this.portForwarding = portForwarding; // header + TCP port + UDP port size += HEADER_SIZE + PORTS_SIZE + (net6 ? Utils.IPV6_BYTES : Utils.IPV4_BYTES); if (peerSocketAddresses == null) { this.peerSocketAddresses = EMPTY_PEER_SOCKET_ADDRESSES; this.relayType = EMPTY_RELAY_TYPE; relaySize = 0; } else { relaySize = peerSocketAddresses.size(); if (relaySize > TYPE_BIT_SIZE) { throw new IllegalArgumentException( String.format("Can only store up to %s relay peers. Tried to store %s relay peers.", TYPE_BIT_SIZE, relaySize)); } this.peerSocketAddresses = peerSocketAddresses; this.relayType = new BitSet(relaySize); } int index = 0; for (PeerSocketAddress psa : this.peerSocketAddresses) { boolean isIPV6 = psa.inetAddress() instanceof Inet6Address; this.relayType.set(index, isIPV6); size += psa.size(); index++; } this.size = size; // unused here this.offset = -1; } /** * Facade for PeerAddress(Number160, PeerSocketAddress, boolean, boolean, boolean, Collection-PeerSocketAddress>). * * @param peerId * The id of the peer * @param inetAddress * The internet address of the peer * @param tcpPort * The TCP port of the peer * @param udpPort * The UDP port of the peer */ public PeerAddress(final Number160 peerId, final InetAddress inetAddress, final int tcpPort, final int udpPort) { this(peerId, new PeerSocketAddress(inetAddress, tcpPort, udpPort), false, false, false, false, false, EMPTY_PEER_SOCKET_ADDRESSES); } /** * Facade for {@link #PeerAddress(Number160, InetAddress, int, int, boolean, boolean, PeerSocketAddress[])}. * * @param id * The id of the peer * @param inetAddress * The address of the peer, how to reach this peer * @param tcpPort * The TCP port how to reach the peer * @param udpPort * The UDP port how to reach the peer * @param options * The options for the created PeerAddress. */ public PeerAddress(final Number160 id, final InetAddress inetAddress, final int tcpPort, final int udpPort, final int options) { this(id, new PeerSocketAddress(inetAddress, tcpPort, udpPort), isFirewalledTCP(options), isFirewalledUDP(options), isRelay(options), isSlow(options), isPortForwarding(options), EMPTY_PEER_SOCKET_ADDRESSES); } /** * Facade for PeerAddress(Number160, InetAddress, int, int). * * @param id * The id of the peer * @param address * The address of the peer, how to reach this peer * @param tcpPort * The TCP port how to reach the peer * @param udpPort * The UDP port how to reach the peer * @throws UnknownHostException * If no IP address for the host could be found, or if a scope_id was specified for a global IPv6 * address. */ public PeerAddress(final Number160 id, final String address, final int tcpPort, final int udpPort) throws UnknownHostException { this(id, InetAddress.getByName(address), tcpPort, udpPort); } /** * Facade for PeerAddress(Number160, InetAddress, int, int). * * @param id * The id of the peer * @param inetSocketAddress * The socket address of the peer, how to reach this peer. Both TCP and UDP will be set to the same port */ public PeerAddress(final Number160 id, final InetSocketAddress inetSocketAddress) { this(id, inetSocketAddress.getAddress(), inetSocketAddress.getPort(), inetSocketAddress.getPort()); } /** * When deserializing, we need to know how much we deserialized from the constructor call. * * @return The new offset */ public int offset() { return offset; } /** * @return The size of the serialized peer address */ public int size() { return size; } /** * Serializes to a new array with the proper size. * * @return The serialized representation. */ public byte[] toByteArray() { byte[] me = new byte[size]; toByteArray(me, 0); return me; } /** * Serializes to an existing array. * * @param me * The array where the result should be stored * @param offset * The offset where to start to save the result in the byte array * @return The new offset. */ public int toByteArray(final byte[] me, final int offset) { // save the peer id int newOffset = offset; me[newOffset++] = options(); me[newOffset++] = relays(); newOffset = peerId.toByteArray(me, newOffset); // we store both the addresses of the peer and the relays. Currently this is not needed, as we don't consider // asymmetric relays. But in future we may. newOffset = peerSocketAddress.toByteArray(me, newOffset); for (PeerSocketAddress psa : peerSocketAddresses) { newOffset = psa.toByteArray(me, newOffset); } return newOffset; } public PeerSocketAddress peerSocketAddress() { return peerSocketAddress; } /** * Returns the address or null if no address set. * * @return The address of this peer */ public InetAddress inetAddress() { return peerSocketAddress.inetAddress(); } /** * Creates and returns the socket address using the TCP port. * * @return The socket address how to reach this peer. */ public InetSocketAddress createSocketTCP() { return new InetSocketAddress(peerSocketAddress.inetAddress(), peerSocketAddress.tcpPort()); } /** * Creates and returns the socket address using the UDP port. * * @return The socket address how to reach this peer */ public InetSocketAddress createSocketUDP() { return new InetSocketAddress(peerSocketAddress.inetAddress(), peerSocketAddress.udpPort()); } public InetSocketAddress createSocketUDP(int port) { return new InetSocketAddress(peerSocketAddress.inetAddress(), port); } /** * The id of the peer. A peer cannot change its id. * * @return Id of the peer */ public Number160 peerId() { return peerId; } /** * @return The encoded options */ public byte options() { byte result = 0; if (net6) { result |= NET6; } if (firewalledUDP) { result |= FIREWALL_UDP; } if (firewalledTCP) { result |= FIREWALL_TCP; } if (relayed) { result |= RELAYED; } if (slow) { result |= SLOW; } if (portForwarding) { result |= PORT_FORWARDING; } return result; } /** * @return The encoded relays. There are maximal 5 relays. */ public byte relays() { if (relaySize > 0) { byte result = (byte) (relaySize << TYPE_BIT_SIZE); byte types = Utils.createByte(relayType); result |= (byte) (types & MASK_1F); return result; } return 0; } @Override public String toString() { StringBuilder sb = new StringBuilder("paddr["); sb.append(peerId.toString()).append(peerSocketAddress.toString()).append("]"); sb.append("/relay(").append(relayed); if (relayed) { sb.append(",").append(peerSocketAddresses.size()).append(")=") .append(Arrays.toString(peerSocketAddresses.toArray())); } else { sb.append(")"); } sb.append("/slow(").append(slow).append(")"); return sb.toString(); } @Override public int compareTo(final PeerAddress nodeAddress) { // the id determines if two peers are equal, the address does not matter return peerId.compareTo(nodeAddress.peerId); } @Override public boolean equals(final Object obj) { if (!(obj instanceof PeerAddress)) { return false; } if (this == obj) { return true; } PeerAddress pa = (PeerAddress) obj; return peerId.equals(pa.peerId); } @Override public int hashCode() { // don't calculate all the time, only once. return this.hashCode; } /** * @return UDP port */ public int udpPort() { return peerSocketAddress.udpPort(); } /** * @return TCP port */ public int tcpPort() { return peerSocketAddress.tcpPort(); } /** * @return True if the peer cannot be reached via UDP */ public boolean isFirewalledUDP() { return firewalledUDP; } /** * @return True if the peer cannot be reached via TCP */ public boolean isFirewalledTCP() { return firewalledTCP; } /** * @return True if the inet address is IPv6 */ public boolean isIPv6() { return net6; } /** * @return True if the inet address is IPv4 */ public boolean isIPv4() { return !net6; } /** * @return If this peer address is used as a relay */ public boolean isRelayed() { return relayed; } /** * @return if the peer is slow (might be a relayed mobile device) */ public boolean isSlow() { return slow; } public boolean isPortForwarding() { return portForwarding; } /** * Create a new PeerAddress and change the relayed status. * @param isRelayed * the new relay status * @return The newly created peer address */ public PeerAddress changeRelayed(final boolean isRelayed) { return new PeerAddress(peerId, peerSocketAddress, firewalledTCP, firewalledUDP, isRelayed, slow, portForwarding, peerSocketAddresses).internalPeerSocketAddress(internalPeerSocketAddress); } /** * Create a new PeerAddress and change the slow status * @param isSlow * the new status * @return The newly created peer address */ public PeerAddress changeSlow(final boolean isSlow) { return new PeerAddress(peerId, peerSocketAddress, firewalledTCP, firewalledUDP, relayed, isSlow, portForwarding, peerSocketAddresses).internalPeerSocketAddress(internalPeerSocketAddress); } public PeerAddress changePortForwarding(final boolean portForwarding) { return new PeerAddress(peerId, peerSocketAddress, firewalledTCP, firewalledUDP, relayed, slow, portForwarding, peerSocketAddresses).internalPeerSocketAddress(internalPeerSocketAddress); } /** * Create a new PeerAddress and change the firewall UDP status. * * @param firewalledUDP * the new status * @return The newly created peer address */ public PeerAddress changeFirewalledUDP(final boolean firewalledUDP) { return new PeerAddress(peerId, peerSocketAddress, firewalledTCP, firewalledUDP, relayed, slow, portForwarding, peerSocketAddresses).internalPeerSocketAddress(internalPeerSocketAddress); } /** * Create a new PeerAddress and change the firewall TCP status. * * @param firewalledTCP * the new status * @return The newly created peer address */ public PeerAddress changeFirewalledTCP(final boolean firewalledTCP) { return new PeerAddress(peerId, peerSocketAddress, firewalledTCP, firewalledUDP, relayed, slow, portForwarding, peerSocketAddresses).internalPeerSocketAddress(internalPeerSocketAddress); } /** * Create a new PeerAddress and change the TCP and UDP ports. * * @param tcpPort * The new TCP port * @param udpPort * The new UDP port * @return The newly created peer address */ public PeerAddress changePorts(final int tcpPort, final int udpPort) { return new PeerAddress(peerId, new PeerSocketAddress(peerSocketAddress.inetAddress(), tcpPort, udpPort), firewalledTCP, firewalledUDP, relayed, slow, portForwarding, peerSocketAddresses) .internalPeerSocketAddress(internalPeerSocketAddress); } /** * Create a new PeerAddress and change the InetAddress. * * @param inetAddress * The new InetAddress * @return The newly created peer address */ public PeerAddress changeAddress(final InetAddress inetAddress) { return new PeerAddress(peerId, new PeerSocketAddress(inetAddress, peerSocketAddress.tcpPort(), peerSocketAddress.udpPort()), firewalledTCP, firewalledUDP, relayed, slow, portForwarding, peerSocketAddresses) .internalPeerSocketAddress(internalPeerSocketAddress); } /** * Create a new PeerAddress and change the peer id. * * @param peerId * the new peer id * @return The newly created peer address */ public PeerAddress changePeerId(final Number160 peerId) { return new PeerAddress(peerId, peerSocketAddress, firewalledTCP, firewalledUDP, relayed, slow, portForwarding, peerSocketAddresses).internalPeerSocketAddress(internalPeerSocketAddress); } public PeerAddress changePeerSocketAddresses(Collection<PeerSocketAddress> peerSocketAddresses) { return new PeerAddress(peerId, peerSocketAddress, firewalledTCP, firewalledUDP, relayed, slow, portForwarding, peerSocketAddresses).internalPeerSocketAddress(internalPeerSocketAddress); } public PeerAddress changePeerSocketAddress(PeerSocketAddress peerSocketAddress) { return new PeerAddress(peerId, peerSocketAddress, firewalledTCP, firewalledUDP, relayed, slow, portForwarding, peerSocketAddresses).internalPeerSocketAddress(internalPeerSocketAddress); } /** * @return The relay peers */ public Collection<PeerSocketAddress> peerSocketAddresses() { return peerSocketAddresses; } /** * Checks if option has IPv6 set. * * @param options * The option field, lowest 8 bit * @return <code>True</code> if its IPv6 */ private static boolean isNet6(final int options) { return ((options & Utils.MASK_FF) & NET6) > 0; } /** * Checks if option has firewall TCP set. * * @param options * The option field, lowest 8 bit * @return <code>True</code> if it firewalled via TCP */ private static boolean isFirewalledTCP(final int options) { return ((options & Utils.MASK_FF) & FIREWALL_TCP) > 0; } /** * Checks if option has firewall UDP set. * * @param options * The option field, lowest 8 bit * @return <code>True</code> if it firewalled via UDP */ private static boolean isFirewalledUDP(final int options) { return ((options & Utils.MASK_FF) & FIREWALL_UDP) > 0; } /** * Checks if option has relay flag set. * * @param options * The option field, lowest 8 bit * @return <code>True</code> if it is used as a relay */ private static boolean isRelay(final int options) { return ((options & Utils.MASK_FF) & RELAYED) > 0; } /** * Checks if option has slow flag set * * @param options * The option field * @return <code>true</code> if the peer is slow */ private static boolean isSlow(final int options) { return ((options & Utils.MASK_FF) & SLOW) > 0; } private static boolean isPortForwarding(final int options) { return ((options & Utils.MASK_FF) & PORT_FORWARDING) > 0; } /** * Calculates the size based on the two header bytes. * * @param header * The header in the lower 16 bits of this integer. * @return the expected size of the peer address */ public static int size(final int header) { int options = (header >>> Utils.BYTE_BITS) & Utils.MASK_FF; int relays = header & Utils.MASK_FF; return size(options, relays); } /** * Calculates the size based on the two header bytes. * * @param options * The option tells us if the inet address is IPv4 or IPv6 * @param relays * The relays tells us how many relays we have and of what type they are. * @return returns the expected size of the peer address */ public static int size(final int options, final int relays) { // header + tcp port + udp port + peer id int size = HEADER_SIZE + PORTS_SIZE + Number160.BYTE_ARRAY_SIZE; if (isNet6(options)) { size += Utils.IPV6_BYTES; } else { size += Utils.IPV4_BYTES; } // count the relays final int relaySize = (relays >>> TYPE_BIT_SIZE) & MASK_07; final byte b = (byte) (relays & MASK_1F); final BitSet relayType = Utils.createBitSet(b); for (int i = 0; i < relaySize; i++) { size += PORTS_SIZE; if (relayType.get(i)) { size += Utils.IPV6_BYTES; } else { size += Utils.IPV4_BYTES; } } return size; } public int relaySize() { return relaySize; } public PeerSocketAddress internalPeerSocketAddress() { return internalPeerSocketAddress; } public PeerAddress internalPeerSocketAddress(PeerSocketAddress internalPeerSocketAddress) { this.internalPeerSocketAddress = internalPeerSocketAddress; return this; } }