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