coccicheck --mode=patch --cocci=semicolon.cocci Signed-off-by: Flavio Ceolin <flavio.ceolin@intel.com>
440 lines
11 KiB
C
440 lines
11 KiB
C
/*
|
|
* Copyright (c) 2019 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(net_websocket_client_sample, LOG_LEVEL_DBG);
|
|
|
|
#include <net/net_ip.h>
|
|
#include <net/socket.h>
|
|
#include <net/tls_credentials.h>
|
|
#include <net/websocket.h>
|
|
#include <random/rand32.h>
|
|
#include <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);
|
|
}
|