xref: /4.0.0/platform/src/random.cc (revision c0012dd1)
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            if (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) > 0) {
45                reason.assign(win_msg);
46                LocalFree(win_msg);
47            } else {
48                reason.assign("Failed to determine error cause");
49            }
50#else
51            reason.assign(strerror(errno));
52#endif
53            ss << "Failed to initialize random generator: " << reason;
54            throw std::runtime_error(ss.str());
55         }
56      }
57
58      virtual ~RandomGeneratorProvider() {
59         (void)cb_rand_close(provider);
60      }
61
62      virtual bool getBytes(void *dest, size_t size) {
63         return cb_rand_get(provider, dest, size) == 0;
64      }
65
66   protected:
67      cb_rand_t provider;
68   };
69
70   class SharedRandomGeneratorProvider : public RandomGeneratorProvider {
71   public:
72      SharedRandomGeneratorProvider() {
73         cb_mutex_initialize(&mutex);
74      }
75
76      ~SharedRandomGeneratorProvider() {
77         cb_mutex_destroy(&mutex);
78      }
79
80      virtual bool getBytes(void *dest, size_t size) {
81         bool ret;
82         cb_mutex_enter(&mutex);
83         ret = RandomGeneratorProvider::getBytes(dest, size);
84         cb_mutex_exit(&mutex);
85         return ret;
86      }
87
88   private:
89      cb_mutex_t mutex;
90   };
91
92   /*
93    * I don't want all processes that link with platform to open a
94    * random generator, so let's use the runtime linker to create
95    * a class that initialize the mutex so that I can have a safe
96    * way of just creating one (and only one) instance of the
97    * the shared generator
98    */
99   class SharedRandomGeneratorSingleton {
100   public:
101      SharedRandomGeneratorSingleton() : instance(NULL) {
102         cb_mutex_initialize(&mutex);
103      }
104
105      ~SharedRandomGeneratorSingleton() {
106         cb_mutex_destroy(&mutex);
107      }
108
109      SharedRandomGeneratorProvider *get() {
110         cb_mutex_enter(&mutex);
111         if (instance == NULL) {
112            instance = new SharedRandomGeneratorProvider();
113         }
114         cb_mutex_exit(&mutex);
115         return instance;
116      }
117
118   private:
119      cb_mutex_t mutex;
120      SharedRandomGeneratorProvider *instance;
121   };
122}
123
124static Couchbase::SharedRandomGeneratorSingleton shrgen;
125
126PLATFORM_PUBLIC_API
127Couchbase::RandomGenerator::RandomGenerator(bool s) : shared(s) {
128   if (shared) {
129      provider = shrgen.get();
130   } else {
131      provider = new RandomGeneratorProvider();
132   }
133}
134
135PLATFORM_PUBLIC_API
136Couchbase::RandomGenerator::~RandomGenerator() {
137   if (!shared) {
138      delete provider;
139   }
140}
141
142PLATFORM_PUBLIC_API
143uint64_t Couchbase::RandomGenerator::next(void) {
144   uint64_t ret;
145   if (provider->getBytes(&ret, sizeof(ret))) {
146      return ret;
147   }
148
149   return gethrtime();
150}
151
152PLATFORM_PUBLIC_API
153bool Couchbase::RandomGenerator::getBytes(void *dest, size_t size) {
154   return provider->getBytes(dest, size);
155}
156
157PLATFORM_PUBLIC_API
158const Couchbase::RandomGeneratorProvider *Couchbase::RandomGenerator::getProvider(void) const {
159   return provider;
160}
161