1/*
2 * ws protocol handler plugin for "lws-minimal"
3 *
4 * Written in 2010-2019 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 * This version uses an lws_ring ringbuffer to cache up to 8 messages at a time,
10 * so it's not so easy to lose messages.
11 */
12
13#if !defined (LWS_PLUGIN_STATIC)
14#define LWS_DLL
15#define LWS_INTERNAL
16#include <libwebsockets.h>
17#endif
18
19#include <string.h>
20#include <stdlib.h>
21
22/* one of these created for each message */
23
24struct msg {
25	void *payload; /* is malloc'd */
26	size_t len;
27};
28
29/* one of these is created for each client connecting to us */
30
31struct per_session_data__minimal {
32	struct per_session_data__minimal *pss_list;
33	struct lws *wsi;
34	uint32_t tail;
35};
36
37/* one of these is created for each vhost our protocol is used with */
38
39struct per_vhost_data__minimal {
40	struct lws_context *context;
41	struct lws_vhost *vhost;
42	const struct lws_protocols *protocol;
43
44	lws_sorted_usec_list_t sul;
45
46	struct per_session_data__minimal *pss_list; /* linked-list of live pss*/
47
48	struct lws_ring *ring; /* ringbuffer holding unsent messages */
49	struct lws_client_connect_info i;
50	struct lws *client_wsi;
51};
52
53/* destroys the message when everyone has had a copy of it */
54
55static void
56__minimal_destroy_message(void *_msg)
57{
58	struct msg *msg = _msg;
59
60	free(msg->payload);
61	msg->payload = NULL;
62	msg->len = 0;
63}
64
65static void
66sul_connect_attempt(struct lws_sorted_usec_list *sul)
67{
68	struct per_vhost_data__minimal *vhd =
69		lws_container_of(sul, struct per_vhost_data__minimal, sul);
70
71	vhd->i.context = vhd->context;
72	vhd->i.port = 443;
73	vhd->i.address = "libwebsockets.org";
74	vhd->i.path = "/";
75	vhd->i.host = vhd->i.address;
76	vhd->i.origin = vhd->i.address;
77	vhd->i.ssl_connection = 1;
78
79	vhd->i.protocol = "dumb-increment-protocol";
80	vhd->i.local_protocol_name = "lws-minimal-proxy";
81	vhd->i.pwsi = &vhd->client_wsi;
82
83	if (!lws_client_connect_via_info(&vhd->i))
84		lws_sul_schedule(vhd->context, 0, &vhd->sul,
85				 sul_connect_attempt, 10 * LWS_US_PER_SEC);
86}
87
88static int
89callback_minimal(struct lws *wsi, enum lws_callback_reasons reason,
90			void *user, void *in, size_t len)
91{
92	struct per_session_data__minimal *pss =
93			(struct per_session_data__minimal *)user;
94	struct per_vhost_data__minimal *vhd =
95			(struct per_vhost_data__minimal *)
96			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
97					lws_get_protocol(wsi));
98	const struct msg *pmsg;
99	struct msg amsg;
100	int m;
101
102	switch (reason) {
103
104	/* --- protocol lifecycle callbacks --- */
105
106	case LWS_CALLBACK_PROTOCOL_INIT:
107		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
108				lws_get_protocol(wsi),
109				sizeof(struct per_vhost_data__minimal));
110		vhd->context = lws_get_context(wsi);
111		vhd->protocol = lws_get_protocol(wsi);
112		vhd->vhost = lws_get_vhost(wsi);
113
114		vhd->ring = lws_ring_create(sizeof(struct msg), 8,
115					    __minimal_destroy_message);
116		if (!vhd->ring)
117			return 1;
118
119		sul_connect_attempt(&vhd->sul);
120		break;
121
122	case LWS_CALLBACK_PROTOCOL_DESTROY:
123		lws_ring_destroy(vhd->ring);
124		lws_sul_cancel(&vhd->sul);
125		break;
126
127	/* --- serving callbacks --- */
128
129	case LWS_CALLBACK_ESTABLISHED:
130		/* add ourselves to the list of live pss held in the vhd */
131		lws_ll_fwd_insert(pss, pss_list, vhd->pss_list);
132		pss->tail = lws_ring_get_oldest_tail(vhd->ring);
133		pss->wsi = wsi;
134		break;
135
136	case LWS_CALLBACK_CLOSED:
137		/* remove our closing pss from the list of live pss */
138		lws_ll_fwd_remove(struct per_session_data__minimal, pss_list,
139				  pss, vhd->pss_list);
140		break;
141
142	case LWS_CALLBACK_SERVER_WRITEABLE:
143		pmsg = lws_ring_get_element(vhd->ring, &pss->tail);
144		if (!pmsg)
145			break;
146
147		/* notice we allowed for LWS_PRE in the payload already */
148		m = lws_write(wsi, ((unsigned char *)pmsg->payload) + LWS_PRE,
149			      pmsg->len, LWS_WRITE_TEXT);
150		if (m < (int)pmsg->len) {
151			lwsl_err("ERROR %d writing to ws socket\n", m);
152			return -1;
153		}
154
155		lws_ring_consume_and_update_oldest_tail(
156			vhd->ring,	/* lws_ring object */
157			struct per_session_data__minimal, /* type of objects with tails */
158			&pss->tail,	/* tail of guy doing the consuming */
159			1,		/* number of payload objects being consumed */
160			vhd->pss_list,	/* head of list of objects with tails */
161			tail,		/* member name of tail in objects with tails */
162			pss_list	/* member name of next object in objects with tails */
163		);
164
165		/* more to do? */
166		if (lws_ring_get_element(vhd->ring, &pss->tail))
167			/* come back as soon as we can write more */
168			lws_callback_on_writable(pss->wsi);
169		break;
170
171	/* --- client callbacks --- */
172
173	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
174		lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
175			 in ? (char *)in : "(null)");
176		vhd->client_wsi = NULL;
177		lws_sul_schedule(vhd->context, 0, &vhd->sul,
178				 sul_connect_attempt, LWS_US_PER_SEC);
179		break;
180
181	case LWS_CALLBACK_CLIENT_ESTABLISHED:
182		lwsl_user("%s: established\n", __func__);
183		break;
184
185	case LWS_CALLBACK_CLIENT_RECEIVE:
186		/* if no clients, just drop incoming */
187		if (!vhd->pss_list)
188			break;
189
190		if (!lws_ring_get_count_free_elements(vhd->ring)) {
191			lwsl_user("dropping!\n");
192			break;
193		}
194
195		amsg.len = len;
196		/* notice we over-allocate by LWS_PRE */
197		amsg.payload = malloc(LWS_PRE + len);
198		if (!amsg.payload) {
199			lwsl_user("OOM: dropping\n");
200			break;
201		}
202
203		memcpy((char *)amsg.payload + LWS_PRE, in, len);
204		if (!lws_ring_insert(vhd->ring, &amsg, 1)) {
205			__minimal_destroy_message(&amsg);
206			lwsl_user("dropping!\n");
207			break;
208		}
209
210		/*
211		 * let everybody know we want to write something on them
212		 * as soon as they are ready
213		 */
214		lws_start_foreach_llp(struct per_session_data__minimal **,
215				      ppss, vhd->pss_list) {
216			lws_callback_on_writable((*ppss)->wsi);
217		} lws_end_foreach_llp(ppss, pss_list);
218		break;
219
220	case LWS_CALLBACK_CLIENT_CLOSED:
221		vhd->client_wsi = NULL;
222		lws_sul_schedule(vhd->context, 0, &vhd->sul,
223				 sul_connect_attempt, LWS_US_PER_SEC);
224		break;
225
226	default:
227		break;
228	}
229
230	return 0;
231}
232
233#define LWS_PLUGIN_PROTOCOL_MINIMAL \
234	{  \
235		"lws-minimal-proxy", \
236		callback_minimal, \
237		sizeof(struct per_session_data__minimal), \
238		128, \
239		0, NULL, 0 \
240	}
241