zephyr/net/ip/er-coap/er-coap-engine.c

541 lines
20 KiB
C

/*
* Copyright (c) 2013, Institute for Pervasive Computing, ETH Zurich
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* This file is part of the Contiki operating system.
*/
/**
* \file
* CoAP implementation for the REST Engine.
* \author
* Matthias Kovatsch <kovatsch@inf.ethz.ch>
*/
#include "sys/cc.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "er-coap-engine.h"
#include "er-coap-context.h"
#if CONFIG_NETWORK_IP_STACK_DEBUG_COAP_ENGINE
#define DEBUG 1
#endif
#include "contiki/ip/uip-debug.h"
PROCESS(coap_engine, "CoAP Engine");
/*---------------------------------------------------------------------------*/
/*- Variables ---------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
static service_callback_t service_cbk = NULL;
#if NET_COAP_CONF_STATS
net_coap_stats_t net_coap_stats;
#endif /* NET_COAP_CONF_STATS */
/*---------------------------------------------------------------------------*/
/*- Internal API ------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
int
coap_engine_receive(coap_context_t *coap_ctx)
{
erbium_status_code = NO_ERROR;
coap_packet_t message[1]; /* this way the packet can be treated as pointer as usual */
coap_packet_t response[1];
coap_transaction_t *transaction = NULL;
PRINTF("%s(): received data len %u\n", __FUNCTION__,
(uint16_t)uip_appdatalen(coap_ctx->buf));
if(uip_newdata(coap_ctx->buf)) {
PRINTF("receiving UDP datagram from: ");
PRINT6ADDR(&UIP_IP_BUF(coap_ctx->buf)->srcipaddr);
PRINTF(":%u\n Length: %u\n",
uip_ntohs(UIP_UDP_BUF(coap_ctx->buf)->srcport),
uip_appdatalen(coap_ctx->buf));
erbium_status_code =
coap_parse_message(message, uip_appdata(coap_ctx->buf), uip_appdatalen(coap_ctx->buf));
coap_set_context(message, coap_ctx);
if(erbium_status_code == NO_ERROR) {
NET_COAP_STAT(recv++);
/*TODO duplicates suppression, if required by application */
PRINTF(" Parsed: v %u, t %u, tkl %u, c %u, mid %u\n", message->version,
message->type, message->token_len, message->code, message->mid);
PRINTF(" URL[%d]: %.*s\n", message->uri_path_len, message->uri_path_len, message->uri_path);
PRINTF(" Payload[%d]: %.*s\n", message->payload_len, message->payload_len, message->payload);
/* handle requests */
if(message->code >= COAP_GET && message->code <= COAP_DELETE) {
/* use transaction buffer for response to confirmable request */
if((transaction = coap_new_transaction(message->mid, coap_ctx,
&UIP_IP_BUF(coap_ctx->buf)->srcipaddr,
UIP_UDP_BUF(coap_ctx->buf)->srcport))) {
uint32_t block_num = 0;
uint16_t block_size = REST_MAX_CHUNK_SIZE;
uint32_t block_offset = 0;
int32_t new_offset = 0;
/* prepare response */
if(message->type == COAP_TYPE_CON) {
/* reliable CON requests are answered with an ACK */
coap_init_message(response, COAP_TYPE_ACK, CONTENT_2_05,
message->mid);
} else {
/* unreliable NON requests are answered with a NON as well */
coap_init_message(response, COAP_TYPE_NON, CONTENT_2_05,
coap_get_mid());
/* mirror token */
} if(message->token_len) {
coap_set_token(response, message->token, message->token_len);
/* get offset for blockwise transfers */
}
if(coap_get_header_block2
(message, &block_num, NULL, &block_size, &block_offset)) {
PRINTF("Blockwise: block request %lu (%u/%u) @ %lu bytes\n",
(unsigned long)block_num, block_size, REST_MAX_CHUNK_SIZE, (unsigned long)block_offset);
block_size = MIN(block_size, REST_MAX_CHUNK_SIZE);
new_offset = block_offset;
}
/* invoke resource handler */
if(service_cbk) {
/* call REST framework and check if found and allowed */
if(service_cbk
(message, response, transaction->packet + COAP_MAX_HEADER_SIZE,
block_size, &new_offset)) {
if(erbium_status_code == NO_ERROR) {
/* TODO coap_handle_blockwise(request, response, start_offset, end_offset); */
/* resource is unaware of Block1 */
if(IS_OPTION(message, COAP_OPTION_BLOCK1)
&& response->code < BAD_REQUEST_4_00
&& !IS_OPTION(response, COAP_OPTION_BLOCK1)) {
PRINTF("Block1 NOT IMPLEMENTED\n");
erbium_status_code = NOT_IMPLEMENTED_5_01;
coap_error_message = "NoBlock1Support";
/* client requested Block2 transfer */
} else if(IS_OPTION(message, COAP_OPTION_BLOCK2)) {
/* unchanged new_offset indicates that resource is unaware of blockwise transfer */
if(new_offset == block_offset) {
PRINTF
("Blockwise: unaware resource with payload length %u/%u\n",
response->payload_len, block_size);
if(block_offset >= response->payload_len) {
PRINTF
("handle_incoming_data(): block_offset >= response->payload_len\n");
response->code = BAD_OPTION_4_02;
coap_set_payload(response, "BlockOutOfScope", 15); /* a const char str[] and sizeof(str) produces larger code size */
} else {
coap_set_header_block2(response, block_num,
response->payload_len -
block_offset > block_size,
block_size);
coap_set_payload(response,
response->payload + block_offset,
MIN(response->payload_len -
block_offset, block_size));
} /* if(valid offset) */
/* resource provides chunk-wise data */
} else {
PRINTF("Blockwise: blockwise resource, new offset %ld\n",
(long)new_offset);
coap_set_header_block2(response, block_num,
new_offset != -1
|| response->payload_len >
block_size, block_size);
if(response->payload_len > block_size) {
coap_set_payload(response, response->payload,
block_size);
}
} /* if(resource aware of blockwise) */
/* Resource requested Block2 transfer */
} else if(new_offset != 0) {
PRINTF
("Blockwise: no block option for blockwise resource, using block size %u\n",
COAP_MAX_BLOCK_SIZE);
coap_set_header_block2(response, 0, new_offset != -1,
COAP_MAX_BLOCK_SIZE);
coap_set_payload(response, response->payload,
MIN(response->payload_len,
COAP_MAX_BLOCK_SIZE));
} /* blockwise transfer handling */
} /* no errors/hooks */
/* successful service callback */
/* serialize response */
}
if(erbium_status_code == NO_ERROR) {
if((transaction->packet_len = coap_serialize_message(response,
transaction->
packet)) ==
0) {
erbium_status_code = PACKET_SERIALIZATION_ERROR;
}
}
} else {
erbium_status_code = NOT_IMPLEMENTED_5_01;
coap_error_message = "NoServiceCallbck"; /* no 'a' to fit into 16 bytes */
} /* if(service callback) */
} else {
erbium_status_code = SERVICE_UNAVAILABLE_5_03;
coap_error_message = "NoFreeTraBuffer";
} /* if(transaction buffer) */
/* handle responses */
} else {
if(message->type == COAP_TYPE_CON && message->code == 0) {
PRINTF("Received Ping\n");
erbium_status_code = PING_RESPONSE;
} else if(message->type == COAP_TYPE_ACK) {
/* transactions are closed through lookup below */
PRINTF("Received ACK\n");
} else if(message->type == COAP_TYPE_RST) {
PRINTF("Received RST\n");
/* cancel possible subscriptions */
coap_remove_observer_by_mid(coap_ctx, &UIP_IP_BUF(coap_ctx->buf)->srcipaddr,
UIP_UDP_BUF(coap_ctx->buf)->srcport, message->mid);
}
if((transaction = coap_get_transaction_by_mid(message->mid))) {
/* free transaction memory before callback, as it may create a new transaction */
restful_response_handler callback = transaction->callback;
void *callback_data = transaction->callback_data;
coap_clear_transaction(transaction);
/* check if someone registered for the response */
if(callback) {
callback(callback_data, message);
}
}
/* if(ACKed transaction) */
transaction = NULL;
#if COAP_OBSERVE_CLIENT
/* if observe notification */
if((message->type == COAP_TYPE_CON || message->type == COAP_TYPE_NON)
&& IS_OPTION(message, COAP_OPTION_OBSERVE)) {
PRINTF("Observe [%u]\n", message->observe);
coap_handle_notification(coap_ctx, &UIP_IP_BUF(coap_ctx->buf)->srcipaddr,
UIP_UDP_BUF(coap_ctx->buf)->srcport, message);
}
#endif /* COAP_OBSERVE_CLIENT */
} /* request or response */
} else { /* parsed correctly */
NET_COAP_STAT(recv_err++);
}
/* if(parsed correctly) */
if(erbium_status_code == NO_ERROR) {
if(transaction) {
coap_send_transaction(transaction);
}
} else if(erbium_status_code == MANUAL_RESPONSE) {
PRINTF("Clearing transaction for manual response");
coap_clear_transaction(transaction);
} else {
coap_message_type_t reply_type = COAP_TYPE_ACK;
PRINTF("ERROR %u: %s\n", erbium_status_code, coap_error_message);
coap_clear_transaction(transaction);
if(erbium_status_code == PING_RESPONSE) {
erbium_status_code = 0;
reply_type = COAP_TYPE_RST;
} else if(erbium_status_code >= 192) {
/* set to sendable error code */
erbium_status_code = INTERNAL_SERVER_ERROR_5_00;
/* reuse input buffer for error message */
}
coap_init_message(message, reply_type, erbium_status_code,
message->mid);
coap_set_payload(message, coap_error_message,
strlen(coap_error_message));
coap_send_message(coap_ctx, &UIP_IP_BUF(coap_ctx->buf)->srcipaddr,
UIP_UDP_BUF(coap_ctx->buf)->srcport,
uip_appdata(coap_ctx->buf),
coap_serialize_message(message,
uip_appdata(coap_ctx->buf)));
}
}
/* if(new data) */
return erbium_status_code;
}
/*---------------------------------------------------------------------------*/
void
coap_init_engine(void)
{
coap_context_init();
process_start(&coap_engine, NULL, NULL);
}
/*---------------------------------------------------------------------------*/
void
coap_set_service_callback(service_callback_t callback)
{
service_cbk = callback;
}
/*---------------------------------------------------------------------------*/
rest_resource_flags_t
coap_get_rest_method(void *packet)
{
return (rest_resource_flags_t)(1 <<
(((coap_packet_t *)packet)->code - 1));
}
/*---------------------------------------------------------------------------*/
/*- Server Part -------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
/* the discover resource is automatically included for CoAP */
extern resource_t res_well_known_core;
coap_context_t *coap_init_server(uip_ipaddr_t *server_addr,
uint16_t server_port,
uip_ipaddr_t *peer_addr,
uint16_t peer_port)
{
PRINTF("Starting %s receiver...\n", coap_rest_implementation.name);
rest_activate_resource(&res_well_known_core, ".well-known/core");
coap_register_as_transaction_handler();
return coap_init_connection(server_addr, server_port, peer_addr, peer_port);
}
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(coap_engine, ev, data, buf, user_data)
{
PROCESS_BEGIN();
#if 0
/* This is not used in Zephyr. */
PRINTF("Starting %s receiver...\n", coap_rest_implementation.name);
rest_activate_resource(&res_well_known_core, ".well-known/core");
coap_register_as_transaction_handler();
coap_init_connection(SERVER_LISTEN_PORT);
while(1) {
PROCESS_YIELD();
if(ev == tcpip_event) {
coap_engine_receive(COAP_CONTEXT_NONE);
} else if(ev == PROCESS_EVENT_TIMER) {
/* retransmissions are handled here */
coap_check_transactions();
}
} /* while (1) */
#endif /* 0 */
PROCESS_END();
}
/*---------------------------------------------------------------------------*/
/*- Client Part -------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
void
coap_blocking_request_callback(void *callback_data, void *response)
{
struct request_state_t *state = (struct request_state_t *)callback_data;
state->response = (coap_packet_t *)response;
process_poll(state->process);
}
/*---------------------------------------------------------------------------*/
PT_THREAD(coap_blocking_request
(struct request_state_t *state, process_event_t ev,
coap_context_t *coap_ctx,
uip_ipaddr_t *remote_ipaddr, uint16_t remote_port,
coap_packet_t *request,
blocking_response_handler request_callback))
{
PT_BEGIN(&state->pt);
static uint8_t more;
static uint32_t res_block;
static uint8_t block_error;
state->block_num = 0;
state->response = NULL;
state->process = PROCESS_CURRENT();
more = 0;
res_block = 0;
block_error = 0;
do {
request->mid = coap_get_mid();
if((state->transaction = coap_new_transaction(request->mid, coap_ctx,
remote_ipaddr, remote_port))) {
state->transaction->callback = coap_blocking_request_callback;
state->transaction->callback_data = state;
if(state->block_num > 0) {
coap_set_header_block2(request, state->block_num, 0,
REST_MAX_CHUNK_SIZE);
}
state->transaction->packet_len = coap_serialize_message(request,
state->
transaction->
packet);
coap_send_transaction(state->transaction);
PRINTF("Requested #%lu (MID %u)\n", (unsigned long)state->block_num, request->mid);
PT_YIELD_UNTIL(&state->pt, ev == PROCESS_EVENT_POLL);
if(!state->response) {
PRINTF("Server not responding\n");
PT_EXIT(&state->pt);
}
coap_get_header_block2(state->response, &res_block, &more, NULL, NULL);
PRINTF("Received #%lu%s (%u bytes)\n", (unsigned long)res_block, more ? "+" : "",
state->response->payload_len);
if(res_block == state->block_num) {
request_callback(state->response);
++(state->block_num);
} else {
PRINTF("WRONG BLOCK %lu/%lu\n", (unsigned long)res_block, (unsigned long)state->block_num);
++block_error;
}
} else {
PRINTF("%s: Could not allocate transaction buffer", __FUNCTION__);
PT_EXIT(&state->pt);
}
} while(more && block_error < COAP_MAX_ATTEMPTS);
PT_END(&state->pt);
}
/*---------------------------------------------------------------------------*/
/*- REST Engine Interface ---------------------------------------------------*/
/*---------------------------------------------------------------------------*/
const struct rest_implementation coap_rest_implementation = {
.name = "CoAP-18",
.init = coap_init_engine,
.set_service_callback = coap_set_service_callback,
.get_url = coap_get_header_uri_path,
.get_method_type = coap_get_rest_method,
.set_response_status = coap_set_status_code,
.get_header_content_type = coap_get_header_content_format,
.set_header_content_type = coap_set_header_content_format,
.get_header_accept = coap_get_header_accept,
.get_header_length = coap_get_header_size2,
.set_header_length = coap_set_header_size2,
.get_header_max_age = coap_get_header_max_age,
.set_header_max_age = coap_set_header_max_age,
.set_header_etag = coap_set_header_etag,
.get_header_if_match = coap_get_header_if_match,
.get_header_if_none_match = coap_get_header_if_none_match,
.get_header_host = coap_get_header_uri_host,
.set_header_location = coap_set_header_location_path,
.get_request_payload = coap_get_payload,
.set_response_payload = coap_set_payload,
.get_query = coap_get_header_uri_query,
.get_query_variable = coap_get_query_variable,
.get_post_variable = coap_get_post_variable,
.notify_subscribers = coap_notify_observers,
.subscription_handler = coap_observe_handler,
.status = {
CONTENT_2_05,
CREATED_2_01,
CHANGED_2_04,
DELETED_2_02,
VALID_2_03,
BAD_REQUEST_4_00,
UNAUTHORIZED_4_01,
BAD_OPTION_4_02,
FORBIDDEN_4_03,
NOT_FOUND_4_04,
METHOD_NOT_ALLOWED_4_05,
NOT_ACCEPTABLE_4_06,
REQUEST_ENTITY_TOO_LARGE_4_13,
UNSUPPORTED_MEDIA_TYPE_4_15,
INTERNAL_SERVER_ERROR_5_00,
NOT_IMPLEMENTED_5_01,
BAD_GATEWAY_5_02,
SERVICE_UNAVAILABLE_5_03,
GATEWAY_TIMEOUT_5_04,
PROXYING_NOT_SUPPORTED_5_05
},
.type = {
TEXT_PLAIN,
TEXT_XML,
TEXT_CSV,
TEXT_HTML,
IMAGE_GIF,
IMAGE_JPEG,
IMAGE_PNG,
IMAGE_TIFF,
AUDIO_RAW,
VIDEO_RAW,
APPLICATION_LINK_FORMAT,
APPLICATION_XML,
APPLICATION_OCTET_STREAM,
APPLICATION_RDF_XML,
APPLICATION_SOAP_XML,
APPLICATION_ATOM_XML,
APPLICATION_XMPP_XML,
APPLICATION_EXI,
APPLICATION_FASTINFOSET,
APPLICATION_SOAP_FASTINFOSET,
APPLICATION_JSON,
APPLICATION_X_OBIX_BINARY
}
};
/*---------------------------------------------------------------------------*/