summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xconfigure6
-rw-r--r--doc/api/debugger.md14
-rw-r--r--lib/internal/bootstrap_node.js4
-rw-r--r--node.gyp39
-rw-r--r--src/env-inl.h3
-rw-r--r--src/env.h12
-rw-r--r--src/inspector_agent.cc506
-rw-r--r--src/inspector_agent.h97
-rw-r--r--src/inspector_socket.cc679
-rw-r--r--src/inspector_socket.h57
-rw-r--r--src/node.cc67
-rw-r--r--src/node_internals.h6
-rw-r--r--src/signal_wrap.cc9
-rw-r--r--test/cctest/test_inspector_socket.cc864
14 files changed, 2358 insertions, 5 deletions
diff --git a/configure b/configure
index 10fd1b5fff..f7a3f41ae0 100755
--- a/configure
+++ b/configure
@@ -415,6 +415,11 @@ parser.add_option('--no-browser-globals',
help='do not export browser globals like setTimeout, console, etc. ' +
'(This mode is not officially supported for regular applications)')
+parser.add_option('--without-inspector',
+ action='store_true',
+ dest='without_inspector',
+ help='disable experimental V8 inspector support')
+
(options, args) = parser.parse_args()
# Expand ~ in the install prefix now, it gets written to multiple files.
@@ -810,6 +815,7 @@ def configure_node(o):
o['variables']['library_files'] = options.linked_module
o['variables']['asan'] = int(options.enable_asan or 0)
+ o['variables']['v8_inspector'] = b(not options.without_inspector)
if options.use_xcode and options.use_ninja:
raise Exception('--xcode and --ninja cannot be used together.')
diff --git a/doc/api/debugger.md b/doc/api/debugger.md
index a966ee2b01..6a31212d9c 100644
--- a/doc/api/debugger.md
+++ b/doc/api/debugger.md
@@ -179,4 +179,18 @@ process or via URI reference to the listening debugger:
* `node debug <URI>` - Connects to the process via the URI such as
localhost:5858
+## V8 Inspector Integration for Node.js
+
+__NOTE: This is an experimental feature.__
+
+V8 Inspector integration allows attaching Chrome DevTools to Node.js
+instances for debugging and profiling.
+
+V8 Inspector can be enabled by passing the `--inspect` flag when starting a
+Node.js application. It is also possible to supply a custom port with that flag,
+e.g. `--inspect=9222` will accept DevTools connections on port 9222.
+
+To break on the first line of the application code, provide the `--debug-brk`
+flag in addition to `--inspect`.
+
[TCP-based protocol]: https://github.com/v8/v8/wiki/Debugging-Protocol
diff --git a/lib/internal/bootstrap_node.js b/lib/internal/bootstrap_node.js
index 79359bed55..27f05a4fcf 100644
--- a/lib/internal/bootstrap_node.js
+++ b/lib/internal/bootstrap_node.js
@@ -69,6 +69,10 @@
// Start the debugger agent
NativeModule.require('_debugger').start();
+ } else if (process.argv[1] == '--remote_debugging_server') {
+ // Start the debugging server
+ NativeModule.require('internal/inspector/remote_debugging_server');
+
} else if (process.argv[1] == '--debug-agent') {
// Start the debugger agent
NativeModule.require('_debug_agent').start();
diff --git a/node.gyp b/node.gyp
index 0d32905b6c..05a5530a2b 100644
--- a/node.gyp
+++ b/node.gyp
@@ -250,6 +250,28 @@
'deps/v8/src/third_party/vtune/v8vtune.gyp:v8_vtune'
],
}],
+ [ 'v8_inspector=="true"', {
+ 'defines': [
+ 'HAVE_INSPECTOR=1',
+ 'V8_INSPECTOR_USE_STL=1',
+ ],
+ 'sources': [
+ 'src/inspector_agent.cc',
+ 'src/inspector_socket.cc',
+ 'src/inspector_socket.h',
+ 'src/inspector-agent.h',
+ ],
+ 'dependencies': [
+ 'deps/v8_inspector/v8_inspector.gyp:v8_inspector',
+ ],
+ 'include_dirs': [
+ 'deps/v8_inspector',
+ 'deps/v8_inspector/deps/wtf', # temporary
+ '<(SHARED_INTERMEDIATE_DIR)/blink', # for inspector
+ ],
+ }, {
+ 'defines': [ 'HAVE_INSPECTOR=0' ]
+ }],
[ 'node_use_openssl=="true"', {
'defines': [ 'HAVE_OPENSSL=1' ],
'sources': [
@@ -690,7 +712,10 @@
'target_name': 'cctest',
'type': 'executable',
'dependencies': [
+ 'deps/openssl/openssl.gyp:openssl',
+ 'deps/http_parser/http_parser.gyp:http_parser',
'deps/gtest/gtest.gyp:gtest',
+ 'deps/uv/uv.gyp:libuv',
'deps/v8/tools/gyp/v8.gyp:v8',
'deps/v8/tools/gyp/v8.gyp:v8_libplatform'
],
@@ -711,6 +736,20 @@
'sources': [
'test/cctest/util.cc',
],
+
+ 'conditions': [
+ ['v8_inspector=="true"', {
+ 'dependencies': [
+ 'deps/openssl/openssl.gyp:openssl',
+ 'deps/http_parser/http_parser.gyp:http_parser',
+ 'deps/uv/uv.gyp:libuv'
+ ],
+ 'sources': [
+ 'src/inspector_socket.cc',
+ 'test/cctest/test_inspector_socket.cc'
+ ]
+ }]
+ ]
}
], # end targets
diff --git a/src/env-inl.h b/src/env-inl.h
index 34f9bf7d72..97e1ba8f76 100644
--- a/src/env-inl.h
+++ b/src/env-inl.h
@@ -225,6 +225,9 @@ inline Environment::Environment(v8::Local<v8::Context> context,
makecallback_cntr_(0),
async_wrap_uid_(0),
debugger_agent_(this),
+#if HAVE_INSPECTOR
+ inspector_agent_(this),
+#endif
http_parser_buffer_(nullptr),
context_(context->GetIsolate(), context) {
// We'll be creating new objects so make sure we've entered the context.
diff --git a/src/env.h b/src/env.h
index 0c95abd56c..4c310c8831 100644
--- a/src/env.h
+++ b/src/env.h
@@ -5,6 +5,9 @@
#include "ares.h"
#include "debug-agent.h"
+#if HAVE_INSPECTOR
+#include "inspector_agent.h"
+#endif
#include "handle_wrap.h"
#include "req-wrap.h"
#include "tree.h"
@@ -549,6 +552,12 @@ class Environment {
return &debugger_agent_;
}
+#if HAVE_INSPECTOR
+ inline inspector::Agent* inspector_agent() {
+ return &inspector_agent_;
+ }
+#endif
+
typedef ListHead<HandleWrap, &HandleWrap::handle_wrap_queue_> HandleWrapQueue;
typedef ListHead<ReqWrap<uv_req_t>, &ReqWrap<uv_req_t>::req_wrap_queue_>
ReqWrapQueue;
@@ -586,6 +595,9 @@ class Environment {
size_t makecallback_cntr_;
int64_t async_wrap_uid_;
debugger::Agent debugger_agent_;
+#if HAVE_INSPECTOR
+ inspector::Agent inspector_agent_;
+#endif
HandleWrapQueue handle_wrap_queue_;
ReqWrapQueue req_wrap_queue_;
diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc
new file mode 100644
index 0000000000..cd2ae83b19
--- /dev/null
+++ b/src/inspector_agent.cc
@@ -0,0 +1,506 @@
+#include "inspector_agent.h"
+
+#include "node.h"
+#include "env.h"
+#include "env-inl.h"
+#include "node_version.h"
+#include "v8-platform.h"
+#include "util.h"
+
+#include "platform/v8_inspector/public/V8Inspector.h"
+#include "platform/inspector_protocol/FrontendChannel.h"
+#include "platform/inspector_protocol/String16.h"
+#include "platform/inspector_protocol/Values.h"
+
+#include "libplatform/libplatform.h"
+
+#include <string.h>
+
+// We need pid to use as ID with Chrome
+#if defined(_MSC_VER)
+#include <direct.h>
+#include <io.h>
+#define getpid GetCurrentProcessId
+#else
+#include <unistd.h> // setuid, getuid
+#endif
+
+namespace node {
+namespace {
+
+const char DEVTOOLS_PATH[] = "/node";
+
+void PrintDebuggerReadyMessage(int port) {
+ fprintf(stderr, "Debugger listening on port %d. "
+ "To start debugging, open the following URL in Chrome:\n"
+ " chrome-devtools://devtools/remote/serve_file/"
+ "@521e5b7e2b7cc66b4006a8a54cb9c4e57494a5ef/inspector.html?"
+ "experiments=true&v8only=true&ws=localhost:%d/node\n", port, port);
+}
+
+bool AcceptsConnection(inspector_socket_t* socket, const char* path) {
+ return strncmp(DEVTOOLS_PATH, path, sizeof(DEVTOOLS_PATH)) == 0;
+}
+
+void DisposeInspector(inspector_socket_t* socket, int status) {
+ free(socket);
+}
+
+void DisconnectAndDisposeIO(inspector_socket_t* socket) {
+ if (socket) {
+ inspector_close(socket, DisposeInspector);
+ }
+}
+
+void OnBufferAlloc(uv_handle_t* handle, size_t len, uv_buf_t* buf) {
+ if (len > 0) {
+ buf->base = static_cast<char*>(malloc(len));
+ CHECK_NE(buf->base, nullptr);
+ }
+ buf->len = len;
+}
+
+void SendHttpResponse(inspector_socket_t* socket, const char* response,
+ size_t len) {
+ const char HEADERS[] = "HTTP/1.0 200 OK\r\n"
+ "Content-Type: application/json; charset=UTF-8\r\n"
+ "Cache-Control: no-cache\r\n"
+ "Content-Length: %ld\r\n"
+ "\r\n";
+ char header[sizeof(HEADERS) + 20];
+ int header_len = snprintf(header, sizeof(header), HEADERS, len);
+ inspector_write(socket, header, header_len);
+ inspector_write(socket, response, len);
+}
+
+void SendVersionResponse(inspector_socket_t* socket) {
+ const char VERSION_RESPONSE_TEMPLATE[] =
+ "[ {"
+ " \"Browser\": \"node.js/%s\","
+ " \"Protocol-Version\": \"1.1\","
+ " \"User-Agent\": \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
+ "(KHTML, like Gecko) Chrome/45.0.2446.0 Safari/537.36\","
+ " \"WebKit-Version\": \"537.36 (@198122)\""
+ "} ]";
+ char buffer[sizeof(VERSION_RESPONSE_TEMPLATE) + 128];
+ size_t len = snprintf(buffer, sizeof(buffer), VERSION_RESPONSE_TEMPLATE,
+ NODE_VERSION);
+ ASSERT_LT(len, sizeof(buffer));
+ SendHttpResponse(socket, buffer, len);
+}
+
+void SendTargentsListResponse(inspector_socket_t* socket) {
+ const char LIST_RESPONSE_TEMPLATE[] =
+ "[ {"
+ " \"description\": \"node.js instance\","
+ " \"devtoolsFrontendUrl\": "
+ "\"https://chrome-devtools-frontend.appspot.com/serve_file/"
+ "@4604d24a75168768584760ba56d175507941852f/inspector.html\","
+ " \"faviconUrl\": \"https://nodejs.org/static/favicon.ico\","
+ " \"id\": \"%d\","
+ " \"title\": \"%s\","
+ " \"type\": \"node\","
+ " \"webSocketDebuggerUrl\": \"ws://%s\""
+ "} ]";
+ char buffer[sizeof(LIST_RESPONSE_TEMPLATE) + 4096];
+ char title[2048]; // uv_get_process_title trims the title if too long
+ int err = uv_get_process_title(title, sizeof(title));
+ ASSERT_EQ(0, err);
+ char* c = title;
+ while (!c) {
+ if (*c < ' ' || *c == '\"') {
+ *c = '_';
+ }
+ c++;
+ }
+ size_t len = snprintf(buffer, sizeof(buffer), LIST_RESPONSE_TEMPLATE,
+ getpid(), title, DEVTOOLS_PATH);
+ ASSERT_LT(len, sizeof(buffer));
+ SendHttpResponse(socket, buffer, len);
+}
+
+bool RespondToGet(inspector_socket_t* socket, const char* path) {
+ const char PATH[] = "/json";
+ const char PATH_LIST[] = "/json/list";
+ const char PATH_VERSION[] = "/json/version";
+ const char PATH_ACTIVATE[] = "/json/activate/";
+ if (!strncmp(PATH_VERSION, path, sizeof(PATH_VERSION))) {
+ SendVersionResponse(socket);
+ } else if (!strncmp(PATH_LIST, path, sizeof(PATH_LIST)) ||
+ !strncmp(PATH, path, sizeof(PATH))) {
+ SendTargentsListResponse(socket);
+ } else if (!strncmp(path, PATH_ACTIVATE, sizeof(PATH_ACTIVATE) - 1) &&
+ atoi(path + (sizeof(PATH_ACTIVATE) - 1)) == getpid()) {
+ const char TARGET_ACTIVATED[] = "Target activated";
+ SendHttpResponse(socket, TARGET_ACTIVATED, sizeof(TARGET_ACTIVATED) - 1);
+ } else {
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+namespace inspector {
+
+using blink::protocol::DictionaryValue;
+using blink::protocol::String16;
+
+void InterruptCallback(v8::Isolate*, void* agent) {
+ static_cast<Agent*>(agent)->PostMessages();
+}
+
+class DispatchOnInspectorBackendTask : public v8::Task {
+ public:
+ explicit DispatchOnInspectorBackendTask(Agent* agent) : agent_(agent) {}
+
+ void Run() override {
+ agent_->PostMessages();
+ }
+
+ private:
+ Agent* agent_;
+};
+
+class ChannelImpl final : public blink::protocol::FrontendChannel {
+ public:
+ explicit ChannelImpl(Agent* agent): agent_(agent) {}
+ virtual ~ChannelImpl() {}
+ private:
+ virtual void sendProtocolResponse(int sessionId, int callId,
+ std::unique_ptr<DictionaryValue> message)
+ override {
+ sendMessageToFrontend(std::move(message));
+ }
+
+ virtual void sendProtocolNotification(
+ std::unique_ptr<DictionaryValue> message) override {
+ sendMessageToFrontend(std::move(message));
+ }
+
+ virtual void flush() override { }
+
+ void sendMessageToFrontend(std::unique_ptr<DictionaryValue> message) {
+ agent_->Write(message->toJSONString().utf8());
+ }
+
+ Agent* const agent_;
+};
+
+class SetConnectedTask : public v8::Task {
+ public:
+ SetConnectedTask(Agent* agent, bool connected)
+ : agent_(agent),
+ connected_(connected) {}
+
+ void Run() override {
+ agent_->SetConnected(connected_);
+ }
+
+ private:
+ Agent* agent_;
+ bool connected_;
+};
+
+class V8NodeInspector : public blink::V8Inspector {
+ public:
+ V8NodeInspector(Agent* agent, node::Environment* env, v8::Platform* platform)
+ : blink::V8Inspector(env->isolate(), env->context()),
+ agent_(agent),
+ isolate_(env->isolate()),
+ platform_(platform),
+ terminated_(false),
+ running_nested_loop_(false) {}
+
+ void runMessageLoopOnPause(int context_group_id) override {
+ if (running_nested_loop_)
+ return;
+ terminated_ = false;
+ running_nested_loop_ = true;
+ do {
+ uv_mutex_lock(&agent_->pause_lock_);
+ uv_cond_wait(&agent_->pause_cond_, &agent_->pause_lock_);
+ uv_mutex_unlock(&agent_->pause_lock_);
+ while (v8::platform::PumpMessageLoop(platform_, isolate_))
+ {}
+ } while (!terminated_);
+ terminated_ = false;
+ running_nested_loop_ = false;
+ }
+
+ void quitMessageLoopOnPause() override {
+ terminated_ = true;
+ }
+
+ private:
+ Agent* agent_;
+ v8::Isolate* isolate_;
+ v8::Platform* platform_;
+ bool terminated_;
+ bool running_nested_loop_;
+};
+
+Agent::Agent(Environment* env) : port_(9229),
+ wait_(false),
+ connected_(false),
+ shutting_down_(false),
+ parent_env_(env),
+ client_socket_(nullptr),
+ inspector_(nullptr),
+ platform_(nullptr),
+ dispatching_messages_(false) {
+ int err;
+ err = uv_sem_init(&start_sem_, 0);
+ CHECK_EQ(err, 0);
+}
+
+Agent::~Agent() {
+ if (!inspector_)
+ return;
+ uv_mutex_destroy(&queue_lock_);
+ uv_mutex_destroy(&pause_lock_);
+ uv_cond_destroy(&pause_cond_);
+ uv_close(reinterpret_cast<uv_handle_t*>(&data_written_), nullptr);
+}
+
+void Agent::Start(v8::Platform* platform, int port, bool wait) {
+ auto env = parent_env_;
+ inspector_ = new V8NodeInspector(this, env, platform);
+
+ int err;
+
+ platform_ = platform;
+
+ err = uv_loop_init(&child_loop_);
+ CHECK_EQ(err, 0);
+ err = uv_async_init(env->event_loop(), &data_written_, nullptr);
+ CHECK_EQ(err, 0);
+ err = uv_mutex_init(&queue_lock_);
+ CHECK_EQ(err, 0);
+ err = uv_mutex_init(&pause_lock_);
+ CHECK_EQ(err, 0);
+ err = uv_cond_init(&pause_cond_);
+ CHECK_EQ(err, 0);
+
+ uv_unref(reinterpret_cast<uv_handle_t*>(&data_written_));
+
+ port_ = port;
+ wait_ = wait;
+
+ err = uv_thread_create(&thread_, Agent::ThreadCbIO, this);
+ CHECK_EQ(err, 0);
+ uv_sem_wait(&start_sem_);
+
+ if (wait) {
+ // Flush messages in case of wait to connect, see OnRemoteDataIO on how it
+ // should be fixed.
+ SetConnected(true);
+ PostMessages();
+ }
+}
+
+void Agent::Stop() {
+ // TODO(repenaxa): hop on the right thread.
+ DisconnectAndDisposeIO(client_socket_);
+ int err = uv_thread_join(&thread_);
+ CHECK_EQ(err, 0);
+
+ uv_run(&child_loop_, UV_RUN_NOWAIT);
+
+ err = uv_loop_close(&child_loop_);
+ CHECK_EQ(err, 0);
+ delete inspector_;
+}
+
+bool Agent::IsStarted() {
+ return !!platform_;
+}
+
+void Agent::WaitForDisconnect() {
+ shutting_down_ = true;
+ fprintf(stderr, "Waiting for the debugger to disconnect...\n");
+ inspector_->runMessageLoopOnPause(0);
+}
+
+// static
+void Agent::ThreadCbIO(void* agent) {
+ static_cast<Agent*>(agent)->WorkerRunIO();
+}
+
+// static
+void Agent::OnSocketConnectionIO(uv_stream_t* server, int status) {
+ if (status == 0) {
+ inspector_socket_t* socket =
+ static_cast<inspector_socket_t*>(malloc(sizeof(*socket)));
+ ASSERT_NE(nullptr, socket);
+ memset(socket, 0, sizeof(*socket));
+ socket->data = server->data;
+ if (inspector_accept(server, socket, Agent::OnInspectorHandshakeIO) != 0) {
+ free(socket);
+ }
+ }
+}
+
+// static
+bool Agent::OnInspectorHandshakeIO(inspector_socket_t* socket,
+ enum inspector_handshake_event state,
+ const char* path) {
+ Agent* agent = static_cast<Agent*>(socket->data);
+ switch (state) {
+ case kInspectorHandshakeHttpGet:
+ return RespondToGet(socket, path);
+ case kInspectorHandshakeUpgrading:
+ return AcceptsConnection(socket, path);
+ case kInspectorHandshakeUpgraded:
+ agent->OnInspectorConnectionIO(socket);
+ return true;
+ case kInspectorHandshakeFailed:
+ return false;
+ default:
+ UNREACHABLE();
+ }
+}
+
+// static
+void Agent::OnRemoteDataIO(uv_stream_t* stream,
+ ssize_t read,
+ const uv_buf_t* b) {
+ inspector_socket_t* socket = static_cast<inspector_socket_t*>(stream->data);
+ Agent* agent = static_cast<Agent*>(socket->data);
+ if (read > 0) {
+ std::string str(b->base, read);
+ agent->PushPendingMessage(&agent->message_queue_, str);
+ free(b->base);
+
+ // TODO(pfeldman): Instead of blocking execution while debugger
+ // engages, node should wait for the run callback from the remote client
+ // and initiate its startup. This is a change to node.cc that should be
+ // upstreamed separately.
+ if (agent->wait_ && str.find("\"Runtime.run\"") != std::string::npos) {
+ agent->wait_ = false;
+ uv_sem_post(&agent->start_sem_);
+ }
+
+ agent->platform_->CallOnForegroundThread(agent->parent_env_->isolate(),
+ new DispatchOnInspectorBackendTask(agent));
+ agent->parent_env_->isolate()
+ ->RequestInterrupt(InterruptCallback, agent);
+ uv_async_send(&agent->data_written_);
+ } else if (read < 0) {
+ if (agent->client_socket_ == socket) {
+ agent->client_socket_ = nullptr;
+ }
+ DisconnectAndDisposeIO(socket);
+ } else {
+ // EOF
+ if (agent->client_socket_ == socket) {
+ agent->client_socket_ = nullptr;
+ agent->platform_->CallOnForegroundThread(agent->parent_env_->isolate(),
+ new SetConnectedTask(agent, false));
+ uv_async_send(&agent->data_written_);
+ }
+ }
+ uv_cond_broadcast(&agent->pause_cond_);
+}
+
+void Agent::PushPendingMessage(std::vector<std::string>* queue,
+ const std::string& message) {
+ uv_mutex_lock(&queue_lock_);
+ queue->push_back(message);
+ uv_mutex_unlock(&queue_lock_);
+}
+
+void Agent::SwapBehindLock(std::vector<std::string> Agent::*queue,
+ std::vector<std::string>* output) {
+ uv_mutex_lock(&queue_lock_);
+ (this->*queue).swap(*output);
+ uv_mutex_unlock(&queue_lock_);
+}
+
+// static
+void Agent::WriteCbIO(uv_async_t* async) {
+ Agent* agent = static_cast<Agent*>(async->data);
+ inspector_socket_t* socket = agent->client_socket_;
+ if (socket) {
+ std::vector<std::string> outgoing_messages;
+ agent->SwapBehindLock(&Agent::outgoing_message_queue_, &outgoing_messages);
+ for (auto const& message : outgoing_messages)
+ inspector_write(socket, message.c_str(), message.length());
+ }
+}
+
+void Agent::WorkerRunIO() {
+ sockaddr_in addr;
+ uv_tcp_t server;
+ int err = uv_async_init(&child_loop_, &io_thread_req_, Agent::WriteCbIO);
+ CHECK_EQ(0, err);
+ io_thread_req_.data = this;
+ uv_tcp_init(&child_loop_, &server);
+ uv_ip4_addr("0.0.0.0", port_, &addr);
+ server.data = this;
+ err = uv_tcp_bind(&server,
+ reinterpret_cast<const struct sockaddr*>(&addr), 0);
+ if (err == 0) {
+ err = uv_listen(reinterpret_cast<uv_stream_t*>(&server), 1,
+ OnSocketConnectionIO);
+ }
+ if (err == 0) {
+ PrintDebuggerReadyMessage(port_);
+ } else {
+ fprintf(stderr, "Unable to open devtools socket: %s\n", uv_strerror(err));
+ ABORT();
+ }
+ if (!wait_) {
+ uv_sem_post(&start_sem_);
+ }
+ uv_run(&child_loop_, UV_RUN_DEFAULT);
+ uv_close(reinterpret_cast<uv_handle_t*>(&io_thread_req_), nullptr);
+ uv_close(reinterpret_cast<uv_handle_t*>(&server), nullptr);
+ uv_run(&child_loop_, UV_RUN_DEFAULT);
+}
+
+void Agent::OnInspectorConnectionIO(inspector_socket_t* socket) {
+ if (client_socket_) {
+ return;
+ }
+ client_socket_ = socket;
+ inspector_read_start(socket, OnBufferAlloc, Agent::OnRemoteDataIO);
+ platform_->CallOnForegroundThread(parent_env_->isolate(),
+ new SetConnectedTask(this, true));
+}
+
+void Agent::PostMessages() {
+ if (dispatching_messages_)
+ return;
+ dispatching_messages_ = true;
+ std::vector<std::string> messages;
+ SwapBehindLock(&Agent::message_queue_, &messages);
+ for (auto const& message : messages)
+ inspector_->dispatchMessageFromFrontend(
+ String16::fromUTF8(message.c_str(), message.length()));
+ uv_async_send(&data_written_);
+ dispatching_messages_ = false;
+}
+
+void Agent::SetConnected(bool connected) {
+ if (connected_ == connected)
+ return;
+
+ connected_ = connected;
+ if (connected) {
+ fprintf(stderr, "Debugger attached.\n");
+ inspector_->connectFrontend(new ChannelImpl(this));
+ } else {
+ if (!shutting_down_)
+ PrintDebuggerReadyMessage(port_);
+ inspector_->quitMessageLoopOnPause();
+ inspector_->disconnectFrontend();
+ }
+}
+
+void Agent::Write(const std::string& message) {
+ PushPendingMessage(&outgoing_message_queue_, message);
+ ASSERT_EQ(0, uv_async_send(&io_thread_req_));
+}
+} // namespace debugger
+} // namespace node
diff --git a/src/inspector_agent.h b/src/inspector_agent.h
new file mode 100644
index 0000000000..65a4abeff7
--- /dev/null
+++ b/src/inspector_agent.h
@@ -0,0 +1,97 @@
+#ifndef SRC_INSPECTOR_AGENT_H_
+#define SRC_INSPECTOR_AGENT_H_
+
+#if !HAVE_INSPECTOR
+#error("This header can only be used when inspector is enabled")
+#endif
+
+#include "inspector_socket.h"
+#include "uv.h"
+#include "v8.h"
+#include "util.h"
+
+#include <string>
+#include <vector>
+
+namespace blink {
+class V8Inspector;
+}
+
+// Forward declaration to break recursive dependency chain with src/env.h.
+namespace node {
+class Environment;
+} // namespace node
+
+namespace node {
+namespace inspector {
+
+class ChannelImpl;
+
+class Agent {
+ public:
+ explicit Agent(node::Environment* env);
+ ~Agent();
+
+ // Start the inspector agent thread
+ void Start(v8::Platform* platform, int port, bool wait);
+ // Stop the inspector agent
+ void Stop();
+
+ bool IsStarted();
+ bool connected() { return connected_; }
+ void WaitForDisconnect();
+
+ protected:
+ static void ThreadCbIO(void* agent);
+ static void OnSocketConnectionIO(uv_stream_t* server, int status);
+ static bool OnInspectorHandshakeIO(inspector_socket_t* socket,
+ enum inspector_handshake_event state,
+ const char* path);
+ static void OnRemoteDataIO(uv_stream_t* stream, ssize_t read,
+ const uv_buf_t* b);
+ static void WriteCbIO(uv_async_t* async);
+
+ void WorkerRunIO();
+ void OnInspectorConnectionIO(inspector_socket_t* socket);
+ void PushPendingMessage(std::vector<std::string>* queue,
+ const std::string& message);
+ void SwapBehindLock(std::vector<std::string> Agent::*queue,
+ std::vector<std::string>* output);
+ void PostMessages();
+ void SetConnected(bool connected);
+ void Write(const std::string& message);
+
+ uv_sem_t start_sem_;
+ uv_cond_t pause_cond_;
+ uv_mutex_t queue_lock_;
+ uv_mutex_t pause_lock_;
+ uv_thread_t thread_;
+ uv_loop_t child_loop_;
+ uv_tcp_t server_;
+
+ int port_;
+ bool wait_;
+ bool connected_;
+ bool shutting_down_;
+ node::Environment* parent_env_;
+
+ uv_async_t data_written_;
+ uv_async_t io_thread_req_;
+ inspector_socket_t* client_socket_;
+ blink::V8Inspector* inspector_;
+ v8::Platform* platform_;
+ std::vector<std::string> message_queue_;
+ std::vector<std::string> outgoing_message_queue_;
+ bool dispatching_messages_;
+
+ friend class ChannelImpl;
+ friend class DispatchOnInspectorBackendTask;
+ friend class SetConnectedTask;
+ friend class V8NodeInspector;
+ friend void InterruptCallback(v8::Isolate*, void* agent);
+};
+
+} // namespace inspector
+} // namespace node
+
+#endif // SRC_INSPECTOR_AGENT_H_
diff --git a/src/inspector_socket.cc b/src/inspector_socket.cc
new file mode 100644
index 0000000000..cb248ec59f
--- /dev/null
+++ b/src/inspector_socket.cc
@@ -0,0 +1,679 @@
+#include "inspector_socket.h"
+
+#define NODE_WANT_INTERNALS 1
+#include "base64.h"
+
+#include "openssl/sha.h" // Sha-1 hash
+
+#include <string.h>
+#include <vector>
+
+#define ACCEPT_KEY_LENGTH base64_encoded_size(20)
+#define BUFFER_GROWTH_CHUNK_SIZE 1024
+
+#define DUMP_READS 0
+#define DUMP_WRITES 0
+
+static const char CLOSE_FRAME[] = {'\x88', '\x00'};
+
+struct http_parsing_state_s {
+ http_parser parser;
+ http_parser_settings parser_settings;
+ handshake_cb callback;
+ bool parsing_value;
+ char* ws_key;
+ char* path;
+ char* current_header;
+};
+
+struct ws_state_s {
+ uv_alloc_cb alloc_cb;
+ uv_read_cb read_cb;
+ inspector_cb close_cb;
+ bool close_sent;
+ bool received_close;
+};
+
+enum ws_decode_result {
+ FRAME_OK, FRAME_INCOMPLETE, FRAME_CLOSE, FRAME_ERROR
+};
+
+#if DUMP_READS || DUMP_WRITES
+static void dump_hex(const char* buf, size_t len) {
+ const char* ptr = buf;
+ const char* end = ptr + len;
+ const char* cptr;
+ char c;
+ int i;
+
+ while (ptr < end) {
+ cptr = ptr;
+ for (i = 0; i < 16 && ptr < end; i++) {
+ printf("%2.2X ", *(ptr++));
+ }
+ for (i = 72 - (i * 4); i > 0; i--) {
+ printf(" ");
+ }
+ for (i = 0; i < 16 && cptr < end; i++) {
+ c = *(cptr++);
+ printf("%c", (c > 0x19) ? c : '.');
+ }
+ printf("\n");
+ }
+ printf("\n\n");
+}
+#endif
+
+static void dispose_inspector(uv_handle_t* handle) {
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>(handle->data);
+ inspector_cb close =
+ inspector->ws_mode ? inspector->ws_state->close_cb : nullptr;
+ free(inspector->buffer);
+ free(inspector->ws_state);
+ inspector->ws_state = nullptr;
+ inspector->buffer = nullptr;
+ inspector->buffer_size = 0;
+ inspector->data_len = 0;
+ inspector->last_read_end = 0;
+ if (close) {
+ close(inspector, 0);
+ }
+}
+
+static void close_connection(inspector_socket_t* inspector) {
+ uv_handle_t* socket = reinterpret_cast<uv_handle_t*>(&inspector->client);
+ if (!uv_is_closing(socket)) {
+ uv_read_stop(reinterpret_cast<uv_stream_t*>(socket));
+ uv_close(socket, dispose_inspector);
+ } else if (inspector->ws_state->close_cb) {
+ inspector->ws_state->close_cb(inspector, 0);
+ }
+}
+
+// Cleanup
+static void write_request_cleanup(uv_write_t* req, int status) {
+ free((reinterpret_cast<uv_buf_t*>(req->data))->base);
+ free(req->data);
+ free(req);
+}
+
+static int write_to_client(inspector_socket_t* inspector,
+ const char* msg,
+ size_t len,
+ uv_write_cb write_cb = write_request_cleanup) {
+#if DUMP_WRITES
+ printf("%s (%ld bytes):\n", __FUNCTION__, len);
+ dump_hex(msg, len);
+#endif
+
+ // Freed in write_request_cleanup
+ uv_buf_t* buf = reinterpret_cast<uv_buf_t*>(malloc(sizeof(uv_buf_t)));
+ uv_write_t* req = reinterpret_cast<uv_write_t*>(malloc(sizeof(uv_write_t)));
+ CHECK_NE(buf, nullptr);
+ CHECK_NE(req, nullptr);
+ memset(req, 0, sizeof(*req));
+ buf->base = reinterpret_cast<char*>(malloc(len));
+
+ CHECK_NE(buf->base, nullptr);
+
+ memcpy(buf->base, msg, len);
+ buf->len = len;
+ req->data = buf;
+
+ uv_stream_t* stream = reinterpret_cast<uv_stream_t*>(&inspector->client);
+ return uv_write(req, stream, buf, 1, write_cb) < 0;
+}
+
+// Constants for hybi-10 frame format.
+
+typedef int OpCode;
+
+const OpCode kOpCodeContinuation = 0x0;
+const OpCode kOpCodeText = 0x1;
+const OpCode kOpCodeBinary = 0x2;
+const OpCode kOpCodeClose = 0x8;
+const OpCode kOpCodePing = 0x9;
+const OpCode kOpCodePong = 0xA;
+
+const unsigned char kFinalBit = 0x80;
+const unsigned char kReserved1Bit = 0x40;
+const unsigned char kReserved2Bit = 0x20;
+const unsigned char kReserved3Bit = 0x10;
+const unsigned char kOpCodeMask = 0xF;
+const unsigned char kMaskBit = 0x80;
+const unsigned char kPayloadLengthMask = 0x7F;
+
+const size_t kMaxSingleBytePayloadLength = 125;
+const size_t kTwoBytePayloadLengthField = 126;
+const size_t kEightBytePayloadLengthField = 127;
+const size_t kMaskingKeyWidthInBytes = 4;
+
+static std::vector<char> encode_frame_hybi17(const char* message,
+ size_t data_length) {
+ std::vector<char> frame;
+ OpCode op_code = kOpCodeText;
+ frame.push_back(kFinalBit | op_code);
+ if (data_length <= kMaxSingleBytePayloadLength) {
+ frame.push_back(static_cast<char>(data_length));
+ } else if (data_length <= 0xFFFF) {
+ frame.push_back(kTwoBytePayloadLengthField);
+ frame.push_back((data_length & 0xFF00) >> 8);
+ frame.push_back(data_length & 0xFF);
+ } else {
+ frame.push_back(kEightBytePayloadLengthField);
+ char extended_payload_length[8];
+ size_t remaining = data_length;
+ // Fill the length into extended_payload_length in the network byte order.
+ for (int i = 0; i < 8; ++i) {
+ extended_payload_length[7 - i] = remaining & 0xFF;
+ remaining >>= 8;
+ }
+ frame.insert(frame.end(), extended_payload_length,
+ extended_payload_length + 8);
+ ASSERT_EQ(0, remaining);
+ }
+ frame.insert(frame.end(), message, message + data_length);
+ return frame;
+}
+
+static ws_decode_result decode_frame_hybi17(const char* buffer_begin,
+ size_t data_length,
+ bool client_frame,
+ int* bytes_consumed,
+ std::vector<char>* output,
+ bool* compressed) {
+ *bytes_consumed = 0;
+ if (data_length < 2)
+ return FRAME_INCOMPLETE;
+
+ const char* p = buffer_begin;
+ const char* buffer_end = p + data_length;
+
+ unsigned char first_byte = *p++;
+ unsigned char second_byte = *p++;
+
+ bool final = (first_byte & kFinalBit) != 0;
+ bool reserved1 = (first_byte & kReserved1Bit) != 0;
+ bool reserved2 = (first_byte & kReserved2Bit) != 0;
+ bool reserved3 = (first_byte & kReserved3Bit) != 0;
+ int op_code = first_byte & kOpCodeMask;
+ bool masked = (second_byte & kMaskBit) != 0;
+ *compressed = reserved1;
+ if (!final || reserved2 || reserved3)
+ return FRAME_ERROR; // Only compression extension is supported.
+
+ bool closed = false;
+ switch (op_code) {
+ case kOpCodeClose:
+ closed = true;
+ break;
+ case kOpCodeText:
+ break;
+ case kOpCodeBinary: // We don't support binary frames yet.
+ case kOpCodeContinuation: // We don't support binary frames yet.
+ case kOpCodePing: // We don't support binary frames yet.
+ case kOpCodePong: // We don't support binary frames yet.
+ default:
+ return FRAME_ERROR;
+ }
+
+ // In Hybi-17 spec client MUST mask its frame.
+ if (client_frame && !masked) {
+ return FRAME_ERROR;
+ }
+
+ uint64_t payload_length64 = second_byte & kPayloadLengthMask;
+ if (payload_length64 > kMaxSingleBytePayloadLength) {
+ int extended_payload_length_size;
+ if (payload_length64 == kTwoBytePayloadLengthField) {
+ extended_payload_length_size = 2;
+ } else if (payload_length64 == kEightBytePayloadLengthField) {
+ extended_payload_length_size = 8;
+ } else {
+ return FRAME_ERROR;
+ }
+ if (buffer_end - p < extended_payload_length_size)
+ return FRAME_INCOMPLETE;
+ payload_length64 = 0;
+ for (int i = 0; i < extended_payload_length_size; ++i) {
+ payload_length64 <<= 8;
+ payload_length64 |= static_cast<unsigned char>(*p++);
+ }
+ }
+
+ static const uint64_t max_payload_length = 0x7FFFFFFFFFFFFFFFull;
+ static const size_t max_length = SIZE_MAX;
+ if (payload_length64 > max_payload_length ||
+ payload_length64 > max_length - kMaskingKeyWidthInBytes) {
+ // WebSocket frame length too large.
+ return FRAME_ERROR;
+ }
+ size_t payload_length = static_cast<size_t>(payload_length64);
+
+ if (data_length - kMaskingKeyWidthInBytes < payload_length)
+ return FRAME_INCOMPLETE;
+
+ const char* masking_key = p;
+ const char* payload = p + kMaskingKeyWidthInBytes;
+ for (size_t i = 0; i < payload_length; ++i) // Unmask the payload.
+ output->insert(output->end(),
+ payload[i] ^ masking_key[i % kMaskingKeyWidthInBytes]);
+
+ size_t pos = p + kMaskingKeyWidthInBytes + payload_length - buffer_begin;
+ *bytes_consumed = pos;
+ return closed ? FRAME_CLOSE : FRAME_OK;
+}
+
+static void invoke_read_callback(inspector_socket_t* inspector,
+ int status, const uv_buf_t* buf) {
+ if (inspector->ws_state->read_cb) {
+ inspector->ws_state->read_cb(
+ reinterpret_cast<uv_stream_t*>(&inspector->client), status, buf);
+ }
+}
+
+static void shutdown_complete(inspector_socket_t* inspector) {
+ if (inspector->ws_state->close_cb) {
+ inspector->ws_state->close_cb(inspector, 0);
+ }
+ close_connection(inspector);
+}
+
+static void on_close_frame_written(uv_write_t* write, int status) {
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>(write->handle->data);
+ write_request_cleanup(write, status);
+ inspector->ws_state->close_sent = true;
+ if (inspector->ws_state->received_close) {
+ shutdown_complete(inspector);
+ }
+}
+
+static void close_frame_received(inspector_socket_t* inspector) {
+ inspector->ws_state->received_close = true;
+ if (!inspector->ws_state->close_sent) {
+ invoke_read_callback(inspector, 0, 0);
+ write_to_client(inspector, CLOSE_FRAME, sizeof(CLOSE_FRAME),
+ on_close_frame_written);
+ } else {
+ shutdown_complete(inspector);
+ }
+}
+
+static int parse_ws_frames(inspector_socket_t* inspector, size_t len) {
+ int bytes_consumed = 0;
+ std::vector<char> output;
+ bool compressed = false;
+
+ ws_decode_result r = decode_frame_hybi17(inspector->buffer,
+ len, true /* client_frame */,
+ &bytes_consumed, &output,
+ &compressed);
+ // Compressed frame means client is ignoring the headers and misbehaves
+ if (compressed || r == FRAME_ERROR) {
+ invoke_read_callback(inspector, UV_EPROTO, nullptr);
+ close_connection(inspector);
+ bytes_consumed = 0;
+ } else if (r == FRAME_CLOSE) {
+ close_frame_received(inspector);
+ bytes_consumed = 0;
+ } else if (r == FRAME_OK && inspector->ws_state->alloc_cb
+ && inspector->ws_state->read_cb) {
+ uv_buf_t buffer;
+ size_t len = output.size();
+ inspector->ws_state->alloc_cb(
+ reinterpret_cast<uv_handle_t*>(&inspector->client),
+ len, &buffer);
+ CHECK_GE(buffer.len, len);
+ memcpy(buffer.base, &output[0], len);
+ invoke_read_callback(inspector, len, &buffer);
+ }
+ return bytes_consumed;
+}
+
+static void prepare_buffer(uv_handle_t* stream, size_t len, uv_buf_t* buf) {
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>(stream->data);
+
+ if (len > (inspector->buffer_size - inspector->data_len)) {
+ int new_size = (inspector->data_len + len + BUFFER_GROWTH_CHUNK_SIZE - 1) /
+ BUFFER_GROWTH_CHUNK_SIZE *
+ BUFFER_GROWTH_CHUNK_SIZE;
+ inspector->buffer_size = new_size;
+ inspector->buffer = reinterpret_cast<char*>(realloc(inspector->buffer,
+ inspector->buffer_size));
+ ASSERT_NE(inspector->buffer, nullptr);
+ }
+ buf->base = inspector->buffer + inspector->data_len;
+ buf->len = len;
+ inspector->data_len += len;
+}
+
+static void websockets_data_cb(uv_stream_t* stream, ssize_t nread,
+ const uv_buf_t* buf) {
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>(stream->data);
+ if (nread < 0 || nread == UV_EOF) {
+ inspector->connection_eof = true;
+ if (!inspector->shutting_down && inspector->ws_state->read_cb) {
+ inspector->ws_state->read_cb(stream, nread, nullptr);
+ }
+ } else {
+ #if DUMP_READS
+ printf("%s read %ld bytes\n", __FUNCTION__, nread);
+ if (nread > 0) {
+ dump_hex(buf->base, nread);
+ }
+ #endif
+ // 1. Move read bytes to continue the buffer
+ // Should be same as this is supposedly last buffer
+ ASSERT_EQ(buf->base + buf->len, inspector->buffer + inspector->data_len);
+
+ // Should be noop...
+ memmove(inspector->buffer + inspector->last_read_end, buf->base, nread);
+ inspector->last_read_end += nread;
+
+ // 2. Parse.
+ int processed = 0;
+ do {
+ processed = parse_ws_frames(inspector, inspector->last_read_end);
+ // 3. Fix the buffer size & length
+ if (processed > 0) {
+ memmove(inspector->buffer, inspector->buffer + processed,
+ inspector->last_read_end - processed);
+ inspector->last_read_end -= processed;
+ inspector->data_len = inspector->last_read_end;
+ }
+ } while (processed > 0 && inspector->data_len > 0);
+ }
+}
+
+int inspector_read_start(inspector_socket_t* inspector,
+ uv_alloc_cb alloc_cb, uv_read_cb read_cb) {
+ ASSERT(inspector->ws_mode);
+ ASSERT(!inspector->shutting_down || read_cb == nullptr);
+ inspector->ws_state->close_sent = false;
+ inspector->ws_state->alloc_cb = alloc_cb;
+ inspector->ws_state->read_cb = read_cb;
+ int err =
+ uv_read_start(reinterpret_cast<uv_stream_t*>(&inspector->client),
+ prepare_buffer,
+ websockets_data_cb);
+ if (err < 0) {
+ close_connection(inspector);
+ }
+ return err;
+}
+
+void inspector_read_stop(inspector_socket_t* inspector) {
+ uv_read_stop(reinterpret_cast<uv_stream_t*>(&inspector->client));
+ inspector->ws_state->alloc_cb = nullptr;
+ inspector->ws_state->read_cb = nullptr;
+}
+
+static void generate_accept_string(const char* client_key, char* buffer) {
+ // Magic string from websockets spec.
+ const char ws_magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ size_t key_len = strlen(client_key);
+ size_t magic_len = sizeof(ws_magic) - 1;
+
+ char* buf = reinterpret_cast<char*>(malloc(key_len + magic_len));
+ CHECK_NE(buf, nullptr);
+ memcpy(buf, client_key, key_len);
+ memcpy(buf + key_len, ws_magic, magic_len);
+ char hash[20];
+ SHA1((unsigned char*) buf, key_len + magic_len, (unsigned char*) hash);
+ free(buf);
+ node::base64_encode(hash, 20, buffer, ACCEPT_KEY_LENGTH);
+ buffer[ACCEPT_KEY_LENGTH] = '\0';
+}
+
+static void append(char** value, const char* string, size_t length) {
+ const size_t INCREMENT = 500; // There should never be more then 1 chunk...
+
+ int current_len = *value ? strlen(*value) : 0;
+ int new_len = current_len + length;
+ int adjusted = (new_len / INCREMENT + 1) * INCREMENT;
+ *value = reinterpret_cast<char*>(realloc(*value, adjusted));
+ memcpy(*value + current_len, string, length);
+ (*value)[new_len] = '\0';
+}
+
+static int header_value_cb(http_parser* parser, const char* at, size_t length) {
+ char SEC_WEBSOCKET_KEY_HEADER[] = "Sec-WebSocket-Key";
+ struct http_parsing_state_s* state = (struct http_parsing_state_s*)
+ (reinterpret_cast<inspector_socket_t*>(parser->data))->http_parsing_state;
+ state->parsing_value = true;
+ if (state->current_header && strncmp(state->current_header,
+ SEC_WEBSOCKET_KEY_HEADER,
+ sizeof(SEC_WEBSOCKET_KEY_HEADER)) == 0) {
+ append(&state->ws_key, at, length);
+ }
+ return 0;
+}
+
+static int header_field_cb(http_parser* parser, const char* at, size_t length) {
+ struct http_parsing_state_s* state = (struct http_parsing_state_s*)
+ (reinterpret_cast<inspector_socket_t*>(parser->data))->http_parsing_state;
+ if (state->parsing_value) {
+ state->parsing_value = false;
+ if (state->current_header)
+ state->current_header[0] = '\0';
+ }
+ append(&state->current_header, at, length);
+ return 0;
+}
+
+static int path_cb(http_parser* parser, const char* at, size_t length) {
+ struct http_parsing_state_s* state = (struct http_parsing_state_s*)
+ (reinterpret_cast<inspector_socket_t*>(parser->data))->http_parsing_state;
+ append(&state->path, at, length);
+ return 0;
+}
+
+static void handshake_complete(inspector_socket_t* inspector) {
+ uv_read_stop(reinterpret_cast<uv_stream_t*>(&inspector->client));
+ handshake_cb callback = inspector->http_parsing_state->callback;
+ inspector->ws_state = (struct ws_state_s*) malloc(sizeof(struct ws_state_s));
+ ASSERT_NE(nullptr, inspector->ws_state);
+ memset(inspector->ws_state, 0, sizeof(struct ws_state_s));
+ inspector->last_read_end = 0;
+ inspector->ws_mode = true;
+ callback(inspector, kInspectorHandshakeUpgraded,
+ inspector->http_parsing_state->path);
+}
+
+static void cleanup_http_parsing_state(struct http_parsing_state_s* state) {
+ free(state->current_header);
+ free(state->path);
+ free(state->ws_key);
+ free(state);
+}
+
+static void handshake_failed(inspector_socket_t* inspector) {
+ http_parsing_state_s* state = inspector->http_parsing_state;
+ const char HANDSHAKE_FAILED_RESPONSE[] =
+ "HTTP/1.0 400 Bad Request\r\n"
+ "Content-Type: text/html; charset=UTF-8\r\n\r\n"
+ "WebSockets request was expected\r\n";
+ write_to_client(inspector, HANDSHAKE_FAILED_RESPONSE,
+ sizeof(HANDSHAKE_FAILED_RESPONSE) - 1);
+ close_connection(inspector);
+ inspector->http_parsing_state = nullptr;
+ state->callback(inspector, kInspectorHandshakeFailed, state->path);
+}
+
+// init_handshake references message_complete_cb
+static void init_handshake(inspector_socket_t* inspector);
+
+static int message_complete_cb(http_parser* parser) {
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>(parser->data);
+ struct http_parsing_state_s* state =
+ (struct http_parsing_state_s*) inspector->http_parsing_state;
+ if (parser->method != HTTP_GET) {
+ handshake_failed(inspector);
+ } else if (!parser->upgrade) {
+ if (state->callback(inspector, kInspectorHandshakeHttpGet, state->path)) {
+ init_handshake(inspector);
+ } else {
+ handshake_failed(inspector);
+ }
+ } else if (!state->ws_key) {
+ handshake_failed(inspector);
+ } else if (state->callback(inspector, kInspectorHandshakeUpgrading,
+ state->path)) {
+ char accept_string[ACCEPT_KEY_LENGTH + 1];
+ generate_accept_string(state->ws_key, accept_string);
+
+ const char accept_ws_prefix[] = "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: ";
+ const char accept_ws_suffix[] = "\r\n\r\n";
+ // Format has two chars (%s) that are replaced with actual key
+ char accept_response[sizeof(accept_ws_prefix) - 1 +
+ sizeof(accept_ws_suffix) - 1 +
+ ACCEPT_KEY_LENGTH];
+ memcpy(accept_response, accept_ws_prefix, sizeof(accept_ws_prefix) - 1);
+ memcpy(accept_response + sizeof(accept_ws_prefix) - 1,
+ accept_string, ACCEPT_KEY_LENGTH);
+ memcpy(accept_response + sizeof(accept_ws_prefix) - 1 + ACCEPT_KEY_LENGTH,
+ accept_ws_suffix, sizeof(accept_ws_suffix) - 1);
+ int len = sizeof(accept_response);
+ if (write_to_client(inspector, accept_response, len) >= 0) {
+ handshake_complete(inspector);
+ } else {
+ state->callback(inspector, kInspectorHandshakeFailed, nullptr);
+ close_connection(inspector);
+ }
+ inspector->http_parsing_state = nullptr;
+ } else {
+ handshake_failed(inspector);
+ }
+ return 0;
+}
+
+static void data_received_cb(uv_stream_s* client, ssize_t nread,
+ const uv_buf_t* buf) {
+#if DUMP_READS
+ if (nread >= 0) {
+ printf("%s (%ld bytes)\n", __FUNCTION__, nread);
+ dump_hex(buf->base, nread);
+ } else {
+ printf("[%s:%d] %s\n", __FUNCTION__, __LINE__, uv_err_name(nread));
+ }
+#endif
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>((client->data));
+ http_parsing_state_s* state = inspector->http_parsing_state;
+ if (nread < 0 || nread == UV_EOF) {
+ inspector->http_parsing_state->callback(inspector,
+ kInspectorHandshakeFailed,
+ nullptr);
+ close_connection(inspector);
+ inspector->http_parsing_state = nullptr;
+ } else {
+ http_parser* parser = &state->parser;
+ ssize_t parsed = http_parser_execute(parser, &state->parser_settings,
+ inspector->buffer,
+ nread);
+ if (parsed < nread) {
+ handshake_failed(inspector);
+ }
+ inspector->data_len = 0;
+ }
+
+ if (inspector->http_parsing_state == nullptr) {
+ cleanup_http_parsing_state(state);
+ }
+}
+
+static void init_handshake(inspector_socket_t* inspector) {
+ http_parsing_state_s* state = inspector->http_parsing_state;
+ CHECK_NE(state, nullptr);
+ if (state->current_header) {
+ state->current_header[0] = '\0';
+ }
+ if (state->ws_key) {
+ state->ws_key[0] = '\0';
+ }
+ if (state->path) {
+ state->path[0] = '\0';
+ }
+ http_parser_init(&state->parser, HTTP_REQUEST);
+ state->parser.data = inspector;
+ http_parser_settings* settings = &state->parser_settings;
+ http_parser_settings_init(settings);
+ settings->on_header_field = header_field_cb;
+ settings->on_header_value = header_value_cb;
+ settings->on_message_complete = message_complete_cb;
+ settings->on_url = path_cb;
+}
+
+int inspector_accept(uv_stream_t* server, inspector_socket_t* inspector,
+ handshake_cb callback) {
+ ASSERT_NE(callback, nullptr);
+ // The only field that users should care about.
+ void* data = inspector->data;
+ memset(inspector, 0, sizeof(*inspector));
+ inspector->data = data;
+
+ inspector->http_parsing_state = (struct http_parsing_state_s*)
+ malloc(sizeof(struct http_parsing_state_s));
+ ASSERT_NE(nullptr, inspector->http_parsing_state);
+ memset(inspector->http_parsing_state, 0, sizeof(struct http_parsing_state_s));
+ uv_stream_t* client = reinterpret_cast<uv_stream_t*>(&inspector->client);
+ CHECK_NE(client, nullptr);
+ int err = uv_tcp_init(server->loop, &inspector->client);
+
+ if (err == 0) {
+ err = uv_accept(server, client);
+ }
+ if (err == 0) {
+ client->data = inspector;
+ init_handshake(inspector);
+ inspector->http_parsing_state->callback = callback;
+ err = uv_read_start(client, prepare_buffer,
+ data_received_cb);
+ }
+ if (err != 0) {
+ uv_close(reinterpret_cast<uv_handle_t*>(client), NULL);
+ }
+ return err;
+}
+
+void inspector_write(inspector_socket_t* inspector, const char* data,
+ size_t len) {
+ if (inspector->ws_mode) {
+ std::vector<char> output = encode_frame_hybi17(data, len);
+ write_to_client(inspector, &output[0], output.size());
+ } else {
+ write_to_client(inspector, data, len);
+ }
+}
+
+void inspector_close(inspector_socket_t* inspector,
+ inspector_cb callback) {
+ // libuv throws assertions when closing stream that's already closed - we
+ // need to do the same.
+ ASSERT(!uv_is_closing(reinterpret_cast<uv_handle_t*>(&inspector->client)));
+ ASSERT(!inspector->shutting_down);
+ inspector->shutting_down = true;
+ inspector->ws_state->close_cb = callback;
+ if (inspector->connection_eof) {
+ close_connection(inspector);
+ } else {
+ inspector_read_stop(inspector);
+ write_to_client(inspector, CLOSE_FRAME, sizeof(CLOSE_FRAME),
+ on_close_frame_written);
+ inspector_read_start(inspector, nullptr, nullptr);
+ }
+}
+
+bool inspector_is_active(const struct inspector_socket_s* inspector) {
+ const uv_handle_t* client =
+ reinterpret_cast<const uv_handle_t*>(&inspector->client);
+ return !inspector->shutting_down && !uv_is_closing(client);
+}
diff --git a/src/inspector_socket.h b/src/inspector_socket.h
new file mode 100644
index 0000000000..3e52762e71
--- /dev/null
+++ b/src/inspector_socket.h
@@ -0,0 +1,57 @@
+#ifndef SRC_INSPECTOR_SOCKET_H_
+#define SRC_INSPECTOR_SOCKET_H_
+
+#include "http_parser.h"
+#include "uv.h"
+
+enum inspector_handshake_event {
+ kInspectorHandshakeUpgrading,
+ kInspectorHandshakeUpgraded,
+ kInspectorHandshakeHttpGet,
+ kInspectorHandshakeFailed
+};
+
+struct inspector_socket_s;
+
+typedef void (*inspector_cb)(struct inspector_socket_s*, int);
+// Notifies as handshake is progressing. Returning false as a response to
+// kInspectorHandshakeUpgrading or kInspectorHandshakeHttpGet event will abort
+// the connection. inspector_write can be used from the callback.
+typedef bool (*handshake_cb)(struct inspector_socket_s*,
+ enum inspector_handshake_event state,
+ const char* path);
+
+struct http_parsing_state_s;
+struct ws_state_s;
+
+struct inspector_socket_s {
+ void* data;
+ struct http_parsing_state_s* http_parsing_state;
+ struct ws_state_s* ws_state;
+ char* buffer;
+ size_t buffer_size;
+ size_t data_len;
+ size_t last_read_end;
+ uv_tcp_t client;
+ bool ws_mode;
+ bool shutting_down;
+ bool connection_eof;
+};
+
+typedef struct inspector_socket_s inspector_socket_t;
+
+int inspector_accept(uv_stream_t* server, struct inspector_socket_s* inspector,
+ handshake_cb callback);
+
+void inspector_close(struct inspector_socket_s* inspector,
+ inspector_cb callback);
+
+// Callbacks will receive handles that has inspector in data field...
+int inspector_read_start(struct inspector_socket_s* inspector, uv_alloc_cb,
+ uv_read_cb);
+void inspector_read_stop(struct inspector_socket_s* inspector);
+void inspector_write(struct inspector_socket_s* inspector,
+ const char* data, size_t len);
+bool inspector_is_active(const struct inspector_socket_s* inspector);
+
+#endif // SRC_INSPECTOR_SOCKET_H_
diff --git a/src/node.cc b/src/node.cc
index cda2bac096..258ebb596a 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -137,6 +137,9 @@ static bool track_heap_objects = false;
static const char* eval_string = nullptr;
static unsigned int preload_module_count = 0;
static const char** preload_modules = nullptr;
+#if HAVE_INSPECTOR
+static bool use_inspector = false;
+#endif
static bool use_debug_agent = false;
static bool debug_wait_connect = false;
static int debug_port = 5858;
@@ -3412,6 +3415,22 @@ static bool ParseDebugOpt(const char* arg) {
port = arg + sizeof("--debug-brk=") - 1;
} else if (!strncmp(arg, "--debug-port=", sizeof("--debug-port=") - 1)) {
port = arg + sizeof("--debug-port=") - 1;
+#if HAVE_INSPECTOR
+ // Specifying both --inspect and --debug means debugging is on, using Chromium
+ // inspector.
+ } else if (!strcmp(arg, "--inspect")) {
+ use_debug_agent = true;
+ use_inspector = true;
+ } else if (!strncmp(arg, "--inspect=", sizeof("--inspect=") - 1)) {
+ use_debug_agent = true;
+ use_inspector = true;
+ port = arg + sizeof("--inspect=") - 1;
+#else
+ } else if (!strncmp(arg, "--inspect", sizeof("--inspect") - 1)) {
+ fprintf(stderr,
+ "Inspector support is not available with this Node.js build\n");
+ return false;
+#endif
} else {
return false;
}
@@ -3682,10 +3701,19 @@ static void DispatchMessagesDebugAgentCallback(Environment* env) {
static void StartDebug(Environment* env, bool wait) {
CHECK(!debugger_running);
+#if HAVE_INSPECTOR
+ if (use_inspector) {
+ env->inspector_agent()->Start(default_platform, debug_port, wait);
+ debugger_running = true;
+ } else {
+#endif
+ env->debugger_agent()->set_dispatch_handler(
+ DispatchMessagesDebugAgentCallback);
+ debugger_running = env->debugger_agent()->Start(debug_port, wait);
+#if HAVE_INSPECTOR
+ }
+#endif
- env->debugger_agent()->set_dispatch_handler(
- DispatchMessagesDebugAgentCallback);
- debugger_running = env->debugger_agent()->Start(debug_port, wait);
if (debugger_running == false) {
fprintf(stderr, "Starting debugger on port %d failed\n", debug_port);
fflush(stderr);
@@ -3697,6 +3725,11 @@ static void StartDebug(Environment* env, bool wait) {
// Called from the main thread.
static void EnableDebug(Environment* env) {
CHECK(debugger_running);
+#if HAVE_INSPECTOR
+ if (use_inspector) {
+ return;
+ }
+#endif
// Send message to enable debug in workers
HandleScope handle_scope(env->isolate());
@@ -3991,7 +4024,15 @@ static void DebugPause(const FunctionCallbackInfo<Value>& args) {
static void DebugEnd(const FunctionCallbackInfo<Value>& args) {
if (debugger_running) {
Environment* env = Environment::GetCurrent(args);
- env->debugger_agent()->Stop();
+#if HAVE_INSPECTOR
+ if (use_inspector) {
+ env->inspector_agent()->Stop();
+ } else {
+#endif
+ env->debugger_agent()->Stop();
+#if HAVE_INSPECTOR
+ }
+#endif
debugger_running = false;
}
}
@@ -4420,6 +4461,24 @@ static void StartNodeInstance(void* arg) {
instance_data->set_exit_code(exit_code);
RunAtExit(env);
+#if HAVE_INSPECTOR
+ if (env->inspector_agent()->connected()) {
+ // Restore signal dispositions, the app is done and is no longer
+ // capable of handling signals.
+#ifdef __POSIX__
+ struct sigaction act;
+ memset(&act, 0, sizeof(act));
+ for (unsigned nr = 1; nr < 32; nr += 1) {
+ if (nr == SIGKILL || nr == SIGSTOP || nr == SIGPROF)
+ continue;
+ act.sa_handler = (nr == SIGPIPE) ? SIG_IGN : SIG_DFL;
+ CHECK_EQ(0, sigaction(nr, &act, nullptr));
+ }
+#endif
+ env->inspector_agent()->WaitForDisconnect();
+ }
+#endif
+
#if defined(LEAK_SANITIZER)
__lsan_do_leak_check();
#endif
diff --git a/src/node_internals.h b/src/node_internals.h
index 2875f5ac79..64134d9ab8 100644
--- a/src/node_internals.h
+++ b/src/node_internals.h
@@ -221,7 +221,7 @@ class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
// by clearing all callbacks that could handle the error.
void ClearFatalExceptionHandlers(Environment* env);
-enum NodeInstanceType { MAIN, WORKER };
+enum NodeInstanceType { MAIN, WORKER, REMOTE_DEBUG_SERVER };
class NodeInstanceData {
public:
@@ -265,6 +265,10 @@ class NodeInstanceData {
return node_instance_type_ == WORKER;
}
+ bool is_remote_debug_server() {
+ return node_instance_type_ == REMOTE_DEBUG_SERVER;
+ }
+
int argc() {
return argc_;
}
diff --git a/src/signal_wrap.cc b/src/signal_wrap.cc
index 3ee0251f9b..8d31dbf623 100644
--- a/src/signal_wrap.cc
+++ b/src/signal_wrap.cc
@@ -65,6 +65,15 @@ class SignalWrap : public HandleWrap {
SignalWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
int signum = args[0]->Int32Value();
+#if defined(__POSIX__) && defined(HAVE_INSPECTOR)
+ if (signum == SIGPROF) {
+ Environment* env = Environment::GetCurrent(args);
+ if (env->inspector_agent()->IsStarted()) {
+ fprintf(stderr, "process.on(SIGPROF) is reserved while debugging\n");
+ return;
+ }
+ }
+#endif
int err = uv_signal_start(&wrap->handle_, OnSignal, signum);
args.GetReturnValue().Set(err);
}
diff --git a/test/cctest/test_inspector_socket.cc b/test/cctest/test_inspector_socket.cc
new file mode 100644
index 0000000000..ebe4215af5
--- /dev/null
+++ b/test/cctest/test_inspector_socket.cc
@@ -0,0 +1,864 @@
+#include "inspector_socket.h"
+
+#include "gtest/gtest.h"
+
+#define PORT 9444
+
+static const int MAX_LOOP_ITERATIONS = 10000;
+
+#define SPIN_WHILE(condition) \
+ { \
+ bool timed_out = false; \
+ timeout_timer.data = &timed_out; \
+ uv_timer_start(&timeout_timer, set_timeout_flag, 5000, 0); \
+ while (((condition)) && !timed_out) { \
+ uv_run(&loop, UV_RUN_NOWAIT); \
+ } \
+ ASSERT_FALSE((condition)); \
+ uv_timer_stop(&timeout_timer); \
+ }
+
+static uv_timer_t timeout_timer;
+static bool connected = false;
+static bool inspector_ready = false;
+static int handshake_events = 0;
+static enum inspector_handshake_event last_event = kInspectorHandshakeHttpGet;
+static uv_loop_t loop;
+static uv_tcp_t server, client_socket;
+static inspector_socket_t inspector;
+static char last_path[100];
+static void (*handshake_delegate)(enum inspector_handshake_event state,
+ const char* path, bool* should_continue);
+
+struct read_expects {
+ const char* expected;
+ size_t expected_len;
+ size_t pos;
+ bool read_expected;
+ bool callback_called;
+};
+
+static const char HANDSHAKE_REQ[] = "GET /ws/path HTTP/1.1\r\n"
+ "Host: localhost:9222\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: aaa==\r\n"
+ "Sec-WebSocket-Version: 13\r\n\r\n";
+
+static void set_timeout_flag(uv_timer_t* timer) {
+ *(static_cast<bool*>(timer->data)) = true;
+}
+
+static void stop_if_stop_path(enum inspector_handshake_event state,
+ const char* path, bool* cont) {
+ *cont = path == nullptr || strcmp(path, "/close") != 0;
+}
+
+static bool connected_cb(inspector_socket_t* socket,
+ enum inspector_handshake_event state,
+ const char* path) {
+ inspector_ready = state == kInspectorHandshakeUpgraded;
+ last_event = state;
+ if (!path) {
+ strcpy(last_path, "@@@ Nothing Recieved @@@");
+ } else {
+ strncpy(last_path, path, sizeof(last_path) - 1);
+ }
+ handshake_events++;
+ bool should_continue = true;
+ handshake_delegate(state, path, &should_continue);
+ return should_continue;
+}
+
+static void on_new_connection(uv_stream_t* server, int status) {
+ GTEST_ASSERT_EQ(0, status);
+ connected = true;
+ inspector_accept(server, reinterpret_cast<inspector_socket_t*>(server->data),
+ connected_cb);
+}
+
+void write_done(uv_write_t* req, int status) { req->data = nullptr; }
+
+static void do_write(const char* data, int len) {
+ uv_write_t req;
+ bool done = false;
+ req.data = &done;
+ uv_buf_t buf[1];
+ buf[0].base = const_cast<char *>(data);
+ buf[0].len = len;
+ uv_write(&req, reinterpret_cast<uv_stream_t *>(&client_socket), buf, 1,
+ write_done);
+ SPIN_WHILE(req.data);
+}
+
+static void buffer_alloc_cb(uv_handle_t* stream, size_t len, uv_buf_t* buf) {
+ buf->base = static_cast<char *>(malloc(len));
+ buf->len = len;
+}
+
+static void check_data_cb(read_expects* expectation, ssize_t nread,
+ const uv_buf_t* buf, bool* retval) {
+ *retval = false;
+ EXPECT_TRUE(nread >= 0 && nread != UV_EOF);
+ ssize_t i;
+ char c, actual;
+ ASSERT_TRUE(expectation->expected_len > 0);
+ for (i = 0; i < nread && expectation->pos <= expectation->expected_len; i++) {
+ c = expectation->expected[expectation->pos++];
+ actual = buf->base[i];
+ if (c != actual) {
+ fprintf(stderr, "Unexpected character at position %ld\n",
+ expectation->pos - 1);
+ GTEST_ASSERT_EQ(c, actual);
+ }
+ }
+ GTEST_ASSERT_EQ(i, nread);
+ free(buf->base);
+ if (expectation->pos == expectation->expected_len) {
+ expectation->read_expected = true;
+ *retval = true;
+ }
+}
+
+static void check_data_cb(uv_stream_t* stream, ssize_t nread,
+ const uv_buf_t* buf) {
+ bool retval = false;
+ read_expects* expects = static_cast<read_expects *>(stream->data);
+ expects->callback_called = true;
+ check_data_cb(expects, nread, buf, &retval);
+ if (retval) {
+ stream->data = nullptr;
+ uv_read_stop(stream);
+ }
+}
+
+static read_expects prepare_expects(const char* data, size_t len) {
+ read_expects expectation;
+ expectation.expected = data;
+ expectation.expected_len = len;
+ expectation.pos = 0;
+ expectation.read_expected = false;
+ expectation.callback_called = false;
+ return expectation;
+}
+
+static void fail_callback(uv_stream_t* stream, ssize_t nread,
+ const uv_buf_t* buf) {
+ if (nread < 0) {
+ fprintf(stderr, "IO error: %s\n", uv_strerror(nread));
+ } else {
+ fprintf(stderr, "Read %ld bytes\n", nread);
+ }
+ ASSERT_TRUE(false); // Shouldn't have been called
+}
+
+static void expect_nothing_on_client() {
+ int err = uv_read_start(reinterpret_cast<uv_stream_t *>(&client_socket),
+ buffer_alloc_cb, fail_callback);
+ GTEST_ASSERT_EQ(0, err);
+ for (int i = 0; i < MAX_LOOP_ITERATIONS; i++)
+ uv_run(&loop, UV_RUN_NOWAIT);
+}
+
+static void expect_on_client(const char* data, size_t len) {
+ read_expects expectation = prepare_expects(data, len);
+ client_socket.data = &expectation;
+ uv_read_start(reinterpret_cast<uv_stream_t *>(&client_socket),
+ buffer_alloc_cb, check_data_cb);
+ SPIN_WHILE(!expectation.read_expected);
+}
+
+struct expectations {
+ char* actual_data;
+ size_t actual_offset;
+ size_t actual_end;
+ int err_code;
+};
+
+static void grow_expects_buffer(uv_handle_t* stream, size_t size, uv_buf_t* b) {
+ expectations* expects = static_cast<expectations*>(
+ (static_cast<inspector_socket_t*>(stream->data))->data);
+ size_t end = expects->actual_end;
+ // Grow the buffer in chunks of 64k.
+ size_t new_length = (end + size + 65535) & ~((size_t) 0xFFFF);
+ expects->actual_data =
+ static_cast<char*>(realloc(expects->actual_data, new_length));
+ *b = uv_buf_init(expects->actual_data + end, new_length - end);
+}
+
+// static void dump_hex(const char* buf, size_t len) {
+// const char* ptr = buf;
+// const char* end = ptr + len;
+// const char* cptr;
+// char c;
+// int i;
+
+// while (ptr < end) {
+// cptr = ptr;
+// for (i = 0; i < 16 && ptr < end; i++) {
+// printf("%2.2X ", *(ptr++));
+// }
+// for (i = 72 - (i * 4); i > 0; i--) {
+// printf(" ");
+// }
+// for (i = 0; i < 16 && cptr < end; i++) {
+// c = *(cptr++);
+// printf("%c", (c > 0x19) ? c : '.');
+// }
+// printf("\n");
+// }
+// printf("\n\n");
+// }
+
+static void save_read_data(uv_stream_t* stream, ssize_t nread,
+ const uv_buf_t* buf) {
+ expectations* expects =static_cast<expectations*>(
+ (static_cast<inspector_socket_t*>(stream->data))->data);
+ expects->err_code = nread < 0 ? nread : 0;
+ if (nread > 0) {
+ expects->actual_end += nread;
+ }
+}
+
+static void setup_inspector_expecting() {
+ if (inspector.data) {
+ return;
+ }
+ expectations* expects = static_cast<expectations*>(malloc(sizeof(*expects)));
+ memset(expects, 0, sizeof(*expects));
+ inspector.data = expects;
+ inspector_read_start(&inspector, grow_expects_buffer, save_read_data);
+}
+
+static void expect_on_server(const char* data, size_t len) {
+ setup_inspector_expecting();
+ expectations* expects = static_cast<expectations*>(inspector.data);
+ for (size_t i = 0; i < len;) {
+ SPIN_WHILE(expects->actual_offset == expects->actual_end);
+ for (; i < len && expects->actual_offset < expects->actual_end; i++) {
+ char actual = expects->actual_data[expects->actual_offset++];
+ char expected = data[i];
+ if (expected != actual) {
+ fprintf(stderr, "Character %ld:\n", i);
+ GTEST_ASSERT_EQ(expected, actual);
+ }
+ }
+ }
+ expects->actual_end -= expects->actual_offset;
+ if (!expects->actual_end) {
+ memmove(expects->actual_data,
+ expects->actual_data + expects->actual_offset,
+ expects->actual_end);
+ }
+ expects->actual_offset = 0;
+}
+
+static void inspector_record_error_code(uv_stream_t* stream, ssize_t nread,
+ const uv_buf_t* buf) {
+ inspector_socket_t *inspector =
+ reinterpret_cast<inspector_socket_t*>(stream->data);
+ // Increment instead of assign is to ensure the function is only called once
+ *(static_cast<int *>(inspector->data)) += nread;
+}
+
+static void expect_server_read_error() {
+ setup_inspector_expecting();
+ expectations* expects = static_cast<expectations*>(inspector.data);
+ SPIN_WHILE(expects->err_code != UV_EPROTO);
+}
+
+static void expect_handshake() {
+ const char UPGRADE_RESPONSE[] =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: Dt87H1OULVZnSJo/KgMUYI7xPCg=\r\n\r\n";
+ expect_on_client(UPGRADE_RESPONSE, sizeof(UPGRADE_RESPONSE) - 1);
+}
+
+static void expect_handshake_failure() {
+ const char UPGRADE_RESPONSE[] =
+ "HTTP/1.0 400 Bad Request\r\n"
+ "Content-Type: text/html; charset=UTF-8\r\n\r\n"
+ "WebSockets request was expected\r\n";
+ expect_on_client(UPGRADE_RESPONSE, sizeof(UPGRADE_RESPONSE) - 1);
+}
+
+static bool waiting_to_close = true;
+
+void handle_closed(uv_handle_t* handle) { waiting_to_close = false; }
+
+static void really_close(uv_tcp_t* socket) {
+ waiting_to_close = true;
+ if (!uv_is_closing(reinterpret_cast<uv_handle_t*>(socket))) {
+ uv_close(reinterpret_cast<uv_handle_t*>(socket), handle_closed);
+ SPIN_WHILE(waiting_to_close);
+ }
+}
+
+// Called when the test leaves inspector socket in active state
+static void manual_inspector_socket_cleanup() {
+ EXPECT_EQ(0, uv_is_active(
+ reinterpret_cast<uv_handle_t*>(&inspector.client)));
+ free(inspector.ws_state);
+ free(inspector.http_parsing_state);
+ free(inspector.buffer);
+ inspector.buffer = nullptr;
+}
+
+static void on_connection(uv_connect_t* connect, int status) {
+ GTEST_ASSERT_EQ(0, status);
+ connect->data = connect;
+}
+
+class InspectorSocketTest : public ::testing::Test {
+protected:
+ virtual void SetUp() {
+ handshake_delegate = stop_if_stop_path;
+ handshake_events = 0;
+ connected = false;
+ inspector_ready = false;
+ last_event = kInspectorHandshakeHttpGet;
+ uv_loop_init(&loop);
+ memset(&inspector, 0, sizeof(inspector));
+ memset(&server, 0, sizeof(server));
+ memset(&client_socket, 0, sizeof(client_socket));
+ server.data = &inspector;
+ sockaddr_in addr;
+ uv_timer_init(&loop, &timeout_timer);
+ uv_tcp_init(&loop, &server);
+ uv_tcp_init(&loop, &client_socket);
+ uv_ip4_addr("localhost", PORT, &addr);
+ uv_tcp_bind(&server, reinterpret_cast<const struct sockaddr *>(&addr), 0);
+ int err = uv_listen(reinterpret_cast<uv_stream_t *>(&server),
+ 0, on_new_connection);
+ GTEST_ASSERT_EQ(0, err);
+ uv_connect_t connect;
+ connect.data = nullptr;
+ uv_tcp_connect(&connect, &client_socket,
+ reinterpret_cast<const sockaddr *>(&addr), on_connection);
+ uv_tcp_nodelay(&client_socket, 1); // The buffering messes up the test
+ SPIN_WHILE(!connect.data || !connected);
+ really_close(&server);
+ uv_unref(reinterpret_cast<uv_handle_t*>(&server));
+ }
+
+ virtual void TearDown() {
+ really_close(&client_socket);
+ for (int i = 0; i < MAX_LOOP_ITERATIONS; i++)
+ uv_run(&loop, UV_RUN_NOWAIT);
+ EXPECT_EQ(nullptr, inspector.buffer);
+ uv_stop(&loop);
+ int err = uv_run(&loop, UV_RUN_ONCE);
+ if (err != 0) {
+ uv_print_active_handles(&loop, stderr);
+ }
+ EXPECT_EQ(0, err);
+ expectations* expects = static_cast<expectations*>(inspector.data);
+ if (expects != nullptr) {
+ GTEST_ASSERT_EQ(expects->actual_end, expects->actual_offset);
+ free(expects->actual_data);
+ expects->actual_data = nullptr;
+ free(expects);
+ inspector.data = nullptr;
+ }
+ uv_loop_close(&loop);
+ }
+};
+
+TEST_F(InspectorSocketTest, ReadsAndWritesInspectorMessage) {
+ ASSERT_TRUE(connected);
+ ASSERT_FALSE(inspector_ready);
+ do_write(const_cast<char *>(HANDSHAKE_REQ), sizeof(HANDSHAKE_REQ) - 1);
+ SPIN_WHILE(!inspector_ready);
+ expect_handshake();
+
+ // 2. Brief exchange
+ const char SERVER_MESSAGE[] = "abcd";
+ const char CLIENT_FRAME[] = {'\x81', '\x04', 'a', 'b', 'c', 'd'};
+ inspector_write(&inspector, SERVER_MESSAGE, sizeof(SERVER_MESSAGE) - 1);
+ expect_on_client(CLIENT_FRAME, sizeof(CLIENT_FRAME));
+
+ const char SERVER_FRAME[] = {'\x81', '\x84', '\x7F', '\xC2', '\x66',
+ '\x31', '\x4E', '\xF0', '\x55', '\x05'};
+ const char CLIENT_MESSAGE[] = "1234";
+ do_write(SERVER_FRAME, sizeof(SERVER_FRAME));
+ expect_on_server(CLIENT_MESSAGE, sizeof(CLIENT_MESSAGE) - 1);
+
+ // 3. Close
+ const char CLIENT_CLOSE_FRAME[] = {'\x88', '\x80', '\x2D',
+ '\x0E', '\x1E', '\xFA'};
+ const char SERVER_CLOSE_FRAME[] = {'\x88', '\x00'};
+ do_write(CLIENT_CLOSE_FRAME, sizeof(CLIENT_CLOSE_FRAME));
+ expect_on_client(SERVER_CLOSE_FRAME, sizeof(SERVER_CLOSE_FRAME));
+ GTEST_ASSERT_EQ(0, uv_is_active(
+ reinterpret_cast<uv_handle_t*>(&client_socket)));
+}
+
+TEST_F(InspectorSocketTest, BufferEdgeCases) {
+
+ do_write(const_cast<char *>(HANDSHAKE_REQ), sizeof(HANDSHAKE_REQ) - 1);
+ expect_handshake();
+
+ const char MULTIPLE_REQUESTS[] = {
+ '\x81', '\xCB', '\x76', '\xCA', '\x06', '\x0C', '\x0D', '\xE8', '\x6F',
+ '\x68', '\x54', '\xF0', '\x37', '\x3E', '\x5A', '\xE8', '\x6B', '\x69',
+ '\x02', '\xA2', '\x69', '\x68', '\x54', '\xF0', '\x24', '\x5B', '\x19',
+ '\xB8', '\x6D', '\x69', '\x04', '\xE4', '\x75', '\x69', '\x02', '\x8B',
+ '\x73', '\x78', '\x19', '\xA9', '\x69', '\x62', '\x18', '\xAF', '\x65',
+ '\x78', '\x22', '\xA5', '\x51', '\x63', '\x04', '\xA1', '\x63', '\x7E',
+ '\x05', '\xE8', '\x2A', '\x2E', '\x06', '\xAB', '\x74', '\x6D', '\x1B',
+ '\xB9', '\x24', '\x36', '\x0D', '\xE8', '\x70', '\x6D', '\x1A', '\xBF',
+ '\x63', '\x2E', '\x4C', '\xBE', '\x74', '\x79', '\x13', '\xB7', '\x7B',
+ '\x81', '\xA2', '\xFC', '\x9E', '\x0D', '\x15', '\x87', '\xBC', '\x64',
+ '\x71', '\xDE', '\xA4', '\x3C', '\x26', '\xD0', '\xBC', '\x60', '\x70',
+ '\x88', '\xF6', '\x62', '\x71', '\xDE', '\xA4', '\x2F', '\x42', '\x93',
+ '\xEC', '\x66', '\x70', '\x8E', '\xB0', '\x68', '\x7B', '\x9D', '\xFC',
+ '\x61', '\x70', '\xDE', '\xE3', '\x81', '\xA4', '\x4E', '\x37', '\xB0',
+ '\x22', '\x35', '\x15', '\xD9', '\x46', '\x6C', '\x0D', '\x81', '\x16',
+ '\x62', '\x15', '\xDD', '\x47', '\x3A', '\x5F', '\xDF', '\x46', '\x6C',
+ '\x0D', '\x92', '\x72', '\x3C', '\x58', '\xD6', '\x4B', '\x22', '\x52',
+ '\xC2', '\x0C', '\x2B', '\x59', '\xD1', '\x40', '\x22', '\x52', '\x92',
+ '\x5F', '\x81', '\xCB', '\xCD', '\xF0', '\x30', '\xC5', '\xB6', '\xD2',
+ '\x59', '\xA1', '\xEF', '\xCA', '\x01', '\xF0', '\xE1', '\xD2', '\x5D',
+ '\xA0', '\xB9', '\x98', '\x5F', '\xA1', '\xEF', '\xCA', '\x12', '\x95',
+ '\xBF', '\x9F', '\x56', '\xAC', '\xA1', '\x95', '\x42', '\xEB', '\xBE',
+ '\x95', '\x44', '\x96', '\xAC', '\x9D', '\x40', '\xA9', '\xA4', '\x9E',
+ '\x57', '\x8C', '\xA3', '\x84', '\x55', '\xB7', '\xBB', '\x91', '\x5C',
+ '\xE7', '\xE1', '\xD2', '\x40', '\xA4', '\xBF', '\x91', '\x5D', '\xB6',
+ '\xEF', '\xCA', '\x4B', '\xE7', '\xA4', '\x9E', '\x44', '\xA0', '\xBF',
+ '\x86', '\x51', '\xA9', '\xEF', '\xCA', '\x01', '\xF5', '\xFD', '\x8D',
+ '\x4D', '\x81', '\xA9', '\x74', '\x6B', '\x72', '\x43', '\x0F', '\x49',
+ '\x1B', '\x27', '\x56', '\x51', '\x43', '\x75', '\x58', '\x49', '\x1F',
+ '\x26', '\x00', '\x03', '\x1D', '\x27', '\x56', '\x51', '\x50', '\x10',
+ '\x11', '\x19', '\x04', '\x2A', '\x17', '\x0E', '\x25', '\x2C', '\x06',
+ '\x00', '\x17', '\x31', '\x5A', '\x0E', '\x1C', '\x22', '\x16', '\x07',
+ '\x17', '\x61', '\x09', '\x81', '\xB8', '\x7C', '\x1A', '\xEA', '\xEB',
+ '\x07', '\x38', '\x83', '\x8F', '\x5E', '\x20', '\xDB', '\xDC', '\x50',
+ '\x38', '\x87', '\x8E', '\x08', '\x72', '\x85', '\x8F', '\x5E', '\x20',
+ '\xC8', '\xA5', '\x19', '\x6E', '\x9D', '\x84', '\x0E', '\x71', '\xC4',
+ '\x88', '\x1D', '\x74', '\xAF', '\x86', '\x09', '\x76', '\x8B', '\x9F',
+ '\x19', '\x54', '\x8F', '\x9F', '\x0B', '\x75', '\x98', '\x80', '\x3F',
+ '\x75', '\x84', '\x8F', '\x15', '\x6E', '\x83', '\x84', '\x12', '\x69',
+ '\xC8', '\x96'};
+
+ const char EXPECT[] = {
+ "{\"id\":12,\"method\":\"Worker.setAutoconnectToWorkers\","
+ "\"params\":{\"value\":true}}"
+ "{\"id\":13,\"method\":\"Worker.enable\"}"
+ "{\"id\":14,\"method\":\"Profiler.enable\"}"
+ "{\"id\":15,\"method\":\"Profiler.setSamplingInterval\","
+ "\"params\":{\"interval\":100}}"
+ "{\"id\":16,\"method\":\"ServiceWorker.enable\"}"
+ "{\"id\":17,\"method\":\"Network.canEmulateNetworkConditions\"}"};
+
+ do_write(MULTIPLE_REQUESTS, sizeof(MULTIPLE_REQUESTS));
+ expect_on_server(EXPECT, sizeof(EXPECT) - 1);
+ inspector_read_stop(&inspector);
+ manual_inspector_socket_cleanup();
+}
+
+TEST_F(InspectorSocketTest, AcceptsRequestInSeveralWrites) {
+ ASSERT_TRUE(connected);
+ ASSERT_FALSE(inspector_ready);
+ // Specifically, break up the request in the "Sec-WebSocket-Key" header name
+ // and value
+ const int write1 = 95;
+ const int write2 = 5;
+ const int write3 = sizeof(HANDSHAKE_REQ) - write1 - write2 - 1;
+ do_write(const_cast<char *>(HANDSHAKE_REQ), write1);
+ ASSERT_FALSE(inspector_ready);
+ do_write(const_cast<char *>(HANDSHAKE_REQ) + write1, write2);
+ ASSERT_FALSE(inspector_ready);
+ do_write(const_cast<char *>(HANDSHAKE_REQ) + write1 + write2, write3);
+ SPIN_WHILE(!inspector_ready);
+ expect_handshake();
+ inspector_read_stop(&inspector);
+ GTEST_ASSERT_EQ(uv_is_active(reinterpret_cast<uv_handle_t*>(&client_socket)), 0);
+ manual_inspector_socket_cleanup();
+}
+
+TEST_F(InspectorSocketTest, ExtraTextBeforeRequest) {
+ last_event = kInspectorHandshakeUpgraded;
+ char UNCOOL_BRO[] = "Uncool, bro: Text before the first req\r\n";
+ do_write(const_cast<char *>(UNCOOL_BRO), sizeof(UNCOOL_BRO) - 1);
+
+ ASSERT_FALSE(inspector_ready);
+ do_write(const_cast<char *>(HANDSHAKE_REQ), sizeof(HANDSHAKE_REQ) - 1);
+ SPIN_WHILE(last_event != kInspectorHandshakeFailed);
+ expect_handshake_failure();
+ EXPECT_EQ(uv_is_active(reinterpret_cast<uv_handle_t*>(&client_socket)), 0);
+ EXPECT_EQ(uv_is_active(reinterpret_cast<uv_handle_t*>(&socket)), 0);
+}
+
+TEST_F(InspectorSocketTest, ExtraLettersBeforeRequest) {
+ char UNCOOL_BRO[] = "Uncool!!";
+ do_write(const_cast<char *>(UNCOOL_BRO), sizeof(UNCOOL_BRO) - 1);
+
+ ASSERT_FALSE(inspector_ready);
+ do_write(const_cast<char *>(HANDSHAKE_REQ), sizeof(HANDSHAKE_REQ) - 1);
+ SPIN_WHILE(last_event != kInspectorHandshakeFailed);
+ expect_handshake_failure();
+ EXPECT_EQ(uv_is_active(reinterpret_cast<uv_handle_t*>(&client_socket)), 0);
+ EXPECT_EQ(uv_is_active(reinterpret_cast<uv_handle_t*>(&socket)), 0);
+}
+
+TEST_F(InspectorSocketTest, RequestWithoutKey) {
+ const char BROKEN_REQUEST[] = "GET / HTTP/1.1\r\n"
+ "Host: localhost:9222\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Version: 13\r\n\r\n";
+ ;
+
+ do_write(const_cast<char *>(BROKEN_REQUEST), sizeof(BROKEN_REQUEST) - 1);
+ SPIN_WHILE(last_event != kInspectorHandshakeFailed);
+ expect_handshake_failure();
+ EXPECT_EQ(uv_is_active(reinterpret_cast<uv_handle_t*>(&client_socket)), 0);
+ EXPECT_EQ(uv_is_active(reinterpret_cast<uv_handle_t*>(&socket)), 0);
+}
+
+TEST_F(InspectorSocketTest, KillsConnectionOnProtocolViolation) {
+ ASSERT_TRUE(connected);
+ ASSERT_FALSE(inspector_ready);
+ do_write(const_cast<char *>(HANDSHAKE_REQ), sizeof(HANDSHAKE_REQ) - 1);
+ SPIN_WHILE(!inspector_ready);
+ ASSERT_TRUE(inspector_ready);
+ expect_handshake();
+ const char SERVER_FRAME[] = "I'm not a good WS frame. Nope!";
+ do_write(SERVER_FRAME, sizeof(SERVER_FRAME));
+ expect_server_read_error();
+ GTEST_ASSERT_EQ(uv_is_active(reinterpret_cast<uv_handle_t*>(&client_socket)), 0);
+}
+
+TEST_F(InspectorSocketTest, CanStopReadingFromInspector) {
+ ASSERT_TRUE(connected);
+ ASSERT_FALSE(inspector_ready);
+ do_write(const_cast<char *>(HANDSHAKE_REQ), sizeof(HANDSHAKE_REQ) - 1);
+ expect_handshake();
+ ASSERT_TRUE(inspector_ready);
+
+ // 2. Brief exchange
+ const char SERVER_FRAME[] = {'\x81', '\x84', '\x7F', '\xC2', '\x66',
+ '\x31', '\x4E', '\xF0', '\x55', '\x05'};
+ const char CLIENT_MESSAGE[] = "1234";
+ do_write(SERVER_FRAME, sizeof(SERVER_FRAME));
+ expect_on_server(CLIENT_MESSAGE, sizeof(CLIENT_MESSAGE) - 1);
+
+ inspector_read_stop(&inspector);
+ do_write(SERVER_FRAME, sizeof(SERVER_FRAME));
+ GTEST_ASSERT_EQ(uv_is_active(
+ reinterpret_cast<uv_handle_t*>(&client_socket)), 0);
+ manual_inspector_socket_cleanup();
+}
+
+static bool inspector_closed;
+
+void inspector_closed_cb(inspector_socket_t *inspector, int code) {
+ inspector_closed = true;
+}
+
+TEST_F(InspectorSocketTest, CloseDoesNotNotifyReadCallback) {
+ inspector_closed = false;
+ do_write(const_cast<char *>(HANDSHAKE_REQ), sizeof(HANDSHAKE_REQ) - 1);
+ expect_handshake();
+
+ int error_code = 0;
+ inspector.data = &error_code;
+ inspector_read_start(&inspector, buffer_alloc_cb,
+ inspector_record_error_code);
+ inspector_close(&inspector, inspector_closed_cb);
+ char CLOSE_FRAME[] = {'\x88', '\x00'};
+ expect_on_client(CLOSE_FRAME, sizeof(CLOSE_FRAME));
+ ASSERT_FALSE(inspector_closed);
+ const char CLIENT_CLOSE_FRAME[] = {'\x88', '\x80', '\x2D',
+ '\x0E', '\x1E', '\xFA'};
+ do_write(CLIENT_CLOSE_FRAME, sizeof(CLIENT_CLOSE_FRAME));
+ EXPECT_NE(UV_EOF, error_code);
+ SPIN_WHILE(!inspector_closed);
+ inspector.data = nullptr;
+}
+
+TEST_F(InspectorSocketTest, CloseWorksWithoutReadEnabled) {
+ inspector_closed = false;
+ do_write(const_cast<char *>(HANDSHAKE_REQ), sizeof(HANDSHAKE_REQ) - 1);
+ expect_handshake();
+ inspector_close(&inspector, inspector_closed_cb);
+ char CLOSE_FRAME[] = {'\x88', '\x00'};
+ expect_on_client(CLOSE_FRAME, sizeof(CLOSE_FRAME));
+ ASSERT_FALSE(inspector_closed);
+ const char CLIENT_CLOSE_FRAME[] = {'\x88', '\x80', '\x2D',
+ '\x0E', '\x1E', '\xFA'};
+ do_write(CLIENT_CLOSE_FRAME, sizeof(CLIENT_CLOSE_FRAME));
+ SPIN_WHILE(!inspector_closed);
+}
+
+// Make sure buffering works
+static void send_in_chunks(const char* data, size_t len) {
+ const int step = 7;
+ size_t i = 0;
+ // Do not send it all at once - test the buffering!
+ for (; i < len - step; i += step) {
+ do_write(data + i, step);
+ }
+ if (i < len) {
+ do_write(data + i, len - i);
+ }
+}
+
+static const char TEST_SUCCESS[] = "Test Success\n\n";
+
+static void ReportsHttpGet_handshake(enum inspector_handshake_event state,
+ const char* path, bool* cont) {
+ *cont = true;
+ enum inspector_handshake_event expected_state = kInspectorHandshakeHttpGet;
+ const char* expected_path;
+ switch (handshake_events) {
+ case 1:
+ expected_path = "/some/path";
+ break;
+ case 2:
+ expected_path = "/respond/withtext";
+ inspector_write(&inspector, TEST_SUCCESS, sizeof(TEST_SUCCESS) - 1);
+ break;
+ case 3:
+ expected_path = "/some/path2";
+ break;
+ case 5:
+ expected_state = kInspectorHandshakeFailed;
+ case 4:
+ expected_path = "/close";
+ *cont = false;
+ break;
+ default:
+ expected_path = nullptr;
+ ASSERT_TRUE(false);
+ }
+ EXPECT_EQ(expected_state, state);
+ EXPECT_STREQ(expected_path, path);
+}
+
+TEST_F(InspectorSocketTest, ReportsHttpGet) {
+ handshake_delegate = ReportsHttpGet_handshake;
+
+ const char GET_REQ[] = "GET /some/path HTTP/1.1\r\n"
+ "Host: localhost:9222\r\n"
+ "Sec-WebSocket-Key: aaa==\r\n"
+ "Sec-WebSocket-Version: 13\r\n\r\n";
+ send_in_chunks(GET_REQ, sizeof(GET_REQ) - 1);
+
+ expect_nothing_on_client();
+
+ const char WRITE_REQUEST[] = "GET /respond/withtext HTTP/1.1\r\n"
+ "Host: localhost:9222\r\n\r\n";
+ send_in_chunks(WRITE_REQUEST, sizeof(WRITE_REQUEST) - 1);
+
+ expect_on_client(TEST_SUCCESS, sizeof(TEST_SUCCESS) - 1);
+
+ const char GET_REQS[] = "GET /some/path2 HTTP/1.1\r\n"
+ "Host: localhost:9222\r\n"
+ "Sec-WebSocket-Key: aaa==\r\n"
+ "Sec-WebSocket-Version: 13\r\n\r\n"
+ "GET /close HTTP/1.1\r\n"
+ "Host: localhost:9222\r\n"
+ "Sec-WebSocket-Key: aaa==\r\n"
+ "Sec-WebSocket-Version: 13\r\n\r\n";
+ send_in_chunks(GET_REQS, sizeof(GET_REQS) - 1);
+
+ expect_handshake_failure();
+ EXPECT_EQ(5, handshake_events);
+}
+
+static void
+HandshakeCanBeCanceled_handshake(enum inspector_handshake_event state,
+ const char* path, bool* cont) {
+ switch (handshake_events - 1) {
+ case 0:
+ EXPECT_EQ(kInspectorHandshakeUpgrading, state);
+ break;
+ case 1:
+ EXPECT_EQ(kInspectorHandshakeFailed, state);
+ break;
+ default:
+ EXPECT_TRUE(false);
+ break;
+ }
+ EXPECT_STREQ("/ws/path", path);
+ *cont = false;
+}
+
+TEST_F(InspectorSocketTest, HandshakeCanBeCanceled) {
+ handshake_delegate = HandshakeCanBeCanceled_handshake;
+
+ do_write(const_cast<char *>(HANDSHAKE_REQ), sizeof(HANDSHAKE_REQ) - 1);
+
+ expect_handshake_failure();
+ EXPECT_EQ(2, handshake_events);
+}
+
+static void GetThenHandshake_handshake(enum inspector_handshake_event state,
+ const char* path, bool* cont) {
+ *cont = true;
+ const char* expected_path = "/ws/path";
+ switch (handshake_events - 1) {
+ case 0:
+ EXPECT_EQ(kInspectorHandshakeHttpGet, state);
+ expected_path = "/respond/withtext";
+ inspector_write(&inspector, TEST_SUCCESS, sizeof(TEST_SUCCESS) - 1);
+ break;
+ case 1:
+ EXPECT_EQ(kInspectorHandshakeUpgrading, state);
+ break;
+ case 2:
+ EXPECT_EQ(kInspectorHandshakeUpgraded, state);
+ break;
+ default:
+ EXPECT_TRUE(false);
+ break;
+ }
+ EXPECT_STREQ(expected_path, path);
+}
+
+TEST_F(InspectorSocketTest, GetThenHandshake) {
+ handshake_delegate = GetThenHandshake_handshake;
+ const char WRITE_REQUEST[] = "GET /respond/withtext HTTP/1.1\r\n"
+ "Host: localhost:9222\r\n\r\n";
+ send_in_chunks(WRITE_REQUEST, sizeof(WRITE_REQUEST) - 1);
+
+ expect_on_client(TEST_SUCCESS, sizeof(TEST_SUCCESS) - 1);
+
+ do_write(const_cast<char *>(HANDSHAKE_REQ), sizeof(HANDSHAKE_REQ) - 1);
+ expect_handshake();
+ EXPECT_EQ(3, handshake_events);
+ manual_inspector_socket_cleanup();
+}
+
+static void WriteBeforeHandshake_close_cb(uv_handle_t* handle) {
+ *(static_cast<bool *>(handle->data)) = true;
+}
+
+TEST_F(InspectorSocketTest, WriteBeforeHandshake) {
+ const char MESSAGE1[] = "Message 1";
+ const char MESSAGE2[] = "Message 2";
+ const char EXPECTED[] = "Message 1Message 2";
+
+ inspector_write(&inspector, MESSAGE1, sizeof(MESSAGE1) - 1);
+ inspector_write(&inspector, MESSAGE2, sizeof(MESSAGE2) - 1);
+ expect_on_client(EXPECTED, sizeof(EXPECTED) - 1);
+ bool flag = false;
+ client_socket.data = &flag;
+ uv_close(reinterpret_cast<uv_handle_t*>(&client_socket),
+ WriteBeforeHandshake_close_cb);
+ SPIN_WHILE(!flag);
+}
+
+static void CleanupSocketAfterEOF_close_cb(inspector_socket_t* inspector,
+ int status) {
+ *(static_cast<bool *>(inspector->data)) = true;
+}
+
+static void CleanupSocketAfterEOF_read_cb(uv_stream_t* stream, ssize_t nread,
+ const uv_buf_t* buf) {
+ EXPECT_EQ(UV_EOF, nread);
+ inspector_socket_t* insp =
+ reinterpret_cast<inspector_socket_t*>(stream->data);
+ inspector_close(insp, CleanupSocketAfterEOF_close_cb);
+}
+
+TEST_F(InspectorSocketTest, CleanupSocketAfterEOF) {
+ do_write(const_cast<char *>(HANDSHAKE_REQ), sizeof(HANDSHAKE_REQ) - 1);
+ expect_handshake();
+
+ inspector_read_start(&inspector, buffer_alloc_cb,
+ CleanupSocketAfterEOF_read_cb);
+
+ for (int i = 0; i < MAX_LOOP_ITERATIONS; ++i) {
+ uv_run(&loop, UV_RUN_NOWAIT);
+ }
+
+ uv_close(reinterpret_cast<uv_handle_t*>(&client_socket), nullptr);
+ bool flag = false;
+ inspector.data = &flag;
+ SPIN_WHILE(!flag);
+ inspector.data = nullptr;
+}
+
+TEST_F(InspectorSocketTest, EOFBeforeHandshake) {
+ const char MESSAGE[] = "We'll send EOF afterwards";
+ inspector_write(&inspector, MESSAGE, sizeof(MESSAGE) - 1);
+ expect_on_client(MESSAGE, sizeof(MESSAGE) - 1);
+ uv_close(reinterpret_cast<uv_handle_t*>(&client_socket), nullptr);
+ SPIN_WHILE(last_event != kInspectorHandshakeFailed);
+}
+
+static void fill_message(char* buffer, size_t len) {
+ buffer[len - 1] = '\0';
+ for (size_t i = 0; i < len - 1; i++) {
+ buffer[i] = 'a' + (i % ('z' - 'a'));
+ }
+}
+
+static void mask_message(const char* message,
+ char* buffer, const char mask[]) {
+ const size_t mask_len = 4;
+ int i = 0;
+ while (*message != '\0') {
+ *buffer++ = *message++ ^ mask[i++ % mask_len];
+ }
+}
+
+TEST_F(InspectorSocketTest, Send1Mb) {
+ ASSERT_TRUE(connected);
+ ASSERT_FALSE(inspector_ready);
+ do_write(const_cast<char *>(HANDSHAKE_REQ), sizeof(HANDSHAKE_REQ) - 1);
+ SPIN_WHILE(!inspector_ready);
+ expect_handshake();
+
+ const size_t message_len = 1000000;
+
+ // 2. Brief exchange
+ char* message = static_cast<char*>(malloc(message_len + 1));
+ fill_message(message, message_len + 1);
+
+ // 1000000 is 0xF4240 hex
+ const char EXPECTED_FRAME_HEADER[] = {
+ '\x81', '\x7f', '\x00', '\x00', '\x00', '\x00', '\x00', '\x0F',
+ '\x42', '\x40'
+ };
+ char* expected =
+ static_cast<char*>(malloc(sizeof(EXPECTED_FRAME_HEADER) + message_len));
+
+ memcpy(expected, EXPECTED_FRAME_HEADER, sizeof(EXPECTED_FRAME_HEADER));
+ memcpy(expected + sizeof(EXPECTED_FRAME_HEADER), message, message_len);
+
+ inspector_write(&inspector, message, message_len);
+ expect_on_client(expected, sizeof(EXPECTED_FRAME_HEADER) + message_len);
+
+ char MASK[4] = {'W', 'h', 'O', 'a'};
+
+ const char FRAME_TO_SERVER_HEADER[] = {
+ '\x81', '\xff', '\x00', '\x00', '\x00', '\x00', '\x00', '\x0F',
+ '\x42', '\x40', MASK[0], MASK[1], MASK[2], MASK[3]
+ };
+
+ const size_t outgoing_len = sizeof(FRAME_TO_SERVER_HEADER) + message_len;
+ char* outgoing = static_cast<char*>(malloc(outgoing_len));
+ memcpy(outgoing, FRAME_TO_SERVER_HEADER, sizeof(FRAME_TO_SERVER_HEADER));
+ mask_message(message, outgoing + sizeof(FRAME_TO_SERVER_HEADER), MASK);
+
+ setup_inspector_expecting(); // Buffer on the client side.
+ do_write(outgoing, outgoing_len);
+ expect_on_server(message, message_len);
+
+ // 3. Close
+ const char CLIENT_CLOSE_FRAME[] = {'\x88', '\x80', '\x2D',
+ '\x0E', '\x1E', '\xFA'};
+ const char SERVER_CLOSE_FRAME[] = {'\x88', '\x00'};
+ do_write(CLIENT_CLOSE_FRAME, sizeof(CLIENT_CLOSE_FRAME));
+ expect_on_client(SERVER_CLOSE_FRAME, sizeof(SERVER_CLOSE_FRAME));
+ GTEST_ASSERT_EQ(0, uv_is_active(
+ reinterpret_cast<uv_handle_t*>(&client_socket)));
+ free(outgoing);
+ free(expected);
+ free(message);
+}