blob: b8f84c9a43081b8070a58107e4c6ddd261486867 [file] [log] [blame]
/*
* Copyright (c) 2019 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_websocket_client_sample, LOG_LEVEL_DBG);
#include <zephyr/net/net_ip.h>
#include <zephyr/net/socket.h>
#include <zephyr/net/tls_credentials.h>
#include <zephyr/net/websocket.h>
#include <zephyr/random/rand32.h>
#include <zephyr/shell/shell.h>
#include "ca_certificate.h"
#define SERVER_PORT 9001
#if defined(CONFIG_NET_CONFIG_PEER_IPV6_ADDR)
#define SERVER_ADDR6 CONFIG_NET_CONFIG_PEER_IPV6_ADDR
#else
#define SERVER_ADDR6 ""
#endif
#if defined(CONFIG_NET_CONFIG_PEER_IPV4_ADDR)
#define SERVER_ADDR4 CONFIG_NET_CONFIG_PEER_IPV4_ADDR
#else
#define SERVER_ADDR4 ""
#endif
/* Generated by http://www.lipsum.com/
* 2 paragraphs, 178 words, 1160 bytes of Lorem Ipsum
*/
static const char lorem_ipsum[] =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
"Vestibulum ultricies sapien tellus, ac viverra dolor bibendum "
"lacinia. Vestibulum et nisl tristique tellus finibus gravida "
"vitae sit amet nunc. Suspendisse maximus justo mi, vitae porta "
"risus suscipit vitae. Curabitur ut fringilla velit. Donec ac nisi "
"in dui semper lobortis sed nec ante. Sed nec luctus dui. Sed ut "
"ante nisi. Mauris congue euismod felis, et maximus ex pellentesque "
"nec. Proin nibh nisl, semper at nunc in, mattis pharetra metus. Nam "
"turpis risus, pulvinar sit amet varius ac, pellentesque quis purus."
" "
"Nam consequat purus in lacinia fringilla. Morbi volutpat, tellus "
"nec tempus dapibus, ante sem aliquam dui, eu feugiat libero diam "
"at leo. Sed suscipit egestas orci in ultrices. Integer in elementum "
"ligula, vel sollicitudin velit. Nullam sit amet eleifend libero. "
"Proin sit amet consequat tellus, vel vulputate arcu. Curabitur quis "
"lobortis lacus. Sed faucibus vestibulum enim vel elementum. Vivamus "
"enim nunc, auctor in purus at, aliquet pulvinar eros. Cras dapibus "
"nec quam laoreet sagittis. Quisque dictum ante odio, at imperdiet "
"est convallis a. Morbi mattis ut orci vitae volutpat."
"\n";
#define MAX_RECV_BUF_LEN (sizeof(lorem_ipsum) - 1)
const int ipsum_len = MAX_RECV_BUF_LEN;
static uint8_t recv_buf_ipv4[MAX_RECV_BUF_LEN];
static uint8_t recv_buf_ipv6[MAX_RECV_BUF_LEN];
/* We need to allocate bigger buffer for the websocket data we receive so that
* the websocket header fits into it.
*/
#define EXTRA_BUF_SPACE 30
static uint8_t temp_recv_buf_ipv4[MAX_RECV_BUF_LEN + EXTRA_BUF_SPACE];
static uint8_t temp_recv_buf_ipv6[MAX_RECV_BUF_LEN + EXTRA_BUF_SPACE];
static int setup_socket(sa_family_t family, const char *server, int port,
int *sock, struct sockaddr *addr, socklen_t addr_len)
{
const char *family_str = family == AF_INET ? "IPv4" : "IPv6";
int ret = 0;
memset(addr, 0, addr_len);
if (family == AF_INET) {
net_sin(addr)->sin_family = AF_INET;
net_sin(addr)->sin_port = htons(port);
inet_pton(family, server, &net_sin(addr)->sin_addr);
} else {
net_sin6(addr)->sin6_family = AF_INET6;
net_sin6(addr)->sin6_port = htons(port);
inet_pton(family, server, &net_sin6(addr)->sin6_addr);
}
if (IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS)) {
sec_tag_t sec_tag_list[] = {
CA_CERTIFICATE_TAG,
};
*sock = socket(family, SOCK_STREAM, IPPROTO_TLS_1_2);
if (*sock >= 0) {
ret = setsockopt(*sock, SOL_TLS, TLS_SEC_TAG_LIST,
sec_tag_list, sizeof(sec_tag_list));
if (ret < 0) {
LOG_ERR("Failed to set %s secure option (%d)",
family_str, -errno);
ret = -errno;
goto fail;
}
ret = setsockopt(*sock, SOL_TLS, TLS_HOSTNAME,
TLS_PEER_HOSTNAME,
sizeof(TLS_PEER_HOSTNAME));
if (ret < 0) {
LOG_ERR("Failed to set %s TLS_HOSTNAME "
"option (%d)", family_str, -errno);
ret = -errno;
goto fail;
}
}
} else {
*sock = socket(family, SOCK_STREAM, IPPROTO_TCP);
}
if (*sock < 0) {
LOG_ERR("Failed to create %s HTTP socket (%d)", family_str,
-errno);
}
return ret;
fail:
if (*sock >= 0) {
close(*sock);
*sock = -1;
}
return ret;
}
static int connect_socket(sa_family_t family, const char *server, int port,
int *sock, struct sockaddr *addr, socklen_t addr_len)
{
int ret;
ret = setup_socket(family, server, port, sock, addr, addr_len);
if (ret < 0 || *sock < 0) {
return -1;
}
ret = connect(*sock, addr, addr_len);
if (ret < 0) {
LOG_ERR("Cannot connect to %s remote (%d)",
family == AF_INET ? "IPv4" : "IPv6",
-errno);
ret = -errno;
}
return ret;
}
static int connect_cb(int sock, struct http_request *req, void *user_data)
{
LOG_INF("Websocket %d for %s connected.", sock, (char *)user_data);
return 0;
}
static size_t how_much_to_send(size_t max_len)
{
size_t amount;
do {
amount = sys_rand32_get() % max_len;
} while (amount == 0U);
return amount;
}
static ssize_t sendall_with_ws_api(int sock, const void *buf, size_t len)
{
return websocket_send_msg(sock, buf, len, WEBSOCKET_OPCODE_DATA_TEXT,
true, true, SYS_FOREVER_MS);
}
static ssize_t sendall_with_bsd_api(int sock, const void *buf, size_t len)
{
return send(sock, buf, len, 0);
}
static void recv_data_wso_api(int sock, size_t amount, uint8_t *buf,
size_t buf_len, const char *proto)
{
uint64_t remaining = ULLONG_MAX;
int total_read;
uint32_t message_type;
int ret, read_pos;
read_pos = 0;
total_read = 0;
while (remaining > 0) {
ret = websocket_recv_msg(sock, buf + read_pos,
buf_len - read_pos,
&message_type,
&remaining,
0);
if (ret <= 0) {
if (ret == -EAGAIN) {
k_sleep(K_MSEC(50));
continue;
}
LOG_DBG("%s connection closed while "
"waiting (%d/%d)", proto, ret, errno);
break;
}
read_pos += ret;
total_read += ret;
}
if (remaining != 0 || total_read != amount ||
/* Do not check the final \n at the end of the msg */
memcmp(lorem_ipsum, buf, amount - 1) != 0) {
LOG_ERR("%s data recv failure %zd/%d bytes (remaining %" PRId64 ")",
proto, amount, total_read, remaining);
LOG_HEXDUMP_DBG(buf, total_read, "received ws buf");
LOG_HEXDUMP_DBG(lorem_ipsum, total_read, "sent ws buf");
} else {
LOG_DBG("%s recv %d bytes", proto, total_read);
}
}
static void recv_data_bsd_api(int sock, size_t amount, uint8_t *buf,
size_t buf_len, const char *proto)
{
int remaining;
int ret, read_pos;
remaining = amount;
read_pos = 0;
while (remaining > 0) {
ret = recv(sock, buf + read_pos, buf_len - read_pos, 0);
if (ret <= 0) {
if (errno == EAGAIN || errno == ETIMEDOUT) {
k_sleep(K_MSEC(50));
continue;
}
LOG_DBG("%s connection closed while "
"waiting (%d/%d)", proto, ret, errno);
break;
}
read_pos += ret;
remaining -= ret;
}
if (remaining != 0 ||
/* Do not check the final \n at the end of the msg */
memcmp(lorem_ipsum, buf, amount - 1) != 0) {
LOG_ERR("%s data recv failure %zd/%d bytes (remaining %d)",
proto, amount, read_pos, remaining);
LOG_HEXDUMP_DBG(buf, read_pos, "received bsd buf");
LOG_HEXDUMP_DBG(lorem_ipsum, read_pos, "sent bsd buf");
} else {
LOG_DBG("%s recv %d bytes", proto, read_pos);
}
}
static bool send_and_wait_msg(int sock, size_t amount, const char *proto,
uint8_t *buf, size_t buf_len)
{
static int count;
int ret;
if (sock < 0) {
return true;
}
/* Terminate the sent data with \n so that we can use the
* websocketd --port=9001 cat
* command in server side.
*/
memcpy(buf, lorem_ipsum, amount);
buf[amount] = '\n';
/* Send every 2nd message using dedicated websocket API and generic
* BSD socket API. Real applications would not work like this but here
* we want to test both APIs. We also need to send the \n so add it
* here to amount variable.
*/
if (count % 2) {
ret = sendall_with_ws_api(sock, buf, amount + 1);
} else {
ret = sendall_with_bsd_api(sock, buf, amount + 1);
}
if (ret <= 0) {
if (ret < 0) {
LOG_ERR("%s failed to send data using %s (%d)", proto,
(count % 2) ? "ws API" : "socket API", ret);
} else {
LOG_DBG("%s connection closed", proto);
}
return false;
} else {
LOG_DBG("%s sent %d bytes", proto, ret);
}
if (count % 2) {
recv_data_wso_api(sock, amount + 1, buf, buf_len, proto);
} else {
recv_data_bsd_api(sock, amount + 1, buf, buf_len, proto);
}
count++;
return true;
}
void main(void)
{
/* Just an example how to set extra headers */
const char *extra_headers[] = {
"Origin: http://foobar\r\n",
NULL
};
int sock4 = -1, sock6 = -1;
int websock4 = -1, websock6 = -1;
int32_t timeout = 3 * MSEC_PER_SEC;
struct sockaddr_in6 addr6;
struct sockaddr_in addr4;
size_t amount;
int ret;
if (IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS)) {
ret = tls_credential_add(CA_CERTIFICATE_TAG,
TLS_CREDENTIAL_CA_CERTIFICATE,
ca_certificate,
sizeof(ca_certificate));
if (ret < 0) {
LOG_ERR("Failed to register public certificate: %d",
ret);
k_sleep(K_FOREVER);
}
}
if (IS_ENABLED(CONFIG_NET_IPV4)) {
(void)connect_socket(AF_INET, SERVER_ADDR4, SERVER_PORT,
&sock4, (struct sockaddr *)&addr4,
sizeof(addr4));
}
if (IS_ENABLED(CONFIG_NET_IPV6)) {
(void)connect_socket(AF_INET6, SERVER_ADDR6, SERVER_PORT,
&sock6, (struct sockaddr *)&addr6,
sizeof(addr6));
}
if (sock4 < 0 && sock6 < 0) {
LOG_ERR("Cannot create HTTP connection.");
k_sleep(K_FOREVER);
}
if (sock4 >= 0 && IS_ENABLED(CONFIG_NET_IPV4)) {
struct websocket_request req;
memset(&req, 0, sizeof(req));
req.host = SERVER_ADDR4;
req.url = "/";
req.optional_headers = extra_headers;
req.cb = connect_cb;
req.tmp_buf = temp_recv_buf_ipv4;
req.tmp_buf_len = sizeof(temp_recv_buf_ipv4);
websock4 = websocket_connect(sock4, &req, timeout, "IPv4");
if (websock4 < 0) {
LOG_ERR("Cannot connect to %s:%d", SERVER_ADDR4,
SERVER_PORT);
close(sock4);
}
}
if (sock6 >= 0 && IS_ENABLED(CONFIG_NET_IPV6)) {
struct websocket_request req;
memset(&req, 0, sizeof(req));
req.host = SERVER_ADDR6;
req.url = "/";
req.optional_headers = extra_headers;
req.cb = connect_cb;
req.tmp_buf = temp_recv_buf_ipv6;
req.tmp_buf_len = sizeof(temp_recv_buf_ipv6);
websock6 = websocket_connect(sock6, &req, timeout, "IPv6");
if (websock6 < 0) {
LOG_ERR("Cannot connect to [%s]:%d", SERVER_ADDR6,
SERVER_PORT);
close(sock6);
}
}
if (websock4 < 0 && websock6 < 0) {
LOG_ERR("No IPv4 or IPv6 connectivity");
k_sleep(K_FOREVER);
}
LOG_INF("Websocket IPv4 %d IPv6 %d", websock4, websock6);
while (1) {
amount = how_much_to_send(ipsum_len);
if (websock4 >= 0 &&
!send_and_wait_msg(websock4, amount, "IPv4",
recv_buf_ipv4, sizeof(recv_buf_ipv4))) {
break;
}
if (websock6 >= 0 &&
!send_and_wait_msg(websock6, amount, "IPv6",
recv_buf_ipv6, sizeof(recv_buf_ipv6))) {
break;
}
k_sleep(K_MSEC(250));
}
if (websock4 >= 0) {
close(websock4);
}
if (websock6 >= 0) {
close(websock6);
}
k_sleep(K_FOREVER);
}