xref: /unit/src/nxt_h1proto_websocket.c (revision 1131)
1*1131Smax.romanov@nginx.com 
2*1131Smax.romanov@nginx.com /*
3*1131Smax.romanov@nginx.com  * Copyright (C) NGINX, Inc.
4*1131Smax.romanov@nginx.com  */
5*1131Smax.romanov@nginx.com 
6*1131Smax.romanov@nginx.com #include <nxt_main.h>
7*1131Smax.romanov@nginx.com #include <nxt_router.h>
8*1131Smax.romanov@nginx.com #include <nxt_http.h>
9*1131Smax.romanov@nginx.com #include <nxt_h1proto.h>
10*1131Smax.romanov@nginx.com #include <nxt_websocket.h>
11*1131Smax.romanov@nginx.com #include <nxt_websocket_header.h>
12*1131Smax.romanov@nginx.com 
13*1131Smax.romanov@nginx.com typedef struct {
14*1131Smax.romanov@nginx.com     uint16_t   code;
15*1131Smax.romanov@nginx.com     uint8_t    args;
16*1131Smax.romanov@nginx.com     nxt_str_t  desc;
17*1131Smax.romanov@nginx.com } nxt_ws_error_t;
18*1131Smax.romanov@nginx.com 
19*1131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_keepalive(nxt_task_t *task, void *obj, void *data);
20*1131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_frame_header_read(nxt_task_t *task, void *obj,
21*1131Smax.romanov@nginx.com     void *data);
22*1131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_keepalive_disable(nxt_task_t *task,
23*1131Smax.romanov@nginx.com     nxt_h1proto_t *h1p);
24*1131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_keepalive_enable(nxt_task_t *task,
25*1131Smax.romanov@nginx.com     nxt_h1proto_t *h1p);
26*1131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_frame_process(nxt_task_t *task, nxt_conn_t *c,
27*1131Smax.romanov@nginx.com     nxt_h1proto_t *h1p, nxt_websocket_header_t *wsh);
28*1131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_error(nxt_task_t *task, void *obj, void *data);
29*1131Smax.romanov@nginx.com static ssize_t nxt_h1p_ws_io_read_handler(nxt_conn_t *c);
30*1131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_timeout(nxt_task_t *task, void *obj, void *data);
31*1131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_frame_payload_read(nxt_task_t *task, void *obj,
32*1131Smax.romanov@nginx.com     void *data);
33*1131Smax.romanov@nginx.com static void hxt_h1p_send_ws_error(nxt_task_t *task, nxt_http_request_t *r,
34*1131Smax.romanov@nginx.com     const nxt_ws_error_t *err, ...);
35*1131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_error_sent(nxt_task_t *task, void *obj, void *data);
36*1131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_pong(nxt_task_t *task, void *obj, void *data);
37*1131Smax.romanov@nginx.com 
38*1131Smax.romanov@nginx.com static const nxt_conn_state_t  nxt_h1p_read_ws_frame_header_state;
39*1131Smax.romanov@nginx.com static const nxt_conn_state_t  nxt_h1p_read_ws_frame_payload_state;
40*1131Smax.romanov@nginx.com 
41*1131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_out_of_memory = {
42*1131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_INTERNAL_SERVER_ERROR,
43*1131Smax.romanov@nginx.com     0, nxt_string("Out of memory") };
44*1131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_too_big = {
45*1131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_MESSAGE_TOO_BIG,
46*1131Smax.romanov@nginx.com     1, nxt_string("Message too big: %uL bytes") };
47*1131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_invalid_close_code = {
48*1131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
49*1131Smax.romanov@nginx.com     1, nxt_string("Close code %ud is not valid") };
50*1131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_going_away = {
51*1131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_GOING_AWAY,
52*1131Smax.romanov@nginx.com     0, nxt_string("Remote peer is going away") };
53*1131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_not_masked = {
54*1131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
55*1131Smax.romanov@nginx.com     0, nxt_string("Not masked client frame") };
56*1131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_ctrl_fragmented = {
57*1131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
58*1131Smax.romanov@nginx.com     0, nxt_string("Fragmented control frame") };
59*1131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_ctrl_too_big = {
60*1131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
61*1131Smax.romanov@nginx.com     1, nxt_string("Control frame too big: %uL bytes") };
62*1131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_invalid_close_len = {
63*1131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
64*1131Smax.romanov@nginx.com     0, nxt_string("Close frame payload length cannot be 1") };
65*1131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_invalid_opcode = {
66*1131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
67*1131Smax.romanov@nginx.com     1, nxt_string("Unrecognized opcode %ud") };
68*1131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_cont_expected = {
69*1131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
70*1131Smax.romanov@nginx.com     1, nxt_string("Continuation expected, but %ud opcode received") };
71*1131Smax.romanov@nginx.com 
72*1131Smax.romanov@nginx.com void
73*1131Smax.romanov@nginx.com nxt_h1p_websocket_first_frame_start(nxt_task_t *task, nxt_http_request_t *r,
74*1131Smax.romanov@nginx.com     nxt_buf_t *ws_frame)
75*1131Smax.romanov@nginx.com {
76*1131Smax.romanov@nginx.com     nxt_conn_t               *c;
77*1131Smax.romanov@nginx.com     nxt_timer_t              *timer;
78*1131Smax.romanov@nginx.com     nxt_h1proto_t            *h1p;
79*1131Smax.romanov@nginx.com     nxt_socket_conf_joint_t  *joint;
80*1131Smax.romanov@nginx.com 
81*1131Smax.romanov@nginx.com     nxt_debug(task, "h1p ws first frame start");
82*1131Smax.romanov@nginx.com 
83*1131Smax.romanov@nginx.com     h1p = r->proto.h1;
84*1131Smax.romanov@nginx.com     c = h1p->conn;
85*1131Smax.romanov@nginx.com 
86*1131Smax.romanov@nginx.com     if (!c->tcp_nodelay) {
87*1131Smax.romanov@nginx.com         nxt_conn_tcp_nodelay_on(task, c);
88*1131Smax.romanov@nginx.com     }
89*1131Smax.romanov@nginx.com 
90*1131Smax.romanov@nginx.com     joint = c->listen->socket.data;
91*1131Smax.romanov@nginx.com 
92*1131Smax.romanov@nginx.com     if (nxt_slow_path(joint != NULL
93*1131Smax.romanov@nginx.com         && joint->socket_conf->websocket_conf.keepalive_interval != 0))
94*1131Smax.romanov@nginx.com     {
95*1131Smax.romanov@nginx.com         h1p->websocket_timer = nxt_mp_zget(c->mem_pool,
96*1131Smax.romanov@nginx.com                                            sizeof(nxt_h1p_websocket_timer_t));
97*1131Smax.romanov@nginx.com         if (nxt_slow_path(h1p->websocket_timer == NULL)) {
98*1131Smax.romanov@nginx.com             hxt_h1p_send_ws_error(task, r, &nxt_ws_err_out_of_memory);
99*1131Smax.romanov@nginx.com             return;
100*1131Smax.romanov@nginx.com         }
101*1131Smax.romanov@nginx.com 
102*1131Smax.romanov@nginx.com         h1p->websocket_timer->keepalive_interval =
103*1131Smax.romanov@nginx.com             joint->socket_conf->websocket_conf.keepalive_interval;
104*1131Smax.romanov@nginx.com         h1p->websocket_timer->h1p = h1p;
105*1131Smax.romanov@nginx.com 
106*1131Smax.romanov@nginx.com         timer = &h1p->websocket_timer->timer;
107*1131Smax.romanov@nginx.com         timer->task = &c->task;
108*1131Smax.romanov@nginx.com         timer->work_queue = &task->thread->engine->fast_work_queue;
109*1131Smax.romanov@nginx.com         timer->log = &c->log;
110*1131Smax.romanov@nginx.com         timer->bias = NXT_TIMER_DEFAULT_BIAS;
111*1131Smax.romanov@nginx.com         timer->handler = nxt_h1p_conn_ws_keepalive;
112*1131Smax.romanov@nginx.com     }
113*1131Smax.romanov@nginx.com 
114*1131Smax.romanov@nginx.com     nxt_h1p_websocket_frame_start(task, r, ws_frame);
115*1131Smax.romanov@nginx.com }
116*1131Smax.romanov@nginx.com 
117*1131Smax.romanov@nginx.com 
118*1131Smax.romanov@nginx.com void
119*1131Smax.romanov@nginx.com nxt_h1p_websocket_frame_start(nxt_task_t *task, nxt_http_request_t *r,
120*1131Smax.romanov@nginx.com     nxt_buf_t *ws_frame)
121*1131Smax.romanov@nginx.com {
122*1131Smax.romanov@nginx.com     size_t         size;
123*1131Smax.romanov@nginx.com     nxt_buf_t      *in;
124*1131Smax.romanov@nginx.com     nxt_conn_t     *c;
125*1131Smax.romanov@nginx.com     nxt_h1proto_t  *h1p;
126*1131Smax.romanov@nginx.com 
127*1131Smax.romanov@nginx.com     nxt_debug(task, "h1p ws frame start");
128*1131Smax.romanov@nginx.com 
129*1131Smax.romanov@nginx.com     h1p = r->proto.h1;
130*1131Smax.romanov@nginx.com 
131*1131Smax.romanov@nginx.com     if (nxt_slow_path(h1p->websocket_closed)) {
132*1131Smax.romanov@nginx.com         return;
133*1131Smax.romanov@nginx.com     }
134*1131Smax.romanov@nginx.com 
135*1131Smax.romanov@nginx.com     c = h1p->conn;
136*1131Smax.romanov@nginx.com     c->read = ws_frame;
137*1131Smax.romanov@nginx.com 
138*1131Smax.romanov@nginx.com     nxt_h1p_complete_buffers(task, h1p);
139*1131Smax.romanov@nginx.com 
140*1131Smax.romanov@nginx.com     in = c->read;
141*1131Smax.romanov@nginx.com     c->read_state = &nxt_h1p_read_ws_frame_header_state;
142*1131Smax.romanov@nginx.com 
143*1131Smax.romanov@nginx.com     if (in == NULL) {
144*1131Smax.romanov@nginx.com         nxt_conn_read(task->thread->engine, c);
145*1131Smax.romanov@nginx.com         nxt_h1p_conn_ws_keepalive_enable(task, h1p);
146*1131Smax.romanov@nginx.com 
147*1131Smax.romanov@nginx.com     } else {
148*1131Smax.romanov@nginx.com         size = nxt_buf_mem_used_size(&in->mem);
149*1131Smax.romanov@nginx.com 
150*1131Smax.romanov@nginx.com         nxt_debug(task, "h1p read client ws frame");
151*1131Smax.romanov@nginx.com 
152*1131Smax.romanov@nginx.com         nxt_memmove(in->mem.start, in->mem.pos, size);
153*1131Smax.romanov@nginx.com 
154*1131Smax.romanov@nginx.com         in->mem.pos = in->mem.start;
155*1131Smax.romanov@nginx.com         in->mem.free = in->mem.start + size;
156*1131Smax.romanov@nginx.com 
157*1131Smax.romanov@nginx.com         nxt_h1p_conn_ws_frame_header_read(task, c, h1p);
158*1131Smax.romanov@nginx.com     }
159*1131Smax.romanov@nginx.com }
160*1131Smax.romanov@nginx.com 
161*1131Smax.romanov@nginx.com 
162*1131Smax.romanov@nginx.com static void
163*1131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive(nxt_task_t *task, void *obj, void *data)
164*1131Smax.romanov@nginx.com {
165*1131Smax.romanov@nginx.com     nxt_buf_t                  *out;
166*1131Smax.romanov@nginx.com     nxt_timer_t                *timer;
167*1131Smax.romanov@nginx.com     nxt_h1proto_t              *h1p;
168*1131Smax.romanov@nginx.com     nxt_http_request_t         *r;
169*1131Smax.romanov@nginx.com     nxt_websocket_header_t     *wsh;
170*1131Smax.romanov@nginx.com     nxt_h1p_websocket_timer_t  *ws_timer;
171*1131Smax.romanov@nginx.com 
172*1131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws keepalive");
173*1131Smax.romanov@nginx.com 
174*1131Smax.romanov@nginx.com     timer = obj;
175*1131Smax.romanov@nginx.com     ws_timer = nxt_timer_data(timer, nxt_h1p_websocket_timer_t, timer);
176*1131Smax.romanov@nginx.com     h1p = ws_timer->h1p;
177*1131Smax.romanov@nginx.com 
178*1131Smax.romanov@nginx.com     r = h1p->request;
179*1131Smax.romanov@nginx.com     if (nxt_slow_path(r == NULL)) {
180*1131Smax.romanov@nginx.com         return;
181*1131Smax.romanov@nginx.com     }
182*1131Smax.romanov@nginx.com 
183*1131Smax.romanov@nginx.com     out = nxt_http_buf_mem(task, r, 2);
184*1131Smax.romanov@nginx.com     if (nxt_slow_path(out == NULL)) {
185*1131Smax.romanov@nginx.com         nxt_http_request_error_handler(task, r, r->proto.any);
186*1131Smax.romanov@nginx.com         return;
187*1131Smax.romanov@nginx.com     }
188*1131Smax.romanov@nginx.com 
189*1131Smax.romanov@nginx.com     out->mem.start[0] = 0;
190*1131Smax.romanov@nginx.com     out->mem.start[1] = 0;
191*1131Smax.romanov@nginx.com 
192*1131Smax.romanov@nginx.com     wsh = (nxt_websocket_header_t *) out->mem.start;
193*1131Smax.romanov@nginx.com     out->mem.free = nxt_websocket_frame_init(wsh, 0);
194*1131Smax.romanov@nginx.com 
195*1131Smax.romanov@nginx.com     wsh->fin = 1;
196*1131Smax.romanov@nginx.com     wsh->opcode = NXT_WEBSOCKET_OP_PING;
197*1131Smax.romanov@nginx.com 
198*1131Smax.romanov@nginx.com     nxt_http_request_send(task, r, out);
199*1131Smax.romanov@nginx.com }
200*1131Smax.romanov@nginx.com 
201*1131Smax.romanov@nginx.com 
202*1131Smax.romanov@nginx.com static const nxt_conn_state_t  nxt_h1p_read_ws_frame_header_state
203*1131Smax.romanov@nginx.com     nxt_aligned(64) =
204*1131Smax.romanov@nginx.com {
205*1131Smax.romanov@nginx.com     .ready_handler = nxt_h1p_conn_ws_frame_header_read,
206*1131Smax.romanov@nginx.com     .close_handler = nxt_h1p_conn_ws_error,
207*1131Smax.romanov@nginx.com     .error_handler = nxt_h1p_conn_ws_error,
208*1131Smax.romanov@nginx.com 
209*1131Smax.romanov@nginx.com     .io_read_handler = nxt_h1p_ws_io_read_handler,
210*1131Smax.romanov@nginx.com 
211*1131Smax.romanov@nginx.com     .timer_handler = nxt_h1p_conn_ws_timeout,
212*1131Smax.romanov@nginx.com     .timer_value = nxt_h1p_conn_request_timer_value,
213*1131Smax.romanov@nginx.com     .timer_data = offsetof(nxt_socket_conf_t, websocket_conf.read_timeout),
214*1131Smax.romanov@nginx.com     .timer_autoreset = 1,
215*1131Smax.romanov@nginx.com };
216*1131Smax.romanov@nginx.com 
217*1131Smax.romanov@nginx.com 
218*1131Smax.romanov@nginx.com static void
219*1131Smax.romanov@nginx.com nxt_h1p_conn_ws_frame_header_read(nxt_task_t *task, void *obj, void *data)
220*1131Smax.romanov@nginx.com {
221*1131Smax.romanov@nginx.com     size_t                   size, hsize, frame_size, max_frame_size;
222*1131Smax.romanov@nginx.com     uint64_t                 payload_len;
223*1131Smax.romanov@nginx.com     nxt_conn_t               *c;
224*1131Smax.romanov@nginx.com     nxt_h1proto_t            *h1p;
225*1131Smax.romanov@nginx.com     nxt_http_request_t       *r;
226*1131Smax.romanov@nginx.com     nxt_event_engine_t       *engine;
227*1131Smax.romanov@nginx.com     nxt_websocket_header_t   *wsh;
228*1131Smax.romanov@nginx.com     nxt_socket_conf_joint_t  *joint;
229*1131Smax.romanov@nginx.com 
230*1131Smax.romanov@nginx.com     c = obj;
231*1131Smax.romanov@nginx.com     h1p = data;
232*1131Smax.romanov@nginx.com 
233*1131Smax.romanov@nginx.com     nxt_h1p_conn_ws_keepalive_disable(task, h1p);
234*1131Smax.romanov@nginx.com 
235*1131Smax.romanov@nginx.com     size = nxt_buf_mem_used_size(&c->read->mem);
236*1131Smax.romanov@nginx.com 
237*1131Smax.romanov@nginx.com     engine = task->thread->engine;
238*1131Smax.romanov@nginx.com 
239*1131Smax.romanov@nginx.com     if (size < 2) {
240*1131Smax.romanov@nginx.com         nxt_debug(task, "h1p conn ws frame header read %z", size);
241*1131Smax.romanov@nginx.com 
242*1131Smax.romanov@nginx.com         nxt_conn_read(engine, c);
243*1131Smax.romanov@nginx.com         nxt_h1p_conn_ws_keepalive_enable(task, h1p);
244*1131Smax.romanov@nginx.com 
245*1131Smax.romanov@nginx.com         return;
246*1131Smax.romanov@nginx.com     }
247*1131Smax.romanov@nginx.com 
248*1131Smax.romanov@nginx.com     wsh = (nxt_websocket_header_t *) c->read->mem.pos;
249*1131Smax.romanov@nginx.com 
250*1131Smax.romanov@nginx.com     hsize = nxt_websocket_frame_header_size(wsh);
251*1131Smax.romanov@nginx.com 
252*1131Smax.romanov@nginx.com     if (size < hsize) {
253*1131Smax.romanov@nginx.com         nxt_debug(task, "h1p conn ws frame header read %z < %z", size, hsize);
254*1131Smax.romanov@nginx.com 
255*1131Smax.romanov@nginx.com         nxt_conn_read(engine, c);
256*1131Smax.romanov@nginx.com         nxt_h1p_conn_ws_keepalive_enable(task, h1p);
257*1131Smax.romanov@nginx.com 
258*1131Smax.romanov@nginx.com         return;
259*1131Smax.romanov@nginx.com     }
260*1131Smax.romanov@nginx.com 
261*1131Smax.romanov@nginx.com     r = h1p->request;
262*1131Smax.romanov@nginx.com     if (nxt_slow_path(r == NULL)) {
263*1131Smax.romanov@nginx.com         return;
264*1131Smax.romanov@nginx.com     }
265*1131Smax.romanov@nginx.com 
266*1131Smax.romanov@nginx.com     r->ws_frame = c->read;
267*1131Smax.romanov@nginx.com 
268*1131Smax.romanov@nginx.com     joint = c->listen->socket.data;
269*1131Smax.romanov@nginx.com 
270*1131Smax.romanov@nginx.com     if (nxt_slow_path(joint == NULL)) {
271*1131Smax.romanov@nginx.com         /*
272*1131Smax.romanov@nginx.com          * Listening socket had been closed while
273*1131Smax.romanov@nginx.com          * connection was in keep-alive state.
274*1131Smax.romanov@nginx.com          */
275*1131Smax.romanov@nginx.com         c->read_state = &nxt_h1p_idle_close_state;
276*1131Smax.romanov@nginx.com         return;
277*1131Smax.romanov@nginx.com     }
278*1131Smax.romanov@nginx.com 
279*1131Smax.romanov@nginx.com     if (nxt_slow_path(wsh->mask == 0)) {
280*1131Smax.romanov@nginx.com         hxt_h1p_send_ws_error(task, r, &nxt_ws_err_not_masked);
281*1131Smax.romanov@nginx.com         return;
282*1131Smax.romanov@nginx.com     }
283*1131Smax.romanov@nginx.com 
284*1131Smax.romanov@nginx.com     if ((wsh->opcode & NXT_WEBSOCKET_OP_CTRL) != 0) {
285*1131Smax.romanov@nginx.com         if (nxt_slow_path(wsh->fin == 0)) {
286*1131Smax.romanov@nginx.com             hxt_h1p_send_ws_error(task, r, &nxt_ws_err_ctrl_fragmented);
287*1131Smax.romanov@nginx.com             return;
288*1131Smax.romanov@nginx.com         }
289*1131Smax.romanov@nginx.com 
290*1131Smax.romanov@nginx.com         if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_PING
291*1131Smax.romanov@nginx.com                           && wsh->opcode != NXT_WEBSOCKET_OP_PONG
292*1131Smax.romanov@nginx.com                           && wsh->opcode != NXT_WEBSOCKET_OP_CLOSE))
293*1131Smax.romanov@nginx.com         {
294*1131Smax.romanov@nginx.com             hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_opcode,
295*1131Smax.romanov@nginx.com                                   wsh->opcode);
296*1131Smax.romanov@nginx.com             return;
297*1131Smax.romanov@nginx.com         }
298*1131Smax.romanov@nginx.com 
299*1131Smax.romanov@nginx.com         if (nxt_slow_path(wsh->payload_len > 125)) {
300*1131Smax.romanov@nginx.com             hxt_h1p_send_ws_error(task, r, &nxt_ws_err_ctrl_too_big,
301*1131Smax.romanov@nginx.com                                   nxt_websocket_frame_payload_len(wsh));
302*1131Smax.romanov@nginx.com             return;
303*1131Smax.romanov@nginx.com         }
304*1131Smax.romanov@nginx.com 
305*1131Smax.romanov@nginx.com         if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_CLOSE
306*1131Smax.romanov@nginx.com                           && wsh->payload_len == 1))
307*1131Smax.romanov@nginx.com         {
308*1131Smax.romanov@nginx.com             hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_close_len);
309*1131Smax.romanov@nginx.com             return;
310*1131Smax.romanov@nginx.com         }
311*1131Smax.romanov@nginx.com 
312*1131Smax.romanov@nginx.com     } else {
313*1131Smax.romanov@nginx.com         if (h1p->websocket_cont_expected) {
314*1131Smax.romanov@nginx.com             if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_CONT)) {
315*1131Smax.romanov@nginx.com                 hxt_h1p_send_ws_error(task, r, &nxt_ws_err_cont_expected,
316*1131Smax.romanov@nginx.com                                       wsh->opcode);
317*1131Smax.romanov@nginx.com                 return;
318*1131Smax.romanov@nginx.com             }
319*1131Smax.romanov@nginx.com 
320*1131Smax.romanov@nginx.com         } else {
321*1131Smax.romanov@nginx.com             if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_BINARY
322*1131Smax.romanov@nginx.com                               && wsh->opcode != NXT_WEBSOCKET_OP_TEXT))
323*1131Smax.romanov@nginx.com             {
324*1131Smax.romanov@nginx.com                 hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_opcode,
325*1131Smax.romanov@nginx.com                                       wsh->opcode);
326*1131Smax.romanov@nginx.com                 return;
327*1131Smax.romanov@nginx.com             }
328*1131Smax.romanov@nginx.com         }
329*1131Smax.romanov@nginx.com 
330*1131Smax.romanov@nginx.com         h1p->websocket_cont_expected = !wsh->fin;
331*1131Smax.romanov@nginx.com     }
332*1131Smax.romanov@nginx.com 
333*1131Smax.romanov@nginx.com     max_frame_size = joint->socket_conf->websocket_conf.max_frame_size;
334*1131Smax.romanov@nginx.com 
335*1131Smax.romanov@nginx.com     payload_len = nxt_websocket_frame_payload_len(wsh);
336*1131Smax.romanov@nginx.com 
337*1131Smax.romanov@nginx.com     if (nxt_slow_path(hsize > max_frame_size
338*1131Smax.romanov@nginx.com                       || payload_len > (max_frame_size - hsize)))
339*1131Smax.romanov@nginx.com     {
340*1131Smax.romanov@nginx.com         hxt_h1p_send_ws_error(task, r, &nxt_ws_err_too_big, payload_len);
341*1131Smax.romanov@nginx.com         return;
342*1131Smax.romanov@nginx.com     }
343*1131Smax.romanov@nginx.com 
344*1131Smax.romanov@nginx.com     c->read_state = &nxt_h1p_read_ws_frame_payload_state;
345*1131Smax.romanov@nginx.com 
346*1131Smax.romanov@nginx.com     frame_size = payload_len + hsize;
347*1131Smax.romanov@nginx.com 
348*1131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws frame header read: %z, %z", size, frame_size);
349*1131Smax.romanov@nginx.com 
350*1131Smax.romanov@nginx.com     if (frame_size <= size) {
351*1131Smax.romanov@nginx.com         nxt_h1p_conn_ws_frame_process(task, c, h1p, wsh);
352*1131Smax.romanov@nginx.com 
353*1131Smax.romanov@nginx.com         return;
354*1131Smax.romanov@nginx.com     }
355*1131Smax.romanov@nginx.com 
356*1131Smax.romanov@nginx.com     if (frame_size < (size_t) nxt_buf_mem_size(&c->read->mem)) {
357*1131Smax.romanov@nginx.com         c->read->mem.end = c->read->mem.start + frame_size;
358*1131Smax.romanov@nginx.com 
359*1131Smax.romanov@nginx.com     } else {
360*1131Smax.romanov@nginx.com         nxt_buf_t *b = nxt_buf_mem_alloc(c->mem_pool, frame_size - size, 0);
361*1131Smax.romanov@nginx.com 
362*1131Smax.romanov@nginx.com         c->read->next = b;
363*1131Smax.romanov@nginx.com         c->read = b;
364*1131Smax.romanov@nginx.com     }
365*1131Smax.romanov@nginx.com 
366*1131Smax.romanov@nginx.com     nxt_conn_read(engine, c);
367*1131Smax.romanov@nginx.com     nxt_h1p_conn_ws_keepalive_enable(task, h1p);
368*1131Smax.romanov@nginx.com }
369*1131Smax.romanov@nginx.com 
370*1131Smax.romanov@nginx.com 
371*1131Smax.romanov@nginx.com static void
372*1131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive_disable(nxt_task_t *task, nxt_h1proto_t *h1p)
373*1131Smax.romanov@nginx.com {
374*1131Smax.romanov@nginx.com     nxt_timer_t  *timer;
375*1131Smax.romanov@nginx.com 
376*1131Smax.romanov@nginx.com     if (h1p->websocket_timer == NULL) {
377*1131Smax.romanov@nginx.com         return;
378*1131Smax.romanov@nginx.com     }
379*1131Smax.romanov@nginx.com 
380*1131Smax.romanov@nginx.com     timer = &h1p->websocket_timer->timer;
381*1131Smax.romanov@nginx.com 
382*1131Smax.romanov@nginx.com     if (nxt_slow_path(timer->handler != nxt_h1p_conn_ws_keepalive)) {
383*1131Smax.romanov@nginx.com         nxt_debug(task, "h1p ws keepalive disable: scheduled ws shutdown");
384*1131Smax.romanov@nginx.com         return;
385*1131Smax.romanov@nginx.com     }
386*1131Smax.romanov@nginx.com 
387*1131Smax.romanov@nginx.com     nxt_timer_disable(task->thread->engine, timer);
388*1131Smax.romanov@nginx.com }
389*1131Smax.romanov@nginx.com 
390*1131Smax.romanov@nginx.com 
391*1131Smax.romanov@nginx.com static void
392*1131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive_enable(nxt_task_t *task, nxt_h1proto_t *h1p)
393*1131Smax.romanov@nginx.com {
394*1131Smax.romanov@nginx.com     nxt_timer_t  *timer;
395*1131Smax.romanov@nginx.com 
396*1131Smax.romanov@nginx.com     if (h1p->websocket_timer == NULL) {
397*1131Smax.romanov@nginx.com         return;
398*1131Smax.romanov@nginx.com     }
399*1131Smax.romanov@nginx.com 
400*1131Smax.romanov@nginx.com     timer = &h1p->websocket_timer->timer;
401*1131Smax.romanov@nginx.com 
402*1131Smax.romanov@nginx.com     if (nxt_slow_path(timer->handler != nxt_h1p_conn_ws_keepalive)) {
403*1131Smax.romanov@nginx.com         nxt_debug(task, "h1p ws keepalive enable: scheduled ws shutdown");
404*1131Smax.romanov@nginx.com         return;
405*1131Smax.romanov@nginx.com     }
406*1131Smax.romanov@nginx.com 
407*1131Smax.romanov@nginx.com     nxt_timer_add(task->thread->engine, timer,
408*1131Smax.romanov@nginx.com                   h1p->websocket_timer->keepalive_interval);
409*1131Smax.romanov@nginx.com }
410*1131Smax.romanov@nginx.com 
411*1131Smax.romanov@nginx.com 
412*1131Smax.romanov@nginx.com static void
413*1131Smax.romanov@nginx.com nxt_h1p_conn_ws_frame_process(nxt_task_t *task, nxt_conn_t *c,
414*1131Smax.romanov@nginx.com     nxt_h1proto_t *h1p, nxt_websocket_header_t *wsh)
415*1131Smax.romanov@nginx.com {
416*1131Smax.romanov@nginx.com     size_t              hsize;
417*1131Smax.romanov@nginx.com     uint8_t             *p, *mask;
418*1131Smax.romanov@nginx.com     uint16_t            code;
419*1131Smax.romanov@nginx.com     nxt_http_request_t  *r;
420*1131Smax.romanov@nginx.com     nxt_event_engine_t  *engine;
421*1131Smax.romanov@nginx.com 
422*1131Smax.romanov@nginx.com     engine = task->thread->engine;
423*1131Smax.romanov@nginx.com     r = h1p->request;
424*1131Smax.romanov@nginx.com 
425*1131Smax.romanov@nginx.com     c->read = NULL;
426*1131Smax.romanov@nginx.com 
427*1131Smax.romanov@nginx.com     if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_PING)) {
428*1131Smax.romanov@nginx.com         nxt_work_queue_add(&engine->fast_work_queue, nxt_h1p_conn_ws_pong,
429*1131Smax.romanov@nginx.com                            task, r, NULL);
430*1131Smax.romanov@nginx.com         return;
431*1131Smax.romanov@nginx.com     }
432*1131Smax.romanov@nginx.com 
433*1131Smax.romanov@nginx.com     if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_CLOSE)) {
434*1131Smax.romanov@nginx.com         if (wsh->payload_len >= 2) {
435*1131Smax.romanov@nginx.com             hsize = nxt_websocket_frame_header_size(wsh);
436*1131Smax.romanov@nginx.com             mask = nxt_pointer_to(wsh, hsize - 4);
437*1131Smax.romanov@nginx.com             p = nxt_pointer_to(wsh, hsize);
438*1131Smax.romanov@nginx.com 
439*1131Smax.romanov@nginx.com             code = ((p[0] ^ mask[0]) << 8) + (p[1] ^ mask[1]);
440*1131Smax.romanov@nginx.com 
441*1131Smax.romanov@nginx.com             if (nxt_slow_path(code < 1000 || code >= 5000
442*1131Smax.romanov@nginx.com                               || (code > 1003 && code < 1007)
443*1131Smax.romanov@nginx.com                               || (code > 1014 && code < 3000)))
444*1131Smax.romanov@nginx.com             {
445*1131Smax.romanov@nginx.com                 hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_close_code,
446*1131Smax.romanov@nginx.com                                       code);
447*1131Smax.romanov@nginx.com                 return;
448*1131Smax.romanov@nginx.com             }
449*1131Smax.romanov@nginx.com         }
450*1131Smax.romanov@nginx.com 
451*1131Smax.romanov@nginx.com         h1p->websocket_closed = 1;
452*1131Smax.romanov@nginx.com     }
453*1131Smax.romanov@nginx.com 
454*1131Smax.romanov@nginx.com     nxt_work_queue_add(&engine->fast_work_queue, r->state->ready_handler,
455*1131Smax.romanov@nginx.com                        task, r, NULL);
456*1131Smax.romanov@nginx.com }
457*1131Smax.romanov@nginx.com 
458*1131Smax.romanov@nginx.com 
459*1131Smax.romanov@nginx.com static void
460*1131Smax.romanov@nginx.com nxt_h1p_conn_ws_error(nxt_task_t *task, void *obj, void *data)
461*1131Smax.romanov@nginx.com {
462*1131Smax.romanov@nginx.com     nxt_h1proto_t       *h1p;
463*1131Smax.romanov@nginx.com     nxt_http_request_t  *r;
464*1131Smax.romanov@nginx.com 
465*1131Smax.romanov@nginx.com     h1p = data;
466*1131Smax.romanov@nginx.com 
467*1131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws error");
468*1131Smax.romanov@nginx.com 
469*1131Smax.romanov@nginx.com     r = h1p->request;
470*1131Smax.romanov@nginx.com 
471*1131Smax.romanov@nginx.com     h1p->keepalive = 0;
472*1131Smax.romanov@nginx.com 
473*1131Smax.romanov@nginx.com     if (nxt_fast_path(r != NULL)) {
474*1131Smax.romanov@nginx.com         r->state->error_handler(task, r, h1p);
475*1131Smax.romanov@nginx.com     }
476*1131Smax.romanov@nginx.com }
477*1131Smax.romanov@nginx.com 
478*1131Smax.romanov@nginx.com 
479*1131Smax.romanov@nginx.com static ssize_t
480*1131Smax.romanov@nginx.com nxt_h1p_ws_io_read_handler(nxt_conn_t *c)
481*1131Smax.romanov@nginx.com {
482*1131Smax.romanov@nginx.com     size_t     size;
483*1131Smax.romanov@nginx.com     ssize_t    n;
484*1131Smax.romanov@nginx.com     nxt_buf_t  *b;
485*1131Smax.romanov@nginx.com 
486*1131Smax.romanov@nginx.com     b = c->read;
487*1131Smax.romanov@nginx.com 
488*1131Smax.romanov@nginx.com     if (b == NULL) {
489*1131Smax.romanov@nginx.com         /* Enough for control frame. */
490*1131Smax.romanov@nginx.com         size = 10 + 125;
491*1131Smax.romanov@nginx.com 
492*1131Smax.romanov@nginx.com         b = nxt_buf_mem_alloc(c->mem_pool, size, 0);
493*1131Smax.romanov@nginx.com         if (nxt_slow_path(b == NULL)) {
494*1131Smax.romanov@nginx.com             c->socket.error = NXT_ENOMEM;
495*1131Smax.romanov@nginx.com             return NXT_ERROR;
496*1131Smax.romanov@nginx.com         }
497*1131Smax.romanov@nginx.com     }
498*1131Smax.romanov@nginx.com 
499*1131Smax.romanov@nginx.com     n = c->io->recvbuf(c, b);
500*1131Smax.romanov@nginx.com 
501*1131Smax.romanov@nginx.com     if (n > 0) {
502*1131Smax.romanov@nginx.com         c->read = b;
503*1131Smax.romanov@nginx.com 
504*1131Smax.romanov@nginx.com     } else {
505*1131Smax.romanov@nginx.com         c->read = NULL;
506*1131Smax.romanov@nginx.com         nxt_mp_free(c->mem_pool, b);
507*1131Smax.romanov@nginx.com     }
508*1131Smax.romanov@nginx.com 
509*1131Smax.romanov@nginx.com     return n;
510*1131Smax.romanov@nginx.com }
511*1131Smax.romanov@nginx.com 
512*1131Smax.romanov@nginx.com 
513*1131Smax.romanov@nginx.com static void
514*1131Smax.romanov@nginx.com nxt_h1p_conn_ws_timeout(nxt_task_t *task, void *obj, void *data)
515*1131Smax.romanov@nginx.com {
516*1131Smax.romanov@nginx.com     nxt_conn_t          *c;
517*1131Smax.romanov@nginx.com     nxt_timer_t         *timer;
518*1131Smax.romanov@nginx.com     nxt_h1proto_t       *h1p;
519*1131Smax.romanov@nginx.com     nxt_http_request_t  *r;
520*1131Smax.romanov@nginx.com 
521*1131Smax.romanov@nginx.com     timer = obj;
522*1131Smax.romanov@nginx.com 
523*1131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws timeout");
524*1131Smax.romanov@nginx.com 
525*1131Smax.romanov@nginx.com     c = nxt_read_timer_conn(timer);
526*1131Smax.romanov@nginx.com     c->block_read = 1;
527*1131Smax.romanov@nginx.com     /*
528*1131Smax.romanov@nginx.com      * Disable SO_LINGER off during socket closing
529*1131Smax.romanov@nginx.com      * to send "408 Request Timeout" error response.
530*1131Smax.romanov@nginx.com      */
531*1131Smax.romanov@nginx.com     c->socket.timedout = 0;
532*1131Smax.romanov@nginx.com 
533*1131Smax.romanov@nginx.com     h1p = c->socket.data;
534*1131Smax.romanov@nginx.com     h1p->keepalive = 0;
535*1131Smax.romanov@nginx.com 
536*1131Smax.romanov@nginx.com     r = h1p->request;
537*1131Smax.romanov@nginx.com     if (nxt_slow_path(r == NULL)) {
538*1131Smax.romanov@nginx.com         return;
539*1131Smax.romanov@nginx.com     }
540*1131Smax.romanov@nginx.com 
541*1131Smax.romanov@nginx.com     hxt_h1p_send_ws_error(task, r, &nxt_ws_err_going_away);
542*1131Smax.romanov@nginx.com }
543*1131Smax.romanov@nginx.com 
544*1131Smax.romanov@nginx.com 
545*1131Smax.romanov@nginx.com static const nxt_conn_state_t  nxt_h1p_read_ws_frame_payload_state
546*1131Smax.romanov@nginx.com     nxt_aligned(64) =
547*1131Smax.romanov@nginx.com {
548*1131Smax.romanov@nginx.com     .ready_handler = nxt_h1p_conn_ws_frame_payload_read,
549*1131Smax.romanov@nginx.com     .close_handler = nxt_h1p_conn_ws_error,
550*1131Smax.romanov@nginx.com     .error_handler = nxt_h1p_conn_ws_error,
551*1131Smax.romanov@nginx.com 
552*1131Smax.romanov@nginx.com     .timer_handler = nxt_h1p_conn_ws_timeout,
553*1131Smax.romanov@nginx.com     .timer_value = nxt_h1p_conn_request_timer_value,
554*1131Smax.romanov@nginx.com     .timer_data = offsetof(nxt_socket_conf_t, websocket_conf.read_timeout),
555*1131Smax.romanov@nginx.com     .timer_autoreset = 1,
556*1131Smax.romanov@nginx.com };
557*1131Smax.romanov@nginx.com 
558*1131Smax.romanov@nginx.com 
559*1131Smax.romanov@nginx.com static void
560*1131Smax.romanov@nginx.com nxt_h1p_conn_ws_frame_payload_read(nxt_task_t *task, void *obj, void *data)
561*1131Smax.romanov@nginx.com {
562*1131Smax.romanov@nginx.com     nxt_conn_t              *c;
563*1131Smax.romanov@nginx.com     nxt_h1proto_t           *h1p;
564*1131Smax.romanov@nginx.com     nxt_http_request_t      *r;
565*1131Smax.romanov@nginx.com     nxt_event_engine_t      *engine;
566*1131Smax.romanov@nginx.com     nxt_websocket_header_t  *wsh;
567*1131Smax.romanov@nginx.com 
568*1131Smax.romanov@nginx.com     c = obj;
569*1131Smax.romanov@nginx.com     h1p = data;
570*1131Smax.romanov@nginx.com 
571*1131Smax.romanov@nginx.com     nxt_h1p_conn_ws_keepalive_disable(task, h1p);
572*1131Smax.romanov@nginx.com 
573*1131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws frame read");
574*1131Smax.romanov@nginx.com 
575*1131Smax.romanov@nginx.com     if (nxt_buf_mem_free_size(&c->read->mem) == 0) {
576*1131Smax.romanov@nginx.com         r = h1p->request;
577*1131Smax.romanov@nginx.com         if (nxt_slow_path(r == NULL)) {
578*1131Smax.romanov@nginx.com             return;
579*1131Smax.romanov@nginx.com         }
580*1131Smax.romanov@nginx.com 
581*1131Smax.romanov@nginx.com         wsh = (nxt_websocket_header_t *) r->ws_frame->mem.pos;
582*1131Smax.romanov@nginx.com 
583*1131Smax.romanov@nginx.com         nxt_h1p_conn_ws_frame_process(task, c, h1p, wsh);
584*1131Smax.romanov@nginx.com 
585*1131Smax.romanov@nginx.com         return;
586*1131Smax.romanov@nginx.com     }
587*1131Smax.romanov@nginx.com 
588*1131Smax.romanov@nginx.com     engine = task->thread->engine;
589*1131Smax.romanov@nginx.com 
590*1131Smax.romanov@nginx.com     nxt_conn_read(engine, c);
591*1131Smax.romanov@nginx.com     nxt_h1p_conn_ws_keepalive_enable(task, h1p);
592*1131Smax.romanov@nginx.com }
593*1131Smax.romanov@nginx.com 
594*1131Smax.romanov@nginx.com 
595*1131Smax.romanov@nginx.com static void
596*1131Smax.romanov@nginx.com hxt_h1p_send_ws_error(nxt_task_t *task, nxt_http_request_t *r,
597*1131Smax.romanov@nginx.com     const nxt_ws_error_t *err, ...)
598*1131Smax.romanov@nginx.com {
599*1131Smax.romanov@nginx.com     u_char                  *p;
600*1131Smax.romanov@nginx.com     va_list                 args;
601*1131Smax.romanov@nginx.com     nxt_buf_t               *out;
602*1131Smax.romanov@nginx.com     nxt_str_t               desc;
603*1131Smax.romanov@nginx.com     nxt_websocket_header_t  *wsh;
604*1131Smax.romanov@nginx.com     u_char                  buf[125];
605*1131Smax.romanov@nginx.com 
606*1131Smax.romanov@nginx.com     if (nxt_slow_path(err->args)) {
607*1131Smax.romanov@nginx.com         va_start(args, err);
608*1131Smax.romanov@nginx.com         p = nxt_vsprintf(buf, buf + sizeof(buf), (char *) err->desc.start,
609*1131Smax.romanov@nginx.com                          args);
610*1131Smax.romanov@nginx.com         va_end(args);
611*1131Smax.romanov@nginx.com 
612*1131Smax.romanov@nginx.com         desc.start = buf;
613*1131Smax.romanov@nginx.com         desc.length = p - buf;
614*1131Smax.romanov@nginx.com 
615*1131Smax.romanov@nginx.com     } else {
616*1131Smax.romanov@nginx.com         desc = err->desc;
617*1131Smax.romanov@nginx.com     }
618*1131Smax.romanov@nginx.com 
619*1131Smax.romanov@nginx.com     nxt_log(task, NXT_LOG_INFO, "websocket error %d: %V", err->code, &desc);
620*1131Smax.romanov@nginx.com 
621*1131Smax.romanov@nginx.com     out = nxt_http_buf_mem(task, r, 2 + sizeof(err->code) + desc.length);
622*1131Smax.romanov@nginx.com     if (nxt_slow_path(out == NULL)) {
623*1131Smax.romanov@nginx.com         nxt_http_request_error_handler(task, r, r->proto.any);
624*1131Smax.romanov@nginx.com         return;
625*1131Smax.romanov@nginx.com     }
626*1131Smax.romanov@nginx.com 
627*1131Smax.romanov@nginx.com     out->mem.start[0] = 0;
628*1131Smax.romanov@nginx.com     out->mem.start[1] = 0;
629*1131Smax.romanov@nginx.com 
630*1131Smax.romanov@nginx.com     wsh = (nxt_websocket_header_t *) out->mem.start;
631*1131Smax.romanov@nginx.com     p = nxt_websocket_frame_init(wsh, sizeof(err->code) + desc.length);
632*1131Smax.romanov@nginx.com 
633*1131Smax.romanov@nginx.com     wsh->fin = 1;
634*1131Smax.romanov@nginx.com     wsh->opcode = NXT_WEBSOCKET_OP_CLOSE;
635*1131Smax.romanov@nginx.com 
636*1131Smax.romanov@nginx.com     *p++ = (err->code >> 8) & 0xFF;
637*1131Smax.romanov@nginx.com     *p++ = err->code & 0xFF;
638*1131Smax.romanov@nginx.com 
639*1131Smax.romanov@nginx.com     out->mem.free = nxt_cpymem(p, desc.start, desc.length);
640*1131Smax.romanov@nginx.com     out->next = nxt_http_buf_last(r);
641*1131Smax.romanov@nginx.com 
642*1131Smax.romanov@nginx.com     if (out->next != NULL) {
643*1131Smax.romanov@nginx.com         out->next->completion_handler = nxt_h1p_conn_ws_error_sent;
644*1131Smax.romanov@nginx.com     }
645*1131Smax.romanov@nginx.com 
646*1131Smax.romanov@nginx.com     nxt_http_request_send(task, r, out);
647*1131Smax.romanov@nginx.com }
648*1131Smax.romanov@nginx.com 
649*1131Smax.romanov@nginx.com 
650*1131Smax.romanov@nginx.com static void
651*1131Smax.romanov@nginx.com nxt_h1p_conn_ws_error_sent(nxt_task_t *task, void *obj, void *data)
652*1131Smax.romanov@nginx.com {
653*1131Smax.romanov@nginx.com     nxt_http_request_t  *r;
654*1131Smax.romanov@nginx.com 
655*1131Smax.romanov@nginx.com     r = data;
656*1131Smax.romanov@nginx.com 
657*1131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws error sent");
658*1131Smax.romanov@nginx.com 
659*1131Smax.romanov@nginx.com     r->state->error_handler(task, r, r->proto.any);
660*1131Smax.romanov@nginx.com }
661*1131Smax.romanov@nginx.com 
662*1131Smax.romanov@nginx.com 
663*1131Smax.romanov@nginx.com static void
664*1131Smax.romanov@nginx.com nxt_h1p_conn_ws_pong(nxt_task_t *task, void *obj, void *data)
665*1131Smax.romanov@nginx.com {
666*1131Smax.romanov@nginx.com     uint8_t                 payload_len, i;
667*1131Smax.romanov@nginx.com     nxt_buf_t               *b, *out, *next;
668*1131Smax.romanov@nginx.com     nxt_http_request_t      *r;
669*1131Smax.romanov@nginx.com     nxt_websocket_header_t  *wsh;
670*1131Smax.romanov@nginx.com     uint8_t                 mask[4];
671*1131Smax.romanov@nginx.com 
672*1131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws pong");
673*1131Smax.romanov@nginx.com 
674*1131Smax.romanov@nginx.com     r = obj;
675*1131Smax.romanov@nginx.com     b = r->ws_frame;
676*1131Smax.romanov@nginx.com 
677*1131Smax.romanov@nginx.com     wsh = (nxt_websocket_header_t *) b->mem.pos;
678*1131Smax.romanov@nginx.com     payload_len = wsh->payload_len;
679*1131Smax.romanov@nginx.com 
680*1131Smax.romanov@nginx.com     b->mem.pos += 2;
681*1131Smax.romanov@nginx.com 
682*1131Smax.romanov@nginx.com     nxt_memcpy(mask, b->mem.pos, 4);
683*1131Smax.romanov@nginx.com 
684*1131Smax.romanov@nginx.com     b->mem.pos += 4;
685*1131Smax.romanov@nginx.com 
686*1131Smax.romanov@nginx.com     out = nxt_http_buf_mem(task, r, 2 + payload_len);
687*1131Smax.romanov@nginx.com     if (nxt_slow_path(out == NULL)) {
688*1131Smax.romanov@nginx.com         nxt_http_request_error_handler(task, r, r->proto.any);
689*1131Smax.romanov@nginx.com         return;
690*1131Smax.romanov@nginx.com     }
691*1131Smax.romanov@nginx.com 
692*1131Smax.romanov@nginx.com     out->mem.start[0] = 0;
693*1131Smax.romanov@nginx.com     out->mem.start[1] = 0;
694*1131Smax.romanov@nginx.com 
695*1131Smax.romanov@nginx.com     wsh = (nxt_websocket_header_t *) out->mem.start;
696*1131Smax.romanov@nginx.com     out->mem.free = nxt_websocket_frame_init(wsh, payload_len);
697*1131Smax.romanov@nginx.com 
698*1131Smax.romanov@nginx.com     wsh->fin = 1;
699*1131Smax.romanov@nginx.com     wsh->opcode = NXT_WEBSOCKET_OP_PONG;
700*1131Smax.romanov@nginx.com 
701*1131Smax.romanov@nginx.com     for (i = 0; i < payload_len; i++) {
702*1131Smax.romanov@nginx.com         while (nxt_buf_mem_used_size(&b->mem) == 0) {
703*1131Smax.romanov@nginx.com             next = b->next;
704*1131Smax.romanov@nginx.com 
705*1131Smax.romanov@nginx.com             nxt_work_queue_add(&task->thread->engine->fast_work_queue,
706*1131Smax.romanov@nginx.com                                b->completion_handler, task, b, b->parent);
707*1131Smax.romanov@nginx.com 
708*1131Smax.romanov@nginx.com             b = next;
709*1131Smax.romanov@nginx.com         }
710*1131Smax.romanov@nginx.com 
711*1131Smax.romanov@nginx.com         *out->mem.free++ = *b->mem.pos++ ^ mask[i % 4];
712*1131Smax.romanov@nginx.com     }
713*1131Smax.romanov@nginx.com 
714*1131Smax.romanov@nginx.com     r->ws_frame = b;
715*1131Smax.romanov@nginx.com 
716*1131Smax.romanov@nginx.com     nxt_http_request_send(task, r, out);
717*1131Smax.romanov@nginx.com 
718*1131Smax.romanov@nginx.com     nxt_http_request_ws_frame_start(task, r, r->ws_frame);
719*1131Smax.romanov@nginx.com }
720