| /* |
| * 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; |
| } |