HpackDraft05Test.java revision 3c938a3f6b61ce5e2dba0d039b03fe73b89fd26c
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 */ 16package com.squareup.okhttp.internal.spdy; 17 18import java.io.IOException; 19import java.util.Arrays; 20import java.util.List; 21import okio.ByteString; 22import okio.OkBuffer; 23import org.junit.Before; 24import org.junit.Test; 25 26import static com.squareup.okhttp.internal.Util.headerEntries; 27import static org.junit.Assert.assertEquals; 28import static org.junit.Assert.assertFalse; 29import static org.junit.Assert.assertTrue; 30 31public 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 /** 310 * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.4 311 */ 312 @Test public void readIndexedHeaderFieldFromStaticTableWithoutBuffering() throws IOException { 313 bytesIn.writeByte(0x82); // == Indexed - Add == 314 // idx = 2 -> :method: GET 315 316 hpackReader.maxHeaderTableByteCount(0); // SETTINGS_HEADER_TABLE_SIZE == 0 317 hpackReader.readHeaders(); 318 hpackReader.emitReferenceSet(); 319 320 // Not buffered in header table. 321 assertEquals(0, hpackReader.headerCount); 322 323 assertEquals(headerEntries(":method", "GET"), hpackReader.getAndReset()); 324 } 325 326 /** 327 * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.2 328 */ 329 @Test public void readRequestExamplesWithoutHuffman() throws IOException { 330 OkBuffer out = firstRequestWithoutHuffman(); 331 bytesIn.write(out, out.size()); 332 hpackReader.readHeaders(); 333 hpackReader.emitReferenceSet(); 334 checkReadFirstRequestWithoutHuffman(); 335 336 out = secondRequestWithoutHuffman(); 337 bytesIn.write(out, out.size()); 338 hpackReader.readHeaders(); 339 hpackReader.emitReferenceSet(); 340 checkReadSecondRequestWithoutHuffman(); 341 342 out = thirdRequestWithoutHuffman(); 343 bytesIn.write(out, out.size()); 344 hpackReader.readHeaders(); 345 hpackReader.emitReferenceSet(); 346 checkReadThirdRequestWithoutHuffman(); 347 } 348 349 private OkBuffer firstRequestWithoutHuffman() { 350 OkBuffer out = new OkBuffer(); 351 352 out.writeByte(0x82); // == Indexed - Add == 353 // idx = 2 -> :method: GET 354 out.writeByte(0x87); // == Indexed - Add == 355 // idx = 7 -> :scheme: http 356 out.writeByte(0x86); // == Indexed - Add == 357 // idx = 6 -> :path: / 358 out.writeByte(0x04); // == Literal indexed == 359 // Indexed name (idx = 4) -> :authority 360 out.writeByte(0x0f); // Literal value (len = 15) 361 out.writeUtf8("www.example.com"); 362 363 return out; 364 } 365 366 private void checkReadFirstRequestWithoutHuffman() { 367 assertEquals(4, hpackReader.headerCount); 368 369 // [ 1] (s = 57) :authority: www.example.com 370 Header entry = hpackReader.headerTable[headerTableLength() - 4]; 371 checkEntry(entry, ":authority", "www.example.com", 57); 372 assertHeaderReferenced(headerTableLength() - 4); 373 374 // [ 2] (s = 38) :path: / 375 entry = hpackReader.headerTable[headerTableLength() - 3]; 376 checkEntry(entry, ":path", "/", 38); 377 assertHeaderReferenced(headerTableLength() - 3); 378 379 // [ 3] (s = 43) :scheme: http 380 entry = hpackReader.headerTable[headerTableLength() - 2]; 381 checkEntry(entry, ":scheme", "http", 43); 382 assertHeaderReferenced(headerTableLength() - 2); 383 384 // [ 4] (s = 42) :method: GET 385 entry = hpackReader.headerTable[headerTableLength() - 1]; 386 checkEntry(entry, ":method", "GET", 42); 387 assertHeaderReferenced(headerTableLength() - 1); 388 389 // Table size: 180 390 assertEquals(180, hpackReader.headerTableByteCount); 391 392 // Decoded header set: 393 assertEquals(headerEntries( 394 ":method", "GET", 395 ":scheme", "http", 396 ":path", "/", 397 ":authority", "www.example.com"), hpackReader.getAndReset()); 398 } 399 400 private OkBuffer secondRequestWithoutHuffman() { 401 OkBuffer out = new OkBuffer(); 402 403 out.writeByte(0x1b); // == Literal indexed == 404 // Indexed name (idx = 27) -> cache-control 405 out.writeByte(0x08); // Literal value (len = 8) 406 out.writeUtf8("no-cache"); 407 408 return out; 409 } 410 411 private void checkReadSecondRequestWithoutHuffman() { 412 assertEquals(5, hpackReader.headerCount); 413 414 // [ 1] (s = 53) cache-control: no-cache 415 Header entry = hpackReader.headerTable[headerTableLength() - 5]; 416 checkEntry(entry, "cache-control", "no-cache", 53); 417 assertHeaderReferenced(headerTableLength() - 5); 418 419 // [ 2] (s = 57) :authority: www.example.com 420 entry = hpackReader.headerTable[headerTableLength() - 4]; 421 checkEntry(entry, ":authority", "www.example.com", 57); 422 assertHeaderReferenced(headerTableLength() - 4); 423 424 // [ 3] (s = 38) :path: / 425 entry = hpackReader.headerTable[headerTableLength() - 3]; 426 checkEntry(entry, ":path", "/", 38); 427 assertHeaderReferenced(headerTableLength() - 3); 428 429 // [ 4] (s = 43) :scheme: http 430 entry = hpackReader.headerTable[headerTableLength() - 2]; 431 checkEntry(entry, ":scheme", "http", 43); 432 assertHeaderReferenced(headerTableLength() - 2); 433 434 // [ 5] (s = 42) :method: GET 435 entry = hpackReader.headerTable[headerTableLength() - 1]; 436 checkEntry(entry, ":method", "GET", 42); 437 assertHeaderReferenced(headerTableLength() - 1); 438 439 // Table size: 233 440 assertEquals(233, hpackReader.headerTableByteCount); 441 442 // Decoded header set: 443 assertEquals(headerEntries( 444 ":method", "GET", 445 ":scheme", "http", 446 ":path", "/", 447 ":authority", "www.example.com", 448 "cache-control", "no-cache"), hpackReader.getAndReset()); 449 } 450 451 private OkBuffer thirdRequestWithoutHuffman() { 452 OkBuffer out = new OkBuffer(); 453 454 out.writeByte(0x80); // == Empty reference set == 455 out.writeByte(0x85); // == Indexed - Add == 456 // idx = 5 -> :method: GET 457 out.writeByte(0x8c); // == Indexed - Add == 458 // idx = 12 -> :scheme: https 459 out.writeByte(0x8b); // == Indexed - Add == 460 // idx = 11 -> :path: /index.html 461 out.writeByte(0x84); // == Indexed - Add == 462 // idx = 4 -> :authority: www.example.com 463 out.writeByte(0x00); // Literal indexed 464 out.writeByte(0x0a); // Literal name (len = 10) 465 out.writeUtf8("custom-key"); 466 out.writeByte(0x0c); // Literal value (len = 12) 467 out.writeUtf8("custom-value"); 468 469 return out; 470 } 471 472 private void checkReadThirdRequestWithoutHuffman() { 473 assertEquals(8, hpackReader.headerCount); 474 475 // [ 1] (s = 54) custom-key: custom-value 476 Header entry = hpackReader.headerTable[headerTableLength() - 8]; 477 checkEntry(entry, "custom-key", "custom-value", 54); 478 assertHeaderReferenced(headerTableLength() - 8); 479 480 // [ 2] (s = 48) :path: /index.html 481 entry = hpackReader.headerTable[headerTableLength() - 7]; 482 checkEntry(entry, ":path", "/index.html", 48); 483 assertHeaderReferenced(headerTableLength() - 7); 484 485 // [ 3] (s = 44) :scheme: https 486 entry = hpackReader.headerTable[headerTableLength() - 6]; 487 checkEntry(entry, ":scheme", "https", 44); 488 assertHeaderReferenced(headerTableLength() - 6); 489 490 // [ 4] (s = 53) cache-control: no-cache 491 entry = hpackReader.headerTable[headerTableLength() - 5]; 492 checkEntry(entry, "cache-control", "no-cache", 53); 493 assertHeaderNotReferenced(headerTableLength() - 5); 494 495 // [ 5] (s = 57) :authority: www.example.com 496 entry = hpackReader.headerTable[headerTableLength() - 4]; 497 checkEntry(entry, ":authority", "www.example.com", 57); 498 assertHeaderReferenced(headerTableLength() - 4); 499 500 // [ 6] (s = 38) :path: / 501 entry = hpackReader.headerTable[headerTableLength() - 3]; 502 checkEntry(entry, ":path", "/", 38); 503 assertHeaderNotReferenced(headerTableLength() - 3); 504 505 // [ 7] (s = 43) :scheme: http 506 entry = hpackReader.headerTable[headerTableLength() - 2]; 507 checkEntry(entry, ":scheme", "http", 43); 508 assertHeaderNotReferenced(headerTableLength() - 2); 509 510 // [ 8] (s = 42) :method: GET 511 entry = hpackReader.headerTable[headerTableLength() - 1]; 512 checkEntry(entry, ":method", "GET", 42); 513 assertHeaderReferenced(headerTableLength() - 1); 514 515 // Table size: 379 516 assertEquals(379, hpackReader.headerTableByteCount); 517 518 // Decoded header set: 519 // TODO: order is not correct per docs, but then again, the spec doesn't require ordering. 520 assertEquals(headerEntries( 521 ":method", "GET", 522 ":authority", "www.example.com", 523 ":scheme", "https", 524 ":path", "/index.html", 525 "custom-key", "custom-value"), hpackReader.getAndReset()); 526 } 527 528 /** 529 * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.3 530 */ 531 @Test public void readRequestExamplesWithHuffman() throws IOException { 532 OkBuffer out = firstRequestWithHuffman(); 533 bytesIn.write(out, out.size()); 534 hpackReader.readHeaders(); 535 hpackReader.emitReferenceSet(); 536 checkReadFirstRequestWithHuffman(); 537 538 out = secondRequestWithHuffman(); 539 bytesIn.write(out, out.size()); 540 hpackReader.readHeaders(); 541 hpackReader.emitReferenceSet(); 542 checkReadSecondRequestWithHuffman(); 543 544 out = thirdRequestWithHuffman(); 545 bytesIn.write(out, out.size()); 546 hpackReader.readHeaders(); 547 hpackReader.emitReferenceSet(); 548 checkReadThirdRequestWithHuffman(); 549 } 550 551 private OkBuffer firstRequestWithHuffman() { 552 OkBuffer out = new OkBuffer(); 553 554 out.writeByte(0x82); // == Indexed - Add == 555 // idx = 2 -> :method: GET 556 out.writeByte(0x87); // == Indexed - Add == 557 // idx = 7 -> :scheme: http 558 out.writeByte(0x86); // == Indexed - Add == 559 // idx = 6 -> :path: / 560 out.writeByte(0x04); // == Literal indexed == 561 // Indexed name (idx = 4) -> :authority 562 out.writeByte(0x8b); // Literal value Huffman encoded 11 bytes 563 // decodes to www.example.com which is length 15 564 byte[] huffmanBytes = new byte[] { 565 (byte) 0xdb, (byte) 0x6d, (byte) 0x88, (byte) 0x3e, 566 (byte) 0x68, (byte) 0xd1, (byte) 0xcb, (byte) 0x12, 567 (byte) 0x25, (byte) 0xba, (byte) 0x7f}; 568 out.write(huffmanBytes, 0, huffmanBytes.length); 569 570 return out; 571 } 572 573 private void checkReadFirstRequestWithHuffman() { 574 assertEquals(4, hpackReader.headerCount); 575 576 // [ 1] (s = 57) :authority: www.example.com 577 Header entry = hpackReader.headerTable[headerTableLength() - 4]; 578 checkEntry(entry, ":authority", "www.example.com", 57); 579 assertHeaderReferenced(headerTableLength() - 4); 580 581 // [ 2] (s = 38) :path: / 582 entry = hpackReader.headerTable[headerTableLength() - 3]; 583 checkEntry(entry, ":path", "/", 38); 584 assertHeaderReferenced(headerTableLength() - 3); 585 586 // [ 3] (s = 43) :scheme: http 587 entry = hpackReader.headerTable[headerTableLength() - 2]; 588 checkEntry(entry, ":scheme", "http", 43); 589 assertHeaderReferenced(headerTableLength() - 2); 590 591 // [ 4] (s = 42) :method: GET 592 entry = hpackReader.headerTable[headerTableLength() - 1]; 593 checkEntry(entry, ":method", "GET", 42); 594 assertHeaderReferenced(headerTableLength() - 1); 595 596 // Table size: 180 597 assertEquals(180, hpackReader.headerTableByteCount); 598 599 // Decoded header set: 600 assertEquals(headerEntries( 601 ":method", "GET", 602 ":scheme", "http", 603 ":path", "/", 604 ":authority", "www.example.com"), hpackReader.getAndReset()); 605 } 606 607 private OkBuffer secondRequestWithHuffman() { 608 OkBuffer out = new OkBuffer(); 609 610 out.writeByte(0x1b); // == Literal indexed == 611 // Indexed name (idx = 27) -> cache-control 612 out.writeByte(0x86); // Literal value Huffman encoded 6 bytes 613 // decodes to no-cache which is length 8 614 byte[] huffmanBytes = new byte[] { 615 (byte) 0x63, (byte) 0x65, (byte) 0x4a, (byte) 0x13, 616 (byte) 0x98, (byte) 0xff}; 617 out.write(huffmanBytes, 0, huffmanBytes.length); 618 619 return out; 620 } 621 622 private void checkReadSecondRequestWithHuffman() { 623 assertEquals(5, hpackReader.headerCount); 624 625 // [ 1] (s = 53) cache-control: no-cache 626 Header entry = hpackReader.headerTable[headerTableLength() - 5]; 627 checkEntry(entry, "cache-control", "no-cache", 53); 628 assertHeaderReferenced(headerTableLength() - 5); 629 630 // [ 2] (s = 57) :authority: www.example.com 631 entry = hpackReader.headerTable[headerTableLength() - 4]; 632 checkEntry(entry, ":authority", "www.example.com", 57); 633 assertHeaderReferenced(headerTableLength() - 4); 634 635 // [ 3] (s = 38) :path: / 636 entry = hpackReader.headerTable[headerTableLength() - 3]; 637 checkEntry(entry, ":path", "/", 38); 638 assertHeaderReferenced(headerTableLength() - 3); 639 640 // [ 4] (s = 43) :scheme: http 641 entry = hpackReader.headerTable[headerTableLength() - 2]; 642 checkEntry(entry, ":scheme", "http", 43); 643 assertHeaderReferenced(headerTableLength() - 2); 644 645 // [ 5] (s = 42) :method: GET 646 entry = hpackReader.headerTable[headerTableLength() - 1]; 647 checkEntry(entry, ":method", "GET", 42); 648 assertHeaderReferenced(headerTableLength() - 1); 649 650 // Table size: 233 651 assertEquals(233, hpackReader.headerTableByteCount); 652 653 // Decoded header set: 654 assertEquals(headerEntries( 655 ":method", "GET", 656 ":scheme", "http", 657 ":path", "/", 658 ":authority", "www.example.com", 659 "cache-control", "no-cache"), hpackReader.getAndReset()); 660 } 661 662 private OkBuffer thirdRequestWithHuffman() { 663 OkBuffer out = new OkBuffer(); 664 665 out.writeByte(0x80); // == Empty reference set == 666 out.writeByte(0x85); // == Indexed - Add == 667 // idx = 5 -> :method: GET 668 out.writeByte(0x8c); // == Indexed - Add == 669 // idx = 12 -> :scheme: https 670 out.writeByte(0x8b); // == Indexed - Add == 671 // idx = 11 -> :path: /index.html 672 out.writeByte(0x84); // == Indexed - Add == 673 // idx = 4 -> :authority: www.example.com 674 out.writeByte(0x00); // Literal indexed 675 out.writeByte(0x88); // Literal name Huffman encoded 8 bytes 676 // decodes to custom-key which is length 10 677 byte[] huffmanBytes = new byte[] { 678 (byte) 0x4e, (byte) 0xb0, (byte) 0x8b, (byte) 0x74, 679 (byte) 0x97, (byte) 0x90, (byte) 0xfa, (byte) 0x7f}; 680 out.write(huffmanBytes, 0, huffmanBytes.length); 681 out.writeByte(0x89); // Literal value Huffman encoded 6 bytes 682 // decodes to custom-value which is length 12 683 huffmanBytes = new byte[] { 684 (byte) 0x4e, (byte) 0xb0, (byte) 0x8b, (byte) 0x74, 685 (byte) 0x97, (byte) 0x9a, (byte) 0x17, (byte) 0xa8, 686 (byte) 0xff}; 687 out.write(huffmanBytes, 0, huffmanBytes.length); 688 689 return out; 690 } 691 692 private void checkReadThirdRequestWithHuffman() { 693 assertEquals(8, hpackReader.headerCount); 694 695 // [ 1] (s = 54) custom-key: custom-value 696 Header entry = hpackReader.headerTable[headerTableLength() - 8]; 697 checkEntry(entry, "custom-key", "custom-value", 54); 698 assertHeaderReferenced(headerTableLength() - 8); 699 700 // [ 2] (s = 48) :path: /index.html 701 entry = hpackReader.headerTable[headerTableLength() - 7]; 702 checkEntry(entry, ":path", "/index.html", 48); 703 assertHeaderReferenced(headerTableLength() - 7); 704 705 // [ 3] (s = 44) :scheme: https 706 entry = hpackReader.headerTable[headerTableLength() - 6]; 707 checkEntry(entry, ":scheme", "https", 44); 708 assertHeaderReferenced(headerTableLength() - 6); 709 710 // [ 4] (s = 53) cache-control: no-cache 711 entry = hpackReader.headerTable[headerTableLength() - 5]; 712 checkEntry(entry, "cache-control", "no-cache", 53); 713 assertHeaderNotReferenced(headerTableLength() - 5); 714 715 // [ 5] (s = 57) :authority: www.example.com 716 entry = hpackReader.headerTable[headerTableLength() - 4]; 717 checkEntry(entry, ":authority", "www.example.com", 57); 718 assertHeaderReferenced(headerTableLength() - 4); 719 720 // [ 6] (s = 38) :path: / 721 entry = hpackReader.headerTable[headerTableLength() - 3]; 722 checkEntry(entry, ":path", "/", 38); 723 assertHeaderNotReferenced(headerTableLength() - 3); 724 725 // [ 7] (s = 43) :scheme: http 726 entry = hpackReader.headerTable[headerTableLength() - 2]; 727 checkEntry(entry, ":scheme", "http", 43); 728 assertHeaderNotReferenced(headerTableLength() - 2); 729 730 // [ 8] (s = 42) :method: GET 731 entry = hpackReader.headerTable[headerTableLength() - 1]; 732 checkEntry(entry, ":method", "GET", 42); 733 assertHeaderReferenced(headerTableLength() - 1); 734 735 // Table size: 379 736 assertEquals(379, hpackReader.headerTableByteCount); 737 738 // Decoded header set: 739 // TODO: order is not correct per docs, but then again, the spec doesn't require ordering. 740 assertEquals(headerEntries( 741 ":method", "GET", 742 ":authority", "www.example.com", 743 ":scheme", "https", 744 ":path", "/index.html", 745 "custom-key", "custom-value"), hpackReader.getAndReset()); 746 } 747 748 @Test public void readSingleByteInt() throws IOException { 749 assertEquals(10, newReader(byteStream()).readInt(10, 31)); 750 assertEquals(10, newReader(byteStream()).readInt(0xe0 | 10, 31)); 751 } 752 753 @Test public void readMultibyteInt() throws IOException { 754 assertEquals(1337, newReader(byteStream(154, 10)).readInt(31, 31)); 755 } 756 757 @Test public void writeSingleByteInt() throws IOException { 758 hpackWriter.writeInt(10, 31, 0); 759 assertBytes(10); 760 hpackWriter.writeInt(10, 31, 0xe0); 761 assertBytes(0xe0 | 10); 762 } 763 764 @Test public void writeMultibyteInt() throws IOException { 765 hpackWriter.writeInt(1337, 31, 0); 766 assertBytes(31, 154, 10); 767 hpackWriter.writeInt(1337, 31, 0xe0); 768 assertBytes(0xe0 | 31, 154, 10); 769 } 770 771 @Test public void max31BitValue() throws IOException { 772 hpackWriter.writeInt(0x7fffffff, 31, 0); 773 assertBytes(31, 224, 255, 255, 255, 7); 774 assertEquals(0x7fffffff, 775 newReader(byteStream(224, 255, 255, 255, 7)).readInt(31, 31)); 776 } 777 778 @Test public void prefixMask() throws IOException { 779 hpackWriter.writeInt(31, 31, 0); 780 assertBytes(31, 0); 781 assertEquals(31, newReader(byteStream(0)).readInt(31, 31)); 782 } 783 784 @Test public void prefixMaskMinusOne() throws IOException { 785 hpackWriter.writeInt(30, 31, 0); 786 assertBytes(30); 787 assertEquals(31, newReader(byteStream(0)).readInt(31, 31)); 788 } 789 790 @Test public void zero() throws IOException { 791 hpackWriter.writeInt(0, 31, 0); 792 assertBytes(0); 793 assertEquals(0, newReader(byteStream()).readInt(0, 31)); 794 } 795 796 @Test public void headerName() throws IOException { 797 hpackWriter.writeByteString(ByteString.encodeUtf8("foo")); 798 assertBytes(3, 'f', 'o', 'o'); 799 assertEquals("foo", newReader(byteStream(3, 'F', 'o', 'o')).readByteString(true).utf8()); 800 } 801 802 @Test public void emptyHeaderName() throws IOException { 803 hpackWriter.writeByteString(ByteString.encodeUtf8("")); 804 assertBytes(0); 805 assertEquals(ByteString.EMPTY, newReader(byteStream(0)).readByteString(true)); 806 assertEquals(ByteString.EMPTY, newReader(byteStream(0)).readByteString(false)); 807 } 808 809 private HpackDraft05.Reader newReader(OkBuffer source) { 810 return new HpackDraft05.Reader(false, 4096, source); 811 } 812 813 private OkBuffer byteStream(int... bytes) { 814 return new OkBuffer().write(intArrayToByteArray(bytes)); 815 } 816 817 private void checkEntry(Header entry, String name, String value, int size) { 818 assertEquals(name, entry.name.utf8()); 819 assertEquals(value, entry.value.utf8()); 820 assertEquals(size, entry.hpackSize); 821 } 822 823 private void assertBytes(int... bytes) { 824 ByteString expected = intArrayToByteArray(bytes); 825 ByteString actual = bytesOut.readByteString(bytesOut.size()); 826 assertEquals(expected, actual); 827 } 828 829 private ByteString intArrayToByteArray(int[] bytes) { 830 byte[] data = new byte[bytes.length]; 831 for (int i = 0; i < bytes.length; i++) { 832 data[i] = (byte) bytes[i]; 833 } 834 return ByteString.of(data); 835 } 836 837 private void assertHeaderReferenced(int index) { 838 assertTrue(hpackReader.referencedHeaders.get(index)); 839 } 840 841 private void assertHeaderNotReferenced(int index) { 842 assertFalse(hpackReader.referencedHeaders.get(index)); 843 } 844 845 private int headerTableLength() { 846 return hpackReader.headerTable.length; 847 } 848} 849