1 # Written by Arno Bakker
2 # see LICENSE.txt for license information
9 from BaseLib.Core.Utilities.Crypto import sha
10 from traceback import print_exc
11 from types import StringType, ListType, DictType, IntType
14 from BaseLib.Test.test_as_server import TestAsServer
15 from olconn import OLConnection
17 from BaseLib.Core.BitTornado.bencode import bencode,bdecode
18 from BaseLib.Core.BitTornado.BT1.MessageID import *
19 from BaseLib.Core.simpledefs import *
24 class TestBuddyCastMsg(TestAsServer):
26 Testing BUDDYCAST message of BuddyCast extension V1+2+3
28 Note this is based on a reverse-engineering of the protocol.
29 Source code of the specific Tribler release is authoritative.
33 """ override TestAsServer """
34 TestAsServer.setUp(self)
36 def setUpPreSession(self):
37 """ override TestAsServer """
38 TestAsServer.setUpPreSession(self)
40 self.config.set_buddycast(True)
41 self.config.set_start_recommender(True)
43 fd,self.superpeerfilename = tempfile.mkstemp()
46 self.config.set_superpeer_file(self.superpeerfilename)
48 def setUpPostSession(self):
49 """ override TestAsServer """
50 TestAsServer.setUpPostSession(self)
52 self.mypermid = str(self.my_keypair.pub().get_der())
53 self.hispermid = str(self.his_keypair.pub().get_der())
54 self.myhash = sha(self.mypermid).digest()
56 # Give Tribler some download history
57 print >>sys.stderr,"test: Populating MYPREFERENCES table"
58 self.myprefdb = self.session.open_dbhandler(NTFY_MYPREFERENCES)
59 data = {'destination_path':'.'}
60 infohashes = self.create_good_my_prefs(self,btconn.current_version)
61 for i in range(0,len(infohashes)):
62 commit = (i == len(infohashes)-1)
63 self.myprefdb.addMyPreference(infohashes[i], data, commit=commit)
65 # Give Tribler some peers
66 print >>sys.stderr,"test: Populating PEERS table"
67 self.peerdb = self.session.open_dbhandler(NTFY_PEERS)
68 past = int(time.time())-1000000000
69 peers = self.create_good_random_peers(btconn.current_version,num=200)
73 for i in range(0,len(peers)):
75 peer.update({'last_seen':past, 'last_connected':past})
76 del peer['connect_time']
77 peer['num_torrents'] = peer['nfiles']
79 commit = (i == len(peers)-1)
80 self.peerdb.addPeer(peer['permid'], peer, update_dns=True, update_connected=True, commit=commit)
84 """ override TestAsServer """
85 TestAsServer.tearDown(self)
87 os.remove(self.superpeerfilename)
92 #Arno, 2010-02-16: we now kick v2 peers, so can't test anymore.
93 #def singtest_good_buddycast2(self):
94 # self.subtest_good_buddycast(2)
96 def singtest_good_buddycast3(self):
97 """ I want a fresh Tribler for this """
98 self.subtest_good_buddycast(3)
100 def singtest_good_buddycast4(self):
101 """ I want a fresh Tribler for this """
102 self.subtest_good_buddycast(4)
104 def singtest_good_buddycast6(self):
105 """ I want a fresh Tribler for this """
106 self.subtest_good_buddycast(6)
108 def singtest_bad_all(self):
110 I want to start a Tribler client once and then connect to
111 it many times. So there must be only one test method
112 to prevent setUp() from creating a new client every time.
114 The code is constructed so unittest will show the name of the
115 (sub)test where the error occured in the traceback it prints.
117 self.subtest_bad_not_bdecodable()
118 self.subtest_bad_not_dict1()
119 self.subtest_bad_not_dict2()
120 self.subtest_bad_empty_dict()
121 self.subtest_bad_wrong_dict_keys()
122 self.subtest_bad_buddycast_simple()
123 self.subtest_bad_taste_buddies()
124 self.subtest_bad_random_peers()
129 def subtest_good_buddycast(self,oversion):
131 test good BUDDYCAST messages
133 print >>sys.stderr,"test: good BUDDYCAST",oversion
134 s = OLConnection(self.my_keypair,'localhost',self.hisport,myoversion=oversion)
135 msg = self.create_good_buddycast_payload(oversion)
138 s.b.s.settimeout(60.0)
142 self.assert_(len(resp) > 0)
143 print >>sys.stderr,"test: good BUDDYCAST: Got reply",getMessageName(resp[0])
144 if resp[0] == BUDDYCAST:
146 elif resp[0] == GET_METADATA:
147 self.check_get_metadata(resp[1:])
148 elif resp[0] == KEEP_ALIVE:
150 self.check_keep_alive(resp[1:])
152 print >> sys.stderr,"test: Tribler sent KEEP_ALIVE, not allowed in olproto ver",oversion
154 except socket.timeout:
155 print >> sys.stderr,"test: Timeout, bad, peer didn't reply with BUDDYCAST message"
158 self.check_buddycast(resp[1:],oversion)
160 # the other side should not have closed the connection, as
161 # this is all valid, so this should not throw an exception:
165 def create_good_buddycast_payload(self,oversion):
166 d = self.create_good_buddycast(oversion)
167 return self.create_payload(d)
169 def create_good_buddycast(self,oversion):
170 self.myprefs = self.create_good_my_prefs(oversion)
171 tastebuddies = self.create_good_taste_buddies(oversion)
172 randompeers = self.create_good_random_peers(oversion)
173 recentcoll = self.create_good_recently_collected_torrents(oversion)
175 d['ip'] = '127.0.0.1'
177 d['name'] = 'Bud Spencer'
178 d['preferences'] = self.myprefs
179 d['taste buddies'] = tastebuddies
180 d['random peers'] = randompeers
183 d['connectable'] = True
186 d['collected torrents'] = recentcoll
193 #print >>sys.stderr,"test: Sending",`d`
197 def create_good_my_prefs(self,oversion,num=50):
199 for i in range(0,num):
200 infohash = chr(ord('a')+i) * 20
204 create_good_recently_collected_torrents = create_good_my_prefs
206 def create_good_taste_buddies(self,oversion):
208 for i in range(0,10):
209 tb = self.create_good_peer(i,oversion)
213 def create_good_random_peers(self,oversion,num=10):
215 for i in range(0,num):
216 tb = self.create_good_peer(i,oversion)
220 def create_good_peer(self,id,oversion):
222 d['permid'] = 'peer '+str(id)
223 d['ip'] = '192.168.0.'+str(id)
225 d['connect_time'] = int(time.time())
231 d['preferences'] = self.create_good_my_prefs(oversion,num=10)
236 d['oversion'] = btconn.current_version
241 def create_payload(self,r):
242 return BUDDYCAST+bencode(r)
244 def check_buddycast(self,data,oversion):
247 print >>sys.stderr,"test: Got BUDDYCAST",d.keys()
248 #print >>sys.stderr,"test: Got CONTENT",`d`
250 self.assert_(type(d) == DictType)
251 self.assert_('ip' in d)
252 self.assert_(type(d['ip']) == StringType)
253 self.assert_('port' in d)
254 self.assert_(type(d['port']) == IntType)
255 self.assert_('name' in d)
256 self.assert_(type(d['name']) == StringType)
257 self.assert_('preferences' in d)
258 self.check_preferences(d['preferences'],oversion)
259 self.assert_('taste buddies' in d)
260 self.check_taste_buddies(d['taste buddies'],oversion)
261 self.assert_('random peers' in d)
262 self.check_random_peers(d['random peers'],oversion
265 self.assert_('connectable' in d)
266 #print >>sys.stderr,"CONNECTABLE TYPE",type(d['connectable'])
267 self.assert_(type(d['connectable']) == IntType)
269 self.assert_('collected torrents' in d)
270 self.check_collected_torrents(d['collected torrents'],oversion)
272 self.assert_('npeers' in d)
273 self.assert_(type(d['npeers']) == IntType)
274 self.assert_('nfiles' in d)
275 self.assert_(type(d['nfiles']) == IntType)
276 self.assert_('ndls' in d)
277 self.assert_(type(d['ndls']) == IntType)
279 def check_preferences(self,p,oversion):
280 self.assert_(type(p) == ListType)
281 self.assert_(len(p) <= 50)
283 self.check_infohash(infohash)
285 check_collected_torrents = check_preferences
287 def check_infohash(self,infohash):
288 self.assert_(type(infohash) == StringType)
289 self.assert_(len(infohash) == 20)
291 def check_taste_buddies(self,peerlist,oversion):
292 return self.check_peer_list(peerlist,True,oversion)
294 def check_random_peers(self,peerlist,oversion):
295 return self.check_peer_list(peerlist,False,oversion)
297 def check_peer_list(self,peerlist,taste,oversion):
298 self.assert_(type(peerlist) == ListType)
300 self.check_peer(p,taste,oversion)
302 def check_peer(self,d,taste,oversion):
303 self.assert_(type(d) == DictType)
304 self.assert_('permid' in d)
305 self.assert_(type(d['permid']) == StringType)
306 self.assert_('ip' in d)
307 self.assert_(type(d['ip']) == StringType)
308 self.assert_('port' in d)
309 self.assert_(type(d['port']) == IntType)
310 self.assert_('connect_time' in d)
311 self.assert_(type(d['connect_time']) == IntType)
313 if oversion <= 3 and taste:
314 self.assert_('preferences' in d)
315 self.check_preferences(d['preferences'],oversion)
318 self.assert_('similarity' in d)
319 self.assert_(type(d['similarity']) == IntType)
323 # Jie made this optional, only if peer has enough collected files
324 # its record will contain these fields
325 self.assert_(type(d['oversion']) == IntType)
326 self.assert_('nfiles' in d)
327 self.assert_(type(d['nfiles']) == IntType)
330 def check_get_metadata(self,data):
331 infohash = bdecode(data)
332 self.check_infohash(infohash)
334 # Extra check: he can only ask us for metadata for an infohash we
336 self.assert_(infohash in self.myprefs)
338 def check_keep_alive(self,data):
339 self.assert_(len(data) == 0)
344 def subtest_bad_not_bdecodable(self):
345 self._test_bad(self.create_not_bdecodable)
347 def subtest_bad_not_dict1(self):
348 self._test_bad(self.create_not_dict1)
350 def subtest_bad_not_dict2(self):
351 self._test_bad(self.create_not_dict2)
353 def subtest_bad_empty_dict(self):
354 self._test_bad(self.create_empty_dict)
356 def subtest_bad_wrong_dict_keys(self):
357 self._test_bad(self.create_wrong_dict_keys)
359 def subtest_bad_buddycast_simple(self):
364 self.make_bad_preferences,
365 self.make_bad_collected_torrents]
366 for method in methods:
367 print >> sys.stderr,"\ntest: ",method,
368 self._test_bad(method)
371 def make_bad_ip(self):
372 d = self.create_good_buddycast(btconn.current_version)
374 return self.create_payload(d)
376 def make_bad_port(self):
377 d = self.create_good_buddycast(btconn.current_version)
378 d['port'] = '127.0.0.1'
379 return self.create_payload(d)
381 def make_bad_name(self):
382 d = self.create_good_buddycast(btconn.current_version)
384 return self.create_payload(d)
386 def make_bad_preferences(self):
387 d = self.create_good_buddycast(btconn.current_version)
388 d['preferences'] = 481
389 return self.create_payload(d)
391 def make_bad_collected_torrents(self):
392 d = self.create_good_buddycast(btconn.current_version)
393 d['collected torrents'] = 481
394 return self.create_payload(d)
397 def subtest_bad_taste_buddies(self):
399 self.make_bad_tb_not_list,
400 self.make_bad_tb_list_not_dictelems,
401 self.make_bad_tb_list_bad_peer]
402 for method in methods:
403 d = self.create_good_buddycast(btconn.current_version)
404 d['taste buddies'] = method()
405 func = lambda:self.create_payload(d)
407 print >> sys.stderr,"\ntest: ",method,
410 def make_bad_tb_not_list(self):
414 def make_bad_tb_list_not_dictelems(self):
416 for i in range(0,50):
420 def make_bad_tb_list_bad_peer(self):
422 for i in range(0,50):
423 tbs.append(self.make_bad_peer())
426 def make_bad_peer(self):
428 d['permid'] = 'peer 481'
429 # Error is too little fields.
430 # TODO: test all possible bad peers
435 def subtest_bad_random_peers(self):
440 self.make_bad_preferences,
441 self.make_bad_collected_torrents]
442 for method in methods:
443 d = self.create_good_buddycast(btconn.current_version)
444 d['taste buddies'] = method()
445 func = lambda:self.create_payload(d)
447 print >> sys.stderr,"\ntest: ",method,
450 def _test_bad(self,gen_buddycast_func):
451 print >>sys.stderr,"test: bad BUDDYCAST",gen_buddycast_func
452 s = OLConnection(self.my_keypair,'localhost',self.hisport)
453 msg = gen_buddycast_func()
456 # the other side should not like this and close the connection
457 self.assert_(len(s.recv())==0)
460 def create_not_bdecodable(self):
461 return BUDDYCAST+"bla"
463 def create_not_dict1(self):
465 return self.create_payload(buddycast)
467 def create_not_dict2(self):
469 return self.create_payload(buddycast)
471 def create_empty_dict(self):
473 return self.create_payload(buddycast)
475 def create_wrong_dict_keys(self):
477 buddycast['bla1'] = '\x00\x00\x00\x00\x00\x30\x00\x00'
478 buddycast['bla2'] = '\x00\x00\x00\x00\x00\x30\x00\x00'
479 return self.create_payload(buddycast)
482 suite = unittest.TestSuite()
483 # We should run the tests in a separate Python interpreter to prevent
484 # problems with our singleton classes, e.g. PeerDB, etc.
485 if len(sys.argv) != 2:
486 print "Usage: python test_buddycast_msg.py <method name>"
488 suite.addTest(TestBuddyCastMsg(sys.argv[1]))
493 unittest.main(defaultTest='test_suite',argv=[sys.argv[0]])
495 if __name__ == "__main__":