-module(apiculture@random). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/apiculture/random.gleam"). -export([random_bytes/1, random_chars/2]). -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( " Secure randomness abstraction.\n" "\n" " This module provides cryptographically secure random generation for Erlang\n" " and JavaScript targets. It exposes both byte generation and unbiased direct\n" " alphabet sampling.\n" ). -file("src/apiculture/random.gleam", 22). ?DOC( " Generates cryptographically secure random bytes.\n" "\n" " On Erlang: Uses `crypto:strong_rand_bytes/1`\n" " On JavaScript: Uses `crypto.getRandomValues()` (Web Crypto API)\n" " \n" " Returns an error if secure randomness is unavailable.\n" ). -spec random_bytes(integer()) -> {ok, bitstring()} | {error, apiculture@error:error()}. random_bytes(Count) -> case Count =< 0 of true -> {error, invalid_byte_count}; false -> {ok, crypto:strong_rand_bytes(Count)} end. -file("src/apiculture/random.gleam", 51). -spec do_rejection_sample(integer(), integer(), list(integer())) -> {ok, list(integer())} | {error, apiculture@error:error()}. do_rejection_sample(Alphabet_size, Count, Acc) -> case Count of 0 -> {ok, lists:reverse(Acc)}; _ -> Bytes = crypto:strong_rand_bytes(1), case Bytes of <> -> Max_acceptable = (case Alphabet_size of 0 -> 0; Gleam@denominator -> 256 div Gleam@denominator end) * Alphabet_size, case Byte < Max_acceptable of true -> Char_index = case Alphabet_size of 0 -> 0; Gleam@denominator@1 -> Byte rem Gleam@denominator@1 end, do_rejection_sample( Alphabet_size, Count - 1, [Char_index | Acc] ); false -> do_rejection_sample(Alphabet_size, Count, Acc) end; _ -> {error, secure_random_unavailable} end end. -file("src/apiculture/random.gleam", 38). ?DOC( " Generates cryptographically secure random characters by sampling directly\n" " from an alphabet using rejection sampling to avoid modulo bias.\n" "\n" " This is mathematically distinct from generating random bytes and then\n" " encoding them - this samples characters directly from the alphabet.\n" ). -spec random_chars(apiculture@alphabet:alphabet(), integer()) -> {ok, list(integer())} | {error, apiculture@error:error()}. random_chars(Alphabet, Count) -> case Count =< 0 of true -> {error, invalid_byte_count}; false -> Alphabet_size = apiculture@alphabet:size(Alphabet), do_rejection_sample(Alphabet_size, Count, []) end.