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