/* ************************************************************************ # # DivConq # # http://divconq.com/ # # Copyright: # Copyright 2014 eTimeline, LLC. All rights reserved. # # License: # See the license.txt file in the project's top-level directory for details. # # Authors: # * Andy White # ************************************************************************ */ package divconq.bus.net; import java.util.List; import divconq.lang.op.OperationContext; import divconq.log.Logger; import divconq.net.ByteToMessageDecoder; import divconq.struct.RecordStruct; import divconq.struct.builder.ObjectBuilder; import divconq.struct.serial.BufferToCompositeParser; import io.netty.buffer.ByteBuf; import io.netty.buffer.EmptyByteBuf; import io.netty.channel.ChannelHandlerContext; public class StreamDecoder extends ByteToMessageDecoder { enum State { HEADER, PAYLOAD_SIZE, PAYLOAD } protected State state = State.HEADER; protected BufferToCompositeParser headerparser = null; protected ObjectBuilder builder = null; protected int size = 0; @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { if ((in instanceof EmptyByteBuf) || (in.readableBytes() == 0)) return; OperationContext.useHubContext(); Logger.trace("Decoding Stream Data: " + in.readableBytes()); switch (this.state) { case HEADER: { if (this.headerparser == null) { this.builder = new ObjectBuilder(); this.headerparser = new BufferToCompositeParser(this.builder); } this.headerparser.parseStruct(in); // if not done wait for more bytes if (!this.headerparser.isDone()) return; this.state = State.PAYLOAD_SIZE; // deliberate fall through } case PAYLOAD_SIZE: { if (in.readableBytes() < 4) return; this.size = in.readInt(); this.state = State.PAYLOAD; // deliberate fall through } case PAYLOAD: { // return here, without any state reset, means we need more before we can decide what to do if (in.readableBytes() < this.size) return; // we have enough data to send the message... StreamMessage msg = new StreamMessage(); // add Data only if there are some bytes, otherwise skip buffer allocation if (this.size > 0) { ByteBuf bb = in.readSlice(this.size); bb.retain(); msg.setData(bb); } msg.copyFields((RecordStruct) this.builder.getRoot()); out.add(msg); // set state to start over - ready to process next message this.headerparser = null; this.size = 0; this.state = State.HEADER; } } } }