File 2873-Implement-rand-bytes-1-and-rand-bytes_s-2.patch of Package erlang

From c0fbb37cd4e8220f8d331eeb3eac540fd7f20470 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen <raimo@erlang.org>
Date: Fri, 7 Aug 2020 17:12:07 +0200
Subject: [PATCH 3/6] Implement rand:bytes/1 and rand:bytes_s/2

---
 lib/stdlib/doc/src/rand.xml    |  29 +++++
 lib/stdlib/src/rand.erl        |  57 ++++++++++
 lib/stdlib/test/rand_SUITE.erl | 197 ++++++++++++++++++++++++++-------
 3 files changed, 245 insertions(+), 38 deletions(-)

diff --git a/lib/stdlib/doc/src/rand.xml b/lib/stdlib/doc/src/rand.xml
index 0eed1c2e49..9e3c628920 100644
--- a/lib/stdlib/doc/src/rand.xml
+++ b/lib/stdlib/doc/src/rand.xml
@@ -350,6 +350,35 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
   </datatypes>
 
   <funcs>
+    <func>
+      <name name="bytes" arity="1" since="OTP 24.0"/>
+      <fsummary>Return a random binary.</fsummary>
+      <desc><marker id="bytes-1"/>
+      <p>
+        Returns, for a specified integer <c><anno>N</anno> >= 0</c>,
+        a <c>binary()</c> with that number of random bytes.
+        Generates as many random numbers as required using
+        the selected algorithm to compose the binary,
+        and updates the state in the process dictionary accordingly.
+      </p>
+      </desc>
+    </func>
+
+    <func>
+      <name name="bytes_s" arity="2" since="OTP 24.0"/>
+      <fsummary>Return a random binary.</fsummary>
+      <desc><marker id="bytes-1"/>
+      <p>
+        Returns, for a specified integer <c><anno>N</anno> >= 0</c>
+        and a state, a <c>binary()</c> with that number of random bytes,
+        and a new state.
+        Generates as many random numbers as required using
+        the selected algorithm to compose the binary,
+        and the new state.
+      </p>
+      </desc>
+    </func>
+
     <func>
       <name name="export_seed" arity="0" since="OTP 18.0"/>
       <fsummary>Export the random number generation state.</fsummary>
diff --git a/lib/stdlib/src/rand.erl b/lib/stdlib/src/rand.erl
index 1de78a23ac..d5906d3730 100644
--- a/lib/stdlib/src/rand.erl
+++ b/lib/stdlib/src/rand.erl
@@ -31,6 +31,7 @@
 	 export_seed/0, export_seed_s/1,
          uniform/0, uniform/1, uniform_s/1, uniform_s/2,
          uniform_real/0, uniform_real_s/1,
+         bytes/1, bytes_s/2,
          jump/0, jump/1,
          normal/0, normal/2, normal_s/1, normal_s/3
 	]).
@@ -539,6 +540,62 @@ uniform_real_s(#{max:=_} = AlgHandler, Next, M0, BitNo, R0) ->
     {V1, R1} = Next(R0),
     uniform_real_s(AlgHandler, Next, M0, BitNo, R1, ?MASK(56, V1), 56).
 
+
+%% bytes/1: given a number N,
+%% returns a random binary with N bytes
+
+-spec bytes(N :: non_neg_integer()) -> Bytes :: binary().
+bytes(N) ->
+    {Bytes, State} = bytes_s(N, seed_get()),
+    _ = seed_put(State),
+    Bytes.
+
+
+%% bytes_s/2: given a number N and a state,
+%% returns a random binary with N bytes and a new state
+
+-spec bytes_s(N :: non_neg_integer(), State :: state()) ->
+                     {Bytes :: binary(), NewState :: state()}.
+bytes_s(N, {#{bits:=Bits, next:=Next} = AlgHandler, R})
+  when is_integer(N), 0 =< N ->
+    WeakLowBits = maps:get(weak_low_bits, AlgHandler, 0),
+    bytes_r(N, AlgHandler, Next, R, Bits, WeakLowBits);
+bytes_s(N, {#{max:=Mask, next:=Next} = AlgHandler, R})
+  when is_integer(N), 0 =< N, ?MASK(58) =< Mask ->
+    %% Old spec - assume 58 bits and 2 weak low bits
+    %% giving 56 bits i.e precisely 7 bytes per generated number
+    Bits = 58,
+    WeakLowBits = 2,
+    bytes_r(N, AlgHandler, Next, R, Bits, WeakLowBits).
+
+%% N:           Number of bytes to generate
+%% Bits:        Number of bits in the generated word
+%% WeakLowBits: Number of low bits in the generated word
+%%              to waste due to poor quality
+bytes_r(N, AlgHandler, Next, R, Bits, WeakLowBits) ->
+    %% We use whole bytes from each generator word,
+    %% GoodBytes: that number of bytes
+    GoodBytes = (Bits - WeakLowBits) bsr 3,
+    GoodBits = GoodBytes bsl 3,
+    %% Shift: how many bits of each generator word to waste
+    %% by shifting right - we use the bits from the big end
+    Shift = Bits - GoodBits,
+    bytes_r(N, AlgHandler, Next, R, <<>>, GoodBytes, GoodBits, Shift).
+%%
+bytes_r(N0, AlgHandler, Next, R0, Bytes0, GoodBytes, GoodBits, Shift)
+  when GoodBytes < N0 ->
+    {V, R1} = Next(R0),
+    Bytes1 = <<Bytes0/binary, (V bsr Shift):GoodBits>>,
+    N1 = N0 - GoodBytes,
+    bytes_r(N1, AlgHandler, Next, R1, Bytes1, GoodBytes, GoodBits, Shift);
+bytes_r(N, AlgHandler, Next, R0, Bytes, _GoodBytes, GoodBits, _Shift) ->
+    {V, R1} = Next(R0),
+    Bits = N bsl 3,
+    %% Use the big end bits
+    Shift = GoodBits - Bits,
+    {<<Bytes/binary, (V bsr Shift):Bits>>, {AlgHandler, R1}}.
+
+
 %% jump/1: given a state, jump/1
 %% returns a new state which is equivalent to that
 %% after a large number of call defined for each algorithm.
diff --git a/lib/stdlib/test/rand_SUITE.erl b/lib/stdlib/test/rand_SUITE.erl
index 230020857f..207cff00e5 100644
--- a/lib/stdlib/test/rand_SUITE.erl
+++ b/lib/stdlib/test/rand_SUITE.erl
@@ -33,6 +33,7 @@ suite() ->
 
 all() ->
     [seed, interval_int, interval_float,
+     bytes_count,
      api_eq,
      reference,
      {group, basic_stats},
@@ -45,7 +46,7 @@ all() ->
 
 groups() ->
     [{basic_stats, [parallel],
-      [basic_stats_uniform_1, basic_stats_uniform_2,
+      [basic_stats_uniform_1, basic_stats_uniform_2, basic_stats_bytes,
        basic_stats_standard_normal]},
      {distr_stats, [parallel],
       [stats_standard_normal_box_muller,
@@ -81,6 +82,17 @@ test() ->
 algs() ->
     [exsss, exrop, exsp, exs1024s, exs64, exsplus, exs1024, exro928ss].
 
+crypto_support() ->
+    try crypto:strong_rand_bytes(1) of
+        <<_>> ->
+            ok
+    catch
+        error : low_entropy ->
+            low_entropy;
+        error : undef ->
+            no_crypto
+    end.
+
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
 %% Test that seed and seed_s and export_seed/0 is working.
@@ -164,7 +176,9 @@ api_eq_1(S00) ->
 		    V1 = rand:uniform(1000000),
 		    {V2, S2} = rand:normal_s(S1),
 		    V2 = rand:normal(),
-		    S2
+                    B3 = rand:bytes(64),
+                    {B3, S3} = rand:bytes_s(64, S2),
+		    S3
 	    end,
     S1 = lists:foldl(Check, S00, lists:seq(1, 200)),
     S1 = get(rand_seed),
@@ -251,6 +265,25 @@ interval_float_1(N) ->
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
+%% Check that bytes/1 and bytes_s/2 generates
+%% the right number of bytes
+
+bytes_count(Config) when is_list(Config) ->
+    Algs = [default|algs()],
+    Counts = lists:seq(0, 255),
+    [begin
+         _ = rand:seed(Alg),
+         [begin
+              ExportState = rand:export_seed(),
+              B = rand:bytes(N),
+              {B, _NewState} = rand:bytes_s(N, rand:seed_s(ExportState)),
+              N = byte_size(B)
+          end || N <- Counts]
+     end || Alg <- Algs],
+    ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
 %% Check if each algorithm generates the proper sequence.
 reference(Config) when is_list(Config) ->
     [reference_1(Alg) || Alg <- algs()],
@@ -327,6 +360,13 @@ basic_stats_uniform_2(Config) when is_list(Config) ->
      || Alg <- [default|algs()]],
     ok.
 
+basic_stats_bytes(Config) when is_list(Config) ->
+    ct:timetrap({minutes,15}), %% valgrind needs a lot of time
+    [basic_bytes(
+       ?LOOP div 100, rand:seed_s(Alg), 0, array:new(256, [{default, 0}]))
+     || Alg <- [default|algs()]],
+    ok.
+
 basic_stats_standard_normal(Config) when is_list(Config) ->
     ct:timetrap({minutes,6}), %% valgrind needs a lot of time
     io:format("Testing standard normal~n",[]),
@@ -357,6 +397,7 @@ basic_stats_normal(Config) when is_list(Config) ->
       end,
       IntendedMeanVariancePairs).
 
+
 basic_uniform_1(N, S0, Sum, A0) when N > 0 ->
     {X,S} =
         case N band 1 of
@@ -369,39 +410,78 @@ basic_uniform_1(N, S0, Sum, A0) when N > 0 ->
     A = array:set(I, 1+array:get(I,A0), A0),
     basic_uniform_1(N-1, S, Sum+X, A);
 basic_uniform_1(0, {#{type:=Alg}, _}, Sum, A) ->
-    AverN = Sum / ?LOOP,
-    io:format("~.12w: Average: ~.4f~n", [Alg, AverN]),
+    Loop = ?LOOP,
+    AverExp = 1.0 / 2,
+    Buckets = 100,
     Counters = array:to_list(A),
     Min = lists:min(Counters),
     Max = lists:max(Counters),
-    io:format("~.12w: Min: ~p Max: ~p~n", [Alg, Min, Max]),
-
-    %% Verify that the basic statistics are ok
-    %% be gentle we don't want to see to many failing tests
-    abs(0.5 - AverN) < 0.005 orelse ct:fail({average, Alg, AverN}),
-    abs(?LOOP div 100 - Min) < 1000 orelse ct:fail({min, Alg, Min}),
-    abs(?LOOP div 100 - Max) < 1000 orelse ct:fail({max, Alg, Max}),
-    ok.
+    basic_verify(Alg, Loop, Sum, AverExp, Buckets, Min, Max).
 
 basic_uniform_2(N, S0, Sum, A0) when N > 0 ->
     {X,S} = rand:uniform_s(100, S0),
     A = array:set(X-1, 1+array:get(X-1,A0), A0),
     basic_uniform_2(N-1, S, Sum+X, A);
 basic_uniform_2(0, {#{type:=Alg}, _}, Sum, A) ->
-    AverN = Sum / ?LOOP,
-    io:format("~.12w: Average: ~.4f~n", [Alg, AverN]),
+    Loop = ?LOOP,
+    AverExp = ((100 - 1) / 2) + 1,
+    Buckets = 100,
     Counters = tl(array:to_list(A)),
     Min = lists:min(Counters),
     Max = lists:max(Counters),
-    io:format("~.12w: Min: ~p Max: ~p~n", [Alg, Min, Max]),
+    basic_verify(Alg, Loop, Sum, AverExp, Buckets, Min, Max).
+
+basic_bytes(N, S0, Sum0, A0) when N > 0 ->
+    ByteSize = 100,
+    {Bin,S} = rand:bytes_s(ByteSize, S0),
+    {Sum,A} = basic_bytes_incr(Bin, Sum0, A0),
+    basic_bytes(N-1, S, Sum, A);
+basic_bytes(0, {#{type:=Alg}, _}, Sum, A) ->
+    ByteSize = 100,
+    Loop = (?LOOP * ByteSize) div 100,
+    Buckets = 256,
+    AverExp = (Buckets - 1) / 2,
+    Counters = array:to_list(A),
+    Min = lists:min(Counters),
+    Max = lists:max(Counters),
+    basic_verify(Alg, Loop, Sum, AverExp, Buckets, Min, Max).
 
+basic_bytes_incr(Bin, Sum, A) ->
+    basic_bytes_incr(Bin, Sum, A, 0).
+%%
+basic_bytes_incr(Bin, Sum, A, N) ->
+    case Bin of
+        <<_:N/binary, B, _/binary>> ->
+            basic_bytes_incr(
+              Bin, Sum+B, array:set(B, array:get(B, A)+1, A), N+1);
+        <<_/binary>> ->
+            {Sum,A}
+    end.
+
+basic_verify(Alg, Loop, Sum, AverExp, Buckets, Min, Max) ->
+    AverDiff = AverExp * 0.01,
+    Aver = Sum / Loop,
+    io:format(
+      "~.12w: Expected Average: ~.4f, Allowed Diff: ~.4f, Average: ~.4f~n",
+      [Alg, AverExp, AverDiff, Aver]),
+    %%
+    CountExp = Loop / Buckets,
+    CountDiff = CountExp * 0.1,
+    io:format(
+      "~.12w: Expected Count: ~p, Allowed Diff: ~p, Min: ~p, Max: ~p~n",
+      [Alg, CountExp, CountDiff, Min, Max]),
+    %%
     %% Verify that the basic statistics are ok
     %% be gentle we don't want to see to many failing tests
-    abs(50.5 - AverN) < 0.5 orelse ct:fail({average, Alg, AverN}),
-    abs(?LOOP div 100 - Min) < 1000 orelse ct:fail({min, Alg, Min}),
-    abs(?LOOP div 100 - Max) < 1000 orelse ct:fail({max, Alg, Max}),
+    abs(Aver - AverExp) < AverDiff orelse
+        ct:fail({average, Alg, Aver, AverExp, AverDiff}),
+    abs(Min - CountExp) < CountDiff orelse
+        ct:fail({min, Alg, Min, CountExp, CountDiff}),
+    abs(Max - CountExp) < CountDiff orelse
+        ct:fail({max, Alg, Max, CountExp, CountDiff}),
     ok.
 
+
 basic_normal_1(N, IntendedMean, IntendedVariance, S0, StandardSum, StandardSq) when N > 0 ->
     {X,S} = normal_s(IntendedMean, IntendedVariance, S0),
     % We now shape X into a standard normal distribution (in case it wasn't already)
@@ -417,6 +497,7 @@ basic_normal_1(0, _IntendedMean, _IntendedVariance, {#{type:=Alg}, _}, StandardS
     StandardStdDev =  math:sqrt(StandardVariance),
     io:format("~.12w: Standardised Average: ~7.4f, Standardised StdDev ~6.4f~n",
               [Alg, StandardMean, StandardStdDev]),
+    %%
     %% Verify that the basic statistics are ok
     %% be gentle we don't want to see to many failing tests
     abs(StandardMean) < 0.005 orelse ct:fail({average, Alg, StandardMean}),
@@ -779,8 +860,8 @@ rand_state(Gen) ->
 
 %% Test that the user can write algorithms.
 plugin(Config) when is_list(Config) ->
-    try crypto:strong_rand_bytes(1) of
-        <<_>> ->
+    case crypto_support() of
+        ok ->
             _ = lists:foldl(
                   fun(_, S0) ->
                           {V1, S1} = rand:uniform_s(10000, S0),
@@ -789,12 +870,9 @@ plugin(Config) when is_list(Config) ->
                           true = is_float(V2),
                           S2
                   end, crypto64_seed(), lists:seq(1, 200)),
-            ok
-    catch
-        error:low_entropy ->
-            {skip,low_entropy};
-        error:undef ->
-            {skip,no_crypto}
+            ok;
+        Problem ->
+            {skip,Problem}
     end.
 
 %% Test implementation
@@ -855,16 +933,19 @@ measure(Config) ->
             {(X), (St)} when is_float(X) ->
                 St
         end).
+-define(CHECK_BYTE_SIZE(Gen, Size, Bin, St),
+        case (Gen) of
+            {(Bin), (St)} when byte_size(Bin) =:= (Size) ->
+                St
+        end).
 
 do_measure(_Config) ->
     Algs =
-        algs() ++
-        try crypto:strong_rand_bytes(1) of
-            <<_>> ->
-		[crypto64, crypto_cache, crypto_aes, crypto]
-        catch
-            error:low_entropy -> [];
-            error:undef -> []
+        case crypto_support() of
+            ok ->
+                algs() ++ [crypto64, crypto_cache, crypto_aes, crypto];
+            _ ->
+                algs()
         end,
     %%
     ct:pal("~nRNG uniform integer range 10000 performance~n",[]),
@@ -1021,6 +1102,27 @@ do_measure(_Config) ->
           end,
           Algs),
     %%
+    ByteSize = 16, % At about 100 bytes crypto_bytes breaks even to exsss
+    ct:pal("~nRNG ~w bytes performance~n",[ByteSize]),
+    _ =
+        measure_1(
+          fun (_) -> ByteSize end,
+          fun (State, Size, Mod) ->
+                  measure_loop(
+                    fun (St0) ->
+                            ?CHECK_BYTE_SIZE(
+                               Mod:bytes_s(Size, St0), Size,
+                               Bin, St1)
+                    end,
+                    State)
+          end,
+          case crypto_support() of
+              ok ->
+                  Algs ++ [crypto_bytes, crypto_bytes_cached];
+              _ ->
+                  Algs
+          end),
+    %%
     ct:pal("~nRNG uniform float performance~n",[]),
     _ =
         measure_1(
@@ -1116,6 +1218,10 @@ measure_1(RangeFun, Fun, Alg, TMark) ->
 		   crypto_aes, crypto:strong_rand_bytes(256))};
             random ->
                 {random, random:seed(os:timestamp()), get(random_seed)};
+            crypto_bytes ->
+                {?MODULE, ignored_state};
+            crypto_bytes_cached ->
+                {?MODULE, <<>>};
             _ ->
                 {rand, rand:seed_s(Alg)}
         end,
@@ -1139,6 +1245,21 @@ measure_1(RangeFun, Fun, Alg, TMark) ->
 	{Pid, Msg} -> Msg
     end.
 
+%% Comparison algorithm for rand:bytes_s/2 vs. crypto:strong_rand_bytes/1
+bytes_s(N, Cache) when is_binary(Cache) ->
+    %% crypto_bytes_cached
+    case Cache of
+        <<Bytes:N/binary, Rest/binary>> ->
+            {Bytes, Rest};
+        <<Part/binary>> ->
+            <<Bytes:N/binary, Rest/binary>> =
+                <<Part/binary, (crypto:strong_rand_bytes(N * 16))/binary>>,
+            {Bytes, Rest}
+    end;
+bytes_s(N, ignored_state = St) ->
+    %% crypto_bytes
+    {crypto:strong_rand_bytes(N), St}.
+
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %% The jump sequence tests has two parts
 %% for those with the functional API (jump/1)
@@ -1268,16 +1389,16 @@ short_jump(Config) when is_list(Config) ->
       fun ({Alg,AlgState}) ->
 	      {Alg,rand:exro928_jump_2pow20(AlgState)}
       end),
-    try crypto:strong_rand_bytes(1) of
-        _ ->
+    case crypto_support() of
+        ok ->
             short_jump(
               crypto:rand_seed_alg_s(crypto_aes, integer_to_list(Seed)),
               fun ({Alg,AlgState}) ->
                       {Alg,crypto:rand_plugin_aes_jump_2pow20(AlgState)}
               end),
-            ok
-    catch error:undef ->
-            {skip,no_crypto}
+            ok;
+        Problem ->
+            {skip,Problem}
     end.
 
 short_jump({#{bits := Bits},_} = State_0, Jump2Pow20) ->
-- 
2.26.2

openSUSE Build Service is sponsored by