4de069ff6818e1c81b31f32b13b468f7adeeb63e
[quix0rs-apt-p2p.git] / hash.py
1 ## Copyright 2002 Andrew Loewenstern, All Rights Reserved
2
3 from sha import sha
4 import whrandom
5
6 def intify(hstr):
7     """20 bit hash, big-endian -> long python integer"""
8     assert len(hstr) == 20
9     return long(hstr.encode('hex'), 16)
10
11 def stringify(num):
12     """long int -> 20-character string"""
13     str = hex(num)[2:]
14     if str[-1] == 'L':
15         str = str[:-1]
16     if len(str) % 2 != 0:
17         str = '0' + str
18     str = str.decode('hex')
19     return (20 - len(str)) *'\x00' + str
20     
21 def distance(a, b):
22     """distance between two 160-bit hashes expressed as 20-character strings"""
23     return intify(a) ^ intify(b)
24
25
26 def newID():
27     """returns a new pseudorandom globally unique ID string"""
28     h = sha()
29     for i in range(20):
30         h.update(chr(whrandom.randrange(0,256)))
31     return h.digest()
32
33 def newIDInRange(min, max):
34     return stringify(randRange(min,max))
35     
36 def randRange(min, max):
37     return min + intify(newID()) % (max - min)
38
39
40 ### Test Cases ###
41 import unittest
42
43 class NewID(unittest.TestCase):
44     def testLength(self):
45         self.assertEqual(len(newID()), 20)
46     def testHundreds(self):
47         for x in xrange(100):
48             self.testLength
49
50 class Intify(unittest.TestCase):
51     known = [('\0' * 20, 0),
52              ('\xff' * 20, 2L**160 - 1),
53             ]
54     def testKnown(self):
55         for str, value in self.known: 
56             self.assertEqual(intify(str),  value)
57     def testEndianessOnce(self):
58         h = newID()
59         while h[-1] == '\xff':
60             h = newID()
61         k = h[:-1] + chr(ord(h[-1]) + 1)
62         self.assertEqual(intify(k) - intify(h), 1)
63     def testEndianessLots(self):
64         for x in xrange(100):
65             self.testEndianessOnce()
66
67 class Disantance(unittest.TestCase):
68     known = [
69             (("\0" * 20, "\xff" * 20), 2**160L -1),
70             ((sha("foo").digest(), sha("foo").digest()), 0),
71             ((sha("bar").digest(), sha("bar").digest()), 0)
72             ]
73     def testKnown(self):
74         for pair, dist in self.known:
75             self.assertEqual(distance(pair[0], pair[1]), dist)
76     def testCommutitive(self):
77         for i in xrange(100):
78             x, y, z = newID(), newID(), newID()
79             self.assertEqual(distance(x,y) ^ distance(y, z), distance(x, z))
80         
81 class RandRange(unittest.TestCase):
82     def testOnce(self):
83         a = intify(newID())
84         b = intify(newID())
85         if a < b:
86             c = randRange(a, b)
87             self.assertEqual(a <= c < b, 1, "output out of range %d  %d  %d" % (b, c, a))
88         else:
89             c = randRange(b, a)
90             assert b <= c < a, "output out of range %d  %d  %d" % (b, c, a)
91
92     def testOneHundredTimes(self):
93         for i in xrange(100):
94             self.testOnce()
95
96
97
98 if __name__ == '__main__':
99     unittest.main()   
100
101