diff --git a/doc/api/buffer.markdown b/doc/api/buffer.markdown index 106097451b73d1..84ae79ab11a11c 100644 --- a/doc/api/buffer.markdown +++ b/doc/api/buffer.markdown @@ -213,7 +213,7 @@ Supports up to 48 bits of accuracy. For example: var b = new Buffer(6); b.writeUIntBE(0x1234567890ab, 0, 6); - // + // Set `noAssert` to `true` to skip validation of `value` and `offset`. Defaults to `false`. @@ -283,7 +283,7 @@ Example: }); console.log(copy); - // + // ### buf[index] @@ -498,6 +498,37 @@ Example: // 0x03042342 // 0x42230403 +### buf.readUInt64LE(offset[, noAssert]) +### buf.readUInt64BE(offset[, noAssert]) + +* `offset` Number +* `noAssert` Boolean, Optional, Default: false +* Return: Number or String + +Reads an unsigned 64 bit integer from the buffer at the specified offset with +specified endian format. + +If the value is greater than `Number.MAX_SAFE_INTEGER` then a String is +returned, otherwise a Number is returned. Because of this, it is assumed +that you will pass the return value to a JavaScript Int64 module, rather +than interact with the value directly. + +Set `noAssert` to true to skip validation of `offset`. This means that `offset` +may be beyond the end of the buffer. Defaults to `false`. + +Example: + + var buf = new Buffer(8); + buf[0] = buf[1] = buf[2] = buf[3] = 0x00; + buf[4] = buf[5] = buf[6] = buf[7] = 0xff; + // + + b.readUInt64BE(0); + // 4294967295 + + b.readUInt64LE(0); + // '1844674406941458432' + ### buf.readInt8(offset[, noAssert]) * `offset` Number @@ -544,6 +575,28 @@ may be beyond the end of the buffer. Defaults to `false`. Works as `buffer.readUInt32*`, except buffer contents are treated as two's complement signed values. +### buf.readInt64LE(offset[, noAssert]) +### buf.readInt64BE(offset[, noAssert]) + +* `offset` Number or String +* `noAssert` Boolean, Optional, Default: false +* Return: Number + +Reads a signed 64 bit integer from the buffer at the specified offset with +specified endian format. + +If the value is less than `Number.MIN_SAFE_INTEGER` or greater than +`Number.MAX_SAFE_INTEGER` then a String is returned, otherwise a Number +is returned. Because of this, it is assumed that you will pass the +return value to a JavaScript Int64 module, rather than interact with the +value directly. + +Set `noAssert` to true to skip validation of `offset`. This means that `offset` +may be beyond the end of the buffer. Defaults to `false`. + +Works as `buffer.readUInt64*`, except buffer contents are treated as two's +complement signed values. + ### buf.readFloatLE(offset[, noAssert]) ### buf.readFloatBE(offset[, noAssert]) @@ -624,7 +677,7 @@ Example: console.log(buf); - // + // ### buf.writeUInt16LE(value, offset[, noAssert]) ### buf.writeUInt16BE(value, offset[, noAssert]) @@ -654,8 +707,8 @@ Example: console.log(buf); - // - // + // + // ### buf.writeUInt32LE(value, offset[, noAssert]) ### buf.writeUInt32BE(value, offset[, noAssert]) @@ -683,8 +736,33 @@ Example: console.log(buf); - // - // + // + // + +### buf.writeUInt64LE(value, offset[, noAssert]) +### buf.writeUInt64BE(value, offset[, noAssert]) + +* `value` Number or String +* `offset` Number +* `noAssert` Boolean, Optional, Default: false + +Writes `value` to the buffer at the specified offset with specified endian +format. Note, `value` must be a valid unsigned 64 bit integer, or a String +representation of one. + +Set `noAssert` to true to skip validation of `value` and `offset`. This means +that `value` may be too large for the specific function and `offset` may be +beyond the end of the buffer leading to the values being silently dropped. This +should not be used unless you are certain of correctness. Defaults to `false`. + +Example: + + var buf = new Buffer(8) + + b.writeUInt64LE('0xffffffffffff') + + console.log(b); + // ### buf.writeInt8(value, offset[, noAssert]) @@ -739,6 +817,25 @@ should not be used unless you are certain of correctness. Defaults to `false`. Works as `buffer.writeUInt32*`, except value is written out as a two's complement signed integer into `buffer`. +### buf.writeInt64(value, offset[, noAssert]) +### buf.writeInt64(value, offset[, noAssert]) + +* `value` Number or String +* `offset` Number +* `noAssert` Boolean, Optional, Default: false + +Writes `value` to the buffer at the specified offset with specified endian +format. Note, `value` must be a valid signed 64 bit integer, or a String +representation of one. + +Set `noAssert` to true to skip validation of `value` and `offset`. This means +that `value` may be too large for the specific function and `offset` may be +beyond the end of the buffer leading to the values being silently dropped. This +should not be used unless you are certain of correctness. Defaults to `false`. + +Works as `buffer.writeUInt64*`, except value is written out as a two's +complement signed integer into `buffer`. + ### buf.writeFloatLE(value, offset[, noAssert]) ### buf.writeFloatBE(value, offset[, noAssert]) @@ -765,8 +862,8 @@ Example: console.log(buf); - // - // + // + // ### buf.writeDoubleLE(value, offset[, noAssert]) ### buf.writeDoubleBE(value, offset[, noAssert]) @@ -794,8 +891,8 @@ Example: console.log(buf); - // - // + // + // ### buf.fill(value[, offset][, end]) @@ -810,6 +907,15 @@ buffer. var b = new Buffer(50); b.fill("h"); +### buffer.address() + +Returns a String representation of the hex address of the pointer in memory. + + var b = new Buffer(1); + + b.address(); + // '10202ee08' + ### buffer.values() Creates iterator for buffer values (bytes). This function is called automatically diff --git a/doc/api/errors.markdown b/doc/api/errors.markdown index df5f2d74ac4ce1..109bca49685328 100644 --- a/doc/api/errors.markdown +++ b/doc/api/errors.markdown @@ -389,7 +389,7 @@ fs.readFile('/some/file/that/does-not-exist', function nodeStyleCallback(err, da fs.readFile('/some/file/that/does-exist', function(err, data) { console.log(err) // null - console.log(data) // + console.log(data) // }) ``` diff --git a/lib/buffer.js b/lib/buffer.js index 1b9c68465d6b03..ab6f224650adcb 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -398,7 +398,7 @@ Buffer.prototype.inspect = function inspect() { if (this.length > max) str += ' ... '; } - return '<' + this.constructor.name + ' ' + str + '>'; + return '<' + this.constructor.name + '@0x' + this.address() + ' ' + str + '>'; }; @@ -454,6 +454,13 @@ Buffer.prototype.fill = function fill(val, start, end) { }; +Buffer.prototype.address = function address() { + if (!(this instanceof Buffer)) + throw new TypeError('this must be a Buffer'); + return binding.address(this); +}; + + // XXX remove in v0.13 Buffer.prototype.get = util.deprecate(function get(offset) { offset = ~~offset; @@ -818,6 +825,38 @@ Buffer.prototype.readDoubleBE = function readDoubleBE(offset, noAssert) { }; +Buffer.prototype.readInt64LE = function readInt64LE(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) + checkOffset(offset, 8, this.length); + return binding.readInt64LE(this, offset); +}; + + +Buffer.prototype.readInt64BE = function readInt64BE(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) + checkOffset(offset, 8, this.length); + return binding.readInt64BE(this, offset); +}; + + +Buffer.prototype.readUInt64LE = function readUInt64LE(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) + checkOffset(offset, 8, this.length); + return binding.readUInt64LE(this, offset); +}; + + +Buffer.prototype.readUInt64BE = function readUInt64BE(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) + checkOffset(offset, 8, this.length); + return binding.readUInt64BE(this, offset); +}; + + function checkInt(buffer, value, offset, ext, max, min) { if (!(buffer instanceof Buffer)) throw new TypeError('buffer must be a Buffer instance'); @@ -1071,6 +1110,72 @@ Buffer.prototype.writeDoubleBE = function writeDoubleBE(val, offset, noAssert) { return offset + 8; }; + +function checkInt64(buffer, value, offset, ext) { + if (!(buffer instanceof Buffer)) + throw new TypeError('buffer must be a Buffer instance'); + if (!(typeof value === 'string' || typeof value === 'number')) + throw new TypeError('must pass a "string" or "number" for value'); + if (offset + ext > buffer.length) + throw new RangeError('index out of range'); +} + + +Buffer.prototype.writeInt64LE = function writeInt64LE(val, offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) + checkInt64(this, val, offset, 8); + return binding.writeInt64LE(this, val, offset); +}; + + +Buffer.prototype.writeInt64BE = function writeInt64BE(val, offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) + checkInt64(this, val, offset, 8); + return binding.writeInt64BE(this, val, offset); +}; + + +Buffer.prototype.writeUInt64LE = function writeUInt64LE(val, offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) + checkInt64(this, val, offset, 8); + return binding.writeUInt64LE(this, val, offset); +}; + + +Buffer.prototype.writeUInt64BE = function writeUInt64BE(val, offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) + checkInt64(this, val, offset, 8); + return binding.writeUInt64BE(this, val, offset); +}; + + +function checkPointer(buffer, value, offset) { + if (!(buffer instanceof Buffer)) + throw new TypeError('buffer must be a Buffer instance'); + if (!(value === null || value instanceof Buffer)) + throw new TypeError('value must be a Buffer instance or null'); +} + +Buffer.prototype.writePointerLE = function writePointerLE(val, offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) + checkPointer(this, val, offset); + return binding.writePointerLE(this, val, offset); +}; + + +Buffer.prototype.writePointerBE = function writePointerBE(val, offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) + checkPointer(this, val, offset); + return binding.writePointerBE(this, val, offset); +}; + + // ES6 iterator var ITERATOR_KIND_KEYS = 1; diff --git a/src/node_buffer.cc b/src/node_buffer.cc index fca08599e50feb..0a94994a02f8ea 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -9,8 +9,20 @@ #include "v8.h" #include +#include +#include +#include #include +#ifdef _WIN32 + #define strtoll _strtoi64 + #define strtoull _strtoui64 + #define PRId64 "lld" + #define PRIu64 "llu" +#else + #include +#endif + #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define CHECK_NOT_OOB(r) \ @@ -35,6 +47,12 @@ CHECK_NOT_OOB(end <= end_max); \ size_t length = end - start; +// used by the ReadInt64 functions to determine whether to return a Number +// or String, based on whether or not a JS Number will lose precision. +// http://mdn.io/max_safe_integer, http://mdn.io/min_safe_integer +#define JS_MAX_INT +9007199254740991LL +#define JS_MIN_INT -9007199254740991LL + namespace node { namespace Buffer { @@ -499,6 +517,62 @@ void ReadDoubleBE(const FunctionCallbackInfo& args) { } +template +void ReadInt64Generic(const FunctionCallbackInfo& args, + const char* formatter) { + Environment* env = Environment::GetCurrent(args); + + ARGS_THIS(args[0].As()); + + uint32_t offset = args[1]->Uint32Value(); + CHECK_LE(offset + sizeof(T), obj_length); + + union NoAlias { + T val; + char bytes[sizeof(T)]; + }; + + union NoAlias na; + const char* ptr = static_cast(obj_data) + offset; + memcpy(na.bytes, ptr, sizeof(na.bytes)); + if (endianness != GetEndianness()) + Swizzle(na.bytes, sizeof(na.bytes)); + + if (na.val < min || na.val > max) { + // return a String + char strbuf[20]; + snprintf(strbuf, sizeof(strbuf), formatter, na.val); + args.GetReturnValue().Set(node::OneByteString(env->isolate(), + strbuf, strlen(strbuf))); + } else { + // return a Number + args.GetReturnValue().Set(static_cast(na.val)); + } +} + + +void ReadInt64LE(const FunctionCallbackInfo& args) { + ReadInt64Generic(args, + "%" PRId64); +} + + +void ReadInt64BE(const FunctionCallbackInfo& args) { + ReadInt64Generic(args, + "%" PRId64); +} + + +void ReadUInt64LE(const FunctionCallbackInfo& args) { + ReadInt64Generic(args, "%" PRIu64); +} + + +void ReadUInt64BE(const FunctionCallbackInfo& args) { + ReadInt64Generic(args, "%" PRIu64); +} + + template uint32_t WriteFloatGeneric(const FunctionCallbackInfo& args) { ARGS_THIS(args[0].As()) @@ -541,6 +615,78 @@ void WriteDoubleBE(const FunctionCallbackInfo& args) { } +template +uint32_t WriteInt64Generic(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK(args[0]->IsObject()); + CHECK(args[1]->IsString() || args[1]->IsNumber()); + ARGS_THIS(args[0].As()) + + T val; + if (args[1]->IsNumber()) { + val = args[1]->IntegerValue(); + } else if (args[1]->IsString()) { + node::Utf8Value str(env->isolate(), args[1]); + const char* cstr = *str; + char* endptr; + + errno = 0; /* To distinguish success/failure after call */ + val = strtoT(cstr, &endptr, 10); + + if (errno == ERANGE && (val == min || val == max)) { + env->ThrowRangeError("value is out-of-range"); + return 0; + } else if (endptr == cstr || *endptr != '\0') { + env->ThrowTypeError("value is invalid"); + return 0; + } + } else { + UNREACHABLE(); + return 0; + } + + uint32_t offset = args[2]->Uint32Value(); + CHECK_LE(offset + sizeof(T), obj_length); + + union NoAlias { + T val; + char bytes[sizeof(T)]; + }; + + union NoAlias na = { val }; + char* ptr = static_cast(obj_data) + offset; + if (endianness != GetEndianness()) + Swizzle(na.bytes, sizeof(na.bytes)); + memcpy(ptr, na.bytes, sizeof(na.bytes)); + return offset + sizeof(na.bytes); +} + + +void WriteInt64LE(const FunctionCallbackInfo& args) { + args.GetReturnValue().Set(WriteInt64Generic(args)); +} + + +void WriteInt64BE(const FunctionCallbackInfo& args) { + args.GetReturnValue().Set(WriteInt64Generic(args)); +} + +void WriteUInt64LE(const FunctionCallbackInfo& args) { + args.GetReturnValue().Set(WriteInt64Generic(args)); +} + + +void WriteUInt64BE(const FunctionCallbackInfo& args) { + args.GetReturnValue().Set(WriteInt64Generic(args)); +} + + void ByteLengthUtf8(const FunctionCallbackInfo &args) { CHECK(args[0]->IsString()); @@ -695,6 +841,26 @@ void IndexOfNumber(const FunctionCallbackInfo& args) { } +void Address(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + if (!HasInstance(args[0])) + return env->ThrowTypeError("first arg should be a Buffer"); + + CHECK(args[0]->IsObject()); + ARGS_THIS(args[0].As()) + + // pointer-size * 2 (for hex printout) + 1 null byte + char strbuf[(sizeof(obj_data) * 2) + 1]; + const uintptr_t pointer = reinterpret_cast(obj_data); + + snprintf(strbuf, sizeof(strbuf), "%lx", pointer); + + args.GetReturnValue().Set(node::OneByteString(env->isolate(), + strbuf, strlen(strbuf))); +} + + // pass Buffer object to load prototype methods void SetupBufferJS(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -739,6 +905,7 @@ void Initialize(Handle target, env->SetMethod(target, "setupBufferJS", SetupBufferJS); + env->SetMethod(target, "address", Address); env->SetMethod(target, "byteLengthUtf8", ByteLengthUtf8); env->SetMethod(target, "compare", Compare); env->SetMethod(target, "fill", Fill); @@ -750,11 +917,19 @@ void Initialize(Handle target, env->SetMethod(target, "readDoubleLE", ReadDoubleLE); env->SetMethod(target, "readFloatBE", ReadFloatBE); env->SetMethod(target, "readFloatLE", ReadFloatLE); + env->SetMethod(target, "readInt64BE", ReadInt64BE); + env->SetMethod(target, "readInt64LE", ReadInt64LE); + env->SetMethod(target, "readUInt64BE", ReadUInt64BE); + env->SetMethod(target, "readUInt64LE", ReadUInt64LE); env->SetMethod(target, "writeDoubleBE", WriteDoubleBE); env->SetMethod(target, "writeDoubleLE", WriteDoubleLE); env->SetMethod(target, "writeFloatBE", WriteFloatBE); env->SetMethod(target, "writeFloatLE", WriteFloatLE); + env->SetMethod(target, "writeInt64BE", WriteInt64BE); + env->SetMethod(target, "writeInt64LE", WriteInt64LE); + env->SetMethod(target, "writeUInt64BE", WriteUInt64BE); + env->SetMethod(target, "writeUInt64LE", WriteUInt64LE); } diff --git a/test/parallel/test-buffer-inspect.js b/test/parallel/test-buffer-inspect.js index 707f778255ad51..8c5dd250c4c2a1 100644 --- a/test/parallel/test-buffer-inspect.js +++ b/test/parallel/test-buffer-inspect.js @@ -14,10 +14,10 @@ b.fill('1234'); var s = new buffer.SlowBuffer(4); s.fill('1234'); -var expected = ''; +var expected = //; -assert.strictEqual(util.inspect(b), expected); -assert.strictEqual(util.inspect(s), expected); +assert(expected.test(util.inspect(b))); +assert(expected.test(util.inspect(s))); b = new Buffer(2); b.fill('12'); @@ -25,14 +25,14 @@ b.fill('12'); s = new buffer.SlowBuffer(2); s.fill('12'); -expected = ''; +expected = //; -assert.strictEqual(util.inspect(b), expected); -assert.strictEqual(util.inspect(s), expected); +assert(expected.test(util.inspect(b))); +assert(expected.test(util.inspect(s))); buffer.INSPECT_MAX_BYTES = Infinity; assert.doesNotThrow(function() { - assert.strictEqual(util.inspect(b), expected); - assert.strictEqual(util.inspect(s), expected); -}); \ No newline at end of file + assert(expected.test(util.inspect(b))); + assert(expected.test(util.inspect(s))); +}); diff --git a/test/parallel/test-buffer-int64.js b/test/parallel/test-buffer-int64.js new file mode 100644 index 00000000000000..8408ab51ee23ae --- /dev/null +++ b/test/parallel/test-buffer-int64.js @@ -0,0 +1,86 @@ +'use strict'; +var common = require('../common'); +var assert = require('assert'); + +var Buffer = require('buffer').Buffer; + +var buf = new Buffer(8); + +['LE', 'BE'].forEach(function (endianness) { + // should allow simple ints to be written and read + var val = 123456789; + buf['writeInt64' + endianness](val, 0); + var rtn = buf['readInt64' + endianness](0); + assert.equal(val, rtn); + + // should allow INT64_MAX to be written and read + var val = '9223372036854775807'; + buf['writeInt64' + endianness](val, 0); + var rtn = buf['readInt64' + endianness](0); + assert.equal(val, rtn); + + // should return a Number when reading Number.MIN_SAFE_INTEGER + buf['writeInt64' + endianness](Number.MIN_SAFE_INTEGER, 0); + var rtn = buf['readInt64' + endianness](0); + assert.equal('number', typeof rtn); + assert.equal(Number.MIN_SAFE_INTEGER, rtn); + + // should return a Number when reading Number.MAX_SAFE_INTEGER + buf['writeInt64' + endianness](Number.MAX_SAFE_INTEGER, 0); + var rtn = buf['readInt64' + endianness](0); + assert.equal('number', typeof rtn); + assert.equal(Number.MAX_SAFE_INTEGER, rtn); + + // should return a String when reading Number.MAX_SAFE_INTEGER+1 + var plus_one = '9007199254740992'; + buf['writeInt64' + endianness](plus_one, 0); + var rtn = buf['readInt64' + endianness](0); + assert.equal('string', typeof rtn); + assert.equal(plus_one, rtn); + + // should return a String when reading Number.MIN_SAFE_INTEGER-1 + var minus_one = '-9007199254740992'; + buf['writeInt64' + endianness](minus_one, 0); + var rtn = buf['readInt64' + endianness](0); + assert.equal('string', typeof rtn); + assert.equal(minus_one, rtn); + + // should return a Number when reading 0, even when written as a String + var zero = '0'; + buf['writeInt64' + endianness](zero, 0); + var rtn = buf['readInt64' + endianness](0); + assert.equal('number', typeof rtn); + assert.equal(0, rtn); + + // should read and write a negative signed 64-bit integer + var val = -123456789; + buf['writeInt64' + endianness](val, 0); + assert.equal(val, buf['readInt64' + endianness](0)); + + // should read and write an unsigned 64-bit integer + var val = 123456789; + buf['writeUInt64' + endianness](val, 0); + assert.equal(val, buf['readUInt64' + endianness](0)); + + // should throw a RangeError upon INT64_MAX+1 being written + assert.throws(function () { + var val = '9223372036854775808'; + buf['writeInt64' + endianness](val, 0); + }, RangeError); + + // should throw a RangeError upon UINT64_MAX+1 being written + assert.throws(function () { + var val = '18446744073709551616'; + buf['writeUInt64' + endianness](val, 0); + }, RangeError); + + // should throw a TypeError upon invalid input + assert.throws(function () { + buf['writeInt64' + endianness]('bad', 0); + }, TypeError); + + // should throw a TypeError upon invalid input + assert.throws(function () { + buf['writeUInt64' + endianness]('bad', 0); + }, TypeError); +}); diff --git a/test/parallel/test-buffer.js b/test/parallel/test-buffer.js index 1d02148734e38a..7826774df14e2d 100644 --- a/test/parallel/test-buffer.js +++ b/test/parallel/test-buffer.js @@ -619,7 +619,7 @@ function buildBuffer(data) { var x = buildBuffer([0x81, 0xa3, 0x66, 0x6f, 0x6f, 0xa3, 0x62, 0x61, 0x72]); console.log(x.inspect()); -assert.equal('', x.inspect()); +assert(//.test(x.inspect())); var z = x.slice(4); console.log(z.inspect()); @@ -1172,3 +1172,12 @@ Buffer.poolSize = ps; assert.throws(function() { Buffer(10).copy(); }); + +// Test that Buffer#address() returns a hex string +var b = new Buffer(1); + +// just assert that it's a lowercase hex value with length >= 1 +var hex = /[\da-f]+/; + +console.log(b.address()); +assert(hex.test(b.address()));