Current section
Files
Jump to
Current section
Files
src/youid@uuid.erl
-module(youid@uuid).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([time/1, clock_sequence/1, node/1, dns_uuid/0, url_uuid/0, oid_uuid/0, x500_uuid/0, to_bit_array/1, version/1, from_bit_array/1, variant/1, from_string/1, time_posix_microsec/1, time_posix_millisec/1, format/2, to_string/1, v1/0, v1_string/0, v1_custom/2, v3/2, v4/0, v4_string/0, v5/2, v7_from_millisec/1, v7/0, v7_string/0]).
-export_type([uuid/0, version/0, variant/0, format/0, v1_node/0, v1_clock_seq/0]).
-opaque uuid() :: {uuid, bitstring()}.
-type version() :: v1 | v2 | v3 | v4 | v5 | v7 | v_unknown.
-type variant() :: reserved_future | reserved_microsoft | reserved_ncs | rfc4122.
-type format() :: string | hex | urn.
-type v1_node() :: default_node | random_node | {custom_node, binary()}.
-type v1_clock_seq() :: random_clock_seq | {custom_clock_seq, bitstring()}.
-spec random_uuid1_clockseq() -> bitstring().
random_uuid1_clockseq() ->
_assert_subject = crypto:strong_rand_bytes(2),
<<Clock_seq:14, _:2>> = case _assert_subject of
<<_:14, _:2>> -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"youid/uuid"/utf8>>,
function => <<"random_uuid1_clockseq"/utf8>>,
line => 181})
end,
<<Clock_seq:14>>.
-spec random_uuid1_node() -> bitstring().
random_uuid1_node() ->
_assert_subject = crypto:strong_rand_bytes(6),
<<Rnd_hi:7, _:1, Rnd_low:40>> = case _assert_subject of
<<_:7, _:1, _:40>> -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"youid/uuid"/utf8>>,
function => <<"random_uuid1_node"/utf8>>,
line => 197})
end,
<<Rnd_hi:7, 1:1, Rnd_low:40>>.
-spec md5(bitstring()) -> bitstring().
md5(Data) ->
gleam_crypto_ffi:hash(md5, Data).
-spec sha1(bitstring()) -> bitstring().
sha1(Data) ->
_assert_subject = gleam_crypto_ffi:hash(sha1, Data),
<<Sha:128, _:32>> = case _assert_subject of
<<_:128, _:32>> -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"youid/uuid"/utf8>>,
function => <<"sha1"/utf8>>,
line => 290})
end,
<<Sha:128>>.
-spec time(uuid()) -> integer().
time(Uuid) ->
_assert_subject = erlang:element(2, Uuid),
<<T_low:32, T_mid:16, _:4, T_hi:12, _:64>> = case _assert_subject of
<<_:32, _:16, _:4, _:12, _:64>> -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"youid/uuid"/utf8>>,
function => <<"time"/utf8>>,
line => 342})
end,
_assert_subject@1 = <<T_hi:12, T_mid:16, T_low:32>>,
<<T:60>> = case _assert_subject@1 of
<<_:60>> -> _assert_subject@1;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail@1,
module => <<"youid/uuid"/utf8>>,
function => <<"time"/utf8>>,
line => 343})
end,
T.
-spec clock_sequence(uuid()) -> integer().
clock_sequence(Uuid) ->
_assert_subject = erlang:element(2, Uuid),
<<_:66, Clock_seq:14, _:48>> = case _assert_subject of
<<_:66, _:14, _:48>> -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"youid/uuid"/utf8>>,
function => <<"clock_sequence"/utf8>>,
line => 365})
end,
Clock_seq.
-spec node(uuid()) -> binary().
node(Uuid) ->
_assert_subject = erlang:element(2, Uuid),
<<_:80, A:4, B:4, C:4, D:4, E:4, F:4, G:4, H:4, I:4, J:4, K:4, L:4>> = case _assert_subject of
<<_:80, _:4, _:4, _:4, _:4, _:4, _:4, _:4, _:4, _:4, _:4, _:4, _:4>> -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"youid/uuid"/utf8>>,
function => <<"node"/utf8>>,
line => 372})
end,
_pipe = [A, B, C, D, E, F, G, H, I, J, K, L],
_pipe@1 = gleam@list:map(_pipe, fun gleam@int:to_base16/1),
gleam@string:concat(_pipe@1).
-spec to_string_help(bitstring(), integer(), binary(), binary()) -> binary().
to_string_help(Ints, Position, Acc, Separator) ->
case Position of
8 ->
to_string_help(
Ints,
Position + 1,
<<Acc/binary, Separator/binary>>,
Separator
);
13 ->
to_string_help(
Ints,
Position + 1,
<<Acc/binary, Separator/binary>>,
Separator
);
18 ->
to_string_help(
Ints,
Position + 1,
<<Acc/binary, Separator/binary>>,
Separator
);
23 ->
to_string_help(
Ints,
Position + 1,
<<Acc/binary, Separator/binary>>,
Separator
);
_ ->
case Ints of
<<I:4, Rest/bitstring>> ->
to_string_help(
Rest,
Position + 1,
<<Acc/binary, (gleam@int:to_base16(I))/binary>>,
Separator
);
_ ->
Acc
end
end.
-spec dns_uuid() -> uuid().
dns_uuid() ->
{uuid, <<143098242404177361603877621312831893704:128>>}.
-spec url_uuid() -> uuid().
url_uuid() ->
{uuid, <<143098242483405524118141958906375844040:128>>}.
-spec oid_uuid() -> uuid().
oid_uuid() ->
{uuid, <<143098242562633686632406296499919794376:128>>}.
-spec x500_uuid() -> uuid().
x500_uuid() ->
{uuid, <<143098242721090011660934971687007695048:128>>}.
-spec to_bit_array(uuid()) -> bitstring().
to_bit_array(Uuid) ->
erlang:element(2, Uuid).
-spec decode_version(integer()) -> version().
decode_version(Int) ->
case Int of
1 ->
v1;
2 ->
v2;
3 ->
v3;
4 ->
v4;
5 ->
v5;
7 ->
v7;
_ ->
v_unknown
end.
-spec version(uuid()) -> version().
version(Uuid) ->
_assert_subject = erlang:element(2, Uuid),
<<_:48, Ver:4, _:76>> = case _assert_subject of
<<_:48, _:4, _:76>> -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"youid/uuid"/utf8>>,
function => <<"version"/utf8>>,
line => 324})
end,
decode_version(Ver).
-spec from_bit_array(bitstring()) -> {ok, uuid()} | {error, nil}.
from_bit_array(Bit_array) ->
Uuid = {uuid, Bit_array},
case erlang:byte_size(Bit_array) of
16 ->
case version(Uuid) of
v_unknown ->
{error, nil};
_ ->
{ok, Uuid}
end;
_ ->
{error, nil}
end.
-spec decode_variant(bitstring()) -> variant().
decode_variant(Variant_bits) ->
case Variant_bits of
<<1:1, 1:1, 1:1>> ->
reserved_future;
<<1:1, 1:1, 0:1>> ->
reserved_microsoft;
<<1:1, 0:1, _:1>> ->
rfc4122;
<<0:1, _:1, _:1>> ->
reserved_ncs;
_ ->
reserved_ncs
end.
-spec variant(uuid()) -> variant().
variant(Uuid) ->
_assert_subject = erlang:element(2, Uuid),
<<_:64, Var:3, _:61>> = case _assert_subject of
<<_:64, _:3, _:61>> -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"youid/uuid"/utf8>>,
function => <<"variant"/utf8>>,
line => 330})
end,
decode_variant(<<Var:3>>).
-spec hex_to_int(binary()) -> {ok, integer()} | {error, nil}.
hex_to_int(C) ->
I = case C of
<<"0"/utf8>> ->
0;
<<"1"/utf8>> ->
1;
<<"2"/utf8>> ->
2;
<<"3"/utf8>> ->
3;
<<"4"/utf8>> ->
4;
<<"5"/utf8>> ->
5;
<<"6"/utf8>> ->
6;
<<"7"/utf8>> ->
7;
<<"8"/utf8>> ->
8;
<<"9"/utf8>> ->
9;
<<"a"/utf8>> ->
10;
<<"A"/utf8>> ->
10;
<<"b"/utf8>> ->
11;
<<"B"/utf8>> ->
11;
<<"c"/utf8>> ->
12;
<<"C"/utf8>> ->
12;
<<"d"/utf8>> ->
13;
<<"D"/utf8>> ->
13;
<<"e"/utf8>> ->
14;
<<"E"/utf8>> ->
14;
<<"f"/utf8>> ->
15;
<<"F"/utf8>> ->
15;
_ ->
16
end,
case I of
16 ->
{error, nil};
X ->
{ok, X}
end.
-spec validate_custom_node(binary(), integer(), bitstring()) -> {ok,
bitstring()} |
{error, nil}.
validate_custom_node(Str, Index, Acc) ->
case gleam@string:pop_grapheme(Str) of
{error, nil} when Index =:= 12 ->
{ok, Acc};
{ok, {<<":"/utf8>>, Rest}} ->
validate_custom_node(Rest, Index, Acc);
{ok, {C, Rest@1}} ->
case hex_to_int(C) of
{ok, I} when Index < 12 ->
validate_custom_node(
Rest@1,
Index + 1,
<<Acc/bitstring, I:4>>
);
_ ->
{error, nil}
end;
_ ->
{error, nil}
end.
-spec to_bitstring_help(binary(), integer(), bitstring()) -> {ok, bitstring()} |
{error, nil}.
to_bitstring_help(Str, Index, Acc) ->
case gleam@string:pop_grapheme(Str) of
{error, nil} when Index =:= 32 ->
{ok, Acc};
{ok, {<<"-"/utf8>>, Rest}} when Index < 32 ->
to_bitstring_help(Rest, Index, Acc);
{ok, {C, Rest@1}} when Index < 32 ->
case hex_to_int(C) of
{ok, I} ->
to_bitstring_help(Rest@1, Index + 1, <<Acc/bitstring, I:4>>);
{error, _} ->
{error, nil}
end;
_ ->
{error, nil}
end.
-spec to_bit_array_helper(binary()) -> {ok, bitstring()} | {error, nil}.
to_bit_array_helper(Str) ->
to_bitstring_help(Str, 0, <<>>).
-spec from_string(binary()) -> {ok, uuid()} | {error, nil}.
from_string(In) ->
Hex = case In of
<<"urn:uuid:"/utf8, In@1/binary>> ->
In@1;
_ ->
In
end,
case to_bit_array_helper(Hex) of
{ok, Bits} ->
{ok, {uuid, Bits}};
{error, _} ->
{error, nil}
end.
-spec default_uuid1_node() -> bitstring().
default_uuid1_node() ->
case youid_ffi:mac_address() of
{ok, Node} ->
Node;
_ ->
random_uuid1_node()
end.
-spec validate_node(v1_node()) -> {ok, bitstring()} | {error, nil}.
validate_node(Node) ->
case Node of
default_node ->
{ok, default_uuid1_node()};
random_node ->
{ok, random_uuid1_node()};
{custom_node, Str} ->
validate_custom_node(Str, 0, <<>>)
end.
-spec validate_clock_seq(v1_clock_seq()) -> {ok, bitstring()} | {error, nil}.
validate_clock_seq(Clock_seq) ->
case Clock_seq of
random_clock_seq ->
{ok, random_uuid1_clockseq()};
{custom_clock_seq, Bs} ->
case erlang:bit_size(Bs) =:= 14 of
true ->
{ok, Bs};
false ->
{error, nil}
end
end.
-spec uuid1_time() -> bitstring().
uuid1_time() ->
{Mega_sec, Sec, Micro_sec} = os:timestamp(),
Epoch = ((Mega_sec * 1000000000000) + (Sec * 1000000)) + Micro_sec,
Timestamp = 122192928000000000 + (10 * Epoch),
<<Timestamp:60>>.
-spec time_posix_microsec(uuid()) -> integer().
time_posix_microsec(Uuid) ->
case version(Uuid) of
v7 ->
_assert_subject = erlang:element(2, Uuid),
<<T:48, _:80>> = case _assert_subject of
<<_:48, _:80>> -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"youid/uuid"/utf8>>,
function => <<"time_posix_microsec"/utf8>>,
line => 355})
end,
T * 1000;
_ ->
case 10 of
0 -> 0;
Gleam@denominator -> (time(Uuid) - 122192928000000000) div Gleam@denominator
end
end.
-spec time_posix_millisec(uuid()) -> integer().
time_posix_millisec(Uuid) ->
case version(Uuid) of
v1 ->
(case 10 of
0 -> 0;
Gleam@denominator -> (time(Uuid) - 122192928000000000) div Gleam@denominator
end) div 1000;
_ ->
_assert_subject = erlang:element(2, Uuid),
<<T:48, _:80>> = case _assert_subject of
<<_:48, _:80>> -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"youid/uuid"/utf8>>,
function => <<"time_posix_millisec"/utf8>>,
line => 401})
end,
T
end.
-spec hash_to_uuid_value(bitstring(), integer()) -> bitstring().
hash_to_uuid_value(Hash, Ver) ->
<<Time_low:32,
Time_mid:16,
_:4,
Time_hi:12,
_:2,
Clock_seq_hi:6,
Clock_seq_low:8,
Node:48>> = case Hash of
<<_:32, _:16, _:4, _:12, _:2, _:6, _:8, _:48>> -> Hash;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"youid/uuid"/utf8>>,
function => <<"hash_to_uuid_value"/utf8>>,
line => 224})
end,
<<Time_low:32,
Time_mid:16,
Ver:4,
Time_hi:12,
2:2,
Clock_seq_hi:6,
Clock_seq_low:8,
Node:48>>.
-spec format(uuid(), format()) -> binary().
format(Uuid, Format) ->
Separator = case Format of
string ->
<<"-"/utf8>>;
_ ->
<<""/utf8>>
end,
Start = case Format of
urn ->
<<"urn:uuid:"/utf8>>;
_ ->
<<""/utf8>>
end,
to_string_help(erlang:element(2, Uuid), 0, Start, Separator).
-spec to_string(uuid()) -> binary().
to_string(Uuid) ->
format(Uuid, string).
-spec do_v1(bitstring(), bitstring()) -> uuid().
do_v1(Node, Clock_seq) ->
<<Node@1:48>> = case Node of
<<_:48>> -> Node;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"youid/uuid"/utf8>>,
function => <<"do_v1"/utf8>>,
line => 117})
end,
_assert_subject = uuid1_time(),
<<Time_hi:12, Time_mid:16, Time_low:32>> = case _assert_subject of
<<_:12, _:16, _:32>> -> _assert_subject;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail@1,
module => <<"youid/uuid"/utf8>>,
function => <<"do_v1"/utf8>>,
line => 118})
end,
<<Clock_seq@1:14>> = case Clock_seq of
<<_:14>> -> Clock_seq;
_assert_fail@2 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail@2,
module => <<"youid/uuid"/utf8>>,
function => <<"do_v1"/utf8>>,
line => 119})
end,
Value = <<Time_low:32,
Time_mid:16,
1:4,
Time_hi:12,
2:2,
Clock_seq@1:14,
Node@1:48>>,
{uuid, Value}.
-spec v1() -> uuid().
v1() ->
do_v1(default_uuid1_node(), random_uuid1_clockseq()).
-spec v1_string() -> binary().
v1_string() ->
_pipe = v1(),
to_string(_pipe).
-spec v1_custom(v1_node(), v1_clock_seq()) -> {ok, uuid()} | {error, nil}.
v1_custom(Node, Clock_seq) ->
case {validate_node(Node), validate_clock_seq(Clock_seq)} of
{{ok, N}, {ok, Cs}} ->
{ok, do_v1(N, Cs)};
{_, _} ->
{error, nil}
end.
-spec v3(uuid(), bitstring()) -> {ok, uuid()} | {error, nil}.
v3(Namespace, Name) ->
case (erlang:bit_size(Name) rem 8) =:= 0 of
true ->
_pipe = <<(erlang:element(2, Namespace))/bitstring, Name/bitstring>>,
_pipe@1 = md5(_pipe),
_pipe@2 = hash_to_uuid_value(_pipe@1, 3),
_pipe@3 = {uuid, _pipe@2},
{ok, _pipe@3};
false ->
{error, nil}
end.
-spec v4() -> uuid().
v4() ->
_assert_subject = crypto:strong_rand_bytes(16),
<<A:48, _:4, B:12, _:2, C:62>> = case _assert_subject of
<<_:48, _:4, _:12, _:2, _:62>> -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"youid/uuid"/utf8>>,
function => <<"v4"/utf8>>,
line => 252})
end,
Value = <<A:48, 4:4, B:12, 2:2, C:62>>,
{uuid, Value}.
-spec v4_string() -> binary().
v4_string() ->
_pipe = v4(),
format(_pipe, string).
-spec v5(uuid(), bitstring()) -> {ok, uuid()} | {error, nil}.
v5(Namespace, Name) ->
case (erlang:bit_size(Name) rem 8) =:= 0 of
true ->
_pipe = <<(erlang:element(2, Namespace))/bitstring, Name/bitstring>>,
_pipe@1 = sha1(_pipe),
_pipe@2 = hash_to_uuid_value(_pipe@1, 5),
_pipe@3 = {uuid, _pipe@2},
{ok, _pipe@3};
false ->
{error, nil}
end.
-spec v7_from_millisec(integer()) -> uuid().
v7_from_millisec(Timestamp) ->
_assert_subject = crypto:strong_rand_bytes(10),
<<A:12, B:62, _:6>> = case _assert_subject of
<<_:12, _:62, _:6>> -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"youid/uuid"/utf8>>,
function => <<"v7_from_millisec"/utf8>>,
line => 306})
end,
Value = <<Timestamp:48, 7:4, A:12, 2:2, B:62>>,
{uuid, Value}.
-spec v7() -> uuid().
v7() ->
Ms = os:system_time(1000),
v7_from_millisec(Ms).
-spec v7_string() -> binary().
v7_string() ->
_pipe = v7(),
format(_pipe, string).