1%% This Source Code Form is subject to the terms of the Mozilla Public
2%% License, v. 2.0. If a copy of the MPL was not distributed with this
3%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
4%%
5%% Copyright (c) 2007-2021 VMware, Inc. or its affiliates.  All rights reserved.
6%%
7
8-module(rabbit_guid).
9
10-behaviour(gen_server).
11
12-export([start_link/0]).
13-export([filename/0]).
14-export([gen/0, gen_secure/0, string/2, binary/2, to_string/1]).
15
16-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
17         code_change/3]).
18
19-define(SERVER, ?MODULE).
20-define(SERIAL_FILENAME, "rabbit_serial").
21
22-record(state, {serial}).
23
24%%----------------------------------------------------------------------------
25
26-export_type([guid/0]).
27
28-type guid() :: binary().
29
30%%----------------------------------------------------------------------------
31
32-spec start_link() -> rabbit_types:ok_pid_or_error().
33
34start_link() ->
35    gen_server:start_link({local, ?SERVER}, ?MODULE,
36                          [update_disk_serial()], []).
37
38%% We use this to detect a (possibly rather old) Mnesia directory,
39%% since it has existed since at least 1.7.0 (as far back as I cared
40%% to go).
41
42-spec filename() -> string().
43
44filename() ->
45    filename:join(rabbit_mnesia:dir(), ?SERIAL_FILENAME).
46
47update_disk_serial() ->
48    Filename = filename(),
49    Serial = case rabbit_file:read_term_file(Filename) of
50                 {ok, [Num]}     -> Num;
51                 {ok, []}        -> 0; %% [1]
52                 {error, enoent} -> 0;
53                 {error, Reason} ->
54                     throw({error, {cannot_read_serial_file, Filename, Reason}})
55             end,
56    case rabbit_file:write_term_file(Filename, [Serial + 1]) of
57        ok -> ok;
58        {error, Reason1} ->
59            throw({error, {cannot_write_serial_file, Filename, Reason1}})
60    end,
61    Serial.
62%% [1] a couple of users have reported startup failures due to the
63%% file being empty, presumably as a result of filesystem
64%% corruption. While rabbit doesn't cope with that in general, in this
65%% specific case we can be more accommodating.
66
67%% Generate an un-hashed guid.
68fresh() ->
69    %% We don't use erlang:now() here because a) it may return
70    %% duplicates when the system clock has been rewound prior to a
71    %% restart, or ids were generated at a high rate (which causes
72    %% now() to move ahead of the system time), and b) it is really
73    %% slow since it takes a global lock and makes a system call.
74    %%
75    %% A persisted serial number, the node, and a unique reference
76    %% (per node incarnation) uniquely identifies a process in space
77    %% and time.
78    Serial = gen_server:call(?SERVER, serial, infinity),
79    {Serial, node(), make_ref()}.
80
81advance_blocks({B1, B2, B3, B4}, I) ->
82    %% To produce a new set of blocks, we create a new 32bit block
83    %% hashing {B5, I}. The new hash is used as last block, and the
84    %% other three blocks are XORed with it.
85    %%
86    %% Doing this is convenient because it avoids cascading conflicts,
87    %% while being very fast. The conflicts are avoided by propagating
88    %% the changes through all the blocks at each round by XORing, so
89    %% the only occasion in which a collision will take place is when
90    %% all 4 blocks are the same and the counter is the same.
91    %%
92    %% The range (2^32) is provided explicitly since phash uses 2^27
93    %% by default.
94    B5 = erlang:phash2({B1, I}, 4294967296),
95    {{(B2 bxor B5), (B3 bxor B5), (B4 bxor B5), B5}, I+1}.
96
97%% generate a GUID. This function should be used when performance is a
98%% priority and predictability is not an issue. Otherwise use
99%% gen_secure/0.
100
101-spec gen() -> guid().
102
103gen() ->
104    %% We hash a fresh GUID with md5, split it in 4 blocks, and each
105    %% time we need a new guid we rotate them producing a new hash
106    %% with the aid of the counter. Look at the comments in
107    %% advance_blocks/2 for details.
108    case get(guid) of
109        undefined -> <<B1:32, B2:32, B3:32, B4:32>> = Res =
110                         erlang:md5(term_to_binary(fresh())),
111                     put(guid, {{B1, B2, B3, B4}, 0}),
112                     Res;
113        {BS, I}   -> {{B1, B2, B3, B4}, _} = S = advance_blocks(BS, I),
114                     put(guid, S),
115                     <<B1:32, B2:32, B3:32, B4:32>>
116    end.
117
118%% generate a non-predictable GUID.
119%%
120%% The id is only unique within a single cluster and as long as the
121%% serial store hasn't been deleted.
122%%
123%% If you are not concerned with predictability, gen/0 is faster.
124
125-spec gen_secure() -> guid().
126
127gen_secure() ->
128    %% Here instead of hashing once we hash the GUID and the counter
129    %% each time, so that the GUID is not predictable.
130    G = case get(guid_secure) of
131            undefined -> {fresh(), 0};
132            {S, I}    -> {S, I+1}
133        end,
134    put(guid_secure, G),
135    erlang:md5(term_to_binary(G)).
136
137%% generate a readable string representation of a GUID.
138%%
139%% employs base64url encoding, which is safer in more contexts than
140%% plain base64.
141
142-spec string(guid() | string(), any()) -> string().
143
144string(G, Prefix) when is_list(Prefix) ->
145    Prefix ++ "-" ++ rabbit_misc:base64url(G);
146string(G, Prefix) when is_binary(Prefix) ->
147    binary_to_list(Prefix) ++ "-" ++ rabbit_misc:base64url(G).
148
149-spec binary(guid() | string(), any()) -> binary().
150
151binary(G, Prefix) ->
152    list_to_binary(string(G, Prefix)).
153
154%% copied from https://stackoverflow.com/questions/1657204/erlang-uuid-generator
155to_string(<<TL:32, TM:16, THV:16, CSR:8, CSL:8, N:48>>) ->
156    lists:flatten(
157      io_lib:format("~8.16.0b-~4.16.0b-~4.16.0b-~2.16.0b~2.16.0b-~12.16.0b",
158                    [TL, TM, THV, CSR, CSL, N])).
159
160%%----------------------------------------------------------------------------
161
162init([Serial]) ->
163    {ok, #state{serial = Serial}}.
164
165handle_call(serial, _From, State = #state{serial = Serial}) ->
166    {reply, Serial, State};
167
168handle_call(_Request, _From, State) ->
169    {noreply, State}.
170
171handle_cast(_Msg, State) ->
172    {noreply, State}.
173
174handle_info(_Info, State) ->
175    {noreply, State}.
176
177terminate(_Reason, _State) ->
178    ok.
179
180code_change(_OldVsn, State, _Extra) ->
181    {ok, State}.
182