@scriptc/runtime 0.0.9 → 0.0.11
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.
- package/package.json +1 -1
- package/src/scr_assert.c +43 -4
- package/src/scr_async.c +24 -4
- package/src/scr_bytes.c +47 -9
- package/src/scr_bytes_io.c +410 -0
- package/src/scr_dc.c +6 -0
- package/src/scr_dgram.c +176 -1
- package/src/scr_dyn_invoke.c +96 -2
- package/src/scr_events_emitter.c +58 -5
- package/src/scr_inspect.c +27 -3
- package/src/scr_island.c +389 -3
- package/src/scr_json.c +488 -7
- package/src/scr_lib.c +21 -1
- package/src/scr_library.c +15 -0
- package/src/scr_net.c +112 -0
- package/src/scr_regex.c +39 -6
- package/src/scr_runtime.h +296 -17
- package/src/scr_stream.c +214 -7
- package/src/scr_tls.c +172 -0
package/src/scr_stream.c
CHANGED
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
* nothing.
|
|
14
14
|
*
|
|
15
15
|
* EVENT TIMING. Node schedules most stream emissions on process.nextTick;
|
|
16
|
-
* here
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
16
|
+
* here each deferral enqueues on the stream tick FIFO AND posts a raw
|
|
17
|
+
* marker on the USER nextTick queue (scr_next_tick_raw), so stream
|
|
18
|
+
* emissions and user nextTicks run in true FIFO enqueue order — Node's,
|
|
19
|
+
* where they are the same queue. The scr_loop_set_stream station remains
|
|
20
|
+
* as the drain of anything a marker never reached (and the uncaught-throw
|
|
21
|
+
* cleanup). The implemented orderings follow lib/internal/streams:
|
|
21
22
|
* - on('data') starts flowing on a TICK (resume_): synchronous code
|
|
22
23
|
* after the registration runs before the first 'data'.
|
|
23
24
|
* - push() while flowing with an empty buffer emits 'data'
|
|
@@ -103,6 +104,10 @@ struct ScrStreamState {
|
|
|
103
104
|
bool encoded; /* string-chunk mode (decoder active) */
|
|
104
105
|
ScrStr *enc; /* owned canonical encoding name, or NULL */
|
|
105
106
|
double dec_pending; /* scr_strdec packed pending state */
|
|
107
|
+
/* The defaultEncoding option's push-side effect: how push(string)
|
|
108
|
+
* DECODES string chunks into bytes (Node's Buffer.from(chunk,
|
|
109
|
+
* state.defaultEncoding)). Owned canonical name; NULL = utf8. */
|
|
110
|
+
ScrStr *push_enc;
|
|
106
111
|
/* Readable.from mode: entries are whole OBJECTS (each counts 1
|
|
107
112
|
* toward length/hwm and delivers undivided — Node's objectMode
|
|
108
113
|
* accounting for the from() surface; hwm is 1). */
|
|
@@ -223,6 +228,7 @@ static void scr_stream_state_drop(ScrStreamState *st, bool gc) {
|
|
|
223
228
|
for (size_t i = 0; i < st->r.n; i++) scr_stream_entry_release(st, st->r.buf[i]);
|
|
224
229
|
free(st->r.buf);
|
|
225
230
|
if (st->r.enc) scr_str_release(st->r.enc);
|
|
231
|
+
if (st->r.push_enc) scr_str_release(st->r.push_enc);
|
|
226
232
|
if (!gc && st->r.next_waiter) scr_promise_release(st->r.next_waiter);
|
|
227
233
|
if (!gc) {
|
|
228
234
|
if (st->r.read_cb) scr_closure_release(st->r.read_cb);
|
|
@@ -350,6 +356,8 @@ typedef struct ScrStreamTick {
|
|
|
350
356
|
static ScrStreamTick *scr_st_head = NULL;
|
|
351
357
|
static ScrStreamTick *scr_st_tail = NULL;
|
|
352
358
|
|
|
359
|
+
static void scr_stream_dispatch_one(void);
|
|
360
|
+
|
|
353
361
|
static void scr_st_tick(ScrStream *s, ScrStreamTickOp op, ScrError *err /*moves*/,
|
|
354
362
|
ScrClosure *cb /*moves*/) {
|
|
355
363
|
ScrStreamTick *t = calloc(1, sizeof *t);
|
|
@@ -361,6 +369,13 @@ static void scr_st_tick(ScrStream *s, ScrStreamTickOp op, ScrError *err /*moves*
|
|
|
361
369
|
if (scr_st_tail) scr_st_tail->next = t;
|
|
362
370
|
else scr_st_head = t;
|
|
363
371
|
scr_st_tail = t;
|
|
372
|
+
/* One marker per tick on the USER nextTick queue: stream emissions are
|
|
373
|
+
* process.nextTicks in Node (resume_, emitReadable_, endReadableNT,
|
|
374
|
+
* afterWrite, ...), so they must interleave with user nextTicks in
|
|
375
|
+
* enqueue order — a `push(); on('data'); process.nextTick(assert)`
|
|
376
|
+
* sequence sees its data before the assert runs. The station dispatch
|
|
377
|
+
* below stays as the drain of anything a marker never reached. */
|
|
378
|
+
scr_next_tick_raw(&scr_stream_dispatch_one);
|
|
364
379
|
}
|
|
365
380
|
|
|
366
381
|
static bool scr_stream_ticks_pending(void) { return scr_st_head != NULL; }
|
|
@@ -1222,6 +1237,24 @@ static ScrStream *scr_stream_alloc(const ScrVt *vt, const char *cls, bool has_r,
|
|
|
1222
1237
|
s->reg = NULL;
|
|
1223
1238
|
s->cls = cls;
|
|
1224
1239
|
s->st = scr_stream_state_new(has_r, has_w, rhwm, whwm, auto_destroy, emit_close, allow_half_open);
|
|
1240
|
+
/* Node's stream constructors pre-create their known _events keys (a V8
|
|
1241
|
+
* shape optimization) — eventNames() lists these BEFORE user events
|
|
1242
|
+
* added earlier. Same names, same order: Readable close/error/data/end/
|
|
1243
|
+
* readable, Writable close/error/prefinish/finish/drain, Duplex the
|
|
1244
|
+
* union (writable trio first — Node's observed key order). */
|
|
1245
|
+
ScrEmitter *em = (ScrEmitter *)s;
|
|
1246
|
+
scr_emitter_reserve(em, "close");
|
|
1247
|
+
scr_emitter_reserve(em, "error");
|
|
1248
|
+
if (has_w) {
|
|
1249
|
+
scr_emitter_reserve(em, "prefinish");
|
|
1250
|
+
scr_emitter_reserve(em, "finish");
|
|
1251
|
+
scr_emitter_reserve(em, "drain");
|
|
1252
|
+
}
|
|
1253
|
+
if (has_r) {
|
|
1254
|
+
scr_emitter_reserve(em, "data");
|
|
1255
|
+
scr_emitter_reserve(em, "end");
|
|
1256
|
+
scr_emitter_reserve(em, "readable");
|
|
1257
|
+
}
|
|
1225
1258
|
scr_obj_alloc_note();
|
|
1226
1259
|
return s;
|
|
1227
1260
|
}
|
|
@@ -2193,6 +2226,133 @@ ScrPromise *scr_sp_pipeline(double n, ScrStream **streams) {
|
|
|
2193
2226
|
return p;
|
|
2194
2227
|
}
|
|
2195
2228
|
|
|
2229
|
+
/* ── node:stream/consumers ────────────────────────────────────────────
|
|
2230
|
+
* text/json/buffer over the readable machinery: a native 'data' listener
|
|
2231
|
+
* accumulates every chunk (Buffer chunks as-is; string chunks — an
|
|
2232
|
+
* encoded stream or Readable.from strings — as their utf8 bytes, the
|
|
2233
|
+
* Blob rule Node's consumers share for whole-stream accumulation), and
|
|
2234
|
+
* the finished watcher settles at the terminal point — the accumulated
|
|
2235
|
+
* result on a clean end (right after 'close', Node's own timing: the
|
|
2236
|
+
* consumer's async iterator completes through eos), the stream's error,
|
|
2237
|
+
* or ERR_STREAM_PREMATURE_CLOSE on an early close — and marks lifecycle
|
|
2238
|
+
* errors handled, exactly like the iterator's eos registration. Both
|
|
2239
|
+
* closures share the cap layout: caps[0] the pending promise, caps[1]
|
|
2240
|
+
* the chunk list (Buffer entries). */
|
|
2241
|
+
|
|
2242
|
+
enum { SCR_SC_TEXT = 0, SCR_SC_JSON = 1, SCR_SC_BUFFER = 2 };
|
|
2243
|
+
|
|
2244
|
+
static void scr_sc_settle_ok(ScrClosure *cb, int kind) {
|
|
2245
|
+
ScrPromise *p = scr_box_get_ref(cb->caps[0]); /* +1 */
|
|
2246
|
+
ScrArr *chunks = scr_box_get_ref(cb->caps[1]); /* +1 */
|
|
2247
|
+
ScrBytes *all = scr_bytes_concat(chunks);
|
|
2248
|
+
scr_arr_release(chunks);
|
|
2249
|
+
if (kind == SCR_SC_BUFFER) {
|
|
2250
|
+
scr_promise_fulfill_ref(p, all, scr_bytes_retain_v, scr_bytes_release_v, NULL);
|
|
2251
|
+
scr_promise_release(p);
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
ScrStr *enc = scr_str_new("utf8", 4);
|
|
2255
|
+
ScrStr *text = scr_bytes_to_str(all, enc); /* U+FFFD per invalid subpart */
|
|
2256
|
+
scr_str_release(enc);
|
|
2257
|
+
scr_bytes_release(all);
|
|
2258
|
+
if (kind == SCR_SC_TEXT) {
|
|
2259
|
+
scr_promise_fulfill_str(p, text); /* moves */
|
|
2260
|
+
} else {
|
|
2261
|
+
ScrDyn *doc = scr_json_parse(text);
|
|
2262
|
+
scr_str_release(text);
|
|
2263
|
+
if (doc == NULL) {
|
|
2264
|
+
/* the parse's SyntaxError rides the cell — the rejection, like
|
|
2265
|
+
* Node's json() rejecting with JSON.parse's throw */
|
|
2266
|
+
scr_promise_reject_pending(p);
|
|
2267
|
+
} else {
|
|
2268
|
+
scr_promise_fulfill_ref(p, doc, scr_dyn_retain_v, scr_dyn_release_v, NULL);
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
scr_promise_release(p);
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
/* The 'data' accumulation (the emit ABI's two payload slots — exactly
|
|
2275
|
+
* one non-NULL). */
|
|
2276
|
+
static void scr_sc_data(ScrClosure *cb, void *b, void *str) {
|
|
2277
|
+
ScrArr *chunks = scr_box_get_ref(cb->caps[1]); /* +1 */
|
|
2278
|
+
if (b != NULL) {
|
|
2279
|
+
scr_arr_push_ref(chunks, scr_bytes_retain((ScrBytes *)b));
|
|
2280
|
+
} else if (str != NULL) {
|
|
2281
|
+
ScrStr *enc = scr_str_new("utf8", 4);
|
|
2282
|
+
scr_arr_push_ref(chunks, scr_bytes_from_str((ScrStr *)str, enc));
|
|
2283
|
+
scr_str_release(enc);
|
|
2284
|
+
}
|
|
2285
|
+
scr_arr_release(chunks);
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
static void scr_sc_fin(ScrClosure *cb, ScrError *err, int kind) {
|
|
2289
|
+
if (err != NULL) {
|
|
2290
|
+
ScrPromise *p = scr_box_get_ref(cb->caps[0]); /* +1 */
|
|
2291
|
+
scr_throw_obj(scr_error_retain(err), &scr_error_retain_v, &scr_error_release_v,
|
|
2292
|
+
scr_error_trace_arg());
|
|
2293
|
+
scr_promise_reject_pending(p);
|
|
2294
|
+
scr_promise_release(p);
|
|
2295
|
+
return;
|
|
2296
|
+
}
|
|
2297
|
+
scr_sc_settle_ok(cb, kind);
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
static void scr_sc_fin_text(ScrClosure *cb, ScrStream *s, ScrError *err) {
|
|
2301
|
+
(void)s;
|
|
2302
|
+
scr_sc_fin(cb, err, SCR_SC_TEXT);
|
|
2303
|
+
}
|
|
2304
|
+
static void scr_sc_fin_json(ScrClosure *cb, ScrStream *s, ScrError *err) {
|
|
2305
|
+
(void)s;
|
|
2306
|
+
scr_sc_fin(cb, err, SCR_SC_JSON);
|
|
2307
|
+
}
|
|
2308
|
+
static void scr_sc_fin_buffer(ScrClosure *cb, ScrStream *s, ScrError *err) {
|
|
2309
|
+
(void)s;
|
|
2310
|
+
scr_sc_fin(cb, err, SCR_SC_BUFFER);
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
static ScrClosure *scr_sc_closure(void *fn, ScrPromise *p, ScrArr *chunks) {
|
|
2314
|
+
ScrClosure *c = scr_closure_new(fn, 2);
|
|
2315
|
+
c->caps[0] = scr_box_new_obj(scr_promise_retain_v, scr_promise_release_v, scr_promise_trace_v);
|
|
2316
|
+
scr_box_set_ref(c->caps[0], scr_promise_retain(p));
|
|
2317
|
+
c->caps[1] = scr_box_new_obj(scr_arr_retain_v, scr_arr_release_v, NULL);
|
|
2318
|
+
scr_box_set_ref(c->caps[1], scr_arr_retain(chunks));
|
|
2319
|
+
return c;
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
static ScrPromise *scr_sc_consume(ScrStream *s, int kind) {
|
|
2323
|
+
ScrPromise *p = scr_promise_new();
|
|
2324
|
+
if (s->st == NULL || !s->st->has_r) {
|
|
2325
|
+
/* Node's consumers for-await the argument; a stream with no readable
|
|
2326
|
+
* side has no async iterator — the TypeError rejects. */
|
|
2327
|
+
static const char msg[] = "stream is not async iterable";
|
|
2328
|
+
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
|
2329
|
+
scr_promise_reject_pending(p);
|
|
2330
|
+
return p;
|
|
2331
|
+
}
|
|
2332
|
+
ScrArr *chunks = scr_arr_new_ref(scr_bytes_retain_v, scr_bytes_release_v, NULL, 4);
|
|
2333
|
+
ScrStreamErrInv fin_inv = kind == SCR_SC_TEXT ? &scr_sc_fin_text
|
|
2334
|
+
: kind == SCR_SC_JSON ? &scr_sc_fin_json
|
|
2335
|
+
: &scr_sc_fin_buffer;
|
|
2336
|
+
/* The terminal watcher first (it marks lifecycle errors handled); the
|
|
2337
|
+
* promise form exposes no unhook — the cleanup closure drops. */
|
|
2338
|
+
ScrClosure *cleanup = scr_stream_finished(s, scr_sc_closure((void *)fin_inv, p, chunks), fin_inv);
|
|
2339
|
+
scr_closure_release(cleanup);
|
|
2340
|
+
ScrStr *dn = scr_str_new("data", 4);
|
|
2341
|
+
scr_emitter_release(scr_emitter_on((ScrEmitter *)s, dn,
|
|
2342
|
+
scr_sc_closure((void *)&scr_sc_data, p, chunks),
|
|
2343
|
+
scr_ee_inv_fixed2, false, false));
|
|
2344
|
+
scr_str_release(dn);
|
|
2345
|
+
/* resume() rather than the 'data' hook alone: Node's consumer pulls
|
|
2346
|
+
* through the iterator, which drains a PAUSED stream too. */
|
|
2347
|
+
scr_stream_release(scr_stream_resume(s));
|
|
2348
|
+
scr_arr_release(chunks);
|
|
2349
|
+
return p;
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
ScrPromise *scr_sc_text(ScrStream *s) { return scr_sc_consume(s, SCR_SC_TEXT); }
|
|
2353
|
+
ScrPromise *scr_sc_json(ScrStream *s) { return scr_sc_consume(s, SCR_SC_JSON); }
|
|
2354
|
+
ScrPromise *scr_sc_buffer(ScrStream *s) { return scr_sc_consume(s, SCR_SC_BUFFER); }
|
|
2355
|
+
|
|
2196
2356
|
/* ── the readable surface ─────────────────────────────────────────────── */
|
|
2197
2357
|
|
|
2198
2358
|
bool scr_stream_push(ScrStream *s, ScrBytes *chunk) {
|
|
@@ -2200,13 +2360,41 @@ bool scr_stream_push(ScrStream *s, ScrBytes *chunk) {
|
|
|
2200
2360
|
}
|
|
2201
2361
|
|
|
2202
2362
|
bool scr_stream_push_str(ScrStream *s, ScrStr *str) {
|
|
2203
|
-
|
|
2204
|
-
|
|
2363
|
+
ScrStr *enc = s->st->has_r ? s->st->r.push_enc : NULL;
|
|
2364
|
+
ScrBytes *b;
|
|
2365
|
+
if (enc != NULL) {
|
|
2366
|
+
/* Node: Buffer.from(chunk, state.defaultEncoding). */
|
|
2367
|
+
b = scr_bytes_from_str(str, enc);
|
|
2368
|
+
} else {
|
|
2369
|
+
b = scr_bytes_new(SCR_BYTES_U8, (double)str->len);
|
|
2370
|
+
memcpy(b->data, str->data, str->len);
|
|
2371
|
+
}
|
|
2372
|
+
bool ret = scr_stream_add_chunk(s, b, false);
|
|
2373
|
+
scr_bytes_release(b);
|
|
2374
|
+
return ret;
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
/* push(chunk, encoding) with an explicit non-utf8 literal: the per-call
|
|
2378
|
+
* encoding overrides the stream's default. Borrows both strings. */
|
|
2379
|
+
bool scr_stream_push_str_enc(ScrStream *s, ScrStr *str, ScrStr *enc) {
|
|
2380
|
+
ScrBytes *b = scr_bytes_from_str(str, enc);
|
|
2205
2381
|
bool ret = scr_stream_add_chunk(s, b, false);
|
|
2206
2382
|
scr_bytes_release(b);
|
|
2207
2383
|
return ret;
|
|
2208
2384
|
}
|
|
2209
2385
|
|
|
2386
|
+
/* The defaultEncoding option's push side (canonical literal, frontend-
|
|
2387
|
+
* folded; never "utf8" — that stays the NULL fast path). Answers the
|
|
2388
|
+
* receiver +1, the setEncoding chaining shape. */
|
|
2389
|
+
ScrStream *scr_stream_set_push_encoding(ScrStream *s, ScrStr *enc) {
|
|
2390
|
+
ScrStreamState *st = s->st;
|
|
2391
|
+
if (st->has_r) {
|
|
2392
|
+
if (st->r.push_enc) scr_str_release(st->r.push_enc);
|
|
2393
|
+
st->r.push_enc = scr_str_retain(enc);
|
|
2394
|
+
}
|
|
2395
|
+
return scr_stream_retain(s);
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2210
2398
|
bool scr_stream_push_null(ScrStream *s) {
|
|
2211
2399
|
ScrStreamState *st = s->st;
|
|
2212
2400
|
if (!st->has_r || st->destroyed) return false;
|
|
@@ -2891,6 +3079,25 @@ static void scr_stream_run_tick(ScrStreamTick *t) {
|
|
|
2891
3079
|
}
|
|
2892
3080
|
}
|
|
2893
3081
|
|
|
3082
|
+
static void scr_stream_dispatch(void);
|
|
3083
|
+
|
|
3084
|
+
/* One tick-marker's dispatch: the queue's FIFO head (markers and entries
|
|
3085
|
+
* are enqueued 1:1 in the same order). After an uncaught throw the
|
|
3086
|
+
* remaining entries drop through the station's exc branch below, keeping
|
|
3087
|
+
* the RC audit clean. */
|
|
3088
|
+
static void scr_stream_dispatch_one(void) {
|
|
3089
|
+
ScrStreamTick *t = scr_st_head;
|
|
3090
|
+
if (t == NULL || scr_exc_pending()) return;
|
|
3091
|
+
scr_st_head = t->next;
|
|
3092
|
+
if (scr_st_head == NULL) scr_st_tail = NULL;
|
|
3093
|
+
scr_stream_run_tick(t);
|
|
3094
|
+
scr_stream_release(t->s);
|
|
3095
|
+
if (t->err) scr_error_release(t->err);
|
|
3096
|
+
if (t->cb) scr_closure_release(t->cb);
|
|
3097
|
+
free(t);
|
|
3098
|
+
if (scr_exc_pending()) scr_stream_dispatch(); /* its exc branch drops the rest */
|
|
3099
|
+
}
|
|
3100
|
+
|
|
2894
3101
|
static void scr_stream_dispatch(void) {
|
|
2895
3102
|
while (scr_st_head != NULL && !scr_exc_pending() && !scr_loop_has_ready()) {
|
|
2896
3103
|
ScrStreamTick *t = scr_st_head;
|
package/src/scr_tls.c
CHANGED
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
#include "scr_runtime.h"
|
|
74
74
|
|
|
75
75
|
#include <errno.h>
|
|
76
|
+
#include <math.h>
|
|
76
77
|
#include <stdio.h>
|
|
77
78
|
#include <stdlib.h>
|
|
78
79
|
#include <string.h>
|
|
@@ -1102,6 +1103,121 @@ static const char *const SCR_TLS_SRV_FENCED_OPTIONS[] = {
|
|
|
1102
1103
|
"sessionTimeout", "sigalgs", "ticketKeys", NULL,
|
|
1103
1104
|
};
|
|
1104
1105
|
|
|
1106
|
+
/* ── the typed option-validation ladders (checked-dynamic lane) ────────
|
|
1107
|
+
* Node's configSecureContext / Server-constructor argument contracts over
|
|
1108
|
+
* every PRESENT validated key, run BEFORE the pem walk and its fences so
|
|
1109
|
+
* Node's typed errors always come first (the invalid-input probes isolate
|
|
1110
|
+
* one bad option per call, so the fixed order below matches each).
|
|
1111
|
+
* false = exception pending. */
|
|
1112
|
+
static bool scr_tls_opts_validate(const ScrDyn *opts) {
|
|
1113
|
+
if (opts == NULL || opts->kind != SCR_DYN_OBJ) return true;
|
|
1114
|
+
static const char *const STR_OPTS[] = { "ciphers", "passphrase", "ecdhCurve", "sessionIdContext", NULL };
|
|
1115
|
+
for (size_t i = 0; STR_OPTS[i] != NULL; i++) {
|
|
1116
|
+
const ScrDyn *v = scr_dyn_obj_get(opts, STR_OPTS[i], strlen(STR_OPTS[i]));
|
|
1117
|
+
if (v != NULL && v->kind != SCR_DYN_UNDEF && v->kind != SCR_DYN_NULL && v->kind != SCR_DYN_STR) {
|
|
1118
|
+
char name[48];
|
|
1119
|
+
snprintf(name, sizeof name, "options.%s", STR_OPTS[i]);
|
|
1120
|
+
scr_dyn_prop_type_fail(name, "of type string", v);
|
|
1121
|
+
return false;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
static const char *const ENGINE_OPTS[] = { "clientCertEngine", "privateKeyEngine", "privateKeyIdentifier", NULL };
|
|
1125
|
+
for (size_t i = 0; ENGINE_OPTS[i] != NULL; i++) {
|
|
1126
|
+
const ScrDyn *v = scr_dyn_obj_get(opts, ENGINE_OPTS[i], strlen(ENGINE_OPTS[i]));
|
|
1127
|
+
if (v != NULL && v->kind != SCR_DYN_UNDEF && v->kind != SCR_DYN_NULL && v->kind != SCR_DYN_STR) {
|
|
1128
|
+
char name[48];
|
|
1129
|
+
snprintf(name, sizeof name, "options.%s", ENGINE_OPTS[i]);
|
|
1130
|
+
scr_dyn_prop_type_fail(name, "of type string or one of null or undefined", v);
|
|
1131
|
+
return false;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
static const char *const VERSION_OPTS[] = { "minVersion", "maxVersion", NULL };
|
|
1135
|
+
for (size_t i = 0; VERSION_OPTS[i] != NULL; i++) {
|
|
1136
|
+
const ScrDyn *v = scr_dyn_obj_get(opts, VERSION_OPTS[i], strlen(VERSION_OPTS[i]));
|
|
1137
|
+
if (v == NULL || v->kind == SCR_DYN_UNDEF) continue;
|
|
1138
|
+
bool valid = false;
|
|
1139
|
+
if (v->kind == SCR_DYN_STR) {
|
|
1140
|
+
static const char *const KNOWN[] = { "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3", NULL };
|
|
1141
|
+
for (size_t k = 0; KNOWN[k] != NULL; k++) {
|
|
1142
|
+
if (v->v.str->len == strlen(KNOWN[k]) && memcmp(v->v.str->data, KNOWN[k], v->v.str->len) == 0) {
|
|
1143
|
+
valid = true;
|
|
1144
|
+
break;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
if (!valid) {
|
|
1149
|
+
/* %j: strings render JSON-quoted, everything else inspect-lite. */
|
|
1150
|
+
char rendered[96];
|
|
1151
|
+
if (v->kind == SCR_DYN_STR) {
|
|
1152
|
+
snprintf(rendered, sizeof rendered, "\"%.*s\"",
|
|
1153
|
+
(int)(v->v.str->len < 80 ? v->v.str->len : 80), v->v.str->data);
|
|
1154
|
+
} else {
|
|
1155
|
+
/* %j over non-strings: JSON.stringify's scalar renderings. */
|
|
1156
|
+
char lite[64];
|
|
1157
|
+
snprintf(rendered, sizeof rendered, "%s", scr_dyn_inspect_lite(v, lite, sizeof lite));
|
|
1158
|
+
}
|
|
1159
|
+
char msg[192];
|
|
1160
|
+
int len = snprintf(msg, sizeof msg, "%s is not a valid %s TLS protocol version",
|
|
1161
|
+
rendered, VERSION_OPTS[i][2] == 'n' ? "minimum" : "maximum");
|
|
1162
|
+
scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)len, "ERR_TLS_INVALID_PROTOCOL_VERSION");
|
|
1163
|
+
return false;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
static const char *const NUM_OPTS[] = { "handshakeTimeout", "keepAliveInitialDelay", NULL };
|
|
1167
|
+
for (size_t i = 0; NUM_OPTS[i] != NULL; i++) {
|
|
1168
|
+
const ScrDyn *v = scr_dyn_obj_get(opts, NUM_OPTS[i], strlen(NUM_OPTS[i]));
|
|
1169
|
+
if (v != NULL && v->kind != SCR_DYN_UNDEF && v->kind != SCR_DYN_NULL && v->kind != SCR_DYN_NUM) {
|
|
1170
|
+
char name[48];
|
|
1171
|
+
snprintf(name, sizeof name, "options.%s", NUM_OPTS[i]);
|
|
1172
|
+
scr_dyn_prop_type_fail(name, "of type number", v);
|
|
1173
|
+
return false;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
{
|
|
1177
|
+
const ScrDyn *v = scr_dyn_obj_get(opts, "sessionTimeout", 14);
|
|
1178
|
+
if (v != NULL && v->kind != SCR_DYN_UNDEF && v->kind != SCR_DYN_NULL) {
|
|
1179
|
+
if (v->kind != SCR_DYN_NUM) {
|
|
1180
|
+
scr_dyn_prop_type_fail("options.sessionTimeout", "of type number", v);
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
1183
|
+
double n = v->v.num;
|
|
1184
|
+
char recv[48], msg[192];
|
|
1185
|
+
if (!(isfinite(n) && trunc(n) == n)) {
|
|
1186
|
+
scr_num_received(n, recv);
|
|
1187
|
+
int len = snprintf(msg, sizeof msg,
|
|
1188
|
+
"The value of \"options.sessionTimeout\" is out of range. It must be an integer. Received %s", recv);
|
|
1189
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
1190
|
+
return false;
|
|
1191
|
+
}
|
|
1192
|
+
if (n < 0 || n > 2147483647.0) {
|
|
1193
|
+
scr_num_received(n, recv);
|
|
1194
|
+
int len = snprintf(msg, sizeof msg,
|
|
1195
|
+
"The value of \"options.sessionTimeout\" is out of range. It must be >= 0 && <= 2147483647. Received %s", recv);
|
|
1196
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
1197
|
+
return false;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
{
|
|
1202
|
+
const ScrDyn *v = scr_dyn_obj_get(opts, "ticketKeys", 10);
|
|
1203
|
+
if (v != NULL && v->kind != SCR_DYN_UNDEF && v->kind != SCR_DYN_NULL) {
|
|
1204
|
+
if (v->kind != SCR_DYN_BYTES) {
|
|
1205
|
+
scr_dyn_prop_type_fail("options.ticketKeys", "an instance of Buffer, TypedArray, or DataView", v);
|
|
1206
|
+
return false;
|
|
1207
|
+
}
|
|
1208
|
+
double bytelen = scr_bytes_byte_len(v->v.bytes);
|
|
1209
|
+
if (bytelen != 48) {
|
|
1210
|
+
char msg[160];
|
|
1211
|
+
int len = snprintf(msg, sizeof msg,
|
|
1212
|
+
"The property 'options.ticketKeys' must be exactly 48 bytes. Received %.0f", bytelen);
|
|
1213
|
+
scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)len, "ERR_INVALID_ARG_VALUE");
|
|
1214
|
+
return false;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
return true;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1105
1221
|
/* The server-options walk. Fills the cert/key out-params (+1 each) from a
|
|
1106
1222
|
* DOM options record; false = exception pending (partial results released). */
|
|
1107
1223
|
static bool scr_tls_srv_opts_walk(const ScrDyn *opts, const char *api, ScrBytes **cert_out,
|
|
@@ -1112,6 +1228,7 @@ static bool scr_tls_srv_opts_walk(const ScrDyn *opts, const char *api, ScrBytes
|
|
|
1112
1228
|
scr_dyn_arg_type_fail("options", "of type object", opts ? opts : scr_dyn_undefined());
|
|
1113
1229
|
return false;
|
|
1114
1230
|
}
|
|
1231
|
+
if (!scr_tls_opts_validate(opts)) return false; /* Node's typed errors first */
|
|
1115
1232
|
bool ok = true;
|
|
1116
1233
|
for (size_t i = 0; ok && i < opts->v.obj.len; i++) {
|
|
1117
1234
|
const ScrDynEntry *e = &opts->v.obj.entries[i];
|
|
@@ -1176,6 +1293,45 @@ static bool scr_tls_srv_opts_walk(const ScrDyn *opts, const char *api, ScrBytes
|
|
|
1176
1293
|
return ok;
|
|
1177
1294
|
}
|
|
1178
1295
|
|
|
1296
|
+
/* createSecureContext over a RUNTIME options record: Node's typed
|
|
1297
|
+
* validations first (scr_tls_opts_validate), then the pem walk — a bag
|
|
1298
|
+
* that validates AND carries both cert and key builds the real context;
|
|
1299
|
+
* everything else met its ladder error or the walk's per-key fence.
|
|
1300
|
+
* +1, or NULL with the exception pending. */
|
|
1301
|
+
ScrSecureCtx *scr_tls_create_secure_context_dyn(const ScrDyn *opts /*borrowed*/) {
|
|
1302
|
+
ScrBytes *cert, *key;
|
|
1303
|
+
if (!scr_tls_srv_opts_walk(opts, "tls.createSecureContext", &cert, &key)) return NULL;
|
|
1304
|
+
ScrSecureCtx *c = scr_tls_create_secure_context((const char *)cert->data, cert->len,
|
|
1305
|
+
(const char *)key->data, key->len);
|
|
1306
|
+
scr_bytes_release(cert);
|
|
1307
|
+
scr_bytes_release(key);
|
|
1308
|
+
return c;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/* tls.getCACertificates(type): validateString, then the documented name
|
|
1312
|
+
* set — an unknown name answers ERR_INVALID_ARG_VALUE; the real CA list
|
|
1313
|
+
* has no lowering, so a valid name meets the fence. Always throws. */
|
|
1314
|
+
void scr_tls_ca_certs_chk(const ScrDyn *type, const ScrStr *fence) {
|
|
1315
|
+
if (type->kind != SCR_DYN_STR) {
|
|
1316
|
+
scr_dyn_arg_type_fail("type", "of type string", type);
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
static const char *const KNOWN[] = { "default", "system", "bundled", "extra", NULL };
|
|
1320
|
+
bool valid = false;
|
|
1321
|
+
for (size_t i = 0; KNOWN[i] != NULL; i++) {
|
|
1322
|
+
if (type->v.str->len == strlen(KNOWN[i]) &&
|
|
1323
|
+
memcmp(type->v.str->data, KNOWN[i], type->v.str->len) == 0) {
|
|
1324
|
+
valid = true;
|
|
1325
|
+
break;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
if (!valid) {
|
|
1329
|
+
scr_dyn_arg_value_fail("type", NULL, type);
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
scr_throw_lowering_fence(fence);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1179
1335
|
ScrNetServer *scr_tls_create_server_dyn(const ScrDyn *opts /*borrowed*/,
|
|
1180
1336
|
ScrClosure *handler /*moves, nullable*/,
|
|
1181
1337
|
ScrNetConnFn fn) {
|
|
@@ -1241,6 +1397,22 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable
|
|
|
1241
1397
|
for (size_t i = 0; ok && i < opts->v.obj.len; i++) {
|
|
1242
1398
|
const ScrDynEntry *e = &opts->v.obj.entries[i];
|
|
1243
1399
|
const ScrDyn *v = e->value;
|
|
1400
|
+
if (strcmp(e->key, "checkServerIdentity") == 0) {
|
|
1401
|
+
/* Node spreads user options over the defaults, so a PRESENT key
|
|
1402
|
+
* replaces the builtin verifier even when its value is undefined
|
|
1403
|
+
* — validateFunction then throws for anything non-callable. A
|
|
1404
|
+
* real function keeps the fence (custom verification has no
|
|
1405
|
+
* lowering). */
|
|
1406
|
+
if (v->kind != SCR_DYN_FUNC) {
|
|
1407
|
+
scr_dyn_prop_type_fail("options.checkServerIdentity", "of type function", v);
|
|
1408
|
+
ok = false;
|
|
1409
|
+
} else {
|
|
1410
|
+
scr_tls_opt_fence("tls.connect", "checkServerIdentity",
|
|
1411
|
+
"custom identity verification has no lowering — the runtime verifies against the servername/host");
|
|
1412
|
+
ok = false;
|
|
1413
|
+
}
|
|
1414
|
+
continue;
|
|
1415
|
+
}
|
|
1244
1416
|
if (v->kind == SCR_DYN_UNDEF) continue;
|
|
1245
1417
|
if (strcmp(e->key, "port") == 0) {
|
|
1246
1418
|
if (port >= 0) continue; /* the argument form wins, like Node */
|