From ae6913a66ab737416388a95f90f4ce6a21a2a81e Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 17 May 2023 18:38:34 +0100 Subject: [PATCH 1/3] Add HTTP client examples Examples to demonstrate HTTP, HTTPS and TLS validation Fixes #318 --- README.md | 4 + pico_w/wifi/CMakeLists.txt | 1 + pico_w/wifi/freertos/CMakeLists.txt | 3 +- .../wifi/freertos/http_client/CMakeLists.txt | 53 +++++++ .../freertos/http_client/FreeRTOSConfig.h | 7 + pico_w/wifi/freertos/http_client/lwipopts.h | 36 +++++ .../freertos/http_client/mbedtls_config.h | 6 + .../http_client/picow_freertos_http_client.c | 121 +++++++++++++++ pico_w/wifi/http_client/CMakeLists.txt | 58 +++++++ .../http_client/example_http_client_util.c | 141 ++++++++++++++++++ .../http_client/example_http_client_util.h | 126 ++++++++++++++++ pico_w/wifi/http_client/lwipopts.h | 27 ++++ pico_w/wifi/http_client/mbedtls_config.h | 6 + pico_w/wifi/http_client/picow_http_client.c | 58 +++++++ pico_w/wifi/http_client/picow_http_verify.c | 97 ++++++++++++ 15 files changed, 743 insertions(+), 1 deletion(-) create mode 100644 pico_w/wifi/freertos/http_client/CMakeLists.txt create mode 100644 pico_w/wifi/freertos/http_client/FreeRTOSConfig.h create mode 100644 pico_w/wifi/freertos/http_client/lwipopts.h create mode 100644 pico_w/wifi/freertos/http_client/mbedtls_config.h create mode 100644 pico_w/wifi/freertos/http_client/picow_freertos_http_client.c create mode 100644 pico_w/wifi/http_client/CMakeLists.txt create mode 100644 pico_w/wifi/http_client/example_http_client_util.c create mode 100644 pico_w/wifi/http_client/example_http_client_util.h create mode 100644 pico_w/wifi/http_client/lwipopts.h create mode 100644 pico_w/wifi/http_client/mbedtls_config.h create mode 100644 pico_w/wifi/http_client/picow_http_client.c create mode 100644 pico_w/wifi/http_client/picow_http_verify.c diff --git a/README.md b/README.md index 59a75068a..959753327 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,8 @@ App|Description [picow_wifi_scan](pico_w/wifi/wifi_scan) | Scans for WiFi networks and prints the results. [picow_udp_beacon](pico_w/wifi/udp_beacon) | A simple UDP transmitter. [picow_httpd](pico_w/wifi/httpd) | Runs a LWIP HTTP server test app +[picow_http_client](pico_w/wifi/http_client) | Demonstrates how to make http and https requests +[picow_http_client_verify](pico_w/wifi/http_client) | Demonstrates how to make a https request with server authentication #### FreeRTOS examples @@ -204,6 +206,8 @@ App|Description [picow_freertos_ntp_client_socket](pico_w/wifi/freertos/ntp_client_socket) | Connects to an NTP server using the LwIP Socket API with FreeRTOS in NO_SYS=0 (i.e. full FreeRTOS integration) mode. [pico_freertos_httpd_nosys](pico_w/wifi/freertos/httpd) | Runs a LWIP HTTP server test app under FreeRTOS in NO_SYS=1 mode. [pico_freertos_httpd_sys](pico_w/wifi/freertos/httpd) | Runs a LWIP HTTP server test app under FreeRTOS in NO_SYS=0 (i.e. full FreeRTOS integration) mode. +[picow_freertos_http_client_nosys](pico_w/wifi/freertos/http_client) | Demonstrates how to make a https request in NO_SYS=1 mode +[picow_freertos_http_client_sys](pico_w/wifi/freertos/http_client) | Demonstrates how to make a https request in NO_SYS=0 (i.e. full FreeRTOS integration) ### Pico W Bluetooth diff --git a/pico_w/wifi/CMakeLists.txt b/pico_w/wifi/CMakeLists.txt index 8b6e2b9b5..4599a21cd 100644 --- a/pico_w/wifi/CMakeLists.txt +++ b/pico_w/wifi/CMakeLists.txt @@ -17,6 +17,7 @@ else() add_subdirectory_exclude_platforms(tcp_client) add_subdirectory_exclude_platforms(tcp_server) add_subdirectory_exclude_platforms(udp_beacon) + add_subdirectory_exclude_platforms(http_client) if (NOT PICO_MBEDTLS_PATH) message("Skipping tls examples as PICO_MBEDTLS_PATH is not defined") diff --git a/pico_w/wifi/freertos/CMakeLists.txt b/pico_w/wifi/freertos/CMakeLists.txt index b4057602f..f32473b9a 100644 --- a/pico_w/wifi/freertos/CMakeLists.txt +++ b/pico_w/wifi/freertos/CMakeLists.txt @@ -7,4 +7,5 @@ else() add_subdirectory_exclude_platforms(iperf) add_subdirectory_exclude_platforms(ntp_client_socket) add_subdirectory_exclude_platforms(ping) -endif() \ No newline at end of file + add_subdirectory_exclude_platforms(http_client) +endif() diff --git a/pico_w/wifi/freertos/http_client/CMakeLists.txt b/pico_w/wifi/freertos/http_client/CMakeLists.txt new file mode 100644 index 000000000..e5ce4b8e5 --- /dev/null +++ b/pico_w/wifi/freertos/http_client/CMakeLists.txt @@ -0,0 +1,53 @@ +if (0) +# Currently disabled as mbedtls uses too much stack +add_executable(picow_freertos_http_client_nosys + picow_freertos_http_client.c + ) +target_compile_definitions(picow_freertos_http_client_nosys PRIVATE + WIFI_SSID=\"${WIFI_SSID}\" + WIFI_PASSWORD=\"${WIFI_PASSWORD}\" + ALTCP_MBEDTLS_AUTHMODE=MBEDTLS_SSL_VERIFY_REQUIRED + ) +target_include_directories(picow_freertos_http_client_nosys PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common FreeRTOSConfig + ${CMAKE_CURRENT_LIST_DIR}/../.. # for our common lwipopts and mbedtls_config + ) +target_link_libraries(picow_freertos_http_client_nosys + pico_cyw43_arch_lwip_threadsafe_background + pico_stdlib + example_lwip_http_util + FreeRTOS-Kernel-Heap4 # FreeRTOS kernel and dynamic heap + ) +pico_add_extra_outputs(picow_freertos_http_client_nosys) +endif() + +add_executable(picow_freertos_http_client_sys + picow_freertos_http_client.c + ) +target_compile_definitions(picow_freertos_http_client_sys PRIVATE + WIFI_SSID=\"${WIFI_SSID}\" + WIFI_PASSWORD=\"${WIFI_PASSWORD}\" + NO_SYS=0 # don't want NO_SYS (generally this would be in your lwipopts.h) + ALTCP_MBEDTLS_AUTHMODE=MBEDTLS_SSL_VERIFY_REQUIRED + CYW43_TASK_STACK_SIZE=2048 + ) +target_include_directories(picow_freertos_http_client_sys PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common FreeRTOSConfig + ${CMAKE_CURRENT_LIST_DIR}/../.. # for our common lwipopts + ) +target_link_libraries(picow_freertos_http_client_sys + pico_cyw43_arch_lwip_sys_freertos + pico_stdlib + example_lwip_http_util + FreeRTOS-Kernel-Heap4 # FreeRTOS kernel and dynamic heap + ) +pico_add_extra_outputs(picow_freertos_http_client_sys) + +# Ignore warnings from lwip code +set_source_files_properties( + ${PICO_LWIP_PATH}/src/apps/altcp_tls/altcp_tls_mbedtls.c + PROPERTIES + COMPILE_OPTIONS "-Wno-unused-result" + ) \ No newline at end of file diff --git a/pico_w/wifi/freertos/http_client/FreeRTOSConfig.h b/pico_w/wifi/freertos/http_client/FreeRTOSConfig.h new file mode 100644 index 000000000..831e53500 --- /dev/null +++ b/pico_w/wifi/freertos/http_client/FreeRTOSConfig.h @@ -0,0 +1,7 @@ +#ifndef FREERTOS_CONFIG_H +#define FREERTOS_CONFIG_H + +// This example uses a common include to avoid repetition +#include "FreeRTOSConfig_examples_common.h" + +#endif diff --git a/pico_w/wifi/freertos/http_client/lwipopts.h b/pico_w/wifi/freertos/http_client/lwipopts.h new file mode 100644 index 000000000..caf985cd8 --- /dev/null +++ b/pico_w/wifi/freertos/http_client/lwipopts.h @@ -0,0 +1,36 @@ +#ifndef _LWIPOPTS_H +#define _LWIPOPTS_H + +// Generally you would define your own explicit list of lwIP options +// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html) +// +// This example uses a common include to avoid repetition +#include "lwipopts_examples_common.h" + +#if !NO_SYS +#define TCPIP_THREAD_STACKSIZE 1024 +#define DEFAULT_THREAD_STACKSIZE 1024 +#define DEFAULT_RAW_RECVMBOX_SIZE 8 +#define TCPIP_MBOX_SIZE 8 +#define LWIP_TIMEVAL_PRIVATE 0 + +// not necessary, can be done either way +#define LWIP_TCPIP_CORE_LOCKING_INPUT 1 + +// ping_thread sets socket receive timeout, so enable this feature +#define LWIP_SO_RCVTIMEO 1 +#endif + +#define LWIP_ALTCP 1 + +// If you don't want to use TLS (just a http request) you can avoid linking to mbedtls and remove the following +#define LWIP_ALTCP_TLS 1 +#define LWIP_ALTCP_TLS_MBEDTLS 1 + +// Note bug in lwip with LWIP_ALTCP and LWIP_DEBUG +// https://savannah.nongnu.org/bugs/index.php?62159 +//#define LWIP_DEBUG 1 +#undef LWIP_DEBUG +#define ALTCP_MBEDTLS_DEBUG LWIP_DBG_ON + +#endif diff --git a/pico_w/wifi/freertos/http_client/mbedtls_config.h b/pico_w/wifi/freertos/http_client/mbedtls_config.h new file mode 100644 index 000000000..f182c5661 --- /dev/null +++ b/pico_w/wifi/freertos/http_client/mbedtls_config.h @@ -0,0 +1,6 @@ +#ifndef MBEDTLS_CONFIG_TLS_CLIENT_H +#define MBEDTLS_CONFIG_TLS_CLIENT_H + +#include "mbedtls_config_examples_common.h" + +#endif \ No newline at end of file diff --git a/pico_w/wifi/freertos/http_client/picow_freertos_http_client.c b/pico_w/wifi/freertos/http_client/picow_freertos_http_client.c new file mode 100644 index 000000000..bb4bd998a --- /dev/null +++ b/pico_w/wifi/freertos/http_client/picow_freertos_http_client.c @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico/cyw43_arch.h" +#include "pico/stdlib.h" +#include "lwip/altcp_tls.h" + +#include "lwip/netif.h" + +#include "FreeRTOS.h" +#include "task.h" +#include "example_http_client_util.h" + +#ifndef RUN_FREERTOS_ON_CORE +#define RUN_FREERTOS_ON_CORE 0 +#endif + +#define TEST_TASK_PRIORITY ( tskIDLE_PRIORITY + 2UL ) +#define TEST_TASK_STACK_SIZE 1024 + +// Using this url as we know the root cert won't change for a long time +#define HOST "fw-download-alias1.raspberrypi.com" +#define URL_REQUEST "/net_install/boot.sig" + +// This is the PUBLIC root certificate exported from a browser +// Note that the newlines are needed +#define TLS_ROOT_CERT_OK "-----BEGIN CERTIFICATE-----\n\ +MIIC+jCCAn+gAwIBAgICEAAwCgYIKoZIzj0EAwIwgbcxCzAJBgNVBAYTAkdCMRAw\n\ +DgYDVQQIDAdFbmdsYW5kMRIwEAYDVQQHDAlDYW1icmlkZ2UxHTAbBgNVBAoMFFJh\n\ +c3BiZXJyeSBQSSBMaW1pdGVkMRwwGgYDVQQLDBNSYXNwYmVycnkgUEkgRUNDIENB\n\ +MR0wGwYDVQQDDBRSYXNwYmVycnkgUEkgUm9vdCBDQTEmMCQGCSqGSIb3DQEJARYX\n\ +c3VwcG9ydEByYXNwYmVycnlwaS5jb20wIBcNMjExMjA5MTEzMjU1WhgPMjA3MTEx\n\ +MjcxMTMyNTVaMIGrMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEdMBsG\n\ +A1UECgwUUmFzcGJlcnJ5IFBJIExpbWl0ZWQxHDAaBgNVBAsME1Jhc3BiZXJyeSBQ\n\ +SSBFQ0MgQ0ExJTAjBgNVBAMMHFJhc3BiZXJyeSBQSSBJbnRlcm1lZGlhdGUgQ0Ex\n\ +JjAkBgkqhkiG9w0BCQEWF3N1cHBvcnRAcmFzcGJlcnJ5cGkuY29tMHYwEAYHKoZI\n\ +zj0CAQYFK4EEACIDYgAEcN9K6Cpv+od3w6yKOnec4EbyHCBzF+X2ldjorc0b2Pq0\n\ +N+ZvyFHkhFZSgk2qvemsVEWIoPz+K4JSCpgPstz1fEV6WzgjYKfYI71ghELl5TeC\n\ +byoPY+ee3VZwF1PTy0cco2YwZDAdBgNVHQ4EFgQUJ6YzIqFh4rhQEbmCnEbWmHEo\n\ +XAUwHwYDVR0jBBgwFoAUIIAVCSiDPXut23NK39LGIyAA7NAwEgYDVR0TAQH/BAgw\n\ +BgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwIDaQAwZgIxAJYM+wIM\n\ +PC3wSPqJ1byJKA6D+ZyjKR1aORbiDQVEpDNWRKiQ5QapLg8wbcED0MrRKQIxAKUT\n\ +v8TJkb/8jC/oBVTmczKlPMkciN+uiaZSXahgYKyYhvKTatCTZb+geSIhc0w/2w==\n\ +-----END CERTIFICATE-----\n" + +void main_task(__unused void *params) { + if (cyw43_arch_init()) { + printf("failed to initialise\n"); + return; + } + + cyw43_arch_enable_sta_mode(); + printf("Connecting to Wi-Fi...\n"); + if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) { + printf("failed to connect.\n"); + exit(1); + } else { + printf("Connected.\n"); + } + + static const uint8_t cert_ok[] = TLS_ROOT_CERT_OK; + static EXAMPLE_HTTP_REQUEST_T req = {0}; + req.hostname = HOST; + req.url = URL_REQUEST; + req.headers_fn = http_client_header_print_fn; + req.recv_fn = http_client_receive_print_fn; + req.tls_config = altcp_tls_create_config_client(cert_ok, sizeof(cert_ok)); + + int pass = http_client_request_sync(cyw43_arch_async_context(), &req); + altcp_tls_free_config(req.tls_config); + if (pass != 0) { + panic("test failed"); + } + + cyw43_arch_deinit(); + panic("Test passed"); +} + +void vLaunch( void) { + TaskHandle_t task; + xTaskCreate(main_task, "TestMainThread", TEST_TASK_STACK_SIZE, NULL, TEST_TASK_PRIORITY, &task); + +#if NO_SYS && configUSE_CORE_AFFINITY && configNUM_CORES > 1 + // we must bind the main task to one core (well at least while the init is called) + // (note we only do this in NO_SYS mode, because cyw43_arch_freertos + // takes care of it otherwise) + vTaskCoreAffinitySet(task, 1); +#endif + + /* Start the tasks and timer running. */ + vTaskStartScheduler(); +} + +int main( void ) +{ + stdio_init_all(); + + /* Configure the hardware ready to run the demo. */ + const char *rtos_name; +#if ( portSUPPORT_SMP == 1 ) + rtos_name = "FreeRTOS SMP"; +#else + rtos_name = "FreeRTOS"; +#endif + +#if ( portSUPPORT_SMP == 1 ) && ( configNUM_CORES == 2 ) + printf("Starting %s on both cores:\n", rtos_name); + vLaunch(); +#elif ( RUN_FREERTOS_ON_CORE == 1 ) + printf("Starting %s on core 1:\n", rtos_name); + multicore_launch_core1(vLaunch); + while (true); +#else + printf("Starting %s on core 0:\n", rtos_name); + vLaunch(); +#endif + return 0; +} diff --git a/pico_w/wifi/http_client/CMakeLists.txt b/pico_w/wifi/http_client/CMakeLists.txt new file mode 100644 index 000000000..a1be39398 --- /dev/null +++ b/pico_w/wifi/http_client/CMakeLists.txt @@ -0,0 +1,58 @@ +pico_add_library(example_lwip_http_util NOFLAG) +target_sources(example_lwip_http_util INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/example_http_client_util.c + ) +pico_mirrored_target_link_libraries(example_lwip_http_util INTERFACE + pico_lwip_http + pico_lwip_mbedtls + pico_mbedtls + ) +target_include_directories(example_lwip_http_util INTERFACE + ${CMAKE_CURRENT_LIST_DIR} + ) + +add_executable(picow_http_client + picow_http_client.c + ) +target_compile_definitions(picow_http_client PRIVATE + WIFI_SSID=\"${WIFI_SSID}\" + WIFI_PASSWORD=\"${WIFI_PASSWORD}\" + ) +target_include_directories(picow_http_client PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts and mbedtls_config + ) +target_link_libraries(picow_http_client + pico_cyw43_arch_lwip_threadsafe_background + example_lwip_http_util + pico_stdlib + ) +pico_add_extra_outputs(picow_http_client) + +add_executable(picow_http_client_verify + picow_http_verify.c + ) +target_compile_definitions(picow_http_client_verify PRIVATE + WIFI_SSID=\"${WIFI_SSID}\" + WIFI_PASSWORD=\"${WIFI_PASSWORD}\" + # By default verification is optional (MBEDTLS_SSL_VERIFY_OPTIONAL) + # Make it required for this test + ALTCP_MBEDTLS_AUTHMODE=MBEDTLS_SSL_VERIFY_REQUIRED + ) +target_include_directories(picow_http_client_verify PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts and mbedtls_config + ) +target_link_libraries(picow_http_client_verify + pico_cyw43_arch_lwip_threadsafe_background + example_lwip_http_util + pico_stdlib + ) +pico_add_extra_outputs(picow_http_client_verify) + +# Ignore warnings from lwip code +set_source_files_properties( + ${PICO_LWIP_PATH}/src/apps/altcp_tls/altcp_tls_mbedtls.c + PROPERTIES + COMPILE_OPTIONS "-Wno-unused-result" + ) diff --git a/pico_w/wifi/http_client/example_http_client_util.c b/pico_w/wifi/http_client/example_http_client_util.c new file mode 100644 index 000000000..d67f59853 --- /dev/null +++ b/pico_w/wifi/http_client/example_http_client_util.c @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include "pico/async_context.h" +#include "lwip/altcp.h" +#include "lwip/altcp_tls.h" +#include "example_http_client_util.h" + +#ifndef HTTP_INFO +#define HTTP_INFO printf +#endif + +#ifndef HTTP_INFOC +#define HTTP_INFOC putchar +#endif + +#ifndef HTTP_INFOC +#define HTTP_INFOC putchar +#endif + +#ifndef HTTP_DEBUG +#ifdef NDEBUG +#define HTTP_DEBUG +#else +#define HTTP_DEBUG printf +#endif +#endif + +#ifndef HTTP_ERROR +#define HTTP_ERROR printf +#endif + +// Print headers to stdout +err_t http_client_header_print_fn(__unused httpc_state_t *connection, __unused void *arg, struct pbuf *hdr, u16_t hdr_len, __unused u32_t content_len) { + HTTP_INFO("\nheaders %u\n", hdr_len); + u16_t offset = 0; + while (offset < hdr->tot_len && offset < hdr_len) { + char c = (char)pbuf_get_at(hdr, offset++); + HTTP_INFOC(c); + } + return ERR_OK; +} + +// Print body to stdout +err_t http_client_receive_print_fn(__unused void *arg, __unused struct altcp_pcb *conn, struct pbuf *p, err_t err) { + HTTP_INFO("\ncontent err %d\n", err); + u16_t offset = 0; + while (offset < p->tot_len) { + char c = (char)pbuf_get_at(p, offset++); + HTTP_INFOC(c); + } + return ERR_OK; +} + + +static err_t internal_header_fn(httpc_state_t *connection, void *arg, struct pbuf *hdr, u16_t hdr_len, u32_t content_len) { + assert(arg); + EXAMPLE_HTTP_REQUEST_T *req = (EXAMPLE_HTTP_REQUEST_T*)arg; + if (req->headers_fn) { + return req->headers_fn(connection, req->callback_arg, hdr, hdr_len, content_len); + } + return ERR_OK; +} + +static err_t internal_recv_fn(void *arg, struct altcp_pcb *conn, struct pbuf *p, err_t err) { + assert(arg); + EXAMPLE_HTTP_REQUEST_T *req = (EXAMPLE_HTTP_REQUEST_T*)arg; + if (req->recv_fn) { + return req->recv_fn(req->callback_arg, conn, p, err); + } + return ERR_OK; +} + +static void internal_result_fn(void *arg, httpc_result_t httpc_result, u32_t rx_content_len, u32_t srv_res, err_t err) { + assert(arg); + EXAMPLE_HTTP_REQUEST_T *req = (EXAMPLE_HTTP_REQUEST_T*)arg; + HTTP_DEBUG("result %d len %u server_response %u err %d\n", httpc_result, rx_content_len, srv_res, err); + req->complete = true; + req->result = httpc_result; + if (req->result_fn) { + req->result_fn(req->callback_arg, httpc_result, rx_content_len, srv_res, err); + } +} + +// Override altcp_tls_alloc to set sni +static struct altcp_pcb *altcp_tls_alloc_sni(void *arg, u8_t ip_type) { + assert(arg); + EXAMPLE_HTTP_REQUEST_T *req = (EXAMPLE_HTTP_REQUEST_T*)arg; + struct altcp_pcb *pcb = altcp_tls_alloc(req->tls_config, ip_type); + if (!pcb) { + HTTP_ERROR("Failed to allocate PCB\n"); + return NULL; + } + mbedtls_ssl_set_hostname(altcp_tls_context(pcb), req->hostname); + return pcb; +} + +// Make a http request, complete when req->complete returns true +int http_client_request_async(async_context_t *context, EXAMPLE_HTTP_REQUEST_T *req) { +#if LWIP_ALTCP + const uint16_t default_port = req->tls_config ? 443 : 80; + if (req->tls_config) { + if (!req->tls_allocator.alloc) { + req->tls_allocator.alloc = altcp_tls_alloc_sni; + req->tls_allocator.arg = req; + } + req->settings.altcp_allocator = &req->tls_allocator; + } +#else + const uint16_t default_port = 80; +#endif + req->complete = false; + req->settings.headers_done_fn = req->headers_fn ? internal_header_fn : NULL; + req->settings.result_fn = internal_result_fn; + async_context_acquire_lock_blocking(context); + err_t ret = httpc_get_file_dns(req->hostname, req->port ? req->port : default_port, req->url, &req->settings, internal_recv_fn, req, NULL); + async_context_release_lock(context); + if (ret != ERR_OK) { + HTTP_ERROR("http request failed: %d", ret); + } + return ret; +} + +// Make a http request and only return when it has completed. Returns true on success +int http_client_request_sync(async_context_t *context, EXAMPLE_HTTP_REQUEST_T *req) { + assert(req); + int ret = http_client_request_async(context, req); + if (ret != 0) { + return ret; + } + while(!req->complete) { + async_context_poll(context); + async_context_wait_for_work_ms(context, 1000); + } + return req->result; +} diff --git a/pico_w/wifi/http_client/example_http_client_util.h b/pico_w/wifi/http_client/example_http_client_util.h new file mode 100644 index 000000000..2049bd721 --- /dev/null +++ b/pico_w/wifi/http_client/example_http_client_util.h @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2024 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef EXAMPLE_HTTP_CLIENT_UTIL_H +#define EXAMPLE_HTTP_CLIENT_UTIL_H + +#include "lwip/apps/http_client.h" + +/*! \brief Parameters used to make HTTP request + * \ingroup pico_lwip + */ +typedef struct EXAMPLE_HTTP_REQUEST { + /*! + * The name of the host, e.g. www.raspberrypi.com + */ + const char *hostname; + /*! + * The url to request, e.g. /favicon.ico + */ + const char *url; + /*! + * Function to callback with headers, can be null + * @see httpc_headers_done_fn + */ + httpc_headers_done_fn headers_fn; + /*! + * Function to callback with results from the server, can be null + * @see altcp_recv_fn + */ + altcp_recv_fn recv_fn; + /*! + * Function to callback with final results of the request, can be null + * @see httpc_result_fn + */ + httpc_result_fn result_fn; + /*! + * Callback to pass to calback functions + */ + void *callback_arg; + /*! + * The port to use. A default port is chosen if this is set to zero + */ + uint16_t port; +#if LWIP_ALTCP && LWIP_ALTCP_TLS + /*! + * TLS configuration, can be null or set to a correctly configured tls configuration. + * e.g altcp_tls_create_config_client(NULL, 0) would use https without a certificate + */ + struct altcp_tls_config *tls_config; + /*! + * TLS allocator, used internall for setting TLS server name indication + */ + altcp_allocator_t tls_allocator; +#endif + /*! + * LwIP HTTP client settings + */ + httpc_connection_t settings; + /*! + * Flag to indicate when the request is complete + */ + int complete; + /*! + * Overall result of http request, only valid when complete is set + */ + httpc_result_t result; + +} EXAMPLE_HTTP_REQUEST_T; + +struct async_context; + +/*! \brief Perform a http request asynchronously + * \ingroup pico_lwip + * + * Perform the http request asynchronously + * + * @param context async context + * @param req HTTP request parameters. As a minimum this should be initialised to zero with hostname and url set to valid values + * @return If zero is returned the request has been made and is complete when \em req->complete is true or the result callback has been called. + * A non-zero return value indicates an error. + * + * @see async_context + */ +int http_client_request_async(struct async_context *context, EXAMPLE_HTTP_REQUEST_T *req); + +/*! \brief Perform a http request synchronously + * \ingroup pico_lwip + * + * Perform the http request synchronously + * + * @param context async context + * @param req HTTP request parameters. As a minimum this should be initialised to zero with hostname and url set to valid values + * @param result Returns the overall result of the http request when complete. Zero indicates success. + */ +int http_client_request_sync(struct async_context *context, EXAMPLE_HTTP_REQUEST_T *req); + +/*! \brief A http header callback that can be passed to \em http_client_init or \em http_client_init_secure + * \ingroup pico_http_client + * + * An implementation of the http header callback which just prints headers to stdout + * + * @param arg argument specified on initialisation + * @param hdr header pbuf(s) (may contain data also) + * @param hdr_len length of the headers in 'hdr' + * @param content_len content length as received in the headers (-1 if not received) + * @return if != zero is returned, the connection is aborted + */ +err_t http_client_header_print_fn(httpc_state_t *connection, void *arg, struct pbuf *hdr, u16_t hdr_len, u32_t content_len); + +/*! \brief A http recv callback that can be passed to http_client_init or http_client_init_secure + * \ingroup pico_http_client + * + * An implementation of the http recv callback which just prints the http body to stdout + * + * @param arg argument specified on initialisation + * @param conn http client connection + * @param p body pbuf(s) + * @param err Error code in the case of an error + * @return if != zero is returned, the connection is aborted + */ +err_t http_client_receive_print_fn(void *arg, struct altcp_pcb *conn, struct pbuf *p, err_t err); + +#endif diff --git a/pico_w/wifi/http_client/lwipopts.h b/pico_w/wifi/http_client/lwipopts.h new file mode 100644 index 000000000..50b0ab140 --- /dev/null +++ b/pico_w/wifi/http_client/lwipopts.h @@ -0,0 +1,27 @@ +#ifndef _LWIPOPTS_H +#define _LWIPOPTS_H + +// Generally you would define your own explicit list of lwIP options +// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html) +// +// This example uses a common include to avoid repetition +#include "lwipopts_examples_common.h" + +/* TCP WND must be at least 16 kb to match TLS record size + or you will get a warning "altcp_tls: TCP_WND is smaller than the RX decrypion buffer, connection RX might stall!" */ +#undef TCP_WND +#define TCP_WND 16384 + +#define LWIP_ALTCP 1 + +// If you don't want to use TLS (just a http request) you can avoid linking to mbedtls and remove the following +#define LWIP_ALTCP_TLS 1 +#define LWIP_ALTCP_TLS_MBEDTLS 1 + +// Note bug in lwip with LWIP_ALTCP and LWIP_DEBUG +// https://savannah.nongnu.org/bugs/index.php?62159 +//#define LWIP_DEBUG 1 +#undef LWIP_DEBUG +#define ALTCP_MBEDTLS_DEBUG LWIP_DBG_ON + +#endif diff --git a/pico_w/wifi/http_client/mbedtls_config.h b/pico_w/wifi/http_client/mbedtls_config.h new file mode 100644 index 000000000..f182c5661 --- /dev/null +++ b/pico_w/wifi/http_client/mbedtls_config.h @@ -0,0 +1,6 @@ +#ifndef MBEDTLS_CONFIG_TLS_CLIENT_H +#define MBEDTLS_CONFIG_TLS_CLIENT_H + +#include "mbedtls_config_examples_common.h" + +#endif \ No newline at end of file diff --git a/pico_w/wifi/http_client/picow_http_client.c b/pico_w/wifi/http_client/picow_http_client.c new file mode 100644 index 000000000..86eeee29f --- /dev/null +++ b/pico_w/wifi/http_client/picow_http_client.c @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "pico/stdio.h" +#include "pico/cyw43_arch.h" +#include "pico/async_context.h" +#include "lwip/altcp_tls.h" +#include "example_http_client_util.h" + +#define HOST "fw-download-alias1.raspberrypi.com" +#define URL_REQUEST "/net_install/boot.sig" + +int main() { + stdio_init_all(); + if (cyw43_arch_init()) { + printf("failed to initialise\n"); + return 1; + } + cyw43_arch_enable_sta_mode(); + if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 10000)) { + printf("failed to connect\n"); + return 1; + } + + EXAMPLE_HTTP_REQUEST_T req1 = {0}; + req1.hostname = HOST; + req1.url = URL_REQUEST; + req1.headers_fn = http_client_header_print_fn; + req1.recv_fn = http_client_receive_print_fn; + int result = http_client_request_sync(cyw43_arch_async_context(), &req1); + result += http_client_request_sync(cyw43_arch_async_context(), &req1); // repeat + + // test async + EXAMPLE_HTTP_REQUEST_T req2 = req1; + result += http_client_request_async(cyw43_arch_async_context(), &req1); + result += http_client_request_async(cyw43_arch_async_context(), &req2); + while(!req1.complete && !req2.complete) { + async_context_poll(cyw43_arch_async_context()); + async_context_wait_for_work_ms(cyw43_arch_async_context(), 1000); + } + + req1.tls_config = altcp_tls_create_config_client(NULL, 0); // https + result += http_client_request_sync(cyw43_arch_async_context(), &req1); + result += http_client_request_sync(cyw43_arch_async_context(), &req1); // repeat + altcp_tls_free_config(req1.tls_config); + + if (result != 0) { + panic("test failed"); + } + cyw43_arch_deinit(); + printf("Test passed\n"); + sleep_ms(100); + return 0; +} \ No newline at end of file diff --git a/pico_w/wifi/http_client/picow_http_verify.c b/pico_w/wifi/http_client/picow_http_verify.c new file mode 100644 index 000000000..a87fc16a3 --- /dev/null +++ b/pico_w/wifi/http_client/picow_http_verify.c @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "pico/stdio.h" +#include "pico/cyw43_arch.h" +#include "pico/async_context.h" +#include "lwip/altcp_tls.h" +#include "example_http_client_util.h" + +// Using this url as we know the root cert won't change for a long time +#define HOST "fw-download-alias1.raspberrypi.com" +#define URL_REQUEST "/net_install/boot.sig" + +// This is the PUBLIC root certificate exported from a browser +// Note that the newlines are needed +#define TLS_ROOT_CERT_OK "-----BEGIN CERTIFICATE-----\n\ +MIIC+jCCAn+gAwIBAgICEAAwCgYIKoZIzj0EAwIwgbcxCzAJBgNVBAYTAkdCMRAw\n\ +DgYDVQQIDAdFbmdsYW5kMRIwEAYDVQQHDAlDYW1icmlkZ2UxHTAbBgNVBAoMFFJh\n\ +c3BiZXJyeSBQSSBMaW1pdGVkMRwwGgYDVQQLDBNSYXNwYmVycnkgUEkgRUNDIENB\n\ +MR0wGwYDVQQDDBRSYXNwYmVycnkgUEkgUm9vdCBDQTEmMCQGCSqGSIb3DQEJARYX\n\ +c3VwcG9ydEByYXNwYmVycnlwaS5jb20wIBcNMjExMjA5MTEzMjU1WhgPMjA3MTEx\n\ +MjcxMTMyNTVaMIGrMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEdMBsG\n\ +A1UECgwUUmFzcGJlcnJ5IFBJIExpbWl0ZWQxHDAaBgNVBAsME1Jhc3BiZXJyeSBQ\n\ +SSBFQ0MgQ0ExJTAjBgNVBAMMHFJhc3BiZXJyeSBQSSBJbnRlcm1lZGlhdGUgQ0Ex\n\ +JjAkBgkqhkiG9w0BCQEWF3N1cHBvcnRAcmFzcGJlcnJ5cGkuY29tMHYwEAYHKoZI\n\ +zj0CAQYFK4EEACIDYgAEcN9K6Cpv+od3w6yKOnec4EbyHCBzF+X2ldjorc0b2Pq0\n\ +N+ZvyFHkhFZSgk2qvemsVEWIoPz+K4JSCpgPstz1fEV6WzgjYKfYI71ghELl5TeC\n\ +byoPY+ee3VZwF1PTy0cco2YwZDAdBgNVHQ4EFgQUJ6YzIqFh4rhQEbmCnEbWmHEo\n\ +XAUwHwYDVR0jBBgwFoAUIIAVCSiDPXut23NK39LGIyAA7NAwEgYDVR0TAQH/BAgw\n\ +BgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwIDaQAwZgIxAJYM+wIM\n\ +PC3wSPqJ1byJKA6D+ZyjKR1aORbiDQVEpDNWRKiQ5QapLg8wbcED0MrRKQIxAKUT\n\ +v8TJkb/8jC/oBVTmczKlPMkciN+uiaZSXahgYKyYhvKTatCTZb+geSIhc0w/2w==\n\ +-----END CERTIFICATE-----\n" + +// This is a test certificate +#define TLS_ROOT_CERT_BAD "-----BEGIN CERTIFICATE-----\n\ +MIIDezCCAwGgAwIBAgICEAEwCgYIKoZIzj0EAwIwgasxCzAJBgNVBAYTAkdCMRAw\n\ +DgYDVQQIDAdFbmdsYW5kMR0wGwYDVQQKDBRSYXNwYmVycnkgUEkgTGltaXRlZDEc\n\ +MBoGA1UECwwTUmFzcGJlcnJ5IFBJIEVDQyBDQTElMCMGA1UEAwwcUmFzcGJlcnJ5\n\ +IFBJIEludGVybWVkaWF0ZSBDQTEmMCQGCSqGSIb3DQEJARYXc3VwcG9ydEByYXNw\n\ +YmVycnlwaS5jb20wHhcNMjExMjA5MTMwMjIyWhcNNDYxMjAzMTMwMjIyWjA6MQsw\n\ +CQYDVQQGEwJHQjErMCkGA1UEAwwiZnctZG93bmxvYWQtYWxpYXMxLnJhc3BiZXJy\n\ +eXBpLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJ6BQv8YtNiNv7ibLtt4\n\ +lwpgEr2XD4sOl9wu/l8GnGD5p39YK8jZV0j6HaTNkqi86Nly1H7YklzbxhFy5orM\n\ +356jggGDMIIBfzAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIGQDAzBglghkgB\n\ +hvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQgU2VydmVyIENlcnRpZmljYXRlMB0G\n\ +A1UdDgQWBBRlONP3G2wTERZA9D+VxJABfiaCVTCB5QYDVR0jBIHdMIHagBQnpjMi\n\ +oWHiuFARuYKcRtaYcShcBaGBvaSBujCBtzELMAkGA1UEBhMCR0IxEDAOBgNVBAgM\n\ +B0VuZ2xhbmQxEjAQBgNVBAcMCUNhbWJyaWRnZTEdMBsGA1UECgwUUmFzcGJlcnJ5\n\ +IFBJIExpbWl0ZWQxHDAaBgNVBAsME1Jhc3BiZXJyeSBQSSBFQ0MgQ0ExHTAbBgNV\n\ +BAMMFFJhc3BiZXJyeSBQSSBSb290IENBMSYwJAYJKoZIhvcNAQkBFhdzdXBwb3J0\n\ +QHJhc3BiZXJyeXBpLmNvbYICEAAwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoG\n\ +CCsGAQUFBwMBMAoGCCqGSM49BAMCA2gAMGUCMEHerJRT0WmG5tz4oVLSIxLbCizd\n\ +//SdJBCP+072zRUKs0mfl5EcO7dXWvBAb386PwIxAL7LrgpJroJYrYJtqeufJ3a9\n\ +zVi56JFnA3cNTcDYfIzyzy5wUskPAykdrRrCS534ig==\n\ +-----END CERTIFICATE-----\n" + +int main() { + stdio_init_all(); + if (cyw43_arch_init()) { + printf("failed to initialise\n"); + return 1; + } + cyw43_arch_enable_sta_mode(); + if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 10000)) { + printf("failed to connect\n"); + return 1; + } + // This should work + static const uint8_t cert_ok[] = TLS_ROOT_CERT_OK; + EXAMPLE_HTTP_REQUEST_T req = {0}; + req.hostname = HOST; + req.url = URL_REQUEST; + req.headers_fn = http_client_header_print_fn; + req.recv_fn = http_client_receive_print_fn; + req.tls_config = altcp_tls_create_config_client(cert_ok, sizeof(cert_ok)); + int pass = http_client_request_sync(cyw43_arch_async_context(), &req); + altcp_tls_free_config(req.tls_config); + + // Repeat the test with the wrong certificate. It should fail + static const uint8_t cert_bad[] = TLS_ROOT_CERT_BAD; + req.tls_config = altcp_tls_create_config_client(cert_bad, sizeof(cert_bad)); + int fail = http_client_request_sync(cyw43_arch_async_context(), &req); + altcp_tls_free_config(req.tls_config); + + if (pass != 0 || fail == 0) { + panic("test failed"); + } + cyw43_arch_deinit(); + printf("Test passed\n"); + sleep_ms(100); + return 0; +} \ No newline at end of file From 437a2e2850bc68556f1a3b90059f092182e27586 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Fri, 22 Nov 2024 20:28:50 +0000 Subject: [PATCH 2/3] Add a bit more about why the nosys example is disabled --- pico_w/wifi/freertos/http_client/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pico_w/wifi/freertos/http_client/CMakeLists.txt b/pico_w/wifi/freertos/http_client/CMakeLists.txt index e5ce4b8e5..2010621a9 100644 --- a/pico_w/wifi/freertos/http_client/CMakeLists.txt +++ b/pico_w/wifi/freertos/http_client/CMakeLists.txt @@ -1,5 +1,7 @@ +# This example is disabled as mbedtls uses too much stack, and Freertos SMP is problematic with +# pico_cyw43_arch_lwip_threadsafe_background (nosys), where WiFi and LwIP activity is performed in an IRQ. +# Prefer to use pico_cyw43_arch_lwip_sys_freertos instead, where WiFi and LwIP activity is performed in a thread. if (0) -# Currently disabled as mbedtls uses too much stack add_executable(picow_freertos_http_client_nosys picow_freertos_http_client.c ) From f428de04ce1f321979ca3b8669eeb23076c4d75b Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Sat, 23 Nov 2024 19:23:07 +0000 Subject: [PATCH 3/3] Remove picow_freertos_http_client_nosys from readme --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 959753327..6913f4e96 100644 --- a/README.md +++ b/README.md @@ -206,7 +206,6 @@ App|Description [picow_freertos_ntp_client_socket](pico_w/wifi/freertos/ntp_client_socket) | Connects to an NTP server using the LwIP Socket API with FreeRTOS in NO_SYS=0 (i.e. full FreeRTOS integration) mode. [pico_freertos_httpd_nosys](pico_w/wifi/freertos/httpd) | Runs a LWIP HTTP server test app under FreeRTOS in NO_SYS=1 mode. [pico_freertos_httpd_sys](pico_w/wifi/freertos/httpd) | Runs a LWIP HTTP server test app under FreeRTOS in NO_SYS=0 (i.e. full FreeRTOS integration) mode. -[picow_freertos_http_client_nosys](pico_w/wifi/freertos/http_client) | Demonstrates how to make a https request in NO_SYS=1 mode [picow_freertos_http_client_sys](pico_w/wifi/freertos/http_client) | Demonstrates how to make a https request in NO_SYS=0 (i.e. full FreeRTOS integration) ### Pico W Bluetooth