diff --git a/packages/emnapi/include/node/node_api.h b/packages/emnapi/include/node/node_api.h index 7fa08bf4..aa258d4e 100644 --- a/packages/emnapi/include/node/node_api.h +++ b/packages/emnapi/include/node/node_api.h @@ -145,6 +145,18 @@ napi_create_external_buffer(napi_env env, void* finalize_hint, napi_value* result); #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + +#ifdef NAPI_EXPERIMENTAL +#define NODE_API_EXPERIMENTAL_HAS_CREATE_BUFFER_FROM_ARRAYBUFFER + +NAPI_EXTERN napi_status NAPI_CDECL +node_api_create_buffer_from_arraybuffer(napi_env env, + napi_value arraybuffer, + size_t byte_offset, + size_t byte_length, + napi_value* result); +#endif // NAPI_EXPERIMENTAL + NAPI_EXTERN napi_status NAPI_CDECL napi_create_buffer_copy(napi_env env, size_t length, const void* data, diff --git a/packages/emnapi/src/value/create.ts b/packages/emnapi/src/value/create.ts index 520bb59f..ccb7995e 100644 --- a/packages/emnapi/src/value/create.ts +++ b/packages/emnapi/src/value/create.ts @@ -412,6 +412,63 @@ export function napi_create_external_buffer ( ) } +/** + * @__sig ippppp + */ +export function node_api_create_buffer_from_arraybuffer ( + env: napi_env, + arraybuffer: napi_value, + byte_offset: size_t, + byte_length: size_t, + result: Pointer +): napi_status { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let value: number + + return $PREAMBLE!(env, (envObject) => { + $CHECK_ARG!(envObject, arraybuffer) + $CHECK_ARG!(envObject, result) + from64('byte_offset') + from64('byte_length') + byte_offset = byte_offset >>> 0 + byte_length = byte_length >>> 0 + const handle = emnapiCtx.handleStore.get(arraybuffer)! + const buffer = handle.value + if (!(buffer instanceof ArrayBuffer)) { + return envObject.setLastError(napi_status.napi_invalid_arg) + } + + if ((byte_length + byte_offset) > buffer.byteLength) { + const err: RangeError & { code?: string } = new RangeError('The byte offset + length is out of range') + err.code = 'ERR_OUT_OF_RANGE' + throw err + } + + const Buffer = emnapiCtx.feature.Buffer! + if (!Buffer) { + throw emnapiCtx.createNotSupportBufferError('node_api_create_buffer_from_arraybuffer', '') + } + const out = Buffer.from(buffer, byte_offset, byte_length) + if (buffer === wasmMemory.buffer) { + if (!emnapiExternalMemory.wasmMemoryViewTable.has(out)) { + emnapiExternalMemory.wasmMemoryViewTable.set(out, { + Ctor: Buffer, + address: byte_offset, + length: byte_length, + ownership: ReferenceOwnership.kUserland, + runtimeAllocated: 0 + }) + } + } + from64('result') + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + value = emnapiCtx.addToCurrentScope(out).id + makeSetValue('result', 0, 'value', '*') + return envObject.getReturnStatus() + }) +} + /** * @__sig ippppp */ diff --git a/packages/test/buffer/binding.c b/packages/test/buffer/binding.c index 54735849..37f1f6aa 100644 --- a/packages/test/buffer/binding.c +++ b/packages/test/buffer/binding.c @@ -190,6 +190,64 @@ static napi_value getMemoryDataAsArray(napi_env env, napi_callback_info info) { return ret; } +NAPI_EXTERN napi_status NAPI_CDECL +node_api_create_buffer_from_arraybuffer(napi_env env, + napi_value arraybuffer, + size_t byte_offset, + size_t byte_length, + napi_value* result); + +static napi_value testBufferFromArrayBuffer(napi_env env, + napi_callback_info info) { + napi_status status; + napi_value arraybuffer; + void* data; + napi_value buffer; + size_t byte_length = 1024; + size_t byte_offset = 0; + + status = napi_create_arraybuffer(env, byte_length, &data, &arraybuffer); + NODE_API_ASSERT(env, status == napi_ok, "Failed to create arraybuffer"); + + status = node_api_create_buffer_from_arraybuffer( + env, arraybuffer, byte_offset, byte_length, &buffer); + NODE_API_ASSERT( + env, status == napi_ok, "Failed to create buffer from arraybuffer"); + + void* buffer_data; + size_t buffer_length; + status = napi_get_buffer_info(env, buffer, &buffer_data, &buffer_length); + NODE_API_ASSERT(env, status == napi_ok, "Failed to get buffer info"); + NODE_API_ASSERT(env, buffer_length == byte_length, "Buffer length mismatch"); + + bool is_buffer; + status = napi_is_buffer(env, buffer, &is_buffer); + NODE_API_ASSERT(env, status == napi_ok, "Failed to check if value is buffer"); + NODE_API_ASSERT(env, is_buffer, "Expected a Buffer but did not get one"); + + status = node_api_create_buffer_from_arraybuffer( + env, arraybuffer, byte_length, byte_length + 1, &buffer); + NODE_API_ASSERT(env, + status == 10, + "Expected range error for invalid byte offset"); + napi_value last_error; + status = napi_get_and_clear_last_exception(env, &last_error); + NODE_API_ASSERT(env, status == napi_ok, "Failed to call napi_get_and_clear_last_exception"); + + napi_value non_arraybuffer; + status = napi_create_uint32(env, 123, &non_arraybuffer); + NODE_API_ASSERT( + env, status == napi_ok, "Failed to create non-arraybuffer value"); + + status = node_api_create_buffer_from_arraybuffer( + env, non_arraybuffer, byte_offset, byte_length, &buffer); + NODE_API_ASSERT(env, + status == napi_invalid_arg, + "Expected invalid arg error for non-arraybuffer input"); + + return arraybuffer; +} + static napi_value Init(napi_env env, napi_value exports) { napi_value theValue; @@ -208,6 +266,8 @@ static napi_value Init(napi_env env, napi_value exports) { DECLARE_NODE_API_PROPERTY("staticBuffer", staticBuffer), DECLARE_NODE_API_PROPERTY("invalidObjectAsBuffer", invalidObjectAsBuffer), DECLARE_NODE_API_PROPERTY("getMemoryDataAsArray", getMemoryDataAsArray), + DECLARE_NODE_API_PROPERTY("testBufferFromArrayBuffer", + testBufferFromArrayBuffer), }; NODE_API_CALL(env, napi_define_properties( diff --git a/packages/test/buffer/buffer.test.js b/packages/test/buffer/buffer.test.js index 4f3f13e9..48bb1875 100644 --- a/packages/test/buffer/buffer.test.js +++ b/packages/test/buffer/buffer.test.js @@ -58,4 +58,6 @@ module.exports = load('buffer').then(async binding => { assert.deepStrictEqual(binding.getMemoryDataAsArray(buffer), Array(6).fill(99)) assert.deepStrictEqual(binding.getMemoryDataAsArray(typedArray), Array(6).fill(99)) assert.deepStrictEqual(binding.getMemoryDataAsArray(dataView), Array(6).fill(99)) + + binding.testBufferFromArrayBuffer() })