| /* |
| * Copyright 2015-present Open Networking Laboratory |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.onosproject.ovsdb.rfc.utils; |
| |
| import io.netty.buffer.ByteBuf; |
| import io.netty.buffer.ByteBufInputStream; |
| |
| import java.io.DataInput; |
| import java.io.IOException; |
| import java.util.List; |
| import java.util.Stack; |
| |
| import org.onosproject.ovsdb.rfc.exception.UnsupportedException; |
| import org.onosproject.ovsdb.rfc.jsonrpc.JsonReadContext; |
| |
| import com.fasterxml.jackson.core.JsonEncoding; |
| import com.fasterxml.jackson.core.JsonParseException; |
| import com.fasterxml.jackson.core.JsonParser; |
| import com.fasterxml.jackson.core.io.IOContext; |
| import com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper; |
| import com.fasterxml.jackson.core.util.BufferRecycler; |
| import com.fasterxml.jackson.databind.JsonNode; |
| import com.fasterxml.jackson.databind.MappingJsonFactory; |
| |
| /** |
| * Decoder utility class. |
| */ |
| public final class JsonRpcReaderUtil { |
| |
| /** |
| * Constructs a JsonRpcReaderUtil object. Utility classes should not have a |
| * public or default constructor, otherwise IDE will compile unsuccessfully. |
| * This class should not be instantiated. |
| */ |
| private JsonRpcReaderUtil() { |
| } |
| |
| /** |
| * Decode the bytes to Json object. |
| * @param in input of bytes |
| * @param out ouput of Json object list |
| * @param jrContext context for the last decoding process |
| * @throws IOException IOException |
| * @throws JsonParseException JsonParseException |
| */ |
| public static void readToJsonNode(ByteBuf in, List<Object> out, JsonReadContext jrContext) |
| throws JsonParseException, IOException { |
| int lastReadBytes = jrContext.getLastReadBytes(); |
| if (lastReadBytes == 0) { |
| if (in.readableBytes() < 4) { |
| return; |
| } |
| checkEncoding(in); |
| } |
| |
| int i = lastReadBytes + in.readerIndex(); |
| Stack<Byte> bufStack = jrContext.getBufStack(); |
| for (; i < in.writerIndex(); i++) { |
| byte b = in.getByte(i); |
| switch (b) { |
| case '{': |
| if (!isDoubleQuote(bufStack)) { |
| bufStack.push(b); |
| jrContext.setStartMatch(true); |
| } |
| break; |
| case '}': |
| if (!isDoubleQuote(bufStack)) { |
| bufStack.pop(); |
| } |
| break; |
| case '"': |
| if (in.getByte(i - 1) != '\\') { |
| if (!bufStack.isEmpty() && bufStack.peek() != '"') { |
| bufStack.push(b); |
| } else { |
| bufStack.pop(); |
| } |
| } |
| break; |
| default: |
| break; |
| } |
| |
| if (jrContext.isStartMatch() && bufStack.isEmpty()) { |
| ByteBuf buf = in.readSlice(i - in.readerIndex() + 1); |
| JsonParser jf = new MappingJsonFactory().createParser((DataInput) new ByteBufInputStream(buf)); |
| JsonNode jsonNode = jf.readValueAsTree(); |
| out.add(jsonNode); |
| lastReadBytes = 0; |
| jrContext.setLastReadBytes(lastReadBytes); |
| break; |
| } |
| } |
| |
| if (i >= in.writerIndex()) { |
| lastReadBytes = in.readableBytes(); |
| jrContext.setLastReadBytes(lastReadBytes); |
| } |
| } |
| |
| /** |
| * Filter the invalid characters before decoding. |
| * @param in input of bytes |
| * @param lastReadBytes the bytes for last decoding incomplete record |
| */ |
| private static void fliterCharaters(ByteBuf in) { |
| while (in.isReadable()) { |
| int ch = in.getByte(in.readerIndex()); |
| if ((ch != ' ') && (ch != '\n') && (ch != '\t') && (ch != '\r')) { |
| break; |
| } else { |
| in.readByte(); |
| } |
| } |
| } |
| |
| /** |
| * Check whether the peek of the stack element is double quote. |
| * @param jrContext context for the last decoding process |
| * @return boolean |
| */ |
| private static boolean isDoubleQuote(Stack<Byte> bufStack) { |
| if (!bufStack.isEmpty() && bufStack.peek() == '"') { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Check whether the encoding is valid. |
| * @param in input of bytes |
| * @throws IOException this is an IO exception |
| * @throws UnsupportedException this is an unsupported exception |
| */ |
| private static void checkEncoding(ByteBuf in) throws IOException { |
| int inputStart = 0; |
| int inputLength = 4; |
| fliterCharaters(in); |
| byte[] buff = new byte[4]; |
| in.getBytes(in.readerIndex(), buff); |
| ByteSourceJsonBootstrapper strapper = new ByteSourceJsonBootstrapper(new IOContext(new BufferRecycler(), |
| null, |
| false), |
| buff, inputStart, |
| inputLength); |
| JsonEncoding jsonEncoding = strapper.detectEncoding(); |
| if (!JsonEncoding.UTF8.equals(jsonEncoding)) { |
| throw new UnsupportedException("Only UTF-8 encoding is supported."); |
| } |
| } |
| |
| } |