def __init__(self, map): self.togglees = [ ] # Calls to make to update display if wodge is folded/unfolded self.wodge = None self.string = None self.signature = None self.initial_time = None self.initial_distance = None self.unit_decay_time = 60*60*24*7 self.opinions = { } # acq.name -> distance at initial_time self.collapsed = 0 for (key, tmpl) in self.member_tmpls.items(): if map.has_key(key): val = map[key] if check.matches(val, tmpl): setattr(self, key, val) # .initial_time. if type(self.initial_time) in (types.FloatType, types.IntType, types.LongType): try: self.initial_time = float(self.initial_time) except: self.initial_time = None if type(self.initial_time) != types.FloatType: self.initial_time = time.time() # .opinions. for (key, dist) in self.opinions.items(): if type(dist) not in (types.FloatType, types.IntType, types.LongType): del self.opinions[key]
def get_checked_config(name, tmpl, default): """Ensures @E13: (ret is default) or check.matches(ret, tmpl).""" check.check_has_type(name, types.StringType) check.check_is_template(tmpl) try: ret = try_get_config(name) except: return default if not check.matches(ret, tmpl): print (_("Garbled config file `%s'; expecting something matching template `%s'.") % (name, str(tmpl))) ret = default if ret is not default: check.check_matches(ret, tmpl) return ret
def __init__(self,app): self.lock = threading.RLock() self.node = app.node self.app = app # Visible status self.quiet = 0 self.activity = '' self.n_unseen_messages = 0 # Results of /find and /search self.identity_list = [ ] self.file_list=[ ] # Python environment self.exec_vars = { 'app' : app, 'node' : app.node, 'daemon':app.daemon } # Detect repeated offline messages self.offline_message_buffer = [ ] # Store a copy of recent messages self.incoming_message_history = [ ] # used for /ls self.current_path_nickname = '' self.current_path = [ ] # While quiet, messages are buffered for later display. # Tuples of form (request, opt-address, time.time()). # address for offline-sent messages is None. # The time item must be wrt this client's epoch. self.unread_message_list = [ ] config = utility.get_checked_config("chat", types.DictionaryType, { }) for name,tmpl in [('quiet', 'any'), ('activity', 'any'), ('offline_message_buffer', 'any'), ('incoming_message_history', [('any', 'any timestamp')]), ('unread_message_list', [((types.StringType,), 'opt-address', 'any timestamp')])]: if config.has_key(name): config_val = config[name] if check.matches(config_val, tmpl): setattr(self, name, config_val) else: print (_("Warning: ignoring chat config item %s, which doesn't match %s.") % (name, tmpl)) # The current version of Circle always writes these timestamps in # standardized form, but previous versions wrote a mixture of floats # and longs. for i in xrange(len(self.incoming_message_history)): request,tstamp = self.incoming_message_history[i] if type(tstamp) == types.LongType: self.incoming_message_history[i] = (request, standard2host_timestamp(tstamp)) check.check_matches(self.incoming_message_history[i], incoming_message_history_item_tmpl) # Loose proof: definition of check.is_any_timestamp, @E17. # Relevance: @R.I16. for i in xrange(len(self.unread_message_list)): request,addr,tstamp = self.unread_message_list[i] if type(tstamp) == types.LongType: self.unread_message_list[i] = (request, addr, standard2host_timestamp(tstamp)) check.check_matches(self.unread_message_list[i], unread_message_list_item_tmpl) # Loose proof: as above. # Relevance: @R.I15. self.recall_list = [ ] # Detect repeated messages self.message_buffer = [ ] self.channel = [ ] self.channels = channels.Channels(self.app) self.chat_check_invar()
def retrieve_cached_messages_thread(self,on_complete): pipe = self.node.retrieve( hash.hash_of('offline message '+self.app.name_server.public_key_name), settings.cache_redundancy) # Loose proof of @R50: @I22, @E22. pipe_reads = [ ] if not pipe.finished(): while 1: pipe_reads.extend(pipe.read_all()) if pipe.finished(): break yield 'sleep',1 pipe.stop() # pjm 2002-08-05: I've changed the above to sleep only if # one read_all call isn't enough. However, I don't know why # sleep is wanted in the first place, or whether a different # duration might be better. (Python sleep allows its # argument to be fractional, implemented in terms of # select.) unique_messages = [ ] for item in pipe_reads: # I think we're guaranteed that item matches ('af_inet_address', 'any'). if not check.matches(item[1], {'type' : types.StringType, 'crypt' : ('any', 'any')}): # print bad peer pass elif item[1] not in unique_messages: unique_messages.append(item[1]) message_list = [ ] for raw_msg in unique_messages: if type(raw_msg) == type({}) \ and raw_msg.get('type','') == 'offline message' \ and raw_msg.has_key('crypt'): try: decrypt = self.app.name_server.decrypt(raw_msg['crypt']) if not check.matches(decrypt, ('text', (types.StringType,), types.LongType)): raise error.Error('bad decrypted reply') # Remove from caches for thing in pipe_reads: if thing[1] == raw_msg: try: ticket, template, wait = self.node.call(\ thing[0],('data cache remove',decrypt[0])) if wait: yield ('call',(self.node,ticket)) self.node.get_reply(ticket,template) except error.Error: pass message_list.append((decrypt[2],decrypt[1])) except error.Error: pass message_list.sort() self.lock.acquire() try: any = 0 for msg in message_list: if msg not in self.offline_message_buffer: self.offline_message_buffer = [msg] + self.offline_message_buffer[:50] new_item = (msg[1], None, standard2host_timestamp(msg[0])) # Proof of @R36: msg is taken from message_list. # message_list is local to this method, and is not # passed to any other method (so is not shared with any # other thread). message_list starts as empty and is # written to solely as (decrypt[2],decrypt[1]) pairs, # and only where decrypt has already been found to # match ('any', ('string',), 'long'). The relevant # types are immutable. check.check_matches(new_item, unread_message_list_item_tmpl) # Proof: the @R36 proof just above also shows that # msg[1] (i.e. decrypt[1]) matches ('string',) and # is immutable. new_item[1] matches because # is_opt_address(None). new_item[2] matches from @E17. # Relevance: @R.I15 self.unread_message_list.append(new_item) any = 1 else: print _("Duplicate offline message.") finally: self.lock.release() on_complete(self,any)
def start(self, status_monitor=None): utility.Task_manager.start(self) acq_list = utility.get_checked_config('acquaintances', types.ListType, [ ]) for item in acq_list: if not check.matches(item, Acquaintance.map_tmpl): print _("Warning: corrupted acquaintances config file; ignoring item: "), item continue acq = Acquaintance(self, item, 1) self.acquaintances[acq.name] = acq self.nicknames[acq.nickname] = acq acq.start() def make_acquaintance_noaddr(self, info): name = key_name(info['key']) acq = Acquaintance(self, {'info': info, 'name': name, 'nickname': self.choose_nickname(info['name'])},0) self.acquaintances[name] = acq self.nicknames[acq.nickname] = acq acq.start() acq.start_watching(self.node) self.acquaintance_status_changed(acq, "create") return acq self.me = make_acquaintance_noaddr(self,self.info) # Other me may want to test identity # May start chatting before test complete self.node.add_handler('identity test', self, ('name',), crypto.pubkey.signature_tmpl) self.node.add_handler('identity query', self, (), Acquaintance.info_template) self.node.add_handler('identity watch', self, (), types.DictionaryType) self.node.add_handler('identity connecting', self) self.node.add_handler('identity status changed', self, ('any', Acquaintance.status_template)) self.node.add_handler('identity disconnecting', self,('string', 'opt-text')) self.node.add_handler('identity abort', self) self.node.publish(self.public_key_name,self.get_info_func, settings.identity_redundancy) self.node.publish(self.public_key_name_offline,self.get_info_func, settings.identity_redundancy) self.node.publish(self.service_name,self.get_info_func, settings.identity_redundancy) for item in self.info['keywords']: self.node.publish(hash.hash_of('identity-name '+item), self.get_info_func, settings.identity_redundancy) def startup_thread(self, status_monitor=status_monitor): list = self.acquaintances.values() list.sort(lambda x,y: cmp(x.sort_value(),y.sort_value())) for item in list: item.start_watching(self.node) #start watching tends to breed, try to make sure we don't get #too many threads. #yes, this is hacky #print item.nickname, threading.activeCount() #time.sleep(0.25) while 1: yield 'sleep',0.25 if threading.activeCount() < 40: break self.me.start_watching(self.node) while not self.me.watched: yield 'sleep',0.1 online = self.me.online address = self.me.address if online: if status_monitor: status_monitor(_('Shutting down your other peer.')) while 1: ticket,template,wait = self.node.call(address, ('identity abort',)) if wait: yield 'call',(self.node,ticket) try: dummy_result = self.node.get_reply(ticket, template) except error.Error: break yield 'sleep',4 self.me.online = 1 self.me.address = self.node.address self.me.connect_time = time.time() # Task to retrieve existing watchers # Task to poll existing watchers utility.start_thread(name_server_watch_poller_thread(self)) # now refresh my own offline presence pipe = self.node.retrieve(self.public_key_name_offline, settings.cache_redundancy) list = [ ] while not pipe.finished(): for item in pipe.read_all(): if type(item[1]) == types.DictType and \ item[1].get('type') == 'identity offline' and \ item[1].get('salt'): list.append(item) yield 'sleep',2 if not self.running: return pipe.stop() #if len(list) != 4: # print _("%d peers holding your offline presence.") % len(list) for item in list: address, value = item key = hash.hash_of(safe_pickle.dumps(self.sign(value['salt']))) ticket, template, wait = self.node.call(address, ('data cache remove',key)) if wait: yield 'call',(self.node,ticket) try: dummy_result = self.node.get_reply(ticket,template) except error.Error: pass self.lock.acquire() try: package = { 'name' : self.info['name'], 'human-name' : self.info['human-name'], 'description': self.info['description'], 'timezone' : self.info['timezone'], 'key' : self.public_key, 'keywords' : self.info['keywords'], } finally: self.lock.release() package_dumped = safe_pickle.dumps(package) signature = self.sign(package_dumped) # now publish and cache offline identity value = { 'type' : 'identity offline', 'package' : package_dumped, 'signature' : signature, 'salt' : utility.random_bytes(settings.name_bytes) } lock = hash.hash_of(hash.hash_of(safe_pickle.dumps(self.sign(value['salt'])))) publications = [ self.public_key_name_offline, self.service_name ] for item in package['keywords']: publications.append(hash.hash_of('identity-name '+item)) # thomasV # redundancy 4: this is the meta-publish result, publish_thread = self.app.cache.publish(publications,value,lock, 4) yield 'wait',publish_thread utility.start_thread(startup_thread(self))
def try_address_thread(self, address, the_node, result): if self.online and self.address == address: result.append(0) return key = self.info['key'] id_test_result = [] yield 'wait', identity_test_thread(address, key, the_node, id_test_result) if not id_test_result[0]: print "identity did not pass test" #result.append(0) #return ticket,template, wait = the_node.call(address,('identity query',)) if wait: yield 'call',(the_node,ticket) try: info = the_node.get_reply(ticket,template) except: result.append(0) return if not check.matches(info, Acquaintance.info_template): node.bad_peer(address, _("bad response to 'identity query': ") + `info`) result.append(0) return self.info = info if info.get('peer-name') == the_node.name: result.append(0) return ticket,template, wait = the_node.call(address,('identity watch',)) if wait: yield 'call',(the_node,ticket) try: status = the_node.get_reply(ticket,template) except: result.append(0) return if type(status) != types.DictionaryType: status = { } was_online = self.online self.address = address self.online = 1 self.watched = 1 self.status = status # up_time is an interval. connect_time is time in my zone. up_time = self.info.get('up time') if type(up_time) == types.IntType: self.connect_time = time.time() - up_time else: self.connect_time = None if self.drm: the_node.trusted_addresses.append(address) else: while address in the_node.trusted_addresses: the_node.trusted_addresses.remove(address) self.start_watching(the_node) self.name_server.acquaintance_status_changed(self, 'discover') result.append(not was_online) return
def gossip_fetch_thread(self,acq): self.fetch_threads+=1 if not self.running: self.fetch_threads-=1 return if acq.distance != None: #acq.start_watching(self.node, 1) acq.lock.acquire() online = acq.online address = acq.address distance = acq.distance acq.lock.release() if distance == None or not online: self.fetch_threads-=1 return pos = 0 fetch_timeout = node.make_timeout(settings.gossip_fetch_time) while not node.is_timed_out(fetch_timeout): try: ticket, template, wait = self.node.call(address,('gossip list',pos,pos+20)) if wait: yield 'call',(self.node,ticket) result = self.node.get_reply(ticket,template) if not check.matches(result, [(types.IntType, 'any', 'any')]): node.bad_peer(address, _("Bad reply to 'gossip list': ") + `result`) break if len(result) == 0: break all_ok = 1 # effic: sort self.gossip, sort the returned results, # to speed up searches for signatures. However, I # haven't yet seen gossip_fetch_task come up in # profiles. for item in result: already_there = 0 for wodge in self.gossip: if wodge.signature == item[2]: #if wodge.distance(self.app.name_server) > from_fixed(item[0])+distance: # # TODO: What to do with unit_decay_time? # wodge.initial_distance = from_fixed(item[0])+distance # wodge.initial_time = time.time() wodge.opinions[acq.name] = from_fixed(item[0]) - wodge.decay() already_there = 1 break if already_there: continue try: ticket, template, wait = self.node.call( address,('gossip get',item[2])) if wait: yield 'call',(self.node,ticket) string = self.node.get_reply(ticket, template) wodgewodge = safe_pickle.loads(string) except error.Error: all_ok = 0 break #TODO: Confirm signature of known people wodge = Wodge({ 'wodge': wodgewodge, 'string': string, 'signature': item[2], 'initial_time': time.time(), 'initial_distance': None, #'initial_distance': from_fixed(item[0]) + distance 'unit_decay_time': item[1], 'opinions': { acq.name : from_fixed(item[0]) }, 'collapsed': 0}) if not self.insert_wodge(wodge): all_ok = 0 break if not all_ok: break pos = pos + 18 except error.Error: break self.fetch_threads-=1