2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
9 package org.opendaylight.controller.netconf.nettyutil.handler;
11 import java.util.List;
13 import org.slf4j.Logger;
14 import org.slf4j.LoggerFactory;
16 import io.netty.buffer.ByteBuf;
17 import io.netty.buffer.CompositeByteBuf;
18 import io.netty.buffer.Unpooled;
19 import io.netty.channel.ChannelHandlerContext;
20 import io.netty.handler.codec.ByteToMessageDecoder;
22 public class NetconfChunkAggregator extends ByteToMessageDecoder {
23 private final static Logger logger = LoggerFactory.getLogger(NetconfChunkAggregator.class);
24 private static final String GOT_PARAM_WHILE_WAITING_FOR_PARAM = "Got byte {} while waiting for {}";
25 private static final String GOT_PARAM_WHILE_WAITING_FOR_PARAM_PARAM = "Got byte {} while waiting for {}-{}";
26 public static final int DEFAULT_MAXIMUM_CHUNK_SIZE = 16 * 1024 * 1024;
28 private static enum State {
31 HEADER_LENGTH_FIRST, // [1-9]
32 HEADER_LENGTH_OTHER, // [0-9]*\n
40 private final int maxChunkSize = DEFAULT_MAXIMUM_CHUNK_SIZE;
41 private State state = State.HEADER_ONE;
42 private long chunkSize;
43 private CompositeByteBuf chunk;
45 private void checkNewLine(byte b,String errorMessage){
47 logger.debug(GOT_PARAM_WHILE_WAITING_FOR_PARAM, b, (byte)'\n');
48 throw new IllegalStateException(errorMessage);
52 private void checkHash(byte b,String errorMessage){
54 logger.debug(GOT_PARAM_WHILE_WAITING_FOR_PARAM, b, (byte)'#');
55 throw new IllegalStateException(errorMessage);
59 private void checkChunkSize(){
60 if (chunkSize > maxChunkSize) {
61 logger.debug("Parsed chunk size {}, maximum allowed is {}", chunkSize, maxChunkSize);
62 throw new IllegalStateException("Maximum chunk size exceeded");
67 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws IllegalStateException {
68 while (in.isReadable()) {
72 final byte b = in.readByte();
73 checkNewLine(b, "Malformed chunk header encountered (byte 0)");
75 state = State.HEADER_TWO;
82 final byte b = in.readByte();
83 checkHash(b, "Malformed chunk header encountered (byte 1)");
85 state = State.HEADER_LENGTH_FIRST;
88 case HEADER_LENGTH_FIRST:
90 final byte b = in.readByte();
91 chunkSize = processHeaderLengthFirst(b);
92 state = State.HEADER_LENGTH_OTHER;
95 case HEADER_LENGTH_OTHER:
97 final byte b = in.readByte();
103 if (b < '0' || b > '9') {
104 logger.debug(GOT_PARAM_WHILE_WAITING_FOR_PARAM_PARAM, b, (byte)'0', (byte)'9');
105 throw new IllegalStateException("Invalid chunk size encountered");
109 chunkSize += b - '0';
115 * FIXME: this gathers all data into one big chunk before passing
116 * it on. Make sure the pipeline can work with partial data
117 * and then change this piece to pass the data on as it
120 if (in.readableBytes() < chunkSize) {
121 logger.debug("Buffer has {} bytes, need {} to complete chunk", in.readableBytes(), chunkSize);
122 in.discardReadBytes();
125 aggregateChunks(in.readBytes((int) chunkSize));
126 state = State.FOOTER_ONE;
130 final byte b = in.readByte();
131 checkNewLine(b,"Malformed chunk footer encountered (byte 0)");
132 state = State.FOOTER_TWO;
138 final byte b = in.readByte();
139 checkHash(b,"Malformed chunk footer encountered (byte 1)");
140 state = State.FOOTER_THREE;
145 final byte b = in.readByte();
147 // In this state, either header-of-new-chunk or message-end is expected
148 // Depends on the next character
150 extractNewChunkOrMessageEnd(b);
156 final byte b = in.readByte();
157 checkNewLine(b,"Malformed chunk footer encountered (byte 3)");
158 state = State.HEADER_ONE;
166 in.discardReadBytes();
169 private void extractNewChunkOrMessageEnd(byte b) {
170 if (isHeaderLengthFirst(b)) {
171 // Extract header length#1 from new chunk
172 chunkSize = processHeaderLengthFirst(b);
173 // Proceed with next chunk processing
174 state = State.HEADER_LENGTH_OTHER;
175 } else if (b == '#') {
176 state = State.FOOTER_FOUR;
178 logger.debug(GOT_PARAM_WHILE_WAITING_FOR_PARAM_PARAM, b, (byte) '#', (byte) '1', (byte) '9');
179 throw new IllegalStateException("Malformed chunk footer encountered (byte 2)");
183 private void initChunk() {
184 chunk = Unpooled.compositeBuffer();
187 private void aggregateChunks(ByteBuf newChunk) {
188 chunk.addComponent(chunk.numComponents(), newChunk);
190 // Update writer index, addComponent does not update it
191 chunk.writerIndex(chunk.writerIndex() + newChunk.readableBytes());
194 private static int processHeaderLengthFirst(byte b) {
195 if (!isHeaderLengthFirst(b)) {
196 logger.debug(GOT_PARAM_WHILE_WAITING_FOR_PARAM_PARAM, b, (byte)'1', (byte)'9');
197 throw new IllegalStateException("Invalid chunk size encountered (byte 0)");
203 private static boolean isHeaderLengthFirst(byte b) {
204 return b >= '1' && b <= '9';