# Copyright 2018 Faraz Haider. All Rights Reserved. # # Licensed 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. defmodule Bargad.Utils do @moduledoc """ Utility functions required by `Bargad.Merkle`, `Bargad.Log`, `Bargad.Map`. """ @type tree :: Bargad.Types.tree() @type tree_node :: Bargad.Types.tree_node() @type tree_type :: Bargad.Types.tree_type() @type backend :: Bargad.Types.backend() @type hash_algorithm :: Bargad.Types.hash_algorithm() @type hash :: Bargad.Types.hash() @doc """ Generates a unique TreeId for every tree. This TreeId is used by `Storage` and `Bargad.TreeStorage` for persisting `t:tree/0` and `t:tree_node/0` """ def generate_tree_id() do UUID.uuid4(:hex) # :rand.uniform(100000) end @doc """ Helper function to convert a Tuple List into a map. This is required as `Bargad.Trees` stores a map as a `List` of `Tuple`. """ @spec tuple_list_to_map(tuple) :: map def tuple_list_to_map(tpl) do Enum.into(tpl, %{}) end @doc """ Creates a new tree of type `t:tree/0`. Called by `Bargad.Merkle.new/4` when a new `Bargad.Map` or `Bargad.Log` has to be created. """ @spec make_tree(tree_type, binary, hash_algorithm, backend) :: tree def make_tree(tree_type, tree_name, hash_function, backend) do Bargad.Trees.Tree.new( treeId: generate_tree_id(), treeName: tree_name, treeType: tree_type, hashFunction: hash_function, root: nil, backend: backend ) end @doc """ Creates a new node in the tree of type `t:tree_node/0`. """ def make_node(tree, hash, children, size, metadata) do Bargad.Nodes.Node.new( treeId: tree.treeId, hash: hash, children: children, size: size, metadata: metadata ) end @doc """ Creates an inner node in the tree of type `t:tree_node/0`. Creates a new node with its children as `left` and `right`. """ def make_node(tree, left, right) do # Salting of non leaf nodes is not required Bargad.Utils.make_node( tree, Bargad.Utils.make_hash(tree, left.hash <> right.hash), [left.hash, right.hash], left.size + right.size, nil ) end def make_map_node( tree, left = %Bargad.Nodes.Node{treeId: _, hash: _, children: _, metadata: _, key: _, size: _}, right ) do Bargad.Nodes.Node.new( treeId: tree.treeId, hash: Bargad.Utils.make_hash(tree, left.hash <> right.hash), children: [left.hash, right.hash], size: left.size + right.size, key: max(left.key, right.key) ) end def make_map_node(tree, key, value) do # salt the node with the key to prevent storage collisions # eg. if two keys have the same values, their hashes would be the same and as the nodes are being # indexed by their keys, storage would collide. # This scheme would prevent against preimage attacks as well Bargad.Nodes.Node.new( treeId: tree.treeId, hash: Bargad.Utils.make_hash(tree, Bargad.Utils.salt_node(key, value)), children: [], size: 1, metadata: value, key: key ) end def salt_node(k, v) do k <> v end @doc """ Hashes the binary data supplied based on the hash algorithm `t:hash_algorithm/0` specified in `t:tree`. """ def make_hash(tree, data) do :crypto.hash(tree.hashFunction, data) end @doc false def closest_pow_2(n) do p = :math.log2(n) case :math.ceil(p) - p do 0.0 -> trunc(:math.pow(2, p - 1)) _ -> trunc(:math.pow(2, trunc(p))) end end @doc """ Encodes `t:tree_node/0` into a `binary` using `exprotobuf`. """ def encode_node(node) do Bargad.Nodes.Node.encode(node) end @doc """ Decodes a `binary` into a `t:tree_node/0` using `exprotobuf`. """ def decode_node(node) do Bargad.Nodes.Node.decode(node) end @doc """ Encodes `t:tree/0` into a `binary` using `exprotobuf`. """ def encode_tree(tree) do Bargad.Trees.Tree.encode(tree) end @doc """ Decodes a `binary` into a `t:tree/0` using `exprotobuf`. """ def decode_tree(tree) do Bargad.Trees.Tree.decode(tree) end @doc """ Utility function for persisting a tree node. Calls `Storage.set_node/3`. """ def set_node(tree, key, value) do Storage.set_node(tree.backend, key, encode_node(value)) end @doc """ Utility function for retrieving a tree node. Calls `Storage.get_node/2`. """ def get_node(tree, key) do decode_node(Storage.get_node(tree.backend, key)) end def delete_node(tree, key) do Storage.delete_node(tree.backend, key) end @doc """ Utility function to retrieve the backend module from `backend`. """ def get_backend_module(backend) do backend = Bargad.Utils.tuple_list_to_map(backend) String.to_existing_atom("Elixir." <> backend["module"]) end end