Packages
macula
0.30.5
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
Current section
Files
src/macula_protocol.erl
%% @doc Behaviour defining the protocol that Macula mesh applications must implement.
%%
%% == Overview ==
%%
%% This behaviour defines the contract that applications must implement to be
%% considered "mesh-worthy" and allowed to participate in the Macula mesh network.
%% The gatekeeper validates that apps implement this protocol before granting
%% mesh access.
%%
%% == Why This Matters ==
%%
%% Not every container or process should be allowed to join the mesh. This protocol
%% ensures that:
%% - Apps have proper identity (backed by certificates)
%% - Apps declare their capabilities upfront
%% - Apps can receive mesh events
%% - Apps can be health-checked during sessions
%%
%% == Implementation ==
%%
%% BEAM apps implement this as a behaviour. The implementation must export:
%% - mesh_identity/0 - Returns the app's identity binary
%% - mesh_capabilities/0 - Returns list of capabilities
%% - mesh_api/0 - Returns map of topics, procedures, content_types
%% - handle_mesh_event/2 - Handles incoming events
%% - mesh_health/0 - Returns ok or error tuple
%%
%% Non-BEAM apps implement equivalent gRPC or HTTP endpoints that the gatekeeper
%% can probe.
%%
%% == Security Model ==
%%
%% 1. Identity must match the certificate's subject
%% 2. Capabilities are validated against licenses at operation time
%% 3. Health checks run periodically during sessions
%% 4. Failed health checks may result in session termination
%%
%% @see macula_gatekeeper
%% @author Macula Team
%% @end
-module(macula_protocol).
%%====================================================================
%% Types
%%====================================================================
-type identity() :: binary().
%% App identity, e.g., "io.macula.rgfaber.my-app"
-type capability() ::
publish | % Can publish messages to topics
subscribe | % Can subscribe to topics
call | % Can make RPC calls
register | % Can register RPC handlers
provide_content | % Can provide content (files/artifacts)
consume_content. % Can consume content (files/artifacts)
%% What the app can do on the mesh
-type api_spec() :: #{
topics => [binary()], % PubSub topics provided
procedures => [binary()], % RPC procedures provided
content_types => [binary()] % Content types provided (e.g., "application/wasm")
}.
%% API declaration: topics, procedures, and content types this app provides
-type health_status() :: ok | {error, term()}.
%% Health check result
%%====================================================================
%% Behaviour Callbacks
%%====================================================================
%% Returns the app's mesh identity.
%% Must match the certificate's subject: {realm}.{organization}.{app-name}
-callback mesh_identity() -> identity().
%% Returns the capabilities this app requires.
%% Capabilities: publish, subscribe, call, register, provide_content, consume_content
-callback mesh_capabilities() -> [capability()].
%% Returns the API specification (topics, procedures, content_types).
-callback mesh_api() -> api_spec().
%% Handles incoming pub/sub events.
-callback handle_mesh_event(Topic :: binary(), Payload :: term()) -> ok | {error, term()}.
%% Handles incoming RPC calls.
-callback handle_rpc_call(Procedure :: binary(), Args :: list()) ->
{ok, term()} | {error, term()}.
%% Provides content by content ID.
-callback provide_content(ContentId :: binary()) ->
{ok, binary()} | {error, term()}.
%% Called when content has been received.
-callback content_received(ContentId :: binary(), Content :: binary()) ->
ok | {error, term()}.
%% Health check callback. Return ok or {error, Reason}.
-callback mesh_health() -> health_status().
%%====================================================================
%% Exports
%%====================================================================
-export_type([identity/0, capability/0, api_spec/0, health_status/0]).
%% Utility functions for protocol validation
-export([
validate_identity/1,
validate_capabilities/1,
validate_api_spec/1,
is_valid_capability/1
]).
%%====================================================================
%% Utility Functions
%%====================================================================
%% @doc Validates that an identity is well-formed.
%%
%% Valid identities:
%% - Are non-empty binaries
%% - Contain at least 3 dot-separated segments
%% - Each segment contains only alphanumeric characters and hyphens
-spec validate_identity(identity()) -> ok | {error, term()}.
validate_identity(Identity) when is_binary(Identity), byte_size(Identity) > 0 ->
Parts = binary:split(Identity, <<".">>, [global]),
case length(Parts) >= 3 of
true ->
case lists:all(fun is_valid_segment/1, Parts) of
true -> ok;
false -> {error, invalid_segment}
end;
false ->
{error, insufficient_segments}
end;
validate_identity(_) ->
{error, invalid_identity}.
%% @doc Validates that capabilities list is valid.
-spec validate_capabilities([capability()]) -> ok | {error, term()}.
validate_capabilities(Caps) when is_list(Caps) ->
case lists:all(fun is_valid_capability/1, Caps) of
true -> ok;
false -> {error, invalid_capability}
end;
validate_capabilities(_) ->
{error, invalid_capabilities_format}.
%% @doc Validates that an API spec is well-formed.
-spec validate_api_spec(api_spec()) -> ok | {error, term()}.
validate_api_spec(Spec) when is_map(Spec) ->
Topics = maps:get(topics, Spec, []),
Procs = maps:get(procedures, Spec, []),
ContentTypes = maps:get(content_types, Spec, []),
TopicsValid = is_list(Topics) andalso lists:all(fun is_binary/1, Topics),
ProcsValid = is_list(Procs) andalso lists:all(fun is_binary/1, Procs),
ContentTypesValid = is_list(ContentTypes) andalso lists:all(fun is_binary/1, ContentTypes),
case TopicsValid andalso ProcsValid andalso ContentTypesValid of
true -> ok;
false -> {error, invalid_api_entries}
end;
validate_api_spec(_) ->
{error, invalid_api_spec_format}.
%% @doc Checks if a capability is valid.
-spec is_valid_capability(term()) -> boolean().
is_valid_capability(publish) -> true;
is_valid_capability(subscribe) -> true;
is_valid_capability(call) -> true;
is_valid_capability(register) -> true;
is_valid_capability(provide_content) -> true;
is_valid_capability(consume_content) -> true;
is_valid_capability(_) -> false.
%%====================================================================
%% Internal Functions
%%====================================================================
%% Validates a single segment of an identity
-spec is_valid_segment(binary()) -> boolean().
is_valid_segment(<<>>) ->
false;
is_valid_segment(Segment) ->
lists:all(
fun(Char) ->
(Char >= $a andalso Char =< $z) orelse
(Char >= $A andalso Char =< $Z) orelse
(Char >= $0 andalso Char =< $9) orelse
Char =:= $-
end,
binary_to_list(Segment)
).