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