Commit e04b0937 authored by Jonathan Mace's avatar Jonathan Mace

Move in baggage protocol code and tests

parent fc937400
......@@ -22,6 +22,10 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
......
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>brown.tracingplane</groupId>
<artifactId>baggagecontext-staticapi</artifactId>
<packaging>jar</packaging>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>brown.tracingplane</groupId>
<artifactId>baggagecontext-staticapi</artifactId>
<packaging>jar</packaging>
<name>Baggage Context - Static API</name>
<name>Baggage Context - Static API</name>
<parent>
<groupId>brown.tracingplane</groupId>
<artifactId>baggagecontext</artifactId>
<version>1.0</version>
</parent>
<parent>
<groupId>brown.tracingplane</groupId>
<artifactId>baggagecontext</artifactId>
<version>1.0</version>
</parent>
<dependencies>
<dependency>
<groupId>brown.tracingplane</groupId>
<artifactId>baggagecontext-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
<dependencies>
<dependency>
<groupId>brown.tracingplane</groupId>
<artifactId>baggagecontext-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>
......@@ -24,6 +24,10 @@
<artifactId>atomlayer-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
<build>
......
package brown.tracingplane.baggageprotocol;
import brown.tracingplane.baggageprotocol.BagOptions.MergeBehavior;
/**
* This class has the bitwise logic for different bags prefixes.
*
* For now the protocol is such that the first byte of each atom is its prefix, and the remaining bytes are its payload.
*/
public class AtomPrefixTypes {
private AtomPrefixTypes() {}
private static final AtomType[] bagTypes = new AtomType[AtomType.VALUES];
private static final HeaderType[] headerTypes = new HeaderType[HeaderType.VALUES];
/**
* The first bit of a prefix is the atom type:
*
* 0 is a data atom <br>
* 1 is a header atom <br>
*
* There is also an overflow marker {@link BaggageAtoms#OVERFLOW_MARKER} that is just the empty byte array (ie, it
* has no prefix) and therefore lexicographically less than all other atoms
*/
public enum AtomType {
Data(0), Header(1);
public static final int BITS = 1;
public static final int VALUES = 2;
private static final int mask = 0x80;
private static final int offset = 7;
public final int id;
public final byte byteValue;
private AtomType(int id) {
this.id = id;
this.byteValue = (byte) ((id << offset) & mask);
bagTypes[id] = this;
}
public static AtomType fromByte(byte b) {
int id = (b & mask) >>> offset;
return bagTypes[id];
}
public boolean match(byte b) {
return (b & mask) == byteValue;
}
}
/**
* <p>
* Levels are used by headers. Within a header prefix, bytes 2 through 5 specify the {@link Level} of the header,
* while the final two bits specify the {@link HeaderType}.
* </p>
*
* Since four bits are used for a level, there are 16 possible valid levels. Levels {@code l} is encoded as
* {@code 15 - l} to ensure that, lexicographically, lower levels come last. Specifically:
*
* level 0 is 1111 <br>
* level 1 is 1110 <br>
* level 2 is 1101 <br>
* level 3 is 1100 <br>
* level 4 is 1011 <br>
* level 5 is 1010 <br>
* level 6 is 1001 <br>
* level 7 is 1000 <br>
* level 8 is 0111 <br>
* level 9 is 0110 <br>
* level 10 is 0101 <br>
* level 11 is 0100 <br>
* level 12 is 0011 <br>
* level 13 is 0010 <br>
* level 14 is 0001 <br>
* level 15 is 0000 <br>
*/
public static class Level {
public static final int LEVELS = 16;
private static final int MAXLEVEL = 15;
private static final int mask = 0x78;
private static final int offset = 3;
static final Level[] levels = new Level[LEVELS];
static {
for (int i = 0; i < LEVELS; i++) {
levels[i] = new Level(i);
}
}
public final int level;
public final byte byteValue;
private Level(int level) {
this.level = level;
this.byteValue = (byte) (((MAXLEVEL - level) << offset) & mask);
}
public static boolean isValidLevel(int level) {
return (level & 0xF0) == 0;
}
public static Level get(int level) {
if (isValidLevel(level)) {
return levels[level];
} else {
return null;
}
}
public static int valueOf(byte b) {
return MAXLEVEL - ((b & mask) >> offset);
}
public static Level fromByte(byte b) {
return levels[valueOf(b)];
}
public boolean match(byte b) {
return (b & mask) == byteValue;
}
}
/**
* There are two different types of header. Within a header prefix, the middle four bits specify the {@link Level}
* of the header, while the final two bits specify the {@link HeaderType}.
*
* 00 is not currently used <br>
* 01 is an indexed header <br>
* 10 is a keyed header <br>
* 11 is not currently used
*/
public enum HeaderType {
Indexed(0), Keyed(1);
public static final int BITS = 1;
public static final int VALUES = 2;
private static final int mask = 0x04;
private static final int offset = 2;
public final int id;
public final byte byteValue;
private HeaderType(int id) {
this.id = id;
this.byteValue = (byte) ((id << offset) & mask);
headerTypes[id] = this;
}
/**
* Inspect the final two bits of the provided byte and return the corresponding {@link HeaderType}
*
* @param b an atom prefix
* @return the HeaderType encoded in this prefix
*/
public static HeaderType fromByte(byte b) {
int id = (b & mask) >> offset;
return headerTypes[id];
}
public boolean match(byte b) {
return (b & mask) == byteValue;
}
}
public static class BagOptionsInPrefix {
public static final int mask = 0x01;
public static final int offset = 0;
public final int id;
public final MergeBehavior merge;
public final byte byteValue;
static final BagOptionsInPrefix[] values = new BagOptionsInPrefix[2];
static {
values[0] = new BagOptionsInPrefix(0, MergeBehavior.TakeAll);
values[1] = new BagOptionsInPrefix(1, MergeBehavior.TakeFirst);
}
public BagOptionsInPrefix(int id, MergeBehavior merge) {
this.id = id;
this.merge = merge;
this.byteValue = (byte) ((merge.ordinal() << offset) & mask);
}
public static BagOptionsInPrefix fromByte(byte b) {
int merge = (b & mask) >> offset;
return values[merge];
}
public static BagOptionsInPrefix get(BagOptions options) {
return values[options.merge.ordinal()];
}
public boolean match(byte b) {
return (b & mask) == byteValue;
}
}
}
\ No newline at end of file
package brown.tracingplane.baggageprotocol;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import brown.tracingplane.atomlayer.Lexicographic;
import brown.tracingplane.atomlayer.TypeUtils;
import brown.tracingplane.baggageprotocol.AtomPrefixTypes.AtomType;
import brown.tracingplane.baggageprotocol.AtomPrefixTypes.BagOptionsInPrefix;
import brown.tracingplane.baggageprotocol.AtomPrefixTypes.HeaderType;
import brown.tracingplane.baggageprotocol.AtomPrefixTypes.Level;
/**
* <p>
* This class has the logic for creating and checking bag prefixes. It does not directly create or deal with bit
* representations; that is contained in {@link AtomPrefixTypes}.
* </p>
*
* <p>
* The baggage protocol only uses the first byte of an atom as a prefix. Consequently, during parsing, instead of
* interpreting the first byte, we just look it up in an array of 256 prefix objects.
* </p>
*/
public class AtomPrefixes {
private static final AtomPrefix[] prefixes;
static {
// Initialize all prefixes as unsupported
prefixes = new AtomPrefix[256];
for (int i = 0; i < prefixes.length; i++) {
prefixes[i] = new UnsupportedPrefix((byte) i);
}
// Construct all valid prefixes
List<AtomPrefix> allowedAtoms = new ArrayList<>(256);
allowedAtoms.add(DataPrefix.prefix());
for (IndexedHeaderPrefix atom : IndexedHeaderPrefix.prefixes) {
allowedAtoms.add(atom);
}
for (KeyedHeaderPrefix atom : KeyedHeaderPrefix.prefixes) {
allowedAtoms.add(atom);
}
// Insert into array
for (AtomPrefix atom : allowedAtoms) {
if (atom.prefix >= 0) {
prefixes[atom.prefix] = atom;
} else {
prefixes[256 + atom.prefix] = atom;
}
}
}
private AtomPrefixes() {}
/**
* Get the {@link AtomPrefix} object for the specified prefix
*
* @param prefix the first byte of an atom
* @return the {@link AtomPrefix} object that corresponds to this prefix
*/
public static AtomPrefix get(byte prefix) {
if (prefix >= 0) {
return prefixes[prefix];
} else {
return prefixes[256 + prefix];
}
}
/** AtomPrefix has some convenience methods for checking what kind of prefix a byte is and what it supports */
public static abstract class AtomPrefix implements Comparable<AtomPrefix> {
protected final AtomType atomType;
public final byte prefix;
public AtomPrefix(AtomType atomType, byte prefix) {
this.atomType = atomType;
this.prefix = prefix;
}
boolean isValid() {
return true;
}
boolean isHeader() {
return false;
}
boolean isData() {
return false;
}
int level(int currentLevel) {
return currentLevel + 1;
}
@Override
public int compareTo(AtomPrefix o) {
return Lexicographic.compare(prefix, o.prefix);
}
}
public static abstract class HeaderPrefix extends AtomPrefix {
public static final AtomType atomType = AtomType.Header;
protected static final int distinctLevels = Level.LEVELS;
protected static final int distinctBagOptions = BagOptionsInPrefix.values.length;
protected static final int distinctPrefixes = distinctLevels * distinctBagOptions;
protected final Level level;
protected final HeaderType headerType;
protected final BagOptionsInPrefix options;
public HeaderPrefix(Level level, HeaderType headerType, BagOptionsInPrefix options) {
super(AtomType.Header,
(byte) (AtomType.Header.byteValue | level.byteValue | headerType.byteValue | options.byteValue));
this.level = level;
this.headerType = headerType;
this.options = options;
}
protected static int indexOf(int level, BagOptions options) {
return indexOf(level, BagOptionsInPrefix.get(options));
}
protected static int indexOf(int level, BagOptionsInPrefix options) {
return level * distinctBagOptions + options.id;
}
@Override
boolean isHeader() {
return true;
}
int level() {
return this.level.level;
}
@Override
int level(int currentLevel) {
return this.level.level;
}
BagOptions options() {
return BagOptions.create(options.merge);
}
abstract BagKey parse(ByteBuffer buf) throws BaggageLayerException;
}
public static class IndexedHeaderPrefix extends HeaderPrefix {
public static final HeaderType headerType = HeaderType.Indexed;
private static final IndexedHeaderPrefix[] prefixes;
static {
prefixes = new IndexedHeaderPrefix[distinctPrefixes];
for (BagOptionsInPrefix options : BagOptionsInPrefix.values) {
for (Level level : Level.levels) {
prefixes[indexOf(level.level, options)] = new IndexedHeaderPrefix(level, options);
}
}
}
public static IndexedHeaderPrefix prefixFor(int level, BagOptions options) {
if (Level.isValidLevel(level)) {
return prefixes[indexOf(level, options)];
} else {
return null;
}
}
private IndexedHeaderPrefix(Level level, BagOptionsInPrefix options) {
super(level, headerType, options);
}
@Override
public String toString() {
return String.format("[IndexedHeaderPrefix prefix=%s level=%d options=%s]",
TypeUtils.toBinaryString(prefix), level.level, options);
}
@Override
BagKey parse(ByteBuffer buf) throws BaggageLayerException {
return HeaderSerialization.parseIndexedHeaderPayload(this, buf);
}
}
public static class KeyedHeaderPrefix extends HeaderPrefix {
public static final HeaderType headerType = HeaderType.Keyed;
private static final KeyedHeaderPrefix[] prefixes;
static {
prefixes = new KeyedHeaderPrefix[distinctPrefixes];
for (BagOptionsInPrefix options : BagOptionsInPrefix.values) {
for (Level level : Level.levels) {
prefixes[indexOf(level.level, options)] = new KeyedHeaderPrefix(level, options);
}
}
}
public static KeyedHeaderPrefix prefixFor(int level, BagOptions options) {
if (Level.isValidLevel(level)) {
return prefixes[indexOf(level, options)];
} else {
return null;
}
}
private KeyedHeaderPrefix(Level level, BagOptionsInPrefix options) {
super(level, headerType, options);
}
@Override
public String toString() {
return String.format("[KeyedHeaderPrefix prefix=%s level=%d, options=%s]",
TypeUtils.toBinaryString(prefix), level.level, options);
}
@Override
BagKey parse(ByteBuffer buf) throws BaggageLayerException {
return HeaderSerialization.parseKeyedHeaderPayload(this, buf);
}
}
public static class DataPrefix extends AtomPrefix {
public static final AtomType atomType = AtomType.Data;
public static final byte prefix = atomType.byteValue;
static final DataPrefix instance = new DataPrefix();
private DataPrefix() {
super(atomType, DataPrefix.prefix);
}
public static DataPrefix prefix() {
return instance;
}
@Override
public boolean isData() {
return true;
}
@Override
public String toString() {
return String.format("[DataPrefix prefix=%s]", TypeUtils.toBinaryString(super.prefix));
}
}
public static class UnsupportedPrefix extends AtomPrefix {
private UnsupportedPrefix(byte prefix) {
super(null, prefix);
}
@Override
boolean isValid() {
return false;
}
@Override
public String toString() {
return String.format("[UnsupportedPrefix prefix=%s]", TypeUtils.toBinaryString(prefix));
}
}
public static void main(String[] args) {
for (int i = 0; i < prefixes.length; i++) {
System.out.printf("%d: %s\n", i, prefixes[i]);
}
}
}