blob: 9dbaf70cc717104f20f489f522aab2bb71fb3e6a [file] [log] [blame]
/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_REGISTER(net_coap_client_sample, LOG_LEVEL_DBG);
#include <errno.h>
#include <sys/printk.h>
#include <sys/byteorder.h>
#include <zephyr.h>
#include <net/socket.h>
#include <net/net_mgmt.h>
#include <net/net_ip.h>
#include <net/udp.h>
#include <net/coap.h>
#include "net_private.h"
#define PEER_PORT 5683
#define MAX_COAP_MSG_LEN 256
/* CoAP socket fd */
static int sock;
struct pollfd fds[1];
static int nfds;
/* CoAP Options */
static const char * const test_path[] = { "test", NULL };
static const char * const large_path[] = { "large", NULL };
static const char * const obs_path[] = { "obs", NULL };
#define BLOCK_WISE_TRANSFER_SIZE_GET 2048
static struct coap_block_context blk_ctx;
static void wait(void)
{
if (poll(fds, nfds, -1) < 0) {
LOG_ERR("Error in poll:%d", errno);
}
}
static void prepare_fds(void)
{
fds[nfds].fd = sock;
fds[nfds].events = POLLIN;
nfds++;
}
static int start_coap_client(void)
{
int ret = 0;
struct sockaddr_in6 addr6;
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(PEER_PORT);
addr6.sin6_scope_id = 0U;
inet_pton(AF_INET6, CONFIG_NET_CONFIG_PEER_IPV6_ADDR,
&addr6.sin6_addr);
sock = socket(addr6.sin6_family, SOCK_DGRAM, IPPROTO_UDP);
if (sock < 0) {
LOG_ERR("Failed to create UDP socket %d", errno);
return -errno;
}
ret = connect(sock, (struct sockaddr *)&addr6, sizeof(addr6));
if (ret < 0) {
LOG_ERR("Cannot connect to UDP remote : %d", errno);
return -errno;
}
prepare_fds();
return 0;
}
static int process_simple_coap_reply(void)
{
struct coap_packet reply;
uint8_t *data;
int rcvd;
int ret;
wait();
data = (uint8_t *)k_malloc(MAX_COAP_MSG_LEN);
if (!data) {
return -ENOMEM;
}
rcvd = recv(sock, data, MAX_COAP_MSG_LEN, MSG_DONTWAIT);
if (rcvd == 0) {
ret = -EIO;
goto end;
}
if (rcvd < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
ret = 0;
} else {
ret = -errno;
}
goto end;
}
net_hexdump("Response", data, rcvd);
ret = coap_packet_parse(&reply, data, rcvd, NULL, 0);
if (ret < 0) {
LOG_ERR("Invalid data received");
}
end:
k_free(data);
return ret;
}
static int send_simple_coap_request(uint8_t method)
{
uint8_t payload[] = "payload";
struct coap_packet request;
const char * const *p;
uint8_t *data;
int r;
data = (uint8_t *)k_malloc(MAX_COAP_MSG_LEN);
if (!data) {
return -ENOMEM;
}
r = coap_packet_init(&request, data, MAX_COAP_MSG_LEN,
COAP_VERSION_1, COAP_TYPE_CON,
COAP_TOKEN_MAX_LEN, coap_next_token(),
method, coap_next_id());
if (r < 0) {
LOG_ERR("Failed to init CoAP message");
goto end;
}
for (p = test_path; p && *p; p++) {
r = coap_packet_append_option(&request, COAP_OPTION_URI_PATH,
*p, strlen(*p));
if (r < 0) {
LOG_ERR("Unable add option to request");
goto end;
}
}
switch (method) {
case COAP_METHOD_GET:
case COAP_METHOD_DELETE:
break;
case COAP_METHOD_PUT:
case COAP_METHOD_POST:
r = coap_packet_append_payload_marker(&request);
if (r < 0) {
LOG_ERR("Unable to append payload marker");
goto end;
}
r = coap_packet_append_payload(&request, (uint8_t *)payload,
sizeof(payload) - 1);
if (r < 0) {
LOG_ERR("Not able to append payload");
goto end;
}
break;
default:
r = -EINVAL;
goto end;
}
net_hexdump("Request", request.data, request.offset);
r = send(sock, request.data, request.offset, 0);
end:
k_free(data);
return 0;
}
static int send_simple_coap_msgs_and_wait_for_reply(void)
{
uint8_t test_type = 0U;
int r;
while (1) {
switch (test_type) {
case 0:
/* Test CoAP GET method */
printk("\nCoAP client GET\n");
r = send_simple_coap_request(COAP_METHOD_GET);
if (r < 0) {
return r;
}
break;
case 1:
/* Test CoAP PUT method */
printk("\nCoAP client PUT\n");
r = send_simple_coap_request(COAP_METHOD_PUT);
if (r < 0) {
return r;
}
break;
case 2:
/* Test CoAP POST method*/
printk("\nCoAP client POST\n");
r = send_simple_coap_request(COAP_METHOD_POST);
if (r < 0) {
return r;
}
break;
case 3:
/* Test CoAP DELETE method*/
printk("\nCoAP client DELETE\n");
r = send_simple_coap_request(COAP_METHOD_DELETE);
if (r < 0) {
return r;
}
break;
default:
return 0;
}
r = process_simple_coap_reply();
if (r < 0) {
return r;
}
test_type++;
}
return 0;
}
static int process_large_coap_reply(void)
{
struct coap_packet reply;
uint8_t *data;
bool last_block;
int rcvd;
int ret;
wait();
data = (uint8_t *)k_malloc(MAX_COAP_MSG_LEN);
if (!data) {
return -ENOMEM;
}
rcvd = recv(sock, data, MAX_COAP_MSG_LEN, MSG_DONTWAIT);
if (rcvd == 0) {
ret = -EIO;
goto end;
}
if (rcvd < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
ret = 0;
} else {
ret = -errno;
}
goto end;
}
net_hexdump("Response", data, rcvd);
ret = coap_packet_parse(&reply, data, rcvd, NULL, 0);
if (ret < 0) {
LOG_ERR("Invalid data received");
goto end;
}
ret = coap_update_from_block(&reply, &blk_ctx);
if (ret < 0) {
goto end;
}
last_block = coap_next_block(&reply, &blk_ctx);
if (!last_block) {
ret = 1;
goto end;
}
ret = 0;
end:
k_free(data);
return ret;
}
static int send_large_coap_request(void)
{
struct coap_packet request;
const char * const *p;
uint8_t *data;
int r;
if (blk_ctx.total_size == 0) {
coap_block_transfer_init(&blk_ctx, COAP_BLOCK_64,
BLOCK_WISE_TRANSFER_SIZE_GET);
}
data = (uint8_t *)k_malloc(MAX_COAP_MSG_LEN);
if (!data) {
return -ENOMEM;
}
r = coap_packet_init(&request, data, MAX_COAP_MSG_LEN,
COAP_VERSION_1, COAP_TYPE_CON,
COAP_TOKEN_MAX_LEN, coap_next_token(),
COAP_METHOD_GET, coap_next_id());
if (r < 0) {
LOG_ERR("Failed to init CoAP message");
goto end;
}
for (p = large_path; p && *p; p++) {
r = coap_packet_append_option(&request, COAP_OPTION_URI_PATH,
*p, strlen(*p));
if (r < 0) {
LOG_ERR("Unable add option to request");
goto end;
}
}
r = coap_append_block2_option(&request, &blk_ctx);
if (r < 0) {
LOG_ERR("Unable to add block2 option.");
goto end;
}
net_hexdump("Request", request.data, request.offset);
r = send(sock, request.data, request.offset, 0);
end:
k_free(data);
return r;
}
static int get_large_coap_msgs(void)
{
int r;
while (1) {
/* Test CoAP Large GET method */
printk("\nCoAP client Large GET (block %zd)\n",
blk_ctx.current / 64 /*COAP_BLOCK_64*/);
r = send_large_coap_request();
if (r < 0) {
return r;
}
r = process_large_coap_reply();
if (r < 0) {
return r;
}
/* Received last block */
if (r == 1) {
memset(&blk_ctx, 0, sizeof(blk_ctx));
return 0;
}
}
return 0;
}
static void send_obs_reply_ack(uint16_t id)
{
struct coap_packet request;
uint8_t *data;
int r;
data = (uint8_t *)k_malloc(MAX_COAP_MSG_LEN);
if (!data) {
return;
}
r = coap_packet_init(&request, data, MAX_COAP_MSG_LEN,
COAP_VERSION_1, COAP_TYPE_ACK, 0, NULL, 0, id);
if (r < 0) {
LOG_ERR("Failed to init CoAP message");
goto end;
}
net_hexdump("ACK", request.data, request.offset);
r = send(sock, request.data, request.offset, 0);
if (r < 0) {
LOG_ERR("Failed to send CoAP ACK");
}
end:
k_free(data);
}
static int obs_notification_cb(const struct coap_packet *response,
struct coap_reply *reply,
const struct sockaddr *from)
{
uint16_t id = coap_header_get_id(response);
uint8_t type = coap_header_get_type(response);
uint8_t *counter = (uint8_t *)reply->user_data;
ARG_UNUSED(from);
printk("\nCoAP OBS Notification\n");
(*counter)++;
if (type == COAP_TYPE_CON) {
send_obs_reply_ack(id);
}
return 0;
}
static int process_obs_coap_reply(struct coap_reply *reply)
{
struct coap_packet reply_msg;
uint8_t *data;
int rcvd;
int ret;
wait();
data = (uint8_t *)k_malloc(MAX_COAP_MSG_LEN);
if (!data) {
return -ENOMEM;
}
rcvd = recv(sock, data, MAX_COAP_MSG_LEN, MSG_DONTWAIT);
if (rcvd == 0) {
ret = -EIO;
goto end;
}
if (rcvd < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
ret = 0;
} else {
ret = -errno;
}
goto end;
}
net_hexdump("Response", data, rcvd);
ret = coap_packet_parse(&reply_msg, data, rcvd, NULL, 0);
if (ret < 0) {
LOG_ERR("Invalid data received");
goto end;
}
if (coap_response_received(&reply_msg, NULL, reply, 1) == NULL) {
printk("\nOther response received\n");
}
end:
k_free(data);
return ret;
}
static int send_obs_coap_request(struct coap_reply *reply, void *user_data)
{
struct coap_packet request;
const char * const *p;
uint8_t *data;
int r;
data = (uint8_t *)k_malloc(MAX_COAP_MSG_LEN);
if (!data) {
return -ENOMEM;
}
r = coap_packet_init(&request, data, MAX_COAP_MSG_LEN,
COAP_VERSION_1, COAP_TYPE_CON,
COAP_TOKEN_MAX_LEN, coap_next_token(),
COAP_METHOD_GET, coap_next_id());
if (r < 0) {
LOG_ERR("Failed to init CoAP message");
goto end;
}
r = coap_append_option_int(&request, COAP_OPTION_OBSERVE, 0);
if (r < 0) {
LOG_ERR("Failed to append Observe option");
goto end;
}
for (p = obs_path; p && *p; p++) {
r = coap_packet_append_option(&request, COAP_OPTION_URI_PATH,
*p, strlen(*p));
if (r < 0) {
LOG_ERR("Unable add option to request");
goto end;
}
}
net_hexdump("Request", request.data, request.offset);
coap_reply_init(reply, &request);
reply->reply = obs_notification_cb;
reply->user_data = user_data;
r = send(sock, request.data, request.offset, 0);
end:
k_free(data);
return r;
}
static int send_obs_reset_coap_request(struct coap_reply *reply)
{
struct coap_packet request;
const char * const *p;
uint8_t *data;
int r;
data = (uint8_t *)k_malloc(MAX_COAP_MSG_LEN);
if (!data) {
return -ENOMEM;
}
r = coap_packet_init(&request, data, MAX_COAP_MSG_LEN,
COAP_VERSION_1, COAP_TYPE_CON,
reply->tkl, reply->token,
COAP_METHOD_GET, coap_next_id());
if (r < 0) {
LOG_ERR("Failed to init CoAP message");
goto end;
}
r = coap_append_option_int(&request, COAP_OPTION_OBSERVE, 1);
if (r < 0) {
LOG_ERR("Failed to append Observe option");
goto end;
}
for (p = obs_path; p && *p; p++) {
r = coap_packet_append_option(&request, COAP_OPTION_URI_PATH,
*p, strlen(*p));
if (r < 0) {
LOG_ERR("Unable add option to request");
goto end;
}
}
net_hexdump("Request", request.data, request.offset);
r = send(sock, request.data, request.offset, 0);
end:
k_free(data);
return r;
}
static int register_observer(void)
{
struct coap_reply reply;
uint8_t counter = 0;
int r;
printk("\nCoAP client OBS GET\n");
r = send_obs_coap_request(&reply, &counter);
if (r < 0) {
return r;
}
while (1) {
r = process_obs_coap_reply(&reply);
if (r < 0) {
return r;
}
if (counter >= 5) {
/* TODO: Functionality can be verified byt waiting for
* some time and make sure client shouldn't receive
* any notifications. If client still receives
* notifications means, Observer is not removed.
*/
r = send_obs_reset_coap_request(&reply);
if (r < 0) {
return r;
}
/* Wait for the final ACK */
r = process_obs_coap_reply(&reply);
if (r < 0) {
return r;
}
break;
}
}
return 0;
}
void main(void)
{
int r;
LOG_DBG("Start CoAP-client sample");
r = start_coap_client();
if (r < 0) {
goto quit;
}
/* GET, PUT, POST, DELETE */
r = send_simple_coap_msgs_and_wait_for_reply();
if (r < 0) {
goto quit;
}
/* Block-wise transfer */
r = get_large_coap_msgs();
if (r < 0) {
goto quit;
}
/* Register observer, get notifications and unregister */
r = register_observer();
if (r < 0) {
goto quit;
}
/* Close the socket */
(void)close(sock);
LOG_DBG("Done");
return;
quit:
(void)close(sock);
LOG_ERR("quit");
}