OLD | NEW |
| (Empty) |
1 """Class for caching TLS sessions.""" | |
2 | |
3 import thread | |
4 import time | |
5 | |
6 class SessionCache: | |
7 """This class is used by the server to cache TLS sessions. | |
8 | |
9 Caching sessions allows the client to use TLS session resumption | |
10 and avoid the expense of a full handshake. To use this class, | |
11 simply pass a SessionCache instance into the server handshake | |
12 function. | |
13 | |
14 This class is thread-safe. | |
15 """ | |
16 | |
17 #References to these instances | |
18 #are also held by the caller, who may change the 'resumable' | |
19 #flag, so the SessionCache must return the same instances | |
20 #it was passed in. | |
21 | |
22 def __init__(self, maxEntries=10000, maxAge=14400): | |
23 """Create a new SessionCache. | |
24 | |
25 @type maxEntries: int | |
26 @param maxEntries: The maximum size of the cache. When this | |
27 limit is reached, the oldest sessions will be deleted as | |
28 necessary to make room for new ones. The default is 10000. | |
29 | |
30 @type maxAge: int | |
31 @param maxAge: The number of seconds before a session expires | |
32 from the cache. The default is 14400 (i.e. 4 hours).""" | |
33 | |
34 self.lock = thread.allocate_lock() | |
35 | |
36 # Maps sessionIDs to sessions | |
37 self.entriesDict = {} | |
38 | |
39 #Circular list of (sessionID, timestamp) pairs | |
40 self.entriesList = [(None,None)] * maxEntries | |
41 | |
42 self.firstIndex = 0 | |
43 self.lastIndex = 0 | |
44 self.maxAge = maxAge | |
45 | |
46 def __getitem__(self, sessionID): | |
47 self.lock.acquire() | |
48 try: | |
49 self._purge() #Delete old items, so we're assured of a new one | |
50 session = self.entriesDict[sessionID] | |
51 | |
52 #When we add sessions they're resumable, but it's possible | |
53 #for the session to be invalidated later on (if a fatal alert | |
54 #is returned), so we have to check for resumability before | |
55 #returning the session. | |
56 | |
57 if session.valid(): | |
58 return session | |
59 else: | |
60 raise KeyError() | |
61 finally: | |
62 self.lock.release() | |
63 | |
64 | |
65 def __setitem__(self, sessionID, session): | |
66 self.lock.acquire() | |
67 try: | |
68 #Add the new element | |
69 self.entriesDict[sessionID] = session | |
70 self.entriesList[self.lastIndex] = (sessionID, time.time()) | |
71 self.lastIndex = (self.lastIndex+1) % len(self.entriesList) | |
72 | |
73 #If the cache is full, we delete the oldest element to make an | |
74 #empty space | |
75 if self.lastIndex == self.firstIndex: | |
76 del(self.entriesDict[self.entriesList[self.firstIndex][0]]) | |
77 self.firstIndex = (self.firstIndex+1) % len(self.entriesList) | |
78 finally: | |
79 self.lock.release() | |
80 | |
81 #Delete expired items | |
82 def _purge(self): | |
83 currentTime = time.time() | |
84 | |
85 #Search through the circular list, deleting expired elements until | |
86 #we reach a non-expired element. Since elements in list are | |
87 #ordered in time, we can break once we reach the first non-expired | |
88 #element | |
89 index = self.firstIndex | |
90 while index != self.lastIndex: | |
91 if currentTime - self.entriesList[index][1] > self.maxAge: | |
92 del(self.entriesDict[self.entriesList[index][0]]) | |
93 index = (index+1) % len(self.entriesList) | |
94 else: | |
95 break | |
96 self.firstIndex = index | |
97 | |
98 def _test(): | |
99 import doctest, SessionCache | |
100 return doctest.testmod(SessionCache) | |
101 | |
102 if __name__ == "__main__": | |
103 _test() | |
OLD | NEW |