2 * ============LICENSE_START====================================
3 * DCAEGEN2-SERVICES-SDK
4 * =========================================================
5 * Copyright (C) 2019 Nokia. All rights reserved.
6 * =========================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=====================================
21 package org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.listener;
23 import io.vavr.Function1;
24 import io.vavr.collection.HashMap;
25 import io.vavr.collection.List;
26 import io.vavr.collection.Map;
27 import io.vavr.control.Option;
28 import java.security.MessageDigest;
29 import java.security.NoSuchAlgorithmException;
30 import java.util.Arrays;
31 import java.util.Objects;
32 import org.jetbrains.annotations.NotNull;
35 * An immutable <a href="https://en.wikipedia.org/wiki/Merkle_tree" target="_blank">Merkle Tree</a> implementation.
37 * Each node is labelled with a {@code String} label. A path of a node is defined as a list of labels from root to a
40 * @author <a href="mailto:piotr.jaszczyk@nokia.com">Piotr Jaszczyk</a>
43 public final class MerkleTree<V> {
45 private static final String DEFAULT_DIGEST_ALGORITHM = "SHA-256";
46 private final ValueSerializer<V> valueSerializer;
47 private final HashAlgorithm hashAlgorithm;
48 private final MTNode<V> root;
49 private final Function1<V, byte[]> hashForValue;
52 @NotNull ValueSerializer<V> valueSerializer,
53 @NotNull HashAlgorithm hashAlgorithm,
54 @NotNull MTNode<V> root) {
55 this.valueSerializer = Objects.requireNonNull(valueSerializer);
56 this.hashAlgorithm = Objects.requireNonNull(hashAlgorithm);
57 this.root = Objects.requireNonNull(root);
58 hashForValue = valueSerializer.andThen(hashAlgorithm);
62 * Create an empty tree with given serializer and using default digest algorithm as a hash function.
64 * @param serializer a way of serializing a value to array of bytes
65 * @param <V> type of values kept in a tree
68 public static @NotNull <V> MerkleTree<V> emptyWithDefaultDigest(@NotNull ValueSerializer<V> serializer) {
69 return emptyWithDigest(DEFAULT_DIGEST_ALGORITHM, serializer);
73 * Create an empty tree with given serializer and given digest algorithm used as a hash function.
75 * @param digestAlgorithmName name of a digest algorithm as used by {@link MessageDigest#getInstance(String)}
76 * @param serializer a way of serializing a value to array of bytes
77 * @param <V> type of values kept in a tree
80 public static @NotNull <V> MerkleTree<V> emptyWithDigest(
81 @NotNull String digestAlgorithmName,
82 @NotNull ValueSerializer<V> serializer) {
83 return emptyWithHashProvider(serializer, messages -> {
84 final MessageDigest messageDigest = messageDigest(digestAlgorithmName);
85 messages.forEach(messageDigest::update);
86 return messageDigest.digest();
91 * Create an empty tree with given hash function.
93 * @param serializer a function which serializes values to a byte array
94 * @param hashAlgorithm a function which calculates a hash of a serialized value
95 * @param <V> type of values kept in a tree
98 public static <V> MerkleTree<V> emptyWithHashProvider(ValueSerializer<V> serializer, HashAlgorithm hashAlgorithm) {
99 return new MerkleTree<>(serializer, hashAlgorithm, MTNode.empty(hashAlgorithm));
102 private static MessageDigest messageDigest(String digestAlgorithmName) {
104 return MessageDigest.getInstance(digestAlgorithmName);
105 } catch (NoSuchAlgorithmException e) {
106 throw new IllegalArgumentException("Unsupported hash algorithm " + digestAlgorithmName, e);
111 * Assigns a value to a given path.
113 * Overrides current value if already exists.
115 * @param value a value to assign
116 * @param path path of labels from root
117 * @return an updated tree instance or <code>this</code> if hashes are the same
119 public MerkleTree<V> add(V value, String... path) {
120 return add(List.of(path), value);
124 * Assigns a value to a given path.
126 * Overrides current value if already exists.
128 * @param path path of labels from root
129 * @param value a value to assign
130 * @return an updated tree instance or <code>this</code> if hashes are the same
132 public MerkleTree<V> add(List<String> path, V value) {
133 final MTNode<V> result = root.addChild(path, MTNode.leaf(hashAlgorithm, hashForValue.apply(value), value));
134 return Arrays.equals(result.hash(), root.hash())
136 : new MerkleTree<>(valueSerializer, hashAlgorithm, result);
141 * Gets a value assigned to a given path.
143 * @param path to search for
144 * @return Some(value) if path exists and contains a value, None otherwise
146 public Option<V> get(String... path) {
147 return get(List.of(path));
151 * Gets a value assigned to a given path.
153 * @param path to search for
154 * @return Some(value) if path exists and contains a value, None otherwise
156 public Option<V> get(List<String> path) {
157 return root.findNode(path).flatMap(MTNode::value);
161 * Checks if nodes under given path are the same in {@code this} and {@code other} tree.
163 * @param other a tree to compare with
164 * @param path a path to a subtree to compare
165 * @return true if hashes are the same, false otherwise
167 public boolean isSame(MerkleTree<V> other, String... path) {
168 return isSame(List.of(path), other);
172 * Checks if nodes under given path are the same in {@code this} and {@code other} tree.
174 * @param other a tree to compare with
175 * @param path a path to a subtree to compare
176 * @return true if hashes are the same, false otherwise
178 public boolean isSame(List<String> path, MerkleTree<V> other) {
179 final byte[] oldHash = other.hashOf(path);
180 final byte[] curHash = hashOf(path);
181 return Arrays.equals(oldHash, curHash);
185 * Returns a hash of a node under given path.
187 * @param path a path of a node to check
188 * @return a hash or empty array if node does not exist
190 public byte[] hashOf(List<String> path) {
193 .map(node -> node.hash().clone())
194 .getOrElse(() -> new byte[0]);
198 * Returns a hash of a node under given path.
200 * @param path a path of a node to check
201 * @return a hash or empty array if node does not exist
203 public byte[] hashOf(String... path) {
204 return hashOf(List.of(path));
208 * Returns a subtree with given node as a root.
210 * @param path a path of a node to be a subtree root
211 * @return Some(subtree) if path exists, None otherwise
213 public Option<MerkleTree<V>> subtree(List<String> path) {
214 return root.findNode(path).map(node -> new MerkleTree<>(valueSerializer, hashAlgorithm, node));
218 * Returns a subtree with given node as a root.
220 * @param path a path of a node to be a subtree root
221 * @return Some(subtree) if path exists, None otherwise
223 public Option<MerkleTree<V>> subtree(String... path) {
224 return subtree(List.of(path));
228 * Hash of a root node.
230 * @return a copy of root node's hash
232 public byte[] hash() {
233 return root.hash().clone();
237 public String toString() {
238 return root.toString();
242 public boolean equals(Object o) {
246 if (o == null || getClass() != o.getClass()) {
249 MerkleTree<?> that = (MerkleTree<?>) o;
250 return Objects.equals(root, that.root);
254 public int hashCode() {
255 return Objects.hash(root);
259 final class MTNode<V> {
261 private final byte[] hash;
262 private final Option<V> value;
263 private final Map<String, MTNode<V>> children;
264 private final HashAlgorithm hashAlgorithm;
266 static <V> MTNode<V> empty(HashAlgorithm hashAlgorithm) {
267 return new MTNode<>(hashAlgorithm, new byte[0], Option.none(), HashMap.empty());
270 static <V> MTNode<V> leaf(HashAlgorithm hashAlgorithm, byte[] hash, V value) {
271 return new MTNode<>(hashAlgorithm, hash, Option.of(value), HashMap.empty());
275 HashAlgorithm hashAlgorithm,
278 Map<String, MTNode<V>> children) {
279 this.hashAlgorithm = hashAlgorithm;
280 this.hash = hash.clone();
282 this.children = children;
285 MTNode<V> addChild(final List<String> path, final MTNode<V> child) {
286 if (path.isEmpty()) {
289 String label = path.head();
290 MTNode<V> newChild = children.get(label).fold(
291 () -> MTNode.<V>empty(hashAlgorithm).addChild(path.tail(), child),
292 node -> node.addChild(path.tail(), child)
294 return addChild(label, newChild);
302 Option<MTNode<V>> findNode(List<String> path) {
303 return path.headOption().fold(
304 () -> Option.of(this),
305 head -> children.get(head).flatMap(child -> child.findNode(path.tail()))
313 private MTNode<V> addChild(String label, MTNode<V> child) {
314 final Map<String, MTNode<V>> newChildren = children.put(label, child);
315 byte[] newHash = composeHashes(newChildren.iterator(this::hashForChild));
316 return Arrays.equals(newHash, hash) ? this : new MTNode<>(
324 private byte[] hashForChild(String label, MTNode<V> child) {
325 return composeHashes(List.of(label.getBytes(), child.hash()));
328 private byte[] composeHashes(Iterable<byte[]> hashes) {
329 return hashAlgorithm.apply(hashes);
333 public String toString() {
334 return "(\"" + value.map(Object::toString).getOrElse("") + "\" ["
335 + children.map(entry -> entry._1 + "=" + entry._2).mkString(", ")
340 public boolean equals(Object o) {
344 if (o == null || getClass() != o.getClass()) {
347 MTNode<?> mtNode = (MTNode<?>) o;
348 return Arrays.equals(hash, mtNode.hash);
352 public int hashCode() {
353 return Arrays.hashCode(hash);