Coverage Report

Created: 2026-03-19 06:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libwebsockets/lib/secure-streams/protocols/ss-ws.c
Line
Count
Source
1
/*
2
 * libwebsockets - small server side websockets and web server implementation
3
 *
4
 * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com>
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to
8
 * deal in the Software without restriction, including without limitation the
9
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
 * sell copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
 * IN THE SOFTWARE.
23
 */
24
25
#include <private-lib-core.h>
26
27
int
28
secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user,
29
       void *in, size_t len);
30
31
static int
32
secstream_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user,
33
       void *in, size_t len)
34
0
{
35
0
#if defined(LWS_WITH_SERVER)
36
0
  struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi];
37
0
#endif
38
0
  lws_ss_handle_t *h = (lws_ss_handle_t *)lws_get_opaque_user_data(wsi);
39
0
  uint8_t buf[LWS_PRE + 1400];
40
0
  enum lws_write_protocol f1;
41
0
  lws_ss_state_return_t r;
42
0
  int f = 0, n;
43
0
  size_t buflen;
44
45
0
  switch (reason) {
46
47
  /* because we are protocols[0] ... */
48
0
  case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
49
0
    lwsl_info("%s: CLIENT_CONNECTION_ERROR: %s\n", __func__,
50
0
       in ? (char *)in : "(null)");
51
0
    if (!h)
52
0
      break;
53
54
0
#if defined(LWS_WITH_CONMON)
55
0
    lws_conmon_ss_json(h);
56
0
#endif
57
58
0
    r = lws_ss_event_helper(h, LWSSSCS_UNREACHABLE);
59
0
    if (r == LWSSSSRET_DESTROY_ME)
60
0
      return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
61
62
0
    h->wsi = NULL;
63
0
    r = lws_ss_backoff(h);
64
0
    if (r != LWSSSSRET_OK)
65
0
      return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
66
0
    break;
67
68
0
  case LWS_CALLBACK_CLOSED: /* server */
69
0
  case LWS_CALLBACK_CLIENT_CLOSED:
70
0
    if (!h)
71
0
      break;
72
0
    lws_sul_cancel(&h->sul_timeout);
73
74
0
#if defined(LWS_WITH_CONMON)
75
0
    lws_conmon_ss_json(h);
76
0
#endif
77
78
0
    r = lws_ss_event_helper(h, LWSSSCS_DISCONNECTED);
79
0
    if (r == LWSSSSRET_DESTROY_ME)
80
0
      return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
81
82
0
    if (h->wsi)
83
0
      lws_set_opaque_user_data(h->wsi, NULL);
84
0
    h->wsi = NULL;
85
86
0
#if defined(LWS_WITH_SERVER)
87
0
    lws_pt_lock(pt, __func__);
88
0
    lws_dll2_remove(&h->cli_list);
89
0
    lws_pt_unlock(pt);
90
0
#endif
91
92
0
    if (reason == LWS_CALLBACK_CLIENT_CLOSED) {
93
0
      if (h->policy &&
94
0
          !(h->policy->flags & LWSSSPOLF_OPPORTUNISTIC) &&
95
0
#if defined(LWS_WITH_SERVER)
96
0
          !(h->info.flags & LWSSSINFLAGS_ACCEPTED) && /* not server */
97
0
#endif
98
0
          !wsi->a.context->being_destroyed) {
99
0
        r = lws_ss_backoff(h);
100
0
        if (r != LWSSSSRET_OK)
101
0
          return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
102
0
        break;
103
0
      }
104
105
0
#if defined(LWS_WITH_SERVER)
106
0
      if (h->info.flags & LWSSSINFLAGS_ACCEPTED) {
107
        /*
108
         * was an accepted client connection to
109
         * our server, so the stream is over now
110
         */
111
0
        lws_ss_destroy(&h);
112
0
        return 0;
113
0
      }
114
0
#endif
115
116
0
    }
117
0
    break;
118
119
0
  case LWS_CALLBACK_ESTABLISHED:
120
0
  case LWS_CALLBACK_CLIENT_ESTABLISHED:
121
0
    if (!h) {
122
0
      return LWSSSSRET_DISCONNECT_ME;
123
0
    }
124
0
    h->retry    = 0;
125
0
    h->seqstate   = SSSEQ_CONNECTED;
126
0
    wsi->ws->last_valid = 0;
127
0
    wsi->ws->last_fin = 0;
128
0
    lws_sul_cancel(&h->sul);
129
#if defined(LWS_WITH_SYS_METRICS)
130
    /*
131
     * If any hanging caliper measurement, dump it, and free any tags
132
     */
133
    lws_metrics_caliper_report_hist(h->cal_txn, (struct lws *)NULL);
134
#endif
135
0
    r = lws_ss_event_helper(h, LWSSSCS_CONNECTED);
136
0
    if (r != LWSSSSRET_OK)
137
0
      return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
138
0
    break;
139
140
0
  case LWS_CALLBACK_RECEIVE:
141
0
  case LWS_CALLBACK_CLIENT_RECEIVE:
142
    // lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE: read %d\n", (int)len);
143
0
    if (!h || !h->info.rx)
144
0
      return 0;
145
0
    if (lws_is_first_fragment(wsi))
146
0
      f |= LWSSS_FLAG_SOM;
147
0
    if (lws_is_final_fragment(wsi))
148
0
      f |= LWSSS_FLAG_EOM;
149
    // lws_frame_is_binary(wsi);
150
151
0
    h->subseq = 1;
152
153
0
    r = h->info.rx(ss_to_userobj(h), (const uint8_t *)in, len, f);
154
0
    if (r != LWSSSSRET_OK)
155
0
      return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
156
157
0
    return 0; /* don't passthru */
158
159
0
  case LWS_CALLBACK_SERVER_WRITEABLE:
160
0
  case LWS_CALLBACK_CLIENT_WRITEABLE:
161
    // lwsl_notice("%s: %s: WRITEABLE\n", __func__, lws_ss_tag(h));
162
0
    if (!h || !h->info.tx)
163
0
      return 0;
164
165
0
    if (h->seqstate != SSSEQ_CONNECTED) {
166
0
      lwsl_warn("%s: seqstate %d\n", __func__, h->seqstate);
167
0
      break;
168
0
    }
169
170
0
    buflen = sizeof(buf) - LWS_PRE;
171
172
    /*
173
     * Let's prepare *flags with information about the channel ws message state
174
     * which the callback can simply overwrite.  But if you are tracing problems
175
     * with your own flags state blowing assert()s below, you can use this for
176
     * debugging the illegal state while still in your ss tx callback; otherwise
177
     * it can be harder to understand what you're doing wrong after we returned
178
     * from the callback and blow chunks here.
179
     *
180
     * b1 of flags is set if we know the last fin state.  If b1 is set, b0 is
181
     * the last FIN state.  You can use this info to see if your flags will
182
     * cause us to assert when you return.
183
     */
184
185
0
    f = (!!wsi->ws->last_valid << 1) | (!!wsi->ws->last_fin);
186
0
    r = h->info.tx(ss_to_userobj(h),  h->txord++, buf + LWS_PRE,
187
0
          &buflen, &f);
188
0
    if (r == LWSSSSRET_TX_DONT_SEND)
189
0
      return 0;
190
0
    if (r != LWSSSSRET_OK)
191
0
      return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
192
193
0
    if ((f & LWSSS_FLAG_SOM) && wsi->ws->last_valid && !wsi->ws->last_fin) {
194
0
      lwsl_ss_err(h, "%s TX: Illegal LWSSS_FLAG_SOM after previous frame without LWSSS_FLAG_EOM", h->policy ? h->policy->streamtype : "unknown");
195
0
      assert(0);
196
0
    }
197
0
    if (!(f & LWSSS_FLAG_SOM) && wsi->ws->last_valid && wsi->ws->last_fin) {
198
0
      lwsl_ss_err(h, "%s TX: Missing LWSSS_FLAG_SOM after previous frame with LWSSS_FLAG_EOM", h->policy ? h->policy->streamtype : "unknown");
199
0
      assert(0);
200
0
    }
201
0
    if (!(f & LWSSS_FLAG_SOM) && !wsi->ws->last_valid) {
202
0
      lwsl_ss_err(h, "%s TX: Missing LWSSS_FLAG_SOM on first frame", h->policy ? h->policy->streamtype : "unknown");
203
0
      assert(0);
204
0
    }
205
206
0
    f1 = lws_write_ws_flags(h->policy->u.http.u.ws.binary ?
207
0
             LWS_WRITE_BINARY : LWS_WRITE_TEXT,
208
0
          !!(f & LWSSS_FLAG_SOM),
209
0
          !!(f & LWSSS_FLAG_EOM));
210
211
0
    n = lws_write(wsi, buf + LWS_PRE, buflen, f1);
212
0
    if (n < (int)buflen) {
213
0
      lwsl_info("%s: write failed %d %d\n", __func__,
214
0
          n, (int)buflen);
215
216
0
      return -1;
217
0
    }
218
219
0
    return 0;
220
221
0
#if defined(LWS_WITH_SERVER)
222
0
  case LWS_CALLBACK_HTTP_WRITEABLE:
223
0
  case LWS_CALLBACK_HTTP:
224
0
  case LWS_CALLBACK_CLOSED_HTTP: /* server */
225
0
    return secstream_h1(wsi, reason, user, in, len);
226
0
#endif
227
228
0
  default:
229
0
    break;
230
0
  }
231
232
0
  return lws_callback_http_dummy(wsi, reason, user, in, len);
233
0
}
234
235
const struct lws_protocols protocol_secstream_ws = {
236
  "lws-secstream-ws",
237
  secstream_ws,
238
  0, 0, 0, NULL, 0
239
};
240
/*
241
 * Munge connect info according to protocol-specific considerations... this
242
 * usually means interpreting aux in a protocol-specific way and using the
243
 * pieces at connection setup time, eg, http url pieces.
244
 *
245
 * len bytes of buf can be used for things with scope until after the actual
246
 * connect.
247
 *
248
 * For ws, protocol aux is <url path>;<ws subprotocol name>
249
 */
250
251
static int
252
secstream_connect_munge_ws(lws_ss_handle_t *h, char *buf, size_t len,
253
         struct lws_client_connect_info *i,
254
         union lws_ss_contemp *ct)
255
0
{
256
0
  const char *pbasis = h->policy->u.http.url;
257
0
  size_t used_in, used_out;
258
0
  lws_strexp_t exp;
259
260
  /* i.path on entry is used to override the policy urlpath if not "" */
261
262
0
  if (i->path[0])
263
0
    pbasis = i->path;
264
265
0
  if (!pbasis)
266
0
    return 0;
267
268
0
  if (h->policy->flags & LWSSSPOLF_HTTP_CACHE_COOKIES)
269
0
    i->ssl_connection |= LCCSCF_CACHE_COOKIES;
270
271
0
  if (h->policy->flags & LWSSSPOLF_PRIORITIZE_READS)
272
0
    i->ssl_connection |= LCCSCF_PRIORITIZE_READS;
273
274
  /* protocol aux is the path part ; ws subprotocol name */
275
276
0
  i->path = buf;
277
0
  buf[0] = '/';
278
279
0
  lws_strexp_init(&exp, (void *)h, lws_ss_exp_cb_metadata, buf + 1, len - 1);
280
281
0
  if (lws_strexp_expand(&exp, pbasis, strlen(pbasis),
282
0
            &used_in, &used_out) != LSTRX_DONE)
283
0
    return 1;
284
285
0
  __lws_lc_tag_append(&h->lc, buf);
286
287
0
  i->protocol = h->policy->u.http.u.ws.subprotocol;
288
289
0
  lwsl_ss_info(h, "url %s, ws subprotocol %s", buf, i->protocol);
290
291
0
  return 0;
292
0
}
293
294
const struct ss_pcols ss_pcol_ws = {
295
  "ws",  "http/1.1",  &protocol_secstream_ws, secstream_connect_munge_ws, 0, 0
296
};