104 lines
3.4 KiB
Python
104 lines
3.4 KiB
Python
"""Class for caching TLS sessions."""
|
|
|
|
import thread
|
|
import time
|
|
|
|
class SessionCache:
|
|
"""This class is used by the server to cache TLS sessions.
|
|
|
|
Caching sessions allows the client to use TLS session resumption
|
|
and avoid the expense of a full handshake. To use this class,
|
|
simply pass a SessionCache instance into the server handshake
|
|
function.
|
|
|
|
This class is thread-safe.
|
|
"""
|
|
|
|
#References to these instances
|
|
#are also held by the caller, who may change the 'resumable'
|
|
#flag, so the SessionCache must return the same instances
|
|
#it was passed in.
|
|
|
|
def __init__(self, maxEntries=10000, maxAge=14400):
|
|
"""Create a new SessionCache.
|
|
|
|
@type maxEntries: int
|
|
@param maxEntries: The maximum size of the cache. When this
|
|
limit is reached, the oldest sessions will be deleted as
|
|
necessary to make room for new ones. The default is 10000.
|
|
|
|
@type maxAge: int
|
|
@param maxAge: The number of seconds before a session expires
|
|
from the cache. The default is 14400 (i.e. 4 hours)."""
|
|
|
|
self.lock = thread.allocate_lock()
|
|
|
|
# Maps sessionIDs to sessions
|
|
self.entriesDict = {}
|
|
|
|
#Circular list of (sessionID, timestamp) pairs
|
|
self.entriesList = [(None,None)] * maxEntries
|
|
|
|
self.firstIndex = 0
|
|
self.lastIndex = 0
|
|
self.maxAge = maxAge
|
|
|
|
def __getitem__(self, sessionID):
|
|
self.lock.acquire()
|
|
try:
|
|
self._purge() #Delete old items, so we're assured of a new one
|
|
session = self.entriesDict[sessionID]
|
|
|
|
#When we add sessions they're resumable, but it's possible
|
|
#for the session to be invalidated later on (if a fatal alert
|
|
#is returned), so we have to check for resumability before
|
|
#returning the session.
|
|
|
|
if session.valid():
|
|
return session
|
|
else:
|
|
raise KeyError()
|
|
finally:
|
|
self.lock.release()
|
|
|
|
|
|
def __setitem__(self, sessionID, session):
|
|
self.lock.acquire()
|
|
try:
|
|
#Add the new element
|
|
self.entriesDict[sessionID] = session
|
|
self.entriesList[self.lastIndex] = (sessionID, time.time())
|
|
self.lastIndex = (self.lastIndex+1) % len(self.entriesList)
|
|
|
|
#If the cache is full, we delete the oldest element to make an
|
|
#empty space
|
|
if self.lastIndex == self.firstIndex:
|
|
del(self.entriesDict[self.entriesList[self.firstIndex][0]])
|
|
self.firstIndex = (self.firstIndex+1) % len(self.entriesList)
|
|
finally:
|
|
self.lock.release()
|
|
|
|
#Delete expired items
|
|
def _purge(self):
|
|
currentTime = time.time()
|
|
|
|
#Search through the circular list, deleting expired elements until
|
|
#we reach a non-expired element. Since elements in list are
|
|
#ordered in time, we can break once we reach the first non-expired
|
|
#element
|
|
index = self.firstIndex
|
|
while index != self.lastIndex:
|
|
if currentTime - self.entriesList[index][1] > self.maxAge:
|
|
del(self.entriesDict[self.entriesList[index][0]])
|
|
index = (index+1) % len(self.entriesList)
|
|
else:
|
|
break
|
|
self.firstIndex = index
|
|
|
|
def _test():
|
|
import doctest, SessionCache
|
|
return doctest.testmod(SessionCache)
|
|
|
|
if __name__ == "__main__":
|
|
_test()
|