1 /* 2 * Copyright (C) 2013 Square, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.squareup.okhttp.internal.spdy; 17 18 import com.squareup.okhttp.Protocol; 19 import java.io.IOException; 20 import java.util.List; 21 import okio.BufferedSink; 22 import okio.BufferedSource; 23 import okio.ByteString; 24 import okio.Deadline; 25 import okio.OkBuffer; 26 import okio.Source; 27 28 /** 29 * Read and write http/2 v09 frames. 30 * http://tools.ietf.org/html/draft-ietf-httpbis-http2-09 31 */ 32 public final class Http20Draft09 implements Variant { 33 34 @Override public Protocol getProtocol() { 35 return Protocol.HTTP_2; 36 } 37 38 private static final ByteString CONNECTION_HEADER 39 = ByteString.encodeUtf8("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"); 40 41 static final byte TYPE_DATA = 0x0; 42 static final byte TYPE_HEADERS = 0x1; 43 static final byte TYPE_PRIORITY = 0x2; 44 static final byte TYPE_RST_STREAM = 0x3; 45 static final byte TYPE_SETTINGS = 0x4; 46 static final byte TYPE_PUSH_PROMISE = 0x5; 47 static final byte TYPE_PING = 0x6; 48 static final byte TYPE_GOAWAY = 0x7; 49 static final byte TYPE_WINDOW_UPDATE = 0x9; 50 static final byte TYPE_CONTINUATION = 0xa; 51 52 static final byte FLAG_NONE = 0x0; 53 static final byte FLAG_ACK = 0x1; 54 static final byte FLAG_END_STREAM = 0x1; 55 static final byte FLAG_END_HEADERS = 0x4; // Used for headers and continuation. 56 static final byte FLAG_END_PUSH_PROMISE = 0x4; 57 static final byte FLAG_PRIORITY = 0x8; 58 59 @Override public FrameReader newReader(BufferedSource source, boolean client) { 60 return new Reader(source, 4096, client); 61 } 62 63 @Override public FrameWriter newWriter(BufferedSink sink, boolean client) { 64 return new Writer(sink, client); 65 } 66 67 @Override public int maxFrameSize() { 68 return 16383; 69 } 70 71 static final class Reader implements FrameReader { 72 private final BufferedSource source; 73 private final ContinuationSource continuation; 74 private final boolean client; 75 76 // Visible for testing. 77 final HpackDraft05.Reader hpackReader; 78 79 Reader(BufferedSource source, int headerTableSize, boolean client) { 80 this.source = source; 81 this.client = client; 82 this.continuation = new ContinuationSource(this.source); 83 this.hpackReader = new HpackDraft05.Reader(client, headerTableSize, continuation); 84 } 85 86 @Override public void readConnectionHeader() throws IOException { 87 if (client) return; // Nothing to read; servers don't send connection headers! 88 ByteString connectionHeader = source.readByteString(CONNECTION_HEADER.size()); 89 if (!CONNECTION_HEADER.equals(connectionHeader)) { 90 throw ioException("Expected a connection header but was %s", connectionHeader.utf8()); 91 } 92 } 93 94 @Override public boolean nextFrame(Handler handler) throws IOException { 95 int w1; 96 int w2; 97 try { 98 w1 = source.readInt(); 99 w2 = source.readInt(); 100 } catch (IOException e) { 101 return false; // This might be a normal socket close. 102 } 103 104 // boolean r = (w1 & 0xc0000000) != 0; // Reserved: Ignore first 2 bits. 105 short length = (short) ((w1 & 0x3fff0000) >> 16); // 14-bit unsigned == max 16383 106 byte type = (byte) ((w1 & 0xff00) >> 8); 107 byte flags = (byte) (w1 & 0xff); 108 // boolean r = (w2 & 0x80000000) != 0; // Reserved: Ignore first bit. 109 int streamId = (w2 & 0x7fffffff); // 31-bit opaque identifier. 110 111 switch (type) { 112 case TYPE_DATA: 113 readData(handler, length, flags, streamId); 114 break; 115 116 case TYPE_HEADERS: 117 readHeaders(handler, length, flags, streamId); 118 break; 119 120 case TYPE_PRIORITY: 121 readPriority(handler, length, flags, streamId); 122 break; 123 124 case TYPE_RST_STREAM: 125 readRstStream(handler, length, flags, streamId); 126 break; 127 128 case TYPE_SETTINGS: 129 readSettings(handler, length, flags, streamId); 130 break; 131 132 case TYPE_PUSH_PROMISE: 133 readPushPromise(handler, length, flags, streamId); 134 break; 135 136 case TYPE_PING: 137 readPing(handler, length, flags, streamId); 138 break; 139 140 case TYPE_GOAWAY: 141 readGoAway(handler, length, flags, streamId); 142 break; 143 144 case TYPE_WINDOW_UPDATE: 145 readWindowUpdate(handler, length, flags, streamId); 146 break; 147 148 default: 149 // Implementations MUST ignore frames of unsupported or unrecognized types. 150 source.skip(length); 151 } 152 return true; 153 } 154 155 private void readHeaders(Handler handler, short length, byte flags, int streamId) 156 throws IOException { 157 if (streamId == 0) throw ioException("PROTOCOL_ERROR: TYPE_HEADERS streamId == 0"); 158 159 boolean endStream = (flags & FLAG_END_STREAM) != 0; 160 161 int priority = -1; 162 if ((flags & FLAG_PRIORITY) != 0) { 163 priority = source.readInt() & 0x7fffffff; 164 length -= 4; // account for above read. 165 } 166 167 List<Header> headerBlock = readHeaderBlock(length, flags, streamId); 168 169 handler.headers(false, endStream, streamId, -1, priority, headerBlock, 170 HeadersMode.HTTP_20_HEADERS); 171 } 172 173 private List<Header> readHeaderBlock(short length, byte flags, int streamId) 174 throws IOException { 175 continuation.length = continuation.left = length; 176 continuation.flags = flags; 177 continuation.streamId = streamId; 178 179 hpackReader.readHeaders(); 180 hpackReader.emitReferenceSet(); 181 // TODO: Concat multi-value headers with 0x0, except COOKIE, which uses 0x3B, 0x20. 182 // http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-8.1.3 183 return hpackReader.getAndReset(); 184 } 185 186 private void readData(Handler handler, short length, byte flags, int streamId) 187 throws IOException { 188 boolean inFinished = (flags & FLAG_END_STREAM) != 0; 189 // TODO: checkState open or half-closed (local) or raise STREAM_CLOSED 190 handler.data(inFinished, streamId, source, length); 191 } 192 193 private void readPriority(Handler handler, short length, byte flags, int streamId) 194 throws IOException { 195 if (length != 4) throw ioException("TYPE_PRIORITY length: %d != 4", length); 196 if (streamId == 0) throw ioException("TYPE_PRIORITY streamId == 0"); 197 int w1 = source.readInt(); 198 // boolean r = (w1 & 0x80000000) != 0; // Reserved. 199 int priority = (w1 & 0x7fffffff); 200 handler.priority(streamId, priority); 201 } 202 203 private void readRstStream(Handler handler, short length, byte flags, int streamId) 204 throws IOException { 205 if (length != 4) throw ioException("TYPE_RST_STREAM length: %d != 4", length); 206 if (streamId == 0) throw ioException("TYPE_RST_STREAM streamId == 0"); 207 int errorCodeInt = source.readInt(); 208 ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt); 209 if (errorCode == null) { 210 throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt); 211 } 212 handler.rstStream(streamId, errorCode); 213 } 214 215 private void readSettings(Handler handler, short length, byte flags, int streamId) 216 throws IOException { 217 if (streamId != 0) throw ioException("TYPE_SETTINGS streamId != 0"); 218 if ((flags & FLAG_ACK) != 0) { 219 if (length != 0) throw ioException("FRAME_SIZE_ERROR ack frame should be empty!"); 220 handler.ackSettings(); 221 return; 222 } 223 224 if (length % 8 != 0) throw ioException("TYPE_SETTINGS length %% 8 != 0: %s", length); 225 Settings settings = new Settings(); 226 for (int i = 0; i < length; i += 8) { 227 int w1 = source.readInt(); 228 int value = source.readInt(); 229 // int r = (w1 & 0xff000000) >>> 24; // Reserved. 230 int id = w1 & 0xffffff; 231 settings.set(id, 0, value); 232 } 233 handler.settings(false, settings); 234 if (settings.getHeaderTableSize() >= 0) { 235 hpackReader.maxHeaderTableByteCount(settings.getHeaderTableSize()); 236 } 237 } 238 239 private void readPushPromise(Handler handler, short length, byte flags, int streamId) 240 throws IOException { 241 if (streamId == 0) { 242 throw ioException("PROTOCOL_ERROR: TYPE_PUSH_PROMISE streamId == 0"); 243 } 244 int promisedStreamId = source.readInt() & 0x7fffffff; 245 length -= 4; // account for above read. 246 List<Header> headerBlock = readHeaderBlock(length, flags, streamId); 247 handler.pushPromise(streamId, promisedStreamId, headerBlock); 248 } 249 250 private void readPing(Handler handler, short length, byte flags, int streamId) 251 throws IOException { 252 if (length != 8) throw ioException("TYPE_PING length != 8: %s", length); 253 if (streamId != 0) throw ioException("TYPE_PING streamId != 0"); 254 int payload1 = source.readInt(); 255 int payload2 = source.readInt(); 256 boolean ack = (flags & FLAG_ACK) != 0; 257 handler.ping(ack, payload1, payload2); 258 } 259 260 private void readGoAway(Handler handler, short length, byte flags, int streamId) 261 throws IOException { 262 if (length < 8) throw ioException("TYPE_GOAWAY length < 8: %s", length); 263 if (streamId != 0) throw ioException("TYPE_GOAWAY streamId != 0"); 264 int lastStreamId = source.readInt(); 265 int errorCodeInt = source.readInt(); 266 int opaqueDataLength = length - 8; 267 ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt); 268 if (errorCode == null) { 269 throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt); 270 } 271 ByteString debugData = ByteString.EMPTY; 272 if (opaqueDataLength > 0) { // Must read debug data in order to not corrupt the connection. 273 debugData = source.readByteString(opaqueDataLength); 274 } 275 handler.goAway(lastStreamId, errorCode, debugData); 276 } 277 278 private void readWindowUpdate(Handler handler, short length, byte flags, int streamId) 279 throws IOException { 280 if (length != 4) throw ioException("TYPE_WINDOW_UPDATE length !=4: %s", length); 281 long increment = (source.readInt() & 0x7fffffff); 282 if (increment == 0) throw ioException("windowSizeIncrement was 0", increment); 283 handler.windowUpdate(streamId, increment); 284 } 285 286 @Override public void close() throws IOException { 287 source.close(); 288 } 289 } 290 291 static final class Writer implements FrameWriter { 292 private final BufferedSink sink; 293 private final boolean client; 294 private final OkBuffer hpackBuffer; 295 private final HpackDraft05.Writer hpackWriter; 296 private boolean closed; 297 298 Writer(BufferedSink sink, boolean client) { 299 this.sink = sink; 300 this.client = client; 301 this.hpackBuffer = new OkBuffer(); 302 this.hpackWriter = new HpackDraft05.Writer(hpackBuffer); 303 } 304 305 @Override public synchronized void flush() throws IOException { 306 if (closed) throw new IOException("closed"); 307 sink.flush(); 308 } 309 310 @Override public synchronized void ackSettings() throws IOException { 311 if (closed) throw new IOException("closed"); 312 int length = 0; 313 byte type = TYPE_SETTINGS; 314 byte flags = FLAG_ACK; 315 int streamId = 0; 316 frameHeader(length, type, flags, streamId); 317 sink.flush(); 318 } 319 320 @Override public synchronized void connectionHeader() throws IOException { 321 if (closed) throw new IOException("closed"); 322 if (!client) return; // Nothing to write; servers don't send connection headers! 323 sink.write(CONNECTION_HEADER.toByteArray()); 324 sink.flush(); 325 } 326 327 @Override public synchronized void synStream(boolean outFinished, boolean inFinished, 328 int streamId, int associatedStreamId, int priority, int slot, List<Header> headerBlock) 329 throws IOException { 330 if (inFinished) throw new UnsupportedOperationException(); 331 if (closed) throw new IOException("closed"); 332 headers(outFinished, streamId, priority, headerBlock); 333 } 334 335 @Override public synchronized void synReply(boolean outFinished, int streamId, 336 List<Header> headerBlock) throws IOException { 337 if (closed) throw new IOException("closed"); 338 headers(outFinished, streamId, -1, headerBlock); 339 } 340 341 @Override public synchronized void headers(int streamId, List<Header> headerBlock) 342 throws IOException { 343 if (closed) throw new IOException("closed"); 344 headers(false, streamId, -1, headerBlock); 345 } 346 347 @Override public synchronized void pushPromise(int streamId, int promisedStreamId, 348 List<Header> requestHeaders) throws IOException { 349 if (closed) throw new IOException("closed"); 350 if (hpackBuffer.size() != 0) throw new IllegalStateException(); 351 hpackWriter.writeHeaders(requestHeaders); 352 353 int length = (int) (4 + hpackBuffer.size()); 354 byte type = TYPE_PUSH_PROMISE; 355 byte flags = FLAG_END_HEADERS; 356 frameHeader(length, type, flags, streamId); // TODO: CONTINUATION 357 sink.writeInt(promisedStreamId & 0x7fffffff); 358 sink.write(hpackBuffer, hpackBuffer.size()); 359 } 360 361 private void headers(boolean outFinished, int streamId, int priority, 362 List<Header> headerBlock) throws IOException { 363 if (closed) throw new IOException("closed"); 364 if (hpackBuffer.size() != 0) throw new IllegalStateException(); 365 hpackWriter.writeHeaders(headerBlock); 366 367 int length = (int) hpackBuffer.size(); 368 byte type = TYPE_HEADERS; 369 byte flags = FLAG_END_HEADERS; 370 if (outFinished) flags |= FLAG_END_STREAM; 371 if (priority != -1) flags |= FLAG_PRIORITY; 372 if (priority != -1) length += 4; 373 frameHeader(length, type, flags, streamId); // TODO: CONTINUATION 374 if (priority != -1) sink.writeInt(priority & 0x7fffffff); 375 sink.write(hpackBuffer, hpackBuffer.size()); 376 } 377 378 @Override public synchronized void rstStream(int streamId, ErrorCode errorCode) 379 throws IOException { 380 if (closed) throw new IOException("closed"); 381 if (errorCode.spdyRstCode == -1) throw new IllegalArgumentException(); 382 383 int length = 4; 384 byte type = TYPE_RST_STREAM; 385 byte flags = FLAG_NONE; 386 frameHeader(length, type, flags, streamId); 387 sink.writeInt(errorCode.httpCode); 388 sink.flush(); 389 } 390 391 @Override public synchronized void data(boolean outFinished, int streamId, OkBuffer source) 392 throws IOException { 393 data(outFinished, streamId, source, (int) source.size()); 394 } 395 396 @Override public synchronized void data(boolean outFinished, int streamId, OkBuffer source, 397 int byteCount) throws IOException { 398 if (closed) throw new IOException("closed"); 399 byte flags = FLAG_NONE; 400 if (outFinished) flags |= FLAG_END_STREAM; 401 dataFrame(streamId, flags, source, byteCount); 402 } 403 404 void dataFrame(int streamId, byte flags, OkBuffer buffer, int byteCount) throws IOException { 405 byte type = TYPE_DATA; 406 frameHeader(byteCount, type, flags, streamId); 407 if (byteCount > 0) { 408 sink.write(buffer, byteCount); 409 } 410 } 411 412 @Override public synchronized void settings(Settings settings) throws IOException { 413 if (closed) throw new IOException("closed"); 414 int length = settings.size() * 8; 415 byte type = TYPE_SETTINGS; 416 byte flags = FLAG_NONE; 417 int streamId = 0; 418 frameHeader(length, type, flags, streamId); 419 for (int i = 0; i < Settings.COUNT; i++) { 420 if (!settings.isSet(i)) continue; 421 sink.writeInt(i & 0xffffff); 422 sink.writeInt(settings.get(i)); 423 } 424 sink.flush(); 425 } 426 427 @Override public synchronized void ping(boolean ack, int payload1, int payload2) 428 throws IOException { 429 if (closed) throw new IOException("closed"); 430 int length = 8; 431 byte type = TYPE_PING; 432 byte flags = ack ? FLAG_ACK : FLAG_NONE; 433 int streamId = 0; 434 frameHeader(length, type, flags, streamId); 435 sink.writeInt(payload1); 436 sink.writeInt(payload2); 437 sink.flush(); 438 } 439 440 @Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode, 441 byte[] debugData) throws IOException { 442 if (closed) throw new IOException("closed"); 443 if (errorCode.httpCode == -1) throw illegalArgument("errorCode.httpCode == -1"); 444 int length = 8 + debugData.length; 445 byte type = TYPE_GOAWAY; 446 byte flags = FLAG_NONE; 447 int streamId = 0; 448 frameHeader(length, type, flags, streamId); 449 sink.writeInt(lastGoodStreamId); 450 sink.writeInt(errorCode.httpCode); 451 if (debugData.length > 0) { 452 sink.write(debugData); 453 } 454 sink.flush(); 455 } 456 457 @Override public synchronized void windowUpdate(int streamId, long windowSizeIncrement) 458 throws IOException { 459 if (closed) throw new IOException("closed"); 460 if (windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL) { 461 throw illegalArgument("windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL: %s", 462 windowSizeIncrement); 463 } 464 int length = 4; 465 byte type = TYPE_WINDOW_UPDATE; 466 byte flags = FLAG_NONE; 467 frameHeader(length, type, flags, streamId); 468 sink.writeInt((int) windowSizeIncrement); 469 sink.flush(); 470 } 471 472 @Override public synchronized void close() throws IOException { 473 closed = true; 474 sink.close(); 475 } 476 477 void frameHeader(int length, byte type, byte flags, int streamId) throws IOException { 478 if (length > 16383) throw illegalArgument("FRAME_SIZE_ERROR length > 16383: %s", length); 479 if ((streamId & 0x80000000) != 0) throw illegalArgument("reserved bit set: %s", streamId); 480 sink.writeInt((length & 0x3fff) << 16 | (type & 0xff) << 8 | (flags & 0xff)); 481 sink.writeInt(streamId & 0x7fffffff); 482 } 483 } 484 485 private static IllegalArgumentException illegalArgument(String message, Object... args) { 486 throw new IllegalArgumentException(String.format(message, args)); 487 } 488 489 private static IOException ioException(String message, Object... args) throws IOException { 490 throw new IOException(String.format(message, args)); 491 } 492 493 /** 494 * Decompression of the header block occurs above the framing layer. This 495 * class lazily reads continuation frames as they are needed by {@link 496 * HpackDraft05.Reader#readHeaders()}. 497 */ 498 static final class ContinuationSource implements Source { 499 private final BufferedSource source; 500 501 int length; 502 byte flags; 503 int streamId; 504 505 int left; 506 507 public ContinuationSource(BufferedSource source) { 508 this.source = source; 509 } 510 511 @Override public long read(OkBuffer sink, long byteCount) throws IOException { 512 while (left == 0) { 513 if ((flags & FLAG_END_HEADERS) != 0) return -1; 514 readContinuationHeader(); 515 // TODO: test case for empty continuation header? 516 } 517 518 long read = source.read(sink, Math.min(byteCount, left)); 519 if (read == -1) return -1; 520 left -= read; 521 return read; 522 } 523 524 @Override public Source deadline(Deadline deadline) { 525 source.deadline(deadline); 526 return this; 527 } 528 529 @Override public void close() throws IOException { 530 } 531 532 private void readContinuationHeader() throws IOException { 533 int previousStreamId = streamId; 534 int w1 = source.readInt(); 535 int w2 = source.readInt(); 536 length = left = (short) ((w1 & 0x3fff0000) >> 16); 537 byte type = (byte) ((w1 & 0xff00) >> 8); 538 flags = (byte) (w1 & 0xff); 539 streamId = (w2 & 0x7fffffff); 540 if (type != TYPE_CONTINUATION) throw ioException("%s != TYPE_CONTINUATION", type); 541 if (streamId != previousStreamId) throw ioException("TYPE_CONTINUATION streamId changed"); 542 } 543 } 544 } 545