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