def __broadcast_message(self, msg: ReliableMessage) -> Optional[Content]: """ Deliver message to everyone@everywhere, including all neighbours """ self.debug('broadcasting message from: %s' % msg.sender) if self.__traced(msg=msg, station=self.station): self.error('ignore traced msg: %s in %s' % (self.station, msg.get('traces'))) return None session_server = self.session_server # push to all neighbors connected th current station neighbors = self.__neighbors.copy() sent_neighbors = [] success = 0 for sid in neighbors: if sid == self.station: continue sessions = session_server.active_sessions(identifier=sid) if len(sessions) == 0: self.warning('remote station (%s) not connected, try later.' % sid) continue if self.__push_message(msg=msg, receiver=sid, sessions=sessions): sent_neighbors.append(sid) success += 1 # push to the bridge (octopus) of current station sid = self.station sessions = session_server.active_sessions(identifier=sid) if len(sessions) > 0: # tell the bridge ignore this neighbor stations sent_neighbors.append(sid) msg['sent_neighbors'] = ID.revert(sent_neighbors) self.__push_message(msg=msg, receiver=sid, sessions=sessions) # FIXME: what about the failures # response text = 'Message broadcast to %d/%d stations' % (success, len(neighbors)) res = TextContent(text=text) res.group = msg.group return res
def __receipt(message: str, msg: ReliableMessage) -> Content: receipt = ReceiptCommand.new(message=message) for key in ['sender', 'receiver', 'time', 'group', 'signature']: value = msg.get(key) if value is not None: receipt[key] = value return receipt
def __message_exists(self, msg: ReliableMessage, path: str) -> bool: if not self.exists(path=path): return False # check whether message duplicated messages = self.__load_messages(path=path) for item in messages: if item.get('signature') == msg.get('signature'): # only same messages will have same signature return True
def __traced(self, msg: ReliableMessage, station: Station) -> bool: sid = station.identifier traces = msg.get('traces') if traces is not None: for node in traces: if isinstance(node, str): return sid == node elif isinstance(node, dict): return sid == node.get('ID') else: self.error('traces node error: %s' % node)
def __forward_group_message(self, msg: ReliableMessage) -> bool: receiver = msg.receiver key = msg.get('key') if key is None: # get key from cache sender = msg.sender group = msg.group key = self.__key_cache.get_key(sender=sender, member=receiver, group=group) if key is None: # cannot forward group message without key return False msg['key'] = key forward = ForwardContent(message=msg) return self.send_content(sender=None, receiver=receiver, content=forward)
def __traced(self, msg: ReliableMessage, station: ID) -> bool: traces = msg.get('traces') if traces is None: # broadcast message starts from here traces = [station] else: for node in traces: if isinstance(node, str): if station == node: return True elif isinstance(node, dict): if station == node.get('ID'): return True else: self.error('traces node error: %s' % node) # broadcast message go through here traces.append(station) msg['traces'] = ID.revert(members=traces) return False
def departure(self, msg: ReliableMessage) -> Optional[ReliableMessage]: receiver = msg.receiver if receiver == g_station.identifier: self.debug('msg for %s will be stopped here' % receiver) return None sent_neighbors = msg.get('sent_neighbors') if sent_neighbors is None: sent_neighbors = [] else: msg.pop('sent_neighbors') neighbors = self.__neighbors.copy() success = 0 for sid in neighbors: if sid in sent_neighbors: self.debug('station %s in sent list, ignore this neighbor' % sid) continue if self.__deliver_message(msg=msg, neighbor=sid): success += 1 # FIXME: what about the failures if g_released: # FIXME: how to let the client knows where the message reached return None # response sender = msg.sender meta = g_messenger.facebook.meta(identifier=sender) if meta is None: # waiting for meta return None text = 'Message broadcast to %d/%d stations' % (success, len(neighbors)) self.debug('outgo: %s, %s | %s | %s' % (text, msg['signature'][:8], sender.name, msg.receiver)) res = TextContent(text=text) res.group = msg.group return self.__pack_message(content=res, receiver=sender)
def __process_group_message( self, msg: ReliableMessage) -> Optional[ReliableMessage]: """ Separate group message and forward them one by one if members not found, drop this message and query group info from sender; if 'keys' found in group message, update them to cache; remove non-exists member from 'keys split group message with members, forward them :param msg: :return: ReliableMessage as result """ s_msg = self.verify_message(msg=msg) if s_msg is None: # signature error? return None sender = msg.sender receiver = msg.receiver if not self.facebook.exists_member(member=sender, group=receiver): if not self.facebook.is_owner(member=sender, group=receiver): # not allow, kick it out cmd = GroupCommand.expel(group=receiver, member=sender) sender = self.facebook.current_user.identifier receiver = msg.sender env = Envelope.create(sender=sender, receiver=receiver) i_msg = InstantMessage.create(head=env, body=cmd) s_msg = self.encrypt_message(msg=i_msg) if s_msg is None: self.error('failed to encrypt message: %s' % i_msg) self.suspend_message(msg=i_msg) return None return self.sign_message(msg=s_msg) members = self.facebook.members(receiver) if members is None or len(members) == 0: # members not found for this group, # query it from sender res = GroupCommand.query(group=receiver) else: # check 'keys' keys = msg.get('keys') if keys is not None: # remove non-exists members from 'keys' expel_list = [] for item in keys: if item == 'digest': self.info('key digest: %s' % keys[item]) continue m = ID.parse(identifier=item) if m not in members: self.info('remove non-exists member: %s' % item) expel_list.append(m) if len(expel_list) > 0: # send 'expel' command to the sender cmd = GroupCommand.expel(group=receiver, members=expel_list) g_messenger.send_content(sender=None, receiver=sender, content=cmd) # update key map self.__key_cache.update_keys(keys=keys, sender=sender, group=receiver) # split and forward group message, # respond receipt with success or failed list res = self.__split_group_message(msg=msg, members=members) # pack response if res is not None: sender = self.facebook.current_user.identifier receiver = msg.sender env = Envelope.create(sender=sender, receiver=receiver) i_msg = InstantMessage.create(head=env, body=res) s_msg = self.encrypt_message(msg=i_msg) if s_msg is None: self.error('failed to encrypt message: %s' % i_msg) self.suspend_message(msg=i_msg) return None return self.sign_message(msg=s_msg)