def __attach_key_digest(self, msg: ReliableMessage): # check message delegate if msg.delegate is None: msg.delegate = self.transceiver if msg.encrypted_key is not None: # 'key' exists return keys = msg.encrypted_keys if keys is None: keys = {} elif 'digest' in keys: # key digest already exists return # get key with direction sender = msg.sender group = msg.group if group is None: receiver = msg.receiver key = self.key_cache.cipher_key(sender=sender, receiver=receiver) else: key = self.key_cache.cipher_key(sender=sender, receiver=group) # get key data data = key.data if data is None or len(data) < 6: return # get digest pos = len(data) - 6 digest = sha256(data[pos:]) base64 = base64_encode(digest) # set digest pos = len(base64) - 8 keys['digest'] = base64[pos:] msg['keys'] = keys
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 __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 __split_group_message(self, msg: ReliableMessage, members: List[ID]) -> Optional[Content]: """ Split group message for each member """ messages = msg.split(members=members) success_list = [] failed_list = [] for item in messages: if item.delegate is None: item.delegate = msg.delegate if self.__forward_group_message(msg=item): success_list.append(item.receiver) else: failed_list.append(item.receiver) response = ReceiptCommand(message='Group message delivering') if len(success_list) > 0: response['success'] = ID.revert(success_list) if len(failed_list) > 0: response['failed'] = ID.revert(failed_list) # failed to get keys for this members, # query from sender by invite members sender = msg.sender group = msg.receiver cmd = GroupCommand.invite(group=group, members=failed_list) self.send_content(sender=None, receiver=sender, content=cmd) return response
def process_reliable_message( self, msg: ReliableMessage) -> Optional[ReliableMessage]: # check message delegate if msg.delegate is None: msg.delegate = self.transceiver receiver = msg.receiver if receiver.is_group: # deliver group message res = self.transmitter.deliver_message(msg=msg) if receiver.is_broadcast: # if this is a broadcast, deliver it, send back the response # and continue to process it with the station. # because this station is also a recipient too. if res is not None: self.messenger.send_message(msg=res, callback=None) else: # or, this is is an ordinary group message, # just deliver it to the group assistant # and return the response to the sender. return res # try to decrypt and process message try: return super().process_reliable_message(msg=msg) except LookupError as error: if str(error).startswith('receiver error'): # not mine? deliver it return self.transmitter.deliver_message(msg=msg) else: raise error
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 __load_login( self, identifier: ID ) -> (Optional[LoginCommand], Optional[ReliableMessage]): path = self.__path(identifier=identifier) self.info('Loading login from: %s' % path) dictionary = self.read_json(path=path) if dictionary is None: return None, None cmd = dictionary.get('cmd') msg = dictionary.get('msg') if cmd is not None: cmd = LoginCommand(cmd) if msg is not None: msg = ReliableMessage.parse(msg=msg) return cmd, msg
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_reliable_message( self, msg: ReliableMessage) -> Optional[ReliableMessage]: # check message delegate if msg.delegate is None: msg.delegate = self receiver = msg.receiver if receiver.is_group: # FIXME: check group meta/profile meta = self.facebook.meta(identifier=receiver) if meta is None: self.suspend_message(msg=msg) self.info('waiting for meta of group: %s' % receiver) return None # process group message return self.__process_group_message(msg=msg) # try to decrypt and process message return super().process_reliable_message(msg=msg)
def __load_messages(self, path: str) -> List[ReliableMessage]: text = self.read_text(path=path) lines = text.splitlines() self.debug('read %d line(s) from %s' % (len(lines), path)) messages = [] for line in lines: msg = line.strip() if len(msg) == 0: self.warning('skip empty line') continue try: msg = json_decode(data=utf8_encode(string=msg)) msg = ReliableMessage.parse(msg=msg) messages.append(msg) except Exception as error: self.error('message package error %s, %s' % (error, line)) return messages
def __load_messages(self, path: str) -> list: data = self.read_text(path=path) lines = data.splitlines() self.info('read %d line(s) from %s' % (len(lines), path)) # messages = [ReliableMessage(json.loads(line)) for line in lines] messages = [] for line in lines: msg = line.strip() if len(msg) == 0: self.info('skip empty line') continue try: msg = json.loads(msg) msg = ReliableMessage(msg) messages.append(msg) except Exception as error: self.info('message package error %s, %s' % (error, line)) return messages
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 __split_group_message(self, msg: ReliableMessage) -> Optional[Content]: receiver = self.facebook.identifier(msg.envelope.receiver) assert receiver.type.is_group(), 'receiver not a group: %s' % receiver members = self.facebook.members(identifier=receiver) if members is not None: messages = msg.split(members=members) success_list = [] failed_list = [] for item in messages: if self.deliver(msg=item) is None: failed_list.append(item.envelope.receiver) else: success_list.append(item.envelope.receiver) response = ReceiptCommand.new( message='Message split and delivering') if len(success_list) > 0: response['success'] = success_list if len(failed_list) > 0: response['failed'] = failed_list return response
def arrival(self, msg: ReliableMessage) -> Optional[ReliableMessage]: # check message delegate if msg.delegate is None: msg.delegate = self sid = g_station.identifier if self.__traced(msg=msg, station=g_station): self.debug( 'current station %s in traces list, ignore this message: %s' % (sid, msg)) return None if not self.__deliver_message(msg=msg, neighbor=sid): self.error('failed to send income message: %s' % msg) return None if g_released: # FIXME: how to let the client knows where the message reached return None # response sender = msg.sender text = 'Message reached station: %s' % g_station self.debug('income: %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)
def unpack(self, msg: dimp.ReliableMessage) -> dimp.Content: msg = transceiver.verify(msg) msg = msg.trim(self.identifier) msg = transceiver.decrypt(msg) content = msg.content return content
def __load_message(self, signature: str) -> Optional[ReliableMessage]: path = msg_path(signature=signature) msg = self.read_json(path=path) if msg is not None: return ReliableMessage.parse(msg=msg)