/**
* Copyright 2012 Ronen Hamias, Anton Kharenko
*
* 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 io.scalecube.socketio.serialization;
import java.io.IOException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.util.CharsetUtil;
import io.scalecube.socketio.packets.Packet;
/**
* Class that provides encoding Socket.IO packets according to specification
* below.
*
* <h1>Encoding</h1>
* <p/>
* Messages have to be encoded before they're sent. The structure of a message
* is as follows:
* <p/>
* {@code [message type] ':' [message id ('+')] ':' [message endpoint] (':' [message
* data])}
* <p/>
* The message type is a single digit integer.
* <p/>
* The message id is an incremental integer, required for ACKs (can be
* ommitted). If the message id is followed by a {@code +}, the ACK is not handled by
* socket.io, but by the user instead.
* <p/>
* Socket.IO has built-in support for multiple channels of communication (which
* we call "multiple sockets"). Each socket is identified by an endpoint (can be
* omitted).
*
* @author Anton Kharenko
*
*/
public final class PacketEncoder {
private static final String DELIMITER = ":";
private static final byte[] DELIMITER_BYTES = DELIMITER.getBytes(CharsetUtil.UTF_8);
private static final int DELIMITER_LENGTH = DELIMITER_BYTES.length;
/**
* Don't let anyone instantiate this class.
*/
private PacketEncoder() {
}
public static ByteBuf encodePacket(final Packet packet) throws IOException {
ByteBuf dataBytes = packet.getData();
boolean hasData = dataBytes != null;
CompositeByteBuf compositeByteBuf = PooledByteBufAllocator.DEFAULT.compositeBuffer(hasData ? 1 : 2);
byte[] typeBytes = packet.getType().getValueAsBytes();
int headerCapacity = typeBytes.length + DELIMITER_LENGTH + DELIMITER_LENGTH + (hasData ? DELIMITER_LENGTH : 0);
ByteBuf headerByteBuf = PooledByteBufAllocator.DEFAULT.buffer(headerCapacity, headerCapacity);
headerByteBuf.writeBytes(typeBytes);
headerByteBuf.writeBytes(DELIMITER_BYTES);
headerByteBuf.writeBytes(DELIMITER_BYTES);
if (hasData) {
headerByteBuf.writeBytes(DELIMITER_BYTES);
}
compositeByteBuf.addComponent(headerByteBuf);
int compositeReadableBytes = headerByteBuf.readableBytes();
if (hasData) {
compositeByteBuf.addComponent(dataBytes);
compositeReadableBytes += dataBytes.readableBytes();
}
compositeByteBuf.writerIndex(compositeReadableBytes);
return compositeByteBuf;
}
}