chore: initial commit
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <netdb.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "esp_crt_bundle.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#define PLATFORM_HTTPS_DEFAULT_TIMEOUT_MS 10000
|
||||
#define PLATFORM_HTTPS_DNS_RETRY_COUNT 2
|
||||
#define PLATFORM_HTTPS_DNS_RETRY_DELAY_MS 500
|
||||
#define PLATFORM_HTTPS_HOST_MAX_LEN 256
|
||||
|
||||
static const char *TAG = "platform_https";
|
||||
|
||||
static int effective_positive_or_zero(int value)
|
||||
{
|
||||
return value > 0 ? value : 0;
|
||||
}
|
||||
|
||||
static const char *dns_error_name(int err)
|
||||
{
|
||||
switch (err) {
|
||||
#ifdef EAI_NONAME
|
||||
case EAI_NONAME:
|
||||
return "EAI_NONAME";
|
||||
#endif
|
||||
#ifdef EAI_SERVICE
|
||||
case EAI_SERVICE:
|
||||
return "EAI_SERVICE";
|
||||
#endif
|
||||
#ifdef EAI_FAIL
|
||||
case EAI_FAIL:
|
||||
return "EAI_FAIL";
|
||||
#endif
|
||||
#ifdef EAI_MEMORY
|
||||
case EAI_MEMORY:
|
||||
return "EAI_MEMORY";
|
||||
#endif
|
||||
#ifdef EAI_FAMILY
|
||||
case EAI_FAMILY:
|
||||
return "EAI_FAMILY";
|
||||
#endif
|
||||
default:
|
||||
return "EAI_UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t parse_url_host(const char *url, char *host, size_t host_capacity)
|
||||
{
|
||||
if (url == NULL || host == NULL || host_capacity == 0U) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
const char *host_begin = strstr(url, "://");
|
||||
host_begin = host_begin != NULL ? host_begin + 3 : url;
|
||||
if (host_begin[0] == '\0') {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
const char *host_end = NULL;
|
||||
if (host_begin[0] == '[') {
|
||||
host_begin++;
|
||||
host_end = strchr(host_begin, ']');
|
||||
if (host_end == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
} else {
|
||||
host_end = host_begin;
|
||||
while (host_end[0] != '\0' &&
|
||||
host_end[0] != '/' &&
|
||||
host_end[0] != '?' &&
|
||||
host_end[0] != '#' &&
|
||||
host_end[0] != ':') {
|
||||
host_end++;
|
||||
}
|
||||
}
|
||||
|
||||
size_t host_len = (size_t)(host_end - host_begin);
|
||||
if (host_len == 0U || host_len >= host_capacity) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
memcpy(host, host_begin, host_len);
|
||||
host[host_len] = '\0';
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t resolve_host_before_open(const char *url)
|
||||
{
|
||||
char host[PLATFORM_HTTPS_HOST_MAX_LEN];
|
||||
esp_err_t err = parse_url_host(url, host, sizeof(host));
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
for (int attempt = 1; attempt <= PLATFORM_HTTPS_DNS_RETRY_COUNT; ++attempt) {
|
||||
struct addrinfo hints = {
|
||||
.ai_family = AF_UNSPEC,
|
||||
.ai_socktype = SOCK_STREAM,
|
||||
};
|
||||
struct addrinfo *addr = NULL;
|
||||
int dns_err = getaddrinfo(host, NULL, &hints, &addr);
|
||||
if (dns_err == 0 && addr != NULL) {
|
||||
freeaddrinfo(addr);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (addr != NULL) {
|
||||
freeaddrinfo(addr);
|
||||
}
|
||||
if (dns_err == EAI_MEMORY) {
|
||||
ESP_LOGW(TAG, "DNS resolve failed host=%s err=%s(%d)", host, dns_error_name(dns_err), dns_err);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
if (attempt < PLATFORM_HTTPS_DNS_RETRY_COUNT) {
|
||||
ESP_LOGW(TAG,
|
||||
"DNS resolve failed host=%s err=%s(%d), retrying",
|
||||
host,
|
||||
dns_error_name(dns_err),
|
||||
dns_err);
|
||||
vTaskDelay(pdMS_TO_TICKS(PLATFORM_HTTPS_DNS_RETRY_DELAY_MS));
|
||||
} else {
|
||||
ESP_LOGW(TAG,
|
||||
"DNS resolve failed host=%s err=%s(%d) after %d attempts",
|
||||
host,
|
||||
dns_error_name(dns_err),
|
||||
dns_err,
|
||||
PLATFORM_HTTPS_DNS_RETRY_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_ERR_HTTP_CONNECT;
|
||||
}
|
||||
|
||||
static esp_err_t write_all(esp_http_client_handle_t client, const char *payload)
|
||||
{
|
||||
size_t payload_len = strlen(payload);
|
||||
if (payload_len > (size_t)INT_MAX) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
size_t total_written = 0U;
|
||||
|
||||
while (total_written < payload_len) {
|
||||
int written = esp_http_client_write(client,
|
||||
payload + total_written,
|
||||
(int)(payload_len - total_written));
|
||||
if (written <= 0) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
total_written += (size_t)written;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t platform_https_post_json(const platform_https_post_json_request_t *request,
|
||||
platform_https_response_t *response)
|
||||
{
|
||||
if (response != NULL) {
|
||||
response->body_len = 0U;
|
||||
response->status_code = -1;
|
||||
if (response->body != NULL && response->body_capacity > 0U) {
|
||||
response->body[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
if (request == NULL ||
|
||||
response == NULL ||
|
||||
request->url == NULL ||
|
||||
request->url[0] == '\0' ||
|
||||
request->payload == NULL ||
|
||||
response->body == NULL ||
|
||||
response->body_capacity < 2U ||
|
||||
(request->header_count > 0U && request->headers == NULL)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
esp_err_t err = resolve_host_before_open(request->url);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_http_client_config_t cfg = {
|
||||
.url = request->url,
|
||||
.method = HTTP_METHOD_POST,
|
||||
.timeout_ms = request->timeout_ms > 0 ? request->timeout_ms
|
||||
: PLATFORM_HTTPS_DEFAULT_TIMEOUT_MS,
|
||||
.crt_bundle_attach = esp_crt_bundle_attach,
|
||||
.buffer_size_tx = effective_positive_or_zero(request->tx_buffer_size),
|
||||
.buffer_size = effective_positive_or_zero(request->rx_buffer_size),
|
||||
};
|
||||
|
||||
esp_http_client_handle_t client = esp_http_client_init(&cfg);
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
esp_http_client_set_header(client, "Content-Type", "application/json");
|
||||
for (size_t i = 0; i < request->header_count; ++i) {
|
||||
const platform_https_header_t *header = &request->headers[i];
|
||||
if (header->name == NULL || header->name[0] == '\0' ||
|
||||
header->value == NULL) {
|
||||
esp_http_client_cleanup(client);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
esp_http_client_set_header(client, header->name, header->value);
|
||||
}
|
||||
|
||||
size_t payload_len = strlen(request->payload);
|
||||
if (payload_len > (size_t)INT_MAX) {
|
||||
esp_http_client_cleanup(client);
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
err = esp_http_client_open(client, (int)payload_len);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "HTTPS open failed: %s", esp_err_to_name(err));
|
||||
esp_http_client_cleanup(client);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = write_all(client, request->payload);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "HTTPS write failed: %s", esp_err_to_name(err));
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
return err;
|
||||
}
|
||||
|
||||
int64_t content_length = esp_http_client_fetch_headers(client);
|
||||
int status = esp_http_client_get_status_code(client);
|
||||
response->status_code = status;
|
||||
|
||||
size_t max_read = response->body_capacity - 1U;
|
||||
if (content_length >= 0 && (uint64_t)content_length > (uint64_t)max_read) {
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
size_t total_read = 0U;
|
||||
while (true) {
|
||||
if (total_read >= max_read) {
|
||||
uint8_t overflow = 0U;
|
||||
int overflow_len = esp_http_client_read(client, (char *)&overflow, 1);
|
||||
if (overflow_len > 0) {
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
if (overflow_len < 0) {
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
return ESP_ERR_INVALID_RESPONSE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int read_len = esp_http_client_read(client,
|
||||
response->body + total_read,
|
||||
(int)(max_read - total_read));
|
||||
if (read_len < 0) {
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
return ESP_ERR_INVALID_RESPONSE;
|
||||
}
|
||||
if (read_len == 0) {
|
||||
break;
|
||||
}
|
||||
total_read += (size_t)read_len;
|
||||
}
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
|
||||
response->body[total_read] = '\0';
|
||||
response->body_len = total_read;
|
||||
return ESP_OK;
|
||||
}
|
||||
Reference in New Issue
Block a user