1 /*
2 * Copyright (c) 2014, 2016, 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.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
99 " | 49 | proxy-authorization | |\n" +
100 " | 50 | range | |\n" +
101 " | 51 | referer | |\n" +
102 " | 52 | refresh | |\n" +
103 " | 53 | retry-after | |\n" +
104 " | 54 | server | |\n" +
105 " | 55 | set-cookie | |\n" +
106 " | 56 | strict-transport-security | |\n" +
107 " | 57 | transfer-encoding | |\n" +
108 " | 58 | user-agent | |\n" +
109 " | 59 | vary | |\n" +
110 " | 60 | via | |\n" +
111 " | 61 | www-authenticate | |\n";
112 // @formatter:on
113
114 private static final int STATIC_TABLE_LENGTH = createStaticEntries().size();
115 private final Random rnd = newRandom();
116
117 @Test
118 public void staticData() {
119 HeaderTable table = new HeaderTable(0);
120 Map<Integer, HeaderField> staticHeaderFields = createStaticEntries();
121
122 Map<String, Integer> minimalIndexes = new HashMap<>();
123
124 for (Map.Entry<Integer, HeaderField> e : staticHeaderFields.entrySet()) {
125 Integer idx = e.getKey();
126 String hName = e.getValue().name;
127 Integer midx = minimalIndexes.get(hName);
128 if (midx == null) {
129 minimalIndexes.put(hName, idx);
130 } else {
131 minimalIndexes.put(hName, Math.min(idx, midx));
132 }
133 }
134
135 staticHeaderFields.entrySet().forEach(
136 e -> {
137 // lookup
138 HeaderField actualHeaderField = table.get(e.getKey());
139 HeaderField expectedHeaderField = e.getValue();
142 // reverse lookup (name, value)
143 String hName = expectedHeaderField.name;
144 String hValue = expectedHeaderField.value;
145 int expectedIndex = e.getKey();
146 int actualIndex = table.indexOf(hName, hValue);
147
148 assertEquals(actualIndex, expectedIndex);
149
150 // reverse lookup (name)
151 int expectedMinimalIndex = minimalIndexes.get(hName);
152 int actualMinimalIndex = table.indexOf(hName, "blah-blah");
153
154 assertEquals(-actualMinimalIndex, expectedMinimalIndex);
155 }
156 );
157 }
158
159 @Test
160 public void constructorSetsMaxSize() {
161 int size = rnd.nextInt(64);
162 HeaderTable t = new HeaderTable(size);
163 assertEquals(t.size(), 0);
164 assertEquals(t.maxSize(), size);
165 }
166
167 @Test
168 public void negativeMaximumSize() {
169 int maxSize = -(rnd.nextInt(100) + 1); // [-100, -1]
170 IllegalArgumentException e =
171 assertVoidThrows(IllegalArgumentException.class,
172 () -> new HeaderTable(0).setMaxSize(maxSize));
173 assertExceptionMessageContains(e, "maxSize");
174 }
175
176 @Test
177 public void zeroMaximumSize() {
178 HeaderTable table = new HeaderTable(0);
179 table.setMaxSize(0);
180 assertEquals(table.maxSize(), 0);
181 }
182
183 @Test
184 public void negativeIndex() {
185 int idx = -(rnd.nextInt(256) + 1); // [-256, -1]
186 IllegalArgumentException e =
187 assertVoidThrows(IllegalArgumentException.class,
188 () -> new HeaderTable(0).get(idx));
189 assertExceptionMessageContains(e, "index");
190 }
191
192 @Test
193 public void zeroIndex() {
194 IllegalArgumentException e =
195 assertThrows(IllegalArgumentException.class,
196 () -> new HeaderTable(0).get(0));
197 assertExceptionMessageContains(e, "index");
198 }
199
200 @Test
201 public void length() {
202 HeaderTable table = new HeaderTable(0);
203 assertEquals(table.length(), STATIC_TABLE_LENGTH);
204 }
205
206 @Test
207 public void indexOutsideStaticRange() {
208 HeaderTable table = new HeaderTable(0);
209 int idx = table.length() + (rnd.nextInt(256) + 1);
210 IllegalArgumentException e =
211 assertThrows(IllegalArgumentException.class,
212 () -> table.get(idx));
213 assertExceptionMessageContains(e, "index");
214 }
215
216 @Test
217 public void entryPutAfterStaticArea() {
218 HeaderTable table = new HeaderTable(256);
219 int idx = table.length() + 1;
220 assertThrows(IllegalArgumentException.class, () -> table.get(idx));
221
222 byte[] bytes = new byte[32];
223 rnd.nextBytes(bytes);
224 String name = new String(bytes, StandardCharsets.ISO_8859_1);
225 String value = "custom-value";
226
227 table.put(name, value);
228 HeaderField f = table.get(idx);
229 assertEquals(name, f.name);
230 assertEquals(value, f.value);
231 }
232
233 @Test
234 public void staticTableHasZeroSize() {
235 HeaderTable table = new HeaderTable(0);
236 assertEquals(0, table.size());
237 }
238
239 @Test
240 public void lowerIndexPriority() {
241 HeaderTable table = new HeaderTable(256);
242 int oldLength = table.length();
243 table.put("bender", "rodriguez");
244 table.put("bender", "rodriguez");
245 table.put("bender", "rodriguez");
246
247 assertEquals(table.length(), oldLength + 3); // more like an assumption
248 int i = table.indexOf("bender", "rodriguez");
249 assertEquals(oldLength + 1, i);
250 }
251
252 @Test
253 public void lowerIndexPriority2() {
254 HeaderTable table = new HeaderTable(256);
255 int oldLength = table.length();
256 int idx = rnd.nextInt(oldLength) + 1;
257 HeaderField f = table.get(idx);
258 table.put(f.name, f.value);
259 assertEquals(table.length(), oldLength + 1);
260 int i = table.indexOf(f.name, f.value);
261 assertEquals(idx, i);
262 }
263
264 // TODO: negative indexes check
265 // TODO: ensure full table clearance when adding huge header field
266 // TODO: ensure eviction deletes minimum needed entries, not more
267
268 @Test
269 public void fifo() {
270 HeaderTable t = new HeaderTable(Integer.MAX_VALUE);
271 // Let's add a series of header fields
272 int NUM_HEADERS = 32;
273 for (int i = 1; i <= NUM_HEADERS; i++) {
274 String s = String.valueOf(i);
275 t.put(s, s);
276 }
277 // They MUST appear in a FIFO order:
278 // newer entries are at lower indexes
279 // older entries are at higher indexes
280 for (int j = 1; j <= NUM_HEADERS; j++) {
281 HeaderField f = t.get(STATIC_TABLE_LENGTH + j);
282 int actualName = Integer.parseInt(f.name);
283 int expectedName = NUM_HEADERS - j + 1;
284 assertEquals(expectedName, actualName);
285 }
286 // Entries MUST be evicted in the order they were added:
287 // the newer the entry the later it is evicted
288 for (int k = 1; k <= NUM_HEADERS; k++) {
289 HeaderField f = t.evictEntry();
290 assertEquals(String.valueOf(k), f.name);
291 }
292 }
293
294 @Test
295 public void indexOf() {
296 HeaderTable t = new HeaderTable(Integer.MAX_VALUE);
297 // Let's put a series of header fields
298 int NUM_HEADERS = 32;
299 for (int i = 1; i <= NUM_HEADERS; i++) {
300 String s = String.valueOf(i);
301 t.put(s, s);
302 }
303 // and verify indexOf (reverse lookup) returns correct indexes for
304 // full lookup
305 for (int j = 1; j <= NUM_HEADERS; j++) {
306 String s = String.valueOf(j);
307 int actualIndex = t.indexOf(s, s);
308 int expectedIndex = STATIC_TABLE_LENGTH + NUM_HEADERS - j + 1;
309 assertEquals(expectedIndex, actualIndex);
310 }
311 // as well as for just a name lookup
312 for (int j = 1; j <= NUM_HEADERS; j++) {
313 String s = String.valueOf(j);
314 int actualIndex = t.indexOf(s, "blah");
315 int expectedIndex = -(STATIC_TABLE_LENGTH + NUM_HEADERS - j + 1);
316 assertEquals(expectedIndex, actualIndex);
317 }
318 // lookup for non-existent name returns 0
319 assertEquals(0, t.indexOf("chupacabra", "1"));
320 }
321
322 @Test
323 public void testToString() {
324 testToString0();
325 }
326
327 @Test
328 public void testToStringDifferentLocale() {
329 Locale.setDefault(Locale.FRENCH);
330 String s = format("%.1f", 3.1);
331 assertEquals("3,1", s); // assumption of the test, otherwise the test is useless
332 testToString0();
333 }
334
335 private void testToString0() {
336 HeaderTable table = new HeaderTable(0);
337 {
338 table.setMaxSize(2048);
339 String expected =
340 format("entries: %d; used %s/%s (%.1f%%)", 0, 0, 2048, 0.0);
341 assertEquals(expected, table.toString());
342 }
343
344 {
345 String name = "custom-name";
346 String value = "custom-value";
347 int size = 512;
348
349 table.setMaxSize(size);
350 table.put(name, value);
351 String s = table.toString();
352
353 int used = name.length() + value.length() + 32;
354 double ratio = used * 100.0 / size;
355
356 String expected =
357 format("entries: 1; used %s/%s (%.1f%%)", used, size, ratio);
358 assertEquals(expected, s);
359 }
360
361 {
362 table.setMaxSize(78);
363 table.put(":method", "");
364 table.put(":status", "");
365 String s = table.toString();
366 String expected =
367 format("entries: %d; used %s/%s (%.1f%%)", 2, 78, 78, 100.0);
368 assertEquals(expected, s);
369 }
370 }
371
372 @Test
373 public void stateString() {
374 HeaderTable table = new HeaderTable(256);
375 table.put("custom-key", "custom-header");
376 // @formatter:off
377 assertEquals("[ 1] (s = 55) custom-key: custom-header\n" +
378 " Table size: 55", table.getStateString());
379 // @formatter:on
380 }
381
382 private static Map<Integer, HeaderField> createStaticEntries() {
383 Pattern line = Pattern.compile(
384 "\\|\\s*(?<index>\\d+?)\\s*\\|\\s*(?<name>.+?)\\s*\\|\\s*(?<value>.*?)\\s*\\|");
385 Matcher m = line.matcher(SPEC);
386 Map<Integer, HeaderField> result = new HashMap<>();
387 while (m.find()) {
388 int index = Integer.parseInt(m.group("index"));
389 String name = m.group("name");
390 String value = m.group("value");
391 HeaderField f = new HeaderField(name, value);
392 result.put(index, f);
393 }
394 return Collections.unmodifiableMap(result); // lol
|
1 /*
2 * Copyright (c) 2014, 2017, 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.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
99 " | 49 | proxy-authorization | |\n" +
100 " | 50 | range | |\n" +
101 " | 51 | referer | |\n" +
102 " | 52 | refresh | |\n" +
103 " | 53 | retry-after | |\n" +
104 " | 54 | server | |\n" +
105 " | 55 | set-cookie | |\n" +
106 " | 56 | strict-transport-security | |\n" +
107 " | 57 | transfer-encoding | |\n" +
108 " | 58 | user-agent | |\n" +
109 " | 59 | vary | |\n" +
110 " | 60 | via | |\n" +
111 " | 61 | www-authenticate | |\n";
112 // @formatter:on
113
114 private static final int STATIC_TABLE_LENGTH = createStaticEntries().size();
115 private final Random rnd = newRandom();
116
117 @Test
118 public void staticData() {
119 HeaderTable table = new HeaderTable(0, HPACK.getLogger());
120 Map<Integer, HeaderField> staticHeaderFields = createStaticEntries();
121
122 Map<String, Integer> minimalIndexes = new HashMap<>();
123
124 for (Map.Entry<Integer, HeaderField> e : staticHeaderFields.entrySet()) {
125 Integer idx = e.getKey();
126 String hName = e.getValue().name;
127 Integer midx = minimalIndexes.get(hName);
128 if (midx == null) {
129 minimalIndexes.put(hName, idx);
130 } else {
131 minimalIndexes.put(hName, Math.min(idx, midx));
132 }
133 }
134
135 staticHeaderFields.entrySet().forEach(
136 e -> {
137 // lookup
138 HeaderField actualHeaderField = table.get(e.getKey());
139 HeaderField expectedHeaderField = e.getValue();
142 // reverse lookup (name, value)
143 String hName = expectedHeaderField.name;
144 String hValue = expectedHeaderField.value;
145 int expectedIndex = e.getKey();
146 int actualIndex = table.indexOf(hName, hValue);
147
148 assertEquals(actualIndex, expectedIndex);
149
150 // reverse lookup (name)
151 int expectedMinimalIndex = minimalIndexes.get(hName);
152 int actualMinimalIndex = table.indexOf(hName, "blah-blah");
153
154 assertEquals(-actualMinimalIndex, expectedMinimalIndex);
155 }
156 );
157 }
158
159 @Test
160 public void constructorSetsMaxSize() {
161 int size = rnd.nextInt(64);
162 HeaderTable t = new HeaderTable(size, HPACK.getLogger());
163 assertEquals(t.size(), 0);
164 assertEquals(t.maxSize(), size);
165 }
166
167 @Test
168 public void negativeMaximumSize() {
169 int maxSize = -(rnd.nextInt(100) + 1); // [-100, -1]
170 IllegalArgumentException e =
171 assertVoidThrows(IllegalArgumentException.class,
172 () -> new HeaderTable(0, HPACK.getLogger()).setMaxSize(maxSize));
173 assertExceptionMessageContains(e, "maxSize");
174 }
175
176 @Test
177 public void zeroMaximumSize() {
178 HeaderTable table = new HeaderTable(0, HPACK.getLogger());
179 table.setMaxSize(0);
180 assertEquals(table.maxSize(), 0);
181 }
182
183 @Test
184 public void negativeIndex() {
185 int idx = -(rnd.nextInt(256) + 1); // [-256, -1]
186 IndexOutOfBoundsException e =
187 assertVoidThrows(IndexOutOfBoundsException.class,
188 () -> new HeaderTable(0, HPACK.getLogger()).get(idx));
189 assertExceptionMessageContains(e, "index");
190 }
191
192 @Test
193 public void zeroIndex() {
194 IndexOutOfBoundsException e =
195 assertThrows(IndexOutOfBoundsException.class,
196 () -> new HeaderTable(0, HPACK.getLogger()).get(0));
197 assertExceptionMessageContains(e, "index");
198 }
199
200 @Test
201 public void length() {
202 HeaderTable table = new HeaderTable(0, HPACK.getLogger());
203 assertEquals(table.length(), STATIC_TABLE_LENGTH);
204 }
205
206 @Test
207 public void indexOutsideStaticRange() {
208 HeaderTable table = new HeaderTable(0, HPACK.getLogger());
209 int idx = table.length() + (rnd.nextInt(256) + 1);
210 IndexOutOfBoundsException e =
211 assertThrows(IndexOutOfBoundsException.class,
212 () -> table.get(idx));
213 assertExceptionMessageContains(e, "index");
214 }
215
216 @Test
217 public void entryPutAfterStaticArea() {
218 HeaderTable table = new HeaderTable(256, HPACK.getLogger());
219 int idx = table.length() + 1;
220 assertThrows(IndexOutOfBoundsException.class, () -> table.get(idx));
221
222 byte[] bytes = new byte[32];
223 rnd.nextBytes(bytes);
224 String name = new String(bytes, StandardCharsets.ISO_8859_1);
225 String value = "custom-value";
226
227 table.put(name, value);
228 HeaderField f = table.get(idx);
229 assertEquals(name, f.name);
230 assertEquals(value, f.value);
231 }
232
233 @Test
234 public void staticTableHasZeroSize() {
235 HeaderTable table = new HeaderTable(0, HPACK.getLogger());
236 assertEquals(0, table.size());
237 }
238
239 @Test
240 public void lowerIndexPriority() {
241 HeaderTable table = new HeaderTable(256, HPACK.getLogger());
242 int oldLength = table.length();
243 table.put("bender", "rodriguez");
244 table.put("bender", "rodriguez");
245 table.put("bender", "rodriguez");
246
247 assertEquals(table.length(), oldLength + 3); // more like an assumption
248 int i = table.indexOf("bender", "rodriguez");
249 assertEquals(oldLength + 1, i);
250 }
251
252 @Test
253 public void lowerIndexPriority2() {
254 HeaderTable table = new HeaderTable(256, HPACK.getLogger());
255 int oldLength = table.length();
256 int idx = rnd.nextInt(oldLength) + 1;
257 HeaderField f = table.get(idx);
258 table.put(f.name, f.value);
259 assertEquals(table.length(), oldLength + 1);
260 int i = table.indexOf(f.name, f.value);
261 assertEquals(idx, i);
262 }
263
264 // TODO: negative indexes check
265 // TODO: ensure full table clearance when adding huge header field
266 // TODO: ensure eviction deletes minimum needed entries, not more
267
268 @Test
269 public void fifo() {
270 // Let's add a series of header fields
271 int NUM_HEADERS = 32;
272 HeaderTable t = new HeaderTable((32 + 4) * NUM_HEADERS, HPACK.getLogger());
273 // ^ ^
274 // entry overhead symbols per entry (max 2x2 digits)
275 for (int i = 1; i <= NUM_HEADERS; i++) {
276 String s = String.valueOf(i);
277 t.put(s, s);
278 }
279 // They MUST appear in a FIFO order:
280 // newer entries are at lower indexes
281 // older entries are at higher indexes
282 for (int j = 1; j <= NUM_HEADERS; j++) {
283 HeaderField f = t.get(STATIC_TABLE_LENGTH + j);
284 int actualName = Integer.parseInt(f.name);
285 int expectedName = NUM_HEADERS - j + 1;
286 assertEquals(expectedName, actualName);
287 }
288 // Entries MUST be evicted in the order they were added:
289 // the newer the entry the later it is evicted
290 for (int k = 1; k <= NUM_HEADERS; k++) {
291 HeaderField f = t.evictEntry();
292 assertEquals(String.valueOf(k), f.name);
293 }
294 }
295
296 @Test
297 public void indexOf() {
298 // Let's put a series of header fields
299 int NUM_HEADERS = 32;
300 HeaderTable t = new HeaderTable((32 + 4) * NUM_HEADERS, HPACK.getLogger());
301 // ^ ^
302 // entry overhead symbols per entry (max 2x2 digits)
303 for (int i = 1; i <= NUM_HEADERS; i++) {
304 String s = String.valueOf(i);
305 t.put(s, s);
306 }
307 // and verify indexOf (reverse lookup) returns correct indexes for
308 // full lookup
309 for (int j = 1; j <= NUM_HEADERS; j++) {
310 String s = String.valueOf(j);
311 int actualIndex = t.indexOf(s, s);
312 int expectedIndex = STATIC_TABLE_LENGTH + NUM_HEADERS - j + 1;
313 assertEquals(expectedIndex, actualIndex);
314 }
315 // as well as for just a name lookup
316 for (int j = 1; j <= NUM_HEADERS; j++) {
317 String s = String.valueOf(j);
318 int actualIndex = t.indexOf(s, "blah");
319 int expectedIndex = -(STATIC_TABLE_LENGTH + NUM_HEADERS - j + 1);
320 assertEquals(expectedIndex, actualIndex);
321 }
322 // lookup for non-existent name returns 0
323 assertEquals(0, t.indexOf("chupacabra", "1"));
324 }
325
326 @Test
327 public void testToString() {
328 testToString0();
329 }
330
331 @Test
332 public void testToStringDifferentLocale() {
333 Locale locale = Locale.getDefault();
334 Locale.setDefault(Locale.FRENCH);
335 try {
336 String s = format("%.1f", 3.1);
337 assertEquals("3,1", s); // assumption of the test, otherwise the test is useless
338 testToString0();
339 } finally {
340 Locale.setDefault(locale);
341 }
342 }
343
344 private void testToString0() {
345 HeaderTable table = new HeaderTable(0, HPACK.getLogger());
346 {
347 int maxSize = 2048;
348 table.setMaxSize(maxSize);
349 String expected = format(
350 "dynamic length: %s, full length: %s, used space: %s/%s (%.1f%%)",
351 0, STATIC_TABLE_LENGTH, 0, maxSize, 0.0);
352 assertEquals(expected, table.toString());
353 }
354
355 {
356 String name = "custom-name";
357 String value = "custom-value";
358 int size = 512;
359
360 table.setMaxSize(size);
361 table.put(name, value);
362 String s = table.toString();
363
364 int used = name.length() + value.length() + 32;
365 double ratio = used * 100.0 / size;
366
367 String expected = format(
368 "dynamic length: %s, full length: %s, used space: %s/%s (%.1f%%)",
369 1, STATIC_TABLE_LENGTH + 1, used, size, ratio);
370 assertEquals(expected, s);
371 }
372
373 {
374 table.setMaxSize(78);
375 table.put(":method", "");
376 table.put(":status", "");
377 String s = table.toString();
378 String expected =
379 format("dynamic length: %s, full length: %s, used space: %s/%s (%.1f%%)",
380 2, STATIC_TABLE_LENGTH + 2, 78, 78, 100.0);
381 assertEquals(expected, s);
382 }
383 }
384
385 @Test
386 public void stateString() {
387 HeaderTable table = new HeaderTable(256, HPACK.getLogger());
388 table.put("custom-key", "custom-header");
389 // @formatter:off
390 assertEquals("[ 1] (s = 55) custom-key: custom-header\n" +
391 " Table size: 55", table.getStateString());
392 // @formatter:on
393 }
394
395 private static Map<Integer, HeaderField> createStaticEntries() {
396 Pattern line = Pattern.compile(
397 "\\|\\s*(?<index>\\d+?)\\s*\\|\\s*(?<name>.+?)\\s*\\|\\s*(?<value>.*?)\\s*\\|");
398 Matcher m = line.matcher(SPEC);
399 Map<Integer, HeaderField> result = new HashMap<>();
400 while (m.find()) {
401 int index = Integer.parseInt(m.group("index"));
402 String name = m.group("name");
403 String value = m.group("value");
404 HeaderField f = new HeaderField(name, value);
405 result.put(index, f);
406 }
407 return Collections.unmodifiableMap(result); // lol
|