diff --git a/src/network/dandelion.py b/src/network/dandelion.py index b92bd7f8..2678ca57 100644 --- a/src/network/dandelion.py +++ b/src/network/dandelion.py @@ -23,6 +23,7 @@ Stem = namedtuple('Stem', ['child', 'stream', 'timeout']) @Singleton class Dandelion(): + """Dandelion class for tracking stem/fluff stages.""" def __init__(self): # currently assignable child stems self.stem = [] @@ -35,6 +36,7 @@ class Dandelion(): self.lock = RLock() def poissonTimeout(self, start=None, average=0): + """Generate deadline using Poisson distribution""" if start is None: start = time() if average == 0: @@ -42,6 +44,7 @@ class Dandelion(): return start + expovariate(1.0 / average) + FLUFF_TRIGGER_FIXED_DELAY def addHash(self, hashId, source=None, stream=1): + """Add inventory vector to dandelion stem""" if not state.dandelion: return with self.lock: @@ -51,6 +54,10 @@ class Dandelion(): self.poissonTimeout()) def setHashStream(self, hashId, stream=1): + """ + Update stream for inventory vector (as inv/dinv commands don't + include streams, we only learn this after receiving the object) + """ with self.lock: if hashId in self.hashMap: self.hashMap[hashId] = Stem( @@ -59,6 +66,7 @@ class Dandelion(): self.poissonTimeout()) def removeHash(self, hashId, reason="no reason specified"): + """Switch inventory vector from stem to fluff mode""" logging.debug( "%s entering fluff mode due to %s.", ''.join('%02x' % ord(i) for i in hashId), reason) @@ -69,12 +77,19 @@ class Dandelion(): pass def hasHash(self, hashId): + """Is inventory vector in stem mode?""" return hashId in self.hashMap def objectChildStem(self, hashId): + """Child (i.e. next) node for an inventory vector during stem mode""" return self.hashMap[hashId].child def maybeAddStem(self, connection): + """ + If we had too few outbound connections, add the current one to the + current stem list. Dandelion as designed by the authors should + always have two active stem child connections. + """ # fewer than MAX_STEMS outbound connections at last reshuffle? with self.lock: if len(self.stem) < MAX_STEMS: @@ -90,6 +105,10 @@ class Dandelion(): invQueue.put((v.stream, k, v.child)) def maybeRemoveStem(self, connection): + """ + Remove current connection from the stem list (called e.g. when + a connection is closed). + """ # is the stem active? with self.lock: if connection in self.stem: @@ -107,6 +126,10 @@ class Dandelion(): None, v.stream, self.poissonTimeout()) def pickStem(self, parent=None): + """ + Pick a random active stem, but not the parent one + (the one where an object came from) + """ try: # pick a random from available stems stem = choice(range(len(self.stem))) @@ -123,6 +146,10 @@ class Dandelion(): return None def getNodeStem(self, node=None): + """ + Return child stem node for a given parent stem node + (the mapping is static for about 10 minutes, then it reshuffles) + """ with self.lock: try: return self.nodeMap[node] @@ -131,6 +158,7 @@ class Dandelion(): return self.nodeMap[node] def expire(self): + """Switch expired objects from stem to fluff mode""" with self.lock: deadline = time() toDelete = [ @@ -144,6 +172,7 @@ class Dandelion(): return toDelete def reRandomiseStems(self): + """Re-shuffle stem mapping (parent <-> child pairs)""" with self.lock: try: # random two connections diff --git a/src/network/socks4a.py b/src/network/socks4a.py index c50c6db5..bdf59944 100644 --- a/src/network/socks4a.py +++ b/src/network/socks4a.py @@ -5,6 +5,7 @@ from proxy import Proxy, ProxyError, GeneralProxyError class Socks4aError(ProxyError): + """SOCKS4a error base class""" errorCodes = ( "Request granted", "Request rejected or failed", @@ -17,16 +18,19 @@ class Socks4aError(ProxyError): class Socks4a(Proxy): + """SOCKS4a proxy class""" def __init__(self, address=None): Proxy.__init__(self, address) self.ipaddr = None self.destport = address[1] def state_init(self): + """Protocol initialisation (before connection is established)""" self.set_state("auth_done", 0) return True def state_pre_connect(self): + """Handle feedback from SOCKS4a while it is connecting on our behalf""" # Get the response if self.read_buf[0:1] != chr(0x00).encode(): # bad data @@ -53,14 +57,20 @@ class Socks4a(Proxy): return True def proxy_sock_name(self): + """ + Handle return value when using SOCKS4a for DNS resolving + instead of connecting. + """ return socket.inet_ntoa(self.__proxysockname[0]) class Socks4aConnection(Socks4a): + """Child SOCKS4a class used for making outbound connections.""" def __init__(self, address): Socks4a.__init__(self, address=address) def state_auth_done(self): + """Request connection to be made""" # Now we can request the actual connection rmtrslv = False self.append_write_buf( @@ -92,6 +102,7 @@ class Socks4aConnection(Socks4a): return True def state_pre_connect(self): + """Tell SOCKS4a to initiate a connection""" try: return Socks4a.state_pre_connect(self) except Socks4aError as e: @@ -100,6 +111,7 @@ class Socks4aConnection(Socks4a): class Socks4aResolver(Socks4a): + """DNS resolver class using SOCKS4a""" def __init__(self, host): self.host = host self.port = 8444 @@ -118,4 +130,9 @@ class Socks4aResolver(Socks4a): return True def resolved(self): + """ + Resolving is done, process the return value. To use this within + PyBitmessage, a callback needs to be implemented which hasn't + been done yet. + """ print "Resolved %s as %s" % (self.host, self.proxy_sock_name())