diff --git a/doc/api/buffer.markdown b/doc/api/buffer.markdown index 106097451b73d1..ba927029ff72be 100644 --- a/doc/api/buffer.markdown +++ b/doc/api/buffer.markdown @@ -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 diff --git a/src/node_buffer.cc b/src/node_buffer.cc index fa77c0779762a6..ecdc8845ddb178 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -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 val) { return val->IsObject() && HasInstance(val.As()); } @@ -310,6 +316,71 @@ void Base64Slice(const FunctionCallbackInfo& args) { } +// bytesCopied = buffer.mask(mask, target, targetOffset, sourceStart, sourceEnd) +void Mask(const FunctionCallbackInfo &args) { + Environment* env = Environment::GetCurrent(args); + + CopyDWord mask; + mask.as_dword = args[0]->Uint32Value(); + + CHECK(args[1]->IsObject()); + Local target = args[1].As(); + + 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( + 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; + 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); + 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: {} + } + return args.GetReturnValue().Set(to_copy); +} + + // bytesCopied = buffer.copy(target[, targetStart][, sourceStart][, sourceEnd]); void Copy(const FunctionCallbackInfo &args) { Environment* env = Environment::GetCurrent(args); @@ -741,6 +812,7 @@ void SetupBufferJS(const FunctionCallbackInfo& args) { env->SetMethod(proto, "utf8Write", Utf8Write); env->SetMethod(proto, "copy", Copy); + env->SetMethod(proto, "mask", Mask); // for backwards compatibility proto->ForceSet(env->offset_string(), diff --git a/test/parallel/test-buffer-mask.js b/test/parallel/test-buffer-mask.js new file mode 100644 index 00000000000000..072e90ed9f139f --- /dev/null +++ b/test/parallel/test-buffer-mask.js @@ -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) +}