coap_client_example_main.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. /* CoAP client Example
  2. This example code is in the Public Domain (or CC0 licensed, at your option.)
  3. Unless required by applicable law or agreed to in writing, this
  4. software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  5. CONDITIONS OF ANY KIND, either express or implied.
  6. */
  7. /*
  8. * WARNING
  9. * libcoap is not multi-thread safe, so only this thread must make any coap_*()
  10. * calls. Any external (to this thread) data transmitted in/out via libcoap
  11. * therefore has to be passed in/out by xQueue*() via this thread.
  12. */
  13. #include <string.h>
  14. #include <sys/socket.h>
  15. #include <netdb.h>
  16. #include <sys/param.h>
  17. #include "freertos/FreeRTOS.h"
  18. #include "freertos/task.h"
  19. #include "freertos/event_groups.h"
  20. #include "esp_log.h"
  21. #include "esp_wifi.h"
  22. #include "esp_event.h"
  23. #include "nvs_flash.h"
  24. #include "protocol_examples_common.h"
  25. #include "coap3/coap.h"
  26. #define COAP_DEFAULT_TIME_SEC 60
  27. /* The examples use simple Pre-Shared-Key configuration that you can set via
  28. 'idf.py menuconfig'.
  29. If you'd rather not, just change the below entries to strings with
  30. the config you want - ie #define EXAMPLE_COAP_PSK_KEY "some-agreed-preshared-key"
  31. Note: PSK will only be used if the URI is prefixed with coaps://
  32. instead of coap:// and the PSK must be one that the server supports
  33. (potentially associated with the IDENTITY)
  34. */
  35. #define EXAMPLE_COAP_PSK_KEY CONFIG_EXAMPLE_COAP_PSK_KEY
  36. #define EXAMPLE_COAP_PSK_IDENTITY CONFIG_EXAMPLE_COAP_PSK_IDENTITY
  37. /* The examples use uri Logging Level that
  38. you can set via 'idf.py menuconfig'.
  39. If you'd rather not, just change the below entry to a value
  40. that is between 0 and 7 with
  41. the config you want - ie #define EXAMPLE_COAP_LOG_DEFAULT_LEVEL 7
  42. */
  43. #define EXAMPLE_COAP_LOG_DEFAULT_LEVEL CONFIG_COAP_LOG_DEFAULT_LEVEL
  44. /* The examples use uri "coap://californium.eclipseprojects.io" that
  45. you can set via the project configuration (idf.py menuconfig)
  46. If you'd rather not, just change the below entries to strings with
  47. the config you want - ie #define COAP_DEFAULT_DEMO_URI "coaps://californium.eclipseprojects.io"
  48. */
  49. #define COAP_DEFAULT_DEMO_URI CONFIG_EXAMPLE_TARGET_DOMAIN_URI
  50. const static char *TAG = "CoAP_client";
  51. static int resp_wait = 1;
  52. static coap_optlist_t *optlist = NULL;
  53. static int wait_ms;
  54. #ifdef CONFIG_COAP_MBEDTLS_PKI
  55. /* CA cert, taken from coap_ca.pem
  56. Client cert, taken from coap_client.crt
  57. Client key, taken from coap_client.key
  58. The PEM, CRT and KEY file are examples taken from
  59. https://github.com/eclipse/californium/tree/master/demo-certs/src/main/resources
  60. as the Certificate test (by default) is against the californium server.
  61. To embed it in the app binary, the PEM, CRT and KEY file is named
  62. in the component.mk COMPONENT_EMBED_TXTFILES variable.
  63. */
  64. extern uint8_t ca_pem_start[] asm("_binary_coap_ca_pem_start");
  65. extern uint8_t ca_pem_end[] asm("_binary_coap_ca_pem_end");
  66. extern uint8_t client_crt_start[] asm("_binary_coap_client_crt_start");
  67. extern uint8_t client_crt_end[] asm("_binary_coap_client_crt_end");
  68. extern uint8_t client_key_start[] asm("_binary_coap_client_key_start");
  69. extern uint8_t client_key_end[] asm("_binary_coap_client_key_end");
  70. #endif /* CONFIG_COAP_MBEDTLS_PKI */
  71. static coap_response_t
  72. message_handler(coap_session_t *session,
  73. const coap_pdu_t *sent,
  74. const coap_pdu_t *received,
  75. const coap_mid_t mid)
  76. {
  77. const unsigned char *data = NULL;
  78. size_t data_len;
  79. size_t offset;
  80. size_t total;
  81. coap_pdu_code_t rcvd_code = coap_pdu_get_code(received);
  82. if (COAP_RESPONSE_CLASS(rcvd_code) == 2) {
  83. if (coap_get_data_large(received, &data_len, &data, &offset, &total)) {
  84. if (data_len != total) {
  85. printf("Unexpected partial data received offset %u, length %u\n", offset, data_len);
  86. }
  87. printf("Received:\n%.*s\n", (int)data_len, data);
  88. resp_wait = 0;
  89. }
  90. return COAP_RESPONSE_OK;
  91. }
  92. printf("%d.%02d", (rcvd_code >> 5), rcvd_code & 0x1F);
  93. if (coap_get_data_large(received, &data_len, &data, &offset, &total)) {
  94. printf(": ");
  95. while(data_len--) {
  96. printf("%c", isprint(*data) ? *data : '.');
  97. data++;
  98. }
  99. }
  100. printf("\n");
  101. resp_wait = 0;
  102. return COAP_RESPONSE_OK;
  103. }
  104. #ifdef CONFIG_COAP_MBEDTLS_PKI
  105. static int
  106. verify_cn_callback(const char *cn,
  107. const uint8_t *asn1_public_cert,
  108. size_t asn1_length,
  109. coap_session_t *session,
  110. unsigned depth,
  111. int validated,
  112. void *arg
  113. )
  114. {
  115. coap_log(LOG_INFO, "CN '%s' presented by server (%s)\n",
  116. cn, depth ? "CA" : "Certificate");
  117. return 1;
  118. }
  119. #endif /* CONFIG_COAP_MBEDTLS_PKI */
  120. static void
  121. coap_log_handler (coap_log_t level, const char *message)
  122. {
  123. uint32_t esp_level = ESP_LOG_INFO;
  124. char *cp = strchr(message, '\n');
  125. if (cp)
  126. ESP_LOG_LEVEL(esp_level, TAG, "%.*s", (int)(cp-message), message);
  127. else
  128. ESP_LOG_LEVEL(esp_level, TAG, "%s", message);
  129. }
  130. static coap_address_t *
  131. coap_get_address(coap_uri_t *uri)
  132. {
  133. static coap_address_t dst_addr;
  134. char *phostname = NULL;
  135. struct addrinfo hints;
  136. struct addrinfo *addrres;
  137. int error;
  138. char tmpbuf[INET6_ADDRSTRLEN];
  139. phostname = (char *)calloc(1, uri->host.length + 1);
  140. if (phostname == NULL) {
  141. ESP_LOGE(TAG, "calloc failed");
  142. return NULL;
  143. }
  144. memcpy(phostname, uri->host.s, uri->host.length);
  145. memset ((char *)&hints, 0, sizeof(hints));
  146. hints.ai_socktype = SOCK_DGRAM;
  147. hints.ai_family = AF_UNSPEC;
  148. error = getaddrinfo(phostname, NULL, &hints, &addrres);
  149. if (error != 0) {
  150. ESP_LOGE(TAG, "DNS lookup failed for destination address %s. error: %d", phostname, error);
  151. free(phostname);
  152. return NULL;
  153. }
  154. if (addrres == NULL) {
  155. ESP_LOGE(TAG, "DNS lookup %s did not return any addresses", phostname);
  156. free(phostname);
  157. return NULL;
  158. }
  159. free(phostname);
  160. coap_address_init(&dst_addr);
  161. switch (addrres->ai_family) {
  162. case AF_INET:
  163. memcpy(&dst_addr.addr.sin, addrres->ai_addr, sizeof(dst_addr.addr.sin));
  164. dst_addr.addr.sin.sin_port = htons(uri->port);
  165. inet_ntop(AF_INET, &dst_addr.addr.sin.sin_addr, tmpbuf, sizeof(tmpbuf));
  166. ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", tmpbuf);
  167. break;
  168. case AF_INET6:
  169. memcpy(&dst_addr.addr.sin6, addrres->ai_addr, sizeof(dst_addr.addr.sin6));
  170. dst_addr.addr.sin6.sin6_port = htons(uri->port);
  171. inet_ntop(AF_INET6, &dst_addr.addr.sin6.sin6_addr, tmpbuf, sizeof(tmpbuf));
  172. ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", tmpbuf);
  173. break;
  174. default:
  175. ESP_LOGE(TAG, "DNS lookup response failed");
  176. return NULL;
  177. }
  178. freeaddrinfo(addrres);
  179. return &dst_addr;
  180. }
  181. static int
  182. coap_build_optlist(coap_uri_t *uri)
  183. {
  184. #define BUFSIZE 40
  185. unsigned char _buf[BUFSIZE];
  186. unsigned char *buf;
  187. size_t buflen;
  188. int res;
  189. optlist = NULL;
  190. if (uri->scheme == COAP_URI_SCHEME_COAPS && !coap_dtls_is_supported()) {
  191. ESP_LOGE(TAG, "MbedTLS (D)TLS Client Mode not configured");
  192. return 0;
  193. }
  194. if (uri->scheme == COAP_URI_SCHEME_COAPS_TCP && !coap_tls_is_supported()) {
  195. ESP_LOGE(TAG, "CoAP server uri->+tcp:// scheme is not supported");
  196. return 0;
  197. }
  198. if (uri->path.length) {
  199. buflen = BUFSIZE;
  200. buf = _buf;
  201. res = coap_split_path(uri->path.s, uri->path.length, buf, &buflen);
  202. while (res--) {
  203. coap_insert_optlist(&optlist,
  204. coap_new_optlist(COAP_OPTION_URI_PATH,
  205. coap_opt_length(buf),
  206. coap_opt_value(buf)));
  207. buf += coap_opt_size(buf);
  208. }
  209. }
  210. if (uri->query.length) {
  211. buflen = BUFSIZE;
  212. buf = _buf;
  213. res = coap_split_query(uri->query.s, uri->query.length, buf, &buflen);
  214. while (res--) {
  215. coap_insert_optlist(&optlist,
  216. coap_new_optlist(COAP_OPTION_URI_QUERY,
  217. coap_opt_length(buf),
  218. coap_opt_value(buf)));
  219. buf += coap_opt_size(buf);
  220. }
  221. }
  222. return 1;
  223. }
  224. #ifdef CONFIG_COAP_MBEDTLS_PSK
  225. static coap_session_t *
  226. coap_start_psk_session(coap_context_t *ctx, coap_address_t *dst_addr, coap_uri_t *uri)
  227. {
  228. static coap_dtls_cpsk_t dtls_psk;
  229. static char client_sni[256];
  230. memset(client_sni, 0, sizeof(client_sni));
  231. memset (&dtls_psk, 0, sizeof(dtls_psk));
  232. dtls_psk.version = COAP_DTLS_CPSK_SETUP_VERSION;
  233. dtls_psk.validate_ih_call_back = NULL;
  234. dtls_psk.ih_call_back_arg = NULL;
  235. if (uri->host.length)
  236. memcpy(client_sni, uri->host.s, MIN(uri->host.length, sizeof(client_sni) - 1));
  237. else
  238. memcpy(client_sni, "localhost", 9);
  239. dtls_psk.client_sni = client_sni;
  240. dtls_psk.psk_info.identity.s = (const uint8_t *)EXAMPLE_COAP_PSK_IDENTITY;
  241. dtls_psk.psk_info.identity.length = sizeof(EXAMPLE_COAP_PSK_IDENTITY)-1;
  242. dtls_psk.psk_info.key.s = (const uint8_t *)EXAMPLE_COAP_PSK_KEY;
  243. dtls_psk.psk_info.key.length = sizeof(EXAMPLE_COAP_PSK_KEY)-1;
  244. return coap_new_client_session_psk2(ctx, NULL, dst_addr,
  245. uri->scheme == COAP_URI_SCHEME_COAPS ? COAP_PROTO_DTLS : COAP_PROTO_TLS,
  246. &dtls_psk);
  247. }
  248. #endif /* CONFIG_COAP_MBEDTLS_PSK */
  249. #ifdef CONFIG_COAP_MBEDTLS_PKI
  250. static coap_session_t *
  251. coap_start_pki_session(coap_context_t *ctx, coap_address_t *dst_addr, coap_uri_t *uri)
  252. {
  253. unsigned int ca_pem_bytes = ca_pem_end - ca_pem_start;
  254. unsigned int client_crt_bytes = client_crt_end - client_crt_start;
  255. unsigned int client_key_bytes = client_key_end - client_key_start;
  256. static coap_dtls_pki_t dtls_pki;
  257. static char client_sni[256];
  258. memset (&dtls_pki, 0, sizeof(dtls_pki));
  259. dtls_pki.version = COAP_DTLS_PKI_SETUP_VERSION;
  260. if (ca_pem_bytes) {
  261. /*
  262. * Add in additional certificate checking.
  263. * This list of enabled can be tuned for the specific
  264. * requirements - see 'man coap_encryption'.
  265. *
  266. * Note: A list of root cas file can be setup separately using
  267. * coap_context_set_pki_root_cas(), but the below is used to
  268. * define what checking actually takes place.
  269. */
  270. dtls_pki.verify_peer_cert = 1;
  271. dtls_pki.check_common_ca = 1;
  272. dtls_pki.allow_self_signed = 1;
  273. dtls_pki.allow_expired_certs = 1;
  274. dtls_pki.cert_chain_validation = 1;
  275. dtls_pki.cert_chain_verify_depth = 2;
  276. dtls_pki.check_cert_revocation = 1;
  277. dtls_pki.allow_no_crl = 1;
  278. dtls_pki.allow_expired_crl = 1;
  279. dtls_pki.allow_bad_md_hash = 1;
  280. dtls_pki.allow_short_rsa_length = 1;
  281. dtls_pki.validate_cn_call_back = verify_cn_callback;
  282. dtls_pki.cn_call_back_arg = NULL;
  283. dtls_pki.validate_sni_call_back = NULL;
  284. dtls_pki.sni_call_back_arg = NULL;
  285. memset(client_sni, 0, sizeof(client_sni));
  286. if (uri->host.length) {
  287. memcpy(client_sni, uri->host.s, MIN(uri->host.length, sizeof(client_sni)));
  288. } else {
  289. memcpy(client_sni, "localhost", 9);
  290. }
  291. dtls_pki.client_sni = client_sni;
  292. }
  293. dtls_pki.pki_key.key_type = COAP_PKI_KEY_PEM_BUF;
  294. dtls_pki.pki_key.key.pem_buf.public_cert = client_crt_start;
  295. dtls_pki.pki_key.key.pem_buf.public_cert_len = client_crt_bytes;
  296. dtls_pki.pki_key.key.pem_buf.private_key = client_key_start;
  297. dtls_pki.pki_key.key.pem_buf.private_key_len = client_key_bytes;
  298. dtls_pki.pki_key.key.pem_buf.ca_cert = ca_pem_start;
  299. dtls_pki.pki_key.key.pem_buf.ca_cert_len = ca_pem_bytes;
  300. return coap_new_client_session_pki(ctx, NULL, dst_addr,
  301. uri->scheme == COAP_URI_SCHEME_COAPS ? COAP_PROTO_DTLS : COAP_PROTO_TLS,
  302. &dtls_pki);
  303. }
  304. #endif /* CONFIG_COAP_MBEDTLS_PKI */
  305. static void coap_example_client(void *p)
  306. {
  307. coap_address_t *dst_addr;
  308. static coap_uri_t uri;
  309. const char *server_uri = COAP_DEFAULT_DEMO_URI;
  310. coap_context_t *ctx = NULL;
  311. coap_session_t *session = NULL;
  312. coap_pdu_t *request = NULL;
  313. unsigned char token[8];
  314. size_t tokenlength;
  315. /* Set up the CoAP logging */
  316. coap_set_log_handler(coap_log_handler);
  317. coap_set_log_level(EXAMPLE_COAP_LOG_DEFAULT_LEVEL);
  318. /* Set up the CoAP context */
  319. ctx = coap_new_context(NULL);
  320. if (!ctx) {
  321. ESP_LOGE(TAG, "coap_new_context() failed");
  322. goto clean_up;
  323. }
  324. coap_context_set_block_mode(ctx,
  325. COAP_BLOCK_USE_LIBCOAP|COAP_BLOCK_SINGLE_BODY);
  326. coap_register_response_handler(ctx, message_handler);
  327. if (coap_split_uri((const uint8_t *)server_uri, strlen(server_uri), &uri) == -1) {
  328. ESP_LOGE(TAG, "CoAP server uri error");
  329. goto clean_up;
  330. }
  331. if (!coap_build_optlist(&uri))
  332. goto clean_up;
  333. dst_addr = coap_get_address(&uri);
  334. if (!dst_addr)
  335. goto clean_up;
  336. /*
  337. * Note that if the URI starts with just coap:// (not coaps://) the
  338. * session will still be plain text.
  339. *
  340. * coaps+tcp:// is NOT yet supported by the libcoap->mbedtls interface
  341. * so COAP_URI_SCHEME_COAPS_TCP will have failed in a test above,
  342. * but the code is left in for completeness.
  343. */
  344. if (uri.scheme == COAP_URI_SCHEME_COAPS || uri.scheme == COAP_URI_SCHEME_COAPS_TCP) {
  345. #ifndef CONFIG_MBEDTLS_TLS_CLIENT
  346. ESP_LOGE(TAG, "MbedTLS (D)TLS Client Mode not configured");
  347. goto clean_up;
  348. #endif /* CONFIG_MBEDTLS_TLS_CLIENT */
  349. #ifdef CONFIG_COAP_MBEDTLS_PSK
  350. session = coap_start_psk_session(ctx, dst_addr, &uri);
  351. #endif /* CONFIG_COAP_MBEDTLS_PSK */
  352. #ifdef CONFIG_COAP_MBEDTLS_PKI
  353. session = coap_start_pki_session(ctx, dst_addr, &uri);
  354. #endif /* CONFIG_COAP_MBEDTLS_PKI */
  355. } else {
  356. session = coap_new_client_session(ctx, NULL, dst_addr,
  357. uri.scheme == COAP_URI_SCHEME_COAP_TCP ? COAP_PROTO_TCP :
  358. COAP_PROTO_UDP);
  359. }
  360. if (!session) {
  361. ESP_LOGE(TAG, "coap_new_client_session() failed");
  362. goto clean_up;
  363. }
  364. while (1) {
  365. request = coap_new_pdu(coap_is_mcast(dst_addr) ? COAP_MESSAGE_NON : COAP_MESSAGE_CON,
  366. COAP_REQUEST_CODE_GET, session);
  367. if (!request) {
  368. ESP_LOGE(TAG, "coap_new_pdu() failed");
  369. goto clean_up;
  370. }
  371. /* Add in an unique token */
  372. coap_session_new_token(session, &tokenlength, token);
  373. coap_add_token(request, tokenlength, token);
  374. /*
  375. * To make this a POST, you will need to do the following
  376. * Change COAP_REQUEST_CODE_GET to COAP_REQUEST_CODE_POST for coap_new_pdu()
  377. * Add in here a Content-Type Option based on the format of the POST text. E.G. for JSON
  378. * u_char buf[4];
  379. * coap_insert_optlist(&optlist,
  380. * coap_new_optlist(COAP_OPTION_CONTENT_FORMAT,
  381. * coap_encode_var_safe (buf, sizeof (buf),
  382. * COAP_MEDIATYPE_APPLICATION_JSON));
  383. * Add in here the POST data of length length. E.G.
  384. * coap_add_data_large_request(session, request length, data, NULL, NULL);
  385. */
  386. coap_add_optlist_pdu(request, &optlist);
  387. resp_wait = 1;
  388. coap_send(session, request);
  389. wait_ms = COAP_DEFAULT_TIME_SEC * 1000;
  390. while (resp_wait) {
  391. int result = coap_io_process(ctx, wait_ms > 1000 ? 1000 : wait_ms);
  392. if (result >= 0) {
  393. if (result >= wait_ms) {
  394. ESP_LOGE(TAG, "No response from server");
  395. break;
  396. } else {
  397. wait_ms -= result;
  398. }
  399. }
  400. }
  401. for(int countdown = 10; countdown >= 0; countdown--) {
  402. ESP_LOGI(TAG, "%d... ", countdown);
  403. vTaskDelay(1000 / portTICK_PERIOD_MS);
  404. }
  405. ESP_LOGI(TAG, "Starting again!");
  406. }
  407. clean_up:
  408. if (optlist) {
  409. coap_delete_optlist(optlist);
  410. optlist = NULL;
  411. }
  412. if (session) {
  413. coap_session_release(session);
  414. }
  415. if (ctx) {
  416. coap_free_context(ctx);
  417. }
  418. coap_cleanup();
  419. ESP_LOGI(TAG, "Finished");
  420. vTaskDelete(NULL);
  421. }
  422. void app_main(void)
  423. {
  424. ESP_ERROR_CHECK( nvs_flash_init() );
  425. ESP_ERROR_CHECK(esp_netif_init());
  426. ESP_ERROR_CHECK(esp_event_loop_create_default());
  427. /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
  428. * Read "Establishing Wi-Fi or Ethernet Connection" section in
  429. * examples/protocols/README.md for more information about this function.
  430. */
  431. ESP_ERROR_CHECK(example_connect());
  432. xTaskCreate(coap_example_client, "coap", 8 * 1024, NULL, 5, NULL);
  433. }