blob: d1c1a1144168e0f204384396b4dd2f7056efd768 [file] [log] [blame]
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#if 1
#define SYS_LOG_DOMAIN "rpl-br/coap"
#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG
#define NET_LOG_ENABLED 1
#endif
#include <zephyr.h>
#include <stdio.h>
#include <stdlib.h>
#include <net/net_app.h>
#include <net/coap.h>
#include <net/udp.h>
#include "rpl.h"
#include "net_private.h"
#include "config.h"
#define MY_COAP_PORT 0xC0AB
#define PEER_COAP_PORT htons(0x1633) /* 5683 */
#define PKT_WAIT_TIME K_SECONDS(1)
#define RESPONSE_TIME K_SECONDS(3)
#define MAX_COAP_REQUEST_ATTEMPTS 5
#define MAX_COAP_REQUESTS 20
struct coap_request {
struct k_delayed_work timer; /* Timer to retransmit messages */
struct sockaddr_in6 peer; /* Peer CoAP server socket address */
u16_t id; /* Message id sent */
u8_t code; /* Expecting reply code */
u8_t count; /* Number of trails */
bool used; /* Entry used or not */
enum coap_request_type type;
coap_reply_cb_t cb;
void *user_data;
};
static struct net_context *coap;
static struct coap_request requests[MAX_COAP_REQUESTS];
static const char * const led_uri_path[] = { "led", NULL };
static const char * const rpl_obs_path[] = { "rpl-obs", NULL };
static void get_from_ip_addr(struct coap_packet *cpkt,
struct sockaddr_in6 *from)
{
struct net_udp_hdr hdr, *udp_hdr;
udp_hdr = net_udp_get_hdr(cpkt->pkt, &hdr);
if (!udp_hdr) {
return;
}
net_ipaddr_copy(&from->sin6_addr, &NET_IPV6_HDR(cpkt->pkt)->src);
from->sin6_port = udp_hdr->src_port;
from->sin6_family = AF_INET6;
}
static struct coap_request *
get_coap_request_by_type(const struct sockaddr_in6 *peer,
enum coap_request_type type)
{
u8_t i;
for (i = 0; i < MAX_COAP_REQUESTS; i++) {
if (!requests[i].used) {
continue;
}
if (requests[i].type == type &&
net_ipv6_addr_cmp(&requests[i].peer.sin6_addr,
&peer->sin6_addr)) {
return &requests[i];
}
}
return NULL;
}
static struct coap_request *
get_coap_request_by_id(const struct sockaddr_in6 *peer, u16_t id)
{
u8_t i;
for (i = 0; i < MAX_COAP_REQUESTS; i++) {
if (!requests[i].used) {
continue;
}
if (requests[i].id == id &&
net_ipv6_addr_cmp(&requests[i].peer.sin6_addr,
&peer->sin6_addr)) {
return &requests[i];
}
}
return NULL;
}
static struct coap_request *
get_coap_request_by_addr(const struct in6_addr *peer)
{
u8_t i;
for (i = 0; i < MAX_COAP_REQUESTS; i++) {
if (!requests[i].used) {
continue;
}
if (net_ipv6_addr_cmp(&requests[i].peer.sin6_addr, peer)) {
return &requests[i];
}
}
return NULL;
}
static struct coap_request *get_free_coap_request(void)
{
int i;
for (i = 0; i < MAX_COAP_REQUESTS; i++) {
if (!requests[i].used) {
return &requests[i];
}
}
return NULL;
}
static void clear_coap_request(struct coap_request *request)
{
if (!request->used) {
return;
}
request->type = COAP_REQ_NONE;
request->id = 0;
request->code = 0;
request->count = 0;
request->used = false;
request->cb = NULL;
request->user_data = NULL;
k_delayed_work_cancel(&request->timer);
}
static bool toggle_led(const struct sockaddr_in6 *peer, u16_t id)
{
struct net_pkt *pkt;
struct net_buf *frag;
struct coap_packet request;
const char * const *p;
int r;
pkt = net_pkt_get_tx(coap, PKT_WAIT_TIME);
if (!pkt) {
NET_ERR("Ran out of network packets");
return false;
}
frag = net_pkt_get_data(coap, PKT_WAIT_TIME);
if (!frag) {
NET_ERR("Ran out of network buffers");
goto end;
}
net_pkt_frag_add(pkt, frag);
r = coap_packet_init(&request, pkt, 1, COAP_TYPE_NON_CON,
0, NULL, COAP_METHOD_POST, id);
if (r < 0) {
NET_ERR("Failed to initialize CoAP packet");
goto end;
}
for (p = led_uri_path; p && *p; p++) {
r = coap_packet_append_option(&request, COAP_OPTION_URI_PATH,
*p, strlen(*p));
if (r < 0) {
NET_ERR("Unable add option to request.\n");
goto end;
}
}
r = net_context_sendto(pkt, (const struct sockaddr *)peer,
sizeof(struct sockaddr_in6),
NULL, 0, NULL, NULL);
if (r < 0) {
NET_ERR("Cannot send data to peer (%d)", r);
goto end;
}
return true;
end:
net_pkt_unref(pkt);
return false;
}
static bool set_rpl_observer(const struct sockaddr_in6 *peer, u16_t id)
{
struct net_pkt *pkt;
struct net_buf *frag;
struct coap_packet request;
const char * const *p;
int r;
pkt = net_pkt_get_tx(coap, PKT_WAIT_TIME);
if (!pkt) {
NET_ERR("Ran out of network packets");
return false;
}
frag = net_pkt_get_data(coap, PKT_WAIT_TIME);
if (!frag) {
NET_ERR("Ran out of network buffers");
goto end;
}
net_pkt_frag_add(pkt, frag);
r = coap_packet_init(&request, pkt, 1, COAP_TYPE_CON,
8, coap_next_token(),
COAP_METHOD_GET, id);
if (r < 0) {
NET_ERR("Failed to initialize CoAP packet");
goto end;
}
r = coap_append_option_int(&request, COAP_OPTION_OBSERVE, 0);
if (r < 0) {
NET_ERR("Unable add option to request");
goto end;
}
for (p = rpl_obs_path; p && *p; p++) {
r = coap_packet_append_option(&request, COAP_OPTION_URI_PATH,
*p, strlen(*p));
if (r < 0) {
NET_ERR("Unable add option to request");
goto end;
}
}
r = net_context_sendto(pkt, (const struct sockaddr *)peer,
sizeof(struct sockaddr_in6),
NULL, 0, NULL, NULL);
if (r < 0) {
NET_ERR("Cannot send data to peer (%d)", r);
goto end;
}
return true;
end:
net_pkt_unref(pkt);
return false;
}
static void request_timeout(struct k_work *work)
{
struct coap_request *request = CONTAINER_OF(work,
struct coap_request,
timer);
if (request->type != COAP_REQ_RPL_OBS) {
return;
}
/* Check if number of CoAP requests to this peer reached max or not. */
if (request->count >= MAX_COAP_REQUEST_ATTEMPTS) {
clear_coap_request(request);
return;
}
request->count++;
set_rpl_observer(&request->peer, request->id);
k_delayed_work_submit(&request->timer, RESPONSE_TIME);
}
static void add_nbr_to_topology(struct in6_addr *nbr)
{
if (topology.nodes[0].used) {
return;
}
topology.nodes[0].id = 1;
topology.nodes[0].used = true;
snprintk(topology.nodes[0].label, sizeof(topology.nodes[0].label),
"NBR");
net_ipaddr_copy(&topology.nodes[0].addr, nbr);
}
static void add_node_to_topology(struct in6_addr *node)
{
u8_t i;
/* BR takes 'id : 1', so node's id starts from 2 */
for (i = 1; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) {
if (topology.nodes[i].used) {
continue;
}
topology.nodes[i].id = i + 1;
topology.nodes[i].used = true;
snprintk(topology.nodes[i].label,
sizeof(topology.nodes[i].label),
"N%d", topology.nodes[i].id);
net_ipaddr_copy(&topology.nodes[i].addr, node);
break;
}
}
static void update_node_topology(struct in6_addr *node,
struct in6_addr *parent,
u16_t rank)
{
u8_t i;
for (i = 0; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) {
if (!topology.nodes[i].used) {
continue;
}
if (!net_ipv6_addr_cmp(&topology.nodes[i].addr,
node)) {
continue;
}
topology.nodes[i].rank = rank;
net_ipaddr_copy(&topology.nodes[i].parent, parent);
break;
}
}
static void remove_node_from_topology(struct in6_addr *node)
{
u8_t i;
for (i = 1; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) {
if (!topology.nodes[i].used) {
continue;
}
if (!net_ipv6_addr_cmp(&topology.nodes[i].addr,
node)) {
continue;
}
topology.nodes[i].used = false;
}
}
#define COAP_REPLY_PARENT strlen("parent-")
#define COAP_REPLY_RANK strlen("rank-")
#define COAP_REPLY_NONE strlen("None")
#define COAP_REPLY_RANK_VALUE 6
#define MAX_PAYLOAD_SIZE (NET_IPV6_ADDR_LEN + \
COAP_REPLY_PARENT + \
COAP_REPLY_RANK + \
COAP_REPLY_RANK_VALUE)
static void node_obs_reply(struct coap_packet *response, void *user_data)
{
char payload[MAX_PAYLOAD_SIZE];
char parent_str[NET_IPV6_ADDR_LEN + 1];
char rank_str[COAP_REPLY_RANK_VALUE + 1];
struct sockaddr_in6 from;
struct in6_addr parent;
struct net_buf *frag;
u16_t offset;
u16_t len;
u16_t rank;
u8_t i;
frag = coap_packet_get_payload(response, &offset, &len);
if (!frag && offset == 0xffff) {
NET_ERR("Error while getting payload");
return;
}
if (!len) {
NET_ERR("Invalid response");
return;
}
frag = net_frag_read(frag, offset, &offset, len, (u8_t *) payload);
if (!frag && offset == 0xffff) {
return;
}
if (strncmp(payload, "parent-", COAP_REPLY_PARENT)) {
return;
}
if (!strncmp(payload + COAP_REPLY_PARENT, "None", COAP_REPLY_NONE)) {
return;
}
i = 0;
offset = COAP_REPLY_PARENT;
while (payload[offset] != '\n') {
parent_str[i++] = payload[offset++];
}
parent_str[i] = '\0';
i++;
if (strncmp(payload + COAP_REPLY_PARENT + i, "rank-",
COAP_REPLY_RANK)) {
return;
}
if (!strncmp(payload + COAP_REPLY_PARENT + i, "None",
COAP_REPLY_NONE)) {
return;
}
offset = COAP_REPLY_PARENT + i + COAP_REPLY_RANK;
i = 0;
while (offset < len) {
rank_str[i++] = payload[offset++];
}
if (i > strlen(rank_str)) {
return;
}
rank_str[i] = '\0';
if (net_addr_pton(AF_INET6, parent_str, &parent) < 0) {
NET_ERR("Failed to convert parent address");
return;
}
get_from_ip_addr(response, &from);
rank = atoi(rank_str);
update_node_topology(&from.sin6_addr, &parent, rank);
}
static void pkt_receive(struct net_context *context,
struct net_pkt *pkt,
int status,
void *user_data)
{
struct coap_option options[4] = { 0 };
struct coap_packet response;
struct coap_request *coap_req;
struct sockaddr_in6 from;
u16_t id;
u8_t type;
u8_t code;
u8_t opt_num = 4;
int r;
r = coap_packet_parse(&response, pkt, options, opt_num);
if (r < 0) {
NET_ERR("Invalid data received (%d)\n", r);
net_pkt_unref(pkt);
return;
}
type = coap_header_get_type(&response);
code = coap_header_get_code(&response);
id = coap_header_get_id(&response);
get_from_ip_addr(&response, &from);
if (type != COAP_TYPE_ACK) {
NET_ERR("Invalid response, type %d", type);
net_pkt_unref(pkt);
return;
}
NET_DBG("Received %d bytes coap payload",
net_pkt_appdatalen(pkt) - response.hdr_len - response.opt_len);
coap_req = get_coap_request_by_id(&from, id);
if (!coap_req) {
net_pkt_unref(pkt);
return;
}
if (code != coap_req->code) {
NET_ERR("Invalid response, code %d", code);
goto end;
}
if (coap_req->type == COAP_REQ_RPL_OBS) {
node_obs_reply(&response, coap_req->user_data);
}
if (coap_req->cb) {
coap_req->cb(&response, coap_req->user_data);
}
end:
clear_coap_request(coap_req);
net_pkt_unref(pkt);
}
void coap_remove_node_from_topology(struct in6_addr *peer)
{
struct coap_request *request;
request = get_coap_request_by_addr(peer);
if (request) {
clear_coap_request(request);
}
remove_node_from_topology(peer);
}
void coap_send_request(struct in6_addr *peer_addr,
enum coap_request_type type,
coap_reply_cb_t cb,
void *user_data)
{
struct sockaddr_in6 peer;
struct coap_request *request;
peer.sin6_family = AF_INET6;
peer.sin6_port = PEER_COAP_PORT;
net_ipaddr_copy(&peer.sin6_addr, peer_addr);
if (type == COAP_REQ_TOGGLE_LED) {
toggle_led(&peer, coap_next_id());
return;
}
if (type != COAP_REQ_RPL_OBS) {
return;
}
/* Check for request has been sent already or not. If request has been
* sent already then timer will run until number of requests reach
* MAX_COAP_REQUEST_ATTEMPTS.
*/
request = get_coap_request_by_type(&peer, type);
if (request) {
return;
}
request = get_free_coap_request();
if (!request) {
NET_ERR("Failed to get free coap request");
return;
}
request->peer.sin6_family = peer.sin6_family;
request->peer.sin6_port = peer.sin6_port;
net_ipaddr_copy(&request->peer.sin6_addr, &peer.sin6_addr);
request->id = coap_next_id();
request->count = 1;
request->type = type;
request->cb = cb;
request->user_data = user_data;
request->used = true;
request->code = COAP_RESPONSE_CODE_CONTENT;
add_node_to_topology(peer_addr);
set_rpl_observer(&request->peer, request->id);
k_delayed_work_init(&request->timer, request_timeout);
k_delayed_work_submit(&request->timer, RESPONSE_TIME);
}
int coap_init(void)
{
struct net_if *iface = NULL;
static struct sockaddr_in6 my_addr = {
.sin6_family = AF_INET6,
.sin6_port = htons(MY_COAP_PORT)
};
u8_t i;
int r;
iface = net_if_get_ieee802154();
if (!iface) {
NET_ERR("No IEEE 802.15.4 network interface found.");
return -EINVAL;
}
for (i = 0; i < NET_IF_MAX_IPV6_ADDR; i++) {
if (iface->config.ip.ipv6->unicast[i].is_used) {
break;
}
}
if (i >= NET_IF_MAX_IPV6_ADDR) {
return -EINVAL;
}
net_ipaddr_copy(&my_addr.sin6_addr,
&iface->config.ip.ipv6->unicast[i].address.in6_addr);
add_nbr_to_topology(&my_addr.sin6_addr);
r = net_context_get(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, &coap);
if (r < 0) {
NET_ERR("Could not get UDP context");
return r;
}
r = net_context_bind(coap, (struct sockaddr *) &my_addr,
sizeof(my_addr));
if (r < 0) {
NET_ERR("Could not bind to the context");
return r;
}
r = net_context_recv(coap, pkt_receive, 0, NULL);
if (r < 0) {
NET_ERR("Could not set recv callback in the context");
return r;
}
return 0;
}