async def _send_message(self, request): """Send message and receive response data""" data = b'' await self.online.wait() try: if request.destination not in self.dest_cache: self.dest_cache[request.destination] = await i2plib.dest_lookup( request.destination + ".b32.i2p", loop=self.loop, sam_address=self.sam_address) reader, writer = await i2plib.stream_connect(self.session_name, self.dest_cache[request.destination], loop=self.loop, sam_address=self.sam_address) except (i2plib.CantReachPeer, i2plib.InvalidKey, i2plib.Timeout, \ i2plib.KeyNotFound, i2plib.PeerNotFound, i2plib.I2PError): logger.debug("Can't connect to {}".format(request.destination)) except ConnectionError: logger.warning("_send_message fails: can't connect to SAM") else: writer.write(bytes(request)) data = await reader.read(MAX_MESSAGE_LENGTH) writer.close() return data
async def _receive_message(self, reader, writer, destination): with suppress(asyncio.CancelledError): destination = i2plib.Destination(destination.decode()) name = self.addressbook.get_name(destination.base32) if not name and self.ignore_unauthorized: writer.close() return try: data = await asyncio.wait_for(reader.read(MAX_MESSAGE_LENGTH), DEFAULT_TIMEOUT) except asyncio.TimeoutError: writer.close() return try: request = Message.parse(data, destination.base32) request.name = name except ValidationError as e: logger.warning("Invalid request: "+str(e)) writer.close() return if request.uuid.hex in self.uuid_log: logger.debug("Duplicate message: "+ str(request)) writer.write(bytes(Message(code=Message.OK))) writer.close() return self.uuid_log.append(request.uuid.hex) logger.debug("Received message: " + str(request)) if request.code == Message.PING \ or request.code == Message.AUTHORIZATION: writer.write(bytes(Message(code=Message.OK))) writer.close() if request.code == Message.PING: await self.on_ping(request) elif request.code == Message.AUTHORIZATION: await self.on_authorization(request) elif request.name: writer.write(bytes(Message(code=Message.OK))) writer.close() if request.code == Message.PRIVATE: await self.on_private_message(request) elif request.code == Message.PUBLIC: await self.on_public_message(request) elif request.code == Message.UNAUTHORIZED: await self.on_unauthorized(request) else: writer.write(bytes(Message(code=Message.UNAUTHORIZED))) writer.close() return await self._dest_online(request.destination)
async def _dest_online(self, destination): """Is triggered when any Message or data is received from the destination""" name = self.addressbook.get_name(destination) if name: if not self.addressbook.is_online(destination): logger.debug("Contact becomes online "+destination) await self.on_contact_online(name) await self.senders[destination].send_stash() self.addressbook.set_online(destination)
async def start(self): """Start all tasks""" self.destination = await load_destination(self.loop, self.sam_address, self.datadir) c = await load_contacts(self.loop, self.datadir) self.addressbook.update(c) logger.debug("Contacts: " + str(self.addressbook)) for address in self.addressbook.values(): self.senders[address] = MessageSender(self.loop, self.sender) self.receiver_task = self.create_task(self.receiver()) self.pinger_task = self.create_task(self.pinger()) self.sam_session_loop_task = self.create_task(self.sam_session_loop())
async def sam_session_loop(self): """Self-healing SAM session""" with suppress(asyncio.CancelledError): while True: try: self.session_reader, self.session_writer = await \ i2plib.create_session(self.session_name, destination=self.destination, sam_address=self.sam_address, loop=self.loop) except (i2plib.DuplicatedDest): logger.error("SAM destination already exists") except ConnectionError: logger.error("SAM API is unavailable") else: self.online.set() logger.debug("SAM session is created: " \ + self.destination.base32) await self.session_reader.read() logger.error("SAM session is dead") self.online.clear() logger.info("Restarting SAM session in {} seconds...".format( SESSION_RESTART_TIMEOUT)) await asyncio.sleep(SESSION_RESTART_TIMEOUT)
async def sender(self, queue): """Message sender task""" with suppress(asyncio.CancelledError): while True: await self.online.wait() msg = await queue.get() delivered = False for x in range(SEND_RETRIES): try: data = await asyncio.wait_for(self._send_message(msg), DEFAULT_TIMEOUT) except asyncio.TimeoutError: pass else: if data: try: resp = Message.parse(data, msg.destination) except ValidationError as e: logger.warning( "Invalid response from {}: {}".format( msg.destination, e)) else: delivered = True await self._dest_online(msg.destination) if resp.code == Message.OK: logger.debug(str(msg) + " delivered") elif resp.code == Message.UNAUTHORIZED: logger.debug(str(msg) + " unauthorized") resp.name = self.addressbook.get_name( resp.destination) await self.on_unauthorized(resp) break else: logger.debug(str(id(msg)) + " retrying") await asyncio.sleep(DEFAULT_TIMEOUT / 2) if delivered: logger.debug(str(msg) + " delivered, retries: " + str(x)) else: self.senders[msg.destination].stash(msg)