Files
libcoap/pdu.c
T
2013-01-11 11:32:22 +01:00

375 lines
9.2 KiB
C

/* pdu.c -- CoAP message structure
*
* Copyright (C) 2010,2011 Olaf Bergmann <bergmann@tzi.org>
*
* This file is part of the CoAP library libcoap. Please see
* README for terms of use.
*/
#include "config.h"
#if defined(HAVE_ASSERT_H) && !defined(assert)
# include <assert.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#include "debug.h"
#include "pdu.h"
#include "option.h"
#include "encode.h"
#ifdef WITH_CONTIKI
#include "memb.h"
typedef unsigned char _pdu[sizeof(coap_pdu_t) + COAP_MAX_PDU_SIZE];
MEMB(pdu_storage, _pdu, COAP_PDU_MAXCNT);
void
coap_pdu_resources_init() {
memb_init(&pdu_storage);
}
#else /* WITH_CONTIKI */
#include "mem.h"
#endif /* WITH_CONTIKI */
void
coap_pdu_clear(coap_pdu_t *pdu, size_t size) {
assert(pdu);
memset(pdu, 0, sizeof(coap_pdu_t) + size);
pdu->max_size = size;
pdu->hdr = (coap_hdr_t *)((unsigned char *)pdu + sizeof(coap_pdu_t));
pdu->hdr->version = COAP_DEFAULT_VERSION;
/* data points after the header; when options are added, the data
pointer is moved to the back */
pdu->length = sizeof(coap_hdr_t);
pdu->data = (unsigned char *)pdu->hdr + pdu->length;
}
coap_pdu_t *
coap_pdu_init(unsigned char type, unsigned char code,
unsigned short id, size_t size) {
coap_pdu_t *pdu;
assert(size <= COAP_MAX_PDU_SIZE);
/* Size must be large enough to fit the header. */
if (size < sizeof(coap_hdr_t) || size > COAP_MAX_PDU_SIZE)
return NULL;
/* size must be large enough for hdr */
#ifndef WITH_CONTIKI
pdu = coap_malloc(sizeof(coap_pdu_t) + size);
#else /* WITH_CONTIKI */
pdu = (coap_pdu_t *)memb_alloc(&pdu_storage);
#endif /* WITH_CONTIKI */
if (pdu) {
coap_pdu_clear(pdu, size);
pdu->hdr->id = id;
pdu->hdr->type = type;
pdu->hdr->code = code;
}
return pdu;
}
coap_pdu_t *
coap_new_pdu() {
coap_pdu_t *pdu;
#ifndef WITH_CONTIKI
pdu = coap_pdu_init(0, 0, ntohs(COAP_INVALID_TID), COAP_MAX_PDU_SIZE);
#else /* WITH_CONTIKI */
pdu = coap_pdu_init(0, 0, uip_ntohs(COAP_INVALID_TID), COAP_MAX_PDU_SIZE);
#endif /* WITH_CONTIKI */
#ifndef NDEBUG
if (!pdu)
coap_log(LOG_CRIT, "coap_new_pdu: cannot allocate memory for new PDU\n");
#endif
return pdu;
}
void
coap_delete_pdu(coap_pdu_t *pdu) {
#ifndef WITH_CONTIKI
coap_free( pdu );
#else /* WITH_CONTIKI */
memb_free(&pdu_storage, pdu);
#endif /* WITH_CONTIKI */
}
int
coap_add_token(coap_pdu_t *pdu, size_t len, const unsigned char *data) {
const size_t HEADERLENGTH = len + 4;
if (!pdu || len > 8 || pdu->max_size < HEADERLENGTH)
return 0;
pdu->hdr->token_length = len;
if (len)
memcpy(pdu->hdr->token, data, len);
pdu->max_delta = 0;
pdu->length = HEADERLENGTH;
pdu->data = NULL;
return 1;
}
size_t
coap_add_option(coap_pdu_t *pdu, unsigned short type, unsigned int len, const unsigned char *data) {
size_t optsize;
coap_opt_t *opt;
assert(pdu);
pdu->data = NULL;
if (type < pdu->max_delta) {
warn("coap_add_option: options are not in correct order\n");
return 0;
}
opt = (unsigned char *)pdu->hdr + pdu->length;
/* encode option and check length */
optsize = coap_opt_encode(opt, pdu->max_size - pdu->length,
type - pdu->max_delta, data, len);
if (!optsize) {
warn("coap_add_option: cannot add option\n");
/* error */
return 0;
} else {
pdu->max_delta = type;
pdu->length += optsize;
}
return optsize;
}
int
coap_add_data(coap_pdu_t *pdu, unsigned int len, const unsigned char *data) {
assert(pdu);
if ( pdu->length + len > pdu->max_size ) {
#ifndef NDEBUG
coap_log(LOG_WARN, "coap_add_data: cannot add: data too large for PDU\n");
#endif
return 0;
}
memcpy( (unsigned char *)pdu->hdr + pdu->length, data, len );
pdu->length += len;
return 1;
}
int
coap_get_data(coap_pdu_t *pdu, size_t *len, unsigned char **data) {
if ( !pdu )
return 0;
if ( pdu->data < (unsigned char *)pdu->hdr + pdu->length ) {
/* pdu contains data */
*len = (unsigned char *)pdu->hdr + pdu->length - pdu->data;
*data = pdu->data;
} else { /* no data, clear everything */
*len = 0;
*data = NULL;
}
return 1;
}
#ifndef SHORT_ERROR_RESPONSE
typedef struct {
unsigned char code;
char *phrase;
} error_desc_t;
/* if you change anything here, make sure, that the longest string does not
* exceed COAP_ERROR_PHRASE_LENGTH. */
error_desc_t coap_error[] = {
{ COAP_RESPONSE_CODE(65), "2.01 Created" },
{ COAP_RESPONSE_CODE(66), "2.02 Deleted" },
{ COAP_RESPONSE_CODE(67), "2.03 Valid" },
{ COAP_RESPONSE_CODE(68), "2.04 Changed" },
{ COAP_RESPONSE_CODE(69), "2.05 Content" },
{ COAP_RESPONSE_CODE(400), "Bad Request" },
{ COAP_RESPONSE_CODE(401), "Unauthorized" },
{ COAP_RESPONSE_CODE(402), "Bad Option" },
{ COAP_RESPONSE_CODE(403), "Forbidden" },
{ COAP_RESPONSE_CODE(404), "Not Found" },
{ COAP_RESPONSE_CODE(405), "Method Not Allowed" },
{ COAP_RESPONSE_CODE(408), "Request Entity Incomplete" },
{ COAP_RESPONSE_CODE(413), "Request Entity Too Large" },
{ COAP_RESPONSE_CODE(415), "Unsupported Media Type" },
{ COAP_RESPONSE_CODE(500), "Internal Server Error" },
{ COAP_RESPONSE_CODE(501), "Not Implemented" },
{ COAP_RESPONSE_CODE(502), "Bad Gateway" },
{ COAP_RESPONSE_CODE(503), "Service Unavailable" },
{ COAP_RESPONSE_CODE(504), "Gateway Timeout" },
{ COAP_RESPONSE_CODE(505), "Proxying Not Supported" },
{ 0, NULL } /* end marker */
};
char *
coap_response_phrase(unsigned char code) {
int i;
for (i = 0; coap_error[i].code; ++i) {
if (coap_error[i].code == code)
return coap_error[i].phrase;
}
return NULL;
}
#endif
#if 0
int
coap_get_request_uri(coap_pdu_t *pdu, coap_uri_t *result) {
coap_opt_t *opt;
coap_opt_iterator_t opt_iter;
if (!pdu || !result)
return 0;
memset(result, 0, sizeof(*result));
if ((opt = coap_check_option(pdu, COAP_OPTION_URI_HOST, &opt_iter)))
COAP_SET_STR(&result->host, COAP_OPT_LENGTH(*opt), COAP_OPT_VALUE(*opt));
if ((opt = coap_check_option(pdu, COAP_OPTION_URI_PORT, &opt_iter)))
result->port =
coap_decode_var_bytes(COAP_OPT_VALUE(*opt), COAP_OPT_LENGTH(*opt));
else
result->port = COAP_DEFAULT_PORT;
if ((opt = coap_check_option(pdu, COAP_OPTION_URI_PATH, &opt_iter))) {
result->path.s = COAP_OPT_VALUE(*opt);
result->path.length = COAP_OPT_LENGTH(*opt);
while (coap_option_next(&opt_iter) && opt_iter.type == COAP_OPTION_URI_PATH)
result->path.length += COAP_OPT_SIZE(*opt_iter.option);
}
if ((opt = coap_check_option(pdu, COAP_OPTION_URI_QUERY, &opt_iter))) {
result->query.s = COAP_OPT_VALUE(*opt);
result->query.length = COAP_OPT_LENGTH(*opt);
while (coap_option_next(&opt_iter) && opt_iter.type == COAP_OPTION_URI_QUERY)
result->query.length += COAP_OPT_SIZE(*opt_iter.option);
}
return 1;
}
#endif
/**
* Advances *optp to next option if still in PDU. This function
* returns the number of bytes opt has been advanced or @c 0
* on error.
*/
static size_t
next_option_safe(coap_opt_t **optp, size_t *length) {
coap_option_t option;
size_t optsize;
assert(optp); assert(*optp);
assert(length);
optsize = coap_opt_parse(*optp, *length, &option);
if (optsize) {
assert(optsize <= *length);
*optp += optsize;
*length -= optsize;
}
return optsize;
}
int
coap_pdu_parse(unsigned char *data, size_t length, coap_pdu_t *pdu) {
coap_opt_t *opt;
assert(data);
assert(pdu);
if (pdu->max_size < length) {
debug("insufficient space to store parsed PDU\n");
return 0;
}
if (length < sizeof(coap_hdr_t)) {
debug("discarded invalid PDU\n");
}
pdu->hdr->version = data[0] >> 6;
pdu->hdr->type = (data[0] >> 4) & 0x03;
pdu->hdr->token_length = data[0] & 0x0f;
pdu->hdr->code = data[1];
pdu->data = NULL;
/* sanity checks */
if (pdu->hdr->code == 0) {
if (length != sizeof(coap_hdr_t) || pdu->hdr->token_length) {
debug("coap_pdu_parse: empty message is not empty\n");
goto discard;
}
}
if (length < sizeof(coap_hdr_t) + pdu->hdr->token_length
|| pdu->hdr->token_length > 8) {
debug("coap_pdu_parse: invalid Token\n");
goto discard;
}
/* Copy message id in network byte order, so we can easily write the
* response back to the network. */
memcpy(&pdu->hdr->id, data + 2, 2);
/* append data (including the Token) to pdu structure */
memcpy(pdu->hdr + 1, data + sizeof(coap_hdr_t), length - sizeof(coap_hdr_t));
pdu->length = length;
/* Finally calculate beginning of data block and thereby check integrity
* of the PDU structure. */
/* skip header + token */
length -= (pdu->hdr->token_length + sizeof(coap_hdr_t));
opt = (unsigned char *)(pdu->hdr + 1) + pdu->hdr->token_length;
while (length && *opt != COAP_PAYLOAD_START) {
if (!next_option_safe(&opt, (size_t *)&length)) {
debug("coap_pdu_parse: drop\n");
goto discard;
}
}
/* end of packet or start marker */
if (length) {
assert(*opt == COAP_PAYLOAD_START);
opt++; length--;
if (!length) {
debug("coap_pdu_parse: message ending in payload start marker\n");
goto discard;
}
debug("set data to %p (pdu ends at %p)\n", (unsigned char *)opt,
(unsigned char *)pdu->hdr + pdu->length);
pdu->data = (unsigned char *)opt;
}
return 1;
discard:
return 0;
}