1#include "node_blob.h" 2#include "async_wrap-inl.h" 3#include "base_object-inl.h" 4#include "env-inl.h" 5#include "memory_tracker-inl.h" 6#include "node_errors.h" 7#include "node_external_reference.h" 8#include "threadpoolwork-inl.h" 9#include "v8.h" 10 11#include <algorithm> 12 13namespace node { 14 15using v8::Array; 16using v8::ArrayBuffer; 17using v8::ArrayBufferView; 18using v8::BackingStore; 19using v8::Context; 20using v8::EscapableHandleScope; 21using v8::Function; 22using v8::FunctionCallbackInfo; 23using v8::FunctionTemplate; 24using v8::HandleScope; 25using v8::Isolate; 26using v8::Local; 27using v8::MaybeLocal; 28using v8::Number; 29using v8::Object; 30using v8::String; 31using v8::Uint32; 32using v8::Undefined; 33using v8::Value; 34 35void Blob::Initialize( 36 Local<Object> target, 37 Local<Value> unused, 38 Local<Context> context, 39 void* priv) { 40 Realm* realm = Realm::GetCurrent(context); 41 42 BlobBindingData* const binding_data = 43 realm->AddBindingData<BlobBindingData>(context, target); 44 if (binding_data == nullptr) return; 45 46 SetMethod(context, target, "createBlob", New); 47 SetMethod(context, target, "storeDataObject", StoreDataObject); 48 SetMethod(context, target, "getDataObject", GetDataObject); 49 SetMethod(context, target, "revokeDataObject", RevokeDataObject); 50 FixedSizeBlobCopyJob::Initialize(realm->env(), target); 51} 52 53Local<FunctionTemplate> Blob::GetConstructorTemplate(Environment* env) { 54 Local<FunctionTemplate> tmpl = env->blob_constructor_template(); 55 if (tmpl.IsEmpty()) { 56 Isolate* isolate = env->isolate(); 57 tmpl = NewFunctionTemplate(isolate, nullptr); 58 tmpl->InstanceTemplate()->SetInternalFieldCount( 59 BaseObject::kInternalFieldCount); 60 tmpl->Inherit(BaseObject::GetConstructorTemplate(env)); 61 tmpl->SetClassName( 62 FIXED_ONE_BYTE_STRING(env->isolate(), "Blob")); 63 SetProtoMethod(isolate, tmpl, "toArrayBuffer", ToArrayBuffer); 64 SetProtoMethod(isolate, tmpl, "slice", ToSlice); 65 env->set_blob_constructor_template(tmpl); 66 } 67 return tmpl; 68} 69 70bool Blob::HasInstance(Environment* env, v8::Local<v8::Value> object) { 71 return GetConstructorTemplate(env)->HasInstance(object); 72} 73 74BaseObjectPtr<Blob> Blob::Create(Environment* env, 75 const std::vector<BlobEntry>& store, 76 size_t length) { 77 HandleScope scope(env->isolate()); 78 79 Local<Function> ctor; 80 if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor)) 81 return BaseObjectPtr<Blob>(); 82 83 Local<Object> obj; 84 if (!ctor->NewInstance(env->context()).ToLocal(&obj)) 85 return BaseObjectPtr<Blob>(); 86 87 return MakeBaseObject<Blob>(env, obj, store, length); 88} 89 90void Blob::New(const FunctionCallbackInfo<Value>& args) { 91 Environment* env = Environment::GetCurrent(args); 92 CHECK(args[0]->IsArray()); // sources 93 CHECK(args[1]->IsUint32()); // length 94 95 std::vector<BlobEntry> entries; 96 97 size_t length = args[1].As<Uint32>()->Value(); 98 size_t len = 0; 99 Local<Array> ary = args[0].As<Array>(); 100 for (size_t n = 0; n < ary->Length(); n++) { 101 Local<Value> entry; 102 if (!ary->Get(env->context(), n).ToLocal(&entry)) 103 return; 104 CHECK(entry->IsArrayBufferView() || Blob::HasInstance(env, entry)); 105 if (entry->IsArrayBufferView()) { 106 Local<ArrayBufferView> view = entry.As<ArrayBufferView>(); 107 CHECK_EQ(view->ByteOffset(), 0); 108 std::shared_ptr<BackingStore> store = view->Buffer()->GetBackingStore(); 109 size_t byte_length = view->ByteLength(); 110 view->Buffer()->Detach(); // The Blob will own the backing store now. 111 entries.emplace_back(BlobEntry{std::move(store), byte_length, 0}); 112 len += byte_length; 113 } else { 114 Blob* blob; 115 ASSIGN_OR_RETURN_UNWRAP(&blob, entry); 116 auto source = blob->entries(); 117 entries.insert(entries.end(), source.begin(), source.end()); 118 len += blob->length(); 119 } 120 } 121 CHECK_EQ(length, len); 122 123 BaseObjectPtr<Blob> blob = Create(env, entries, length); 124 if (blob) 125 args.GetReturnValue().Set(blob->object()); 126} 127 128void Blob::ToArrayBuffer(const FunctionCallbackInfo<Value>& args) { 129 Environment* env = Environment::GetCurrent(args); 130 Blob* blob; 131 ASSIGN_OR_RETURN_UNWRAP(&blob, args.Holder()); 132 Local<Value> ret; 133 if (blob->GetArrayBuffer(env).ToLocal(&ret)) 134 args.GetReturnValue().Set(ret); 135} 136 137void Blob::ToSlice(const FunctionCallbackInfo<Value>& args) { 138 Environment* env = Environment::GetCurrent(args); 139 Blob* blob; 140 ASSIGN_OR_RETURN_UNWRAP(&blob, args.Holder()); 141 CHECK(args[0]->IsUint32()); 142 CHECK(args[1]->IsUint32()); 143 size_t start = args[0].As<Uint32>()->Value(); 144 size_t end = args[1].As<Uint32>()->Value(); 145 BaseObjectPtr<Blob> slice = blob->Slice(env, start, end); 146 if (slice) 147 args.GetReturnValue().Set(slice->object()); 148} 149 150void Blob::MemoryInfo(MemoryTracker* tracker) const { 151 tracker->TrackFieldWithSize("store", length_); 152} 153 154MaybeLocal<Value> Blob::GetArrayBuffer(Environment* env) { 155 EscapableHandleScope scope(env->isolate()); 156 size_t len = length(); 157 std::shared_ptr<BackingStore> store = 158 ArrayBuffer::NewBackingStore(env->isolate(), len); 159 if (len > 0) { 160 unsigned char* dest = static_cast<unsigned char*>(store->Data()); 161 size_t total = 0; 162 for (const auto& entry : entries()) { 163 unsigned char* src = static_cast<unsigned char*>(entry.store->Data()); 164 src += entry.offset; 165 memcpy(dest, src, entry.length); 166 dest += entry.length; 167 total += entry.length; 168 CHECK_LE(total, len); 169 } 170 } 171 172 return scope.Escape(ArrayBuffer::New(env->isolate(), store)); 173} 174 175BaseObjectPtr<Blob> Blob::Slice(Environment* env, size_t start, size_t end) { 176 CHECK_LE(start, length()); 177 CHECK_LE(end, length()); 178 CHECK_LE(start, end); 179 180 std::vector<BlobEntry> slices; 181 size_t total = end - start; 182 size_t remaining = total; 183 184 if (total == 0) return Create(env, slices, 0); 185 186 for (const auto& entry : entries()) { 187 if (start + entry.offset > entry.store->ByteLength()) { 188 start -= entry.length; 189 continue; 190 } 191 192 size_t offset = entry.offset + start; 193 size_t len = std::min(remaining, entry.store->ByteLength() - offset); 194 slices.emplace_back(BlobEntry{entry.store, len, offset}); 195 196 remaining -= len; 197 start = 0; 198 199 if (remaining == 0) 200 break; 201 } 202 203 return Create(env, slices, total); 204} 205 206Blob::Blob( 207 Environment* env, 208 v8::Local<v8::Object> obj, 209 const std::vector<BlobEntry>& store, 210 size_t length) 211 : BaseObject(env, obj), 212 store_(store), 213 length_(length) { 214 MakeWeak(); 215} 216 217BaseObjectPtr<BaseObject> 218Blob::BlobTransferData::Deserialize( 219 Environment* env, 220 Local<Context> context, 221 std::unique_ptr<worker::TransferData> self) { 222 if (context != env->context()) { 223 THROW_ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE(env); 224 return {}; 225 } 226 return Blob::Create(env, store_, length_); 227} 228 229BaseObject::TransferMode Blob::GetTransferMode() const { 230 return BaseObject::TransferMode::kCloneable; 231} 232 233std::unique_ptr<worker::TransferData> Blob::CloneForMessaging() const { 234 return std::make_unique<BlobTransferData>(store_, length_); 235} 236 237void Blob::StoreDataObject(const v8::FunctionCallbackInfo<v8::Value>& args) { 238 Environment* env = Environment::GetCurrent(args); 239 BlobBindingData* binding_data = Realm::GetBindingData<BlobBindingData>(args); 240 241 CHECK(args[0]->IsString()); // ID key 242 CHECK(Blob::HasInstance(env, args[1])); // Blob 243 CHECK(args[2]->IsUint32()); // Length 244 CHECK(args[3]->IsString()); // Type 245 246 Utf8Value key(env->isolate(), args[0]); 247 Blob* blob; 248 ASSIGN_OR_RETURN_UNWRAP(&blob, args[1]); 249 250 size_t length = args[2].As<Uint32>()->Value(); 251 Utf8Value type(env->isolate(), args[3]); 252 253 binding_data->store_data_object( 254 std::string(*key, key.length()), 255 BlobBindingData::StoredDataObject( 256 BaseObjectPtr<Blob>(blob), 257 length, 258 std::string(*type, type.length()))); 259} 260 261void Blob::RevokeDataObject(const v8::FunctionCallbackInfo<v8::Value>& args) { 262 BlobBindingData* binding_data = Realm::GetBindingData<BlobBindingData>(args); 263 264 Environment* env = Environment::GetCurrent(args); 265 CHECK(args[0]->IsString()); // ID key 266 267 Utf8Value key(env->isolate(), args[0]); 268 269 binding_data->revoke_data_object(std::string(*key, key.length())); 270} 271 272void Blob::GetDataObject(const v8::FunctionCallbackInfo<v8::Value>& args) { 273 BlobBindingData* binding_data = Realm::GetBindingData<BlobBindingData>(args); 274 275 Environment* env = Environment::GetCurrent(args); 276 CHECK(args[0]->IsString()); 277 278 Utf8Value key(env->isolate(), args[0]); 279 280 BlobBindingData::StoredDataObject stored = 281 binding_data->get_data_object(std::string(*key, key.length())); 282 if (stored.blob) { 283 Local<Value> type; 284 if (!String::NewFromUtf8( 285 env->isolate(), 286 stored.type.c_str(), 287 v8::NewStringType::kNormal, 288 static_cast<int>(stored.type.length())).ToLocal(&type)) { 289 return; 290 } 291 292 Local<Value> values[] = { 293 stored.blob->object(), 294 Uint32::NewFromUnsigned(env->isolate(), stored.length), 295 type 296 }; 297 298 args.GetReturnValue().Set( 299 Array::New( 300 env->isolate(), 301 values, 302 arraysize(values))); 303 } 304} 305 306FixedSizeBlobCopyJob::FixedSizeBlobCopyJob(Environment* env, 307 Local<Object> object, 308 Blob* blob, 309 FixedSizeBlobCopyJob::Mode mode) 310 : AsyncWrap(env, object, AsyncWrap::PROVIDER_FIXEDSIZEBLOBCOPY), 311 ThreadPoolWork(env, "blob"), 312 mode_(mode) { 313 if (mode == FixedSizeBlobCopyJob::Mode::SYNC) MakeWeak(); 314 source_ = blob->entries(); 315 length_ = blob->length(); 316} 317 318void FixedSizeBlobCopyJob::AfterThreadPoolWork(int status) { 319 Environment* env = AsyncWrap::env(); 320 CHECK_EQ(mode_, Mode::ASYNC); 321 CHECK(status == 0 || status == UV_ECANCELED); 322 std::unique_ptr<FixedSizeBlobCopyJob> ptr(this); 323 HandleScope handle_scope(env->isolate()); 324 Context::Scope context_scope(env->context()); 325 Local<Value> args[2]; 326 327 if (status == UV_ECANCELED) { 328 args[0] = Number::New(env->isolate(), status), 329 args[1] = Undefined(env->isolate()); 330 } else { 331 args[0] = Undefined(env->isolate()); 332 args[1] = ArrayBuffer::New(env->isolate(), destination_); 333 } 334 335 ptr->MakeCallback(env->ondone_string(), arraysize(args), args); 336} 337 338void FixedSizeBlobCopyJob::DoThreadPoolWork() { 339 unsigned char* dest = static_cast<unsigned char*>(destination_->Data()); 340 if (length_ > 0) { 341 size_t total = 0; 342 for (const auto& entry : source_) { 343 unsigned char* src = static_cast<unsigned char*>(entry.store->Data()); 344 src += entry.offset; 345 memcpy(dest, src, entry.length); 346 dest += entry.length; 347 total += entry.length; 348 CHECK_LE(total, length_); 349 } 350 } 351} 352 353void FixedSizeBlobCopyJob::MemoryInfo(MemoryTracker* tracker) const { 354 tracker->TrackFieldWithSize("source", length_); 355 tracker->TrackFieldWithSize( 356 "destination", 357 destination_ ? destination_->ByteLength() : 0); 358} 359 360void FixedSizeBlobCopyJob::Initialize(Environment* env, Local<Object> target) { 361 Isolate* isolate = env->isolate(); 362 v8::Local<v8::FunctionTemplate> job = NewFunctionTemplate(isolate, New); 363 job->Inherit(AsyncWrap::GetConstructorTemplate(env)); 364 job->InstanceTemplate()->SetInternalFieldCount( 365 AsyncWrap::kInternalFieldCount); 366 SetProtoMethod(isolate, job, "run", Run); 367 SetConstructorFunction(env->context(), target, "FixedSizeBlobCopyJob", job); 368} 369 370void FixedSizeBlobCopyJob::New(const FunctionCallbackInfo<Value>& args) { 371 static constexpr size_t kMaxSyncLength = 4096; 372 static constexpr size_t kMaxEntryCount = 4; 373 374 Environment* env = Environment::GetCurrent(args); 375 CHECK(args.IsConstructCall()); 376 CHECK(args[0]->IsObject()); 377 CHECK(Blob::HasInstance(env, args[0])); 378 379 Blob* blob; 380 ASSIGN_OR_RETURN_UNWRAP(&blob, args[0]); 381 382 // This is a fairly arbitrary heuristic. We want to avoid deferring to 383 // the threadpool if the amount of data being copied is small and there 384 // aren't that many entries to copy. 385 FixedSizeBlobCopyJob::Mode mode = 386 (blob->length() < kMaxSyncLength && 387 blob->entries().size() < kMaxEntryCount) ? 388 FixedSizeBlobCopyJob::Mode::SYNC : 389 FixedSizeBlobCopyJob::Mode::ASYNC; 390 391 new FixedSizeBlobCopyJob(env, args.This(), blob, mode); 392} 393 394void FixedSizeBlobCopyJob::Run(const FunctionCallbackInfo<Value>& args) { 395 Environment* env = Environment::GetCurrent(args); 396 FixedSizeBlobCopyJob* job; 397 ASSIGN_OR_RETURN_UNWRAP(&job, args.Holder()); 398 job->destination_ = 399 ArrayBuffer::NewBackingStore(env->isolate(), job->length_); 400 if (job->mode() == FixedSizeBlobCopyJob::Mode::ASYNC) 401 return job->ScheduleWork(); 402 403 job->DoThreadPoolWork(); 404 args.GetReturnValue().Set( 405 ArrayBuffer::New(env->isolate(), job->destination_)); 406} 407 408void FixedSizeBlobCopyJob::RegisterExternalReferences( 409 ExternalReferenceRegistry* registry) { 410 registry->Register(New); 411 registry->Register(Run); 412} 413 414void BlobBindingData::StoredDataObject::MemoryInfo( 415 MemoryTracker* tracker) const { 416 tracker->TrackField("blob", blob); 417 tracker->TrackFieldWithSize("type", type.length()); 418} 419 420BlobBindingData::StoredDataObject::StoredDataObject( 421 const BaseObjectPtr<Blob>& blob_, 422 size_t length_, 423 const std::string& type_) 424 : blob(blob_), 425 length(length_), 426 type(type_) {} 427 428BlobBindingData::BlobBindingData(Realm* realm, Local<Object> wrap) 429 : SnapshotableObject(realm, wrap, type_int) { 430 MakeWeak(); 431} 432 433void BlobBindingData::MemoryInfo(MemoryTracker* tracker) const { 434 tracker->TrackField("data_objects", data_objects_); 435} 436 437void BlobBindingData::store_data_object( 438 const std::string& uuid, 439 const BlobBindingData::StoredDataObject& object) { 440 data_objects_[uuid] = object; 441} 442 443void BlobBindingData::revoke_data_object(const std::string& uuid) { 444 if (data_objects_.find(uuid) == data_objects_.end()) { 445 return; 446 } 447 data_objects_.erase(uuid); 448 CHECK_EQ(data_objects_.find(uuid), data_objects_.end()); 449} 450 451BlobBindingData::StoredDataObject BlobBindingData::get_data_object( 452 const std::string& uuid) { 453 auto entry = data_objects_.find(uuid); 454 if (entry == data_objects_.end()) 455 return BlobBindingData::StoredDataObject {}; 456 return entry->second; 457} 458 459void BlobBindingData::Deserialize(Local<Context> context, 460 Local<Object> holder, 461 int index, 462 InternalFieldInfoBase* info) { 463 DCHECK_EQ(index, BaseObject::kEmbedderType); 464 HandleScope scope(context->GetIsolate()); 465 Realm* realm = Realm::GetCurrent(context); 466 BlobBindingData* binding = 467 realm->AddBindingData<BlobBindingData>(context, holder); 468 CHECK_NOT_NULL(binding); 469} 470 471bool BlobBindingData::PrepareForSerialization(Local<Context> context, 472 v8::SnapshotCreator* creator) { 473 // Stored blob objects are not actually persisted. 474 // Return true because we need to maintain the reference to the binding from 475 // JS land. 476 return true; 477} 478 479InternalFieldInfoBase* BlobBindingData::Serialize(int index) { 480 DCHECK_EQ(index, BaseObject::kEmbedderType); 481 InternalFieldInfo* info = 482 InternalFieldInfoBase::New<InternalFieldInfo>(type()); 483 return info; 484} 485 486void Blob::RegisterExternalReferences(ExternalReferenceRegistry* registry) { 487 registry->Register(Blob::New); 488 registry->Register(Blob::ToArrayBuffer); 489 registry->Register(Blob::ToSlice); 490 registry->Register(Blob::StoreDataObject); 491 registry->Register(Blob::GetDataObject); 492 registry->Register(Blob::RevokeDataObject); 493 494 FixedSizeBlobCopyJob::RegisterExternalReferences(registry); 495} 496 497} // namespace node 498 499NODE_BINDING_CONTEXT_AWARE_INTERNAL(blob, node::Blob::Initialize) 500NODE_BINDING_EXTERNAL_REFERENCE(blob, node::Blob::RegisterExternalReferences) 501