1*1c60b9acSAndroid Build Coastguard Worker /*
2*1c60b9acSAndroid Build Coastguard Worker  * lws-minimal-secure-streams-binance
3*1c60b9acSAndroid Build Coastguard Worker  *
4*1c60b9acSAndroid Build Coastguard Worker  * Written in 2010-2021 by Andy Green <[email protected]>
5*1c60b9acSAndroid Build Coastguard Worker  *                         Kutoga <[email protected]>
6*1c60b9acSAndroid Build Coastguard Worker  *
7*1c60b9acSAndroid Build Coastguard Worker  * This file is made available under the Creative Commons CC0 1.0
8*1c60b9acSAndroid Build Coastguard Worker  * Universal Public Domain Dedication.
9*1c60b9acSAndroid Build Coastguard Worker  *
10*1c60b9acSAndroid Build Coastguard Worker  * This demonstrates a Secure Streams implementation of a client that connects
11*1c60b9acSAndroid Build Coastguard Worker  * to binance ws server efficiently.
12*1c60b9acSAndroid Build Coastguard Worker  *
13*1c60b9acSAndroid Build Coastguard Worker  * Build lws with -DLWS_WITH_SECURE_STREAMS=1 -DLWS_WITHOUT_EXTENSIONS=0
14*1c60b9acSAndroid Build Coastguard Worker  *
15*1c60b9acSAndroid Build Coastguard Worker  * "policy.json" contains all the information about endpoints, protocols and
16*1c60b9acSAndroid Build Coastguard Worker  * connection validation, tagged by streamtype name.
17*1c60b9acSAndroid Build Coastguard Worker  *
18*1c60b9acSAndroid Build Coastguard Worker  * The example tries to load it from the cwd, it lives
19*1c60b9acSAndroid Build Coastguard Worker  * in ./minimal-examples/secure-streams/minimal-secure-streams-binance dir, so
20*1c60b9acSAndroid Build Coastguard Worker  * either run it from there, or copy the policy.json to your cwd.  It's also
21*1c60b9acSAndroid Build Coastguard Worker  * possible to put the policy json in the code as a string and pass that at
22*1c60b9acSAndroid Build Coastguard Worker  * context creation time.
23*1c60b9acSAndroid Build Coastguard Worker  */
24*1c60b9acSAndroid Build Coastguard Worker 
25*1c60b9acSAndroid Build Coastguard Worker #include <libwebsockets.h>
26*1c60b9acSAndroid Build Coastguard Worker #include <string.h>
27*1c60b9acSAndroid Build Coastguard Worker #include <signal.h>
28*1c60b9acSAndroid Build Coastguard Worker #include <ctype.h>
29*1c60b9acSAndroid Build Coastguard Worker 
30*1c60b9acSAndroid Build Coastguard Worker static int interrupted;
31*1c60b9acSAndroid Build Coastguard Worker 
32*1c60b9acSAndroid Build Coastguard Worker typedef struct range {
33*1c60b9acSAndroid Build Coastguard Worker 	uint64_t		sum;
34*1c60b9acSAndroid Build Coastguard Worker 	uint64_t		lowest;
35*1c60b9acSAndroid Build Coastguard Worker 	uint64_t		highest;
36*1c60b9acSAndroid Build Coastguard Worker 
37*1c60b9acSAndroid Build Coastguard Worker 	unsigned int		samples;
38*1c60b9acSAndroid Build Coastguard Worker } range_t;
39*1c60b9acSAndroid Build Coastguard Worker 
40*1c60b9acSAndroid Build Coastguard Worker typedef struct binance {
41*1c60b9acSAndroid Build Coastguard Worker 	struct lws_ss_handle 	*ss;
42*1c60b9acSAndroid Build Coastguard Worker 	void			*opaque_data;
43*1c60b9acSAndroid Build Coastguard Worker 
44*1c60b9acSAndroid Build Coastguard Worker 	lws_sorted_usec_list_t	sul_hz;	     /* 1hz summary dump */
45*1c60b9acSAndroid Build Coastguard Worker 
46*1c60b9acSAndroid Build Coastguard Worker 	range_t			e_lat_range;
47*1c60b9acSAndroid Build Coastguard Worker 	range_t			price_range;
48*1c60b9acSAndroid Build Coastguard Worker } binance_t;
49*1c60b9acSAndroid Build Coastguard Worker 
50*1c60b9acSAndroid Build Coastguard Worker /****** Part 1 / 3: application data processing */
51*1c60b9acSAndroid Build Coastguard Worker 
52*1c60b9acSAndroid Build Coastguard Worker static void
range_reset(range_t * r)53*1c60b9acSAndroid Build Coastguard Worker range_reset(range_t *r)
54*1c60b9acSAndroid Build Coastguard Worker {
55*1c60b9acSAndroid Build Coastguard Worker 	r->sum = r->highest = 0;
56*1c60b9acSAndroid Build Coastguard Worker 	r->lowest = 999999999999ull;
57*1c60b9acSAndroid Build Coastguard Worker 	r->samples = 0;
58*1c60b9acSAndroid Build Coastguard Worker }
59*1c60b9acSAndroid Build Coastguard Worker 
60*1c60b9acSAndroid Build Coastguard Worker static uint64_t
get_us_timeofday(void)61*1c60b9acSAndroid Build Coastguard Worker get_us_timeofday(void)
62*1c60b9acSAndroid Build Coastguard Worker {
63*1c60b9acSAndroid Build Coastguard Worker 	struct timeval tv;
64*1c60b9acSAndroid Build Coastguard Worker 
65*1c60b9acSAndroid Build Coastguard Worker 	gettimeofday(&tv, NULL);
66*1c60b9acSAndroid Build Coastguard Worker 
67*1c60b9acSAndroid Build Coastguard Worker 	return (uint64_t)((lws_usec_t)tv.tv_sec * LWS_US_PER_SEC) +
68*1c60b9acSAndroid Build Coastguard Worker 			  (uint64_t)tv.tv_usec;
69*1c60b9acSAndroid Build Coastguard Worker }
70*1c60b9acSAndroid Build Coastguard Worker 
71*1c60b9acSAndroid Build Coastguard Worker static uint64_t
pennies(const char * s)72*1c60b9acSAndroid Build Coastguard Worker pennies(const char *s)
73*1c60b9acSAndroid Build Coastguard Worker {
74*1c60b9acSAndroid Build Coastguard Worker 	uint64_t price = (uint64_t)atoll(s) * 100;
75*1c60b9acSAndroid Build Coastguard Worker 
76*1c60b9acSAndroid Build Coastguard Worker 	s = strchr(s, '.');
77*1c60b9acSAndroid Build Coastguard Worker 
78*1c60b9acSAndroid Build Coastguard Worker 	if (s && isdigit(s[1]) && isdigit(s[2]))
79*1c60b9acSAndroid Build Coastguard Worker 		price = price + (uint64_t)((10 * (s[1] - '0')) + (s[2] - '0'));
80*1c60b9acSAndroid Build Coastguard Worker 
81*1c60b9acSAndroid Build Coastguard Worker 	return price;
82*1c60b9acSAndroid Build Coastguard Worker }
83*1c60b9acSAndroid Build Coastguard Worker 
84*1c60b9acSAndroid Build Coastguard Worker static void
sul_hz_cb(lws_sorted_usec_list_t * sul)85*1c60b9acSAndroid Build Coastguard Worker sul_hz_cb(lws_sorted_usec_list_t *sul)
86*1c60b9acSAndroid Build Coastguard Worker {
87*1c60b9acSAndroid Build Coastguard Worker 	binance_t *bin = lws_container_of(sul, binance_t, sul_hz);
88*1c60b9acSAndroid Build Coastguard Worker 
89*1c60b9acSAndroid Build Coastguard Worker 	/*
90*1c60b9acSAndroid Build Coastguard Worker 	 * We are called once a second to dump statistics on the connection
91*1c60b9acSAndroid Build Coastguard Worker 	 */
92*1c60b9acSAndroid Build Coastguard Worker 
93*1c60b9acSAndroid Build Coastguard Worker 	lws_sul_schedule(lws_ss_get_context(bin->ss), 0, &bin->sul_hz,
94*1c60b9acSAndroid Build Coastguard Worker 			 sul_hz_cb, LWS_US_PER_SEC);
95*1c60b9acSAndroid Build Coastguard Worker 
96*1c60b9acSAndroid Build Coastguard Worker 	if (bin->price_range.samples)
97*1c60b9acSAndroid Build Coastguard Worker 		lwsl_notice("%s: price: min: %llu¢, max: %llu¢, avg: %llu¢, "
98*1c60b9acSAndroid Build Coastguard Worker 			    "(%d prices/s)\n", __func__,
99*1c60b9acSAndroid Build Coastguard Worker 			    (unsigned long long)bin->price_range.lowest,
100*1c60b9acSAndroid Build Coastguard Worker 			    (unsigned long long)bin->price_range.highest,
101*1c60b9acSAndroid Build Coastguard Worker 			    (unsigned long long)(bin->price_range.sum /
102*1c60b9acSAndroid Build Coastguard Worker 						    bin->price_range.samples),
103*1c60b9acSAndroid Build Coastguard Worker 			    bin->price_range.samples);
104*1c60b9acSAndroid Build Coastguard Worker 	if (bin->e_lat_range.samples)
105*1c60b9acSAndroid Build Coastguard Worker 		lwsl_notice("%s: elatency: min: %llums, max: %llums, "
106*1c60b9acSAndroid Build Coastguard Worker 			    "avg: %llums, (%d msg/s)\n", __func__,
107*1c60b9acSAndroid Build Coastguard Worker 			    (unsigned long long)bin->e_lat_range.lowest / 1000,
108*1c60b9acSAndroid Build Coastguard Worker 			    (unsigned long long)bin->e_lat_range.highest / 1000,
109*1c60b9acSAndroid Build Coastguard Worker 			    (unsigned long long)(bin->e_lat_range.sum /
110*1c60b9acSAndroid Build Coastguard Worker 					   bin->e_lat_range.samples) / 1000,
111*1c60b9acSAndroid Build Coastguard Worker 			    bin->e_lat_range.samples);
112*1c60b9acSAndroid Build Coastguard Worker 
113*1c60b9acSAndroid Build Coastguard Worker 	range_reset(&bin->e_lat_range);
114*1c60b9acSAndroid Build Coastguard Worker 	range_reset(&bin->price_range);
115*1c60b9acSAndroid Build Coastguard Worker }
116*1c60b9acSAndroid Build Coastguard Worker 
117*1c60b9acSAndroid Build Coastguard Worker /****** Part 2 / 3: communication */
118*1c60b9acSAndroid Build Coastguard Worker 
119*1c60b9acSAndroid Build Coastguard Worker static lws_ss_state_return_t
binance_rx(void * userobj,const uint8_t * in,size_t len,int flags)120*1c60b9acSAndroid Build Coastguard Worker binance_rx(void *userobj, const uint8_t *in, size_t len, int flags)
121*1c60b9acSAndroid Build Coastguard Worker {
122*1c60b9acSAndroid Build Coastguard Worker 	binance_t *bin = (binance_t *)userobj;
123*1c60b9acSAndroid Build Coastguard Worker 	uint64_t latency_us, now_us;
124*1c60b9acSAndroid Build Coastguard Worker 	char numbuf[16];
125*1c60b9acSAndroid Build Coastguard Worker 	uint64_t price;
126*1c60b9acSAndroid Build Coastguard Worker 	const char *p;
127*1c60b9acSAndroid Build Coastguard Worker 	size_t alen;
128*1c60b9acSAndroid Build Coastguard Worker 
129*1c60b9acSAndroid Build Coastguard Worker 	now_us = (uint64_t)get_us_timeofday();
130*1c60b9acSAndroid Build Coastguard Worker 
131*1c60b9acSAndroid Build Coastguard Worker 	p = lws_json_simple_find((const char *)in, len, "\"depthUpdate\"",
132*1c60b9acSAndroid Build Coastguard Worker 				 &alen);
133*1c60b9acSAndroid Build Coastguard Worker 	if (!p)
134*1c60b9acSAndroid Build Coastguard Worker 		return LWSSSSRET_OK;
135*1c60b9acSAndroid Build Coastguard Worker 
136*1c60b9acSAndroid Build Coastguard Worker 	p = lws_json_simple_find((const char *)in, len, "\"E\":", &alen);
137*1c60b9acSAndroid Build Coastguard Worker 	if (!p) {
138*1c60b9acSAndroid Build Coastguard Worker 		lwsl_err("%s: no E JSON\n", __func__);
139*1c60b9acSAndroid Build Coastguard Worker 		return LWSSSSRET_OK;
140*1c60b9acSAndroid Build Coastguard Worker 	}
141*1c60b9acSAndroid Build Coastguard Worker 
142*1c60b9acSAndroid Build Coastguard Worker 	lws_strnncpy(numbuf, p, alen, sizeof(numbuf));
143*1c60b9acSAndroid Build Coastguard Worker 	latency_us = now_us - ((uint64_t)atoll(numbuf) * LWS_US_PER_MS);
144*1c60b9acSAndroid Build Coastguard Worker 
145*1c60b9acSAndroid Build Coastguard Worker 	if (latency_us < bin->e_lat_range.lowest)
146*1c60b9acSAndroid Build Coastguard Worker 		bin->e_lat_range.lowest = latency_us;
147*1c60b9acSAndroid Build Coastguard Worker 	if (latency_us > bin->e_lat_range.highest)
148*1c60b9acSAndroid Build Coastguard Worker 		bin->e_lat_range.highest = latency_us;
149*1c60b9acSAndroid Build Coastguard Worker 
150*1c60b9acSAndroid Build Coastguard Worker 	bin->e_lat_range.sum += latency_us;
151*1c60b9acSAndroid Build Coastguard Worker 	bin->e_lat_range.samples++;
152*1c60b9acSAndroid Build Coastguard Worker 
153*1c60b9acSAndroid Build Coastguard Worker 	p = lws_json_simple_find((const char *)in, len, "\"a\":[[\"", &alen);
154*1c60b9acSAndroid Build Coastguard Worker 	if (!p)
155*1c60b9acSAndroid Build Coastguard Worker 		return LWSSSSRET_OK;
156*1c60b9acSAndroid Build Coastguard Worker 
157*1c60b9acSAndroid Build Coastguard Worker 	lws_strnncpy(numbuf, p, alen, sizeof(numbuf));
158*1c60b9acSAndroid Build Coastguard Worker 	price = pennies(numbuf);
159*1c60b9acSAndroid Build Coastguard Worker 
160*1c60b9acSAndroid Build Coastguard Worker 	if (price < bin->price_range.lowest)
161*1c60b9acSAndroid Build Coastguard Worker 		bin->price_range.lowest = price;
162*1c60b9acSAndroid Build Coastguard Worker 	if (price > bin->price_range.highest)
163*1c60b9acSAndroid Build Coastguard Worker 		bin->price_range.highest = price;
164*1c60b9acSAndroid Build Coastguard Worker 
165*1c60b9acSAndroid Build Coastguard Worker 	bin->price_range.sum += price;
166*1c60b9acSAndroid Build Coastguard Worker 	bin->price_range.samples++;
167*1c60b9acSAndroid Build Coastguard Worker 
168*1c60b9acSAndroid Build Coastguard Worker 	return LWSSSSRET_OK;
169*1c60b9acSAndroid Build Coastguard Worker }
170*1c60b9acSAndroid Build Coastguard Worker 
171*1c60b9acSAndroid Build Coastguard Worker static lws_ss_state_return_t
binance_state(void * userobj,void * h_src,lws_ss_constate_t state,lws_ss_tx_ordinal_t ack)172*1c60b9acSAndroid Build Coastguard Worker binance_state(void *userobj, void *h_src, lws_ss_constate_t state,
173*1c60b9acSAndroid Build Coastguard Worker 	      lws_ss_tx_ordinal_t ack)
174*1c60b9acSAndroid Build Coastguard Worker {
175*1c60b9acSAndroid Build Coastguard Worker 	binance_t *bin = (binance_t *)userobj;
176*1c60b9acSAndroid Build Coastguard Worker 
177*1c60b9acSAndroid Build Coastguard Worker 	lwsl_ss_info(bin->ss, "%s (%d), ord 0x%x",
178*1c60b9acSAndroid Build Coastguard Worker 		     lws_ss_state_name((int)state), state, (unsigned int)ack);
179*1c60b9acSAndroid Build Coastguard Worker 
180*1c60b9acSAndroid Build Coastguard Worker 	switch (state) {
181*1c60b9acSAndroid Build Coastguard Worker 
182*1c60b9acSAndroid Build Coastguard Worker 	case LWSSSCS_CONNECTED:
183*1c60b9acSAndroid Build Coastguard Worker 		lws_sul_schedule(lws_ss_get_context(bin->ss), 0, &bin->sul_hz,
184*1c60b9acSAndroid Build Coastguard Worker 				 sul_hz_cb, LWS_US_PER_SEC);
185*1c60b9acSAndroid Build Coastguard Worker 		range_reset(&bin->e_lat_range);
186*1c60b9acSAndroid Build Coastguard Worker 		range_reset(&bin->price_range);
187*1c60b9acSAndroid Build Coastguard Worker 
188*1c60b9acSAndroid Build Coastguard Worker 		return LWSSSSRET_OK;
189*1c60b9acSAndroid Build Coastguard Worker 
190*1c60b9acSAndroid Build Coastguard Worker 	case LWSSSCS_DISCONNECTED:
191*1c60b9acSAndroid Build Coastguard Worker 		lws_sul_cancel(&bin->sul_hz);
192*1c60b9acSAndroid Build Coastguard Worker 		break;
193*1c60b9acSAndroid Build Coastguard Worker 
194*1c60b9acSAndroid Build Coastguard Worker 	default:
195*1c60b9acSAndroid Build Coastguard Worker 		break;
196*1c60b9acSAndroid Build Coastguard Worker 	}
197*1c60b9acSAndroid Build Coastguard Worker 
198*1c60b9acSAndroid Build Coastguard Worker 	return LWSSSSRET_OK;
199*1c60b9acSAndroid Build Coastguard Worker }
200*1c60b9acSAndroid Build Coastguard Worker 
201*1c60b9acSAndroid Build Coastguard Worker static const lws_ss_info_t ssi_binance = {
202*1c60b9acSAndroid Build Coastguard Worker 	.handle_offset		  = offsetof(binance_t, ss),
203*1c60b9acSAndroid Build Coastguard Worker 	.opaque_user_data_offset  = offsetof(binance_t, opaque_data),
204*1c60b9acSAndroid Build Coastguard Worker 	.rx			  = binance_rx,
205*1c60b9acSAndroid Build Coastguard Worker 	.state			  = binance_state,
206*1c60b9acSAndroid Build Coastguard Worker 	.user_alloc		  = sizeof(binance_t),
207*1c60b9acSAndroid Build Coastguard Worker 	.streamtype		  = "binance", /* bind to corresponding policy */
208*1c60b9acSAndroid Build Coastguard Worker };
209*1c60b9acSAndroid Build Coastguard Worker 
210*1c60b9acSAndroid Build Coastguard Worker /****** Part 3 / 3: init and event loop */
211*1c60b9acSAndroid Build Coastguard Worker 
212*1c60b9acSAndroid Build Coastguard Worker static const struct lws_extension extensions[] = {
213*1c60b9acSAndroid Build Coastguard Worker 	{
214*1c60b9acSAndroid Build Coastguard Worker 		"permessage-deflate", lws_extension_callback_pm_deflate,
215*1c60b9acSAndroid Build Coastguard Worker 		"permessage-deflate" "; client_no_context_takeover"
216*1c60b9acSAndroid Build Coastguard Worker 		 "; client_max_window_bits"
217*1c60b9acSAndroid Build Coastguard Worker 	},
218*1c60b9acSAndroid Build Coastguard Worker 	{ NULL, NULL, NULL /* terminator */ }
219*1c60b9acSAndroid Build Coastguard Worker };
220*1c60b9acSAndroid Build Coastguard Worker 
221*1c60b9acSAndroid Build Coastguard Worker static void
sigint_handler(int sig)222*1c60b9acSAndroid Build Coastguard Worker sigint_handler(int sig)
223*1c60b9acSAndroid Build Coastguard Worker {
224*1c60b9acSAndroid Build Coastguard Worker 	interrupted = 1;
225*1c60b9acSAndroid Build Coastguard Worker }
226*1c60b9acSAndroid Build Coastguard Worker 
main(int argc,const char ** argv)227*1c60b9acSAndroid Build Coastguard Worker int main(int argc, const char **argv)
228*1c60b9acSAndroid Build Coastguard Worker {
229*1c60b9acSAndroid Build Coastguard Worker 	struct lws_context_creation_info info;
230*1c60b9acSAndroid Build Coastguard Worker 	struct lws_context *cx;
231*1c60b9acSAndroid Build Coastguard Worker 	int n = 0;
232*1c60b9acSAndroid Build Coastguard Worker 
233*1c60b9acSAndroid Build Coastguard Worker 	signal(SIGINT, sigint_handler);
234*1c60b9acSAndroid Build Coastguard Worker 
235*1c60b9acSAndroid Build Coastguard Worker 	memset(&info, 0, sizeof info);
236*1c60b9acSAndroid Build Coastguard Worker 	lws_cmdline_option_handle_builtin(argc, argv, &info);
237*1c60b9acSAndroid Build Coastguard Worker 
238*1c60b9acSAndroid Build Coastguard Worker 	lwsl_user("LWS minimal Secure Streams binance client\n");
239*1c60b9acSAndroid Build Coastguard Worker 
240*1c60b9acSAndroid Build Coastguard Worker 	info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
241*1c60b9acSAndroid Build Coastguard Worker 		       LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
242*1c60b9acSAndroid Build Coastguard Worker 	info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
243*1c60b9acSAndroid Build Coastguard Worker 	info.fd_limit_per_thread = 1 + 1 + 1;
244*1c60b9acSAndroid Build Coastguard Worker 	info.extensions = extensions;
245*1c60b9acSAndroid Build Coastguard Worker 	info.pss_policies_json = "policy.json"; /* literal JSON, or path */
246*1c60b9acSAndroid Build Coastguard Worker 
247*1c60b9acSAndroid Build Coastguard Worker 	cx = lws_create_context(&info);
248*1c60b9acSAndroid Build Coastguard Worker 	if (!cx) {
249*1c60b9acSAndroid Build Coastguard Worker 		lwsl_err("lws init failed\n");
250*1c60b9acSAndroid Build Coastguard Worker 		return 1;
251*1c60b9acSAndroid Build Coastguard Worker 	}
252*1c60b9acSAndroid Build Coastguard Worker 
253*1c60b9acSAndroid Build Coastguard Worker 	if (lws_ss_create(cx, 0, &ssi_binance, NULL, NULL, NULL, NULL)) {
254*1c60b9acSAndroid Build Coastguard Worker 		lwsl_cx_err(cx, "failed to create secure stream");
255*1c60b9acSAndroid Build Coastguard Worker 		interrupted = 1;
256*1c60b9acSAndroid Build Coastguard Worker 	}
257*1c60b9acSAndroid Build Coastguard Worker 
258*1c60b9acSAndroid Build Coastguard Worker 	while (n >= 0 && !interrupted)
259*1c60b9acSAndroid Build Coastguard Worker 		n = lws_service(cx, 0);
260*1c60b9acSAndroid Build Coastguard Worker 
261*1c60b9acSAndroid Build Coastguard Worker 	lws_context_destroy(cx);
262*1c60b9acSAndroid Build Coastguard Worker 
263*1c60b9acSAndroid Build Coastguard Worker 	lwsl_user("Completed\n");
264*1c60b9acSAndroid Build Coastguard Worker 
265*1c60b9acSAndroid Build Coastguard Worker 	return 0;
266*1c60b9acSAndroid Build Coastguard Worker }
267