/* * option.c -- helpers for handling options in CoAP PDUs * * Copyright (C) 2010,2011 Olaf Bergmann * * 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 #endif #include #include #include "option.h" #include "debug.h" coap_opt_t * options_start(coap_pdu_t *pdu) { if (pdu && pdu->hdr && (pdu->hdr->token + pdu->hdr->token_length < (unsigned char *)pdu->hdr + pdu->length)) { coap_opt_t *opt = pdu->hdr->token + pdu->hdr->token_length; return (*opt == COAP_PAYLOAD_START) ? NULL : opt; } else return NULL; } size_t coap_opt_parse(const coap_opt_t *opt, size_t length, coap_option_t *result) { const coap_opt_t *opt_start = opt; /* store where parsing starts */ assert(opt); assert(result); #define ADVANCE_OPT(o,e,step) if ((e) < step) { \ debug("cannot advance opt past end\n"); \ return 0; \ } else { \ (e) -= step; \ (o) = ((unsigned char *)(o)) + step; \ } if (length < 1) return 0; result->delta = (*opt & 0xf0) >> 4; result->length = *opt & 0x0f; switch(result->delta) { case 15: if (*opt == COAP_PAYLOAD_START) debug("found payload marker when expecting option\n"); else debug("ignored reserved option delta 15\n"); return 0; case 14: /* Handle two-byte value: First, the MSB + 269 is stored as delta value. * After that, the option pointer is advanced to the LSB which is handled * just like case delta == 13. */ ADVANCE_OPT(opt,length,1); result->delta = ((*opt & 0xff) << 8) + 269; if (result->delta < 269) { debug("delta too large\n"); return 0; } /* fall through */ case 13: ADVANCE_OPT(opt,length,1); result->delta += *opt & 0xff; break; default: ; } switch(result->length) { case 15: debug("found reserved option length 15\n"); return 0; case 14: /* Handle two-byte value: First, the MSB + 269 is stored as delta value. * After that, the option pointer is advanced to the LSB which is handled * just like case delta == 13. */ ADVANCE_OPT(opt,length,1); result->length = ((*opt & 0xff) << 8) + 269; /* fall through */ case 13: ADVANCE_OPT(opt,length,1); result->length += *opt & 0xff; break; default: ; } ADVANCE_OPT(opt,length,1); /* opt now points to value, if present */ result->value = (unsigned char *)opt; if (length < result->length) { debug("invalid option length\n"); return 0; } #undef ADVANCE_OPT return (opt + result->length) - opt_start; } const coap_opt_filter_t COAP_OPT_ALL = { 0xff, 0xff, 0xff }; /* must be sizeof(coap_opt_filter_t) */ coap_opt_iterator_t * coap_option_iterator_init(coap_pdu_t *pdu, coap_opt_iterator_t *oi, const coap_opt_filter_t filter) { assert(pdu); assert(oi); memset(oi, 0, sizeof(coap_opt_iterator_t)); oi->option = options_start(pdu); if (oi->option) { /* Note that we do not check if options exceed the length of @p * pdu. This must be checked before coap_option_iterator_init() is * called. */ oi->length = pdu->length - ((unsigned char *)oi->option - (unsigned char *)pdu->hdr); oi->type = coap_opt_delta(oi->option); memcpy(oi->filter, filter, sizeof(coap_opt_filter_t)); return oi; } return NULL; } #define opt_finished(oi) \ ((oi)->length == 0 || *((oi)->option) == COAP_PAYLOAD_START) coap_opt_t * coap_option_next(coap_opt_iterator_t *oi) { assert(oi); if (opt_finished(oi)) return NULL; /* proceed to next option */ if (oi->n++) { oi->option = options_next(oi->option); if (opt_finished(oi)) return NULL; oi->type += coap_opt_delta(oi->option); } /* Skip subsequent options if the filter bit is not set. */ while (oi->option && coap_option_getb(oi->filter, oi->type) == 0) { oi->n++; oi->option = options_next(oi->option); if (!oi->option || opt_finished(oi)) return NULL; oi->type += coap_opt_delta(oi->option); } return oi->option; } coap_opt_t * coap_check_option(coap_pdu_t *pdu, unsigned char type, coap_opt_iterator_t *oi) { coap_opt_filter_t f; memset(f, 0, sizeof(coap_opt_filter_t)); coap_option_setb(f, type); coap_option_iterator_init(pdu, oi, f); coap_option_next(oi); return oi->option && oi->type == type ? oi->option : NULL; } unsigned short coap_opt_check_delta(coap_opt_t *opt, size_t maxlen) { unsigned short n = 0; if (!maxlen) /* need to look at first byte */ return 0; /* check for option jumps */ switch (*opt) { case 0xf1: case 0xf2: case 0xf3: n += (*opt & 0x03); break; default: ; } return n < maxlen ? n+1 : 0; } unsigned short coap_opt_delta(coap_opt_t *opt) { unsigned short n; n = *opt & 0xf0; switch (n) { case 15: /* error */ warn("coap_opt_delta: illegal option delta\n"); /* This case usually should not happen, hence we do not have a * proper way to indicate an error. */ return 0; case 14: /* Handle two-byte value: First, the MSB + 269 is stored as delta value. * After that, the option pointer is advanced to the LSB which is handled * just like case delta == 13. */ n = ((*opt & 0xff) << 8) + 269; ++opt; /* fall through */ case 13: n += *opt & 0xff; break; default: /* n already contains the actual delta value */ ; } return n; } /** * Skips the bytes that are used to encode an option jump. The caller * of this function must ensure that @p opt is still valid afterwards. */ #define COAP_SKIP_OPTION_JUMP(opt) \ do { \ switch (*(opt)) { \ case 0xf1: case 0xf2: case 0xf3: \ (opt) = PCHAR(opt) + (*(opt) & 0x03); \ break; \ default: \ ; \ } \ } while (0) unsigned short coap_opt_length(const coap_opt_t *opt) { unsigned short length; COAP_SKIP_OPTION_JUMP(opt); if ((*opt & 0xf0) == 0xf0) /* not an option -> length is 0 */ return 0; if ((*opt & 0x0f) < 15) /* 0..14 */ return *opt & 0x0f; length = 15; /* add 255 for each 0xff byte */ while (*++opt == 0xff && length < 780) length += 255; return length + (*opt & 0xff); } unsigned char * coap_opt_value(coap_opt_t *opt) { unsigned char *p = opt; unsigned char n = 0; COAP_SKIP_OPTION_JUMP(p); if ((*p & 0xf0) == 0xf0) /* something we do not know, possibly encoding error */ return NULL; if ((*p & 0x0f) < 15) /* 0..14 */ return ++p; while (*++p == 0xff && n++ < 3) ; return p; } size_t coap_opt_size(coap_opt_t *opt) { unsigned char *opt_value = coap_opt_value(opt); if (opt_value) return (opt_value + coap_opt_length(opt)) - PCHAR(opt); else return 0; } size_t coap_opt_setheader(coap_opt_t *opt, size_t maxlen, unsigned short delta, size_t length) { size_t skip = 0; assert(opt); if (maxlen == 0) /* need at least one byte */ return 0; if (delta < 13) { opt[0] = delta << 4; } else if (delta < 270) { if (maxlen < 2) { debug("insufficient space to encode option delta %d", delta); return 0; } opt[0] = 0xd0; opt[++skip] = delta - 13; } else { if (maxlen < 3) { debug("insufficient space to encode option delta %d", delta); return 0; } opt[0] = 0xe0; opt[++skip] = ((delta - 269) >> 8) & 0xff; opt[++skip] = (delta - 269) & 0xff; } if (length < 13) { opt[0] |= length & 0x0f; } else if (length < 270) { if (maxlen < skip + 1) { debug("insufficient space to encode option length %d", length); return 0; } opt[0] |= 0x0d; opt[++skip] = length - 13; } else { if (maxlen < skip + 2) { debug("insufficient space to encode option delta %d", delta); return 0; } opt[0] |= 0x0e; opt[++skip] = ((length - 269) >> 8) & 0xff; opt[++skip] = (length - 269) & 0xff; } return skip + 1; } size_t coap_opt_encode(coap_opt_t *opt, size_t maxlen, unsigned short delta, const unsigned char *val, size_t length) { size_t l = 1; l = coap_opt_setheader(opt, maxlen, delta, length); assert(l <= maxlen); if (!l) { debug("coap_opt_encode: cannot set option header\n"); return 0; } maxlen -= l; opt += l; if (maxlen < length) { debug("coap_opt_encode: option too large for buffer\n"); return 0; } if (val) /* better be safe here */ memcpy(opt, val, length); return l + length; }