mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
5 Commits
variable-l
...
hash-trees
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f36ba9014 | ||
|
|
438d200afe | ||
|
|
bd9ebe75a0 | ||
|
|
4b04e6a21d | ||
|
|
f915eb4d36 |
@@ -0,0 +1,20 @@
|
|||||||
|
package org.briarproject.bramble.api.io;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.TreeHash;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface BlockSink {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a block of the message with the given temporary ID.
|
||||||
|
*/
|
||||||
|
void putBlock(HashingId h, int blockNumber, byte[] data) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the hash tree path of a previously stored block.
|
||||||
|
*/
|
||||||
|
void setPath(HashingId h, int blockNumber, List<TreeHash> path)
|
||||||
|
throws DbException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package org.briarproject.bramble.api.io;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type-safe wrapper for a byte array that uniquely identifies a
|
||||||
|
* {@link Message} while it's being hashed and the {@link MessageId} is not
|
||||||
|
* yet known.
|
||||||
|
*/
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
public class HashingId extends UniqueId {
|
||||||
|
|
||||||
|
public HashingId(byte[] id) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o instanceof HashingId && super.equals(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.bramble.api.sync;
|
package org.briarproject.bramble.api.sync;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.TreeHash;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface MessageFactory {
|
public interface MessageFactory {
|
||||||
@@ -10,4 +11,6 @@ public interface MessageFactory {
|
|||||||
Message createMessage(byte[] raw);
|
Message createMessage(byte[] raw);
|
||||||
|
|
||||||
byte[] getRawMessage(Message m);
|
byte[] getRawMessage(Message m);
|
||||||
|
|
||||||
|
MessageId getMessageId(GroupId g, long timestamp, TreeHash rootHash);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ public class MessageId extends UniqueId {
|
|||||||
public static final String BLOCK_LABEL =
|
public static final String BLOCK_LABEL =
|
||||||
"org.briarproject.bramble/MESSAGE_BLOCK";
|
"org.briarproject.bramble/MESSAGE_BLOCK";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for hashing two tree hashes to produce a parent.
|
||||||
|
*/
|
||||||
|
public static final String TREE_LABEL =
|
||||||
|
"org.briarproject.bramble/MESSAGE_TREE";
|
||||||
|
|
||||||
public MessageId(byte[] id) {
|
public MessageId(byte[] id) {
|
||||||
super(id);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,4 +35,9 @@ public interface SyncConstants {
|
|||||||
* The maximum number of message IDs in an ack, offer or request record.
|
* The maximum number of message IDs in an ack, offer or request record.
|
||||||
*/
|
*/
|
||||||
int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_BYTES / UniqueId.LENGTH;
|
int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_BYTES / UniqueId.LENGTH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum length of a message block in bytes.
|
||||||
|
*/
|
||||||
|
int MAX_BLOCK_LENGTH = 32 * 2014; // 32 KiB
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.briarproject.bramble.api.sync.tree;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public class LeafNode extends TreeNode {
|
||||||
|
|
||||||
|
public LeafNode(TreeHash hash, int blockNumber) {
|
||||||
|
super(hash, 0, blockNumber, blockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TreeNode getLeftChild() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TreeNode getRightChild() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package org.briarproject.bramble.api.sync.tree;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public class ParentNode extends TreeNode {
|
||||||
|
|
||||||
|
private final TreeNode left, right;
|
||||||
|
|
||||||
|
public ParentNode(TreeHash hash, TreeNode left, TreeNode right) {
|
||||||
|
super(hash, Math.max(left.getHeight(), right.getHeight()) + 1,
|
||||||
|
left.getFirstBlockNumber(), right.getLastBlockNumber());
|
||||||
|
this.left = left;
|
||||||
|
this.right = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TreeNode getLeftChild() {
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TreeNode getRightChild() {
|
||||||
|
return right;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.briarproject.bramble.api.sync.tree;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.io.BlockSink;
|
||||||
|
import org.briarproject.bramble.api.io.HashingId;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface StreamHasher {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the given input stream, divides the data into blocks, stores
|
||||||
|
* the blocks and the resulting hash tree using the given block sink and
|
||||||
|
* temporary ID, and returns the hash tree.
|
||||||
|
*/
|
||||||
|
TreeNode hash(InputStream in, BlockSink sink, HashingId h)
|
||||||
|
throws IOException, DbException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package org.briarproject.bramble.api.sync.tree;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type-safe wrapper for a byte array that uniquely identifies a sequence of
|
||||||
|
* one or more message blocks.
|
||||||
|
*/
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
public class TreeHash extends UniqueId {
|
||||||
|
|
||||||
|
public TreeHash(byte[] id) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o instanceof TreeHash && super.equals(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.briarproject.bramble.api.sync.tree;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface TreeHasher {
|
||||||
|
|
||||||
|
LeafNode hashBlock(int blockNumber, byte[] data);
|
||||||
|
|
||||||
|
ParentNode mergeTrees(TreeNode left, TreeNode right);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package org.briarproject.bramble.api.sync.tree;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public abstract class TreeNode {
|
||||||
|
|
||||||
|
private final TreeHash hash;
|
||||||
|
private final int height, firstBlockNumber, lastBlockNumber;
|
||||||
|
|
||||||
|
TreeNode(TreeHash hash, int height, int firstBlockNumber,
|
||||||
|
int lastBlockNumber) {
|
||||||
|
this.hash = hash;
|
||||||
|
this.height = height;
|
||||||
|
this.firstBlockNumber = firstBlockNumber;
|
||||||
|
this.lastBlockNumber = lastBlockNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeHash getHash() {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFirstBlockNumber() {
|
||||||
|
return firstBlockNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLastBlockNumber() {
|
||||||
|
return lastBlockNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract TreeNode getLeftChild();
|
||||||
|
|
||||||
|
public abstract TreeNode getRightChild();
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package org.briarproject.bramble.util;
|
package org.briarproject.bramble.util;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
|
|
||||||
public class ByteUtils {
|
public class ByteUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -14,26 +12,15 @@ public class ByteUtils {
|
|||||||
*/
|
*/
|
||||||
public static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1
|
public static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1
|
||||||
|
|
||||||
/**
|
/** The number of bytes needed to encode a 16-bit integer. */
|
||||||
* The number of bytes needed to encode a 16-bit integer.
|
|
||||||
*/
|
|
||||||
public static final int INT_16_BYTES = 2;
|
public static final int INT_16_BYTES = 2;
|
||||||
|
|
||||||
/**
|
/** The number of bytes needed to encode a 32-bit integer. */
|
||||||
* The number of bytes needed to encode a 32-bit integer.
|
|
||||||
*/
|
|
||||||
public static final int INT_32_BYTES = 4;
|
public static final int INT_32_BYTES = 4;
|
||||||
|
|
||||||
/**
|
/** The number of bytes needed to encode a 64-bit integer. */
|
||||||
* The number of bytes needed to encode a 64-bit integer.
|
|
||||||
*/
|
|
||||||
public static final int INT_64_BYTES = 8;
|
public static final int INT_64_BYTES = 8;
|
||||||
|
|
||||||
/**
|
|
||||||
* The maximum number of bytes needed to encode a variable-length integer.
|
|
||||||
*/
|
|
||||||
public static final int MAX_VARINT_BYTES = 9;
|
|
||||||
|
|
||||||
public static void writeUint16(int src, byte[] dest, int offset) {
|
public static void writeUint16(int src, byte[] dest, int offset) {
|
||||||
if (src < 0) throw new IllegalArgumentException();
|
if (src < 0) throw new IllegalArgumentException();
|
||||||
if (src > MAX_16_BIT_UNSIGNED) throw new IllegalArgumentException();
|
if (src > MAX_16_BIT_UNSIGNED) throw new IllegalArgumentException();
|
||||||
@@ -68,42 +55,6 @@ public class ByteUtils {
|
|||||||
dest[offset + 7] = (byte) (src & 0xFF);
|
dest[offset + 7] = (byte) (src & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of bytes needed to represent 'src' as a
|
|
||||||
* variable-length integer.
|
|
||||||
* <p>
|
|
||||||
* 'src' must not be negative.
|
|
||||||
*/
|
|
||||||
public static int getVarIntBytes(long src) {
|
|
||||||
if (src < 0) throw new IllegalArgumentException();
|
|
||||||
int len = 1;
|
|
||||||
while ((src >>= 7) > 0) len++;
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes 'src' to 'dest' as a variable-length integer, starting at
|
|
||||||
* 'offset', and returns the number of bytes written.
|
|
||||||
* <p>
|
|
||||||
* `src` must not be negative.
|
|
||||||
*/
|
|
||||||
public static int writeVarInt(long src, byte[] dest, int offset) {
|
|
||||||
if (src < 0) throw new IllegalArgumentException();
|
|
||||||
int len = getVarIntBytes(src);
|
|
||||||
if (dest.length < offset + len) throw new IllegalArgumentException();
|
|
||||||
// Work backwards from the end
|
|
||||||
int end = offset + len - 1;
|
|
||||||
for (int i = end; i >= offset; i--) {
|
|
||||||
// Encode 7 bits
|
|
||||||
dest[i] = (byte) (src & 0x7F);
|
|
||||||
// Raise the continuation flag, except for the last byte
|
|
||||||
if (i < end) dest[i] |= (byte) 0x80;
|
|
||||||
// Shift out the bits that were encoded
|
|
||||||
src >>= 7;
|
|
||||||
}
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int readUint16(byte[] src, int offset) {
|
public static int readUint16(byte[] src, int offset) {
|
||||||
if (src.length < offset + INT_16_BYTES)
|
if (src.length < offset + INT_16_BYTES)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
@@ -132,46 +83,14 @@ public class ByteUtils {
|
|||||||
| (src[offset + 7] & 0xFFL);
|
| (src[offset + 7] & 0xFFL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static int readUint(byte[] src, int bits) {
|
||||||
* Returns the length in bytes of a variable-length integer encoded in
|
if (src.length << 3 < bits) throw new IllegalArgumentException();
|
||||||
* 'src' starting at 'offset'.
|
int dest = 0;
|
||||||
*
|
for (int i = 0; i < bits; i++) {
|
||||||
* @throws FormatException if there is not a valid variable-length integer
|
if ((src[i >> 3] & 128 >> (i & 7)) != 0) dest |= 1 << bits - i - 1;
|
||||||
* at the specified position.
|
|
||||||
*/
|
|
||||||
public static int getVarIntBytes(byte[] src, int offset)
|
|
||||||
throws FormatException {
|
|
||||||
if (src.length < offset) throw new IllegalArgumentException();
|
|
||||||
for (int i = 0; i < MAX_VARINT_BYTES && offset + i < src.length; i++) {
|
|
||||||
// If the continuation flag is lowered, this is the last byte
|
|
||||||
if ((src[offset + i] & 0x80) == 0) return i + 1;
|
|
||||||
}
|
}
|
||||||
// We've read 9 bytes or reached the end of the input without finding
|
if (dest < 0) throw new AssertionError();
|
||||||
// the last byte
|
if (dest >= 1 << bits) throw new AssertionError();
|
||||||
throw new FormatException();
|
return dest;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a variable-length integer from 'src' starting at 'offset' and
|
|
||||||
* returns it.
|
|
||||||
*
|
|
||||||
* @throws FormatException if there is not a valid variable-length integer
|
|
||||||
* at the specified position.
|
|
||||||
*/
|
|
||||||
public static long readVarInt(byte[] src, int offset)
|
|
||||||
throws FormatException {
|
|
||||||
if (src.length < offset) throw new IllegalArgumentException();
|
|
||||||
long dest = 0;
|
|
||||||
for (int i = 0; i < MAX_VARINT_BYTES && offset + i < src.length; i++) {
|
|
||||||
// Decode 7 bits
|
|
||||||
dest |= src[offset + i] & 0x7F;
|
|
||||||
// If the continuation flag is lowered, this is the last byte
|
|
||||||
if ((src[offset + i] & 0x80) == 0) return dest;
|
|
||||||
// Make room for the next 7 bits
|
|
||||||
dest <<= 7;
|
|
||||||
}
|
|
||||||
// We've read 9 bytes or reached the end of the input without finding
|
|
||||||
// the last byte
|
|
||||||
throw new FormatException();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.briarproject.bramble.api.sync.GroupId;
|
|||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.TreeHash;
|
||||||
import org.briarproject.bramble.util.ByteUtils;
|
import org.briarproject.bramble.util.ByteUtils;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
@@ -39,13 +40,19 @@ class MessageFactoryImpl implements MessageFactory {
|
|||||||
if (body.length == 0) throw new IllegalArgumentException();
|
if (body.length == 0) throw new IllegalArgumentException();
|
||||||
if (body.length > MAX_MESSAGE_BODY_LENGTH)
|
if (body.length > MAX_MESSAGE_BODY_LENGTH)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
MessageId id = getMessageId(g, timestamp, body);
|
MessageId id = getMessageIdFromBody(g, timestamp, body);
|
||||||
return new Message(id, g, timestamp, body);
|
return new Message(id, g, timestamp, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MessageId getMessageId(GroupId g, long timestamp, byte[] body) {
|
private MessageId getMessageIdFromBody(GroupId g, long timestamp,
|
||||||
|
byte[] body) {
|
||||||
// There's only one block, so the root hash is the hash of the block
|
// There's only one block, so the root hash is the hash of the block
|
||||||
byte[] rootHash = crypto.hash(BLOCK_LABEL, FORMAT_VERSION_BYTES, body);
|
byte[] rootHash = crypto.hash(BLOCK_LABEL, FORMAT_VERSION_BYTES, body);
|
||||||
|
return getMessageIdFromRootHash(g, timestamp, rootHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MessageId getMessageIdFromRootHash(GroupId g, long timestamp,
|
||||||
|
byte[] rootHash) {
|
||||||
byte[] timeBytes = new byte[INT_64_BYTES];
|
byte[] timeBytes = new byte[INT_64_BYTES];
|
||||||
ByteUtils.writeUint64(timestamp, timeBytes, 0);
|
ByteUtils.writeUint64(timestamp, timeBytes, 0);
|
||||||
byte[] idHash = crypto.hash(ID_LABEL, FORMAT_VERSION_BYTES,
|
byte[] idHash = crypto.hash(ID_LABEL, FORMAT_VERSION_BYTES,
|
||||||
@@ -65,7 +72,7 @@ class MessageFactoryImpl implements MessageFactory {
|
|||||||
long timestamp = ByteUtils.readUint64(raw, UniqueId.LENGTH);
|
long timestamp = ByteUtils.readUint64(raw, UniqueId.LENGTH);
|
||||||
byte[] body = new byte[raw.length - MESSAGE_HEADER_LENGTH];
|
byte[] body = new byte[raw.length - MESSAGE_HEADER_LENGTH];
|
||||||
System.arraycopy(raw, MESSAGE_HEADER_LENGTH, body, 0, body.length);
|
System.arraycopy(raw, MESSAGE_HEADER_LENGTH, body, 0, body.length);
|
||||||
MessageId id = getMessageId(g, timestamp, body);
|
MessageId id = getMessageIdFromBody(g, timestamp, body);
|
||||||
return new Message(id, g, timestamp, body);
|
return new Message(id, g, timestamp, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,4 +85,10 @@ class MessageFactoryImpl implements MessageFactory {
|
|||||||
System.arraycopy(body, 0, raw, MESSAGE_HEADER_LENGTH, body.length);
|
System.arraycopy(body, 0, raw, MESSAGE_HEADER_LENGTH, body.length);
|
||||||
return raw;
|
return raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageId getMessageId(GroupId g, long timestamp,
|
||||||
|
TreeHash rootHash) {
|
||||||
|
return getMessageIdFromRootHash(g, timestamp, rootHash.getBytes());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.briarproject.bramble.sync.tree;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.LeafNode;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.TreeNode;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.NotThreadSafe;
|
||||||
|
|
||||||
|
@NotThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
interface HashTree {
|
||||||
|
|
||||||
|
void addLeaf(LeafNode leaf);
|
||||||
|
|
||||||
|
TreeNode getRoot();
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package org.briarproject.bramble.sync.tree;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.LeafNode;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.TreeHasher;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.TreeNode;
|
||||||
|
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class HashTreeImpl implements HashTree {
|
||||||
|
|
||||||
|
private final TreeHasher treeHasher;
|
||||||
|
private final Deque<TreeNode> nodes = new LinkedList<>();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
HashTreeImpl(TreeHasher treeHasher) {
|
||||||
|
this.treeHasher = treeHasher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addLeaf(LeafNode leaf) {
|
||||||
|
TreeNode add = leaf;
|
||||||
|
int height = leaf.getHeight();
|
||||||
|
TreeNode last = nodes.peekLast();
|
||||||
|
while (last != null && last.getHeight() == height) {
|
||||||
|
add = treeHasher.mergeTrees(last, add);
|
||||||
|
height = add.getHeight();
|
||||||
|
nodes.removeLast();
|
||||||
|
last = nodes.peekLast();
|
||||||
|
}
|
||||||
|
nodes.addLast(add);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TreeNode getRoot() {
|
||||||
|
TreeNode root = nodes.removeLast();
|
||||||
|
while (!nodes.isEmpty()) {
|
||||||
|
root = treeHasher.mergeTrees(nodes.removeLast(), root);
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package org.briarproject.bramble.sync.tree;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.io.BlockSink;
|
||||||
|
import org.briarproject.bramble.api.io.HashingId;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.StreamHasher;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.TreeHash;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.TreeHasher;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.TreeNode;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Provider;
|
||||||
|
|
||||||
|
import static java.util.Arrays.copyOfRange;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_BLOCK_LENGTH;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class StreamHasherImpl implements StreamHasher {
|
||||||
|
|
||||||
|
private final TreeHasher treeHasher;
|
||||||
|
private final Provider<HashTree> hashTreeProvider;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
StreamHasherImpl(TreeHasher treeHasher,
|
||||||
|
Provider<HashTree> hashTreeProvider) {
|
||||||
|
this.treeHasher = treeHasher;
|
||||||
|
this.hashTreeProvider = hashTreeProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TreeNode hash(InputStream in, BlockSink sink, HashingId h)
|
||||||
|
throws IOException, DbException {
|
||||||
|
HashTree tree = hashTreeProvider.get();
|
||||||
|
byte[] block = new byte[MAX_BLOCK_LENGTH];
|
||||||
|
int read;
|
||||||
|
for (int blockNumber = 0; (read = read(in, block)) > 0; blockNumber++) {
|
||||||
|
byte[] data;
|
||||||
|
if (read == block.length) data = block;
|
||||||
|
else data = copyOfRange(block, 0, read);
|
||||||
|
sink.putBlock(h, blockNumber, data);
|
||||||
|
tree.addLeaf(treeHasher.hashBlock(blockNumber, data));
|
||||||
|
}
|
||||||
|
TreeNode root = tree.getRoot();
|
||||||
|
setPaths(sink, h, root, new LinkedList<>());
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a block from the given input stream and returns the number of
|
||||||
|
* bytes read, or 0 if no bytes were read before reaching the end of the
|
||||||
|
* stream.
|
||||||
|
*/
|
||||||
|
private int read(InputStream in, byte[] block) throws IOException {
|
||||||
|
int offset = 0;
|
||||||
|
while (offset < block.length) {
|
||||||
|
int read = in.read(block, offset, block.length - offset);
|
||||||
|
if (read == -1) return offset;
|
||||||
|
offset += read;
|
||||||
|
}
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPaths(BlockSink sink, HashingId h, TreeNode node,
|
||||||
|
LinkedList<TreeHash> path) throws DbException {
|
||||||
|
if (node.getHeight() == 0) {
|
||||||
|
// We've reached a leaf - store the path
|
||||||
|
sink.setPath(h, node.getFirstBlockNumber(), path);
|
||||||
|
} else {
|
||||||
|
// Add the right child's hash to the path and traverse the left
|
||||||
|
path.addFirst(node.getRightChild().getHash());
|
||||||
|
setPaths(sink, h, node.getLeftChild(), path);
|
||||||
|
// Add the left child's hash to the path and traverse the right
|
||||||
|
path.removeFirst();
|
||||||
|
path.addFirst(node.getLeftChild().getHash());
|
||||||
|
setPaths(sink, h, node.getRightChild(), path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.briarproject.bramble.sync.tree;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.LeafNode;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.ParentNode;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.TreeHash;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.TreeHasher;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.TreeNode;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.sync.Message.FORMAT_VERSION;
|
||||||
|
import static org.briarproject.bramble.api.sync.MessageId.BLOCK_LABEL;
|
||||||
|
import static org.briarproject.bramble.api.sync.MessageId.TREE_LABEL;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class TreeHasherImpl implements TreeHasher {
|
||||||
|
|
||||||
|
private static final byte[] FORMAT_VERSION_BYTES =
|
||||||
|
new byte[] {FORMAT_VERSION};
|
||||||
|
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
TreeHasherImpl(CryptoComponent crypto) {
|
||||||
|
this.crypto = crypto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LeafNode hashBlock(int blockNumber, byte[] data) {
|
||||||
|
byte[] hash = crypto.hash(BLOCK_LABEL, FORMAT_VERSION_BYTES, data);
|
||||||
|
return new LeafNode(new TreeHash(hash), blockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParentNode mergeTrees(TreeNode left, TreeNode right) {
|
||||||
|
byte[] hash = crypto.hash(TREE_LABEL, FORMAT_VERSION_BYTES,
|
||||||
|
left.getHash().getBytes(), right.getHash().getBytes());
|
||||||
|
return new ParentNode(new TreeHash(hash), left, right);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.sync.tree.TreeHash;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||||
|
|
||||||
@@ -27,4 +29,10 @@ public class TestMessageFactory implements MessageFactory {
|
|||||||
System.arraycopy(body, 0, raw, MESSAGE_HEADER_LENGTH, body.length);
|
System.arraycopy(body, 0, raw, MESSAGE_HEADER_LENGTH, body.length);
|
||||||
return raw;
|
return raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageId getMessageId(GroupId g, long timestamp,
|
||||||
|
TreeHash rootHash) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,135 +1,118 @@
|
|||||||
package org.briarproject.bramble.util;
|
package org.briarproject.bramble.util;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import static java.util.Arrays.fill;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
|
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
|
||||||
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||||
import static org.briarproject.bramble.util.ByteUtils.MAX_VARINT_BYTES;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.getVarIntBytes;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.readUint16;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.readUint32;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.readUint64;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.readVarInt;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.writeUint16;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.writeUint32;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.writeUint64;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.writeVarInt;
|
|
||||||
import static org.briarproject.bramble.util.StringUtils.fromHexString;
|
|
||||||
import static org.briarproject.bramble.util.StringUtils.toHexString;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
|
||||||
public class ByteUtilsTest extends BrambleTestCase {
|
public class ByteUtilsTest extends BrambleTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadUint16() {
|
public void testReadUint16() {
|
||||||
byte[] b = fromHexString("00000000");
|
byte[] b = StringUtils.fromHexString("00000000");
|
||||||
assertEquals(0, readUint16(b, 1));
|
assertEquals(0, ByteUtils.readUint16(b, 1));
|
||||||
b = fromHexString("00000100");
|
b = StringUtils.fromHexString("00000100");
|
||||||
assertEquals(1, readUint16(b, 1));
|
assertEquals(1, ByteUtils.readUint16(b, 1));
|
||||||
b = fromHexString("007FFF00");
|
b = StringUtils.fromHexString("007FFF00");
|
||||||
assertEquals(Short.MAX_VALUE, readUint16(b, 1));
|
assertEquals(Short.MAX_VALUE, ByteUtils.readUint16(b, 1));
|
||||||
b = fromHexString("00FFFF00");
|
b = StringUtils.fromHexString("00FFFF00");
|
||||||
assertEquals(65535, readUint16(b, 1));
|
assertEquals(65535, ByteUtils.readUint16(b, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testReadUint16ValidatesArguments1() {
|
public void testReadUint16ValidatesArguments1() {
|
||||||
readUint16(new byte[1], 0);
|
ByteUtils.readUint16(new byte[1], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testReadUint16ValidatesArguments2() {
|
public void testReadUint16ValidatesArguments2() {
|
||||||
readUint16(new byte[2], 1);
|
ByteUtils.readUint16(new byte[2], 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadUint32() {
|
public void testReadUint32() {
|
||||||
byte[] b = fromHexString("000000000000");
|
byte[] b = StringUtils.fromHexString("000000000000");
|
||||||
assertEquals(0, readUint32(b, 1));
|
assertEquals(0, ByteUtils.readUint32(b, 1));
|
||||||
b = fromHexString("000000000100");
|
b = StringUtils.fromHexString("000000000100");
|
||||||
assertEquals(1, readUint32(b, 1));
|
assertEquals(1, ByteUtils.readUint32(b, 1));
|
||||||
b = fromHexString("007FFFFFFF00");
|
b = StringUtils.fromHexString("007FFFFFFF00");
|
||||||
assertEquals(Integer.MAX_VALUE, readUint32(b, 1));
|
assertEquals(Integer.MAX_VALUE, ByteUtils.readUint32(b, 1));
|
||||||
b = fromHexString("00FFFFFFFF00");
|
b = StringUtils.fromHexString("00FFFFFFFF00");
|
||||||
assertEquals(4294967295L, readUint32(b, 1));
|
assertEquals(4294967295L, ByteUtils.readUint32(b, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testReadUint32ValidatesArguments1() {
|
public void testReadUint32ValidatesArguments1() {
|
||||||
readUint32(new byte[3], 0);
|
ByteUtils.readUint32(new byte[3], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testReadUint32ValidatesArguments2() {
|
public void testReadUint32ValidatesArguments2() {
|
||||||
readUint32(new byte[4], 1);
|
ByteUtils.readUint32(new byte[4], 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadUint64() {
|
public void testReadUint64() {
|
||||||
byte[] b = fromHexString("00000000000000000000");
|
byte[] b = StringUtils.fromHexString("00000000000000000000");
|
||||||
assertEquals(0L, readUint64(b, 1));
|
assertEquals(0L, ByteUtils.readUint64(b, 1));
|
||||||
b = fromHexString("00000000000000000100");
|
b = StringUtils.fromHexString("00000000000000000100");
|
||||||
assertEquals(1L, readUint64(b, 1));
|
assertEquals(1L, ByteUtils.readUint64(b, 1));
|
||||||
b = fromHexString("007FFFFFFFFFFFFFFF00");
|
b = StringUtils.fromHexString("007FFFFFFFFFFFFFFF00");
|
||||||
assertEquals(Long.MAX_VALUE, readUint64(b, 1));
|
assertEquals(Long.MAX_VALUE, ByteUtils.readUint64(b, 1));
|
||||||
b = fromHexString("00800000000000000000");
|
b = StringUtils.fromHexString("00800000000000000000");
|
||||||
assertEquals(Long.MIN_VALUE, readUint64(b, 1));
|
assertEquals(Long.MIN_VALUE, ByteUtils.readUint64(b, 1));
|
||||||
b = fromHexString("00FFFFFFFFFFFFFFFF00");
|
b = StringUtils.fromHexString("00FFFFFFFFFFFFFFFF00");
|
||||||
assertEquals(-1L, readUint64(b, 1));
|
assertEquals(-1L, ByteUtils.readUint64(b, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testReadUint64ValidatesArguments1() {
|
public void testReadUint64ValidatesArguments1() {
|
||||||
readUint64(new byte[7], 0);
|
ByteUtils.readUint64(new byte[7], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testReadUint64ValidatesArguments2() {
|
public void testReadUint64ValidatesArguments2() {
|
||||||
readUint64(new byte[8], 1);
|
ByteUtils.readUint64(new byte[8], 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWriteUint16() {
|
public void testWriteUint16() {
|
||||||
byte[] b = new byte[4];
|
byte[] b = new byte[4];
|
||||||
writeUint16(0, b, 1);
|
ByteUtils.writeUint16(0, b, 1);
|
||||||
assertEquals("00000000", toHexString(b));
|
assertEquals("00000000", StringUtils.toHexString(b));
|
||||||
writeUint16(1, b, 1);
|
ByteUtils.writeUint16(1, b, 1);
|
||||||
assertEquals("00000100", toHexString(b));
|
assertEquals("00000100", StringUtils.toHexString(b));
|
||||||
writeUint16(Short.MAX_VALUE, b, 1);
|
ByteUtils.writeUint16(Short.MAX_VALUE, b, 1);
|
||||||
assertEquals("007FFF00", toHexString(b));
|
assertEquals("007FFF00", StringUtils.toHexString(b));
|
||||||
writeUint16(MAX_16_BIT_UNSIGNED, b, 1);
|
ByteUtils.writeUint16(MAX_16_BIT_UNSIGNED, b, 1);
|
||||||
assertEquals("00FFFF00", toHexString(b));
|
assertEquals("00FFFF00", StringUtils.toHexString(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWriteUint16ValidatesArguments() {
|
public void testWriteUint16ValidatesArguments() {
|
||||||
try {
|
try {
|
||||||
writeUint16(0, new byte[1], 0);
|
ByteUtils.writeUint16(0, new byte[1], 0);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
writeUint16(0, new byte[2], 1);
|
ByteUtils.writeUint16(0, new byte[2], 1);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
writeUint16(-1, new byte[2], 0);
|
ByteUtils.writeUint16(-1, new byte[2], 0);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
writeUint16(MAX_16_BIT_UNSIGNED + 1, new byte[2], 0);
|
ByteUtils.writeUint16(MAX_16_BIT_UNSIGNED + 1, new byte[2], 0);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
@@ -139,38 +122,38 @@ public class ByteUtilsTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testWriteUint32() {
|
public void testWriteUint32() {
|
||||||
byte[] b = new byte[6];
|
byte[] b = new byte[6];
|
||||||
writeUint32(0, b, 1);
|
ByteUtils.writeUint32(0, b, 1);
|
||||||
assertEquals("000000000000", toHexString(b));
|
assertEquals("000000000000", StringUtils.toHexString(b));
|
||||||
writeUint32(1, b, 1);
|
ByteUtils.writeUint32(1, b, 1);
|
||||||
assertEquals("000000000100", toHexString(b));
|
assertEquals("000000000100", StringUtils.toHexString(b));
|
||||||
writeUint32(Integer.MAX_VALUE, b, 1);
|
ByteUtils.writeUint32(Integer.MAX_VALUE, b, 1);
|
||||||
assertEquals("007FFFFFFF00", toHexString(b));
|
assertEquals("007FFFFFFF00", StringUtils.toHexString(b));
|
||||||
writeUint32(MAX_32_BIT_UNSIGNED, b, 1);
|
ByteUtils.writeUint32(MAX_32_BIT_UNSIGNED, b, 1);
|
||||||
assertEquals("00FFFFFFFF00", toHexString(b));
|
assertEquals("00FFFFFFFF00", StringUtils.toHexString(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWriteUint32ValidatesArguments() {
|
public void testWriteUint32ValidatesArguments() {
|
||||||
try {
|
try {
|
||||||
writeUint32(0, new byte[3], 0);
|
ByteUtils.writeUint32(0, new byte[3], 0);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
writeUint32(0, new byte[4], 1);
|
ByteUtils.writeUint32(0, new byte[4], 1);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
writeUint32(-1, new byte[4], 0);
|
ByteUtils.writeUint32(-1, new byte[4], 0);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
writeUint32(MAX_32_BIT_UNSIGNED + 1, new byte[4], 0);
|
ByteUtils.writeUint32(MAX_32_BIT_UNSIGNED + 1, new byte[4], 0);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
@@ -180,30 +163,30 @@ public class ByteUtilsTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testWriteUint64() {
|
public void testWriteUint64() {
|
||||||
byte[] b = new byte[10];
|
byte[] b = new byte[10];
|
||||||
writeUint64(0, b, 1);
|
ByteUtils.writeUint64(0, b, 1);
|
||||||
assertEquals("00000000000000000000", toHexString(b));
|
assertEquals("00000000000000000000", StringUtils.toHexString(b));
|
||||||
writeUint64(1, b, 1);
|
ByteUtils.writeUint64(1, b, 1);
|
||||||
assertEquals("00000000000000000100", toHexString(b));
|
assertEquals("00000000000000000100", StringUtils.toHexString(b));
|
||||||
writeUint64(Long.MAX_VALUE, b, 1);
|
ByteUtils.writeUint64(Long.MAX_VALUE, b, 1);
|
||||||
assertEquals("007FFFFFFFFFFFFFFF00", toHexString(b));
|
assertEquals("007FFFFFFFFFFFFFFF00", StringUtils.toHexString(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWriteUint64ValidatesArguments() {
|
public void testWriteUint64ValidatesArguments() {
|
||||||
try {
|
try {
|
||||||
writeUint64(0, new byte[7], 0);
|
ByteUtils.writeUint64(0, new byte[7], 0);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
writeUint64(0, new byte[8], 1);
|
ByteUtils.writeUint64(0, new byte[8], 1);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
writeUint64(-1, new byte[8], 0);
|
ByteUtils.writeUint64(-1, new byte[8], 0);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
@@ -211,170 +194,17 @@ public class ByteUtilsTest extends BrambleTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetVarIntBytesToWrite() {
|
public void testReadUint() {
|
||||||
assertEquals(1, getVarIntBytes(0));
|
byte[] b = new byte[1];
|
||||||
assertEquals(1, getVarIntBytes(0x7F)); // Max 7-bit int
|
b[0] = (byte) 128;
|
||||||
assertEquals(2, getVarIntBytes(0x7F + 1));
|
for (int i = 0; i < 8; i++) {
|
||||||
assertEquals(2, getVarIntBytes(0x3FFF)); // Max 14-bit int
|
assertEquals(1 << i, ByteUtils.readUint(b, i + 1));
|
||||||
assertEquals(3, getVarIntBytes(0x3FFF + 1));
|
}
|
||||||
assertEquals(3, getVarIntBytes(0x1FFFFF)); // Max 21-bit int
|
b = new byte[2];
|
||||||
assertEquals(4, getVarIntBytes(0x1FFFFF + 1));
|
for (int i = 0; i < 65535; i++) {
|
||||||
assertEquals(4, getVarIntBytes(0xFFFFFFF)); // Max 28-bit int
|
ByteUtils.writeUint16(i, b, 0);
|
||||||
assertEquals(5, getVarIntBytes(0xFFFFFFF + 1));
|
assertEquals(i, ByteUtils.readUint(b, 16));
|
||||||
assertEquals(5, getVarIntBytes(0x7FFFFFFFFL)); // Max 35-bit int
|
assertEquals(i >> 1, ByteUtils.readUint(b, 15));
|
||||||
assertEquals(6, getVarIntBytes(0x7FFFFFFFFL + 1));
|
|
||||||
assertEquals(6, getVarIntBytes(0x3FFFFFFFFFFL)); // Max 42-bit int
|
|
||||||
assertEquals(7, getVarIntBytes(0x3FFFFFFFFFFL + 1));
|
|
||||||
assertEquals(7, getVarIntBytes(0x1FFFFFFFFFFFFL)); // Max 49-bit int
|
|
||||||
assertEquals(8, getVarIntBytes(0x1FFFFFFFFFFFFL + 1));
|
|
||||||
assertEquals(8, getVarIntBytes(0xFFFFFFFFFFFFFFL)); // Max 56-bit int
|
|
||||||
assertEquals(9, getVarIntBytes(0xFFFFFFFFFFFFFFL + 1));
|
|
||||||
assertEquals(9, getVarIntBytes(0x7FFFFFFFFFFFFFFFL)); // Max 63-bit int
|
|
||||||
assertEquals(MAX_VARINT_BYTES, getVarIntBytes(Long.MAX_VALUE)); // Same
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWriteVarInt() {
|
|
||||||
testWriteVarInt(0, 1, "00");
|
|
||||||
testWriteVarInt(1, 1, "01");
|
|
||||||
testWriteVarInt(0x7F, 1, "7F"); // Max 7-bit int
|
|
||||||
testWriteVarInt(0x7F + 1, 2, "8100");
|
|
||||||
testWriteVarInt(0x3FFF, 2, "FF7F"); // Max 14-bit int
|
|
||||||
testWriteVarInt(0x3FFF + 1, 3, "818000");
|
|
||||||
testWriteVarInt(0x1FFFFF, 3, "FFFF7F"); // Max 21-bit int
|
|
||||||
testWriteVarInt(0x1FFFFF + 1, 4, "81808000");
|
|
||||||
testWriteVarInt(0xFFFFFFF, 4, "FFFFFF7F"); // Max 28-bit int
|
|
||||||
testWriteVarInt(0xFFFFFFF + 1, 5, "8180808000");
|
|
||||||
testWriteVarInt(0x7FFFFFFFFL, 5, "FFFFFFFF7F"); // Max 35-bit int
|
|
||||||
testWriteVarInt(0x7FFFFFFFFL + 1, 6, "818080808000");
|
|
||||||
testWriteVarInt(0x3FFFFFFFFFFL, 6, "FFFFFFFFFF7F"); // Max 42-bit int
|
|
||||||
testWriteVarInt(0x3FFFFFFFFFFL + 1, 7, "81808080808000");
|
|
||||||
testWriteVarInt(0x1FFFFFFFFFFFFL, 7, "FFFFFFFFFFFF7F"); // Max 49
|
|
||||||
testWriteVarInt(0x1FFFFFFFFFFFFL + 1, 8, "8180808080808000");
|
|
||||||
testWriteVarInt(0xFFFFFFFFFFFFFFL, 8, "FFFFFFFFFFFFFF7F"); // Max 56
|
|
||||||
testWriteVarInt(0xFFFFFFFFFFFFFFL + 1, 9, "818080808080808000");
|
|
||||||
testWriteVarInt(0x7FFFFFFFFFFFFFFFL, 9, "FFFFFFFFFFFFFFFF7F"); // Max 63
|
|
||||||
testWriteVarInt(Long.MAX_VALUE, MAX_VARINT_BYTES, "FFFFFFFFFFFFFFFF7F");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testWriteVarInt(long src, int len, String destHex) {
|
|
||||||
byte[] dest = new byte[9];
|
|
||||||
assertEquals(len, writeVarInt(src, dest, 0));
|
|
||||||
assertEquals(destHex, toHexString(dest).substring(0, len * 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetVarIntBytesToRead() throws FormatException {
|
|
||||||
testGetVarIntBytesToRead(1, "00", 0);
|
|
||||||
testGetVarIntBytesToRead(1, "01", 0);
|
|
||||||
testGetVarIntBytesToRead(1, "7F", 0); // Max 7-bit int
|
|
||||||
testGetVarIntBytesToRead(2, "8100", 0);
|
|
||||||
testGetVarIntBytesToRead(2, "FF7F", 0); // Max 14-bit int
|
|
||||||
testGetVarIntBytesToRead(3, "818000", 0);
|
|
||||||
testGetVarIntBytesToRead(3, "FFFF7F", 0); // Max 21-bit int
|
|
||||||
testGetVarIntBytesToRead(4, "81808000", 0);
|
|
||||||
testGetVarIntBytesToRead(4, "FFFFFF7F", 0); // Max 28-bit int
|
|
||||||
testGetVarIntBytesToRead(5, "8180808000", 0);
|
|
||||||
testGetVarIntBytesToRead(5, "FFFFFFFF7F", 0); // Max 35-bit int
|
|
||||||
testGetVarIntBytesToRead(6, "818080808000", 0);
|
|
||||||
testGetVarIntBytesToRead(6, "FFFFFFFFFF7F", 0); // Max 42-bit int
|
|
||||||
testGetVarIntBytesToRead(7, "81808080808000", 0);
|
|
||||||
testGetVarIntBytesToRead(7, "FFFFFFFFFFFF7F", 0); // Max 49-bit int
|
|
||||||
testGetVarIntBytesToRead(8, "8180808080808000", 0);
|
|
||||||
testGetVarIntBytesToRead(8, "FFFFFFFFFFFFFF7F", 0); // Max 56-bit int
|
|
||||||
testGetVarIntBytesToRead(9, "818080808080808000", 0);
|
|
||||||
testGetVarIntBytesToRead(9, "FFFFFFFFFFFFFFFF7F", 0); // Max 63-bit int
|
|
||||||
// Start at offset, ignore trailing data
|
|
||||||
testGetVarIntBytesToRead(1, "FF0000", 1);
|
|
||||||
testGetVarIntBytesToRead(9, "00FFFFFFFFFFFFFFFF7F00", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testGetVarIntBytesToRead(int len, String srcHex, int offset)
|
|
||||||
throws FormatException {
|
|
||||||
assertEquals(len, getVarIntBytes(fromHexString(srcHex), offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
|
||||||
public void testGetVarIntBytesToReadThrowsExceptionAtEndOfInput()
|
|
||||||
throws FormatException {
|
|
||||||
byte[] src = new byte[MAX_VARINT_BYTES - 1];
|
|
||||||
fill(src, (byte) 0xFF);
|
|
||||||
// Reaches end of input without finding lowered continuation flag
|
|
||||||
getVarIntBytes(src, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
|
||||||
public void testGetVarIntBytesToReadThrowsExceptionAfterNineBytes()
|
|
||||||
throws FormatException {
|
|
||||||
byte[] src = new byte[MAX_VARINT_BYTES];
|
|
||||||
fill(src, (byte) 0xFF);
|
|
||||||
// Reaches max length without finding lowered continuation flag
|
|
||||||
getVarIntBytes(src, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testReadVarInt() throws FormatException {
|
|
||||||
testReadVarInt(0, "00", 0);
|
|
||||||
testReadVarInt(1, "01", 0);
|
|
||||||
testReadVarInt(0x7F, "7F", 0); // Max 7-bit int
|
|
||||||
testReadVarInt(0x7F + 1, "8100", 0);
|
|
||||||
testReadVarInt(0x3FFF, "FF7F", 0); // Max 14-bit int
|
|
||||||
testReadVarInt(0x3FFF + 1, "818000", 0);
|
|
||||||
testReadVarInt(0x1FFFFF, "FFFF7F", 0); // Max 21-bit int
|
|
||||||
testReadVarInt(0x1FFFFF + 1, "81808000", 0);
|
|
||||||
testReadVarInt(0xFFFFFFF, "FFFFFF7F", 0); // Max 28-bit int
|
|
||||||
testReadVarInt(0xFFFFFFF + 1, "8180808000", 0);
|
|
||||||
testReadVarInt(0x7FFFFFFFFL, "FFFFFFFF7F", 0); // Max 35-bit int
|
|
||||||
testReadVarInt(0x7FFFFFFFFL + 1, "818080808000", 0);
|
|
||||||
testReadVarInt(0x3FFFFFFFFFFL, "FFFFFFFFFF7F", 0); // Max 42-bit int
|
|
||||||
testReadVarInt(0x3FFFFFFFFFFL + 1, "81808080808000", 0);
|
|
||||||
testReadVarInt(0x1FFFFFFFFFFFFL, "FFFFFFFFFFFF7F", 0); // Max 49-bit int
|
|
||||||
testReadVarInt(0x1FFFFFFFFFFFFL + 1, "8180808080808000", 0);
|
|
||||||
testReadVarInt(0xFFFFFFFFFFFFFFL, "FFFFFFFFFFFFFF7F", 0); // Max 56
|
|
||||||
testReadVarInt(0xFFFFFFFFFFFFFFL + 1, "818080808080808000", 0);
|
|
||||||
testReadVarInt(0x7FFFFFFFFFFFFFFFL, "FFFFFFFFFFFFFFFF7F", 0); // Max 63
|
|
||||||
testReadVarInt(Long.MAX_VALUE, "FFFFFFFFFFFFFFFF7F", 0);
|
|
||||||
// Start at offset, ignore trailing data
|
|
||||||
testReadVarInt(0, "FF0000", 1);
|
|
||||||
testReadVarInt(Long.MAX_VALUE, "00FFFFFFFFFFFFFFFF7F00", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testReadVarInt(long dest, String srcHex, int offset)
|
|
||||||
throws FormatException {
|
|
||||||
assertEquals(dest, readVarInt(fromHexString(srcHex), offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
|
||||||
public void testReadVarIntThrowsExceptionAtEndOfInput()
|
|
||||||
throws FormatException {
|
|
||||||
byte[] src = new byte[MAX_VARINT_BYTES - 1];
|
|
||||||
fill(src, (byte) 0xFF);
|
|
||||||
// Reaches end of input without finding lowered continuation flag
|
|
||||||
readVarInt(src, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
|
||||||
public void testReadVarIntThrowsExceptionAfterNineBytes()
|
|
||||||
throws FormatException {
|
|
||||||
byte[] src = new byte[MAX_VARINT_BYTES];
|
|
||||||
fill(src, (byte) 0xFF);
|
|
||||||
// Reaches max length without finding lowered continuation flag
|
|
||||||
readVarInt(src, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWriteAndReadVarInt() throws FormatException {
|
|
||||||
Random random = new Random();
|
|
||||||
int padding = 10;
|
|
||||||
byte[] buf = new byte[MAX_VARINT_BYTES + padding];
|
|
||||||
for (int i = 0; i < 1000; i++) {
|
|
||||||
long src = random.nextLong() & 0x7FFFFFFFFFFFFFFFL; // Non-negative
|
|
||||||
int offset = random.nextInt(padding);
|
|
||||||
int len = getVarIntBytes(src);
|
|
||||||
assertEquals(len, writeVarInt(src, buf, offset));
|
|
||||||
assertEquals(len, getVarIntBytes(buf, offset));
|
|
||||||
assertEquals(src, readVarInt(buf, offset));
|
|
||||||
fill(buf, (byte) 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user