Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions doc/api/buffer.md
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,99 @@ socket.on('readable', () => {

A `TypeError` will be thrown if `size` is not a number.

### Static method: `Buffer.copy(source, target, targetStart[, sourceStart[, sourceEnd]])`

<!-- YAML
added: REPLACEME
-->

* `source` {Buffer|TypedArray|DataView|ArrayBuffer|SharedArrayBuffer} The source
to copy data from.
* `target` {Buffer|TypedArray|DataView|ArrayBuffer|SharedArrayBuffer} The target
to copy data to.
* `targetStart` {integer} The offset within `target` at which to begin writing.
**Default:** `0`.
* `sourceStart` {integer} The offset within `source` from which to begin copying.
**Default:** `0`.
* `sourceEnd` {integer} The offset within `source` at which to stop copying
(exclusive). **Default:** `source.byteLength`.
* Returns: {integer} The number of bytes copied.

Copies data from `source` to `target`. This is a method that can copy data
between different types of binary data structures, including `Buffer`,
`TypedArray`, `DataView`, `ArrayBuffer`, and `SharedArrayBuffer` instances.

```mjs
import { Buffer } from 'node:buffer';

const src = Buffer.from([1, 2, 3, 4]);
const dst = Buffer.alloc(4);

const bytesCopied = Buffer.copy(src, dst, 0);
console.log(bytesCopied); // 4
console.log(dst); // <Buffer 01 02 03 04>
```

```cjs
const { Buffer } = require('node:buffer');

const src = Buffer.from([1, 2, 3, 4]);
const dst = Buffer.alloc(4);

const bytesCopied = Buffer.copy(src, dst, 0);
console.log(bytesCopied); // 4
console.log(dst); // <Buffer 01 02 03 04>
```

The method can also copy between different types:

```mjs
import { Buffer } from 'node:buffer';

// Copy from ArrayBuffer to Buffer
const ab = new Uint8Array([5, 6, 7, 8]).buffer;
const buf = Buffer.alloc(4);

Buffer.copy(ab, buf, 0);
console.log(buf); // <Buffer 05 06 07 08>

// Copy from Buffer to DataView
const src = Buffer.from([1, 2, 3]);
const targetAB = new ArrayBuffer(5);
const dv = new DataView(targetAB);

Buffer.copy(src, dv, 2);
console.log(new Uint8Array(targetAB)); // Uint8Array(5) [ 0, 0, 1, 2, 3 ]
```

```cjs
const { Buffer } = require('node:buffer');

// Copy from ArrayBuffer to Buffer
const ab = new Uint8Array([5, 6, 7, 8]).buffer;
const buf = Buffer.alloc(4);

Buffer.copy(ab, buf, 0);
console.log(buf); // <Buffer 05 06 07 08>

// Copy from Buffer to DataView
const src = Buffer.from([1, 2, 3]);
const targetAB = new ArrayBuffer(5);
const dv = new DataView(targetAB);

Buffer.copy(src, dv, 2);
console.log(new Uint8Array(targetAB)); // Uint8Array(5) [ 0, 0, 1, 2, 3 ]
```

If `targetStart` is negative or beyond the length of `target`, a \[`RangeError`]\[]
is thrown. If `sourceStart` is negative or beyond the length of `source`, a
\[`RangeError`]\[] is thrown. If `sourceEnd` is negative, a \[`RangeError`]\[] is
thrown. Values that exceed the respective buffer lengths are clamped to the
appropriate limits.

If `sourceStart` is greater than or equal to `sourceEnd`, zero bytes are copied
and the method returns `0`.

### Static method: `Buffer.byteLength(string[, encoding])`

<!-- YAML
Expand Down
95 changes: 57 additions & 38 deletions lib/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

const {
Array,
ArrayBufferIsView,
ArrayIsArray,
ArrayPrototypeForEach,
MathFloor,
Expand Down Expand Up @@ -65,6 +64,7 @@ const {
indexOfBuffer,
indexOfNumber,
indexOfString,
staticCopy,
swap16: _swap16,
swap32: _swap32,
swap64: _swap64,
Expand Down Expand Up @@ -203,42 +203,6 @@ function toInteger(n, defaultVal) {
return defaultVal;
}

function copyImpl(source, target, targetStart, sourceStart, sourceEnd) {
if (!ArrayBufferIsView(source))
throw new ERR_INVALID_ARG_TYPE('source', ['Buffer', 'Uint8Array'], source);
if (!ArrayBufferIsView(target))
throw new ERR_INVALID_ARG_TYPE('target', ['Buffer', 'Uint8Array'], target);

if (targetStart === undefined) {
targetStart = 0;
} else {
targetStart = NumberIsInteger(targetStart) ? targetStart : toInteger(targetStart, 0);
if (targetStart < 0)
throw new ERR_OUT_OF_RANGE('targetStart', '>= 0', targetStart);
}

if (sourceStart === undefined) {
sourceStart = 0;
} else {
sourceStart = NumberIsInteger(sourceStart) ? sourceStart : toInteger(sourceStart, 0);
if (sourceStart < 0 || sourceStart > source.byteLength)
throw new ERR_OUT_OF_RANGE('sourceStart', `>= 0 && <= ${source.byteLength}`, sourceStart);
}

if (sourceEnd === undefined) {
sourceEnd = source.byteLength;
} else {
sourceEnd = NumberIsInteger(sourceEnd) ? sourceEnd : toInteger(sourceEnd, 0);
if (sourceEnd < 0)
throw new ERR_OUT_OF_RANGE('sourceEnd', '>= 0', sourceEnd);
}

if (targetStart >= target.byteLength || sourceStart >= sourceEnd)
return 0;

return _copyActual(source, target, targetStart, sourceStart, sourceEnd);
}

function _copyActual(source, target, targetStart, sourceStart, sourceEnd, isUint8Copy = false) {
if (sourceEnd - sourceStart > target.byteLength - targetStart)
sourceEnd = sourceStart + target.byteLength - targetStart;
Expand Down Expand Up @@ -618,6 +582,61 @@ Buffer.concat = function concat(list, length) {
return buffer;
};

Buffer.copy = function copy(source, target, targetStart, sourceStart, sourceEnd) {
if (!isAnyArrayBuffer(source) && !isArrayBufferView(source)) {
throw new ERR_INVALID_ARG_TYPE('source', ['ArrayBuffer', 'SharedArrayBuffer', 'TypedArray'], source);
}

if (!isAnyArrayBuffer(target) && !isArrayBufferView(target)) {
throw new ERR_INVALID_ARG_TYPE('target', ['Buffer', 'ArrayBuffer', 'SharedArrayBuffer', 'TypedArray'], target);
}

if (targetStart === undefined) {
targetStart = 0;
} else {
targetStart = NumberIsInteger(targetStart) ? targetStart : toInteger(targetStart, 0);
if (targetStart < 0) {
throw new ERR_OUT_OF_RANGE('targetStart', '>= 0', targetStart);
}
}

const sourceByteLengthValue = source.byteLength;

if (sourceStart === undefined) {
sourceStart = 0;
} else {
sourceStart = NumberIsInteger(sourceStart) ? sourceStart : toInteger(sourceStart, 0);
if (sourceStart < 0 || sourceStart > sourceByteLengthValue) {
throw new ERR_OUT_OF_RANGE('sourceStart', `>= 0 && <= ${sourceByteLengthValue}`, sourceStart);
}
}

if (sourceEnd === undefined) {
sourceEnd = sourceByteLengthValue;
} else {
sourceEnd = NumberIsInteger(sourceEnd) ? sourceEnd : toInteger(sourceEnd, 0);
if (sourceEnd < 0) {
throw new ERR_OUT_OF_RANGE('sourceEnd', '>= 0', sourceEnd);
}

if (sourceEnd > sourceByteLengthValue) {
sourceEnd = sourceByteLengthValue;
}
}

if (sourceStart >= sourceEnd) {
return 0;
}

const targetByteLengthValue = target.byteLength;

if (targetStart >= targetByteLengthValue) {
return 0;
}

return staticCopy(source, target, targetStart, sourceStart, sourceEnd);
};

function base64ByteLength(str, bytes) {
// Handle padding
if (StringPrototypeCharCodeAt(str, bytes - 1) === 0x3D)
Expand Down Expand Up @@ -827,7 +846,7 @@ ObjectDefineProperty(Buffer.prototype, 'offset', {

Buffer.prototype.copy =
function copy(target, targetStart, sourceStart, sourceEnd) {
return copyImpl(this, target, targetStart, sourceStart, sourceEnd);
return Buffer.copy(this, target, targetStart, sourceStart, sourceEnd);
};

// No need to verify that "buf.length <= MAX_UINT32" since it's a read-only
Expand Down
64 changes: 63 additions & 1 deletion src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1399,7 +1399,14 @@ namespace {
std::pair<void*, size_t> DecomposeBufferToParts(Local<Value> buffer) {
void* pointer;
size_t byte_length;
if (buffer->IsArrayBuffer()) {

if (buffer->IsArrayBufferView()) {
Local<ArrayBufferView> view = buffer.As<ArrayBufferView>();
Local<ArrayBuffer> buffer = view->Buffer();

pointer = static_cast<uint8_t*>(buffer->Data()) + view->ByteOffset();
byte_length = view->ByteLength();
} else if (buffer->IsArrayBuffer()) {
Local<ArrayBuffer> ab = buffer.As<ArrayBuffer>();
pointer = ab->Data();
byte_length = ab->ByteLength();
Expand Down Expand Up @@ -1449,6 +1456,59 @@ void CopyArrayBuffer(const FunctionCallbackInfo<Value>& args) {
memcpy(dest, src, bytes_to_copy);
}

void StaticCopy(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

Local<Value> source = args[0];
Local<Value> target = args[1];

void* source_data;
size_t source_byte_length;
std::tie(source_data, source_byte_length) = DecomposeBufferToParts(source);

void* target_data;
size_t target_byte_length;
std::tie(target_data, target_byte_length) = DecomposeBufferToParts(target);

size_t target_start = static_cast<size_t>(args[2].As<Number>()->Value());
size_t source_start = static_cast<size_t>(args[3].As<Number>()->Value());
size_t source_end = static_cast<size_t>(args[4].As<Number>()->Value());

if (source_data == nullptr || target_data == nullptr) {
args.GetReturnValue().Set(0);
return;
}

if (target_start >= target_byte_length) {
return THROW_ERR_OUT_OF_RANGE(env, "targetStart is out of bounds");
}

if (source_start > source_byte_length || source_end > source_byte_length) {
return THROW_ERR_OUT_OF_RANGE(env,
"sourceStart or sourceEnd is out of bounds");
}

if (source_start >= source_end) {
args.GetReturnValue().Set(0);
return;
}

size_t bytes_to_copy = source_end - source_start;
size_t target_remaining = target_byte_length - target_start;

if (bytes_to_copy > target_remaining) {
bytes_to_copy = target_remaining;
}

if (bytes_to_copy > 0) {
uint8_t* dest = static_cast<uint8_t*>(target_data) + target_start;
uint8_t* src = static_cast<uint8_t*>(source_data) + source_start;
memmove(dest, src, bytes_to_copy);
}

args.GetReturnValue().Set(static_cast<uint32_t>(bytes_to_copy));
}

template <encoding encoding>
uint32_t WriteOneByteString(const char* src,
uint32_t src_len,
Expand Down Expand Up @@ -1576,6 +1636,7 @@ void Initialize(Local<Object> target,
SetMethodNoSideEffect(context, target, "indexOfString", IndexOfString);

SetMethod(context, target, "copyArrayBuffer", CopyArrayBuffer);
SetMethod(context, target, "staticCopy", StaticCopy);

SetMethod(context, target, "swap16", Swap16);
SetMethod(context, target, "swap32", Swap32);
Expand Down Expand Up @@ -1646,6 +1707,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(SlowIndexOfNumber);
registry->Register(fast_index_of_number);
registry->Register(IndexOfString);
registry->Register(StaticCopy);

registry->Register(Swap16);
registry->Register(Swap32);
Expand Down
4 changes: 2 additions & 2 deletions test/parallel/test-buffer-alloc.js
Original file line number Diff line number Diff line change
Expand Up @@ -1086,8 +1086,8 @@ assert.throws(
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "target" argument must be an instance of Buffer or ' +
'Uint8Array. Received undefined'
message: 'The "target" argument must be an instance of Buffer, ' +
'ArrayBuffer, SharedArrayBuffer, or TypedArray. Received undefined'
});

assert.throws(() => Buffer.from(), {
Expand Down
Loading
Loading