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 java.io.IOException; 19 import java.util.Arrays; 20 import java.util.List; 21 import okio.ByteString; 22 import okio.OkBuffer; 23 import org.junit.Before; 24 import org.junit.Test; 25 26 import static com.squareup.okhttp.internal.Util.headerEntries; 27 import static org.junit.Assert.assertEquals; 28 import static org.junit.Assert.assertFalse; 29 import static org.junit.Assert.assertTrue; 30 31 public class HpackDraft05Test { 32 33 private final OkBuffer bytesIn = new OkBuffer(); 34 private HpackDraft05.Reader hpackReader; 35 private OkBuffer bytesOut = new OkBuffer(); 36 private HpackDraft05.Writer hpackWriter; 37 38 @Before public void reset() { 39 hpackReader = newReader(bytesIn); 40 hpackWriter = new HpackDraft05.Writer(bytesOut); 41 } 42 43 /** 44 * Variable-length quantity special cases strings which are longer than 127 45 * bytes. Values such as cookies can be 4KiB, and should be possible to send. 46 * 47 * <p> http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#section-4.1.2 48 */ 49 @Test public void largeHeaderValue() throws IOException { 50 char[] value = new char[4096]; 51 Arrays.fill(value, '!'); 52 List<Header> headerBlock = headerEntries("cookie", new String(value)); 53 54 hpackWriter.writeHeaders(headerBlock); 55 bytesIn.write(bytesOut, bytesOut.size()); 56 hpackReader.readHeaders(); 57 hpackReader.emitReferenceSet(); 58 59 assertEquals(0, hpackReader.headerCount); 60 61 assertEquals(headerBlock, hpackReader.getAndReset()); 62 } 63 64 /** 65 * HPACK has a max header table size, which can be smaller than the max header message. 66 * Ensure the larger header content is not lost. 67 */ 68 @Test public void tooLargeToHPackIsStillEmitted() throws IOException { 69 OkBuffer out = new OkBuffer(); 70 71 out.writeByte(0x00); // Literal indexed 72 out.writeByte(0x0a); // Literal name (len = 10) 73 out.writeUtf8("custom-key"); 74 75 out.writeByte(0x0d); // Literal value (len = 13) 76 out.writeUtf8("custom-header"); 77 78 bytesIn.write(out, out.size()); 79 hpackReader.maxHeaderTableByteCount(1); 80 hpackReader.readHeaders(); 81 hpackReader.emitReferenceSet(); 82 83 assertEquals(0, hpackReader.headerCount); 84 85 assertEquals(headerEntries("custom-key", "custom-header"), hpackReader.getAndReset()); 86 } 87 88 /** Oldest entries are evicted to support newer ones. */ 89 @Test public void testEviction() throws IOException { 90 OkBuffer out = new OkBuffer(); 91 92 out.writeByte(0x00); // Literal indexed 93 out.writeByte(0x0a); // Literal name (len = 10) 94 out.writeUtf8("custom-foo"); 95 96 out.writeByte(0x0d); // Literal value (len = 13) 97 out.writeUtf8("custom-header"); 98 99 out.writeByte(0x00); // Literal indexed 100 out.writeByte(0x0a); // Literal name (len = 10) 101 out.writeUtf8("custom-bar"); 102 103 out.writeByte(0x0d); // Literal value (len = 13) 104 out.writeUtf8("custom-header"); 105 106 out.writeByte(0x00); // Literal indexed 107 out.writeByte(0x0a); // Literal name (len = 10) 108 out.writeUtf8("custom-baz"); 109 110 out.writeByte(0x0d); // Literal value (len = 13) 111 out.writeUtf8("custom-header"); 112 113 bytesIn.write(out, out.size()); 114 // Set to only support 110 bytes (enough for 2 headers). 115 hpackReader.maxHeaderTableByteCount(110); 116 hpackReader.readHeaders(); 117 hpackReader.emitReferenceSet(); 118 119 assertEquals(2, hpackReader.headerCount); 120 121 Header entry = hpackReader.headerTable[headerTableLength() - 1]; 122 checkEntry(entry, "custom-bar", "custom-header", 55); 123 assertHeaderReferenced(headerTableLength() - 1); 124 125 entry = hpackReader.headerTable[headerTableLength() - 2]; 126 checkEntry(entry, "custom-baz", "custom-header", 55); 127 assertHeaderReferenced(headerTableLength() - 2); 128 129 // foo isn't here as it is no longer in the table. 130 // TODO: emit before eviction? 131 assertEquals(headerEntries("custom-bar", "custom-header", "custom-baz", "custom-header"), 132 hpackReader.getAndReset()); 133 134 // Simulate receiving a small settings frame, that implies eviction. 135 hpackReader.maxHeaderTableByteCount(55); 136 assertEquals(1, hpackReader.headerCount); 137 } 138 139 /** Header table backing array is initially 8 long, let's ensure it grows. */ 140 @Test public void dynamicallyGrowsBeyond64Entries() throws IOException { 141 OkBuffer out = new OkBuffer(); 142 143 for (int i = 0; i < 256; i++) { 144 out.writeByte(0x00); // Literal indexed 145 out.writeByte(0x0a); // Literal name (len = 10) 146 out.writeUtf8("custom-foo"); 147 148 out.writeByte(0x0d); // Literal value (len = 13) 149 out.writeUtf8("custom-header"); 150 } 151 152 bytesIn.write(out, out.size()); 153 hpackReader.maxHeaderTableByteCount(16384); // Lots of headers need more room! 154 hpackReader.readHeaders(); 155 hpackReader.emitReferenceSet(); 156 157 assertEquals(256, hpackReader.headerCount); 158 assertHeaderReferenced(headerTableLength() - 1); 159 assertHeaderReferenced(headerTableLength() - hpackReader.headerCount); 160 } 161 162 @Test public void huffmanDecodingSupported() throws IOException { 163 OkBuffer out = new OkBuffer(); 164 165 out.writeByte(0x04); // == Literal indexed == 166 // Indexed name (idx = 4) -> :path 167 out.writeByte(0x8b); // Literal value Huffman encoded 11 bytes 168 // decodes to www.example.com which is length 15 169 byte[] huffmanBytes = new byte[] { 170 (byte) 0xdb, (byte) 0x6d, (byte) 0x88, (byte) 0x3e, 171 (byte) 0x68, (byte) 0xd1, (byte) 0xcb, (byte) 0x12, 172 (byte) 0x25, (byte) 0xba, (byte) 0x7f}; 173 out.write(huffmanBytes, 0, huffmanBytes.length); 174 175 bytesIn.write(out, out.size()); 176 hpackReader.readHeaders(); 177 hpackReader.emitReferenceSet(); 178 179 assertEquals(1, hpackReader.headerCount); 180 assertEquals(52, hpackReader.headerTableByteCount); 181 182 Header entry = hpackReader.headerTable[headerTableLength() - 1]; 183 checkEntry(entry, ":path", "www.example.com", 52); 184 assertHeaderReferenced(headerTableLength() - 1); 185 } 186 187 /** 188 * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.1 189 */ 190 @Test public void readLiteralHeaderFieldWithIndexing() throws IOException { 191 OkBuffer out = new OkBuffer(); 192 193 out.writeByte(0x00); // Literal indexed 194 out.writeByte(0x0a); // Literal name (len = 10) 195 out.writeUtf8("custom-key"); 196 197 out.writeByte(0x0d); // Literal value (len = 13) 198 out.writeUtf8("custom-header"); 199 200 bytesIn.write(out, out.size()); 201 hpackReader.readHeaders(); 202 hpackReader.emitReferenceSet(); 203 204 assertEquals(1, hpackReader.headerCount); 205 assertEquals(55, hpackReader.headerTableByteCount); 206 207 Header entry = hpackReader.headerTable[headerTableLength() - 1]; 208 checkEntry(entry, "custom-key", "custom-header", 55); 209 assertHeaderReferenced(headerTableLength() - 1); 210 211 assertEquals(headerEntries("custom-key", "custom-header"), hpackReader.getAndReset()); 212 } 213 214 /** 215 * Literal Header Field without Indexing - New Name 216 */ 217 @Test public void literalHeaderFieldWithoutIndexingNewName() throws IOException { 218 List<Header> headerBlock = headerEntries("custom-key", "custom-header"); 219 220 OkBuffer expectedBytes = new OkBuffer(); 221 222 expectedBytes.writeByte(0x40); // Not indexed 223 expectedBytes.writeByte(0x0a); // Literal name (len = 10) 224 expectedBytes.write("custom-key".getBytes(), 0, 10); 225 226 expectedBytes.writeByte(0x0d); // Literal value (len = 13) 227 expectedBytes.write("custom-header".getBytes(), 0, 13); 228 229 hpackWriter.writeHeaders(headerBlock); 230 assertEquals(expectedBytes, bytesOut); 231 232 bytesIn.write(bytesOut, bytesOut.size()); 233 hpackReader.readHeaders(); 234 hpackReader.emitReferenceSet(); 235 236 assertEquals(0, hpackReader.headerCount); 237 238 assertEquals(headerBlock, hpackReader.getAndReset()); 239 } 240 241 /** 242 * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.2 243 */ 244 @Test public void literalHeaderFieldWithoutIndexingIndexedName() throws IOException { 245 List<Header> headerBlock = headerEntries(":path", "/sample/path"); 246 247 OkBuffer expectedBytes = new OkBuffer(); 248 expectedBytes.writeByte(0x44); // == Literal not indexed == 249 // Indexed name (idx = 4) -> :path 250 expectedBytes.writeByte(0x0c); // Literal value (len = 12) 251 expectedBytes.write("/sample/path".getBytes(), 0, 12); 252 253 hpackWriter.writeHeaders(headerBlock); 254 assertEquals(expectedBytes, bytesOut); 255 256 bytesIn.write(bytesOut, bytesOut.size()); 257 hpackReader.readHeaders(); 258 hpackReader.emitReferenceSet(); 259 260 assertEquals(0, hpackReader.headerCount); 261 262 assertEquals(headerBlock, hpackReader.getAndReset()); 263 } 264 265 /** 266 * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.3 267 */ 268 @Test public void readIndexedHeaderField() throws IOException { 269 bytesIn.writeByte(0x82); // == Indexed - Add == 270 // idx = 2 -> :method: GET 271 272 hpackReader.readHeaders(); 273 hpackReader.emitReferenceSet(); 274 275 assertEquals(1, hpackReader.headerCount); 276 assertEquals(42, hpackReader.headerTableByteCount); 277 278 Header entry = hpackReader.headerTable[headerTableLength() - 1]; 279 checkEntry(entry, ":method", "GET", 42); 280 assertHeaderReferenced(headerTableLength() - 1); 281 282 assertEquals(headerEntries(":method", "GET"), hpackReader.getAndReset()); 283 } 284 285 /** 286 * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#section-3.2.1 287 */ 288 @Test public void toggleIndex() throws IOException { 289 // Static table entries are copied to the top of the reference set. 290 bytesIn.writeByte(0x82); // == Indexed - Add == 291 // idx = 2 -> :method: GET 292 // Specifying an index to an entry in the reference set removes it. 293 bytesIn.writeByte(0x81); // == Indexed - Remove == 294 // idx = 1 -> :method: GET 295 296 hpackReader.readHeaders(); 297 hpackReader.emitReferenceSet(); 298 299 assertEquals(1, hpackReader.headerCount); 300 assertEquals(42, hpackReader.headerTableByteCount); 301 302 Header entry = hpackReader.headerTable[headerTableLength() - 1]; 303 checkEntry(entry, ":method", "GET", 42); 304 assertHeaderNotReferenced(headerTableLength() - 1); 305 306 assertTrue(hpackReader.getAndReset().isEmpty()); 307 } 308 309 /** Ensure a later toggle of the same index emits! */ 310 @Test public void toggleIndexOffOn() throws IOException { 311 312 bytesIn.writeByte(0x82); // Copy static header 1 to the header table as index 1. 313 bytesIn.writeByte(0x81); // Remove index 1 from the reference set. 314 315 hpackReader.readHeaders(); 316 hpackReader.emitReferenceSet(); 317 assertEquals(1, hpackReader.headerCount); 318 assertTrue(hpackReader.getAndReset().isEmpty()); 319 320 bytesIn.writeByte(0x81); // Add index 1 back to the reference set. 321 322 hpackReader.readHeaders(); 323 hpackReader.emitReferenceSet(); 324 assertEquals(1, hpackReader.headerCount); 325 assertEquals(headerEntries(":method", "GET"), hpackReader.getAndReset()); 326 } 327 328 /** Check later toggle of the same index for large header sets. */ 329 @Test public void toggleIndexOffBeyond64Entries() throws IOException { 330 int expectedHeaderCount = 65; 331 332 for (int i = 0; i < expectedHeaderCount; i++) { 333 bytesIn.writeByte(0x82 + i); // Copy static header 1 to the header table as index 1. 334 bytesIn.writeByte(0x81); // Remove index 1 from the reference set. 335 } 336 337 hpackReader.readHeaders(); 338 hpackReader.emitReferenceSet(); 339 assertEquals(expectedHeaderCount, hpackReader.headerCount); 340 assertTrue(hpackReader.getAndReset().isEmpty()); 341 342 bytesIn.writeByte(0x81); // Add index 1 back to the reference set. 343 344 hpackReader.readHeaders(); 345 hpackReader.emitReferenceSet(); 346 assertEquals(expectedHeaderCount, hpackReader.headerCount); 347 assertHeaderReferenced(headerTableLength() - expectedHeaderCount); 348 assertEquals(headerEntries(":method", "GET"), hpackReader.getAndReset()); 349 } 350 351 /** 352 * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.4 353 */ 354 @Test public void readIndexedHeaderFieldFromStaticTableWithoutBuffering() throws IOException { 355 bytesIn.writeByte(0x82); // == Indexed - Add == 356 // idx = 2 -> :method: GET 357 358 hpackReader.maxHeaderTableByteCount(0); // SETTINGS_HEADER_TABLE_SIZE == 0 359 hpackReader.readHeaders(); 360 hpackReader.emitReferenceSet(); 361 362 // Not buffered in header table. 363 assertEquals(0, hpackReader.headerCount); 364 365 assertEquals(headerEntries(":method", "GET"), hpackReader.getAndReset()); 366 } 367 368 /** 369 * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.2 370 */ 371 @Test public void readRequestExamplesWithoutHuffman() throws IOException { 372 OkBuffer out = firstRequestWithoutHuffman(); 373 bytesIn.write(out, out.size()); 374 hpackReader.readHeaders(); 375 hpackReader.emitReferenceSet(); 376 checkReadFirstRequestWithoutHuffman(); 377 378 out = secondRequestWithoutHuffman(); 379 bytesIn.write(out, out.size()); 380 hpackReader.readHeaders(); 381 hpackReader.emitReferenceSet(); 382 checkReadSecondRequestWithoutHuffman(); 383 384 out = thirdRequestWithoutHuffman(); 385 bytesIn.write(out, out.size()); 386 hpackReader.readHeaders(); 387 hpackReader.emitReferenceSet(); 388 checkReadThirdRequestWithoutHuffman(); 389 } 390 391 private OkBuffer firstRequestWithoutHuffman() { 392 OkBuffer out = new OkBuffer(); 393 394 out.writeByte(0x82); // == Indexed - Add == 395 // idx = 2 -> :method: GET 396 out.writeByte(0x87); // == Indexed - Add == 397 // idx = 7 -> :scheme: http 398 out.writeByte(0x86); // == Indexed - Add == 399 // idx = 6 -> :path: / 400 out.writeByte(0x04); // == Literal indexed == 401 // Indexed name (idx = 4) -> :authority 402 out.writeByte(0x0f); // Literal value (len = 15) 403 out.writeUtf8("www.example.com"); 404 405 return out; 406 } 407 408 private void checkReadFirstRequestWithoutHuffman() { 409 assertEquals(4, hpackReader.headerCount); 410 411 // [ 1] (s = 57) :authority: www.example.com 412 Header entry = hpackReader.headerTable[headerTableLength() - 4]; 413 checkEntry(entry, ":authority", "www.example.com", 57); 414 assertHeaderReferenced(headerTableLength() - 4); 415 416 // [ 2] (s = 38) :path: / 417 entry = hpackReader.headerTable[headerTableLength() - 3]; 418 checkEntry(entry, ":path", "/", 38); 419 assertHeaderReferenced(headerTableLength() - 3); 420 421 // [ 3] (s = 43) :scheme: http 422 entry = hpackReader.headerTable[headerTableLength() - 2]; 423 checkEntry(entry, ":scheme", "http", 43); 424 assertHeaderReferenced(headerTableLength() - 2); 425 426 // [ 4] (s = 42) :method: GET 427 entry = hpackReader.headerTable[headerTableLength() - 1]; 428 checkEntry(entry, ":method", "GET", 42); 429 assertHeaderReferenced(headerTableLength() - 1); 430 431 // Table size: 180 432 assertEquals(180, hpackReader.headerTableByteCount); 433 434 // Decoded header set: 435 assertEquals(headerEntries( 436 ":method", "GET", 437 ":scheme", "http", 438 ":path", "/", 439 ":authority", "www.example.com"), hpackReader.getAndReset()); 440 } 441 442 private OkBuffer secondRequestWithoutHuffman() { 443 OkBuffer out = new OkBuffer(); 444 445 out.writeByte(0x1b); // == Literal indexed == 446 // Indexed name (idx = 27) -> cache-control 447 out.writeByte(0x08); // Literal value (len = 8) 448 out.writeUtf8("no-cache"); 449 450 return out; 451 } 452 453 private void checkReadSecondRequestWithoutHuffman() { 454 assertEquals(5, hpackReader.headerCount); 455 456 // [ 1] (s = 53) cache-control: no-cache 457 Header entry = hpackReader.headerTable[headerTableLength() - 5]; 458 checkEntry(entry, "cache-control", "no-cache", 53); 459 assertHeaderReferenced(headerTableLength() - 5); 460 461 // [ 2] (s = 57) :authority: www.example.com 462 entry = hpackReader.headerTable[headerTableLength() - 4]; 463 checkEntry(entry, ":authority", "www.example.com", 57); 464 assertHeaderReferenced(headerTableLength() - 4); 465 466 // [ 3] (s = 38) :path: / 467 entry = hpackReader.headerTable[headerTableLength() - 3]; 468 checkEntry(entry, ":path", "/", 38); 469 assertHeaderReferenced(headerTableLength() - 3); 470 471 // [ 4] (s = 43) :scheme: http 472 entry = hpackReader.headerTable[headerTableLength() - 2]; 473 checkEntry(entry, ":scheme", "http", 43); 474 assertHeaderReferenced(headerTableLength() - 2); 475 476 // [ 5] (s = 42) :method: GET 477 entry = hpackReader.headerTable[headerTableLength() - 1]; 478 checkEntry(entry, ":method", "GET", 42); 479 assertHeaderReferenced(headerTableLength() - 1); 480 481 // Table size: 233 482 assertEquals(233, hpackReader.headerTableByteCount); 483 484 // Decoded header set: 485 assertEquals(headerEntries( 486 ":method", "GET", 487 ":scheme", "http", 488 ":path", "/", 489 ":authority", "www.example.com", 490 "cache-control", "no-cache"), hpackReader.getAndReset()); 491 } 492 493 private OkBuffer thirdRequestWithoutHuffman() { 494 OkBuffer out = new OkBuffer(); 495 496 out.writeByte(0x80); // == Empty reference set == 497 out.writeByte(0x85); // == Indexed - Add == 498 // idx = 5 -> :method: GET 499 out.writeByte(0x8c); // == Indexed - Add == 500 // idx = 12 -> :scheme: https 501 out.writeByte(0x8b); // == Indexed - Add == 502 // idx = 11 -> :path: /index.html 503 out.writeByte(0x84); // == Indexed - Add == 504 // idx = 4 -> :authority: www.example.com 505 out.writeByte(0x00); // Literal indexed 506 out.writeByte(0x0a); // Literal name (len = 10) 507 out.writeUtf8("custom-key"); 508 out.writeByte(0x0c); // Literal value (len = 12) 509 out.writeUtf8("custom-value"); 510 511 return out; 512 } 513 514 private void checkReadThirdRequestWithoutHuffman() { 515 assertEquals(8, hpackReader.headerCount); 516 517 // [ 1] (s = 54) custom-key: custom-value 518 Header entry = hpackReader.headerTable[headerTableLength() - 8]; 519 checkEntry(entry, "custom-key", "custom-value", 54); 520 assertHeaderReferenced(headerTableLength() - 8); 521 522 // [ 2] (s = 48) :path: /index.html 523 entry = hpackReader.headerTable[headerTableLength() - 7]; 524 checkEntry(entry, ":path", "/index.html", 48); 525 assertHeaderReferenced(headerTableLength() - 7); 526 527 // [ 3] (s = 44) :scheme: https 528 entry = hpackReader.headerTable[headerTableLength() - 6]; 529 checkEntry(entry, ":scheme", "https", 44); 530 assertHeaderReferenced(headerTableLength() - 6); 531 532 // [ 4] (s = 53) cache-control: no-cache 533 entry = hpackReader.headerTable[headerTableLength() - 5]; 534 checkEntry(entry, "cache-control", "no-cache", 53); 535 assertHeaderNotReferenced(headerTableLength() - 5); 536 537 // [ 5] (s = 57) :authority: www.example.com 538 entry = hpackReader.headerTable[headerTableLength() - 4]; 539 checkEntry(entry, ":authority", "www.example.com", 57); 540 assertHeaderReferenced(headerTableLength() - 4); 541 542 // [ 6] (s = 38) :path: / 543 entry = hpackReader.headerTable[headerTableLength() - 3]; 544 checkEntry(entry, ":path", "/", 38); 545 assertHeaderNotReferenced(headerTableLength() - 3); 546 547 // [ 7] (s = 43) :scheme: http 548 entry = hpackReader.headerTable[headerTableLength() - 2]; 549 checkEntry(entry, ":scheme", "http", 43); 550 assertHeaderNotReferenced(headerTableLength() - 2); 551 552 // [ 8] (s = 42) :method: GET 553 entry = hpackReader.headerTable[headerTableLength() - 1]; 554 checkEntry(entry, ":method", "GET", 42); 555 assertHeaderReferenced(headerTableLength() - 1); 556 557 // Table size: 379 558 assertEquals(379, hpackReader.headerTableByteCount); 559 560 // Decoded header set: 561 // TODO: order is not correct per docs, but then again, the spec doesn't require ordering. 562 assertEquals(headerEntries( 563 ":method", "GET", 564 ":authority", "www.example.com", 565 ":scheme", "https", 566 ":path", "/index.html", 567 "custom-key", "custom-value"), hpackReader.getAndReset()); 568 } 569 570 /** 571 * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.3 572 */ 573 @Test public void readRequestExamplesWithHuffman() throws IOException { 574 OkBuffer out = firstRequestWithHuffman(); 575 bytesIn.write(out, out.size()); 576 hpackReader.readHeaders(); 577 hpackReader.emitReferenceSet(); 578 checkReadFirstRequestWithHuffman(); 579 580 out = secondRequestWithHuffman(); 581 bytesIn.write(out, out.size()); 582 hpackReader.readHeaders(); 583 hpackReader.emitReferenceSet(); 584 checkReadSecondRequestWithHuffman(); 585 586 out = thirdRequestWithHuffman(); 587 bytesIn.write(out, out.size()); 588 hpackReader.readHeaders(); 589 hpackReader.emitReferenceSet(); 590 checkReadThirdRequestWithHuffman(); 591 } 592 593 private OkBuffer firstRequestWithHuffman() { 594 OkBuffer out = new OkBuffer(); 595 596 out.writeByte(0x82); // == Indexed - Add == 597 // idx = 2 -> :method: GET 598 out.writeByte(0x87); // == Indexed - Add == 599 // idx = 7 -> :scheme: http 600 out.writeByte(0x86); // == Indexed - Add == 601 // idx = 6 -> :path: / 602 out.writeByte(0x04); // == Literal indexed == 603 // Indexed name (idx = 4) -> :authority 604 out.writeByte(0x8b); // Literal value Huffman encoded 11 bytes 605 // decodes to www.example.com which is length 15 606 byte[] huffmanBytes = new byte[] { 607 (byte) 0xdb, (byte) 0x6d, (byte) 0x88, (byte) 0x3e, 608 (byte) 0x68, (byte) 0xd1, (byte) 0xcb, (byte) 0x12, 609 (byte) 0x25, (byte) 0xba, (byte) 0x7f}; 610 out.write(huffmanBytes, 0, huffmanBytes.length); 611 612 return out; 613 } 614 615 private void checkReadFirstRequestWithHuffman() { 616 assertEquals(4, hpackReader.headerCount); 617 618 // [ 1] (s = 57) :authority: www.example.com 619 Header entry = hpackReader.headerTable[headerTableLength() - 4]; 620 checkEntry(entry, ":authority", "www.example.com", 57); 621 assertHeaderReferenced(headerTableLength() - 4); 622 623 // [ 2] (s = 38) :path: / 624 entry = hpackReader.headerTable[headerTableLength() - 3]; 625 checkEntry(entry, ":path", "/", 38); 626 assertHeaderReferenced(headerTableLength() - 3); 627 628 // [ 3] (s = 43) :scheme: http 629 entry = hpackReader.headerTable[headerTableLength() - 2]; 630 checkEntry(entry, ":scheme", "http", 43); 631 assertHeaderReferenced(headerTableLength() - 2); 632 633 // [ 4] (s = 42) :method: GET 634 entry = hpackReader.headerTable[headerTableLength() - 1]; 635 checkEntry(entry, ":method", "GET", 42); 636 assertHeaderReferenced(headerTableLength() - 1); 637 638 // Table size: 180 639 assertEquals(180, hpackReader.headerTableByteCount); 640 641 // Decoded header set: 642 assertEquals(headerEntries( 643 ":method", "GET", 644 ":scheme", "http", 645 ":path", "/", 646 ":authority", "www.example.com"), hpackReader.getAndReset()); 647 } 648 649 private OkBuffer secondRequestWithHuffman() { 650 OkBuffer out = new OkBuffer(); 651 652 out.writeByte(0x1b); // == Literal indexed == 653 // Indexed name (idx = 27) -> cache-control 654 out.writeByte(0x86); // Literal value Huffman encoded 6 bytes 655 // decodes to no-cache which is length 8 656 byte[] huffmanBytes = new byte[] { 657 (byte) 0x63, (byte) 0x65, (byte) 0x4a, (byte) 0x13, 658 (byte) 0x98, (byte) 0xff}; 659 out.write(huffmanBytes, 0, huffmanBytes.length); 660 661 return out; 662 } 663 664 private void checkReadSecondRequestWithHuffman() { 665 assertEquals(5, hpackReader.headerCount); 666 667 // [ 1] (s = 53) cache-control: no-cache 668 Header entry = hpackReader.headerTable[headerTableLength() - 5]; 669 checkEntry(entry, "cache-control", "no-cache", 53); 670 assertHeaderReferenced(headerTableLength() - 5); 671 672 // [ 2] (s = 57) :authority: www.example.com 673 entry = hpackReader.headerTable[headerTableLength() - 4]; 674 checkEntry(entry, ":authority", "www.example.com", 57); 675 assertHeaderReferenced(headerTableLength() - 4); 676 677 // [ 3] (s = 38) :path: / 678 entry = hpackReader.headerTable[headerTableLength() - 3]; 679 checkEntry(entry, ":path", "/", 38); 680 assertHeaderReferenced(headerTableLength() - 3); 681 682 // [ 4] (s = 43) :scheme: http 683 entry = hpackReader.headerTable[headerTableLength() - 2]; 684 checkEntry(entry, ":scheme", "http", 43); 685 assertHeaderReferenced(headerTableLength() - 2); 686 687 // [ 5] (s = 42) :method: GET 688 entry = hpackReader.headerTable[headerTableLength() - 1]; 689 checkEntry(entry, ":method", "GET", 42); 690 assertHeaderReferenced(headerTableLength() - 1); 691 692 // Table size: 233 693 assertEquals(233, hpackReader.headerTableByteCount); 694 695 // Decoded header set: 696 assertEquals(headerEntries( 697 ":method", "GET", 698 ":scheme", "http", 699 ":path", "/", 700 ":authority", "www.example.com", 701 "cache-control", "no-cache"), hpackReader.getAndReset()); 702 } 703 704 private OkBuffer thirdRequestWithHuffman() { 705 OkBuffer out = new OkBuffer(); 706 707 out.writeByte(0x80); // == Empty reference set == 708 out.writeByte(0x85); // == Indexed - Add == 709 // idx = 5 -> :method: GET 710 out.writeByte(0x8c); // == Indexed - Add == 711 // idx = 12 -> :scheme: https 712 out.writeByte(0x8b); // == Indexed - Add == 713 // idx = 11 -> :path: /index.html 714 out.writeByte(0x84); // == Indexed - Add == 715 // idx = 4 -> :authority: www.example.com 716 out.writeByte(0x00); // Literal indexed 717 out.writeByte(0x88); // Literal name Huffman encoded 8 bytes 718 // decodes to custom-key which is length 10 719 byte[] huffmanBytes = new byte[] { 720 (byte) 0x4e, (byte) 0xb0, (byte) 0x8b, (byte) 0x74, 721 (byte) 0x97, (byte) 0x90, (byte) 0xfa, (byte) 0x7f}; 722 out.write(huffmanBytes, 0, huffmanBytes.length); 723 out.writeByte(0x89); // Literal value Huffman encoded 6 bytes 724 // decodes to custom-value which is length 12 725 huffmanBytes = new byte[] { 726 (byte) 0x4e, (byte) 0xb0, (byte) 0x8b, (byte) 0x74, 727 (byte) 0x97, (byte) 0x9a, (byte) 0x17, (byte) 0xa8, 728 (byte) 0xff}; 729 out.write(huffmanBytes, 0, huffmanBytes.length); 730 731 return out; 732 } 733 734 private void checkReadThirdRequestWithHuffman() { 735 assertEquals(8, hpackReader.headerCount); 736 737 // [ 1] (s = 54) custom-key: custom-value 738 Header entry = hpackReader.headerTable[headerTableLength() - 8]; 739 checkEntry(entry, "custom-key", "custom-value", 54); 740 assertHeaderReferenced(headerTableLength() - 8); 741 742 // [ 2] (s = 48) :path: /index.html 743 entry = hpackReader.headerTable[headerTableLength() - 7]; 744 checkEntry(entry, ":path", "/index.html", 48); 745 assertHeaderReferenced(headerTableLength() - 7); 746 747 // [ 3] (s = 44) :scheme: https 748 entry = hpackReader.headerTable[headerTableLength() - 6]; 749 checkEntry(entry, ":scheme", "https", 44); 750 assertHeaderReferenced(headerTableLength() - 6); 751 752 // [ 4] (s = 53) cache-control: no-cache 753 entry = hpackReader.headerTable[headerTableLength() - 5]; 754 checkEntry(entry, "cache-control", "no-cache", 53); 755 assertHeaderNotReferenced(headerTableLength() - 5); 756 757 // [ 5] (s = 57) :authority: www.example.com 758 entry = hpackReader.headerTable[headerTableLength() - 4]; 759 checkEntry(entry, ":authority", "www.example.com", 57); 760 assertHeaderReferenced(headerTableLength() - 4); 761 762 // [ 6] (s = 38) :path: / 763 entry = hpackReader.headerTable[headerTableLength() - 3]; 764 checkEntry(entry, ":path", "/", 38); 765 assertHeaderNotReferenced(headerTableLength() - 3); 766 767 // [ 7] (s = 43) :scheme: http 768 entry = hpackReader.headerTable[headerTableLength() - 2]; 769 checkEntry(entry, ":scheme", "http", 43); 770 assertHeaderNotReferenced(headerTableLength() - 2); 771 772 // [ 8] (s = 42) :method: GET 773 entry = hpackReader.headerTable[headerTableLength() - 1]; 774 checkEntry(entry, ":method", "GET", 42); 775 assertHeaderReferenced(headerTableLength() - 1); 776 777 // Table size: 379 778 assertEquals(379, hpackReader.headerTableByteCount); 779 780 // Decoded header set: 781 // TODO: order is not correct per docs, but then again, the spec doesn't require ordering. 782 assertEquals(headerEntries( 783 ":method", "GET", 784 ":authority", "www.example.com", 785 ":scheme", "https", 786 ":path", "/index.html", 787 "custom-key", "custom-value"), hpackReader.getAndReset()); 788 } 789 790 @Test public void readSingleByteInt() throws IOException { 791 assertEquals(10, newReader(byteStream()).readInt(10, 31)); 792 assertEquals(10, newReader(byteStream()).readInt(0xe0 | 10, 31)); 793 } 794 795 @Test public void readMultibyteInt() throws IOException { 796 assertEquals(1337, newReader(byteStream(154, 10)).readInt(31, 31)); 797 } 798 799 @Test public void writeSingleByteInt() throws IOException { 800 hpackWriter.writeInt(10, 31, 0); 801 assertBytes(10); 802 hpackWriter.writeInt(10, 31, 0xe0); 803 assertBytes(0xe0 | 10); 804 } 805 806 @Test public void writeMultibyteInt() throws IOException { 807 hpackWriter.writeInt(1337, 31, 0); 808 assertBytes(31, 154, 10); 809 hpackWriter.writeInt(1337, 31, 0xe0); 810 assertBytes(0xe0 | 31, 154, 10); 811 } 812 813 @Test public void max31BitValue() throws IOException { 814 hpackWriter.writeInt(0x7fffffff, 31, 0); 815 assertBytes(31, 224, 255, 255, 255, 7); 816 assertEquals(0x7fffffff, 817 newReader(byteStream(224, 255, 255, 255, 7)).readInt(31, 31)); 818 } 819 820 @Test public void prefixMask() throws IOException { 821 hpackWriter.writeInt(31, 31, 0); 822 assertBytes(31, 0); 823 assertEquals(31, newReader(byteStream(0)).readInt(31, 31)); 824 } 825 826 @Test public void prefixMaskMinusOne() throws IOException { 827 hpackWriter.writeInt(30, 31, 0); 828 assertBytes(30); 829 assertEquals(31, newReader(byteStream(0)).readInt(31, 31)); 830 } 831 832 @Test public void zero() throws IOException { 833 hpackWriter.writeInt(0, 31, 0); 834 assertBytes(0); 835 assertEquals(0, newReader(byteStream()).readInt(0, 31)); 836 } 837 838 @Test public void headerName() throws IOException { 839 hpackWriter.writeByteString(ByteString.encodeUtf8("foo")); 840 assertBytes(3, 'f', 'o', 'o'); 841 assertEquals("foo", newReader(byteStream(3, 'F', 'o', 'o')).readByteString(true).utf8()); 842 } 843 844 @Test public void emptyHeaderName() throws IOException { 845 hpackWriter.writeByteString(ByteString.encodeUtf8("")); 846 assertBytes(0); 847 assertEquals(ByteString.EMPTY, newReader(byteStream(0)).readByteString(true)); 848 assertEquals(ByteString.EMPTY, newReader(byteStream(0)).readByteString(false)); 849 } 850 851 private HpackDraft05.Reader newReader(OkBuffer source) { 852 return new HpackDraft05.Reader(false, 4096, source); 853 } 854 855 private OkBuffer byteStream(int... bytes) { 856 return new OkBuffer().write(intArrayToByteArray(bytes)); 857 } 858 859 private void checkEntry(Header entry, String name, String value, int size) { 860 assertEquals(name, entry.name.utf8()); 861 assertEquals(value, entry.value.utf8()); 862 assertEquals(size, entry.hpackSize); 863 } 864 865 private void assertBytes(int... bytes) { 866 ByteString expected = intArrayToByteArray(bytes); 867 ByteString actual = bytesOut.readByteString(bytesOut.size()); 868 assertEquals(expected, actual); 869 } 870 871 private ByteString intArrayToByteArray(int[] bytes) { 872 byte[] data = new byte[bytes.length]; 873 for (int i = 0; i < bytes.length; i++) { 874 data[i] = (byte) bytes[i]; 875 } 876 return ByteString.of(data); 877 } 878 879 private void assertHeaderReferenced(int index) { 880 assertTrue(hpackReader.referencedHeaders.get(index)); 881 } 882 883 private void assertHeaderNotReferenced(int index) { 884 assertFalse(hpackReader.referencedHeaders.get(index)); 885 } 886 887 private int headerTableLength() { 888 return hpackReader.headerTable.length; 889 } 890 } 891