/*
* 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.message;
import java.net.InetSocketAddress;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Queue;
import java.util.Random;
import java.util.TreeMap;
import net.tomp2p.connection.PeerConnection;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.Number640;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerSocketAddress;
import net.tomp2p.peers.PeerSocketAddress.PeerSocket4Address;
import net.tomp2p.peers.PeerSocketAddress.PeerSocket6Address;
import net.tomp2p.rpc.RPC;
import net.tomp2p.rpc.RPC.Commands;
import net.tomp2p.rpc.SimpleBloomFilter;
import net.tomp2p.storage.Data;
/**
* The message is in binary format in TomP2P. It has several header and payload fields. Since
* we do the serialization/encoding manually, we do not need a serialization field.
*
* @author Thomas Bocek
*/
public class Message {
// used for creating random message id
private static final transient Random RND = new Random();
public static final int CONTENT_TYPE_LENGTH = 8;
/**
* 8 x 4 bit.
*/
public enum Content {
EMPTY, KEY, MAP_KEY640_DATA, MAP_KEY640_KEYS, SET_KEY640, SET_NEIGHBORS, BYTE_BUFFER,
LONG, INTEGER, PUBLIC_KEY_SIGNATURE, SET_TRACKER_DATA, BLOOM_FILTER, MAP_KEY640_BYTE,
PUBLIC_KEY, PEER_SOCKET4, PEER_SOCKET6
};
/**
* 1 x 4 bit.
*/
public enum Type {
/**
* <ul>
* <li>the normal request</li>
* <li>for {@link Commands#NEIGHBOR} means check for put (no digest) for tracker and storage</li>
* <li>for TASK is submit new task</li>
* <li>for {@link Commands#RELAY} is for setup</li>
* <li>for {@link Commands#RCON} means forward reverse connection to unreachable peer</li>
* </ul>
*/
REQUEST_1,
/**
* <ul>
* <li>for {@link Commands#GET} returns the extended digest (hashes of all stored data)</li>
* <li>for {@link Commands#PUT}/{@link Commands#ADD}/COMPARE_PUT means protect domain</li>
* <li>for {@link Commands#REMOVE} means send back results</li>
* <li>for RAW_DATA means serialize object</li>
* <li>for {@link Commands#NEIGHBOR} means check for get (with digest) for storage</li>
* <li>for TASK is status</li>
* <li>for {@link Commands#RELAY} means send piggybacked message</li>
* <li>for {@link Commands#RCON} open TCP channel and transmit {@link PeerConnection}</li>
* </ul>
*/
REQUEST_2,
/**
* <ul>
* <li>for {@link Commands#GET} returns a Bloom filter</li>
* <li>for {@link Commands#PUT} means put if absent</li>
* <li>for COMPARE_PUT means partial (partial means that put those data that match compare, ignore
* others)</li>
* <li>for TASK is send back result</li>
* <li>for {@link Commands#RELAY} means update the routing table</li>
* <li>for {@link Commands#RCON} use open {@link PeerConnection} to transmit original message</li>
* </ul>
*/
REQUEST_3,
/**
* <ul>
* <li>for {@link Commands#GET} returns a range (min/max)</li>
* <li>for {@link Commands#PUT} for PUT means protect domain and put if absent</li>
* <li>for COMPARE_PUT means partial and protect domain</li>
* <li>for {@link Commands#NEIGHBOR} means check for put (with digest) for task</li>
* <li>for {@link Commands#RELAY} fetch the buffer from the relay peer (Android only)</li>
* </ul>
*/
REQUEST_4,
/**
* <ul>
* <li>for {@link Commands#RELAY} means that a late response arrived at the relay peer (slow peers
* only)</li>
* </ul>
*/
REQUEST_5,
/**
* <ul>
* <li>for {@link Commands#PEX} means fire and forget, coming from mesh</li>
* </ul>
*/
REQUEST_FF_1,
/**
* <ul>
* <li>for {@link Commands#PEX} means fire and forget, coming from primary</li>
* </ul>
*/
REQUEST_FF_2,
/**
* The request was processed and everything is alright
*/
OK,
/**
* When the called node has {@link PeerAddress#isSlow()} activated, the relay peer returns a partial ok
*/
PARTIALLY_OK,
NOT_FOUND,
DENIED,
UNKNOWN_ID,
EXCEPTION,
CANCEL,
/**
* Still unused
*/
RESERVED1
};
// Header:
private int messageId;
private int version;
private Type type;
private byte command;
private PeerAddress sender;
private PeerAddress recipient;
private int options = 0;
// Payload:
// we can send 8 types
private Content[] contentTypes = new Content[CONTENT_TYPE_LENGTH];
private final transient Queue<MessageContentIndex> contentReferences = new LinkedList<MessageContentIndex>();
// ********* Here comes the payload objects ************
// The content lists:
private List<NeighborSet> neighborsList = null;
private List<Number160> keyList = null;
private List<SimpleBloomFilter<Number160>> bloomFilterList = null;
private List<DataMap> dataMapList = null;
// private PublicKey publicKey = null; // there can only be one
private List<Integer> integerList = null;
private List<Long> longList = null;
private List<KeyCollection> keyCollectionList = null;
private List<KeyMap640Keys> keyMap640KeysList = null;
private List<KeyMapByte> keyMapByteList = null;
private List<Buffer> bufferList = null;
private List<TrackerData> trackerDataList = null;
private List<PublicKey> publicKeyList = null;
private List<PeerSocket4Address> peerSocket4AddressList = null;
private List<PeerSocket6Address> peerSocket6AddressList = null;
private SignatureCodec signatureEncode = null;
// this will not be transferred, status variables
private transient boolean presetContentTypes = false;
private transient PrivateKey privateKey;
private transient InetSocketAddress senderSocket;
private transient InetSocketAddress recipientSocket;
private transient boolean udp = false;
private transient boolean done = false;
private transient boolean sign = false;
private transient boolean content = false;
private transient boolean verified = false;
private transient boolean sendSelf = false;
private transient PeerAddress recipientRelay;
private transient PeerAddress recipientReflected;
/**
* Creates message with a random ID.
*/
public Message() {
this.messageId = RND.nextInt();
}
/**
* Randomly generated message ID.
*
* @return message Id
*/
public int messageId() {
return messageId;
}
/**
* For deserialization, we need to set the id.
*
* @param messageId
* The message Id
* @return This class
*/
public Message messageId(final int messageId) {
this.messageId = messageId;
return this;
}
/**
* Returns the version, which is 32bit. Each application can choose and version to not intefere with other
* applications
*
* @return The application version that uses this P2P framework
*/
public int version() {
return version;
}
/**
* For deserialization.
*
* @param version
* The 24bit version
* @return This class
*/
public Message version(final int version) {
this.version = version;
return this;
}
/**
* Determines if its a request or command reply, and what kind of reply (error, warning states).
*
* @return Type of the message
*/
public Type type() {
return type;
}
/**
* Set the message type. Either its a request or reply (with error and warning codes).
*
* @param type
* Type of the message
* @return This class
*/
public Message type(final Type type) {
this.type = type;
return this;
}
/**
* Command of the message, such as GET, PING, etc.
*
* @return Command
*/
public byte command() {
return command;
}
/**
* Command of the message, such as GET, PING, etc.
*
* @param command
* Command
* @return This class
*/
public Message command(final byte command) {
this.command = command;
return this;
}
/**
* The ID of the sender. Note that the IP is set via the socket.
*
* @return The ID of the sender.
*/
public PeerAddress sender() {
return sender;
}
/**
* The ID of the sender. The IP of the sender will *not* be transferred, as this information is in the IP packet.
*
* @param sender
* The ID of the sender.
* @return This class
*/
public Message sender(final PeerAddress sender) {
this.sender = sender;
return this;
}
/**
* The ID of the recipient. Note that the IP is set via the socket.
*
* @return The ID of the recipient
*/
public PeerAddress recipient() {
return recipient;
}
/**
* Set the ID of the recipient. The IP is used to connect to the recipient, but the IP is *not* transferred.
*
* @param recipient
* The ID of the recipient
* @return This class
*/
public Message recipient(final PeerAddress recipient) {
this.recipient = recipient;
return this;
}
public PeerAddress recipientRelay() {
return recipientRelay;
}
public Message recipientRelay(PeerAddress recipientRelay) {
this.recipientRelay = recipientRelay;
return this;
}
public PeerAddress recipientReflected() {
return recipientReflected;
}
public Message recipientReflected(PeerAddress recipientReflected) {
this.recipientReflected = recipientReflected;
return this;
}
/**
* Return content types. Content type can be empty if not set
*
* @return Content type 1
*/
public Content[] contentTypes() {
return contentTypes;
}
/**
* Convenient method to set content type. Set first content type 1, if this is set (not empty), then set the second
* one, etc.
*
* @param contentType
* The content type to set
* @return This class
*/
public Message contentType(final Content contentType) {
for (int i = 0, reference = 0; i < CONTENT_TYPE_LENGTH; i++) {
if (contentTypes[i] == null) {
if (contentType == Content.PUBLIC_KEY_SIGNATURE && i != 0) {
throw new IllegalStateException("The public key needs to be the first to be set.");
}
contentTypes[i] = contentType;
contentReferences.add(new MessageContentIndex(reference, contentType));
return this;
} else if (contentTypes[i] == contentType) {
reference++;
} else if (contentTypes[i] == Content.PUBLIC_KEY_SIGNATURE || contentTypes[i] == Content.PUBLIC_KEY) {
//special handling for public key as we store both in the same list
if (contentType == Content.PUBLIC_KEY_SIGNATURE || contentType == Content.PUBLIC_KEY) {
reference++;
}
}
}
throw new IllegalStateException("Already set 8 content types.");
}
/**
* Restore the content references if only the content types array is
* present. The content references are removed when decoding a message. That
* means if a message was received it cannot be used a second time as the
* content references are not there anymore. This method restores the
* content references based on the content types of the message.
*/
public void restoreContentReferences() {
Map<Content, Integer> refs = new HashMap<Content, Integer>(contentTypes.length * 2);
for (Content contentType : contentTypes) {
if (contentType == Content.EMPTY || contentType == null) {
return;
}
int index = 0;
if (contentType == Content.PUBLIC_KEY_SIGNATURE || contentType == Content.PUBLIC_KEY) {
Integer i1 = refs.get(Content.PUBLIC_KEY_SIGNATURE);
if(i1 != null) {
index = i1.intValue();
} else {
i1 = refs.get(Content.PUBLIC_KEY);
if(i1 != null) {
index = i1.intValue();
}
}
}
if (!refs.containsKey(contentType)) {
refs.put(contentType, index);
} else {
index = refs.get(contentType);
}
contentReferences.add(new MessageContentIndex(index, contentType));
refs.put(contentType, index + 1);
}
}
/**
* Restores all buffers such that they can be re-read (e.g. used for encoding). If the message does not
* have any buffer, this method does nothing.
*/
public void restoreBuffers() {
for (Buffer buffer : bufferList()) {
buffer.reset();
}
}
/**
* Sets or replaces the content type at a specific index.
*
* @param index
* The index
* @param contentType
* The content type
* @return This class
*/
public Message contentType(final int index, final Content contentType) {
contentTypes[index] = contentType;
return this;
}
/**
* Used for deserialization.
*
* @param contentTypes
* The content types that were decoded.
* @return This class
*/
public Message contentTypes(final Content[] contentTypes) {
this.contentTypes = contentTypes;
return this;
}
/**
* @return The serialized content and references to the respective arrays
*/
public Queue<MessageContentIndex> contentReferences() {
return contentReferences;
}
/**
* @return True if we have content and not only the header
*/
public boolean hasContent() {
return contentReferences.size() > 0 || content;
}
/**
* @param content
* We can set this already in the header to know if we have content or not
* @return This class
*/
public Message hasContent(final boolean content) {
this.content = content;
return this;
}
// Types of requests
/**
* @return True if this is a request, a regular or a fire and forget
*/
public boolean isRequest() {
return type == Type.REQUEST_1 || type == Type.REQUEST_2 || type == Type.REQUEST_3 || type == Type.REQUEST_4
|| type == Type.REQUEST_5 || type == Type.REQUEST_FF_1 || type == Type.REQUEST_FF_2;
}
/**
* @return True if its a fire and forget, that means we don't expect an answer
*/
public boolean isFireAndForget() {
return type == Type.REQUEST_FF_1 || type == Type.REQUEST_FF_2;
}
/**
* @return True if the message was ok, or at least send partial data
*/
public boolean isOk() {
return type == Type.OK || type == Type.PARTIALLY_OK;
}
/**
* @return True if the message arrived, but data was not found or access was denied
*/
public boolean isNotOk() {
return type == Type.NOT_FOUND || type == Type.DENIED;
}
/**
* @return True if the message contained an unexpected error or behavior
*/
public boolean isError() {
return isError(type);
}
/**
* @param type
* The type to check
* @return True if the message contained an unexpected error or behavior
*/
public static boolean isError(final Type type) {
return type == Type.UNKNOWN_ID || type == Type.EXCEPTION || type == Type.CANCEL;
}
/**
* @param options
* The option from the last byte of the header
* @return This class
*/
public Message options(final int options) {
this.options = options;
return this;
}
/**
* @return The options from the last byte of the header
*/
public int options() {
return options;
}
/**
*
* @param isKeepAlive
* True if the connection should remain open. We need to announce this in the header, as otherwise the
* other end has an idle handler that will close the connection.
* @return This class
*/
public Message keepAlive(final boolean isKeepAlive) {
if (isKeepAlive) {
options |= 1;
} else {
options &= ~1;
}
return this;
}
/**
* @return True if this message was sent on a connection that should be kept alive
*/
public boolean isKeepAlive() {
return (options & 1) > 0;
}
public Message streaming() {
return streaming(true);
}
public Message streaming(boolean streaming) {
if (streaming) {
options |= 2;
} else {
options &= ~2;
}
return this;
}
public boolean isStreaming() {
return (options & 2) > 0;
}
public Message expectDuplicate(boolean expectDuplicate) {
if (expectDuplicate) {
options |= 4;
} else {
options &= ~4;
}
return this;
}
public boolean isExpectDuplicate() {
return (options & 4) > 0;
}
// Header data ends here *********************************** static payload starts now
public Message key(final Number160 key) {
if (!presetContentTypes) {
contentType(Content.KEY);
}
if (keyList == null) {
keyList = new ArrayList<Number160>(1);
}
keyList.add(key);
return this;
}
public List<Number160> keyList() {
if (keyList == null) {
return Collections.emptyList();
}
return keyList;
}
public Number160 key(final int index) {
if (keyList == null || index > keyList.size() - 1) {
return null;
}
return keyList.get(index);
}
public Message bloomFilter(final SimpleBloomFilter<Number160> bloomFilter) {
if (!presetContentTypes) {
contentType(Content.BLOOM_FILTER);
}
if (bloomFilterList == null) {
bloomFilterList = new ArrayList<SimpleBloomFilter<Number160>>(1);
}
bloomFilterList.add(bloomFilter);
return this;
}
public List<SimpleBloomFilter<Number160>> bloomFilterList() {
if (bloomFilterList == null) {
return Collections.emptyList();
}
return bloomFilterList;
}
public SimpleBloomFilter<Number160> bloomFilter(final int index) {
if (bloomFilterList == null || index > bloomFilterList.size() - 1) {
return null;
}
return bloomFilterList.get(index);
}
public Message publicKeyAndSign(KeyPair keyPair) {
if (!presetContentTypes) {
contentType(Content.PUBLIC_KEY_SIGNATURE);
}
publicKey0(keyPair.getPublic());
this.privateKey = keyPair.getPrivate();
return this;
}
public Message intValue(final int integer) {
if (!presetContentTypes) {
contentType(Content.INTEGER);
}
if (integerList == null) {
integerList = new ArrayList<Integer>(1);
}
this.integerList.add(integer);
return this;
}
public List<Integer> intList() {
if (integerList == null) {
return Collections.emptyList();
}
return integerList;
}
public Integer intAt(final int index) {
if (integerList == null || index > integerList.size() - 1) {
return null;
}
return integerList.get(index);
}
public Message longValue(long long0) {
if (!presetContentTypes) {
contentType(Content.LONG);
}
if (longList == null) {
longList = new ArrayList<Long>(1);
}
this.longList.add(long0);
return this;
}
public List<Long> longList() {
if (longList == null) {
return Collections.emptyList();
}
return longList;
}
public Long longAt(int index) {
if (longList == null || index > longList.size() - 1) {
return null;
}
return longList.get(index);
}
public Message neighborsSet(final NeighborSet neighborSet) {
if (!presetContentTypes) {
contentType(Content.SET_NEIGHBORS);
}
if (neighborsList == null) {
neighborsList = new ArrayList<NeighborSet>(1);
}
this.neighborsList.add(neighborSet);
return this;
}
public List<NeighborSet> neighborsSetList() {
if (neighborsList == null) {
return Collections.emptyList();
}
return neighborsList;
}
public NeighborSet neighborsSet(final int index) {
if (neighborsList == null || index > neighborsList.size() - 1) {
return null;
}
return neighborsList.get(index);
}
public Message setDataMap(final DataMap dataMap) {
if (!presetContentTypes) {
contentType(Content.MAP_KEY640_DATA);
}
if (dataMapList == null) {
dataMapList = new ArrayList<DataMap>(1);
}
this.dataMapList.add(dataMap);
return this;
}
public List<DataMap> dataMapList() {
if (dataMapList == null) {
return Collections.emptyList();
}
return dataMapList;
}
public DataMap dataMap(final int index) {
if (dataMapList == null || index > dataMapList.size() - 1) {
return null;
}
return dataMapList.get(index);
}
public Message keyCollection(final KeyCollection key) {
if (!presetContentTypes) {
contentType(Content.SET_KEY640);
}
if (keyCollectionList == null) {
keyCollectionList = new ArrayList<KeyCollection>(1);
}
keyCollectionList.add(key);
return this;
}
public List<KeyCollection> keyCollectionList() {
if (keyCollectionList == null) {
return Collections.emptyList();
}
return keyCollectionList;
}
public KeyCollection keyCollection(final int index) {
if (keyCollectionList == null || index > keyCollectionList.size() - 1) {
return null;
}
return keyCollectionList.get(index);
}
public Message keyMap640Keys(final KeyMap640Keys keyMap) {
if (!presetContentTypes) {
contentType(Content.MAP_KEY640_KEYS);
}
if (keyMap640KeysList == null) {
keyMap640KeysList = new ArrayList<KeyMap640Keys>(1);
}
keyMap640KeysList.add(keyMap);
return this;
}
public List<KeyMap640Keys> keyMap640KeysList() {
if (keyMap640KeysList == null) {
return Collections.emptyList();
}
return keyMap640KeysList;
}
public KeyMap640Keys keyMap640Keys(final int index) {
if (keyMap640KeysList == null || index > keyMap640KeysList.size() - 1) {
return null;
}
return keyMap640KeysList.get(index);
}
public Message keyMapByte(final KeyMapByte keyMap) {
if (!presetContentTypes) {
contentType(Content.MAP_KEY640_BYTE);
}
if (keyMapByteList == null) {
keyMapByteList = new ArrayList<KeyMapByte>(1);
}
keyMapByteList.add(keyMap);
return this;
}
public List<KeyMapByte> keyMapByteList() {
if (keyMapByteList == null) {
return Collections.emptyList();
}
return keyMapByteList;
}
public KeyMapByte keyMapByte(final int index) {
if (keyMapByteList == null || index > keyMapByteList.size() - 1) {
return null;
}
return keyMapByteList.get(index);
}
public Message publicKey(final PublicKey publicKey) {
if (!presetContentTypes) {
contentType(Content.PUBLIC_KEY);
}
if(publicKeyList == null) {
publicKeyList = new ArrayList<PublicKey>(1);
}
publicKeyList.add(publicKey);
return this;
}
private Message publicKey0(final PublicKey publicKey) {
if(publicKeyList == null) {
publicKeyList = new ArrayList<PublicKey>(1);
}
publicKeyList.add(publicKey);
return this;
}
public List<PublicKey> publicKeyList() {
if (publicKeyList == null) {
return Collections.emptyList();
}
return publicKeyList;
}
public PublicKey publicKey(final int index) {
if (publicKeyList == null || index > publicKeyList.size() - 1) {
return null;
}
return publicKeyList.get(index);
}
public Message peerSocketAddress(final PeerSocketAddress peerSocketAddress) {
if(peerSocketAddress instanceof PeerSocket4Address) {
return peerSocket4Address((PeerSocket4Address)peerSocketAddress);
} else {
return peerSocket6Address((PeerSocket6Address)peerSocketAddress);
}
}
public List<PeerSocketAddress> peerSocketAddressList() {
List<PeerSocketAddress> retVal = new ArrayList<PeerSocketAddress>();
retVal.addAll(peerSocket4AddressList());
retVal.addAll(peerSocket6AddressList());
return retVal;
}
public Message peerSocket4Address(final PeerSocket4Address peerSocket4Address) {
if (!presetContentTypes) {
contentType(Content.PEER_SOCKET4);
}
if(peerSocket4AddressList == null) {
peerSocket4AddressList = new ArrayList<PeerSocket4Address>(1);
}
peerSocket4AddressList.add(peerSocket4Address);
return this;
}
public List<PeerSocket4Address> peerSocket4AddressList() {
if (peerSocket4AddressList == null) {
return Collections.emptyList();
}
return peerSocket4AddressList;
}
public PeerSocket4Address peerSocket4Address(final int index) {
if (peerSocket4AddressList == null || index > peerSocket4AddressList.size() - 1) {
return null;
}
return peerSocket4AddressList.get(index);
}
public Message peerSocket6Address(final PeerSocket6Address peerSocket6Address) {
if (!presetContentTypes) {
contentType(Content.PEER_SOCKET6);
}
if(peerSocket6AddressList == null) {
peerSocket6AddressList = new ArrayList<PeerSocket6Address>(1);
}
peerSocket6AddressList.add(peerSocket6Address);
return this;
}
public List<PeerSocket6Address> peerSocket6AddressList() {
if (peerSocket6AddressList == null) {
return Collections.emptyList();
}
return peerSocket6AddressList;
}
public PeerSocket6Address peerSocket6Address(final int index) {
if (peerSocket6AddressList == null || index > peerSocket6AddressList.size() - 1) {
return null;
}
return peerSocket6AddressList.get(index);
}
public PrivateKey privateKey() {
return privateKey;
}
public Message buffer(final Buffer byteBuf) {
if (!presetContentTypes) {
contentType(Content.BYTE_BUFFER);
}
if (bufferList == null) {
bufferList = new ArrayList<Buffer>(1);
}
bufferList.add(byteBuf);
return this;
}
public List<Buffer> bufferList() {
if (bufferList == null) {
return Collections.emptyList();
}
return bufferList;
}
public Buffer buffer(final int index) {
if (bufferList == null || index > bufferList.size() - 1) {
return null;
}
return bufferList.get(index);
}
public Message trackerData(final TrackerData trackerData) {
if (!presetContentTypes) {
contentType(Content.SET_TRACKER_DATA);
}
if (trackerDataList == null) {
trackerDataList = new ArrayList<TrackerData>(1);
}
this.trackerDataList.add(trackerData);
return this;
}
public List<TrackerData> trackerDataList() {
if (trackerDataList == null) {
return Collections.emptyList();
}
return trackerDataList;
}
public TrackerData trackerData(final int index) {
if (trackerDataList == null || index > trackerDataList.size() - 1) {
return null;
}
return trackerDataList.get(index);
}
public Message receivedSignature(SignatureCodec signatureEncode) {
this.signatureEncode = signatureEncode;
return this;
}
public SignatureCodec receivedSignature() {
return signatureEncode;
}
//*************************************** End of content payload ********************
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("msgid=");
return sb.append(messageId()).append(",t=").append(type).
append(",c=").append(RPC.Commands.find(command).toString()).append(",").append(isUdp()?"udp":"tcp").
append(",s=").append(sender).append(",r=").append(recipient).toString();
}
// *************************** No transferable objects here *********************************
/**
* If we are setting values from the decoder, then the content type is already set.
*
* @param presetContentTypes
* True if the content type is already set.
* @return This class
*/
public Message presetContentTypes(final boolean presetContentTypes) {
this.presetContentTypes = presetContentTypes;
return this;
}
/**
* Store the sender of the packet. This is needed for UDP traffic.
*
* @param senderSocket
* The sender as we saw it on the interface
* @return This class
*/
public Message senderSocket(final InetSocketAddress senderSocket) {
this.senderSocket = senderSocket;
return this;
}
/**
* @return The sender of the packet. This is needed for UDP traffic.
*/
public InetSocketAddress senderSocket() {
return senderSocket;
}
/**
* Store the recipient of the packet. This is needed for UDP (especially broadcast) packets
* @param recipientSocket The recipient as we saw it on the interface
* @return This class
*/
public Message recipientSocket(InetSocketAddress recipientSocket) {
this.recipientSocket = recipientSocket;
return this;
}
/**
* @return The recipient as we saw it on the interface. This is needed for UDP (especially broadcast) packets
*/
public InetSocketAddress recipientSocket() {
return recipientSocket;
}
/**
* Set if we have a signed message.
*
* @return This class
*/
public Message setHintSign() {
sign = true;
return this;
}
/**
* @return True if message is or should be signed
*/
public boolean isSign() {
/*
* boolean hasType = false; for (Content type : contentTypes) { if (type == Content.PUBLIC_KEY_SIGNATURE) {
* hasType = true; } }
*/
return sign || privateKey != null; // || hasType;
}
/**
* @param udp
* True if connection is UDP
* @return This class
*/
public Message udp(final boolean udp) {
this.udp = udp;
return this;
}
/**
* @return True if connection is UDP
*/
public boolean isUdp() {
return udp;
}
public Message verified(boolean verified) {
this.verified = verified;
return this;
}
public boolean verified() {
return verified;
}
public Message setVerified() {
this.verified = true;
return this;
}
/**
* @param done
* True if message decoding or encoding is done
* @return This class
*/
public Message setDone(final boolean done) {
this.done = done;
return this;
}
/**
* Set done to true if message decoding or encoding is done.
* @return This class
*/
public Message setDone() {
return setDone(true);
}
/**
* @return True if message decoding or encoding is done
*/
public boolean isDone() {
return done;
}
public Message sendSelf(final boolean sendSelf) {
this.sendSelf = sendSelf;
return this;
}
public Message sendSelf() {
return sendSelf(true);
}
public boolean isSendSelf() {
return sendSelf;
}
public Message duplicate() {
return duplicate(null);
}
public Message duplicate(DataFilter dataFilter) {
Message message = new Message();
// Header
message.messageId = this.messageId;
message.version = this.version;
message.type = this.type;
message.command = this.command;
message.sender = this.sender;
message.recipient = this.recipient;
// recipientRelay is not transferred
message.options = this.options;
// Payload
message.contentTypes = this.contentTypes;
// contentReferences is transient
// ********* Here comes the payload objects ************
// The content lists:
message.neighborsList = this.neighborsList;
message.keyList = this.keyList;
message.bloomFilterList = this.bloomFilterList;
if(dataFilter == null) {
message.dataMapList = this.dataMapList;
} else {
message.dataMapList = filter(dataFilter);
}
message.integerList = this.integerList;
message.longList = this.longList;
message.keyCollectionList = this.keyCollectionList;
message.keyMap640KeysList = this.keyMap640KeysList;
message.keyMapByteList = this.keyMapByteList;
message.bufferList = this.bufferList;
message.trackerDataList = this.trackerDataList;
message.publicKeyList = this.publicKeyList;
message.peerSocket4AddressList = this.peerSocket4AddressList;
message.peerSocket6AddressList = this.peerSocket6AddressList;
message.signatureEncode = this.signatureEncode;
// these are transient, copy anyway
message.presetContentTypes = presetContentTypes;
message.privateKey = this.privateKey;
message.senderSocket = this.senderSocket;
message.recipientSocket = this.recipientSocket;
message.udp = this.udp;
message.done = this.done;
message.sign = this.sign;
message.content = this.content;
message.verified = this.verified;
message.sendSelf = this.sendSelf;
message.recipientRelay = this.recipientRelay;
message.recipientReflected = this.recipientReflected;
return message;
}
/**
* Change the data and make a shallow copy of the data. This means fields
* such as TTL or other are copied, the underlying buffer remains the same
*
* @param dataFilter
* The filter that will be applied on each data item
* @return The filtered data
*/
private List<DataMap> filter(DataFilter dataFilter) {
final List<DataMap> dataMapListCopy = new ArrayList<DataMap>(this.dataMapList().size());
for (DataMap dataMap : this.dataMapList()) {
final NavigableMap<Number640, Data> dataMapCopy = new TreeMap<Number640, Data>();
for (Map.Entry<Number640, Data> entry : dataMap.dataMap()
.entrySet()) {
Data filteredData = dataFilter.filter(entry.getValue(), dataMap.isConvertMeta(), !isRequest());
dataMapCopy.put(entry.getKey(), filteredData);
}
dataMapListCopy.add(new DataMap(dataMapCopy, dataMap.isConvertMeta()));
}
return dataMapListCopy;
}
/**
* Returns the estimated message size. If the message contains data, a constant value of 1000bytes is added.
*/
public int estimateSize() {
int current = MessageHeaderCodec.HEADER_SIZE_STATIC + sender.size();
if(neighborsList != null) {
for (NeighborSet neighbors : neighborsList) {
for (PeerAddress address : neighbors.neighbors()) {
current += address.size() + 1;
}
}
}
if(keyList != null) {
current += keyList.size() * Number160.BYTE_ARRAY_SIZE;
}
if(bloomFilterList != null) {
for (SimpleBloomFilter<Number160> filter : bloomFilterList) {
current += filter.size();
}
}
if(integerList != null) {
current += integerList.size() * 4;
}
if(longList != null) {
current += longList.size() * 8;
}
if(keyCollectionList != null) {
for (KeyCollection coll : keyCollectionList) {
current += 4 + coll.size() * Number640.BYTE_ARRAY_SIZE;
}
}
if(keyMap640KeysList != null) {
for (KeyMap640Keys keys : keyMap640KeysList) {
current += 4 + keys.size() * Number640.BYTE_ARRAY_SIZE;
}
}
if(keyMapByteList != null) {
for (KeyMapByte keys : keyMapByteList) {
current += 4 + keys.size();
}
}
if(bufferList != null) {
for (Buffer buffer : bufferList) {
current += 4 + buffer.length();
}
}
if(publicKeyList != null) {
for (PublicKey key : publicKeyList) {
current += key.getEncoded().length;
}
}
if(peerSocket4AddressList != null) {
current += (peerSocket4AddressList.size() * PeerSocket4Address.SIZE);
}
if(peerSocket6AddressList != null) {
current += (peerSocket6AddressList.size() * PeerSocket6Address.SIZE);
}
if(signatureEncode != null) {
current += signatureEncode.signatureSize();
}
/**
* Here are the estimations to skip CPU intensive calculations
*/
if(dataMapList != null) {
current += 1000;
}
if(trackerDataList != null) {
current += 1000; // estimated size
}
return current;
}
public void release() {
/*for(DataMap dataMap: dataMapList()) {
for(Data data: dataMap.dataMap().values()) {
data.release();
}
}
for(Buffer buffer: bufferList()) {
buffer.buffer().release();
}
for(TrackerData trackerData:trackerDataList()) {
for(Data data:trackerData.peerAddresses().values()) {
data.release();
}
}*/
}
}