diff --git a/CMakeLists.txt b/CMakeLists.txt index e9e90abd03cf139703050ad1cf0886f426e3532f..aea9e94c9e7283a719c0a4c54892e952c452069b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,6 +110,7 @@ else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always -std=c++17 -fPIC -march=x86-64 -mfpmath=sse -Wall -Werror=unused-result -Werror=return-type") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3") set(OS_LIBS "dl") endif() diff --git a/src/universe.cpp b/src/universe.cpp index 539d3650680bd3d24a3640205a82ecbb56e215e0..7858a1a3d5f2c901a3aee1fd3a937d1b38871fa4 100644 --- a/src/universe.cpp +++ b/src/universe.cpp @@ -172,6 +172,12 @@ void Universe::shutdown() { active_ = false; thread_.join(); + + // FIXME: This shouldn't be needed + while (peer_instances_ > 0 && ftl::pool.size() > 0) { + LOG(INFO) << "waiting for peers to destroy..."; + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + } } bool Universe::listen(const ftl::URI &addr) { @@ -291,8 +297,6 @@ socket_t Universe::_setDescriptors() { } } - // FIXME: Bug, it crashes here sometimes, segfault on reading the shared_ptr - //Set the file descriptors for each client for (const auto &s : peers_) { // NOTE: s->isValid() should return true only and only if a valid OS @@ -386,7 +390,7 @@ void Universe::_periodic() { std::string addr = i->peer->getURI(); { - UNIQUE_LOCK(net_mutex_,lk); + SHARED_LOCK(net_mutex_,lk); ftl::URI u(addr); bool removed = false; @@ -452,8 +456,7 @@ void Universe::_run() { // It is an error to use "select" with no sockets ... so just sleep if (n == 0) { std::shared_lock lk(net_mutex_); - socket_cv_.wait_for(lk, std::chrono::milliseconds(300), [this](){ return listeners_.size() > 0 || connection_count_ > 0; }); - //std::this_thread::sleep_for(std::chrono::milliseconds(100)); + socket_cv_.wait_for(lk, std::chrono::milliseconds(100), [this](){ return listeners_.size() > 0 || connection_count_ > 0; }); continue; } @@ -506,7 +509,7 @@ void Universe::_run() { for (size_t p=0; p<peers_.size(); ++p) { auto s = peers_[(p+phase_)%peers_.size()]; - if (s != NULL && s->isValid()) { + if (s && s->isValid()) { // Note: It is possible that the socket becomes invalid after check but before // looking at the FD sets, therefore cache the original socket SOCKET sock = s->_socket(); @@ -514,13 +517,17 @@ void Universe::_run() { if (FD_ISSET(sock, &impl_->sfderror_)) { if (s->socketError()) { + //lk.unlock(); s->close(); + //lk.lock(); continue; // No point in reading data... } } //If message received from this client then deal with it if (FD_ISSET(sock, &impl_->sfdread_)) { + //lk.unlock(); s->data(); + //lk.lock(); } } } diff --git a/src/universe.hpp b/src/universe.hpp index 9b8fb0dc4c8c3e93a7d5428f7cd6191f15a93c27..0f8d99943fbcf00fe28fc2ab22dc3d979c9397af 100644 --- a/src/universe.hpp +++ b/src/universe.hpp @@ -205,8 +205,8 @@ private: std::list<std::shared_ptr<ftl::net::Peer>> garbage_; ftl::Handle garbage_timer_; - size_t send_size_; - size_t recv_size_; + // size_t send_size_; + // size_t recv_size_; double periodic_time_; int reconnect_attempts_; std::atomic_int connection_count_ = 0; // Active connections @@ -236,7 +236,7 @@ void Universe::bind(const std::string &name, F func) { template <typename... ARGS> void Universe::broadcast(const std::string &name, ARGS... args) { SHARED_LOCK(net_mutex_,lk); - for (auto p : peers_) { + for (auto &p : peers_) { if (!p->waitConnection()) continue; p->send(name, args...); } @@ -265,7 +265,7 @@ std::optional<R> Universe::findOne(const std::string &name, ARGS... args) { { SHARED_LOCK(net_mutex_,lk); - for (auto p : peers_) { + for (auto &p : peers_) { if (!p->waitConnection()) continue; p->asyncCall<std::optional<R>>(name, handler, args...); } @@ -302,7 +302,7 @@ std::vector<R> Universe::findAll(const std::string &name, ARGS... args) { { SHARED_LOCK(net_mutex_,lk); - for (auto p : peers_) { + for (auto &p : peers_) { if (!p->waitConnection()) continue; ++sdata->sentcount; p->asyncCall<std::vector<R>>(name, handler, args...); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f6dd15f3c01fe2f3c61236f6b0c6408b3db2d8fc..3777c230bdcc33d0c4ac980089ace4aec46cc2fe 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -39,3 +39,13 @@ target_link_libraries(net_integration beyond-protocol ${URIPARSER_LIBRARIES}) add_test(NetIntegrationTest net_integration) + +### Net performance ############################################################ +add_executable(net_performance + $<TARGET_OBJECTS:CatchTestFTL> + ./net_performance.cpp) +target_include_directories(net_performance PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}../include") +target_link_libraries(net_performance beyond-protocol + GnuTLS::GnuTLS Threads::Threads ${UUID_LIBRARIES} ${URIPARSER_LIBRARIES} ${OS_LIBS}) + +add_test(NetPerformance net_performance) diff --git a/test/net_performance.cpp b/test/net_performance.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1472746f3936f939a86c18ab46c84580ef8708c5 --- /dev/null +++ b/test/net_performance.cpp @@ -0,0 +1,112 @@ +#include "catch.hpp" + +#include <nlohmann/json.hpp> + +#include "../src/universe.hpp" +#include "../src/peer.hpp" +#include "../src/protocol.hpp" + +#include <thread> +#include <chrono> + +#include "../src/protocol/connection.hpp" +#include "../src/protocol/tcp.hpp" + +// msgpack uses binary format when DTYPE is char +typedef char DTYPE; +constexpr int BSIZE = (1 << 23); // bytes in each send (2^20 = 1 MiB) +constexpr int COUNT = 100; // how many times send called + +using namespace ftl::net; + +static std::vector<DTYPE> data_test; +static std::atomic_uint64_t recv_cnt_ = 0; +static auto t_last_recv_ = std::chrono::steady_clock::now(); + +static void recv_data(std::vector<DTYPE> data) { + recv_cnt_.fetch_add(data.size() * sizeof(DTYPE)); + t_last_recv_ = std::chrono::steady_clock::now(); +} + +static float peer_send(ftl::net::Peer* p, const std::vector<DTYPE>& data, int cnt) { + auto t_start = std::chrono::steady_clock::now(); + auto t_stop = std::chrono::steady_clock::now(); + + size_t bytes_sent = 0; + size_t bytes = data.size() * sizeof(DTYPE); + + for (int i = 0; i < cnt; i++) { + p->send("recv_data", data); + bytes_sent += bytes; + } + + t_stop = std::chrono::steady_clock::now(); + + // should be ok, since blocking sockets are used + float ms = std::chrono::duration_cast<std::chrono::milliseconds> + (t_stop - t_start).count(); + float throughput_send = (float(bytes_sent >> 20)/ms)*1000.0f*8.0f; + + LOG(INFO) << "sent " << (bytes_sent >> 20) << " MiB in " << ms << " ms, " + << "connection throughput: " + << throughput_send << " MBit/s"; + + ms = std::chrono::duration_cast<std::chrono::milliseconds> + (t_last_recv_ - t_start).count(); + float throughput_recv = (float(recv_cnt_ >> 20)/ms)*1000.0f*8.0f; + + LOG(INFO) << "received " << (bytes_sent >> 20) << " MiB in " << ms << " ms, " + << "connection throughput: " + << throughput_recv << " MBit/s"; + + recv_cnt_ = 0; + + return (throughput_send + throughput_recv)/2.0f; +} + +std::unique_ptr<Universe> net_server; +std::unique_ptr<Universe> net_client; +std::atomic_bool init_ = false; + +static void init() { + if (init_.exchange(true)) { return; } + + net_server = std::make_unique<Universe>(); + net_server->setLocalID(ftl::UUID()); + net_client = std::make_unique<Universe>(); + net_client->setLocalID(ftl::UUID()); + + net_server->bind("test_server", [](){ LOG(INFO) << "test_server"; }); + net_client->bind("test_client", [](){ LOG(INFO) << "test_client"; }); + net_server->bind("recv_data", recv_data); + + data_test.clear(); data_test.reserve(BSIZE); + for (int i = 0; i < BSIZE; i++) { data_test.push_back(i ^ (i - 1)); } +} + +ftl::URI uri(""); + +TEST_CASE("throughput", "[net]") { + + SECTION("create server and client Universe instances") { + init(); + } + + SECTION("create TCP server") { + std::string host = "localhost"; + int port = 0; // pick random port + net_server->listen(ftl::URI("tcp://" + host + ":" + std::to_string(port))); + int listening_port = net_server->getListeningURIs()[0].getPort(); + uri = ftl::URI("tcp://localhost:" + std::to_string(listening_port)); + } + + SECTION("TCP throughput") { + LOG(INFO) << "connecting to " << uri.to_string(); + auto p = net_client->connect(uri); + + while(!p->isConnected()) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } + + auto r = peer_send(p.get(), data_test, COUNT); + //REQUIRE(r > 8000); + } +} \ No newline at end of file diff --git a/test/peer_unit.cpp b/test/peer_unit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e0407614779ba501601284b470c08d9d48f764c9 --- /dev/null +++ b/test/peer_unit.cpp @@ -0,0 +1,455 @@ +/** Peer unit test. Does not test lower level protocols (TLS/WebSocket) */ + +#include "catch.hpp" + +#include <iostream> +#include <memory> + +#include <vector> +#include <tuple> +#include <thread> +#include <chrono> +#include <functional> +#include <sstream> + +#include <ftl/net/common.hpp> +#include <ftl/net/peer.hpp> +#include <ftl/net/protocol.hpp> +#include <ftl/config.h> + +#include "../src/socket.hpp" +#include "../src/protocol/connection.hpp" + +#define _FTL_NET_UNIVERSE_HPP_ + +using std::tuple; +using std::get; +using std::vector; +using ftl::net::Peer; +using std::this_thread::sleep_for; +using std::chrono::milliseconds; + +// --- Mock -------------------------------------------------------------------- + +namespace ftl { +namespace net { + +typedef unsigned int callback_t; + +class Universe { + public: + Universe() {}; + + ftl::UUID id() { return ftl::UUID(); } + + void _notifyConnect(Peer*) {} + void _notifyDisconnect(Peer*) {} + void removeCallback(callback_t id) {} + + callback_t onConnect(const std::function<void(Peer*)> &f) { return 0; } + callback_t onDisconnect(const std::function<void(Peer*)> &f) { return 0; } + + size_t getSendBufferSize(ftl::URI::scheme_t s) const { return 10*1024; } + size_t getRecvBufferSize(ftl::URI::scheme_t s) const { return 10*1024; } +}; +} +} + +using ftl::net::internal::Socket; +using ftl::net::internal::SocketConnection; + +// Mock connection, reads/writes from fakedata +// TODO: use separate in/out data +static std::map<int, std::string> fakedata; +//static std::mutex fakedata_mtx; + +class Connection_Mock : public SocketConnection { +public: + const int id_; + Connection_Mock(int id) : SocketConnection(), id_(id) { + + } + + void connect(const ftl::URI&, int) override {} + + ssize_t send(const char* buffer, size_t len) override { + fakedata[id_] += std::string(buffer, len); + return len; + } + + ssize_t recv(char *buffer, size_t len) override { + if (fakedata.count(id_) == 0) { + // this is an error in test + std::cout << "unrecognised socket, test error (FIXME)" << std::endl; + return 0; + } + + size_t l = fakedata[id_].size(); + CHECK(l <= len); // FIXME: buffer overflow + std::memcpy(buffer, fakedata[id_].c_str(), l); + + fakedata.erase(id_); + + return l; + } + + ssize_t writev(const struct iovec *iov, int iovcnt) override { + size_t sent = 0; + std::stringstream ss; + for (int i = 0; i < iovcnt; i++) { + ss << std::string((char*)(iov[i].iov_base), size_t(iov[i].iov_len)); + sent += iov[i].iov_len; + } + fakedata[id_] += ss.str(); + return sent; + } +}; + +static std::atomic<int> ctr_ = 0; +std::unique_ptr<SocketConnection> ftl::net::internal::createConnection(const ftl::URI &uri) { + return std::make_unique<Connection_Mock>(ctr_++); +} + +bool ftl::net::internal::Socket::is_fatal() { + return false; +} + +class MockPeer : public Peer { +private: + MockPeer(int) : + Peer(ftl::net::internal::createConnection(ftl::URI("")), new ftl::net::Universe()), + idx(ctr_ - 1) {} + + MockPeer(std::string uri) : + Peer(ftl::URI(""), new ftl::net::Universe()), idx(ctr_ - 1) {} + +public: + int idx; + + void mock_data() { data(); } + + static MockPeer create_connecting_peer() { + return MockPeer(""); + }; + + static MockPeer create_listening_peer() { + return MockPeer(0); + }; +}; + +// --- Support ----------------------------------------------------------------- + +void send_handshake(Peer &p) { + ftl::UUID id; + p.send("__handshake__", ftl::net::kMagic, ((8 << 16) + (5 << 8) + 2), id); +} + +template <typename T> +tuple<std::string, T> readResponse(int s) { + msgpack::object_handle msg = msgpack::unpack(fakedata[s].data(), fakedata[s].size()); + tuple<uint8_t, std::string, T> req; + msg.get().convert(req); + return std::make_tuple(get<1>(req), get<2>(req)); +} + +template <typename T> +tuple<uint32_t, T> readRPC(int s) { + msgpack::object_handle msg = msgpack::unpack(fakedata[s].data(), fakedata[s].size()); + tuple<uint8_t, uint32_t, std::string, T> req; + msg.get().convert(req); + return std::make_tuple(get<1>(req), get<3>(req)); +} + +template <typename T> +T readRPCReturn(int s) { + msgpack::object_handle msg = msgpack::unpack(fakedata[s].data(), fakedata[s].size()); + tuple<uint8_t, uint32_t, std::string, T> req; + msg.get().convert(req); + return get<3>(req); +} + +// --- Files to test ----------------------------------------------------------- + +#include "../src/peer.cpp" + +// --- Tests ------------------------------------------------------------------- + +TEST_CASE("Peer(int)", "[]") { + SECTION("initiates a valid handshake") { + MockPeer s = MockPeer::create_listening_peer(); + + auto [name, hs] = readResponse<ftl::net::Handshake>(0); + + REQUIRE( name == "__handshake__" ); + + // 1) Sends magic (64bits) + REQUIRE( get<0>(hs) == ftl::net::kMagic ); + + // 2) Sends FTL Version + REQUIRE( get<1>(hs) == (FTL_VERSION_MAJOR << 16) + (FTL_VERSION_MINOR << 8) + FTL_VERSION_PATCH ); + + // 3) Sends peer UUID + + + REQUIRE( s.status() == Peer::kConnecting ); + } + + SECTION("completes on full handshake") { + MockPeer s_l = MockPeer::create_listening_peer(); + MockPeer s_c = MockPeer::create_connecting_peer(); + + // get sent message by s_l and place it in s_c's buffer + fakedata[s_c.idx] = fakedata[s_l.idx]; + s_l.mock_data(); // listenin peer: process + // vice versa, listening peer gets reply and processes it + fakedata[s_l.idx] = fakedata[s_c.idx]; + s_c.mock_data(); // connecting peer: process + sleep_for(milliseconds(50)); + + // both peers should be connected now + REQUIRE( s_c.status() == Peer::kConnected ); + REQUIRE( s_l.status() == Peer::kConnected ); + } + + SECTION("has correct version on full handshake") { + + // MockPeer s = MockPeer::create_connecting_peer(); + // Send handshake response + // send_handshake(s); + // s.mock_data(); + //sleep_for(milliseconds(50)); + // REQUIRE( (s.getFTLVersion() == (8 << 16) + (5 << 8) + 2) ); + } + + SECTION("has correct peer id on full handshake") { + // MockPeer s = MockPeer::create_connecting_peer(); + // + // Send handshake response + //REQUIRE( s.id() == ); + } +} + +TEST_CASE("Peer::call()", "[rpc]") { + MockPeer s = MockPeer::create_connecting_peer(); + send_handshake(s); + s.mock_data(); + sleep_for(milliseconds(50)); + + SECTION("one argument call") { + REQUIRE( s.isConnected() ); + + fakedata[s.idx] = ""; + + // Thread to provide response to otherwise blocking call + std::thread thr([&s]() { + while (fakedata[s.idx].size() == 0) std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + auto [id,value] = readRPC<tuple<int>>(s.idx); + auto res_obj = std::make_tuple(1,id,"__return__",get<0>(value)+22); + std::stringstream buf; + msgpack::pack(buf, res_obj); + fakedata[s.idx] = buf.str(); + s.mock_data(); + sleep_for(milliseconds(50)); + }); + int res = s.call<int>("test1", 44); + thr.join(); + + REQUIRE( (res == 66) ); + } + + SECTION("no argument call") { + REQUIRE( s.isConnected() ); + + fakedata[s.idx] = ""; + + // Thread to provide response to otherwise blocking call + std::thread thr([&s]() { + while (fakedata[s.idx].size() == 0) std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + auto res = readRPC<tuple<>>(s.idx); + auto res_obj = std::make_tuple(1,std::get<0>(res),"__return__",77); + std::stringstream buf; + msgpack::pack(buf, res_obj); + fakedata[s.idx] = buf.str(); + s.mock_data(); + sleep_for(milliseconds(50)); + }); + + int res = s.call<int>("test1"); + + thr.join(); + + REQUIRE( (res == 77) ); + } + + SECTION("vector return from call") { + REQUIRE( s.isConnected() ); + + fakedata[s.idx] = ""; + + // Thread to provide response to otherwise blocking call + std::thread thr([&s]() { + while (fakedata[s.idx].size() == 0) std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + auto res = readRPC<tuple<>>(s.idx); + vector<int> data = {44,55,66}; + auto res_obj = std::make_tuple(1,std::get<0>(res),"__return__",data); + std::stringstream buf; + msgpack::pack(buf, res_obj); + fakedata[s.idx] = buf.str(); + s.mock_data(); + sleep_for(milliseconds(50)); + }); + + vector<int> res = s.call<vector<int>>("test1"); + + thr.join(); + + REQUIRE( (res[0] == 44) ); + REQUIRE( (res[2] == 66) ); + } +} + +TEST_CASE("Peer::bind()", "[rpc]") { + MockPeer s = MockPeer::create_listening_peer(); + send_handshake(s); + s.mock_data(); + sleep_for(milliseconds(50)); + + + SECTION("no argument call") { + bool done = false; + + s.bind("hello", [&]() { + done = true; + }); + + s.send("hello"); + s.mock_data(); // Force it to read the fake send... + sleep_for(milliseconds(50)); + + REQUIRE( done ); + } + + SECTION("one argument call") { + int done = 0; + + s.bind("hello", [&](int a) { + done = a; + }); + + s.send("hello", 55); + s.mock_data(); // Force it to read the fake send... + sleep_for(milliseconds(50)); + + REQUIRE( (done == 55) ); + } + + SECTION("two argument call") { + std::string done; + + s.bind("hello", [&](int a, std::string b) { + done = b; + }); + + s.send("hello", 55, "world"); + s.mock_data(); // Force it to read the fake send... + sleep_for(milliseconds(50)); + + REQUIRE( (done == "world") ); + } + + SECTION("int return value") { + int done = 0; + + s.bind("hello", [&](int a) -> int { + done = a; + return a; + }); + + s.asyncCall<int>("hello", [](int a){}, 55); + s.mock_data(); // Force it to read the fake send... + sleep_for(milliseconds(50)); + + REQUIRE( (done == 55) ); + REQUIRE( (readRPCReturn<int>(s.idx) == 55) ); + } + + SECTION("vector return value") { + int done = 0; + + s.bind("hello", [&](int a) -> vector<int> { + done = a; + vector<int> b = {a,45}; + return b; + }); + + s.asyncCall<int>("hello", [](int a){}, 55); + s.mock_data(); // Force it to read the fake send... + sleep_for(milliseconds(50)); + + REQUIRE( (done == 55) ); + + auto res = readRPCReturn<vector<int>>(s.idx); + REQUIRE( (res[1] == 45) ); + } +} + +TEST_CASE("Socket::send()", "[io]") { + MockPeer s = MockPeer::create_connecting_peer(); + sleep_for(milliseconds(50)); + + SECTION("send an int") { + int i = 607; + + s.send("dummy",i); + + auto [name, value] = readResponse<tuple<int>>(s.idx); + + REQUIRE( (name == "dummy") ); + REQUIRE( (get<0>(value) == 607) ); + } + + SECTION("send a string") { + std::string str("hello world"); + s.send("dummy",str); + + auto [name, value] = readResponse<tuple<std::string>>(s.idx); + + REQUIRE( (name == "dummy") ); + REQUIRE( (get<0>(value) == "hello world") ); + } + + SECTION("send const char* string") { + s.send("dummy","hello world"); + + auto [name, value] = readResponse<tuple<std::string>>(s.idx); + + REQUIRE( (name == "dummy") ); + REQUIRE( (get<0>(value) == "hello world") ); + } + + SECTION("send a tuple") { + auto tup = std::make_tuple(55,66,true,6.7); + s.send("dummy",tup); + + auto [name, value] = readResponse<tuple<decltype(tup)>>(s.idx); + + REQUIRE( (name == "dummy") ); + REQUIRE( (get<1>(get<0>(value)) == 66) ); + } + + SECTION("send multiple strings") { + std::string str("hello "); + std::string str2("world"); + s.send("dummy2",str,str2); + + auto [name, value] = readResponse<tuple<std::string,std::string>>(s.idx); + + REQUIRE( (name == "dummy2") ); + REQUIRE( (get<0>(value) == "hello ") ); + REQUIRE( (get<1>(value) == "world") ); + } +} + diff --git a/test/socket_mock.cpp b/test/socket_mock.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b9e71e893afe951c1c91d92ac4b8db2c7155a73a --- /dev/null +++ b/test/socket_mock.cpp @@ -0,0 +1,114 @@ +/** no-op socket for unit tests. Simulated data transfer implemented in + * Connection_Mock */ + +#include "../src/socket.hpp" + +#include <string> + +using ftl::net::internal::Socket; +using ftl::net::internal::SocketAddress; + +bool ftl::net::internal::resolve_inet_address(const std::string &hostname, int port, SocketAddress &address) { + return true; +} + +Socket::Socket(int domain, int type, int protocol) : + status_(STATUS::UNCONNECTED), fd_(-1), family_(domain) { +} + +bool Socket::is_valid() { return true; } + +bool Socket::is_open() { return true; } + +ssize_t Socket::recv(char *buffer, size_t len, int flags) { + return 0; +} + +ssize_t Socket::send(const char* buffer, size_t len, int flags) { + return 0; +} + +ssize_t Socket::writev(const struct iovec *iov, int iovcnt) { + return 0; +} + +int Socket::bind(const SocketAddress &addr) { + return 0; +} + +int Socket::listen(int backlog) { + return 0; +} + +Socket Socket::accept(SocketAddress &addr) { + return Socket(); +} + +int Socket::connect(const SocketAddress& address) { + return 0; +} + +int Socket::connect(const SocketAddress &address, int timeout) { + return 0; +} + +/// Close socket (if open). Multiple calls are safe. +bool Socket::close() { + return true; +} + +int Socket::setsockopt(int level, int optname, const void *optval, socklen_t optlen) { + return 0; +} + +int Socket::getsockopt(int level, int optname, void *optval, socklen_t *optlen) { + return 0; +} + +bool Socket::set_recv_buffer_size(size_t sz) { + return true; +} + +bool Socket::set_send_buffer_size(size_t sz) { + return true; +} + +size_t Socket::get_recv_buffer_size() { + return 0; +} + +size_t Socket::get_send_buffer_size() { + return 0; +} + + +void Socket::set_blocking(bool val) { +} + + +bool Socket::is_blocking() { + return true; +} + +std::string Socket::get_error_string() { + return "not real socket"; +} + +bool Socket::set_nodelay(bool val) { + return true; +} + +SocketAddress Socket::getsockname() { + SocketAddress addr; + return addr; +} + +// TCP socket + +Socket ftl::net::internal::create_tcp_socket() { + return Socket(); +} + +std::string ftl::net::internal::get_ip(SocketAddress& address) { return ""; } +std::string ftl::net::internal::get_host(SocketAddress& address) { return ""; } +int ftl::net::internal::get_port(SocketAddress& address) { return 0; } \ No newline at end of file