15#if DETWS_ENABLE_COAP_OBSERVE
23 static constexpr uint8_t COAP_OPT_OBSERVE = 6;
24 static constexpr uint8_t COAP_OPT_URI_PATH = 11;
25 static constexpr uint8_t COAP_OPT_CONTENT_FORMAT = 12;
26 static constexpr uint8_t COAP_OPT_URI_QUERY = 15;
27 static constexpr uint8_t COAP_OPT_BLOCK2 = 23;
28 static constexpr uint8_t COAP_OPT_BLOCK1 = 27;
31static const uint8_t COAP_PAYLOAD_MARKER = 0xFF;
32static const size_t COAP_MAX_TOKEN = 8;
45#if DETWS_ENABLE_COAP_OBSERVE
72#if DETWS_ENABLE_COAP_OBSERVE
76 int last_observe = -1;
77 uint8_t last_method = 0;
78 uint8_t last_token[8];
82#if DETWS_ENABLE_COAP_BLOCK
92void coap_server_init()
95 memset(s_coap.res, 0,
sizeof(s_coap.res));
96#if DETWS_ENABLE_COAP_BLOCK
102bool coap_server_add_resource(
const char *path, uint8_t methods, CoapHandler handler)
106 s_coap.res[s_coap.res_count].path = path;
107 s_coap.res[s_coap.res_count].methods = methods;
108 s_coap.res[s_coap.res_count].handler = handler;
118static uint32_t opt_uint(
const uint8_t *v,
size_t n)
121 for (
size_t i = 0; i < n; i++)
129static bool seg_append(
char *buf,
size_t cap,
size_t *len,
char sep,
const uint8_t *seg,
size_t seglen)
131 size_t need = (sep ? 1u : 0u) + seglen + 1u;
132 if (*len + need > cap)
136 memcpy(buf + *len, seg, seglen);
142static const CoapResource *find_resource(
const CoapCtx &c,
const char *path)
144 for (
size_t i = 0; i < c.res_count; i++)
145 if (strcmp(c.res[i].path, path) == 0)
152static size_t emit_header(uint8_t *out,
size_t cap, CoapType type, uint8_t code, uint16_t mid,
const uint8_t *token,
155 if (cap < (
size_t)(4 + tkl))
157 out[0] = (uint8_t)((1 << 6) | ((uint8_t)type << 4) | tkl);
159 out[2] = (uint8_t)(mid >> 8);
160 out[3] = (uint8_t)(mid & 0xFF);
162 memcpy(out + 4, token, tkl);
163 return (
size_t)(4 + tkl);
168static uint8_t enc_uint_minimal(uint32_t v, uint8_t out[3])
172 out[k++] = (uint8_t)(v >> 16);
174 out[k++] = (uint8_t)(v >> 8);
176 out[k++] = (uint8_t)v;
185static size_t append_opt(uint8_t *resp,
size_t cap,
size_t n, uint32_t *last_opt, uint32_t opt_num,
const uint8_t *val,
188 uint32_t delta = opt_num - *last_opt;
189 uint8_t dn = (uint8_t)(delta < 13 ? delta : 13);
190 bool ext = delta >= 13;
191 if (n + 1u + (ext ? 1u : 0u) + vlen > cap)
193 resp[n++] = (uint8_t)((dn << 4) | vlen);
195 resp[n++] = (uint8_t)(delta - 13);
196 for (uint8_t i = 0; i < vlen; i++)
208static size_t emit_options_payload(uint8_t *resp,
size_t cap,
size_t n, uint8_t code, int32_t observe_seq,
209 CoapContentFormat content_format, int32_t block2_val, int32_t block1_val,
210 const uint8_t *payload,
size_t payload_len)
212 uint32_t last_opt = 0;
215 if (observe_seq >= 0 && (code >> 5) == 2)
216 n = append_opt(resp, cap, n, &last_opt, CoapOpt::COAP_OPT_OBSERVE, v,
217 enc_uint_minimal((uint32_t)observe_seq & 0xFFFFFF, v));
219 if (content_format != CoapContentFormat::COAP_CF_NONE)
220 n = append_opt(resp, cap, n, &last_opt, CoapOpt::COAP_OPT_CONTENT_FORMAT, v,
221 enc_uint_minimal((uint16_t)content_format, v));
223#if DETWS_ENABLE_COAP_BLOCK
225 n = append_opt(resp, cap, n, &last_opt, CoapOpt::COAP_OPT_BLOCK2, v, enc_uint_minimal((uint32_t)block2_val, v));
227 n = append_opt(resp, cap, n, &last_opt, CoapOpt::COAP_OPT_BLOCK1, v, enc_uint_minimal((uint32_t)block1_val, v));
235 if (n + 1 + payload_len > cap)
237 resp[n++] = COAP_PAYLOAD_MARKER;
238 memcpy(resp + n, payload, payload_len);
248size_t coap_server_process_ex(
const uint8_t *req,
size_t req_len, uint8_t *resp,
size_t resp_cap, int32_t observe_seq)
253 uint8_t ver = (req[0] >> 6) & 0x03;
254 CoapType type = (CoapType)((req[0] >> 4) & 0x03);
255 uint8_t tkl = req[0] & 0x0F;
256 uint8_t code = req[1];
257 uint16_t mid = (uint16_t)((req[2] << 8) | req[3]);
261 if (type != CoapType::COAP_TYPE_CON && type != CoapType::COAP_TYPE_NON)
263 CoapType rsp_type = (type == CoapType::COAP_TYPE_CON) ? CoapType::COAP_TYPE_ACK : CoapType::COAP_TYPE_NON;
267 if (ver != 1 || tkl > COAP_MAX_TOKEN)
268 return (type == CoapType::COAP_TYPE_CON)
269 ? emit_header(resp, resp_cap, CoapType::COAP_TYPE_RST, 0, mid,
nullptr, 0)
272 const uint8_t *p = req + 4;
273 const uint8_t *end = req + req_len;
275 return (type == CoapType::COAP_TYPE_CON)
276 ? emit_header(resp, resp_cap, CoapType::COAP_TYPE_RST, 0, mid,
nullptr, 0)
278 const uint8_t *token = p;
283 return (type == CoapType::COAP_TYPE_CON)
284 ? emit_header(resp, resp_cap, CoapType::COAP_TYPE_RST, 0, mid,
nullptr, 0)
287#if DETWS_ENABLE_COAP_OBSERVE
288 s_coap.last_observe = -1;
289 s_coap.last_method = code;
290 s_coap.last_tkl = tkl;
292 memcpy(s_coap.last_token, token, tkl);
297 size_t query_len = 0;
298 s_coap.path[0] =
'\0';
299 s_coap.query[0] =
'\0';
300 CoapContentFormat req_cf = CoapContentFormat::COAP_CF_NONE;
301 const uint8_t *payload =
nullptr;
302 size_t payload_len = 0;
303 uint32_t opt_num = 0;
305 bool bad_option =
false;
306#if DETWS_ENABLE_COAP_BLOCK
307 int32_t req_block1 = -1;
308 int32_t req_block2 = -1;
314 if (b == COAP_PAYLOAD_MARKER)
317 payload_len = (size_t)(end - p);
320 uint32_t delta = b >> 4;
321 uint32_t olen = b & 0x0F;
322 if (delta == 15 || olen == 15)
335 delta = (uint32_t)(*p++) + 13;
337 else if (delta == 14)
344 delta = (uint32_t)((p[0] << 8) | p[1]) + 269;
354 olen = (uint32_t)(*p++) + 13;
363 olen = (uint32_t)((p[0] << 8) | p[1]) + 269;
372 const uint8_t *val = p;
377 case CoapOpt::COAP_OPT_URI_PATH:
378 if (!seg_append(s_coap.path,
sizeof(s_coap.path), &path_len,
'/', val, olen))
381 case CoapOpt::COAP_OPT_URI_QUERY:
382 if (!seg_append(s_coap.query,
sizeof(s_coap.query), &query_len, query_len ?
'&' :
'\0', val, olen))
385 case CoapOpt::COAP_OPT_CONTENT_FORMAT:
386 req_cf = (CoapContentFormat)opt_uint(val, olen);
388#if DETWS_ENABLE_COAP_OBSERVE
389 case CoapOpt::COAP_OPT_OBSERVE:
390 s_coap.last_observe = (int)opt_uint(val, olen);
393#if DETWS_ENABLE_COAP_BLOCK
394 case CoapOpt::COAP_OPT_BLOCK1:
398 req_block1 = (int32_t)opt_uint(val, olen);
400 case CoapOpt::COAP_OPT_BLOCK2:
404 req_block2 = (int32_t)opt_uint(val, olen);
421 return emit_header(resp, resp_cap, rsp_type, (uint8_t)CoapResponseCode::COAP_RSP_BAD_REQUEST, mid, token, tkl);
423 return emit_header(resp, resp_cap, rsp_type, (uint8_t)CoapResponseCode::COAP_RSP_BAD_OPTION, mid, token, tkl);
427 s_coap.path[0] =
'/';
428 s_coap.path[1] =
'\0';
434 if ((code >> 5) != 0 || code < (uint8_t)CoapMethod::COAP_GET || code > (uint8_t)CoapMethod::COAP_DELETE)
435 return emit_header(resp, resp_cap, rsp_type, (uint8_t)CoapResponseCode::COAP_RSP_METHOD_NOT_ALLOWED, mid, token,
441 cresp.code = (uint8_t)CoapResponseCode::COAP_RSP_CONTENT;
442 cresp.content_format = CoapContentFormat::COAP_CF_NONE;
443 cresp.payload = s_coap.pl;
444 cresp.payload_cap =
sizeof(s_coap.pl);
445 cresp.payload_len = 0;
446 int32_t block1_echo = -1;
450 if (strcmp(s_coap.path,
"/.well-known/core") == 0)
452 if (code != (uint8_t)CoapMethod::COAP_GET)
453 return emit_header(resp, resp_cap, rsp_type, (uint8_t)CoapResponseCode::COAP_RSP_METHOD_NOT_ALLOWED, mid,
456 for (
size_t i = 0; i < s_coap.res_count; i++)
458 const char *rpath = s_coap.res[i].path;
459 size_t plen = strnlen(rpath,
sizeof(s_coap.pl));
460 size_t need = (pl ? 1u : 0u) + 2u + plen;
461 if (pl + need >
sizeof(s_coap.pl))
464 s_coap.pl[pl++] =
',';
465 s_coap.pl[pl++] =
'<';
466 memcpy(s_coap.pl + pl, rpath, plen);
468 s_coap.pl[pl++] =
'>';
470 cresp.content_format = CoapContentFormat::COAP_CF_LINK;
471 cresp.payload_len = pl;
475 const CoapResource *r = find_resource(s_coap, s_coap.path);
477 return emit_header(resp, resp_cap, rsp_type, (uint8_t)CoapResponseCode::COAP_RSP_NOT_FOUND, mid, token,
479 if (!(r->methods & (1u << code)))
480 return emit_header(resp, resp_cap, rsp_type, (uint8_t)CoapResponseCode::COAP_RSP_METHOD_NOT_ALLOWED, mid,
483 const uint8_t *eff_payload = payload;
484 size_t eff_payload_len = payload_len;
486#if DETWS_ENABLE_COAP_BLOCK
488 if (req_block1 >= 0 && (code == (uint8_t)CoapMethod::COAP_POST || code == (uint8_t)CoapMethod::COAP_PUT))
490 uint32_t b = (uint32_t)req_block1;
491 uint32_t num = b >> 4;
492 uint8_t more = (uint8_t)((b >> 3) & 1);
493 uint8_t szx = (uint8_t)(b & 7);
495 return emit_header(resp, resp_cap, rsp_type, (uint8_t)CoapResponseCode::COAP_RSP_BAD_OPTION, mid, token,
497 uint32_t bsize = 1u << (szx + 4);
505 if (szx != s_coap.b1_szx || (
size_t)num * bsize != s_coap.b1_len)
508 return emit_header(resp, resp_cap, rsp_type, (uint8_t)CoapResponseCode::COAP_RSP_REQUEST_INCOMPLETE,
511 if (s_coap.b1_len + payload_len >
sizeof(s_coap.b1))
514 return emit_header(resp, resp_cap, rsp_type, (uint8_t)CoapResponseCode::COAP_RSP_REQUEST_TOO_LARGE, mid,
518 memcpy(s_coap.b1 + s_coap.b1_len, payload, payload_len);
519 s_coap.b1_len += payload_len;
525 size_t cn = emit_header(resp, resp_cap, rsp_type, (uint8_t)CoapResponseCode::COAP_RSP_CONTINUE, mid,
529 return emit_options_payload(resp, resp_cap, cn, (uint8_t)CoapResponseCode::COAP_RSP_CONTINUE, -1,
530 CoapContentFormat::COAP_CF_NONE, -1,
531 (int32_t)((num << 4) | (1u << 3) | szx),
nullptr, 0);
534 eff_payload = s_coap.b1;
535 eff_payload_len = s_coap.b1_len;
536 block1_echo = (int32_t)((num << 4) | szx);
541 creq.method = (CoapMethod)code;
542 creq.path = s_coap.path;
543 creq.query = s_coap.query;
544 creq.payload = eff_payload;
545 creq.payload_len = eff_payload_len;
546 creq.content_format = req_cf;
548 r->handler(&creq, &cresp);
549 if (cresp.payload_len >
sizeof(s_coap.pl))
550 cresp.payload_len =
sizeof(s_coap.pl);
553 int32_t block2_echo = -1;
555#if DETWS_ENABLE_COAP_BLOCK
556 if (block1_echo >= 0)
561 if ((cresp.code >> 5) == 2)
565 bool block_wise =
false;
568 uint32_t b = (uint32_t)req_block2;
570 szx = (uint8_t)(b & 7);
572 return emit_header(resp, resp_cap, rsp_type, (uint8_t)CoapResponseCode::COAP_RSP_BAD_OPTION, mid, token,
578 else if (cresp.payload_len > (
size_t)(1u << (szx + 4)))
584 uint32_t bsize = 1u << (szx + 4);
585 size_t off = (size_t)num * bsize;
587 if (off > cresp.payload_len || (off == cresp.payload_len && num > 0))
588 return emit_header(resp, resp_cap, rsp_type, (uint8_t)CoapResponseCode::COAP_RSP_BAD_REQUEST, mid,
590 size_t this_len = cresp.payload_len - off;
592 if (this_len > bsize)
597 block2_echo = (int32_t)((num << 4) | ((uint32_t)more << 3) | szx);
598 cresp.payload += off;
599 cresp.payload_len = this_len;
605 size_t n = emit_header(resp, resp_cap, rsp_type, cresp.code, mid, token, tkl);
608 return emit_options_payload(resp, resp_cap, n, cresp.code, observe_seq, cresp.content_format, block2_echo,
609 block1_echo, cresp.payload, cresp.payload_len);
612size_t coap_server_process(
const uint8_t *req,
size_t req_len, uint8_t *resp,
size_t resp_cap)
614 return coap_server_process_ex(req, req_len, resp, resp_cap, -1);
621#if DETWS_ENABLE_COAP_OBSERVE
626static int find_resource_index(
const CoapCtx &c,
const char *path)
628 for (
size_t i = 0; i < c.res_count; i++)
629 if (strcmp(c.res[i].path, path) == 0)
634static bool same_token(
const CoapObserver *o,
const uint8_t *token, uint8_t tkl)
636 return o->tkl == tkl && (tkl == 0 || memcmp(o->token, token, tkl) == 0);
640static int obs_register(
const char *ip, uint16_t port,
const uint8_t *token, uint8_t tkl,
int res_idx)
643 if (s_coap.obs[i].active && s_coap.obs[i].res_idx == res_idx && s_coap.obs[i].port == port &&
644 same_token(&s_coap.obs[i], token, tkl) && strcmp(s_coap.obs[i].ip, ip) == 0)
647 if (!s_coap.obs[i].active)
649 s_coap.obs[i].active =
true;
650 strncpy(s_coap.obs[i].ip, ip,
sizeof(s_coap.obs[i].ip) - 1);
651 s_coap.obs[i].ip[
sizeof(s_coap.obs[i].ip) - 1] =
'\0';
652 s_coap.obs[i].port = port;
653 s_coap.obs[i].tkl = tkl;
655 memcpy(s_coap.obs[i].token, token, tkl);
656 s_coap.obs[i].res_idx = res_idx;
657 s_coap.obs[i].seq = 1;
665static void obs_remove(
const char *ip, uint16_t port,
const uint8_t *token, uint8_t tkl)
668 if (s_coap.obs[i].active && s_coap.obs[i].port == port && strcmp(s_coap.obs[i].ip, ip) == 0 &&
669 (token ==
nullptr || same_token(&s_coap.obs[i], token, tkl)))
670 s_coap.obs[i].active =
false;
673void coap_notify(
const char *path)
675 int ridx = find_resource_index(s_coap, path);
680 if (!s_coap.obs[i].active || s_coap.obs[i].res_idx != ridx)
684 creq.method = CoapMethod::COAP_GET;
685 creq.path = s_coap.res[ridx].path;
687 creq.payload =
nullptr;
688 creq.payload_len = 0;
689 creq.content_format = CoapContentFormat::COAP_CF_NONE;
691 cresp.code = (uint8_t)CoapResponseCode::COAP_RSP_CONTENT;
692 cresp.content_format = CoapContentFormat::COAP_CF_NONE;
693 cresp.payload = s_coap.pl;
694 cresp.payload_cap =
sizeof(s_coap.pl);
695 cresp.payload_len = 0;
696 s_coap.res[ridx].handler(&creq, &cresp);
697 if (cresp.payload_len >
sizeof(s_coap.pl))
698 cresp.payload_len =
sizeof(s_coap.pl);
702 s_coap.obs[i].seq = (s_coap.obs[i].seq + 1) & 0xFFFFFF;
703 size_t n = emit_header(s_coap.tx,
sizeof(s_coap.tx), CoapType::COAP_TYPE_NON, cresp.code, mid,
704 s_coap.obs[i].token, s_coap.obs[i].tkl);
706 n = emit_options_payload(s_coap.tx,
sizeof(s_coap.tx), n, cresp.code, (int32_t)s_coap.obs[i].seq,
707 cresp.content_format, -1, -1, cresp.payload, cresp.payload_len);
709 s_coap.obs[i].active =
false;
713static void coap_udp_handler(
const uint8_t *data,
size_t len,
struct DetUdpPeer *peer,
void *ctx)
721 if (len >= 1 && ((data[0] >> 4) & 0x03) == (uint8_t)CoapType::COAP_TYPE_RST)
724 obs_remove(ip, pport,
nullptr, 0);
728 size_t rn = coap_server_process_ex(data, len, s_coap.tx,
sizeof(s_coap.tx), -1);
732 if (s_coap.last_method == (uint8_t)CoapMethod::COAP_GET && s_coap.last_observe == 0 && have_peer)
734 int ridx = find_resource_index(s_coap, s_coap.path);
737 int slot = obs_register(ip, pport, s_coap.last_token, s_coap.last_tkl, ridx);
742 coap_server_process_ex(data, len, s_coap.tx,
sizeof(s_coap.tx), (int32_t)s_coap.obs[slot].seq);
748 else if (s_coap.last_observe == 1 && have_peer)
750 obs_remove(ip, pport, s_coap.last_token, s_coap.last_tkl);
756void coap_server_begin_udp(uint16_t port)
760 s_coap.obs[i].active =
false;
766static void coap_udp_handler(
const uint8_t *data,
size_t len,
struct DetUdpPeer *peer,
void *ctx)
769 size_t rn = coap_server_process(data, len, s_coap.tx,
sizeof(s_coap.tx));
774void coap_server_begin_udp(uint16_t port)
#define DETWS_COAP_BLOCK1_MAX
Reassembly buffer for a block-wise (Block1) request upload, in bytes.
#define DETWS_COAP_MAX_PAYLOAD
Maximum CoAP request/response payload in bytes.
#define DETWS_COAP_OBSERVE_PORT
Default UDP port the CoAP observe transport notifies from (IANA well-known 5683).
#define DETWS_COAP_MAX_RESOURCES
Maximum registered CoAP resources (the server's fixed routing table).
#define DETWS_COAP_MAX_PATH
Maximum reconstructed Uri-Path length, including separators and the leading '/'.
#define DETWS_COAP_MAX_QUERY
Maximum reconstructed Uri-Query length (segments joined by '&').
#define DETWS_COAP_MSG_BUF_SIZE
Static response-datagram buffer for the CoAP UDP server.
#define DETWS_COAP_MAX_OBSERVERS
Maximum simultaneous CoAP observers (one slot per observed resource per client).
#define DETWS_COAP_BLOCK_SZX_MAX
Largest block-size exponent (SZX) the server will use: block size = 2^(SZX+4) bytes,...
Pluggable monotonic clock for all library timing.
uint32_t detws_millis(void)
The library's monotonic time at 1000 Hz (milliseconds).
Zero-heap CoAP server (RFC 7252): message codec + a fixed resource table.
bool det_udp_peer_addr(const struct DetUdpPeer *peer, char *ip_out, size_t ip_cap, uint16_t *port_out)
Copy a received peer's source IPv4 address (dotted-quad) and port out.
bool det_udp_send(struct DetUdpPeer *peer, const uint8_t *data, size_t len)
Send a datagram back to the peer captured during the handler call.
bool det_udp_listen(uint16_t port, DetUdpHandler handler, void *ctx)
Bind a UDP port and route incoming datagrams to handler.
bool det_udp_listener_sendto(uint16_t listen_port, const char *dst_ip, uint16_t dst_port, const uint8_t *data, size_t len)
Send a datagram from the listener bound on listen_port to an address.
Layer 4 (Transport) - connectionless UDP datagram service.