Coverage Summary for Class: CompactEncoder (org.ethereum.util)
Class |
Class, %
|
Method, %
|
Line, %
|
CompactEncoder |
0%
(0/1)
|
0%
(0/6)
|
0%
(0/54)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2017 RSK Labs Ltd.
4 * (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>)
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 package org.ethereum.util;
21
22 import java.io.ByteArrayOutputStream;
23 import java.util.Arrays;
24 import java.util.HashMap;
25 import java.util.Map;
26
27 import static java.util.Arrays.copyOf;
28 import static java.util.Arrays.copyOfRange;
29 import static org.ethereum.util.ByteUtil.appendByte;
30 import static org.bouncycastle.util.Arrays.concatenate;
31 import static org.bouncycastle.util.encoders.Hex.encode;
32
33 /**
34 * Compact encoding of hex sequence with optional terminator
35 *
36 * The traditional compact way of encoding a hex string is to convert it into binary
37 * - that is, a string like 0f1248 would become three bytes 15, 18, 72. However,
38 * this approach has one slight problem: what if the length of the hex string is odd?
39 * In that case, there is no way to distinguish between, say, 0f1248 and f1248.
40 *
41 * Additionally, our application in the Merkle Patricia tree requires the additional feature
42 * that a hex string can also have a special "terminator symbol" at the end (denoted by the 'T').
43 * A terminator symbol can occur only once, and only at the end.
44 *
45 * An alternative way of thinking about this to not think of there being a terminator symbol,
46 * but instead treat bit specifying the existence of the terminator symbol as a bit specifying
47 * that the given node encodes a final node, where the value is an actual value, rather than
48 * the hash of yet another node.
49 *
50 * To solve both of these issues, we force the first nibble of the final byte-stream to encode
51 * two flags, specifying oddness of length (ignoring the 'T' symbol) and terminator status;
52 * these are placed, respectively, into the two lowest significant bits of the first nibble.
53 * In the case of an even-length hex string, we must introduce a second nibble (of value zero)
54 * to ensure the hex-string is even in length and thus is representable by a whole number of bytes.
55 *
56 * Examples:
57 * > [ 1, 2, 3, 4, 5 ]
58 * '\x11\x23\x45'
59 * > [ 0, 1, 2, 3, 4, 5 ]
60 * '\x00\x01\x23\x45'
61 * > [ 0, 15, 1, 12, 11, 8, T ]
62 * '\x20\x0f\x1c\xb8'
63 * > [ 15, 1, 12, 11, 8, T ]
64 * '\x3f\x1c\xb8'
65 */
66 public class CompactEncoder {
67
68 private static final byte TERMINATOR = 16;
69 private static final Map<Character, Byte> hexMap = new HashMap<>();
70
71 static {
72 hexMap.put('0', (byte) 0x0);
73 hexMap.put('1', (byte) 0x1);
74 hexMap.put('2', (byte) 0x2);
75 hexMap.put('3', (byte) 0x3);
76 hexMap.put('4', (byte) 0x4);
77 hexMap.put('5', (byte) 0x5);
78 hexMap.put('6', (byte) 0x6);
79 hexMap.put('7', (byte) 0x7);
80 hexMap.put('8', (byte) 0x8);
81 hexMap.put('9', (byte) 0x9);
82 hexMap.put('a', (byte) 0xa);
83 hexMap.put('b', (byte) 0xb);
84 hexMap.put('c', (byte) 0xc);
85 hexMap.put('d', (byte) 0xd);
86 hexMap.put('e', (byte) 0xe);
87 hexMap.put('f', (byte) 0xf);
88 }
89
90 /**
91 * Pack nibbles to binary
92 *
93 * @param nibbles sequence. may have a terminator
94 * @return hex-encoded byte array
95 */
96 public static byte[] packNibbles(byte[] nibbles) {
97 int terminator = 0;
98
99 if (nibbles[nibbles.length - 1] == TERMINATOR) {
100 terminator = 1;
101 nibbles = copyOf(nibbles, nibbles.length - 1);
102 }
103 int oddlen = nibbles.length % 2;
104 int flag = 2 * terminator + oddlen;
105 if (oddlen != 0) {
106 byte[] flags = new byte[]{(byte) flag};
107 nibbles = concatenate(flags, nibbles);
108 } else {
109 byte[] flags = new byte[]{(byte) flag, 0};
110 nibbles = concatenate(flags, nibbles);
111 }
112 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
113 for (int i = 0; i < nibbles.length; i += 2) {
114 buffer.write(16 * nibbles[i] + nibbles[i + 1]); //lgtm [java/index-out-of-bounds]
115 }
116 return buffer.toByteArray();
117 }
118
119 /**
120 * Unpack a binary string to its nibbles equivalent
121 *
122 * @param str of binary data
123 * @return array of nibbles in byte-format
124 */
125 public static byte[] unpackToNibbles(byte[] str) {
126 byte[] base = binToNibbles(str);
127 base = copyOf(base, base.length - 1);
128 if (base[0] >= 2) {
129 base = appendByte(base, TERMINATOR);
130 }
131 if (base[0] % 2 == 1) {
132 base = copyOfRange(base, 1, base.length);
133 } else {
134 base = copyOfRange(base, 2, base.length);
135 }
136 return base;
137 }
138
139 /**
140 * Transforms a binary array to hexadecimal format + terminator
141 *
142 * @param str byte[]
143 * @return array with each individual nibble adding a terminator at the end
144 */
145 public static byte[] binToNibbles(byte[] str) {
146
147 byte[] hexEncoded = encode(str);
148 byte[] hexEncodedTerminated = Arrays.copyOf(hexEncoded, hexEncoded.length + 1);
149
150 for (int i = 0; i < hexEncoded.length; ++i){
151 byte b = hexEncodedTerminated[i];
152 hexEncodedTerminated[i] = hexMap.get((char) b);
153 }
154
155 hexEncodedTerminated[hexEncodedTerminated.length - 1] = TERMINATOR;
156 return hexEncodedTerminated;
157 }
158
159
160 public static byte[] binToNibblesNoTerminator(byte[] str) {
161
162 byte[] hexEncoded = encode(str);
163
164 for (int i = 0; i < hexEncoded.length; ++i){
165 byte b = hexEncoded[i];
166 hexEncoded[i] = hexMap.get((char) b);
167 }
168
169 return hexEncoded;
170 }
171 }