/* * 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; } }