async def start(self): logger.debug("Starting app") self.initialize() loop = asyncio.get_running_loop() for s in self.services: logger.debug(f"Starting {s._name}") self.service_tasks.append(loop.create_task(self._start_service(s)))
async def send(self, message): connection = self.connection() if not connection or not connection.active: logger.debug(f"can't send {connection}") return False else: return await connection.send(message)
async def handle_request(self, request): logger.debug("handling friend request") encrypted_data = await request.read() data = self.app.identity.unseal(encrypted_data) payload = Request.decode(data) m = hashlib.sha256() m.update(payload["cert"]) data_digest = m.digest() friend_request = FriendRequest( self.app, cert_bytes=payload["cert"], name=payload["name"], public_key=payload["public_key"], digest=data_digest, ) self.app.handle_friend_request(friend_request) accepted = await friend_request.accepted() if accepted: return web.Response( content_type="application/octet-stream", body=friend_request.seal(await self.app.identity.greeting_payload()), ) else: return web.Response(status=401)
async def send(self, message): async with self.session.post(f"https://{self.host}/", ssl=self.ssl_context, data=message.encode()) as resp: logger.debug("send response %s", resp) if resp.status == 201: return True else: logger.warning("got an unusual status response %s", resp) return False
async def get_file(self, path, range=None): url = f"https://{self.host}{path}" logger.debug("send response %s", url) headers = {} if range: headers["Range"] = f"bytes={range[0]}-{range[1]}" async with self.session.get(url, ssl=self.ssl_context, headers=headers) as resp: return await resp.content.read()
async def add(self): self.accepted_result.set_result(True) friend = Friend( self.app, onion=self.onion_service, name=self.name, cert=self.cert_bytes.decode(), public_key=self.public_key, ) logger.debug(f"adding {friend}") await self.app.friend_list.add(friend) logger.debug(f"done adding {friend}")
async def start(self): logger.debug("starting certificate") if not os.path.isfile(self.server_key_path): name = await self.app.identity.name() logger.debug( f"no key exists in {self.server_key_path}, generating one") key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) service_host = await self.app.identity.service_host() with open(self.server_key_path, "wb") as f: f.write( key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), )) subject = issuer = x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, service_host), x509.NameAttribute(NameOID.GIVEN_NAME, name), ]) cert = (x509.CertificateBuilder().subject_name( subject).issuer_name(issuer).public_key( key.public_key()).serial_number( x509.random_serial_number()).not_valid_before( datetime.datetime.utcnow()).not_valid_after( datetime.datetime.utcnow() + datetime.timedelta(weeks=520)).add_extension( x509.SubjectAlternativeName( [x509.DNSName(service_host)]), critical=False, ).add_extension(x509.BasicConstraints( ca=False, path_length=None), critical=True).sign( key, hashes.SHA256(), default_backend())) public_cert_bytes = cert.public_bytes(serialization.Encoding.PEM) with open(self.server_cert_path, "wb") as f: f.write(public_cert_bytes) else: logger.debug(f"a key exists in {self.server_key_path}, reading it") with open(self.server_cert_path, "rb") as f: public_cert_bytes = f.read() logger.debug(f"done reading key from {self.server_key_path}") self.public_cert_bytes_result.set_result(public_cert_bytes) logger.debug(f"finished certificate")
async def stop(self): try: loop = asyncio.get_running_loop() logger.debug("Stopping app") for t in self.service_tasks: t.cancel() await asyncio.gather(*[ loop.create_task(self._stop_service(s)) for s in self.services ]) finally: if self.delete_at_exit: shutil.rmtree(self.base)
async def connect(self): try: while True: logger.debug(f"starting connect for {self}") self.active = False self.connect_task = asyncio.ensure_future(self._connect()) await self.connect_task except asyncio.CancelledError as e: logger.debug("re-connecting...") except Exception as e: logger.exception(e) await asyncio.sleep(self.pause_time) finally: asyncio.create_task(self.connect())
async def offer_file(self, path): abspath = os.path.abspath(path) name = os.path.basename(path) url = self.app.offer_file(self.friend, abspath) async with aiofiles.open(abspath, "rb") as fh: ft = filetype.match(await fh.read(261)) mimetype = ft.mime if ft else "application/octet-stream" stat = os.stat(abspath) data = File.encode({ "url": url, "size": stat.st_size, "type": ft.mime, "name": name }) async with self.session.post( f"https://{self.host}/", ssl=self.ssl_context, data=data, headers={"Content-Type": "x-slick/file"}, ) as resp: logger.debug("send response %s", resp)
async def get_file(self, *, path, size, target): connection = self.connection() if not connection or not connection.active: logger.debug(f"cannot get connection {connection}") return False else: loop = asyncio.get_event_loop() queue = asyncio.Queue() chunk_count = math.ceil(size / file_chunk_size) for i in range(chunk_count): await queue.put(i) async with aiofiles.open(target, "wb") as fh: await fh.truncate(size) with tqdm(total=size, unit="B", unit_scale=True) as bar: async with AIOFile(target, "wb") as fh: for i in range(concurrency): worker = Worker(queue, fh, connection, size, path, bar) loop.create_task(worker.run()) await queue.join() await fh.fsync()
async def _connect(self): try: logger.debug("trying direct connect") if not self.friend.nearby: logger.debug("no nearby, sleeping") await asyncio.sleep(self.pause_time) return self.host = self.friend.nearby.direct_talk_ip_port logger.debug("doing a direct connect to %s", self.host) async with aiohttp.ClientSession() as session: self.session = session logger.debug("initiating ping for %s", self.host) await self.ping() except asyncio.CancelledError as e: raise e except Exception as e: logger.exception(e) raise e
async def ping(self): while self.running: start_time = datetime.now() try: logger.debug(f"pinging {self}") async with self.session.head(f"https://{self.host}/", ssl=self.ssl_context, timeout=60) as resp: logger.debug(f"ping response from {self} {resp}") self.active = True except Exception as e: logger.debug("error while pinging %s", e) self.active = False finally: end_time = datetime.now() sleep_time = max([ self.pause_time - (end_time - start_time).total_seconds(), 0 ]) logger.debug("sleeping for %s", sleep_time) await asyncio.sleep(sleep_time)
async def run(self): while self.queue.qsize() != 0: index = self.queue.get_nowait() logger.debug(f"worker got index {index} for {self.fh}") byte_range = ( index * file_chunk_size, min(self.size, (index + 1) * file_chunk_size), ) logger.debug(f"worker getting byte range {byte_range} for {index}") content = await self.connection.get_file(self.path, range=byte_range) await self.fh.write(content, offset=byte_range[0]) logger.debug( f"worker done writing byte range {byte_range} for {index}") self.queue.task_done() self.bar.update(byte_range[1] - byte_range[0])
def restart_connection(self): if not self.connect_task: return logger.debug(f"restarting {self}") self.connect_task.cancel()
def print_bootstrap_lines(line): logger.debug(line)