diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index bb4c24b..970d300 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -12,6 +12,7 @@ idf_component_register( "gateway/ws_server.c" "cli/serial_cli.c" "ota/ota_manager.c" + "proxy/http_proxy.c" INCLUDE_DIRS "." REQUIRES diff --git a/main/cli/serial_cli.c b/main/cli/serial_cli.c index 97287fd..8e25a1b 100644 --- a/main/cli/serial_cli.c +++ b/main/cli/serial_cli.c @@ -5,6 +5,7 @@ #include "llm/llm_proxy.h" #include "memory/memory_store.h" #include "memory/session_mgr.h" +#include "proxy/http_proxy.h" #include #include @@ -169,6 +170,33 @@ static int cmd_heap_info(int argc, char **argv) return 0; } +/* --- set_proxy command --- */ +static struct { + struct arg_str *host; + struct arg_int *port; + struct arg_end *end; +} proxy_args; + +static int cmd_set_proxy(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **)&proxy_args); + if (nerrors != 0) { + arg_print_errors(stderr, proxy_args.end, argv[0]); + return 1; + } + http_proxy_set(proxy_args.host->sval[0], (uint16_t)proxy_args.port->ival[0]); + printf("Proxy set. Restart to apply.\n"); + return 0; +} + +/* --- clear_proxy command --- */ +static int cmd_clear_proxy(int argc, char **argv) +{ + http_proxy_clear(); + printf("Proxy cleared. Restart to apply.\n"); + return 0; +} + /* --- restart command --- */ static int cmd_restart(int argc, char **argv) { @@ -291,6 +319,26 @@ esp_err_t serial_cli_init(void) }; esp_console_cmd_register(&heap_cmd); + /* set_proxy */ + proxy_args.host = arg_str1(NULL, NULL, "", "Proxy host/IP"); + proxy_args.port = arg_int1(NULL, NULL, "", "Proxy port"); + proxy_args.end = arg_end(2); + esp_console_cmd_t proxy_cmd = { + .command = "set_proxy", + .help = "Set HTTP proxy (e.g. set_proxy 192.168.1.83 7897)", + .func = &cmd_set_proxy, + .argtable = &proxy_args, + }; + esp_console_cmd_register(&proxy_cmd); + + /* clear_proxy */ + esp_console_cmd_t clear_proxy_cmd = { + .command = "clear_proxy", + .help = "Remove proxy configuration", + .func = &cmd_clear_proxy, + }; + esp_console_cmd_register(&clear_proxy_cmd); + /* restart */ esp_console_cmd_t restart_cmd = { .command = "restart", diff --git a/main/llm/llm_proxy.c b/main/llm/llm_proxy.c index 18553db..5b2a212 100644 --- a/main/llm/llm_proxy.c +++ b/main/llm/llm_proxy.c @@ -1,5 +1,6 @@ #include "llm_proxy.h" #include "mimi_config.h" +#include "proxy/http_proxy.h" #include #include @@ -131,6 +132,110 @@ esp_err_t llm_proxy_init(void) return ESP_OK; } +/* ── Direct path: esp_http_client ───────────────────────────── */ + +static esp_err_t llm_chat_direct(const char *post_data, sse_ctx_t *ctx, int *out_status) +{ + esp_http_client_config_t config = { + .url = MIMI_LLM_API_URL, + .event_handler = http_event_handler, + .user_data = ctx, + .timeout_ms = 120 * 1000, + .buffer_size = 4096, + .buffer_size_tx = 4096, + .crt_bundle_attach = esp_crt_bundle_attach, + }; + + esp_http_client_handle_t client = esp_http_client_init(&config); + if (!client) return ESP_FAIL; + + esp_http_client_set_method(client, HTTP_METHOD_POST); + esp_http_client_set_header(client, "Content-Type", "application/json"); + esp_http_client_set_header(client, "x-api-key", s_api_key); + esp_http_client_set_header(client, "anthropic-version", MIMI_LLM_API_VERSION); + esp_http_client_set_post_field(client, post_data, strlen(post_data)); + + esp_err_t err = esp_http_client_perform(client); + *out_status = esp_http_client_get_status_code(client); + esp_http_client_cleanup(client); + return err; +} + +/* ── Proxy path: manual HTTP over CONNECT tunnel ────────────── */ + +static esp_err_t llm_chat_via_proxy(const char *post_data, sse_ctx_t *ctx, int *out_status) +{ + proxy_conn_t *conn = proxy_conn_open("api.anthropic.com", 443, 30000); + if (!conn) return ESP_ERR_HTTP_CONNECT; + + /* Build HTTP request */ + int body_len = strlen(post_data); + char header[512]; + int hlen = snprintf(header, sizeof(header), + "POST /v1/messages HTTP/1.1\r\n" + "Host: api.anthropic.com\r\n" + "Content-Type: application/json\r\n" + "x-api-key: %s\r\n" + "anthropic-version: %s\r\n" + "Content-Length: %d\r\n" + "Connection: close\r\n\r\n", + s_api_key, MIMI_LLM_API_VERSION, body_len); + + if (proxy_conn_write(conn, header, hlen) < 0 || + proxy_conn_write(conn, post_data, body_len) < 0) { + proxy_conn_close(conn); + return ESP_ERR_HTTP_WRITE_DATA; + } + + /* Read response — first line is status */ + size_t raw_len = 0; + size_t raw_cap = 32768; + char *raw = calloc(1, raw_cap); + if (!raw) { proxy_conn_close(conn); return ESP_ERR_NO_MEM; } + + while (1) { + if (raw_len + 4096 >= raw_cap) { + raw_cap *= 2; + char *tmp = realloc(raw, raw_cap); + if (!tmp) break; + raw = tmp; + } + int n = proxy_conn_read(conn, raw + raw_len, 4096, 120000); + if (n <= 0) break; + raw_len += n; + } + raw[raw_len] = '\0'; + proxy_conn_close(conn); + + /* Parse status line */ + *out_status = 0; + if (strncmp(raw, "HTTP/", 5) == 0) { + const char *sp = strchr(raw, ' '); + if (sp) *out_status = atoi(sp + 1); + } + + /* Find body after \r\n\r\n */ + char *body = strstr(raw, "\r\n\r\n"); + if (body) { + body += 4; + size_t body_len = raw_len - (body - raw); + if (*out_status == 200) { + /* Feed body to SSE parser */ + sse_feed(ctx, body, body_len); + } else { + /* For error responses, capture raw body */ + size_t copy_len = body_len < ctx->resp_cap - 1 ? body_len : ctx->resp_cap - 1; + memcpy(ctx->response, body, copy_len); + ctx->response[copy_len] = '\0'; + ctx->resp_len = copy_len; + ESP_LOGE(TAG, "API error body: %.500s", body); + } + } + + free(raw); + return ESP_OK; +} + esp_err_t llm_chat(const char *system_prompt, const char *messages_json, char *response_buf, size_t buf_size) { @@ -182,33 +287,14 @@ esp_err_t llm_chat(const char *system_prompt, const char *messages_json, return ESP_ERR_NO_MEM; } - esp_http_client_config_t config = { - .url = MIMI_LLM_API_URL, - .event_handler = http_event_handler, - .user_data = &ctx, - .timeout_ms = 120 * 1000, /* 2 min timeout for long responses */ - .buffer_size = 4096, - .buffer_size_tx = 4096, - .crt_bundle_attach = esp_crt_bundle_attach, - }; + esp_err_t err; + int status = 0; - esp_http_client_handle_t client = esp_http_client_init(&config); - if (!client) { - free(post_data); - free(ctx.response); - snprintf(response_buf, buf_size, "Error: HTTP client init failed"); - return ESP_FAIL; + if (http_proxy_is_enabled()) { + err = llm_chat_via_proxy(post_data, &ctx, &status); + } else { + err = llm_chat_direct(post_data, &ctx, &status); } - - esp_http_client_set_method(client, HTTP_METHOD_POST); - esp_http_client_set_header(client, "Content-Type", "application/json"); - esp_http_client_set_header(client, "x-api-key", s_api_key); - esp_http_client_set_header(client, "anthropic-version", MIMI_LLM_API_VERSION); - esp_http_client_set_post_field(client, post_data, strlen(post_data)); - - esp_err_t err = esp_http_client_perform(client); - int status = esp_http_client_get_status_code(client); - esp_http_client_cleanup(client); free(post_data); if (err != ESP_OK) { @@ -221,7 +307,6 @@ esp_err_t llm_chat(const char *system_prompt, const char *messages_json, if (status != 200) { ESP_LOGE(TAG, "API returned status %d", status); if (ctx.resp_len > 0) { - /* Response might contain error info */ snprintf(response_buf, buf_size, "API error (HTTP %d): %.200s", status, ctx.response); } else { snprintf(response_buf, buf_size, "API error (HTTP %d)", status); diff --git a/main/mimi.c b/main/mimi.c index ba04578..672d0de 100644 --- a/main/mimi.c +++ b/main/mimi.c @@ -19,6 +19,7 @@ #include "memory/session_mgr.h" #include "gateway/ws_server.h" #include "cli/serial_cli.h" +#include "proxy/http_proxy.h" static const char *TAG = "mimi"; @@ -100,6 +101,7 @@ void app_main(void) ESP_ERROR_CHECK(memory_store_init()); ESP_ERROR_CHECK(session_mgr_init()); ESP_ERROR_CHECK(wifi_manager_init()); + ESP_ERROR_CHECK(http_proxy_init()); ESP_ERROR_CHECK(telegram_bot_init()); ESP_ERROR_CHECK(llm_proxy_init()); ESP_ERROR_CHECK(agent_loop_init()); diff --git a/main/mimi_config.h b/main/mimi_config.h index e97cd3e..7f9868b 100644 --- a/main/mimi_config.h +++ b/main/mimi_config.h @@ -10,18 +10,18 @@ /* Telegram Bot */ #define MIMI_TG_POLL_TIMEOUT_S 30 #define MIMI_TG_MAX_MSG_LEN 4096 -#define MIMI_TG_POLL_STACK (8 * 1024) +#define MIMI_TG_POLL_STACK (12 * 1024) #define MIMI_TG_POLL_PRIO 5 #define MIMI_TG_POLL_CORE 0 /* Agent Loop */ -#define MIMI_AGENT_STACK (8 * 1024) +#define MIMI_AGENT_STACK (12 * 1024) #define MIMI_AGENT_PRIO 6 #define MIMI_AGENT_CORE 1 #define MIMI_AGENT_MAX_HISTORY 20 /* LLM */ -#define MIMI_LLM_DEFAULT_MODEL "claude-opus-4-5-20251101" +#define MIMI_LLM_DEFAULT_MODEL "claude-opus-4-6" #define MIMI_LLM_MAX_TOKENS 4096 #define MIMI_LLM_API_URL "https://api.anthropic.com/v1/messages" #define MIMI_LLM_API_VERSION "2023-06-01" @@ -29,7 +29,7 @@ /* Message Bus */ #define MIMI_BUS_QUEUE_LEN 8 -#define MIMI_OUTBOUND_STACK (4 * 1024) +#define MIMI_OUTBOUND_STACK (8 * 1024) #define MIMI_OUTBOUND_PRIO 5 #define MIMI_OUTBOUND_CORE 0 @@ -57,6 +57,7 @@ #define MIMI_NVS_WIFI "wifi_config" #define MIMI_NVS_TG "tg_config" #define MIMI_NVS_LLM "llm_config" +#define MIMI_NVS_PROXY "proxy_config" /* NVS Keys */ #define MIMI_NVS_KEY_SSID "ssid" @@ -64,3 +65,5 @@ #define MIMI_NVS_KEY_TG_TOKEN "bot_token" #define MIMI_NVS_KEY_API_KEY "api_key" #define MIMI_NVS_KEY_MODEL "model" +#define MIMI_NVS_KEY_PROXY_HOST "host" +#define MIMI_NVS_KEY_PROXY_PORT "port" diff --git a/main/proxy/http_proxy.c b/main/proxy/http_proxy.c new file mode 100644 index 0000000..009ff0f --- /dev/null +++ b/main/proxy/http_proxy.c @@ -0,0 +1,234 @@ +#include "http_proxy.h" +#include "mimi_config.h" + +#include +#include +#include +#include +#include +#include + +#include "esp_log.h" +#include "nvs.h" +#include "esp_tls.h" +#include "esp_crt_bundle.h" + +static const char *TAG = "proxy"; + +/* ── Config (cached from NVS) ─────────────────────────────────── */ + +static char s_proxy_host[64] = {0}; +static uint16_t s_proxy_port = 0; + +esp_err_t http_proxy_init(void) +{ + nvs_handle_t nvs; + esp_err_t err = nvs_open(MIMI_NVS_PROXY, NVS_READONLY, &nvs); + if (err == ESP_OK) { + size_t len = sizeof(s_proxy_host); + nvs_get_str(nvs, MIMI_NVS_KEY_PROXY_HOST, s_proxy_host, &len); + nvs_get_u16(nvs, MIMI_NVS_KEY_PROXY_PORT, &s_proxy_port); + nvs_close(nvs); + } + + if (s_proxy_host[0] && s_proxy_port) { + ESP_LOGI(TAG, "Proxy configured: %s:%d", s_proxy_host, s_proxy_port); + } else { + ESP_LOGI(TAG, "No proxy configured (direct connection)"); + } + return ESP_OK; +} + +bool http_proxy_is_enabled(void) +{ + return s_proxy_host[0] != '\0' && s_proxy_port != 0; +} + +esp_err_t http_proxy_set(const char *host, uint16_t port) +{ + nvs_handle_t nvs; + ESP_ERROR_CHECK(nvs_open(MIMI_NVS_PROXY, NVS_READWRITE, &nvs)); + ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_PROXY_HOST, host)); + ESP_ERROR_CHECK(nvs_set_u16(nvs, MIMI_NVS_KEY_PROXY_PORT, port)); + ESP_ERROR_CHECK(nvs_commit(nvs)); + nvs_close(nvs); + + strncpy(s_proxy_host, host, sizeof(s_proxy_host) - 1); + s_proxy_port = port; + ESP_LOGI(TAG, "Proxy set to %s:%d", s_proxy_host, s_proxy_port); + return ESP_OK; +} + +esp_err_t http_proxy_clear(void) +{ + nvs_handle_t nvs; + ESP_ERROR_CHECK(nvs_open(MIMI_NVS_PROXY, NVS_READWRITE, &nvs)); + nvs_erase_key(nvs, MIMI_NVS_KEY_PROXY_HOST); + nvs_erase_key(nvs, MIMI_NVS_KEY_PROXY_PORT); + nvs_commit(nvs); + nvs_close(nvs); + + s_proxy_host[0] = '\0'; + s_proxy_port = 0; + ESP_LOGI(TAG, "Proxy cleared"); + return ESP_OK; +} + +/* ── Proxied TLS connection ───────────────────────────────────── */ + +struct proxy_conn { + int sock; /* raw TCP socket (for timeout control) */ + esp_tls_t *tls; /* esp_tls handle owns TLS + socket lifecycle */ +}; + +/* Read a line from socket (up to CR-LF). Returns length or -1. */ +static int sock_read_line(int fd, char *buf, int max, int timeout_ms) +{ + int pos = 0; + struct timeval tv = { .tv_sec = timeout_ms / 1000, .tv_usec = (timeout_ms % 1000) * 1000 }; + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + + while (pos < max - 1) { + char c; + int r = recv(fd, &c, 1, 0); + if (r <= 0) return -1; + if (c == '\n') { buf[pos] = '\0'; return pos; } + if (c != '\r') buf[pos++] = c; + } + buf[pos] = '\0'; + return pos; +} + +/* Open TCP + CONNECT tunnel, returns socket fd or -1 */ +static int open_connect_tunnel(const char *host, int port, int timeout_ms) +{ + struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM }; + struct addrinfo *res = NULL; + char port_str[8]; + snprintf(port_str, sizeof(port_str), "%d", s_proxy_port); + + if (getaddrinfo(s_proxy_host, port_str, &hints, &res) != 0 || !res) { + ESP_LOGE(TAG, "DNS resolve failed for proxy %s", s_proxy_host); + return -1; + } + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { freeaddrinfo(res); return -1; } + + struct timeval tv = { .tv_sec = timeout_ms / 1000, .tv_usec = (timeout_ms % 1000) * 1000 }; + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + + if (connect(sock, res->ai_addr, res->ai_addrlen) != 0) { + ESP_LOGE(TAG, "TCP connect to proxy %s:%d failed", s_proxy_host, s_proxy_port); + freeaddrinfo(res); close(sock); return -1; + } + freeaddrinfo(res); + ESP_LOGI(TAG, "Connected to proxy %s:%d", s_proxy_host, s_proxy_port); + + char req[256]; + int len = snprintf(req, sizeof(req), + "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n\r\n", host, port, host, port); + + if (send(sock, req, len, 0) != len) { + ESP_LOGE(TAG, "Failed to send CONNECT"); close(sock); return -1; + } + + char line[256]; + if (sock_read_line(sock, line, sizeof(line), timeout_ms) < 0) { + ESP_LOGE(TAG, "No response from proxy"); close(sock); return -1; + } + if (strstr(line, "200") == NULL) { + ESP_LOGE(TAG, "CONNECT rejected: %s", line); close(sock); return -1; + } + + /* Consume remaining response headers */ + while (sock_read_line(sock, line, sizeof(line), timeout_ms) > 0) { } + + ESP_LOGI(TAG, "CONNECT tunnel established to %s:%d", host, port); + return sock; +} + +proxy_conn_t *proxy_conn_open(const char *host, int port, int timeout_ms) +{ + if (!http_proxy_is_enabled()) { + ESP_LOGE(TAG, "proxy_conn_open called but no proxy configured"); + return NULL; + } + + int sock = open_connect_tunnel(host, port, timeout_ms); + if (sock < 0) return NULL; + + proxy_conn_t *conn = calloc(1, sizeof(*conn)); + if (!conn) { close(sock); return NULL; } + conn->sock = sock; + + /* ── TLS handshake via esp_tls over tunnel ───────────────── */ + conn->tls = esp_tls_init(); + if (!conn->tls) { + ESP_LOGE(TAG, "esp_tls_init failed"); + close(sock); free(conn); return NULL; + } + + /* Inject our CONNECT-tunnel socket and skip TCP connect phase */ + esp_tls_set_conn_sockfd(conn->tls, sock); + esp_tls_set_conn_state(conn->tls, ESP_TLS_CONNECTING); + + esp_tls_cfg_t cfg = { + .crt_bundle_attach = esp_crt_bundle_attach, + .timeout_ms = timeout_ms, + }; + + int ret = esp_tls_conn_new_sync(host, strlen(host), port, &cfg, conn->tls); + if (ret <= 0) { + ESP_LOGE(TAG, "TLS handshake failed over proxy tunnel"); + esp_tls_conn_destroy(conn->tls); + /* esp_tls_conn_destroy closes the socket */ + free(conn); + return NULL; + } + + ESP_LOGI(TAG, "TLS handshake OK with %s:%d via proxy", host, port); + return conn; +} + +int proxy_conn_write(proxy_conn_t *conn, const char *data, int len) +{ + int written = 0; + while (written < len) { + ssize_t ret = esp_tls_conn_write(conn->tls, data + written, len - written); + if (ret > 0) { + written += (int)ret; + } else if (ret == ESP_TLS_ERR_SSL_WANT_WRITE) { + continue; + } else { + ESP_LOGE(TAG, "esp_tls_conn_write error: %d", (int)ret); + return -1; + } + } + return written; +} + +int proxy_conn_read(proxy_conn_t *conn, char *buf, int len, int timeout_ms) +{ + struct timeval tv = { .tv_sec = timeout_ms / 1000, .tv_usec = (timeout_ms % 1000) * 1000 }; + setsockopt(conn->sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + + ssize_t ret = esp_tls_conn_read(conn->tls, buf, len); + if (ret == ESP_TLS_ERR_SSL_WANT_READ) return 0; + if (ret == 0) return 0; + if (ret < 0) { + ESP_LOGE(TAG, "esp_tls_conn_read error: %d", (int)ret); + return -1; + } + return (int)ret; +} + +void proxy_conn_close(proxy_conn_t *conn) +{ + if (!conn) return; + if (conn->tls) { + esp_tls_conn_destroy(conn->tls); + } + free(conn); +} diff --git a/main/proxy/http_proxy.h b/main/proxy/http_proxy.h new file mode 100644 index 0000000..522e514 --- /dev/null +++ b/main/proxy/http_proxy.h @@ -0,0 +1,48 @@ +#pragma once + +#include "esp_err.h" +#include +#include + +/** + * Initialize proxy module — loads config from NVS. + */ +esp_err_t http_proxy_init(void); + +/** + * Returns true if a proxy host:port is configured. + */ +bool http_proxy_is_enabled(void); + +/** + * Save proxy host and port to NVS. + */ +esp_err_t http_proxy_set(const char *host, uint16_t port); + +/** + * Remove proxy config from NVS. + */ +esp_err_t http_proxy_clear(void); + +/* ── Proxied HTTPS connection ─────────────────────────────────── */ + +typedef struct proxy_conn proxy_conn_t; + +/** + * Open an HTTPS connection through the configured proxy. + * 1) TCP connect to proxy + * 2) Send HTTP CONNECT to target host:port + * 3) TLS handshake over the tunnel + * + * Returns NULL on failure. + */ +proxy_conn_t *proxy_conn_open(const char *host, int port, int timeout_ms); + +/** Write raw bytes through the TLS tunnel. Returns bytes written or -1. */ +int proxy_conn_write(proxy_conn_t *conn, const char *data, int len); + +/** Read raw bytes from the TLS tunnel. Returns bytes read or -1. */ +int proxy_conn_read(proxy_conn_t *conn, char *buf, int len, int timeout_ms); + +/** Close and free the connection. */ +void proxy_conn_close(proxy_conn_t *conn); diff --git a/main/telegram/telegram_bot.c b/main/telegram/telegram_bot.c index 25355bb..178c1e9 100644 --- a/main/telegram/telegram_bot.c +++ b/main/telegram/telegram_bot.c @@ -1,6 +1,7 @@ #include "telegram_bot.h" #include "mimi_config.h" #include "bus/message_bus.h" +#include "proxy/http_proxy.h" #include #include @@ -43,7 +44,76 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt) return ESP_OK; } -static char *tg_api_call(const char *method, const char *post_data) +/* ── Proxy path: manual HTTP over CONNECT tunnel ────────────── */ + +static char *tg_api_call_via_proxy(const char *path, const char *post_data) +{ + proxy_conn_t *conn = proxy_conn_open("api.telegram.org", 443, + (MIMI_TG_POLL_TIMEOUT_S + 5) * 1000); + if (!conn) return NULL; + + /* Build HTTP request */ + char header[512]; + int hlen; + if (post_data) { + hlen = snprintf(header, sizeof(header), + "POST /bot%s/%s HTTP/1.1\r\n" + "Host: api.telegram.org\r\n" + "Content-Type: application/json\r\n" + "Content-Length: %d\r\n" + "Connection: close\r\n\r\n", + s_bot_token, path, (int)strlen(post_data)); + } else { + hlen = snprintf(header, sizeof(header), + "GET /bot%s/%s HTTP/1.1\r\n" + "Host: api.telegram.org\r\n" + "Connection: close\r\n\r\n", + s_bot_token, path); + } + + if (proxy_conn_write(conn, header, hlen) < 0) { + proxy_conn_close(conn); + return NULL; + } + if (post_data && proxy_conn_write(conn, post_data, strlen(post_data)) < 0) { + proxy_conn_close(conn); + return NULL; + } + + /* Read response — accumulate until connection close */ + size_t cap = 4096, len = 0; + char *buf = calloc(1, cap); + if (!buf) { proxy_conn_close(conn); return NULL; } + + int timeout = (MIMI_TG_POLL_TIMEOUT_S + 5) * 1000; + while (1) { + if (len + 1024 >= cap) { + cap *= 2; + char *tmp = realloc(buf, cap); + if (!tmp) break; + buf = tmp; + } + int n = proxy_conn_read(conn, buf + len, cap - len - 1, timeout); + if (n <= 0) break; + len += n; + } + buf[len] = '\0'; + proxy_conn_close(conn); + + /* Skip HTTP headers — find \r\n\r\n */ + char *body = strstr(buf, "\r\n\r\n"); + if (!body) { free(buf); return NULL; } + body += 4; + + /* Return just the body */ + char *result = strdup(body); + free(buf); + return result; +} + +/* ── Direct path: esp_http_client ───────────────────────────── */ + +static char *tg_api_call_direct(const char *method, const char *post_data) { char url[256]; snprintf(url, sizeof(url), "https://api.telegram.org/bot%s/%s", s_bot_token, method); @@ -89,6 +159,14 @@ static char *tg_api_call(const char *method, const char *post_data) return resp.buf; } +static char *tg_api_call(const char *method, const char *post_data) +{ + if (http_proxy_is_enabled()) { + return tg_api_call_via_proxy(method, post_data); + } + return tg_api_call_direct(method, post_data); +} + static void process_updates(const char *json_str) { cJSON *root = cJSON_Parse(json_str); diff --git a/sdkconfig.defaults.esp32s3 b/sdkconfig.defaults.esp32s3 index 57aa1b1..f85efab 100644 --- a/sdkconfig.defaults.esp32s3 +++ b/sdkconfig.defaults.esp32s3 @@ -25,7 +25,7 @@ CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16 # TLS optimization (PSRAM allocation + small buffers) CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y -CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=4096 +CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096 # WebSocket support