1# Copyright (C) 2009 Nominum, Inc.
2#
3# Permission to use, copy, modify, and distribute this software and its
4# documentation for any purpose with or without fee is hereby granted,
5# provided that the above copyright notice and this permission notice
6# appear in all copies.
7#
8# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
9# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
11# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
14# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16import os
17import time
18try:
19    import threading as _threading
20except ImportError:
21    import dummy_threading as _threading
22
23class EntropyPool(object):
24    def __init__(self, seed=None):
25        self.pool_index = 0
26        self.digest = None
27        self.next_byte = 0
28        self.lock = _threading.Lock()
29        try:
30            import hashlib
31            self.hash = hashlib.sha1()
32            self.hash_len = 20
33        except:
34            try:
35                import sha
36                self.hash = sha.new()
37                self.hash_len = 20
38            except:
39                import md5
40                self.hash = md5.new()
41                self.hash_len = 16
42        self.pool = '\0' * self.hash_len
43        if not seed is None:
44            self.stir(seed)
45            self.seeded = True
46        else:
47            self.seeded = False
48
49    def stir(self, entropy, already_locked=False):
50        if not already_locked:
51            self.lock.acquire()
52        try:
53            bytes = [ord(c) for c in self.pool]
54            for c in entropy:
55                if self.pool_index == self.hash_len:
56                    self.pool_index = 0
57                b = ord(c) & 0xff
58                bytes[self.pool_index] ^= b
59                self.pool_index += 1
60            self.pool = ''.join([chr(c) for c in bytes])
61        finally:
62            if not already_locked:
63                self.lock.release()
64
65    def _maybe_seed(self):
66        if not self.seeded:
67            try:
68                seed = os.urandom(16)
69            except:
70                try:
71                    r = file('/dev/urandom', 'r', 0)
72                    try:
73                        seed = r.read(16)
74                    finally:
75                        r.close()
76                except:
77                    seed = str(time.time())
78            self.seeded = True
79            self.stir(seed, True)
80
81    def random_8(self):
82        self.lock.acquire()
83        self._maybe_seed()
84        try:
85            if self.digest is None or self.next_byte == self.hash_len:
86                self.hash.update(self.pool)
87                self.digest = self.hash.digest()
88                self.stir(self.digest, True)
89                self.next_byte = 0
90            value = ord(self.digest[self.next_byte])
91            self.next_byte += 1
92        finally:
93            self.lock.release()
94        return value
95
96    def random_16(self):
97        return self.random_8() * 256 + self.random_8()
98
99    def random_32(self):
100        return self.random_16() * 65536 + self.random_16()
101
102    def random_between(self, first, last):
103        size = last - first + 1
104        if size > 4294967296L:
105            raise ValueError('too big')
106        if size > 65536:
107            rand = self.random_32
108            max = 4294967295L
109        elif size > 256:
110            rand = self.random_16
111            max = 65535
112        else:
113            rand = self.random_8
114            max = 255
115	return (first + size * rand() // (max + 1))
116
117pool = EntropyPool()
118
119def random_16():
120    return pool.random_16()
121
122def between(first, last):
123    return pool.random_between(first, last)
124