@ -1,62 +1,78 @@
from random import choice , shuffle
from collections import namedtuple
from random import choice , sample
from threading import RLock
from time import time
from bmconfigparser import BMConfigParser
import network . connectionpool
from debug import logging
from queues import invQueue
from singleton import Singleton
import state
# randomise routes after 600 seconds
REASSIGN_INTERVAL = 600
FLUFF_TRIGGER_TIMEOUT = 300
# trigger fluff due to expiration in 2 minutes
FLUFF_TRIGGER_TIMEOUT = 120
MAX_STEMS = 2
Stem = namedtuple ( ' Stem ' , [ ' child ' , ' stream ' , ' timeout ' ] )
@Singleton
class Dandelion ( ) :
def __init__ ( self ) :
# currently assignable child stems
self . stem = [ ]
# currently assigned parent <-> child mappings
self . nodeMap = { }
# currently existing objects in stem mode
self . hashMap = { }
self . fluff = { }
self . timeout = { }
# when to rerandomise routes
self . refresh = time ( ) + REASSIGN_INTERVAL
self . lock = RLock ( )
def addHash ( self , hashId , source ) :
if BMConfigParser ( ) . safeGetInt ( ' network ' , ' dandelion ' ) == 0 :
def addHash ( self , hashId , source = None , stream = 1 ) :
if not state . dandelion :
return
with self . lock :
self . hashMap [ hashId ] = self . getNodeStem ( source )
self . timeout [ hashId ] = time ( ) + FLUFF_TRIGGER_TIMEOUT
self . hashMap [ hashId ] = Stem (
self . getNodeStem ( source ) ,
stream ,
time ( ) + FLUFF_TRIGGER_TIMEOUT )
def setHashStream ( self , hashId , stream = 1 ) :
with self . lock :
if hashId in self . hashMap :
self . hashMap [ hashId ] = Stem (
self . hashMap [ hashId ] . child ,
stream ,
time ( ) + FLUFF_TRIGGER_TIMEOUT )
def removeHash ( self , hashId ) :
def removeHash ( self , hashId , reason = " no reason specified " ) :
logging . debug ( " %s entering fluff mode due to %s . " , ' ' . join ( ' %02x ' % ord ( i ) for i in hashId ) , reason )
with self . lock :
try :
del self . hashMap [ hashId ]
except KeyError :
pass
try :
del self . timeout [ hashId ]
except KeyError :
pass
try :
del self . fluff [ hashId ]
except KeyError :
pass
def fluffTrigger ( self , hashId ) :
with self . lock :
self . fluff [ hashId ] = None
def hasHash ( self , hashId ) :
return hashId in self . hashMap
def objectChildStem ( self , hashId ) :
return self . hashMap [ hashId ] . child
def maybeAddStem ( self , connection ) :
# fewer than MAX_STEMS outbound connections at last reshuffle?
with self . lock :
if len ( self . stem ) < MAX_STEMS :
self . stem . append ( connection )
# active mappings pointing nowhere
for k in ( k for k , v in self . nodeMap . iteritems ( ) if self . nodeMap [ k ] is None ) :
for k in ( k for k , v in self . nodeMap . iteritems ( ) if v is None ) :
self . nodeMap [ k ] = connection
for k in ( k for k , v in self . hashMap . iteritems ( ) if self . hashMap [ k ] is None ) :
self . hashMap [ k ] = connection
for k , v in { k : v for k , v in self . hashMap . iteritems ( ) if v . child is None } . iteritems ( ) :
self . hashMap [ k ] = Stem ( connection , v . stream , time ( ) + FLUFF_TRIGGER_TIMEOUT )
invQueue . put ( ( v . stream , k , v . child ) )
def maybeRemoveStem ( self , connection ) :
# is the stem active?
@ -64,12 +80,10 @@ class Dandelion():
if connection in self . stem :
self . stem . remove ( connection )
# active mappings to pointing to the removed node
for k in ( k for k , v in self . nodeMap . iteritems ( ) if self . nodeMap [ k ] == connection ) :
for k in ( k for k , v in self . nodeMap . iteritems ( ) if v == connection ) :
self . nodeMap [ k ] = None
for k in ( k for k , v in self . hashMap . iteritems ( ) if self . hashMap [ k ] == connection ) :
self . hashMap [ k ] = None
if len ( self . stem ) < MAX_STEMS :
self . stem . append ( connection )
for k , v in { k : v for k , v in self . hashMap . iteritems ( ) if v . child == connection } . iteritems ( ) :
self . hashMap [ k ] = Stem ( None , v . stream , time ( ) + FLUFF_TRIGGER_TIMEOUT )
def pickStem ( self , parent = None ) :
try :
@ -92,26 +106,26 @@ class Dandelion():
try :
return self . nodeMap [ node ]
except KeyError :
self . nodeMap [ node ] = self . pickStem ( )
self . nodeMap [ node ] = self . pickStem ( node )
return self . nodeMap [ node ]
def getHashStem ( self , hashId ) :
with self . lock :
return self . hashMap [ hashId ]
def expire ( self ) :
with self . lock :
deadline = time ( )
toDelete = [ k for k , v in self . hashMap . iteritems ( ) if self . timeout [ k ] < deadline ]
for k in toDelete :
del self . timeout [ k ]
del self . hashMap [ k ]
# only expire those that have a child node, i.e. those without a child not will stick around
toDelete = [ [ v . stream , k , v . child ] for k , v in self . hashMap . iteritems ( ) if v . timeout < deadline and v . child ]
for row in toDelete :
self . removeHash ( row [ 1 ] , ' expiration ' )
invQueue . put ( ( row [ 0 ] , row [ 1 ] , row [ 2 ] ) )
def reRandomiseStems ( self , connections ) :
shuffle ( connections )
def reRandomiseStems ( self ) :
with self . lock :
# random two connections
self . stem = connections [ : MAX_STEMS ]
try :
# random two connections
self . stem = sample ( network . connectionpool . BMConnectionPool ( ) . outboundConnections . values ( ) , MAX_STEMS )
# not enough stems available
except ValueError :
self . stem = network . connectionpool . BMConnectionPool ( ) . outboundConnections . values ( )
self . nodeMap = { }
# hashMap stays to cater for pending stems
self . refresh = time ( ) + REASSIGN_INTERVAL