%% %% Copyright 2012 Dmitry Kolesnikov, All Rights Reserved %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% @description %% k-ordered unique identity -module(uid). -include("uid.hrl"). %% k-order interface -export([ z/0 ,l/0 ,l/1 ,g/0 ,g/1 ,encode/1 ,encode/2 ,decode/1 ,decode/2 ,encode64/1 ,encode64/2 ,decode64/1 ,decode64/2 ]). %% k-order utility -export([ gtol/1 ,d/2 ,t/1 ]). %% v-clock interface -export([ vclock/0, vclock/1, join/2, descend/2, descend/3, diff/2 ]). %% %% data types -export_type([uid/0, l/0, g/0, vclock/0]). -type uid() :: l() | g(). -type l() :: {uid, t(), seq()}. -type g() :: {uid, id(), t(), seq()}. -type t() :: {integer(), integer(), integer()}. -type seq() :: <<_:14>>. -type id() :: node() | <<_:32>>. -type vclock() :: [g()]. %%%---------------------------------------------------------------------------- %%% %%% k-order interface %%% %%%---------------------------------------------------------------------------- %% %% -spec z() -> l(). z() -> {uid, {0,0,0,0}, 0}. %% %% @doc %% generate locally unique 64-bit k-order identifier -spec l() -> l(). -spec l(g()) -> l(). l() -> {uid, t(), seq()}. l({uid, _, _} = Uid) -> Uid; l({uid, Node, T, Seq}) when is_binary(Node) -> case hid(erlang:node()) of Node -> {uid, T, Seq}; _ -> exit(badarg) end; l({uid, Node, T, Seq}) when is_atom(Node) -> case erlang:node() of Node -> {uid, T, Seq}; _ -> exit(badarg) end. %% %% @doc %% generate globally unique 96-bit k-order identifier -spec g() -> g(). -spec g(l()) -> g(). g() -> {uid, erlang:node(), t(), seq()}. g({uid, T, Seq}) -> {uid, erlang:node(), T, Seq}; g({uid, _, _, _} = Uid) -> Uid. %% %% @doc %% encode k-order number to binary format -spec encode(l() | g()) -> binary(). -spec encode(integer(), l() | g()) -> binary(). encode(Uid) -> encode(?CONFIG_DRIFT, Uid). encode(_Bits, {uid, T, Seq}) -> <<(encode_t(T))/bits, Seq:14>>; encode(Bits, {uid, Node, T, Seq}) -> <<(encode_t(Bits, Node, T))/bits, Seq:14>>. encode_t({A, B, C}) -> <> = <>, T. encode_t(Bits, Node, {A, B, C}) when is_binary(Node), Bits >= ?CONFIG_DRIFT_LOW, Bits =< ?CONFIG_DRIFT_HIGH -> L0 = 20 - Bits, L1 = Bits, B0 = B bsr Bits, B1 = B band ((1 bsl Bits) - 1), <> = <>, T; encode_t(Bits, Node, T) -> encode_t(Bits, hid(Node), T). %% %% @doc %% decode k-order number from binary format -spec decode(binary()) -> l() | g(). -spec decode(integer(), binary()) -> l() | g(). decode(Uid) -> decode(?CONFIG_DRIFT, Uid). decode(_Bits, <>) -> {uid, decode_lt(Prefix), Seq}; decode(Bits, <>) -> {Node, T} = decode_gt(Bits, Prefix), {uid, Node, T, Seq}. decode_lt(<> ) -> {A, B, C bsl 10}. decode_gt(Bits, T) -> L0 = 20 - Bits, L1 = Bits, <> = T, case hid(erlang:node()) of Node -> {erlang:node(), {A, B bsl Bits + C, D bsl 10}}; _ -> {Node, {A, B bsl Bits + C, D bsl 10}} end. %% %% @doc %% encode k-order number to base64 url -spec encode64(l() | g()) -> binary(). -spec encode64(integer(), l() | g()) -> binary(). encode64(Uid) -> encode64(?CONFIG_DRIFT, Uid). encode64(Drift, Uid) -> << << (encode_digit(X)) >> || <> <= base64:encode( encode(Drift, Uid) ), X =/= $= >>. encode_digit($/) -> $_; encode_digit($+) -> $-; encode_digit(X) -> X. %% %% @doc %% decode k-order number from base64 url -spec decode64(binary()) -> l() | g(). -spec decode64(integer(), binary()) -> l() | g(). decode64(Uid) -> decode64(?CONFIG_DRIFT, Uid). decode64(Drift, Uid) -> decode(Drift, base64:decode(<< << (decode_digit(X)) >> || <> <= decode_pad(Uid) >>) ). decode_pad(Bin) when byte_size(Bin) rem 4 =:= 2 -> <>; decode_pad(Bin) when byte_size(Bin) rem 4 =:= 3 -> <>; decode_pad(Bin) -> Bin. decode_digit($_) -> $/; decode_digit($-) -> $+; decode_digit(D) -> D. %%%---------------------------------------------------------------------------- %%% %%% k-order utility %%% %%%---------------------------------------------------------------------------- %% %% @doc %% cast with force global to local k-order identifier %% the operation do not guarantee uniqueness of the result -spec gtol(g()) -> l(). gtol({uid, _, T, Seq}) -> {uid, T, Seq}. %% %% @doc %% approximate distance between k-order values -spec d(uid(), uid()) -> uid(). d({uid, A, Sa}, {uid, B, Sb}) -> {uid, d(A, B), Sa - Sb}; d({uid, Node, A, Sa}, {uid, Node, B, Sb}) -> {uid, Node, d(A, B), Sa - Sb}; d({A2, A1, A0}, {B2, B1, B0}) -> X = timer:now_diff({A2, A1, A0}, {B2, B1, B0}), C = X rem ?BASE, Y = X div ?BASE, B = Y rem ?BASE, A = Y div ?BASE, {A, B, C}. %% %% @doc %% helper function to extract time-stamp in milliseconds from k-order value -spec t(l() | g()) -> integer(). t({uid, {A, B, C}, _}) -> (A * ?BASE + B) * 1000 + C div 1000; t({uid, _, {A, B, C}, _}) -> (A * ?BASE + B) * 1000 + C div 1000. %%%---------------------------------------------------------------------------- %%% %%% v-clock interface %%% %%%---------------------------------------------------------------------------- %% %% create new v-clock -spec vclock() -> vclock(). vclock() -> [g()]. %% %% increment v-clock -spec vclock(vclock()) -> vclock(). vclock(Vclock) -> case lists:keytake(erlang:node(), 2, Vclock) of false -> [g() | Vclock]; {value, _, List} -> [g() | List] end. %% %% join two v-clock -spec join(vclock(), vclock()) -> vclock(). join(A, B) -> do_join(lists:keysort(2, A), lists:keysort(2, B)). do_join([{uid, NodeA, _, _}=X|A], [{uid, NodeB, _, _}|_]=B) when NodeA < NodeB -> [X | do_join(A, B)]; do_join([{uid, NodeA, _, _}|_]=A, [{uid, NodeB, _, _}=X|B]) when NodeA > NodeB -> [X | do_join(A, B)]; do_join([X|A], [Y|B]) -> [erlang:max(X, Y) | do_join(A, B)]; do_join([], B) -> B; do_join(A, []) -> A. %% %% return true if A v-clock is descend of B v-clock : A -> B -spec descend(vclock(), vclock()) -> boolean(). descend(_, []) -> true; descend(A, [{uid, Node, _, _} = X|B]) -> case lists:keyfind(Node, 2, A) of false -> false; Y -> (X =< Y) andalso descend(A, B) end. %% %% return true if A clock is descend B with an exception to given peer %% the method allows to discover local conflicts -spec descend(node(), vclock(), vclock()) -> boolean(). descend(Node, A, B) when is_atom(Node) -> descend(lists:keydelete(Node, 2, A), lists:keydelete(Node, 2, B)). %% %% return difference of A clock to compare with B -spec diff(vclock(), vclock()) -> [node()]. diff(_, []) -> []; diff(A, [{uid, Node, _, _} = X|B]) -> case lists:keyfind(Node, 2, A) of false -> [X | diff(A, B)]; Y when X =< Y -> diff(A, B); Y -> [d(X, Y) | diff(A, B)] end. %%%---------------------------------------------------------------------------- %%% %%% private %%% %%%---------------------------------------------------------------------------- %% %% local time stamp t() -> {A, B, C} = os:timestamp(), {A, B, C band 16#ffc00}. %% %% locally unique sequential number 14-bit length seq() -> erlang:unique_integer([monotonic, positive]) band 16#3fff. %% %% host unique identifier hid(Node) -> <<(erlang:phash(Node, 1 bsl 32)):32>>.