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.incubator.http.internal.frame;
  27 
  28 import jdk.incubator.http.internal.common.Log;
  29 import jdk.incubator.http.internal.common.Utils;
  30 
  31 import java.io.IOException;
  32 import java.lang.System.Logger.Level;
  33 import java.nio.ByteBuffer;
  34 import java.util.ArrayDeque;
  35 import java.util.ArrayList;
  36 import java.util.List;
  37 import java.util.Queue;
  38 
  39 /**
  40  * Frames Decoder
  41  * <p>
  42  * collect buffers until frame decoding is possible,
  43  * all decoded frames are passed to the FrameProcessor callback in order of decoding.
  44  *
  45  * It's a stateful class due to the fact that FramesDecoder stores buffers inside.
  46  * Should be allocated only the single instance per connection.
  47  */
  48 public class FramesDecoder {
  49 
  50     static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
  51     static final System.Logger DEBUG_LOGGER =
  52             Utils.getDebugLogger("FramesDecoder"::toString, DEBUG);
  53 
  54     @FunctionalInterface
  55     public interface FrameProcessor {
  56         void processFrame(Http2Frame frame) throws IOException;
  57     }
  58 
  59     private final FrameProcessor frameProcessor;
  60     private final int maxFrameSize;
  61 
  62     private ByteBuffer currentBuffer; // current buffer either null or hasRemaining
  63 
  64     private final ArrayDeque<ByteBuffer> tailBuffers = new ArrayDeque<>();
  65     private int tailSize = 0;
  66 
  67     private boolean slicedToDataFrame = false;
  68 
  69     private final List<ByteBuffer> prepareToRelease = new ArrayList<>();
  70 
  71     // if true  - Frame Header was parsed (9 bytes consumed) and subsequent fields have meaning
  72     // otherwise - stopped at frames boundary
  73     private boolean frameHeaderParsed = false;
  74     private int frameLength;
  75     private int frameType;
  76     private int frameFlags;
  77     private int frameStreamid;
  78     private boolean closed;
  79 
  80     /**
  81      * Creates Frame Decoder
  82      *
  83      * @param frameProcessor - callback for decoded frames
  84      */
  85     public FramesDecoder(FrameProcessor frameProcessor) {
  86         this(frameProcessor, 16 * 1024);
  87     }
  88 
  89     /**
  90      * Creates Frame Decoder
  91      * @param frameProcessor - callback for decoded frames
  92      * @param maxFrameSize - maxFrameSize accepted by this decoder
  93      */
  94     public FramesDecoder(FrameProcessor frameProcessor, int maxFrameSize) {
  95         this.frameProcessor = frameProcessor;
  96         this.maxFrameSize = Math.min(Math.max(16 * 1024, maxFrameSize), 16 * 1024 * 1024 - 1);
  97     }
  98 
  99     /** Threshold beyond which data is no longer copied into the current buffer,
 100      * if that buffer has enough unused space. */
 101     private static final int COPY_THRESHOLD = 8192;
 102 
 103     /**
 104      * Adds the data from the given buffer, and performs frame decoding if
 105      * possible.   Either 1) appends the data from the given buffer to the
 106      * current buffer ( if there is enough unused space ), or 2) adds it to the
 107      * next buffer in the queue.
 108      *
 109      * If there is enough data to perform frame decoding then, all buffers are
 110      * decoded and the FrameProcessor is invoked.
 111      */
 112     public void decode(ByteBuffer inBoundBuffer) throws IOException {
 113         if (closed) {
 114             DEBUG_LOGGER.log(Level.DEBUG, "closed: ignoring buffer (%s bytes)",
 115                     inBoundBuffer.remaining());
 116             inBoundBuffer.position(inBoundBuffer.limit());
 117             return;
 118         }
 119         int remaining = inBoundBuffer.remaining();
 120         DEBUG_LOGGER.log(Level.DEBUG, "decodes: %d", remaining);
 121         if (remaining > 0) {
 122             if (currentBuffer == null) {
 123                 currentBuffer = inBoundBuffer;
 124             } else {
 125                 ByteBuffer b = currentBuffer;
 126                 if (!tailBuffers.isEmpty()) {
 127                     b = tailBuffers.getLast();
 128                 }
 129 
 130                 int limit = b.limit();
 131                 int freeSpace = b.capacity() - limit;
 132                 if (remaining <= COPY_THRESHOLD && freeSpace >= remaining) {
 133                     // append the new data to the unused space in the current buffer
 134                     int position = b.position();
 135                     b.position(limit);
 136                     b.limit(limit + inBoundBuffer.remaining());
 137                     b.put(inBoundBuffer);
 138                     b.position(position);
 139                     if (b != currentBuffer)
 140                         tailSize += remaining;
 141                     DEBUG_LOGGER.log(Level.DEBUG, "copied: %d", remaining);
 142                 } else {
 143                     DEBUG_LOGGER.log(Level.DEBUG, "added: %d", remaining);
 144                     tailBuffers.add(inBoundBuffer);
 145                     tailSize += remaining;
 146                 }
 147             }
 148         }
 149         DEBUG_LOGGER.log(Level.DEBUG, "Tail size is now: %d, current=",
 150                 tailSize,
 151                 (currentBuffer == null ? 0 :
 152                    currentBuffer.remaining()));
 153         Http2Frame frame;
 154         while ((frame = nextFrame()) != null) {
 155             DEBUG_LOGGER.log(Level.DEBUG, "Got frame: %s", frame);
 156             frameProcessor.processFrame(frame);
 157             frameProcessed();
 158         }
 159     }
 160 
 161     private Http2Frame nextFrame() throws IOException {
 162         while (true) {
 163             if (currentBuffer == null) {
 164                 return null; // no data at all
 165             }
 166             long available = currentBuffer.remaining() + tailSize;
 167             if (!frameHeaderParsed) {
 168                 if (available >= Http2Frame.FRAME_HEADER_SIZE) {
 169                     parseFrameHeader();
 170                     if (frameLength > maxFrameSize) {
 171                         // connection error
 172                         return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
 173                                 "Frame type("+frameType+") "
 174                                 +"length("+frameLength
 175                                 +") exceeds MAX_FRAME_SIZE("
 176                                 + maxFrameSize+")");
 177                     }
 178                     frameHeaderParsed = true;
 179                 } else {
 180                     DEBUG_LOGGER.log(Level.DEBUG,
 181                             "Not enough data to parse header, needs: %d, has: %d",
 182                             Http2Frame.FRAME_HEADER_SIZE, available);
 183                     return null;
 184                 }
 185             }
 186             available = currentBuffer == null ? 0 : currentBuffer.remaining() + tailSize;
 187             if ((frameLength == 0) ||
 188                     (currentBuffer != null && available >= frameLength)) {
 189                 Http2Frame frame = parseFrameBody();
 190                 frameHeaderParsed = false;
 191                 // frame == null means we have to skip this frame and try parse next
 192                 if (frame != null) {
 193                     return frame;
 194                 }
 195             } else {
 196                 DEBUG_LOGGER.log(Level.DEBUG,
 197                         "Not enough data to parse frame body, needs: %d,  has: %d",
 198                         frameLength, available);
 199                 return null;  // no data for the whole frame header
 200             }
 201         }
 202     }
 203 
 204     private void frameProcessed() {
 205         prepareToRelease.clear();
 206     }
 207 
 208     private void parseFrameHeader() throws IOException {
 209         int x = getInt();
 210         this.frameLength = (x >>> 8) & 0x00ffffff;
 211         this.frameType = x & 0xff;
 212         this.frameFlags = getByte();
 213         this.frameStreamid = getInt() & 0x7fffffff;
 214         // R: A reserved 1-bit field.  The semantics of this bit are undefined,
 215         // MUST be ignored when receiving.
 216     }
 217 
 218     // move next buffer from tailBuffers to currentBuffer if required
 219     private void nextBuffer() {
 220         if (!currentBuffer.hasRemaining()) {
 221             if (!slicedToDataFrame) {
 222                 prepareToRelease.add(currentBuffer);
 223             }
 224             slicedToDataFrame = false;
 225             currentBuffer = tailBuffers.poll();
 226             if (currentBuffer != null) {
 227                 tailSize -= currentBuffer.remaining();
 228             }
 229         }
 230     }
 231 
 232     public int getByte() {
 233         int res = currentBuffer.get() & 0xff;
 234         nextBuffer();
 235         return res;
 236     }
 237 
 238     public int getShort() {
 239         if (currentBuffer.remaining() >= 2) {
 240             int res = currentBuffer.getShort() & 0xffff;
 241             nextBuffer();
 242             return res;
 243         }
 244         int val = getByte();
 245         val = (val << 8) + getByte();
 246         return val;
 247     }
 248 
 249     public int getInt() {
 250         if (currentBuffer.remaining() >= 4) {
 251             int res = currentBuffer.getInt();
 252             nextBuffer();
 253             return res;
 254         }
 255         int val = getByte();
 256         val = (val << 8) + getByte();
 257         val = (val << 8) + getByte();
 258         val = (val << 8) + getByte();
 259         return val;
 260 
 261     }
 262 
 263     public byte[] getBytes(int n) {
 264         byte[] bytes = new byte[n];
 265         int offset = 0;
 266         while (n > 0) {
 267             int length = Math.min(n, currentBuffer.remaining());
 268             currentBuffer.get(bytes, offset, length);
 269             offset += length;
 270             n -= length;
 271             nextBuffer();
 272         }
 273         return bytes;
 274 
 275     }
 276 
 277     private List<ByteBuffer> getBuffers(boolean isDataFrame, int bytecount) {
 278         List<ByteBuffer> res = new ArrayList<>();
 279         while (bytecount > 0) {
 280             int remaining = currentBuffer.remaining();
 281             int extract = Math.min(remaining, bytecount);
 282             ByteBuffer extractedBuf;
 283             if (isDataFrame) {
 284                 extractedBuf = Utils.sliceWithLimitedCapacity(currentBuffer, extract);
 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));
 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 }