coap_client_example_main.c 17 KB

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