blob: f606b1f89604c4bb11afe05eeeaf49ea76c3df74 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.apache.tuweni.trie;
import static com.google.common.base.Preconditions.checkArgument;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.MutableBytes;
/**
* Compact (Hex-prefix) encoding and decoding.
*
* <p>
* An implementation of <a href=
* "https://github.com/ethereum/wiki/wiki/Patricia-Tree#specification-compact-encoding-of-hex-sequence-with-optional-terminator">Compact
* (Hex-prefix) encoding</a>.
*/
public final class CompactEncoding {
private CompactEncoding() {}
public static final byte LEAF_TERMINATOR = 0x10;
/**
* Calculate a RADIX-16 path for a given byte sequence.
*
* @param bytes The byte sequence to calculate the path for.
* @return The Radix-16 path.
*/
public static Bytes bytesToPath(Bytes bytes) {
MutableBytes path = MutableBytes.create(bytes.size() * 2 + 1);
int j = 0;
for (int i = 0; i < bytes.size(); i += 1, j += 2) {
byte b = bytes.get(i);
path.set(j, (byte) ((b >>> 4) & 0x0f));
path.set(j + 1, (byte) (b & 0x0f));
}
path.set(j, LEAF_TERMINATOR);
return path;
}
/**
* Encode a Radix-16 path.
*
* @param path A Radix-16 path.
* @return A compact-encoded path.
*/
public static Bytes encode(Bytes path) {
int size = path.size();
boolean isLeaf = size > 0 && path.get(size - 1) == LEAF_TERMINATOR;
if (isLeaf) {
size = size - 1;
}
MutableBytes encoded = MutableBytes.create((size + 2) / 2);
int i = 0;
int j = 0;
if (size % 2 == 1) {
// add first nibble to magic
byte high = (byte) (isLeaf ? 0x03 : 0x01);
byte low = path.get(i++);
if ((low & 0xf0) != 0) {
throw new IllegalArgumentException("Invalid path: contains elements larger than a nibble");
}
encoded.set(j++, (byte) (high << 4 | low));
} else {
byte high = (byte) (isLeaf ? 0x02 : 0x00);
encoded.set(j++, (byte) (high << 4));
}
while (i < size) {
byte high = path.get(i++);
byte low = path.get(i++);
if ((high & 0xf0) != 0 || (low & 0xf0) != 0) {
throw new IllegalArgumentException("Invalid path: contains elements larger than a nibble");
}
encoded.set(j++, (byte) (high << 4 | low));
}
return encoded;
}
/**
* Decode a compact-encoded path to Radix-16.
*
* @param encoded A compact-encoded path.
* @return A Radix-16 path.
*/
public static Bytes decode(Bytes encoded) {
int size = encoded.size();
checkArgument(size > 0);
byte magic = encoded.get(0);
checkArgument((magic & 0xc0) == 0, "Invalid compact encoding");
boolean isLeaf = (magic & 0x20) != 0;
int pathLength = ((size - 1) * 2) + (isLeaf ? 1 : 0);
MutableBytes path;
int i = 0;
if ((magic & 0x10) != 0) {
// need to use lower nibble of magic
path = MutableBytes.create(pathLength + 1);
path.set(i++, (byte) (magic & 0x0f));
} else {
path = MutableBytes.create(pathLength);
}
for (int j = 1; j < size; j++) {
byte b = encoded.get(j);
path.set(i++, (byte) ((b >>> 4) & 0x0f));
path.set(i++, (byte) (b & 0x0f));
}
if (isLeaf) {
path.set(i, LEAF_TERMINATOR);
}
return path;
}
}