package com.netthreads.network.osc.decoder; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.socket.DatagramPacket; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.MessageToMessageDecoder; import io.netty.handler.codec.string.StringDecoder; import com.netthreads.osc.common.domain.OSCDefinition; import com.netthreads.osc.common.domain.OSCMessage; /** * Decode OSC Message. * */ public class OSCMessageDecoder extends MessageToMessageDecoder<DatagramPacket, OSCMessage> { private DelimiterBasedFrameDecoder delimiterBasedFrameDecoder; private LengthFieldBasedFrameDecoder lengthFieldBasedFrameDecoder; private StringDecoder stringDecoder; public OSCMessageDecoder() { delimiterBasedFrameDecoder = new DelimiterBasedFrameDecoder(8192, Delimiters.nulDelimiter()); lengthFieldBasedFrameDecoder = new LengthFieldBasedFrameDecoder(2 ^ 32, 0, 4); stringDecoder = new StringDecoder(); } /** * Decode message and populate OSCMessage from pool. * */ @Override public OSCMessage decode(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception { ByteBuf byteBuf = msg.data(); OSCMessage oscMessage = decode(ctx, byteBuf); return oscMessage; } /** * Decode message. * * @param ctx * @param byteBuf * * @return Populated message or null if not complete. * * @throws Exception */ public OSCMessage decode(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception { OSCMessage oscMessage = null; skipPadding(byteBuf, DecoderHelper.PAD_BYTES); ByteBuf data = (ByteBuf) delimiterBasedFrameDecoder.decode(ctx, byteBuf); if (data != null) { String address = stringDecoder.decode(ctx, data); if (address != null) { oscMessage = OSCMessage.$(address); skipPadding(byteBuf, DecoderHelper.PAD_BYTES); data = (ByteBuf) delimiterBasedFrameDecoder.decode(ctx, byteBuf); if (data != null) { String types = stringDecoder.decode(ctx, data); if (types != null) { skipPadding(byteBuf, DecoderHelper.PAD_BYTES); int typeCount = types.length(); // Note we skip leading comma for (int index = 1; index < typeCount; index++) { char typeChar = types.charAt(index); Object argument = extractArgument(ctx, typeChar, byteBuf); // Note type oscMessage.getTypes().add(typeChar); // Note argument object. oscMessage.addArgument(argument); } } } } } return oscMessage; } /** * Extract argument. * * @param type * @param byteBuf * * @return The argument object. * @throws Exception */ private Object extractArgument(ChannelHandlerContext ctx, char type, ByteBuf byteBuf) throws Exception { Object argument = null; switch (type) { case OSCDefinition.TYPE_STRING: ByteBuf data = (ByteBuf) delimiterBasedFrameDecoder.decode(ctx, byteBuf); argument = stringDecoder.decode(ctx, data); break; case OSCDefinition.TYPE_FLOAT: argument = byteBuf.readFloat(); break; case OSCDefinition.TYPE_INT: argument = byteBuf.readInt(); break; case OSCDefinition.TYPE_LONG: argument = byteBuf.readLong(); break; case OSCDefinition.TYPE_BLOB: argument = Unpooled.copiedBuffer((ByteBuf) lengthFieldBasedFrameDecoder.decode(ctx, byteBuf)); skipPadding(byteBuf, DecoderHelper.PAD_BYTES); break; case OSCDefinition.TYPE_TRUE: argument = byteBuf.readBoolean(); break; case OSCDefinition.TYPE_FALSE: argument = byteBuf.readBoolean(); break; case OSCDefinition.TYPE_ARRAY_START: // TODO break; case OSCDefinition.TYPE_ARRAY_END: // TODO break; default: break; } return argument; } /** * Skip pad * * @param byteBuf */ private void skipPadding(ByteBuf byteBuf, int padding) { // Calculate padding. int padBytes = DecoderHelper.padBytes(byteBuf, padding); // Skip the padding. byteBuf.readerIndex(byteBuf.readerIndex() + padBytes); } }