Files
triangles_v5/src/rpc_httpsocket.h
T
Hermes bfdb399772 WIP: modernization applied to DNS2 tree
Brings in uncommitted work from SAMI-PC E:\repos\triangles
cpp20-modernization branch:
- RocksDB default chain DB + auto-migrate from txleveldb
- SQLite default wallet + non-destructive migration from Berkeley
- Boost.Asio removed from RPC (rpc_httpsocket.h)
- Boost removed from all daemon + GUI code
- New: walletdb-base.h, walletdb-batch.h, walletdb-sqlite.{h,cpp},
  walletdb-factory.{h,cpp}, walletmigrate.{h,cpp}
- New tests: chaindb_runtime_tests, chaindb_equivalence_tests,
  snapshotnet_tests
- Docs: BOOST-REMOVAL.md, ROCKSDB-DEFAULT-MIGRATION.md,
  WALLET-SQLITE-MIGRATION.md

Does not yet build — needs CWalletDB->CWalletBatchTyped rebase in
walletdb.cpp/wallet.cpp/db.cpp and merge with origin/master for
v6 source files (checkpointpublisher, tor/, snapshot/, utxosnapshot,
bootstrap.cpp).

Build flags: -DBUILD_QT=OFF -DUSE_I2P_EMBEDDED=OFF
2026-06-29 20:08:06 -07:00

201 lines
6.9 KiB
C++

// Copyright (c) 2026 The Triangles developers.
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//
// Raw-socket transport for the JSON-RPC / REST HTTP server, replacing the
// previous Boost.Asio implementation. Provides:
//
// - CSocketIOStream : a std::iostream backed by a connected SOCKET, so the
// existing HTTP/JSON/SSE/REST code (which reads and writes std::iostream)
// is unchanged.
// - ConnectRPCSocket() : client-side connect (used by CallRPC).
// - BindRPCSockets() : create listening sockets for the RPC server.
// - SockaddrToString() : numeric host string for a peer address.
//
// TLS for the RPC port is intentionally not supported here (it was a rarely
// used Boost.Asio::ssl feature). For remote access, front the RPC port with a
// TLS terminator (stunnel / nginx) or reach it over SSH / Tor — the same
// guidance Bitcoin Core adopted when it moved its RPC server off Boost.Asio.
#ifndef TRIANGLES_RPC_HTTPSOCKET_H
#define TRIANGLES_RPC_HTTPSOCKET_H
#include "compat.h" // SOCKET, closesocket, INVALID_SOCKET, MSG_NOSIGNAL
#include <cstring>
#include <iostream>
#include <streambuf>
#include <string>
#include <vector>
#ifndef WIN32
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif
// ── std::streambuf over a connected socket ──────────────────────────────────
class CSocketStreamBuf : public std::streambuf
{
public:
explicit CSocketStreamBuf(SOCKET s) : m_socket(s)
{
setg(m_in, m_in, m_in); // empty get area to start
}
protected:
// Refill the get area with one recv().
int_type underflow() override
{
if (gptr() < egptr())
return traits_type::to_int_type(*gptr());
int n = ::recv(m_socket, m_in, static_cast<int>(sizeof(m_in)), 0);
if (n <= 0)
return traits_type::eof(); // peer closed or error
setg(m_in, m_in, m_in + n);
return traits_type::to_int_type(*gptr());
}
// Bulk write (operator<< on strings lands here).
std::streamsize xsputn(const char* s, std::streamsize n) override
{
return SendAll(s, n) ? n : 0;
}
int_type overflow(int_type ch) override
{
if (traits_type::eq_int_type(ch, traits_type::eof()))
return traits_type::not_eof(ch);
char c = static_cast<char>(ch);
return SendAll(&c, 1) ? ch : traits_type::eof();
}
int sync() override { return 0; } // sends are immediate; nothing buffered
private:
bool SendAll(const char* s, std::streamsize n)
{
std::streamsize sent = 0;
while (sent < n) {
int r = ::send(m_socket, s + sent, static_cast<int>(n - sent), MSG_NOSIGNAL);
if (r <= 0)
return false;
sent += r;
}
return true;
}
SOCKET m_socket;
char m_in[8192];
};
// std::iostream that owns a CSocketStreamBuf bound to a socket. The socket
// itself is owned by the caller (AcceptedConnection / CallRPC), not closed here.
class CSocketIOStream : public std::iostream
{
public:
explicit CSocketIOStream(SOCKET s) : std::iostream(nullptr), m_buf(s)
{
rdbuf(&m_buf);
}
private:
CSocketStreamBuf m_buf;
};
// Numeric (no DNS) host string for a peer sockaddr, e.g. "127.0.0.1" or "::1".
inline std::string SockaddrToString(const struct sockaddr* sa, socklen_t salen)
{
char host[NI_MAXHOST] = {0};
if (::getnameinfo(sa, salen, host, sizeof(host), nullptr, 0, NI_NUMERICHOST) != 0)
return "unknown";
return std::string(host);
}
// Client connect to host:port. Returns INVALID_SOCKET on failure.
inline SOCKET ConnectRPCSocket(const std::string& host, int port)
{
struct addrinfo hints;
std::memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
struct addrinfo* res = nullptr;
const std::string portStr = std::to_string(port);
if (::getaddrinfo(host.c_str(), portStr.c_str(), &hints, &res) != 0)
return INVALID_SOCKET;
SOCKET hSocket = INVALID_SOCKET;
for (struct addrinfo* rp = res; rp != nullptr; rp = rp->ai_next) {
hSocket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (hSocket == INVALID_SOCKET)
continue;
if (::connect(hSocket, rp->ai_addr, static_cast<int>(rp->ai_addrlen)) == 0)
break;
closesocket(hSocket);
hSocket = INVALID_SOCKET;
}
::freeaddrinfo(res);
return hSocket;
}
// Create listening sockets for the RPC server. When loopbackOnly is true the
// server binds the loopback interface(s) only; otherwise it binds the wildcard
// address(es). IPv4 and IPv6 are bound on separate sockets (IPV6_V6ONLY) so the
// two never conflict. Returns the bound, listening sockets; empty + strError on
// total failure (partial success — e.g. only IPv4 — is returned as success).
inline std::vector<SOCKET> BindRPCSockets(int port, bool loopbackOnly, std::string& strError)
{
std::vector<SOCKET> vListen;
struct addrinfo hints;
std::memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // wildcard when node == nullptr
struct addrinfo* res = nullptr;
const std::string portStr = std::to_string(port);
// "localhost" resolves to the loopback addresses (127.0.0.1 and ::1);
// nullptr + AI_PASSIVE yields the wildcard addresses.
const char* node = loopbackOnly ? "localhost" : nullptr;
int gai = ::getaddrinfo(node, portStr.c_str(), &hints, &res);
if (gai != 0) {
strError = std::string("RPC bind: getaddrinfo failed: ") + gai_strerror(gai);
return vListen;
}
for (struct addrinfo* rp = res; rp != nullptr; rp = rp->ai_next) {
if (rp->ai_family != AF_INET && rp->ai_family != AF_INET6)
continue;
SOCKET s = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (s == INVALID_SOCKET)
continue;
int one = 1;
::setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<const char*>(&one), sizeof(one));
if (rp->ai_family == AF_INET6) {
// Keep IPv6 sockets v6-only so a separate IPv4 socket can also bind.
::setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
reinterpret_cast<const char*>(&one), sizeof(one));
}
if (::bind(s, rp->ai_addr, static_cast<int>(rp->ai_addrlen)) != 0 ||
::listen(s, SOMAXCONN) != 0) {
closesocket(s);
continue;
}
vListen.push_back(s);
}
::freeaddrinfo(res);
if (vListen.empty())
strError = "RPC bind: could not bind any address (port in use?)";
return vListen;
}
#endif // TRIANGLES_RPC_HTTPSOCKET_H