diff --git a/examples/gcoap/gcoap_cli.c b/examples/gcoap/gcoap_cli.c index b3aca2db04f3..a14e936a2533 100644 --- a/examples/gcoap/gcoap_cli.c +++ b/examples/gcoap/gcoap_cli.c @@ -302,7 +302,7 @@ static size_t _send(uint8_t *buf, size_t len, char *addr_str, char *port_str) int gcoap_cli_cmd(int argc, char **argv) { /* Ordered like the RFC method code numbers, but off by 1. GET is code 0. */ - char *method_codes[] = {"get", "post", "put"}; + char *method_codes[] = {"ping", "get", "post", "put"}; uint8_t buf[CONFIG_GCOAP_PDU_BUF_SIZE]; coap_pkt_t pdu; size_t len; @@ -351,7 +351,7 @@ int gcoap_cli_cmd(int argc, char **argv) return 1; } - /* if not 'info' and 'proxy', must be a method code */ + /* if not 'info' and 'proxy', must be a method code or ping */ int code_pos = -1; for (size_t i = 0; i < ARRAY_SIZE(method_codes); i++) { if (strcmp(argv[1], method_codes[i]) == 0) { @@ -363,32 +363,33 @@ int gcoap_cli_cmd(int argc, char **argv) } /* parse options */ - int apos = 2; /* position of address argument */ - unsigned msg_type = COAP_TYPE_NON; + int apos = 2; /* position of address argument */ + /* ping must be confirmable */ + unsigned msg_type = (!code_pos ? COAP_TYPE_CON : COAP_TYPE_NON); if (argc > apos && strcmp(argv[apos], "-c") == 0) { msg_type = COAP_TYPE_CON; apos++; } - /* - * "get" (code_pos 0) must have exactly apos + 3 arguments - * while "post" (code_pos 1) and "put" (code_pos 2) and must have exactly - * apos + 4 arguments - */ - if (((argc == apos + 3) && (code_pos == 0)) || - ((argc == apos + 4) && (code_pos != 0))) { + if (((argc == apos + 2) && (code_pos == 0)) || /* ping */ + ((argc == apos + 3) && (code_pos == 1)) || /* get */ + ((argc == apos + 4) && (code_pos > 1))) { /* post or put */ - char *uri = argv[apos+2]; - int uri_len = strlen(argv[apos+2]); + char *uri = NULL; + int uri_len = 0; + if (code_pos) { + uri = argv[apos+2]; + uri_len = strlen(argv[apos+2]); + } if (_proxied) { uri_len = snprintf(proxy_uri, 64, "coap://[%s]:%s%s", argv[apos], argv[apos+1], uri); uri = proxy_uri; - gcoap_req_init(&pdu, &buf[0], CONFIG_GCOAP_PDU_BUF_SIZE, code_pos+1, NULL); + gcoap_req_init(&pdu, &buf[0], CONFIG_GCOAP_PDU_BUF_SIZE, code_pos, NULL); } else{ - gcoap_req_init(&pdu, &buf[0], CONFIG_GCOAP_PDU_BUF_SIZE, code_pos+1, uri); + gcoap_req_init(&pdu, &buf[0], CONFIG_GCOAP_PDU_BUF_SIZE, code_pos, uri); } coap_hdr_set_type(pdu.hdr, msg_type); @@ -450,13 +451,14 @@ int gcoap_cli_cmd(int argc, char **argv) else { printf("usage: %s [-c] [%%iface] [data]\n", argv[0]); + printf(" %s ping [%%iface] \n", argv[0]); printf("Options\n"); printf(" -c Send confirmably (defaults to non-confirmable)\n"); return 1; } end: - printf("usage: %s \n", argv[0]); + printf("usage: %s \n", argv[0]); return 1; } diff --git a/sys/include/net/gcoap.h b/sys/include/net/gcoap.h index a0cdb03c3203..d2c104a65e40 100644 --- a/sys/include/net/gcoap.h +++ b/sys/include/net/gcoap.h @@ -734,10 +734,14 @@ void gcoap_register_listener(gcoap_listener_t *listener); /** * @brief Initializes a CoAP request PDU on a buffer. * + * If @p code is COAP_CODE_EMPTY, prepares a complete "CoAP ping" 4 byte empty + * message request, ready to send. + * * @param[out] pdu Request metadata * @param[out] buf Buffer containing the PDU * @param[in] len Length of the buffer - * @param[in] code Request code, one of COAP_METHOD_XXX + * @param[in] code Request code, one of COAP_METHOD_XXX or COAP_CODE_EMPTY + * to ping * @param[in] path Resource path, may be NULL * * @pre @p path must start with `/` if not NULL diff --git a/sys/net/application_layer/gcoap/gcoap.c b/sys/net/application_layer/gcoap/gcoap.c index 9a8f03e8524f..5c3bd4ba9b37 100644 --- a/sys/net/application_layer/gcoap/gcoap.c +++ b/sys/net/application_layer/gcoap/gcoap.c @@ -150,16 +150,29 @@ static void _on_sock_evt(sock_udp_t *sock, sock_async_flags_t type, void *arg) return; } - if (pdu.hdr->code == COAP_CODE_EMPTY) { - DEBUG("gcoap: empty messages not handled yet\n"); - return; - } - /* validate class and type for incoming */ switch (coap_get_code_class(&pdu)) { - /* incoming request */ + /* incoming request or empty */ case COAP_CLASS_REQ: - if (coap_get_type(&pdu) == COAP_TYPE_NON + if (coap_get_code_raw(&pdu) == COAP_CODE_EMPTY) { + /* ping request */ + if (coap_get_type(&pdu) == COAP_TYPE_CON) { + coap_hdr_set_type(pdu.hdr, COAP_TYPE_RST); + + ssize_t bytes = sock_udp_send(sock, _listen_buf, + sizeof(coap_hdr_t), &remote); + if (bytes <= 0) { + DEBUG("gcoap: ping response failed: %d\n", (int)bytes); + } + } else if (coap_get_type(&pdu) == COAP_TYPE_NON) { + DEBUG("gcoap: empty NON msg\n"); + } + else { + goto empty_as_response; + } + } + /* normal request */ + else if (coap_get_type(&pdu) == COAP_TYPE_NON || coap_get_type(&pdu) == COAP_TYPE_CON) { size_t pdu_len = _handle_req(&pdu, _listen_buf, sizeof(_listen_buf), &remote); @@ -176,6 +189,10 @@ static void _on_sock_evt(sock_udp_t *sock, sock_async_flags_t type, void *arg) } break; +empty_as_response: + DEBUG("gcoap: empty ack/reset not handled yet\n"); + return; + /* incoming response */ case COAP_CLASS_SUCCESS: case COAP_CLASS_CLIENT_FAILURE: @@ -640,22 +657,28 @@ int gcoap_req_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, pdu->hdr = (coap_hdr_t *)buf; /* generate token */ -#if CONFIG_GCOAP_TOKENLEN - uint8_t token[CONFIG_GCOAP_TOKENLEN]; - for (size_t i = 0; i < CONFIG_GCOAP_TOKENLEN; i += 4) { - uint32_t rand = random_uint32(); - memcpy(&token[i], - &rand, - (CONFIG_GCOAP_TOKENLEN - i >= 4) ? 4 : CONFIG_GCOAP_TOKENLEN - i); - } uint16_t msgid = (uint16_t)atomic_fetch_add(&_coap_state.next_message_id, 1); - ssize_t res = coap_build_hdr(pdu->hdr, COAP_TYPE_NON, &token[0], - CONFIG_GCOAP_TOKENLEN, code, msgid); + ssize_t res; + if (code) { +#if CONFIG_GCOAP_TOKENLEN + uint8_t token[CONFIG_GCOAP_TOKENLEN]; + for (size_t i = 0; i < CONFIG_GCOAP_TOKENLEN; i += 4) { + uint32_t rand = random_uint32(); + memcpy(&token[i], + &rand, + (CONFIG_GCOAP_TOKENLEN - i >= 4) ? 4 : CONFIG_GCOAP_TOKENLEN - i); + } + res = coap_build_hdr(pdu->hdr, COAP_TYPE_NON, &token[0], + CONFIG_GCOAP_TOKENLEN, code, msgid); #else - uint16_t msgid = (uint16_t)atomic_fetch_add(&_coap_state.next_message_id, 1); - ssize_t res = coap_build_hdr(pdu->hdr, COAP_TYPE_NON, NULL, - CONFIG_GCOAP_TOKENLEN, code, msgid); + res = coap_build_hdr(pdu->hdr, COAP_TYPE_NON, NULL, + CONFIG_GCOAP_TOKENLEN, code, msgid); #endif + } + else { + /* ping request */ + res = coap_build_hdr(pdu->hdr, COAP_TYPE_CON, NULL, 0, code, msgid); + } coap_pkt_init(pdu, buf, len - CONFIG_GCOAP_REQ_OPTIONS_BUF, res); if (path != NULL) { diff --git a/sys/net/application_layer/nanocoap/nanocoap.c b/sys/net/application_layer/nanocoap/nanocoap.c index 7498e38c3521..4611e1097327 100644 --- a/sys/net/application_layer/nanocoap/nanocoap.c +++ b/sys/net/application_layer/nanocoap/nanocoap.c @@ -69,6 +69,15 @@ int coap_parse(coap_pkt_t *pkt, uint8_t *buf, size_t len) pkt->payload = NULL; pkt->payload_len = 0; + if (len < sizeof(coap_hdr_t)) { + DEBUG("msg too short\n"); + return -EBADMSG; + } + else if ((coap_get_code_raw(pkt) == 0) && (len > sizeof(coap_hdr_t))) { + DEBUG("empty msg too long\n"); + return -EBADMSG; + } + /* token value (tkl bytes) */ if (coap_get_token_len(pkt)) { pkt->token = pkt_pos; diff --git a/tests/unittests/tests-gcoap/tests-gcoap.c b/tests/unittests/tests-gcoap/tests-gcoap.c index c458046ac891..10047629dc50 100644 --- a/tests/unittests/tests-gcoap/tests-gcoap.c +++ b/tests/unittests/tests-gcoap/tests-gcoap.c @@ -203,6 +203,28 @@ static void test_gcoap__client_get_path_defer(void) TEST_ASSERT_EQUAL_STRING(&path[0], &uri[0]); } +/* + * Validate client CoAP ping empty message request. + */ +static void test_gcoap__client_ping(void) +{ + uint8_t buf[CONFIG_GCOAP_PDU_BUF_SIZE]; + coap_pkt_t pdu; + int res; + + res = gcoap_req_init(&pdu, buf, CONFIG_GCOAP_PDU_BUF_SIZE, COAP_CODE_EMPTY, + NULL); + + TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT_EQUAL_INT(COAP_CODE_EMPTY, coap_get_code(&pdu)); + TEST_ASSERT_EQUAL_INT(COAP_TYPE_CON, coap_get_type(&pdu)); + TEST_ASSERT_EQUAL_INT(0, coap_get_token_len(&pdu)); + + /* confirm length */ + res = coap_opt_finish(&pdu, COAP_OPT_FINISH_NONE); + TEST_ASSERT_EQUAL_INT(4, res); +} + /* * Helper for server_get tests below. * Request from libcoap example for gcoap_cli /cli/stats resource @@ -371,6 +393,7 @@ Test *tests_gcoap_tests(void) new_TestFixture(test_gcoap__client_put_req), new_TestFixture(test_gcoap__client_put_req_overfill), new_TestFixture(test_gcoap__client_get_path_defer), + new_TestFixture(test_gcoap__client_ping), new_TestFixture(test_gcoap__server_get_req), new_TestFixture(test_gcoap__server_get_resp), new_TestFixture(test_gcoap__server_con_req), diff --git a/tests/unittests/tests-nanocoap/tests-nanocoap.c b/tests/unittests/tests-nanocoap/tests-nanocoap.c index c98ff2ffc1f4..8f564e451016 100644 --- a/tests/unittests/tests-nanocoap/tests-nanocoap.c +++ b/tests/unittests/tests-nanocoap/tests-nanocoap.c @@ -713,6 +713,38 @@ static void test_nanocoap__options_get_opaque(void) TEST_ASSERT_EQUAL_INT(-ENOENT, optlen); } +/* + * Validates empty message parsing. + */ +static void test_nanocoap__empty(void) +{ + /* first four bytes are valid empty msg; include 5th byte for test */ + static uint8_t pkt_data[] = { + 0x40, 0x00, 0xAB, 0xCD, 0x00 + }; + + uint16_t msgid = 0xABCD; + + coap_pkt_t pkt; + int res = coap_parse(&pkt, pkt_data, 4); + + TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT_EQUAL_INT(0, coap_get_code_raw(&pkt)); + TEST_ASSERT_EQUAL_INT(msgid, coap_get_id(&pkt)); + TEST_ASSERT_EQUAL_INT(0, coap_get_token_len(&pkt)); + TEST_ASSERT_EQUAL_INT(0, pkt.payload_len); + + /* too short */ + memset(&pkt, 0, sizeof(coap_pkt_t)); + res = coap_parse(&pkt, pkt_data, 3); + TEST_ASSERT_EQUAL_INT(-EBADMSG, res); + + /* too long */ + memset(&pkt, 0, sizeof(coap_pkt_t)); + res = coap_parse(&pkt, pkt_data, 5); + TEST_ASSERT_EQUAL_INT(-EBADMSG, res); +} + Test *tests_nanocoap_tests(void) { EMB_UNIT_TESTFIXTURES(fixtures) { @@ -736,6 +768,7 @@ Test *tests_nanocoap_tests(void) new_TestFixture(test_nanocoap__server_reply_simple_con), new_TestFixture(test_nanocoap__server_option_count_overflow_check), new_TestFixture(test_nanocoap__server_option_count_overflow), + new_TestFixture(test_nanocoap__empty), }; EMB_UNIT_TESTCALLER(nanocoap_tests, NULL, NULL, fixtures);