@sproutboat/runtime 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2997,10 +2997,13 @@ void sb_async_submit(u32 id, int port, int tls, char* host, char* path, char* me
2997
2997
  const char* sb_async_reply(u32 id) {
2998
2998
  return sb_async_current && sb_async_current->id == id ? sb_async_current->out : 0;
2999
2999
  }
3000
+ // Set by the outbound WebSocket code below (a constructor there), which this block stands without.
3001
+ static void (*sb_wsc_drain_hook)(void) = 0;
3000
3002
  // Host: the fd became readable. Fires each finished request's callback.
3001
3003
  void sb_async_drain(void) {
3002
3004
  char buf[64];
3003
3005
  while (read(sb_async_pipe[0], buf, sizeof(buf)) > 0) {}
3006
+ if (sb_wsc_drain_hook) sb_wsc_drain_hook();
3004
3007
  pthread_mutex_lock(&sb_async_lock);
3005
3008
  sb_async_job* done = sb_async_done;
3006
3009
  sb_async_done = 0;
@@ -3016,6 +3019,642 @@ void sb_async_drain(void) {
3016
3019
  }
3017
3020
  }
3018
3021
  // ASYNC:END
3022
+
3023
+ // WSC:BEGIN
3024
+ // Outbound WebSockets (compatibility date 2026-11-01): new WebSocket(url) and fetch() with
3025
+ // Upgrade: websocket. One I/O thread per socket connects (through sb_tcp_connect, so the
3026
+ // egress policy and a sandboxed host's egress socket apply), does the TLS handshake for wss,
3027
+ // sends the upgrade request, then multiplexes the socket with a wake pipe: frames the JS side
3028
+ // queued (already masked) go out, frames that arrive are parsed into events. The JS side polls
3029
+ // the events through the same ops as a host socket (sb_ws_hook; ids here have the top bit set):
3030
+ // 'u' connect, 't' 'b' send, 'c' close, 'p' 'i' take an event, 'k' acknowledge bytes dispatched,
3031
+ // 'w' park a timer for the next event, 'r' release. Threads touch only their own malloc'd
3032
+ // state, never the JS heap; the loop is woken through the async pipe (sproutd) or the host's
3033
+ // waker (the standalone server), then sb_async_drain() fires the parked timers.
3034
+ // Event kinds: 0 text, 1 binary, 2 close (code, reason), 3 connection lost (no close frame, or
3035
+ // it never opened: data holds the reason), 4 opened (data holds the chosen subprotocol), 5 the
3036
+ // server answered the upgrade with another status (code; data holds its head and what followed).
3037
+ #include <poll.h>
3038
+ #include <fcntl.h>
3039
+ #include <unistd.h>
3040
+ #define SB_WSC_BIT 0x80000000u
3041
+ #define SB_WSC_MAX 64
3042
+ #define SB_WSC_MAXMSG (32u << 20)
3043
+ #define SB_WSC_WINDOW (4u << 20)
3044
+ typedef struct sb_wsc_ev { struct sb_wsc_ev* next; int kind; unsigned code; char* data; size_t len; } sb_wsc_ev;
3045
+ typedef struct sb_wsc {
3046
+ struct sb_wsc* next;
3047
+ unsigned id;
3048
+ pthread_mutex_t lock;
3049
+ int wake[2];
3050
+ sb_wsc_ev *evh, *evt;
3051
+ unsigned waiter;
3052
+ int wake_pending;
3053
+ size_t unacked;
3054
+ unsigned char* outq; size_t outq_len, outq_cap;
3055
+ int released, finished, close_queued;
3056
+ uint64_t rng;
3057
+ char *host, *path, *headers, *protocols;
3058
+ int port, tls;
3059
+ } sb_wsc;
3060
+ static pthread_mutex_t sb_wsc_lock = PTHREAD_MUTEX_INITIALIZER;
3061
+ static sb_wsc* sb_wscs = 0;
3062
+ static unsigned sb_wsc_next = 0;
3063
+ static void (*sb_wsc_waker)(void) = 0;
3064
+ void sb_async_set_waker(void (*wake)(void)) { sb_wsc_waker = wake; }
3065
+ static int sb_wsc_pipe_enabled(void) { return sb_async_pipe[0] >= 0; }
3066
+ static void sb_async_poke(void) {
3067
+ char one = 1;
3068
+ while (write(sb_async_pipe[1], &one, 1) < 0 && errno == EINTR) {}
3069
+ }
3070
+
3071
+ static uint64_t sb_wsc_rand(uint64_t* s) {
3072
+ uint64_t x = *s;
3073
+ x ^= x << 13; x ^= x >> 7; x ^= x << 17;
3074
+ *s = x;
3075
+ return x * 0x2545F4914F6CDD1DULL;
3076
+ }
3077
+ static void sb_wsc_seed(sb_wsc* c) {
3078
+ uint64_t seed = 0;
3079
+ int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
3080
+ if (fd >= 0) { if (read(fd, &seed, sizeof(seed)) != (ssize_t)sizeof(seed)) seed = 0; close(fd); }
3081
+ if (!seed) { struct timeval tv; gettimeofday(&tv, 0); seed = (uint64_t)tv.tv_usec * 1000003ULL ^ (uint64_t)tv.tv_sec ^ (uint64_t)(uintptr_t)c; }
3082
+ c->rng = seed ? seed : 88172645463325252ULL;
3083
+ }
3084
+
3085
+ // Wakes the loop: the async pipe where the host watches it, and the host's waker.
3086
+ static void sb_wsc_signal(void) {
3087
+ if (sb_wsc_pipe_enabled()) sb_async_poke();
3088
+ if (sb_wsc_waker) sb_wsc_waker();
3089
+ }
3090
+ static void sb_wsc_push(sb_wsc* c, int kind, unsigned code, const char* data, size_t len) {
3091
+ sb_wsc_ev* e = (sb_wsc_ev*)calloc(1, sizeof(sb_wsc_ev));
3092
+ if (!e) return;
3093
+ e->kind = kind; e->code = code; e->len = len;
3094
+ if (len) {
3095
+ e->data = (char*)malloc(len);
3096
+ if (!e->data) { free(e); return; }
3097
+ memcpy(e->data, data, len);
3098
+ }
3099
+ pthread_mutex_lock(&c->lock);
3100
+ if (c->evt) c->evt->next = e; else c->evh = e;
3101
+ c->evt = e;
3102
+ if (kind <= 1) c->unacked += len;
3103
+ if (c->waiter) c->wake_pending = 1;
3104
+ pthread_mutex_unlock(&c->lock);
3105
+ sb_wsc_signal();
3106
+ }
3107
+ static void sb_wsc_wake_thread(sb_wsc* c) {
3108
+ char one = 1;
3109
+ while (write(c->wake[1], &one, 1) < 0 && errno == EINTR) {}
3110
+ }
3111
+
3112
+ // One masked client frame, appended to the socket's queue for the I/O thread.
3113
+ static int sb_wsc_frame(sb_wsc* c, int opcode, const char* data, size_t len, unsigned code, int with_code) {
3114
+ size_t payload = len + (with_code ? 2 : 0);
3115
+ unsigned char head[14];
3116
+ size_t h = 0;
3117
+ head[h++] = (unsigned char)(0x80 | opcode);
3118
+ if (payload < 126) head[h++] = (unsigned char)(0x80 | payload);
3119
+ else if (payload < 65536) { head[h++] = 0x80 | 126; head[h++] = (unsigned char)(payload >> 8); head[h++] = (unsigned char)payload; }
3120
+ else { head[h++] = 0x80 | 127; for (int i = 7; i >= 0; i--) head[h++] = (unsigned char)((uint64_t)payload >> (8 * i)); }
3121
+ pthread_mutex_lock(&c->lock);
3122
+ uint32_t m = (uint32_t)sb_wsc_rand(&c->rng);
3123
+ unsigned char mask[4] = { (unsigned char)m, (unsigned char)(m >> 8), (unsigned char)(m >> 16), (unsigned char)(m >> 24) };
3124
+ memcpy(head + h, mask, 4); h += 4;
3125
+ size_t need = c->outq_len + h + payload;
3126
+ if (need > c->outq_cap) {
3127
+ size_t cap = c->outq_cap ? c->outq_cap : 4096;
3128
+ while (cap < need) cap *= 2;
3129
+ unsigned char* grown = (unsigned char*)realloc(c->outq, cap);
3130
+ if (!grown) { pthread_mutex_unlock(&c->lock); return -1; }
3131
+ c->outq = grown; c->outq_cap = cap;
3132
+ }
3133
+ memcpy(c->outq + c->outq_len, head, h);
3134
+ unsigned char* at = c->outq + c->outq_len + h;
3135
+ size_t i = 0;
3136
+ if (with_code) { at[0] = (unsigned char)(code >> 8) ^ mask[0]; at[1] = (unsigned char)code ^ mask[1]; i = 2; }
3137
+ for (size_t k = 0; k < len; k++, i++) at[i] = (unsigned char)data[k] ^ mask[i & 3];
3138
+ c->outq_len += h + payload;
3139
+ pthread_mutex_unlock(&c->lock);
3140
+ sb_wsc_wake_thread(c);
3141
+ return 0;
3142
+ }
3143
+
3144
+ // --- the socket the I/O thread owns: plain, or TLS through BearSSL's engine ---
3145
+ typedef struct {
3146
+ int fd, tls;
3147
+ br_ssl_client_context* sc;
3148
+ br_x509_minimal_context* xc;
3149
+ unsigned char* iobuf;
3150
+ } sb_wsio;
3151
+
3152
+ static int sb_wsio_wait(int fd, int wakefd, short events, int ms) {
3153
+ struct pollfd p[2];
3154
+ p[0].fd = fd; p[0].events = fd >= 0 ? events : 0; p[0].revents = 0;
3155
+ p[1].fd = wakefd; p[1].events = POLLIN; p[1].revents = 0;
3156
+ int rc;
3157
+ do { rc = poll(p, 2, ms); } while (rc < 0 && errno == EINTR);
3158
+ if (rc < 0) return -1;
3159
+ int out = 0;
3160
+ if (p[0].revents & (events | POLLERR | POLLHUP)) out |= 1;
3161
+ if (p[1].revents & POLLIN) out |= 2;
3162
+ return out;
3163
+ }
3164
+ static void sb_wsc_drain_wake(sb_wsc* c) {
3165
+ char buf[64];
3166
+ while (read(c->wake[0], buf, sizeof(buf)) > 0) {}
3167
+ }
3168
+ // Writes everything in buf (the socket is non-blocking), waiting for it to drain.
3169
+ static int sb_wsio_raw_write(sb_wsio* io, const unsigned char* buf, size_t len) {
3170
+ size_t at = 0;
3171
+ while (at < len) {
3172
+ ssize_t n = write(io->fd, buf + at, len - at);
3173
+ if (n > 0) { at += (size_t)n; continue; }
3174
+ if (n < 0 && errno == EINTR) continue;
3175
+ if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
3176
+ struct pollfd p; p.fd = io->fd; p.events = POLLOUT; p.revents = 0;
3177
+ int rc = poll(&p, 1, 30000);
3178
+ if (rc <= 0) return -1;
3179
+ continue;
3180
+ }
3181
+ return -1;
3182
+ }
3183
+ return 0;
3184
+ }
3185
+
3186
+ // Moves bytes both ways until there is application data to hand over (returned in buf, up to
3187
+ // cap), the wake pipe fires or the timeout passes (0, or -2 for the timeout when ms >= 0), or the
3188
+ // connection ends (-1). out/outlen is application data to send first. With may_read false (the
3189
+ // reader is far behind) it only sends, and waits for the wake pipe.
3190
+ static long sb_wsio_pump(sb_wsc* c, sb_wsio* io, unsigned char* buf, size_t cap, unsigned char* out, size_t* outlen, int may_read, int ms) {
3191
+ if (!io->tls) {
3192
+ if (*outlen) {
3193
+ if (sb_wsio_raw_write(io, out, *outlen) != 0) return -1;
3194
+ *outlen = 0;
3195
+ }
3196
+ for (;;) {
3197
+ if (may_read) {
3198
+ ssize_t n = read(io->fd, buf, cap);
3199
+ if (n > 0) return (long)n;
3200
+ if (n == 0) return -1;
3201
+ if (errno == EINTR) continue;
3202
+ if (errno != EAGAIN && errno != EWOULDBLOCK) return -1;
3203
+ }
3204
+ int w = may_read ? sb_wsio_wait(io->fd, c->wake[0], POLLIN, ms) : sb_wsio_wait(-1, c->wake[0], POLLIN, ms);
3205
+ if (w < 0) return -1;
3206
+ if (w & 2) return 0;
3207
+ if (w == 0 && ms >= 0) return -2;
3208
+ }
3209
+ }
3210
+ br_ssl_engine_context* eng = &io->sc->eng;
3211
+ for (;;) {
3212
+ unsigned st = br_ssl_engine_current_state(eng);
3213
+ if (st & BR_SSL_CLOSED) return -1;
3214
+ if (st & BR_SSL_SENDREC) {
3215
+ size_t n; unsigned char* b = br_ssl_engine_sendrec_buf(eng, &n);
3216
+ if (sb_wsio_raw_write(io, b, n) != 0) return -1;
3217
+ br_ssl_engine_sendrec_ack(eng, n);
3218
+ continue;
3219
+ }
3220
+ if ((st & BR_SSL_RECVAPP) && may_read) {
3221
+ size_t n; unsigned char* b = br_ssl_engine_recvapp_buf(eng, &n);
3222
+ if (n > cap) n = cap;
3223
+ memcpy(buf, b, n);
3224
+ br_ssl_engine_recvapp_ack(eng, n);
3225
+ return (long)n;
3226
+ }
3227
+ if ((st & BR_SSL_SENDAPP) && *outlen) {
3228
+ size_t n; unsigned char* b = br_ssl_engine_sendapp_buf(eng, &n);
3229
+ if (n > *outlen) n = *outlen;
3230
+ memcpy(b, out, n);
3231
+ br_ssl_engine_sendapp_ack(eng, n);
3232
+ memmove(out, out + n, *outlen - n);
3233
+ *outlen -= n;
3234
+ if (*outlen == 0) br_ssl_engine_flush(eng, 0);
3235
+ continue;
3236
+ }
3237
+ // Handshaking, or waiting for the peer: read records while the reader may; else only wake.
3238
+ int reading = (st & BR_SSL_RECVREC) && (may_read || !(st & BR_SSL_SENDAPP));
3239
+ if (reading) {
3240
+ size_t n; unsigned char* b = br_ssl_engine_recvrec_buf(eng, &n);
3241
+ ssize_t got = read(io->fd, b, n);
3242
+ if (got > 0) { br_ssl_engine_recvrec_ack(eng, (size_t)got); continue; }
3243
+ if (got == 0) return -1;
3244
+ if (errno == EINTR) continue;
3245
+ if (errno != EAGAIN && errno != EWOULDBLOCK) return -1;
3246
+ }
3247
+ int w = sb_wsio_wait(reading ? io->fd : -1, c->wake[0], POLLIN, ms);
3248
+ if (w < 0) return -1;
3249
+ if (w & 2) return 0;
3250
+ if (w == 0 && ms >= 0) return -2;
3251
+ }
3252
+ }
3253
+
3254
+ static int sb_wsio_open(sb_wsio* io, const char* host, int port, int tls, char* why, size_t whylen) {
3255
+ memset(io, 0, sizeof(*io));
3256
+ io->fd = sb_tcp_connect(host, port, why, whylen);
3257
+ if (io->fd < 0) { if (!why[0]) snprintf(why, whylen, "could not connect to %s", host); return -1; }
3258
+ struct timeval none; none.tv_sec = 0; none.tv_usec = 0;
3259
+ setsockopt(io->fd, SOL_SOCKET, SO_RCVTIMEO, &none, sizeof(none));
3260
+ setsockopt(io->fd, SOL_SOCKET, SO_SNDTIMEO, &none, sizeof(none));
3261
+ fcntl(io->fd, F_SETFL, fcntl(io->fd, F_GETFL) | O_NONBLOCK);
3262
+ io->tls = tls;
3263
+ if (!tls) return 0;
3264
+ io->sc = (br_ssl_client_context*)malloc(sizeof(br_ssl_client_context));
3265
+ io->xc = (br_x509_minimal_context*)malloc(sizeof(br_x509_minimal_context));
3266
+ io->iobuf = (unsigned char*)malloc(BR_SSL_BUFSIZE_BIDI);
3267
+ if (!io->sc || !io->xc || !io->iobuf) { snprintf(why, whylen, "out of memory setting up TLS for %s", host); return -1; }
3268
+ sb_init_anchors();
3269
+ br_ssl_client_init_full(io->sc, io->xc, sb_all_anchors, sb_all_anchor_count);
3270
+ br_ssl_engine_set_buffer(&io->sc->eng, io->iobuf, BR_SSL_BUFSIZE_BIDI, 1);
3271
+ char bare[256];
3272
+ size_t hl = strlen(host);
3273
+ if (hl > 2 && hl < sizeof(bare) && host[0] == '[' && host[hl - 1] == ']') { memcpy(bare, host + 1, hl - 2); bare[hl - 2] = 0; }
3274
+ else snprintf(bare, sizeof(bare), "%s", host);
3275
+ br_ssl_client_reset(io->sc, bare, 0);
3276
+ return 0;
3277
+ }
3278
+ static void sb_wsio_close(sb_wsio* io) {
3279
+ if (io->fd >= 0) close(io->fd);
3280
+ free(io->sc); free(io->xc); free(io->iobuf);
3281
+ memset(io, 0, sizeof(*io));
3282
+ io->fd = -1;
3283
+ }
3284
+
3285
+ static const char sb_wsc_b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3286
+ static void sb_wsc_base64(const unsigned char* in, size_t n, char* out) {
3287
+ size_t o = 0;
3288
+ for (size_t i = 0; i < n; i += 3) {
3289
+ unsigned v = (unsigned)in[i] << 16 | (i + 1 < n ? (unsigned)in[i + 1] << 8 : 0) | (i + 2 < n ? in[i + 2] : 0);
3290
+ out[o++] = sb_wsc_b64[(v >> 18) & 63];
3291
+ out[o++] = sb_wsc_b64[(v >> 12) & 63];
3292
+ out[o++] = i + 1 < n ? sb_wsc_b64[(v >> 6) & 63] : '=';
3293
+ out[o++] = i + 2 < n ? sb_wsc_b64[v & 63] : '=';
3294
+ }
3295
+ out[o] = 0;
3296
+ }
3297
+
3298
+ // Messages being assembled and the parser's place in a frame.
3299
+ typedef struct {
3300
+ unsigned char* msg; size_t msg_len, msg_cap; int msg_kind, in_msg;
3301
+ unsigned char head[14]; size_t head_got; int in_payload;
3302
+ int opcode, fin, masked; uint64_t need, got; unsigned char mask[4];
3303
+ unsigned char ctl[125]; size_t ctl_len;
3304
+ } sb_wsc_parser;
3305
+
3306
+ // The connection ends with a close frame of ours and the reason for the app.
3307
+ static int sb_wsc_fail(sb_wsc* c, unsigned code) {
3308
+ sb_wsc_frame(c, 8, 0, 0, code, 1);
3309
+ sb_wsc_push(c, 2, code, "", 0);
3310
+ return 1;
3311
+ }
3312
+
3313
+ // A control frame is complete. Returns 1 when the connection is over.
3314
+ static int sb_wsc_control(sb_wsc* c, sb_wsc_parser* p) {
3315
+ if (p->opcode == 8) {
3316
+ unsigned code = p->ctl_len >= 2 ? ((unsigned)p->ctl[0] << 8 | p->ctl[1]) : 1005;
3317
+ pthread_mutex_lock(&c->lock);
3318
+ int ours = c->close_queued;
3319
+ c->close_queued = 1;
3320
+ pthread_mutex_unlock(&c->lock);
3321
+ // Echo the close (unless ours went first), then report it.
3322
+ if (!ours) sb_wsc_frame(c, 8, (const char*)p->ctl, p->ctl_len >= 2 ? 2 : 0, 0, 0);
3323
+ sb_wsc_push(c, 2, code, p->ctl_len > 2 ? (const char*)p->ctl + 2 : "", p->ctl_len > 2 ? p->ctl_len - 2 : 0);
3324
+ return 1;
3325
+ }
3326
+ if (p->opcode == 9) sb_wsc_frame(c, 10, (const char*)p->ctl, p->ctl_len, 0, 0);
3327
+ return 0;
3328
+ }
3329
+
3330
+ // Feeds received bytes to the parser. Returns 1 when the socket is over (a close frame, or a
3331
+ // protocol error already reported), else 0.
3332
+ static int sb_wsc_feed(sb_wsc* c, sb_wsc_parser* p, const unsigned char* in, size_t n) {
3333
+ size_t at = 0;
3334
+ for (;;) {
3335
+ if (!p->in_payload) {
3336
+ if (at >= n) break;
3337
+ p->head[p->head_got++] = in[at++];
3338
+ if (p->head_got < 2) continue;
3339
+ size_t len7 = p->head[1] & 0x7f;
3340
+ size_t hdr = 2 + (len7 == 126 ? 2 : len7 == 127 ? 8 : 0) + ((p->head[1] & 0x80) ? 4 : 0);
3341
+ if (p->head_got < hdr) continue;
3342
+ p->fin = p->head[0] >> 7; p->opcode = p->head[0] & 0xf; p->masked = (p->head[1] & 0x80) != 0;
3343
+ uint64_t l = len7;
3344
+ if (len7 == 126) l = (uint64_t)p->head[2] << 8 | p->head[3];
3345
+ else if (len7 == 127) { l = 0; for (int i = 0; i < 8; i++) l = l << 8 | p->head[2 + i]; }
3346
+ if (p->masked) memcpy(p->mask, p->head + hdr - 4, 4);
3347
+ p->need = l; p->got = 0; p->ctl_len = 0; p->in_payload = 1;
3348
+ if ((p->head[0] & 0x70) || (p->opcode > 2 && p->opcode < 8) || p->opcode > 10) return sb_wsc_fail(c, 1002);
3349
+ if (p->opcode >= 8) { if (l > 125 || !p->fin) return sb_wsc_fail(c, 1002); }
3350
+ else {
3351
+ if (p->opcode == 0 ? !p->in_msg : p->in_msg) return sb_wsc_fail(c, 1002);
3352
+ if (p->msg_len + l > SB_WSC_MAXMSG) return sb_wsc_fail(c, 1009);
3353
+ if (p->opcode != 0) { p->msg_kind = p->opcode == 1 ? 0 : 1; p->in_msg = 1; }
3354
+ }
3355
+ }
3356
+ size_t take = n - at;
3357
+ if ((uint64_t)take > p->need - p->got) take = (size_t)(p->need - p->got);
3358
+ if (take) {
3359
+ const unsigned char* src = in + at;
3360
+ if (p->opcode >= 8) {
3361
+ memcpy(p->ctl + p->ctl_len, src, take);
3362
+ if (p->masked) for (size_t i = 0; i < take; i++) p->ctl[p->ctl_len + i] ^= p->mask[(p->got + i) & 3];
3363
+ p->ctl_len += take;
3364
+ } else {
3365
+ if (p->msg_len + take > p->msg_cap) {
3366
+ size_t cap = p->msg_cap ? p->msg_cap : 4096;
3367
+ while (cap < p->msg_len + take) cap *= 2;
3368
+ unsigned char* grown = (unsigned char*)realloc(p->msg, cap);
3369
+ if (!grown) return sb_wsc_fail(c, 1011);
3370
+ p->msg = grown; p->msg_cap = cap;
3371
+ }
3372
+ memcpy(p->msg + p->msg_len, src, take);
3373
+ if (p->masked) for (size_t i = 0; i < take; i++) p->msg[p->msg_len + i] ^= p->mask[(p->got + i) & 3];
3374
+ p->msg_len += take;
3375
+ }
3376
+ p->got += take; at += take;
3377
+ }
3378
+ if (p->got < p->need) break;
3379
+ if (p->opcode >= 8) { if (sb_wsc_control(c, p)) return 1; }
3380
+ else if (p->fin) { sb_wsc_push(c, p->msg_kind, 0, (const char*)p->msg, p->msg_len); p->msg_len = 0; p->in_msg = 0; }
3381
+ p->in_payload = 0; p->head_got = 0;
3382
+ }
3383
+ return 0;
3384
+ }
3385
+
3386
+ static void sb_wsc_free(sb_wsc* c) {
3387
+ while (c->evh) { sb_wsc_ev* e = c->evh; c->evh = e->next; free(e->data); free(e); }
3388
+ close(c->wake[0]); close(c->wake[1]);
3389
+ free(c->outq); free(c->host); free(c->path); free(c->headers); free(c->protocols);
3390
+ pthread_mutex_destroy(&c->lock);
3391
+ free(c);
3392
+ }
3393
+
3394
+ static void* sb_wsc_thread(void* arg) {
3395
+ sb_wsc* c = (sb_wsc*)arg;
3396
+ sb_wsio io;
3397
+ memset(&io, 0, sizeof(io));
3398
+ io.fd = -1;
3399
+ sb_wsc_parser* p = (sb_wsc_parser*)calloc(1, sizeof(sb_wsc_parser));
3400
+ char why[512] = "";
3401
+ unsigned char buf[16384];
3402
+ unsigned char* wq = 0; size_t wq_len = 0, wq_cap = 0;
3403
+ int opened = 0;
3404
+ if (!p) goto finish;
3405
+ if (sb_wsio_open(&io, c->host, c->port, c->tls, why, sizeof(why)) != 0) goto failed;
3406
+ {
3407
+ // The upgrade request.
3408
+ unsigned char keybytes[16];
3409
+ for (int i = 0; i < 16; i += 4) { uint32_t r = (uint32_t)sb_wsc_rand(&c->rng); memcpy(keybytes + i, &r, 4); }
3410
+ char key[32];
3411
+ sb_wsc_base64(keybytes, 16, key);
3412
+ sb_buf req = { 0, 0, 0 };
3413
+ sb_puts(&req, "GET "); sb_puts(&req, c->path); sb_puts(&req, " HTTP/1.1\r\nHost: "); sb_puts(&req, c->host);
3414
+ if (c->port != (c->tls ? 443 : 80)) { char pt[16]; snprintf(pt, sizeof(pt), ":%d", c->port); sb_puts(&req, pt); }
3415
+ sb_puts(&req, "\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: ");
3416
+ sb_puts(&req, key);
3417
+ sb_puts(&req, "\r\n");
3418
+ if (c->protocols && *c->protocols) { sb_puts(&req, "Sec-WebSocket-Protocol: "); sb_puts(&req, c->protocols); sb_puts(&req, "\r\n"); }
3419
+ if (c->headers && *c->headers) sb_puts(&req, c->headers);
3420
+ sb_puts(&req, "\r\n");
3421
+ size_t reqlen = req.len;
3422
+ if (reqlen > wq_cap) { wq = (unsigned char*)malloc(reqlen); wq_cap = reqlen; }
3423
+ if (!wq) { free(req.p); snprintf(why, sizeof(why), "out of memory"); goto failed; }
3424
+ memcpy(wq, req.p, reqlen); wq_len = reqlen; free(req.p);
3425
+ // The answer's head: up to the blank line; what follows it is already frame data.
3426
+ char head[8192]; size_t head_len = 0; size_t body_at = 0; int have_head = 0;
3427
+ unsigned char acceptwant[32];
3428
+ {
3429
+ br_sha1_context sh; br_sha1_init(&sh);
3430
+ br_sha1_update(&sh, key, strlen(key));
3431
+ br_sha1_update(&sh, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 36);
3432
+ unsigned char dig[20]; br_sha1_out(&sh, dig);
3433
+ sb_wsc_base64(dig, 20, (char*)acceptwant);
3434
+ }
3435
+ while (!have_head) {
3436
+ long n = sb_wsio_pump(c, &io, buf, sizeof(buf), wq, &wq_len, 1, 30000);
3437
+ if (n == -2) { snprintf(why, sizeof(why), "timed out in the WebSocket handshake with %s", c->host); goto failed; }
3438
+ if (n < 0) { snprintf(why, sizeof(why), "connection closed during the WebSocket handshake with %s", c->host); goto failed; }
3439
+ if (n == 0) { pthread_mutex_lock(&c->lock); int rel = c->released; pthread_mutex_unlock(&c->lock); sb_wsc_drain_wake(c); if (rel) goto finish; continue; }
3440
+ if (head_len + (size_t)n > sizeof(head) - 1) { snprintf(why, sizeof(why), "WebSocket handshake response too large"); goto failed; }
3441
+ memcpy(head + head_len, buf, (size_t)n); head_len += (size_t)n;
3442
+ for (size_t i = 3; i < head_len; i++) if (head[i - 3] == '\r' && head[i - 2] == '\n' && head[i - 1] == '\r' && head[i] == '\n') { have_head = 1; body_at = i + 1; break; }
3443
+ }
3444
+ head[head_len] = 0;
3445
+ int status = 0;
3446
+ if (sscanf(head, "HTTP/1.%*d %d", &status) != 1) {
3447
+ snprintf(why, sizeof(why), "WebSocket handshake with %s failed: not an HTTP response", c->host);
3448
+ goto failed;
3449
+ }
3450
+ // Another answer (a 401, a redirect, a 404): the app gets it as the response.
3451
+ if (status != 101) { sb_wsc_push(c, 5, (unsigned)status, head, head_len); goto finish; }
3452
+ head[body_at - 1] = 0;
3453
+ char accept_got[64] = ""; char proto[256] = ""; int upgrade_ok = 0;
3454
+ for (char* line = strstr(head, "\r\n"); line; line = strstr(line + 2, "\r\n")) {
3455
+ char* l = line + 2;
3456
+ if (!strncasecmp(l, "sec-websocket-accept:", 21)) { char* v = l + 21; while (*v == ' ') v++; size_t k = strcspn(v, "\r\n"); if (k < sizeof(accept_got)) { memcpy(accept_got, v, k); accept_got[k] = 0; } }
3457
+ else if (!strncasecmp(l, "sec-websocket-protocol:", 23)) { char* v = l + 23; while (*v == ' ') v++; size_t k = strcspn(v, "\r\n"); if (k < sizeof(proto)) { memcpy(proto, v, k); proto[k] = 0; } }
3458
+ else if (!strncasecmp(l, "upgrade:", 8)) { char* v = l + 8; while (*v == ' ') v++; if (!strncasecmp(v, "websocket", 9)) upgrade_ok = 1; }
3459
+ }
3460
+ if (!upgrade_ok || strcmp(accept_got, (const char*)acceptwant)) { snprintf(why, sizeof(why), "WebSocket handshake with %s failed: bad Sec-WebSocket-Accept", c->host); goto failed; }
3461
+ opened = 1;
3462
+ sb_wsc_push(c, 4, 0, proto, strlen(proto));
3463
+ if (head_len > body_at && sb_wsc_feed(c, p, (const unsigned char*)head + body_at, head_len - body_at)) goto closing;
3464
+ }
3465
+ for (;;) {
3466
+ pthread_mutex_lock(&c->lock);
3467
+ int rel = c->released;
3468
+ size_t window = c->unacked;
3469
+ if (c->outq_len) {
3470
+ if (wq_len + c->outq_len > wq_cap) {
3471
+ size_t cap = wq_cap ? wq_cap : 4096;
3472
+ while (cap < wq_len + c->outq_len) cap *= 2;
3473
+ unsigned char* grown = (unsigned char*)realloc(wq, cap);
3474
+ if (grown) { wq = grown; wq_cap = cap; }
3475
+ }
3476
+ if (wq_len + c->outq_len <= wq_cap) { memcpy(wq + wq_len, c->outq, c->outq_len); wq_len += c->outq_len; c->outq_len = 0; }
3477
+ }
3478
+ pthread_mutex_unlock(&c->lock);
3479
+ if (rel) goto finish;
3480
+ long n = sb_wsio_pump(c, &io, buf, sizeof(buf), wq, &wq_len, window < SB_WSC_WINDOW, -1);
3481
+ if (n < 0) break;
3482
+ if (n == 0) { sb_wsc_drain_wake(c); continue; }
3483
+ if (sb_wsc_feed(c, p, buf, (size_t)n)) goto closing;
3484
+ }
3485
+ // The peer went without a close frame.
3486
+ sb_wsc_push(c, 3, 1006, "", 0);
3487
+ goto finish;
3488
+ closing:
3489
+ // Our echo of the close frame is queued: let it go out, then end.
3490
+ {
3491
+ pthread_mutex_lock(&c->lock);
3492
+ if (c->outq_len && wq_len + c->outq_len <= wq_cap) { memcpy(wq + wq_len, c->outq, c->outq_len); wq_len += c->outq_len; c->outq_len = 0; }
3493
+ pthread_mutex_unlock(&c->lock);
3494
+ if (wq_len) sb_wsio_pump(c, &io, buf, sizeof(buf), wq, &wq_len, 0, 0);
3495
+ }
3496
+ goto finish;
3497
+ failed:
3498
+ if (!opened) sb_wsc_push(c, 3, 1006, why, strlen(why));
3499
+ else sb_wsc_push(c, 3, 1006, "", 0);
3500
+ finish:
3501
+ sb_wsio_close(&io);
3502
+ if (p) { free(p->msg); free(p); }
3503
+ free(wq);
3504
+ pthread_mutex_lock(&c->lock);
3505
+ c->finished = 1;
3506
+ int rel = c->released;
3507
+ pthread_mutex_unlock(&c->lock);
3508
+ if (rel) {
3509
+ pthread_mutex_lock(&sb_wsc_lock);
3510
+ for (sb_wsc** at = &sb_wscs; *at; at = &(*at)->next) if (*at == c) { *at = c->next; break; }
3511
+ pthread_mutex_unlock(&sb_wsc_lock);
3512
+ sb_wsc_free(c);
3513
+ }
3514
+ return 0;
3515
+ }
3516
+
3517
+ static sb_wsc* sb_wsc_find(unsigned id) {
3518
+ pthread_mutex_lock(&sb_wsc_lock);
3519
+ sb_wsc* c = sb_wscs;
3520
+ while (c && c->id != id) c = c->next;
3521
+ pthread_mutex_unlock(&sb_wsc_lock);
3522
+ return c;
3523
+ }
3524
+
3525
+ // Host: fires the timers parked for sockets that have events.
3526
+ static void sb_wsc_drain(void) {
3527
+ for (;;) {
3528
+ unsigned timer = 0;
3529
+ pthread_mutex_lock(&sb_wsc_lock);
3530
+ for (sb_wsc* c = sb_wscs; c; c = c->next) {
3531
+ pthread_mutex_lock(&c->lock);
3532
+ if (c->waiter && (c->wake_pending || c->evh)) { timer = c->waiter; c->waiter = 0; c->wake_pending = 0; }
3533
+ pthread_mutex_unlock(&c->lock);
3534
+ if (timer) break;
3535
+ }
3536
+ pthread_mutex_unlock(&sb_wsc_lock);
3537
+ if (!timer) return;
3538
+ porf_native_fetch_timer_clear(timer);
3539
+ porf_native_fetch_fire_timer(timer);
3540
+ }
3541
+ }
3542
+
3543
+ // The JS side's ops. data is borrowed for the call.
3544
+ static long long sb_wsc_op(int op, unsigned id, const char* data, size_t len, size_t n, char* out, size_t cap) {
3545
+ if (op == 'u') {
3546
+ // data: host \n port \n tls(0|1) \n path \n protocols \n header lines (to the end)
3547
+ sb_wsc* c = (sb_wsc*)calloc(1, sizeof(sb_wsc));
3548
+ if (!c) return 0;
3549
+ char* copy = (char*)malloc(len + 1);
3550
+ if (!copy) { free(c); return 0; }
3551
+ memcpy(copy, data, len); copy[len] = 0;
3552
+ char* parts[5]; char* cur = copy; int i;
3553
+ for (i = 0; i < 5; i++) {
3554
+ parts[i] = cur;
3555
+ char* nl = strchr(cur, '\n');
3556
+ if (!nl) { if (i < 4) { free(copy); free(c); return 0; } cur = cur + strlen(cur); break; }
3557
+ *nl = 0; cur = nl + 1;
3558
+ }
3559
+ c->host = strdup(parts[0]); c->port = atoi(parts[1]); c->tls = atoi(parts[2]);
3560
+ c->path = strdup(parts[3]); c->protocols = strdup(parts[4]); c->headers = strdup(cur);
3561
+ free(copy);
3562
+ if (!c->host || !c->path || !c->protocols || !c->headers || pipe(c->wake) != 0) { free(c->host); free(c->path); free(c->protocols); free(c->headers); free(c); return 0; }
3563
+ for (int k = 0; k < 2; k++) { fcntl(c->wake[k], F_SETFD, FD_CLOEXEC); fcntl(c->wake[k], F_SETFL, fcntl(c->wake[k], F_GETFL) | O_NONBLOCK); }
3564
+ pthread_mutex_init(&c->lock, 0);
3565
+ sb_wsc_seed(c);
3566
+ pthread_mutex_lock(&sb_wsc_lock);
3567
+ int live = 0;
3568
+ for (sb_wsc* k = sb_wscs; k; k = k->next) live++;
3569
+ if (live >= SB_WSC_MAX) { pthread_mutex_unlock(&sb_wsc_lock); sb_wsc_free(c); return 0; }
3570
+ sb_wsc_next = (sb_wsc_next + 1) & 0x7fffffffu;
3571
+ if (!sb_wsc_next) sb_wsc_next = 1;
3572
+ c->id = sb_wsc_next | SB_WSC_BIT;
3573
+ c->next = sb_wscs; sb_wscs = c;
3574
+ pthread_mutex_unlock(&sb_wsc_lock);
3575
+ pthread_attr_t attr;
3576
+ pthread_attr_init(&attr);
3577
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
3578
+ pthread_attr_setstacksize(&attr, 512 * 1024);
3579
+ pthread_t thread;
3580
+ int failed = pthread_create(&thread, &attr, sb_wsc_thread, c);
3581
+ pthread_attr_destroy(&attr);
3582
+ if (failed) {
3583
+ pthread_mutex_lock(&sb_wsc_lock);
3584
+ for (sb_wsc** at = &sb_wscs; *at; at = &(*at)->next) if (*at == c) { *at = c->next; break; }
3585
+ pthread_mutex_unlock(&sb_wsc_lock);
3586
+ sb_wsc_free(c);
3587
+ return 0;
3588
+ }
3589
+ return (long long)c->id;
3590
+ }
3591
+ sb_wsc* c = sb_wsc_find(id);
3592
+ if (!c) return op == 'p' || op == 'k' || op == 'w' || op == 'r' ? 0 : -1;
3593
+ switch (op) {
3594
+ case 't': case 'b': return sb_wsc_frame(c, op == 't' ? 1 : 2, data, len, 0, 0);
3595
+ case 'c': {
3596
+ pthread_mutex_lock(&c->lock);
3597
+ int already = c->close_queued;
3598
+ c->close_queued = 1;
3599
+ pthread_mutex_unlock(&c->lock);
3600
+ if (already) return 0;
3601
+ return sb_wsc_frame(c, 8, data, len, (unsigned)n, 1);
3602
+ }
3603
+ case 'p': {
3604
+ if (!out || !cap) return -1;
3605
+ out[0] = 0;
3606
+ pthread_mutex_lock(&c->lock);
3607
+ if (c->evh) snprintf(out, cap, "%d %zu %u", c->evh->kind, c->evh->len, c->evh->code);
3608
+ pthread_mutex_unlock(&c->lock);
3609
+ return 0;
3610
+ }
3611
+ case 'i': {
3612
+ pthread_mutex_lock(&c->lock);
3613
+ sb_wsc_ev* e = c->evh;
3614
+ if (!e) { pthread_mutex_unlock(&c->lock); return -1; }
3615
+ c->evh = e->next; if (!c->evh) c->evt = 0;
3616
+ pthread_mutex_unlock(&c->lock);
3617
+ size_t got = e->len < cap ? e->len : cap;
3618
+ if (got) memcpy(out, e->data, got);
3619
+ free(e->data); free(e);
3620
+ return (long long)got;
3621
+ }
3622
+ case 'k':
3623
+ pthread_mutex_lock(&c->lock);
3624
+ c->unacked -= n < c->unacked ? n : c->unacked;
3625
+ pthread_mutex_unlock(&c->lock);
3626
+ sb_wsc_wake_thread(c);
3627
+ return 0;
3628
+ case 'w':
3629
+ pthread_mutex_lock(&c->lock);
3630
+ c->waiter = (unsigned)n;
3631
+ if (c->evh) c->wake_pending = 1;
3632
+ pthread_mutex_unlock(&c->lock);
3633
+ if (c->evh) sb_wsc_signal();
3634
+ return 0;
3635
+ case 'r': {
3636
+ pthread_mutex_lock(&c->lock);
3637
+ c->released = 1;
3638
+ int done = c->finished;
3639
+ pthread_mutex_unlock(&c->lock);
3640
+ if (done) {
3641
+ pthread_mutex_lock(&sb_wsc_lock);
3642
+ for (sb_wsc** at = &sb_wscs; *at; at = &(*at)->next) if (*at == c) { *at = c->next; break; }
3643
+ pthread_mutex_unlock(&sb_wsc_lock);
3644
+ sb_wsc_free(c);
3645
+ } else sb_wsc_wake_thread(c);
3646
+ return 0;
3647
+ }
3648
+ }
3649
+ return -1;
3650
+ }
3651
+ // 1 when something will run the loop when an event arrives (the async pipe or a waker).
3652
+ static int sb_wsc_wakeable(void) { return sb_wsc_pipe_enabled() || sb_wsc_waker != 0; }
3653
+ extern long long (*sb_wsc_hook)(int op, unsigned int id, const char* data, size_t length, size_t n, char* out, size_t cap);
3654
+ extern int (*sb_wsc_wakeable_hook)(void);
3655
+ static void sb_wsc_install(void) __attribute__((constructor));
3656
+ static void sb_wsc_install(void) { sb_wsc_drain_hook = sb_wsc_drain; sb_wsc_hook = sb_wsc_op; sb_wsc_wakeable_hook = sb_wsc_wakeable; }
3657
+ // WSC:END
3019
3658
  `;
3020
3659
 
3021
3660
  // R2 put: the body is its own parameter, so it never gets escaped.