Skip to content
37 changes: 37 additions & 0 deletions benchmark/buffers/buffer-copy-static.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict';
const common = require('../common.js');

const bench = common.createBenchmark(main, {
bytes: [8, 128, 1024, 16384],
type: ['buffer', 'uint8array', 'arraybuffer'],
partial: ['true', 'false'],
n: [5e5],
});

function main({ n, bytes, type, partial }) {
let source, target;

switch (type) {
case 'buffer':
source = Buffer.allocUnsafe(bytes);
target = Buffer.allocUnsafe(bytes);
break;
case 'uint8array':
source = new Uint8Array(bytes);
target = new Uint8Array(bytes);
break;
case 'arraybuffer':
source = new ArrayBuffer(bytes);
target = new ArrayBuffer(bytes);
break;
}

const sourceStart = (partial === 'true' ? Math.floor(bytes / 2) : 0);
const sourceEnd = bytes;

bench.start();
for (let i = 0; i < n; i++) {
Buffer.copy(source, target, 0, sourceStart, sourceEnd);
}
bench.end(n);
}
50 changes: 50 additions & 0 deletions benchmark/buffers/buffer-copy-web-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict';
const common = require('../common.js');

const bench = common.createBenchmark(main, {
bytes: [8, 128, 1024, 16384],
type: ['buffer', 'uint8array', 'arraybuffer'],
partial: ['true', 'false'],
n: [5e5],
});

function main({ n, bytes, type, partial }) {
let source, target, srcView, dstBuffer;

switch (type) {
case 'buffer':
source = Buffer.allocUnsafe(bytes);
target = Buffer.allocUnsafe(bytes);
break;
case 'uint8array':
source = new Uint8Array(bytes);
target = new Uint8Array(bytes);
break;
case 'arraybuffer':
source = new ArrayBuffer(bytes);
target = new ArrayBuffer(bytes);
break;
}

const srcOffset = (partial === 'true' ? Math.floor(bytes / 2) : 0);
const length = bytes - srcOffset;
const dstOffset = 0;

if (type === 'arraybuffer') {
srcView = new Uint8Array(source, srcOffset, length);
dstBuffer = new Uint8Array(target);
} else {
srcView = new Uint8Array(
source.buffer || source,
source.byteOffset !== undefined ? source.byteOffset + srcOffset : srcOffset,
length

Check failure on line 40 in benchmark/buffers/buffer-copy-web-api.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Missing trailing comma
);
dstBuffer = new Uint8Array(target.buffer || target, target.byteOffset || 0);
}

bench.start();
for (let i = 0; i < n; i++) {
dstBuffer.set(srcView, dstOffset);
}
bench.end(n);
}
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
Loading
Loading