blob: 6b33731336e788f84f29f09d36317c64c2e32eb1 [file] [log] [blame]
Austin Schuh36244a12019-09-21 17:52:38 -07001// Copyright 2017 The Abseil Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#ifndef ABSL_RANDOM_INTERNAL_RANDEN_ENGINE_H_
16#define ABSL_RANDOM_INTERNAL_RANDEN_ENGINE_H_
17
18#include <algorithm>
19#include <cinttypes>
20#include <cstdlib>
21#include <iostream>
22#include <iterator>
23#include <limits>
24#include <type_traits>
25
26#include "absl/meta/type_traits.h"
27#include "absl/random/internal/iostream_state_saver.h"
28#include "absl/random/internal/randen.h"
29
30namespace absl {
Austin Schuhb4691e92020-12-31 12:37:18 -080031ABSL_NAMESPACE_BEGIN
Austin Schuh36244a12019-09-21 17:52:38 -070032namespace random_internal {
33
34// Deterministic pseudorandom byte generator with backtracking resistance
35// (leaking the state does not compromise prior outputs). Based on Reverie
36// (see "A Robust and Sponge-Like PRNG with Improved Efficiency") instantiated
37// with an improved Simpira-like permutation.
38// Returns values of type "T" (must be a built-in unsigned integer type).
39//
40// RANDen = RANDom generator or beetroots in Swiss High German.
41// 'Strong' (well-distributed, unpredictable, backtracking-resistant) random
42// generator, faster in some benchmarks than std::mt19937_64 and pcg64_c32.
43template <typename T>
44class alignas(16) randen_engine {
45 public:
46 // C++11 URBG interface:
47 using result_type = T;
48 static_assert(std::is_unsigned<result_type>::value,
49 "randen_engine template argument must be a built-in unsigned "
50 "integer type");
51
52 static constexpr result_type(min)() {
53 return (std::numeric_limits<result_type>::min)();
54 }
55
56 static constexpr result_type(max)() {
57 return (std::numeric_limits<result_type>::max)();
58 }
59
60 explicit randen_engine(result_type seed_value = 0) { seed(seed_value); }
61
62 template <class SeedSequence,
63 typename = typename absl::enable_if_t<
64 !std::is_same<SeedSequence, randen_engine>::value>>
65 explicit randen_engine(SeedSequence&& seq) {
66 seed(seq);
67 }
68
69 randen_engine(const randen_engine&) = default;
70
71 // Returns random bits from the buffer in units of result_type.
72 result_type operator()() {
73 // Refill the buffer if needed (unlikely).
74 if (next_ >= kStateSizeT) {
75 next_ = kCapacityT;
76 impl_.Generate(state_);
77 }
78
79 return state_[next_++];
80 }
81
82 template <class SeedSequence>
83 typename absl::enable_if_t<
84 !std::is_convertible<SeedSequence, result_type>::value>
85 seed(SeedSequence&& seq) {
86 // Zeroes the state.
87 seed();
88 reseed(seq);
89 }
90
91 void seed(result_type seed_value = 0) {
92 next_ = kStateSizeT;
93 // Zeroes the inner state and fills the outer state with seed_value to
94 // mimics behaviour of reseed
95 std::fill(std::begin(state_), std::begin(state_) + kCapacityT, 0);
96 std::fill(std::begin(state_) + kCapacityT, std::end(state_), seed_value);
97 }
98
99 // Inserts entropy into (part of) the state. Calling this periodically with
100 // sufficient entropy ensures prediction resistance (attackers cannot predict
101 // future outputs even if state is compromised).
102 template <class SeedSequence>
103 void reseed(SeedSequence& seq) {
104 using sequence_result_type = typename SeedSequence::result_type;
105 static_assert(sizeof(sequence_result_type) == 4,
106 "SeedSequence::result_type must be 32-bit");
107
108 constexpr size_t kBufferSize =
109 Randen::kSeedBytes / sizeof(sequence_result_type);
110 alignas(16) sequence_result_type buffer[kBufferSize];
111
112 // Randen::Absorb XORs the seed into state, which is then mixed by a call
113 // to Randen::Generate. Seeding with only the provided entropy is preferred
114 // to using an arbitrary generate() call, so use [rand.req.seed_seq]
115 // size as a proxy for the number of entropy units that can be generated
116 // without relying on seed sequence mixing...
117 const size_t entropy_size = seq.size();
118 if (entropy_size < kBufferSize) {
119 // ... and only request that many values, or 256-bits, when unspecified.
120 const size_t requested_entropy = (entropy_size == 0) ? 8u : entropy_size;
121 std::fill(std::begin(buffer) + requested_entropy, std::end(buffer), 0);
122 seq.generate(std::begin(buffer), std::begin(buffer) + requested_entropy);
123 // The Randen paper suggests preferentially initializing even-numbered
124 // 128-bit vectors of the randen state (there are 16 such vectors).
125 // The seed data is merged into the state offset by 128-bits, which
126 // implies prefering seed bytes [16..31, ..., 208..223]. Since the
127 // buffer is 32-bit values, we swap the corresponding buffer positions in
128 // 128-bit chunks.
129 size_t dst = kBufferSize;
130 while (dst > 7) {
131 // leave the odd bucket as-is.
132 dst -= 4;
133 size_t src = dst >> 1;
134 // swap 128-bits into the even bucket
135 std::swap(buffer[--dst], buffer[--src]);
136 std::swap(buffer[--dst], buffer[--src]);
137 std::swap(buffer[--dst], buffer[--src]);
138 std::swap(buffer[--dst], buffer[--src]);
139 }
140 } else {
141 seq.generate(std::begin(buffer), std::end(buffer));
142 }
143 impl_.Absorb(buffer, state_);
144
145 // Generate will be called when operator() is called
146 next_ = kStateSizeT;
147 }
148
149 void discard(uint64_t count) {
150 uint64_t step = std::min<uint64_t>(kStateSizeT - next_, count);
151 count -= step;
152
153 constexpr uint64_t kRateT = kStateSizeT - kCapacityT;
154 while (count > 0) {
155 next_ = kCapacityT;
156 impl_.Generate(state_);
157 step = std::min<uint64_t>(kRateT, count);
158 count -= step;
159 }
160 next_ += step;
161 }
162
163 bool operator==(const randen_engine& other) const {
164 return next_ == other.next_ &&
165 std::equal(std::begin(state_), std::end(state_),
166 std::begin(other.state_));
167 }
168
169 bool operator!=(const randen_engine& other) const {
170 return !(*this == other);
171 }
172
173 template <class CharT, class Traits>
174 friend std::basic_ostream<CharT, Traits>& operator<<(
175 std::basic_ostream<CharT, Traits>& os, // NOLINT(runtime/references)
176 const randen_engine<T>& engine) { // NOLINT(runtime/references)
177 using numeric_type =
178 typename random_internal::stream_format_type<result_type>::type;
179 auto saver = random_internal::make_ostream_state_saver(os);
180 for (const auto& elem : engine.state_) {
181 // In the case that `elem` is `uint8_t`, it must be cast to something
182 // larger so that it prints as an integer rather than a character. For
183 // simplicity, apply the cast all circumstances.
184 os << static_cast<numeric_type>(elem) << os.fill();
185 }
186 os << engine.next_;
187 return os;
188 }
189
190 template <class CharT, class Traits>
191 friend std::basic_istream<CharT, Traits>& operator>>(
192 std::basic_istream<CharT, Traits>& is, // NOLINT(runtime/references)
193 randen_engine<T>& engine) { // NOLINT(runtime/references)
194 using numeric_type =
195 typename random_internal::stream_format_type<result_type>::type;
196 result_type state[kStateSizeT];
197 size_t next;
198 for (auto& elem : state) {
199 // It is not possible to read uint8_t from wide streams, so it is
200 // necessary to read a wider type and then cast it to uint8_t.
201 numeric_type value;
202 is >> value;
203 elem = static_cast<result_type>(value);
204 }
205 is >> next;
206 if (is.fail()) {
207 return is;
208 }
209 std::memcpy(engine.state_, state, sizeof(engine.state_));
210 engine.next_ = next;
211 return is;
212 }
213
214 private:
215 static constexpr size_t kStateSizeT =
216 Randen::kStateBytes / sizeof(result_type);
217 static constexpr size_t kCapacityT =
218 Randen::kCapacityBytes / sizeof(result_type);
219
220 // First kCapacityT are `inner', the others are accessible random bits.
221 alignas(16) result_type state_[kStateSizeT];
222 size_t next_; // index within state_
223 Randen impl_;
224};
225
226} // namespace random_internal
Austin Schuhb4691e92020-12-31 12:37:18 -0800227ABSL_NAMESPACE_END
Austin Schuh36244a12019-09-21 17:52:38 -0700228} // namespace absl
229
230#endif // ABSL_RANDOM_INTERNAL_RANDEN_ENGINE_H_