// Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. #include "node.h" #include "node_internals.h" #include "node_buffer.h" #include "smalloc.h" #include "string_bytes.h" #include "v8.h" #include "v8-profiler.h" #include #include #include #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define CHECK_NOT_OOB(r) \ do { if (!(r)) return ThrowRangeError("out of range index"); } while (0) #define ARGS_THIS(argT) \ Local obj = argT; \ size_t obj_length = obj->GetIndexedPropertiesExternalArrayDataLength(); \ char* obj_data = static_cast( \ obj->GetIndexedPropertiesExternalArrayData()); #define SLICE_START_END(start_arg, end_arg, end_max) \ size_t start; \ size_t end; \ CHECK_NOT_OOB(ParseArrayIndex(start_arg, 0, &start)); \ CHECK_NOT_OOB(ParseArrayIndex(end_arg, end_max, &end)); \ if (end < start) end = start; \ CHECK_NOT_OOB(end <= end_max); \ size_t length = end - start; namespace node { namespace Buffer { using v8::Arguments; using v8::Function; using v8::FunctionTemplate; using v8::Handle; using v8::HandleScope; using v8::Local; using v8::Number; using v8::Object; using v8::Persistent; using v8::String; using v8::Uint32; using v8::Undefined; using v8::Value; Persistent p_buffer_fn; bool HasInstance(Handle val) { return val->IsObject() && HasInstance(val.As()); } bool HasInstance(Handle obj) { if (!obj->HasIndexedPropertiesInExternalArrayData()) return false; v8::ExternalArrayType type = obj->GetIndexedPropertiesExternalArrayDataType(); return type == v8::kExternalUnsignedByteArray; } char* Data(Handle val) { assert(val->IsObject()); return Data(val.As()); } char* Data(Handle obj) { assert(obj->HasIndexedPropertiesInExternalArrayData()); return static_cast(obj->GetIndexedPropertiesExternalArrayData()); } size_t Length(Handle val) { assert(val->IsObject()); return Length(val.As()); } size_t Length(Handle obj) { assert(obj->HasIndexedPropertiesInExternalArrayData()); return obj->GetIndexedPropertiesExternalArrayDataLength(); } // TODO(trevnorris): would be more efficient to use StringBytes to calculate the // length and write out the data beforehand then do the same as New(). Local New(Handle string) { HandleScope scope(node_isolate); Handle argv[1] = { string }; Local obj = p_buffer_fn->NewInstance(1, argv); return scope.Close(obj); } // TODO(trevnorris): these have a flaw by needing to call the Buffer inst then // Alloc. continue to look for a better architecture. Local New(size_t length) { HandleScope scope(node_isolate); assert(length <= kMaxLength); Handle argv[2]; // this is safe b/c Undefined and length fits in an SMI, so there's no risk // of GC reclaiming the values prematurely. argv[0] = Undefined(node_isolate); argv[1] = Uint32::New(length, node_isolate); Local obj = p_buffer_fn->NewInstance(2, argv); smalloc::Alloc(obj, new char[length], length); return scope.Close(obj); } // TODO(trevnorris): for backwards compatibility this is left to copy the data, // but for consistency w/ the other should use data. And a copy version renamed // to something else. Local New(const char* data, size_t length) { HandleScope scope(node_isolate); assert(length <= kMaxLength); Handle argv[2]; // this is safe b/c Undefined and length fits in an SMI, so there's no risk // of GC reclaiming the values prematurely. argv[0] = Undefined(node_isolate); argv[1] = Uint32::New(length, node_isolate); Local obj = p_buffer_fn->NewInstance(2, argv); char* new_data = new char[length]; memcpy(new_data, data, length); smalloc::Alloc(obj, new_data, length); return scope.Close(obj); } Local New(char* data, size_t length, smalloc::FreeCallback callback, void* hint) { HandleScope scope(node_isolate); assert(length <= kMaxLength); Handle argv[2]; // this is safe b/c Undefined and length fits in an SMI, so there's no risk // of GC reclaiming the values prematurely. argv[0] = Undefined(node_isolate); argv[1] = Uint32::New(length, node_isolate); Local obj = p_buffer_fn->NewInstance(2, argv); smalloc::Alloc(obj, data, length, callback, hint); return scope.Close(obj); } Local Use(char* data, uint32_t length) { HandleScope scope(node_isolate); assert(length <= kMaxLength); Handle argv[2]; // this is safe b/c Undefined and length fits in an SMI, so there's no risk // of GC reclaiming the values prematurely. argv[0] = Undefined(node_isolate); argv[1] = Uint32::New(length, node_isolate); Local obj = p_buffer_fn->NewInstance(2, argv); smalloc::Alloc(obj, data, length); return scope.Close(obj); } template Handle StringSlice(const Arguments& args) { HandleScope scope(node_isolate); ARGS_THIS(args.This()) SLICE_START_END(args[0], args[1], obj_length) return scope.Close(StringBytes::Encode(obj_data + start, length, encoding)); } Handle BinarySlice(const Arguments& args) { return StringSlice(args); } Handle AsciiSlice(const Arguments& args) { return StringSlice(args); } Handle Utf8Slice(const Arguments& args) { return StringSlice(args); } Handle Ucs2Slice(const Arguments& args) { return StringSlice(args); } Handle HexSlice(const Arguments& args) { return StringSlice(args); } Handle Base64Slice(const Arguments& args) { return StringSlice(args); } // bytesCopied = buffer.copy(target[, targetStart][, sourceStart][, sourceEnd]); Handle Copy(const Arguments &args) { HandleScope scope(node_isolate); Local target = args[0]->ToObject(); if (!HasInstance(target)) return ThrowTypeError("first 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[1], 0, &target_start)); CHECK_NOT_OOB(ParseArrayIndex(args[2], 0, &source_start)); CHECK_NOT_OOB(ParseArrayIndex(args[3], obj_length, &source_end)); // Copy 0 bytes; we're done if (target_start >= target_length || source_start >= source_end) return scope.Close(Uint32::New(0, node_isolate)); if (source_start > obj_length) return ThrowRangeError("out of range index"); if (source_end - source_start > target_length - target_start) source_end = source_start + target_length - target_start; size_t to_copy = MIN(MIN(source_end - source_start, target_length - target_start), obj_length - source_start); memmove((void *)(target_data + target_start), (const void*)(obj_data + source_start), to_copy); return scope.Close(Uint32::New(to_copy, node_isolate)); } // buffer.fill(value[, start][, end]); Handle Fill(const Arguments &args) { HandleScope scope(node_isolate); ARGS_THIS(args.This()) int value; if (args[0]->IsString()) { String::AsciiValue at(args[0]); value = (*at)[0]; } else { value = (char)args[0]->Int32Value(); } SLICE_START_END(args[1], args[2], obj_length) memset((void*)(obj_data + start), value, length); return args.This(); } template Handle StringWrite(const Arguments& args) { HandleScope scope(node_isolate); ARGS_THIS(args.This()) if (!args[0]->IsString()) return ThrowTypeError("Argument must be a string"); Local str = args[0]->ToString(); if (encoding == HEX && str->Length() % 2 != 0) return ThrowTypeError("Invalid hex string"); size_t offset; size_t max_length; CHECK_NOT_OOB(ParseArrayIndex(args[1], 0, &offset)); CHECK_NOT_OOB(ParseArrayIndex(args[2], obj_length - offset, &max_length)); max_length = MIN(obj_length - offset, max_length); if (max_length == 0) return scope.Close(Uint32::New(0, node_isolate)); if (encoding == UCS2) max_length = max_length / 2; if (offset >= obj_length) return ThrowRangeError("Offset is out of bounds"); size_t written = StringBytes::Write(obj_data + offset, max_length, str, encoding, NULL); return scope.Close(Uint32::New(written, node_isolate)); } Handle Base64Write(const Arguments& args) { return StringWrite(args); } Handle BinaryWrite(const Arguments& args) { return StringWrite(args); } Handle Utf8Write(const Arguments& args) { return StringWrite(args); } Handle Ucs2Write(const Arguments& args) { return StringWrite(args); } Handle HexWrite(const Arguments& args) { return StringWrite(args); } Handle AsciiWrite(const Arguments& args) { return StringWrite(args); } static inline void Swizzle(char* start, unsigned int len) { char* end = start + len - 1; while (start < end) { char tmp = *start; *start++ = *end; *end-- = tmp; } } template Handle ReadFloatGeneric(const Arguments& args) { bool doAssert = !args[1]->BooleanValue(); size_t offset; CHECK_NOT_OOB(ParseArrayIndex(args[0], 0, &offset)); if (doAssert) { size_t len = Length(args.This()); if (offset + sizeof(T) > len || offset + sizeof(T) < offset) return ThrowRangeError("Trying to read beyond buffer length"); } union NoAlias { T val; char bytes[sizeof(T)]; }; union NoAlias na; const void* data = args.This()->GetIndexedPropertiesExternalArrayData(); const char* ptr = static_cast(data) + offset; memcpy(na.bytes, ptr, sizeof(na.bytes)); if (endianness != GetEndianness()) Swizzle(na.bytes, sizeof(na.bytes)); return Number::New(na.val); } Handle ReadFloatLE(const Arguments& args) { return ReadFloatGeneric(args); } Handle ReadFloatBE(const Arguments& args) { return ReadFloatGeneric(args); } Handle ReadDoubleLE(const Arguments& args) { return ReadFloatGeneric(args); } Handle ReadDoubleBE(const Arguments& args) { return ReadFloatGeneric(args); } template Handle WriteFloatGeneric(const Arguments& args) { bool doAssert = !args[2]->BooleanValue(); T val = static_cast(args[0]->NumberValue()); size_t offset; CHECK_NOT_OOB(ParseArrayIndex(args[1], 0, &offset)); if (doAssert) { size_t len = Length(args.This()); if (offset + sizeof(T) > len || offset + sizeof(T) < offset) return ThrowRangeError("Trying to write beyond buffer length"); } union NoAlias { T val; char bytes[sizeof(T)]; }; union NoAlias na = { val }; void* data = args.This()->GetIndexedPropertiesExternalArrayData(); char* ptr = static_cast(data) + offset; if (endianness != GetEndianness()) Swizzle(na.bytes, sizeof(na.bytes)); memcpy(ptr, na.bytes, sizeof(na.bytes)); return Undefined(node_isolate); } Handle WriteFloatLE(const Arguments& args) { return WriteFloatGeneric(args); } Handle WriteFloatBE(const Arguments& args) { return WriteFloatGeneric(args); } Handle WriteDoubleLE(const Arguments& args) { return WriteFloatGeneric(args); } Handle WriteDoubleBE(const Arguments& args) { return WriteFloatGeneric(args); } Handle ByteLength(const Arguments &args) { HandleScope scope(node_isolate); if (!args[0]->IsString()) return ThrowTypeError("Argument must be a string"); Local s = args[0]->ToString(); enum encoding e = ParseEncoding(args[1], UTF8); return scope.Close(Uint32::New(StringBytes::Size(s, e), node_isolate)); } // pass Buffer object to load prototype methods Handle SetupBufferJS(const Arguments& args) { HandleScope scope(node_isolate); assert(args[0]->IsFunction()); Local bv = args[0].As(); p_buffer_fn = Persistent::New(node_isolate, bv); Local proto_v = bv->Get(String::New("prototype")); assert(proto_v->IsObject()); Local proto = proto_v.As(); bv->Set(String::New("byteLength"), FunctionTemplate::New(ByteLength)->GetFunction()); proto->Set(String::New("asciiSlice"), FunctionTemplate::New(AsciiSlice)->GetFunction()); proto->Set(String::New("base64Slice"), FunctionTemplate::New(Base64Slice)->GetFunction()); proto->Set(String::New("binarySlice"), FunctionTemplate::New(BinarySlice)->GetFunction()); proto->Set(String::New("hexSlice"), FunctionTemplate::New(HexSlice)->GetFunction()); proto->Set(String::New("ucs2Slice"), FunctionTemplate::New(Ucs2Slice)->GetFunction()); proto->Set(String::New("utf8Slice"), FunctionTemplate::New(Utf8Slice)->GetFunction()); proto->Set(String::New("asciiWrite"), FunctionTemplate::New(AsciiWrite)->GetFunction()); proto->Set(String::New("base64Write"), FunctionTemplate::New(Base64Write)->GetFunction()); proto->Set(String::New("binaryWrite"), FunctionTemplate::New(BinaryWrite)->GetFunction()); proto->Set(String::New("hexWrite"), FunctionTemplate::New(HexWrite)->GetFunction()); proto->Set(String::New("ucs2Write"), FunctionTemplate::New(Ucs2Write)->GetFunction()); proto->Set(String::New("utf8Write"), FunctionTemplate::New(Utf8Write)->GetFunction()); proto->Set(String::New("readDoubleBE"), FunctionTemplate::New(ReadDoubleBE)->GetFunction()); proto->Set(String::New("readDoubleLE"), FunctionTemplate::New(ReadDoubleLE)->GetFunction()); proto->Set(String::New("readFloatBE"), FunctionTemplate::New(ReadFloatBE)->GetFunction()); proto->Set(String::New("readFloatLE"), FunctionTemplate::New(ReadFloatLE)->GetFunction()); proto->Set(String::New("writeDoubleBE"), FunctionTemplate::New(WriteDoubleBE)->GetFunction()); proto->Set(String::New("writeDoubleLE"), FunctionTemplate::New(WriteDoubleLE)->GetFunction()); proto->Set(String::New("writeFloatBE"), FunctionTemplate::New(WriteFloatBE)->GetFunction()); proto->Set(String::New("writeFloatLE"), FunctionTemplate::New(WriteFloatLE)->GetFunction()); proto->Set(String::New("copy"), FunctionTemplate::New(Copy)->GetFunction()); proto->Set(String::New("fill"), FunctionTemplate::New(Fill)->GetFunction()); // for backwards compatibility proto->Set(String::New("offset"), Uint32::New(0, node_isolate), v8::ReadOnly); return Undefined(node_isolate); } void Initialize(Handle target) { HandleScope scope(node_isolate); target->Set(String::New("setupBufferJS"), FunctionTemplate::New(SetupBufferJS)->GetFunction()); } } // namespace Buffer } // namespace node NODE_MODULE(node_buffer, node::Buffer::Initialize)