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 
13 namespace node {
14 
15 using v8::Array;
16 using v8::ArrayBuffer;
17 using v8::ArrayBufferView;
18 using v8::BackingStore;
19 using v8::Context;
20 using v8::EscapableHandleScope;
21 using v8::Function;
22 using v8::FunctionCallbackInfo;
23 using v8::FunctionTemplate;
24 using v8::HandleScope;
25 using v8::Isolate;
26 using v8::Local;
27 using v8::MaybeLocal;
28 using v8::Number;
29 using v8::Object;
30 using v8::String;
31 using v8::Uint32;
32 using v8::Undefined;
33 using v8::Value;
34 
Initialize( Local<Object> target, Local<Value> unused, Local<Context> context, void* priv)35 void 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 
GetConstructorTemplate(Environment* env)53 Local<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 
HasInstance(Environment* env, v8::Local<v8::Value> object)70 bool Blob::HasInstance(Environment* env, v8::Local<v8::Value> object) {
71   return GetConstructorTemplate(env)->HasInstance(object);
72 }
73 
Create(Environment* env, const std::vector<BlobEntry>& store, size_t length)74 BaseObjectPtr<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 
New(const FunctionCallbackInfo<Value>& args)90 void 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 
ToArrayBuffer(const FunctionCallbackInfo<Value>& args)128 void 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 
ToSlice(const FunctionCallbackInfo<Value>& args)137 void 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 
MemoryInfo(MemoryTracker* tracker) const150 void Blob::MemoryInfo(MemoryTracker* tracker) const {
151   tracker->TrackFieldWithSize("store", length_);
152 }
153 
GetArrayBuffer(Environment* env)154 MaybeLocal<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 
Slice(Environment* env, size_t start, size_t end)175 BaseObjectPtr<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 
Blob( Environment* env, v8::Local<v8::Object> obj, const std::vector<BlobEntry>& store, size_t length)206 Blob::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 
217 BaseObjectPtr<BaseObject>
Deserialize( Environment* env, Local<Context> context, std::unique_ptr<worker::TransferData> self)218 Blob::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 
GetTransferMode() const229 BaseObject::TransferMode Blob::GetTransferMode() const {
230   return BaseObject::TransferMode::kCloneable;
231 }
232 
CloneForMessaging() const233 std::unique_ptr<worker::TransferData> Blob::CloneForMessaging() const {
234   return std::make_unique<BlobTransferData>(store_, length_);
235 }
236 
StoreDataObject(const v8::FunctionCallbackInfo<v8::Value>& args)237 void 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 
RevokeDataObject(const v8::FunctionCallbackInfo<v8::Value>& args)261 void 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 
GetDataObject(const v8::FunctionCallbackInfo<v8::Value>& args)272 void 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 
FixedSizeBlobCopyJob(Environment* env, Local<Object> object, Blob* blob, FixedSizeBlobCopyJob::Mode mode)306 FixedSizeBlobCopyJob::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 
AfterThreadPoolWork(int status)318 void 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 
DoThreadPoolWork()338 void 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 
MemoryInfo(MemoryTracker* tracker) const353 void FixedSizeBlobCopyJob::MemoryInfo(MemoryTracker* tracker) const {
354   tracker->TrackFieldWithSize("source", length_);
355   tracker->TrackFieldWithSize(
356       "destination",
357       destination_ ? destination_->ByteLength() : 0);
358 }
359 
Initialize(Environment* env, Local<Object> target)360 void 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 
New(const FunctionCallbackInfo<Value>& args)370 void 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 
Run(const FunctionCallbackInfo<Value>& args)394 void 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 
RegisterExternalReferences( ExternalReferenceRegistry* registry)408 void FixedSizeBlobCopyJob::RegisterExternalReferences(
409     ExternalReferenceRegistry* registry) {
410   registry->Register(New);
411   registry->Register(Run);
412 }
413 
MemoryInfo( MemoryTracker* tracker) const414 void BlobBindingData::StoredDataObject::MemoryInfo(
415     MemoryTracker* tracker) const {
416   tracker->TrackField("blob", blob);
417   tracker->TrackFieldWithSize("type", type.length());
418 }
419 
StoredDataObject( const BaseObjectPtr<Blob>& blob_, size_t length_, const std::string& type_)420 BlobBindingData::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 
BlobBindingData(Realm* realm, Local<Object> wrap)428 BlobBindingData::BlobBindingData(Realm* realm, Local<Object> wrap)
429     : SnapshotableObject(realm, wrap, type_int) {
430   MakeWeak();
431 }
432 
MemoryInfo(MemoryTracker* tracker) const433 void BlobBindingData::MemoryInfo(MemoryTracker* tracker) const {
434   tracker->TrackField("data_objects", data_objects_);
435 }
436 
store_data_object( const std::string& uuid, const BlobBindingData::StoredDataObject& object)437 void BlobBindingData::store_data_object(
438     const std::string& uuid,
439     const BlobBindingData::StoredDataObject& object) {
440   data_objects_[uuid] = object;
441 }
442 
revoke_data_object(const std::string& uuid)443 void 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 
get_data_object( const std::string& uuid)451 BlobBindingData::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 
Deserialize(Local<Context> context, Local<Object> holder, int index, InternalFieldInfoBase* info)459 void 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 
PrepareForSerialization(Local<Context> context, v8::SnapshotCreator* creator)471 bool 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 
Serialize(int index)479 InternalFieldInfoBase* BlobBindingData::Serialize(int index) {
480   DCHECK_EQ(index, BaseObject::kEmbedderType);
481   InternalFieldInfo* info =
482       InternalFieldInfoBase::New<InternalFieldInfo>(type());
483   return info;
484 }
485 
RegisterExternalReferences(ExternalReferenceRegistry* registry)486 void 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 
499 NODE_BINDING_CONTEXT_AWARE_INTERNAL(blob, node::Blob::Initialize)
500 NODE_BINDING_EXTERNAL_REFERENCE(blob, node::Blob::RegisterExternalReferences)
501