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