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