Packages

macula

0.37.7
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_names.erl
Raw

src/macula_names.erl

%%%-------------------------------------------------------------------
%%% @doc
%%% Shared naming utilities for hierarchical dot-separated names.
%%% Used by both pub/sub topics and RPC procedure names.
%%% Supports DNS-style reverse notation: org.domain.service.method
%%% @end
%%%-------------------------------------------------------------------
-module(macula_names).
%% API
-export([
validate/1,
validate/2,
normalize/1,
namespace/1,
segment_count/1,
%% Node identity
local_node_id/0
]).
%% Types
-type name() :: binary().
-type options() :: #{allow_wildcards => boolean()}.
-export_type([name/0, options/0]).
%%%===================================================================
%%% API Functions
%%%===================================================================
%% @doc Validate name syntax with default options (no wildcards).
-spec validate(name()) -> ok | {error, invalid_name}.
validate(Name) ->
validate(Name, #{allow_wildcards => false}).
%% @doc Validate name syntax with options.
%% Valid names:
%% - Non-empty
%% - Segments separated by dots
%% - Segments contain alphanumeric, underscore, hyphen
%% - Optionally allow wildcards (* and #) for patterns
%% - No leading or trailing dots
%% - No double dots
-spec validate(name(), options()) -> ok | {error, invalid_name}.
validate(<<>>, _Opts) ->
{error, invalid_name};
validate(Name, Opts) ->
%% Check for leading/trailing dots
case binary:first(Name) of
$. -> {error, invalid_name};
_ ->
case binary:last(Name) of
$. -> {error, invalid_name};
_ -> validate_segments(Name, Opts)
end
end.
%% @doc Normalize name (lowercase, trim, remove double dots).
-spec normalize(name()) -> name().
normalize(Name) ->
%% Trim whitespace
Trimmed = string:trim(Name),
%% Lowercase
Lower = string:lowercase(Trimmed),
%% Remove double dots
remove_double_dots(Lower).
%% @doc Extract namespace (first segment).
-spec namespace(name()) -> binary().
namespace(<<>>) ->
<<>>;
namespace(Name) ->
case binary:split(Name, <<".">>) of
[Namespace] -> Namespace;
[Namespace, _Rest] -> Namespace
end.
%% @doc Count number of segments in name.
-spec segment_count(name()) -> non_neg_integer().
segment_count(<<>>) ->
0;
segment_count(Name) ->
Segments = binary:split(Name, <<".">>, [global]),
length(Segments).
%% @doc Get the local node ID.
%% Returns the node ID stored in persistent_term, or generates one if not set.
%% This is used for NAT detection to identify the local node.
-spec local_node_id() -> binary().
local_node_id() ->
case persistent_term:get({macula, local_node_id}, undefined) of
undefined ->
%% Generate a node ID based on the Erlang node name
NodeBin = atom_to_binary(node(), utf8),
NodeId = crypto:hash(sha256, NodeBin),
persistent_term:put({macula, local_node_id}, NodeId),
NodeId;
NodeId ->
NodeId
end.
%%%===================================================================
%%% Internal Functions
%%%===================================================================
%% @doc Validate all segments contain valid characters.
validate_segments(Name, Opts) ->
Segments = binary:split(Name, <<".">>, [global]),
case lists:all(fun(Seg) -> is_valid_segment(Seg, Opts) end, Segments) of
true -> ok;
false -> {error, invalid_name}
end.
%% @doc Check if segment contains only valid characters.
is_valid_segment(<<>>, _Opts) ->
false; % Empty segment not allowed
is_valid_segment(Segment, Opts) ->
AllowWildcards = maps:get(allow_wildcards, Opts, false),
is_valid_segment_chars(binary_to_list(Segment), AllowWildcards).
is_valid_segment_chars([], _AllowWildcards) ->
true;
is_valid_segment_chars([C | Rest], AllowWildcards) when (C >= $a andalso C =< $z) orelse
(C >= $A andalso C =< $Z) orelse
(C >= $0 andalso C =< $9) orelse
C =:= $_ orelse
C =:= $- ->
is_valid_segment_chars(Rest, AllowWildcards);
is_valid_segment_chars([C | Rest], true) when C =:= $* orelse C =:= $# ->
%% Allow wildcards if option is set
is_valid_segment_chars(Rest, true);
is_valid_segment_chars(_, _) ->
false.
%% @doc Remove consecutive dots.
remove_double_dots(Binary) ->
case binary:replace(Binary, <<"..">>, <<".">>, [global]) of
Binary -> Binary; % No change, done
Modified -> remove_double_dots(Modified) % Keep removing
end.