xref: /3.0.3-GA/platform/src/random.cc (revision efb25dad)
1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2014 Couchbase, Inc
4 *
5 *   Licensed under the Apache License, Version 2.0 (the "License");
6 *   you may not use this file except in compliance with the License.
7 *   You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *   Unless required by applicable law or agreed to in writing, software
12 *   distributed under the License is distributed on an "AS IS" BASIS,
13 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *   See the License for the specific language governing permissions and
15 *   limitations under the License.
16 */
17#include "config.h"
18
19#include <errno.h>
20#include <cstring>
21#include <platform/platform.h>
22#include <platform/random.h>
23#include <sstream>
24#include <stdexcept>
25
26namespace Couchbase {
27   class RandomGeneratorProvider {
28
29   public:
30      RandomGeneratorProvider() {
31         if (cb_rand_open(&provider) == -1) {
32            std::stringstream ss;
33            std::string reason;
34
35#ifdef WIN32
36            DWORD err = GetLastError();
37            char* win_msg = NULL;
38            FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
39                           FORMAT_MESSAGE_FROM_SYSTEM |
40                           FORMAT_MESSAGE_IGNORE_INSERTS,
41                           NULL, err,
42                           MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
43                           (LPTSTR)&win_msg,
44                           0, NULL);
45            reason.assign(win_msg);
46            LocalFree(win_msg);
47#else
48            reason.assign(strerror(errno));
49#endif
50            ss << "Failed to initialize random generator: " << reason;
51            throw std::runtime_error(ss.str());
52         }
53      }
54
55      virtual ~RandomGeneratorProvider() {
56         (void)cb_rand_close(provider);
57      }
58
59      virtual bool getBytes(void *dest, size_t size) {
60         return cb_rand_get(provider, dest, size) == 0;
61      }
62
63   protected:
64      cb_rand_t provider;
65   };
66
67   class SharedRandomGeneratorProvider : public RandomGeneratorProvider {
68   public:
69      SharedRandomGeneratorProvider() {
70         cb_mutex_initialize(&mutex);
71      }
72
73      ~SharedRandomGeneratorProvider() {
74         cb_mutex_destroy(&mutex);
75      }
76
77      virtual bool getBytes(void *dest, size_t size) {
78         bool ret;
79         cb_mutex_enter(&mutex);
80         ret = RandomGeneratorProvider::getBytes(dest, size);
81         cb_mutex_exit(&mutex);
82         return ret;
83      }
84
85   private:
86      cb_mutex_t mutex;
87   };
88
89   /*
90    * I don't want all processes that link with platform to open a
91    * random generator, so let's use the runtime linker to create
92    * a class that initialize the mutex so that I can have a safe
93    * way of just creating one (and only one) instance of the
94    * the shared generator
95    */
96   class SharedRandomGeneratorSingleton {
97   public:
98      SharedRandomGeneratorSingleton() : instance(NULL) {
99         cb_mutex_initialize(&mutex);
100      }
101
102      ~SharedRandomGeneratorSingleton() {
103         cb_mutex_destroy(&mutex);
104      }
105
106      SharedRandomGeneratorProvider *get() {
107         cb_mutex_enter(&mutex);
108         if (instance == NULL) {
109            instance = new SharedRandomGeneratorProvider();
110         }
111         cb_mutex_exit(&mutex);
112         return instance;
113      }
114
115   private:
116      cb_mutex_t mutex;
117      SharedRandomGeneratorProvider *instance;
118   };
119}
120
121static Couchbase::SharedRandomGeneratorSingleton shrgen;
122
123PLATFORM_PUBLIC_API
124Couchbase::RandomGenerator::RandomGenerator(bool s) : shared(s) {
125   if (shared) {
126      provider = shrgen.get();
127   } else {
128      provider = new RandomGeneratorProvider();
129   }
130}
131
132PLATFORM_PUBLIC_API
133Couchbase::RandomGenerator::~RandomGenerator() {
134   if (!shared) {
135      delete provider;
136   }
137}
138
139PLATFORM_PUBLIC_API
140uint64_t Couchbase::RandomGenerator::next(void) {
141   uint64_t ret;
142   if (provider->getBytes(&ret, sizeof(ret))) {
143      return ret;
144   }
145
146   return gethrtime();
147}
148
149PLATFORM_PUBLIC_API
150bool Couchbase::RandomGenerator::getBytes(void *dest, size_t size) {
151   return provider->getBytes(dest, size);
152}
153
154PLATFORM_PUBLIC_API
155const Couchbase::RandomGeneratorProvider *Couchbase::RandomGenerator::getProvider(void) const {
156   return provider;
157}
158