Packages

macula

0.45.3
7.1.0 7.0.0 6.0.0 5.2.2 5.2.1 5.2.0 5.1.0 5.0.0 4.8.0 4.7.1 4.7.0 4.6.0 4.5.0 4.4.10 4.4.9 4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.1 4.3.0 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.1 4.1.0 4.0.0 3.16.0 3.15.3 3.15.2 3.15.1 3.14.0 3.13.0 3.12.1 3.12.0 3.11.1 3.11.0 3.10.3 3.10.2 3.10.1 3.9.0 3.8.0 3.7.0 3.5.0 3.4.0 3.3.0 3.2.0 3.1.0 3.0.0 2.1.1 2.1.0 2.0.0 1.5.2 1.5.1 1.4.30 1.4.29 1.4.28 1.4.27 1.4.26 1.4.25 1.4.24 1.4.23 1.4.22 1.4.21 1.4.20 1.4.19 1.4.18 1.4.17 1.4.16 1.4.15 1.4.14 1.4.13 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.1 1.3.0 1.2.0 1.1.0 1.0.10 1.0.9 1.0.8 1.0.7 1.0.6 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 1.0.0 0.48.6 0.48.5 0.48.4 0.48.3 0.48.2 0.48.1 0.48.0 0.47.1 0.47.0 0.46.3 0.46.1 0.46.0 0.45.3 0.45.2 0.45.1 0.45.0 0.44.2 0.44.1 0.44.0 0.43.3 0.43.2 0.43.1 0.43.0 0.42.9 0.42.8 0.42.7 0.42.6 0.42.5 0.42.4 0.42.3 0.42.2 0.42.1 0.42.0 0.41.1 0.41.0 0.40.1 0.40.0 0.39.9 0.39.8 0.39.7 0.39.6 0.39.5 0.39.4 0.39.3 0.39.2 0.39.1 0.39.0 0.38.8 0.38.7 0.38.6 0.38.5 0.38.4 0.38.3 0.38.2 0.38.1 0.38.0 0.37.7 0.37.6 0.37.5 0.37.4 0.37.3 0.37.2 0.37.1 0.37.0 0.36.6 0.36.5 0.36.4 0.36.3 0.36.2 0.36.1 0.36.0 0.35.4 0.35.3 0.35.2 0.35.1 0.35.0 0.34.1 0.34.0 0.33.1 0.33.0 0.32.5 0.32.4 0.32.3 0.32.2 0.32.1 0.32.0 0.31.9 0.31.8 0.31.7 0.31.6 0.31.5 0.31.4 0.31.3 0.31.2 0.31.1 0.31.0 0.30.10 0.30.9 0.30.8 0.30.7 0.30.6 0.30.5 0.30.4 0.30.3 0.30.2 0.30.1 0.30.0 0.29.0 0.28.3 0.28.2 0.28.1 0.28.0 0.27.1 0.27.0 0.26.1 0.26.0 0.25.6 0.25.5 0.25.4 0.25.3 0.25.2 0.25.1 0.25.0 0.24.6 0.24.5 0.24.4 0.24.3 0.24.2 0.24.1 0.24.0 0.23.3 0.23.2 0.23.1 0.23.0 0.22.12 0.22.11 0.22.10 0.22.9 0.22.8 0.22.7 0.22.6 0.22.5 0.22.4 0.22.3 0.22.2 0.22.1 0.22.0 0.21.7 0.21.6 0.21.5 0.21.4 0.21.2 0.21.1 0.21.0 0.20.25 0.20.24 0.20.23 0.20.22 0.20.21 0.20.20 0.20.19 0.20.18 0.20.17 0.20.16 0.20.15 0.20.14 0.20.13 0.20.12 0.20.11 0.20.10 0.20.9 0.20.8 0.20.7 0.20.6 0.20.5 0.20.3 0.20.2 0.20.1 0.20.0 0.19.2 0.19.1 0.19.0 0.18.1 0.18.0 0.17.4 0.17.3 0.17.2 0.17.1 0.17.0 0.16.6 0.16.5 0.16.4 0.16.3 0.16.2 0.16.1 0.16.0 0.15.1 0.15.0 0.14.3 0.14.2 0.14.1 0.14.0 0.12.6 0.12.5 0.12.3 0.11.3 0.10.2 0.10.1 0.10.0 0.9.2 0.9.1 0.9.0 0.8.25 0.8.24 0.8.23 0.8.22 0.8.21 0.8.20 0.8.19 0.8.18 0.8.17 0.8.16 0.8.15 0.8.14 0.8.13 0.8.12 0.8.11 0.8.10 0.8.9 0.8.8 0.8.7 0.8.6 0.8.5 0.8.4 0.8.3 0.8.2 0.8.1 0.8.0 0.7.30 0.7.29 0.7.28 0.7.27 0.7.26 0.7.25 0.7.24 0.7.23 0.7.22 0.7.21 0.7.20 0.7.19 0.7.18 0.7.17 0.7.16 0.7.15 0.7.14 0.7.13 0.7.12 0.7.11 0.7.10 0.7.9 0.7.8 0.7.7 0.7.6 0.7.5 0.7.4 0.7.3 0.7.2 0.7.1 0.7.0 0.6.7 0.6.6 0.6.5 0.6.4 0.6.3 0.6.2 0.6.1 0.6.0 0.5.0 0.4.4 0.4.3 0.4.2 0.4.1 0.4.0 0.3.4 0.3.3 0.3.2 0.3.1

Macula HTTP/3 Mesh SDK — connect, subscribe, publish, call, advertise

Current section

Files

Jump to
macula src macula_routing_system macula_routing_nodeid.erl
Raw

src/macula_routing_system/macula_routing_nodeid.erl

%%%-------------------------------------------------------------------
%%% @doc
%%% Node ID utilities for Kademlia DHT.
%%% 256-bit node identifiers with XOR distance metric.
%%% @end
%%%-------------------------------------------------------------------
-module(macula_routing_nodeid).
%% API
-export([
generate/0,
from_binary/1,
normalize/1,
distance/2,
leading_zeros/1,
closer_to/3,
compare/3,
bucket_index/2,
to_hex/1,
from_hex/1
]).
%% Types
-type node_id() :: binary(). % 32 bytes (256 bits)
-export_type([node_id/0]).
%%%===================================================================
%%% API Functions
%%%===================================================================
%% @doc Generate a random 256-bit node ID.
-spec generate() -> node_id().
generate() ->
crypto:strong_rand_bytes(32).
%% @doc Create node ID from binary (validates size).
-spec from_binary(binary()) -> {ok, node_id()} | {error, invalid_size}.
from_binary(Binary) when byte_size(Binary) =:= 32 ->
{ok, Binary};
from_binary(_) ->
{error, invalid_size}.
%% @doc Normalize any binary to a 32-byte node ID.
%% If already 32 bytes, returns as-is. Otherwise, hashes with SHA-256.
-spec normalize(binary()) -> node_id().
normalize(Binary) when byte_size(Binary) =:= 32 ->
Binary;
normalize(Binary) when is_binary(Binary) ->
crypto:hash(sha256, Binary).
%% @doc Calculate XOR distance between two node IDs.
%% Normalizes inputs to 32 bytes if needed.
-spec distance(binary(), binary()) -> binary().
distance(NodeId1, NodeId2) ->
crypto:exor(normalize(NodeId1), normalize(NodeId2)).
%% @doc Count leading zero bits in binary.
-spec leading_zeros(binary()) -> 0..256.
leading_zeros(Binary) ->
leading_zeros(Binary, 0).
leading_zeros(<<>>, Acc) ->
Acc;
leading_zeros(<<0:8, Rest/binary>>, Acc) ->
leading_zeros(Rest, Acc + 8);
leading_zeros(<<Byte:8, _/binary>>, Acc) ->
%% Count leading zeros in this byte
Acc + leading_zeros_byte(Byte).
%% @doc Count leading zeros in a single byte.
-spec leading_zeros_byte(0..255) -> 0..8.
leading_zeros_byte(0) -> 8;
leading_zeros_byte(Byte) when Byte >= 128 -> 0; % 1xxxxxxx
leading_zeros_byte(Byte) when Byte >= 64 -> 1; % 01xxxxxx
leading_zeros_byte(Byte) when Byte >= 32 -> 2; % 001xxxxx
leading_zeros_byte(Byte) when Byte >= 16 -> 3; % 0001xxxx
leading_zeros_byte(Byte) when Byte >= 8 -> 4; % 00001xxx
leading_zeros_byte(Byte) when Byte >= 4 -> 5; % 000001xx
leading_zeros_byte(Byte) when Byte >= 2 -> 6; % 0000001x
leading_zeros_byte(1) -> 7; % 00000001
leading_zeros_byte(0) -> 8. % 00000000
%% @doc Check if NodeA is closer to Target than NodeB.
%% Normalizes inputs to 32 bytes if needed.
-spec closer_to(binary(), binary(), binary()) -> boolean().
closer_to(Target, NodeA, NodeB) ->
DistA = distance(Target, NodeA),
DistB = distance(Target, NodeB),
DistA < DistB.
%% @doc Compare distances of NodeA and NodeB to Target.
%% Returns: less (A closer), equal (same distance), greater (B closer).
%% Normalizes inputs to 32 bytes if needed.
-spec compare(binary(), binary(), binary()) -> less | equal | greater.
compare(Target, NodeA, NodeB) ->
DistA = distance(Target, NodeA),
DistB = distance(Target, NodeB),
compare_distances(DistA, DistB).
compare_distances(DistA, DistB) when DistA < DistB -> less;
compare_distances(DistA, DistB) when DistA > DistB -> greater;
compare_distances(_DistA, _DistB) -> equal.
%% @doc Calculate bucket index for a node relative to local node.
%% Returns leading zero count of XOR distance (0..255).
%% Special case: distance 0 (same node) returns 256.
%% Normalizes inputs to 32 bytes if needed.
-spec bucket_index(binary(), binary()) -> 0..256.
bucket_index(LocalNodeId, TargetNodeId) ->
Distance = distance(LocalNodeId, TargetNodeId),
distance_to_bucket_index(Distance).
%% Same node (distance 0) gets special bucket index 256
distance_to_bucket_index(<<0:256>>) -> 256;
distance_to_bucket_index(Distance) -> leading_zeros(Distance).
%% @doc Convert node ID to hex string.
-spec to_hex(node_id()) -> string().
to_hex(NodeId) ->
binary:bin_to_list(binary:encode_hex(NodeId, lowercase)).
%% @doc Parse node ID from hex string.
%% Crashes on invalid hex or wrong length - exposes bugs in validation logic.
-spec from_hex(string()) -> node_id().
from_hex(HexString) when length(HexString) =:= 64 ->
%% Decode hex (let it crash on invalid characters)
binary:decode_hex(list_to_binary(HexString)).