fd9d4f8cdf8ce5e1f5cc122e3dda67ef6cc2245b
[kai/samba-autobuild/.git] / lib / dnspython / dns / entropy.py
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
16 import os
17 import time
18 try:
19     import threading as _threading
20 except ImportError:
21     import dummy_threading as _threading
22
23 class 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
117 pool = EntropyPool()
118
119 def random_16():
120     return pool.random_16()
121
122 def between(first, last):
123     return pool.random_between(first, last)