xref: /unit/src/nxt_h1proto_websocket.c (revision 1268)
11131Smax.romanov@nginx.com 
21131Smax.romanov@nginx.com /*
31131Smax.romanov@nginx.com  * Copyright (C) NGINX, Inc.
41131Smax.romanov@nginx.com  */
51131Smax.romanov@nginx.com 
61131Smax.romanov@nginx.com #include <nxt_main.h>
71131Smax.romanov@nginx.com #include <nxt_router.h>
81131Smax.romanov@nginx.com #include <nxt_http.h>
91131Smax.romanov@nginx.com #include <nxt_h1proto.h>
101131Smax.romanov@nginx.com #include <nxt_websocket.h>
111131Smax.romanov@nginx.com #include <nxt_websocket_header.h>
121131Smax.romanov@nginx.com 
131131Smax.romanov@nginx.com typedef struct {
141131Smax.romanov@nginx.com     uint16_t   code;
151131Smax.romanov@nginx.com     uint8_t    args;
161131Smax.romanov@nginx.com     nxt_str_t  desc;
171131Smax.romanov@nginx.com } nxt_ws_error_t;
181131Smax.romanov@nginx.com 
191131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_keepalive(nxt_task_t *task, void *obj, void *data);
201131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_frame_header_read(nxt_task_t *task, void *obj,
211131Smax.romanov@nginx.com     void *data);
221131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_keepalive_disable(nxt_task_t *task,
231131Smax.romanov@nginx.com     nxt_h1proto_t *h1p);
241131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_keepalive_enable(nxt_task_t *task,
251131Smax.romanov@nginx.com     nxt_h1proto_t *h1p);
261131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_frame_process(nxt_task_t *task, nxt_conn_t *c,
271131Smax.romanov@nginx.com     nxt_h1proto_t *h1p, nxt_websocket_header_t *wsh);
281131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_error(nxt_task_t *task, void *obj, void *data);
29*1268Sigor@sysoev.ru static ssize_t nxt_h1p_ws_io_read_handler(nxt_task_t *task, nxt_conn_t *c);
301131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_timeout(nxt_task_t *task, void *obj, void *data);
311131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_frame_payload_read(nxt_task_t *task, void *obj,
321131Smax.romanov@nginx.com     void *data);
331131Smax.romanov@nginx.com static void hxt_h1p_send_ws_error(nxt_task_t *task, nxt_http_request_t *r,
341131Smax.romanov@nginx.com     const nxt_ws_error_t *err, ...);
351131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_error_sent(nxt_task_t *task, void *obj, void *data);
361131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_pong(nxt_task_t *task, void *obj, void *data);
371131Smax.romanov@nginx.com 
381131Smax.romanov@nginx.com static const nxt_conn_state_t  nxt_h1p_read_ws_frame_header_state;
391131Smax.romanov@nginx.com static const nxt_conn_state_t  nxt_h1p_read_ws_frame_payload_state;
401131Smax.romanov@nginx.com 
411131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_out_of_memory = {
421131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_INTERNAL_SERVER_ERROR,
431131Smax.romanov@nginx.com     0, nxt_string("Out of memory") };
441131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_too_big = {
451131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_MESSAGE_TOO_BIG,
461131Smax.romanov@nginx.com     1, nxt_string("Message too big: %uL bytes") };
471131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_invalid_close_code = {
481131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
491131Smax.romanov@nginx.com     1, nxt_string("Close code %ud is not valid") };
501131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_going_away = {
511131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_GOING_AWAY,
521131Smax.romanov@nginx.com     0, nxt_string("Remote peer is going away") };
531131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_not_masked = {
541131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
551131Smax.romanov@nginx.com     0, nxt_string("Not masked client frame") };
561131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_ctrl_fragmented = {
571131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
581131Smax.romanov@nginx.com     0, nxt_string("Fragmented control frame") };
591131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_ctrl_too_big = {
601131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
611131Smax.romanov@nginx.com     1, nxt_string("Control frame too big: %uL bytes") };
621131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_invalid_close_len = {
631131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
641131Smax.romanov@nginx.com     0, nxt_string("Close frame payload length cannot be 1") };
651131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_invalid_opcode = {
661131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
671131Smax.romanov@nginx.com     1, nxt_string("Unrecognized opcode %ud") };
681131Smax.romanov@nginx.com static const nxt_ws_error_t  nxt_ws_err_cont_expected = {
691131Smax.romanov@nginx.com     NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
701131Smax.romanov@nginx.com     1, nxt_string("Continuation expected, but %ud opcode received") };
711131Smax.romanov@nginx.com 
721131Smax.romanov@nginx.com void
731131Smax.romanov@nginx.com nxt_h1p_websocket_first_frame_start(nxt_task_t *task, nxt_http_request_t *r,
741131Smax.romanov@nginx.com     nxt_buf_t *ws_frame)
751131Smax.romanov@nginx.com {
761131Smax.romanov@nginx.com     nxt_conn_t               *c;
771131Smax.romanov@nginx.com     nxt_timer_t              *timer;
781131Smax.romanov@nginx.com     nxt_h1proto_t            *h1p;
791131Smax.romanov@nginx.com     nxt_socket_conf_joint_t  *joint;
801131Smax.romanov@nginx.com 
811131Smax.romanov@nginx.com     nxt_debug(task, "h1p ws first frame start");
821131Smax.romanov@nginx.com 
831131Smax.romanov@nginx.com     h1p = r->proto.h1;
841131Smax.romanov@nginx.com     c = h1p->conn;
851131Smax.romanov@nginx.com 
861131Smax.romanov@nginx.com     if (!c->tcp_nodelay) {
871131Smax.romanov@nginx.com         nxt_conn_tcp_nodelay_on(task, c);
881131Smax.romanov@nginx.com     }
891131Smax.romanov@nginx.com 
901131Smax.romanov@nginx.com     joint = c->listen->socket.data;
911131Smax.romanov@nginx.com 
921131Smax.romanov@nginx.com     if (nxt_slow_path(joint != NULL
931131Smax.romanov@nginx.com         && joint->socket_conf->websocket_conf.keepalive_interval != 0))
941131Smax.romanov@nginx.com     {
951131Smax.romanov@nginx.com         h1p->websocket_timer = nxt_mp_zget(c->mem_pool,
961131Smax.romanov@nginx.com                                            sizeof(nxt_h1p_websocket_timer_t));
971131Smax.romanov@nginx.com         if (nxt_slow_path(h1p->websocket_timer == NULL)) {
981131Smax.romanov@nginx.com             hxt_h1p_send_ws_error(task, r, &nxt_ws_err_out_of_memory);
991131Smax.romanov@nginx.com             return;
1001131Smax.romanov@nginx.com         }
1011131Smax.romanov@nginx.com 
1021131Smax.romanov@nginx.com         h1p->websocket_timer->keepalive_interval =
1031131Smax.romanov@nginx.com             joint->socket_conf->websocket_conf.keepalive_interval;
1041131Smax.romanov@nginx.com         h1p->websocket_timer->h1p = h1p;
1051131Smax.romanov@nginx.com 
1061131Smax.romanov@nginx.com         timer = &h1p->websocket_timer->timer;
1071131Smax.romanov@nginx.com         timer->task = &c->task;
1081131Smax.romanov@nginx.com         timer->work_queue = &task->thread->engine->fast_work_queue;
1091131Smax.romanov@nginx.com         timer->log = &c->log;
1101131Smax.romanov@nginx.com         timer->bias = NXT_TIMER_DEFAULT_BIAS;
1111131Smax.romanov@nginx.com         timer->handler = nxt_h1p_conn_ws_keepalive;
1121131Smax.romanov@nginx.com     }
1131131Smax.romanov@nginx.com 
1141131Smax.romanov@nginx.com     nxt_h1p_websocket_frame_start(task, r, ws_frame);
1151131Smax.romanov@nginx.com }
1161131Smax.romanov@nginx.com 
1171131Smax.romanov@nginx.com 
1181131Smax.romanov@nginx.com void
1191131Smax.romanov@nginx.com nxt_h1p_websocket_frame_start(nxt_task_t *task, nxt_http_request_t *r,
1201131Smax.romanov@nginx.com     nxt_buf_t *ws_frame)
1211131Smax.romanov@nginx.com {
1221131Smax.romanov@nginx.com     size_t         size;
1231131Smax.romanov@nginx.com     nxt_buf_t      *in;
1241131Smax.romanov@nginx.com     nxt_conn_t     *c;
1251131Smax.romanov@nginx.com     nxt_h1proto_t  *h1p;
1261131Smax.romanov@nginx.com 
1271131Smax.romanov@nginx.com     nxt_debug(task, "h1p ws frame start");
1281131Smax.romanov@nginx.com 
1291131Smax.romanov@nginx.com     h1p = r->proto.h1;
1301131Smax.romanov@nginx.com 
1311131Smax.romanov@nginx.com     if (nxt_slow_path(h1p->websocket_closed)) {
1321131Smax.romanov@nginx.com         return;
1331131Smax.romanov@nginx.com     }
1341131Smax.romanov@nginx.com 
1351131Smax.romanov@nginx.com     c = h1p->conn;
1361131Smax.romanov@nginx.com     c->read = ws_frame;
1371131Smax.romanov@nginx.com 
1381131Smax.romanov@nginx.com     nxt_h1p_complete_buffers(task, h1p);
1391131Smax.romanov@nginx.com 
1401131Smax.romanov@nginx.com     in = c->read;
1411131Smax.romanov@nginx.com     c->read_state = &nxt_h1p_read_ws_frame_header_state;
1421131Smax.romanov@nginx.com 
1431131Smax.romanov@nginx.com     if (in == NULL) {
1441131Smax.romanov@nginx.com         nxt_conn_read(task->thread->engine, c);
1451131Smax.romanov@nginx.com         nxt_h1p_conn_ws_keepalive_enable(task, h1p);
1461131Smax.romanov@nginx.com 
1471131Smax.romanov@nginx.com     } else {
1481131Smax.romanov@nginx.com         size = nxt_buf_mem_used_size(&in->mem);
1491131Smax.romanov@nginx.com 
1501131Smax.romanov@nginx.com         nxt_debug(task, "h1p read client ws frame");
1511131Smax.romanov@nginx.com 
1521131Smax.romanov@nginx.com         nxt_memmove(in->mem.start, in->mem.pos, size);
1531131Smax.romanov@nginx.com 
1541131Smax.romanov@nginx.com         in->mem.pos = in->mem.start;
1551131Smax.romanov@nginx.com         in->mem.free = in->mem.start + size;
1561131Smax.romanov@nginx.com 
1571131Smax.romanov@nginx.com         nxt_h1p_conn_ws_frame_header_read(task, c, h1p);
1581131Smax.romanov@nginx.com     }
1591131Smax.romanov@nginx.com }
1601131Smax.romanov@nginx.com 
1611131Smax.romanov@nginx.com 
1621131Smax.romanov@nginx.com static void
1631131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive(nxt_task_t *task, void *obj, void *data)
1641131Smax.romanov@nginx.com {
1651131Smax.romanov@nginx.com     nxt_buf_t                  *out;
1661131Smax.romanov@nginx.com     nxt_timer_t                *timer;
1671131Smax.romanov@nginx.com     nxt_h1proto_t              *h1p;
1681131Smax.romanov@nginx.com     nxt_http_request_t         *r;
1691131Smax.romanov@nginx.com     nxt_websocket_header_t     *wsh;
1701131Smax.romanov@nginx.com     nxt_h1p_websocket_timer_t  *ws_timer;
1711131Smax.romanov@nginx.com 
1721131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws keepalive");
1731131Smax.romanov@nginx.com 
1741131Smax.romanov@nginx.com     timer = obj;
1751131Smax.romanov@nginx.com     ws_timer = nxt_timer_data(timer, nxt_h1p_websocket_timer_t, timer);
1761131Smax.romanov@nginx.com     h1p = ws_timer->h1p;
1771131Smax.romanov@nginx.com 
1781131Smax.romanov@nginx.com     r = h1p->request;
1791131Smax.romanov@nginx.com     if (nxt_slow_path(r == NULL)) {
1801131Smax.romanov@nginx.com         return;
1811131Smax.romanov@nginx.com     }
1821131Smax.romanov@nginx.com 
1831131Smax.romanov@nginx.com     out = nxt_http_buf_mem(task, r, 2);
1841131Smax.romanov@nginx.com     if (nxt_slow_path(out == NULL)) {
1851131Smax.romanov@nginx.com         nxt_http_request_error_handler(task, r, r->proto.any);
1861131Smax.romanov@nginx.com         return;
1871131Smax.romanov@nginx.com     }
1881131Smax.romanov@nginx.com 
1891131Smax.romanov@nginx.com     out->mem.start[0] = 0;
1901131Smax.romanov@nginx.com     out->mem.start[1] = 0;
1911131Smax.romanov@nginx.com 
1921131Smax.romanov@nginx.com     wsh = (nxt_websocket_header_t *) out->mem.start;
1931131Smax.romanov@nginx.com     out->mem.free = nxt_websocket_frame_init(wsh, 0);
1941131Smax.romanov@nginx.com 
1951131Smax.romanov@nginx.com     wsh->fin = 1;
1961131Smax.romanov@nginx.com     wsh->opcode = NXT_WEBSOCKET_OP_PING;
1971131Smax.romanov@nginx.com 
1981131Smax.romanov@nginx.com     nxt_http_request_send(task, r, out);
1991131Smax.romanov@nginx.com }
2001131Smax.romanov@nginx.com 
2011131Smax.romanov@nginx.com 
2021131Smax.romanov@nginx.com static const nxt_conn_state_t  nxt_h1p_read_ws_frame_header_state
2031131Smax.romanov@nginx.com     nxt_aligned(64) =
2041131Smax.romanov@nginx.com {
2051131Smax.romanov@nginx.com     .ready_handler = nxt_h1p_conn_ws_frame_header_read,
2061131Smax.romanov@nginx.com     .close_handler = nxt_h1p_conn_ws_error,
2071131Smax.romanov@nginx.com     .error_handler = nxt_h1p_conn_ws_error,
2081131Smax.romanov@nginx.com 
2091131Smax.romanov@nginx.com     .io_read_handler = nxt_h1p_ws_io_read_handler,
2101131Smax.romanov@nginx.com 
2111131Smax.romanov@nginx.com     .timer_handler = nxt_h1p_conn_ws_timeout,
2121131Smax.romanov@nginx.com     .timer_value = nxt_h1p_conn_request_timer_value,
2131131Smax.romanov@nginx.com     .timer_data = offsetof(nxt_socket_conf_t, websocket_conf.read_timeout),
2141131Smax.romanov@nginx.com     .timer_autoreset = 1,
2151131Smax.romanov@nginx.com };
2161131Smax.romanov@nginx.com 
2171131Smax.romanov@nginx.com 
2181131Smax.romanov@nginx.com static void
2191131Smax.romanov@nginx.com nxt_h1p_conn_ws_frame_header_read(nxt_task_t *task, void *obj, void *data)
2201131Smax.romanov@nginx.com {
2211131Smax.romanov@nginx.com     size_t                   size, hsize, frame_size, max_frame_size;
2221131Smax.romanov@nginx.com     uint64_t                 payload_len;
2231131Smax.romanov@nginx.com     nxt_conn_t               *c;
2241131Smax.romanov@nginx.com     nxt_h1proto_t            *h1p;
2251131Smax.romanov@nginx.com     nxt_http_request_t       *r;
2261131Smax.romanov@nginx.com     nxt_event_engine_t       *engine;
2271131Smax.romanov@nginx.com     nxt_websocket_header_t   *wsh;
2281131Smax.romanov@nginx.com     nxt_socket_conf_joint_t  *joint;
2291131Smax.romanov@nginx.com 
2301131Smax.romanov@nginx.com     c = obj;
2311131Smax.romanov@nginx.com     h1p = data;
2321131Smax.romanov@nginx.com 
2331131Smax.romanov@nginx.com     nxt_h1p_conn_ws_keepalive_disable(task, h1p);
2341131Smax.romanov@nginx.com 
2351131Smax.romanov@nginx.com     size = nxt_buf_mem_used_size(&c->read->mem);
2361131Smax.romanov@nginx.com 
2371131Smax.romanov@nginx.com     engine = task->thread->engine;
2381131Smax.romanov@nginx.com 
2391131Smax.romanov@nginx.com     if (size < 2) {
2401131Smax.romanov@nginx.com         nxt_debug(task, "h1p conn ws frame header read %z", size);
2411131Smax.romanov@nginx.com 
2421131Smax.romanov@nginx.com         nxt_conn_read(engine, c);
2431131Smax.romanov@nginx.com         nxt_h1p_conn_ws_keepalive_enable(task, h1p);
2441131Smax.romanov@nginx.com 
2451131Smax.romanov@nginx.com         return;
2461131Smax.romanov@nginx.com     }
2471131Smax.romanov@nginx.com 
2481131Smax.romanov@nginx.com     wsh = (nxt_websocket_header_t *) c->read->mem.pos;
2491131Smax.romanov@nginx.com 
2501131Smax.romanov@nginx.com     hsize = nxt_websocket_frame_header_size(wsh);
2511131Smax.romanov@nginx.com 
2521131Smax.romanov@nginx.com     if (size < hsize) {
2531131Smax.romanov@nginx.com         nxt_debug(task, "h1p conn ws frame header read %z < %z", size, hsize);
2541131Smax.romanov@nginx.com 
2551131Smax.romanov@nginx.com         nxt_conn_read(engine, c);
2561131Smax.romanov@nginx.com         nxt_h1p_conn_ws_keepalive_enable(task, h1p);
2571131Smax.romanov@nginx.com 
2581131Smax.romanov@nginx.com         return;
2591131Smax.romanov@nginx.com     }
2601131Smax.romanov@nginx.com 
2611131Smax.romanov@nginx.com     r = h1p->request;
2621131Smax.romanov@nginx.com     if (nxt_slow_path(r == NULL)) {
2631131Smax.romanov@nginx.com         return;
2641131Smax.romanov@nginx.com     }
2651131Smax.romanov@nginx.com 
2661131Smax.romanov@nginx.com     r->ws_frame = c->read;
2671131Smax.romanov@nginx.com 
2681131Smax.romanov@nginx.com     joint = c->listen->socket.data;
2691131Smax.romanov@nginx.com 
2701131Smax.romanov@nginx.com     if (nxt_slow_path(joint == NULL)) {
2711131Smax.romanov@nginx.com         /*
2721131Smax.romanov@nginx.com          * Listening socket had been closed while
2731131Smax.romanov@nginx.com          * connection was in keep-alive state.
2741131Smax.romanov@nginx.com          */
2751131Smax.romanov@nginx.com         c->read_state = &nxt_h1p_idle_close_state;
2761131Smax.romanov@nginx.com         return;
2771131Smax.romanov@nginx.com     }
2781131Smax.romanov@nginx.com 
2791131Smax.romanov@nginx.com     if (nxt_slow_path(wsh->mask == 0)) {
2801131Smax.romanov@nginx.com         hxt_h1p_send_ws_error(task, r, &nxt_ws_err_not_masked);
2811131Smax.romanov@nginx.com         return;
2821131Smax.romanov@nginx.com     }
2831131Smax.romanov@nginx.com 
2841131Smax.romanov@nginx.com     if ((wsh->opcode & NXT_WEBSOCKET_OP_CTRL) != 0) {
2851131Smax.romanov@nginx.com         if (nxt_slow_path(wsh->fin == 0)) {
2861131Smax.romanov@nginx.com             hxt_h1p_send_ws_error(task, r, &nxt_ws_err_ctrl_fragmented);
2871131Smax.romanov@nginx.com             return;
2881131Smax.romanov@nginx.com         }
2891131Smax.romanov@nginx.com 
2901131Smax.romanov@nginx.com         if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_PING
2911131Smax.romanov@nginx.com                           && wsh->opcode != NXT_WEBSOCKET_OP_PONG
2921131Smax.romanov@nginx.com                           && wsh->opcode != NXT_WEBSOCKET_OP_CLOSE))
2931131Smax.romanov@nginx.com         {
2941131Smax.romanov@nginx.com             hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_opcode,
2951131Smax.romanov@nginx.com                                   wsh->opcode);
2961131Smax.romanov@nginx.com             return;
2971131Smax.romanov@nginx.com         }
2981131Smax.romanov@nginx.com 
2991131Smax.romanov@nginx.com         if (nxt_slow_path(wsh->payload_len > 125)) {
3001131Smax.romanov@nginx.com             hxt_h1p_send_ws_error(task, r, &nxt_ws_err_ctrl_too_big,
3011131Smax.romanov@nginx.com                                   nxt_websocket_frame_payload_len(wsh));
3021131Smax.romanov@nginx.com             return;
3031131Smax.romanov@nginx.com         }
3041131Smax.romanov@nginx.com 
3051131Smax.romanov@nginx.com         if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_CLOSE
3061131Smax.romanov@nginx.com                           && wsh->payload_len == 1))
3071131Smax.romanov@nginx.com         {
3081131Smax.romanov@nginx.com             hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_close_len);
3091131Smax.romanov@nginx.com             return;
3101131Smax.romanov@nginx.com         }
3111131Smax.romanov@nginx.com 
3121131Smax.romanov@nginx.com     } else {
3131131Smax.romanov@nginx.com         if (h1p->websocket_cont_expected) {
3141131Smax.romanov@nginx.com             if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_CONT)) {
3151131Smax.romanov@nginx.com                 hxt_h1p_send_ws_error(task, r, &nxt_ws_err_cont_expected,
3161131Smax.romanov@nginx.com                                       wsh->opcode);
3171131Smax.romanov@nginx.com                 return;
3181131Smax.romanov@nginx.com             }
3191131Smax.romanov@nginx.com 
3201131Smax.romanov@nginx.com         } else {
3211131Smax.romanov@nginx.com             if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_BINARY
3221131Smax.romanov@nginx.com                               && wsh->opcode != NXT_WEBSOCKET_OP_TEXT))
3231131Smax.romanov@nginx.com             {
3241131Smax.romanov@nginx.com                 hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_opcode,
3251131Smax.romanov@nginx.com                                       wsh->opcode);
3261131Smax.romanov@nginx.com                 return;
3271131Smax.romanov@nginx.com             }
3281131Smax.romanov@nginx.com         }
3291131Smax.romanov@nginx.com 
3301131Smax.romanov@nginx.com         h1p->websocket_cont_expected = !wsh->fin;
3311131Smax.romanov@nginx.com     }
3321131Smax.romanov@nginx.com 
3331131Smax.romanov@nginx.com     max_frame_size = joint->socket_conf->websocket_conf.max_frame_size;
3341131Smax.romanov@nginx.com 
3351131Smax.romanov@nginx.com     payload_len = nxt_websocket_frame_payload_len(wsh);
3361131Smax.romanov@nginx.com 
3371131Smax.romanov@nginx.com     if (nxt_slow_path(hsize > max_frame_size
3381131Smax.romanov@nginx.com                       || payload_len > (max_frame_size - hsize)))
3391131Smax.romanov@nginx.com     {
3401131Smax.romanov@nginx.com         hxt_h1p_send_ws_error(task, r, &nxt_ws_err_too_big, payload_len);
3411131Smax.romanov@nginx.com         return;
3421131Smax.romanov@nginx.com     }
3431131Smax.romanov@nginx.com 
3441131Smax.romanov@nginx.com     c->read_state = &nxt_h1p_read_ws_frame_payload_state;
3451131Smax.romanov@nginx.com 
3461131Smax.romanov@nginx.com     frame_size = payload_len + hsize;
3471131Smax.romanov@nginx.com 
3481131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws frame header read: %z, %z", size, frame_size);
3491131Smax.romanov@nginx.com 
3501131Smax.romanov@nginx.com     if (frame_size <= size) {
3511131Smax.romanov@nginx.com         nxt_h1p_conn_ws_frame_process(task, c, h1p, wsh);
3521131Smax.romanov@nginx.com 
3531131Smax.romanov@nginx.com         return;
3541131Smax.romanov@nginx.com     }
3551131Smax.romanov@nginx.com 
3561131Smax.romanov@nginx.com     if (frame_size < (size_t) nxt_buf_mem_size(&c->read->mem)) {
3571131Smax.romanov@nginx.com         c->read->mem.end = c->read->mem.start + frame_size;
3581131Smax.romanov@nginx.com 
3591131Smax.romanov@nginx.com     } else {
3601131Smax.romanov@nginx.com         nxt_buf_t *b = nxt_buf_mem_alloc(c->mem_pool, frame_size - size, 0);
3611131Smax.romanov@nginx.com 
3621131Smax.romanov@nginx.com         c->read->next = b;
3631131Smax.romanov@nginx.com         c->read = b;
3641131Smax.romanov@nginx.com     }
3651131Smax.romanov@nginx.com 
3661131Smax.romanov@nginx.com     nxt_conn_read(engine, c);
3671131Smax.romanov@nginx.com     nxt_h1p_conn_ws_keepalive_enable(task, h1p);
3681131Smax.romanov@nginx.com }
3691131Smax.romanov@nginx.com 
3701131Smax.romanov@nginx.com 
3711131Smax.romanov@nginx.com static void
3721131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive_disable(nxt_task_t *task, nxt_h1proto_t *h1p)
3731131Smax.romanov@nginx.com {
3741131Smax.romanov@nginx.com     nxt_timer_t  *timer;
3751131Smax.romanov@nginx.com 
3761131Smax.romanov@nginx.com     if (h1p->websocket_timer == NULL) {
3771131Smax.romanov@nginx.com         return;
3781131Smax.romanov@nginx.com     }
3791131Smax.romanov@nginx.com 
3801131Smax.romanov@nginx.com     timer = &h1p->websocket_timer->timer;
3811131Smax.romanov@nginx.com 
3821131Smax.romanov@nginx.com     if (nxt_slow_path(timer->handler != nxt_h1p_conn_ws_keepalive)) {
3831131Smax.romanov@nginx.com         nxt_debug(task, "h1p ws keepalive disable: scheduled ws shutdown");
3841131Smax.romanov@nginx.com         return;
3851131Smax.romanov@nginx.com     }
3861131Smax.romanov@nginx.com 
3871131Smax.romanov@nginx.com     nxt_timer_disable(task->thread->engine, timer);
3881131Smax.romanov@nginx.com }
3891131Smax.romanov@nginx.com 
3901131Smax.romanov@nginx.com 
3911131Smax.romanov@nginx.com static void
3921131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive_enable(nxt_task_t *task, nxt_h1proto_t *h1p)
3931131Smax.romanov@nginx.com {
3941131Smax.romanov@nginx.com     nxt_timer_t  *timer;
3951131Smax.romanov@nginx.com 
3961131Smax.romanov@nginx.com     if (h1p->websocket_timer == NULL) {
3971131Smax.romanov@nginx.com         return;
3981131Smax.romanov@nginx.com     }
3991131Smax.romanov@nginx.com 
4001131Smax.romanov@nginx.com     timer = &h1p->websocket_timer->timer;
4011131Smax.romanov@nginx.com 
4021131Smax.romanov@nginx.com     if (nxt_slow_path(timer->handler != nxt_h1p_conn_ws_keepalive)) {
4031131Smax.romanov@nginx.com         nxt_debug(task, "h1p ws keepalive enable: scheduled ws shutdown");
4041131Smax.romanov@nginx.com         return;
4051131Smax.romanov@nginx.com     }
4061131Smax.romanov@nginx.com 
4071131Smax.romanov@nginx.com     nxt_timer_add(task->thread->engine, timer,
4081131Smax.romanov@nginx.com                   h1p->websocket_timer->keepalive_interval);
4091131Smax.romanov@nginx.com }
4101131Smax.romanov@nginx.com 
4111131Smax.romanov@nginx.com 
4121131Smax.romanov@nginx.com static void
4131131Smax.romanov@nginx.com nxt_h1p_conn_ws_frame_process(nxt_task_t *task, nxt_conn_t *c,
4141131Smax.romanov@nginx.com     nxt_h1proto_t *h1p, nxt_websocket_header_t *wsh)
4151131Smax.romanov@nginx.com {
4161131Smax.romanov@nginx.com     size_t              hsize;
4171131Smax.romanov@nginx.com     uint8_t             *p, *mask;
4181131Smax.romanov@nginx.com     uint16_t            code;
4191131Smax.romanov@nginx.com     nxt_http_request_t  *r;
4201131Smax.romanov@nginx.com 
4211131Smax.romanov@nginx.com     r = h1p->request;
4221131Smax.romanov@nginx.com 
4231131Smax.romanov@nginx.com     c->read = NULL;
4241131Smax.romanov@nginx.com 
4251131Smax.romanov@nginx.com     if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_PING)) {
4261155Smax.romanov@nginx.com         nxt_h1p_conn_ws_pong(task, r, NULL);
4271131Smax.romanov@nginx.com         return;
4281131Smax.romanov@nginx.com     }
4291131Smax.romanov@nginx.com 
4301131Smax.romanov@nginx.com     if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_CLOSE)) {
4311131Smax.romanov@nginx.com         if (wsh->payload_len >= 2) {
4321131Smax.romanov@nginx.com             hsize = nxt_websocket_frame_header_size(wsh);
4331131Smax.romanov@nginx.com             mask = nxt_pointer_to(wsh, hsize - 4);
4341131Smax.romanov@nginx.com             p = nxt_pointer_to(wsh, hsize);
4351131Smax.romanov@nginx.com 
4361131Smax.romanov@nginx.com             code = ((p[0] ^ mask[0]) << 8) + (p[1] ^ mask[1]);
4371131Smax.romanov@nginx.com 
4381131Smax.romanov@nginx.com             if (nxt_slow_path(code < 1000 || code >= 5000
4391131Smax.romanov@nginx.com                               || (code > 1003 && code < 1007)
4401131Smax.romanov@nginx.com                               || (code > 1014 && code < 3000)))
4411131Smax.romanov@nginx.com             {
4421131Smax.romanov@nginx.com                 hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_close_code,
4431131Smax.romanov@nginx.com                                       code);
4441131Smax.romanov@nginx.com                 return;
4451131Smax.romanov@nginx.com             }
4461131Smax.romanov@nginx.com         }
4471131Smax.romanov@nginx.com 
4481131Smax.romanov@nginx.com         h1p->websocket_closed = 1;
4491131Smax.romanov@nginx.com     }
4501131Smax.romanov@nginx.com 
4511155Smax.romanov@nginx.com     r->state->ready_handler(task, r, NULL);
4521131Smax.romanov@nginx.com }
4531131Smax.romanov@nginx.com 
4541131Smax.romanov@nginx.com 
4551131Smax.romanov@nginx.com static void
4561131Smax.romanov@nginx.com nxt_h1p_conn_ws_error(nxt_task_t *task, void *obj, void *data)
4571131Smax.romanov@nginx.com {
4581131Smax.romanov@nginx.com     nxt_h1proto_t       *h1p;
4591131Smax.romanov@nginx.com     nxt_http_request_t  *r;
4601131Smax.romanov@nginx.com 
4611131Smax.romanov@nginx.com     h1p = data;
4621131Smax.romanov@nginx.com 
4631131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws error");
4641131Smax.romanov@nginx.com 
4651131Smax.romanov@nginx.com     r = h1p->request;
4661131Smax.romanov@nginx.com 
4671131Smax.romanov@nginx.com     h1p->keepalive = 0;
4681131Smax.romanov@nginx.com 
4691131Smax.romanov@nginx.com     if (nxt_fast_path(r != NULL)) {
4701131Smax.romanov@nginx.com         r->state->error_handler(task, r, h1p);
4711131Smax.romanov@nginx.com     }
4721131Smax.romanov@nginx.com }
4731131Smax.romanov@nginx.com 
4741131Smax.romanov@nginx.com 
4751131Smax.romanov@nginx.com static ssize_t
476*1268Sigor@sysoev.ru nxt_h1p_ws_io_read_handler(nxt_task_t *task, nxt_conn_t *c)
4771131Smax.romanov@nginx.com {
4781131Smax.romanov@nginx.com     size_t     size;
4791131Smax.romanov@nginx.com     ssize_t    n;
4801131Smax.romanov@nginx.com     nxt_buf_t  *b;
4811131Smax.romanov@nginx.com 
4821131Smax.romanov@nginx.com     b = c->read;
4831131Smax.romanov@nginx.com 
4841131Smax.romanov@nginx.com     if (b == NULL) {
4851131Smax.romanov@nginx.com         /* Enough for control frame. */
4861131Smax.romanov@nginx.com         size = 10 + 125;
4871131Smax.romanov@nginx.com 
4881131Smax.romanov@nginx.com         b = nxt_buf_mem_alloc(c->mem_pool, size, 0);
4891131Smax.romanov@nginx.com         if (nxt_slow_path(b == NULL)) {
4901131Smax.romanov@nginx.com             c->socket.error = NXT_ENOMEM;
4911131Smax.romanov@nginx.com             return NXT_ERROR;
4921131Smax.romanov@nginx.com         }
4931131Smax.romanov@nginx.com     }
4941131Smax.romanov@nginx.com 
4951131Smax.romanov@nginx.com     n = c->io->recvbuf(c, b);
4961131Smax.romanov@nginx.com 
4971131Smax.romanov@nginx.com     if (n > 0) {
4981131Smax.romanov@nginx.com         c->read = b;
4991131Smax.romanov@nginx.com 
5001131Smax.romanov@nginx.com     } else {
5011131Smax.romanov@nginx.com         c->read = NULL;
5021131Smax.romanov@nginx.com         nxt_mp_free(c->mem_pool, b);
5031131Smax.romanov@nginx.com     }
5041131Smax.romanov@nginx.com 
5051131Smax.romanov@nginx.com     return n;
5061131Smax.romanov@nginx.com }
5071131Smax.romanov@nginx.com 
5081131Smax.romanov@nginx.com 
5091131Smax.romanov@nginx.com static void
5101131Smax.romanov@nginx.com nxt_h1p_conn_ws_timeout(nxt_task_t *task, void *obj, void *data)
5111131Smax.romanov@nginx.com {
5121131Smax.romanov@nginx.com     nxt_conn_t          *c;
5131131Smax.romanov@nginx.com     nxt_timer_t         *timer;
5141131Smax.romanov@nginx.com     nxt_h1proto_t       *h1p;
5151131Smax.romanov@nginx.com     nxt_http_request_t  *r;
5161131Smax.romanov@nginx.com 
5171131Smax.romanov@nginx.com     timer = obj;
5181131Smax.romanov@nginx.com 
5191131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws timeout");
5201131Smax.romanov@nginx.com 
5211131Smax.romanov@nginx.com     c = nxt_read_timer_conn(timer);
5221131Smax.romanov@nginx.com     c->block_read = 1;
5231131Smax.romanov@nginx.com     /*
5241131Smax.romanov@nginx.com      * Disable SO_LINGER off during socket closing
5251131Smax.romanov@nginx.com      * to send "408 Request Timeout" error response.
5261131Smax.romanov@nginx.com      */
5271131Smax.romanov@nginx.com     c->socket.timedout = 0;
5281131Smax.romanov@nginx.com 
5291131Smax.romanov@nginx.com     h1p = c->socket.data;
5301131Smax.romanov@nginx.com     h1p->keepalive = 0;
5311131Smax.romanov@nginx.com 
5321131Smax.romanov@nginx.com     r = h1p->request;
5331131Smax.romanov@nginx.com     if (nxt_slow_path(r == NULL)) {
5341131Smax.romanov@nginx.com         return;
5351131Smax.romanov@nginx.com     }
5361131Smax.romanov@nginx.com 
5371131Smax.romanov@nginx.com     hxt_h1p_send_ws_error(task, r, &nxt_ws_err_going_away);
5381131Smax.romanov@nginx.com }
5391131Smax.romanov@nginx.com 
5401131Smax.romanov@nginx.com 
5411131Smax.romanov@nginx.com static const nxt_conn_state_t  nxt_h1p_read_ws_frame_payload_state
5421131Smax.romanov@nginx.com     nxt_aligned(64) =
5431131Smax.romanov@nginx.com {
5441131Smax.romanov@nginx.com     .ready_handler = nxt_h1p_conn_ws_frame_payload_read,
5451131Smax.romanov@nginx.com     .close_handler = nxt_h1p_conn_ws_error,
5461131Smax.romanov@nginx.com     .error_handler = nxt_h1p_conn_ws_error,
5471131Smax.romanov@nginx.com 
5481131Smax.romanov@nginx.com     .timer_handler = nxt_h1p_conn_ws_timeout,
5491131Smax.romanov@nginx.com     .timer_value = nxt_h1p_conn_request_timer_value,
5501131Smax.romanov@nginx.com     .timer_data = offsetof(nxt_socket_conf_t, websocket_conf.read_timeout),
5511131Smax.romanov@nginx.com     .timer_autoreset = 1,
5521131Smax.romanov@nginx.com };
5531131Smax.romanov@nginx.com 
5541131Smax.romanov@nginx.com 
5551131Smax.romanov@nginx.com static void
5561131Smax.romanov@nginx.com nxt_h1p_conn_ws_frame_payload_read(nxt_task_t *task, void *obj, void *data)
5571131Smax.romanov@nginx.com {
5581131Smax.romanov@nginx.com     nxt_conn_t              *c;
5591131Smax.romanov@nginx.com     nxt_h1proto_t           *h1p;
5601131Smax.romanov@nginx.com     nxt_http_request_t      *r;
5611131Smax.romanov@nginx.com     nxt_event_engine_t      *engine;
5621131Smax.romanov@nginx.com     nxt_websocket_header_t  *wsh;
5631131Smax.romanov@nginx.com 
5641131Smax.romanov@nginx.com     c = obj;
5651131Smax.romanov@nginx.com     h1p = data;
5661131Smax.romanov@nginx.com 
5671131Smax.romanov@nginx.com     nxt_h1p_conn_ws_keepalive_disable(task, h1p);
5681131Smax.romanov@nginx.com 
5691131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws frame read");
5701131Smax.romanov@nginx.com 
5711131Smax.romanov@nginx.com     if (nxt_buf_mem_free_size(&c->read->mem) == 0) {
5721131Smax.romanov@nginx.com         r = h1p->request;
5731131Smax.romanov@nginx.com         if (nxt_slow_path(r == NULL)) {
5741131Smax.romanov@nginx.com             return;
5751131Smax.romanov@nginx.com         }
5761131Smax.romanov@nginx.com 
5771131Smax.romanov@nginx.com         wsh = (nxt_websocket_header_t *) r->ws_frame->mem.pos;
5781131Smax.romanov@nginx.com 
5791131Smax.romanov@nginx.com         nxt_h1p_conn_ws_frame_process(task, c, h1p, wsh);
5801131Smax.romanov@nginx.com 
5811131Smax.romanov@nginx.com         return;
5821131Smax.romanov@nginx.com     }
5831131Smax.romanov@nginx.com 
5841131Smax.romanov@nginx.com     engine = task->thread->engine;
5851131Smax.romanov@nginx.com 
5861131Smax.romanov@nginx.com     nxt_conn_read(engine, c);
5871131Smax.romanov@nginx.com     nxt_h1p_conn_ws_keepalive_enable(task, h1p);
5881131Smax.romanov@nginx.com }
5891131Smax.romanov@nginx.com 
5901131Smax.romanov@nginx.com 
5911131Smax.romanov@nginx.com static void
5921131Smax.romanov@nginx.com hxt_h1p_send_ws_error(nxt_task_t *task, nxt_http_request_t *r,
5931131Smax.romanov@nginx.com     const nxt_ws_error_t *err, ...)
5941131Smax.romanov@nginx.com {
5951131Smax.romanov@nginx.com     u_char                  *p;
5961131Smax.romanov@nginx.com     va_list                 args;
5971131Smax.romanov@nginx.com     nxt_buf_t               *out;
5981131Smax.romanov@nginx.com     nxt_str_t               desc;
5991131Smax.romanov@nginx.com     nxt_websocket_header_t  *wsh;
6001131Smax.romanov@nginx.com     u_char                  buf[125];
6011131Smax.romanov@nginx.com 
6021131Smax.romanov@nginx.com     if (nxt_slow_path(err->args)) {
6031131Smax.romanov@nginx.com         va_start(args, err);
6041131Smax.romanov@nginx.com         p = nxt_vsprintf(buf, buf + sizeof(buf), (char *) err->desc.start,
6051131Smax.romanov@nginx.com                          args);
6061131Smax.romanov@nginx.com         va_end(args);
6071131Smax.romanov@nginx.com 
6081131Smax.romanov@nginx.com         desc.start = buf;
6091131Smax.romanov@nginx.com         desc.length = p - buf;
6101131Smax.romanov@nginx.com 
6111131Smax.romanov@nginx.com     } else {
6121131Smax.romanov@nginx.com         desc = err->desc;
6131131Smax.romanov@nginx.com     }
6141131Smax.romanov@nginx.com 
6151131Smax.romanov@nginx.com     nxt_log(task, NXT_LOG_INFO, "websocket error %d: %V", err->code, &desc);
6161131Smax.romanov@nginx.com 
6171131Smax.romanov@nginx.com     out = nxt_http_buf_mem(task, r, 2 + sizeof(err->code) + desc.length);
6181131Smax.romanov@nginx.com     if (nxt_slow_path(out == NULL)) {
6191131Smax.romanov@nginx.com         nxt_http_request_error_handler(task, r, r->proto.any);
6201131Smax.romanov@nginx.com         return;
6211131Smax.romanov@nginx.com     }
6221131Smax.romanov@nginx.com 
6231131Smax.romanov@nginx.com     out->mem.start[0] = 0;
6241131Smax.romanov@nginx.com     out->mem.start[1] = 0;
6251131Smax.romanov@nginx.com 
6261131Smax.romanov@nginx.com     wsh = (nxt_websocket_header_t *) out->mem.start;
6271131Smax.romanov@nginx.com     p = nxt_websocket_frame_init(wsh, sizeof(err->code) + desc.length);
6281131Smax.romanov@nginx.com 
6291131Smax.romanov@nginx.com     wsh->fin = 1;
6301131Smax.romanov@nginx.com     wsh->opcode = NXT_WEBSOCKET_OP_CLOSE;
6311131Smax.romanov@nginx.com 
6321131Smax.romanov@nginx.com     *p++ = (err->code >> 8) & 0xFF;
6331131Smax.romanov@nginx.com     *p++ = err->code & 0xFF;
6341131Smax.romanov@nginx.com 
6351131Smax.romanov@nginx.com     out->mem.free = nxt_cpymem(p, desc.start, desc.length);
6361131Smax.romanov@nginx.com     out->next = nxt_http_buf_last(r);
6371131Smax.romanov@nginx.com 
6381131Smax.romanov@nginx.com     if (out->next != NULL) {
6391131Smax.romanov@nginx.com         out->next->completion_handler = nxt_h1p_conn_ws_error_sent;
6401131Smax.romanov@nginx.com     }
6411131Smax.romanov@nginx.com 
6421131Smax.romanov@nginx.com     nxt_http_request_send(task, r, out);
6431131Smax.romanov@nginx.com }
6441131Smax.romanov@nginx.com 
6451131Smax.romanov@nginx.com 
6461131Smax.romanov@nginx.com static void
6471131Smax.romanov@nginx.com nxt_h1p_conn_ws_error_sent(nxt_task_t *task, void *obj, void *data)
6481131Smax.romanov@nginx.com {
6491131Smax.romanov@nginx.com     nxt_http_request_t  *r;
6501131Smax.romanov@nginx.com 
6511131Smax.romanov@nginx.com     r = data;
6521131Smax.romanov@nginx.com 
6531131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws error sent");
6541131Smax.romanov@nginx.com 
6551131Smax.romanov@nginx.com     r->state->error_handler(task, r, r->proto.any);
6561131Smax.romanov@nginx.com }
6571131Smax.romanov@nginx.com 
6581131Smax.romanov@nginx.com 
6591131Smax.romanov@nginx.com static void
6601131Smax.romanov@nginx.com nxt_h1p_conn_ws_pong(nxt_task_t *task, void *obj, void *data)
6611131Smax.romanov@nginx.com {
6621131Smax.romanov@nginx.com     uint8_t                 payload_len, i;
6631131Smax.romanov@nginx.com     nxt_buf_t               *b, *out, *next;
6641131Smax.romanov@nginx.com     nxt_http_request_t      *r;
6651131Smax.romanov@nginx.com     nxt_websocket_header_t  *wsh;
6661131Smax.romanov@nginx.com     uint8_t                 mask[4];
6671131Smax.romanov@nginx.com 
6681131Smax.romanov@nginx.com     nxt_debug(task, "h1p conn ws pong");
6691131Smax.romanov@nginx.com 
6701131Smax.romanov@nginx.com     r = obj;
6711131Smax.romanov@nginx.com     b = r->ws_frame;
6721131Smax.romanov@nginx.com 
6731131Smax.romanov@nginx.com     wsh = (nxt_websocket_header_t *) b->mem.pos;
6741131Smax.romanov@nginx.com     payload_len = wsh->payload_len;
6751131Smax.romanov@nginx.com 
6761131Smax.romanov@nginx.com     b->mem.pos += 2;
6771131Smax.romanov@nginx.com 
6781131Smax.romanov@nginx.com     nxt_memcpy(mask, b->mem.pos, 4);
6791131Smax.romanov@nginx.com 
6801131Smax.romanov@nginx.com     b->mem.pos += 4;
6811131Smax.romanov@nginx.com 
6821131Smax.romanov@nginx.com     out = nxt_http_buf_mem(task, r, 2 + payload_len);
6831131Smax.romanov@nginx.com     if (nxt_slow_path(out == NULL)) {
6841131Smax.romanov@nginx.com         nxt_http_request_error_handler(task, r, r->proto.any);
6851131Smax.romanov@nginx.com         return;
6861131Smax.romanov@nginx.com     }
6871131Smax.romanov@nginx.com 
6881131Smax.romanov@nginx.com     out->mem.start[0] = 0;
6891131Smax.romanov@nginx.com     out->mem.start[1] = 0;
6901131Smax.romanov@nginx.com 
6911131Smax.romanov@nginx.com     wsh = (nxt_websocket_header_t *) out->mem.start;
6921131Smax.romanov@nginx.com     out->mem.free = nxt_websocket_frame_init(wsh, payload_len);
6931131Smax.romanov@nginx.com 
6941131Smax.romanov@nginx.com     wsh->fin = 1;
6951131Smax.romanov@nginx.com     wsh->opcode = NXT_WEBSOCKET_OP_PONG;
6961131Smax.romanov@nginx.com 
6971131Smax.romanov@nginx.com     for (i = 0; i < payload_len; i++) {
6981131Smax.romanov@nginx.com         while (nxt_buf_mem_used_size(&b->mem) == 0) {
6991131Smax.romanov@nginx.com             next = b->next;
7001131Smax.romanov@nginx.com 
7011131Smax.romanov@nginx.com             nxt_work_queue_add(&task->thread->engine->fast_work_queue,
7021131Smax.romanov@nginx.com                                b->completion_handler, task, b, b->parent);
7031131Smax.romanov@nginx.com 
7041131Smax.romanov@nginx.com             b = next;
7051131Smax.romanov@nginx.com         }
7061131Smax.romanov@nginx.com 
7071131Smax.romanov@nginx.com         *out->mem.free++ = *b->mem.pos++ ^ mask[i % 4];
7081131Smax.romanov@nginx.com     }
7091131Smax.romanov@nginx.com 
7101131Smax.romanov@nginx.com     r->ws_frame = b;
7111131Smax.romanov@nginx.com 
7121131Smax.romanov@nginx.com     nxt_http_request_send(task, r, out);
7131131Smax.romanov@nginx.com 
7141131Smax.romanov@nginx.com     nxt_http_request_ws_frame_start(task, r, r->ws_frame);
7151131Smax.romanov@nginx.com }
716