< prev index next >

src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/FramesDecoder.java

Print this page

        

*** 1,7 **** /* ! * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this --- 1,7 ---- /* ! * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this
*** 23,41 **** * questions. */ package jdk.incubator.http.internal.frame; - import jdk.incubator.http.internal.common.ByteBufferReference; import jdk.incubator.http.internal.common.Log; import jdk.incubator.http.internal.common.Utils; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; /** * Frames Decoder * <p> * collect buffers until frame decoding is possible, --- 23,42 ---- * questions. */ package jdk.incubator.http.internal.frame; import jdk.incubator.http.internal.common.Log; import jdk.incubator.http.internal.common.Utils; import java.io.IOException; + import java.lang.System.Logger.Level; import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; + import java.util.Queue; /** * Frames Decoder * <p> * collect buffers until frame decoding is possible,
*** 44,71 **** * It's a stateful class due to the fact that FramesDecoder stores buffers inside. * Should be allocated only the single instance per connection. */ public class FramesDecoder { ! @FunctionalInterface public interface FrameProcessor { void processFrame(Http2Frame frame) throws IOException; } private final FrameProcessor frameProcessor; private final int maxFrameSize; ! private ByteBufferReference currentBuffer; // current buffer either null or hasRemaining ! private final java.util.Queue<ByteBufferReference> tailBuffers = new ArrayDeque<>(); private int tailSize = 0; private boolean slicedToDataFrame = false; ! private final List<ByteBufferReference> prepareToRelease = new ArrayList<>(); // if true - Frame Header was parsed (9 bytes consumed) and subsequent fields have meaning // otherwise - stopped at frames boundary private boolean frameHeaderParsed = false; private int frameLength; --- 45,74 ---- * It's a stateful class due to the fact that FramesDecoder stores buffers inside. * Should be allocated only the single instance per connection. */ public class FramesDecoder { ! static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag. ! static final System.Logger DEBUG_LOGGER = ! Utils.getDebugLogger("FramesDecoder"::toString, DEBUG); @FunctionalInterface public interface FrameProcessor { void processFrame(Http2Frame frame) throws IOException; } private final FrameProcessor frameProcessor; private final int maxFrameSize; ! private ByteBuffer currentBuffer; // current buffer either null or hasRemaining ! private final Queue<ByteBuffer> tailBuffers = new ArrayDeque<>(); private int tailSize = 0; private boolean slicedToDataFrame = false; ! private final List<ByteBuffer> prepareToRelease = new ArrayList<>(); // if true - Frame Header was parsed (9 bytes consumed) and subsequent fields have meaning // otherwise - stopped at frames boundary private boolean frameHeaderParsed = false; private int frameLength;
*** 90,157 **** public FramesDecoder(FrameProcessor frameProcessor, int maxFrameSize) { this.frameProcessor = frameProcessor; this.maxFrameSize = Math.min(Math.max(16 * 1024, maxFrameSize), 16 * 1024 * 1024 - 1); } /** ! * put next buffer into queue, ! * if frame decoding is possible - decode all buffers and invoke FrameProcessor * ! * @param buffer ! * @throws IOException */ ! public void decode(ByteBufferReference buffer) throws IOException { ! int remaining = buffer.get().remaining(); if (remaining > 0) { if (currentBuffer == null) { currentBuffer = buffer; } else { tailBuffers.add(buffer); tailSize += remaining; } } Http2Frame frame; while ((frame = nextFrame()) != null) { frameProcessor.processFrame(frame); frameProcessed(); } } private Http2Frame nextFrame() throws IOException { while (true) { if (currentBuffer == null) { return null; // no data at all } if (!frameHeaderParsed) { ! if (currentBuffer.get().remaining() + tailSize >= Http2Frame.FRAME_HEADER_SIZE) { parseFrameHeader(); if (frameLength > maxFrameSize) { // connection error return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR, ! "Frame type("+frameType+") " +"length("+frameLength+") exceeds MAX_FRAME_SIZE("+ maxFrameSize+")"); } frameHeaderParsed = true; } else { ! return null; // no data for frame header } } if ((frameLength == 0) || ! (currentBuffer != null && currentBuffer.get().remaining() + tailSize >= frameLength)) { Http2Frame frame = parseFrameBody(); frameHeaderParsed = false; // frame == null means we have to skip this frame and try parse next if (frame != null) { return frame; } } else { return null; // no data for the whole frame header } } } private void frameProcessed() { - prepareToRelease.forEach(ByteBufferReference::clear); prepareToRelease.clear(); } private void parseFrameHeader() throws IOException { int x = getInt(); --- 93,195 ---- public FramesDecoder(FrameProcessor frameProcessor, int maxFrameSize) { this.frameProcessor = frameProcessor; this.maxFrameSize = Math.min(Math.max(16 * 1024, maxFrameSize), 16 * 1024 * 1024 - 1); } + /** Threshold beyond which data is no longer copied into the current buffer, + * if that buffer has enough unused space. */ + private static final int COPY_THRESHOLD = 8192; + /** ! * Adds the data from the given buffer, and performs frame decoding if ! * possible. Either 1) appends the data from the given buffer to the ! * current buffer ( if there is enough unused space ), or 2) adds it to the ! * next buffer in the queue. * ! * If there is enough data to perform frame decoding then, all buffers are ! * decoded and the FrameProcessor is invoked. */ ! public void decode(ByteBuffer buffer) throws IOException { ! int remaining = buffer.remaining(); ! DEBUG_LOGGER.log(Level.DEBUG, "decodes: %d", remaining); if (remaining > 0) { if (currentBuffer == null) { currentBuffer = buffer; } else { + int limit = currentBuffer.limit(); + int freeSpace = currentBuffer.capacity() - limit; + if (remaining <= COPY_THRESHOLD && freeSpace >= remaining) { + // append the new data to the unused space in the current buffer + ByteBuffer b = buffer; + int position = currentBuffer.position(); + currentBuffer.position(limit); + currentBuffer.limit(limit + b.limit()); + currentBuffer.put(b); + currentBuffer.position(position); + DEBUG_LOGGER.log(Level.DEBUG, "copied: %d", remaining); + } else { + DEBUG_LOGGER.log(Level.DEBUG, "added: %d", remaining); tailBuffers.add(buffer); tailSize += remaining; } } + } + DEBUG_LOGGER.log(Level.DEBUG, "Tail size is now: %d, current=", + tailSize, + (currentBuffer == null ? 0 : + currentBuffer.remaining())); Http2Frame frame; while ((frame = nextFrame()) != null) { + DEBUG_LOGGER.log(Level.DEBUG, "Got frame: %s", frame); frameProcessor.processFrame(frame); frameProcessed(); } } private Http2Frame nextFrame() throws IOException { while (true) { if (currentBuffer == null) { return null; // no data at all } + long available = currentBuffer.remaining() + tailSize; if (!frameHeaderParsed) { ! if (available >= Http2Frame.FRAME_HEADER_SIZE) { parseFrameHeader(); if (frameLength > maxFrameSize) { // connection error return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR, ! "Frame type("+frameType+") " ! +"length("+frameLength ! +") exceeds MAX_FRAME_SIZE(" ! + maxFrameSize+")"); } frameHeaderParsed = true; } else { ! DEBUG_LOGGER.log(Level.DEBUG, ! "Not enough data to parse header, needs: %d, has: %d", ! Http2Frame.FRAME_HEADER_SIZE, available); } } + available = currentBuffer == null ? 0 : currentBuffer.remaining() + tailSize; if ((frameLength == 0) || ! (currentBuffer != null && available >= frameLength)) { Http2Frame frame = parseFrameBody(); frameHeaderParsed = false; // frame == null means we have to skip this frame and try parse next if (frame != null) { return frame; } } else { + DEBUG_LOGGER.log(Level.DEBUG, + "Not enough data to parse frame body, needs: %d, has: %d", + frameLength, available); return null; // no data for the whole frame header } } } private void frameProcessed() { prepareToRelease.clear(); } private void parseFrameHeader() throws IOException { int x = getInt();
*** 163,207 **** // MUST be ignored when receiving. } // move next buffer from tailBuffers to currentBuffer if required private void nextBuffer() { ! if (!currentBuffer.get().hasRemaining()) { if (!slicedToDataFrame) { prepareToRelease.add(currentBuffer); } slicedToDataFrame = false; currentBuffer = tailBuffers.poll(); if (currentBuffer != null) { ! tailSize -= currentBuffer.get().remaining(); } } } public int getByte() { ! ByteBuffer buf = currentBuffer.get(); ! int res = buf.get() & 0xff; nextBuffer(); return res; } public int getShort() { ! ByteBuffer buf = currentBuffer.get(); ! if (buf.remaining() >= 2) { ! int res = buf.getShort() & 0xffff; nextBuffer(); return res; } int val = getByte(); val = (val << 8) + getByte(); return val; } public int getInt() { ! ByteBuffer buf = currentBuffer.get(); ! if (buf.remaining() >= 4) { ! int res = buf.getInt(); nextBuffer(); return res; } int val = getByte(); val = (val << 8) + getByte(); --- 201,242 ---- // MUST be ignored when receiving. } // move next buffer from tailBuffers to currentBuffer if required private void nextBuffer() { ! if (!currentBuffer.hasRemaining()) { if (!slicedToDataFrame) { prepareToRelease.add(currentBuffer); } slicedToDataFrame = false; currentBuffer = tailBuffers.poll(); if (currentBuffer != null) { ! tailSize -= currentBuffer.remaining(); } } } public int getByte() { ! int res = currentBuffer.get() & 0xff; nextBuffer(); return res; } public int getShort() { ! if (currentBuffer.remaining() >= 2) { ! int res = currentBuffer.getShort() & 0xffff; nextBuffer(); return res; } int val = getByte(); val = (val << 8) + getByte(); return val; } public int getInt() { ! if (currentBuffer.remaining() >= 4) { ! int res = currentBuffer.getInt(); nextBuffer(); return res; } int val = getByte(); val = (val << 8) + getByte();
*** 213,263 **** public byte[] getBytes(int n) { byte[] bytes = new byte[n]; int offset = 0; while (n > 0) { ! ByteBuffer buf = currentBuffer.get(); ! int length = Math.min(n, buf.remaining()); ! buf.get(bytes, offset, length); offset += length; n -= length; nextBuffer(); } return bytes; } ! private ByteBufferReference[] getBuffers(boolean isDataFrame, int bytecount) { ! List<ByteBufferReference> res = new ArrayList<>(); while (bytecount > 0) { ! ByteBuffer buf = currentBuffer.get(); ! int remaining = buf.remaining(); int extract = Math.min(remaining, bytecount); ByteBuffer extractedBuf; if (isDataFrame) { ! extractedBuf = Utils.slice(buf, extract); slicedToDataFrame = true; } else { // Header frames here // HPACK decoding should performed under lock and immediately after frame decoding. // in that case it is safe to release original buffer, // because of sliced buffer has a very short life ! extractedBuf = Utils.slice(buf, extract); } ! res.add(ByteBufferReference.of(extractedBuf)); bytecount -= extract; nextBuffer(); } ! return res.toArray(new ByteBufferReference[0]); } public void skipBytes(int bytecount) { while (bytecount > 0) { ! ByteBuffer buf = currentBuffer.get(); ! int remaining = buf.remaining(); int extract = Math.min(remaining, bytecount); ! buf.position(buf.position() + extract); bytecount -= remaining; nextBuffer(); } } --- 248,295 ---- public byte[] getBytes(int n) { byte[] bytes = new byte[n]; int offset = 0; while (n > 0) { ! int length = Math.min(n, currentBuffer.remaining()); ! currentBuffer.get(bytes, offset, length); offset += length; n -= length; nextBuffer(); } return bytes; } ! private List<ByteBuffer> getBuffers(boolean isDataFrame, int bytecount) { ! List<ByteBuffer> res = new ArrayList<>(); while (bytecount > 0) { ! int remaining = currentBuffer.remaining(); int extract = Math.min(remaining, bytecount); ByteBuffer extractedBuf; if (isDataFrame) { ! extractedBuf = Utils.slice(currentBuffer, extract); slicedToDataFrame = true; } else { // Header frames here // HPACK decoding should performed under lock and immediately after frame decoding. // in that case it is safe to release original buffer, // because of sliced buffer has a very short life ! extractedBuf = Utils.slice(currentBuffer, extract); } ! res.add(extractedBuf); bytecount -= extract; nextBuffer(); } ! return res; } public void skipBytes(int bytecount) { while (bytecount > 0) { ! int remaining = currentBuffer.remaining(); int extract = Math.min(remaining, bytecount); ! currentBuffer.position(currentBuffer.position() + extract); bytecount -= remaining; nextBuffer(); } }
*** 294,309 **** } private Http2Frame parseDataFrame(int frameLength, int streamid, int flags) { // non-zero stream if (streamid == 0) { ! return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR, "zero streamId for DataFrame"); } int padLength = 0; if ((flags & DataFrame.PADDED) != 0) { padLength = getByte(); ! if(padLength >= frameLength) { return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR, "the length of the padding is the length of the frame payload or greater"); } frameLength--; } --- 326,342 ---- } private Http2Frame parseDataFrame(int frameLength, int streamid, int flags) { // non-zero stream if (streamid == 0) { ! return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR, ! "zero streamId for DataFrame"); } int padLength = 0; if ((flags & DataFrame.PADDED) != 0) { padLength = getByte(); ! if (padLength >= frameLength) { return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR, "the length of the padding is the length of the frame payload or greater"); } frameLength--; }
*** 315,325 **** } private Http2Frame parseHeadersFrame(int frameLength, int streamid, int flags) { // non-zero stream if (streamid == 0) { ! return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR, "zero streamId for HeadersFrame"); } int padLength = 0; if ((flags & HeadersFrame.PADDED) != 0) { padLength = getByte(); frameLength--; --- 348,359 ---- } private Http2Frame parseHeadersFrame(int frameLength, int streamid, int flags) { // non-zero stream if (streamid == 0) { ! return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR, ! "zero streamId for HeadersFrame"); } int padLength = 0; if ((flags & HeadersFrame.PADDED) != 0) { padLength = getByte(); frameLength--;
< prev index next >