1 /*
2 * lws-minimal-secure-streams-threads
3 *
4 * Written in 2010-2021 by Andy Green <andy@warmcat.com>
5 *
6 * This file is made available under the Creative Commons CC0 1.0
7 * Universal Public Domain Dedication.
8 *
9 *
10 * This demonstrates how other threads can wake the lws event loop and ask it
11 * to do things via lws_cancel_service(), notifying Secure Streams using the
12 * LWSSSCS_EVENT_WAIT_CANCELLED state callback.
13 *
14 * Because of what we're testing, we don't actually connect the SS just create
15 * it and wait for the states we are testing for to come at 10Hz.
16 *
17 * We run the test for 3s and check we got an appropriate amount of wakes
18 * to call it a success.
19 *
20 * You can use the same pattern to have any amount of shared data protected by
21 * the mutex, containing whatever the other threads want the lws event loop
22 * thread to do for them.
23 */
24
25 #include <libwebsockets.h>
26 #include <string.h>
27 #include <signal.h>
28
29 #include <pthread.h>
30
31 /*
32 * Define this to cause an ss api access from a foreign thread, it will
33 * assert. This is for testing lws, don't do this in your code.
34 */
35 // #define DO_ILLEGAL_API_THREAD
36
37 static int interrupted, bad = 1, finished;
38 static lws_sorted_usec_list_t sul_timeout;
39 static struct lws_context *context;
40 static pthread_t pthread_spam;
41 static int wakes, started_thread;
42
43 #if defined(DO_ILLEGAL_API_THREAD)
44 static struct lws_ss_handle *ss; /* only needed for DO_ILLEGAL_API_THREAD */
45 #endif
46
47 /* the data shared between the spam thread and the lws event loop */
48
49 static pthread_mutex_t lock_shared;
50 static int shared_counter;
51
52
53 #if !defined(LWS_SS_USE_SSPC)
54 static const char * const default_ss_policy =
55 "{"
56 "\"schema-version\":1,"
57 "\"s\": ["
58 "{"
59 "\"mintest\": {"
60 "\"endpoint\": \"connectivitycheck.android.com\","
61 "\"http_url\": \"generate_204\","
62 "\"port\": 80,"
63 "\"protocol\": \"h1\","
64 "\"http_method\": \"GET\","
65 "\"opportunistic\": true,"
66 "\"http_expect\": 204,"
67 "\"http_fail_redirect\": true"
68 "}"
69 "}"
70 "]"
71 "}"
72 ;
73
74 #endif
75
76 typedef struct myss {
77 struct lws_ss_handle *ss;
78 void *opaque_data;
79 /* ... application specific state ... */
80 } myss_t;
81
82 static void *
thread_spam(void * d)83 thread_spam(void *d)
84 {
85
86 do {
87 pthread_mutex_lock(&lock_shared); /* --------- shared lock { */
88
89 /*
90 * prepare the shared data area to indicate whatever it is that
91 * we want doing on the main event loop. In this case, we just
92 * bump a counter, but it can be any amount of data prepared,
93 * eg, whole info struct for a connection we want.
94 */
95
96 shared_counter++;
97
98 lwsl_notice("%s: cancelling wait from spam thread: %d\n",
99 __func__, shared_counter);
100 lws_cancel_service(context);
101
102 #if defined(DO_ILLEGAL_API_THREAD)
103 /*
104 * ILLEGAL...
105 * We cannot call any other lws api from a foreign thread
106 */
107
108 if (ss)
109 lws_ss_request_tx(ss);
110 #endif
111
112 pthread_mutex_unlock(&lock_shared); /* } shared lock ------- */
113
114 usleep(100000); /* wait 100ms and signal main thread again */
115
116 } while (!finished);
117
118 pthread_exit(NULL);
119
120 return NULL;
121 }
122
123
124 static lws_ss_state_return_t
myss_state(void * userobj,void * h_src,lws_ss_constate_t state,lws_ss_tx_ordinal_t ack)125 myss_state(void *userobj, void *h_src, lws_ss_constate_t state,
126 lws_ss_tx_ordinal_t ack)
127 {
128 // myss_t *m = (myss_t *)userobj;
129 void *retval;
130
131 switch (state) {
132 case LWSSSCS_CREATING:
133 if (pthread_create(&pthread_spam, NULL, thread_spam, NULL)) {
134 lwsl_err("thread creation failed\n");
135 return LWSSSSRET_DESTROY_ME;
136 }
137 started_thread = 1;
138 break;
139 case LWSSSCS_DESTROYING:
140 finished = 1;
141 if (started_thread)
142 pthread_join(pthread_spam, &retval);
143 break;
144
145 case LWSSSCS_EVENT_WAIT_CANCELLED:
146 pthread_mutex_lock(&lock_shared); /* --------- shared lock { */
147 lwsl_notice("%s: LWSSSCS_EVENT_WAIT_CANCELLED: %d, shared: %d\n",
148 __func__, ++wakes, shared_counter);
149 pthread_mutex_unlock(&lock_shared); /* } shared lock ------- */
150 break;
151
152 default:
153 break;
154 }
155
156 return LWSSSSRET_OK;
157 }
158
159 static const lws_ss_info_t ssi_lws_threads = {
160 .handle_offset = offsetof(myss_t, ss),
161 .opaque_user_data_offset = offsetof(myss_t, opaque_data),
162 /* we don't actually do any rx or tx in this test */
163 .state = myss_state,
164 .user_alloc = sizeof(myss_t),
165 .streamtype = "mintest",
166 .manual_initial_tx_credit = 0,
167 };
168
169 static void
sul_timeout_cb(lws_sorted_usec_list_t * sul)170 sul_timeout_cb(lws_sorted_usec_list_t *sul)
171 {
172 lwsl_notice("%s: test finishing\n", __func__);
173 interrupted = 1;
174 }
175
176
177 static void
sigint_handler(int sig)178 sigint_handler(int sig)
179 {
180 interrupted = 1;
181 }
182
183 static int
system_notify_cb(lws_state_manager_t * mgr,lws_state_notify_link_t * link,int current,int target)184 system_notify_cb(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
185 int current, int target)
186 {
187 if (current != LWS_SYSTATE_OPERATIONAL || target != LWS_SYSTATE_OPERATIONAL)
188 return 0;
189
190 /* the test SS.. not going to connect it, just see if the cancel_service
191 * messages are coming
192 */
193
194 if (lws_ss_create(context, 0, &ssi_lws_threads, NULL,
195 #if defined(DO_ILLEGAL_API_THREAD)
196 &ss,
197 #else
198 NULL,
199 #endif
200 NULL, NULL)) {
201 lwsl_err("%s: failed to create secure stream\n",
202 __func__);
203
204 return -1;
205 }
206
207 /* set up the test timeout */
208
209 lws_sul_schedule(context, 0, &sul_timeout, sul_timeout_cb,
210 3 * LWS_US_PER_SEC);
211
212 return 0;
213 }
214
main(int argc,const char ** argv)215 int main(int argc, const char **argv)
216 {
217 lws_state_notify_link_t notifier = { { NULL, NULL, NULL},
218 system_notify_cb, "app" };
219 lws_state_notify_link_t *na[] = { ¬ifier, NULL };
220 struct lws_context_creation_info info;
221
222 signal(SIGINT, sigint_handler);
223
224 memset(&info, 0, sizeof info);
225
226 lws_cmdline_option_handle_builtin(argc, argv, &info);
227
228 lwsl_user("LWS Secure Streams threads test client [-d<verb>]\n");
229
230 info.fd_limit_per_thread = 1 + 6 + 1;
231 info.port = CONTEXT_PORT_NO_LISTEN;
232 #if !defined(LWS_SS_USE_SSPC)
233 info.pss_policies_json = default_ss_policy;
234 #else
235 info.protocols = lws_sspc_protocols;
236 {
237 const char *p;
238
239 /* connect to ssproxy via UDS by default, else via
240 * tcp connection to this port */
241 if ((p = lws_cmdline_option(argc, argv, "-p")))
242 info.ss_proxy_port = (uint16_t)atoi(p);
243
244 /* UDS "proxy.ss.lws" in abstract namespace, else this socket
245 * path; when -p given this can specify the network interface
246 * to bind to */
247 if ((p = lws_cmdline_option(argc, argv, "-i")))
248 info.ss_proxy_bind = p;
249
250 /* if -p given, -a specifies the proxy address to connect to */
251 if ((p = lws_cmdline_option(argc, argv, "-a")))
252 info.ss_proxy_address = p;
253 }
254 #endif
255 info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
256 LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
257 info.register_notifier_list = na;
258
259 /* create the context */
260
261 context = lws_create_context(&info);
262 if (!context) {
263 lwsl_err("lws init failed\n");
264 return 1;
265 }
266
267 #if defined(LWS_SS_USE_SSPC)
268 if (!lws_create_vhost(context, &info)) {
269 lwsl_err("%s: failed to create default vhost\n", __func__);
270 goto bail;
271 }
272 #endif
273
274 /* the event loop */
275
276 while (lws_service(context, 0) >= 0 && !interrupted)
277 ;
278
279 /* compare what happened with what we expect */
280
281 if (wakes > 10)
282 /* OSX can do the usleep thread slower than 100ms */
283 bad = 0;
284
285 lwsl_notice("wakes %d\n", wakes);
286
287 #if defined(LWS_SS_USE_SSPC)
288 bail:
289 #endif
290 lws_sul_cancel(&sul_timeout);
291 lws_context_destroy(context);
292
293 lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
294
295 return bad;
296 }
297