-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbigint-buffer.c
More file actions
298 lines (264 loc) 路 9.98 KB
/
Copy pathbigint-buffer.c
File metadata and controls
298 lines (264 loc) 路 9.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#define NAPI_EXPERIMENTAL
#include <node_api.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BIT_MASK(n) (~( ((~0ull) << ((n)-1)) << 1 ))
// Temporary staging capacity for conversion paths. Keeps small payloads on
// stack and avoids heap churn on hot paths.
#define STACK_TMP_BYTES 256u
#define STACK_TMP_WORDS (STACK_TMP_BYTES / sizeof(uint64_t))
#if defined(_WIN16) || defined(_WIN32) || defined(_WIN64)
#define bswap64(x) _byteswap_uint64(x)
#else
#define bswap64(x) __builtin_bswap64(x)
#endif
// Throw a JS error and return NULL. Used in place of bare `assert(status == napi_ok)`
// because asserts compile out under NDEBUG, leaving release builds with no
// error handling at all (CWE-617 Reachable Assertion / CWE-754 Improper Check
// for Unusual or Exceptional Conditions).
#define NAPI_CHECK(env, status, msg) \
do { \
if ((status) != napi_ok) { \
napi_throw_error((env), NULL, (msg)); \
return NULL; \
} \
} while (0)
/**
* Converts a Buffer to bigint.
* node param 0: buffer
* node param 1: big_endian (optional boolean, default false)
*
* returns bigint
*/
napi_value toBigInt(napi_env env, napi_callback_info info) {
napi_value argv[2];
napi_status status;
size_t argc = 2;
status = napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
NAPI_CHECK(env, status, "toBigInt: napi_get_cb_info failed");
if (argc < 1) {
napi_throw_error(env, "EINVAL", "toBigInt: missing buffer argument");
return NULL;
}
// Validate that argv[0] is actually a Buffer/Uint8Array view.
bool is_buffer = false;
status = napi_is_buffer(env, argv[0], &is_buffer);
NAPI_CHECK(env, status, "toBigInt: napi_is_buffer failed");
if (!is_buffer) {
napi_throw_type_error(env, "EINVAL", "toBigInt: first argument must be a Buffer");
return NULL;
}
bool big_endian = false;
if (argc >= 2) {
// napi_get_cb_info guarantees argv[1] is at minimum a napi_value
// representing undefined when not passed, so this is well-defined.
status = napi_get_value_bool(env, argv[1], &big_endian);
if (status != napi_ok) {
big_endian = false; // tolerate non-bool by defaulting to little-endian
}
}
uint8_t* buffer = NULL;
size_t len = 0;
status = napi_get_buffer_info(env, argv[0], (void**)&buffer, &len);
NAPI_CHECK(env, status, "toBigInt: napi_get_buffer_info failed");
// Empty buffer => 0n.
if (len == 0) {
napi_value zero;
status = napi_create_bigint_int64(env, 0, &zero);
NAPI_CHECK(env, status, "toBigInt: napi_create_bigint_int64 failed");
return zero;
}
bool not_64_aligned = (len & 7) != 0;
size_t overflow_len = not_64_aligned ? 8 - (len & 0x7) : 0;
size_t aligned_len = len + overflow_len;
size_t len_in_words = not_64_aligned ? (len >> 3) + 1 : (len >> 3);
uint64_t stack_words[STACK_TMP_WORDS];
bool fits_in_stack = aligned_len <= sizeof(stack_words);
uint8_t* bufTemp = NULL;
if (fits_in_stack) {
bufTemp = (uint8_t*)stack_words;
} else {
bufTemp = (uint8_t*)malloc(aligned_len);
if (bufTemp == NULL) {
napi_throw_error(env, "ENOMEM", "toBigInt: allocation failed");
return NULL;
}
}
if (overflow_len > 0) {
memset(bufTemp + len, 0, overflow_len);
}
memcpy(bufTemp, buffer, len);
uint64_t* as_64_aligned = (uint64_t*)bufTemp;
size_t overflow_in_bits = overflow_len << 3;
napi_value out;
if (big_endian) {
if (len_in_words == 1) {
as_64_aligned[0] = not_64_aligned
? bswap64(as_64_aligned[0]) >> overflow_in_bits
: bswap64(as_64_aligned[0]);
} else {
uint64_t temp;
size_t last_word = len_in_words - 1;
size_t end_ptr = last_word;
int32_t offset;
for (offset = 0; offset < (int32_t)(len_in_words / 2); offset++) {
temp = as_64_aligned[offset];
as_64_aligned[offset] = as_64_aligned[end_ptr];
as_64_aligned[end_ptr] = temp;
end_ptr--;
}
uint64_t prev_overflow = 0;
for (offset = (int32_t)last_word; offset >= 0; offset--) {
uint64_t as_little_endian = bswap64(as_64_aligned[offset]);
uint64_t overflow = as_little_endian & BIT_MASK(overflow_in_bits);
as_64_aligned[offset] = not_64_aligned
? (as_little_endian >> overflow_in_bits) | prev_overflow
: as_little_endian;
prev_overflow = overflow << (64 - overflow_in_bits);
}
}
}
status = napi_create_bigint_words(env, 0, len_in_words, as_64_aligned, &out);
if (status != napi_ok) {
if (!fits_in_stack) free(bufTemp);
napi_throw_error(env, NULL, "toBigInt: napi_create_bigint_words failed");
return NULL;
}
if (!fits_in_stack) {
free(bufTemp);
}
return out;
}
/**
* Converts a BigInt to a Buffer.
* node param 0: BigInt
* node param 1: buffer
* node param 2: big_endian (optional boolean, default false)
*
* returns the input buffer (with bytes written in place)
*/
napi_value fromBigInt(napi_env env, napi_callback_info info) {
napi_value argv[3];
napi_status status;
size_t argc = 3;
status = napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
NAPI_CHECK(env, status, "fromBigInt: napi_get_cb_info failed");
if (argc < 2) {
napi_throw_error(env, "EINVAL", "fromBigInt: requires bigint and buffer arguments");
return NULL;
}
// Validate argv[0] is a bigint.
napi_valuetype t0;
status = napi_typeof(env, argv[0], &t0);
NAPI_CHECK(env, status, "fromBigInt: napi_typeof failed");
if (t0 != napi_bigint) {
napi_throw_type_error(env, "EINVAL", "fromBigInt: first argument must be a bigint");
return NULL;
}
// Validate argv[1] is a Buffer.
bool is_buffer = false;
status = napi_is_buffer(env, argv[1], &is_buffer);
NAPI_CHECK(env, status, "fromBigInt: napi_is_buffer failed");
if (!is_buffer) {
napi_throw_type_error(env, "EINVAL", "fromBigInt: second argument must be a Buffer");
return NULL;
}
bool big_endian = false;
if (argc >= 3) {
status = napi_get_value_bool(env, argv[2], &big_endian);
if (status != napi_ok) {
big_endian = false;
}
}
size_t word_count = 0;
status = napi_get_value_bigint_words(env, argv[0], NULL, &word_count, NULL);
NAPI_CHECK(env, status, "fromBigInt: napi_get_value_bigint_words (size query) failed");
uint8_t* raw_buffer = NULL;
size_t byte_width = 0;
status = napi_get_buffer_info(env, argv[1], (void**)&raw_buffer, &byte_width);
NAPI_CHECK(env, status, "fromBigInt: napi_get_buffer_info failed");
if (word_count == 0 || byte_width == 0) {
if (byte_width > 0) memset(raw_buffer, 0, byte_width);
return argv[1];
}
int sign_bit = 0;
bool not_64_aligned = (byte_width & 7) != 0;
size_t overflow_len = not_64_aligned ? 8 - (byte_width & 0x7) : 0;
size_t word_width = (byte_width >> 3) + (not_64_aligned ? 1 : 0);
size_t original_word_width = word_width;
if (word_count > word_width) {
word_count = word_width; // silent truncation matches JS toBufferBE semantics
}
// Always use an internal aligned staging area so we never alias/write
// uint64_t words through a potentially unaligned Buffer pointer.
uint64_t stack_buffer[STACK_TMP_WORDS];
size_t staged_word_count = original_word_width;
size_t staged_bytes = staged_word_count * sizeof(uint64_t);
size_t stack_capacity_bytes = sizeof(stack_buffer);
bool fits_in_stack = staged_bytes <= stack_capacity_bytes;
uint64_t* conv_buffer = NULL;
bool allocated = false;
if (fits_in_stack) {
conv_buffer = stack_buffer;
} else {
conv_buffer = (uint64_t*)malloc(staged_bytes);
if (conv_buffer == NULL) {
napi_throw_error(env, "ENOMEM", "fromBigInt: allocation failed");
return NULL;
}
allocated = true;
}
memset(conv_buffer, 0, staged_bytes);
status = napi_get_value_bigint_words(env, argv[0], &sign_bit, &word_count, conv_buffer);
if (status != napi_ok) {
if (allocated) free(conv_buffer);
napi_throw_error(env, NULL, "fromBigInt: napi_get_value_bigint_words failed");
return NULL;
}
if (sign_bit != 0) {
if (allocated) free(conv_buffer);
napi_throw_range_error(env, "EINVAL", "fromBigInt: negative bigint values are not supported");
return NULL;
}
if (big_endian) {
uint64_t temp;
size_t conv_words = original_word_width;
size_t last_word = conv_words - 1;
size_t end_ptr = last_word;
int32_t offset;
for (offset = 0; offset < (int32_t)(conv_words / 2); offset++) {
temp = bswap64(conv_buffer[offset]);
conv_buffer[offset] = bswap64(conv_buffer[end_ptr]);
conv_buffer[end_ptr] = temp;
end_ptr--;
}
if (conv_words & 1) {
conv_buffer[conv_words / 2] = bswap64(conv_buffer[conv_words / 2]);
}
}
const uint8_t* src_bytes =
(const uint8_t*)(big_endian && not_64_aligned
? (((uint8_t*)conv_buffer) + (8 - (byte_width & 7)))
: (uint8_t*)conv_buffer);
memcpy(raw_buffer, src_bytes, byte_width);
if (allocated) {
free(conv_buffer);
}
return argv[1];
}
napi_value init_all(napi_env env, napi_value exports) {
napi_value bigint_fn = NULL;
napi_value frombigint_fn = NULL;
napi_status status;
status = napi_create_function(env, NULL, 0, toBigInt, NULL, &bigint_fn);
NAPI_CHECK(env, status, "init: failed to create toBigInt");
status = napi_create_function(env, NULL, 0, fromBigInt, NULL, &frombigint_fn);
NAPI_CHECK(env, status, "init: failed to create fromBigInt");
status = napi_set_named_property(env, exports, "toBigInt", bigint_fn);
NAPI_CHECK(env, status, "init: failed to attach toBigInt");
status = napi_set_named_property(env, exports, "fromBigInt", frombigint_fn);
NAPI_CHECK(env, status, "init: failed to attach fromBigInt");
return exports;
}
NAPI_MODULE(NODE_GYP_MODULE_NAME, init_all);