working decoder that can handle D3 and D4 examples from draft

This commit is contained in:
Greg Wilkins 2014-06-09 01:11:04 +02:00
parent fdf73adf39
commit d7fbd89a75
7 changed files with 333 additions and 29 deletions

View File

@ -26,6 +26,7 @@ import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import org.eclipse.jetty.hpack.HpackDecoder.Listener;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.util.ArrayQueue;
import org.eclipse.jetty.util.ArrayTrie;
@ -232,16 +233,6 @@ public class HpackContext
}
}
public void unuseReferenceSet()
{
Entry entry = _refSet._refSetNext;
while(entry!=_refSet)
{
entry._used=false;
entry=entry._refSetNext;
}
}
public void removedUnusedReferences(ByteBuffer buffer)
{
@ -250,7 +241,9 @@ public class HpackContext
{
Entry next = entry._refSetNext;
if (!entry.isUsed())
if (entry.isUsed())
entry._used=false;
else
{
// encode the reference to remove it
buffer.put((byte)0x80);
@ -259,7 +252,20 @@ public class HpackContext
}
entry=next;
}
}
public void emitUnusedReferences(Listener listener)
{
Entry entry = _refSet._refSetNext;
while(entry!=_refSet)
{
if (entry.isUsed())
entry._used=false;
else
listener.emit(entry.getHttpField());
entry=entry._refSetNext;
}
}
@ -517,4 +523,5 @@ public class HpackContext
}

View File

@ -21,7 +21,9 @@ package org.eclipse.jetty.hpack;
import java.nio.ByteBuffer;
import org.eclipse.jetty.hpack.HpackContext.Entry;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
/* ------------------------------------------------------------ */
@ -36,14 +38,136 @@ public class HpackDecoder
void endHeaders();
}
public HpackDecoder(Listener listenr)
private final HpackContext _context;
private final Listener _listener;
public HpackDecoder(Listener listener)
{
this(listener,4096);
}
public void parse(ByteBuffer buffer)
public HpackDecoder(Listener listener,int maxHeaderTableSize)
{
_listener=listener;
_context=new HpackContext(maxHeaderTableSize);
}
public void decode(ByteBuffer buffer)
{
while(buffer.hasRemaining())
{
byte b = buffer.get();
if (b<0)
{
// indexed
int index = NBitInteger.decode(buffer,7);
Entry entry=_context.get(index);
if (entry.isInReferenceSet())
_context.get(index).removeFromRefSet();
else if (entry.isStatic())
{
// emit field
_listener.emit(entry.getHttpField());
// copy and add to reference set if there is room
Entry new_entry = _context.add(entry.getHttpField());
if (new_entry!=null)
_context.addToRefSet(new_entry);
}
else
{
// emit
_listener.emit(entry.getHttpField());
// add to reference set
_context.addToRefSet(entry);
}
}
else
{
// look at the first nibble in detail
int f=(b&0xF0)>>4;
String name;
HttpHeader header;
String value;
if (f<=1 || f>=4)
{
// literal
boolean indexed=f>=4;
int bits=indexed?6:4;
// decode the name
int name_index=NBitInteger.decode(buffer,bits);
if (name_index>0)
{
Entry name_entry=_context.get(name_index);
name=name_entry.getHttpField().getName();
header=name_entry.getHttpField().getHeader();
}
else
{
boolean huffman = (buffer.get()&0x80)==0x80;
int length = NBitInteger.decode(buffer,7);
if (huffman)
name=Huffman.decode(buffer,length);
else
name=toASCIIString(buffer,length);
header=HttpHeader.CACHE.get(name);
}
// decode the value
boolean huffman = (buffer.get()&0x80)==0x80;
int length = NBitInteger.decode(buffer,7);
if (huffman)
value=Huffman.decode(buffer,length);
else
value=toASCIIString(buffer,length);
// Make the new field
HttpField field = new HttpField(header,name,value);
// emit the field
_listener.emit(field);
// if indexed
if (indexed)
{
// add to header table
Entry new_entry=_context.add(field);
// and to ref set if there was room in header table
if (new_entry!=null)
_context.addToRefSet(new_entry);
}
}
else if (f==2)
{
// change table size
int size = NBitInteger.decode(buffer,4);
_context.resize(size);
}
else if (f==3)
{
// clear reference set
_context.clearReferenceSet();
}
}
}
_context.emitUnusedReferences(_listener);
_listener.endHeaders();
}
public static String toASCIIString(ByteBuffer buffer,int length)
{
StringBuilder builder = new StringBuilder(length);
int start=buffer.arrayOffset()+buffer.position();
int end=start+length;
buffer.position(end);
byte[] array=buffer.array();
for (int i=start;i<end;i++)
builder.append((char)(0x7f&array[i]));
return builder.toString();
}
}

View File

@ -89,9 +89,6 @@ public class HpackEncoder
public void encode(ByteBuffer buffer, HttpFields fields)
{
// Clear the used bits
_context.unuseReferenceSet();
// Add all the known fields
for (HttpField field : fields)
{

View File

@ -340,18 +340,22 @@ public class Huffman
}
}
static public String decode(ByteBuffer buffer)
{
return decode(buffer,buffer.remaining());
}
static public String decode(ByteBuffer buffer,int length)
{
StringBuilder out = new StringBuilder(buffer.remaining()*2);
StringBuilder out = new StringBuilder(length*2);
int node = 0;
int current = 0;
int bits = 0;
byte[] array = buffer.array();
int start=buffer.arrayOffset()+buffer.position();
int end=start+buffer.remaining();
buffer.position(buffer.limit());
int end=start+length;
buffer.position(end);
for (int i=start; i<end; i++)
{

View File

@ -107,13 +107,13 @@ public class NBitInteger
}
}
public static int decode(ByteBuffer buf, int n)
public static int decode(ByteBuffer buffer, int n)
{
if (n==8)
{
int nbits = 0xFF;
int i=buf.get()&0xff;
int i=buffer.get()&0xff;
if (i == nbits)
{
@ -121,7 +121,7 @@ public class NBitInteger
int b;
do
{
b = 0xff&buf.get();
b = 0xff&buffer.get();
i = i + (b&127) * m;
m = m*128;
}
@ -132,7 +132,7 @@ public class NBitInteger
int nbits = 0xFF >>> (8 - n);
int i=buf.get(buf.position()-1)&nbits;
int i=buffer.get(buffer.position()-1)&nbits;
if (i == nbits)
{
@ -140,7 +140,7 @@ public class NBitInteger
int b;
do
{
b = 0xff&buf.get();
b = 0xff&buffer.get();
i = i + (b&127) * m;
m = m*128;
}

View File

@ -0,0 +1,172 @@
//
// ========================================================================
// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.hpack;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import org.eclipse.jetty.hpack.HpackDecoder.Listener;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.util.TypeUtil;
import org.junit.Assert;
import org.junit.Test;
/* ------------------------------------------------------------ */
/**
*/
public class HpackDecoderTest
{
@Test
public void testDecodeD_3()
{
final HttpFields fields = new HttpFields();
Listener listener = new Listener()
{
@Override
public void endHeaders()
{
System.err.println("===");
}
@Override
public void emit(HttpField field)
{
System.err.println(field);
fields.add(field);
}
};
HpackDecoder decoder = new HpackDecoder(listener);
// First request
String encoded="828786440f7777772e6578616d706c652e636f6d";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
decoder.decode(buffer);
assertEquals(4,fields.size());
assertEquals("GET",fields.get(":method"));
assertEquals("http",fields.get(":scheme"));
assertEquals("/",fields.get(":path"));
assertEquals("www.example.com",fields.get(":authority"));
// Second request
fields.clear();
encoded="5c086e6f2d6361636865";
buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
decoder.decode(buffer);
assertEquals(5,fields.size());
assertEquals("GET",fields.get(":method"));
assertEquals("http",fields.get(":scheme"));
assertEquals("/",fields.get(":path"));
assertEquals("www.example.com",fields.get(":authority"));
assertEquals("no-cache",fields.get("cache-control"));
// Third request
fields.clear();
encoded="30858c8b84400a637573746f6d2d6b65790c637573746f6d2d76616c7565";
buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
decoder.decode(buffer);
assertEquals(5,fields.size());
assertEquals("GET",fields.get(":method"));
assertEquals("https",fields.get(":scheme"));
assertEquals("/index.html",fields.get(":path"));
assertEquals("www.example.com",fields.get(":authority"));
assertEquals("custom-value",fields.get("custom-key"));
}
@Test
public void testDecodeD_4()
{
final HttpFields fields = new HttpFields();
Listener listener = new Listener()
{
@Override
public void endHeaders()
{
System.err.println("===");
}
@Override
public void emit(HttpField field)
{
System.err.println(field);
fields.add(field);
}
};
HpackDecoder decoder = new HpackDecoder(listener);
// First request
String encoded="828786448ce7cf9bebe89b6fb16fa9b6ff";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
decoder.decode(buffer);
assertEquals(4,fields.size());
assertEquals("GET",fields.get(":method"));
assertEquals("http",fields.get(":scheme"));
assertEquals("/",fields.get(":path"));
assertEquals("www.example.com",fields.get(":authority"));
// Second request
fields.clear();
encoded="5c86b9b9949556bf";
buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
decoder.decode(buffer);
assertEquals(5,fields.size());
assertEquals("GET",fields.get(":method"));
assertEquals("http",fields.get(":scheme"));
assertEquals("/",fields.get(":path"));
assertEquals("www.example.com",fields.get(":authority"));
assertEquals("no-cache",fields.get("cache-control"));
// Third request
fields.clear();
encoded="30858c8b844088571c5cdb737b2faf89571c5cdb73724d9c57";
buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
decoder.decode(buffer);
assertEquals(5,fields.size());
assertEquals("GET",fields.get(":method"));
assertEquals("https",fields.get(":scheme"));
assertEquals("/index.html",fields.get(":path"));
assertEquals("www.example.com",fields.get(":authority"));
assertEquals("custom-value",fields.get("custom-key"));
}
}

View File

@ -518,7 +518,7 @@ public class BufferUtil
}
/* ------------------------------------------------------------ */
/** Convert a partial buffer to an ISO-8859-1 String
/** Convert a partial buffer to a String
* @param buffer The buffer to convert in flush mode. The buffer is unchanged
* @param charset The {@link Charset} to use to convert the bytes
* @return The buffer as a string.