package test.codec.http2.hpack;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.nio.ByteBuffer;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
import com.firefly.codec.http2.hpack.HpackContext;
import com.firefly.codec.http2.hpack.HpackContext.Entry;
import com.firefly.codec.http2.hpack.Huffman;
import com.firefly.codec.http2.hpack.NBitInteger;
import com.firefly.codec.http2.model.HttpField;
public class HpackContextTest {
@Test
public void testStaticName() {
HpackContext ctx = new HpackContext(4096);
Entry entry = ctx.get(":method");
assertEquals(":method", entry.getHttpField().getName());
Assert.assertTrue(entry.isStatic());
Assert.assertThat(entry.toString(), Matchers.startsWith("{S,2,:method: "));
}
@Test
public void testEmptyAdd() {
HpackContext ctx = new HpackContext(0);
HttpField field = new HttpField("foo", "bar");
Assert.assertNull(ctx.add(field));
}
@Test
public void testTooBigAdd() {
HpackContext ctx = new HpackContext(37);
HttpField field = new HttpField("foo", "bar");
Assert.assertNull(ctx.add(field));
}
@Test
public void testJustRight() {
HpackContext ctx = new HpackContext(38);
HttpField field = new HttpField("foo", "bar");
Entry entry = ctx.add(field);
Assert.assertNotNull(entry);
Assert.assertThat(entry.toString(), Matchers.startsWith("{D,0,foo: bar,"));
}
@Test
public void testEvictOne() {
HpackContext ctx = new HpackContext(38);
HttpField field0 = new HttpField("foo", "bar");
assertEquals(field0, ctx.add(field0).getHttpField());
assertEquals(field0, ctx.get("foo").getHttpField());
HttpField field1 = new HttpField("xxx", "yyy");
assertEquals(field1, ctx.add(field1).getHttpField());
assertNull(ctx.get(field0));
assertNull(ctx.get("foo"));
assertEquals(field1, ctx.get(field1).getHttpField());
assertEquals(field1, ctx.get("xxx").getHttpField());
}
@Test
public void testEvictNames() {
HpackContext ctx = new HpackContext(38 * 2);
HttpField[] field =
{
new HttpField("name", "v0"),
new HttpField("name", "v1"),
new HttpField("name", "v2"),
new HttpField("name", "v3"),
new HttpField("name", "v4"),
new HttpField("name", "v5"),
};
Entry[] entry = new Entry[field.length];
// Add 2 name entries to fill table
for (int i = 0; i <= 1; i++)
entry[i] = ctx.add(field[i]);
// check there is a name reference and it is the most recent added
assertEquals(entry[1], ctx.get("name"));
// Add 1 other entry to table and evict 1
ctx.add(new HttpField("xxx", "yyy"));
// check the name reference has been not been evicted
assertEquals(entry[1], ctx.get("name"));
// Add 1 other entry to table and evict 1
ctx.add(new HttpField("foo", "bar"));
// name is evicted
assertNull(ctx.get("name"));
}
@Test
public void testGetAddStatic() {
HpackContext ctx = new HpackContext(4096);
// Look for the field. Should find static version.
HttpField methodGet = new HttpField(":method", "GET");
assertEquals(methodGet, ctx.get(methodGet).getHttpField());
assertTrue(ctx.get(methodGet).isStatic());
// Add static version to dynamic table
Entry e0 = ctx.add(ctx.get(methodGet).getHttpField());
// Look again and should see dynamic version
assertEquals(methodGet, ctx.get(methodGet).getHttpField());
assertFalse(methodGet == ctx.get(methodGet).getHttpField());
assertFalse(ctx.get(methodGet).isStatic());
// Duplicates allows
Entry e1 = ctx.add(ctx.get(methodGet).getHttpField());
// Look again and should see dynamic version
assertEquals(methodGet, ctx.get(methodGet).getHttpField());
assertFalse(methodGet == ctx.get(methodGet).getHttpField());
assertFalse(ctx.get(methodGet).isStatic());
assertFalse(e0 == e1);
}
@Test
public void testGetAddStaticName() {
HpackContext ctx = new HpackContext(4096);
HttpField methodOther = new HttpField(":method", "OTHER");
// Look for the field by name. Should find static version.
assertEquals(":method", ctx.get(":method").getHttpField().getName());
assertTrue(ctx.get(":method").isStatic());
// Add dynamic entry with method
ctx.add(methodOther);
// Look for the field by name. Should find static version.
assertEquals(":method", ctx.get(":method").getHttpField().getName());
assertTrue(ctx.get(":method").isStatic());
}
@Test
public void testIndexes() {
// Only enough space for 5 entries
HpackContext ctx = new HpackContext(38 * 5);
HttpField methodPost = new HttpField(":method", "POST");
HttpField[] field =
{
new HttpField("fo0", "b0r"),
new HttpField("fo1", "b1r"),
new HttpField("fo2", "b2r"),
new HttpField("fo3", "b3r"),
new HttpField("fo4", "b4r"),
new HttpField("fo5", "b5r"),
new HttpField("fo6", "b6r"),
new HttpField("fo7", "b7r"),
new HttpField("fo8", "b8r"),
new HttpField("fo9", "b9r"),
new HttpField("foA", "bAr"),
};
Entry[] entry = new Entry[100];
// Lookup the index of a static field
assertEquals(0, ctx.size());
assertEquals(":authority", ctx.get(1).getHttpField().getName());
assertEquals(3, ctx.index(ctx.get(methodPost)));
assertEquals(methodPost, ctx.get(3).getHttpField());
assertEquals("www-authenticate", ctx.get(61).getHttpField().getName());
assertEquals(null, ctx.get(62));
// Add a single entry
entry[0] = ctx.add(field[0]);
// Check new entry is 62
assertEquals(1, ctx.size());
assertEquals(62, ctx.index(entry[0]));
assertEquals(entry[0], ctx.get(62));
// and statics still OK
assertEquals(":authority", ctx.get(1).getHttpField().getName());
assertEquals(3, ctx.index(ctx.get(methodPost)));
assertEquals(methodPost, ctx.get(3).getHttpField());
assertEquals("www-authenticate", ctx.get(61).getHttpField().getName());
assertEquals(null, ctx.get(62 + ctx.size()));
// Add 4 more entries
for (int i = 1; i <= 4; i++)
entry[i] = ctx.add(field[i]);
// Check newest entry is at 62 oldest at 66
assertEquals(5, ctx.size());
int index = 66;
for (int i = 0; i <= 4; i++) {
assertEquals(index, ctx.index(entry[i]));
assertEquals(entry[i], ctx.get(index));
index--;
}
// and statics still OK
assertEquals(":authority", ctx.get(1).getHttpField().getName());
assertEquals(3, ctx.index(ctx.get(methodPost)));
assertEquals(methodPost, ctx.get(3).getHttpField());
assertEquals("www-authenticate", ctx.get(61).getHttpField().getName());
assertEquals(null, ctx.get(62 + ctx.size()));
// add 1 more entry and this should cause an eviction!
entry[5] = ctx.add(field[5]);
// Check newest entry is at 1 oldest at 5
index = 66;
for (int i = 1; i <= 5; i++) {
assertEquals(index, ctx.index(entry[i]));
assertEquals(entry[i], ctx.get(index));
index--;
}
// check entry 0 evicted
assertNull(ctx.get(field[0]));
assertEquals(0, ctx.index(entry[0]));
// and statics still OK
assertEquals(":authority", ctx.get(1).getHttpField().getName());
assertEquals(3, ctx.index(ctx.get(methodPost)));
assertEquals(methodPost, ctx.get(3).getHttpField());
assertEquals("www-authenticate", ctx.get(61).getHttpField().getName());
assertEquals(null, ctx.get(62 + ctx.size()));
// Add 4 more entries
for (int i = 6; i <= 9; i++)
entry[i] = ctx.add(field[i]);
// Check newest entry is at 1 oldest at 5
index = 66;
for (int i = 5; i <= 9; i++) {
assertEquals(index, ctx.index(entry[i]));
assertEquals(entry[i], ctx.get(index));
index--;
}
// check entry 0-4 evicted
for (int i = 0; i <= 4; i++) {
assertNull(ctx.get(field[i]));
assertEquals(0, ctx.index(entry[i]));
}
// Add new entries enough so that array queue will wrap
for (int i = 10; i <= 52; i++)
entry[i] = ctx.add(new HttpField("n" + i, "v" + i));
index = 66;
for (int i = 48; i <= 52; i++) {
assertEquals(index, ctx.index(entry[i]));
assertEquals(entry[i], ctx.get(index));
index--;
}
}
@Test
public void testResize() {
// Only enough space for 5 entries
HpackContext ctx = new HpackContext(38 * 5);
HttpField[] field =
{
new HttpField("fo0", "b0r"),
new HttpField("fo1", "b1r"),
new HttpField("fo2", "b2r"),
new HttpField("fo3", "b3r"),
new HttpField("fo4", "b4r"),
new HttpField("fo5", "b5r"),
new HttpField("fo6", "b6r"),
new HttpField("fo7", "b7r"),
new HttpField("fo8", "b8r"),
new HttpField("fo9", "b9r"),
new HttpField("foA", "bAr"),
};
Entry[] entry = new Entry[field.length];
// Add 5 entries
for (int i = 0; i <= 4; i++)
entry[i] = ctx.add(field[i]);
assertEquals(5, ctx.size());
// check indexes
int index = 66;
for (int i = 0; i <= 4; i++) {
assertEquals(index, ctx.index(entry[i]));
assertEquals(entry[i], ctx.get(index));
index--;
}
// resize so that only 2 entries may be held
ctx.resize(38 * 2);
assertEquals(2, ctx.size());
// check indexes
index = 63;
for (int i = 3; i <= 4; i++) {
assertEquals(index, ctx.index(entry[i]));
assertEquals(entry[i], ctx.get(index));
index--;
}
// resize so that 6.5 entries may be held
ctx.resize(38 * 6 + 19);
assertEquals(2, ctx.size());
// check indexes
index = 63;
for (int i = 3; i <= 4; i++) {
assertEquals(index, ctx.index(entry[i]));
assertEquals(entry[i], ctx.get(index));
index--;
}
// Add 5 entries
for (int i = 5; i <= 9; i++)
entry[i] = ctx.add(field[i]);
assertEquals(6, ctx.size());
// check indexes
index = 67;
for (int i = 4; i <= 9; i++) {
assertEquals(index, ctx.index(entry[i]));
assertEquals(entry[i], ctx.get(index));
index--;
}
// resize so that only 100 entries may be held
ctx.resize(38 * 100);
assertEquals(6, ctx.size());
// check indexes
index = 67;
for (int i = 4; i <= 9; i++) {
assertEquals(index, ctx.index(entry[i]));
assertEquals(entry[i], ctx.get(index));
index--;
}
// add 50 fields
for (int i = 0; i < 50; i++)
ctx.add(new HttpField("n" + i, "v" + i));
// check indexes
index = 67 + 50;
for (int i = 4; i <= 9; i++) {
assertEquals(index, ctx.index(entry[i]));
assertEquals(entry[i], ctx.get(index));
index--;
}
}
@Test
public void testStaticHuffmanValues() {
HpackContext ctx = new HpackContext(4096);
for (int i = 2; i <= 14; i++) {
Entry entry = ctx.get(i);
assertTrue(entry.isStatic());
ByteBuffer buffer = ByteBuffer.wrap(entry.getStaticHuffmanValue());
int huff = 0xff & buffer.get();
assertTrue((0x80 & huff) == 0x80);
int len = NBitInteger.decode(buffer, 7);
assertEquals(len, buffer.remaining());
String value = Huffman.decode(buffer);
assertEquals(entry.getHttpField().getValue(), value);
}
}
@Test
public void testNameInsensitivity() {
HpackContext ctx = new HpackContext(4096);
assertEquals("content-length", ctx.get("content-length").getHttpField().getName());
assertEquals("content-length", ctx.get("Content-Length").getHttpField().getName());
assertTrue(ctx.get("Content-Length").isStatic());
assertTrue(ctx.get("Content-Type").isStatic());
ctx.add(new HttpField("Wibble", "Wobble"));
assertEquals("Wibble", ctx.get("wibble").getHttpField().getName());
assertEquals("Wibble", ctx.get("Wibble").getHttpField().getName());
}
}