#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <poll.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <nuttx/config.h>
#include "c_utils/c_utils.h"
#ifdef CONFIG_CRYPTO_MBEDTLS
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/entropy.h>
#include <mbedtls/error.h>
#include <mbedtls/net_sockets.h>
#include <mbedtls/ssl.h>
#endif
#include "cJSON.h"
#define HTTP_DEFAULT_PORT 80
#define HTTPS_DEFAULT_PORT 443
struct http_url_s
{
char scheme[6];
char host[128];
char path[256];
uint16_t port;
bool tls;
};
struct http_tls_s
{
#ifdef CONFIG_CRYPTO_MBEDTLS
mbedtls_net_context net;
mbedtls_ssl_context ssl;
mbedtls_ssl_config conf;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_entropy_context entropy;
#endif
bool inited;
};
static int parse_url(const char *url, struct http_url_s *out)
{
const char *host_start;
const char *path_start;
const char *port_start;
size_t host_len;
size_t path_len;
if (url == NULL || out == NULL)
{
return -EINVAL;
}
memset(out, 0, sizeof(*out));
if (strncmp(url, "http://", 7) == 0)
{
strcpy(out->scheme, "http");
out->port = HTTP_DEFAULT_PORT;
out->tls = false;
host_start = url + 7;
}
else if (strncmp(url, "https://", 8) == 0)
{
strcpy(out->scheme, "https");
out->port = HTTPS_DEFAULT_PORT;
out->tls = true;
host_start = url + 8;
}
else
{
return -EINVAL;
}
path_start = strchr(host_start, '/');
if (path_start == NULL)
{
path_start = host_start + strlen(host_start);
}
port_start = memchr(host_start, ':', (size_t)(path_start - host_start));
if (port_start != NULL)
{
host_len = (size_t)(port_start - host_start);
out->port = (uint16_t)strtoul(port_start + 1, NULL, 10);
}
else
{
host_len = (size_t)(path_start - host_start);
}
if (host_len == 0 || host_len >= sizeof(out->host))
{
return -EINVAL;
}
memcpy(out->host, host_start, host_len);
out->host[host_len] = '\0';
if (*path_start == '\0')
{
strcpy(out->path, "/");
}
else
{
path_len = strlen(path_start);
if (path_len >= sizeof(out->path))
{
return -EINVAL;
}
memcpy(out->path, path_start, path_len + 1);
}
return 0;
}
static int tcp_connect(const char *host, uint16_t port, int timeout_ms)
{
struct addrinfo hints;
struct addrinfo *res = NULL;
struct addrinfo *it;
char port_str[8];
int fd = -1;
int ret;
snprintf(port_str, sizeof(port_str), "%u", port);
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
ret = getaddrinfo(host, port_str, &hints, &res);
if (ret != 0)
{
return -EHOSTUNREACH;
}
for (it = res; it != NULL; it = it->ai_next)
{
struct timeval tv;
fd = socket(it->ai_family, it->ai_socktype, it->ai_protocol);
if (fd < 0)
{
continue;
}
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
ret = connect(fd, it->ai_addr, it->ai_addrlen);
if (ret == 0)
{
break;
}
close(fd);
fd = -1;
}
freeaddrinfo(res);
return fd;
}
static ssize_t socket_write(int fd, const uint8_t *data, size_t len)
{
size_t sent = 0;
while (sent < len)
{
ssize_t ret = send(fd, data + sent, len - sent, 0);
if (ret < 0)
{
if (errno == EINTR)
{
continue;
}
return -errno;
}
sent += (size_t)ret;
}
return (ssize_t)sent;
}
static ssize_t socket_read(int fd, uint8_t *data, size_t len)
{
ssize_t ret = recv(fd, data, len, 0);
if (ret < 0 && errno == EINTR)
{
return 0;
}
return ret;
}
#ifdef CONFIG_CRYPTO_MBEDTLS
static int http_net_recv(void *ctx, unsigned char *buf, size_t len)
{
mbedtls_net_context *net_ctx = (mbedtls_net_context *)ctx;
struct pollfd pfd;
int ret;
pfd.fd = net_ctx->fd;
pfd.events = POLLIN;
pfd.revents = 0;
ret = poll(&pfd, 1, 10000);
if (ret == 0)
{
return MBEDTLS_ERR_SSL_TIMEOUT;
}
if (ret < 0)
{
return MBEDTLS_ERR_NET_RECV_FAILED;
}
if (pfd.revents & (POLLERR | POLLHUP))
{
return MBEDTLS_ERR_NET_CONN_RESET;
}
ret = (int)read(net_ctx->fd, buf, len);
if (ret < 0)
{
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
{
return MBEDTLS_ERR_SSL_WANT_READ;
}
if (errno == EPIPE || errno == ECONNRESET)
{
return MBEDTLS_ERR_NET_CONN_RESET;
}
return MBEDTLS_ERR_NET_RECV_FAILED;
}
return ret;
}
static int http_tcp_connect_ipv4(const char *host, uint16_t port,
int timeout_ms)
{
struct addrinfo hints;
struct addrinfo *res = NULL;
struct addrinfo *it;
char port_str[8];
struct timeval tv;
int fd = -1;
int ret;
snprintf(port_str, sizeof(port_str), "%u", port);
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
ret = getaddrinfo(host, port_str, &hints, &res);
if (ret != 0)
{
return -EHOSTUNREACH;
}
for (it = res; it != NULL; it = it->ai_next)
{
fd = socket(it->ai_family, it->ai_socktype, it->ai_protocol);
if (fd < 0)
{
continue;
}
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
ret = connect(fd, it->ai_addr, it->ai_addrlen);
if (ret == 0)
{
break;
}
close(fd);
fd = -1;
}
freeaddrinfo(res);
return fd;
}
static int tls_connect(struct http_tls_s *tls, const char *host,
uint16_t port, int timeout_ms)
{
int ret;
int fd;
if (tls == NULL)
{
return -EINVAL;
}
mbedtls_net_init(&tls->net);
mbedtls_ssl_init(&tls->ssl);
mbedtls_ssl_config_init(&tls->conf);
mbedtls_ctr_drbg_init(&tls->ctr_drbg);
mbedtls_entropy_init(&tls->entropy);
ret = mbedtls_ctr_drbg_seed(&tls->ctr_drbg, mbedtls_entropy_func,
&tls->entropy, (const unsigned char *)"bailian",
strlen("bailian"));
if (ret != 0)
{
return -EIO;
}
* to avoid internal select()/FD_SET issues with FDCHECK.
*/
fd = http_tcp_connect_ipv4(host, port, timeout_ms);
if (fd < 0)
{
return -EHOSTUNREACH;
}
tls->net.fd = fd;
ret = mbedtls_ssl_config_defaults(&tls->conf, MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT);
if (ret != 0)
{
return -EIO;
}
mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
mbedtls_ssl_conf_rng(&tls->conf, mbedtls_ctr_drbg_random, &tls->ctr_drbg);
if (timeout_ms > 0)
{
mbedtls_ssl_conf_read_timeout(&tls->conf, timeout_ms);
}
ret = mbedtls_ssl_setup(&tls->ssl, &tls->conf);
if (ret != 0)
{
return -EIO;
}
ret = mbedtls_ssl_set_hostname(&tls->ssl, host);
if (ret != 0)
{
return -EIO;
}
* caused by FDCHECK-encoded fd values exceeding FD_SETSIZE.
*/
mbedtls_ssl_set_bio(&tls->ssl, &tls->net, mbedtls_net_send,
http_net_recv, NULL);
do
{
ret = mbedtls_ssl_handshake(&tls->ssl);
}
while (ret == MBEDTLS_ERR_SSL_WANT_READ ||
ret == MBEDTLS_ERR_SSL_WANT_WRITE);
if (ret != 0)
{
return -EIO;
}
tls->inited = true;
return 0;
}
static void tls_close(struct http_tls_s *tls)
{
if (tls == NULL || !tls->inited)
{
return;
}
mbedtls_ssl_close_notify(&tls->ssl);
mbedtls_net_free(&tls->net);
mbedtls_ssl_free(&tls->ssl);
mbedtls_ssl_config_free(&tls->conf);
mbedtls_ctr_drbg_free(&tls->ctr_drbg);
mbedtls_entropy_free(&tls->entropy);
tls->inited = false;
}
static ssize_t tls_write(struct http_tls_s *tls, const uint8_t *data,
size_t len)
{
size_t sent = 0;
while (sent < len)
{
int ret = mbedtls_ssl_write(&tls->ssl, data + sent, len - sent);
if (ret < 0)
{
if (ret == MBEDTLS_ERR_SSL_WANT_READ ||
ret == MBEDTLS_ERR_SSL_WANT_WRITE)
{
continue;
}
return -EIO;
}
sent += (size_t)ret;
}
return (ssize_t)sent;
}
static ssize_t tls_read(struct http_tls_s *tls, uint8_t *data, size_t len)
{
int ret = mbedtls_ssl_read(&tls->ssl, data, len);
if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE)
{
return 0;
}
if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)
{
return 0;
}
if (ret < 0)
{
return -EIO;
}
return ret;
}
#endif
static const char *find_header_case(const char *haystack, const char *needle)
{
size_t needle_len;
const char *p;
if (haystack == NULL || needle == NULL)
{
return NULL;
}
needle_len = strlen(needle);
for (p = haystack; *p != '\0'; p++)
{
if (strncasecmp(p, needle, needle_len) == 0)
{
return p;
}
}
return NULL;
}
static int http_read_response(int fd, struct http_tls_s *tls, bool use_tls,
char *response, size_t response_len)
{
uint8_t buffer[512];
char *header_end = NULL;
size_t header_len = 0;
size_t body_len = 0;
size_t content_len = 0;
bool has_content_len = false;
while (true)
{
ssize_t ret = use_tls
#ifdef CONFIG_CRYPTO_MBEDTLS
? tls_read(tls, buffer, sizeof(buffer))
#else
? -ENOTSUP
#endif
: socket_read(fd, buffer, sizeof(buffer));
if (ret < 0)
{
return (int)ret;
}
if (ret == 0)
{
break;
}
if (header_end == NULL)
{
size_t copy_len = (size_t)ret;
if (header_len + copy_len >= response_len)
{
return -ENOSPC;
}
memcpy(response + header_len, buffer, copy_len);
header_len += copy_len;
response[header_len] = '\0';
header_end = strstr(response, "\r\n\r\n");
if (header_end != NULL)
{
const char *len_str = find_header_case(response, "Content-Length:");
if (len_str != NULL)
{
len_str += strlen("Content-Length:");
while (*len_str == ' ') len_str++;
content_len = (size_t)strtoul(len_str, NULL, 10);
has_content_len = true;
}
body_len = header_len - (size_t)(header_end - response) - 4;
memmove(response, header_end + 4, body_len);
header_len = 0;
response[body_len] = '\0';
if (has_content_len && body_len >= content_len)
{
return (int)body_len;
}
}
}
else
{
size_t copy_len = (size_t)ret;
if (body_len + copy_len >= response_len)
{
return -ENOSPC;
}
memcpy(response + body_len, buffer, copy_len);
body_len += copy_len;
response[body_len] = '\0';
if (has_content_len && body_len >= content_len)
{
return (int)body_len;
}
}
}
return (int)body_len;
}
int bailian_http_post(const char *url, const char *content_type,
const char *payload, char *response,
size_t response_len, int timeout_ms)
{
struct http_url_s parsed;
char request[512];
int fd = -1;
int ret;
size_t payload_len = payload != NULL ? strlen(payload) : 0;
ssize_t sent;
struct http_tls_s tls;
if (parse_url(url, &parsed) != 0)
{
return -EINVAL;
}
memset(&tls, 0, sizeof(tls));
if (parsed.tls)
{
#ifdef CONFIG_CRYPTO_MBEDTLS
ret = tls_connect(&tls, parsed.host, parsed.port, timeout_ms);
if (ret < 0)
{
return ret;
}
#else
return -ENOTSUP;
#endif
}
else
{
fd = tcp_connect(parsed.host, parsed.port, timeout_ms);
if (fd < 0)
{
return fd;
}
}
snprintf(request, sizeof(request),
"POST %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Connection: close\r\n"
"Content-Type: %s\r\n"
"Content-Length: %u\r\n"
"\r\n",
parsed.path, parsed.host,
content_type != NULL ? content_type : "application/json",
(unsigned int)payload_len);
if (parsed.tls)
{
#ifdef CONFIG_CRYPTO_MBEDTLS
sent = tls_write(&tls, (const uint8_t *)request, strlen(request));
#else
sent = -ENOTSUP;
#endif
}
else
{
sent = socket_write(fd, (const uint8_t *)request, strlen(request));
}
if (sent < 0)
{
ret = (int)sent;
goto out;
}
if (payload_len > 0)
{
if (parsed.tls)
{
#ifdef CONFIG_CRYPTO_MBEDTLS
sent = tls_write(&tls, (const uint8_t *)payload, payload_len);
#else
sent = -ENOTSUP;
#endif
}
else
{
sent = socket_write(fd, (const uint8_t *)payload, payload_len);
}
if (sent < 0)
{
ret = (int)sent;
goto out;
}
}
ret = http_read_response(fd, &tls, parsed.tls, response, response_len);
out:
if (parsed.tls)
{
#ifdef CONFIG_CRYPTO_MBEDTLS
tls_close(&tls);
#endif
}
else if (fd >= 0)
{
close(fd);
}
return ret;
}
static int extract_json_data(const char *json_str, char *data_out, size_t data_len)
{
cJSON *root = NULL;
cJSON *data = NULL;
char *data_str = NULL;
int ret = -1;
root = cJSON_Parse(json_str);
if (root == NULL)
{
return -1;
}
data = cJSON_GetObjectItem(root, "data");
if (data == NULL)
{
cJSON_Delete(root);
return -1;
}
data_str = cJSON_PrintUnformatted(data);
if (data_str != NULL)
{
size_t len = strlen(data_str);
if (len < data_len)
{
memcpy(data_out, data_str, len + 1);
ret = (int)len;
}
cJSON_free(data_str);
}
cJSON_Delete(root);
return ret;
}
int bailian_http_register(const char *url, const char *req,
char *rsp, size_t rsp_len)
{
char raw_rsp[2048];
int ret;
ret = bailian_http_post(url, "application/json", req, raw_rsp,
sizeof(raw_rsp), 10000);
if (ret <= 0)
{
return ret;
}
ret = extract_json_data(raw_rsp, rsp, rsp_len);
return ret;
}
int bailian_http_login(const char *url, const char *req,
char *rsp, size_t rsp_len)
{
char raw_rsp[2048];
int ret;
ret = bailian_http_post(url, "application/json", req, raw_rsp,
sizeof(raw_rsp), 10000);
if (ret <= 0)
{
return ret;
}
ret = extract_json_data(raw_rsp, rsp, rsp_len);
return ret;
}