/*
* 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.Inet4Address;
import java.net.InetSocketAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;
import net.tomp2p.message.Message.Content;
import net.tomp2p.message.Message.Type;
import net.tomp2p.peers.IP.IPv4;
import net.tomp2p.peers.IP.IPv6;
import net.tomp2p.peers.PeerSocketAddress.PeerSocket4Address;
import net.tomp2p.peers.PeerSocketAddress.PeerSocket6Address;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.utils.Utils;
/**
* Encodes and decodes the header of a {@code Message} sing 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_STATIC = 34;
public static final int HEADER_SIZE_MIN = HEADER_SIZE_STATIC + PeerAddress.MIN_SIZE_HEADER; //63
/**
* Encodes a message object.
*
* The format looks as follows:
* - 28bit p2p version
* - 4bit message type //4 bytes
* - 32bit message id //8 bytes
* - 8bit message command //9 bytes
* - 160bit recipient id //29 bytes
* - 32bit content types //33 bytes
* - 8bit message options. //34 bytes
* - sender peeraddress, without current IP (variable). The size is
* determined by the first 3 byte. Minimun size is 29 bytes
* //total minimun 63 bytes
*
* It total,
* the header is of size 59 bytes.
*
* @param buffer
* The buffer to encode to
* @param message
* The message with the header that will be encoded
* @return The buffer passed as an argument
*/
public static void encodeHeader(final ByteBuf buf, final Message message) {
final int versionAndType = message.version() << 4 | (message.type().ordinal() & Utils.MASK_0F);
buf.writeInt(versionAndType); // 4
buf.writeInt(message.messageId()); // 8
buf.writeByte(message.command()); // 9
message.recipient().peerId().encode(buf); //29
buf.writeInt(encodeContentTypes(message.contentTypes())); // 33
// three bits for the message options, 5 bits for the sender options
buf.writeByte(message.options()); // 34
message.sender().encode(buf);
}
/**
* Decodes a message object.
*
* The format looks as follows: 28bit p2p version - 4bit message type - 32bit message id - 8bit message command - 160bit
* sender id - 16bit sender tcp port - 16bit sender 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 recipientSocket
* The recipient of the message
* @param senderSocket
* The sender of the packet, which has been set in the socket class
* @return The partial message where only the header fields are set
*/
public static boolean decodeHeader(final ByteBuf buffer, final InetSocketAddress recipientSocket,
final InetSocketAddress senderSocket, final Message message) {
LOG.debug("Decode message. Recipient: {}, Sender:{}.", recipientSocket, senderSocket);
final int versionAndType = buffer.readInt();
message.version(versionAndType >>> 4);
message.type(Type.values()[(versionAndType & Utils.MASK_0F)]);
message.messageId(buffer.readInt());
final int command = buffer.readUnsignedByte();
message.command((byte) command);
final Number160 recipientID = Number160.decode(buffer);
//we only get the id for the recipient, the rest we already know
final PeerAddress recipient = PeerAddress.builder().peerId(recipientID).build();
message.recipient(recipient);
final int contentTypes = buffer.readInt();
message.hasContent(contentTypes != 0);
message.contentTypes(decodeContentTypes(contentTypes, message));
// set the address as we see it, important for port forwarding
// identification
final int messageOptions = buffer.readUnsignedByte();
message.options(messageOptions);
final int header = buffer.readUnsignedMedium();
final int peerAddressSize = PeerAddress.size(header);
if(3 + buffer.readableBytes() < peerAddressSize) {
return false;
}
final PeerAddress sender = PeerAddress.decode(header, buffer);
if(senderSocket.getAddress() instanceof Inet4Address) {
PeerSocket4Address psa4 = sender.ipv4Socket().withIpv4(IPv4.fromInet4Address(senderSocket.getAddress()));
message.sender(sender.withIpv4Socket(psa4));
} else {
PeerSocket6Address psa6 = sender.ipv6Socket().withIpv6(IPv6.fromInet6Address(senderSocket.getAddress()));
message.sender(sender.withIpv6Socket(psa6));
}
//keep the original sockets
message.senderSocket(senderSocket);
message.recipientSocket(recipientSocket);
return true;
}
/**
* Encodes the 8 content types to an integer (32 bit).
*
* @param contentTypes
* The 8 content types to be encoded. Null means Content.Empty
* @return The encoded 32bit integer
*/
public static int encodeContentTypes(final Content[] contentTypes) {
int result = 0;
for (int i = 0; i < Message.CONTENT_TYPE_LENGTH / 2; i++) {
if (contentTypes[i * 2] != null) {
result |= (contentTypes[i * 2].ordinal() << (i * 8));
}
if (contentTypes[(i * 2) + 1] != null) {
result |= ((contentTypes[(i * 2) + 1].ordinal() << 4) << (i * 8));
}
}
return result;
}
/**
* Decodes the 8 content types from an integer (32 bit).
*
* @param contentTypes
* The 8 content types to be decoded. No null values are returned
* @param message
* @return The decoded content types
*/
public static Content[] decodeContentTypes(int contentTypes, Message message) {
Content[] result = new Content[Message.CONTENT_TYPE_LENGTH];
for (int i = 0; i < Message.CONTENT_TYPE_LENGTH; i++) {
Content content = Content.values()[contentTypes & Utils.MASK_0F];
result[i] = content;
if(content == Content.PUBLIC_KEY_SIGNATURE) {
message.setHintSign();
}
contentTypes >>>= 4;
}
return result;
}
}