/* * 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 io.netty.buffer.ByteBuf; import java.io.IOException; import java.net.InetSocketAddress; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.Random; import java.util.concurrent.atomic.AtomicReference; import net.tomp2p.peers.Number160; import net.tomp2p.peers.PeerAddress; import net.tomp2p.rpc.SimpleBloomFilter; /** * The message is in binary format in TomP2P. It is defined as follows and has several header and payload fields. Since * we do the serialization manually, we do not need a serialization field. * * @author Thomas Bocek */ public class Message2 { // used for creating random message id private static final Random RND = new Random(); public static final int CONTENT_TYPE_LENGTH = 8; /** * 8 x 4 bit. */ public enum Content { EMPTY, KEY, MAP_KEY480_DATA, MAP_KEY480_KEY, SET_KEY480, SET_NEIGHBORS, BYTE_BUFFER, LONG, INTEGER, PUBLIC_KEY_SIGNATURE, SET_TRACKER_DATA, BLOOM_FILTER, USER1, USER2, USER3, USER4 }; /** * 1 x 4 bit. */ public enum Type { // REQUEST_1 is the normal request // REQUEST_2 for GET returns the extended digest (hashes of all stored // data) // REQUEST_3 for GET returns a Bloom filter // REQUEST_4 for GET returns a range (min/max) // REQUEST_2 for PUT/ADD/COMPARE_PUT means protect domain // REQUEST_3 for PUT means put if absent // REQUEST_3 for COMPARE_PUT means partial (partial means that put those // data that match compare, ignore others) // REQUEST_4 for PUT means protect domain and put if absent // REQUEST_4 for COMPARE_PUT means partial and protect domain // REQUEST_2 for REMOVE means send back results // REQUEST_2 for RAW_DATA means serialize object // *** NEIGHBORS has four different cases // REQUEST_1 for NEIGHBORS means check for put (no digest) for tracker // and storage // REQUEST_2 for NEIGHBORS means check for get (with digest) for storage // REQUEST_3 for NEIGHBORS means check for get (with digest) for tracker // REQUEST_4 for NEIGHBORS means check for put (with digest) for task // REQUEST_FF_1 for PEX means fire and forget, coming from mesh // REQUEST_FF_1 for PEX means fire and forget, coming from primary // REQUEST_1 for TASK is submit new task // REQUEST_2 for TASK is status // REQUEST_3 for TASK is send back result REQUEST_1, REQUEST_2, REQUEST_3, REQUEST_4, REQUEST_FF_1, REQUEST_FF_2, OK, PARTIALLY_OK, NOT_FOUND, DENIED, UNKNOWN_ID, EXCEPTION, CANCEL, USER1, USER2 }; // Header: private int messageId; private int version; private Type type; // commands so far: // 0: PING // 1: PUT // 2: GET // 3: ADD // 4: REMOVE // 5: NEIGHBORS // 6: QUIT // 7: DIRECT_DATA // 8: TRACKER_ADD // 9: TRACKER_GET // 10: PEX // 11: TASK // 12: BROADCAST_DATA 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 Queue<NumberType> contentRefencencs = new LinkedList<NumberType>(); // ********* 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 AtomicReference<PublicKey> publicKeyReference = null; private List<Integer> integerList = null; private List<Long> longList = null; private List<Keys> keysList = null; private List<KeysMap> keysMapList = null; private List<Buffer> bufferList = null; private List<TrackerData> trackerDataList = 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 Signature signature = null; private transient SHA1Signature signatureEncode = null; private transient PublicKey receivedPublicKey = null; /** * Creates message with a random ID. */ public Message2() { this.messageId = RND.nextInt(); } /** * Randomly generated message ID. * * @return message Id */ public int getMessageId() { return messageId; } /** * For deserialization, we need to set the id. * * @param messageId * The message Id * @return This class */ public Message2 setMessageId(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 getVersion() { return version; } /** * For deserialization. * * @param version * The 24bit version * @return This class */ public Message2 setVersion(final int version) { this.version = version; return this; } /** * Determines if its a request oCommandr reply, and what kind of reply (error, warning states). * * @return Type of the message */ public Type getType() { 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 Message2 setType(final Type type) { this.type = type; return this; } /** * Command of the message, such as GET, PING, etc. * * @return Command */ public byte getCommand() { return command; } /** * Command of the message, such as GET, PING, etc. * * @param command * Command * @return This class */ public Message2 setCommand(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 getSender() { 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 Message2 setSender(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 getRecipient() { 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 Message2 setRecipient(final PeerAddress recipient) { this.recipient = recipient; return this; } /** * Return content types. Content type can be empty if not set * * @return Content type 1 */ public Content[] getContentTypes() { 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 Message2 setContentType(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; contentRefencencs.add(new NumberType(reference, contentType)); return this; } if (contentTypes[i] == contentType) { reference++; } } throw new IllegalStateException("Already set 8 content types"); } /** * Sets or replaces the content type at a specific index. * * @param index * The index * @param contentType * The content type * @return This class */ public Message2 setContentType(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 Message2 setContentTypes(final Content[] contentTypes) { this.contentTypes = contentTypes; return this; } /** * @return The serialized content and references to the respective arrays */ public Queue<NumberType> contentRefencencs() { return contentRefencencs; } /** * @return True if we have content and not only the header */ public boolean hasContent() { return contentRefencencs.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 Message2 hasContent(final boolean content) { this.content = content; return this; } // Types of requests /** * @return True if this is a request, a regural 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_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 Message2 setOptions(final int options) { this.options = options; return this; } /** * @return The option from the last byte of the header */ public int getOptions() { 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 Message2 setKeepAlive(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 Message2 setStreaming() { return streaming(true); } public Message2 streaming(boolean streaming) { if (streaming) { options |= 2; } else { options &= ~2; } return this; } public boolean isStreaming() { return (options & 2) > 0; } // Header data ends here *********************************** static payload starts now public Message2 setKey(final Number160 key) { if (!presetContentTypes) { setContentType(Content.KEY); } if (keyList == null) { keyList = new ArrayList<Number160>(1); } keyList.add(key); return this; } public List<Number160> getKeyList() { if (keyList == null) { return Collections.emptyList(); } return keyList; } public Number160 getKey(final int index) { if (keyList == null || index > keyList.size() - 1) { return null; } return keyList.get(index); } public Message2 setBloomFilter(final SimpleBloomFilter<Number160> bloomFilter) { if (!presetContentTypes) { setContentType(Content.BLOOM_FILTER); } if (bloomFilterList == null) { bloomFilterList = new ArrayList<SimpleBloomFilter<Number160>>(1); } bloomFilterList.add(bloomFilter); return this; } public List<SimpleBloomFilter<Number160>> getBloomFilterList() { if (bloomFilterList == null) { return Collections.emptyList(); } return bloomFilterList; } public SimpleBloomFilter<Number160> getBloomFilter(final int index) { if (bloomFilterList == null || index > bloomFilterList.size() - 1) { return null; } return bloomFilterList.get(index); } public Message2 setPublicKeyAndSign(KeyPair keyPair) { if (!presetContentTypes) { setContentType(Content.PUBLIC_KEY_SIGNATURE); } this.publicKeyReference = new AtomicReference<PublicKey>(keyPair.getPublic()); this.privateKey = keyPair.getPrivate(); return this; } public Message2 setInteger(final int integer) { if (!presetContentTypes) { setContentType(Content.INTEGER); } if (integerList == null) { integerList = new ArrayList<Integer>(1); } this.integerList.add(integer); return this; } public List<Integer> getIntegerList() { if (integerList == null) { return Collections.emptyList(); } return integerList; } public Integer getInteger(final int index) { if (integerList == null || index > integerList.size() - 1) { return null; } return integerList.get(index); } public Message2 setLong(long long0) { if (!presetContentTypes) { setContentType(Content.LONG); } if (longList == null) { longList = new ArrayList<Long>(1); } this.longList.add(long0); return this; } public List<Long> getLongList() { if (longList == null) { return Collections.emptyList(); } return longList; } public Long getLong(int index) { if (longList == null || index > longList.size() - 1) { return null; } return longList.get(index); } public Message2 setNeighborsSet(final NeighborSet neighborSet) { if (!presetContentTypes) { setContentType(Content.SET_NEIGHBORS); } if (neighborsList == null) { neighborsList = new ArrayList<NeighborSet>(1); } this.neighborsList.add(neighborSet); return this; } public List<NeighborSet> getNeighborsSetList() { if (neighborsList == null) { return Collections.emptyList(); } return neighborsList; } public NeighborSet getNeighborsSet(final int index) { if (neighborsList == null || index > neighborsList.size() - 1) { return null; } return neighborsList.get(index); } public Message2 setDataMap(final DataMap dataMap) { if (!presetContentTypes) { setContentType(Content.MAP_KEY480_DATA); } if (dataMapList == null) { dataMapList = new ArrayList<DataMap>(1); } this.dataMapList.add(dataMap); return this; } public List<DataMap> getDataMapList() { if (dataMapList == null) { return Collections.emptyList(); } return dataMapList; } public DataMap getDataMap(final int index) { if (dataMapList == null || index > dataMapList.size() - 1) { return null; } return dataMapList.get(index); } public Message2 setKeys(final Keys key) { if (!presetContentTypes) { setContentType(Content.SET_KEY480); } if (keysList == null) { keysList = new ArrayList<Keys>(1); } keysList.add(key); return this; } public List<Keys> getKeysList() { if (keysList == null) { return Collections.emptyList(); } return keysList; } public Keys getKeys(final int index) { if (keysList == null || index > keysList.size() - 1) { return null; } return keysList.get(index); } public Message2 setKeysMap(final KeysMap keyMap) { if (!presetContentTypes) { setContentType(Content.MAP_KEY480_KEY); } if (keysMapList == null) { keysMapList = new ArrayList<KeysMap>(1); } keysMapList.add(keyMap); return this; } public List<KeysMap> getKeysMapList() { if (keysMapList == null) { return Collections.emptyList(); } return keysMapList; } public KeysMap getKeysMap(final int index) { if (keysMapList == null || index > keysMapList.size() - 1) { return null; } return keysMapList.get(index); } public Message2 setPublicKey(final PublicKey publicKey) { publicKeyReference(); publicKeyReference.set(publicKey); return this; } public PublicKey getPublicKey() { if (publicKeyReference == null) { return null; } return publicKeyReference.get(); } public PrivateKey getPrivateKey() { return privateKey; } public Message2 setBuffer(final Buffer byteBuf) { if (!presetContentTypes) { setContentType(Content.BYTE_BUFFER); } if (bufferList == null) { bufferList = new ArrayList<Buffer>(1); } bufferList.add(byteBuf); return this; } public List<Buffer> getBufferList() { if (bufferList == null) { return Collections.emptyList(); } return bufferList; } public Buffer getBuffer(final int index) { if (bufferList == null || index > bufferList.size() - 1) { return null; } return bufferList.get(index); } public Message2 setTrackerData(final TrackerData trackerData) { if (!presetContentTypes) { setContentType(Content.SET_TRACKER_DATA); } if (trackerDataList == null) { trackerDataList = new ArrayList<TrackerData>(1); } this.trackerDataList.add(trackerData); return this; } public List<TrackerData> getTrackerDataList() { if (trackerDataList == null) { return Collections.emptyList(); } return trackerDataList; } public TrackerData getTrackerData(final int index) { if (trackerDataList == null || index > trackerDataList.size() - 1) { return null; } return trackerDataList.get(index); } public AtomicReference<PublicKey> publicKeyReference() { if (publicKeyReference == null) { publicKeyReference = new AtomicReference<PublicKey>(); } return publicKeyReference; } public Message2 signatureForVerification(Signature signature, PublicKey receivedPublicKey) { this.signature = signature; this.receivedPublicKey = receivedPublicKey; return this; } public Signature signatureForVerification() { return signature; } public PublicKey receivedPublicKey() { return receivedPublicKey; } public Message2 receivedSignature(SHA1Signature signatureEncode) { this.signatureEncode = signatureEncode; return this; } public SHA1Signature receivedSignature() { return signatureEncode; } //*************************************** End of content payload ******************** @Override public String toString() { final StringBuilder sb = new StringBuilder("Message:id="); sb.append(getMessageId()).append(",t=").append(type.toString()).append(",c=").append(command); /* * sb.append(",c=").append(getCommand().toString()).append(",t=").append(type.toString()).append(",l=") * .append(getLength() + MessageCodec.HEADER_SIZE).append(",s=").append(getSender()).append(",r=") * .append(getRecipient()); if (LOG.isDebugEnabled()) { if (dataMap != null) { sb.append(",m={"); for * (Map.Entry<Number160, Data> entry : dataMap.entrySet()) { sb.append("k:"); sb.append(entry.getKey()); * sb.append("v:"); sb.append(entry.getValue().getHash()); } sb.append("}"); } } */ return sb.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 Message2 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 Message2 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 Message2 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 Message2 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 Message2 udp(final boolean udp) { this.udp = udp; return this; } /** * @return True if connection is UDP */ public boolean isUdp() { return udp; } /** * @param done * True if message decoding or encoding is done * @return This class */ public Message2 done(final boolean done) { this.done = done; return this; } /** * Set done to true if message decoding or encoding is done. * @return This class */ public Message2 setDone() { this.done = true; return this; } /** * @return True if message decoding or encoding is done */ public boolean isDone() { return done; } public static boolean decodeSignature(Signature signature, Message2 message, ByteBuf buffer) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, IOException { if (buffer.readableBytes() < 20 + 20) return false; byte[] me = new byte[Number160.BYTE_ARRAY_SIZE]; buffer.readBytes(me); Number160 number1 = new Number160(me); buffer.readBytes(me); Number160 number2 = new Number160(me); SHA1Signature signatureEncode = new SHA1Signature(number1, number2); byte[] signatureReceived = signatureEncode.encode(); if (!signature.verify(signatureReceived)) { // set public key only if signature is correct message.setPublicKey(null); } // set data maps return true; } }