* Copyright (c) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "inspector_socket_server.h"
#include <algorithm>
#include <cstdio>
#include <iostream>
#include <map>
#include <securec.h>
#include <set>
#include <sstream>
#include <string>
#include <unistd.h>
#include "jsvm_dfx.h"
#include "jsvm_version.h"
#include "uv.h"
#include "v8_inspector_protocol_json.h"
#include "zlib.h"
namespace jsvm {
namespace inspector {
std::string FormatWsAddress(const std::string& host, int port, const std::string& targetId, bool includeProtocol);
namespace {
std::string FormatHostPort(const std::string& host, int port)
{
bool isIPV6 = (host.find(':') != std::string::npos);
std::ostringstream url;
if (isIPV6) {
url << '[' << host << ']';
} else {
url << host;
}
url << ':' << port;
return url.str();
}
void Escape(std::string* string)
{
for (char& c : *string) {
c = (c == '\"' || c == '\\') ? '_' : c;
}
}
std::string FormatAddress(const std::string& host, const std::string& targetId, bool includeProtocol)
{
std::ostringstream url;
if (includeProtocol) {
url << "ws://";
}
url << host << '/' << targetId;
return url.str();
}
std::string MapToString(const std::map<std::string, std::string>& object)
{
bool first = true;
std::ostringstream json;
json << "{\n";
for (const auto& nameValue : object) {
if (!first) {
json << ",\n";
}
first = false;
json << " \"" << nameValue.first << "\": \"";
json << nameValue.second << "\"";
}
json << "\n} ";
return json.str();
}
std::string MapsToString(const std::vector<std::map<std::string, std::string>>& array)
{
bool isFirst = true;
std::ostringstream json;
json << "[ ";
for (const auto& object : array) {
if (!isFirst) {
json << ", ";
}
isFirst = false;
json << MapToString(object);
}
json << "]\n\n";
return json.str();
}
void SendHttpResponse(InspectorSocket* socket, const std::string& response, int code)
{
const char headers[] = "HTTP/1.0 %d OK\r\n"
"Content-Type: application/json; charset=UTF-8\r\n"
"Cache-Control: no-cache\r\n"
"Content-Length: %zu\r\n"
"\r\n";
char header[sizeof(headers) + 20];
int headerLen = snprintf_s(header, sizeof(header), sizeof(header) - 1, headers, code, response.size());
CHECK(headerLen >= 0);
socket->Write(header, headerLen);
socket->Write(response.data(), response.size());
}
const char* MatchPathSegment(const char* path, const char* expected)
{
size_t len = strlen(expected);
if (StringEqualNoCaseN(path, expected, len)) {
if (path[len] == '/') {
return path + len + 1;
}
if (path[len] == '\0') {
return path + len;
}
}
return nullptr;
}
enum HttpStatusCode : int {
HTTP_OK = 200,
HTTP_NOT_FOUND = 404,
};
void SendHttpNotFound(InspectorSocket* socket)
{
SendHttpResponse(socket, "", HttpStatusCode::HTTP_NOT_FOUND);
}
void SendVersionResponse(InspectorSocket* socket)
{
std::map<std::string, std::string> response;
response["Protocol-Version"] = "1.1";
response["Browser"] = "jsvm/" JSVM_VERSION_STRING;
SendHttpResponse(socket, MapToString(response), HttpStatusCode::HTTP_OK);
}
void SendProtocolJson(InspectorSocket* socket)
{
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
CHECK_EQ(inflateInit(&strm), Z_OK);
static const size_t decompressedSize = PROTOCOL_JSON[0] * 0x10000u + PROTOCOL_JSON[1] * 0x100u + PROTOCOL_JSON[2];
strm.next_in = const_cast<uint8_t*>(PROTOCOL_JSON + ByteOffset::BYTE_3);
strm.avail_in = sizeof(PROTOCOL_JSON) - ByteOffset::BYTE_3;
std::string data(decompressedSize, '\0');
strm.next_out = reinterpret_cast<Byte*>(data.data());
strm.avail_out = data.size();
CHECK_EQ(inflate(&strm, Z_FINISH), Z_STREAM_END);
CHECK_EQ(strm.avail_out, 0);
CHECK_EQ(inflateEnd(&strm), Z_OK);
SendHttpResponse(socket, data, HttpStatusCode::HTTP_OK);
}
}
std::string FormatWsAddress(const std::string& host, int port, const std::string& targetId, bool includeProtocol)
{
return FormatAddress(FormatHostPort(host, port), targetId, includeProtocol);
}
class SocketSession {
public:
SocketSession(InspectorSocketServer* server, int id, int serverPort);
void Close()
{
wsSocket.reset();
}
void Send(const std::string& message);
void Own(InspectorSocket::Pointer wsSocketParam)
{
wsSocket = std::move(wsSocketParam);
}
int GetId() const
{
return id;
}
int ServerPort()
{
return serverPort;
}
InspectorSocket* GetWsSocket()
{
return wsSocket.get();
}
void Accept(const std::string& wsKey)
{
wsSocket->AcceptUpgrade(wsKey);
}
void Decline()
{
wsSocket->CancelHandshake();
}
class Delegate : public InspectorSocket::Delegate {
public:
Delegate(InspectorSocketServer* server, int sessionId) : server(server), usessionId(sessionId) {}
~Delegate() override
{
server->SessionTerminated(usessionId);
}
void OnHttpGet(const std::string& host, const std::string& path) override;
void OnSocketUpgrade(const std::string& host, const std::string& path, const std::string& wsKey) override;
void OnWsFrame(const std::vector<char>& data) override;
private:
SocketSession* Session()
{
return server->Session(usessionId);
}
InspectorSocketServer* server;
int usessionId;
};
private:
const int id;
InspectorSocket::Pointer wsSocket;
const int serverPort;
};
class ServerSocket {
public:
explicit ServerSocket(InspectorSocketServer* server)
: tcpSocket(uv_tcp_t()), server(server), unixSocket(uv_pipe_t())
{}
int Listen(sockaddr* addr, uv_loop_t* loop, int pid = -1);
void Close()
{
uv_close(reinterpret_cast<uv_handle_t*>(&tcpSocket), FreeOnCloseCallback);
}
void CloseUnix()
{
if (unixSocketOn) {
uv_close(reinterpret_cast<uv_handle_t*>(&unixSocket), nullptr);
unixSocketOn = false;
}
}
int GetPort() const
{
return port;
}
private:
template<typename UvHandle>
static ServerSocket* FromTcpSocket(UvHandle* socket)
{
return jsvm::inspector::ContainerOf(&ServerSocket::tcpSocket, reinterpret_cast<uv_tcp_t*>(socket));
}
static void SocketConnectedCallback(uv_stream_t* tcpSocket, int status);
static void UnixSocketConnectedCallback(uv_stream_t* unixSocket, int status);
static void FreeOnCloseCallback(uv_handle_t* tcpSocket)
{
delete FromTcpSocket(tcpSocket);
}
int DetectPort(uv_loop_t* loop, int pid);
~ServerSocket() = default;
uv_tcp_t tcpSocket;
InspectorSocketServer* server;
uv_pipe_t unixSocket;
int port = -1;
bool unixSocketOn = false;
};
void PrintDebuggerReadyMessage(const std::string& host,
const std::vector<InspectorSocketServer::ServerSocketPtr>& serverSockets,
const std::vector<std::string>& ids,
const char* verb,
bool publishUidStderr,
FILE* out)
{
if (!publishUidStderr || out == nullptr) {
return;
}
for (const auto& serverSocket : serverSockets) {
for (const std::string& id : ids) {
if (fprintf(out, "Debugger %s on %s\n", verb,
FormatWsAddress(host, serverSocket->GetPort(), id, true).c_str()) < 0) {
return;
}
}
}
if (fprintf(out, "For help, see: %s\n", "https://nodejs.org/en/docs/inspector") < 0) {
return;
}
if (fflush(out) != 0) {
return;
}
}
InspectorSocketServer::InspectorSocketServer(std::unique_ptr<SocketServerDelegate> delegateParam,
uv_loop_t* loop,
const std::string& host,
int port,
const InspectPublishUid& inspectPublishUid,
FILE* out,
int pid)
: loop(loop), delegate(std::move(delegateParam)), host(host), port(port), inspectPublishUid(inspectPublishUid),
nextSessionId(0), out(out), pid(pid)
{
delegate->AssignServer(this);
state = ServerState::NEW;
}
InspectorSocketServer::~InspectorSocketServer() = default;
SocketSession* InspectorSocketServer::Session(int sessionId)
{
auto it = connectedSessions.find(sessionId);
return it == connectedSessions.end() ? nullptr : it->second.second.get();
}
void InspectorSocketServer::SessionStarted(int sessionId, const std::string& targetId, const std::string& wsKey)
{
SocketSession* session = Session(sessionId);
DCHECK(session != nullptr);
if (!TargetExists(targetId)) {
session->Decline();
return;
}
connectedSessions[sessionId].first = targetId;
session->Accept(wsKey);
delegate->StartSession(sessionId, targetId);
}
void InspectorSocketServer::SessionTerminated(int sessionId)
{
if (Session(sessionId) == nullptr) {
return;
}
bool wasAttached = connectedSessions[sessionId].first != "";
if (wasAttached) {
delegate->EndSession(sessionId);
}
connectedSessions.erase(sessionId);
if (connectedSessions.empty()) {
if (wasAttached && state == ServerState::RUNNING && !serverSockets.empty()) {
PrintDebuggerReadyMessage(host, serverSockets, delegate->GetTargetIds(), "ending",
inspectPublishUid.console, out);
}
if (state == ServerState::STOPPED) {
delegate.reset();
}
}
}
bool InspectorSocketServer::HandleGetRequest(int sessionId, const std::string& hostName, const std::string& path)
{
SocketSession* session = Session(sessionId);
DCHECK(session != nullptr);
InspectorSocket* socket = session->GetWsSocket();
if (!inspectPublishUid.http) {
SendHttpNotFound(socket);
return true;
}
const char* command = MatchPathSegment(path.c_str(), "/json");
if (!command) {
return false;
}
bool hasHandled = false;
if (MatchPathSegment(command, "list") || command[0] == '\0') {
SendListResponse(socket, hostName, session);
hasHandled = true;
} else if (MatchPathSegment(command, "protocol")) {
SendProtocolJson(socket);
hasHandled = true;
} else if (MatchPathSegment(command, "version")) {
SendVersionResponse(socket);
hasHandled = true;
}
return hasHandled;
}
void InspectorSocketServer::SendListResponse(InspectorSocket* socket,
const std::string& hostName,
SocketSession* session)
{
std::vector<std::map<std::string, std::string>> response;
for (const std::string& id : delegate->GetTargetIds()) {
response.push_back(std::map<std::string, std::string>());
std::map<std::string, std::string>& targetMap = response.back();
targetMap["description"] = "jsvm instance";
targetMap["id"] = id;
targetMap["title"] = delegate->GetTargetTitle(id);
Escape(&targetMap["title"]);
targetMap["type"] = "node";
targetMap["url"] = delegate->GetTargetUrl(id);
Escape(&targetMap["url"]);
std::string detectedHost = hostName;
if (detectedHost.empty()) {
detectedHost = FormatHostPort(socket->GetHost(), session->ServerPort());
}
std::string formattedAddress = FormatAddress(detectedHost, id, false);
targetMap["devtoolsFrontendUrl"] = GetFrontendURL(false, formattedAddress);
targetMap["devtoolsFrontendUrlCompat"] = GetFrontendURL(true, formattedAddress);
targetMap["webSocketDebuggerUrl"] = FormatAddress(detectedHost, id, true);
}
SendHttpResponse(socket, MapsToString(response), HttpStatusCode::HTTP_OK);
}
std::string InspectorSocketServer::GetFrontendURL(bool isCompat, const std::string& formattedAddress)
{
std::ostringstream frontendUrl;
frontendUrl << "devtools://devtools/bundled/";
frontendUrl << (isCompat ? "inspector" : "js_app");
frontendUrl << ".html?v8only=true&ws=";
frontendUrl << formattedAddress;
return frontendUrl.str();
}
bool InspectorSocketServer::Start()
{
CHECK_NOT_NULL(delegate);
CHECK_EQ(state, ServerState::NEW);
std::unique_ptr<SocketServerDelegate> delegateHolder;
delegate.swap(delegateHolder);
struct addrinfo hints;
errno_t ret = memset_s(&hints, sizeof(hints), 0, sizeof(hints));
CHECK(ret == EOK);
hints.ai_flags = AI_NUMERICSERV;
hints.ai_socktype = SOCK_STREAM;
uv_getaddrinfo_t req;
const std::string portString = std::to_string(port);
int err = uv_getaddrinfo(loop, &req, nullptr, host.c_str(), portString.c_str(), &hints);
if (err < 0) {
if (out != nullptr) {
if (fprintf(out, "Unable to resolve \"%s\": %s\n", host.c_str(), uv_strerror(err)) < 0) {
return false;
}
}
return false;
}
for (addrinfo* address = req.addrinfo; address != nullptr; address = address->ai_next) {
auto serverSocket = ServerSocketPtr(new ServerSocket(this));
err = serverSocket->Listen(address->ai_addr, loop, pid);
if (err == 0) {
serverSockets.push_back(std::move(serverSocket));
}
}
uv_freeaddrinfo(req.addrinfo);
if (serverSockets.empty()) {
if (out != nullptr) {
if (fprintf(out, "Starting inspector on %s:%d failed: %s\n", host.c_str(), port, uv_strerror(err)) < 0) {
return false;
}
if (fflush(out) != 0) {
return false;
}
}
return false;
}
delegate.swap(delegateHolder);
state = ServerState::RUNNING;
PrintDebuggerReadyMessage(host, serverSockets, delegate->GetTargetIds(), "listening", inspectPublishUid.console,
out);
return true;
}
void InspectorSocketServer::Stop()
{
if (state == ServerState::STOPPED) {
return;
}
CHECK_EQ(state, ServerState::RUNNING);
state = ServerState::STOPPED;
serverSockets.clear();
if (Done()) {
delegate.reset();
}
}
void InspectorSocketServer::TerminateConnections()
{
for (const auto& keyValue : connectedSessions) {
keyValue.second.second->Close();
}
}
bool InspectorSocketServer::TargetExists(const std::string& id)
{
const std::vector<std::string>& targetIds = delegate->GetTargetIds();
const auto& found = std::find(targetIds.begin(), targetIds.end(), id);
return found != targetIds.end();
}
int InspectorSocketServer::GetPort() const
{
if (!serverSockets.empty()) {
return serverSockets[0]->GetPort();
}
return port;
}
void InspectorSocketServer::Accept(int serverPort, uv_stream_t* serverSocket)
{
std::unique_ptr<SocketSession> session = std::make_unique<SocketSession>(this, nextSessionId++, serverPort);
InspectorSocket::DelegatePointer delegatePointer =
InspectorSocket::DelegatePointer(new SocketSession::Delegate(this, session->GetId()));
InspectorSocket::Pointer inspector = InspectorSocket::Accept(serverSocket, std::move(delegatePointer));
if (inspector) {
session->Own(std::move(inspector));
connectedSessions[session->GetId()].second = std::move(session);
}
}
void InspectorSocketServer::Send(int sessionId, const std::string& message)
{
SocketSession* session = Session(sessionId);
if (session != nullptr) {
session->Send(message);
}
}
void InspectorSocketServer::CloseServerSocket(ServerSocket* server)
{
server->Close();
server->CloseUnix();
}
SocketSession::SocketSession(InspectorSocketServer* server, int id, int serverPort) : id(id), serverPort(serverPort) {}
void SocketSession::Send(const std::string& message)
{
wsSocket->Write(message.data(), message.length());
}
void SocketSession::Delegate::OnHttpGet(const std::string& host, const std::string& path)
{
if (!server->HandleGetRequest(usessionId, host, path)) {
SocketSession* session = Session();
DCHECK(session != nullptr);
session->GetWsSocket()->CancelHandshake();
}
}
void SocketSession::Delegate::OnSocketUpgrade(const std::string& host,
const std::string& path,
const std::string& wsKey)
{
std::string id = path.empty() ? path : path.substr(1);
server->SessionStarted(usessionId, id, wsKey);
}
void SocketSession::Delegate::OnWsFrame(const std::vector<char>& data)
{
server->MessageReceived(usessionId, std::string(data.data(), data.size()));
}
int ServerSocket::DetectPort(uv_loop_t* loop, int pid)
{
sockaddr_storage addr;
int len = sizeof(addr);
int err = uv_tcp_getsockname(&tcpSocket, reinterpret_cast<struct sockaddr*>(&addr), &len);
if (err != 0) {
return err;
}
int portNum;
if (addr.ss_family == AF_INET6) {
portNum = reinterpret_cast<const sockaddr_in6*>(&addr)->sin6_port;
} else {
portNum = reinterpret_cast<const sockaddr_in*>(&addr)->sin_port;
}
port = ntohs(portNum);
if (!unixSocketOn && pid != -1) {
auto unixDomainSocketPath = "jsvm_devtools_remote_" + std::to_string(port) + "_" + std::to_string(pid);
auto* abstract = new char[unixDomainSocketPath.length() + 2];
abstract[0] = '\0';
int ret = strcpy_s(abstract + 1, unixDomainSocketPath.length() + 1, unixDomainSocketPath.c_str());
CHECK(ret == 0);
auto status = uv_pipe_init(loop, &unixSocket, 0);
if (status == 0) {
status = uv_pipe_bind2(&unixSocket, abstract, unixDomainSocketPath.length() + 1, 0);
}
if (status == 0) {
constexpr int unixBacklog = 128;
status = uv_listen(reinterpret_cast<uv_stream_t*>(&unixSocket), unixBacklog,
ServerSocket::UnixSocketConnectedCallback);
}
unixSocketOn = status == 0;
delete[] abstract;
}
return err;
}
int ServerSocket::Listen(sockaddr* addr, uv_loop_t* loop, int pid)
{
uv_tcp_t* server = &tcpSocket;
CHECK_EQ(0, uv_tcp_init(loop, server));
int err = uv_tcp_bind(server, addr, 0);
if (err == 0) {
err = uv_listen(reinterpret_cast<uv_stream_t*>(server), 511, ServerSocket::SocketConnectedCallback);
}
if (err == 0) {
err = DetectPort(loop, pid);
}
return err;
}
void ServerSocket::SocketConnectedCallback(uv_stream_t* tcpSocket, int status)
{
if (status == 0) {
ServerSocket* serverSocket = ServerSocket::FromTcpSocket(tcpSocket);
serverSocket->server->Accept(serverSocket->port, tcpSocket);
}
}
void ServerSocket::UnixSocketConnectedCallback(uv_stream_t* unixSocket, int status)
{
if (status == 0) {
(void)unixSocket;
}
}
}
}