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