src/java.net.http/share/classes/jdk/internal/net/http/frame/FramesDecoder.java
branchhttp-client-branch
changeset 56092 fd85b2bf2b0d
parent 56089 42208b2f224e
child 56255 39e28481492d
equal deleted inserted replaced
56091:aedd6133e7a0 56092:fd85b2bf2b0d
       
     1 /*
       
     2  * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package jdk.internal.net.http.frame;
       
    27 
       
    28 import java.io.IOException;
       
    29 import java.lang.System.Logger.Level;
       
    30 import java.nio.ByteBuffer;
       
    31 import java.util.ArrayDeque;
       
    32 import java.util.ArrayList;
       
    33 import java.util.List;
       
    34 import jdk.internal.net.http.common.Log;
       
    35 import jdk.internal.net.http.common.Utils;
       
    36 import static java.nio.charset.StandardCharsets.UTF_8;
       
    37 
       
    38 /**
       
    39  * Frames Decoder
       
    40  * <p>
       
    41  * collect buffers until frame decoding is possible,
       
    42  * all decoded frames are passed to the FrameProcessor callback in order of decoding.
       
    43  *
       
    44  * It's a stateful class due to the fact that FramesDecoder stores buffers inside.
       
    45  * Should be allocated only the single instance per connection.
       
    46  */
       
    47 public class FramesDecoder {
       
    48 
       
    49     static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
       
    50     static final System.Logger DEBUG_LOGGER =
       
    51             Utils.getDebugLogger("FramesDecoder"::toString, DEBUG);
       
    52 
       
    53     @FunctionalInterface
       
    54     public interface FrameProcessor {
       
    55         void processFrame(Http2Frame frame) throws IOException;
       
    56     }
       
    57 
       
    58     private final FrameProcessor frameProcessor;
       
    59     private final int maxFrameSize;
       
    60 
       
    61     private ByteBuffer currentBuffer; // current buffer either null or hasRemaining
       
    62 
       
    63     private final ArrayDeque<ByteBuffer> tailBuffers = new ArrayDeque<>();
       
    64     private int tailSize = 0;
       
    65 
       
    66     private boolean slicedToDataFrame = false;
       
    67 
       
    68     private final List<ByteBuffer> prepareToRelease = new ArrayList<>();
       
    69 
       
    70     // if true  - Frame Header was parsed (9 bytes consumed) and subsequent fields have meaning
       
    71     // otherwise - stopped at frames boundary
       
    72     private boolean frameHeaderParsed = false;
       
    73     private int frameLength;
       
    74     private int frameType;
       
    75     private int frameFlags;
       
    76     private int frameStreamid;
       
    77     private boolean closed;
       
    78 
       
    79     /**
       
    80      * Creates Frame Decoder
       
    81      *
       
    82      * @param frameProcessor - callback for decoded frames
       
    83      */
       
    84     public FramesDecoder(FrameProcessor frameProcessor) {
       
    85         this(frameProcessor, 16 * 1024);
       
    86     }
       
    87 
       
    88     /**
       
    89      * Creates Frame Decoder
       
    90      * @param frameProcessor - callback for decoded frames
       
    91      * @param maxFrameSize - maxFrameSize accepted by this decoder
       
    92      */
       
    93     public FramesDecoder(FrameProcessor frameProcessor, int maxFrameSize) {
       
    94         this.frameProcessor = frameProcessor;
       
    95         this.maxFrameSize = Math.min(Math.max(16 * 1024, maxFrameSize), 16 * 1024 * 1024 - 1);
       
    96     }
       
    97 
       
    98     /** Threshold beyond which data is no longer copied into the current buffer,
       
    99      * if that buffer has enough unused space. */
       
   100     private static final int COPY_THRESHOLD = 8192;
       
   101 
       
   102     /**
       
   103      * Adds the data from the given buffer, and performs frame decoding if
       
   104      * possible.   Either 1) appends the data from the given buffer to the
       
   105      * current buffer ( if there is enough unused space ), or 2) adds it to the
       
   106      * next buffer in the queue.
       
   107      *
       
   108      * If there is enough data to perform frame decoding then, all buffers are
       
   109      * decoded and the FrameProcessor is invoked.
       
   110      */
       
   111     public void decode(ByteBuffer inBoundBuffer) throws IOException {
       
   112         if (closed) {
       
   113             DEBUG_LOGGER.log(Level.DEBUG, "closed: ignoring buffer (%s bytes)",
       
   114                     inBoundBuffer.remaining());
       
   115             inBoundBuffer.position(inBoundBuffer.limit());
       
   116             return;
       
   117         }
       
   118         int remaining = inBoundBuffer.remaining();
       
   119         DEBUG_LOGGER.log(Level.DEBUG, "decodes: %d", remaining);
       
   120         if (remaining > 0) {
       
   121             if (currentBuffer == null) {
       
   122                 currentBuffer = inBoundBuffer;
       
   123             } else {
       
   124                 ByteBuffer b = currentBuffer;
       
   125                 if (!tailBuffers.isEmpty()) {
       
   126                     b = tailBuffers.getLast();
       
   127                 }
       
   128 
       
   129                 int limit = b.limit();
       
   130                 int freeSpace = b.capacity() - limit;
       
   131                 if (remaining <= COPY_THRESHOLD && freeSpace >= remaining) {
       
   132                     // append the new data to the unused space in the current buffer
       
   133                     int position = b.position();
       
   134                     b.position(limit);
       
   135                     b.limit(limit + inBoundBuffer.remaining());
       
   136                     b.put(inBoundBuffer);
       
   137                     b.position(position);
       
   138                     if (b != currentBuffer)
       
   139                         tailSize += remaining;
       
   140                     DEBUG_LOGGER.log(Level.DEBUG, "copied: %d", remaining);
       
   141                 } else {
       
   142                     DEBUG_LOGGER.log(Level.DEBUG, "added: %d", remaining);
       
   143                     tailBuffers.add(inBoundBuffer);
       
   144                     tailSize += remaining;
       
   145                 }
       
   146             }
       
   147         }
       
   148         DEBUG_LOGGER.log(Level.DEBUG, "Tail size is now: %d, current=",
       
   149                 tailSize,
       
   150                 (currentBuffer == null ? 0 :
       
   151                    currentBuffer.remaining()));
       
   152         Http2Frame frame;
       
   153         while ((frame = nextFrame()) != null) {
       
   154             DEBUG_LOGGER.log(Level.DEBUG, "Got frame: %s", frame);
       
   155             frameProcessor.processFrame(frame);
       
   156             frameProcessed();
       
   157         }
       
   158     }
       
   159 
       
   160     private Http2Frame nextFrame() throws IOException {
       
   161         while (true) {
       
   162             if (currentBuffer == null) {
       
   163                 return null; // no data at all
       
   164             }
       
   165             long available = currentBuffer.remaining() + tailSize;
       
   166             if (!frameHeaderParsed) {
       
   167                 if (available >= Http2Frame.FRAME_HEADER_SIZE) {
       
   168                     parseFrameHeader();
       
   169                     if (frameLength > maxFrameSize) {
       
   170                         // connection error
       
   171                         return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
       
   172                                 "Frame type("+frameType+") "
       
   173                                 +"length("+frameLength
       
   174                                 +") exceeds MAX_FRAME_SIZE("
       
   175                                 + maxFrameSize+")");
       
   176                     }
       
   177                     frameHeaderParsed = true;
       
   178                 } else {
       
   179                     DEBUG_LOGGER.log(Level.DEBUG,
       
   180                             "Not enough data to parse header, needs: %d, has: %d",
       
   181                             Http2Frame.FRAME_HEADER_SIZE, available);
       
   182                     return null;
       
   183                 }
       
   184             }
       
   185             available = currentBuffer == null ? 0 : currentBuffer.remaining() + tailSize;
       
   186             if ((frameLength == 0) ||
       
   187                     (currentBuffer != null && available >= frameLength)) {
       
   188                 Http2Frame frame = parseFrameBody();
       
   189                 frameHeaderParsed = false;
       
   190                 // frame == null means we have to skip this frame and try parse next
       
   191                 if (frame != null) {
       
   192                     return frame;
       
   193                 }
       
   194             } else {
       
   195                 DEBUG_LOGGER.log(Level.DEBUG,
       
   196                         "Not enough data to parse frame body, needs: %d,  has: %d",
       
   197                         frameLength, available);
       
   198                 return null;  // no data for the whole frame header
       
   199             }
       
   200         }
       
   201     }
       
   202 
       
   203     private void frameProcessed() {
       
   204         prepareToRelease.clear();
       
   205     }
       
   206 
       
   207     private void parseFrameHeader() throws IOException {
       
   208         int x = getInt();
       
   209         this.frameLength = (x >>> 8) & 0x00ffffff;
       
   210         this.frameType = x & 0xff;
       
   211         this.frameFlags = getByte();
       
   212         this.frameStreamid = getInt() & 0x7fffffff;
       
   213         // R: A reserved 1-bit field.  The semantics of this bit are undefined,
       
   214         // MUST be ignored when receiving.
       
   215     }
       
   216 
       
   217     // move next buffer from tailBuffers to currentBuffer if required
       
   218     private void nextBuffer() {
       
   219         if (!currentBuffer.hasRemaining()) {
       
   220             if (!slicedToDataFrame) {
       
   221                 prepareToRelease.add(currentBuffer);
       
   222             }
       
   223             slicedToDataFrame = false;
       
   224             currentBuffer = tailBuffers.poll();
       
   225             if (currentBuffer != null) {
       
   226                 tailSize -= currentBuffer.remaining();
       
   227             }
       
   228         }
       
   229     }
       
   230 
       
   231     public int getByte() {
       
   232         int res = currentBuffer.get() & 0xff;
       
   233         nextBuffer();
       
   234         return res;
       
   235     }
       
   236 
       
   237     public int getShort() {
       
   238         if (currentBuffer.remaining() >= 2) {
       
   239             int res = currentBuffer.getShort() & 0xffff;
       
   240             nextBuffer();
       
   241             return res;
       
   242         }
       
   243         int val = getByte();
       
   244         val = (val << 8) + getByte();
       
   245         return val;
       
   246     }
       
   247 
       
   248     public int getInt() {
       
   249         if (currentBuffer.remaining() >= 4) {
       
   250             int res = currentBuffer.getInt();
       
   251             nextBuffer();
       
   252             return res;
       
   253         }
       
   254         int val = getByte();
       
   255         val = (val << 8) + getByte();
       
   256         val = (val << 8) + getByte();
       
   257         val = (val << 8) + getByte();
       
   258         return val;
       
   259 
       
   260     }
       
   261 
       
   262     public byte[] getBytes(int n) {
       
   263         byte[] bytes = new byte[n];
       
   264         int offset = 0;
       
   265         while (n > 0) {
       
   266             int length = Math.min(n, currentBuffer.remaining());
       
   267             currentBuffer.get(bytes, offset, length);
       
   268             offset += length;
       
   269             n -= length;
       
   270             nextBuffer();
       
   271         }
       
   272         return bytes;
       
   273 
       
   274     }
       
   275 
       
   276     private List<ByteBuffer> getBuffers(boolean isDataFrame, int bytecount) {
       
   277         List<ByteBuffer> res = new ArrayList<>();
       
   278         while (bytecount > 0) {
       
   279             int remaining = currentBuffer.remaining();
       
   280             int extract = Math.min(remaining, bytecount);
       
   281             ByteBuffer extractedBuf;
       
   282             if (isDataFrame) {
       
   283                 extractedBuf = Utils.sliceWithLimitedCapacity(currentBuffer, extract)
       
   284                                     .asReadOnlyBuffer();
       
   285                 slicedToDataFrame = true;
       
   286             } else {
       
   287                 // Header frames here
       
   288                 // HPACK decoding should performed under lock and immediately after frame decoding.
       
   289                 // in that case it is safe to release original buffer,
       
   290                 // because of sliced buffer has a very short life
       
   291                 extractedBuf = Utils.sliceWithLimitedCapacity(currentBuffer, extract);
       
   292             }
       
   293             res.add(extractedBuf);
       
   294             bytecount -= extract;
       
   295             nextBuffer();
       
   296         }
       
   297         return res;
       
   298     }
       
   299 
       
   300     public void close(String msg) {
       
   301         closed = true;
       
   302         tailBuffers.clear();
       
   303         int bytes = tailSize;
       
   304         ByteBuffer b = currentBuffer;
       
   305         if (b != null) {
       
   306             bytes += b.remaining();
       
   307             b.position(b.limit());
       
   308         }
       
   309         tailSize = 0;
       
   310         currentBuffer = null;
       
   311         DEBUG_LOGGER.log(Level.DEBUG, "closed %s, ignoring %d bytes", msg, bytes);
       
   312     }
       
   313 
       
   314     public void skipBytes(int bytecount) {
       
   315         while (bytecount > 0) {
       
   316             int remaining = currentBuffer.remaining();
       
   317             int extract = Math.min(remaining, bytecount);
       
   318             currentBuffer.position(currentBuffer.position() + extract);
       
   319             bytecount -= remaining;
       
   320             nextBuffer();
       
   321         }
       
   322     }
       
   323 
       
   324     private Http2Frame parseFrameBody() throws IOException {
       
   325         assert frameHeaderParsed;
       
   326         switch (frameType) {
       
   327             case DataFrame.TYPE:
       
   328                 return parseDataFrame(frameLength, frameStreamid, frameFlags);
       
   329             case HeadersFrame.TYPE:
       
   330                 return parseHeadersFrame(frameLength, frameStreamid, frameFlags);
       
   331             case PriorityFrame.TYPE:
       
   332                 return parsePriorityFrame(frameLength, frameStreamid, frameFlags);
       
   333             case ResetFrame.TYPE:
       
   334                 return parseResetFrame(frameLength, frameStreamid, frameFlags);
       
   335             case SettingsFrame.TYPE:
       
   336                 return parseSettingsFrame(frameLength, frameStreamid, frameFlags);
       
   337             case PushPromiseFrame.TYPE:
       
   338                 return parsePushPromiseFrame(frameLength, frameStreamid, frameFlags);
       
   339             case PingFrame.TYPE:
       
   340                 return parsePingFrame(frameLength, frameStreamid, frameFlags);
       
   341             case GoAwayFrame.TYPE:
       
   342                 return parseGoAwayFrame(frameLength, frameStreamid, frameFlags);
       
   343             case WindowUpdateFrame.TYPE:
       
   344                 return parseWindowUpdateFrame(frameLength, frameStreamid, frameFlags);
       
   345             case ContinuationFrame.TYPE:
       
   346                 return parseContinuationFrame(frameLength, frameStreamid, frameFlags);
       
   347             default:
       
   348                 // RFC 7540 4.1
       
   349                 // Implementations MUST ignore and discard any frame that has a type that is unknown.
       
   350                 Log.logTrace("Unknown incoming frame type: {0}", frameType);
       
   351                 skipBytes(frameLength);
       
   352                 return null;
       
   353         }
       
   354     }
       
   355 
       
   356     private Http2Frame parseDataFrame(int frameLength, int streamid, int flags) {
       
   357         // non-zero stream
       
   358         if (streamid == 0) {
       
   359             return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
       
   360                                       "zero streamId for DataFrame");
       
   361         }
       
   362         int padLength = 0;
       
   363         if ((flags & DataFrame.PADDED) != 0) {
       
   364             padLength = getByte();
       
   365             if (padLength >= frameLength) {
       
   366                 return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
       
   367                         "the length of the padding is the length of the frame payload or greater");
       
   368             }
       
   369             frameLength--;
       
   370         }
       
   371         DataFrame df = new DataFrame(streamid, flags,
       
   372                 getBuffers(true, frameLength - padLength), padLength);
       
   373         skipBytes(padLength);
       
   374         return df;
       
   375 
       
   376     }
       
   377 
       
   378     private Http2Frame parseHeadersFrame(int frameLength, int streamid, int flags) {
       
   379         // non-zero stream
       
   380         if (streamid == 0) {
       
   381             return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
       
   382                                       "zero streamId for HeadersFrame");
       
   383         }
       
   384         int padLength = 0;
       
   385         if ((flags & HeadersFrame.PADDED) != 0) {
       
   386             padLength = getByte();
       
   387             frameLength--;
       
   388         }
       
   389         boolean hasPriority = (flags & HeadersFrame.PRIORITY) != 0;
       
   390         boolean exclusive = false;
       
   391         int streamDependency = 0;
       
   392         int weight = 0;
       
   393         if (hasPriority) {
       
   394             int x = getInt();
       
   395             exclusive = (x & 0x80000000) != 0;
       
   396             streamDependency = x & 0x7fffffff;
       
   397             weight = getByte();
       
   398             frameLength -= 5;
       
   399         }
       
   400         if(frameLength < padLength) {
       
   401             return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
       
   402                     "Padding exceeds the size remaining for the header block");
       
   403         }
       
   404         HeadersFrame hf = new HeadersFrame(streamid, flags,
       
   405                 getBuffers(false, frameLength - padLength), padLength);
       
   406         skipBytes(padLength);
       
   407         if (hasPriority) {
       
   408             hf.setPriority(streamDependency, exclusive, weight);
       
   409         }
       
   410         return hf;
       
   411     }
       
   412 
       
   413     private Http2Frame parsePriorityFrame(int frameLength, int streamid, int flags) {
       
   414         // non-zero stream; no flags
       
   415         if (streamid == 0) {
       
   416             return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
       
   417                     "zero streamId for PriorityFrame");
       
   418         }
       
   419         if(frameLength != 5) {
       
   420             skipBytes(frameLength);
       
   421             return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR, streamid,
       
   422                     "PriorityFrame length is "+ frameLength+", expected 5");
       
   423         }
       
   424         int x = getInt();
       
   425         int weight = getByte();
       
   426         return new PriorityFrame(streamid, x & 0x7fffffff, (x & 0x80000000) != 0, weight);
       
   427     }
       
   428 
       
   429     private Http2Frame parseResetFrame(int frameLength, int streamid, int flags) {
       
   430         // non-zero stream; no flags
       
   431         if (streamid == 0) {
       
   432             return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
       
   433                     "zero streamId for ResetFrame");
       
   434         }
       
   435         if(frameLength != 4) {
       
   436             return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
       
   437                     "ResetFrame length is "+ frameLength+", expected 4");
       
   438         }
       
   439         return new ResetFrame(streamid, getInt());
       
   440     }
       
   441 
       
   442     private Http2Frame parseSettingsFrame(int frameLength, int streamid, int flags) {
       
   443         // only zero stream
       
   444         if (streamid != 0) {
       
   445             return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
       
   446                     "non-zero streamId for SettingsFrame");
       
   447         }
       
   448         if ((SettingsFrame.ACK & flags) != 0 && frameLength > 0) {
       
   449             // RFC 7540 6.5
       
   450             // Receipt of a SETTINGS frame with the ACK flag set and a length
       
   451             // field value other than 0 MUST be treated as a connection error
       
   452             return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
       
   453                     "ACK SettingsFrame is not empty");
       
   454         }
       
   455         if (frameLength % 6 != 0) {
       
   456             return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
       
   457                     "invalid SettingsFrame size: "+frameLength);
       
   458         }
       
   459         SettingsFrame sf = new SettingsFrame(flags);
       
   460         int n = frameLength / 6;
       
   461         for (int i=0; i<n; i++) {
       
   462             int id = getShort();
       
   463             int val = getInt();
       
   464             if (id > 0 && id <= SettingsFrame.MAX_PARAM) {
       
   465                 // a known parameter. Ignore otherwise
       
   466                 sf.setParameter(id, val); // TODO parameters validation
       
   467             }
       
   468         }
       
   469         return sf;
       
   470     }
       
   471 
       
   472     private Http2Frame parsePushPromiseFrame(int frameLength, int streamid, int flags) {
       
   473         // non-zero stream
       
   474         if (streamid == 0) {
       
   475             return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
       
   476                     "zero streamId for PushPromiseFrame");
       
   477         }
       
   478         int padLength = 0;
       
   479         if ((flags & PushPromiseFrame.PADDED) != 0) {
       
   480             padLength = getByte();
       
   481             frameLength--;
       
   482         }
       
   483         int promisedStream = getInt() & 0x7fffffff;
       
   484         frameLength -= 4;
       
   485         if(frameLength < padLength) {
       
   486             return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
       
   487                     "Padding exceeds the size remaining for the PushPromiseFrame");
       
   488         }
       
   489         PushPromiseFrame ppf = new PushPromiseFrame(streamid, flags, promisedStream,
       
   490                 getBuffers(false, frameLength - padLength), padLength);
       
   491         skipBytes(padLength);
       
   492         return ppf;
       
   493     }
       
   494 
       
   495     private Http2Frame parsePingFrame(int frameLength, int streamid, int flags) {
       
   496         // only zero stream
       
   497         if (streamid != 0) {
       
   498             return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
       
   499                     "non-zero streamId for PingFrame");
       
   500         }
       
   501         if(frameLength != 8) {
       
   502             return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
       
   503                     "PingFrame length is "+ frameLength+", expected 8");
       
   504         }
       
   505         return new PingFrame(flags, getBytes(8));
       
   506     }
       
   507 
       
   508     private Http2Frame parseGoAwayFrame(int frameLength, int streamid, int flags) {
       
   509         // only zero stream; no flags
       
   510         if (streamid != 0) {
       
   511             return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
       
   512                     "non-zero streamId for GoAwayFrame");
       
   513         }
       
   514         if (frameLength < 8) {
       
   515             return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
       
   516                     "Invalid GoAway frame size");
       
   517         }
       
   518         int lastStream = getInt() & 0x7fffffff;
       
   519         int errorCode = getInt();
       
   520         byte[] debugData = getBytes(frameLength - 8);
       
   521         if (debugData.length > 0) {
       
   522             Log.logError("GoAway debugData " + new String(debugData, UTF_8));
       
   523         }
       
   524         return new GoAwayFrame(lastStream, errorCode, debugData);
       
   525     }
       
   526 
       
   527     private Http2Frame parseWindowUpdateFrame(int frameLength, int streamid, int flags) {
       
   528         // any stream; no flags
       
   529         if(frameLength != 4) {
       
   530             return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
       
   531                     "WindowUpdateFrame length is "+ frameLength+", expected 4");
       
   532         }
       
   533         return new WindowUpdateFrame(streamid, getInt() & 0x7fffffff);
       
   534     }
       
   535 
       
   536     private Http2Frame parseContinuationFrame(int frameLength, int streamid, int flags) {
       
   537         // non-zero stream;
       
   538         if (streamid == 0) {
       
   539             return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
       
   540                     "zero streamId for ContinuationFrame");
       
   541         }
       
   542         return new ContinuationFrame(streamid, flags, getBuffers(false, frameLength));
       
   543     }
       
   544 
       
   545 }