#include "user_config.h" #include "c_stdio.h" #include "c_string.h" #include "coap.h" #include "uri.h" extern void endpoint_setup(void); extern const coap_endpoint_t endpoints[]; #ifdef COAP_DEBUG void coap_dumpHeader(coap_header_t *hdr) { c_printf("Header:\n"); c_printf(" ver 0x%02X\n", hdr->ver); c_printf(" t 0x%02X\n", hdr->ver); c_printf(" tkl 0x%02X\n", hdr->tkl); c_printf(" code 0x%02X\n", hdr->code); c_printf(" id 0x%02X%02X\n", hdr->id[0], hdr->id[1]); } void coap_dump(const uint8_t *buf, size_t buflen, bool bare) { if (bare) { while(buflen--) c_printf("%02X%s", *buf++, (buflen > 0) ? " " : ""); } else { c_printf("Dump: "); while(buflen--) c_printf("%02X%s", *buf++, (buflen > 0) ? " " : ""); c_printf("\n"); } } #endif int coap_parseHeader(coap_header_t *hdr, const uint8_t *buf, size_t buflen) { if (buflen < 4) return COAP_ERR_HEADER_TOO_SHORT; hdr->ver = (buf[0] & 0xC0) >> 6; if (hdr->ver != 1) return COAP_ERR_VERSION_NOT_1; hdr->t = (buf[0] & 0x30) >> 4; hdr->tkl = buf[0] & 0x0F; hdr->code = buf[1]; hdr->id[0] = buf[2]; hdr->id[1] = buf[3]; return 0; } int coap_buildHeader(const coap_header_t *hdr, uint8_t *buf, size_t buflen) { // build header if (buflen < 4) return COAP_ERR_BUFFER_TOO_SMALL; buf[0] = (hdr->ver & 0x03) << 6; buf[0] |= (hdr->t & 0x03) << 4; buf[0] |= (hdr->tkl & 0x0F); buf[1] = hdr->code; buf[2] = hdr->id[0]; buf[3] = hdr->id[1]; return 4; } int coap_parseToken(coap_buffer_t *tokbuf, const coap_header_t *hdr, const uint8_t *buf, size_t buflen) { if (hdr->tkl == 0) { tokbuf->p = NULL; tokbuf->len = 0; return 0; } else if (hdr->tkl <= 8) { if (4U + hdr->tkl > buflen) return COAP_ERR_TOKEN_TOO_SHORT; // tok bigger than packet tokbuf->p = buf+4; // past header tokbuf->len = hdr->tkl; return 0; } else { // invalid size return COAP_ERR_TOKEN_TOO_SHORT; } } int coap_buildToken(const coap_buffer_t *tokbuf, const coap_header_t *hdr, uint8_t *buf, size_t buflen) { // inject token uint8_t *p; if (buflen < (4U + hdr->tkl)) return COAP_ERR_BUFFER_TOO_SMALL; p = buf + 4; if ((hdr->tkl > 0) && (hdr->tkl != tokbuf->len)) return COAP_ERR_UNSUPPORTED; if (hdr->tkl > 0) c_memcpy(p, tokbuf->p, hdr->tkl); // http://tools.ietf.org/html/rfc7252#section-3.1 // inject options return hdr->tkl; } // advances p int coap_parseOption(coap_option_t *option, uint16_t *running_delta, const uint8_t **buf, size_t buflen) { const uint8_t *p = *buf; uint8_t headlen = 1; uint16_t len, delta; if (buflen < headlen) // too small return COAP_ERR_OPTION_TOO_SHORT_FOR_HEADER; delta = (p[0] & 0xF0) >> 4; len = p[0] & 0x0F; // These are untested and may be buggy if (delta == 13) { headlen++; if (buflen < headlen) return COAP_ERR_OPTION_TOO_SHORT_FOR_HEADER; delta = p[1] + 13; p++; } else if (delta == 14) { headlen += 2; if (buflen < headlen) return COAP_ERR_OPTION_TOO_SHORT_FOR_HEADER; delta = ((p[1] << 8) | p[2]) + 269; p+=2; } else if (delta == 15) return COAP_ERR_OPTION_DELTA_INVALID; if (len == 13) { headlen++; if (buflen < headlen) return COAP_ERR_OPTION_TOO_SHORT_FOR_HEADER; len = p[1] + 13; p++; } else if (len == 14) { headlen += 2; if (buflen < headlen) return COAP_ERR_OPTION_TOO_SHORT_FOR_HEADER; len = ((p[1] << 8) | p[2]) + 269; p+=2; } else if (len == 15) return COAP_ERR_OPTION_LEN_INVALID; if ((p + 1 + len) > (*buf + buflen)) return COAP_ERR_OPTION_TOO_BIG; //printf("option num=%d\n", delta + *running_delta); option->num = delta + *running_delta; option->buf.p = p+1; option->buf.len = len; //coap_dump(p+1, len, false); // advance buf *buf = p + 1 + len; *running_delta += delta; return 0; } // http://tools.ietf.org/html/rfc7252#section-3.1 int coap_parseOptionsAndPayload(coap_option_t *options, uint8_t *numOptions, coap_buffer_t *payload, const coap_header_t *hdr, const uint8_t *buf, size_t buflen) { size_t optionIndex = 0; uint16_t delta = 0; const uint8_t *p = buf + 4 + hdr->tkl; const uint8_t *end = buf + buflen; int rc; if (p > end) return COAP_ERR_OPTION_OVERRUNS_PACKET; // out of bounds //coap_dump(p, end - p); // 0xFF is payload marker while((optionIndex < *numOptions) && (p < end) && (*p != 0xFF)) { if (0 != (rc = coap_parseOption(&options[optionIndex], &delta, &p, end-p))) return rc; optionIndex++; } *numOptions = optionIndex; if (p+1 < end && *p == 0xFF) // payload marker { payload->p = p+1; payload->len = end-(p+1); } else { payload->p = NULL; payload->len = 0; } return 0; } int coap_buildOptionHeader(uint32_t optDelta, size_t length, uint8_t *buf, size_t buflen) { int n = 0; uint8_t *p = buf; uint8_t len, delta = 0; if (buflen < 5) return COAP_ERR_BUFFER_TOO_SMALL; coap_option_nibble(optDelta, &delta); coap_option_nibble(length, &len); *p++ = (0xFF & (delta << 4 | len)); n++; if (delta == 13) { *p++ = (optDelta - 13); n++; } else if (delta == 14) { *p++ = ((optDelta-269) >> 8); *p++ = (0xFF & (optDelta-269)); n+=2; } if (len == 13) { *p++ = (length - 13); n++; } else if (len == 14) { *p++ = (length >> 8); *p++ = (0xFF & (length-269)); n+=2; } return n; } #ifdef COAP_DEBUG void coap_dumpOptions(coap_option_t *opts, size_t numopt) { size_t i; c_printf(" Options:\n"); for (i=0;ihdr); coap_dumpOptions(pkt->opts, pkt->numopts); c_printf("Payload: "); coap_dump(pkt->payload.p, pkt->payload.len, true); c_printf("\n"); } #endif int coap_parse(coap_packet_t *pkt, const uint8_t *buf, size_t buflen) { int rc; // coap_dump(buf, buflen, false); if (0 != (rc = coap_parseHeader(&pkt->hdr, buf, buflen))) return rc; // coap_dumpHeader(&hdr); if (0 != (rc = coap_parseToken(&pkt->tok, &pkt->hdr, buf, buflen))) return rc; pkt->numopts = MAXOPT; if (0 != (rc = coap_parseOptionsAndPayload(pkt->opts, &(pkt->numopts), &(pkt->payload), &pkt->hdr, buf, buflen))) return rc; // coap_dumpOptions(opts, numopt); return 0; } // options are always stored consecutively, so can return a block with same option num const coap_option_t *coap_findOptions(const coap_packet_t *pkt, uint8_t num, uint8_t *count) { // FIXME, options is always sorted, can find faster than this size_t i; const coap_option_t *first = NULL; *count = 0; for (i=0;inumopts;i++) { if (pkt->opts[i].num == num) { if (NULL == first) first = &pkt->opts[i]; (*count)++; } else { if (NULL != first) break; } } return first; } int coap_buffer_to_string(char *strbuf, size_t strbuflen, const coap_buffer_t *buf) { if (buf->len+1 > strbuflen) return COAP_ERR_BUFFER_TOO_SMALL; c_memcpy(strbuf, buf->p, buf->len); strbuf[buf->len] = 0; return 0; } int coap_build(uint8_t *buf, size_t *buflen, const coap_packet_t *pkt) { size_t opts_len = 0, hdr_len = 0, tok_len = 0; size_t i; uint8_t *p = buf; size_t left = *buflen; uint16_t running_delta = 0; hdr_len = coap_buildHeader(&(pkt->hdr), buf, *buflen); p += hdr_len; left -= hdr_len; tok_len = coap_buildToken(&(pkt->tok), &(pkt->hdr), buf, *buflen); p += tok_len; left -= tok_len; for (i=0;inumopts;i++) { uint8_t len, delta = 0; uint16_t optDelta = 0; int rc = 0; if (((size_t)(p-buf)) > *buflen) return COAP_ERR_BUFFER_TOO_SMALL; optDelta = pkt->opts[i].num - running_delta; rc = coap_buildOptionHeader(optDelta, pkt->opts[i].buf.len, p, left); p += rc; left -= rc; c_memcpy(p, pkt->opts[i].buf.p, pkt->opts[i].buf.len); p += pkt->opts[i].buf.len; left -= pkt->opts[i].buf.len; running_delta = pkt->opts[i].num; } opts_len = (p - buf) - 4; // number of bytes used by options if (pkt->payload.len > 0) { if (*buflen < 4 + 1 + pkt->payload.len + opts_len) return COAP_ERR_BUFFER_TOO_SMALL; buf[4 + opts_len] = 0xFF; // payload marker c_memcpy(buf+5 + opts_len, pkt->payload.p, pkt->payload.len); *buflen = opts_len + 5 + pkt->payload.len; } else *buflen = opts_len + 4; return 0; } void coap_option_nibble(uint32_t value, uint8_t *nibble) { if (value<13) { *nibble = (0xFF & value); } else if (value<=0xFF+13) { *nibble = 13; } else if (value<=0xFFFF+269) { *nibble = 14; } } int coap_make_response(coap_rw_buffer_t *scratch, coap_packet_t *pkt, const uint8_t *content, size_t content_len, uint8_t msgid_hi, uint8_t msgid_lo, const coap_buffer_t* tok, coap_responsecode_t rspcode, coap_content_type_t content_type) { pkt->hdr.ver = 0x01; pkt->hdr.t = COAP_TYPE_ACK; pkt->hdr.tkl = 0; pkt->hdr.code = rspcode; pkt->hdr.id[0] = msgid_hi; pkt->hdr.id[1] = msgid_lo; pkt->numopts = 1; // need token in response if (tok) { pkt->hdr.tkl = tok->len; pkt->tok = *tok; } // safe because 1 < MAXOPT pkt->opts[0].num = COAP_OPTION_CONTENT_FORMAT; pkt->opts[0].buf.p = scratch->p; if (scratch->len < 2) return COAP_ERR_BUFFER_TOO_SMALL; scratch->p[0] = ((uint16_t)content_type & 0xFF00) >> 8; scratch->p[1] = ((uint16_t)content_type & 0x00FF); pkt->opts[0].buf.len = 2; pkt->payload.p = content; pkt->payload.len = content_len; return 0; } unsigned int coap_encode_var_bytes(unsigned char *buf, unsigned int val) { unsigned int n, i; for (n = 0, i = val; i && n < sizeof(val); ++n) i >>= 8; i = n; while (i--) { buf[i] = val & 0xff; val >>= 8; } return n; } static uint8_t _token_data[4] = {'n','o','d','e'}; coap_buffer_t the_token = { _token_data, 4 }; static unsigned short message_id; int coap_make_request(coap_rw_buffer_t *scratch, coap_packet_t *pkt, coap_msgtype_t t, coap_method_t m, coap_uri_t *uri, const uint8_t *payload, size_t payload_len) { int res; pkt->hdr.ver = 0x01; pkt->hdr.t = t; pkt->hdr.tkl = 0; pkt->hdr.code = m; pkt->hdr.id[0] = (message_id >> 8) & 0xFF; //msgid_hi; pkt->hdr.id[1] = message_id & 0xFF; //msgid_lo; message_id++; NODE_DBG("message_id: %d.\n", message_id); pkt->numopts = 0; if (the_token.len) { pkt->hdr.tkl = the_token.len; pkt->tok = the_token; } if (scratch->len < 2) // TBD... return COAP_ERR_BUFFER_TOO_SMALL; uint8_t *saved = scratch->p; /* split arg into Uri-* options */ // const char *addr = uri->host.s; // if(uri->host.length && (c_strlen(addr) != uri->host.length || c_memcmp(addr, uri->host.s, uri->host.length) != 0)){ if(uri->host.length){ /* add Uri-Host */ // addr is destination address pkt->opts[pkt->numopts].num = COAP_OPTION_URI_HOST; pkt->opts[pkt->numopts].buf.p = uri->host.s; pkt->opts[pkt->numopts].buf.len = uri->host.length; pkt->numopts++; } if (uri->port != COAP_DEFAULT_PORT) { pkt->opts[pkt->numopts].num = COAP_OPTION_URI_PORT; res = coap_encode_var_bytes(scratch->p, uri->port); pkt->opts[pkt->numopts].buf.len = res; pkt->opts[pkt->numopts].buf.p = scratch->p; scratch->p += res; scratch->len -= res; pkt->numopts++; } if (uri->path.length) { res = coap_split_path(scratch, pkt, uri->path.s, uri->path.length); } if (uri->query.length) { res = coap_split_query(scratch, pkt, uri->query.s, uri->query.length); } pkt->payload.p = payload; pkt->payload.len = payload_len; scratch->p = saved; // save back the pointer. return 0; } // FIXME, if this looked in the table at the path before the method then // it could more easily return 405 errors int coap_handle_req(coap_rw_buffer_t *scratch, const coap_packet_t *inpkt, coap_packet_t *outpkt) { const coap_option_t *opt; int i; uint8_t count; const coap_endpoint_t *ep = endpoints; while(NULL != ep->handler) { if (ep->method != inpkt->hdr.code) goto next; if (NULL != (opt = coap_findOptions(inpkt, COAP_OPTION_URI_PATH, &count))) { // if (count != ep->path->count) if ((count != ep->path->count ) && (count != ep->path->count + 1)) // +1 for /f/[function], /v/[variable] goto next; for (i=0;ipath->count;i++) { if (opt[i].buf.len != c_strlen(ep->path->elems[i])) goto next; if (0 != c_memcmp(ep->path->elems[i], opt[i].buf.p, opt[i].buf.len)) goto next; } // pre-path match! if (count==ep->path->count+1 && ep->user_entry == NULL) goto next; return ep->handler(ep, scratch, inpkt, outpkt, inpkt->hdr.id[0], inpkt->hdr.id[1]); } next: ep++; } coap_make_response(scratch, outpkt, NULL, 0, inpkt->hdr.id[0], inpkt->hdr.id[1], &inpkt->tok, COAP_RSPCODE_NOT_FOUND, COAP_CONTENTTYPE_NONE); return 0; } void coap_setup(void) { message_id = (unsigned short)os_random(); // calculate only once } inline int check_token(coap_packet_t *pkt) { return pkt->tok.len == the_token.len && c_memcmp(pkt->tok.p, the_token.p, the_token.len) == 0; }