Merge "Bug 460 - Fix warning throughout netconf subsystem"
[controller.git] / opendaylight / netconf / netconf-util / src / main / java / org / opendaylight / controller / netconf / util / handler / NetconfXMLToHelloMessageDecoder.java
1 /*
2  * Copyright (c) 2014 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 package org.opendaylight.controller.netconf.util.handler;
9
10 import io.netty.buffer.ByteBuf;
11 import io.netty.buffer.ByteBufUtil;
12 import io.netty.channel.ChannelHandlerContext;
13 import io.netty.handler.codec.ByteToMessageDecoder;
14
15 import java.io.ByteArrayInputStream;
16 import java.io.IOException;
17 import java.nio.ByteBuffer;
18 import java.util.Arrays;
19 import java.util.List;
20
21 import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
22 import org.opendaylight.controller.netconf.api.NetconfMessage;
23 import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage;
24 import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
25 import org.opendaylight.controller.netconf.util.xml.XmlUtil;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28 import org.w3c.dom.Document;
29
30 import com.google.common.annotations.VisibleForTesting;
31 import com.google.common.base.Charsets;
32 import com.google.common.collect.ImmutableList;
33 import org.xml.sax.SAXException;
34
35 /**
36  * Customized NetconfXMLToMessageDecoder that reads additional header with
37  * session metadata from
38  * {@link org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage}
39  * . Used by netconf server to retrieve information about session metadata.
40  */
41 public final class NetconfXMLToHelloMessageDecoder extends ByteToMessageDecoder {
42     private static final Logger LOG = LoggerFactory.getLogger(NetconfXMLToHelloMessageDecoder.class);
43
44     private static final List<byte[]> POSSIBLE_ENDS = ImmutableList.of(
45             new byte[] { ']', '\n' },
46             new byte[] { ']', '\r', '\n' });
47     private static final List<byte[]> POSSIBLE_STARTS = ImmutableList.of(
48             new byte[] { '[' },
49             new byte[] { '\r', '\n', '[' },
50             new byte[] { '\n', '[' });
51
52     @Override
53     @VisibleForTesting
54     public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws IOException, SAXException, NetconfDocumentedException {
55         if (in.readableBytes() == 0) {
56             LOG.debug("No more content in incoming buffer.");
57             return;
58         }
59
60         in.markReaderIndex();
61         try {
62             LOG.trace("Received to decode: {}", ByteBufUtil.hexDump(in));
63             byte[] bytes = new byte[in.readableBytes()];
64             in.readBytes(bytes);
65
66             logMessage(bytes);
67
68             // Extract bytes containing header with additional metadata
69             String additionalHeader = null;
70             if (startsWithAdditionalHeader(bytes)) {
71                 // Auth information containing username, ip address... extracted for monitoring
72                 int endOfAuthHeader = getAdditionalHeaderEndIndex(bytes);
73                 if (endOfAuthHeader > -1) {
74                     byte[] additionalHeaderBytes = Arrays.copyOfRange(bytes, 0, endOfAuthHeader + 2);
75                     additionalHeader = additionalHeaderToString(additionalHeaderBytes);
76                     bytes = Arrays.copyOfRange(bytes, endOfAuthHeader + 2, bytes.length);
77                 }
78             }
79
80             Document doc = XmlUtil.readXmlToDocument(new ByteArrayInputStream(bytes));
81
82             final NetconfMessage message;
83             if (additionalHeader != null) {
84                 message = new NetconfHelloMessage(doc, NetconfHelloMessageAdditionalHeader.fromString(additionalHeader));
85             } else {
86                 message = new NetconfHelloMessage(doc);
87             }
88             out.add(message);
89         } finally {
90             in.discardReadBytes();
91         }
92     }
93
94     private int getAdditionalHeaderEndIndex(byte[] bytes) {
95         for (byte[] possibleEnd : POSSIBLE_ENDS) {
96             int idx = findByteSequence(bytes, possibleEnd);
97
98             if (idx != -1) {
99                 return idx;
100             }
101         }
102
103         return -1;
104     }
105
106     private static int findByteSequence(final byte[] bytes, final byte[] sequence) {
107         if (bytes.length < sequence.length) {
108             throw new IllegalArgumentException("Sequence to be found is longer than the given byte array.");
109         }
110         if (bytes.length == sequence.length) {
111             if (Arrays.equals(bytes, sequence)) {
112                 return 0;
113             } else {
114                 return -1;
115             }
116         }
117         int j = 0;
118         for (int i = 0; i < bytes.length; i++) {
119             if (bytes[i] == sequence[j]) {
120                 j++;
121                 if (j == sequence.length) {
122                     return i - j + 1;
123                 }
124             } else {
125                 j = 0;
126             }
127         }
128         return -1;
129     }
130
131
132     private void logMessage(byte[] bytes) {
133         String s = Charsets.UTF_8.decode(ByteBuffer.wrap(bytes)).toString();
134         LOG.debug("Parsing message \n{}", s);
135     }
136
137     private boolean startsWithAdditionalHeader(byte[] bytes) {
138         for (byte[] possibleStart : POSSIBLE_STARTS) {
139             int i = 0;
140             for (byte b : possibleStart) {
141                 if(bytes[i++] != b) {
142                     break;
143                 }
144
145                 if(i == possibleStart.length) {
146                     return true;
147                 }
148             }
149         }
150
151         return false;
152     }
153
154     private String additionalHeaderToString(byte[] bytes) {
155         return Charsets.UTF_8.decode(ByteBuffer.wrap(bytes)).toString();
156     }
157
158 }