Packages

macula

0.24.2
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_registry_system macula_registry_server.erl
Raw

src/macula_registry_system/macula_registry_server.erl

%%%-------------------------------------------------------------------
%%% @doc Macula Registry Server
%%%
%%% Main API server for package registry operations:
%%% - Package publishing with signature verification
%%% - Package fetching with integrity checks
%%% - Package queries and search
%%% - DHT integration for distributed discovery
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(macula_registry_server).
-behaviour(gen_server).
%% API
-export([start_link/1]).
-export([publish_package/5, publish_package/6]).
-export([fetch_package/1, fetch_package/2]).
-export([verify_package/4]).
-export([list_packages/0, search_packages/1]).
-export([get_package_info/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include_lib("kernel/include/logger.hrl").
-define(SERVER, ?MODULE).
-record(state, {
node_id :: binary(),
realm :: binary(),
store_pid :: pid() | undefined,
routing_pid :: pid() | undefined
}).
%%%===================================================================
%%% API functions
%%%===================================================================
%% @doc Start the registry server
-spec start_link(Config :: map()) -> {ok, pid()} | {error, term()}.
start_link(Config) ->
gen_server:start_link({local, ?SERVER}, ?MODULE, Config, []).
%% @doc Publish a package to the registry
-spec publish_package(Name :: binary(), Version :: binary(),
Manifest :: map(), BeamArchive :: binary(),
PrivateKey :: binary()) ->
{ok, #{package_name := binary(), version := binary(), checksum := binary()}} |
{error, term()}.
publish_package(Name, Version, Manifest, BeamArchive, PrivateKey) ->
gen_server:call(?SERVER, {publish, Name, Version, Manifest, BeamArchive, PrivateKey}, 30000).
%% @doc Publish a package with pre-computed signature
-spec publish_package(Name :: binary(), Version :: binary(),
Manifest :: map(), BeamArchive :: binary(),
Signature :: binary(), PublicKey :: binary()) ->
{ok, #{package_name := binary(), version := binary(), checksum := binary()}} |
{error, term()}.
publish_package(Name, Version, Manifest, BeamArchive, Signature, PublicKey) ->
gen_server:call(?SERVER, {publish_signed, Name, Version, Manifest, BeamArchive, Signature, PublicKey}, 30000).
%% @doc Fetch the latest version of a package
-spec fetch_package(Name :: binary()) ->
{ok, map()} | {error, not_found}.
fetch_package(Name) ->
gen_server:call(?SERVER, {fetch, Name, latest}).
%% @doc Fetch a specific version of a package
-spec fetch_package(Name :: binary(), Version :: binary()) ->
{ok, map()} | {error, not_found}.
fetch_package(Name, Version) ->
gen_server:call(?SERVER, {fetch, Name, Version}).
%% @doc Verify package signature
-spec verify_package(ManifestBin :: binary(), BeamArchive :: binary(),
Signature :: binary(), PublicKey :: binary()) ->
ok | {error, term()}.
verify_package(ManifestBin, BeamArchive, Signature, PublicKey) ->
macula_registry_verify:verify_package(ManifestBin, BeamArchive, Signature, PublicKey).
%% @doc List all packages in the registry
-spec list_packages() -> [map()].
list_packages() ->
gen_server:call(?SERVER, list_packages).
%% @doc Search packages by pattern
-spec search_packages(Pattern :: binary()) -> [map()].
search_packages(Pattern) ->
gen_server:call(?SERVER, {search, Pattern}).
%% @doc Get detailed info about a package
-spec get_package_info(Name :: binary()) ->
{ok, #{versions := [binary()], latest := binary()}} | {error, not_found}.
get_package_info(Name) ->
gen_server:call(?SERVER, {get_info, Name}).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%% @private
init(Config) ->
NodeId = maps:get(node_id, Config, generate_node_id()),
Realm = maps:get(realm, Config, <<"default">>),
%% Find store and routing PIDs after startup
erlang:send_after(100, self(), init_pids),
{ok, #state{
node_id = NodeId,
realm = Realm,
store_pid = undefined,
routing_pid = undefined
}}.
%% @private
handle_call({publish, Name, Version, Manifest, BeamArchive, PrivateKey}, _From, State) ->
Result = do_publish(Name, Version, Manifest, BeamArchive, PrivateKey, State),
{reply, Result, State};
handle_call({publish_signed, Name, Version, Manifest, BeamArchive, Signature, PublicKey}, _From, State) ->
Result = do_publish_signed(Name, Version, Manifest, BeamArchive, Signature, PublicKey, State),
{reply, Result, State};
handle_call({fetch, Name, Version}, _From, State) ->
Result = do_fetch(Name, Version, State),
{reply, Result, State};
handle_call(list_packages, _From, State) ->
Result = do_list_packages(State),
{reply, Result, State};
handle_call({search, Pattern}, _From, State) ->
Result = do_search(Pattern, State),
{reply, Result, State};
handle_call({get_info, Name}, _From, State) ->
Result = do_get_info(Name, State),
{reply, Result, State};
handle_call(_Request, _From, State) ->
{reply, {error, unknown_request}, State}.
%% @private
handle_cast(_Msg, State) ->
{noreply, State}.
%% @private
handle_info(init_pids, State) ->
StorePid = find_store_pid(),
RoutingPid = find_routing_pid(),
{noreply, State#state{store_pid = StorePid, routing_pid = RoutingPid}};
handle_info(_Info, State) ->
{noreply, State}.
%% @private
terminate(_Reason, _State) ->
ok.
%% @private
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
%% @private Publish with private key (compute signature)
do_publish(Name, Version, Manifest, BeamArchive, PrivateKey, State) ->
%% Validate manifest
case macula_registry_manifest:validate(Manifest) of
ok ->
%% Serialize manifest for signing
ManifestBin = macula_registry_manifest:to_binary(Manifest),
%% Sign the package
case macula_registry_verify:sign_package(ManifestBin, BeamArchive, PrivateKey) of
{ok, Signature} ->
%% Derive public key from private key
%% Note: In Ed25519, the private key contains the public key
PublicKey = extract_public_key(PrivateKey),
do_store_package(Name, Version, Manifest, BeamArchive, Signature, PublicKey, State);
{error, Reason} ->
{error, {signing_failed, Reason}}
end;
{error, Reason} ->
{error, {invalid_manifest, Reason}}
end.
%% @private Publish with pre-computed signature
do_publish_signed(Name, Version, Manifest, BeamArchive, Signature, PublicKey, State) ->
%% Validate manifest
case macula_registry_manifest:validate(Manifest) of
ok ->
%% Verify signature
ManifestBin = macula_registry_manifest:to_binary(Manifest),
case macula_registry_verify:verify_package(ManifestBin, BeamArchive, Signature, PublicKey) of
ok ->
do_store_package(Name, Version, Manifest, BeamArchive, Signature, PublicKey, State);
{error, Reason} ->
{error, {invalid_signature, Reason}}
end;
{error, Reason} ->
{error, {invalid_manifest, Reason}}
end.
%% @private Store package in store
do_store_package(Name, Version, Manifest, BeamArchive, Signature, PublicKey, State) ->
case State#state.store_pid of
undefined ->
{error, store_not_available};
StorePid ->
PackageData = #{
package_name => Name,
version => Version,
manifest => Manifest,
beam_archive => BeamArchive,
signature => Signature,
public_key => PublicKey
},
case macula_registry_store:store_package(StorePid, PackageData) of
{ok, Checksum} ->
%% Publish to DHT for discovery
publish_to_dht(Name, Version, Checksum, State),
{ok, #{
package_name => Name,
version => Version,
checksum => Checksum
}};
{error, Reason} ->
{error, {store_failed, Reason}}
end
end.
%% @private Fetch package
do_fetch(Name, Version, State) ->
case State#state.store_pid of
undefined ->
{error, store_not_available};
StorePid ->
case Version of
latest -> macula_registry_store:get_package(StorePid, Name);
_ -> macula_registry_store:get_package(StorePid, Name, Version)
end
end.
%% @private List packages
do_list_packages(State) ->
case State#state.store_pid of
undefined -> [];
StorePid -> macula_registry_store:list_packages(StorePid)
end.
%% @private Search packages
do_search(Pattern, State) ->
case State#state.store_pid of
undefined -> [];
StorePid -> macula_registry_store:search_packages(StorePid, Pattern)
end.
%% @private Get package info
do_get_info(Name, State) ->
case State#state.store_pid of
undefined ->
{error, store_not_available};
StorePid ->
case macula_registry_store:get_latest_version(StorePid, Name) of
{ok, Latest} ->
Versions = macula_registry_store:get_versions(StorePid, Name),
{ok, #{versions => Versions, latest => Latest}};
{error, not_found} ->
{error, not_found}
end
end.
%% @private Find store PID
find_store_pid() ->
case macula_registry_system:get_store_pid() of
{ok, Pid} -> Pid;
{error, _} -> whereis(macula_registry_store)
end.
%% @private Find routing PID for DHT
find_routing_pid() ->
whereis(macula_routing_server).
%% @private Publish package to DHT for discovery
publish_to_dht(Name, Version, Checksum, State) ->
case State#state.routing_pid of
undefined ->
?LOG_DEBUG("[RegistryServer] No routing server - skipping DHT publish"),
ok;
RoutingPid ->
Key = package_dht_key(Name, Version),
Value = #{
package_name => Name,
version => Version,
checksum => Checksum,
node_id => State#state.node_id,
published_at => erlang:system_time(millisecond)
},
do_publish_to_dht(RoutingPid, Key, Value, Name, Version)
end.
%% @private Attempt DHT publish
do_publish_to_dht(RoutingPid, Key, Value, Name, Version) ->
handle_dht_publish_result(catch macula_routing_server:store_local(RoutingPid, Key, Value), Name, Version).
%% @private Handle DHT publish result
handle_dht_publish_result({'EXIT', _}, Name, Version) ->
?LOG_WARNING("[RegistryServer] Failed to publish ~s v~s to DHT", [Name, Version]);
handle_dht_publish_result(_, _Name, _Version) ->
ok.
%% @private Generate DHT key for package
package_dht_key(Name, Version) ->
crypto:hash(sha256, <<"pkg:", Name/binary, ":", Version/binary>>).
%% @private Generate node ID
generate_node_id() ->
crypto:strong_rand_bytes(32).
%% @private Extract public key from private key
%% In Ed25519, the private key is 64 bytes: 32-byte seed + 32-byte public key
extract_public_key(PrivateKey) when byte_size(PrivateKey) =:= 64 ->
<<_Seed:32/binary, PublicKey:32/binary>> = PrivateKey,
PublicKey;
extract_public_key(PrivateKey) when byte_size(PrivateKey) =:= 32 ->
%% This is just a seed, need to derive public key
#{public := PublicKey} = crypto:generate_key(eddsa, ed25519, PrivateKey),
PublicKey.