/* * 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; } }