diff options
Diffstat (limited to 'cpp/src/IceUtil/Random.cpp')
-rw-r--r-- | cpp/src/IceUtil/Random.cpp | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/cpp/src/IceUtil/Random.cpp b/cpp/src/IceUtil/Random.cpp new file mode 100644 index 00000000000..fafe22fd454 --- /dev/null +++ b/cpp/src/IceUtil/Random.cpp @@ -0,0 +1,188 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2011 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +#if defined(_MSC_VER) && (_MSC_VER > 1400) +# define _CRT_RAND_S +#endif + +#include <IceUtil/Random.h> +#include <IceUtil/Mutex.h> +#include <IceUtil/MutexPtrLock.h> + +#ifdef _WIN32 +# include <Wincrypt.h> +#else +# include <unistd.h> +# include <fcntl.h> +#endif + +using namespace std; +using namespace IceUtil; + +#if !defined(_WIN32) || !defined(_MSC_VER) || (_MSC_VER < 1400) +namespace +{ + +// +// The static mutex is required to lazy initialize the file +// descriptor for /dev/urandom (Unix) or the cryptographic +// context (Windows). +// +// Also, unfortunately on Linux (at least up to 2.6.9), concurrent +// access to /dev/urandom can return the same value. Search for +// "Concurrent access to /dev/urandom" in the linux-kernel mailing +// list archive for additional details. Since /dev/urandom on other +// platforms is usually a port from Linux, this problem could be +// widespread. Therefore, we serialize access to /dev/urandom using a +// static mutex. +// +Mutex* staticMutex = 0; +#ifdef _WIN32 +HCRYPTPROV context = NULL; +#else +int fd = -1; +#endif + +class Init +{ +public: + + Init() + { + staticMutex = new IceUtil::Mutex; + } + + ~Init() + { +#ifdef _WIN32 + if(context != NULL) + { + CryptReleaseContext(context, 0); + context = NULL; + } +#else + if(fd != -1) + { + close(fd); + fd = -1; + } +#endif + delete staticMutex; + staticMutex = 0; + } +}; + +Init init; + +} +#endif + +void +IceUtilInternal::generateRandom(char* buffer, int size) +{ +#ifdef _WIN32 + +# if defined(_MSC_VER) && (_MSC_VER >= 1400) + for(int i = 0; i < size; ++i) + { + buffer[i] = random(256); + } +# else + // + // It's not clear from the Microsoft documentation if CryptGenRandom + // can be called concurrently from several threads. To be on the safe + // side, we also serialize calls to to CryptGenRandom with the static + // mutex. + // + + IceUtilInternal::MutexPtrLock<IceUtil::Mutex> lock(staticMutex); + if(context == NULL) + { + if(!CryptAcquireContext(&context, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + { + throw SyscallException(__FILE__, __LINE__, GetLastError()); + } + } + + if(!CryptGenRandom(context, size, reinterpret_cast<unsigned char*>(buffer))) + { + throw SyscallException(__FILE__, __LINE__, GetLastError()); + } +# endif + +#else + + // + // Serialize access to /dev/urandom; see comment above. + // + IceUtilInternal::MutexPtrLock<IceUtil::Mutex> lock(staticMutex); + if(fd == -1) + { + fd = open("/dev/urandom", O_RDONLY); + if(fd == -1) + { + assert(0); + throw SyscallException(__FILE__, __LINE__, errno); + } + } + + // + // Limit the number of attempts to 20 reads to avoid + // a potential "for ever" loop + // + int reads = 0; + size_t index = 0; + while(reads <= 20 && index != static_cast<size_t>(size)) + { + ssize_t bytesRead = read(fd, buffer + index, static_cast<size_t>(size) - index); + + if(bytesRead == -1 && errno != EINTR) + { + SyscallException ex(__FILE__, __LINE__, errno); + cerr << "Reading /dev/urandom failed:\n" << ex << endl; + assert(0); + throw ex; + } + else + { + index += bytesRead; + reads++; + } + } + + if(index != static_cast<size_t>(size)) + { + assert(0); + throw SyscallException(__FILE__, __LINE__, 0); + } +#endif +} + +unsigned int +IceUtilInternal::random(int limit) +{ + unsigned int r; +#if defined(_MSC_VER) && (_MSC_VER > 1400) + errno_t err = rand_s(&r); + if(err != 0) + { + SyscallException ex(__FILE__, __LINE__, errno); + cerr << "rand_s failed:\n" << ex << endl; + assert(0); + throw ex; + } +#else + generateRandom(reinterpret_cast<char*>(&r), static_cast<unsigned int>(sizeof(unsigned int))); +#endif + if(limit > 0) + { + r = r % limit; + } + return r; +} |