Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

buffer: add buf.mask for ws #1202

Closed
wants to merge 8 commits into from
Closed
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
20 changes: 20 additions & 0 deletions doc/api/buffer.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,26 @@ buffer.
var b = new Buffer(50);
b.fill("h");

### buf.mask(maskValue, targetBuffer[, targetStart][, sourceStart][, sourceEnd])

* `maskValue` Number
* `targetBuffer` Buffer
* `targetStart` Number, optional
* `sourceStart` Number, optional
* `sourceEnd` Number, optional

Takes the contents of `buf`, starting at `sourceStart` and ending at
`sourceEnd`, and XOR's them with `maskValue`. The result is written to
`targetBuffer`, starting at `targetOffset`. `sourceStart` and `targetStart`
will default to `0` if not given; `sourceEnd` will default to `buf.length` if
not given. The start, end, and offset parameters function the same as the
corresponding parameters to
[buf.copy](#buffer_buf_copy_targetbuffer_targetstart_sourcestart_sourceend).

The target region of memory may overlap the source region of memory.

Returns the number of bytes masked and written into `targetBuffer`.

### buffer.values()

Creates iterator for buffer values (bytes). This function is called automatically
Expand Down
72 changes: 72 additions & 0 deletions src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ using v8::Uint32;
using v8::Value;


union CopyDWord {
char as_bytes[sizeof(uint32_t)];
uint32_t as_dword;
};


bool HasInstance(Handle<Value> val) {
return val->IsObject() && HasInstance(val.As<Object>());
}
Expand Down Expand Up @@ -310,6 +316,71 @@ void Base64Slice(const FunctionCallbackInfo<Value>& args) {
}


// bytesCopied = buffer.mask(mask, target, targetOffset, sourceStart, sourceEnd)
void Mask(const FunctionCallbackInfo<Value> &args) {
Environment* env = Environment::GetCurrent(args);

CopyDWord mask;
mask.as_dword = args[0]->Uint32Value();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't hurt but you don't have to store the mask in a union, just a uint32_t is alright.


CHECK(args[1]->IsObject());
Local<Object> target = args[1].As<Object>();

if (!HasInstance(target))
return env->ThrowTypeError("second arg should be a Buffer");

ARGS_THIS(args.This())
size_t target_length = target->GetIndexedPropertiesExternalArrayDataLength();
char* target_data = static_cast<char*>(
target->GetIndexedPropertiesExternalArrayData());
size_t target_start;
size_t source_start;
size_t source_end;

CHECK_NOT_OOB(ParseArrayIndex(args[2], 0, &target_start));
CHECK_NOT_OOB(ParseArrayIndex(args[3], 0, &source_start));
CHECK_NOT_OOB(ParseArrayIndex(args[4], obj_length, &source_end));

// Copy 0 bytes; we're done
if (target_start >= target_length || source_start >= source_end)
return args.GetReturnValue().Set(0);

if (source_start > obj_length)
return env->ThrowRangeError("out of range index");

if (source_end - source_start > target_length - target_start)
source_end = source_start + target_length - target_start;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore my previous comment. This calculation can in theory overflow (although not yet in practice due to size restrictions of buffers) assuming 32 bits size_t: source_end = (2**31+1) + 2**31 - 0; // 1

I'd probably add a CHECK_LE(source_start, source_end), just in case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I add CHECK_LE to buffer.copy as well, from which the above code was (more-or-less directly) lifted?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, same issue. Yes, a check would be good.

Medium term, we should look into removing the duplication in that file.

CHECK_LE(source_start, source_end);

uint32_t to_copy = MIN(MIN(source_end - source_start,
target_length - target_start),
obj_length - source_start);
size_t written = (to_copy / sizeof(uint32_t)) * sizeof(uint32_t);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't the linter complain about the sizeof(uint32_t)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, to_copy should probably be size_t. At least, I don't see why it needs to be uint32_t.

obj_data += source_start;
target_data += target_start;

CopyDWord tmp;
for (size_t i = 0; i < written; i += sizeof(tmp.as_bytes)) {
memcpy(tmp.as_bytes, obj_data + i, sizeof(tmp.as_bytes));
tmp.as_dword ^= mask.as_dword;
memcpy(target_data + i, tmp.as_bytes, sizeof(tmp.as_bytes));
}
target_data += written;
obj_data += written;

switch (to_copy % 4) {
case 3:
target_data[2] = obj_data[2] ^ mask.as_bytes[2];
case 2:
target_data[1] = obj_data[1] ^ mask.as_bytes[1];
case 1:
target_data[0] = obj_data[0] ^ mask.as_bytes[0];
case 0: {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just omit the case 0 statement.

}
return args.GetReturnValue().Set(to_copy);
}


// bytesCopied = buffer.copy(target[, targetStart][, sourceStart][, sourceEnd]);
void Copy(const FunctionCallbackInfo<Value> &args) {
Environment* env = Environment::GetCurrent(args);
Expand Down Expand Up @@ -741,6 +812,7 @@ void SetupBufferJS(const FunctionCallbackInfo<Value>& args) {
env->SetMethod(proto, "utf8Write", Utf8Write);

env->SetMethod(proto, "copy", Copy);
env->SetMethod(proto, "mask", Mask);

// for backwards compatibility
proto->ForceSet(env->offset_string(),
Expand Down
94 changes: 94 additions & 0 deletions test/parallel/test-buffer-mask.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
var common = require('../common');
var assert = require('assert');

var tests = [
testBasic,
testBounds,
testOverflow,
testNonAligned,
testReturnValue
]

tests.forEach(function(fn) {
fn();
});

function referenceImplementation(source, maskNum, output, offset, start, end) {
var i = 0;
var mask = new Buffer(4);
var length = end - start;
mask.writeUInt32LE(maskNum, 0);
var toCopy = Math.min(length, output.length - offset);
var maskIdx = 0;
for (var i = 0; i < toCopy; ++i) {
output[i + offset] = source[i + start] ^ mask[maskIdx];
maskIdx = (maskIdx + 1) & 3;
}
}

function testBasic() {
var input = new Buffer(256);
var output = new Buffer(256);
var refoutput = new Buffer(256);
var mask = 0xF0F0F0F0;

for (var i = 0; i < input.length; ++i)
input[i] = i;

output[0] = refoutput[0] = 0;
referenceImplementation(input, mask, refoutput, 1, 0, 255);
input.mask(mask, output, 1, 0, 255);
for (var i = 0; i < input.length; ++i) {
assert.equal(output[i], refoutput[i]);
}
}

function testBounds() {
var input = new Buffer(16);
var output = new Buffer(32);
try {
input.mask(120120, output, 0, 0, 17);
assert.fail('expected error');
} catch(err) {
assert.ok(err);
}
}

function testOverflow() {
var input = new Buffer(16);
var output = new Buffer(15);
try {
input.mask(120120, output);
assert.fail('expected error');
} catch(err) {
assert.ok(err);
}
}

function testNonAligned() {
var input = new Buffer(16);
var output = new Buffer(16);
var refoutput = new Buffer(16);
var mask = 0xF0F0F0F0;

for (var i = 0; i < input.length; ++i)
input[i] = i;

for (var end = 3; end > 0; --end) {
referenceImplementation(input, mask, refoutput, 0, 0, end);
input.mask(mask, output, 0, 0, end);

for (var i = 0; i < end; ++i)
assert.equal(output[i], refoutput[i]);
}
}

function testReturnValue() {
var input = new Buffer(16);
var output = new Buffer(16);
assert.equal(input.mask(0, output), 16)
assert.equal(input.mask(0, output, 4), 12)
assert.equal(input.mask(0, output, 4, 6), 10)
assert.equal(input.mask(0, output, 4, 6, 4), 0)
assert.equal(input.mask(0, output, 4, 6, 8), 2)
}