#!/usr/bin/env python3
"""
catchall - interception + logging harness for the SvT revival.

Every dead backend host is pointed at 10.0.2.2 via /system/etc/hosts on the
emulator. This process answers all of them and logs everything, so the client
tells us what it wants instead of us guessing:

    :8443  TLS. WebSocket upgrade -> gu3 protocol. Anything else -> HTTP log.
    :8080  plain HTTP log.
    :9001  plain ws:// gu3 protocol (the built-in local-server path).

Nothing here tries to be correct yet; it is a listening post. Unknown requests
are answered with something benign so the client keeps moving and reveals the
next thing it asks for.
"""

import asyncio
import json
import ssl
import time
import logging
from http import HTTPStatus

import websockets
from websockets.asyncio.server import serve

import gu3server as gu3

LOG = logging.getLogger("catchall")
HTTP_LOG = logging.getLogger("http")

CERT = "/root/ants/tls/server.pem"

# every HTTP request we see, in order -- this is the interesting artifact
SEEN = []


def _log_http(where, path, headers, host=None):
    host = host or headers.get("Host", "?")
    SEEN.append((time.strftime("%H:%M:%S"), where, host, path))
    HTTP_LOG.info("[%s] %s %s", where, host, path)
    for k, v in headers.items():
        if k.lower() in ("user-agent", "content-type", "content-length",
                         "authorization", "x-requested-with"):
            HTTP_LOG.debug("        %s: %s", k, v)


def make_process_request(where):
    """websockets calls this before the handshake. Return a Response to short
    circuit (i.e. it was a plain HTTP request, not a WS upgrade)."""
    def process_request(connection, request):
        hdrs = request.headers
        upgrade = (hdrs.get("Upgrade") or "").lower()
        if upgrade == "websocket":
            LOG.info("[%s] WEBSOCKET UPGRADE  host=%s path=%s",
                     where, hdrs.get("Host", "?"), request.path)
            return None  # let the WS handshake proceed
        _log_http(where, request.path, hdrs)
        body = json.dumps({"ip": "10.0.2.2", "ok": True,
                           "ts": int(time.time())}).encode()
        return connection.respond(HTTPStatus.OK, body.decode() + "\n")
    return process_request


async def plain_http(reader, writer):
    """Bare HTTP listener for :80 -- just log and answer 200."""
    try:
        line = await asyncio.wait_for(reader.readline(), 10)
        if not line:
            return
        parts = line.decode("latin1").split()
        method, path = (parts + ["?", "?"])[:2]
        headers = {}
        while True:
            l = await asyncio.wait_for(reader.readline(), 10)
            if not l or l in (b"\r\n", b"\n"):
                break
            if b":" in l:
                k, _, v = l.decode("latin1").partition(":")
                headers[k.strip()] = v.strip()
        _log_http("38080", f"{method} {path}", headers)
        # ServerMessenger.FetchPublicIp() GETs the server root and reads "ip"
        # out of the response; without it, publicIp is never populated.
        body = json.dumps({"ip": "10.0.2.2", "ok": True,
                           "ts": int(time.time())}).encode()
        writer.write(b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n"
                     b"Content-Length: " + str(len(body)).encode() +
                     b"\r\nConnection: close\r\n\r\n" + body)
        await writer.drain()
    except Exception as e:
        LOG.debug("plain_http: %s", e)
    finally:
        try:
            writer.close()
        except Exception:
            pass


async def main():
    logging.basicConfig(
        level=logging.DEBUG,
        format="%(asctime)s %(name)-8s %(levelname)-7s %(message)s",
        datefmt="%H:%M:%S",
    )
    logging.getLogger("websockets").setLevel(logging.INFO)

    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    ctx.load_cert_chain(CERT)
    # be maximally permissive -- this is a 2018 Mono/BoringSSL client
    ctx.minimum_version = ssl.TLSVersion.TLSv1
    try:
        ctx.set_ciphers("ALL:@SECLEVEL=0")
    except ssl.SSLError:
        ctx.set_ciphers("DEFAULT")

    LOG.info("=" * 62)
    LOG.info(" catchall up:  :38443 (TLS ws+http) :38080 (http) :39001 (ws)")
    LOG.info("=" * 62)

    srv443 = await serve(gu3.handle, "0.0.0.0", 38443, ssl=ctx,
                         process_request=make_process_request("38443"),
                         ping_interval=None, max_size=8 * 1024 * 1024)
    srv9001 = await serve(gu3.handle, "0.0.0.0", 39001,
                          process_request=make_process_request("39001"),
                          ping_interval=None, max_size=8 * 1024 * 1024)
    srv80 = await asyncio.start_server(plain_http, "0.0.0.0", 38080)

    async with srv443, srv9001, srv80:
        await asyncio.Future()


if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        LOG.info("--- request log ---")
        for t, w, h, p in SEEN:
            LOG.info("%s [%s] %s %s", t, w, h, p)
