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