-
Notifications
You must be signed in to change notification settings - Fork 30.5k
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
Changes from 5 commits
914dc46
3e425fc
7bf9a2d
e92f4b7
7ef29ac
fb4ac8a
0f7b918
8c72550
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -310,6 +310,70 @@ 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); | ||
|
||
uint32_t mask32 = args[0]->Uint32Value(); | ||
char* mask = (char*)&mask32; | ||
|
||
Local<Object> target = args[1]->ToObject(env->isolate()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Before doing this should do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do. Should I add a similar check to buffer.copy? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Go ahead and add it to this PR, but leave it in a separate commit. Thanks :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll add this to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. strange. well, don't worry about it then. don't want that change holding this up. |
||
|
||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: I'd probably add a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should I add There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
|
||
uint32_t to_copy = MIN(MIN(source_end - source_start, | ||
target_length - target_start), | ||
obj_length - source_start); | ||
|
||
obj_data += source_start; | ||
target_data += target_start; | ||
|
||
uint32_t* target_data_32 = (uint32_t*)target_data; | ||
uint32_t* obj_data_32 = (uint32_t*)obj_data; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is pointer aliasing and strictly forbidden. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How should I fix this? Is the goal to only have one name in any frame point to a given pointer address? If so, would it be possible to add something like: static inline size_t Mask32(uint32_t* obj_data, uint32_t* target_data, uint32_t mask, size_t len) {
len = len / 4;
for (size_t i = 0; i < len; ++i) {
target_data[i] = obj_data[i] ^ mask;
}
return len * 4;
}
// then use it like so:
size_t written = Mask32(
reinterpret_cast<uint32_t*>(obj_data),
reinterpret_cast<uint32_t*>(target_data),
mask.as_dword,
to_copy
);
target_data += written;
obj_data += written;
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. iirc in cases like this you'd use a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, I see two issues here: one is pointer aliasing, the other is endianness. Endianness: doing 32 bits xor operations on bytes in a buffer will produce different results on BE and LE architectures. Pointer aliasing: the safe way to go about it is as follows: union {
char bytes[sizeof(uint32_t)];
uint32_t value;
} u;
for (size_t i = 0; i < to_copy_32; i += sizeof(u.bytes)) {
char* const pointer = obj_data + i;
memcpy(u.bytes, pointer, sizeof(u.bytes));
u.value ^= mask32;
memcpy(pointer, u.bytes, sizeof(u.bytes));
} Not a paragon of beauty but there you have it. |
||
size_t to_copy_32 = to_copy / 4; | ||
size_t i; | ||
for (i = 0; i < to_copy_32; ++i) { | ||
target_data_32[i] = mask32 ^ obj_data_32[i]; | ||
} | ||
target_data += to_copy_32 * 4; | ||
obj_data += to_copy_32 * 4; | ||
|
||
switch(to_copy % 4) { | ||
case 3: | ||
target_data[2] = obj_data[2] ^ mask[2]; | ||
case 2: | ||
target_data[1] = obj_data[1] ^ mask[1]; | ||
case 1: | ||
target_data[0] = obj_data[0] ^ mask[0]; | ||
case 0:; | ||
} | ||
return args.GetReturnValue().Set(to_copy); | ||
} | ||
|
||
|
||
// bytesCopied = buffer.copy(target[, targetStart][, sourceStart][, sourceEnd]); | ||
void Copy(const FunctionCallbackInfo<Value> &args) { | ||
Environment* env = Environment::GetCurrent(args); | ||
|
@@ -741,6 +805,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(), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
var common = require('../common'); | ||
var assert = require('assert'); | ||
|
||
var tests = [ | ||
testBasic, | ||
testBounds, | ||
testOverflow, | ||
testNonAligned, | ||
testReturnValue | ||
] | ||
|
||
tests.forEach(Function.prototype.call.bind(Function.prototype.call)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clever... but a hell of a lot longer than Minor style nit: missing semicolon. |
||
|
||
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) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use reinterpret_cast, don't use C-style casts.
I'm not sure if you're aware of this but this would have been pointer aliasing with any other pointer type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aliasing with
char
is okay right? @bnoordhuisThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, a pointer to char may alias a pointer to another type (but not the other way around.)