@pbwire/pbwire 0.0.0-stage → 0.1.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.
- package/README.md +204 -2
- package/build.sh +49 -0
- package/lib/pbwire.js +194 -0
- package/package.json +19 -4
- package/runtime/addon.c +2566 -0
package/runtime/addon.c
ADDED
|
@@ -0,0 +1,2566 @@
|
|
|
1
|
+
/* N-API addon: the C half of the Node.js binding.
|
|
2
|
+
*
|
|
3
|
+
* A message has two representations, and the JS layer picks which one it holds:
|
|
4
|
+
*
|
|
5
|
+
* tree - a pbw_message, what newMessage()/set() build. Values stay in C, so
|
|
6
|
+
* encoding never crosses the boundary again and the core writer runs
|
|
7
|
+
* at core speed.
|
|
8
|
+
*
|
|
9
|
+
* wire - the bytes decode() was given plus one 12-byte slice per wire field.
|
|
10
|
+
* The payload is validated once while those slices are recorded, a
|
|
11
|
+
* value materializes only when it is read, and encode() hands the
|
|
12
|
+
* retained bytes back, so a parsed message round-trips byte-exactly
|
|
13
|
+
* (unknown fields, field order and non-canonical encodings included).
|
|
14
|
+
*
|
|
15
|
+
* The wire form deliberately keeps no native state: the bytes are a JS Buffer
|
|
16
|
+
* and the slices a JS Uint32Array, both passed back on every call. Attaching a
|
|
17
|
+
* finalizer to native memory costs N-API ~250 ns per message, which would be
|
|
18
|
+
* more than the whole decode, so only the tree form (owned by a finalizer,
|
|
19
|
+
* because it is mutable and has no JS-visible storage) pays for one.
|
|
20
|
+
*
|
|
21
|
+
* Everything the addon does per field comes from a desc_table: one cached,
|
|
22
|
+
* per-descriptor record of what the schema says (number, type, wire type,
|
|
23
|
+
* label, oneof, closed enum, sub-message) plus a name hash. Building it costs
|
|
24
|
+
* one pass over the descriptor; the hot paths then touch no pbw_* accessor.
|
|
25
|
+
*
|
|
26
|
+
* Failures are thrown JS Errors; no pbw_* status escapes as a crash.
|
|
27
|
+
*/
|
|
28
|
+
#define NAPI_VERSION 8
|
|
29
|
+
#include <node_api.h>
|
|
30
|
+
|
|
31
|
+
#include <stdint.h>
|
|
32
|
+
#include <stdio.h>
|
|
33
|
+
#include <stdlib.h>
|
|
34
|
+
#include <string.h>
|
|
35
|
+
|
|
36
|
+
#include "pbwire.h"
|
|
37
|
+
|
|
38
|
+
#define NOT_FOUND 0xffffffffu /* field index that no field can have */
|
|
39
|
+
|
|
40
|
+
/* ------------------------------------------------------------ wire reader */
|
|
41
|
+
|
|
42
|
+
/* These mirror src/wire.c exactly: a varint is at most 10 bytes (bits above
|
|
43
|
+
* bit 63 of the tenth are dropped), a tag is a varint32 whose fifth byte may
|
|
44
|
+
* overflow, a length is a strict varint32. The lazy decoder accepts what the
|
|
45
|
+
* core accepts and nothing else, which is what keeps it from accepting bytes
|
|
46
|
+
* the tree decoder would reject. src/wire.c is the definition; keep the two in
|
|
47
|
+
* step. */
|
|
48
|
+
|
|
49
|
+
static int
|
|
50
|
+
rb_varint(const uint8_t **pp, const uint8_t *end, uint64_t *value)
|
|
51
|
+
{
|
|
52
|
+
const uint8_t *p = *pp;
|
|
53
|
+
uint64_t result;
|
|
54
|
+
unsigned i;
|
|
55
|
+
|
|
56
|
+
if (p >= end) {
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
result = p[0];
|
|
60
|
+
if (result < 0x80) {
|
|
61
|
+
*pp = p + 1;
|
|
62
|
+
*value = result;
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
result = 0;
|
|
66
|
+
for (i = 0; i < 10 && p + i < end; i++) {
|
|
67
|
+
uint8_t byte = p[i];
|
|
68
|
+
|
|
69
|
+
result |= (uint64_t)(byte & 0x7f) << (7 * i);
|
|
70
|
+
if ((byte & 0x80) == 0) {
|
|
71
|
+
*pp = p + i + 1;
|
|
72
|
+
*value = result;
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static int
|
|
80
|
+
rb_tag(const uint8_t **pp, const uint8_t *end, uint32_t *tag)
|
|
81
|
+
{
|
|
82
|
+
const uint8_t *p = *pp;
|
|
83
|
+
uint32_t result;
|
|
84
|
+
unsigned i;
|
|
85
|
+
|
|
86
|
+
if (p >= end) {
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
result = p[0];
|
|
90
|
+
if (result < 0x80) {
|
|
91
|
+
*pp = p + 1;
|
|
92
|
+
*tag = result;
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
result &= 0x7f;
|
|
96
|
+
for (i = 1; i < 5 && p + i < end; i++) {
|
|
97
|
+
uint32_t byte = p[i];
|
|
98
|
+
|
|
99
|
+
result |= (byte & 0x7f) << (7 * i);
|
|
100
|
+
if (byte < 0x80) {
|
|
101
|
+
*pp = p + i + 1;
|
|
102
|
+
*tag = result;
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static int
|
|
110
|
+
rb_len(const uint8_t **pp, const uint8_t *end, size_t *len)
|
|
111
|
+
{
|
|
112
|
+
const uint8_t *p = *pp;
|
|
113
|
+
uint32_t result;
|
|
114
|
+
unsigned i;
|
|
115
|
+
|
|
116
|
+
if (p >= end) {
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
result = p[0];
|
|
120
|
+
if (result < 0x80) {
|
|
121
|
+
*pp = p + 1;
|
|
122
|
+
*len = result;
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
125
|
+
result &= 0x7f;
|
|
126
|
+
for (i = 1; i < 5 && p + i < end; i++) {
|
|
127
|
+
uint32_t byte = p[i];
|
|
128
|
+
|
|
129
|
+
if (i == 4 && (byte & 0x7f) > 0x0f) {
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
result |= (byte & 0x7f) << (7 * i);
|
|
133
|
+
if (byte < 0x80) {
|
|
134
|
+
*pp = p + i + 1;
|
|
135
|
+
*len = (size_t)result;
|
|
136
|
+
return 1;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static uint32_t
|
|
143
|
+
rb_le32(const uint8_t *p)
|
|
144
|
+
{
|
|
145
|
+
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) |
|
|
146
|
+
((uint32_t)p[3] << 24);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static uint64_t
|
|
150
|
+
rb_le64(const uint8_t *p)
|
|
151
|
+
{
|
|
152
|
+
return (uint64_t)rb_le32(p) | ((uint64_t)rb_le32(p + 4) << 32);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static uint64_t
|
|
156
|
+
rb_zigzag(uint64_t value, unsigned width)
|
|
157
|
+
{
|
|
158
|
+
uint64_t mask = width == 32 ? 0xffffffffull : ~0ull;
|
|
159
|
+
uint64_t u = value & mask;
|
|
160
|
+
|
|
161
|
+
return ((u >> 1) ^ (0ull - (u & 1))) & mask;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Wire type a field of this type is encoded with (pbw_expected_wire_type is
|
|
165
|
+
* internal). */
|
|
166
|
+
static uint32_t
|
|
167
|
+
wire_type_of(pbw_type type)
|
|
168
|
+
{
|
|
169
|
+
switch (type) {
|
|
170
|
+
case PBW_TYPE_DOUBLE:
|
|
171
|
+
case PBW_TYPE_FIXED64:
|
|
172
|
+
case PBW_TYPE_SFIXED64:
|
|
173
|
+
return 1;
|
|
174
|
+
case PBW_TYPE_FLOAT:
|
|
175
|
+
case PBW_TYPE_FIXED32:
|
|
176
|
+
case PBW_TYPE_SFIXED32:
|
|
177
|
+
return 5;
|
|
178
|
+
case PBW_TYPE_STRING:
|
|
179
|
+
case PBW_TYPE_BYTES:
|
|
180
|
+
case PBW_TYPE_MESSAGE:
|
|
181
|
+
return 2;
|
|
182
|
+
case PBW_TYPE_GROUP:
|
|
183
|
+
return 3;
|
|
184
|
+
default:
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
static int
|
|
190
|
+
packable(pbw_type type)
|
|
191
|
+
{
|
|
192
|
+
switch (type) {
|
|
193
|
+
case PBW_TYPE_STRING:
|
|
194
|
+
case PBW_TYPE_BYTES:
|
|
195
|
+
case PBW_TYPE_MESSAGE:
|
|
196
|
+
case PBW_TYPE_GROUP:
|
|
197
|
+
return 0;
|
|
198
|
+
default:
|
|
199
|
+
return 1;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* Bytes a tag for `number` occupies: START_GROUP and END_GROUP tags differ
|
|
204
|
+
* only in their low three bits, so they are the same length. */
|
|
205
|
+
static uint32_t
|
|
206
|
+
tag_size(uint32_t number)
|
|
207
|
+
{
|
|
208
|
+
uint32_t value = number << 3;
|
|
209
|
+
uint32_t size = 1;
|
|
210
|
+
|
|
211
|
+
while (value >= 0x80) {
|
|
212
|
+
value >>= 7;
|
|
213
|
+
size++;
|
|
214
|
+
}
|
|
215
|
+
return size;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* --------------------------------------------------------- descriptor data */
|
|
219
|
+
|
|
220
|
+
typedef struct desc_table desc_table;
|
|
221
|
+
typedef struct schema_handle schema_handle;
|
|
222
|
+
|
|
223
|
+
typedef struct {
|
|
224
|
+
const pbw_field *field; /* resolved once, so hot loops hand it straight to */
|
|
225
|
+
const char *name; /* pbw_message_{get,set}_field() */
|
|
226
|
+
desc_table *sub; /* resolved on first use, MESSAGE/GROUP only */
|
|
227
|
+
uint32_t number;
|
|
228
|
+
uint32_t oneof; /* 1-based oneof group, 0 = not in a oneof */
|
|
229
|
+
uint8_t type;
|
|
230
|
+
uint8_t label;
|
|
231
|
+
uint8_t wire; /* wire type this field is encoded with */
|
|
232
|
+
uint8_t closed; /* proto2 closed enum: unknown numbers leave the field */
|
|
233
|
+
} field_entry;
|
|
234
|
+
|
|
235
|
+
struct desc_table {
|
|
236
|
+
schema_handle *schema;
|
|
237
|
+
const pbw_descriptor *desc;
|
|
238
|
+
field_entry *fields; /* descriptor field index order */
|
|
239
|
+
uint32_t *by_number; /* field indices, ascending by field number */
|
|
240
|
+
uint32_t *dense; /* field index by number when the numbers are small */
|
|
241
|
+
uint32_t dense_max;
|
|
242
|
+
uint32_t nfields;
|
|
243
|
+
uint32_t oneof_count; /* oneof groups in this message */
|
|
244
|
+
uint32_t mask; /* name hash: slot count - 1 */
|
|
245
|
+
uint32_t *slots; /* name hash: field index + 1, 0 = empty */
|
|
246
|
+
int8_t eager; /* 1: a required field is reachable; -2: resolving */
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
struct schema_handle {
|
|
250
|
+
pbw_schema *schema;
|
|
251
|
+
desc_table **tables;
|
|
252
|
+
size_t count;
|
|
253
|
+
size_t cap;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
typedef struct {
|
|
257
|
+
schema_handle *schema;
|
|
258
|
+
desc_table *table;
|
|
259
|
+
pbw_ctx *enc; /* reusable encode buffer for this type */
|
|
260
|
+
const uint8_t *enc_data;
|
|
261
|
+
size_t enc_len;
|
|
262
|
+
int enc_valid; /* the scratch holds enc_len bytes of this type's encoding */
|
|
263
|
+
napi_ref schema_ref;
|
|
264
|
+
} desc_handle;
|
|
265
|
+
|
|
266
|
+
/* A recorded wire field, laid out exactly as three Uint32Array words so the
|
|
267
|
+
* slice list a decode produces can be handed to JS as is. `off` is an absolute
|
|
268
|
+
* offset into the retained bytes: the value itself for a scalar, the payload
|
|
269
|
+
* for a delimited field (string, bytes, nested message, packed run) and the
|
|
270
|
+
* body for a group. An empty packed run is recorded as nothing, so `len == 0`
|
|
271
|
+
* on a packable field always means "a scalar value at off". */
|
|
272
|
+
typedef struct {
|
|
273
|
+
uint32_t index; /* field index */
|
|
274
|
+
uint32_t off;
|
|
275
|
+
uint32_t len; /* payload length; 0 for an undelimited value */
|
|
276
|
+
} slice_t;
|
|
277
|
+
|
|
278
|
+
typedef struct {
|
|
279
|
+
slice_t *items;
|
|
280
|
+
uint32_t count;
|
|
281
|
+
uint32_t cap;
|
|
282
|
+
slice_t inline_[8];
|
|
283
|
+
} slice_list;
|
|
284
|
+
|
|
285
|
+
typedef struct {
|
|
286
|
+
desc_handle *desc;
|
|
287
|
+
napi_ref desc_ref; /* keeps the descriptor's schema alive */
|
|
288
|
+
pbw_message *msg;
|
|
289
|
+
int freed;
|
|
290
|
+
} message_handle;
|
|
291
|
+
|
|
292
|
+
/* Every napi_* call is checked: a failure becomes a thrown error, never a
|
|
293
|
+
* return into JS with a stale value. */
|
|
294
|
+
#define TRY(call) \
|
|
295
|
+
do { \
|
|
296
|
+
if ((call) != napi_ok) { \
|
|
297
|
+
napi_throw_error(env, NULL, "pbwire: internal napi call failed"); \
|
|
298
|
+
return NULL; \
|
|
299
|
+
} \
|
|
300
|
+
} while (0)
|
|
301
|
+
|
|
302
|
+
static napi_value
|
|
303
|
+
fail_status(napi_env env, const char *what, pbw_status status)
|
|
304
|
+
{
|
|
305
|
+
char message[512];
|
|
306
|
+
|
|
307
|
+
snprintf(message, sizeof(message), "pbwire: %s: %s", what,
|
|
308
|
+
pbw_strerror(status));
|
|
309
|
+
napi_throw_error(env, NULL, message);
|
|
310
|
+
return NULL;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
static napi_value
|
|
314
|
+
fail_argument(napi_env env, const char *what)
|
|
315
|
+
{
|
|
316
|
+
char message[256];
|
|
317
|
+
|
|
318
|
+
snprintf(message, sizeof(message), "pbwire: %s", what);
|
|
319
|
+
napi_throw_type_error(env, NULL, message);
|
|
320
|
+
return NULL;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
static napi_value
|
|
324
|
+
fail_memory(napi_env env)
|
|
325
|
+
{
|
|
326
|
+
napi_throw_error(env, NULL, "pbwire: out of memory");
|
|
327
|
+
return NULL;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
static int
|
|
331
|
+
get_args(napi_env env, napi_callback_info info, napi_value *argv, size_t count)
|
|
332
|
+
{
|
|
333
|
+
size_t argc = count;
|
|
334
|
+
|
|
335
|
+
if (napi_get_cb_info(env, info, &argc, argv, NULL, NULL) != napi_ok ||
|
|
336
|
+
argc < count) {
|
|
337
|
+
napi_throw_type_error(env, NULL, "pbwire: wrong number of arguments");
|
|
338
|
+
return 0;
|
|
339
|
+
}
|
|
340
|
+
return 1;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
static int
|
|
344
|
+
unwrap(napi_env env, napi_value value, void **out, const char *what)
|
|
345
|
+
{
|
|
346
|
+
if (napi_unwrap(env, value, out) != napi_ok || *out == NULL) {
|
|
347
|
+
fail_argument(env, what);
|
|
348
|
+
return 0;
|
|
349
|
+
}
|
|
350
|
+
return 1;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/* Copies a JS string into `buf` when it fits and returns buf, otherwise
|
|
354
|
+
* returns a malloc'd copy with *heap set. Field names take the first branch:
|
|
355
|
+
* they are short, and this keeps the per-call name path allocation-free. */
|
|
356
|
+
typedef struct {
|
|
357
|
+
char stack[64];
|
|
358
|
+
size_t len;
|
|
359
|
+
int heap;
|
|
360
|
+
} string_buf;
|
|
361
|
+
|
|
362
|
+
static const char *
|
|
363
|
+
string_bytes(napi_env env, napi_value value, string_buf *out)
|
|
364
|
+
{
|
|
365
|
+
size_t got = 0;
|
|
366
|
+
char *copy;
|
|
367
|
+
|
|
368
|
+
out->heap = 0;
|
|
369
|
+
if (napi_get_value_string_utf8(env, value, out->stack, sizeof(out->stack),
|
|
370
|
+
&got) != napi_ok) {
|
|
371
|
+
fail_argument(env, "expected a string");
|
|
372
|
+
return NULL;
|
|
373
|
+
}
|
|
374
|
+
if (got < sizeof(out->stack) - 1) {
|
|
375
|
+
out->len = got;
|
|
376
|
+
return out->stack;
|
|
377
|
+
}
|
|
378
|
+
{
|
|
379
|
+
size_t need = 0;
|
|
380
|
+
|
|
381
|
+
if (napi_get_value_string_utf8(env, value, NULL, 0, &need) != napi_ok) {
|
|
382
|
+
fail_argument(env, "expected a string");
|
|
383
|
+
return NULL;
|
|
384
|
+
}
|
|
385
|
+
copy = malloc(need + 1);
|
|
386
|
+
if (copy == NULL) {
|
|
387
|
+
fail_memory(env);
|
|
388
|
+
return NULL;
|
|
389
|
+
}
|
|
390
|
+
if (napi_get_value_string_utf8(env, value, copy, need + 1, &got) !=
|
|
391
|
+
napi_ok) {
|
|
392
|
+
free(copy);
|
|
393
|
+
fail_argument(env, "expected a string");
|
|
394
|
+
return NULL;
|
|
395
|
+
}
|
|
396
|
+
out->len = got;
|
|
397
|
+
out->heap = 1;
|
|
398
|
+
return copy;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
static void
|
|
403
|
+
string_free(string_buf *buf, const char *data)
|
|
404
|
+
{
|
|
405
|
+
if (buf->heap) {
|
|
406
|
+
free((void *)data);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/* ------------------------------------------------------- descriptor tables */
|
|
411
|
+
|
|
412
|
+
static desc_table *table_for(schema_handle *schema, const pbw_descriptor *desc);
|
|
413
|
+
|
|
414
|
+
static uint32_t
|
|
415
|
+
name_hash(const char *name)
|
|
416
|
+
{
|
|
417
|
+
uint32_t hash = 2166136261u;
|
|
418
|
+
|
|
419
|
+
while (*name != '\0') {
|
|
420
|
+
hash ^= (uint8_t)*name++;
|
|
421
|
+
hash *= 16777619u;
|
|
422
|
+
}
|
|
423
|
+
return hash;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
static uint32_t
|
|
427
|
+
table_by_name(const desc_table *table, const char *name)
|
|
428
|
+
{
|
|
429
|
+
uint32_t slot;
|
|
430
|
+
|
|
431
|
+
if (table->slots == NULL) {
|
|
432
|
+
return NOT_FOUND;
|
|
433
|
+
}
|
|
434
|
+
slot = name_hash(name) & table->mask;
|
|
435
|
+
for (;;) {
|
|
436
|
+
uint32_t entry = table->slots[slot];
|
|
437
|
+
|
|
438
|
+
if (entry == 0) {
|
|
439
|
+
return NOT_FOUND;
|
|
440
|
+
}
|
|
441
|
+
if (strcmp(table->fields[entry - 1].name, name) == 0) {
|
|
442
|
+
return entry - 1;
|
|
443
|
+
}
|
|
444
|
+
slot = (slot + 1) & table->mask;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/* Field index for a field number, or NOT_FOUND. A schema whose numbers fit a
|
|
449
|
+
* small direct table (the usual case) answers in one load; a sparse one falls
|
|
450
|
+
* back to the binary search over the sorted numbers. */
|
|
451
|
+
#define DENSE_LIMIT 4096u
|
|
452
|
+
|
|
453
|
+
static uint32_t
|
|
454
|
+
table_by_number(const desc_table *table, uint32_t number)
|
|
455
|
+
{
|
|
456
|
+
uint32_t lo = 0;
|
|
457
|
+
uint32_t hi = table->nfields;
|
|
458
|
+
|
|
459
|
+
if (table->dense != NULL) {
|
|
460
|
+
return number <= table->dense_max ? table->dense[number] : NOT_FOUND;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
while (lo < hi) {
|
|
464
|
+
uint32_t mid = lo + (hi - lo) / 2;
|
|
465
|
+
uint32_t index = table->by_number[mid];
|
|
466
|
+
uint32_t found = table->fields[index].number;
|
|
467
|
+
|
|
468
|
+
if (found < number) {
|
|
469
|
+
lo = mid + 1;
|
|
470
|
+
} else if (found > number) {
|
|
471
|
+
hi = mid;
|
|
472
|
+
} else {
|
|
473
|
+
return index;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return NOT_FOUND;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/* The sub-message table of a MESSAGE/GROUP field, built on first use. A
|
|
480
|
+
* recursive schema terminates because the table is cached before it descends. */
|
|
481
|
+
static desc_table *
|
|
482
|
+
table_sub(desc_table *table, field_entry *entry)
|
|
483
|
+
{
|
|
484
|
+
if (entry->sub == NULL) {
|
|
485
|
+
const pbw_descriptor *sub = pbw_field_message_type(entry->field);
|
|
486
|
+
|
|
487
|
+
if (sub != NULL) {
|
|
488
|
+
entry->sub = table_for(table->schema, sub);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return entry->sub;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
static int
|
|
495
|
+
by_number_compare(const void *a, const void *b)
|
|
496
|
+
{
|
|
497
|
+
uint64_t left = *(const uint64_t *)a;
|
|
498
|
+
uint64_t right = *(const uint64_t *)b;
|
|
499
|
+
|
|
500
|
+
return left < right ? -1 : (left > right ? 1 : 0);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
static desc_table *
|
|
504
|
+
table_build(schema_handle *schema, const pbw_descriptor *desc)
|
|
505
|
+
{
|
|
506
|
+
desc_table *table = calloc(1, sizeof(*table));
|
|
507
|
+
uint32_t nfields;
|
|
508
|
+
uint64_t *order = NULL;
|
|
509
|
+
uint32_t i;
|
|
510
|
+
uint32_t slot_bits = 3; /* 8 slots minimum */
|
|
511
|
+
|
|
512
|
+
if (table == NULL) {
|
|
513
|
+
return NULL;
|
|
514
|
+
}
|
|
515
|
+
table->schema = schema;
|
|
516
|
+
table->desc = desc;
|
|
517
|
+
table->eager = -1;
|
|
518
|
+
nfields = (uint32_t)pbw_descriptor_field_count(desc);
|
|
519
|
+
table->nfields = nfields;
|
|
520
|
+
if (nfields > 0) {
|
|
521
|
+
table->fields = calloc(nfields, sizeof(field_entry));
|
|
522
|
+
table->by_number = calloc(nfields, sizeof(uint32_t));
|
|
523
|
+
order = calloc(nfields, sizeof(uint64_t));
|
|
524
|
+
if (table->fields == NULL || table->by_number == NULL ||
|
|
525
|
+
order == NULL) {
|
|
526
|
+
free(order);
|
|
527
|
+
free(table->dense);
|
|
528
|
+
free(table->by_number);
|
|
529
|
+
free(table->fields);
|
|
530
|
+
free(table);
|
|
531
|
+
return NULL;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
for (i = 0; i < nfields; i++) {
|
|
535
|
+
const pbw_field *field = pbw_descriptor_field_at(desc, i);
|
|
536
|
+
field_entry *entry = &table->fields[i];
|
|
537
|
+
|
|
538
|
+
if (field == NULL) {
|
|
539
|
+
free(order);
|
|
540
|
+
free(table->dense);
|
|
541
|
+
free(table->by_number);
|
|
542
|
+
free(table->fields);
|
|
543
|
+
free(table);
|
|
544
|
+
return NULL;
|
|
545
|
+
}
|
|
546
|
+
entry->field = field;
|
|
547
|
+
entry->name = pbw_field_name(field);
|
|
548
|
+
entry->number = pbw_field_number(field);
|
|
549
|
+
entry->oneof = pbw_field_oneof_index(field);
|
|
550
|
+
if (entry->oneof > table->oneof_count) {
|
|
551
|
+
table->oneof_count = entry->oneof;
|
|
552
|
+
}
|
|
553
|
+
entry->type = (uint8_t)pbw_field_type(field);
|
|
554
|
+
entry->label = (uint8_t)pbw_field_label(field);
|
|
555
|
+
entry->wire = (uint8_t)wire_type_of((pbw_type)entry->type);
|
|
556
|
+
if (entry->type == PBW_TYPE_ENUM) {
|
|
557
|
+
const pbw_descriptor *enum_desc = pbw_field_message_type(field);
|
|
558
|
+
|
|
559
|
+
entry->closed =
|
|
560
|
+
enum_desc != NULL && !pbw_descriptor_is_proto3(enum_desc);
|
|
561
|
+
}
|
|
562
|
+
order[i] = ((uint64_t)entry->number << 32) | i;
|
|
563
|
+
}
|
|
564
|
+
if (nfields > 0) {
|
|
565
|
+
qsort(order, nfields, sizeof(*order), by_number_compare);
|
|
566
|
+
for (i = 0; i < nfields; i++) {
|
|
567
|
+
table->by_number[i] = (uint32_t)(order[i] & 0xffffffffu);
|
|
568
|
+
}
|
|
569
|
+
free(order);
|
|
570
|
+
table->dense_max = table->fields[table->by_number[nfields - 1]].number;
|
|
571
|
+
if (table->dense_max < DENSE_LIMIT) {
|
|
572
|
+
table->dense = malloc(((size_t)table->dense_max + 1) *
|
|
573
|
+
sizeof(*table->dense));
|
|
574
|
+
if (table->dense == NULL) {
|
|
575
|
+
free(table->by_number);
|
|
576
|
+
free(table->fields);
|
|
577
|
+
free(table);
|
|
578
|
+
return NULL;
|
|
579
|
+
}
|
|
580
|
+
for (i = 0; i <= table->dense_max; i++) {
|
|
581
|
+
table->dense[i] = NOT_FOUND;
|
|
582
|
+
}
|
|
583
|
+
for (i = 0; i < nfields; i++) {
|
|
584
|
+
table->dense[table->fields[i].number] = i;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
while (slot_bits < 6 || (1u << slot_bits) < nfields * 2) {
|
|
589
|
+
slot_bits++;
|
|
590
|
+
}
|
|
591
|
+
table->mask = (1u << slot_bits) - 1;
|
|
592
|
+
table->slots = calloc((size_t)table->mask + 1, sizeof(uint32_t));
|
|
593
|
+
if (table->slots == NULL) {
|
|
594
|
+
free(table->dense);
|
|
595
|
+
free(table->by_number);
|
|
596
|
+
free(table->fields);
|
|
597
|
+
free(table);
|
|
598
|
+
return NULL;
|
|
599
|
+
}
|
|
600
|
+
for (i = 0; i < nfields; i++) {
|
|
601
|
+
uint32_t slot = name_hash(table->fields[i].name) & table->mask;
|
|
602
|
+
|
|
603
|
+
while (table->slots[slot] != 0) {
|
|
604
|
+
slot = (slot + 1) & table->mask;
|
|
605
|
+
}
|
|
606
|
+
table->slots[slot] = i + 1;
|
|
607
|
+
}
|
|
608
|
+
return table;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
static desc_table *
|
|
612
|
+
table_for(schema_handle *schema, const pbw_descriptor *desc)
|
|
613
|
+
{
|
|
614
|
+
size_t i;
|
|
615
|
+
desc_table *table;
|
|
616
|
+
|
|
617
|
+
for (i = 0; i < schema->count; i++) {
|
|
618
|
+
if (schema->tables[i]->desc == desc) {
|
|
619
|
+
return schema->tables[i];
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
table = table_build(schema, desc);
|
|
623
|
+
if (table == NULL) {
|
|
624
|
+
return NULL;
|
|
625
|
+
}
|
|
626
|
+
if (schema->count == schema->cap) {
|
|
627
|
+
size_t cap = schema->cap == 0 ? 8 : schema->cap * 2;
|
|
628
|
+
desc_table **grown =
|
|
629
|
+
realloc(schema->tables, cap * sizeof(desc_table *));
|
|
630
|
+
|
|
631
|
+
if (grown == NULL) {
|
|
632
|
+
free(table->slots);
|
|
633
|
+
free(table->dense);
|
|
634
|
+
free(table->by_number);
|
|
635
|
+
free(table->fields);
|
|
636
|
+
free(table);
|
|
637
|
+
return NULL;
|
|
638
|
+
}
|
|
639
|
+
schema->tables = grown;
|
|
640
|
+
schema->cap = cap;
|
|
641
|
+
}
|
|
642
|
+
schema->tables[schema->count++] = table;
|
|
643
|
+
return table;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/* 1 when this descriptor or any message reachable from it declares a proto2
|
|
647
|
+
* `required` field. Such a schema decodes eagerly: PBW_ERR_REQUIRED is a
|
|
648
|
+
* property of the merged message (a field can arrive split across repeated
|
|
649
|
+
* occurrences of one submessage), which is exactly what the tree decoder
|
|
650
|
+
* checks after parsing. A cycle contributes nothing new, hence the
|
|
651
|
+
* "resolving" state instead of a cycle error. */
|
|
652
|
+
static int
|
|
653
|
+
table_is_eager(desc_table *table)
|
|
654
|
+
{
|
|
655
|
+
uint32_t i;
|
|
656
|
+
|
|
657
|
+
if (table->eager >= 0) {
|
|
658
|
+
return table->eager;
|
|
659
|
+
}
|
|
660
|
+
if (table->eager == -2) {
|
|
661
|
+
return 0;
|
|
662
|
+
}
|
|
663
|
+
table->eager = -2;
|
|
664
|
+
for (i = 0; i < table->nfields; i++) {
|
|
665
|
+
field_entry *entry = &table->fields[i];
|
|
666
|
+
|
|
667
|
+
if (entry->label == PBW_LABEL_REQUIRED) {
|
|
668
|
+
table->eager = 1;
|
|
669
|
+
return 1;
|
|
670
|
+
}
|
|
671
|
+
if (entry->type == PBW_TYPE_MESSAGE || entry->type == PBW_TYPE_GROUP) {
|
|
672
|
+
desc_table *sub = table_sub(table, entry);
|
|
673
|
+
|
|
674
|
+
if (sub != NULL && table_is_eager(sub)) {
|
|
675
|
+
table->eager = 1;
|
|
676
|
+
return 1;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
table->eager = 0;
|
|
681
|
+
return 0;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/* ------------------------------------------------------------ pbw -> JS */
|
|
685
|
+
|
|
686
|
+
static napi_value message_to_js(napi_env env, const pbw_message *msg);
|
|
687
|
+
|
|
688
|
+
/* The canonical bit pattern of one wire scalar: the form pbw_value stores, so
|
|
689
|
+
* that both representations hand JS the same number. */
|
|
690
|
+
static uint64_t
|
|
691
|
+
canonical_bits(pbw_type type, uint64_t raw)
|
|
692
|
+
{
|
|
693
|
+
switch (type) {
|
|
694
|
+
case PBW_TYPE_INT32:
|
|
695
|
+
case PBW_TYPE_ENUM:
|
|
696
|
+
return (uint64_t)(int64_t)(int32_t)raw;
|
|
697
|
+
case PBW_TYPE_UINT32:
|
|
698
|
+
return (uint32_t)raw;
|
|
699
|
+
case PBW_TYPE_SINT32:
|
|
700
|
+
return (uint64_t)(int64_t)(int32_t)rb_zigzag(raw, 32);
|
|
701
|
+
case PBW_TYPE_SINT64:
|
|
702
|
+
return rb_zigzag(raw, 64);
|
|
703
|
+
case PBW_TYPE_BOOL:
|
|
704
|
+
return raw != 0 ? 1u : 0u;
|
|
705
|
+
default:
|
|
706
|
+
return raw;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
static napi_value
|
|
711
|
+
scalar_to_js(napi_env env, pbw_type type, uint64_t bits)
|
|
712
|
+
{
|
|
713
|
+
napi_value out = NULL;
|
|
714
|
+
double number;
|
|
715
|
+
float single;
|
|
716
|
+
uint32_t low;
|
|
717
|
+
|
|
718
|
+
switch (type) {
|
|
719
|
+
case PBW_TYPE_DOUBLE:
|
|
720
|
+
memcpy(&number, &bits, sizeof(number));
|
|
721
|
+
napi_create_double(env, number, &out);
|
|
722
|
+
break;
|
|
723
|
+
case PBW_TYPE_FLOAT:
|
|
724
|
+
low = (uint32_t)bits;
|
|
725
|
+
memcpy(&single, &low, sizeof(single));
|
|
726
|
+
napi_create_double(env, (double)single, &out);
|
|
727
|
+
break;
|
|
728
|
+
case PBW_TYPE_BOOL:
|
|
729
|
+
napi_get_boolean(env, bits != 0, &out);
|
|
730
|
+
break;
|
|
731
|
+
case PBW_TYPE_INT32:
|
|
732
|
+
case PBW_TYPE_SINT32:
|
|
733
|
+
case PBW_TYPE_SFIXED32:
|
|
734
|
+
case PBW_TYPE_ENUM:
|
|
735
|
+
napi_create_int32(env, (int32_t)bits, &out);
|
|
736
|
+
break;
|
|
737
|
+
case PBW_TYPE_UINT32:
|
|
738
|
+
case PBW_TYPE_FIXED32:
|
|
739
|
+
napi_create_uint32(env, (uint32_t)bits, &out);
|
|
740
|
+
break;
|
|
741
|
+
case PBW_TYPE_INT64:
|
|
742
|
+
case PBW_TYPE_SINT64:
|
|
743
|
+
case PBW_TYPE_SFIXED64:
|
|
744
|
+
/* Exact: a JS number cannot hold every 64-bit integer. */
|
|
745
|
+
napi_create_bigint_int64(env, (int64_t)bits, &out);
|
|
746
|
+
break;
|
|
747
|
+
default: /* PBW_TYPE_UINT64, PBW_TYPE_FIXED64 */
|
|
748
|
+
napi_create_bigint_uint64(env, bits, &out);
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
return out;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
static napi_value
|
|
755
|
+
bytes_to_js(napi_env env, pbw_type type, const void *data, size_t len)
|
|
756
|
+
{
|
|
757
|
+
napi_value out = NULL;
|
|
758
|
+
|
|
759
|
+
if (type == PBW_TYPE_STRING) {
|
|
760
|
+
napi_create_string_utf8(env, (const char *)data, len, &out);
|
|
761
|
+
} else {
|
|
762
|
+
napi_create_buffer_copy(env, len, data, NULL, &out);
|
|
763
|
+
}
|
|
764
|
+
return out;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
static napi_value
|
|
768
|
+
value_to_js(napi_env env, pbw_type type, const pbw_value *value)
|
|
769
|
+
{
|
|
770
|
+
napi_value out = NULL;
|
|
771
|
+
|
|
772
|
+
switch (pbw_value_get_kind(value)) {
|
|
773
|
+
case PBW_VALUE_LIST: {
|
|
774
|
+
size_t count = pbw_value_count(value);
|
|
775
|
+
size_t i;
|
|
776
|
+
|
|
777
|
+
TRY(napi_create_array_with_length(env, count, &out));
|
|
778
|
+
for (i = 0; i < count; i++) {
|
|
779
|
+
napi_value item = value_to_js(env, type, pbw_value_at(value, i));
|
|
780
|
+
|
|
781
|
+
if (item == NULL) {
|
|
782
|
+
return NULL;
|
|
783
|
+
}
|
|
784
|
+
TRY(napi_set_element(env, out, (uint32_t)i, item));
|
|
785
|
+
}
|
|
786
|
+
return out;
|
|
787
|
+
}
|
|
788
|
+
case PBW_VALUE_MESSAGE:
|
|
789
|
+
return message_to_js(env, pbw_value_get_message(value));
|
|
790
|
+
case PBW_VALUE_BYTES: {
|
|
791
|
+
size_t len = 0;
|
|
792
|
+
const void *bytes = pbw_value_get_bytes(value, &len);
|
|
793
|
+
|
|
794
|
+
return bytes_to_js(env, type, bytes, len);
|
|
795
|
+
}
|
|
796
|
+
case PBW_VALUE_UINT:
|
|
797
|
+
default:
|
|
798
|
+
return scalar_to_js(env, type, pbw_value_get_uint(value));
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
static napi_value
|
|
803
|
+
message_to_js(napi_env env, const pbw_message *msg)
|
|
804
|
+
{
|
|
805
|
+
napi_value out;
|
|
806
|
+
size_t index;
|
|
807
|
+
|
|
808
|
+
TRY(napi_create_object(env, &out));
|
|
809
|
+
for (index = 0;; index++) {
|
|
810
|
+
pbw_value *value = NULL;
|
|
811
|
+
const pbw_field *field = pbw_message_field_at(msg, index, &value);
|
|
812
|
+
napi_value item;
|
|
813
|
+
|
|
814
|
+
if (field == NULL) {
|
|
815
|
+
return out;
|
|
816
|
+
}
|
|
817
|
+
item = value_to_js(env, pbw_field_type(field), value);
|
|
818
|
+
if (item == NULL) {
|
|
819
|
+
return NULL;
|
|
820
|
+
}
|
|
821
|
+
TRY(napi_set_named_property(env, out, pbw_field_name(field), item));
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/* ------------------------------------------------------------ JS -> pbw */
|
|
826
|
+
|
|
827
|
+
static int js_to_message(napi_env env, pbw_message *msg, desc_table *table,
|
|
828
|
+
napi_value object);
|
|
829
|
+
|
|
830
|
+
static int
|
|
831
|
+
js_uint64(napi_env env, napi_value value, uint64_t *out)
|
|
832
|
+
{
|
|
833
|
+
napi_valuetype kind;
|
|
834
|
+
|
|
835
|
+
if (napi_typeof(env, value, &kind) != napi_ok) {
|
|
836
|
+
return 0;
|
|
837
|
+
}
|
|
838
|
+
if (kind == napi_bigint) {
|
|
839
|
+
int64_t bits = 0;
|
|
840
|
+
bool lossless = true;
|
|
841
|
+
|
|
842
|
+
if (napi_get_value_bigint_int64(env, value, &bits, &lossless) !=
|
|
843
|
+
napi_ok) {
|
|
844
|
+
return 0;
|
|
845
|
+
}
|
|
846
|
+
*out = (uint64_t)bits; /* two's complement: the raw bit pattern */
|
|
847
|
+
return 1;
|
|
848
|
+
}
|
|
849
|
+
if (kind == napi_number) {
|
|
850
|
+
double number;
|
|
851
|
+
|
|
852
|
+
if (napi_get_value_double(env, value, &number) != napi_ok) {
|
|
853
|
+
return 0;
|
|
854
|
+
}
|
|
855
|
+
*out = (uint64_t)(int64_t)number;
|
|
856
|
+
return 1;
|
|
857
|
+
}
|
|
858
|
+
return 0;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
static pbw_value *
|
|
862
|
+
value_from_js(napi_env env, pbw_type type, napi_value value)
|
|
863
|
+
{
|
|
864
|
+
pbw_value *out = NULL;
|
|
865
|
+
bool flag = false;
|
|
866
|
+
|
|
867
|
+
switch (type) {
|
|
868
|
+
case PBW_TYPE_DOUBLE: {
|
|
869
|
+
double number;
|
|
870
|
+
|
|
871
|
+
if (napi_get_value_double(env, value, &number) == napi_ok) {
|
|
872
|
+
out = pbw_value_new_double(number);
|
|
873
|
+
}
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
876
|
+
case PBW_TYPE_FLOAT: {
|
|
877
|
+
double number;
|
|
878
|
+
|
|
879
|
+
if (napi_get_value_double(env, value, &number) == napi_ok) {
|
|
880
|
+
out = pbw_value_new_float((float)number);
|
|
881
|
+
}
|
|
882
|
+
break;
|
|
883
|
+
}
|
|
884
|
+
case PBW_TYPE_BOOL:
|
|
885
|
+
if (napi_get_value_bool(env, value, &flag) == napi_ok) {
|
|
886
|
+
out = pbw_value_new_bool(flag);
|
|
887
|
+
}
|
|
888
|
+
break;
|
|
889
|
+
case PBW_TYPE_INT32:
|
|
890
|
+
case PBW_TYPE_SINT32:
|
|
891
|
+
case PBW_TYPE_SFIXED32:
|
|
892
|
+
case PBW_TYPE_ENUM: {
|
|
893
|
+
int32_t number;
|
|
894
|
+
|
|
895
|
+
if (napi_get_value_int32(env, value, &number) == napi_ok) {
|
|
896
|
+
out = pbw_value_new_uint((uint64_t)(int64_t)number);
|
|
897
|
+
}
|
|
898
|
+
break;
|
|
899
|
+
}
|
|
900
|
+
case PBW_TYPE_UINT32:
|
|
901
|
+
case PBW_TYPE_FIXED32: {
|
|
902
|
+
uint32_t number;
|
|
903
|
+
|
|
904
|
+
if (napi_get_value_uint32(env, value, &number) == napi_ok) {
|
|
905
|
+
out = pbw_value_new_uint(number);
|
|
906
|
+
}
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
909
|
+
case PBW_TYPE_STRING: {
|
|
910
|
+
string_buf buf;
|
|
911
|
+
napi_valuetype kind;
|
|
912
|
+
const char *data;
|
|
913
|
+
|
|
914
|
+
if (napi_typeof(env, value, &kind) != napi_ok ||
|
|
915
|
+
kind != napi_string) {
|
|
916
|
+
break; /* "value does not match the field type" below */
|
|
917
|
+
}
|
|
918
|
+
data = string_bytes(env, value, &buf);
|
|
919
|
+
if (data == NULL) {
|
|
920
|
+
return NULL;
|
|
921
|
+
}
|
|
922
|
+
out = pbw_value_new_bytes(data, buf.len);
|
|
923
|
+
string_free(&buf, data);
|
|
924
|
+
break;
|
|
925
|
+
}
|
|
926
|
+
case PBW_TYPE_BYTES: {
|
|
927
|
+
void *data = NULL;
|
|
928
|
+
size_t len = 0;
|
|
929
|
+
|
|
930
|
+
if (napi_get_buffer_info(env, value, &data, &len) == napi_ok) {
|
|
931
|
+
out = pbw_value_new_bytes(data, len);
|
|
932
|
+
}
|
|
933
|
+
break;
|
|
934
|
+
}
|
|
935
|
+
default: { /* 64-bit integers */
|
|
936
|
+
uint64_t bits;
|
|
937
|
+
|
|
938
|
+
if (js_uint64(env, value, &bits)) {
|
|
939
|
+
out = pbw_value_new_uint(bits);
|
|
940
|
+
}
|
|
941
|
+
break;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
if (out == NULL) {
|
|
945
|
+
fail_argument(env, "value does not match the field type");
|
|
946
|
+
}
|
|
947
|
+
return out;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/* One value for one field: nested messages and repeated elements both land
|
|
951
|
+
* here. */
|
|
952
|
+
static int
|
|
953
|
+
set_element(napi_env env, pbw_message *msg, desc_table *table,
|
|
954
|
+
field_entry *entry, napi_value value)
|
|
955
|
+
{
|
|
956
|
+
if (entry->type == PBW_TYPE_MESSAGE || entry->type == PBW_TYPE_GROUP) {
|
|
957
|
+
pbw_message *child;
|
|
958
|
+
|
|
959
|
+
if (entry->label != PBW_LABEL_REPEATED) {
|
|
960
|
+
/* set() replaces a field: no merge into the previous submessage. */
|
|
961
|
+
pbw_message_clear(msg, entry->number);
|
|
962
|
+
}
|
|
963
|
+
child = pbw_message_set_message(msg, entry->number);
|
|
964
|
+
if (child == NULL) {
|
|
965
|
+
fail_argument(env, "cannot set the nested message field");
|
|
966
|
+
return 0;
|
|
967
|
+
}
|
|
968
|
+
return js_to_message(env, child, table_sub(table, entry), value);
|
|
969
|
+
}
|
|
970
|
+
{
|
|
971
|
+
pbw_value *item = value_from_js(env, (pbw_type)entry->type, value);
|
|
972
|
+
pbw_status status;
|
|
973
|
+
|
|
974
|
+
if (item == NULL) {
|
|
975
|
+
return 0;
|
|
976
|
+
}
|
|
977
|
+
status = pbw_message_set_field(msg, entry->field, item);
|
|
978
|
+
if (status != PBW_OK) {
|
|
979
|
+
pbw_value_free(item); /* ownership transfers only on success */
|
|
980
|
+
fail_status(env, entry->name, status);
|
|
981
|
+
return 0;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
return 1;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
static int
|
|
988
|
+
set_field(napi_env env, pbw_message *msg, desc_table *table,
|
|
989
|
+
field_entry *entry, napi_value value)
|
|
990
|
+
{
|
|
991
|
+
napi_valuetype kind;
|
|
992
|
+
bool is_array = false;
|
|
993
|
+
uint32_t count = 0;
|
|
994
|
+
uint32_t i;
|
|
995
|
+
|
|
996
|
+
if (napi_typeof(env, value, &kind) != napi_ok) {
|
|
997
|
+
return 0;
|
|
998
|
+
}
|
|
999
|
+
if (kind == napi_undefined || kind == napi_null) {
|
|
1000
|
+
pbw_message_clear(msg, entry->number);
|
|
1001
|
+
return 1;
|
|
1002
|
+
}
|
|
1003
|
+
if (entry->label != PBW_LABEL_REPEATED) {
|
|
1004
|
+
return set_element(env, msg, table, entry, value);
|
|
1005
|
+
}
|
|
1006
|
+
if (napi_is_array(env, value, &is_array) != napi_ok || !is_array) {
|
|
1007
|
+
fail_argument(env, "a repeated field takes an array");
|
|
1008
|
+
return 0;
|
|
1009
|
+
}
|
|
1010
|
+
if (napi_get_array_length(env, value, &count) != napi_ok) {
|
|
1011
|
+
return 0;
|
|
1012
|
+
}
|
|
1013
|
+
/* set() replaces: an array write does not append to what was there. */
|
|
1014
|
+
pbw_message_clear(msg, entry->number);
|
|
1015
|
+
for (i = 0; i < count; i++) {
|
|
1016
|
+
napi_value item;
|
|
1017
|
+
|
|
1018
|
+
if (napi_get_element(env, value, i, &item) != napi_ok) {
|
|
1019
|
+
return 0;
|
|
1020
|
+
}
|
|
1021
|
+
if (!set_element(env, msg, table, entry, item)) {
|
|
1022
|
+
return 0;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
return 1;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
static int
|
|
1029
|
+
js_to_message(napi_env env, pbw_message *msg, desc_table *table,
|
|
1030
|
+
napi_value object)
|
|
1031
|
+
{
|
|
1032
|
+
napi_valuetype kind;
|
|
1033
|
+
napi_value names;
|
|
1034
|
+
uint32_t count = 0;
|
|
1035
|
+
uint32_t i;
|
|
1036
|
+
|
|
1037
|
+
if (napi_typeof(env, object, &kind) != napi_ok) {
|
|
1038
|
+
return 0;
|
|
1039
|
+
}
|
|
1040
|
+
if (kind != napi_object) {
|
|
1041
|
+
fail_argument(env, "a nested message takes an object");
|
|
1042
|
+
return 0;
|
|
1043
|
+
}
|
|
1044
|
+
if (napi_get_all_property_names(
|
|
1045
|
+
env, object, napi_key_own_only,
|
|
1046
|
+
(napi_key_filter)(napi_key_enumerable | napi_key_skip_symbols),
|
|
1047
|
+
napi_key_numbers_to_strings, &names) != napi_ok) {
|
|
1048
|
+
return 0;
|
|
1049
|
+
}
|
|
1050
|
+
if (napi_get_array_length(env, names, &count) != napi_ok) {
|
|
1051
|
+
return 0;
|
|
1052
|
+
}
|
|
1053
|
+
for (i = 0; i < count; i++) {
|
|
1054
|
+
string_buf buf;
|
|
1055
|
+
napi_value key;
|
|
1056
|
+
napi_value value;
|
|
1057
|
+
const char *name;
|
|
1058
|
+
uint32_t index;
|
|
1059
|
+
int ok;
|
|
1060
|
+
|
|
1061
|
+
if (napi_get_element(env, names, i, &key) != napi_ok) {
|
|
1062
|
+
return 0;
|
|
1063
|
+
}
|
|
1064
|
+
name = string_bytes(env, key, &buf);
|
|
1065
|
+
if (name == NULL) {
|
|
1066
|
+
return 0;
|
|
1067
|
+
}
|
|
1068
|
+
index = table_by_name(table, name);
|
|
1069
|
+
if (index == NOT_FOUND) {
|
|
1070
|
+
char message[512];
|
|
1071
|
+
|
|
1072
|
+
snprintf(message, sizeof(message), "pbwire: %s has no field %s",
|
|
1073
|
+
pbw_descriptor_name(table->desc), name);
|
|
1074
|
+
napi_throw_error(env, NULL, message);
|
|
1075
|
+
string_free(&buf, name);
|
|
1076
|
+
return 0;
|
|
1077
|
+
}
|
|
1078
|
+
ok = napi_get_property(env, object, key, &value) == napi_ok &&
|
|
1079
|
+
set_field(env, msg, table, &table->fields[index], value);
|
|
1080
|
+
string_free(&buf, name);
|
|
1081
|
+
if (!ok) {
|
|
1082
|
+
return 0;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
return 1;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/* -------------------------------------------------------- wire -> slices */
|
|
1089
|
+
|
|
1090
|
+
typedef enum {
|
|
1091
|
+
SCAN_OK = 0,
|
|
1092
|
+
SCAN_INVALID, /* malformed wire data */
|
|
1093
|
+
SCAN_LIMIT, /* nesting past PBW_MAX_DEPTH */
|
|
1094
|
+
SCAN_NOMEM /* slice list could not grow */
|
|
1095
|
+
} scan_status;
|
|
1096
|
+
|
|
1097
|
+
static int
|
|
1098
|
+
slices_record(slice_list *list, uint32_t index, uint32_t off, uint32_t len)
|
|
1099
|
+
{
|
|
1100
|
+
if (list->count == list->cap) {
|
|
1101
|
+
uint32_t cap = list->cap == 0 ? 8 : list->cap * 2;
|
|
1102
|
+
slice_t *grown;
|
|
1103
|
+
|
|
1104
|
+
if (list->items == list->inline_) {
|
|
1105
|
+
grown = malloc((size_t)cap * sizeof(*grown));
|
|
1106
|
+
if (grown != NULL) {
|
|
1107
|
+
memcpy(grown, list->inline_,
|
|
1108
|
+
(size_t)list->count * sizeof(*grown));
|
|
1109
|
+
}
|
|
1110
|
+
} else {
|
|
1111
|
+
grown = realloc(list->items, (size_t)cap * sizeof(*grown));
|
|
1112
|
+
}
|
|
1113
|
+
if (grown == NULL) {
|
|
1114
|
+
return 0;
|
|
1115
|
+
}
|
|
1116
|
+
list->items = grown;
|
|
1117
|
+
list->cap = cap;
|
|
1118
|
+
}
|
|
1119
|
+
list->items[list->count].index = index;
|
|
1120
|
+
list->items[list->count].off = off;
|
|
1121
|
+
list->items[list->count].len = len;
|
|
1122
|
+
list->count++;
|
|
1123
|
+
return 1;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
static void
|
|
1127
|
+
slices_release(slice_list *list)
|
|
1128
|
+
{
|
|
1129
|
+
if (list->items != NULL && list->items != list->inline_) {
|
|
1130
|
+
free(list->items);
|
|
1131
|
+
}
|
|
1132
|
+
list->items = NULL;
|
|
1133
|
+
list->count = 0;
|
|
1134
|
+
list->cap = 0;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
static scan_status skip_group(const uint8_t **pp, const uint8_t *end,
|
|
1138
|
+
uint32_t number, uint32_t depth);
|
|
1139
|
+
|
|
1140
|
+
static scan_status
|
|
1141
|
+
skip_value(const uint8_t **pp, const uint8_t *end, uint32_t number,
|
|
1142
|
+
uint32_t wire_type, uint32_t depth)
|
|
1143
|
+
{
|
|
1144
|
+
uint64_t scratch;
|
|
1145
|
+
size_t len;
|
|
1146
|
+
|
|
1147
|
+
switch (wire_type) {
|
|
1148
|
+
case 0:
|
|
1149
|
+
return rb_varint(pp, end, &scratch) ? SCAN_OK : SCAN_INVALID;
|
|
1150
|
+
case 1:
|
|
1151
|
+
if ((size_t)(end - *pp) < 8) {
|
|
1152
|
+
return SCAN_INVALID;
|
|
1153
|
+
}
|
|
1154
|
+
*pp += 8;
|
|
1155
|
+
return SCAN_OK;
|
|
1156
|
+
case 2:
|
|
1157
|
+
if (!rb_len(pp, end, &len) || len > (size_t)(end - *pp)) {
|
|
1158
|
+
return SCAN_INVALID;
|
|
1159
|
+
}
|
|
1160
|
+
*pp += len;
|
|
1161
|
+
return SCAN_OK;
|
|
1162
|
+
case 3:
|
|
1163
|
+
return skip_group(pp, end, number, depth);
|
|
1164
|
+
case 5:
|
|
1165
|
+
if ((size_t)(end - *pp) < 4) {
|
|
1166
|
+
return SCAN_INVALID;
|
|
1167
|
+
}
|
|
1168
|
+
*pp += 4;
|
|
1169
|
+
return SCAN_OK;
|
|
1170
|
+
default:
|
|
1171
|
+
return SCAN_INVALID;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/* Skips a legacy group up to and including its matching END_GROUP tag. */
|
|
1176
|
+
static scan_status
|
|
1177
|
+
skip_group(const uint8_t **pp, const uint8_t *end, uint32_t number,
|
|
1178
|
+
uint32_t depth)
|
|
1179
|
+
{
|
|
1180
|
+
if (depth >= PBW_MAX_DEPTH) {
|
|
1181
|
+
return SCAN_LIMIT;
|
|
1182
|
+
}
|
|
1183
|
+
for (;;) {
|
|
1184
|
+
uint32_t tag;
|
|
1185
|
+
uint32_t wire_type;
|
|
1186
|
+
|
|
1187
|
+
if (!rb_tag(pp, end, &tag)) {
|
|
1188
|
+
return SCAN_INVALID;
|
|
1189
|
+
}
|
|
1190
|
+
wire_type = tag & 7;
|
|
1191
|
+
if ((tag >> 3) == 0) {
|
|
1192
|
+
return SCAN_INVALID;
|
|
1193
|
+
}
|
|
1194
|
+
if (wire_type == 4) {
|
|
1195
|
+
return (tag >> 3) == number ? SCAN_OK : SCAN_INVALID;
|
|
1196
|
+
}
|
|
1197
|
+
{
|
|
1198
|
+
scan_status status =
|
|
1199
|
+
skip_value(pp, end, tag >> 3, wire_type, depth + 1);
|
|
1200
|
+
|
|
1201
|
+
if (status != SCAN_OK) {
|
|
1202
|
+
return status;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/* A packed run must hold a whole number of complete values. */
|
|
1209
|
+
static int
|
|
1210
|
+
packed_run_ok(pbw_type type, const uint8_t *data, size_t len)
|
|
1211
|
+
{
|
|
1212
|
+
const uint8_t *p = data;
|
|
1213
|
+
const uint8_t *end = data + len;
|
|
1214
|
+
uint64_t scratch;
|
|
1215
|
+
|
|
1216
|
+
switch (type) {
|
|
1217
|
+
case PBW_TYPE_FLOAT:
|
|
1218
|
+
case PBW_TYPE_FIXED32:
|
|
1219
|
+
case PBW_TYPE_SFIXED32:
|
|
1220
|
+
return len % 4 == 0;
|
|
1221
|
+
case PBW_TYPE_DOUBLE:
|
|
1222
|
+
case PBW_TYPE_FIXED64:
|
|
1223
|
+
case PBW_TYPE_SFIXED64:
|
|
1224
|
+
return len % 8 == 0;
|
|
1225
|
+
default:
|
|
1226
|
+
while (p < end) {
|
|
1227
|
+
if (!rb_varint(&p, end, &scratch)) {
|
|
1228
|
+
return 0;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
return 1;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/* One tag scan of one message range; with `out` set it also records a slice
|
|
1236
|
+
* per known wire field. A non-zero `group_number` makes the range a legacy
|
|
1237
|
+
* group body, which ends at the matching END_GROUP tag instead of the buffer
|
|
1238
|
+
* end, and reports through `consumed` how much of the range that took (the
|
|
1239
|
+
* closing tag included). */
|
|
1240
|
+
static scan_status
|
|
1241
|
+
scan_range(desc_table *table, const uint8_t *base, uint32_t off, uint32_t len,
|
|
1242
|
+
uint32_t depth, uint32_t group_number, slice_list *out,
|
|
1243
|
+
size_t *consumed)
|
|
1244
|
+
{
|
|
1245
|
+
const uint8_t *p = base + off;
|
|
1246
|
+
const uint8_t *end = p + len;
|
|
1247
|
+
|
|
1248
|
+
while (p < end) {
|
|
1249
|
+
uint32_t tag;
|
|
1250
|
+
uint32_t number;
|
|
1251
|
+
uint32_t wire_type;
|
|
1252
|
+
uint32_t index;
|
|
1253
|
+
field_entry *entry;
|
|
1254
|
+
scan_status status;
|
|
1255
|
+
|
|
1256
|
+
if (!rb_tag(&p, end, &tag)) {
|
|
1257
|
+
return SCAN_INVALID;
|
|
1258
|
+
}
|
|
1259
|
+
number = tag >> 3;
|
|
1260
|
+
wire_type = tag & 7;
|
|
1261
|
+
if (number == 0 || wire_type > 5) {
|
|
1262
|
+
return SCAN_INVALID;
|
|
1263
|
+
}
|
|
1264
|
+
if (wire_type == 4) {
|
|
1265
|
+
/* END_GROUP closes only the group that started it. */
|
|
1266
|
+
if (group_number != 0 && number == group_number) {
|
|
1267
|
+
if (consumed != NULL) {
|
|
1268
|
+
*consumed = (size_t)(p - (base + off));
|
|
1269
|
+
}
|
|
1270
|
+
return SCAN_OK;
|
|
1271
|
+
}
|
|
1272
|
+
return SCAN_INVALID;
|
|
1273
|
+
}
|
|
1274
|
+
index = table_by_number(table, number);
|
|
1275
|
+
if (index == NOT_FOUND) {
|
|
1276
|
+
status = skip_value(&p, end, number, wire_type, depth);
|
|
1277
|
+
if (status != SCAN_OK) {
|
|
1278
|
+
return status;
|
|
1279
|
+
}
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
entry = &table->fields[index];
|
|
1283
|
+
if (wire_type == entry->wire) {
|
|
1284
|
+
if (entry->type == PBW_TYPE_MESSAGE) {
|
|
1285
|
+
uint32_t payload;
|
|
1286
|
+
size_t payload_len;
|
|
1287
|
+
|
|
1288
|
+
if (depth >= PBW_MAX_DEPTH) {
|
|
1289
|
+
return SCAN_LIMIT;
|
|
1290
|
+
}
|
|
1291
|
+
if (!rb_len(&p, end, &payload_len) ||
|
|
1292
|
+
payload_len > (size_t)(end - p)) {
|
|
1293
|
+
return SCAN_INVALID;
|
|
1294
|
+
}
|
|
1295
|
+
payload = (uint32_t)(p - base);
|
|
1296
|
+
status = scan_range(table_sub(table, entry), base, payload,
|
|
1297
|
+
(uint32_t)payload_len, depth + 1, 0, NULL,
|
|
1298
|
+
NULL);
|
|
1299
|
+
if (status != SCAN_OK) {
|
|
1300
|
+
return status;
|
|
1301
|
+
}
|
|
1302
|
+
p += payload_len;
|
|
1303
|
+
if (out != NULL &&
|
|
1304
|
+
!slices_record(out, index, payload, (uint32_t)payload_len)) {
|
|
1305
|
+
return SCAN_NOMEM;
|
|
1306
|
+
}
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
1309
|
+
if (entry->type == PBW_TYPE_GROUP) {
|
|
1310
|
+
uint32_t body = (uint32_t)(p - base);
|
|
1311
|
+
size_t body_len = 0;
|
|
1312
|
+
uint32_t closing;
|
|
1313
|
+
|
|
1314
|
+
if (depth >= PBW_MAX_DEPTH) {
|
|
1315
|
+
return SCAN_LIMIT;
|
|
1316
|
+
}
|
|
1317
|
+
status = scan_range(table_sub(table, entry), base, body,
|
|
1318
|
+
(uint32_t)(end - p), depth + 1, number,
|
|
1319
|
+
NULL, &body_len);
|
|
1320
|
+
if (status != SCAN_OK) {
|
|
1321
|
+
return status;
|
|
1322
|
+
}
|
|
1323
|
+
closing = tag_size(number);
|
|
1324
|
+
p = base + body + body_len;
|
|
1325
|
+
if (out != NULL &&
|
|
1326
|
+
!slices_record(out, index, body,
|
|
1327
|
+
(uint32_t)(body_len - closing))) {
|
|
1328
|
+
return SCAN_NOMEM;
|
|
1329
|
+
}
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
if (entry->type == PBW_TYPE_STRING ||
|
|
1333
|
+
entry->type == PBW_TYPE_BYTES) {
|
|
1334
|
+
uint32_t payload;
|
|
1335
|
+
size_t payload_len;
|
|
1336
|
+
|
|
1337
|
+
if (!rb_len(&p, end, &payload_len) ||
|
|
1338
|
+
payload_len > (size_t)(end - p)) {
|
|
1339
|
+
return SCAN_INVALID;
|
|
1340
|
+
}
|
|
1341
|
+
payload = (uint32_t)(p - base);
|
|
1342
|
+
p += payload_len;
|
|
1343
|
+
if (out != NULL &&
|
|
1344
|
+
!slices_record(out, index, payload,
|
|
1345
|
+
(uint32_t)payload_len)) {
|
|
1346
|
+
return SCAN_NOMEM;
|
|
1347
|
+
}
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
{
|
|
1351
|
+
uint32_t value_off = (uint32_t)(p - base);
|
|
1352
|
+
|
|
1353
|
+
status = skip_value(&p, end, number, wire_type, depth);
|
|
1354
|
+
if (status != SCAN_OK) {
|
|
1355
|
+
return status;
|
|
1356
|
+
}
|
|
1357
|
+
if (out != NULL && !slices_record(out, index, value_off, 0)) {
|
|
1358
|
+
return SCAN_NOMEM;
|
|
1359
|
+
}
|
|
1360
|
+
continue;
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
if (wire_type == 2 && entry->label == PBW_LABEL_REPEATED &&
|
|
1364
|
+
packable((pbw_type)entry->type)) {
|
|
1365
|
+
uint32_t payload;
|
|
1366
|
+
size_t payload_len;
|
|
1367
|
+
|
|
1368
|
+
if (!rb_len(&p, end, &payload_len) ||
|
|
1369
|
+
payload_len > (size_t)(end - p)) {
|
|
1370
|
+
return SCAN_INVALID;
|
|
1371
|
+
}
|
|
1372
|
+
payload = (uint32_t)(p - base);
|
|
1373
|
+
if (!packed_run_ok((pbw_type)entry->type, base + payload,
|
|
1374
|
+
payload_len)) {
|
|
1375
|
+
return SCAN_INVALID;
|
|
1376
|
+
}
|
|
1377
|
+
p += payload_len;
|
|
1378
|
+
/* An empty run carries no value: the field stays absent, exactly
|
|
1379
|
+
* as the tree decoder leaves it. Not recording it also keeps
|
|
1380
|
+
* `len == 0` meaning "scalar" for a packable field. */
|
|
1381
|
+
if (out != NULL && payload_len > 0 &&
|
|
1382
|
+
!slices_record(out, index, payload, (uint32_t)payload_len)) {
|
|
1383
|
+
return SCAN_NOMEM;
|
|
1384
|
+
}
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
/* A known field with an unexpected wire type is unknown data: the
|
|
1388
|
+
* reference parsers keep it as raw bytes, so the scan skips it. */
|
|
1389
|
+
status = skip_value(&p, end, number, wire_type, depth);
|
|
1390
|
+
if (status != SCAN_OK) {
|
|
1391
|
+
return status;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
if (group_number != 0) {
|
|
1395
|
+
return SCAN_INVALID; /* END_GROUP never came */
|
|
1396
|
+
}
|
|
1397
|
+
if (consumed != NULL) {
|
|
1398
|
+
*consumed = (size_t)(p - (base + off));
|
|
1399
|
+
}
|
|
1400
|
+
return SCAN_OK;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/* ------------------------------------------------------------ wire -> JS */
|
|
1404
|
+
|
|
1405
|
+
/* A packable field's slice is a packed run when it carries a payload. */
|
|
1406
|
+
static int
|
|
1407
|
+
slice_is_packed(const field_entry *entry, const slice_t *slice)
|
|
1408
|
+
{
|
|
1409
|
+
return entry->label == PBW_LABEL_REPEATED &&
|
|
1410
|
+
packable((pbw_type)entry->type) && slice->len != 0;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
/* Reads one scalar value at a validated offset. */
|
|
1414
|
+
static int
|
|
1415
|
+
read_value(const uint8_t **pp, const uint8_t *end, pbw_type type,
|
|
1416
|
+
uint64_t *out)
|
|
1417
|
+
{
|
|
1418
|
+
uint64_t raw;
|
|
1419
|
+
|
|
1420
|
+
switch (type) {
|
|
1421
|
+
case PBW_TYPE_DOUBLE:
|
|
1422
|
+
case PBW_TYPE_FIXED64:
|
|
1423
|
+
case PBW_TYPE_SFIXED64:
|
|
1424
|
+
if ((size_t)(end - *pp) < 8) {
|
|
1425
|
+
return 0;
|
|
1426
|
+
}
|
|
1427
|
+
*out = rb_le64(*pp);
|
|
1428
|
+
*pp += 8;
|
|
1429
|
+
return 1;
|
|
1430
|
+
case PBW_TYPE_FLOAT:
|
|
1431
|
+
case PBW_TYPE_FIXED32:
|
|
1432
|
+
case PBW_TYPE_SFIXED32:
|
|
1433
|
+
if ((size_t)(end - *pp) < 4) {
|
|
1434
|
+
return 0;
|
|
1435
|
+
}
|
|
1436
|
+
*out = rb_le32(*pp);
|
|
1437
|
+
*pp += 4;
|
|
1438
|
+
return 1;
|
|
1439
|
+
default:
|
|
1440
|
+
if (!rb_varint(pp, end, &raw)) {
|
|
1441
|
+
return 0;
|
|
1442
|
+
}
|
|
1443
|
+
*out = canonical_bits(type, raw);
|
|
1444
|
+
return 1;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
/* A proto2 closed enum keeps only the numbers its declaration lists; the
|
|
1449
|
+
* reference parsers move the others to the unknown fields, which for a
|
|
1450
|
+
* value-materializing reader means the field does not take that value. */
|
|
1451
|
+
static int
|
|
1452
|
+
enum_known(field_entry *entry, uint64_t bits)
|
|
1453
|
+
{
|
|
1454
|
+
const pbw_descriptor *enum_desc;
|
|
1455
|
+
|
|
1456
|
+
if (!entry->closed) {
|
|
1457
|
+
return 1;
|
|
1458
|
+
}
|
|
1459
|
+
enum_desc = pbw_field_message_type(entry->field);
|
|
1460
|
+
return enum_desc != NULL &&
|
|
1461
|
+
pbw_enum_find_name(enum_desc, (int32_t)bits, NULL);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
static int
|
|
1465
|
+
slice_known(field_entry *entry, const uint8_t *base, uint32_t wire_len,
|
|
1466
|
+
const slice_t *slice)
|
|
1467
|
+
{
|
|
1468
|
+
const uint8_t *p;
|
|
1469
|
+
uint64_t bits;
|
|
1470
|
+
|
|
1471
|
+
if (!entry->closed || slice->len != 0) {
|
|
1472
|
+
return 1; /* not a closed enum, or a packed run (filtered per item) */
|
|
1473
|
+
}
|
|
1474
|
+
p = base + slice->off;
|
|
1475
|
+
if (!read_value(&p, base + wire_len, (pbw_type)entry->type, &bits)) {
|
|
1476
|
+
return 0;
|
|
1477
|
+
}
|
|
1478
|
+
return enum_known(entry, bits);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
static napi_value wire_object_to_js(napi_env env, desc_table *table,
|
|
1482
|
+
const uint8_t *base, uint32_t off,
|
|
1483
|
+
uint32_t len, uint32_t depth);
|
|
1484
|
+
|
|
1485
|
+
/* One recorded slice as a JS value. `depth` bounds the recursion a hand-built
|
|
1486
|
+
* slice array could otherwise make unbounded (a slice that points inside its
|
|
1487
|
+
* own message would loop forever). */
|
|
1488
|
+
static napi_value
|
|
1489
|
+
slice_to_js(napi_env env, desc_table *table, field_entry *entry,
|
|
1490
|
+
const uint8_t *base, uint32_t wire_len, const slice_t *slice,
|
|
1491
|
+
uint32_t depth)
|
|
1492
|
+
{
|
|
1493
|
+
uint64_t bits;
|
|
1494
|
+
const uint8_t *p;
|
|
1495
|
+
|
|
1496
|
+
if (entry->type == PBW_TYPE_MESSAGE || entry->type == PBW_TYPE_GROUP) {
|
|
1497
|
+
desc_table *sub = table_sub(table, entry);
|
|
1498
|
+
|
|
1499
|
+
if (sub == NULL) {
|
|
1500
|
+
return fail_argument(env, "unresolved message type");
|
|
1501
|
+
}
|
|
1502
|
+
return wire_object_to_js(env, sub, base, slice->off, slice->len,
|
|
1503
|
+
depth + 1);
|
|
1504
|
+
}
|
|
1505
|
+
if (entry->type == PBW_TYPE_STRING || entry->type == PBW_TYPE_BYTES) {
|
|
1506
|
+
return bytes_to_js(env, (pbw_type)entry->type, base + slice->off,
|
|
1507
|
+
slice->len);
|
|
1508
|
+
}
|
|
1509
|
+
p = base + slice->off;
|
|
1510
|
+
if (!read_value(&p, base + wire_len, (pbw_type)entry->type, &bits)) {
|
|
1511
|
+
return fail_argument(env, "truncated field");
|
|
1512
|
+
}
|
|
1513
|
+
return scalar_to_js(env, (pbw_type)entry->type, bits);
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
/* Appends the elements of one packed run. */
|
|
1517
|
+
static int
|
|
1518
|
+
push_packed(napi_env env, napi_value array, uint32_t *pos, field_entry *entry,
|
|
1519
|
+
const uint8_t *base, const slice_t *slice)
|
|
1520
|
+
{
|
|
1521
|
+
const uint8_t *p = base + slice->off;
|
|
1522
|
+
const uint8_t *end = p + slice->len;
|
|
1523
|
+
|
|
1524
|
+
while (p < end) {
|
|
1525
|
+
uint64_t bits;
|
|
1526
|
+
napi_value item;
|
|
1527
|
+
|
|
1528
|
+
if (!read_value(&p, end, (pbw_type)entry->type, &bits)) {
|
|
1529
|
+
return 0;
|
|
1530
|
+
}
|
|
1531
|
+
if (!enum_known(entry, bits)) {
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
item = scalar_to_js(env, (pbw_type)entry->type, bits);
|
|
1535
|
+
if (item == NULL) {
|
|
1536
|
+
return 0;
|
|
1537
|
+
}
|
|
1538
|
+
if (napi_set_element(env, array, (*pos)++, item) != napi_ok) {
|
|
1539
|
+
return 0;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
return 1;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
/* The live slice of a singular field, or -1 when it is absent: the last
|
|
1546
|
+
* occurrence on the wire, except that a later member of the same oneof group
|
|
1547
|
+
* clears it and a closed enum drops a number the schema does not declare. */
|
|
1548
|
+
static int32_t
|
|
1549
|
+
singular_slice(desc_table *table, field_entry *entry, const uint8_t *base,
|
|
1550
|
+
uint32_t wire_len, const slice_t *slices, uint32_t count,
|
|
1551
|
+
uint32_t index)
|
|
1552
|
+
{
|
|
1553
|
+
uint32_t i = count;
|
|
1554
|
+
|
|
1555
|
+
while (i-- > 0) {
|
|
1556
|
+
const slice_t *slice = &slices[i];
|
|
1557
|
+
field_entry *found = &table->fields[slice->index];
|
|
1558
|
+
|
|
1559
|
+
if (!slice_known(found, base, wire_len, slice)) {
|
|
1560
|
+
continue; /* a closed-enum number the schema does not declare */
|
|
1561
|
+
}
|
|
1562
|
+
if (entry->oneof != 0) {
|
|
1563
|
+
if (found->oneof != entry->oneof) {
|
|
1564
|
+
continue; /* some field outside the group */
|
|
1565
|
+
}
|
|
1566
|
+
if (slice->index != index) {
|
|
1567
|
+
return -1; /* another member of the group supersedes it */
|
|
1568
|
+
}
|
|
1569
|
+
} else if (slice->index != index) {
|
|
1570
|
+
continue;
|
|
1571
|
+
}
|
|
1572
|
+
return (int32_t)i;
|
|
1573
|
+
}
|
|
1574
|
+
return -1;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
/* Materializes a message field from every occurrence it had on the wire:
|
|
1578
|
+
* parsing the concatenation as one message is what merging means. */
|
|
1579
|
+
static napi_value
|
|
1580
|
+
joined_to_js(napi_env env, desc_table *table, field_entry *entry,
|
|
1581
|
+
const uint8_t *base, const slice_t *slices, uint32_t count,
|
|
1582
|
+
uint32_t index, uint32_t depth)
|
|
1583
|
+
{
|
|
1584
|
+
desc_table *sub = table_sub(table, entry);
|
|
1585
|
+
size_t total = 0;
|
|
1586
|
+
uint8_t *joined;
|
|
1587
|
+
uint8_t *dst;
|
|
1588
|
+
napi_value out;
|
|
1589
|
+
uint32_t i;
|
|
1590
|
+
|
|
1591
|
+
if (sub == NULL) {
|
|
1592
|
+
return fail_argument(env, "unresolved message type");
|
|
1593
|
+
}
|
|
1594
|
+
for (i = 0; i < count; i++) {
|
|
1595
|
+
if (slices[i].index == index) {
|
|
1596
|
+
total += slices[i].len;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
joined = malloc(total == 0 ? 1 : total);
|
|
1600
|
+
if (joined == NULL) {
|
|
1601
|
+
return fail_memory(env);
|
|
1602
|
+
}
|
|
1603
|
+
dst = joined;
|
|
1604
|
+
for (i = 0; i < count; i++) {
|
|
1605
|
+
if (slices[i].index != index) {
|
|
1606
|
+
continue;
|
|
1607
|
+
}
|
|
1608
|
+
memcpy(dst, base + slices[i].off, slices[i].len);
|
|
1609
|
+
dst += slices[i].len;
|
|
1610
|
+
}
|
|
1611
|
+
out = wire_object_to_js(env, sub, joined, 0, (uint32_t)total, depth + 1);
|
|
1612
|
+
free(joined);
|
|
1613
|
+
return out;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/* The value of one field of a message whose slices are known. */
|
|
1617
|
+
static napi_value
|
|
1618
|
+
field_to_js(napi_env env, desc_table *table, const uint8_t *base,
|
|
1619
|
+
uint32_t wire_len, const slice_t *slices, uint32_t count,
|
|
1620
|
+
uint32_t index, uint32_t depth)
|
|
1621
|
+
{
|
|
1622
|
+
field_entry *entry = &table->fields[index];
|
|
1623
|
+
|
|
1624
|
+
if (entry->label == PBW_LABEL_REPEATED) {
|
|
1625
|
+
napi_value out = NULL;
|
|
1626
|
+
uint32_t pos = 0;
|
|
1627
|
+
uint32_t i;
|
|
1628
|
+
int present = 0;
|
|
1629
|
+
|
|
1630
|
+
for (i = 0; i < count; i++) {
|
|
1631
|
+
present |= slices[i].index == index;
|
|
1632
|
+
}
|
|
1633
|
+
if (!present) {
|
|
1634
|
+
/* No value on the wire, including an empty packed run: the field
|
|
1635
|
+
* reads as absent, exactly like the tree decoder. */
|
|
1636
|
+
TRY(napi_get_undefined(env, &out));
|
|
1637
|
+
return out;
|
|
1638
|
+
}
|
|
1639
|
+
TRY(napi_create_array(env, &out));
|
|
1640
|
+
for (i = 0; i < count; i++) {
|
|
1641
|
+
const slice_t *slice = &slices[i];
|
|
1642
|
+
napi_value item;
|
|
1643
|
+
|
|
1644
|
+
if (slice->index != index) {
|
|
1645
|
+
continue;
|
|
1646
|
+
}
|
|
1647
|
+
if (slice_is_packed(entry, slice)) {
|
|
1648
|
+
if (!push_packed(env, out, &pos, entry, base, slice)) {
|
|
1649
|
+
return NULL;
|
|
1650
|
+
}
|
|
1651
|
+
continue;
|
|
1652
|
+
}
|
|
1653
|
+
if (!slice_known(entry, base, wire_len, slice)) {
|
|
1654
|
+
continue; /* a closed-enum number the schema does not know */
|
|
1655
|
+
}
|
|
1656
|
+
item = slice_to_js(env, table, entry, base, wire_len, slice,
|
|
1657
|
+
depth);
|
|
1658
|
+
if (item == NULL) {
|
|
1659
|
+
return NULL;
|
|
1660
|
+
}
|
|
1661
|
+
TRY(napi_set_element(env, out, pos++, item));
|
|
1662
|
+
}
|
|
1663
|
+
return out;
|
|
1664
|
+
}
|
|
1665
|
+
{
|
|
1666
|
+
int32_t slot = singular_slice(table, entry, base, wire_len, slices,
|
|
1667
|
+
count, index);
|
|
1668
|
+
|
|
1669
|
+
if (slot < 0) {
|
|
1670
|
+
napi_value out = NULL;
|
|
1671
|
+
|
|
1672
|
+
TRY(napi_get_undefined(env, &out));
|
|
1673
|
+
return out;
|
|
1674
|
+
}
|
|
1675
|
+
if (entry->type == PBW_TYPE_MESSAGE ||
|
|
1676
|
+
entry->type == PBW_TYPE_GROUP) {
|
|
1677
|
+
return joined_to_js(env, table, entry, base, slices, count, index,
|
|
1678
|
+
depth);
|
|
1679
|
+
}
|
|
1680
|
+
return slice_to_js(env, table, entry, base, wire_len, &slices[slot],
|
|
1681
|
+
depth);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
/* A slice list as a plain JS object: one JS object and one JS array per
|
|
1686
|
+
* repeated field the wire carried. */
|
|
1687
|
+
static napi_value
|
|
1688
|
+
slices_to_js(napi_env env, desc_table *table, const uint8_t *base,
|
|
1689
|
+
uint32_t wire_len, const slice_t *slices, uint32_t count,
|
|
1690
|
+
uint32_t depth)
|
|
1691
|
+
{
|
|
1692
|
+
napi_value out = NULL;
|
|
1693
|
+
napi_value *arrays;
|
|
1694
|
+
uint32_t *positions;
|
|
1695
|
+
uint32_t *pending;
|
|
1696
|
+
uint32_t *oneof_live;
|
|
1697
|
+
uint32_t npending = 0;
|
|
1698
|
+
uint32_t i;
|
|
1699
|
+
|
|
1700
|
+
arrays = calloc(table->nfields == 0 ? 1 : table->nfields,
|
|
1701
|
+
sizeof(napi_value));
|
|
1702
|
+
positions = calloc(table->nfields == 0 ? 1 : table->nfields,
|
|
1703
|
+
sizeof(uint32_t));
|
|
1704
|
+
pending = calloc(table->nfields == 0 ? 1 : table->nfields,
|
|
1705
|
+
sizeof(uint32_t));
|
|
1706
|
+
oneof_live = calloc(table->oneof_count + 1, sizeof(uint32_t));
|
|
1707
|
+
if (arrays == NULL || positions == NULL || pending == NULL ||
|
|
1708
|
+
oneof_live == NULL) {
|
|
1709
|
+
free(arrays);
|
|
1710
|
+
free(positions);
|
|
1711
|
+
free(pending);
|
|
1712
|
+
free(oneof_live);
|
|
1713
|
+
return fail_memory(env);
|
|
1714
|
+
}
|
|
1715
|
+
if (napi_create_object(env, &out) != napi_ok) {
|
|
1716
|
+
out = NULL;
|
|
1717
|
+
}
|
|
1718
|
+
for (i = 0; out != NULL && i < count; i++) {
|
|
1719
|
+
const slice_t *slice = &slices[i];
|
|
1720
|
+
field_entry *entry = &table->fields[slice->index];
|
|
1721
|
+
napi_value item;
|
|
1722
|
+
|
|
1723
|
+
if (entry->label == PBW_LABEL_REPEATED) {
|
|
1724
|
+
if (arrays[slice->index] == NULL) {
|
|
1725
|
+
napi_value array;
|
|
1726
|
+
|
|
1727
|
+
if (napi_create_array(env, &array) != napi_ok ||
|
|
1728
|
+
napi_set_named_property(env, out, entry->name, array) !=
|
|
1729
|
+
napi_ok) {
|
|
1730
|
+
out = NULL;
|
|
1731
|
+
break;
|
|
1732
|
+
}
|
|
1733
|
+
arrays[slice->index] = array;
|
|
1734
|
+
}
|
|
1735
|
+
if (slice_is_packed(entry, slice)) {
|
|
1736
|
+
if (!push_packed(env, arrays[slice->index],
|
|
1737
|
+
&positions[slice->index], entry, base,
|
|
1738
|
+
slice)) {
|
|
1739
|
+
out = NULL;
|
|
1740
|
+
break;
|
|
1741
|
+
}
|
|
1742
|
+
continue;
|
|
1743
|
+
}
|
|
1744
|
+
if (!slice_known(entry, base, wire_len, slice)) {
|
|
1745
|
+
continue;
|
|
1746
|
+
}
|
|
1747
|
+
item = slice_to_js(env, table, entry, base, wire_len, slice,
|
|
1748
|
+
depth);
|
|
1749
|
+
if (item == NULL) {
|
|
1750
|
+
out = NULL;
|
|
1751
|
+
break;
|
|
1752
|
+
}
|
|
1753
|
+
if (napi_set_element(env, arrays[slice->index],
|
|
1754
|
+
positions[slice->index]++, item) != napi_ok) {
|
|
1755
|
+
out = NULL;
|
|
1756
|
+
}
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
if (entry->oneof != 0) {
|
|
1760
|
+
/* Which member of a group survives is only known at the end: note
|
|
1761
|
+
* the live one and materialize it below. */
|
|
1762
|
+
if (slice_known(entry, base, wire_len, slice)) {
|
|
1763
|
+
oneof_live[entry->oneof] = slice->index + 1;
|
|
1764
|
+
}
|
|
1765
|
+
continue;
|
|
1766
|
+
}
|
|
1767
|
+
if (entry->type == PBW_TYPE_MESSAGE || entry->type == PBW_TYPE_GROUP) {
|
|
1768
|
+
/* A singular message field merges across occurrences: materialize
|
|
1769
|
+
* it once, after the loop, from all of its payloads. */
|
|
1770
|
+
pending[npending++] = slice->index;
|
|
1771
|
+
continue;
|
|
1772
|
+
}
|
|
1773
|
+
if (!slice_known(entry, base, wire_len, slice)) {
|
|
1774
|
+
continue;
|
|
1775
|
+
}
|
|
1776
|
+
item = slice_to_js(env, table, entry, base, wire_len, slice, depth);
|
|
1777
|
+
if (item == NULL) {
|
|
1778
|
+
out = NULL;
|
|
1779
|
+
break;
|
|
1780
|
+
}
|
|
1781
|
+
if (napi_set_named_property(env, out, entry->name, item) != napi_ok) {
|
|
1782
|
+
out = NULL;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
for (i = 0; out != NULL && i < npending; i++) {
|
|
1786
|
+
uint32_t index = pending[i];
|
|
1787
|
+
uint32_t j;
|
|
1788
|
+
int seen = 0;
|
|
1789
|
+
napi_value item;
|
|
1790
|
+
|
|
1791
|
+
for (j = 0; j < i; j++) {
|
|
1792
|
+
seen |= pending[j] == index;
|
|
1793
|
+
}
|
|
1794
|
+
if (seen) {
|
|
1795
|
+
continue;
|
|
1796
|
+
}
|
|
1797
|
+
item = joined_to_js(env, table, &table->fields[index], base, slices,
|
|
1798
|
+
count, index, depth);
|
|
1799
|
+
if (item == NULL ||
|
|
1800
|
+
napi_set_named_property(env, out, table->fields[index].name,
|
|
1801
|
+
item) != napi_ok) {
|
|
1802
|
+
out = NULL;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
for (i = 0; out != NULL && i <= table->oneof_count; i++) {
|
|
1806
|
+
uint32_t index;
|
|
1807
|
+
napi_value item;
|
|
1808
|
+
|
|
1809
|
+
if (oneof_live[i] == 0) {
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
index = oneof_live[i] - 1;
|
|
1813
|
+
item = field_to_js(env, table, base, wire_len, slices, count, index,
|
|
1814
|
+
depth);
|
|
1815
|
+
if (item == NULL ||
|
|
1816
|
+
napi_set_named_property(env, out, table->fields[index].name,
|
|
1817
|
+
item) != napi_ok) {
|
|
1818
|
+
out = NULL;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
free(arrays);
|
|
1822
|
+
free(positions);
|
|
1823
|
+
free(pending);
|
|
1824
|
+
free(oneof_live);
|
|
1825
|
+
return out;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
/* A message range as a plain JS object: one tag scan into a stack slice list,
|
|
1829
|
+
* then the object. */
|
|
1830
|
+
static napi_value
|
|
1831
|
+
wire_object_to_js(napi_env env, desc_table *table, const uint8_t *base,
|
|
1832
|
+
uint32_t off, uint32_t len, uint32_t depth)
|
|
1833
|
+
{
|
|
1834
|
+
slice_list list;
|
|
1835
|
+
napi_value out;
|
|
1836
|
+
scan_status status;
|
|
1837
|
+
|
|
1838
|
+
memset(&list, 0, sizeof(list));
|
|
1839
|
+
list.items = list.inline_;
|
|
1840
|
+
list.cap = 8;
|
|
1841
|
+
if (depth >= PBW_MAX_DEPTH) {
|
|
1842
|
+
return fail_argument(env, "nesting limit exceeded");
|
|
1843
|
+
}
|
|
1844
|
+
status = scan_range(table, base, off, len, depth, 0, &list, NULL);
|
|
1845
|
+
if (status != SCAN_OK) {
|
|
1846
|
+
slices_release(&list);
|
|
1847
|
+
return fail_argument(env, "invalid wire data");
|
|
1848
|
+
}
|
|
1849
|
+
out = slices_to_js(env, table, base, off + len, list.items, list.count,
|
|
1850
|
+
depth);
|
|
1851
|
+
slices_release(&list);
|
|
1852
|
+
return out;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
/* ------------------------------------------------------------- lifecycle */
|
|
1856
|
+
|
|
1857
|
+
static void
|
|
1858
|
+
finalize_schema(napi_env env, void *data, void *hint)
|
|
1859
|
+
{
|
|
1860
|
+
schema_handle *handle = data;
|
|
1861
|
+
size_t i;
|
|
1862
|
+
|
|
1863
|
+
(void)env;
|
|
1864
|
+
(void)hint;
|
|
1865
|
+
for (i = 0; i < handle->count; i++) {
|
|
1866
|
+
desc_table *table = handle->tables[i];
|
|
1867
|
+
|
|
1868
|
+
free(table->slots);
|
|
1869
|
+
free(table->dense);
|
|
1870
|
+
free(table->by_number);
|
|
1871
|
+
free(table->fields);
|
|
1872
|
+
free(table);
|
|
1873
|
+
}
|
|
1874
|
+
free(handle->tables);
|
|
1875
|
+
pbw_schema_free(handle->schema);
|
|
1876
|
+
free(handle);
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
static void
|
|
1880
|
+
free_reference(napi_env env, napi_ref *ref)
|
|
1881
|
+
{
|
|
1882
|
+
if (*ref != NULL) {
|
|
1883
|
+
napi_delete_reference(env, *ref);
|
|
1884
|
+
*ref = NULL;
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
static void
|
|
1889
|
+
finalize_desc(napi_env env, void *data, void *hint)
|
|
1890
|
+
{
|
|
1891
|
+
desc_handle *handle = data;
|
|
1892
|
+
|
|
1893
|
+
(void)hint;
|
|
1894
|
+
pbw_ctx_free(handle->enc);
|
|
1895
|
+
free_reference(env, &handle->schema_ref);
|
|
1896
|
+
free(handle);
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
static void
|
|
1900
|
+
finalize_message(napi_env env, void *data, void *hint)
|
|
1901
|
+
{
|
|
1902
|
+
message_handle *handle = data;
|
|
1903
|
+
|
|
1904
|
+
(void)hint;
|
|
1905
|
+
pbw_message_free(handle->msg);
|
|
1906
|
+
free_reference(env, &handle->desc_ref);
|
|
1907
|
+
free(handle);
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
/* Every handle is a plain wrapped object; the JS layer attaches its own
|
|
1911
|
+
* prototype, so no addon-side shape is promised. */
|
|
1912
|
+
static napi_value
|
|
1913
|
+
new_handle(napi_env env, void *handle, napi_finalize finalize)
|
|
1914
|
+
{
|
|
1915
|
+
napi_value object;
|
|
1916
|
+
|
|
1917
|
+
/* A failed wrap leaves the finalizer unattached: the caller owns handle
|
|
1918
|
+
* and runs finalize itself. */
|
|
1919
|
+
if (napi_create_object(env, &object) != napi_ok ||
|
|
1920
|
+
napi_wrap(env, object, handle, finalize, NULL, NULL) != napi_ok) {
|
|
1921
|
+
napi_throw_error(env, NULL, "pbwire: out of memory");
|
|
1922
|
+
return NULL;
|
|
1923
|
+
}
|
|
1924
|
+
return object;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
/* Wraps a message handle that already holds its tree, taking a strong
|
|
1928
|
+
* reference to the descriptor object so the schema outlives it. */
|
|
1929
|
+
static napi_value
|
|
1930
|
+
new_message_value(napi_env env, desc_handle *desc, napi_value desc_object,
|
|
1931
|
+
message_handle *handle)
|
|
1932
|
+
{
|
|
1933
|
+
napi_value out;
|
|
1934
|
+
|
|
1935
|
+
handle->desc = desc;
|
|
1936
|
+
if (napi_create_reference(env, desc_object, 1, &handle->desc_ref) !=
|
|
1937
|
+
napi_ok) {
|
|
1938
|
+
pbw_message_free(handle->msg);
|
|
1939
|
+
free(handle);
|
|
1940
|
+
return fail_memory(env);
|
|
1941
|
+
}
|
|
1942
|
+
out = new_handle(env, handle, finalize_message);
|
|
1943
|
+
if (out == NULL) {
|
|
1944
|
+
pbw_message_free(handle->msg);
|
|
1945
|
+
free_reference(env, &handle->desc_ref);
|
|
1946
|
+
free(handle);
|
|
1947
|
+
}
|
|
1948
|
+
return out;
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
/* ---------------------------------------------------------- entry points */
|
|
1952
|
+
|
|
1953
|
+
static napi_value
|
|
1954
|
+
abi_version(napi_env env, napi_callback_info info)
|
|
1955
|
+
{
|
|
1956
|
+
napi_value out = NULL;
|
|
1957
|
+
|
|
1958
|
+
(void)info;
|
|
1959
|
+
if (napi_create_int32(env, pbw_abi_version(), &out) != napi_ok) {
|
|
1960
|
+
return NULL;
|
|
1961
|
+
}
|
|
1962
|
+
return out;
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
static napi_value
|
|
1966
|
+
load_descriptor_set(napi_env env, napi_callback_info info)
|
|
1967
|
+
{
|
|
1968
|
+
napi_value argv[1];
|
|
1969
|
+
napi_value out;
|
|
1970
|
+
schema_handle *handle;
|
|
1971
|
+
pbw_schema *schema = NULL;
|
|
1972
|
+
pbw_status status;
|
|
1973
|
+
void *data = NULL;
|
|
1974
|
+
size_t len = 0;
|
|
1975
|
+
|
|
1976
|
+
if (!get_args(env, info, argv, 1)) {
|
|
1977
|
+
return NULL;
|
|
1978
|
+
}
|
|
1979
|
+
if (napi_get_buffer_info(env, argv[0], &data, &len) != napi_ok) {
|
|
1980
|
+
return fail_argument(env, "loadDescriptorSet expects a Buffer");
|
|
1981
|
+
}
|
|
1982
|
+
status = pbw_descriptor_set_load(data, len, &schema);
|
|
1983
|
+
if (status != PBW_OK) {
|
|
1984
|
+
char message[512];
|
|
1985
|
+
|
|
1986
|
+
snprintf(message, sizeof(message), "pbwire: %s",
|
|
1987
|
+
pbw_schema_error(schema));
|
|
1988
|
+
napi_throw_error(env, NULL, message);
|
|
1989
|
+
pbw_schema_free(schema);
|
|
1990
|
+
return NULL;
|
|
1991
|
+
}
|
|
1992
|
+
handle = calloc(1, sizeof(*handle));
|
|
1993
|
+
if (handle == NULL) {
|
|
1994
|
+
pbw_schema_free(schema);
|
|
1995
|
+
return fail_memory(env);
|
|
1996
|
+
}
|
|
1997
|
+
handle->schema = schema;
|
|
1998
|
+
out = new_handle(env, handle, finalize_schema);
|
|
1999
|
+
if (out == NULL) {
|
|
2000
|
+
finalize_schema(env, handle, NULL);
|
|
2001
|
+
}
|
|
2002
|
+
return out;
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
static napi_value
|
|
2006
|
+
descriptor(napi_env env, napi_callback_info info)
|
|
2007
|
+
{
|
|
2008
|
+
napi_value argv[2];
|
|
2009
|
+
napi_value out;
|
|
2010
|
+
schema_handle *schema;
|
|
2011
|
+
desc_handle *handle;
|
|
2012
|
+
const pbw_descriptor *desc;
|
|
2013
|
+
string_buf buf;
|
|
2014
|
+
const char *name;
|
|
2015
|
+
|
|
2016
|
+
if (!get_args(env, info, argv, 2)) {
|
|
2017
|
+
return NULL;
|
|
2018
|
+
}
|
|
2019
|
+
if (!unwrap(env, argv[0], (void **)&schema, "schema handle")) {
|
|
2020
|
+
return NULL;
|
|
2021
|
+
}
|
|
2022
|
+
name = string_bytes(env, argv[1], &buf);
|
|
2023
|
+
if (name == NULL) {
|
|
2024
|
+
return NULL;
|
|
2025
|
+
}
|
|
2026
|
+
desc = pbw_schema_find(schema->schema, name);
|
|
2027
|
+
string_free(&buf, name);
|
|
2028
|
+
if (desc == NULL || pbw_descriptor_is_enum(desc)) {
|
|
2029
|
+
TRY(napi_get_null(env, &out));
|
|
2030
|
+
return out;
|
|
2031
|
+
}
|
|
2032
|
+
handle = calloc(1, sizeof(*handle));
|
|
2033
|
+
if (handle == NULL) {
|
|
2034
|
+
return fail_memory(env);
|
|
2035
|
+
}
|
|
2036
|
+
handle->schema = schema;
|
|
2037
|
+
handle->enc = pbw_ctx_new();
|
|
2038
|
+
handle->table = table_for(schema, desc);
|
|
2039
|
+
if (handle->enc == NULL || handle->table == NULL) {
|
|
2040
|
+
pbw_ctx_free(handle->enc);
|
|
2041
|
+
free(handle);
|
|
2042
|
+
return fail_memory(env);
|
|
2043
|
+
}
|
|
2044
|
+
if (napi_create_reference(env, argv[0], 1, &handle->schema_ref) != napi_ok) {
|
|
2045
|
+
finalize_desc(env, handle, NULL);
|
|
2046
|
+
return fail_memory(env);
|
|
2047
|
+
}
|
|
2048
|
+
out = new_handle(env, handle, finalize_desc);
|
|
2049
|
+
if (out == NULL) {
|
|
2050
|
+
finalize_desc(env, handle, NULL);
|
|
2051
|
+
}
|
|
2052
|
+
return out;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
static napi_value
|
|
2056
|
+
message_new(napi_env env, napi_callback_info info)
|
|
2057
|
+
{
|
|
2058
|
+
napi_value argv[1];
|
|
2059
|
+
desc_handle *desc;
|
|
2060
|
+
message_handle *handle;
|
|
2061
|
+
pbw_message *msg;
|
|
2062
|
+
|
|
2063
|
+
if (!get_args(env, info, argv, 1)) {
|
|
2064
|
+
return NULL;
|
|
2065
|
+
}
|
|
2066
|
+
if (!unwrap(env, argv[0], (void **)&desc, "message descriptor handle")) {
|
|
2067
|
+
return NULL;
|
|
2068
|
+
}
|
|
2069
|
+
msg = pbw_message_new(desc->table->desc);
|
|
2070
|
+
if (msg == NULL) {
|
|
2071
|
+
return fail_memory(env);
|
|
2072
|
+
}
|
|
2073
|
+
handle = calloc(1, sizeof(*handle));
|
|
2074
|
+
if (handle == NULL) {
|
|
2075
|
+
pbw_message_free(msg);
|
|
2076
|
+
return fail_memory(env);
|
|
2077
|
+
}
|
|
2078
|
+
handle->msg = msg;
|
|
2079
|
+
return new_message_value(env, desc, argv[0], handle);
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
/* 1 when this type (or a message reachable from it) declares a proto2
|
|
2083
|
+
* `required` field: the JS layer then decodes through decodeEager(). */
|
|
2084
|
+
static napi_value
|
|
2085
|
+
is_eager(napi_env env, napi_callback_info info)
|
|
2086
|
+
{
|
|
2087
|
+
napi_value argv[1];
|
|
2088
|
+
napi_value out = NULL;
|
|
2089
|
+
desc_handle *desc;
|
|
2090
|
+
|
|
2091
|
+
if (!get_args(env, info, argv, 1)) {
|
|
2092
|
+
return NULL;
|
|
2093
|
+
}
|
|
2094
|
+
if (!unwrap(env, argv[0], (void **)&desc, "message descriptor handle")) {
|
|
2095
|
+
return NULL;
|
|
2096
|
+
}
|
|
2097
|
+
if (napi_get_boolean(env, table_is_eager(desc->table) != 0, &out) !=
|
|
2098
|
+
napi_ok) {
|
|
2099
|
+
return NULL;
|
|
2100
|
+
}
|
|
2101
|
+
return out;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
/* Validates a wire range and returns one (field index, offset, length) triple
|
|
2105
|
+
* per wire field as a Uint32Array. The bytes stay owned by the caller (a JS
|
|
2106
|
+
* Buffer), so a decoded message needs no native state at all. */
|
|
2107
|
+
static napi_value
|
|
2108
|
+
decode(napi_env env, napi_callback_info info)
|
|
2109
|
+
{
|
|
2110
|
+
napi_value argv[2];
|
|
2111
|
+
napi_value out = NULL;
|
|
2112
|
+
napi_value array_buffer = NULL;
|
|
2113
|
+
desc_handle *desc;
|
|
2114
|
+
void *data = NULL;
|
|
2115
|
+
size_t len = 0;
|
|
2116
|
+
slice_list list;
|
|
2117
|
+
scan_status status;
|
|
2118
|
+
void *words = NULL;
|
|
2119
|
+
uint32_t i;
|
|
2120
|
+
|
|
2121
|
+
if (!get_args(env, info, argv, 2)) {
|
|
2122
|
+
return NULL;
|
|
2123
|
+
}
|
|
2124
|
+
if (!unwrap(env, argv[0], (void **)&desc, "message descriptor handle")) {
|
|
2125
|
+
return NULL;
|
|
2126
|
+
}
|
|
2127
|
+
if (napi_get_buffer_info(env, argv[1], &data, &len) != napi_ok) {
|
|
2128
|
+
return fail_argument(env, "decode expects a Buffer");
|
|
2129
|
+
}
|
|
2130
|
+
if ((uint64_t)len > 0xffffffffull) {
|
|
2131
|
+
return fail_argument(env, "decode expects a message below 4 GiB");
|
|
2132
|
+
}
|
|
2133
|
+
if (table_is_eager(desc->table)) {
|
|
2134
|
+
return fail_argument(env, "decode on a schema that needs the tree");
|
|
2135
|
+
}
|
|
2136
|
+
memset(&list, 0, sizeof(list));
|
|
2137
|
+
list.items = list.inline_;
|
|
2138
|
+
list.cap = 8;
|
|
2139
|
+
status = scan_range(desc->table, (const uint8_t *)data, 0, (uint32_t)len,
|
|
2140
|
+
0, 0, &list, NULL);
|
|
2141
|
+
if (status != SCAN_OK) {
|
|
2142
|
+
slices_release(&list);
|
|
2143
|
+
if (status == SCAN_NOMEM) {
|
|
2144
|
+
return fail_memory(env);
|
|
2145
|
+
}
|
|
2146
|
+
return fail_argument(env, status == SCAN_LIMIT
|
|
2147
|
+
? "decode: nesting limit exceeded"
|
|
2148
|
+
: "decode: invalid wire data");
|
|
2149
|
+
}
|
|
2150
|
+
if (napi_create_arraybuffer(env, (size_t)list.count * sizeof(slice_t),
|
|
2151
|
+
&words, &array_buffer) != napi_ok ||
|
|
2152
|
+
napi_create_typedarray(env, napi_uint32_array,
|
|
2153
|
+
(size_t)list.count * 3, array_buffer, 0,
|
|
2154
|
+
&out) != napi_ok) {
|
|
2155
|
+
slices_release(&list);
|
|
2156
|
+
return fail_memory(env);
|
|
2157
|
+
}
|
|
2158
|
+
for (i = 0; i < list.count; i++) {
|
|
2159
|
+
uint32_t *triple = (uint32_t *)words + (size_t)i * 3;
|
|
2160
|
+
|
|
2161
|
+
triple[0] = list.items[i].index;
|
|
2162
|
+
triple[1] = list.items[i].off;
|
|
2163
|
+
triple[2] = list.items[i].len;
|
|
2164
|
+
}
|
|
2165
|
+
slices_release(&list);
|
|
2166
|
+
return out;
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
/* Decodes into a tree for schemas whose proto2 `required` fields have to hold
|
|
2170
|
+
* in the merged message: the tree decoder is the code that checks that. */
|
|
2171
|
+
static napi_value
|
|
2172
|
+
decode_eager(napi_env env, napi_callback_info info)
|
|
2173
|
+
{
|
|
2174
|
+
napi_value argv[2];
|
|
2175
|
+
desc_handle *desc;
|
|
2176
|
+
message_handle *handle;
|
|
2177
|
+
pbw_message *msg = NULL;
|
|
2178
|
+
void *data = NULL;
|
|
2179
|
+
size_t len = 0;
|
|
2180
|
+
pbw_status status;
|
|
2181
|
+
|
|
2182
|
+
if (!get_args(env, info, argv, 2)) {
|
|
2183
|
+
return NULL;
|
|
2184
|
+
}
|
|
2185
|
+
if (!unwrap(env, argv[0], (void **)&desc, "message descriptor handle")) {
|
|
2186
|
+
return NULL;
|
|
2187
|
+
}
|
|
2188
|
+
if (napi_get_buffer_info(env, argv[1], &data, &len) != napi_ok) {
|
|
2189
|
+
return fail_argument(env, "decodeEager expects a Buffer");
|
|
2190
|
+
}
|
|
2191
|
+
status = pbw_decode(desc->table->desc, data, len, &msg);
|
|
2192
|
+
if (status != PBW_OK) {
|
|
2193
|
+
return fail_status(env, "decode", status);
|
|
2194
|
+
}
|
|
2195
|
+
handle = calloc(1, sizeof(*handle));
|
|
2196
|
+
if (handle == NULL) {
|
|
2197
|
+
pbw_message_free(msg);
|
|
2198
|
+
return fail_memory(env);
|
|
2199
|
+
}
|
|
2200
|
+
handle->msg = msg;
|
|
2201
|
+
return new_message_value(env, desc, argv[0], handle);
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
/* The value of one field of a decoded message. */
|
|
2205
|
+
static napi_value
|
|
2206
|
+
wire_get(napi_env env, napi_callback_info info)
|
|
2207
|
+
{
|
|
2208
|
+
napi_value argv[4];
|
|
2209
|
+
desc_handle *desc;
|
|
2210
|
+
void *wire = NULL;
|
|
2211
|
+
size_t wire_len = 0;
|
|
2212
|
+
napi_typedarray_type array_type;
|
|
2213
|
+
size_t count = 0;
|
|
2214
|
+
void *words = NULL;
|
|
2215
|
+
string_buf buf;
|
|
2216
|
+
const char *name;
|
|
2217
|
+
uint32_t index;
|
|
2218
|
+
|
|
2219
|
+
if (!get_args(env, info, argv, 4)) {
|
|
2220
|
+
return NULL;
|
|
2221
|
+
}
|
|
2222
|
+
if (!unwrap(env, argv[0], (void **)&desc, "message descriptor handle")) {
|
|
2223
|
+
return NULL;
|
|
2224
|
+
}
|
|
2225
|
+
if (napi_get_buffer_info(env, argv[1], &wire, &wire_len) != napi_ok) {
|
|
2226
|
+
return fail_argument(env, "wireGet expects a Buffer");
|
|
2227
|
+
}
|
|
2228
|
+
if (napi_get_typedarray_info(env, argv[2], &array_type, &count, &words,
|
|
2229
|
+
NULL, NULL) != napi_ok ||
|
|
2230
|
+
array_type != napi_uint32_array || count % 3 != 0) {
|
|
2231
|
+
return fail_argument(env, "wireGet expects a slice array");
|
|
2232
|
+
}
|
|
2233
|
+
/* The slice array is caller-supplied and mutable: bound every triple
|
|
2234
|
+
* before anything indexes the field table or reads the bytes with it. */
|
|
2235
|
+
{
|
|
2236
|
+
const uint32_t *triples = words;
|
|
2237
|
+
size_t i;
|
|
2238
|
+
|
|
2239
|
+
for (i = 0; i < count; i += 3) {
|
|
2240
|
+
uint64_t off = triples[i + 1];
|
|
2241
|
+
uint64_t len = triples[i + 2];
|
|
2242
|
+
|
|
2243
|
+
if (triples[i] >= desc->table->nfields ||
|
|
2244
|
+
off > (uint64_t)wire_len || len > (uint64_t)wire_len - off) {
|
|
2245
|
+
return fail_argument(env, "wireGet: slice out of range");
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
name = string_bytes(env, argv[3], &buf);
|
|
2250
|
+
if (name == NULL) {
|
|
2251
|
+
return NULL;
|
|
2252
|
+
}
|
|
2253
|
+
index = table_by_name(desc->table, name);
|
|
2254
|
+
if (index == NOT_FOUND) {
|
|
2255
|
+
char message[512];
|
|
2256
|
+
|
|
2257
|
+
snprintf(message, sizeof(message), "pbwire: %s has no field %s",
|
|
2258
|
+
pbw_descriptor_name(desc->table->desc), name);
|
|
2259
|
+
napi_throw_error(env, NULL, message);
|
|
2260
|
+
string_free(&buf, name);
|
|
2261
|
+
return NULL;
|
|
2262
|
+
}
|
|
2263
|
+
string_free(&buf, name);
|
|
2264
|
+
return field_to_js(env, desc->table, (const uint8_t *)wire,
|
|
2265
|
+
(uint32_t)wire_len, (const slice_t *)words,
|
|
2266
|
+
(uint32_t)(count / 3), index, 0);
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
/* A decoded message that is written to becomes a tree. */
|
|
2270
|
+
static napi_value
|
|
2271
|
+
promote(napi_env env, napi_callback_info info)
|
|
2272
|
+
{
|
|
2273
|
+
napi_value argv[2];
|
|
2274
|
+
desc_handle *desc;
|
|
2275
|
+
message_handle *handle;
|
|
2276
|
+
pbw_message *msg = NULL;
|
|
2277
|
+
void *data = NULL;
|
|
2278
|
+
size_t len = 0;
|
|
2279
|
+
pbw_status status;
|
|
2280
|
+
|
|
2281
|
+
if (!get_args(env, info, argv, 2)) {
|
|
2282
|
+
return NULL;
|
|
2283
|
+
}
|
|
2284
|
+
if (!unwrap(env, argv[0], (void **)&desc, "message descriptor handle")) {
|
|
2285
|
+
return NULL;
|
|
2286
|
+
}
|
|
2287
|
+
if (napi_get_buffer_info(env, argv[1], &data, &len) != napi_ok) {
|
|
2288
|
+
return fail_argument(env, "promote expects a Buffer");
|
|
2289
|
+
}
|
|
2290
|
+
status = pbw_decode(desc->table->desc, data, len, &msg);
|
|
2291
|
+
if (status != PBW_OK) {
|
|
2292
|
+
return fail_status(env, "message", status);
|
|
2293
|
+
}
|
|
2294
|
+
handle = calloc(1, sizeof(*handle));
|
|
2295
|
+
if (handle == NULL) {
|
|
2296
|
+
pbw_message_free(msg);
|
|
2297
|
+
return fail_memory(env);
|
|
2298
|
+
}
|
|
2299
|
+
handle->msg = msg;
|
|
2300
|
+
return new_message_value(env, desc, argv[0], handle);
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
/* Shared by every entry point that takes a tree message: a freed handle throws
|
|
2304
|
+
* instead of dereferencing a released tree. */
|
|
2305
|
+
static int
|
|
2306
|
+
unwrap_message(napi_env env, napi_value value, message_handle **out)
|
|
2307
|
+
{
|
|
2308
|
+
message_handle *handle;
|
|
2309
|
+
|
|
2310
|
+
if (!unwrap(env, value, (void **)&handle, "message handle")) {
|
|
2311
|
+
return 0;
|
|
2312
|
+
}
|
|
2313
|
+
if (handle->freed) {
|
|
2314
|
+
fail_argument(env, "message was freed");
|
|
2315
|
+
return 0;
|
|
2316
|
+
}
|
|
2317
|
+
*out = handle;
|
|
2318
|
+
return 1;
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
/* Resolves a JS field name against the message's descriptor. */
|
|
2322
|
+
static field_entry *
|
|
2323
|
+
field_by_name(napi_env env, message_handle *handle, napi_value name_value)
|
|
2324
|
+
{
|
|
2325
|
+
string_buf buf;
|
|
2326
|
+
const char *name = string_bytes(env, name_value, &buf);
|
|
2327
|
+
desc_table *table = handle->desc->table;
|
|
2328
|
+
uint32_t index;
|
|
2329
|
+
|
|
2330
|
+
if (name == NULL) {
|
|
2331
|
+
return NULL;
|
|
2332
|
+
}
|
|
2333
|
+
index = table_by_name(table, name);
|
|
2334
|
+
if (index == NOT_FOUND) {
|
|
2335
|
+
char message[512];
|
|
2336
|
+
|
|
2337
|
+
snprintf(message, sizeof(message), "pbwire: %s has no field %s",
|
|
2338
|
+
pbw_descriptor_name(table->desc), name);
|
|
2339
|
+
napi_throw_error(env, NULL, message);
|
|
2340
|
+
string_free(&buf, name);
|
|
2341
|
+
return NULL;
|
|
2342
|
+
}
|
|
2343
|
+
string_free(&buf, name);
|
|
2344
|
+
return &table->fields[index];
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
static napi_value
|
|
2348
|
+
message_get(napi_env env, napi_callback_info info)
|
|
2349
|
+
{
|
|
2350
|
+
napi_value argv[2];
|
|
2351
|
+
message_handle *handle;
|
|
2352
|
+
field_entry *entry;
|
|
2353
|
+
|
|
2354
|
+
if (!get_args(env, info, argv, 2)) {
|
|
2355
|
+
return NULL;
|
|
2356
|
+
}
|
|
2357
|
+
if (!unwrap_message(env, argv[0], &handle)) {
|
|
2358
|
+
return NULL;
|
|
2359
|
+
}
|
|
2360
|
+
entry = field_by_name(env, handle, argv[1]);
|
|
2361
|
+
if (entry == NULL) {
|
|
2362
|
+
return NULL;
|
|
2363
|
+
}
|
|
2364
|
+
{
|
|
2365
|
+
const pbw_value *value =
|
|
2366
|
+
pbw_message_get_field(handle->msg, entry->field);
|
|
2367
|
+
|
|
2368
|
+
if (value == NULL) {
|
|
2369
|
+
napi_value out = NULL;
|
|
2370
|
+
|
|
2371
|
+
TRY(napi_get_undefined(env, &out));
|
|
2372
|
+
return out;
|
|
2373
|
+
}
|
|
2374
|
+
return value_to_js(env, (pbw_type)entry->type, value);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
static napi_value
|
|
2379
|
+
message_set(napi_env env, napi_callback_info info)
|
|
2380
|
+
{
|
|
2381
|
+
napi_value argv[3];
|
|
2382
|
+
napi_value out;
|
|
2383
|
+
message_handle *handle;
|
|
2384
|
+
field_entry *entry;
|
|
2385
|
+
|
|
2386
|
+
if (!get_args(env, info, argv, 3)) {
|
|
2387
|
+
return NULL;
|
|
2388
|
+
}
|
|
2389
|
+
if (!unwrap_message(env, argv[0], &handle)) {
|
|
2390
|
+
return NULL;
|
|
2391
|
+
}
|
|
2392
|
+
entry = field_by_name(env, handle, argv[1]);
|
|
2393
|
+
if (entry == NULL) {
|
|
2394
|
+
return NULL;
|
|
2395
|
+
}
|
|
2396
|
+
if (!set_field(env, handle->msg, handle->desc->table, entry, argv[2])) {
|
|
2397
|
+
return NULL;
|
|
2398
|
+
}
|
|
2399
|
+
TRY(napi_get_undefined(env, &out));
|
|
2400
|
+
return out;
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
/* Encodes a tree into the type's scratch buffer and returns its length. The
|
|
2404
|
+
* JS layer then allocates a pooled Buffer of exactly that size and calls
|
|
2405
|
+
* copyEncoded(): N-API's own Buffer creation costs ~10x a pooled allocation. */
|
|
2406
|
+
static napi_value
|
|
2407
|
+
encode(napi_env env, napi_callback_info info)
|
|
2408
|
+
{
|
|
2409
|
+
napi_value argv[1];
|
|
2410
|
+
napi_value out = NULL;
|
|
2411
|
+
message_handle *handle;
|
|
2412
|
+
pbw_status status;
|
|
2413
|
+
|
|
2414
|
+
if (!get_args(env, info, argv, 1)) {
|
|
2415
|
+
return NULL;
|
|
2416
|
+
}
|
|
2417
|
+
if (!unwrap_message(env, argv[0], &handle)) {
|
|
2418
|
+
return NULL;
|
|
2419
|
+
}
|
|
2420
|
+
status = pbw_encode_ctx(handle->desc->enc, handle->msg,
|
|
2421
|
+
&handle->desc->enc_data, &handle->desc->enc_len);
|
|
2422
|
+
if (status != PBW_OK) {
|
|
2423
|
+
handle->desc->enc_valid = 0;
|
|
2424
|
+
return fail_status(env, "encode", status);
|
|
2425
|
+
}
|
|
2426
|
+
if (napi_create_double(env, (double)handle->desc->enc_len, &out) !=
|
|
2427
|
+
napi_ok) {
|
|
2428
|
+
return NULL;
|
|
2429
|
+
}
|
|
2430
|
+
handle->desc->enc_valid = 1;
|
|
2431
|
+
return out;
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
/* Builds a tree from a plain object, encodes it into the type's scratch and
|
|
2435
|
+
* returns its length; the tree never outlives this call. */
|
|
2436
|
+
static napi_value
|
|
2437
|
+
encode_object(napi_env env, napi_callback_info info)
|
|
2438
|
+
{
|
|
2439
|
+
napi_value argv[2];
|
|
2440
|
+
napi_value out = NULL;
|
|
2441
|
+
desc_handle *desc;
|
|
2442
|
+
pbw_message *msg;
|
|
2443
|
+
pbw_status status;
|
|
2444
|
+
int ok;
|
|
2445
|
+
|
|
2446
|
+
if (!get_args(env, info, argv, 2)) {
|
|
2447
|
+
return NULL;
|
|
2448
|
+
}
|
|
2449
|
+
if (!unwrap(env, argv[0], (void **)&desc, "message descriptor handle")) {
|
|
2450
|
+
return NULL;
|
|
2451
|
+
}
|
|
2452
|
+
msg = pbw_message_new(desc->table->desc);
|
|
2453
|
+
if (msg == NULL) {
|
|
2454
|
+
return fail_memory(env);
|
|
2455
|
+
}
|
|
2456
|
+
ok = js_to_message(env, msg, desc->table, argv[1]);
|
|
2457
|
+
if (ok) {
|
|
2458
|
+
status = pbw_encode_ctx(desc->enc, msg, &desc->enc_data, &desc->enc_len);
|
|
2459
|
+
if (status != PBW_OK) {
|
|
2460
|
+
desc->enc_valid = 0;
|
|
2461
|
+
fail_status(env, "encode", status);
|
|
2462
|
+
ok = 0;
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
pbw_message_free(msg);
|
|
2466
|
+
if (!ok) {
|
|
2467
|
+
return NULL;
|
|
2468
|
+
}
|
|
2469
|
+
if (napi_create_double(env, (double)desc->enc_len, &out) != napi_ok) {
|
|
2470
|
+
return NULL;
|
|
2471
|
+
}
|
|
2472
|
+
desc->enc_valid = 1;
|
|
2473
|
+
return out;
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
/* Copies the scratch an encode()/encodeObject() call just filled into a
|
|
2477
|
+
* caller-provided Buffer, which lets JS use a pooled allocation. */
|
|
2478
|
+
static napi_value
|
|
2479
|
+
copy_encoded(napi_env env, napi_callback_info info)
|
|
2480
|
+
{
|
|
2481
|
+
napi_value argv[2];
|
|
2482
|
+
napi_value out;
|
|
2483
|
+
desc_handle *desc;
|
|
2484
|
+
void *data = NULL;
|
|
2485
|
+
size_t len = 0;
|
|
2486
|
+
|
|
2487
|
+
if (!get_args(env, info, argv, 2)) {
|
|
2488
|
+
return NULL;
|
|
2489
|
+
}
|
|
2490
|
+
if (!unwrap(env, argv[0], (void **)&desc, "message descriptor handle")) {
|
|
2491
|
+
return NULL;
|
|
2492
|
+
}
|
|
2493
|
+
if (napi_get_buffer_info(env, argv[1], &data, &len) != napi_ok) {
|
|
2494
|
+
return fail_argument(env, "copyEncoded expects a Buffer");
|
|
2495
|
+
}
|
|
2496
|
+
if (!desc->enc_valid) {
|
|
2497
|
+
return fail_argument(env, "copyEncoded without a preceding encode");
|
|
2498
|
+
}
|
|
2499
|
+
if (len < desc->enc_len) {
|
|
2500
|
+
return fail_argument(env, "copyEncoded buffer is too small");
|
|
2501
|
+
}
|
|
2502
|
+
if (desc->enc_len > 0) {
|
|
2503
|
+
memcpy(data, desc->enc_data, desc->enc_len);
|
|
2504
|
+
}
|
|
2505
|
+
desc->enc_valid = 0;
|
|
2506
|
+
TRY(napi_get_undefined(env, &out));
|
|
2507
|
+
return out;
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
static napi_value
|
|
2511
|
+
message_free(napi_env env, napi_callback_info info)
|
|
2512
|
+
{
|
|
2513
|
+
napi_value argv[1];
|
|
2514
|
+
napi_value out;
|
|
2515
|
+
message_handle *handle;
|
|
2516
|
+
|
|
2517
|
+
if (!get_args(env, info, argv, 1)) {
|
|
2518
|
+
return NULL;
|
|
2519
|
+
}
|
|
2520
|
+
if (!unwrap(env, argv[0], (void **)&handle, "message handle")) {
|
|
2521
|
+
return NULL;
|
|
2522
|
+
}
|
|
2523
|
+
pbw_message_free(handle->msg);
|
|
2524
|
+
handle->msg = NULL;
|
|
2525
|
+
handle->freed = 1;
|
|
2526
|
+
free_reference(env, &handle->desc_ref);
|
|
2527
|
+
TRY(napi_get_undefined(env, &out));
|
|
2528
|
+
return out;
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
NAPI_MODULE_INIT()
|
|
2532
|
+
{
|
|
2533
|
+
napi_property_descriptor properties[] = {
|
|
2534
|
+
{"abiVersion", NULL, abi_version, NULL, NULL, NULL, napi_enumerable,
|
|
2535
|
+
NULL},
|
|
2536
|
+
{"loadDescriptorSet", NULL, load_descriptor_set, NULL, NULL, NULL,
|
|
2537
|
+
napi_enumerable, NULL},
|
|
2538
|
+
{"descriptor", NULL, descriptor, NULL, NULL, NULL, napi_enumerable,
|
|
2539
|
+
NULL},
|
|
2540
|
+
{"messageNew", NULL, message_new, NULL, NULL, NULL, napi_enumerable,
|
|
2541
|
+
NULL},
|
|
2542
|
+
{"isEager", NULL, is_eager, NULL, NULL, NULL, napi_enumerable,
|
|
2543
|
+
NULL},
|
|
2544
|
+
{"decode", NULL, decode, NULL, NULL, NULL, napi_enumerable, NULL},
|
|
2545
|
+
{"decodeEager", NULL, decode_eager, NULL, NULL, NULL,
|
|
2546
|
+
napi_enumerable, NULL},
|
|
2547
|
+
{"wireGet", NULL, wire_get, NULL, NULL, NULL, napi_enumerable, NULL},
|
|
2548
|
+
{"promote", NULL, promote, NULL, NULL, NULL, napi_enumerable, NULL},
|
|
2549
|
+
{"get", NULL, message_get, NULL, NULL, NULL, napi_enumerable, NULL},
|
|
2550
|
+
{"set", NULL, message_set, NULL, NULL, NULL, napi_enumerable, NULL},
|
|
2551
|
+
{"encode", NULL, encode, NULL, NULL, NULL, napi_enumerable, NULL},
|
|
2552
|
+
{"encodeObject", NULL, encode_object, NULL, NULL, NULL,
|
|
2553
|
+
napi_enumerable, NULL},
|
|
2554
|
+
{"copyEncoded", NULL, copy_encoded, NULL, NULL, NULL, napi_enumerable,
|
|
2555
|
+
NULL},
|
|
2556
|
+
{"free", NULL, message_free, NULL, NULL, NULL, napi_enumerable, NULL},
|
|
2557
|
+
};
|
|
2558
|
+
|
|
2559
|
+
if (napi_define_properties(env, exports,
|
|
2560
|
+
sizeof(properties) / sizeof(properties[0]),
|
|
2561
|
+
properties) != napi_ok) {
|
|
2562
|
+
napi_throw_error(env, NULL, "pbwire: cannot export the addon");
|
|
2563
|
+
return NULL;
|
|
2564
|
+
}
|
|
2565
|
+
return exports;
|
|
2566
|
+
}
|