package edu.uw.zookeeper.protocol; import static com.google.common.base.Preconditions.*; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.util.ReferenceCounted; import java.io.IOException; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; import com.google.common.base.Optional; import com.google.common.base.Throwables; import com.google.common.collect.Range; import edu.uw.zookeeper.common.AbstractPair; import edu.uw.zookeeper.net.Decoder; import edu.uw.zookeeper.net.Encoder; public final class Frame extends AbstractPair<IntHeader, ByteBuf> implements Encodable, ReferenceCounted { public static Frame fromBuffer(ByteBuf buffer) { int length = buffer.readableBytes(); return of(IntHeader.of(length), buffer); } public static Frame of(IntHeader header, ByteBuf buffer) { return new Frame(header, buffer); } public static final class FramedEncoder<I,T> implements Encoder<I,T> { public static <I,T> FramedEncoder<I,T> create(Encoder<I,T> messageEncoder) { return new FramedEncoder<I,T>(messageEncoder); } private final Logger logger; private final Encoder<I,T> messageEncoder; public FramedEncoder(Encoder<I,T> messageEncoder) { this.logger = LogManager.getLogger(getClass()); this.messageEncoder = checkNotNull(messageEncoder); } public Encoder<I,T> messageEncoder() { return messageEncoder(); } @Override public Class<? extends T> encodeType() { return messageEncoder.encodeType(); } @Override public void encode(I input, ByteBuf output) throws IOException { int beginIndex = output.writerIndex(); try { int headerLength = IntHeader.length(); if (output.writableBytes() >= headerLength) { output.writerIndex(beginIndex + headerLength); } else { output.writeInt(0); } messageEncoder.encode(input, output); int endIndex = output.writerIndex(); int length = endIndex - beginIndex - headerLength; checkState(length >= 0); output.writerIndex(beginIndex); output.writeInt(length); output.writerIndex(endIndex); } catch (Exception e) { logger.warn("Encoding {}", input, e); output.writerIndex(beginIndex); Throwables.propagateIfInstanceOf(e, IOException.class); throw Throwables.propagate(e); } } } /** * Shareable */ public static final class FrameDecoder implements Decoder<Optional<Frame>, Frame> { public static FrameDecoder getDefault() { return create(Range.atLeast(Integer.valueOf(0))); } public static FrameDecoder create(Range<Integer> bounds) { return new FrameDecoder(bounds); } private final Range<Integer> bounds; public FrameDecoder(Range<Integer> bounds) { this.bounds = checkNotNull(bounds); } public Range<Integer> bounds() { return bounds; } @Override public Class<? extends Frame> decodeType() { return Frame.class; } /** * DO NOT discard read bytes until you are done with the returned Frame. * Make sure to release() the returned Frame when you are done with it. */ @Override public Optional<Frame> decode(ByteBuf input) { Optional<Frame> output = Optional.absent(); input.markReaderIndex(); try { Optional<IntHeader> headerOutput = IntHeader.decode(input); if (headerOutput.isPresent()) { IntHeader header = headerOutput.get(); int length = header.intValue(); if (! bounds.contains(length)) { throw new IllegalArgumentException(String.format("Out of bounds frame header %s (%s)", header, bounds)); } if (input.readableBytes() >= length) { ByteBuf buffer = (length > 0) ? input.readSlice(length) : Unpooled.EMPTY_BUFFER; input.retain(); Frame frame = Frame.of(header, buffer); output = Optional.of(frame); } } } finally { if (! output.isPresent()) { input.resetReaderIndex(); } } return output; } } public static final class FramedDecoder<O,T> implements Decoder<Optional<O>, T> { public static <O,T> FramedDecoder<O,T> create( FrameDecoder frameDecoder, Decoder<O,T> messageDecoder) { return new FramedDecoder<O,T>(frameDecoder, messageDecoder); } private final Logger logger; private final FrameDecoder frameDecoder; private final Decoder<O,T> messageDecoder; public FramedDecoder( FrameDecoder frameDecoder, Decoder<O,T> messageDecoder) { this.logger = LogManager.getLogger(getClass()); this.frameDecoder = checkNotNull(frameDecoder); this.messageDecoder = checkNotNull(messageDecoder); } public FrameDecoder frameDecoder() { return frameDecoder; } public Decoder<O,T> messageDecoder() { return messageDecoder; } @Override public Class<? extends T> decodeType() { return messageDecoder.decodeType(); } @Override public Optional<O> decode(ByteBuf input) throws IOException { Optional<O> output = Optional.absent(); Optional<Frame> frameOutput = frameDecoder.decode(input); if (frameOutput.isPresent()) { Frame frame = frameOutput.get(); try { output = Optional.of(messageDecoder.decode(frame.buffer())); // it's probably an error if we didn't consume the entire frame if (frame.buffer().readableBytes() > 0) { logger.warn("Skipping {} unread bytes after {}", frame.buffer().readableBytes(), output.get()); frame.buffer().skipBytes(frame.buffer().readableBytes()); } } catch (Exception e) { logger.warn("Decoding {}", frame, e); Throwables.propagateIfInstanceOf(e, IOException.class); throw Throwables.propagate(e); } finally { frame.release(); } } return output; } } public Frame(IntHeader header, ByteBuf buffer) { super(checkNotNull(header), checkNotNull(buffer)); checkState(header.intValue() == buffer.readableBytes()); } public IntHeader header() { return first; } public ByteBuf buffer() { return second; } @Override public void encode(ByteBuf output) throws IOException { first.encode(output); output.writeBytes(second); } @Override public int refCnt() { return second.refCnt(); } @Override public ReferenceCounted retain() { return second.retain(); } @Override public ReferenceCounted retain(int increment) { return second.retain(increment); } @Override public boolean release() { return second.release(); } @Override public boolean release(int decrement) { return second.release(decrement); } }