def test_change(self): peerTracker = self.peers info_hash = '0' * 20 ip = tracker.dottedQuadToInt('192.168.0.1') p = peers.Peer(ip, 1025, 3) self.assertTrue(peerTracker.updatePeer(info_hash, p)) p = peers.Peer(ip, 1025, 2) self.assertFalse(peerTracker.updatePeer(info_hash, p)) p = peers.Peer(ip, 1025, 1) self.assertFalse(peerTracker.updatePeer(info_hash, p)) p = peers.Peer(ip, 1025, 0) self.assertTrue(peerTracker.updatePeer(info_hash, p))
def test_removePeerNotExistInInfoHash(self): peerTracker = self.peers ip = tracker.dottedQuadToInt('192.168.0.1') p0 = peers.Peer(ip, 1025, 3) info_hash = '0' * 20 self.assertTrue(peerTracker.updatePeer(info_hash, p0)) p1 = peers.Peer(ip, 1026, 3) self.assertFalse(peerTracker.removePeer(info_hash, p1)) self.assertTrue(peerTracker.removePeer(info_hash, p0)) self.assertFalse(peerTracker.removePeer(info_hash, p0))
def test_addingPeers(self): peerList = [] for peerIp in range(1, 17): ip = tracker.dottedQuadToInt('192.168.0.' + str(peerIp)) port = peerIp % 4 + 1025 left = peerIp % 4 done = 4 - left peerList.append(peers.Peer(ip, port, left)) info_hashes = [chr(i) * 20 for i in range(0, 4)] peerTracker = self.peers for info_hash in info_hashes: self.assertEqual(peerTracker.getNumberOfLeeches(info_hash), 0) self.assertEqual(peerTracker.getNumberOfSeeds(info_hash), 0) self.assertEqual(len(list(peerTracker.getPeers(info_hash))), 0) for i, peer in enumerate(peerList): self.assertTrue(peerTracker.updatePeer(info_hash, peer)) l = peerTracker.getPeers(info_hash) l = list(l) self.assertIn(peer, l) self.assertEqual(i + 1, len(l)) self.assertEqual( 1, sum(1 for p in peerTracker.getPeers(info_hash) if p == peer)) for info_hash in info_hashes: self.assertEqual( peerTracker.getNumberOfLeeches(info_hash) + peerTracker.getNumberOfSeeds(info_hash), len(peerList)) numSeeds, numLeeches = peerTracker.getNumberOfPeers(info_hash) self.assertEqual(numSeeds + numLeeches, len(peerList)) l = list(peerTracker.getPeers(info_hash)) self.assertEqual(len(l), len(peerList)) for peer in peerTracker.getPeers(info_hash): self.assertTrue(peer in peerList) for peer in peerList: self.assertTrue(peerTracker.removePeer(info_hash, peer)) self.assertTrue(not peer in peerTracker.getPeers(info_hash))
def test_expireFuncPartial(self): peerTracker = self.peers info_hashes = [i * 20 for i in 'abcdefghijk'] for info_hash in info_hashes: for ip in xrange(0, 2**9): p = peers.Peer(ip, 1025, 3) self.assertTrue(peerTracker.updatePeer(info_hash, p)) peerTracker.peerExpirationPeriod = 60 * 60 peerTracker.removeExpiredPeers() for info_hash in info_hashes: pList = list(peerTracker.getPeers(info_hash)) self.assertEqual(len(pList), 2**9)
def cmd_peer_add(arg): name, ip, port = arg.split(" ") peers.peers.append(peers.Peer(name, (ip, int(port))))
class Sender: def __init__(self, peer): self.peer = peer self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.connected = False def send_message(self, msg): if not self.connected: debug.output(debug.verbose, '[sender] try connect to %s' % str(self.peer)) self.s.connect((self.peer.ip, self.peer.port)) self.connected = True debug.output(debug.verbose, '[sender] connected to %s' % str(self.peer)) debug.output(debug.verbose, '[sender] send %s to %s' % (str(msg), str(self.peer))) content = wrap_message(msg) self.s.send(content) content_length = int(receive(self.s, 10)) return decode_from_bytes(receive(self.s, content_length)) def __del__(self): self.s.close() if __name__ == '__main__': sender = Sender(peers.Peer('127.0.0.1', 10086)) print(sender.send_message(HeartbeatMessage()).timestamp) del sender
def test_removePeerNoSuchInfoHash(self): peerTracker = self.peers ip = tracker.dottedQuadToInt('192.168.0.1') p = peers.Peer(ip, 1025, 3) info_hash = '0' * 20 self.assertFalse(peerTracker.removePeer(info_hash, p))
def announce(self,env,start_response): #Extract and normalize the path #Posix path may not be the best approach here but #no alternate has been found pathInfo = posixpath.normpath(env['PATH_INFO']) #Split the path into components. Drop the first #since it should always be the empty string pathComponents = pathInfo.split('/')[1+self.pathDepth:] #A SHA512 encoded in base64 is 88 characters #but the last two are always '==' so #86 is used here if len(pathComponents) !=2 or len(pathComponents[0]) != 86 or pathComponents[1] != 'announce': return vanilla.http_error(404,env,start_response) #Only GET requests are valid if env['REQUEST_METHOD'] != 'GET': return vanilla.http_error(405,env,start_response) #Add the omitted equals signs back in secretKey = pathComponents[0] + '==' #base64 decode the secret key try: secretKey = base64.urlsafe_b64decode(secretKey) except TypeError: return vanilla.http_error(404,env,start_response) #Extract the IP of the peer peerIp = getClientAddress(env) peerIpAsString = peerIp try: peerIp = dottedQuadToInt(peerIp) except ValueError: return vanilla.http_error(500,env,start_response) #Parse the query string. Absence indicates error if 'QUERY_STRING' not in env: return vanilla.http_error(400,env,start_response) query = urlparse.parse_qs(env['QUERY_STRING']) #List of tuples. Each tuple is # #Parameter name #default value (if any) #type conversion, side-effect free callable params = [] def validateInfoHash(info_hash): #Info hashes are a SHA1 hash, and are always 20 bytes if len(info_hash) != 20: raise ValueError("Length " + str(len(info_hash)) + ' not acceptable') return info_hash params.append(('info_hash',None,validateInfoHash)) def validatePeerId(peer_id): #Peer IDs are a string chosen by the peer to identify itself #and are always 20 bytes if len(peer_id) != 20: raise ValueError("Improper Length") return peer_id params.append(('peer_id',None,validatePeerId)) def validatePort(port): port = int(port) #Ipv4 ports should not be higher than this value if port > 2 ** 16 - 1 or port <= 0: raise ValueError("Port outside of range") return port def validateByteCount(byteCount): byteCount = int(byteCount) if byteCount < 0: raise ValueError('byte count cannot be negative') return byteCount params.append(('port',None,validatePort)) params.append(('uploaded',None,validateByteCount)) params.append(('downloaded',None,validateByteCount)) params.append(('left',None,validateByteCount)) #If the client doesn't specify the compact parameter, it is #safe to assume that compact responses are understood. So a #default value of 1 is used. Additionally, any non zero #value provided assumes the client wants a compact response params.append(('compact',1,int)) def validateEvent(event): event = event.lower() if event not in ['started','stopped','completed']: raise ValueError("Unknown event") return event params.append(('event','update',validateEvent)) maxNumWant = 35 def limitNumWant(numwant): numwant = int(numwant) if numwant < 0: raise ValueError('numwant cannot be negative') numwant = min(numwant,maxNumWant) return numwant params.append(('numwant',maxNumWant,limitNumWant)) #Dictionary holding parameters to query p = dict() #Use the params to generate the parameters for param,defaultValue,typeConversion in params: #If the parameter is in the query, extract the first #occurence and type convert if requested if param in query: p[param] = query[param][0] if typeConversion: try: p[param] = typeConversion(p[param]) except ValueError as e: return vanilla.http_error(400,env,start_response,msg='bad value for ' + param) #If the parameter is not in the query, then #use a default value is present. Otherwise this is an error else: if defaultValue == None: return vanilla.http_error(400,env,start_response,msg='missing ' + param) p[param] = defaultValue #Make sure the secret key is valid userId = self.auth.authenticateSecretKey(secretKey) if userId == None: response = {} response['failure reason'] = 'failed to authenticate secret key' return sendBencodedWsgiResponse(env,start_response,response) #Make sure the info hash is allowed torrentId = self.auth.authorizeInfoHash(p['info_hash']) if torrentId == None: response = {} response['failure reason'] = 'unauthorized info hash' return sendBencodedWsgiResponse(env,start_response,response) #Construct the peers entry peer = peers.Peer(peerIp,p['port'],p['left']) #This is the basic response format response = {} response['interval'] = 5*60 response['complete'] = 0 response['incomplete'] = 0 response['peers'] = [] #This value is set to True if the number of seeds or leeches #changes in the course of processing this result change = False #This value is set to true if the peer is added, false if removed addPeer = False #For all 3 cases here just return peers if p['event'] in ['started','completed','update']: response['complete'] = self.peers.getNumberOfLeeches(p['info_hash']) response['incomplete'] = self.peers.getNumberOfSeeds(p['info_hash']) change = self.peers.updatePeer(p['info_hash'],peer) if change: addPeer = True peersForResponse = self.peers.getPeers(p['info_hash']) #Return a compact response or a traditional response #based on what is requested if p['compact'] != 0: peerStruct = struct.Struct('!IH') maxSize = p['numwant'] * peerStruct.size peersBuffer = array.array('c') for peer in itertools.islice(peersForResponse,0,p['numwant']): peersBuffer.fromstring(peerStruct.pack(peer.ip,peer.port)) response['peers'] = peersBuffer.tostring() else: for peer in itertools.islice(peersForResponse,0,p['numwant']): #For non-compact responses, use a bogus peerId. Hardly any client #uses this type of response anyways. There is no real meaning to the #peer ID except informal agreements. response['peers'].append({'peer id':'0'*20,'ip':socket.inet_ntoa(struct.pack('!I',peer.ip)),'port':peer.port}) #For stop event, just remove the peer. Don't return anything elif p['event'] == 'stopped': change = self.peers.removePeer(p['info_hash'],peer) addPeer = False #Log the successful announce self.announceLog.info('%s:%d %s,%s,%d',peerIpAsString,p['port'],p['info_hash'].encode('hex').upper(),p['event'],p['left']) for callback in self.afterAnnounce: callback(userId,p['info_hash'],peerIpAsString,p['port'],p['peer_id']) return sendBencodedWsgiResponse(env,start_response,response)