instrumentation: add next-share/
[cs-p2p-next.git] / instrumentation / next-share / BaseLib / Core / SocialNetwork / OverlapMsgHandler.py
1 # Written by Arno Bakker
2 # see LICENSE.txt for license information
3
4 import sys
5 from time import time
6 from traceback import print_exc
7
8 from BaseLib.Core.BitTornado.bencode import bencode, bdecode
9 from BaseLib.Core.BitTornado.BT1.MessageID import *
10
11 from BaseLib.Core.Utilities.utilities import *
12 from BaseLib.Core.Utilities.unicode import str2unicode
13
14 DEBUG = False
15
16 MIN_OVERLAP_WAIT = 12.0*3600.0 # half a day in seconds
17
18 ICON_MAX_SIZE = 10*1024
19
20 class OverlapMsgHandler:
21     
22     def __init__(self):
23         
24         self.recentpeers = {}
25
26     def register(self, overlay_bridge, launchmany):
27         if DEBUG:
28             print >> sys.stderr,"socnet: bootstrap: overlap"
29         self.mypermid = launchmany.session.get_permid()
30         self.session = launchmany.session
31         self.peer_db = launchmany.peer_db 
32         self.superpeer_db = launchmany.superpeer_db
33         self.overlay_bridge = overlay_bridge
34
35     #
36     # Incoming SOCIAL_OVERLAP
37     # 
38     def recv_overlap(self,permid,message,selversion):
39         # 1. Check syntax
40         try:
41             oldict = bdecode(message[1:])
42         except:
43             print_exc()
44             if DEBUG:
45                 print >> sys.stderr,"socnet: SOCIAL_OVERLAP: error becoding"
46             return False
47
48         if not isValidDict(oldict,permid):
49             return False
50
51         # 2. Process
52         self.process_overlap(permid,oldict)
53         return True
54
55     def process_overlap(self,permid,oldict):
56         #self.print_hashdict(oldict['hashnetwork'])
57
58         # 1. Clean recently contacted admin
59         self.clean_recentpeers()
60
61         # 3. Save persinfo + hrwidinfo + ipinfo
62         if self.peer_db.hasPeer(permid):
63             save_ssocnet_peer(self,permid,oldict,False,False,False)
64         elif DEBUG:
65             print >> sys.stderr,"socnet: overlap: peer unknown?! Weird, we just established connection"
66
67         # 6. Reply
68         if not (permid in self.recentpeers.keys()):
69             self.recentpeers[permid] = time()
70             self.reply_to_overlap(permid)
71
72     def clean_recentpeers(self):
73         newdict = {}
74         for permid2,t in self.recentpeers.iteritems():
75             if (t+MIN_OVERLAP_WAIT) > time():
76                 newdict[permid2] = t
77             #elif DEBUG:
78             #    print >> sys.stderr,"socnet: overlap: clean recent: not keeping",show_permid_short(permid2)
79                 
80         self.recentpeers = newdict
81
82     def reply_to_overlap(self,permid):
83         oldict = self.create_oldict()
84         self.send_overlap(permid,oldict)
85
86     #
87     # At overlay-connection establishment time.
88     #
89     def initiate_overlap(self,permid,locally_initiated):
90         self.clean_recentpeers()
91         if not (permid in self.recentpeers.keys() or permid in self.superpeer_db.getSuperPeers()):
92             if locally_initiated:
93                 # Make sure only one sends it
94                 self.recentpeers[permid] = time()
95                 self.reply_to_overlap(permid)
96             elif DEBUG:
97                 print >> sys.stderr,"socnet: overlap: active: he should initiate"
98         elif DEBUG:
99             print >> sys.stderr,"socnet: overlap: active: peer recently contacted already"
100
101     #
102     # General
103     #
104     def create_oldict(self):
105         """
106         Send:
107         * Personal info: name, picture, rwidhashes
108         * IP info: IP + port
109         Both are individually signed by us so dest can safely 
110         propagate. We distinguish between what a peer said
111         is his IP+port and the information obtained from the network
112         or from other peers (i.e. BUDDYCAST)
113         """
114
115         nickname = self.session.get_nickname().encode("UTF-8")
116         persinfo = {'name':nickname}
117         # See if we can find icon
118         iconmime, icondata = self.session.get_mugshot()
119         if icondata:
120             persinfo.update({'icontype':iconmime, 'icondata':icondata})
121         
122         oldict = {}
123         oldict['persinfo'] = persinfo
124
125         #print >> sys.stderr, 'Overlap: Sending oldict: %s' % `oldict`
126                             
127         #if DEBUG:
128         #    print >> sys.stderr,"socnet: overlap: active: sending hashdict"
129         #    self.print_hashdict(oldict['hashnetwork'])
130
131         return oldict
132
133
134     def send_overlap(self,permid,oldict):
135         try:
136             body = bencode(oldict)
137             ## Optimization: we know we're currently connected
138             self.overlay_bridge.send(permid, SOCIAL_OVERLAP + body,self.send_callback)
139         except:
140             if DEBUG:
141                 print_exc(file=sys.stderr)
142
143     
144     def send_callback(self,exc,permid):
145         if exc is not None:
146             if DEBUG:
147                 print >> sys.stderr,"socnet: SOCIAL_OVERLAP: error sending to",show_permid_short(permid),exc
148
149     #
150     # Internal methods
151     #
152
153
154 def isValidDict(oldict,source_permid):
155     if not isinstance(oldict, dict):
156         if DEBUG:
157             print >> sys.stderr,"socnet: SOCIAL_OVERLAP: not a dict"
158         return False
159     k = oldict.keys()        
160
161     if DEBUG:
162         print >> sys.stderr,"socnet: SOCIAL_OVERLAP: keys",k
163
164     if not ('persinfo' in k) or not isValidPersinfo(oldict['persinfo'],False):
165         if DEBUG:
166             print >> sys.stderr,"socnet: SOCIAL_OVERLAP: key 'persinfo' missing or value wrong type in dict"
167         return False
168
169     for key in k:
170         if key not in ['persinfo']:
171             if DEBUG:
172                 print >> sys.stderr,"socnet: SOCIAL_OVERLAP: unknown key",key,"in dict"
173             return False
174
175     return True
176
177
178
179 def isValidPersinfo(persinfo,signed):
180     if not isinstance(persinfo,dict):
181         if DEBUG:
182             print >> sys.stderr,"socnet: SOCIAL_*: persinfo: not a dict"
183         return False
184
185     k = persinfo.keys()
186     #print >> sys.stderr,"socnet: SOCIAL_*: persinfo: keys are",k
187     if not ('name' in k) or not isinstance(persinfo['name'],str):
188         if DEBUG:
189             print >> sys.stderr,"socnet: SOCIAL_*: persinfo: key 'name' missing or value wrong type"
190         return False
191
192     if 'icontype' in k and not isValidIconType(persinfo['icontype']):
193         if DEBUG:
194             print >> sys.stderr,"socnet: SOCIAL_*: persinfo: key 'icontype' value wrong type"
195         return False
196
197     if 'icondata' in k and not isValidIconData(persinfo['icondata']):
198         if DEBUG:
199             print >> sys.stderr,"socnet: SOCIAL_*: persinfo: key 'icondata' value wrong type"
200         return False
201
202     if ('icontype' in k and not ('icondata' in k)) or ('icondata' in k and not ('icontype' in k)):
203         if DEBUG:
204             print >> sys.stderr,"socnet: SOCIAL_*: persinfo: key 'icontype' without 'icondata' or vice versa"
205         return False
206
207     if signed:
208         if not ('insert_time' in k) or not isinstance(persinfo['insert_time'],int):
209             if DEBUG:
210                 print >> sys.stderr,"socnet: SOCIAL_*: persinfo: key 'insert_time' missing or value wrong type"
211             return False
212
213     for key in k:
214         if key not in ['name','icontype','icondata','insert_time']:
215             if DEBUG:
216                 print >> sys.stderr,"socnet: SOCIAL_*: persinfo: unknown key",key,"in dict"
217             return False
218
219     return True
220
221
222 def isValidIconType(type):
223     """ MIME-type := type "/" subtype ... """
224     if not isinstance(type,str):
225         return False
226     idx = type.find('/')
227     ridx = type.rfind('/')
228     return idx != -1 and idx == ridx
229
230 def isValidIconData(data):
231     if not isinstance(data,str):
232         return False
233     
234 #    if DEBUG:
235 #        print >>sys.stderr,"socnet: SOCIAL_*: persinfo: IconData length is",len(data)
236     
237     return len(data) <= ICON_MAX_SIZE
238
239
240
241 def save_ssocnet_peer(self,permid,record,persinfo_ignore,hrwidinfo_ignore,ipinfo_ignore):
242     """ This function is used by both BootstrapMsgHandler and 
243         OverlapMsgHandler, and uses their database pointers. Hence the self 
244         parameter. persinfo_ignore and ipinfo_ignore are booleans that
245         indicate whether to ignore the personal info, resp. ip info in
246         this record, because they were unsigned in the message and
247         we already received signed versions before.
248     """
249     if permid == self.mypermid:
250         return
251     
252     # 1. Save persinfo
253     if not persinfo_ignore:
254         persinfo = record['persinfo']
255         
256         if DEBUG:
257             print >>sys.stderr,"socnet: Got persinfo",persinfo.keys()
258             if len(persinfo.keys()) > 1:
259                 print >>sys.stderr,"socnet: Got persinfo THUMB THUMB THUMB THUMB"
260         
261         # Arno, 2008-08-22: to avoid UnicodeDecode errors when commiting 
262         # on sqlite
263         name = str2unicode(persinfo['name'])
264
265         if DEBUG:
266             print >> sys.stderr,"socnet: SOCIAL_OVERLAP",show_permid_short(permid),`name`
267         
268         if self.peer_db.hasPeer(permid):
269             self.peer_db.updatePeer(permid, name=name)
270         else:
271             self.peer_db.addPeer(permid,{'name':name})
272     
273         # b. Save icon
274         if 'icontype' in persinfo and 'icondata' in persinfo: 
275             if DEBUG:
276                 print >> sys.stderr,"socnet: saving icon for",show_permid_short(permid),`name`
277             self.peer_db.updatePeerIcon(permid, persinfo['icontype'],persinfo['icondata'])