esp-idf/examples/protocols/coap_client/main/coap_client_example_main.c
David Cermak a49b934ef8 examples: protocol examples which use common connection component
updated to use esp_netif_init instead of tcpip_adapter in initialization code
2019-11-13 12:36:25 +01:00

454 lines
16 KiB
C

/* CoAP client Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
/*
* WARNING
* libcoap is not multi-thread safe, so only this thread must make any coap_*()
* calls. Any external (to this thread) data transmitted in/out via libcoap
* therefore has to be passed in/out by xQueue*() via this thread.
*/
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#if 1
/* Needed until coap_dtls.h becomes a part of libcoap proper */
#include "libcoap.h"
#include "coap_dtls.h"
#endif
#include "coap.h"
#define COAP_DEFAULT_TIME_SEC 5
/* The examples use simple Pre-Shared-Key configuration that you can set via
'make menuconfig'.
If you'd rather not, just change the below entries to strings with
the config you want - ie #define EXAMPLE_COAP_PSK_KEY "some-agreed-preshared-key"
Note: PSK will only be used if the URI is prefixed with coaps://
instead of coap:// and the PSK must be one that the server supports
(potentially associated with the IDENTITY)
*/
#define EXAMPLE_COAP_PSK_KEY CONFIG_EXAMPLE_COAP_PSK_KEY
#define EXAMPLE_COAP_PSK_IDENTITY CONFIG_EXAMPLE_COAP_PSK_IDENTITY
/* The examples use uri Logging Level that
you can set via 'make menuconfig'.
If you'd rather not, just change the below entry to a value
that is between 0 and 7 with
the config you want - ie #define EXAMPLE_COAP_LOG_DEFAULT_LEVEL 7
*/
#define EXAMPLE_COAP_LOG_DEFAULT_LEVEL CONFIG_COAP_LOG_DEFAULT_LEVEL
/* The examples use uri "coap://californium.eclipse.org" that
you can set via the project configuration (idf.py menuconfig)
If you'd rather not, just change the below entries to strings with
the config you want - ie #define COAP_DEFAULT_DEMO_URI "coaps://californium.eclipse.org"
*/
#define COAP_DEFAULT_DEMO_URI CONFIG_EXAMPLE_TARGET_DOMAIN_URI
const static char *TAG = "CoAP_client";
static int resp_wait = 1;
static coap_optlist_t *optlist = NULL;
static int wait_ms;
#ifdef CONFIG_COAP_MBEDTLS_PKI
/* CA cert, taken from coap_ca.pem
Client cert, taken from coap_client.crt
Client key, taken from coap_client.key
The PEM, CRT and KEY file are examples taken from the wpa2 enterprise
example.
To embed it in the app binary, the PEM, CRT and KEY file is named
in the component.mk COMPONENT_EMBED_TXTFILES variable.
*/
extern uint8_t ca_pem_start[] asm("_binary_coap_ca_pem_start");
extern uint8_t ca_pem_end[] asm("_binary_coap_ca_pem_end");
extern uint8_t client_crt_start[] asm("_binary_coap_client_crt_start");
extern uint8_t client_crt_end[] asm("_binary_coap_client_crt_end");
extern uint8_t client_key_start[] asm("_binary_coap_client_key_start");
extern uint8_t client_key_end[] asm("_binary_coap_client_key_end");
#endif /* CONFIG_COAP_MBEDTLS_PKI */
static void message_handler(coap_context_t *ctx, coap_session_t *session,
coap_pdu_t *sent, coap_pdu_t *received,
const coap_tid_t id)
{
unsigned char *data = NULL;
size_t data_len;
coap_pdu_t *pdu = NULL;
coap_opt_t *block_opt;
coap_opt_iterator_t opt_iter;
unsigned char buf[4];
coap_optlist_t *option;
coap_tid_t tid;
if (COAP_RESPONSE_CLASS(received->code) == 2) {
/* Need to see if blocked response */
block_opt = coap_check_option(received, COAP_OPTION_BLOCK2, &opt_iter);
if (block_opt) {
uint16_t blktype = opt_iter.type;
if (coap_opt_block_num(block_opt) == 0) {
printf("Received:\n");
}
if (coap_get_data(received, &data_len, &data)) {
printf("%.*s", (int)data_len, data);
}
if (COAP_OPT_BLOCK_MORE(block_opt)) {
/* more bit is set */
/* create pdu with request for next block */
pdu = coap_new_pdu(session);
if (!pdu) {
ESP_LOGE(TAG, "coap_new_pdu() failed");
goto clean_up;
}
pdu->type = COAP_MESSAGE_CON;
pdu->tid = coap_new_message_id(session);
pdu->code = COAP_REQUEST_GET;
/* add URI components from optlist */
for (option = optlist; option; option = option->next ) {
switch (option->number) {
case COAP_OPTION_URI_HOST :
case COAP_OPTION_URI_PORT :
case COAP_OPTION_URI_PATH :
case COAP_OPTION_URI_QUERY :
coap_add_option(pdu, option->number, option->length,
option->data);
break;
default:
; /* skip other options */
}
}
/* finally add updated block option from response, clear M bit */
/* blocknr = (blocknr & 0xfffffff7) + 0x10; */
coap_add_option(pdu,
blktype,
coap_encode_var_safe(buf, sizeof(buf),
((coap_opt_block_num(block_opt) + 1) << 4) |
COAP_OPT_BLOCK_SZX(block_opt)), buf);
tid = coap_send(session, pdu);
if (tid != COAP_INVALID_TID) {
resp_wait = 1;
wait_ms = COAP_DEFAULT_TIME_SEC * 1000;
return;
}
}
printf("\n");
} else {
if (coap_get_data(received, &data_len, &data)) {
printf("Received: %.*s\n", (int)data_len, data);
}
}
}
clean_up:
resp_wait = 0;
}
#ifdef CONFIG_COAP_MBEDTLS_PKI
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
)
{
coap_log(LOG_INFO, "CN '%s' presented by server (%s)\n",
cn, depth ? "CA" : "Certificate");
return 1;
}
#endif /* CONFIG_COAP_MBEDTLS_PKI */
static void coap_example_client(void *p)
{
struct hostent *hp;
coap_address_t dst_addr;
static coap_uri_t uri;
const char *server_uri = COAP_DEFAULT_DEMO_URI;
char *phostname = NULL;
coap_set_log_level(EXAMPLE_COAP_LOG_DEFAULT_LEVEL);
while (1) {
#define BUFSIZE 40
unsigned char _buf[BUFSIZE];
unsigned char *buf;
size_t buflen;
int res;
coap_context_t *ctx = NULL;
coap_session_t *session = NULL;
coap_pdu_t *request = NULL;
optlist = NULL;
if (coap_split_uri((const uint8_t *)server_uri, strlen(server_uri), &uri) == -1) {
ESP_LOGE(TAG, "CoAP server uri error");
break;
}
if (uri.scheme == COAP_URI_SCHEME_COAPS && !coap_dtls_is_supported()) {
ESP_LOGE(TAG, "MbedTLS (D)TLS Client Mode not configured");
break;
}
if (uri.scheme == COAP_URI_SCHEME_COAPS_TCP && !coap_tls_is_supported()) {
ESP_LOGE(TAG, "CoAP server uri coaps+tcp:// scheme is not supported");
break;
}
phostname = (char *)calloc(1, uri.host.length + 1);
if (phostname == NULL) {
ESP_LOGE(TAG, "calloc failed");
break;
}
memcpy(phostname, uri.host.s, uri.host.length);
hp = gethostbyname(phostname);
free(phostname);
if (hp == NULL) {
ESP_LOGE(TAG, "DNS lookup failed");
vTaskDelay(1000 / portTICK_PERIOD_MS);
free(phostname);
continue;
}
char tmpbuf[INET6_ADDRSTRLEN];
coap_address_init(&dst_addr);
switch (hp->h_addrtype) {
case AF_INET:
dst_addr.addr.sin.sin_family = AF_INET;
dst_addr.addr.sin.sin_port = htons(uri.port);
memcpy(&dst_addr.addr.sin.sin_addr, hp->h_addr, sizeof(dst_addr.addr.sin.sin_addr));
inet_ntop(AF_INET, &dst_addr.addr.sin.sin_addr, tmpbuf, sizeof(tmpbuf));
ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", tmpbuf);
break;
case AF_INET6:
dst_addr.addr.sin6.sin6_family = AF_INET6;
dst_addr.addr.sin6.sin6_port = htons(uri.port);
memcpy(&dst_addr.addr.sin6.sin6_addr, hp->h_addr, sizeof(dst_addr.addr.sin6.sin6_addr));
inet_ntop(AF_INET6, &dst_addr.addr.sin6.sin6_addr, tmpbuf, sizeof(tmpbuf));
ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", tmpbuf);
break;
default:
ESP_LOGE(TAG, "DNS lookup response failed");
goto clean_up;
}
if (uri.path.length) {
buflen = BUFSIZE;
buf = _buf;
res = coap_split_path(uri.path.s, uri.path.length, buf, &buflen);
while (res--) {
coap_insert_optlist(&optlist,
coap_new_optlist(COAP_OPTION_URI_PATH,
coap_opt_length(buf),
coap_opt_value(buf)));
buf += coap_opt_size(buf);
}
}
if (uri.query.length) {
buflen = BUFSIZE;
buf = _buf;
res = coap_split_query(uri.query.s, uri.query.length, buf, &buflen);
while (res--) {
coap_insert_optlist(&optlist,
coap_new_optlist(COAP_OPTION_URI_QUERY,
coap_opt_length(buf),
coap_opt_value(buf)));
buf += coap_opt_size(buf);
}
}
ctx = coap_new_context(NULL);
if (!ctx) {
ESP_LOGE(TAG, "coap_new_context() failed");
goto clean_up;
}
/*
* Note that if the URI starts with just coap:// (not coaps://) the
* session will still be plain text.
*
* coaps+tcp:// is NOT supported by the libcoap->mbedtls interface
* so COAP_URI_SCHEME_COAPS_TCP will have failed in a test above,
* but the code is left in for completeness.
*/
if (uri.scheme == COAP_URI_SCHEME_COAPS || uri.scheme == COAP_URI_SCHEME_COAPS_TCP) {
#ifndef CONFIG_MBEDTLS_TLS_CLIENT
ESP_LOGE(TAG, "MbedTLS (D)TLS Client Mode not configured");
goto clean_up;
#endif /* CONFIG_MBEDTLS_TLS_CLIENT */
#ifdef CONFIG_COAP_MBEDTLS_PSK
session = coap_new_client_session_psk(ctx, NULL, &dst_addr,
uri.scheme == COAP_URI_SCHEME_COAPS ? COAP_PROTO_DTLS : COAP_PROTO_TLS,
EXAMPLE_COAP_PSK_IDENTITY,
(const uint8_t *)EXAMPLE_COAP_PSK_KEY,
sizeof(EXAMPLE_COAP_PSK_KEY) - 1);
#endif /* CONFIG_COAP_MBEDTLS_PSK */
#ifdef CONFIG_COAP_MBEDTLS_PKI
unsigned int ca_pem_bytes = ca_pem_end - ca_pem_start;
unsigned int client_crt_bytes = client_crt_end - client_crt_start;
unsigned int client_key_bytes = client_key_end - client_key_start;
coap_dtls_pki_t dtls_pki;
static char client_sni[256];
memset (&dtls_pki, 0, sizeof(dtls_pki));
dtls_pki.version = COAP_DTLS_PKI_SETUP_VERSION;
if (ca_pem_bytes) {
/*
* Add in additional certificate checking.
* This list of enabled can be tuned for the specific
* requirements - see 'man coap_encryption'.
*
* Note: A list of root ca file can be setup separately using
* coap_context_set_pki_root_cas(), but the below is used to
* define what checking actually takes place.
*/
dtls_pki.verify_peer_cert = 1;
dtls_pki.require_peer_cert = 1;
dtls_pki.allow_self_signed = 1;
dtls_pki.allow_expired_certs = 1;
dtls_pki.cert_chain_validation = 1;
dtls_pki.cert_chain_verify_depth = 2;
dtls_pki.check_cert_revocation = 1;
dtls_pki.allow_no_crl = 1;
dtls_pki.allow_expired_crl = 1;
dtls_pki.allow_bad_md_hash = 1;
dtls_pki.allow_short_rsa_length = 1;
dtls_pki.validate_cn_call_back = verify_cn_callback;
dtls_pki.cn_call_back_arg = NULL;
dtls_pki.validate_sni_call_back = NULL;
dtls_pki.sni_call_back_arg = NULL;
memset(client_sni, 0, sizeof(client_sni));
if (uri.host.length) {
memcpy(client_sni, uri.host.s, MIN(uri.host.length, sizeof(client_sni)));
} else {
memcpy(client_sni, "localhost", 9);
}
dtls_pki.client_sni = client_sni;
}
dtls_pki.pki_key.key_type = COAP_PKI_KEY_PEM_BUF;
dtls_pki.pki_key.key.pem_buf.public_cert = client_crt_start;
dtls_pki.pki_key.key.pem_buf.public_cert_len = client_crt_bytes;
dtls_pki.pki_key.key.pem_buf.private_key = client_key_start;
dtls_pki.pki_key.key.pem_buf.private_key_len = client_key_bytes;
dtls_pki.pki_key.key.pem_buf.ca_cert = ca_pem_start;
dtls_pki.pki_key.key.pem_buf.ca_cert_len = ca_pem_bytes;
session = coap_new_client_session_pki(ctx, NULL, &dst_addr,
uri.scheme == COAP_URI_SCHEME_COAPS ? COAP_PROTO_DTLS : COAP_PROTO_TLS,
&dtls_pki);
#endif /* CONFIG_COAP_MBEDTLS_PKI */
} else {
session = coap_new_client_session(ctx, NULL, &dst_addr,
uri.scheme == COAP_URI_SCHEME_COAP_TCP ? COAP_PROTO_TCP :
COAP_PROTO_UDP);
}
if (!session) {
ESP_LOGE(TAG, "coap_new_client_session() failed");
goto clean_up;
}
coap_register_response_handler(ctx, message_handler);
request = coap_new_pdu(session);
if (!request) {
ESP_LOGE(TAG, "coap_new_pdu() failed");
goto clean_up;
}
request->type = COAP_MESSAGE_CON;
request->tid = coap_new_message_id(session);
request->code = COAP_REQUEST_GET;
coap_add_optlist_pdu(request, &optlist);
resp_wait = 1;
coap_send(session, request);
wait_ms = COAP_DEFAULT_TIME_SEC * 1000;
while (resp_wait) {
int result = coap_run_once(ctx, wait_ms > 1000 ? 1000 : wait_ms);
if (result >= 0) {
if (result >= wait_ms) {
ESP_LOGE(TAG, "select timeout");
break;
} else {
wait_ms -= result;
}
}
}
clean_up:
if (optlist) {
coap_delete_optlist(optlist);
optlist = NULL;
}
if (session) {
coap_session_release(session);
}
if (ctx) {
coap_free_context(ctx);
}
coap_cleanup();
/*
* change the following line to something like sleep(2)
* if you want the request to continually be sent
*/
break;
}
vTaskDelete(NULL);
}
void app_main(void)
{
ESP_ERROR_CHECK( nvs_flash_init() );
esp_netif_init();
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
xTaskCreate(coap_example_client, "coap", 8 * 1024, NULL, 5, NULL);
}