1+ /**
2+ * Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
3+ *
4+ * SPDX-License-Identifier: BSD-3-Clause
5+ */
6+
7+ #include <stdio.h>
8+ #include <string.h>
9+ #include "http_client.h"
10+ #include "pico/async_context.h"
11+ #include "lwip/altcp_tls.h"
12+
13+ #ifndef DEBUG_printf
14+ #define DEBUG_printf printf
15+ #endif
16+
17+ #ifndef DEBUG_putchar
18+ #define DEBUG_putchar putchar
19+ #endif
20+
21+ typedef struct HTTP_STATE {
22+ bool complete ;
23+ httpc_result_t httpc_result ;
24+ struct altcp_tls_config * tls_config ;
25+ altcp_allocator_t tls_allocator ;
26+ httpc_connection_t settings ;
27+ const char * hostname ;
28+ altcp_recv_fn recv_fn ;
29+ bool use_https ;
30+ void * arg ;
31+ } HTTP_STATE_T ;
32+ static HTTP_STATE_T http_state = { 0 };
33+
34+ // Print headers to stdout
35+ err_t headers_print_fn (httpc_state_t * connection , void * arg , struct pbuf * hdr , u16_t hdr_len , u32_t content_len ) {
36+ DEBUG_printf ("\nheaders %u\n" , hdr_len );
37+ u16_t offset = 0 ;
38+ while (offset < hdr -> tot_len && offset < hdr_len ) {
39+ char c = (char )pbuf_get_at (hdr , offset ++ );
40+ DEBUG_putchar (c );
41+ }
42+ return ERR_OK ;
43+ }
44+
45+ // Print body to stdout
46+ err_t receive_print_fn (void * arg , struct altcp_pcb * conn , struct pbuf * p , err_t err ) {
47+ DEBUG_printf ("\ncontent err %d\n" , err );
48+ u16_t offset = 0 ;
49+ while (offset < p -> tot_len ) {
50+ char c = (char )pbuf_get_at (p , offset ++ );
51+ DEBUG_putchar (c );
52+ }
53+ return ERR_OK ;
54+ }
55+
56+ static void result_fn (void * arg , httpc_result_t httpc_result , u32_t rx_content_len , u32_t srv_res , err_t err ) {
57+ DEBUG_printf ("result %d len %u server_response %u err %d\n" , httpc_result , rx_content_len , srv_res , err );
58+ http_state .complete = true;
59+ http_state .httpc_result = httpc_result ;
60+ }
61+
62+ // Override altcp_tls_alloc to set sni
63+ static struct altcp_pcb * altcp_tls_alloc_sni (void * arg , u8_t ip_type ) {
64+ struct altcp_pcb * pcb = altcp_tls_alloc (http_state .tls_config , ip_type );
65+ if (!pcb ) {
66+ return NULL ;
67+ }
68+ mbedtls_ssl_set_hostname (altcp_tls_context (pcb ), http_state .hostname );
69+ return pcb ;
70+ }
71+
72+ static void http_client_init_internal (const char * hostname , bool use_https , const uint8_t * cert , size_t cert_len , httpc_headers_done_fn headers_fn , altcp_recv_fn recv_fn , void * arg ) {
73+ assert (hostname && !http_state .hostname );
74+ http_state .hostname = hostname ;
75+ http_state .settings .headers_done_fn = headers_fn ; // can be null
76+ http_state .settings .result_fn = result_fn ;
77+ http_state .recv_fn = recv_fn ;
78+ http_state .use_https = use_https ;
79+ http_state .arg = arg ;
80+ if (http_state .use_https ) {
81+ if (!cert || cert_len == 0 ) {
82+ DEBUG_printf ("Warning: https used without a certificate is insecure\n" );
83+ } else {
84+ #if ALTCP_MBEDTLS_AUTHMODE != MBEDTLS_SSL_VERIFY_REQUIRED
85+ DEBUG_printf ("Warning: https used without verificatation is insecure\n" );
86+ #endif
87+ }
88+ http_state .tls_config = altcp_tls_create_config_client (cert , cert_len );
89+ http_state .tls_allocator .alloc = altcp_tls_alloc_sni ;
90+ http_state .settings .altcp_allocator = & http_state .tls_allocator ;
91+ } else {
92+ // Can't use a cert without https
93+ assert (!cert && cert_len == 0 );
94+ }
95+ }
96+
97+ void http_client_init_basic (const char * hostname , httpc_headers_done_fn headers_fn , altcp_recv_fn recv_fn , void * arg ) {
98+ http_client_init_internal (hostname , false, NULL , 0 , headers_fn , recv_fn , arg );
99+ }
100+
101+ void http_client_init_secure (const char * hostname , httpc_headers_done_fn headers_fn , altcp_recv_fn recv_fn , void * arg , const uint8_t * cert , size_t cert_len ) {
102+ http_client_init_internal (hostname , true, cert , cert_len , headers_fn , recv_fn , arg );
103+ }
104+
105+ // Make a http request, complete when http_client_run_complete returns true
106+ bool http_client_run_async (const char * url ) {
107+ http_state .httpc_result = HTTPC_RESULT_ERR_UNKNOWN ; // make sure we see real success
108+ http_state .complete = false;
109+ err_t ret = httpc_get_file_dns (http_state .hostname , http_state .use_https ? 443 : 80 , url , & http_state .settings , http_state .recv_fn , & http_state , NULL );
110+ if (ret != ERR_OK ) {
111+ DEBUG_printf ("http request failed: %d" , ret );
112+ return false;
113+ }
114+ return true;
115+ }
116+
117+ // Check if the http request is complete and return the result
118+ bool http_client_run_complete (httpc_result_t * result ) {
119+ if (http_state .complete ) {
120+ * result = http_state .httpc_result ;
121+ return true;
122+ }
123+ return false;
124+ }
125+
126+ // Make a http request and only return when it has completed. Returns true on success
127+ bool http_client_run_sync (async_context_t * context , const char * url ) {
128+ if (!http_client_run_async (url )) {
129+ return false;
130+ }
131+ while (!http_state .complete ) {
132+ async_context_poll (context );
133+ async_context_wait_for_work_ms (context , 1000 );
134+ }
135+ return http_state .httpc_result == HTTPC_RESULT_OK ;
136+ }
137+
138+ // Deinitialise the http client
139+ void http_client_deinit (void ) {
140+ if (http_state .tls_config ) {
141+ altcp_tls_free_config (http_state .tls_config );
142+ }
143+ memset (& http_state , 0 , sizeof (http_state ));
144+ }
0 commit comments