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