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 }