Merge "Untangle the XML/Hello message decoders"
[controller.git] / opendaylight / netconf / netconf-util / src / main / java / org / opendaylight / controller / netconf / util / handler / NetconfChunkAggregator.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.controller.netconf.util.handler;
10
11 import io.netty.buffer.ByteBuf;
12 import io.netty.channel.ChannelHandlerContext;
13 import io.netty.handler.codec.ByteToMessageDecoder;
14
15 import java.util.List;
16
17 import org.slf4j.Logger;
18 import org.slf4j.LoggerFactory;
19
20 public class NetconfChunkAggregator extends ByteToMessageDecoder {
21     private final static Logger logger = LoggerFactory.getLogger(NetconfChunkAggregator.class);
22     public static final int DEFAULT_MAXIMUM_CHUNK_SIZE = 16 * 1024 * 1024;
23
24     private static enum State {
25         HEADER_ONE, // \n
26         HEADER_TWO, // #
27         HEADER_LENGTH_FIRST, // [1-9]
28         HEADER_LENGTH_OTHER, // [0-9]*\n
29         DATA,
30         FOOTER_ONE, // \n
31         FOOTER_TWO, // #
32         FOOTER_THREE, // #
33         FOOTER_FOUR, // \n
34     }
35
36     private final int maxChunkSize = DEFAULT_MAXIMUM_CHUNK_SIZE;
37     private State state = State.HEADER_ONE;
38     private long chunkSize;
39     private ByteBuf chunk;
40
41     @Override
42     protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
43         while (in.isReadable()) {
44             switch (state) {
45             case HEADER_ONE:
46             {
47                 final byte b = in.readByte();
48                 if (b != '\n') {
49                     logger.debug("Got byte {} while waiting for {}", b, (byte)'\n');
50                     throw new IllegalStateException("Malformed chunk header encountered (byte 0)");
51                 }
52
53                 state = State.HEADER_TWO;
54                 break;
55             }
56             case HEADER_TWO:
57             {
58                 final byte b = in.readByte();
59                 if (b != '#') {
60                     logger.debug("Got byte {} while waiting for {}", b, (byte)'#');
61                     throw new IllegalStateException("Malformed chunk header encountered (byte 1)");
62                 }
63
64                 state = State.HEADER_LENGTH_FIRST;
65                 break;
66             }
67             case HEADER_LENGTH_FIRST:
68             {
69                 final byte b = in.readByte();
70                 if (b < '1' || b > '9') {
71                     logger.debug("Got byte {} while waiting for {}-{}", b, (byte)'1', (byte)'9');
72                     throw new IllegalStateException("Invalid chunk size encountered (byte 0)");
73                 }
74
75                 chunkSize = b - '0';
76                 state = State.HEADER_LENGTH_OTHER;
77                 break;
78             }
79             case HEADER_LENGTH_OTHER:
80             {
81                 final byte b = in.readByte();
82                 if (b == '\n') {
83                     state = State.DATA;
84                     break;
85                 }
86
87                 if (b < '0' || b > '9') {
88                     logger.debug("Got byte {} while waiting for {}-{}", b, (byte)'0', (byte)'9');
89                     throw new IllegalStateException("Invalid chunk size encountered");
90                 }
91
92                 chunkSize *= 10;
93                 chunkSize += b - '0';
94
95                 if (chunkSize > maxChunkSize) {
96                     logger.debug("Parsed chunk size {}, maximum allowed is {}", chunkSize, maxChunkSize);
97                     throw new IllegalStateException("Maximum chunk size exceeded");
98                 }
99                 break;
100             }
101             case DATA:
102                 /*
103                  * FIXME: this gathers all data into one big chunk before passing
104                  *        it on. Make sure the pipeline can work with partial data
105                  *        and then change this piece to pass the data on as it
106                  *        comes through.
107                  */
108                 if (in.readableBytes() < chunkSize) {
109                     logger.debug("Buffer has {} bytes, need {} to complete chunk", in.readableBytes(), chunkSize);
110                     in.discardReadBytes();
111                     return;
112                 }
113
114                 chunk = in.readBytes((int)chunkSize);
115                 state = State.FOOTER_ONE;
116                 break;
117             case FOOTER_ONE:
118             {
119                 final byte b = in.readByte();
120                 if (b != '\n') {
121                     logger.debug("Got byte {} while waiting for {}", b, (byte)'\n');
122                     throw new IllegalStateException("Malformed chunk footer encountered (byte 0)");
123                 }
124
125                 state = State.FOOTER_TWO;
126                 break;
127             }
128             case FOOTER_TWO:
129             {
130                 final byte b = in.readByte();
131                 if (b != '#') {
132                     logger.debug("Got byte {} while waiting for {}", b, (byte)'#');
133                     throw new IllegalStateException("Malformed chunk footer encountered (byte 1)");
134                 }
135
136                 state = State.FOOTER_THREE;
137                 break;
138             }
139             case FOOTER_THREE:
140             {
141                 final byte b = in.readByte();
142                 if (b != '#') {
143                     logger.debug("Got byte {} while waiting for {}", b, (byte)'#');
144                     throw new IllegalStateException("Malformed chunk footer encountered (byte 2)");
145                 }
146
147                 state = State.FOOTER_FOUR;
148                 break;
149             }
150             case FOOTER_FOUR:
151             {
152                 final byte b = in.readByte();
153                 if (b != '\n') {
154                     logger.debug("Got byte {} while waiting for {}", b, (byte)'\n');
155                     throw new IllegalStateException("Malformed chunk footer encountered (byte 3)");
156                 }
157
158                 state = State.HEADER_ONE;
159                 out.add(chunk);
160                 chunkSize = 0;
161                 chunk = null;
162                 break;
163             }
164             }
165         }
166
167         in.discardReadBytes();
168     }
169 }