blob: d1fb84e8cf36bfba5c41a1ef1d8f50104206653b [file] [log] [blame]
/*
* Copyright (c) 2017 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#if defined(CONFIG_NET_DEBUG_HTTP)
#define SYS_LOG_DOMAIN "http"
#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG
#define NET_LOG_ENABLED 1
#endif
#include <zephyr.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <stdlib.h>
#include <version.h>
#include <misc/printk.h>
#include <net/net_core.h>
#include <net/net_ip.h>
#include <net/http.h>
int http_set_cb(struct http_ctx *ctx,
http_connect_cb_t connect_cb,
http_recv_cb_t recv_cb,
http_send_cb_t send_cb,
http_close_cb_t close_cb)
{
if (!ctx) {
return -EINVAL;
}
if (!ctx->is_init) {
return -ENOENT;
}
ctx->cb.connect = connect_cb;
ctx->cb.recv = recv_cb;
ctx->cb.send = send_cb;
ctx->cb.close = close_cb;
return 0;
}
int http_close(struct http_ctx *ctx)
{
if (!ctx) {
return -EINVAL;
}
if (!ctx->is_init) {
return -ENOENT;
}
http_send_flush(ctx, NULL);
if (ctx->pending) {
net_pkt_unref(ctx->pending);
ctx->pending = NULL;
}
#if defined(CONFIG_HTTP_SERVER) && defined(CONFIG_NET_DEBUG_HTTP_CONN)
if (!ctx->is_client) {
http_server_conn_del(ctx);
}
#endif
#if defined(CONFIG_HTTP_SERVER) && defined(CONFIG_WEBSOCKET)
if (ctx->websocket.pending) {
net_pkt_unref(ctx->websocket.pending);
ctx->websocket.pending = NULL;
}
ctx->websocket.data_waiting = 0;
#endif
return net_app_close(&ctx->app_ctx);
}
int http_release(struct http_ctx *ctx)
{
if (!ctx) {
return -EINVAL;
}
if (!ctx->is_init) {
return -ENOENT;
}
ctx->is_tls = false;
#if defined(CONFIG_HTTP_SERVER) && defined(CONFIG_NET_DEBUG_HTTP_CONN)
if (!ctx->is_client) {
http_server_conn_del(ctx);
http_server_disable(ctx);
}
#endif
if (ctx->pending) {
net_pkt_unref(ctx->pending);
ctx->pending = NULL;
}
ctx->is_init = false;
return net_app_release(&ctx->app_ctx);
}
int http_send_msg_raw(struct http_ctx *ctx, struct net_pkt *pkt,
void *user_send_data)
{
int ret;
NET_DBG("[%p] Sending %zd bytes data", ctx, net_pkt_get_len(pkt));
ret = net_app_send_pkt(&ctx->app_ctx, pkt, NULL, 0, 0,
user_send_data);
if (!ret) {
/* We must let the system to send the packet, otherwise TCP
* might timeout before the packet is actually sent. This is
* easily seen if the application calls this functions many
* times in a row.
*/
k_yield();
}
return ret;
}
static inline struct net_pkt *get_net_pkt(struct http_ctx *ctx,
const struct sockaddr *dst)
{
if (!dst) {
return net_app_get_net_pkt(&ctx->app_ctx, AF_UNSPEC,
ctx->timeout);
}
return net_app_get_net_pkt_with_dst(&ctx->app_ctx, dst, ctx->timeout);
}
int http_prepare_and_send(struct http_ctx *ctx,
const char *payload,
size_t payload_len,
const struct sockaddr *dst,
void *user_send_data)
{
size_t added;
int ret;
do {
if (!ctx->pending) {
ctx->pending = get_net_pkt(ctx, dst);
if (!ctx->pending) {
return -ENOMEM;
}
}
ret = net_pkt_append(ctx->pending, payload_len, payload,
ctx->timeout);
if (!ret || ret > payload_len) {
ret = -EINVAL;
goto error;
}
added = ret;
payload_len -= added;
if (payload_len) {
payload += added;
/* Not all data could be added, send what we have now
* and allocate new stuff to be sent.
*/
ret = http_send_flush(ctx, user_send_data);
if (ret < 0) {
goto error;
}
}
} while (payload_len);
return 0;
error:
if (ctx->pending) {
net_pkt_unref(ctx->pending);
ctx->pending = NULL;
}
return ret;
}
int http_send_flush(struct http_ctx *ctx, void *user_send_data)
{
int ret;
if (!ctx->pending) {
return 0;
}
ret = http_send_msg_raw(ctx, ctx->pending, user_send_data);
if (ret < 0) {
return ret;
}
ctx->pending = NULL;
return ret;
}
int http_send_chunk(struct http_ctx *ctx, const char *buf, size_t len,
const struct sockaddr *dst, void *user_send_data)
{
char chunk_header[16];
int ret;
if (!buf) {
len = 0;
}
snprintk(chunk_header, sizeof(chunk_header), "%x" HTTP_CRLF,
(unsigned int)len);
ret = http_prepare_and_send(ctx, chunk_header, strlen(chunk_header),
dst, user_send_data);
if (ret < 0) {
return ret;
}
if (len) {
ret = http_prepare_and_send(ctx, buf, len, dst, user_send_data);
if (ret < 0) {
return ret;
}
}
ret = http_prepare_and_send(ctx, HTTP_CRLF, sizeof(HTTP_CRLF) - 1, dst,
user_send_data);
if (ret < 0) {
return ret;
}
return 0;
}
static int _http_add_header(struct http_ctx *ctx, s32_t timeout,
const char *name, const char *value,
const struct sockaddr *dst,
void *user_send_data)
{
int ret;
ret = http_prepare_and_send(ctx, name, strlen(name), dst,
user_send_data);
if (value && ret >= 0) {
ret = http_prepare_and_send(ctx, ": ", strlen(": "), dst,
user_send_data);
if (ret < 0) {
goto out;
}
ret = http_prepare_and_send(ctx, value, strlen(value), dst,
user_send_data);
if (ret < 0) {
goto out;
}
ret = http_prepare_and_send(ctx, HTTP_CRLF, strlen(HTTP_CRLF),
dst, user_send_data);
if (ret < 0) {
goto out;
}
}
out:
return ret;
}
int http_add_header(struct http_ctx *ctx, const char *field,
const struct sockaddr *dst,
void *user_send_data)
{
return _http_add_header(ctx, ctx->timeout, field, NULL, dst,
user_send_data);
}
int http_add_header_field(struct http_ctx *ctx, const char *name,
const char *value,
const struct sockaddr *dst,
void *user_send_data)
{
return _http_add_header(ctx, ctx->timeout, name, value, dst,
user_send_data);
}