/*
* 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.net.InetSocketAddress;
import net.tomp2p.message.Message2.Content;
import net.tomp2p.message.Message2.Type;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Encodes and decodes the header using a Netty Buffer.
*
* @author Thomas Bocek
*
*/
public final class MessageHeaderCodec {
private static final Logger LOG = LoggerFactory.getLogger(MessageHeaderCodec.class);
/**
* Empty constructor.
*/
private MessageHeaderCodec() {
}
public static final int HEADER_SIZE = 58;
/**
* Encodes the message object.
*
* The format looks as follows: 28bit p2p version - 4bit message type - 32bit id - 8bit message command - 160bit
* sender id - 16bit tcp port - 16bit udp port - 160bit recipient id - 32bit content types - 8bit options. It total,
* the header is of size 58 bytes.
*
* @param buffer
* The Netty buffer to fill
* @param message
* The message with the header that will be serialized
* @return The buffer passed as an argument
*/
public static ByteBuf encodeHeader(final ByteBuf buffer, final Message2 message) {
// CHECKSTYLE:OFF
final int versionAndType = message.getVersion() << 4 | (message.getType().ordinal() & 0xf);
// CHECKSTYLE:ON
buffer.writeInt(versionAndType); // 4
buffer.writeInt(message.getMessageId()); // 8
buffer.writeByte(message.getCommand()); // 9
buffer.writeBytes(message.getSender().getPeerId().toByteArray()); // 29
buffer.writeShort((short) message.getSender().udpPort()); // 31
buffer.writeShort((short) message.getSender().tcpPort()); // 33
buffer.writeBytes(message.getRecipient().getPeerId().toByteArray()); // 53
buffer.writeInt(encodeContentTypes(message.getContentTypes())); // 57
// CHECKSTYLE:OFF
buffer.writeByte((message.getSender().getOptions() << 4) | message.getOptions()); // 58
// CHECKSTYLE:ON
return buffer;
}
/**
* Decode a message header from a Netty buffer.
*
* The format looks as follows: 28bit p2p version - 4bit message type - 32bit id - 8bit message command - 160bit
* sender id - 16bit tcp port - 16bit udp port - 160bit recipient id - 32bit content types - 8bit options. It total,
* the header is of size 58 bytes.
*
* @param buffer
* The buffer to decode from
* @param recipient
* The recipient of the message
* @param sender
* The sender of the packet, which has been set in the socket class
* @return The partial message, only the header fields are filled
*/
public static Message2 decodeHeader(final ByteBuf buffer, final InetSocketAddress recipient,
final InetSocketAddress sender) {
LOG.debug("Decode message, recipient={}, sender={}", recipient, sender);
final Message2 message = new Message2();
final int versionAndType = buffer.readInt();
// CHECKSTYLE:OFF
message.setVersion(versionAndType >>> 4);
message.setType(Type.values()[(versionAndType & 0xf)]);
// CHECKSTYLE:ON
message.setMessageId(buffer.readInt());
final int command = buffer.readUnsignedByte();
message.setCommand((byte) command);
final Number160 senderID = readID(buffer);
final int portTCP = buffer.readUnsignedShort();
final int portUDP = buffer.readUnsignedShort();
final Number160 recipientID = readID(buffer);
message.setRecipient(new PeerAddress(recipientID, recipient));
final int contentTypes = buffer.readInt();
message.hasContent(contentTypes != 0);
message.setContentTypes(decodeContentTypes(contentTypes, message));
// set the address as we see it, important for port forwarding
// identification
final int options = buffer.readUnsignedByte();
// CHECKSTYLE:OFF
message.setOptions(options & 0xf);
final int senderOptions = options >>> 4;
// CHECKSTYLE:ON
final PeerAddress peerAddress = new PeerAddress(senderID, sender.getAddress(), portTCP, portUDP,
senderOptions);
message.setSender(peerAddress);
message.senderSocket(sender);
message.recipientSocket(recipient);
return message;
}
/**
* Read a 160bit number from a Netty buffer. I did not want to include ChannelBuffer in the class Number160.
*
* @param buffer
* The Netty buffer
* @return A 160bit number from the Netty buffer (deserialized)
*/
private static Number160 readID(final ByteBuf buffer) {
byte[] me = new byte[Number160.BYTE_ARRAY_SIZE];
buffer.readBytes(me);
return new Number160(me);
}
/**
* Encodes the content types to a 32bit number. Opposite of {@link #decodeContentTypes(int)}.
*
* @param contentTypes
* The 8 content types. Null means Empty
* @return The encoded 32bit number
*/
public static int encodeContentTypes(final Content[] contentTypes) {
int result = 0;
for (int i = 0; i < Message2.CONTENT_TYPE_LENGTH / 2; i++) {
if (contentTypes[i * 2] != null) {
// CHECKSTYLE:OFF
result |= (contentTypes[i * 2].ordinal() << (i * 8));
// CHECKSTYLE:ON
}
if (contentTypes[(i * 2) + 1] != null) {
// CHECKSTYLE:OFF
result |= ((contentTypes[(i * 2) + 1].ordinal() << 4) << (i * 8));
// CHECKSTYLE:ON
}
}
return result;
}
/**
* Decodes the content types from a 32bit number. Opposite of {@link #encodeContentTypes(Content[])}.
*
* @param contentTypes
* The 8 content types. No null values are returned
* @param message
* @return The decoded content types
*/
// CHECKSTYLE:OFF
public static Content[] decodeContentTypes(int contentTypes, Message2 message) {
Content[] result = new Content[Message2.CONTENT_TYPE_LENGTH];
for (int i = 0; i < Message2.CONTENT_TYPE_LENGTH; i++) {
Content type = Content.values()[contentTypes & Utils.MASK_0F];
result[i] = type;
if(type == Content.PUBLIC_KEY_SIGNATURE) {
message.setHintSign();
}
contentTypes >>>= 4;
// CHECKSTYLE:ON
}
return result;
}
}