mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
Merge pull request #5598 from kb2ma/gcoap/master
GNRC implementation of CoAP
This commit is contained in:
commit
5084d8c5ec
@ -96,6 +96,10 @@ ifneq (,$(filter gnrc_zep,$(USEMODULE)))
|
||||
USEMODULE += xtimer
|
||||
endif
|
||||
|
||||
ifneq (,$(filter gcoap,$(USEMODULE)))
|
||||
USEMODULE += gnrc_udp
|
||||
endif
|
||||
|
||||
ifneq (,$(filter gnrc_tftp,$(USEMODULE)))
|
||||
USEMODULE += gnrc_udp
|
||||
USEMODULE += xtimer
|
||||
|
61
examples/gcoap/Makefile
Normal file
61
examples/gcoap/Makefile
Normal file
@ -0,0 +1,61 @@
|
||||
# Default Makefile, for host native networking
|
||||
|
||||
# name of your application
|
||||
APPLICATION = gcoap
|
||||
|
||||
# If no BOARD is found in the environment, use this default:
|
||||
BOARD ?= native
|
||||
|
||||
# This has to be the absolute path to the RIOT base directory:
|
||||
RIOTBASE ?= $(CURDIR)/../..
|
||||
|
||||
BOARD_INSUFFICIENT_MEMORY := chronos msb-430 msb-430h nucleo-f030 nucleo-f334 \
|
||||
stm32f0discovery telosb weio wsn430-v1_3b wsn430-v1_4 \
|
||||
z1
|
||||
|
||||
# Must read nordic_softdevice_ble package before nanocoap package. However,
|
||||
# can't read it explicitly here because it is read later, as a dependency for
|
||||
# the nrf52dk board.
|
||||
BOARD_BLACKLIST := nrf52dk
|
||||
|
||||
## Uncomment to redefine port, for example use 61616 for RFC 6282 UDP compression.
|
||||
#GCOAP_PORT = 5683
|
||||
#CFLAGS += -DGCOAP_PORT=$(GCOAP_PORT)
|
||||
|
||||
## Uncomment to redefine request token length, max 8.
|
||||
#GCOAP_TOKENLEN = 2
|
||||
#CFLAGS += -DGCOAP_TOKENLEN=$(GCOAP_TOKENLEN)
|
||||
|
||||
USEPKG += nanocoap
|
||||
# Required by nanocoap, but only due to issue #5959.
|
||||
USEMODULE += posix
|
||||
# Required by nanocoap to compile nanocoap_sock.
|
||||
USEMODULE += gnrc_sock_udp
|
||||
|
||||
# Include packages that pull up and auto-init the link layer.
|
||||
# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present
|
||||
USEMODULE += gnrc_netdev_default
|
||||
USEMODULE += auto_init_gnrc_netif
|
||||
# Specify the mandatory networking modules
|
||||
USEMODULE += gnrc_ipv6_default
|
||||
USEMODULE += gcoap
|
||||
# Additional networking modules that can be dropped if not needed
|
||||
USEMODULE += gnrc_icmpv6_echo
|
||||
|
||||
# Required by gcoap example
|
||||
USEMODULE += od
|
||||
USEMODULE += fmt
|
||||
# Add also the shell, some shell commands
|
||||
USEMODULE += shell
|
||||
USEMODULE += shell_commands
|
||||
USEMODULE += ps
|
||||
|
||||
# Comment this out to disable code in RIOT that does safety checking
|
||||
# which is not needed in a production environment but helps in the
|
||||
# development process:
|
||||
CFLAGS += -DDEVELHELP
|
||||
|
||||
# Change this to 0 show compiler invocation lines by default:
|
||||
QUIET ?= 1
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
84
examples/gcoap/Makefile.slip
Normal file
84
examples/gcoap/Makefile.slip
Normal file
@ -0,0 +1,84 @@
|
||||
# Border router Makefile for SLIP based networking
|
||||
# Assumes use of SAMR21 board
|
||||
|
||||
# name of your application
|
||||
APPLICATION = gcoap
|
||||
|
||||
# If no BOARD is found in the environment, use this default:
|
||||
BOARD ?= samr21-xpro
|
||||
|
||||
# This has to be the absolute path to the RIOT base directory:
|
||||
RIOTBASE ?= $(CURDIR)/../..
|
||||
|
||||
BOARD_INSUFFICIENT_MEMORY := chronos msb-430 msb-430h nucleo-f030 nucleo-f334 \
|
||||
stm32f0discovery telosb weio wsn430-v1_3b wsn430-v1_4 \
|
||||
z1
|
||||
|
||||
# Must read nordic_softdevice_ble package before nanocoap package. However,
|
||||
# can't read it explicitly here because it is read later, as a dependency for
|
||||
# the nrf52dk board.
|
||||
BOARD_BLACKLIST := nrf52dk
|
||||
|
||||
# Redefine port, for example use 61616 for RFC 6282 UDP compression.
|
||||
#GCOAP_PORT = 5683
|
||||
#CFLAGS += -DGCOAP_PORT=$(GCOAP_PORT)
|
||||
|
||||
# Redefine request token length, max 8.
|
||||
#GCOAP_TOKENLEN = 2
|
||||
#CFLAGS += -DGCOAP_TOKENLEN=$(GCOAP_TOKENLEN)
|
||||
|
||||
# Border router requirements
|
||||
ifeq (,$(SLIP_UART))
|
||||
# set default (last available UART)
|
||||
SLIP_UART="(UART_NUMOF-1)"
|
||||
endif
|
||||
ifeq (,$(SLIP_BAUDRATE))
|
||||
# set default
|
||||
SLIP_BAUDRATE=115200
|
||||
endif
|
||||
|
||||
GNRC_NETIF_NUMOF := 2
|
||||
INCLUDES += -I$(CURDIR)
|
||||
CFLAGS += -DSLIP_UART=$(SLIP_UART)
|
||||
CFLAGS += -DSLIP_BAUDRATE=$(SLIP_BAUDRATE)
|
||||
|
||||
USEPKG += nanocoap
|
||||
# Required by nanocoap, but only due to issue #5959.
|
||||
USEMODULE += posix
|
||||
# Required by nanocoap to compile nanocoap_sock.
|
||||
USEMODULE += gnrc_sock_udp
|
||||
|
||||
# Include packages that pull up and auto-init the link layer.
|
||||
# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present
|
||||
USEMODULE += gnrc_netdev_default
|
||||
USEMODULE += auto_init_gnrc_netif
|
||||
# Specify the mandatory networking modules
|
||||
USEMODULE += gcoap
|
||||
# Add a routing protocol
|
||||
USEMODULE += gnrc_rpl
|
||||
|
||||
# Border router requirements
|
||||
# Include SLIP package for IP over Serial communication
|
||||
USEMODULE += gnrc_slip
|
||||
# Specify the mandatory networking modules for 6LoWPAN border router
|
||||
USEMODULE += gnrc_sixlowpan_border_router_default
|
||||
# Additional networking modules that can be dropped if not needed
|
||||
USEMODULE += gnrc_icmpv6_echo
|
||||
|
||||
# Required by gcoap example
|
||||
USEMODULE += od
|
||||
USEMODULE += fmt
|
||||
# Add also the shell, some shell commands
|
||||
USEMODULE += shell
|
||||
USEMODULE += shell_commands
|
||||
USEMODULE += ps
|
||||
|
||||
# Comment this out to disable code in RIOT that does safety checking
|
||||
# which is not needed in a production environment but helps in the
|
||||
# development process:
|
||||
CFLAGS += -DDEVELHELP
|
||||
|
||||
# Change this to 0 show compiler invocation lines by default:
|
||||
QUIET ?= 1
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
67
examples/gcoap/README-slip.md
Normal file
67
examples/gcoap/README-slip.md
Normal file
@ -0,0 +1,67 @@
|
||||
`Makefile.slip` assumes use of a SAMR21 Xplained Pro. This file describes how we use the board as a border router. See the [SLIP instructions][1] for the gnrc_border_router example for more background.
|
||||
|
||||
## Background -- Building
|
||||
Build commands, for reference:
|
||||
|
||||
make clean BOARD="samr21-xpro"
|
||||
|
||||
# Use -f only for border router!
|
||||
make -f Makefile.slip BOARD="samr21-xpro"
|
||||
|
||||
../../dist/tools/usb-serial/list-ttys.sh
|
||||
# Use -f only for border router!
|
||||
make -f Makefile.slip flash BOARD="samr21-xpro" SERIAL="???"
|
||||
|
||||
make term BOARD="samr21-xpro" PORT="/dev/ttyACM???"
|
||||
|
||||
## USB serial port
|
||||
The USB serial port requires a USB-TTL converter cable with 3.3 V output.
|
||||
|
||||
Pin connections for SAMR21 board:
|
||||
|
||||
* PA23 (RX) -- TXD wire
|
||||
* PA22 (TX) -- RXD wire
|
||||
* GND -- GND wire
|
||||
|
||||
## Network configuration
|
||||
We will define two networks:
|
||||
|
||||
1. `bbbb` Between Border Router (BR) and Ubuntu host over TUN
|
||||
2. `aaaa` Between RIOT router node and BR over 6LoWPAN
|
||||
|
||||
We include two approaches to configuration below, automated via RPL, and manual via fib.
|
||||
|
||||
First, define a TUN interface on an Ubuntu host with tunslip, in the `dist/tools/tunslip` directory. In the example below, the tun interface is host 1.
|
||||
|
||||
cd ../../dist/tools/tunslip/
|
||||
sudo ./tunslip6 -s ttyUSB0 -t tun0 bbbb::1/64
|
||||
sudo ip -6 route add aaaa::/64 dev tun0
|
||||
|
||||
### RPL-based configuration
|
||||
|
||||
Configure the interface to the `bbbb` network on the BR from the RIOT terminal. The example commands below assume SLIP is on interface 8.
|
||||
|
||||
# Set address for SLIP interface
|
||||
ifconfig 8 add unicast bbbb::2/64
|
||||
# Add the Ubuntu host to the neighbor cache
|
||||
ncache add 8 bbbb::1
|
||||
|
||||
Next configure the interface for the `aaaa` network on the BR. Strictly speaking, it is not necessary to define this address. However, it is convenient for typing, because RIOT automatically creates a more verbose address based on the MAC address for the interface.
|
||||
|
||||
ifconfig 7 add unicast aaaa::1/64
|
||||
rpl init 7
|
||||
|
||||
Next, configure the interface on the `aaaa` network on the RIOT router mote.
|
||||
|
||||
ifconfig 7 add unicast aaaa::2/64
|
||||
rpl init 7
|
||||
|
||||
Finally, set the BR as the RPL DAG root.
|
||||
|
||||
rpl root 1 aaaa::1
|
||||
|
||||
Ping the TUN interface from the router mote, via the BR:
|
||||
|
||||
ping6 bbbb::1
|
||||
|
||||
[1]: https://github.com/RIOT-OS/RIOT/tree/master/examples/gnrc_border_router "SLIP instructions"
|
69
examples/gcoap/README.md
Normal file
69
examples/gcoap/README.md
Normal file
@ -0,0 +1,69 @@
|
||||
# gcoap Example
|
||||
|
||||
This application provides command line access to gcoap, a GNRC implementation of CoAP. See the [CoAP spec][1] for background, and the Modules>Networking>GNRC>CoAP topic in the source documentation for the structure of the implementation.
|
||||
|
||||
We support two setup options for this example:
|
||||
|
||||
### Native networking
|
||||
Build with the standard `Makefile`. Follow the setup [instructions][2] for the gnrc_networking example.
|
||||
|
||||
### SLIP-based border router
|
||||
|
||||
Build with `Makefile.slip`. Follow the setup instructions in README-slip.md, which are based on the [SLIP instructions][3] for the gnrc_border_router example. We also plan to provide or reference the ethos/UHCP instructions, but we don't have it working yet.
|
||||
|
||||
## Current Status
|
||||
gcoap includes server and client capability. Available features include:
|
||||
|
||||
* Server and Client provide helper functions for writing the response/request. See the CoAP topic in the source documentation for details. See the gcoap example for sample implementations.
|
||||
* Server allows an application to register a 'listener', which includes an array of endpoint paths and function callbacks used to write a response.
|
||||
* Server listens on a port at startup; defaults to 5683.
|
||||
* Client operates asynchronously; sends request and then handles response in a user provided callback. Also executes callback on timeout.
|
||||
* Client generates token; length defined at compile time.
|
||||
* Message Type: Supports non-confirmable (NON) messaging.
|
||||
* Options: Supports Content-Format for response payload.
|
||||
|
||||
|
||||
## Example Use
|
||||
This example uses gcoap as a server on RIOT native. Then we send a request from a libcoap example client on the Linux host.
|
||||
|
||||
### Verify setup from RIOT terminal
|
||||
|
||||
> coap info
|
||||
|
||||
Expected response:
|
||||
|
||||
CoAP server is listening on port 5683
|
||||
CLI requests sent: 0
|
||||
CoAP open requests: 0
|
||||
|
||||
### Query from libcoap example client
|
||||
gcoap does not provide any output to the CoAP terminal when it handles a request. We recommend use of Wireshark to see the request and response. You also can add some debug output in the endpoint function callback.
|
||||
|
||||
./coap-client -N -m get -p 5683 coap://[fe80::1843:8eff:fe40:4eaa%tap0]/.well-known/core
|
||||
|
||||
Example response:
|
||||
|
||||
v:1 t:NON c:GET i:0daa {} [ ]
|
||||
</cli/stats>
|
||||
|
||||
The response shows the endpoint registered by the gcoap CLI example.
|
||||
|
||||
### Send query to libcoap example server
|
||||
Start the libcoap example server with the command below.
|
||||
|
||||
./coap-server
|
||||
|
||||
Enter the query below in the RIOT CLI.
|
||||
|
||||
> coap get fe80::d8b8:65ff:feee:121b 5683 /.well-known/core
|
||||
|
||||
CLI output:
|
||||
|
||||
gcoap_cli: sending msg ID 743, 75 bytes
|
||||
> gcoap: response Success, code 2.05, 105 bytes
|
||||
</>;title="General Info";ct=0,</time>;if="clock";rt="Ticks";title="Internal Clock";ct=0;obs,</async>;ct=0
|
||||
|
||||
|
||||
[1]: https://tools.ietf.org/html/rfc7252 "CoAP spec"
|
||||
[2]: https://github.com/RIOT-OS/RIOT/tree/master/examples/gnrc_networking "instructions"
|
||||
[3]: https://github.com/RIOT-OS/RIOT/tree/master/examples/gnrc_border_router "SLIP instructions"
|
181
examples/gcoap/gcoap_cli.c
Normal file
181
examples/gcoap/gcoap_cli.c
Normal file
@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (c) 2015-2016 Ken Bannister. All rights reserved.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup examples
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief gcoap CLI support
|
||||
*
|
||||
* @author Ken Bannister <kb2ma@runbox.com>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "net/gnrc/coap.h"
|
||||
#include "od.h"
|
||||
#include "fmt.h"
|
||||
|
||||
static void _resp_handler(unsigned req_state, coap_pkt_t* pdu);
|
||||
static ssize_t _stats_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len);
|
||||
|
||||
/* CoAP resources */
|
||||
static const coap_resource_t _resources[] = {
|
||||
{ "/cli/stats", COAP_GET, _stats_handler },
|
||||
};
|
||||
static gcoap_listener_t _listener = {
|
||||
(coap_resource_t *)&_resources[0],
|
||||
sizeof(_resources) / sizeof(_resources[0]),
|
||||
NULL
|
||||
};
|
||||
|
||||
/* Counts requests sent by CLI. */
|
||||
static uint16_t req_count = 0;
|
||||
|
||||
/*
|
||||
* Response callback.
|
||||
*/
|
||||
static void _resp_handler(unsigned req_state, coap_pkt_t* pdu)
|
||||
{
|
||||
if (req_state == GCOAP_MEMO_TIMEOUT) {
|
||||
printf("gcoap: timeout for msg ID %02u\n", coap_get_id(pdu));
|
||||
return;
|
||||
}
|
||||
else if (req_state == GCOAP_MEMO_ERR) {
|
||||
printf("gcoap: error in response\n");
|
||||
return;
|
||||
}
|
||||
|
||||
char *class_str = (coap_get_code_class(pdu) == COAP_CLASS_SUCCESS)
|
||||
? "Success" : "Error";
|
||||
printf("gcoap: response %s, code %1u.%02u", class_str,
|
||||
coap_get_code_class(pdu),
|
||||
coap_get_code_detail(pdu));
|
||||
if (pdu->payload_len) {
|
||||
if (pdu->content_type == COAP_FORMAT_TEXT
|
||||
|| pdu->content_type == COAP_FORMAT_LINK
|
||||
|| coap_get_code_class(pdu) == COAP_CLASS_CLIENT_FAILURE
|
||||
|| coap_get_code_class(pdu) == COAP_CLASS_SERVER_FAILURE) {
|
||||
/* Expecting diagnostic payload in failure cases */
|
||||
printf(", %u bytes\n%.*s\n", pdu->payload_len, pdu->payload_len,
|
||||
(char *)pdu->payload);
|
||||
}
|
||||
else {
|
||||
printf(", %u bytes\n", pdu->payload_len);
|
||||
od_hex_dump(pdu->payload, pdu->payload_len, OD_WIDTH_DEFAULT);
|
||||
}
|
||||
}
|
||||
else {
|
||||
printf(", empty payload\n");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Server callback for /cli/stats. Returns the count of packets sent by the
|
||||
* CLI.
|
||||
*/
|
||||
static ssize_t _stats_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len)
|
||||
{
|
||||
gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT);
|
||||
|
||||
size_t payload_len = fmt_u16_dec((char *)pdu->payload, req_count);
|
||||
|
||||
return gcoap_finish(pdu, payload_len, COAP_FORMAT_TEXT);
|
||||
}
|
||||
|
||||
static size_t _send(uint8_t *buf, size_t len, char *addr_str, char *port_str)
|
||||
{
|
||||
ipv6_addr_t addr;
|
||||
uint16_t port;
|
||||
size_t bytes_sent;
|
||||
|
||||
/* parse destination address */
|
||||
if (ipv6_addr_from_str(&addr, addr_str) == NULL) {
|
||||
puts("gcoap_cli: unable to parse destination address");
|
||||
return 0;
|
||||
}
|
||||
/* parse port */
|
||||
port = (uint16_t)atoi(port_str);
|
||||
if (port == 0) {
|
||||
puts("gcoap_cli: unable to parse destination port");
|
||||
return 0;
|
||||
}
|
||||
|
||||
bytes_sent = gcoap_req_send(buf, len, &addr, port, _resp_handler);
|
||||
if (bytes_sent > 0) {
|
||||
req_count++;
|
||||
}
|
||||
return bytes_sent;
|
||||
}
|
||||
|
||||
int gcoap_cli_cmd(int argc, char **argv)
|
||||
{
|
||||
/* Ordered like the RFC method code numbers, but off by 1. GET is code 0. */
|
||||
char *method_codes[] = {"get", "post", "put"};
|
||||
uint8_t buf[GCOAP_PDU_BUF_SIZE];
|
||||
coap_pkt_t pdu;
|
||||
size_t len;
|
||||
|
||||
if (argc == 1) {
|
||||
/* show help for main commands */
|
||||
goto end;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof(method_codes) / sizeof(char*); i++) {
|
||||
if (strcmp(argv[1], method_codes[i]) == 0) {
|
||||
if (argc == 5 || argc == 6) {
|
||||
if (argc == 6) {
|
||||
gcoap_req_init(&pdu, &buf[0], GCOAP_PDU_BUF_SIZE, i+1, argv[4]);
|
||||
memcpy(pdu.payload, argv[5], strlen(argv[5]));
|
||||
len = gcoap_finish(&pdu, strlen(argv[5]), COAP_FORMAT_TEXT);
|
||||
}
|
||||
else {
|
||||
len = gcoap_request(&pdu, &buf[0], GCOAP_PDU_BUF_SIZE, i+1,
|
||||
argv[4]);
|
||||
}
|
||||
printf("gcoap_cli: sending msg ID %u, %u bytes\n", coap_get_id(&pdu),
|
||||
(unsigned) len);
|
||||
if (!_send(&buf[0], len, argv[2], argv[3])) {
|
||||
puts("gcoap_cli: msg send failed");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
printf("usage: %s <get|post|put> <addr> <port> <path> [data]\n",
|
||||
argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "info") == 0) {
|
||||
if (argc == 2) {
|
||||
uint8_t open_reqs;
|
||||
gcoap_op_state(&open_reqs);
|
||||
|
||||
printf("CoAP server is listening on port %u\n", GCOAP_PORT);
|
||||
printf(" CLI requests sent: %u\n", req_count);
|
||||
printf("CoAP open requests: %u\n", open_reqs);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
printf("usage: %s <get|post|put|info>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void gcoap_cli_init(void)
|
||||
{
|
||||
gcoap_register_listener(&_listener);
|
||||
}
|
53
examples/gcoap/main.c
Normal file
53
examples/gcoap/main.c
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2015-2016 Ken Bannister. All rights reserved.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup examples
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief gcoap example
|
||||
*
|
||||
* @author Ken Bannister <kb2ma@runbox.com>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "msg.h"
|
||||
|
||||
#include "net/gnrc/coap.h"
|
||||
#include "kernel_types.h"
|
||||
#include "shell.h"
|
||||
|
||||
#define MAIN_QUEUE_SIZE (4)
|
||||
static msg_t _main_msg_queue[MAIN_QUEUE_SIZE];
|
||||
|
||||
extern int gcoap_cli_cmd(int argc, char **argv);
|
||||
extern void gcoap_cli_init(void);
|
||||
|
||||
static const shell_command_t shell_commands[] = {
|
||||
{ "coap", "CoAP example", gcoap_cli_cmd },
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
int main(void)
|
||||
{
|
||||
/* for the thread running the shell */
|
||||
msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE);
|
||||
gcoap_cli_init();
|
||||
puts("gcoap example app");
|
||||
|
||||
/* start shell */
|
||||
puts("All up, running the shell now");
|
||||
char line_buf[SHELL_DEFAULT_BUFSIZE];
|
||||
shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
|
||||
|
||||
/* should never be reached */
|
||||
return 0;
|
||||
}
|
39
examples/gcoap/slip_params.h
Normal file
39
examples/gcoap/slip_params.h
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Martine Lenders <mlenders@inf.fu-berlin.de>
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup tests
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief slip parameters example, used by auto_init_gnrc_netif
|
||||
*
|
||||
* @author Martine Lenders <mlenders@inf.fu-berlin.de>
|
||||
*/
|
||||
|
||||
#ifndef SLIP_PARAMS_H
|
||||
#define SLIP_PARAMS_H
|
||||
|
||||
#include "net/gnrc/slip.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
static gnrc_slip_params_t gnrc_slip_params[] = {
|
||||
{
|
||||
.uart = SLIP_UART,
|
||||
.baudrate = SLIP_BAUDRATE,
|
||||
},
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* SLIP_PARAMS_H */
|
||||
/** @} */
|
@ -92,6 +92,10 @@
|
||||
#include "random.h"
|
||||
#endif
|
||||
|
||||
#ifdef MODULE_GCOAP
|
||||
#include "net/gnrc/coap.h"
|
||||
#endif
|
||||
|
||||
#define ENABLE_DEBUG (0)
|
||||
#include "debug.h"
|
||||
|
||||
@ -165,6 +169,10 @@ void auto_init(void)
|
||||
DEBUG("Bootstraping lwIP.\n");
|
||||
lwip_bootstrap();
|
||||
#endif
|
||||
#ifdef MODULE_GCOAP
|
||||
DEBUG("Auto init gcoap module.\n");
|
||||
gcoap_init();
|
||||
#endif
|
||||
|
||||
/* initialize network devices */
|
||||
#ifdef MODULE_AUTO_INIT_GNRC_NETIF
|
||||
|
388
sys/include/net/gnrc/coap.h
Normal file
388
sys/include/net/gnrc/coap.h
Normal file
@ -0,0 +1,388 @@
|
||||
/*
|
||||
* Copyright (c) 2015-2016 Ken Bannister. All rights reserved.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup net_gnrc_coap CoAP
|
||||
* @ingroup net_gnrc
|
||||
* @brief GNRC implementation of CoAP protocol, RFC 7252
|
||||
*
|
||||
* ## Architecture ##
|
||||
* Requests and responses are exchanged via an asynchronous RIOT message
|
||||
* processing thread. Depends on nanocoap for base level structs and
|
||||
* functionality.
|
||||
*
|
||||
* Uses a single UDP port for communication to support RFC 6282 compression.
|
||||
*
|
||||
* ## Server Operation ##
|
||||
*
|
||||
* gcoap listens for requests on GCOAP_PORT, 5683 by default. You can redefine
|
||||
* this by uncommenting the appropriate lines in gcoap's make file.
|
||||
*
|
||||
* gcoap allows an application to specify a collection of request resource paths
|
||||
* it wants to be notified about. Create an array of resources, coap_resource_t
|
||||
* structs. Use gcoap_register_listener() at application startup to pass in
|
||||
* these resources, wrapped in a gcoap_listener_t.
|
||||
*
|
||||
* gcoap itself defines a resource for `/.well-known/core` discovery, which
|
||||
* lists all of the registered paths.
|
||||
*
|
||||
* ### Creating a response ###
|
||||
*
|
||||
* An application resource includes a callback function, a coap_handler_t. After
|
||||
* reading the request, the callback must use one or two functions provided by
|
||||
* gcoap to format the response, as described below. The callback *must* read
|
||||
* the request thoroughly before calling the functions, because the response
|
||||
* buffer likely reuses the request buffer. See `examples/gcoap/gcoap_cli.c`
|
||||
* for a simple example of a callback.
|
||||
*
|
||||
* Here is the expected sequence for a callback function:
|
||||
*
|
||||
* Read request completely and parse request payload, if any. Use the
|
||||
* coap_pkt_t _payload_ and _payload_len_ attributes.
|
||||
*
|
||||
* If there is a payload, follow the three steps below.
|
||||
*
|
||||
* -# Call gcoap_resp_init() to initialize the response.
|
||||
* -# Write the request payload, starting at the updated _payload_ pointer
|
||||
* in the coap_pkt_t. If some error occurs, return a negative errno
|
||||
* code from the handler, and gcoap will send a server error (5.00).
|
||||
* -# Call gcoap_finish() to complete the PDU after writing the payload,
|
||||
* and return the result. gcoap will send the message.
|
||||
*
|
||||
* If no payload, call only gcoap_response() to write the full response.
|
||||
* Alternatively, you still can use gcoap_resp_init() and gcoap_finish(), as
|
||||
* described above. In fact, the gcoap_response() function is inline, and uses
|
||||
* those two functions.
|
||||
*
|
||||
* ## Client Operation ##
|
||||
*
|
||||
* gcoap uses RIOT's asynchronous messaging facility to send and receive
|
||||
* messages. So, client operation includes two phases: creating and sending a
|
||||
* request, and handling the response aynchronously in a client supplied
|
||||
* callback. See `examples/gcoap/gcoap_cli.c` for a simple example of sending
|
||||
* a request and reading the response.
|
||||
*
|
||||
* ### Creating a request ###
|
||||
*
|
||||
* Here is the expected sequence for preparing and sending a request:
|
||||
*
|
||||
* Allocate a buffer and a coap_pkt_t for the request.
|
||||
*
|
||||
* If there is a payload, follow the three steps below.
|
||||
*
|
||||
* -# Call gcoap_req_init() to initialize the request.
|
||||
* -# Write the request payload, starting at the updated _payload_ pointer
|
||||
* in the coap_pkt_t.
|
||||
* -# Call gcoap_finish(), which updates the packet for the payload.
|
||||
*
|
||||
* If no payload, call only gcoap_request() to write the full request.
|
||||
* Alternatively, you still can use gcoap_req_init() and gcoap_finish(),
|
||||
* as described above. The gcoap_request() function is inline, and uses those
|
||||
* two functions.
|
||||
*
|
||||
* Finally, call gcoap_req_send() with the destination host and port, as well
|
||||
* as a callback function for the host's response.
|
||||
*
|
||||
* ### Handling the response ###
|
||||
*
|
||||
* When gcoap receives the response to a request, it executes the callback from
|
||||
* the request. gcoap also executes the callback when a response is not
|
||||
* received within GCOAP_RESPONSE_TIMEOUT.
|
||||
*
|
||||
* Here is the expected sequence for handling a response in the callback.
|
||||
*
|
||||
* -# Test for a server response or timeout in the _req_state_ callback
|
||||
* parameter. See the GCOAP_MEMO... constants.
|
||||
* -# Test the response with coap_get_code_class() and coap_get_code_detail().
|
||||
* -# Test the response payload with the coap_pkt_t _payload_len_ and
|
||||
* _content_type_ attributes.
|
||||
* -# Read the payload, if any.
|
||||
*
|
||||
* ## Implementation Notes ##
|
||||
*
|
||||
* ### Building a packet ###
|
||||
*
|
||||
* The sequence and functions described above to build a request or response
|
||||
* is designed to provide a relatively simple API for the user.
|
||||
*
|
||||
* The structure of a CoAP PDU requires that options are placed between the
|
||||
* header and the payload. So, gcoap provides space in the buffer for them in
|
||||
* the request/response ...init() function, and then writes them during
|
||||
* gcoap_finish(). We trade some inefficiency/work in the buffer for
|
||||
* simplicity for the user.
|
||||
*
|
||||
* ### Waiting for a response ###
|
||||
*
|
||||
* We take advantage of RIOT's GNRC stack by using an xtimer to wait for a
|
||||
* response, so the gcoap thread does not block while waiting. The user is
|
||||
* notified via the same callback whether the message is received or the wait
|
||||
* times out. We track the response with an entry in the
|
||||
* `_coap_state.open_reqs` array.
|
||||
*
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief gcoap definition
|
||||
*
|
||||
* @author Ken Bannister <kb2ma@runbox.com>
|
||||
*/
|
||||
|
||||
#ifndef GCOAP_H_
|
||||
#define GCOAP_H_
|
||||
|
||||
#include "net/gnrc.h"
|
||||
#include "net/gnrc/ipv6.h"
|
||||
#include "net/gnrc/udp.h"
|
||||
#include "nanocoap.h"
|
||||
#include "xtimer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @brief Size for module message queue */
|
||||
#define GCOAP_MSG_QUEUE_SIZE (4)
|
||||
|
||||
/** @brief Server port; use RFC 7252 default if not defined */
|
||||
#ifndef GCOAP_PORT
|
||||
#define GCOAP_PORT (5683)
|
||||
#endif
|
||||
|
||||
/** @brief Size of the buffer used to build a CoAP request or response. */
|
||||
#define GCOAP_PDU_BUF_SIZE (128)
|
||||
|
||||
/**
|
||||
* @brief Size of the buffer used to write options, other than Uri-Path, in a
|
||||
* request.
|
||||
*
|
||||
* Accommodates Content-Format.
|
||||
*/
|
||||
#define GCOAP_REQ_OPTIONS_BUF (8)
|
||||
|
||||
/**
|
||||
* @brief Size of the buffer used to write options in a response.
|
||||
*
|
||||
* Accommodates Content-Format.
|
||||
*/
|
||||
#define GCOAP_RESP_OPTIONS_BUF (8)
|
||||
|
||||
/** @brief Maximum number of requests awaiting a response */
|
||||
#define GCOAP_REQ_WAITING_MAX (2)
|
||||
|
||||
/** @brief Maximum length in bytes for a token */
|
||||
#define GCOAP_TOKENLEN_MAX (8)
|
||||
|
||||
/** @brief Maximum length in bytes for a header, including the token */
|
||||
#define GCOAP_HEADER_MAXLEN (sizeof(coap_hdr_t) + GCOAP_TOKENLEN_MAX)
|
||||
|
||||
/** @brief Length in bytes for a token; use 2 if not defined */
|
||||
#ifndef GCOAP_TOKENLEN
|
||||
#define GCOAP_TOKENLEN (2)
|
||||
#endif
|
||||
|
||||
/** @brief Marks the boundary between header and payload */
|
||||
#define GCOAP_PAYLOAD_MARKER (0xFF)
|
||||
|
||||
/**
|
||||
* @name States for the memo used to track waiting for a response
|
||||
* @{
|
||||
*/
|
||||
#define GCOAP_MEMO_UNUSED (0) /**< This memo is unused */
|
||||
#define GCOAP_MEMO_WAIT (1) /**< Request sent; awaiting response */
|
||||
#define GCOAP_MEMO_RESP (2) /**< Got response */
|
||||
#define GCOAP_MEMO_TIMEOUT (3) /**< Timeout waiting for response */
|
||||
#define GCOAP_MEMO_ERR (4) /**< Error processing response packet */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief Default time to wait for a non-confirmable response, in usec
|
||||
*
|
||||
* Set to 0 to disable timeout.
|
||||
*/
|
||||
#define GCOAP_NON_TIMEOUT (5000000U)
|
||||
|
||||
/** @brief Identifies a gcoap-specific timeout IPC message */
|
||||
#define GCOAP_NETAPI_MSG_TYPE_TIMEOUT (0x1501)
|
||||
|
||||
/**
|
||||
* @brief A modular collection of resources for a server
|
||||
*/
|
||||
typedef struct gcoap_listener {
|
||||
coap_resource_t *resources; /**< First element in the array of resources;
|
||||
must order alphabetically */
|
||||
size_t resources_len; /**< Length of array */
|
||||
struct gcoap_listener *next; /**< Next listener in list */
|
||||
} gcoap_listener_t;
|
||||
|
||||
/**
|
||||
* @brief Handler function for a server response, including the state for the
|
||||
* originating request.
|
||||
*
|
||||
* If request timed out, the packet header is for the request.
|
||||
*/
|
||||
typedef void (*gcoap_resp_handler_t)(unsigned req_state, coap_pkt_t* pdu);
|
||||
|
||||
/**
|
||||
* @brief Memo to handle a response for a request
|
||||
*/
|
||||
typedef struct {
|
||||
unsigned state; /**< State of this memo, a GCOAP_MEMO... */
|
||||
uint8_t hdr_buf[GCOAP_HEADER_MAXLEN];
|
||||
/**< Stores a copy of the request header */
|
||||
gcoap_resp_handler_t resp_handler; /**< Callback for the response */
|
||||
xtimer_t response_timer; /**< Limits wait for response */
|
||||
msg_t timeout_msg; /**< For response timer */
|
||||
} gcoap_request_memo_t;
|
||||
|
||||
/**
|
||||
* @brief Container for the state of gcoap itself
|
||||
*/
|
||||
typedef struct {
|
||||
gnrc_netreg_entry_t netreg_port; /**< Registration for IP port */
|
||||
gcoap_listener_t *listeners; /**< List of registered listeners */
|
||||
gcoap_request_memo_t open_reqs[GCOAP_REQ_WAITING_MAX];
|
||||
/**< Storage for open requests; if first
|
||||
byte of an entry is zero, the entry
|
||||
is available */
|
||||
uint16_t last_message_id; /**< Last message ID used */
|
||||
} gcoap_state_t;
|
||||
|
||||
/**
|
||||
* @brief Initializes the gcoap thread and device.
|
||||
*
|
||||
* Must call once before first use.
|
||||
*
|
||||
* @return PID of the gcoap thread on success.
|
||||
* @return -EEXIST, if thread already has been created.
|
||||
* @return -EINVAL, if the IP port already is in use.
|
||||
*/
|
||||
kernel_pid_t gcoap_init(void);
|
||||
|
||||
/**
|
||||
* @brief Starts listening for resource paths.
|
||||
*
|
||||
* @param listener Listener containing the resources.
|
||||
*/
|
||||
void gcoap_register_listener(gcoap_listener_t *listener);
|
||||
|
||||
/**
|
||||
* @brief Initializes a CoAP request PDU on a buffer.
|
||||
*
|
||||
* @param[in] pdu Request metadata
|
||||
* @param[in] buf Buffer containing the PDU
|
||||
* @param[in] len Length of the buffer
|
||||
* @param[in] code Request code
|
||||
* @param[in] path Resource path
|
||||
*
|
||||
* @return 0 on success
|
||||
* @return < 0 on error
|
||||
*/
|
||||
int gcoap_req_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, unsigned code,
|
||||
char *path);
|
||||
|
||||
/**
|
||||
* @brief Finishes formatting a CoAP PDU after the payload has been written.
|
||||
*
|
||||
* Assumes the PDU has been initialized with gcoap_req_init() or
|
||||
* gcoap_resp_init().
|
||||
*
|
||||
* @param[in] pdu Request metadata
|
||||
* @param[in] payload_len Length of the payload, or 0 if none
|
||||
* @param[in] format Format code for the payload; use COAP_FORMAT_NONE if not
|
||||
* specified
|
||||
*
|
||||
* @return size of the PDU
|
||||
* @return < 0 on error
|
||||
*/
|
||||
ssize_t gcoap_finish(coap_pkt_t *pdu, size_t payload_len, unsigned format);
|
||||
|
||||
/**
|
||||
* @brief Writes a complete CoAP request PDU when there is not a payload.
|
||||
*
|
||||
* @param[in] pdu Request metadata
|
||||
* @param[in] buf Buffer containing the PDU
|
||||
* @param[in] len Length of the buffer
|
||||
* @param[in] code Request code
|
||||
* @param[in] path Resource path
|
||||
*
|
||||
* @return size of the PDU within the buffer
|
||||
* @return < 0 on error
|
||||
*/
|
||||
static inline ssize_t gcoap_request(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
||||
unsigned code,
|
||||
char *path)
|
||||
{
|
||||
return (gcoap_req_init(pdu, buf, len, code, path) == 0)
|
||||
? gcoap_finish(pdu, 0, COAP_FORMAT_NONE)
|
||||
: -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends a buffer containing a CoAP request to the provided host/port.
|
||||
*
|
||||
* @param[in] buf Buffer containing the PDU
|
||||
* @param[in] len Length of the buffer
|
||||
* @param[in] addr Destination for the packet
|
||||
* @param[in] port Port at the destination
|
||||
* @param[in] resp_handler Callback when response received
|
||||
*
|
||||
* @return length of the packet
|
||||
* @return 0 if cannot send
|
||||
*/
|
||||
size_t gcoap_req_send(uint8_t *buf, size_t len, ipv6_addr_t *addr, uint16_t port,
|
||||
gcoap_resp_handler_t resp_handler);
|
||||
|
||||
/**
|
||||
* @brief Initializes a CoAP response packet on a buffer.
|
||||
*
|
||||
* Initializes payload location within the buffer based on packet setup.
|
||||
*
|
||||
* @param[in] pdu Response metadata
|
||||
* @param[in] buf Buffer containing the PDU
|
||||
* @param[in] len Length of the buffer
|
||||
* @param[in] code Response code
|
||||
*
|
||||
* @return 0 on success
|
||||
* @return < 0 on error
|
||||
*/
|
||||
int gcoap_resp_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, unsigned code);
|
||||
|
||||
/**
|
||||
* @brief Writes a complete CoAP response PDU when there is no payload.
|
||||
*
|
||||
* @param[in] pdu Response metadata
|
||||
* @param[in] buf Buffer containing the PDU
|
||||
* @param[in] len Length of the buffer
|
||||
* @param[in] code Response code
|
||||
*
|
||||
* @return size of the PDU within the buffer
|
||||
* @return < 0 on error
|
||||
*/
|
||||
static inline ssize_t gcoap_response(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
||||
unsigned code)
|
||||
{
|
||||
return (gcoap_resp_init(pdu, buf, len, code) == 0)
|
||||
? gcoap_finish(pdu, 0, COAP_FORMAT_NONE)
|
||||
: -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Provides important operational statistics.
|
||||
*
|
||||
* Useful for monitoring.
|
||||
*
|
||||
* @param[out] open_reqs Count of unanswered requests
|
||||
*/
|
||||
void gcoap_op_state(uint8_t *open_reqs);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* GCOAP_H_ */
|
||||
/** @} */
|
@ -130,5 +130,8 @@ endif
|
||||
ifneq (,$(filter gnrc_tftp,$(USEMODULE)))
|
||||
DIRS += application_layer/tftp
|
||||
endif
|
||||
ifneq (,$(filter gcoap,$(USEMODULE)))
|
||||
DIRS += application_layer/coap
|
||||
endif
|
||||
|
||||
include $(RIOTBASE)/Makefile.base
|
||||
|
3
sys/net/gnrc/application_layer/coap/Makefile
Normal file
3
sys/net/gnrc/application_layer/coap/Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
MODULE = gcoap
|
||||
|
||||
include $(RIOTBASE)/Makefile.base
|
598
sys/net/gnrc/application_layer/coap/gcoap.c
Normal file
598
sys/net/gnrc/application_layer/coap/gcoap.c
Normal file
@ -0,0 +1,598 @@
|
||||
/*
|
||||
* Copyright (c) 2015-2016 Ken Bannister. All rights reserved.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup net_gnrc_coap
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief GNRC's implementation of CoAP protocol
|
||||
*
|
||||
* Runs a thread (_pid) to manage request/response messaging.
|
||||
*
|
||||
* @author Ken Bannister <kb2ma@runbox.com>
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include "net/gnrc/coap.h"
|
||||
#include "random.h"
|
||||
#include "thread.h"
|
||||
|
||||
#define ENABLE_DEBUG (0)
|
||||
#include "debug.h"
|
||||
|
||||
/** @brief Stack size for module thread */
|
||||
#if ENABLE_DEBUG
|
||||
#define GCOAP_STACK_SIZE (THREAD_STACKSIZE_DEFAULT + THREAD_EXTRA_STACKSIZE_PRINTF)
|
||||
#else
|
||||
#define GCOAP_STACK_SIZE (THREAD_STACKSIZE_DEFAULT)
|
||||
#endif
|
||||
|
||||
/* Internal functions */
|
||||
static void *_event_loop(void *arg);
|
||||
static int _register_port(gnrc_netreg_entry_t *netreg_port, uint16_t port);
|
||||
static void _receive(gnrc_pktsnip_t *pkt, ipv6_addr_t *src, uint16_t port);
|
||||
static size_t _send(gnrc_pktsnip_t *coap_snip, ipv6_addr_t *addr, uint16_t port);
|
||||
static ssize_t _well_known_core_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len);
|
||||
static ssize_t _write_options(coap_pkt_t *pdu, uint8_t *buf, size_t len);
|
||||
static size_t _handle_req(coap_pkt_t *pdu, uint8_t *buf, size_t len);
|
||||
static ssize_t _finish_pdu(coap_pkt_t *pdu, uint8_t *buf, size_t len);
|
||||
static size_t _send_buf( uint8_t *buf, size_t len, ipv6_addr_t *src, uint16_t port);
|
||||
static void _expire_request(gcoap_request_memo_t *memo);
|
||||
static void _find_req_memo(gcoap_request_memo_t **memo_ptr, coap_pkt_t *pdu,
|
||||
uint8_t *buf, size_t len);
|
||||
|
||||
/* Internal variables */
|
||||
const coap_resource_t _default_resources[] = {
|
||||
{ "/.well-known/core", COAP_GET, _well_known_core_handler },
|
||||
};
|
||||
|
||||
static gcoap_listener_t _default_listener = {
|
||||
(coap_resource_t *)&_default_resources[0],
|
||||
sizeof(_default_resources) / sizeof(_default_resources[0]),
|
||||
NULL
|
||||
};
|
||||
|
||||
static gcoap_state_t _coap_state = {
|
||||
.netreg_port = {NULL, 0, KERNEL_PID_UNDEF},
|
||||
.listeners = &_default_listener,
|
||||
};
|
||||
|
||||
static kernel_pid_t _pid = KERNEL_PID_UNDEF;
|
||||
static char _msg_stack[GCOAP_STACK_SIZE];
|
||||
|
||||
|
||||
/* Event/Message loop for gcoap _pid thread. */
|
||||
static void *_event_loop(void *arg)
|
||||
{
|
||||
msg_t msg_rcvd, msg_queue[GCOAP_MSG_QUEUE_SIZE];
|
||||
gnrc_pktsnip_t *pkt, *udp_snip, *ipv6_snip;
|
||||
ipv6_addr_t *src_addr;
|
||||
uint16_t port;
|
||||
|
||||
(void)arg;
|
||||
msg_init_queue(msg_queue, GCOAP_MSG_QUEUE_SIZE);
|
||||
|
||||
while (1) {
|
||||
msg_receive(&msg_rcvd);
|
||||
|
||||
switch (msg_rcvd.type) {
|
||||
case GNRC_NETAPI_MSG_TYPE_RCV:
|
||||
/* find client from UDP destination port */
|
||||
DEBUG("coap: GNRC_NETAPI_MSG_TYPE_RCV\n");
|
||||
pkt = (gnrc_pktsnip_t *)msg_rcvd.content.ptr;
|
||||
if (pkt->type != GNRC_NETTYPE_UNDEF) {
|
||||
gnrc_pktbuf_release(pkt);
|
||||
break;
|
||||
}
|
||||
udp_snip = pkt->next;
|
||||
if (udp_snip->type != GNRC_NETTYPE_UDP) {
|
||||
gnrc_pktbuf_release(pkt);
|
||||
break;
|
||||
}
|
||||
|
||||
/* read source port and address */
|
||||
port = byteorder_ntohs(((udp_hdr_t *)udp_snip->data)->src_port);
|
||||
|
||||
LL_SEARCH_SCALAR(udp_snip, ipv6_snip, type, GNRC_NETTYPE_IPV6);
|
||||
assert(ipv6_snip != NULL);
|
||||
src_addr = &((ipv6_hdr_t *)ipv6_snip->data)->src;
|
||||
|
||||
_receive(pkt, src_addr, port);
|
||||
break;
|
||||
|
||||
case GCOAP_NETAPI_MSG_TYPE_TIMEOUT:
|
||||
_expire_request((gcoap_request_memo_t *)msg_rcvd.content.ptr);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Handles incoming network IPC message. */
|
||||
static void _receive(gnrc_pktsnip_t *pkt, ipv6_addr_t *src, uint16_t port)
|
||||
{
|
||||
coap_pkt_t pdu;
|
||||
uint8_t buf[GCOAP_PDU_BUF_SIZE];
|
||||
size_t pdu_len = 0;
|
||||
gcoap_request_memo_t *memo = NULL;
|
||||
|
||||
/* If too big, handle below based on request vs. response */
|
||||
size_t pkt_size = (pkt->size > sizeof(buf))
|
||||
? sizeof(buf) : pkt->size;
|
||||
|
||||
/* Copy request into temporary buffer, and parse it as CoAP. */
|
||||
memcpy(buf, pkt->data, pkt_size);
|
||||
|
||||
int result = coap_parse(&pdu, buf, pkt_size);
|
||||
if (result < 0) {
|
||||
DEBUG("gcoap: parse failure: %d\n", result);
|
||||
/* If a response, can't clear memo, but it will timeout later. */
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* incoming request */
|
||||
if (coap_get_code_class(&pdu) == COAP_CLASS_REQ) {
|
||||
if (pkt->size > sizeof(buf)) {
|
||||
DEBUG("gcoap: request too big: %u\n", pkt->size);
|
||||
pdu_len = gcoap_response(&pdu, buf, sizeof(buf),
|
||||
COAP_CODE_REQUEST_ENTITY_TOO_LARGE);
|
||||
} else {
|
||||
pdu_len = _handle_req(&pdu, buf, sizeof(buf));
|
||||
}
|
||||
if (pdu_len > 0) {
|
||||
_send_buf(buf, pdu_len, src, port);
|
||||
}
|
||||
}
|
||||
/* incoming response */
|
||||
else {
|
||||
_find_req_memo(&memo, &pdu, buf, sizeof(buf));
|
||||
if (memo) {
|
||||
xtimer_remove(&memo->response_timer);
|
||||
if (pkt->size > sizeof(buf)) {
|
||||
memo->state = GCOAP_MEMO_ERR;
|
||||
DEBUG("gcoap: response too big: %u\n", pkt->size);
|
||||
}
|
||||
memo->resp_handler(memo->state, &pdu);
|
||||
memo->state = GCOAP_MEMO_UNUSED;
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
gnrc_pktbuf_release(pkt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Main request handler: generates response PDU in the provided buffer.
|
||||
*
|
||||
* Caller must finish the PDU and send it.
|
||||
*/
|
||||
static size_t _handle_req(coap_pkt_t *pdu, uint8_t *buf, size_t len)
|
||||
{
|
||||
unsigned method_flag = coap_method2flag(coap_get_code_detail(pdu));
|
||||
|
||||
/* Find path for CoAP msg among listener resources and execute callback. */
|
||||
gcoap_listener_t *listener = _coap_state.listeners;
|
||||
while (listener) {
|
||||
coap_resource_t *resource = listener->resources;
|
||||
for (size_t i = 0; i < listener->resources_len; i++) {
|
||||
if (i) {
|
||||
resource++;
|
||||
}
|
||||
if (! (resource->methods & method_flag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int res = strcmp((char *)&pdu->url[0], resource->path);
|
||||
if (res > 0) {
|
||||
continue;
|
||||
}
|
||||
else if (res < 0) {
|
||||
/* resources expected in alphabetical order */
|
||||
break;
|
||||
}
|
||||
else {
|
||||
ssize_t pdu_len = resource->handler(pdu, buf, len);
|
||||
if (pdu_len < 0) {
|
||||
pdu_len = gcoap_response(pdu, buf, len,
|
||||
COAP_CODE_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
return pdu_len;
|
||||
}
|
||||
}
|
||||
listener = listener->next;
|
||||
}
|
||||
/* resource not found */
|
||||
return gcoap_response(pdu, buf, len, COAP_CODE_PATH_NOT_FOUND);
|
||||
}
|
||||
|
||||
/*
|
||||
* Finishes handling a PDU -- write options and reposition payload.
|
||||
*
|
||||
* Returns the size of the PDU within the buffer, or < 0 on error.
|
||||
*/
|
||||
static ssize_t _finish_pdu(coap_pkt_t *pdu, uint8_t *buf, size_t len)
|
||||
{
|
||||
ssize_t hdr_len = _write_options(pdu, buf, len);
|
||||
DEBUG("gcoap: header length: %u\n", hdr_len);
|
||||
|
||||
if (hdr_len > 0) {
|
||||
/* move payload over unused space after options */
|
||||
if (pdu->payload_len) {
|
||||
memmove(buf + hdr_len, pdu->payload, pdu->payload_len);
|
||||
}
|
||||
|
||||
return hdr_len + pdu->payload_len;
|
||||
}
|
||||
else {
|
||||
return -1; /* generic failure code */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Finds the memo for an outstanding request within the _coap_state.open_reqs
|
||||
* array. Matches on token.
|
||||
*
|
||||
* src_pdu Source for the match token
|
||||
*/
|
||||
static void _find_req_memo(gcoap_request_memo_t **memo_ptr, coap_pkt_t *src_pdu,
|
||||
uint8_t *buf, size_t len)
|
||||
{
|
||||
gcoap_request_memo_t *memo;
|
||||
coap_pkt_t memo_pdu = { .token = NULL };
|
||||
(void) buf;
|
||||
(void) len;
|
||||
|
||||
for (int i = 0; i < GCOAP_REQ_WAITING_MAX; i++) {
|
||||
if (_coap_state.open_reqs[i].state == GCOAP_MEMO_UNUSED)
|
||||
continue;
|
||||
|
||||
/* setup memo PDU from memo header */
|
||||
memo = &_coap_state.open_reqs[i];
|
||||
coap_hdr_t *memo_hdr = (coap_hdr_t *) &memo->hdr_buf[0];
|
||||
memo_pdu.hdr = memo_hdr;
|
||||
if (coap_get_token_len(&memo_pdu)) {
|
||||
memo_pdu.token = &memo_hdr->data[0];
|
||||
}
|
||||
/* match on token */
|
||||
if (coap_get_token_len(src_pdu) == coap_get_token_len(&memo_pdu)) {
|
||||
uint8_t *src_byte = src_pdu->token;
|
||||
uint8_t *memo_byte = memo_pdu.token;
|
||||
size_t j;
|
||||
for (j = 0; j < coap_get_token_len(src_pdu); j++) {
|
||||
if (*src_byte++ != *memo_byte++) {
|
||||
break; /* token mismatch */
|
||||
}
|
||||
}
|
||||
if (j == coap_get_token_len(src_pdu)) {
|
||||
*memo_ptr = memo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Calls handler callback on receipt of a timeout message. */
|
||||
static void _expire_request(gcoap_request_memo_t *memo)
|
||||
{
|
||||
coap_pkt_t req;
|
||||
|
||||
DEBUG("coap: received timeout message\n");
|
||||
if (memo->state == GCOAP_MEMO_WAIT) {
|
||||
memo->state = GCOAP_MEMO_TIMEOUT;
|
||||
/* Pass response to handler */
|
||||
if (memo->resp_handler) {
|
||||
req.hdr = (coap_hdr_t *)&memo->hdr_buf[0]; /* for reference */
|
||||
memo->resp_handler(memo->state, &req);
|
||||
}
|
||||
memo->state = GCOAP_MEMO_UNUSED;
|
||||
}
|
||||
else {
|
||||
/* Response already handled; timeout must have fired while response */
|
||||
/* was in queue. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Registers receive/send port with GNRC registry. */
|
||||
static int _register_port(gnrc_netreg_entry_t *netreg_port, uint16_t port)
|
||||
{
|
||||
if (!gnrc_netreg_lookup(GNRC_NETTYPE_UDP, port)) {
|
||||
netreg_port->demux_ctx = port;
|
||||
netreg_port->target.pid = _pid;
|
||||
gnrc_netreg_register(GNRC_NETTYPE_UDP, netreg_port);
|
||||
DEBUG("coap: registered UDP port %" PRIu32 "\n",
|
||||
netreg_port->demux_ctx);
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends a CoAP message to the provided host/port.
|
||||
*
|
||||
* @return Length of the packet
|
||||
* @return 0 if cannot send
|
||||
*/
|
||||
static size_t _send(gnrc_pktsnip_t *coap_snip, ipv6_addr_t *addr, uint16_t port)
|
||||
{
|
||||
gnrc_pktsnip_t *udp, *ip;
|
||||
size_t pktlen;
|
||||
|
||||
/* allocate UDP header */
|
||||
udp = gnrc_udp_hdr_build(coap_snip, (uint16_t)_coap_state.netreg_port.demux_ctx,
|
||||
port);
|
||||
if (udp == NULL) {
|
||||
DEBUG("gcoap: unable to allocate UDP header\n");
|
||||
gnrc_pktbuf_release(coap_snip);
|
||||
return 0;
|
||||
}
|
||||
/* allocate IPv6 header */
|
||||
ip = gnrc_ipv6_hdr_build(udp, NULL, addr);
|
||||
if (ip == NULL) {
|
||||
DEBUG("gcoap: unable to allocate IPv6 header\n");
|
||||
gnrc_pktbuf_release(udp);
|
||||
return 0;
|
||||
}
|
||||
pktlen = gnrc_pkt_len(ip); /* count length now; snips deallocated after send */
|
||||
|
||||
/* send message */
|
||||
if (!gnrc_netapi_dispatch_send(GNRC_NETTYPE_UDP, GNRC_NETREG_DEMUX_CTX_ALL, ip)) {
|
||||
DEBUG("gcoap: unable to locate UDP thread\n");
|
||||
gnrc_pktbuf_release(ip);
|
||||
return 0;
|
||||
}
|
||||
return pktlen;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copies the request/response buffer to a pktsnip and sends it.
|
||||
*
|
||||
* @return Length of the packet
|
||||
* @return 0 if cannot send
|
||||
*/
|
||||
static size_t _send_buf(uint8_t *buf, size_t len, ipv6_addr_t *src, uint16_t port)
|
||||
{
|
||||
gnrc_pktsnip_t *snip;
|
||||
|
||||
snip = gnrc_pktbuf_add(NULL, NULL, len, GNRC_NETTYPE_UNDEF);
|
||||
if (!snip) {
|
||||
return 0;
|
||||
}
|
||||
memcpy(snip->data, buf, len);
|
||||
|
||||
return _send(snip, src, port);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handler for /.well-known/core. Lists registered handlers, except for
|
||||
* /.well-known/core itself.
|
||||
*/
|
||||
static ssize_t _well_known_core_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len)
|
||||
{
|
||||
/* write header */
|
||||
gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT);
|
||||
|
||||
/* skip the first listener, gcoap itself */
|
||||
gcoap_listener_t *listener = _coap_state.listeners->next;
|
||||
|
||||
/* write payload */
|
||||
uint8_t *bufpos = pdu->payload;
|
||||
|
||||
while (listener) {
|
||||
coap_resource_t *resource = listener->resources;
|
||||
for (size_t i = 0; i < listener->resources_len; i++) {
|
||||
/* Don't overwrite buffer if paths are too long. */
|
||||
if (bufpos + strlen(resource->path) + 3 > buf + len) {
|
||||
break;
|
||||
}
|
||||
if (i) {
|
||||
*bufpos++ = ',';
|
||||
resource++;
|
||||
}
|
||||
*bufpos++ = '<';
|
||||
unsigned url_len = strlen(resource->path);
|
||||
memcpy(bufpos, resource->path, url_len);
|
||||
bufpos += url_len;
|
||||
*bufpos++ = '>';
|
||||
}
|
||||
listener = listener->next;
|
||||
}
|
||||
|
||||
/* response content */
|
||||
return gcoap_finish(pdu, bufpos - pdu->payload, COAP_FORMAT_LINK);
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates CoAP options and sets payload marker, if any.
|
||||
*
|
||||
* Returns length of header + options, or -EINVAL on illegal path.
|
||||
*/
|
||||
static ssize_t _write_options(coap_pkt_t *pdu, uint8_t *buf, size_t len)
|
||||
{
|
||||
uint8_t last_optnum = 0;
|
||||
(void)len;
|
||||
|
||||
uint8_t *bufpos = buf + coap_get_total_hdr_len(pdu); /* position for write */
|
||||
|
||||
/* Uri-Path for request */
|
||||
if (coap_get_code_class(pdu) == COAP_CLASS_REQ) {
|
||||
size_t url_len = strlen((char *)pdu->url);
|
||||
if (url_len) {
|
||||
if (pdu->url[0] != '/') {
|
||||
return -EINVAL;
|
||||
}
|
||||
bufpos += coap_put_option_url(bufpos, last_optnum, (char *)&pdu->url[0]);
|
||||
last_optnum = COAP_OPT_URI_PATH;
|
||||
}
|
||||
}
|
||||
|
||||
/* Content-Format */
|
||||
if (pdu->content_type != COAP_FORMAT_NONE) {
|
||||
bufpos += coap_put_option_ct(bufpos, last_optnum, pdu->content_type);
|
||||
/* uncomment when add an option after Content-Format */
|
||||
/* last_optnum = COAP_OPT_CONTENT_FORMAT; */
|
||||
}
|
||||
|
||||
/* write payload marker */
|
||||
if (pdu->payload_len) {
|
||||
*bufpos++ = GCOAP_PAYLOAD_MARKER;
|
||||
}
|
||||
return bufpos - buf;
|
||||
}
|
||||
|
||||
/*
|
||||
* gcoap interface functions
|
||||
*/
|
||||
|
||||
kernel_pid_t gcoap_init(void)
|
||||
{
|
||||
if (_pid != KERNEL_PID_UNDEF) {
|
||||
return -EEXIST;
|
||||
}
|
||||
_pid = thread_create(_msg_stack, sizeof(_msg_stack), THREAD_PRIORITY_MAIN - 1,
|
||||
THREAD_CREATE_STACKTEST, _event_loop, NULL, "coap");
|
||||
|
||||
/* must establish pid first */
|
||||
if (_register_port(&_coap_state.netreg_port, GCOAP_PORT) < 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
/* Blank list of open requests so we know if an entry is available. */
|
||||
memset(&_coap_state.open_reqs[0], 0, sizeof(_coap_state.open_reqs));
|
||||
/* randomize initial value */
|
||||
_coap_state.last_message_id = random_uint32() & 0xFFFF;
|
||||
|
||||
return _pid;
|
||||
}
|
||||
|
||||
void gcoap_register_listener(gcoap_listener_t *listener)
|
||||
{
|
||||
/* Add the listener to the end of the linked list. */
|
||||
gcoap_listener_t *_last = _coap_state.listeners;
|
||||
while (_last->next)
|
||||
_last = _last->next;
|
||||
|
||||
listener->next = NULL;
|
||||
_last->next = listener;
|
||||
}
|
||||
|
||||
int gcoap_req_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, unsigned code,
|
||||
char *path) {
|
||||
uint8_t token[GCOAP_TOKENLEN];
|
||||
ssize_t hdrlen;
|
||||
(void)len;
|
||||
|
||||
pdu->hdr = (coap_hdr_t *)buf;
|
||||
memset(pdu->url, 0, NANOCOAP_URL_MAX);
|
||||
|
||||
/* generate token */
|
||||
for (size_t i = 0; i < GCOAP_TOKENLEN; i += 4) {
|
||||
uint32_t rand = random_uint32();
|
||||
memcpy(&token[i],
|
||||
&rand,
|
||||
(GCOAP_TOKENLEN - i >= 4) ? 4 : GCOAP_TOKENLEN - i);
|
||||
}
|
||||
hdrlen = coap_build_hdr(pdu->hdr, COAP_TYPE_NON, &token[0], GCOAP_TOKENLEN,
|
||||
code,
|
||||
++_coap_state.last_message_id);
|
||||
|
||||
if (hdrlen > 0) {
|
||||
/* Reserve some space between the header and payload to write options later */
|
||||
pdu->payload = buf + coap_get_total_hdr_len(pdu) + strlen(path)
|
||||
+ GCOAP_REQ_OPTIONS_BUF;
|
||||
/* Payload length really zero at this point, but we set this to the available
|
||||
* length in the buffer. Allows us to reconstruct buffer length later. */
|
||||
pdu->payload_len = len - (pdu->payload - buf);
|
||||
pdu->content_type = COAP_FORMAT_NONE;
|
||||
|
||||
memcpy(&pdu->url[0], path, strlen(path));
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
/* reason for negative hdrlen is not defined, so we also are vague */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t gcoap_finish(coap_pkt_t *pdu, size_t payload_len, unsigned format)
|
||||
{
|
||||
/* reconstruct full PDU buffer length */
|
||||
size_t len = pdu->payload_len + (pdu->payload - (uint8_t *)pdu->hdr);
|
||||
|
||||
pdu->content_type = format;
|
||||
pdu->payload_len = payload_len;
|
||||
return _finish_pdu(pdu, (uint8_t *)pdu->hdr, len);
|
||||
}
|
||||
|
||||
size_t gcoap_req_send(uint8_t *buf, size_t len, ipv6_addr_t *addr, uint16_t port,
|
||||
gcoap_resp_handler_t resp_handler)
|
||||
{
|
||||
gcoap_request_memo_t *memo = NULL;
|
||||
assert(resp_handler != NULL);
|
||||
|
||||
/* Find empty slot in list of open requests. */
|
||||
for (int i = 0; i < GCOAP_REQ_WAITING_MAX; i++) {
|
||||
if (_coap_state.open_reqs[i].state == GCOAP_MEMO_UNUSED) {
|
||||
memo = &_coap_state.open_reqs[i];
|
||||
memo->state = GCOAP_MEMO_WAIT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (memo) {
|
||||
memcpy(&memo->hdr_buf[0], buf, GCOAP_HEADER_MAXLEN);
|
||||
memo->resp_handler = resp_handler;
|
||||
|
||||
size_t res = _send_buf(buf, len, addr, port);
|
||||
if (res && GCOAP_NON_TIMEOUT) {
|
||||
/* start response wait timer */
|
||||
memo->timeout_msg.type = GCOAP_NETAPI_MSG_TYPE_TIMEOUT;
|
||||
memo->timeout_msg.content.ptr = (char *)memo;
|
||||
xtimer_set_msg(&memo->response_timer, GCOAP_NON_TIMEOUT,
|
||||
&memo->timeout_msg, _pid);
|
||||
}
|
||||
else if (!res) {
|
||||
memo->state = GCOAP_MEMO_UNUSED;
|
||||
}
|
||||
return res;
|
||||
} else {
|
||||
DEBUG("gcoap: dropping request; no space for response tracking\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int gcoap_resp_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, unsigned code)
|
||||
{
|
||||
/* Assume NON type request, so response type is the same. */
|
||||
coap_hdr_set_code(pdu->hdr, code);
|
||||
/* Create message ID since NON? */
|
||||
|
||||
/* Reserve some space between the header and payload to write options later */
|
||||
pdu->payload = buf + coap_get_total_hdr_len(pdu) + GCOAP_RESP_OPTIONS_BUF;
|
||||
/* Payload length really zero at this point, but we set this to the available
|
||||
* length in the buffer. Allows us to reconstruct buffer length later. */
|
||||
pdu->payload_len = len - (pdu->payload - buf);
|
||||
pdu->content_type = COAP_FORMAT_NONE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gcoap_op_state(uint8_t *open_reqs)
|
||||
{
|
||||
uint8_t count = 0;
|
||||
for (int i = 0; i < GCOAP_REQ_WAITING_MAX; i++) {
|
||||
if (_coap_state.open_reqs[i].state != GCOAP_MEMO_UNUSED) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
*open_reqs = count;
|
||||
}
|
||||
|
||||
/** @} */
|
1
tests/unittests/tests-gcoap/Makefile
Normal file
1
tests/unittests/tests-gcoap/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
9
tests/unittests/tests-gcoap/Makefile.include
Normal file
9
tests/unittests/tests-gcoap/Makefile.include
Normal file
@ -0,0 +1,9 @@
|
||||
USEPKG += nanocoap
|
||||
# Required by nanocoap to compile nanocoap_sock.
|
||||
USEMODULE += gnrc_sock_udp
|
||||
|
||||
# Specify the mandatory networking modules
|
||||
USEMODULE += gcoap
|
||||
USEMODULE += gnrc_ipv6
|
||||
|
||||
USEMODULE += random
|
177
tests/unittests/tests-gcoap/tests-gcoap.c
Normal file
177
tests/unittests/tests-gcoap/tests-gcoap.c
Normal file
@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Ken Bannister. All rights reserved.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "embUnit.h"
|
||||
|
||||
#include "net/gnrc/coap.h"
|
||||
|
||||
#include "unittests-constants.h"
|
||||
#include "tests-gcoap.h"
|
||||
|
||||
/*
|
||||
* Client GET request success case. Test request generation.
|
||||
* Request /time resource from libcoap example
|
||||
* Includes 2-byte token
|
||||
*/
|
||||
static void test_gcoap__client_get_req(void)
|
||||
{
|
||||
uint8_t buf[GCOAP_PDU_BUF_SIZE];
|
||||
coap_pkt_t pdu;
|
||||
size_t len;
|
||||
char path[] = "/time";
|
||||
|
||||
uint8_t pdu_data[] = {
|
||||
0x52, 0x01, 0xe6, 0x02, 0x9b, 0xce, 0xb4, 0x74,
|
||||
0x69, 0x6d, 0x65
|
||||
};
|
||||
|
||||
len = gcoap_request(&pdu, &buf[0], GCOAP_PDU_BUF_SIZE, COAP_METHOD_GET,
|
||||
&path[0]);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(COAP_METHOD_GET, coap_get_code(&pdu));
|
||||
TEST_ASSERT_EQUAL_INT(GCOAP_TOKENLEN, coap_get_token_len(&pdu));
|
||||
TEST_ASSERT_EQUAL_INT(4 + GCOAP_TOKENLEN, coap_get_total_hdr_len(&pdu));
|
||||
TEST_ASSERT_EQUAL_INT(COAP_TYPE_NON, coap_get_type(&pdu));
|
||||
TEST_ASSERT_EQUAL_STRING(&path[0], (char *)&pdu.url[0]);
|
||||
TEST_ASSERT_EQUAL_INT(0, pdu.payload_len);
|
||||
TEST_ASSERT_EQUAL_INT(sizeof(pdu_data), len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Client GET response success case. Test parsing response.
|
||||
* Response for /time resource from libcoap example
|
||||
*/
|
||||
static void test_gcoap__client_get_resp(void)
|
||||
{
|
||||
uint8_t buf[GCOAP_PDU_BUF_SIZE];
|
||||
coap_pkt_t pdu;
|
||||
int res;
|
||||
char exp_payload[] = "Oct 22 10:46:48";
|
||||
|
||||
uint8_t pdu_data[] = {
|
||||
0x52, 0x45, 0xe6, 0x02, 0x9b, 0xce, 0xc0, 0x21,
|
||||
0x01, 0xff, 0x4f, 0x63, 0x74, 0x20, 0x32, 0x32,
|
||||
0x20, 0x31, 0x30, 0x3a, 0x34, 0x36, 0x3a, 0x34,
|
||||
0x38
|
||||
};
|
||||
memcpy(buf, pdu_data, sizeof(pdu_data));
|
||||
|
||||
res = coap_parse(&pdu, &buf[0], sizeof(pdu_data));
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, res);
|
||||
TEST_ASSERT_EQUAL_INT(COAP_CLASS_SUCCESS, coap_get_code_class(&pdu));
|
||||
TEST_ASSERT_EQUAL_INT(GCOAP_TOKENLEN, coap_get_token_len(&pdu));
|
||||
TEST_ASSERT_EQUAL_INT(4 + GCOAP_TOKENLEN, coap_get_total_hdr_len(&pdu));
|
||||
TEST_ASSERT_EQUAL_INT(COAP_TYPE_NON, coap_get_type(&pdu));
|
||||
TEST_ASSERT_EQUAL_INT(strlen(exp_payload), pdu.payload_len);
|
||||
|
||||
for (size_t i = 0; i < strlen(exp_payload); i++) {
|
||||
TEST_ASSERT_EQUAL_INT(exp_payload[i], pdu.payload[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper for server_get tests below.
|
||||
* Request from libcoap example for gcoap_cli /cli/stats resource
|
||||
* Include 2-byte token and Uri-Host option.
|
||||
*/
|
||||
static int _read_cli_stats_req(coap_pkt_t *pdu, uint8_t *buf)
|
||||
{
|
||||
uint8_t pdu_data[] = {
|
||||
0x52, 0x01, 0x20, 0xb6, 0x35, 0x61, 0x3d, 0x10,
|
||||
0x66, 0x65, 0x38, 0x30, 0x3a, 0x3a, 0x38, 0x63,
|
||||
0x32, 0x3a, 0x31, 0x33, 0x66, 0x66, 0x3a, 0x66,
|
||||
0x65, 0x63, 0x30, 0x3a, 0x35, 0x65, 0x31, 0x32,
|
||||
0x25, 0x74, 0x61, 0x70, 0x30, 0x83, 0x63, 0x6c,
|
||||
0x69, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73
|
||||
};
|
||||
memcpy(buf, pdu_data, sizeof(pdu_data));
|
||||
|
||||
return coap_parse(pdu, buf, sizeof(pdu_data));
|
||||
}
|
||||
|
||||
/* Server GET request success case. Validate request example. */
|
||||
static void test_gcoap__server_get_req(void)
|
||||
{
|
||||
uint8_t buf[GCOAP_PDU_BUF_SIZE];
|
||||
coap_pkt_t pdu;
|
||||
|
||||
int res = _read_cli_stats_req(&pdu, &buf[0]);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, res);
|
||||
TEST_ASSERT_EQUAL_INT(COAP_METHOD_GET, coap_get_code(&pdu));
|
||||
TEST_ASSERT_EQUAL_INT(2, coap_get_token_len(&pdu));
|
||||
TEST_ASSERT_EQUAL_INT(4 + 2, coap_get_total_hdr_len(&pdu));
|
||||
TEST_ASSERT_EQUAL_INT(COAP_TYPE_NON, coap_get_type(&pdu));
|
||||
TEST_ASSERT_EQUAL_INT(0, pdu.payload_len);
|
||||
TEST_ASSERT_EQUAL_STRING("/cli/stats", (char *) &pdu.url[0]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Server GET response success case. Test writing response.
|
||||
* Response for libcoap example for gcoap_cli /cli/stats resource
|
||||
*/
|
||||
static void test_gcoap__server_get_resp(void)
|
||||
{
|
||||
uint8_t buf[GCOAP_PDU_BUF_SIZE];
|
||||
coap_pkt_t pdu;
|
||||
|
||||
/* read request */
|
||||
_read_cli_stats_req(&pdu, &buf[0]);
|
||||
|
||||
/* generate response */
|
||||
gcoap_resp_init(&pdu, &buf[0], sizeof(buf), COAP_CODE_CONTENT);
|
||||
char resp_payload[] = "2";
|
||||
memcpy(&pdu.payload[0], &resp_payload[0], strlen(resp_payload));
|
||||
ssize_t res = gcoap_finish(&pdu, strlen(resp_payload), COAP_FORMAT_TEXT);
|
||||
|
||||
uint8_t resp_data[] = {
|
||||
0x52, 0x45, 0x20, 0xb6, 0x35, 0x61, 0xc0, 0xff,
|
||||
0x32
|
||||
};
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(COAP_CLASS_SUCCESS, coap_get_code_class(&pdu));
|
||||
TEST_ASSERT_EQUAL_INT(2, coap_get_token_len(&pdu));
|
||||
TEST_ASSERT_EQUAL_INT(4 + 2, coap_get_total_hdr_len(&pdu));
|
||||
TEST_ASSERT_EQUAL_INT(COAP_TYPE_NON, coap_get_type(&pdu));
|
||||
TEST_ASSERT_EQUAL_INT(strlen(resp_payload), pdu.payload_len);
|
||||
TEST_ASSERT_EQUAL_INT(sizeof(resp_data), res);
|
||||
|
||||
for (size_t i = 0; i < strlen(resp_payload); i++) {
|
||||
TEST_ASSERT_EQUAL_INT(resp_payload[i], pdu.payload[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Test *tests_gcoap_tests(void)
|
||||
{
|
||||
EMB_UNIT_TESTFIXTURES(fixtures) {
|
||||
new_TestFixture(test_gcoap__client_get_req),
|
||||
new_TestFixture(test_gcoap__client_get_resp),
|
||||
new_TestFixture(test_gcoap__server_get_req),
|
||||
new_TestFixture(test_gcoap__server_get_resp),
|
||||
};
|
||||
|
||||
EMB_UNIT_TESTCALLER(gcoap_tests, NULL, NULL, fixtures);
|
||||
|
||||
return (Test *)&gcoap_tests;
|
||||
}
|
||||
|
||||
void tests_gcoap(void)
|
||||
{
|
||||
TESTS_RUN(tests_gcoap_tests());
|
||||
}
|
||||
/** @} */
|
40
tests/unittests/tests-gcoap/tests-gcoap.h
Normal file
40
tests/unittests/tests-gcoap/tests-gcoap.h
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Ken Bannister. All rights reserved.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup unittests
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Unit tests for the gcoap module
|
||||
*
|
||||
* @author Ken Bannister <kb2ma@runbox.com>
|
||||
*/
|
||||
#ifndef TESTS_GCOAP_H_
|
||||
#define TESTS_GCOAP_H_
|
||||
|
||||
#include "embUnit.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief The entry point of this test suite.
|
||||
*
|
||||
* Includes simple success case tests to get a resource from a server, and
|
||||
* to provide a resource for a client.
|
||||
*/
|
||||
void tests_gcoap(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* TESTS_GCOAP_H_ */
|
||||
/** @} */
|
Loading…
Reference in New Issue
Block a user