Packages

Typed distributed messaging for Gleam on the BEAM.

Retired package: Deprecated - The project needs to be redesigned around a much smaller and clearer core.

Current section

Files

Jump to
distribute src distribute@crypto@types.erl
Raw

src/distribute@crypto@types.erl

-module(distribute@crypto@types).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/distribute/crypto/types.gleam").
-export([new_handle/2, handle_id/1, handle_state/1, new_secure_context/5, context_node_id/1, context_stage/1, context_key_id/1, context_key_material/1, context_created_at/1, new_handshake_state/4, handshake_stage/1, handshake_remote_node/1, handshake_local_node/1, handshake_data/1, empty_metrics/0, is_transient_error/1, is_permanent_error/1]).
-export_type([provider_handle/0, handshake_stage/0, secure_context/0, handshake_state/0, handshake_message/0, handshake_result/0, health_status/0, crypto_metrics/0, provider_options/0, crypto_error/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Crypto layer type definitions.\n"
"\n"
" This module contains all shared types used by crypto providers. These\n"
" types form the contract between the provider implementations and the\n"
" higher-level crypto facade.\n"
"\n"
" ## Type Categories\n"
"\n"
" - **Handles** - Opaque references to running providers\n"
" - **Contexts** - Secure contexts for encrypt/decrypt\n"
" - **Status** - Health and metrics\n"
" - **Errors** - Structured error types\n"
).
-opaque provider_handle() :: {provider_handle,
binary(),
gleam@dynamic:dynamic_()}.
-type handshake_stage() :: plain |
key_exchange_in_progress |
secure_established |
rekeying |
{failed, binary()}.
-opaque secure_context() :: {secure_context,
binary(),
handshake_stage(),
integer(),
binary(),
gleam@dynamic:dynamic_()}.
-opaque handshake_state() :: {handshake_state,
binary(),
binary(),
handshake_stage(),
gleam@dynamic:dynamic_()}.
-type handshake_message() :: {handshake_message,
binary(),
bitstring(),
gleam@option:option(gleam@dict:dict(binary(), binary()))}.
-type handshake_result() :: {continue,
handshake_state(),
gleam@option:option(handshake_message())} |
{established, secure_context()} |
{handshake_error, crypto_error()}.
-type health_status() :: up | {degraded, binary()} | {down, binary()}.
-type crypto_metrics() :: {crypto_metrics,
integer(),
integer(),
integer(),
integer(),
integer(),
integer(),
integer()}.
-type provider_options() :: {provider_options,
binary(),
boolean(),
integer(),
integer(),
gleam@dict:dict(binary(), binary())}.
-type crypto_error() :: not_initialized |
{init_failed, binary()} |
{shutdown_failed, binary()} |
{transient_network, binary()} |
invalid_signature |
key_mismatch |
{decryption_failed, binary()} |
{encryption_failed, binary()} |
{no_secure_context, binary()} |
{handshake_failed, binary()} |
{rekey_failed, binary()} |
{provider_failure, binary()} |
{timeout, integer()}.
-file("src/distribute/crypto/types.gleam", 43).
?DOC(
" Create a new provider handle.\n"
"\n"
" Creates an opaque handle that wraps provider state for safe passing\n"
" between crypto operations. The state is stored as Dynamic to allow\n"
" provider-specific implementations.\n"
"\n"
" ## Arguments\n"
"\n"
" - `id` - Unique identifier for this provider instance\n"
" - `state` - Provider-specific internal state (typically actor subject)\n"
"\n"
" ## Returns\n"
"\n"
" An opaque `ProviderHandle` for use with crypto operations.\n"
).
-spec new_handle(binary(), gleam@dynamic:dynamic_()) -> provider_handle().
new_handle(Id, State) ->
{provider_handle, Id, State}.
-file("src/distribute/crypto/types.gleam", 58).
?DOC(
" Get the handle's identifier.\n"
"\n"
" Returns the unique identifier assigned to this provider handle during\n"
" creation. Useful for logging, debugging, and registry lookups.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let id = types.handle_id(handle)\n"
" io.println(\"Provider: \" <> id)\n"
" ```\n"
).
-spec handle_id(provider_handle()) -> binary().
handle_id(Handle) ->
erlang:element(2, Handle).
-file("src/distribute/crypto/types.gleam", 74).
?DOC(
" Get the handle's internal state.\n"
"\n"
" Returns the opaque internal state of the provider. This is typically\n"
" the actor subject used for message passing.\n"
"\n"
" **Internal use only** - Provider implementations use this to extract\n"
" the actor subject for sending commands.\n"
"\n"
" ## Security Note\n"
"\n"
" Never log or serialize the returned Dynamic as it may contain\n"
" references to sensitive cryptographic state.\n"
).
-spec handle_state(provider_handle()) -> gleam@dynamic:dynamic_().
handle_state(Handle) ->
erlang:element(3, Handle).
-file("src/distribute/crypto/types.gleam", 136).
?DOC(
" Create a new secure context (provider internal use only).\n"
"\n"
" Constructs a `SecureContext` containing all cryptographic material\n"
" needed for encrypt/decrypt operations with a specific remote node.\n"
"\n"
" ## Arguments\n"
"\n"
" - `node_id` - Identifier of the remote node this context is for\n"
" - `stage` - Current handshake stage (should be `SecureEstablished`)\n"
" - `created_at_ms` - Creation timestamp in milliseconds\n"
" - `key_id` - Unique identifier for the current key (for logging)\n"
" - `key_material` - Opaque key material (provider-specific)\n"
"\n"
" ## Security Note\n"
"\n"
" This function is for provider implementations only. The `key_material`\n"
" parameter contains sensitive cryptographic keys and must never be\n"
" logged or serialized.\n"
).
-spec new_secure_context(
binary(),
handshake_stage(),
integer(),
binary(),
gleam@dynamic:dynamic_()
) -> secure_context().
new_secure_context(Node_id, Stage, Created_at_ms, Key_id, Key_material) ->
{secure_context, Node_id, Stage, Created_at_ms, Key_id, Key_material}.
-file("src/distribute/crypto/types.gleam", 166).
?DOC(
" Get the node ID from a secure context.\n"
"\n"
" Returns the identifier of the remote node this context was established\n"
" with. Use this to verify you're using the correct context for a message.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let node = types.context_node_id(ctx)\n"
" case node == expected_sender {\n"
" True -> decrypt(ctx, message)\n"
" False -> Error(WrongContext)\n"
" }\n"
" ```\n"
).
-spec context_node_id(secure_context()) -> binary().
context_node_id(Ctx) ->
erlang:element(2, Ctx).
-file("src/distribute/crypto/types.gleam", 182).
?DOC(
" Get the handshake stage.\n"
"\n"
" Returns the current stage of the handshake state machine. A context\n"
" should only be used for encryption when the stage is `SecureEstablished`.\n"
"\n"
" ## Stages\n"
"\n"
" - `Plain` - No security established\n"
" - `KeyExchangeInProgress` - Handshake ongoing\n"
" - `SecureEstablished` - Ready for encryption\n"
" - `Rekeying` - Key rotation in progress\n"
" - `Failed(reason)` - Handshake failed\n"
).
-spec context_stage(secure_context()) -> handshake_stage().
context_stage(Ctx) ->
erlang:element(3, Ctx).
-file("src/distribute/crypto/types.gleam", 197).
?DOC(
" Get the key ID (for logging/debugging without exposing key material).\n"
"\n"
" Returns a unique identifier for the current encryption key. This ID\n"
" changes after each rekey operation and can be safely logged for\n"
" debugging purposes without exposing actual key material.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" log.debug(\"Using key: \" <> types.context_key_id(ctx))\n"
" ```\n"
).
-spec context_key_id(secure_context()) -> binary().
context_key_id(Ctx) ->
erlang:element(5, Ctx).
-file("src/distribute/crypto/types.gleam", 212).
?DOC(
" Get the key material (provider internal use only).\n"
"\n"
" Returns the opaque key material for encryption/decryption. This is\n"
" provider-specific and typically contains AEAD keys and master secrets.\n"
"\n"
" **WARNING: Security-sensitive function**\n"
"\n"
" - Never log the returned value\n"
" - Never serialize or transmit it\n"
" - Only use within provider implementations\n"
" - Provider should zero memory when context is destroyed\n"
).
-spec context_key_material(secure_context()) -> gleam@dynamic:dynamic_().
context_key_material(Ctx) ->
erlang:element(6, Ctx).
-file("src/distribute/crypto/types.gleam", 231).
?DOC(
" Get the creation timestamp.\n"
"\n"
" Returns the timestamp (in milliseconds since epoch) when this secure\n"
" context was created. Useful for implementing key rotation policies\n"
" based on context age.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let age_ms = now_ms() - types.context_created_at(ctx)\n"
" case age_ms > max_key_age_ms {\n"
" True -> crypto.rekey(node_id)\n"
" False -> Ok(Nil)\n"
" }\n"
" ```\n"
).
-spec context_created_at(secure_context()) -> integer().
context_created_at(Ctx) ->
erlang:element(4, Ctx).
-file("src/distribute/crypto/types.gleam", 272).
?DOC(
" Create a new handshake state.\n"
"\n"
" Constructs intermediate state for tracking an in-progress handshake.\n"
" This state is passed between `handshake_start` and `handshake_continue`\n"
" calls during key exchange.\n"
"\n"
" ## Arguments\n"
"\n"
" - `local_node` - Identifier of the local node\n"
" - `remote_node` - Identifier of the remote node being connected to\n"
" - `stage` - Current stage in the handshake state machine\n"
" - `data` - Provider-specific handshake data (e.g., ephemeral keys)\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let state = types.new_handshake_state(\n"
" \"node_a@localhost\",\n"
" \"node_b@localhost\",\n"
" KeyExchangeInProgress,\n"
" wrap_pending_data(pending),\n"
" )\n"
" ```\n"
).
-spec new_handshake_state(
binary(),
binary(),
handshake_stage(),
gleam@dynamic:dynamic_()
) -> handshake_state().
new_handshake_state(Local_node, Remote_node, Stage, Data) ->
{handshake_state, Local_node, Remote_node, Stage, Data}.
-file("src/distribute/crypto/types.gleam", 297).
?DOC(
" Get handshake stage.\n"
"\n"
" Returns the current stage of the handshake state machine for this\n"
" handshake operation. Used to determine if more message exchanges\n"
" are needed or if the handshake has completed/failed.\n"
"\n"
" ## Returns\n"
"\n"
" - `KeyExchangeInProgress` - More exchanges needed\n"
" - `SecureEstablished` - Handshake complete\n"
" - `Failed(reason)` - Handshake failed\n"
).
-spec handshake_stage(handshake_state()) -> handshake_stage().
handshake_stage(State) ->
erlang:element(4, State).
-file("src/distribute/crypto/types.gleam", 305).
?DOC(
" Get remote node.\n"
"\n"
" Returns the identifier of the remote node this handshake is being\n"
" conducted with. Used for context lookup and message routing.\n"
).
-spec handshake_remote_node(handshake_state()) -> binary().
handshake_remote_node(State) ->
erlang:element(3, State).
-file("src/distribute/crypto/types.gleam", 312).
?DOC(
" Get local node.\n"
"\n"
" Returns the identifier of the local node in this handshake.\n"
).
-spec handshake_local_node(handshake_state()) -> binary().
handshake_local_node(State) ->
erlang:element(2, State).
-file("src/distribute/crypto/types.gleam", 323).
?DOC(
" Get handshake data (provider internal).\n"
"\n"
" Returns the provider-specific handshake data. This typically contains\n"
" ephemeral keypairs and peer public keys needed to complete the\n"
" key exchange.\n"
"\n"
" **Internal use only** - For provider implementations.\n"
).
-spec handshake_data(handshake_state()) -> gleam@dynamic:dynamic_().
handshake_data(State) ->
erlang:element(5, State).
-file("src/distribute/crypto/types.gleam", 390).
?DOC(
" Default empty metrics.\n"
"\n"
" Returns a `CryptoMetrics` struct with all counters initialized to zero.\n"
" Useful as a fallback when metrics cannot be retrieved from a provider.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let metrics = case adapter.metrics(handle) {\n"
" Ok(m) -> m\n"
" Error(_) -> types.empty_metrics()\n"
" }\n"
" ```\n"
).
-spec empty_metrics() -> crypto_metrics().
empty_metrics() ->
{crypto_metrics, 0, 0, 0, 0, 0, 0, 0}.
-file("src/distribute/crypto/types.gleam", 471).
?DOC(
" Check if an error is transient.\n"
"\n"
" Transient errors are temporary and the operation may succeed if retried\n"
" with appropriate backoff. Use this to implement retry logic.\n"
"\n"
" ## Transient Errors\n"
"\n"
" - `TransientNetwork` - Temporary network issues\n"
" - `Timeout` - Operation timed out (may succeed later)\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" case crypto.encrypt(ctx, data) {\n"
" Error(err) if types.is_transient_error(err) ->\n"
" retry_with_backoff(fn() { crypto.encrypt(ctx, data) })\n"
" Error(err) -> Error(err)\n"
" Ok(ciphertext) -> Ok(ciphertext)\n"
" }\n"
" ```\n"
).
-spec is_transient_error(crypto_error()) -> boolean().
is_transient_error(Error) ->
case Error of
{transient_network, _} ->
true;
{timeout, _} ->
true;
_ ->
false
end.
-file("src/distribute/crypto/types.gleam", 503).
?DOC(
" Check if an error is permanent.\n"
"\n"
" Permanent errors indicate a fundamental problem that will not be\n"
" resolved by retrying. These require intervention (e.g., re-handshake,\n"
" configuration fix, or aborting the operation).\n"
"\n"
" ## Permanent Errors\n"
"\n"
" - `InvalidSignature` - Cryptographic verification failed\n"
" - `KeyMismatch` - Keys don't match expected values\n"
" - `DecryptionFailed` - Ciphertext corrupted or wrong key\n"
" - `InitFailed` - Provider couldn't initialize\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" case crypto.decrypt(ctx, data) {\n"
" Error(err) if types.is_permanent_error(err) ->\n"
" // Don't retry, re-establish connection\n"
" reconnect_and_rehandshake(node)\n"
" Error(err) -> retry(fn() { crypto.decrypt(ctx, data) })\n"
" Ok(plaintext) -> Ok(plaintext)\n"
" }\n"
" ```\n"
).
-spec is_permanent_error(crypto_error()) -> boolean().
is_permanent_error(Error) ->
case Error of
invalid_signature ->
true;
key_mismatch ->
true;
{decryption_failed, _} ->
true;
{init_failed, _} ->
true;
_ ->
false
end.