#include "tls.h"
#include "net.h"
#include "crypto.h"
#include "ecc.h"
#include "x509.h"
#include "util.h"
#include "fb.h"
#include "arch.h"
#include "dbg.h"
#include "http.h"

static void phex(const uint8_t *d, uint32_t n)
{
    static const char h[]="0123456789abcdef";
    for(uint32_t i=0;i<n;i++){dbg_putchar(h[(d[i]>>4)&0xF]);dbg_putchar(h[d[i]&0xF]);}
}

static void tls_record_header(uint8_t hdr[5], uint8_t content_type, uint16_t len)
{
    hdr[0] = content_type;
    hdr[1] = 0x03;
    hdr[2] = 0x03;
    hdr[3] = (len >> 8) & 0xFF;
    hdr[4] = len & 0xFF;
}

static int tls_send_record(struct tls_ctx *ctx, uint8_t content_type,
                            const void *data, uint32_t len)
{
    uint8_t record[TLS_RECORD_SIZE + 16];
    uint8_t hdr[5];

    tls_record_header(hdr, content_type, len);

    if(ctx->send_ccs && content_type != TLS_CONTENT_CHANGE_CIPHER_SPEC && len > 0) {
        uint64_t seq = ctx->client_seq;
        uint8_t iv[12];
        memcpy(iv, ctx->client_write_iv, 4);
        iv[4]  = (seq >> 56) & 0xFF; iv[5]  = (seq >> 48) & 0xFF;
        iv[6]  = (seq >> 40) & 0xFF; iv[7]  = (seq >> 32) & 0xFF;
        iv[8]  = (seq >> 24) & 0xFF; iv[9]  = (seq >> 16) & 0xFF;
        iv[10] = (seq >> 8) & 0xFF;  iv[11] = seq & 0xFF;

        uint8_t cipher[TLS_RECORD_SIZE + 16];
        uint8_t tag[16];

        uint16_t wire_len = 8 + len + 16;
        tls_record_header(hdr, content_type, wire_len);

        uint8_t aad[13];
        aad[0]  = (seq >> 56) & 0xFF; aad[1]  = (seq >> 48) & 0xFF;
        aad[2]  = (seq >> 40) & 0xFF; aad[3]  = (seq >> 32) & 0xFF;
        aad[4]  = (seq >> 24) & 0xFF; aad[5]  = (seq >> 16) & 0xFF;
        aad[6]  = (seq >> 8) & 0xFF;  aad[7]  = seq & 0xFF;
        aad[8]  = content_type;
        aad[9]  = 0x03;
        aad[10] = 0x03;
        aad[11] = (len >> 8) & 0xFF;
        aad[12] = len & 0xFF;

        aes128_gcm_encrypt(ctx->client_write_key, iv,
                          data, len, aad, 13, cipher, tag);

        memcpy(record, hdr, 5);
        record[5]  = (seq >> 56) & 0xFF; record[6]  = (seq >> 48) & 0xFF;
        record[7]  = (seq >> 40) & 0xFF; record[8]  = (seq >> 32) & 0xFF;
        record[9]  = (seq >> 24) & 0xFF; record[10] = (seq >> 16) & 0xFF;
        record[11] = (seq >> 8) & 0xFF;  record[12] = seq & 0xFF;
        memcpy(record + 13, cipher, len);
        memcpy(record + 13 + len, tag, 16);
        ctx->client_seq++;
        dbg_printf("[tls] send enc ct=%d len=%d wire=%d seq=%d\n", content_type, len, 5+8+len+16, (int)seq);
        return tcp_send(record, 5 + 8 + len + 16);
    }

    memcpy(record, hdr, 5);
    memcpy(record + 5, data, len);
    return tcp_send(record, 5 + len);
}

static int tls_recv_record(struct tls_ctx *ctx, uint8_t *out_type,
                            void *out_buf, uint32_t max_len, uint32_t timeout_ms)
{
    uint8_t hdr[5];
    uint32_t hdr_pos = 0;

    while(hdr_pos < 5) {
        int n = tcp_recv(hdr + hdr_pos, 5 - hdr_pos, timeout_ms);
        if(n < 0) { dbg_printf("[tls] hdr tcp_recv=%d\n", n); return -1; }
        if(n == 0) return 0;
        hdr_pos += n;
    }

    *out_type = hdr[0];
    uint16_t rec_len = ((uint16_t)hdr[3] << 8) | hdr[4];
    if(rec_len > TLS_RECORD_SIZE + 16) { dbg_printf("[tls] bad rec_len=%d\n", rec_len); return -1; }

    uint8_t body[TLS_RECORD_SIZE + 16];
    uint32_t body_pos = 0;
    while(body_pos < rec_len) {
        int n = tcp_recv(body + body_pos, rec_len - body_pos, timeout_ms);
        if(n < 0) { dbg_printf("[tls] body tcp_recv=%d\n", n); return -1; }
        if(n == 0) return 0;
        body_pos += n;
    }

    if(ctx->recv_ccs && *out_type != TLS_CONTENT_CHANGE_CIPHER_SPEC && rec_len > 24) {
        const uint8_t *nonce_explicit = body;
        uint32_t cipher_len = rec_len - 8 - 16;
        const uint8_t *cipher = body + 8;
        const uint8_t *tag = body + 8 + cipher_len;

        uint8_t iv[12];
        memcpy(iv, ctx->server_write_iv, 4);
        memcpy(iv + 4, nonce_explicit, 8);

        uint8_t aad[13];
        uint64_t sseq = ctx->server_seq;
        aad[0]  = (sseq >> 56) & 0xFF; aad[1]  = (sseq >> 48) & 0xFF;
        aad[2]  = (sseq >> 40) & 0xFF; aad[3]  = (sseq >> 32) & 0xFF;
        aad[4]  = (sseq >> 24) & 0xFF; aad[5]  = (sseq >> 16) & 0xFF;
        aad[6]  = (sseq >> 8) & 0xFF;  aad[7]  = sseq & 0xFF;
        aad[8]  = *out_type;
        aad[9]  = 0x03;
        aad[10] = 0x03;
        aad[11] = (cipher_len >> 8) & 0xFF;
        aad[12] = cipher_len & 0xFF;

        uint8_t plain[TLS_RECORD_SIZE];
        int rc = aes128_gcm_decrypt(ctx->server_write_key, iv,
                                     cipher, cipher_len, aad, 13, plain, tag);
        if(rc < 0) {
            dbg_printf("[tls] GCM decrypt FAILED\n");
            return -1;
        }

        uint32_t copy = cipher_len < max_len ? cipher_len : max_len;
        memcpy(out_buf, plain, copy);
        ctx->server_seq++;
        return (int)copy;
    }

    uint32_t copy = rec_len < max_len ? rec_len : max_len;
    memcpy(out_buf, body, copy);
    return (int)copy;
}

void tls_init(struct tls_ctx *ctx)
{
    memset(ctx, 0, sizeof(*ctx));
    ctx->state = 0;
    ctx->client_seq = 0;
    ctx->server_seq = 0;
}

static void tls_record_hs(struct tls_ctx *ctx, const void *data, uint32_t len)
{
    if(ctx->hs_messages_len + len <= sizeof(ctx->hs_messages)) {
        memcpy(ctx->hs_messages + ctx->hs_messages_len, data, len);
        ctx->hs_messages_len += len;
    }
    sha256_update(&ctx->hs_hash, data, len);
}

static int tls_build_client_hello(struct tls_ctx *ctx, const char *hostname,
                                   uint8_t *out, uint32_t *out_len)
{
    uint8_t *p = out;
    uint32_t host_len = strlen(hostname);

    uint8_t *hs_type = p; p++;
    uint8_t *hs_len3 = p; p += 3;

    *p++ = 0x03; *p++ = 0x03;

    crypto_random(ctx->client_random, 32);
    memcpy(p, ctx->client_random, 32); p += 32;
    *p++ = 0;

    uint8_t cs[4];
    uint16_t cs_list[] = {
        TLS_CS_ECDHE_ECDSA_AES128_GCM_SHA256,
        TLS_CS_ECDHE_RSA_AES128_GCM_SHA256
    };
    cs_list[0] = __builtin_bswap16(cs_list[0]);
    cs_list[1] = __builtin_bswap16(cs_list[1]);
    uint16_t cs_len = sizeof(cs_list);
    *p++ = (cs_len >> 8) & 0xFF; *p++ = cs_len & 0xFF;
    memcpy(p, cs, 0); p += 0;
    memcpy(p, cs_list, cs_len); p += cs_len;
    (void)cs;

    *p++ = 1; *p++ = 0;

    uint8_t *ext_start = p; p += 2;

    *p++ = 0x00; *p++ = 0x00;
    uint32_t sni_len = 5 + host_len;
    *p++ = (sni_len >> 8) & 0xFF; *p++ = sni_len & 0xFF;
    uint32_t sni_list_len = 3 + host_len;
    *p++ = (sni_list_len >> 8) & 0xFF; *p++ = sni_list_len & 0xFF;
    *p++ = 0;
    *p++ = (host_len >> 8) & 0xFF; *p++ = host_len & 0xFF;
    memcpy(p, hostname, host_len); p += host_len;

    *p++ = 0x00; *p++ = 0x0A;
    *p++ = 0x00; *p++ = 0x04;
    *p++ = 0x00; *p++ = 0x02;
    *p++ = 0x00; *p++ = 0x17;

    *p++ = 0x00; *p++ = 0x0D;
    *p++ = 0x00; *p++ = 0x04;
    *p++ = 0x00; *p++ = 0x02;
    *p++ = 0x04; *p++ = 0x03;

    uint32_t ext_total = (uint32_t)(p - ext_start - 2);
    ext_start[0] = (ext_total >> 8) & 0xFF;
    ext_start[1] = ext_total & 0xFF;

    uint32_t hs_len = (uint32_t)(p - out - 4);
    hs_type[0] = TLS_HS_CLIENT_HELLO;
    hs_len3[0] = (hs_len >> 16) & 0xFF;
    hs_len3[1] = (hs_len >> 8) & 0xFF;
    hs_len3[2] = hs_len & 0xFF;

    *out_len = (uint32_t)(p - out);
    return 0;
}

static int tls_parse_server_hello(struct tls_ctx *ctx, const uint8_t *data, uint32_t len)
{
    if(len < 4) return -1;
    uint32_t pos = 4;

    if(pos + 2 > len) return -1;
    pos += 2;

    if(pos + 32 > len) return -1;
    memcpy(ctx->server_random, data + pos, 32);
    pos += 32;

    if(pos >= len) return -1;
    uint8_t sess_len = data[pos]; pos++;
    pos += sess_len;

    if(pos + 2 > len) return -1;
    ctx->cipher_suite = ((uint16_t)data[pos] << 8) | data[pos+1];
    pos += 2;

    dbg_printf("[tls] cipher suite: 0x%04x\n", ctx->cipher_suite);

    if(pos >= len) return -1;
    pos++;

    return 0;
}

static int tls_parse_server_key_exchange(struct tls_ctx *ctx,
                                          const uint8_t *data, uint32_t len)
{
    if(len < 4) return -1;
    uint32_t pos = 4;

    if(pos >= len) return -1;
    uint8_t curve_type = data[pos]; pos++;
    if(curve_type != 3) {
        dbg_printf("[tls] unsupported curve type %d\n", curve_type);
        return -1;
    }

    if(pos + 2 > len) return -1;
    uint16_t named_curve = ((uint16_t)data[pos] << 8) | data[pos+1];
    pos += 2;

    if(pos >= len) return -1;
    uint8_t pk_len = data[pos]; pos++;
    if(pk_len != 65 || data[pos] != 0x04) {
        dbg_printf("[tls] bad public key\n");
        return -1;
    }
    memcpy(ctx->ecdhe_pubkey, data + pos + 1, 64);
    pos += pk_len;
    (void)named_curve;

    dbg_printf("[tls] server ECDHE pubkey OK\n");
    return 0;
}

static void tls_derive_keys(struct tls_ctx *ctx)
{
    uint8_t seed[64];
    memcpy(seed, ctx->client_random, 32);
    memcpy(seed + 32, ctx->server_random, 32);

    tls_prf(ctx->ecdhe_privkey, 32, "master secret", seed, 64,
            ctx->master_secret, 48);

    uint8_t key_seed[64];
    memcpy(key_seed, ctx->server_random, 32);
    memcpy(key_seed + 32, ctx->client_random, 32);

    uint8_t key_block[80];
    tls_prf(ctx->master_secret, 48, "key expansion", key_seed, 64,
            key_block, 80);

    memcpy(ctx->client_write_key, key_block, 16);
    memcpy(ctx->server_write_key, key_block + 16, 16);
    memcpy(ctx->client_write_iv, key_block + 32, 4);
    memcpy(ctx->server_write_iv, key_block + 36, 4);

    dbg_printf("[tls] keys derived\n");
}

static int tls_build_client_key_exchange(struct tls_ctx *ctx, uint8_t *out, uint32_t *out_len)
{
    uint8_t priv[32], pub[64];
    if(ecdhe_generate_key(pub, priv) < 0) {
        dbg_printf("[tls] ECDHE gen fail\n");
        return -1;
    }

    uint8_t shared[32];
    if(ecdhe_compute_shared(priv, ctx->ecdhe_pubkey, shared) < 0) {
        dbg_printf("[tls] ECDHE shared fail\n");
        return -1;
    }

    memcpy(ctx->ecdhe_privkey, priv, 32);

    dbg_printf("[tls] client pubkey: "); phex(pub, 64); dbg_printf("\n");
    
    dbg_printf("[tls] private key: "); phex(priv, 32); dbg_printf("\n");

    dbg_printf("[tls] server pubkey: "); phex(ctx->ecdhe_pubkey, 64); dbg_printf("\n");

    dbg_printf("[tls] ecdhe_shared: "); phex(shared, 32); dbg_printf("\n");

    uint8_t *p = out;
    *p++ = TLS_HS_CLIENT_KEY_EXCHANGE;
    uint32_t body_len = 66;
    *p++ = (body_len >> 16) & 0xFF;
    *p++ = (body_len >> 8) & 0xFF;
    *p++ = body_len & 0xFF;
    *p++ = 65;
    *p++ = 0x04;
    memcpy(p, pub, 64);
    p += 64;

    *out_len = (uint32_t)(p - out);

    memcpy(ctx->ecdhe_privkey, shared, 32);

    return 0;
}

static int tls_send_change_cipher_spec(struct tls_ctx *ctx)
{
    uint8_t ccs = 1;
    return tls_send_record(ctx, TLS_CONTENT_CHANGE_CIPHER_SPEC, &ccs, 1);
}

static int tls_send_finished(struct tls_ctx *ctx)
{
    uint8_t saved[SHA256_CTX_SIZE];
    memcpy(saved, &ctx->hs_hash, SHA256_CTX_SIZE);

    uint8_t hs_hash[32];
    sha256_final(&ctx->hs_hash, hs_hash);

    memcpy(&ctx->hs_hash, saved, SHA256_CTX_SIZE);

    dbg_printf("[tls] hs_hash: "); phex(hs_hash, 32); dbg_printf("\n");

    dbg_printf("[tls] master_secret: "); phex(ctx->master_secret, 48); dbg_printf("\n");

    uint8_t verify_data[32];
    tls_prf(ctx->master_secret, 48, "client finished",
            hs_hash, 32,
            verify_data, 12);

    dbg_printf("[tls] verify_data: "); phex(verify_data, 12); dbg_printf("\n");

    uint8_t hs[16];
    hs[0] = TLS_HS_FINISHED;
    hs[1] = 0; hs[2] = 0; hs[3] = 12;
    memcpy(hs + 4, verify_data, 12);

    tls_record_hs(ctx, hs, 16);

    return tls_send_record(ctx, TLS_CONTENT_HANDSHAKE, hs, 16);
}

int tls_connect(const uint8_t *ip, uint16_t port, const char *hostname,
                struct tls_ctx *ctx)
{
    dbg_printf("[tls] connecting %s:%d\n", hostname, port);
    tls_init(ctx);
    sha256_init(&ctx->hs_hash);

    if(tcp_connect(ip, port) < 0) {
        dbg_printf("[tls] TCP fail\n");
        return -1;
    }

    uint8_t client_hello[512];
    uint32_t ch_len;
    tls_build_client_hello(ctx, hostname, client_hello, &ch_len);

    tls_record_hs(ctx, client_hello, ch_len);
    tls_send_record(ctx, TLS_CONTENT_HANDSHAKE, client_hello, ch_len);
    dbg_printf("[tls] ClientHello sent\n");

    uint8_t rec_type;
    uint8_t rec_buf[TLS_RECORD_SIZE + 16];
    int n = tls_recv_record(ctx, &rec_type, rec_buf, sizeof(rec_buf), 5000);
    if(n < 0) { dbg_printf("[tls] no ServerHello\n"); return -1; }

    if(rec_type == TLS_CONTENT_HANDSHAKE) {
        tls_record_hs(ctx, rec_buf, n);
        tls_parse_server_hello(ctx, rec_buf, n);
    }

    int got_kx = 0, got_done = 0;
    for(int iter = 0; iter < 20 && !got_done; iter++) {
        n = tls_recv_record(ctx, &rec_type, rec_buf, sizeof(rec_buf), 5000);
        if(n < 0) break;

        if(rec_type == TLS_CONTENT_ALERT) {
            dbg_printf("[tls] ALERT %02x %02x\n", rec_buf[0], rec_buf[1]);
            if(rec_buf[0] == 2) return -1;
            break;
        }

        if(rec_type == TLS_CONTENT_HANDSHAKE && n >= 4) {
            uint8_t ht = rec_buf[0];
            uint32_t hl = ((uint32_t)rec_buf[1] << 16) |
                          ((uint32_t)rec_buf[2] << 8) | rec_buf[3];
            (void)hl;

            tls_record_hs(ctx, rec_buf, n);

            if(ht == TLS_HS_CERTIFICATE) {
                dbg_printf("[tls] Certificate received\n");

                uint32_t cpos = 4;
                if(cpos + 3 <= (uint32_t)n) {
                    uint32_t chain_len = ((uint32_t)rec_buf[cpos] << 16) |
                                         ((uint32_t)rec_buf[cpos+1] << 8) | rec_buf[cpos+2];
                    cpos += 3;
                    uint32_t cend = 4 + chain_len;
                    if(cend > (uint32_t)n) cend = (uint32_t)n;

                    while(cpos + 3 < cend) {
                        uint32_t cert_len = ((uint32_t)rec_buf[cpos] << 16) |
                                            ((uint32_t)rec_buf[cpos+1] << 8) | rec_buf[cpos+2];
                        cpos += 3;
                        if(cpos + cert_len > cend) break;

                        struct x509_cert cert;
                        if(x509_parse_cert(rec_buf + cpos, cert_len, &cert) == 0) {
                            dbg_printf("[tls] cert parsed, key=%02x%02x...\n",
                                       cert.pubkey[0], cert.pubkey[1]);
                        }
                        cpos += cert_len;
                    }
                }
            } else if(ht == TLS_HS_SERVER_KEY_EXCHANGE) {
                got_kx = 1;
                tls_parse_server_key_exchange(ctx, rec_buf, n);
            } else if(ht == TLS_HS_SERVER_HELLO_DONE) {
                got_done = 1;
                dbg_printf("[tls] ServerHelloDone\n");
            }
        }

        if(rec_type == TLS_CONTENT_CHANGE_CIPHER_SPEC) {
            dbg_printf("[tls] ChangeCipherSpec from server\n");
            ctx->recv_ccs = 1;
        }
    }

    if(!got_kx) {
        dbg_printf("[tls] no ServerKeyExchange\n");
        return -1;
    }

    uint8_t cke[128];
    uint32_t cke_len;
    tls_build_client_key_exchange(ctx, cke, &cke_len);
    tls_record_hs(ctx, cke, cke_len);
    tls_send_record(ctx, TLS_CONTENT_HANDSHAKE, cke, cke_len);
    dbg_printf("[tls] ClientKeyExchange sent\n");

    tls_derive_keys(ctx);

    tls_send_change_cipher_spec(ctx);
    ctx->send_ccs = 1;
    dbg_printf("[tls] ChangeCipherSpec sent\n");

    tls_send_finished(ctx);
    dbg_printf("[tls] Finished sent\n");

    int got_srv_finished = 0;
    for(int iter = 0; iter < 10 && !got_srv_finished; iter++) {
        n = tls_recv_record(ctx, &rec_type, rec_buf, sizeof(rec_buf), 5000);
        if(n < 0) break;

        if(rec_type == TLS_CONTENT_CHANGE_CIPHER_SPEC) {
            dbg_printf("[tls] server ChangeCipherSpec\n");
            ctx->recv_ccs = 1;
        } else if(rec_type == TLS_CONTENT_HANDSHAKE) {
            if(n >= 4 && rec_buf[0] == 20) {
                dbg_printf("[tls] server Finished\n");
                got_srv_finished = 1;
            }
        } else if(rec_type == TLS_CONTENT_ALERT) {
            dbg_printf("[tls] ALERT after finished: %d %d\n", rec_buf[0], rec_buf[1]);
            return -1;
        } else {
            break;
        }
    }

    ctx->state = 1;
    dbg_printf("[tls] handshake complete!\n");
    return 0;
}

int tls_send(struct tls_ctx *ctx, const void *data, uint32_t len)
{
    return tls_send_record(ctx, TLS_CONTENT_APPLICATION_DATA, data, len);
}

int tls_recv(struct tls_ctx *ctx, void *buf, uint32_t max_len, uint32_t timeout_ms)
{
    uint8_t rec_type;
    int n = tls_recv_record(ctx, &rec_type, buf, max_len, timeout_ms);
    if(n < 0) return -1;
    if(n == 0) return 0;
    if(rec_type == TLS_CONTENT_ALERT) {
        uint8_t *p = buf;
        dbg_printf("[tls] alert %d %d\n", p[0], p[1]);
        return -1;
    }
    return n;
}

void tls_close(struct tls_ctx *ctx)
{
    uint8_t alert[2] = { TLS_ALERT_LEVEL_FATAL, 0 }; /* close_notify */
    tls_send_record(ctx, TLS_CONTENT_ALERT, alert, 2);
    tcp_close();
    ctx->state = 0;
}

int https_get(const char *host, const char *path, char *body, uint32_t max_body)
{
    uint8_t ip[4];
    fb_printf("Resolving %s...\n", host);
    if(dns_resolve(host, ip) < 0) {
        fb_printf("DNS failed for %s\n", host);
        return -1;
    }
    fb_printf("IP: %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);

    fb_printf("Connecting to %s:443 (HTTPS)...\n", host);
    struct tls_ctx tls;
    if(tls_connect(ip, 443, host, &tls) < 0) {
        fb_printf("TLS handshake failed\n");
        return -1;
    }
    fb_printf("TLS connected!\n");

    char req[512];
    int rlen = 0;

    const char *p = "GET ";
    while(*p) req[rlen++] = *p++;
    p = path;
    while(*p) req[rlen++] = *p++;
    p = " HTTP/1.0\r\nHost: ";
    while(*p) req[rlen++] = *p++;
    p = host;
    while(*p) req[rlen++] = *p++;
    p = "\r\nConnection: close\r\n\r\n";
    while(*p) req[rlen++] = *p++;
    req[rlen] = '\0';

    dbg_printf("[https] sending %d bytes\n", rlen);
    int sr = tls_send(&tls, req, rlen);
    dbg_printf("[https] tls_send rc=%d\n", sr);

    uint32_t total = 0;
    uint64_t start = rdtsc();
    int timeouts = 0;

    while(total < max_body - 1) {
        if(rdtsc() - start > 8000000000ULL) { dbg_printf("[https] timeout after %d to\n", timeouts); break; }
        int n = tls_recv(&tls, body + total, max_body - 1 - total, 200);
        if(n > 0) {
            dbg_printf("[https] got %d bytes (total=%d)\n", n, total + n);
            total += n;
            body[total] = '\0';
            start = rdtsc();
            timeouts = 0;
        } else if(n == 0) {
            timeouts++;
            if(timeouts % 50 == 0) dbg_printf("[https] recv timeout %d\n", timeouts);
            continue;
        } else {
            dbg_printf("[https] recv error %d after %d bytes, to=%d\n", n, total, timeouts);
            break;
        }
    }
    body[total] = '\0';
    dbg_printf("[https] total=%d\n", total);

    tls_close(&tls);

    char *hdr_end = NULL;
    for(uint32_t i = 0; i + 3 < total; i++) {
        if(body[i] == '\r' && body[i+1] == '\n' &&
           body[i+2] == '\r' && body[i+3] == '\n') {
            hdr_end = body + i + 4;
            break;
        }
    }

    if(hdr_end) {
        uint32_t hdr_len = (uint32_t)(hdr_end - body);
        uint32_t body_len = total - hdr_len;
        memmove(body, hdr_end, body_len);
        body[body_len] = '\0';
        total = body_len;
    }

    tls_close(&tls);

    return (int)total;
}

int https_post(const char *host, const char *path, const char *content_type,
               const char *data, uint32_t data_len,
               char *body, uint32_t max_body)
{
    uint8_t ip[4];
    fb_printf("DNS resolving %s...\n", host);
    if(dns_resolve(host, ip) < 0) {
        fb_printf("DNS failed for %s\n", host);
        return -1;
    }
    fb_printf("IP: %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);

    fb_printf("Connecting to %s:443 (HTTPS POST)...\n", host);
    struct tls_ctx tls;
    if(tls_connect(ip, 443, host, &tls) < 0) {
        fb_printf("TLS handshake failed\n");
        return -1;
    }
    fb_printf("TLS connected!\n");

    char req[1024];
    int rlen = 0;

    const char *p = "POST ";
    while(*p) req[rlen++] = *p++;
    p = path;
    while(*p) req[rlen++] = *p++;
    p = " HTTP/1.0\r\nHost: ";
    while(*p) req[rlen++] = *p++;
    p = host;
    while(*p) req[rlen++] = *p++;
    p = "\r\nContent-Type: ";
    while(*p) req[rlen++] = *p++;
    p = content_type;
    while(*p) req[rlen++] = *p++;
    p = "\r\nContent-Length: ";
    while(*p) req[rlen++] = *p++;

    char clen_str[16];
    int clen_len = 0;
    uint32_t tmp = data_len;
    if(tmp == 0) clen_str[clen_len++] = '0';
    else {
        char rev[10];
        int ri = 0;
        while(tmp) { rev[ri++] = '0' + (tmp % 10); tmp /= 10; }
        while(ri--) clen_str[clen_len++] = rev[ri];
    }
    for(int i = 0; i < clen_len; i++) req[rlen++] = clen_str[i];

    p = "\r\nConnection: close\r\n\r\n";
    while(*p) req[rlen++] = *p++;

    for(uint32_t i = 0; i < data_len && rlen < 1020; i++)
        req[rlen++] = data[i];

    req[rlen] = '\0';

    dbg_printf("[https] POST sending %d bytes\n", rlen);
    int sr = tls_send(&tls, req, rlen);
    dbg_printf("[https] tls_send rc=%d\n", sr);

    uint32_t total = 0;
    uint64_t start = rdtsc();
    int timeouts = 0;

    while(total < max_body - 1) {
        if(rdtsc() - start > 8000000000ULL) { dbg_printf("[https] timeout after %d to\n", timeouts); break; }
        int n = tls_recv(&tls, body + total, max_body - 1 - total, 200);
        if(n > 0) {
            dbg_printf("[https] got %d bytes (total=%d)\n", n, total + n);
            total += n;
            body[total] = '\0';
            start = rdtsc();
            timeouts = 0;
        } else if(n == 0) {
            timeouts++;
            if(timeouts % 50 == 0) dbg_printf("[https] recv timeout %d\n", timeouts);
            continue;
        } else {
            dbg_printf("[https] recv error %d after %d bytes\n", n, total);
            break;
        }
    }
    body[total] = '\0';

    tls_close(&tls);

    char *hdr_end = NULL;
    for(uint32_t i = 0; i + 3 < total; i++) {
        if(body[i] == '\r' && body[i+1] == '\n' &&
           body[i+2] == '\r' && body[i+3] == '\n') {
            hdr_end = body + i + 4;
            break;
        }
    }

    if(hdr_end) {
        uint32_t hdr_len = (uint32_t)(hdr_end - body);
        uint32_t body_len = total - hdr_len;
        memmove(body, hdr_end, body_len);
        body[body_len] = '\0';
        total = body_len;
    }

    if(total > 0 && total < HTML_BUF_SIZE) {
        memcpy(html_buf, body, total);
        html_buf[total] = '\0';
        html_len = total;
    }

    return (int)total;
}
