1 #include "inspector_io.h"
2
3 #include "inspector_socket_server.h"
4 #include "inspector/main_thread_interface.h"
5 #include "inspector/node_string.h"
6 #include "base_object-inl.h"
7 #include "debug_utils-inl.h"
8 #include "node.h"
9 #include "node_crypto.h"
10 #include "node_internals.h"
11 #include "node_mutex.h"
12 #include "v8-inspector.h"
13 #include "util-inl.h"
14 #include "zlib.h"
15
16 #include <deque>
17 #include <cstring>
18 #include <vector>
19
20 namespace node {
21 namespace inspector {
22 namespace {
23 using v8_inspector::StringBuffer;
24 using v8_inspector::StringView;
25
26 // kKill closes connections and stops the server, kStop only stops the server
27 enum class TransportAction { kKill, kSendMessage, kStop };
28
ScriptPath(uv_loop_t * loop,const std::string & script_name)29 std::string ScriptPath(uv_loop_t* loop, const std::string& script_name) {
30 std::string script_path;
31
32 if (!script_name.empty()) {
33 uv_fs_t req;
34 req.ptr = nullptr;
35 if (0 == uv_fs_realpath(loop, &req, script_name.c_str(), nullptr)) {
36 CHECK_NOT_NULL(req.ptr);
37 script_path = std::string(static_cast<char*>(req.ptr));
38 }
39 uv_fs_req_cleanup(&req);
40 }
41
42 return script_path;
43 }
44
45 // UUID RFC: https://www.ietf.org/rfc/rfc4122.txt
46 // Used ver 4 - with numbers
GenerateID()47 std::string GenerateID() {
48 uint16_t buffer[8];
49 CHECK(crypto::EntropySource(reinterpret_cast<unsigned char*>(buffer),
50 sizeof(buffer)));
51
52 char uuid[256];
53 snprintf(uuid, sizeof(uuid), "%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
54 buffer[0], // time_low
55 buffer[1], // time_mid
56 buffer[2], // time_low
57 (buffer[3] & 0x0fff) | 0x4000, // time_hi_and_version
58 (buffer[4] & 0x3fff) | 0x8000, // clk_seq_hi clk_seq_low
59 buffer[5], // node
60 buffer[6],
61 buffer[7]);
62 return uuid;
63 }
64
65 class RequestToServer {
66 public:
RequestToServer(TransportAction action,int session_id,std::unique_ptr<v8_inspector::StringBuffer> message)67 RequestToServer(TransportAction action,
68 int session_id,
69 std::unique_ptr<v8_inspector::StringBuffer> message)
70 : action_(action),
71 session_id_(session_id),
72 message_(std::move(message)) {}
73
Dispatch(InspectorSocketServer * server) const74 void Dispatch(InspectorSocketServer* server) const {
75 switch (action_) {
76 case TransportAction::kKill:
77 server->TerminateConnections();
78 // Fallthrough
79 case TransportAction::kStop:
80 server->Stop();
81 break;
82 case TransportAction::kSendMessage:
83 server->Send(
84 session_id_,
85 protocol::StringUtil::StringViewToUtf8(message_->string()));
86 break;
87 }
88 }
89
90 private:
91 TransportAction action_;
92 int session_id_;
93 std::unique_ptr<v8_inspector::StringBuffer> message_;
94 };
95
96 class RequestQueueData {
97 public:
98 using MessageQueue = std::deque<RequestToServer>;
99
RequestQueueData(uv_loop_t * loop)100 explicit RequestQueueData(uv_loop_t* loop)
101 : handle_(std::make_shared<RequestQueue>(this)) {
102 int err = uv_async_init(loop, &async_, [](uv_async_t* async) {
103 RequestQueueData* wrapper =
104 node::ContainerOf(&RequestQueueData::async_, async);
105 wrapper->DoDispatch();
106 });
107 CHECK_EQ(0, err);
108 }
109
110 static void CloseAndFree(RequestQueueData* queue);
111
Post(int session_id,TransportAction action,std::unique_ptr<StringBuffer> message)112 void Post(int session_id,
113 TransportAction action,
114 std::unique_ptr<StringBuffer> message) {
115 Mutex::ScopedLock scoped_lock(state_lock_);
116 bool notify = messages_.empty();
117 messages_.emplace_back(action, session_id, std::move(message));
118 if (notify) {
119 CHECK_EQ(0, uv_async_send(&async_));
120 incoming_message_cond_.Broadcast(scoped_lock);
121 }
122 }
123
Wait()124 void Wait() {
125 Mutex::ScopedLock scoped_lock(state_lock_);
126 if (messages_.empty()) {
127 incoming_message_cond_.Wait(scoped_lock);
128 }
129 }
130
SetServer(InspectorSocketServer * server)131 void SetServer(InspectorSocketServer* server) {
132 server_ = server;
133 }
134
handle()135 std::shared_ptr<RequestQueue> handle() {
136 return handle_;
137 }
138
139 private:
140 ~RequestQueueData() = default;
141
GetMessages()142 MessageQueue GetMessages() {
143 Mutex::ScopedLock scoped_lock(state_lock_);
144 MessageQueue messages;
145 messages_.swap(messages);
146 return messages;
147 }
148
DoDispatch()149 void DoDispatch() {
150 if (server_ == nullptr)
151 return;
152 for (const auto& request : GetMessages()) {
153 request.Dispatch(server_);
154 }
155 }
156
157 std::shared_ptr<RequestQueue> handle_;
158 uv_async_t async_;
159 InspectorSocketServer* server_ = nullptr;
160 MessageQueue messages_;
161 Mutex state_lock_; // Locked before mutating the queue.
162 ConditionVariable incoming_message_cond_;
163 };
164 } // namespace
165
166 class RequestQueue {
167 public:
RequestQueue(RequestQueueData * data)168 explicit RequestQueue(RequestQueueData* data) : data_(data) {}
169
Reset()170 void Reset() {
171 Mutex::ScopedLock scoped_lock(lock_);
172 data_ = nullptr;
173 }
174
Post(int session_id,TransportAction action,std::unique_ptr<StringBuffer> message)175 void Post(int session_id,
176 TransportAction action,
177 std::unique_ptr<StringBuffer> message) {
178 Mutex::ScopedLock scoped_lock(lock_);
179 if (data_ != nullptr)
180 data_->Post(session_id, action, std::move(message));
181 }
182
Expired()183 bool Expired() {
184 Mutex::ScopedLock scoped_lock(lock_);
185 return data_ == nullptr;
186 }
187
188 private:
189 RequestQueueData* data_;
190 Mutex lock_;
191 };
192
193 class IoSessionDelegate : public InspectorSessionDelegate {
194 public:
IoSessionDelegate(std::shared_ptr<RequestQueue> queue,int id)195 explicit IoSessionDelegate(std::shared_ptr<RequestQueue> queue, int id)
196 : request_queue_(queue), id_(id) { }
SendMessageToFrontend(const v8_inspector::StringView & message)197 void SendMessageToFrontend(const v8_inspector::StringView& message) override {
198 request_queue_->Post(id_, TransportAction::kSendMessage,
199 StringBuffer::create(message));
200 }
201
202 private:
203 std::shared_ptr<RequestQueue> request_queue_;
204 int id_;
205 };
206
207 // Passed to InspectorSocketServer to handle WS inspector protocol events,
208 // mostly session start, message received, and session end.
209 class InspectorIoDelegate: public node::inspector::SocketServerDelegate {
210 public:
211 InspectorIoDelegate(std::shared_ptr<RequestQueueData> queue,
212 std::shared_ptr<MainThreadHandle> main_threade,
213 const std::string& target_id,
214 const std::string& script_path,
215 const std::string& script_name);
216 ~InspectorIoDelegate() override = default;
217
218 void StartSession(int session_id, const std::string& target_id) override;
219 void MessageReceived(int session_id, const std::string& message) override;
220 void EndSession(int session_id) override;
221
222 std::vector<std::string> GetTargetIds() override;
223 std::string GetTargetTitle(const std::string& id) override;
224 std::string GetTargetUrl(const std::string& id) override;
AssignServer(InspectorSocketServer * server)225 void AssignServer(InspectorSocketServer* server) override {
226 request_queue_->SetServer(server);
227 }
228
229 private:
230 std::shared_ptr<RequestQueueData> request_queue_;
231 std::shared_ptr<MainThreadHandle> main_thread_;
232 std::unordered_map<int, std::unique_ptr<InspectorSession>> sessions_;
233 const std::string script_name_;
234 const std::string script_path_;
235 const std::string target_id_;
236 };
237
238 // static
Start(std::shared_ptr<MainThreadHandle> main_thread,const std::string & path,std::shared_ptr<ExclusiveAccess<HostPort>> host_port,const InspectPublishUid & inspect_publish_uid)239 std::unique_ptr<InspectorIo> InspectorIo::Start(
240 std::shared_ptr<MainThreadHandle> main_thread,
241 const std::string& path,
242 std::shared_ptr<ExclusiveAccess<HostPort>> host_port,
243 const InspectPublishUid& inspect_publish_uid) {
244 auto io = std::unique_ptr<InspectorIo>(
245 new InspectorIo(main_thread,
246 path,
247 host_port,
248 inspect_publish_uid));
249 if (io->request_queue_->Expired()) { // Thread is not running
250 return nullptr;
251 }
252 return io;
253 }
254
InspectorIo(std::shared_ptr<MainThreadHandle> main_thread,const std::string & path,std::shared_ptr<ExclusiveAccess<HostPort>> host_port,const InspectPublishUid & inspect_publish_uid)255 InspectorIo::InspectorIo(std::shared_ptr<MainThreadHandle> main_thread,
256 const std::string& path,
257 std::shared_ptr<ExclusiveAccess<HostPort>> host_port,
258 const InspectPublishUid& inspect_publish_uid)
259 : main_thread_(main_thread),
260 host_port_(host_port),
261 inspect_publish_uid_(inspect_publish_uid),
262 thread_(),
263 script_name_(path),
264 id_(GenerateID()) {
265 Mutex::ScopedLock scoped_lock(thread_start_lock_);
266 CHECK_EQ(uv_thread_create(&thread_, InspectorIo::ThreadMain, this), 0);
267 thread_start_condition_.Wait(scoped_lock);
268 }
269
~InspectorIo()270 InspectorIo::~InspectorIo() {
271 request_queue_->Post(0, TransportAction::kKill, nullptr);
272 int err = uv_thread_join(&thread_);
273 CHECK_EQ(err, 0);
274 }
275
StopAcceptingNewConnections()276 void InspectorIo::StopAcceptingNewConnections() {
277 request_queue_->Post(0, TransportAction::kStop, nullptr);
278 }
279
280 // static
ThreadMain(void * io)281 void InspectorIo::ThreadMain(void* io) {
282 static_cast<InspectorIo*>(io)->ThreadMain();
283 }
284
ThreadMain()285 void InspectorIo::ThreadMain() {
286 uv_loop_t loop;
287 loop.data = nullptr;
288 int err = uv_loop_init(&loop);
289 CHECK_EQ(err, 0);
290 std::shared_ptr<RequestQueueData> queue(new RequestQueueData(&loop),
291 RequestQueueData::CloseAndFree);
292 std::string script_path = ScriptPath(&loop, script_name_);
293 std::unique_ptr<InspectorIoDelegate> delegate(
294 new InspectorIoDelegate(queue, main_thread_, id_,
295 script_path, script_name_));
296 std::string host;
297 int port;
298 {
299 ExclusiveAccess<HostPort>::Scoped host_port(host_port_);
300 host = host_port->host();
301 port = host_port->port();
302 }
303 InspectorSocketServer server(std::move(delegate),
304 &loop,
305 std::move(host),
306 port,
307 inspect_publish_uid_);
308 request_queue_ = queue->handle();
309 // Its lifetime is now that of the server delegate
310 queue.reset();
311 {
312 Mutex::ScopedLock scoped_lock(thread_start_lock_);
313 if (server.Start()) {
314 ExclusiveAccess<HostPort>::Scoped host_port(host_port_);
315 host_port->set_port(server.Port());
316 }
317 thread_start_condition_.Broadcast(scoped_lock);
318 }
319 uv_run(&loop, UV_RUN_DEFAULT);
320 CheckedUvLoopClose(&loop);
321 }
322
GetWsUrl() const323 std::string InspectorIo::GetWsUrl() const {
324 ExclusiveAccess<HostPort>::Scoped host_port(host_port_);
325 return FormatWsAddress(host_port->host(), host_port->port(), id_, true);
326 }
327
InspectorIoDelegate(std::shared_ptr<RequestQueueData> queue,std::shared_ptr<MainThreadHandle> main_thread,const std::string & target_id,const std::string & script_path,const std::string & script_name)328 InspectorIoDelegate::InspectorIoDelegate(
329 std::shared_ptr<RequestQueueData> queue,
330 std::shared_ptr<MainThreadHandle> main_thread,
331 const std::string& target_id,
332 const std::string& script_path,
333 const std::string& script_name)
334 : request_queue_(queue), main_thread_(main_thread),
335 script_name_(script_name), script_path_(script_path),
336 target_id_(target_id) {}
337
StartSession(int session_id,const std::string & target_id)338 void InspectorIoDelegate::StartSession(int session_id,
339 const std::string& target_id) {
340 auto session = main_thread_->Connect(
341 std::unique_ptr<InspectorSessionDelegate>(
342 new IoSessionDelegate(request_queue_->handle(), session_id)), true);
343 if (session) {
344 sessions_[session_id] = std::move(session);
345 fprintf(stderr, "Debugger attached.\n");
346 }
347 }
348
MessageReceived(int session_id,const std::string & message)349 void InspectorIoDelegate::MessageReceived(int session_id,
350 const std::string& message) {
351 auto session = sessions_.find(session_id);
352 if (session != sessions_.end())
353 session->second->Dispatch(Utf8ToStringView(message)->string());
354 }
355
EndSession(int session_id)356 void InspectorIoDelegate::EndSession(int session_id) {
357 sessions_.erase(session_id);
358 }
359
GetTargetIds()360 std::vector<std::string> InspectorIoDelegate::GetTargetIds() {
361 return { target_id_ };
362 }
363
GetTargetTitle(const std::string & id)364 std::string InspectorIoDelegate::GetTargetTitle(const std::string& id) {
365 return script_name_.empty() ? GetHumanReadableProcessName() : script_name_;
366 }
367
GetTargetUrl(const std::string & id)368 std::string InspectorIoDelegate::GetTargetUrl(const std::string& id) {
369 return "file://" + script_path_;
370 }
371
372 // static
CloseAndFree(RequestQueueData * queue)373 void RequestQueueData::CloseAndFree(RequestQueueData* queue) {
374 queue->handle_->Reset();
375 queue->handle_.reset();
376 uv_close(reinterpret_cast<uv_handle_t*>(&queue->async_),
377 [](uv_handle_t* handle) {
378 uv_async_t* async = reinterpret_cast<uv_async_t*>(handle);
379 RequestQueueData* wrapper =
380 node::ContainerOf(&RequestQueueData::async_, async);
381 delete wrapper;
382 });
383 }
384 } // namespace inspector
385 } // namespace node
386