diff --git b/CMakeLists.txt a/CMakeLists.txt new file mode 100644 index 0000000..1aa89c0 --- /dev/null +++ a/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minmum_required(VERSION 3.20) +project(osdev-socket-cpp) + +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) + +# =========================================================================== +# == Include build information +include(osdevversion) +include(projectheader) + +project_header(osdev-socket-cpp) + +include(compiler) + +add_subdirectory(src) +add_subdirectory(examples) +add_subdirectory(tests) diff --git b/examples/linux/canrecv.cpp a/examples/linux/canrecv.cpp new file mode 100644 index 0000000..02675c4 --- /dev/null +++ a/examples/linux/canrecv.cpp @@ -0,0 +1,96 @@ +// canrecv.cpp +// +// Linux SoxketCAN reader example. +// +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2021 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include "sockpp/can_socket.h" +#include "sockpp/can_frame.h" +#include "sockpp/version.h" + +#include +#include + +using namespace std; + +// The clock to use to get time and pace the app. +using sysclock = chrono::system_clock; + +// -------------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + cout << "Sample SocketCAN writer for 'sockpp' " + << sockpp::SOCKPP_VERSION << endl; + + string canIface = (argc > 1) ? argv[1] : "can0"; + canid_t canID = (argc > 2) ? atoi(argv[2]) : 0x20; + + sockpp::socket_initializer sockInit; + + sockpp::can_address addr(canIface); + sockpp::can_socket sock(addr); + + if (!sock) { + cerr << "Error binding to the CAN interface " << canIface << "\n\t" + << sock.last_error_str() << endl; + return 1; + } + + cout << "Created CAN socket on " << sock.address() << endl; + time_t t = sysclock::to_time_t(sysclock::now()); + + cout.setf(ios::fixed, ios::floatfield); + cout << setfill('0'); + + while (true) { + sockpp::can_frame frame; + sock.recv(&frame); + auto t = sock.last_frame_timestamp(); + + cout << t << " "; + for (uint8_t i=0; i +#include +#include +#include +#include "sockpp/can_socket.h" +#include "sockpp/can_frame.h" +#include "sockpp/version.h" + +#include +#include + +using namespace std; + +// The clock to use to get time and pace the app. +using sysclock = chrono::system_clock; + +// -------------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + cout << "Sample SocketCAN writer for 'sockpp' " + << sockpp::SOCKPP_VERSION << endl; + + string canIface = (argc > 1) ? argv[1] : "can0"; + canid_t canID = (argc > 2) ? atoi(argv[2]) : 0x20; + + sockpp::socket_initializer sockInit; + + sockpp::can_address addr(canIface); + sockpp::can_socket sock(addr); + + if (!sock) { + cerr << "Error binding to the CAN interface " << canIface << "\n\t" + << sock.last_error_str() << endl; + return 1; + } + + cout << "Created CAN socket on " << sock.address() << endl; + time_t t = sysclock::to_time_t(sysclock::now()); + + while (true) { + // Sleep until the clock ticks to the next second + this_thread::sleep_until(sysclock::from_time_t(t+1)); + + // Re-read the time in case we fell behind + t = sysclock::to_time_t(sysclock::now()); + + // Write the time to the CAN bus as a 32-bit int + auto nt = uint32_t(t); + + sockpp::can_frame frame { canID, &nt, sizeof(nt) }; + sock.send(frame); + } + + return (!sock) ? 1 : 0; +} diff --git b/examples/tcp/tcp6echo.cpp a/examples/tcp/tcp6echo.cpp new file mode 100644 index 0000000..db045ef --- /dev/null +++ a/examples/tcp/tcp6echo.cpp @@ -0,0 +1,99 @@ +// tcpecho.cpp +// +// Simple TCP echo client +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2014-2017 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include +#include +#include "sockpp/tcp6_connector.h" +#include "sockpp/version.h" + +using namespace std; +using namespace std::chrono; + +// -------------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + cout << "Sample IPv6 TCP echo client for 'sockpp' " + << sockpp::SOCKPP_VERSION << '\n' << endl; + + std::string host = (argc > 1) ? argv[1] : "::1"; + in_port_t port = (argc > 2) ? atoi(argv[2]) : 12345; + + sockpp::socket_initializer sockInit; + + // Implicitly creates an inet6_address from {host,port} + // and then tries the connection. + + sockpp::tcp6_connector conn({host, port}); + if (!conn) { + cerr << "Error connecting to server at " + << sockpp::inet6_address(host, port) + << "\n\t" << conn.last_error_str() << endl; + return 1; + } + + cout << "Created a connection from " << conn.address() << endl; + + // Set a timeout for the responses + if (!conn.read_timeout(seconds(5))) { + cerr << "Error setting timeout on TCP stream: " + << conn.last_error_str() << endl; + } + + string s, sret; + while (getline(cin, s) && !s.empty()) { + if (conn.write(s) != ssize_t(s.length())) { + cerr << "Error writing to the TCP stream: " + << conn.last_error_str() << endl; + break; + } + + sret.resize(s.length()); + ssize_t n = conn.read_n(&sret[0], s.length()); + + if (n != ssize_t(s.length())) { + cerr << "Error reading from TCP stream: " + << conn.last_error_str() << endl; + break; + } + + cout << sret << endl; + } + + return (!conn) ? 1 : 0; +} diff --git b/examples/tcp/tcp6echosvr.cpp a/examples/tcp/tcp6echosvr.cpp new file mode 100644 index 0000000..8352207 --- /dev/null +++ a/examples/tcp/tcp6echosvr.cpp @@ -0,0 +1,109 @@ +// tcp6echosvr.cpp +// +// A multi-threaded TCP v6 echo server for sockpp library. +// This is a simple thread-per-connection TCP server for IPv6. +// +// USAGE: +// tcp6echosvr [port] +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include +#include +#include "sockpp/tcp6_acceptor.h" +#include "sockpp/version.h" + +using namespace std; + +// -------------------------------------------------------------------------- +// The thread function. This is run in a separate thread for each socket. +// Ownership of the socket object is transferred to the thread, so when this +// function exits, the socket is automatically closed. + +void run_echo(sockpp::tcp6_socket sock) +{ + ssize_t n; + char buf[512]; + + while ((n = sock.read(buf, sizeof(buf))) > 0) + sock.write_n(buf, n); + + cout << "Connection closed from " << sock.peer_address() << endl; +} + +// -------------------------------------------------------------------------- +// The main thread runs the TCP port acceptor. Each time a connection is +// made, a new thread is spawned to handle it, leaving this main thread to +// immediately wait for the next connection. + +int main(int argc, char* argv[]) +{ + cout << "Sample IPv6 TCP echo server for 'sockpp' " + << sockpp::SOCKPP_VERSION << '\n' << endl; + + in_port_t port = (argc > 1) ? atoi(argv[1]) : 12345; + + sockpp::socket_initializer sockInit; + sockpp::tcp6_acceptor acc(port); + + if (!acc) { + cerr << "Error creating the acceptor: " << acc.last_error_str() << endl; + return 1; + } + cout << "Awaiting connections on port " << port << "..." << endl; + + while (true) { + sockpp::inet6_address peer; + + // Accept a new client connection + sockpp::tcp6_socket sock = acc.accept(&peer); + cout << "Received a connection request from " << peer << endl; + + if (!sock) { + cerr << "Error accepting incoming connection: " + << acc.last_error_str() << endl; + } + else { + // Create a thread and transfer the new stream to it. + thread thr(run_echo, std::move(sock)); + thr.detach(); + } + } + + return 0; +} + + + diff --git b/examples/tcp/tcpecho.cpp a/examples/tcp/tcpecho.cpp new file mode 100644 index 0000000..03e0df8 --- /dev/null +++ a/examples/tcp/tcpecho.cpp @@ -0,0 +1,97 @@ +// tcpecho.cpp +// +// Simple TCP echo client +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2014-2017 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include +#include +#include "sockpp/tcp_connector.h" +#include "sockpp/version.h" + +using namespace std; +using namespace std::chrono; + +int main(int argc, char* argv[]) +{ + cout << "Sample TCP echo client for 'sockpp' " + << sockpp::SOCKPP_VERSION << '\n' << endl; + + string host = (argc > 1) ? argv[1] : "localhost"; + in_port_t port = (argc > 2) ? atoi(argv[2]) : 12345; + + sockpp::socket_initializer sockInit; + + // Implicitly creates an inet_address from {host,port} + // and then tries the connection. + + sockpp::tcp_connector conn({host, port}); + if (!conn) { + cerr << "Error connecting to server at " + << sockpp::inet_address(host, port) + << "\n\t" << conn.last_error_str() << endl; + return 1; + } + + cout << "Created a connection from " << conn.address() << endl; + + // Set a timeout for the responses + if (!conn.read_timeout(seconds(5))) { + cerr << "Error setting timeout on TCP stream: " + << conn.last_error_str() << endl; + } + + string s, sret; + while (getline(cin, s) && !s.empty()) { + if (conn.write(s) != ssize_t(s.length())) { + cerr << "Error writing to the TCP stream: " + << conn.last_error_str() << endl; + break; + } + + sret.resize(s.length()); + ssize_t n = conn.read_n(&sret[0], s.length()); + + if (n != ssize_t(s.length())) { + cerr << "Error reading from TCP stream: " + << conn.last_error_str() << endl; + break; + } + + cout << sret << endl; + } + + return (!conn) ? 1 : 0; +} diff --git b/examples/tcp/tcpechomt.cpp a/examples/tcp/tcpechomt.cpp new file mode 100644 index 0000000..a739534 --- /dev/null +++ a/examples/tcp/tcpechomt.cpp @@ -0,0 +1,124 @@ +// tcpechomt.cpp +// +// TCP echo client that uses separate read and write threads. +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2014-2017 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include +#include +#include +#include "sockpp/tcp_connector.h" +#include "sockpp/version.h" + +using namespace std; + +// -------------------------------------------------------------------------- +// The read thread will run independently, retrieving packets from the +// server and writing them to the console. When the main (write) thread +// shuts down the socket, we exit. + +void read_thr(sockpp::tcp_socket rdSock) +{ + char buf[512]; + ssize_t n; + + while ((n = rdSock.read(buf, sizeof(buf))) > 0) { + cout.write(buf, n); + cout << endl; + } + + if (n < 0) { + cout << "Read error [" << rdSock.last_error() << "]: " + << rdSock.last_error_str() << endl; + } + rdSock.shutdown(); +} + +// -------------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + cout << "Sample multi-threaded TCP echo client for 'sockpp' " + << sockpp::SOCKPP_VERSION << '\n' << endl; + + string host = (argc > 1) ? argv[1] : "localhost"; + in_port_t port = (argc > 2) ? atoi(argv[2]) : 12345; + + sockpp::socket_initializer sockInit; + + // Implicitly creates an inet_address from {host,port} + // and then tries the connection. + + sockpp::tcp_connector conn({host, port}); + if (!conn) { + cerr << "Error connecting to server at " + << sockpp::inet_address(host, port) + << "\n\t" << conn.last_error_str() << endl; + return 1; + } + + cout << "Created a connection from " << conn.address() << endl; + + // We create a read thread and send it a clone (dup) of the + // connector socket. + + std::thread rdThr(read_thr, std::move(conn.clone())); + + // The write loop get user input and writes it to the socket. + + string s, sret; + while (getline(cin, s) && !s.empty()) { + if (conn.write(s) != (int) s.length()) { + if (conn.last_error() == EPIPE) { + cerr << "It appears that the socket was closed." << endl; + } + else { + cerr << "Error writing to the TCP stream [" + << conn.last_error() << "]: " + << conn.last_error_str() << endl; + } + break; + } + } + int ret = !conn ? 1 : 0; + + // Shutting down the socket will cause the read thread to exit. + // We wait for it to exit before we leave the app. + + conn.shutdown(SHUT_WR); + rdThr.join(); + + return ret; +} diff --git b/examples/tcp/tcpechosvr.cpp a/examples/tcp/tcpechosvr.cpp new file mode 100644 index 0000000..1b7861f --- /dev/null +++ a/examples/tcp/tcpechosvr.cpp @@ -0,0 +1,111 @@ +// tcpechosvr.cpp +// +// A multi-threaded TCP echo server for sockpp library. +// This is a simple thread-per-connection TCP server. +// +// USAGE: +// tcpechosvr [port] +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2014-2017 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include +#include +#include "sockpp/tcp_acceptor.h" +#include "sockpp/version.h" + +using namespace std; + +// -------------------------------------------------------------------------- +// The thread function. This is run in a separate thread for each socket. +// Ownership of the socket object is transferred to the thread, so when this +// function exits, the socket is automatically closed. + +void run_echo(sockpp::tcp_socket sock) +{ + ssize_t n; + char buf[512]; + + while ((n = sock.read(buf, sizeof(buf))) > 0) + sock.write_n(buf, n); + + cout << "Connection closed from " << sock.peer_address() << endl; +} + +// -------------------------------------------------------------------------- +// The main thread runs the TCP port acceptor. Each time a connection is +// made, a new thread is spawned to handle it, leaving this main thread to +// immediately wait for the next connection. + +int main(int argc, char* argv[]) +{ + cout << "Sample TCP echo server for 'sockpp' " + << sockpp::SOCKPP_VERSION << '\n' << endl; + + in_port_t port = (argc > 1) ? atoi(argv[1]) : 12345; + + sockpp::socket_initializer sockInit; + + sockpp::tcp_acceptor acc(port); + + if (!acc) { + cerr << "Error creating the acceptor: " << acc.last_error_str() << endl; + return 1; + } + //cout << "Acceptor bound to address: " << acc.address() << endl; + cout << "Awaiting connections on port " << port << "..." << endl; + + while (true) { + sockpp::inet_address peer; + + // Accept a new client connection + sockpp::tcp_socket sock = acc.accept(&peer); + cout << "Received a connection request from " << peer << endl; + + if (!sock) { + cerr << "Error accepting incoming connection: " + << acc.last_error_str() << endl; + } + else { + // Create a thread and transfer the new stream to it. + thread thr(run_echo, std::move(sock)); + thr.detach(); + } + } + + return 0; +} + + + diff --git b/examples/tcp/tcpechotest.cpp a/examples/tcp/tcpechotest.cpp new file mode 100644 index 0000000..9c2117f --- /dev/null +++ a/examples/tcp/tcpechotest.cpp @@ -0,0 +1,123 @@ +// tcpechotest.cpp +// +// TCP echo timing client. +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2020 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include +#include +#include +#include +#include "sockpp/tcp_connector.h" +#include "sockpp/version.h" + +using namespace std; +using namespace std::chrono; + +const size_t DFLT_N = 100000; +const size_t DFLT_SZ = 512; + +using fpsec = std::chrono::duration; + +// -------------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + cout << "Unix-domain echo timing test client for 'sockpp' " + << sockpp::SOCKPP_VERSION << '\n' << endl; + + string host = (argc > 1) ? argv[1] : "localhost"; + in_port_t port = (argc > 2) ? atoi(argv[2]) : 12345; + + size_t n = (argc > 3) ? size_t(atoll(argv[3])) : DFLT_N; + size_t sz = (argc > 4) ? size_t(atoll(argv[4])) : DFLT_SZ; + + sockpp::socket_initializer sockInit; + + auto t_start = high_resolution_clock::now(); + + + sockpp::tcp_connector conn({host, port}); + if (!conn) { + cerr << "Error connecting to server at " + << sockpp::inet_address(host, port) + << "\n\t" << conn.last_error_str() << endl; + return 1; + } + + cout << "Created a connection from " << conn.address() << endl; + cout << "Created a connection to " << conn.peer_address() << endl; + + // Set a timeout for the responses + if (!conn.read_timeout(seconds(2))) { + cerr << "Error setting timeout on TCP stream: " + << conn.last_error_str() << endl; + } + string s, sret; + + random_device rd; + mt19937 reng(rd()); + uniform_int_distribution dist(0, 25); + + for (size_t i=0; i(dist(reng))); + + auto t_start_tx = high_resolution_clock::now(); + + for (size_t i=0; i +#include +#include "sockpp/udp6_socket.h" +#include "sockpp/version.h" + +using namespace std; + +// -------------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + cout << "Sample IPv6 UDP echo client for 'sockpp' " + << sockpp::SOCKPP_VERSION << '\n' << endl; + + string host = (argc > 1) ? argv[1] : "localhost"; + in_port_t port = (argc > 2) ? atoi(argv[2]) : 12345; + + sockpp::socket_initializer sockInit; + + sockpp::udp6_socket sock; + + if (!sock.connect(sockpp::inet6_address(host, port))) { + cerr << "Error connecting to server at " << host << ":" << port + << "\n\t" << sock.last_error_str() << endl; + return 1; + } + + cout << "Created UDP socket at: " << sock.address() << endl; + + string s, sret; + while (getline(cin, s) && !s.empty()) { + if (sock.send(s) != ssize_t(s.length())) { + cerr << "Error writing to the UDP socket: " + << sock.last_error_str() << endl; + break; + } + + sret.resize(s.length()); + ssize_t n = sock.recv(&sret[0], s.length()); + + if (n != ssize_t(s.length())) { + cerr << "Error reading from UDP socket: " + << sock.last_error_str() << endl; + break; + } + + cout << sret << endl; + } + + return (!sock) ? 1 : 0; +} diff --git b/examples/udp/udpecho.cpp a/examples/udp/udpecho.cpp new file mode 100644 index 0000000..e3fd3e7 --- /dev/null +++ a/examples/udp/udpecho.cpp @@ -0,0 +1,89 @@ +// udpecho.cpp +// +// Simple UDP v4 echo client +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include +#include +#include "sockpp/udp_socket.h" +#include "sockpp/version.h" + +using namespace std; + +// -------------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + cout << "Sample UDP echo client for 'sockpp' " + << sockpp::SOCKPP_VERSION << '\n' << endl; + + string host = (argc > 1) ? argv[1] : "localhost"; + in_port_t port = (argc > 2) ? atoi(argv[2]) : 12345; + + sockpp::socket_initializer sockInit; + + sockpp::udp_socket sock; + + if (!sock.connect(sockpp::inet_address(host, port))) { + cerr << "Error connecting to server at " << host << ":" << port + << "\n\t" << sock.last_error_str() << endl; + return 1; + } + + cout << "Created UDP socket at: " << sock.address() << endl; + + string s, sret; + while (getline(cin, s) && !s.empty()) { + if (sock.send(s) != ssize_t(s.length())) { + cerr << "Error writing to the UDP socket: " + << sock.last_error_str() << endl; + break; + } + + sret.resize(s.length()); + ssize_t n = sock.recv(&sret[0], s.length()); + + if (n != ssize_t(s.length())) { + cerr << "Error reading from UDP socket: " + << sock.last_error_str() << endl; + break; + } + + cout << sret << endl; + } + + return (!sock) ? 1 : 0; +} diff --git b/examples/udp/udpechosvr.cpp a/examples/udp/udpechosvr.cpp new file mode 100644 index 0000000..b3cf3f6 --- /dev/null +++ a/examples/udp/udpechosvr.cpp @@ -0,0 +1,121 @@ +// udpechosvr.cpp +// +// A simple multi-threaded TCP/IP UDP echo server for sockpp library. +// +// This runs a UDP echo server for both IPv4 and IPv6, each in a separate +// thread. They both use the same port number, either as provided by the user +// on the command line, or defaulting to 12345. +// +// USAGE: +// uspechosvr [port] +// +// You can test with a netcat client, like: +// $ nc -u localhost 12345 # IPv4 +// $ nc -6u localhost 12345 # IPv6 +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include +#include +#include "sockpp/udp_socket.h" +#include "sockpp/udp6_socket.h" +#include "sockpp/version.h" + +using namespace std; + +// -------------------------------------------------------------------------- +// The thread function. This is run in a separate thread for each socket. +// Ownership of the socket object is transferred to the thread, so when this +// function exits, the socket is automatically closed. + +template +void run_echo(UDPSOCK sock) +{ + ssize_t n; + char buf[512]; + + // Each UDP socket type knows its address type as `addr_t` + typename UDPSOCK::addr_t srcAddr; + + // Read some data, also getting the address of the sender, + // then just send it back. + while ((n = sock.recv_from(buf, sizeof(buf), &srcAddr)) > 0) + sock.send_to(buf, n, srcAddr); +} + +// -------------------------------------------------------------------------- +// The main thread creates the two UDP sockets (one each for IPv4 and IPv6), +// and then starts them running the echo function each in a separate thread. + +int main(int argc, char* argv[]) +{ + cout << "Sample UDP echo server for 'sockpp' " + << sockpp::SOCKPP_VERSION << '\n' << endl; + + in_port_t port = (argc > 1) ? atoi(argv[1]) : 12345; + + sockpp::socket_initializer sockInit; + + sockpp::udp_socket udpsock; + if (!udpsock) { + cerr << "Error creating the UDP v4 socket: " << udpsock.last_error_str() << endl; + return 1; + } + + if (!udpsock.bind(sockpp::inet_address("localhost", port))) { + cerr << "Error binding the UDP v4 socket: " << udpsock.last_error_str() << endl; + return 1; + } + + sockpp::udp6_socket udp6sock; + if (!udp6sock) { + cerr << "Error creating the UDP v6 socket: " << udp6sock.last_error_str() << endl; + return 1; + } + + if (!udp6sock.bind(sockpp::inet6_address("localhost", port))) { + cerr << "Error binding the UDP v6 socket: " << udp6sock.last_error_str() << endl; + return 1; + } + + // Spin up a thread to run the IPv4 socket. + thread thr(run_echo, std::move(udpsock)); + thr.detach(); + + // Run the IPv6 socket in this thread. (Call doesn't return) + run_echo(std::move(udp6sock)); + return 0; +} + diff --git b/examples/unix/undgramecho.cpp a/examples/unix/undgramecho.cpp new file mode 100644 index 0000000..9fc4a17 --- /dev/null +++ a/examples/unix/undgramecho.cpp @@ -0,0 +1,97 @@ +// udpecho.cpp +// +// Simple Unix-domain UDP echo client +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include +#include +#include "sockpp/unix_dgram_socket.h" + +using namespace std; + +// -------------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + sockpp::socket_initializer sockInit; + + string cliAddr { "/tmp/undgramecho.sock" }, + svrAddr { "/tmp/undgramechosvr.sock" }; + + sockpp::unix_dgram_socket sock; + + // A Unix-domain UDP client needs to bind to its own address + // before it can send or receive packets + + if (!sock.bind(sockpp::unix_address(cliAddr))) { + cerr << "Error connecting to client address at '" << cliAddr << "'" + << "\n\t" << sock.last_error_str() << endl; + return 1; + } + + // "Connect" to the server address. This is a convenience to set the + // default 'send_to' address, as there is no real connection. + + if (!sock.connect(sockpp::unix_address(svrAddr))) { + cerr << "Error connecting to server at '" << svrAddr << "'" + << "\n\t" << sock.last_error_str() << endl; + return 1; + } + + cout << "Created UDP socket at: " << sock.address() << endl; + + string s, sret; + while (getline(cin, s) && !s.empty()) { + if (sock.send(s) != ssize_t(s.length())) { + cerr << "Error writing to the UDP socket: " + << sock.last_error_str() << endl; + break; + } + + sret.resize(s.length()); + ssize_t n = sock.recv(&sret[0], s.length()); + + if (n != ssize_t(s.length())) { + cerr << "Error reading from UDP socket: " + << sock.last_error_str() << endl; + break; + } + + cout << sret << endl; + } + + return (!sock) ? 1 : 0; +} diff --git b/examples/unix/undgramechosvr.cpp a/examples/unix/undgramechosvr.cpp new file mode 100644 index 0000000..fed3643 --- /dev/null +++ a/examples/unix/undgramechosvr.cpp @@ -0,0 +1,93 @@ +// undgramechosvr.cpp +// +// A simple multi-threaded TCP/IP UDP echo server for sockpp library. +// +// This runs a UDP echo server for both IPv4 and IPv6, each in a separate +// thread. They both use the same port number, either as provided by the user +// on the command line, or defaulting to 12345. +// +// USAGE: +// undgramechosvr [port] +// +// You can test with a netcat client, like: +// $ nc -u localhost 12345 # IPv4 +// $ nc -6u localhost 12345 # IPv6 +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include +#include "sockpp/unix_dgram_socket.h" +#include "sockpp/version.h" + +using namespace std; + +// -------------------------------------------------------------------------- +// The main thread creates the UDP socket, and then starts them running the +// echo service in a loop. + +int main(int argc, char* argv[]) +{ + cout << "Sample Unix-domain datagram echo server for 'sockpp' " + << sockpp::SOCKPP_VERSION << '\n' << endl; + + sockpp::socket_initializer sockInit; + + sockpp::unix_dgram_socket sock; + if (!sock) { + cerr << "Error creating the socket: " << sock.last_error_str() << endl; + return 1; + } + + if (!sock.bind(sockpp::unix_address("/tmp/undgramechosvr.sock"))) { + cerr << "Error binding the socket: " << sock.last_error_str() << endl; + return 1; + } + + // Run the socket in this thread. + ssize_t n; + char buf[512]; + + sockpp::unix_address srcAddr; + + cout << "Awaiting packets on: '" << sock.address() << "'" << endl; + + // Read some data, also getting the address of the sender, + // then just send it back. + while ((n = sock.recv_from(buf, sizeof(buf), &srcAddr)) > 0) + sock.send_to(buf, n, srcAddr); + + return 0; +} + diff --git b/examples/unix/unecho.cpp a/examples/unix/unecho.cpp new file mode 100644 index 0000000..5d1661c --- /dev/null +++ a/examples/unix/unecho.cpp @@ -0,0 +1,87 @@ +// unecho.cpp +// +// Simple Unix-domain echo client +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2014-2017 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include +#include +#include "sockpp/unix_connector.h" +#include "sockpp/version.h" + +using namespace std; + +// -------------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + cout << "Sample Unix-domain echo client for 'sockpp' " + << sockpp::SOCKPP_VERSION << '\n' << endl; + + string path = (argc > 1) ? argv[1] : "/tmp/unechosvr.sock"; + + sockpp::socket_initializer sockInit; + + sockpp::unix_connector conn; + + bool ok = conn.connect(sockpp::unix_address(path)); + if (!ok) { + cerr << "Error connecting to UNIX socket at " << path + << "\n\t" << conn.last_error_str() << endl; + return 1; + } + + cout << "Created a connection to '" << conn.peer_address() << "'" << endl; + + string s, sret; + while (getline(cin, s) && !s.empty()) { + if (conn.write(s) != (int) s.length()) { + cerr << "Error writing to the UNIX stream" << endl; + break; + } + + sret.resize(s.length()); + int n = conn.read_n(&sret[0], s.length()); + + if (n != (int) s.length()) { + cerr << "Error reading from UNIX stream" << endl; + break; + } + + cout << sret << endl; + } + + return (!conn) ? 1 : 0; +} diff --git b/examples/unix/unechosvr.cpp a/examples/unix/unechosvr.cpp new file mode 100644 index 0000000..ba5448b --- /dev/null +++ a/examples/unix/unechosvr.cpp @@ -0,0 +1,111 @@ +// unechosvr.cpp +// +// A multi-threaded UNIX-domain echo server for sockpp library. +// This is a simple thread-per-connection UNIX server. +// +// USAGE: +// unechosvr [path] +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2014-2017 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include +#include +#include "sockpp/unix_acceptor.h" +#include "sockpp/version.h" + +using namespace std; + +// -------------------------------------------------------------------------- +// The thread function. This is run in a separate thread for each socket. +// Ownership of the socket object is transferred to the thread, so when this +// function exits, the socket is automatically closed. + +void run_echo(sockpp::unix_socket sock) +{ + int n; + char buf[512]; + + while ((n = sock.read(buf, sizeof(buf))) > 0) + sock.write_n(buf, n); + + cout << "Connection closed" << endl; +} + +// -------------------------------------------------------------------------- +// The main thread runs the UNIX acceptor. +// Each time a connection is made, a new thread is spawned to handle it, +// leaving this main thread to immediately wait for the next connection. + +int main(int argc, char* argv[]) +{ + cout << "Sample Unix-domain echo server for 'sockpp' " + << sockpp::SOCKPP_VERSION << '\n' << endl; + + string path = "/tmp/unechosvr.sock"; + + if (argc > 1) { + path = argv[1]; + } + + sockpp::socket_initializer sockInit; + sockpp::unix_acceptor acc; + + bool ok = acc.open(sockpp::unix_address(path)); + + if (!ok) { + cerr << "Error creating the acceptor: " << acc.last_error_str() << endl; + return 1; + } + cout << "Acceptor bound to address: '" << acc.address() << "'..." << endl; + + while (true) { + // Accept a new client connection + auto sock = acc.accept(); + cout << "Received a connection" << endl; + + if (!sock) { + cerr << "Error accepting incoming connection: " + << acc.last_error_str() << endl; + } + else { + // Create a thread and transfer the new stream to it. + thread thr(run_echo, std::move(sock)); + thr.detach(); + } + } + + return 0; +} + diff --git b/examples/unix/unechotest.cpp a/examples/unix/unechotest.cpp new file mode 100644 index 0000000..a0c1d98 --- /dev/null +++ a/examples/unix/unechotest.cpp @@ -0,0 +1,114 @@ +// unechotest.cpp +// +// Unix-domain echo timing test client +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2020 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include +#include +#include +#include +#include "sockpp/unix_connector.h" +#include "sockpp/version.h" + +using namespace std; +using namespace std::chrono; + +const size_t DFLT_N = 100000; +const size_t DFLT_SZ = 512; + +using fpsec = std::chrono::duration; + +// -------------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + cout << "Unix-domain echo timing test client for 'sockpp' " + << sockpp::SOCKPP_VERSION << '\n' << endl; + + string path = (argc > 1) ? argv[1] : "/tmp/unechosvr.sock"; + size_t n = (argc > 2) ? size_t(atoll(argv[2])) : DFLT_N; + size_t sz = (argc > 3) ? size_t(atoll(argv[3])) : DFLT_SZ; + + sockpp::socket_initializer sockInit; + + auto t_start = high_resolution_clock::now(); + sockpp::unix_connector conn; + + bool ok = conn.connect(sockpp::unix_address(path)); + if (!ok) { + cerr << "Error connecting to UNIX socket at " << path + << "\n\t" << conn.last_error_str() << endl; + return 1; + } + + cout << "Created a connection to '" << conn.peer_address() << "'" << endl; + + string s, sret; + + random_device rd; + mt19937 reng(rd()); + uniform_int_distribution dist(0, 25); + + for (size_t i=0; i; + */ +template +class acceptor_tmpl : public acceptor +{ + /** The base class */ + using base = acceptor; + + // Non-copyable + acceptor_tmpl(const acceptor_tmpl&) =delete; + acceptor_tmpl& operator=(const acceptor_tmpl&) =delete; + +public: + /** The type of streaming socket from the acceptor. */ + using stream_sock_t = STREAM_SOCK; + /** The type of address for the acceptor and streams. */ + using addr_t = ADDR; + + /** + * Creates an unconnected acceptor. + */ + acceptor_tmpl() {} + /** + * Creates a acceptor and starts it listening on the specified address. + * @param addr The TCP address on which to listen. + * @param queSize The listener queue size. + */ + acceptor_tmpl(const addr_t& addr, int queSize=DFLT_QUE_SIZE) { + open(addr, queSize); + } + /** + * Creates a acceptor and starts it listening on the specified port. + * The acceptor binds to the specified port for any address on the local + * host. + * @param port The TCP port on which to listen. + * @param queSize The listener queue size. + */ + acceptor_tmpl(in_port_t port, int queSize=DFLT_QUE_SIZE) { + open(port, queSize); + } + /** + * Move constructor. + * Creates an acceptor by moving the other acceptor to this one. + * @param acc Another acceptor + */ + acceptor_tmpl(acceptor_tmpl&& acc) : base(std::move(acc)) {} + /** + * Creates an unbound acceptor socket with an open OS socket handle. + * An application would need to manually bind and listen to this + * acceptor to get incoming connections. + * @return An open, but unbound acceptor socket. + */ + static acceptor_tmpl create() { + return base::create(addr_t::ADDRESS_FAMILY); + } + /** + * Move assignment. + * @param rhs The other socket to move into this one. + * @return A reference to this object. + */ + acceptor_tmpl& operator=(acceptor_tmpl&& rhs) { + base::operator=(std::move(rhs)); + return *this; + } + /** + * Gets the local address to which we are bound. + * @return The local address to which we are bound. + */ + addr_t address() const { return addr_t(base::address()); } + /** + * Binds the socket to the specified address. + * @param addr The address to which we get bound. + * @return @em true on success, @em false on error + */ + bool bind(const addr_t& addr) { return base::bind(addr); } + /** + * Opens the acceptor socket, binds it to the specified address, and starts + * listening. + * @param addr The address to which this server should be bound. + * @param queSize The listener queue size. + * @return @em true on success, @em false on error + */ + bool open(const addr_t& addr, int queSize=DFLT_QUE_SIZE) { + return base::open(addr, queSize); + } + /** + * Opens the acceptor socket, binds the socket to all adapters and starts it + * listening. + * @param port The TCP port on which to listen. + * @param queSize The listener queue size. + * @return @em true on success, @em false on error + */ + bool open(in_port_t port, int queSize=DFLT_QUE_SIZE) { + return open(addr_t(port), queSize); + } + /** + * Accepts an incoming connection and gets the address of the client. + * @param clientAddr Pointer to the variable that will get the + * address of a client when it connects. + * @return A tcp_socket to the remote client. + */ + stream_sock_t accept(addr_t* clientAddr=nullptr) { + return stream_sock_t(base::accept(clientAddr)); + } +}; + +} // End namespace socket-cpp +} // End namespace components +} // End namespace osdev + diff --git b/include/socket-cpp/can_address.h a/include/socket-cpp/can_address.h new file mode 100644 index 0000000..18e7375 --- /dev/null +++ a/include/socket-cpp/can_address.h @@ -0,0 +1,151 @@ +#pragma once + +#include "socket-cpp/platform.h" +#include "socket-cpp/sock_address.h" +#include +#include +#include +#include + +#include + +namespace osdev { +namespace components { +namespace socket-cpp { + +/** + * Class that represents a Linux SocketCAN address. + * This inherits from the CAN form of a socket address, @em sockaddr_can. + */ +class can_address : public sock_address +{ + /** The underlying C struct for SocketCAN addresses */ + sockaddr_can addr_; + + /** The size of the underlying address struct, in bytes */ + static constexpr size_t SZ = sizeof(sockaddr_can); + +public: + /** The address family for this type of address */ + static constexpr sa_family_t ADDRESS_FAMILY = AF_CAN; + + /** Iface to use to indicate binding to all interfaces */ + static const unsigned ALL_IFACE = 0; + + /** + * Constructs an empty address. + * The address is initialized to all zeroes. + */ + can_address() : addr_() {} + /** + * Creates an address for binding to a specific CAN interface + * @param ifindex The interface index to use. This must, obviously, be + * an index to a CAN interface. + */ + explicit can_address(unsigned ifindex); + /** + * Constructs an address for the specified CAN interface. + * The interface might be "can0", "can1", "vcan0", etc. + * @param iface The name of the CAN interface + */ + can_address(const std::string& iface); + /** + * Constructs the address by copying the specified structure. + * @param addr The generic address + * @throws std::invalid_argument if the address is not a SocketCAN + * address (i.e. family is not AF_CAN) + */ + explicit can_address(const sockaddr& addr); + /** + * Constructs the address by copying the specified structure. + * @param addr The other address + */ + can_address(const sock_address& addr) { + std::memcpy(&addr_, addr.sockaddr_ptr(), SZ); + } + /** + * Constructs the address by copying the specified structure. + * @param addr The other address + * @throws std::invalid_argument if the address is not properly + * initialized as a SocketCAN address (i.e. family is not + * AF_CAN) + */ + can_address(const sockaddr_can& addr) : addr_(addr) {} + /** + * Constructs the address by copying the specified address. + * @param addr The other address + */ + can_address(const can_address& addr) : addr_(addr.addr_) {} + /** + * Checks if the address is set to some value. + * This doesn't attempt to determine if the address is valid, simply + * that it's not all zero. + * @return @em true if the address has been set, @em false otherwise. + */ + bool is_set() const { return family() != AF_UNSPEC; } + /** + * Gets the name of the CAN interface to which this address refers. + * @return The name of the CAN interface to which this address refers. + */ + std::string iface() const; + /** + * Gets the size of the address structure. + * Note: In this implementation, this should return sizeof(this) but + * more convenient in some places, and the implementation might change + * in the future, so it might be more compatible with future revisions + * to use this call. + * @return The size of the address structure. + */ + socklen_t size() const override { return socklen_t(SZ); } + + // TODO: Do we need a: + // create(iface) + // to mimic the inet_address behavior? + + /** + * Gets a pointer to this object cast to a const @em sockaddr. + * @return A pointer to this object cast to a const @em sockaddr. + */ + const sockaddr* sockaddr_ptr() const override { + return reinterpret_cast(&addr_); + } + /** + * Gets a pointer to this object cast to a @em sockaddr. + * @return A pointer to this object cast to a @em sockaddr. + */ + sockaddr* sockaddr_ptr() override { + return reinterpret_cast(&addr_); + } + /** + * Gets a const pointer to this object cast to a @em sockaddr_can. + * @return const sockaddr_can pointer to this object. + */ + const sockaddr_can* sockaddr_can_ptr() const { return &addr_; } + /** + * Gets a pointer to this object cast to a @em sockaddr_can. + * @return sockaddr_can pointer to this object. + */ + sockaddr_can* sockaddr_can_ptr() { return &addr_; } + /** + * Gets a printable string for the address. + * @return A string representation of the address in the form + * "unix:" + */ + std::string to_string() const { + return std::string("can:") + iface(); + } +}; + +// -------------------------------------------------------------------------- + +/** + * Stream inserter for the address. + * @param os The output stream + * @param addr The address + * @return A reference to the output stream. + */ +std::ostream& operator<<(std::ostream& os, const can_address& addr); + +} // End namespace socket-cpp +} // End namespace components +} // End namespace osdev diff --git b/include/socket-cpp/can_frame.h a/include/socket-cpp/can_frame.h new file mode 100644 index 0000000..eb480b1 --- /dev/null +++ a/include/socket-cpp/can_frame.h @@ -0,0 +1,53 @@ +#pragma once + +#include "socket-cpp/platform.h" +#include +#include +//#include +#include + +namespace osdev { +namespace components { +namespace socket-cpp { + +/** + * Class that represents a Linux SocketCAN frame. + * This inherits from the Linux CAN frame struct, just providing easier + construction. + */ +class can_frame : public ::can_frame +{ + using base = ::can_frame; + + /** The size of the underlying address struct, in bytes */ + static constexpr size_t SZ = sizeof(::can_frame); + +public: + /** + * Constructs an empty frame. + * The frame is initialized to all zeroes. + */ + can_frame() : base{} {} + /** + * Constructs a frame with the specified ID and data. + * @param canID The CAN identifier for the frame + * @param data The data field for the frame + */ + can_frame(canid_t canID, const std::string& data) + : can_frame{ canID, data.data(), data.length() } {} + /** + * Constructs a frame with the specified ID and data. + * @param canID The CAN identifier for the frame + * @param data The data field for the frame + * @param n The number of bytes in the data field + */ + can_frame(canid_t canID, const void* data, size_t n) : base{} { + this->can_id = canID; + this->can_dlc = n; + ::memcpy(&this->data, data, n); + } +}; + +} // End namespace socket-cpp +} // End namespace components +} // End namespace osdev diff --git b/include/socket-cpp/can_socket.h a/include/socket-cpp/can_socket.h new file mode 100644 index 0000000..937c72b --- /dev/null +++ a/include/socket-cpp/can_socket.h @@ -0,0 +1,156 @@ +#pragma once + +#include "socket-cpp/datagram_socket.h" +#include "socket-cpp/can_address.h" +#include "socket-cpp/can_frame.h" + +namespace osdev { +namespace components { +namespace socket-cpp { + +/** + * Socket type for Linux SocketCAN. + * + * Note that technically these are RAW sockets, not DGRAM. We can/should + * organize the underlying hierarch to properly indicate this, but for + * practical usge, it doesn't matter too MUCH. + * The BCM CAN sockets are DGRAM sockets, but those aren't implemented yet. + * It wouldn't take too much to add them, though. + */ +class can_socket : public datagram_socket +{ + /** The base class */ + using base = datagram_socket; + + // Non-copyable + can_socket(const can_socket&) =delete; + can_socket& operator=(const can_socket&) =delete; + + /** + * We can't connect to a raw CAN socket; + * we can only bind the address/iface. + */ + bool connect(const sock_address&) =delete; + +protected: + static socket_t create_handle(int type, int protocol) { + return socket_t(::socket(PROTOCOL_FAMILY, type, protocol)); + } + +public: + /** + * The SocketCAN protocol family. + * Note that AF_CAN == PF_CAN, which is used in many of the CAN + * examples. + */ + static const int PROTOCOL_FAMILY = AF_CAN; + + /** The socket 'type' for communications semantics. */ + static constexpr int COMM_TYPE = SOCK_RAW; + + /** + * Creates an uninitialized CAN socket. + */ + can_socket() {} + /** + * Creates a CAN socket from an existing OS socket handle and + * claims ownership of the handle. + * @param handle A socket handle from the operating system. + */ + explicit can_socket(socket_t handle) : base(handle) {} + /** + * Creates a CAN socket and binds it to the address. + * @param addr The address to bind. + */ + explicit can_socket(const can_address& addr); + /** + * Move constructor. + * @param other The other socket to move to this one + */ + can_socket(can_socket&& other) : base(std::move(other)) {} + /** + * Move assignment. + * @param rhs The other socket to move into this one. + * @return A reference to this object. + */ + can_socket& operator=(can_socket&& rhs) { + base::operator=(std::move(rhs)); + return *this; + } + + /** + * Gets the system time of the last frame read from the socket. + * @return The system time of the last frame read from the socket with + * microsecond precision. + */ + std::chrono::system_clock::time_point last_frame_time(); + /** + * Gets a floating point timestamp of the last frame read from the + * socket. + * This is the number of seconds since the Unix epoch (time_t), with + * floating-point, microsecond precision. + * @return A floating-point timestamp with microsecond precision. + */ + double last_frame_timestamp(); + + // ----- I/O ----- + + /** + * Sends a frame to the CAN interfacce at the specified address. + * @param frame The CAN frame to send. + * @param flags The flags. See send(2). + * @param addr The remote destination of the data. + * @return the number of bytes sent on success or, @em -1 on failure. + */ + ssize_t send_to(const can_frame& frame, int flags, const can_address& addr) { + return check_ret( + ::sendto(handle(), &frame, sizeof(can_frame), flags, + addr.sockaddr_ptr(), addr.size()) + ); + } + + /** + * Sends a frame to the CAN interface at the specified address. + * @param frame The CAN frame to send. + * @param addr The remote destination of the data. + * @return the number of bytes sent on success or, @em -1 on failure. + */ + ssize_t send_to(const can_frame& frame, const can_address& addr) { + return check_ret( + ::sendto(handle(), &frame, sizeof(can_frame), 0, + addr.sockaddr_ptr(), addr.size()) + ); + } + /** + * Sends a frame to the CAN bus. + * The socket should be bound before calling this. + * @param frame The CAN frame to send. + * @param flags The option bit flags. See send(2). + * @return @em zero on success, @em -1 on failure. + */ + ssize_t send(const can_frame& frame, int flags=0) { + return check_ret(::send(handle(), &frame, sizeof(can_frame), flags)); + } + /** + * Receives a message from the CAN interface at the specified address. + * @param frame CAN frame to get the incoming data. + * @param flags The option bit flags. See send(2). + * @param srcAddr Receives the address of the peer that sent the + * message + * @return The number of bytes read or @em -1 on error. + */ + ssize_t recv_from(can_frame* frame, int flags, can_address* srcAddr=nullptr); + /** + * Receives a message on the socket. + * @param frame CAN frame to get the incoming data. + * @param flags The option bit flags. See send(2). + * @return The number of bytes read or @em -1 on error. + */ + ssize_t recv(can_frame* frame, int flags=0) { + return check_ret(::recv(handle(), frame, sizeof(can_frame), flags)); + } +}; + +} // End namespace socket-cpp +} // End namespace components +} // End namespace osdev diff --git b/include/socket-cpp/connector.h a/include/socket-cpp/connector.h new file mode 100644 index 0000000..d6a382a --- /dev/null +++ a/include/socket-cpp/connector.h @@ -0,0 +1,147 @@ +#pragma once + +#include "socket-cpp/stream_socket.h" +#include "socket-cpp/sock_address.h" + +namespace osdev { +namespace components { +namespace socket-cpp { + +/** + * Class to create a client stream connection. + * This is a base class for creating active, streaming sockets that initiate + * connections to a server. It can be used to derive classes that implement + * TCP on IPv4 or IPv6. + */ +class connector : public stream_socket +{ + /** The base class */ + using base = stream_socket; + + // Non-copyable + connector(const connector&) =delete; + connector& operator=(const connector&) =delete; + +public: + /** + * Creates an unconnected connector. + */ + connector() {} + /** + * Creates the connector and attempts to connect to the specified + * address. + * @param addr The remote server address. + */ + connector(const sock_address& addr) { connect(addr); } + /** + * Move constructor. + * Creates a connector by moving the other connector to this one. + * @param conn Another connector. + */ + connector(connector&& conn) : base(std::move(conn)) {} + /** + * Move assignment. + * @param rhs The other connector to move into this one. + * @return A reference to this object. + */ + connector& operator=(connector&& rhs) { + base::operator=(std::move(rhs)); + return *this; + } + /** + * Determines if the socket connected to a remote host. + * Note that this is not a reliable determination if the socket is + * currently connected, but rather that an initial connection was + * established. + * @return @em true If the socket connected to a remote host, + * @em false if not. + */ + bool is_connected() const { return is_open(); } + /** + * Attempts to connect to the specified server. + * If the socket is currently connected, this will close the current + * connection and open the new one. + * @param addr The remote server address. + * @return @em true on success, @em false on error + */ + bool connect(const sock_address& addr); +}; + +/** + * Class to create a client TCP connection. + */ +template +class connector_tmpl : public connector +{ + /** The base class */ + using base = connector; + + // Non-copyable + connector_tmpl(const connector_tmpl&) =delete; + connector_tmpl& operator=(const connector_tmpl&) =delete; + +public: + /** The type of streaming socket from the acceptor. */ + using stream_sock_t = STREAM_SOCK; + /** The type of address for the connector. */ + using addr_t = ADDR; + + /** + * Creates an unconnected connector. + */ + connector_tmpl() {} + /** + * Creates the connector and attempts to connect to the specified + * address. + * @param addr The remote server address. + */ + connector_tmpl(const addr_t& addr) : base(addr) {} + /** + * Move constructor. + * Creates a connector by moving the other connector to this one. + * @param conn Another connector. + */ + connector_tmpl(connector_tmpl&& rhs) : base(std::move(rhs)) {} + /** + * Move assignment. + * @param rhs The other connector to move into this one. + * @return A reference to this object. + */ + connector_tmpl& operator=(connector_tmpl&& rhs) { + base::operator=(std::move(rhs)); + return *this; + } + /** + * Gets the local address to which the socket is bound. + * @return The local address to which the socket is bound. + * @throw sys_error on error + */ + addr_t address() const { return addr_t(base::address()); } + /** + * Gets the address of the remote peer, if this socket is connected. + * @return The address of the remote peer, if this socket is connected. + * @throw sys_error on error + */ + addr_t peer_address() const { return addr_t(base::peer_address()); } + /** + * Binds the socket to the specified address. + * This call is optional for a client connector, though it is rarely + * used. + * @param addr The address to which we get bound. + * @return @em true on success, @em false on error + */ + bool bind(const addr_t& addr) { return base::bind(addr); } + /** + * Attempts to connects to the specified server. + * If the socket is currently connected, this will close the current + * connection and open the new one. + * @param addr The remote server address. + * @return @em true on success, @em false on error + */ + bool connect(const addr_t& addr) { return base::connect(addr); } +}; + +} // End namespace socket-cpp +} // End namespace components +} // End namespace osdev + diff --git b/include/socket-cpp/datagram_socket.h a/include/socket-cpp/datagram_socket.h new file mode 100644 index 0000000..f57704b --- /dev/null +++ a/include/socket-cpp/datagram_socket.h @@ -0,0 +1,350 @@ +#pragma once + +#include "socket-cpp/socket.h" + +namespace osdev { +namespace components { +namespace socket-cpp { + +/** + * Base class for datagram sockets. + * + * Datagram sockets are normally connectionless, where each packet is + * individually routed and delivered. + */ +class datagram_socket : public socket +{ + /** The base class */ + using base = socket; + + // Non-copyable + datagram_socket(const datagram_socket&) =delete; + datagram_socket& operator=(const datagram_socket&) =delete; + +protected: + static socket_t create_handle(int domain, int protocol=0) { + return socket_t(::socket(domain, COMM_TYPE, protocol)); + } + + static socket_t create_handle(int domain, int type, int protocol) { + return socket_t(::socket(domain, type, protocol)); + } + +public: + /** The socket 'type' for communications semantics. */ + static constexpr int COMM_TYPE = SOCK_DGRAM; + + /** + * Creates an uninitialized datagram socket. + */ + datagram_socket() {} + /** + * Creates a datagram socket from an existing OS socket handle and + * claims ownership of the handle. + * @param handle A socket handle from the operating system. + */ + explicit datagram_socket(socket_t handle) : base(handle) {} + /** + * Creates a UDP socket and binds it to the address. + * @param addr The address to bind. + */ + explicit datagram_socket(const sock_address& addr); + /** + * Move constructor. + * @param other The other socket to move to this one + */ + datagram_socket(datagram_socket&& other) + : base(std::move(other)) {} + /** + * Move assignment. + * @param rhs The other socket to move into this one. + * @return A reference to this object. + */ + datagram_socket& operator=(datagram_socket&& rhs) { + base::operator=(std::move(rhs)); + return *this; + } + /** + * Connects the socket to the remote address. + * In the case of datagram sockets, this does not create an actual + * connection, but rather specifies the address to which datagrams are + * sent by default and the only address from which packets are + * received. + * @param addr The address on which to "connect". + * @return @em true on success, @em false on failure + */ + bool connect(const sock_address& addr) { + return check_ret_bool(::connect(handle(), addr.sockaddr_ptr(), + addr.size())); + } + + // ----- I/O ----- + + /** + * Sends a message to the socket at the specified address. + * @param buf The data to send. + * @param n The number of bytes in the data buffer. + * @param flags The flags. See send(2). + * @param addr The remote destination of the data. + * @return the number of bytes sent on success or, @em -1 on failure. + */ + ssize_t send_to(const void* buf, size_t n, int flags, const sock_address& addr) { + #if defined(_WIN32) + return check_ret(::sendto(handle(), reinterpret_cast(buf), int(n), + flags, addr.sockaddr_ptr(), addr.size())); + #else + return check_ret(::sendto(handle(), buf, n, flags, + addr.sockaddr_ptr(), addr.size())); + #endif + } + /** + * Sends a string to the socket at the specified address. + * @param s The string to send. + * @param flags The flags. See send(2). + * @param addr The remote destination of the data. + * @return the number of bytes sent on success or, @em -1 on failure. + */ + ssize_t send_to(const std::string& s, int flags, const sock_address& addr) { + return send_to(s.data(), s.length(), flags, addr); + } + /** + * Sends a message to another socket. + * @param buf The data to send. + * @param n The number of bytes in the data buffer. + * @param addr The remote destination of the data. + * @return the number of bytes sent on success or, @em -1 on failure. + */ + ssize_t send_to(const void* buf, size_t n, const sock_address& addr) { + return send_to(buf, n, 0, addr); + } + /** + * Sends a string to another socket. + * @param s The string to send. + * @param addr The remote destination of the data. + * @return the number of bytes sent on success or, @em -1 on failure. + */ + ssize_t send_to(const std::string& s, const sock_address& addr) { + return send_to(s.data(), s.length(), 0, addr); + } + /** + * Sends a message to the socket at the default address. + * The socket should be connected before calling this. + * @param buf The date to send. + * @param n The number of bytes in the data buffer. + * @param flags The option bit flags. See send(2). + * @return @em zero on success, @em -1 on failure. + */ + ssize_t send(const void* buf, size_t n, int flags=0) { + #if defined(_WIN32) + return check_ret(::send(handle(), reinterpret_cast(buf), + int(n), flags)); + #else + return check_ret(::send(handle(), buf, n, flags)); + #endif + } + /** + * Sends a string to the socket at the default address. + * The socket should be connected before calling this + * @param s The string to send. + * @param flags The option bit flags. See send(2). + * @return @em zero on success, @em -1 on failure. + */ + ssize_t send(const std::string& s, int flags=0) { + return send(s.data(), s.length(), flags); + } + /** + * Receives a message on the socket. + * @param buf Buffer to get the incoming data. + * @param n The number of bytes to read. + * @param flags The option bit flags. See send(2). + * @param srcAddr Receives the address of the peer that sent the + * message + * @return The number of bytes read or @em -1 on error. + */ + ssize_t recv_from(void* buf, size_t n, int flags, sock_address* srcAddr=nullptr); + /** + * Receives a message on the socket. + * @param buf Buffer to get the incoming data. + * @param n The number of bytes to read. + * @param srcAddr Receives the address of the peer that sent the + * message + * @return The number of bytes read or @em -1 on error. + */ + ssize_t recv_from(void* buf, size_t n, sock_address* srcAddr=nullptr) { + return recv_from(buf, n, 0, srcAddr); + } + /** + * Receives a message on the socket. + * @param buf Buffer to get the incoming data. + * @param n The number of bytes to read. + * @param flags The option bit flags. See send(2). + * @return The number of bytes read or @em -1 on error. + */ + ssize_t recv(void* buf, size_t n, int flags=0) { + #if defined(_WIN32) + return check_ret(::recv(handle(), reinterpret_cast(buf), + int(n), flags)); + #else + return check_ret(::recv(handle(), buf, n, flags)); + #endif + } +}; + +/** + * Base class for datagram sockets. + * + * Datagram sockets are normally connectionless, where each packet is + * individually routed and delivered. + */ +template +class datagram_socket_tmpl : public datagram_socket +{ + /** The base class */ + using base = datagram_socket; + +public: + /** The address family for this type of address */ + static constexpr sa_family_t ADDRESS_FAMILY = ADDR::ADDRESS_FAMILY; + /** The type of address for the socket. */ + using addr_t = ADDR; + + /** + * Creates an unbound datagram socket. + * This can be used as a client or later bound as a server socket. + */ + datagram_socket_tmpl() : base(create_handle(ADDRESS_FAMILY)) {} + /** + * Creates a datagram socket from an existing OS socket handle and + * claims ownership of the handle. + * @param handle A socket handle from the operating system. + */ + datagram_socket_tmpl(socket_t handle) : base(handle) {} + /** + * Creates a datagram socket and binds it to the address. + * @param addr The address to bind. + */ + datagram_socket_tmpl(const ADDR& addr) : base(addr) {} + /** + * Move constructor. + * @param other The other socket to move to this one + */ + datagram_socket_tmpl(datagram_socket_tmpl&& other) + : base(std::move(other)) {} + /** + * Move assignment. + * @param rhs The other socket to move into this one. + * @return A reference to this object. + */ + datagram_socket_tmpl& operator=(datagram_socket_tmpl&& rhs) { + base::operator=(std::move(rhs)); + return *this; + } + + /** + * Creates a pair of connected stream sockets. + * + * Whether this will work at all is highly system and domain dependent. + * Currently it is only known to work for Unix-domain sockets on *nix + * systems. + * + * @param protocol The protocol to be used with the socket. (Normally 0) + * + * @return A std::tuple of stream sockets. On error both sockets will be + * in an error state with the last error set. + */ + static std::tuple pair(int protocol=0) { + auto pr = base::pair(addr_t::ADDRESS_FAMILY, COMM_TYPE, protocol); + return std::make_tuple( + datagram_socket_tmpl{std::get<0>(pr).release()}, + datagram_socket_tmpl{std::get<1>(pr).release()}); + } + /** + * Binds the socket to the local address. + * Datagram sockets can bind to a local address/adapter to filter which + * incoming packets to receive. + * @param addr The address on which to bind. + * @return @em true on success, @em false on failure + */ + bool bind(const ADDR& addr) { return base::bind(addr); } + /** + * Connects the socket to the remote address. + * In the case of datagram sockets, this does not create an actual + * connection, but rather specifies the address to which datagrams are + * sent by default and the only address from which packets are + * received. + * @param addr The address on which to "connect". + * @return @em true on success, @em false on failure + */ + bool connect(const ADDR& addr) { return base::connect(addr); } + + // ----- I/O ----- + + /** + * Sends a message to the socket at the specified address. + * @param buf The data to send. + * @param n The number of bytes in the data buffer. + * @param flags The option bit flags. See send(2). + * @param addr The remote destination of the data. + * @return the number of bytes sent on success or, @em -1 on failure. + */ + ssize_t send_to(const void* buf, size_t n, int flags, const ADDR& addr) { + return base::send_to(buf, n, flags, addr); + } + /** + * Sends a string to the socket at the specified address. + * @param s The string to send. + * @param flags The flags. See send(2). + * @param addr The remote destination of the data. + * @return the number of bytes sent on success or, @em -1 on failure. + */ + ssize_t send_to(const std::string& s, int flags, const ADDR& addr) { + return base::send_to(s, flags, addr); + } + /** + * Sends a message to another socket. + * @param buf The data to send. + * @param n The number of bytes in the data buffer. + * @param addr The remote destination of the data. + * @return the number of bytes sent on success or, @em -1 on failure. + */ + ssize_t send_to(const void* buf, size_t n, const ADDR& addr) { + return base::send_to(buf, n, 0, addr); + } + /** + * Sends a string to another socket. + * @param s The string to send. + * @param addr The remote destination of the data. + * @return the number of bytes sent on success or, @em -1 on failure. + */ + ssize_t send_to(const std::string& s, const ADDR& addr) { + return base::send_to(s, addr); + } + /** + * Receives a message on the socket. + * @param buf Buffer to get the incoming data. + * @param n The number of bytes to read. + * @param flags The option bit flags. See send(2). + * @param srcAddr Receives the address of the peer that sent + * the message + * @return The number of bytes read or @em -1 on error. + */ + ssize_t recv_from(void* buf, size_t n, int flags, ADDR* srcAddr) { + return base::recv_from(buf, n, flags, srcAddr); + } + /** + * Receives a message on the socket. + * @param buf Buffer to get the incoming data. + * @param n The number of bytes to read. + * @param srcAddr Receives the address of the peer that sent + * the message + * @return The number of bytes read or @em -1 on error. + */ + ssize_t recv_from(void* buf, size_t n, ADDR* srcAddr=nullptr) { + return base::recv_from(buf, n, srcAddr); + } +}; + +} // End namespace socket-cpp +} // End namespace components +} // End namespace osdev + diff --git b/include/socket-cpp/exception.h a/include/socket-cpp/exception.h new file mode 100644 index 0000000..8e24c18 --- /dev/null +++ a/include/socket-cpp/exception.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +namespace osdev { +namespace components { +namespace socket-cpp { + +/** + * System error. + * These are errors that are resulted from system socket calls. The error + * codes are platform 'errno' values (or similar), and the messages are + * typically derived from the system. + */ +class sys_error : public std::runtime_error +{ + /** The system error number (errno) */ + int errno_; + +public: + /** + * Creates an error using the current system 'errno' value. + */ + sys_error() : sys_error(errno) {} + /** + * Constructs an error with the specified system errno. + * @param err The error number. This is the system errno value. + */ + explicit sys_error(int err); + /** + * Get the error number. + * @return The system error number. + */ + int error() const { return errno_; } + /** + * Gets a string describing the specified error. + * This is typically the returned message from the system strerror(). + * @param err The system error number. + * @return A string describing the specified error. + */ + static std::string error_str(int err); +}; + +/** + * Errors from getaddrinfo. + * These are errors relating to DNS lookup, returned from the getaddrinfo system call. + * Their codes are declared in . + */ +class getaddrinfo_error : public std::runtime_error +{ + /** The error number, as returned by getaddrinfo. */ + int error_; + /** The hostname being resolved. */ + std::string hostname_; + +public: + /** + * Constructs an error with the specified getaddrinfo error code. + * @param err The error number, as returned by getaddrinfo. + * @param hostname The DNS name being resolved that triggered the error. + */ + getaddrinfo_error(int err, const std::string &hostname); + /** + * Get the error number. + * @return The error number returned by getaddrinfo. + */ + int error() const { return error_; } + /** + * Get the hostname that triggered the error. + * @return The hostname that getaddrinfo failed to resolve. + */ + const std::string &hostname() const { return hostname_; } +}; + +} // End namespace socket-cpp +} // End namespace components +} // End namespace osdev + diff --git b/include/socket-cpp/inet6_address.h a/include/socket-cpp/inet6_address.h new file mode 100644 index 0000000..c80fae5 --- /dev/null +++ a/include/socket-cpp/inet6_address.h @@ -0,0 +1,200 @@ +#pragma once + +#include "socket-cpp/platform.h" +#include "socket-cpp/sock_address.h" +#include +#include +#include + +namespace osdev { +namespace components { +namespace socket-cpp { + +/** + * Class that represents an internet (IPv6) address. This inherits from the + * IP-specific form of a socket address, @em sockaddr_in. + */ +class inet6_address : public sock_address +{ + /** The underlying C IPv6 struct */ + sockaddr_in6 addr_; + + /** The size of the underlying address struct, in bytes */ + static constexpr size_t SZ = sizeof(sockaddr_in6); + +public: + /** The address family for this type of address */ + static constexpr sa_family_t ADDRESS_FAMILY = AF_INET6; + + /** + * Constructs an empty address. + * The address is initialized to all zeroes. + */ + inet6_address() : addr_() {} + /** + * Constructs an address for any iface using the specified port. + * This is a convenient way for a server to specify an address that will + * bind to all interfaces. + * @param port The port number in native/host byte order. + */ + explicit inet6_address(in_port_t port) { + const in6_addr ANY IN6ADDR_ANY_INIT; + create(ANY, port); + } + /** + * Constructs an address using the name of the host and the specified + * port. This attempts to resolve the host name to an address. + * + * @param saddr The name of the host. + * @param port The port number in native/host byte order. + */ + inet6_address(const std::string& saddr, in_port_t port) { + create(saddr, port); + } + /** + * Constructs the address by copying the specified structure. + * TODO: Do we actually need a conversion from something that's + * cast to a sockaddr, but is really a sockaddr_in6? + * @param addr The other address + */ + inet6_address(const sockaddr_storage& addr) { + std::memcpy(&addr_, &addr, SZ); + } + /** + * Constructs the address by copying the specified structure. + * @param addr The other address + */ + inet6_address(const sock_address& addr) { + std::memcpy(&addr_, addr.sockaddr_ptr(), SZ); + } + /** + * Constructs the address by copying the specified structure. + * @param addr The other address + */ + inet6_address(const sockaddr_in6& addr) { + std::memcpy(&addr_, &addr, SZ); + } + /** + * Constructs the address by copying the specified address. + * @param addr The other address + */ + inet6_address(const inet6_address& addr) : addr_(addr.addr_) {} + /** + * Creates an address on the loopback (localhost) interface. + * @param port The port number (in native/host byte order). + * @return The full address on the loopback interface. + */ + static inet6_address loopback(in_port_t port) { + const in6_addr LOOPBACK IN6ADDR_LOOPBACK_INIT; + inet6_address addr; + addr.create(LOOPBACK, port); + return addr; + } + /** + * Checks if the address is set to some value. + * This doesn't attempt to determine if the address is valid, simply + * that it's not all zero. + * @return bool + */ + bool is_set() const; + /** + * Attempts to resolve the host name into a 32-bit internet address. + * @param saddr The string host name. + * @return The internet address in network byte order. + */ + static in6_addr resolve_name(const std::string& saddr); + /** + * Creates the socket address using the specified host address and port + * number. + * @param addr The host address (16-byte IPv6 address). + * @param port The host port number. + */ + void create(const in6_addr& addr, in_port_t port); + /** + * Creates the socket address using the specified host name and port + * number. + * @param saddr The string host name. + * @param port The port number in native/host byte order. + */ + void create(const std::string& saddr, in_port_t port); + /** + * Gets 128-bit IPv6 address. + * The address is usually stored in network byte order. + * @return The IPv6 address. + */ + in6_addr address() const { return addr_.sin6_addr; } + /** + * Gets a byte of the 128-bit IPv6 Address. + * Note that the address is normally stored in network byte + * order. + * @param i The byte to read (0-7) +{ + return addr_ != sockaddr_in6{}; + * @return The specified byte in the 128-bit IPv6 Address + */ + uint8_t operator[](int i) const { + return addr_.sin6_addr.s6_addr[i]; + } + /** + * Gets the port number. + * @return The port number in native/host byte order. + */ + in_port_t port() const { return ntohs(addr_.sin6_port); } + /** + * Gets the size of this structure. + * This is equivalent to sizeof(this) but more convenient in some + * places. + * @return The size of this structure. + */ + socklen_t size() const override { return socklen_t(SZ); } + /** + * Gets a pointer to this object cast to a @em sockaddr. + * @return A pointer to this object cast to a @em sockaddr. + */ + const sockaddr* sockaddr_ptr() const override { + return reinterpret_cast(&addr_); + } + /** + * Gets a pointer to this object cast to a @em sockaddr. + * @return A pointer to this object cast to a @em sockaddr. + */ + sockaddr* sockaddr_ptr() override { + return reinterpret_cast(&addr_); + } + /** + * Gets a const pointer to this object cast to a @em + * sockaddr_in6. + * @return const sockaddr_in6 pointer to this object. + */ + const sockaddr_in6* sockaddr_in6_ptr() const { return &addr_; } + /** + * Gets a pointer to this object cast to a @em sockaddr_in6. + * @return sockaddr_in6 pointer to this object. + */ + sockaddr_in6* sockaddr_in6_ptr() { return &addr_; } + /** + * Gets a printable string for the address. + * This gets the address in the printable form "[addr]:port" + * using inet_ntop(). It does not attempt to find the host name + * using a lookup. + * @return A string representation of the address in the form + * '[address]:port' + */ + std::string to_string() const; +}; + +// -------------------------------------------------------------------------- + +/** + * Stream inserter for the address. + * This uses the simple dot notation of the address as returned from + * inet_ntop(). It does not attempt a host lookup. + * @param os The output stream + * @param addr The address + * @return A reference to the output stream. + */ +std::ostream& operator<<(std::ostream& os, const inet6_address& addr); + +} // End namespace socket-cpp +} // End namespace components +} // End namespace osdev diff --git b/include/socket-cpp/inet_address.h a/include/socket-cpp/inet_address.h new file mode 100644 index 0000000..409c21a --- /dev/null +++ a/include/socket-cpp/inet_address.h @@ -0,0 +1,192 @@ +#pragma once + +#include "socket-cpp/sock_address.h" +#include +#include +#include +#include + +namespace osdev { +namespace components { +namespace socket-cpp { + +/** + * Class that represents an internet (IPv4) address. + * This inherits from the IP-specific form of a socket address, + * @em sockaddr_in. + */ +class inet_address : public sock_address +{ + /** The underlying C struct for IPv4 addresses */ + sockaddr_in addr_; + + /** The size of the underlying address struct, in bytes */ + static constexpr size_t SZ = sizeof(sockaddr_in); + +public: + /** The address family for this type of address */ + static constexpr sa_family_t ADDRESS_FAMILY = AF_INET; + + /** + * Constructs an empty address. + * The address is initialized to all zeroes. + */ + inet_address() : addr_() {} + /** + * Constructs an address for any iface using the specified port. + * This is a convenient way for a server to specify an address that will + * bind to all interfaces. + * @param port The port number in native/host byte order. + */ + explicit inet_address(in_port_t port) { + create(in_addr_t(INADDR_ANY), port); + } + /** + * Constructs an address for the specified host using the specified + * port. + * @param addr The 32-bit host address in native/host byte order. + * @param port The port number in native/host byte order. + */ + inet_address(uint32_t addr, in_port_t port) { create(addr, port); } + /** + * Constructs an address using the name of the host and the specified + * port. This attempts to resolve the host name to an address. + * + * @param saddr The name of the host. + * @param port The port number in native/host byte order. + */ + inet_address(const std::string& saddr, in_port_t port) { + create(saddr, port); + } + /** + * Constructs the address by copying the specified structure. + * @param addr The other address + */ + inet_address(const sockaddr& addr) { + std::memcpy(&addr_, &addr, SZ); + } + /** + * Constructs the address by copying the specified structure. + * @param addr The other address + */ + inet_address(const sock_address& addr) { + std::memcpy(&addr_, addr.sockaddr_ptr(), SZ); + } + /** + * Constructs the address by copying the specified structure. + * @param addr The other address + */ + inet_address(const sockaddr_in& addr) : addr_(addr) {} + /** + * Constructs the address by copying the specified address. + * @param addr The other address + */ + inet_address(const inet_address& addr) : addr_(addr.addr_) {} + /** + * Checks if the address is set to some value. + * This doesn't attempt to determine if the address is valid, simply + * that it's not all zero. + * @return bool + */ + bool is_set() const; + /** + * Attempts to resolve the host name into a 32-bit internet address. + * @param saddr The string host name. + * @return The internet address in network byte order. + * @throw sys_error, getaddrinfo_error + */ + static in_addr_t resolve_name(const std::string& saddr); + /** + * Creates the socket address using the specified host address and port + * number. + * @param addr The host address. + * @param port The host port number. + */ + void create(in_addr_t addr, in_port_t port); + /** + * Creates the socket address using the specified host name and port + * number. + * @param saddr The string host name. + * @param port The port number in native/host byte order. + * @throw sys_error, getaddrinfo_error + */ + void create(const std::string& saddr, in_port_t port); + /** + * Gets the 32-bit internet address. + * @return The internet address in the local host's byte order. + */ + in_addr_t address() const { return ntohl(addr_.sin_addr.s_addr); } + /** + * Gets a byte of the 32-bit Internet Address + * @param i The byte to read (0-3) + * @return The specified byte in the 32-bit Internet Address + */ + uint8_t operator[](int i) const { + in_addr_t addr = address(); + return ((const uint8_t*)&addr)[i]; + } + /** + * Gets the port number. + * @return The port number in native/host byte order. + */ + in_port_t port() const { return ntohs(addr_.sin_port); } + /** + * Gets the size of this structure. + * This is equivalent to sizeof(this) but more convenient in some + * places. + * @return The size of this structure. + */ + socklen_t size() const override { return socklen_t(SZ); } + /** + * Gets a pointer to this object cast to a @em sockaddr. + * @return A pointer to this object cast to a @em sockaddr. + */ + const sockaddr* sockaddr_ptr() const override { + return reinterpret_cast(&addr_); + } + /** + * Gets a pointer to this object cast to a @em sockaddr. + * @return A pointer to this object cast to a @em sockaddr. + */ + sockaddr* sockaddr_ptr() override { + return reinterpret_cast(&addr_); + } + /** + * Gets a const pointer to this object cast to a @em sockaddr_in. + * @return const sockaddr_in pointer to this object. + */ + const sockaddr_in* sockaddr_in_ptr() const { + return static_cast(&addr_); + } + /** + * Gets a pointer to this object cast to a @em sockaddr_in. + * @return sockaddr_in pointer to this object. + */ + sockaddr_in* sockaddr_in_ptr() { + return static_cast(&addr_); + } + /** + * Gets a printable string for the address. + * This gets the simple dot notation of the address as returned from + * inet_ntop(). It does not attempt a host lookup. + * @return A string representation of the address in the form + * 'address:port' + */ + std::string to_string() const; +}; + +// -------------------------------------------------------------------------- + +/** + * Stream inserter for the address. + * This uses the simple dot notation of the address as returned from + * inet_ntop(). It does not attempt a host lookup. + * @param os The output stream + * @param addr The address + * @return A reference to the output stream. + */ +std::ostream& operator<<(std::ostream& os, const inet_address& addr); + +} // End namespace socket-cpp +} // End namespace components +} // End namespace osdev diff --git b/include/socket-cpp/platform.h a/include/socket-cpp/platform.h new file mode 100644 index 0000000..3641795 --- /dev/null +++ a/include/socket-cpp/platform.h @@ -0,0 +1,72 @@ +#pragma once + +namespace osdev { +namespace components { +namespace socket-cpp { + +#include + +#if defined(_WIN32) + //#pragma warning(4 : 4996) // Deprecated functions (CRT & all) + //#pragma warning(4 : 4250) // Inheritance via dominance + + #if !defined(WIN32_LEAN_AND_MEAN) + #define WIN32_LEAN_AND_MEAN + #endif + + #if !defined(_CRT_SECURE_NO_DEPRECATE) + #define _CRT_SECURE_NO_DEPRECATE + #endif + + //#include + //#include + #include + #include + + #define SOCKPP_SOCKET_T_DEFINED + using socket_t = SOCKET; + + using socklen_t = int; + using in_port_t = uint16_t; + using in_addr_t = uint32_t; + + using sa_family_t = u_short; + + #ifndef _SSIZE_T_DEFINED + #define _SSIZE_T_DEFINED + #undef ssize_t + using ssize_t = SSIZE_T; + #endif // _SSIZE_T_DEFINED + + #ifndef _SUSECONDS_T + #define _SUSECONDS_T + typedef long suseconds_t; // signed # of microseconds in timeval + #endif // _SUSECONDS_T + + #define SHUT_RD SD_RECEIVE + #define SHUT_WR SD_SEND + #define SHUT_RDWR SD_BOTH + + struct iovec + { + void* iov_base; + size_t iov_len; + }; + +#else + #include + #include + #include + #include + #ifdef __FreeBSD__ + #include + #endif + #include + #include + #include +#endif + +} // End namespace socket-cpp +} // End namespace components +} // End namespace osdev + diff --git b/include/socket-cpp/sock_address.h a/include/socket-cpp/sock_address.h new file mode 100644 index 0000000..2a1c0d6 --- /dev/null +++ a/include/socket-cpp/sock_address.h @@ -0,0 +1,153 @@ +#pragma once + +#include "socket-cpp/platform.h" +#include +#include + +namespace osdev { +namespace components { +namespace socket-cpp { + +/** + * Generic socket address. + * Abstract base class for socket addresses. The underlying C socket + * functions typically take or return an address as a `sockaddr` pointer and + * length. So derived classes that wrap the + */ +class sock_address +{ +public: + /** + * Virtual destructor. + */ + virtual ~sock_address() {} + /** + * Gets the size of this structure. + * This is equivalent to sizeof(this) but more convenient in some + * places. + * @return The size of this structure. + */ + virtual socklen_t size() const =0; + /** + * Gets a pointer to this object cast to a @em sockaddr. + * @return A pointer to this object cast to a @em sockaddr. + */ + virtual sockaddr* sockaddr_ptr() =0; + /** + * Gets a pointer to this object cast to a @em sockaddr. + * @return A pointer to this object cast to a @em sockaddr. + */ + virtual const sockaddr* sockaddr_ptr() const =0; + /** + * Gets the network family of the address. + * @return The network family of the address (AF_INET, etc). If the + * address is not known, returns AF_UNSPEC. + */ + virtual sa_family_t family() const { + auto p = sockaddr_ptr(); + return p ? p->sa_family : AF_UNSPEC; + } +}; + +/** + * Generic socket address. + * + * This is a wrapper around `sockaddr_storage` which can hold any family + * address. This should have enough memory to contain any address struct for + * the system on which it is compiled. + */ +class sock_address_any : public sock_address +{ + /** Storage for any kind of socket address */ + sockaddr_storage addr_; + /** Length of the address (in bytes) */ + socklen_t sz_; + + /** The maximum size of an address, in bytes */ + static constexpr size_t MAX_SZ = sizeof(sockaddr_storage); + +public: + /** + * Constructs an empty address. + * The address is initialized to all zeroes. + */ + sock_address_any() : addr_{}, sz_{MAX_SZ} {} + /** + * Constructs an address. + * @param addr Pointer to a buffer holding the address. + * @param n The number of valid bytes in the address + * @throws std::length_error if `n` is greater than the maximum size of + * an address. + */ + sock_address_any(const sockaddr* addr, socklen_t n) { + if (n > MAX_SZ) + throw std::length_error("Address length out of range"); + std::memcpy(&addr_, addr, sz_ = n); + } + /** + * Constructs an address. + * @param addr The buffer holding the address. + * @param n The number of valid bytes in the address + * @throws std::length_error if `n` is greater than the maximum size of + * an address. + */ + sock_address_any(const sockaddr_storage& addr, socklen_t n) { + if (n > MAX_SZ) + throw std::length_error("Address length out of range"); + std::memcpy(&addr_, &addr, sz_ = n); + } + /** + * Copies another address to this one. + * @param addr The other address to copy into this one. + */ + sock_address_any(const sock_address& addr) + : sock_address_any(addr.sockaddr_ptr(), addr.size()) {} + /** + * Gets the size of the address. + * @return The size of the address. This is the number of bytes that are a + * valid part of the address. + */ + socklen_t size() const override { return sz_; } + /** + * Gets a pointer to this object cast to a @em sockaddr. + * @return A pointer to this object cast to a @em sockaddr. + */ + const sockaddr* sockaddr_ptr() const override { + return reinterpret_cast(&addr_); + } + /** + * Gets a pointer to this object cast to a @em sockaddr. + * @return A pointer to this object cast to a @em sockaddr. + */ + sockaddr* sockaddr_ptr() override { + return reinterpret_cast(&addr_); + } +}; + +/** + * Determines if the two objects refer to the same address. + * @param lhs A socket address + * @param rhs A socket address + * @return @em true if `lhs` and `rhs` refer to the same address, @em false + * otherwise. + */ +inline bool operator==(const sock_address& lhs, const sock_address& rhs) { + return lhs.size() == rhs.size() && + std::memcmp(lhs.sockaddr_ptr(), rhs.sockaddr_ptr(), lhs.size()) == 0; +} + +/** + * Determines if the two objects refer to the different address. + * @param lhs A socket address + * @param rhs A socket address + * @return @em true if `lhs` and `rhs` refer to different address, @em false + * if they refer to the same address. + */ +inline bool operator!=(const sock_address& lhs, const sock_address& rhs) { + return !operator==(lhs, rhs); +} + +} // End namespace socket-cpp +} // End namespace components +} // End namespace osdev + diff --git b/include/socket-cpp/socket.h a/include/socket-cpp/socket.h new file mode 100644 index 0000000..f1a2a82 --- /dev/null +++ a/include/socket-cpp/socket.h @@ -0,0 +1,515 @@ +/** + * @file socket.h + * + * Classes for TCP & UDP socket. + * + * @author Frank Pagliughi + * @author SoRo Systems, Inc. + * @author www.sorosys.com + * + * @date December 2014 + */ + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2014-2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#ifndef __sockpp_socket_h +#define __sockpp_socket_h + +#include "sockpp/sock_address.h" +#include +#include +#include + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +#if !defined(SOCKPP_SOCKET_T_DEFINED) + typedef int socket_t; ///< The OS socket handle + const socket_t INVALID_SOCKET = -1; ///< Invalid socket descriptor + #define SOCKPP_SOCKET_T_DEFINED +#endif + +/** + * Converts a number of microseconds to a relative timeval. + * @param dur A chrono duration of microseconds. + * @return A timeval + */ +timeval to_timeval(const std::chrono::microseconds& dur); + +/** + * Converts a chrono duration to a relative timeval. + * @param dur A chrono duration. + * @return A timeval. + */ +template +timeval to_timeval(const std::chrono::duration& dur) { + return to_timeval(std::chrono::duration_cast(dur)); +} + +/** + * Converts a relative timeval to a chrono duration. + * @param tv A timeval. + * @return A chrono duration. + */ +inline std::chrono::microseconds to_duration(const timeval& tv) +{ + auto dur = std::chrono::seconds{tv.tv_sec} + + std::chrono::microseconds{tv.tv_usec}; + return std::chrono::duration_cast(dur); +} + +/** + * Converts an absolute timeval to a chrono time_point. + * @param tv A timeval. + * @return A chrono time_point. + */ +inline std::chrono::system_clock::time_point to_timepoint(const timeval& tv) +{ + return std::chrono::system_clock::time_point { + std::chrono::duration_cast(to_duration(tv)) + }; +} + +///////////////////////////////////////////////////////////////////////////// + +/** + * Base class for socket objects. + * + * This class wraps an OS socket handle and maintains strict ownership + * semantics. If a socket object has a valid, open socket, then it owns the + * handle and will close it when the object is destroyed. + * + * Objects of this class are not copyable, but they are moveable. + */ +class socket +{ + /** The OS integer socket handle */ + socket_t handle_; + /** Cache of the last error (errno) */ + mutable int lastErr_; + /** + * The OS-specific function to close a socket handle/ + * @param h The OS socket handle. + * @return @em true if the sock is closed, @em false on error. + */ + bool close(socket_t h); + + // Non-copyable. + socket(const socket&) =delete; + socket& operator=(const socket&) =delete; + +protected: + /** + * Closes the socket without checking for errors or updating the last + * error. + * This is used in internal open/connect type functions that fail after + * creating the socket, but want to preserve the previous failure + * condition. + * Assumes that the socket handle is valid. + * @return Always returns @em false. + */ + bool close_on_err() { + close(release()); + return false; + } + /** + * OS-specific means to retrieve the last error from an operation. + * This should be called after a failed system call to set the + * lastErr_ member variable. Normally this would be called from + * @ref check_ret. + */ + static int get_last_error(); + /** + * Cache the last system error code into this object. + * This should be called after a failed system call to store the error + * value. + */ + void set_last_error() { + lastErr_ = get_last_error(); + } + /** + * Checks the value and if less than zero, sets last error. + * @tparam T A signed integer type of any size + * @param ret The return value from a library or system call. + * @return Returns the value sent to it, `ret`. + */ + template + T check_ret(T ret) const{ + lastErr_ = (ret < 0) ? get_last_error() : 0; + return ret; + } + /** + * Checks the value and if less than zero, sets last error. + * @tparam T A signed integer type of any size + * @param ret The return value from a library or system call. + * @return @em true if the value is a typical system success value (>=0) + * or @em false is is an error (<0) + */ + template + bool check_ret_bool(T ret) const{ + lastErr_ = (ret < 0) ? get_last_error() : 0; + return ret >= 0; + } + /** + * Checks the value and if it is not a valid socket, sets last error. + * This is specifically required for Windows which uses an unsigned type + * for its SOCKET. + * @param ret The return value from a library or system call that returns + * a socket, such as socket() or accept(). + * @return Returns the value sent to it, `ret`. + */ + socket_t check_socket(socket_t ret) const { + lastErr_ = (ret == INVALID_SOCKET) ? get_last_error() : 0; + return ret; + } + /** + * Checks the value and if it is INVALID_SOCKET, sets last error. + * This is specifically required for Windows which uses an unsigned type + * for its SOCKET. + * @param ret The return value from a library or system call that returns + * a socket such as socket() or accept(). + * @return @em true if the value is a valid socket (not INVALID_SOCKET) + * or @em false is is an error (INVALID_SOCKET) + */ + bool check_socket_bool(socket_t ret) const{ + lastErr_ = (ret == INVALID_SOCKET) ? get_last_error() : 0; + return ret != INVALID_SOCKET; + } + +public: + /** + * Creates an unconnected (invalid) socket + */ + socket() : handle_(INVALID_SOCKET), lastErr_(0) {} + /** + * Creates a socket from an existing OS socket handle. + * The object takes ownership of the handle and will close it when + * destroyed. + * @param h An OS socket handle. + */ + explicit socket(socket_t h) : handle_(h), lastErr_(0) {} + /** + * Move constructor. + * This takes ownership of the underlying handle in sock. + * @param sock An rvalue reference to a socket object. + */ + socket(socket&& sock) noexcept + : handle_(sock.handle_), lastErr_(sock.lastErr_) { + sock.handle_ = INVALID_SOCKET; + } + /** + * Destructor closes the socket. + */ + virtual ~socket() { close(); } + /** + * Initializes the socket (sockpp) library. + * This is only required for Win32. On platforms that use a standard + * socket implementation this is an empty call. + */ + static void initialize(); + /** + * Shuts down the socket library. + * This is only required for Win32. On platforms that use a standard + * socket implementation this is an empty call. + */ + static void destroy(); + /** + * Creates a socket with the specified communications characterics. + * Not that this is not normally how a socket is creates in the sockpp + * library. Applications would typically create a connector (client) or + * acceptor (server) socket which would take care of the details. + * + * This is included for completeness or for creating different types of + * sockets than are supported by the library. + * + * @param domain The communications domain for the sockets (i.e. the + * address family) + * @param type The communication semantics for the sockets (SOCK_STREAM, + * SOCK_DGRAM, etc). + * @param protocol The particular protocol to be used with the sockets + * + * @return A socket with the requested communications characteristics. + */ + static socket create(int domain, int type, int protocol=0); + /** + * Determines if the socket is open (valid). + * @return @em true if the socket is open, @em false otherwise. + */ + bool is_open() const { return handle_ != INVALID_SOCKET; } + /** + * Determines if the socket is closed or in an error state. + * @return @em true if the socket is closed or in an error state, @em + * false otherwise. + */ + bool operator!() const { + return handle_ == INVALID_SOCKET || lastErr_ != 0; + } + /** + * Determines if the socket is open and in an error-free state. + * @return @em true if the socket is open and in an error-free state, + * @em false otherwise. + */ + explicit operator bool() const { + return handle_ != INVALID_SOCKET && lastErr_ == 0; + } + /** + * Get the underlying OS socket handle. + * @return The underlying OS socket handle. + */ + socket_t handle() const { return handle_; } + /** + * Gets the network family of the address to which the socket is bound. + * @return The network family of the address (AF_INET, etc) to which the + * socket is bound. If the socket is not bound, or the address + * is not known, returns AF_UNSPEC. + */ + virtual sa_family_t family() const { + return address().family(); + } + /** + * Creates a new socket that refers to this one. + * This creates a new object with an independent lifetime, but refers + * back to this same socket. On most systems, this duplicates the file + * handle using the dup() call. + * A typical use of this is to have separate threads for reading and + * writing the socket. One thread would get the original socket and the + * other would get the cloned one. + * @return A new socket object that refers to the same socket as this + * one. + */ + socket clone() const; + /** + * Creates a pair of connected sockets. + * + * Whether this will work at all is highly system and domain dependent. + * Currently it is only known to work for Unix-domain sockets on *nix + * systems. + * + * Note that applications would normally call this from a derived socket + * class which would return the properly type-cast sockets to match the + * `domain` and `type`. + * + * @param domain The communications domain for the sockets (i.e. the + * address family) + * @param type The communication semantics for the sockets (SOCK_STREAM, + * SOCK_DGRAM, etc). + * @param protocol The particular protocol to be used with the sockets + * + * @return A std::tuple of sockets. On error both sockets will be in an + * error state with the + */ + static std::tuple pair(int domain, int type, int protocol=0); + /** + * Clears the error flag for the object. + * @param val The value to set the flag, normally zero. + */ + void clear(int val=0) { lastErr_ = val; } + /** + * Releases ownership of the underlying socket object. + * @return The OS socket handle. + */ + socket_t release() { + socket_t h = handle_; + handle_ = INVALID_SOCKET; + return h; + } + /** + * Replaces the underlying managed socket object. + * @param h The new socket handle to manage. + */ + void reset(socket_t h=INVALID_SOCKET); + /** + * Move assignment. + * This assigns ownership of the socket from the other object to this + * one. + * @return A reference to this object. + */ + socket& operator=(socket&& sock) noexcept { + // Give our handle to the other to close. + std::swap(handle_, sock.handle_); + lastErr_ = sock.lastErr_; + return *this; + } + /** + * Binds the socket to the specified address. + * @param addr The address to which we get bound. + * @return @em true on success, @em false on error + */ + bool bind(const sock_address& addr); + /** + * Gets the local address to which the socket is bound. + * @return The local address to which the socket is bound. + */ + sock_address_any address() const; + /** + * Gets the address of the remote peer, if this socket is connected. + * @return The address of the remote peer, if this socket is connected. + */ + sock_address_any peer_address() const; + /** + * Gets the value of a socket option. + * + * This is a thin wrapper for the system `getsockopt`. + * + * @param level The protocol level at which the option resides, such as + * SOL_SOCKET. + * @param optname The option passed directly to the protocol module. + * @param optval The buffer for the value to retrieve + * @param optlen Initially contains the lenth of the buffer, and on return + * contains the length of the value retrieved. + * + * @return bool @em true if the value was retrieved, @em false if an error + * occurred. + */ + bool get_option(int level, int optname, void* optval, socklen_t* optlen) const; + /** + * Gets the value of a socket option. + * + * @param level The protocol level at which the option resides, such as + * SOL_SOCKET. + * @param optname The option passed directly to the protocol module. + * @param val The value to retrieve + * @return bool @em true if the value was retrieved, @em false if an error + * occurred. + */ + template + bool get_option(int level, int optname, T* val) const { + socklen_t len = sizeof(T); + return get_option(level, optname, (void*) val, &len); + } + /** + * Sets the value of a socket option. + * + * This is a thin wrapper for the system `setsockopt`. + * + * @param level The protocol level at which the option resides, such as + * SOL_SOCKET. + * @param optname The option passed directly to the protocol module. + * @param optval The buffer with the value to set. + * @param optlen Contains the lenth of the value buffer. + * + * @return bool @em true if the value was set, @em false if an error + * occurred. + */ + bool set_option(int level, int optname, const void* optval, socklen_t optlen); + /** + * Sets the value of a socket option. + * + * @param level The protocol level at which the option resides, such as + * SOL_SOCKET. + * @param optname The option passed directly to the protocol module. + * @param val The value to set. + * + * @return bool @em true if the value was set, @em false if an error + * occurred. + */ + template + bool set_option(int level, int optname, const T& val) { + return set_option(level, optname, (void*) &val, sizeof(T)); + } + /** + * Places the socket into or out of non-blocking mode. + * When in non-blocking mode, a call that is not immediately ready to + * complete (read, write, accept, etc) will return immediately with the + * error EWOULDBLOCK. + * @param on Whether to turn non-blocking mode on or off. + * @return @em true on success, @em false on failure. + */ + bool set_non_blocking(bool on=true); + /** + * Gets a string describing the specified error. + * This is typically the returned message from the system strerror(). + * @param errNum The error number. + * @return A string describing the specified error. + */ + static std::string error_str(int errNum); + /** + * Gets the code for the last errror. + * This is typically the code from the underlying OS operation. + * @return The code for the last errror. + */ + int last_error() const { return lastErr_; } + /** + * Gets a string describing the last errror. + * This is typically the returned message from the system strerror(). + * @return A string describing the last errror. + */ + std::string last_error_str() const { + return error_str(lastErr_); + } + /** + * Shuts down all or part of the full-duplex connection. + * @param how Which part of the connection should be shut: + * @li SHUT_RD (0) Further reads disallowed. + * @li SHUT_WR (1) Further writes disallowed + * @li SHUT_RDWR (2) Further reads and writes disallowed. + * @return @em true on success, @em false on error. + */ + bool shutdown(int how=SHUT_RDWR); + /** + * Closes the socket. + * After closing the socket, the handle is @em invalid, and can not be + * used again until reassigned. + * @return @em true if the sock is closed, @em false on error. + */ + bool close(); +}; + +///////////////////////////////////////////////////////////////////////////// + +/** + * RAII class to initialize and then shut down the library. + * A single object of this class can be declared before any other classes in + * the library are used. The lifetime of the object should span the use of + * the other classes in the library, so declaring an object at the top of + * main() is usually the best choice. + * This is only required on some platforms, particularly Windows, but is + * harmless on other platforms. On some, such as POSIX, the initializer sets + * optional parameters for the library, and the destructor does nothing. + */ +class socket_initializer +{ +public: + socket_initializer() { socket::initialize(); } + ~socket_initializer() { socket::destroy(); } +}; + +///////////////////////////////////////////////////////////////////////////// +// end namespace sockpp +} + +#endif // __sockpp_socket_h + diff --git b/include/socket-cpp/stream_socket.h a/include/socket-cpp/stream_socket.h new file mode 100644 index 0000000..e0722d2 --- /dev/null +++ a/include/socket-cpp/stream_socket.h @@ -0,0 +1,332 @@ +/** + * @file stream_socket.h + * + * Classes for stream sockets. + * + * @author Frank Pagliughi + * @author SoRo Systems, Inc. + * @author www.sorosys.com + * + * @date December 2014 + */ + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2014-2017 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#ifndef __sockpp_stream_socket_h +#define __sockpp_stream_socket_h + +#include "sockpp/socket.h" +#include + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +/** + * Base class for streaming sockets, such as TCP and Unix Domain. + * This is the streaming connection between two peers. It looks like a + * readable/writeable device. + */ +class stream_socket : public socket +{ + /** The base class */ + using base = socket; + +protected: + /** Acceptor can create stream sockets. */ + friend class acceptor; + + /** + * Creates a streaming socket. + * @return An OS handle to a stream socket. + */ + static socket_t create_handle(int domain) { + return (socket_t) ::socket(domain, COMM_TYPE, 0); + } + +public: + /** The socket 'type' for communications semantics. */ + static constexpr int COMM_TYPE = SOCK_STREAM; + /** + * Creates an unconnected streaming socket. + */ + stream_socket() {} + /** + * Creates a streaming socket from an existing OS socket handle and + * claims ownership of the handle. + * @param handle A socket handle from the operating system. + */ + explicit stream_socket(socket_t handle) : base(handle) {} + /** + * Creates a stream socket by copying the socket handle from the + * specified socket object and transfers ownership of the socket. + */ + stream_socket(stream_socket&& sock) : base(std::move(sock)) {} + + + /** + * Creates a socket with the specified communications characterics. + * Not that this is not normally how a socket is creates in the sockpp + * library. Applications would typically create a connector (client) or + * acceptor (server) socket which would take care of the details. + * + * This is included for completeness or for creating different types of + * sockets than are supported by the library. + * + * @param domain The communications domain for the sockets (i.e. the + * address family) + * @param protocol The particular protocol to be used with the sockets + * + * @return A stream socket with the requested communications + * characteristics. + */ + static stream_socket create(int domain, int protocol=0); + + /** + * Move assignment. + * @param rhs The other socket to move into this one. + * @return A reference to this object. + */ + stream_socket& operator=(stream_socket&& rhs) { + base::operator=(std::move(rhs)); + return *this; + } + /** + * Creates a new stream_socket that refers to this one. + * This creates a new object with an independent lifetime, but refers + * back to this same socket. On most systems, this duplicates the file + * handle using the dup() call. + * A typical use of this is to have separate threads for reading and + * writing the socket. One thread would get the original socket and the + * other would get the cloned one. + * @return A new stream socket object that refers to the same socket as + * this one. + */ + stream_socket clone() const { + auto h = base::clone().release(); + return stream_socket(h); + } + /** + * Reads from the port + * @param buf Buffer to get the incoming data. + * @param n The number of bytes to try to read. + * @return The number of bytes read on success, or @em -1 on error. + */ + virtual ssize_t read(void *buf, size_t n); + /** + * Best effort attempts to read the specified number of bytes. + * This will make repeated read attempts until all the bytes are read in + * or until an error occurs. + * @param buf Buffer to get the incoming data. + * @param n The number of bytes to try to read. + * @return The number of bytes read on success, or @em -1 on error. If + * successful, the number of bytes read should always be 'n'. + */ + virtual ssize_t read_n(void *buf, size_t n); + /** + * Reads discontiguous memory ranges from the socket. + * @param ranges The vector of memory ranges to fill + * @return The number of bytes read, or @em -1 on error. + */ + ssize_t read(const std::vector& ranges); + /** + * Set a timeout for read operations. + * Sets the timeout that the device uses for read operations. Not all + * devices support timeouts, so the caller should prepare for failure. + * @param to The amount of time to wait for the operation to complete. + * @return @em true on success, @em false on failure. + */ + virtual bool read_timeout(const std::chrono::microseconds& to); + /** + * Set a timeout for read operations. + * Sets the timout that the device uses for read operations. Not all + * devices support timouts, so the caller should prepare for failure. + * @param to The amount of time to wait for the operation to complete. + * @return @em true on success, @em false on failure. + */ + template + bool read_timeout(const std::chrono::duration& to) { + return read_timeout(std::chrono::duration_cast(to)); + } + /** + * Writes the buffer to the socket. + * @param buf The buffer to write + * @param n The number of bytes in the buffer. + * @return The number of bytes written, or @em -1 on error. + */ + virtual ssize_t write(const void *buf, size_t n); + /** + * Best effort attempt to write the whole buffer to the socket. + * @param buf The buffer to write + * @param n The number of bytes in the buffer. + * @return The number of bytes written, or @em -1 on error. If + * successful, the number of bytes written should always be 'n'. + */ + virtual ssize_t write_n(const void *buf, size_t n); + /** + * Best effort attempt to write a string to the socket. + * @param s The string to write. + * @return The number of bytes written, or @em -1 on error. On success, + * the number of bytes written should always be the length of + * the string. + */ + virtual ssize_t write(const std::string& s) { + return write_n(s.data(), s.size()); + } + /** + * Writes discontiguous memory ranges to the socket. + * @param ranges The vector of memory ranges to write + * @return The number of bytes written, or @em -1 on error. + */ + virtual ssize_t write(const std::vector &ranges); + /** + * Set a timeout for write operations. + * Sets the timout that the device uses for write operations. Not all + * devices support timouts, so the caller should prepare for failure. + * @param to The amount of time to wait for the operation to complete. + * @return @em true on success, @em false on failure. + */ + virtual bool write_timeout(const std::chrono::microseconds& to); + /** + * Set a timeout for write operations. + * Sets the timout that the device uses for write operations. Not all + * devices support timouts, so the caller should prepare for failure. + * @param to The amount of time to wait for the operation to complete. + * @return @em true on success, @em false on failure. + */ + template + bool write_timeout(const std::chrono::duration& to) { + return write_timeout(std::chrono::duration_cast(to)); + } +}; + +///////////////////////////////////////////////////////////////////////////// + +/** + * Template for creating specific stream types (IPv4, IPv6, etc). + * This just overrides methods that take a generic address and replace them + * with the address type for a specific family. This doesn't add any + * runtime functionality, but has compile-time checks that address types + * aren't accidentally being mixed for an object. + */ +template +class stream_socket_tmpl : public stream_socket +{ + /** The base class */ + using base = stream_socket; + +public: + /** The address family for this type of address */ + static constexpr sa_family_t ADDRESS_FAMILY = ADDR::ADDRESS_FAMILY; + /** The type of network address used with this socket. */ + using addr_t = ADDR; + /** + * Creates an unconnected streaming socket. + */ + stream_socket_tmpl() {} + /** + * Creates a streaming socket from an existing OS socket handle and + * claims ownership of the handle. + * @param handle A socket handle from the operating system. + */ + explicit stream_socket_tmpl(socket_t handle) : base(handle) {} + /** + * Move constructor. + * Creates a stream socket by moving the other socket to this one. + * @param sock Another stream socket. + */ + stream_socket_tmpl(stream_socket&& sock) + : base(std::move(sock)) {} + /** + * Creates a stream socket by copying the socket handle from the + * specified socket object and transfers ownership of the socket. + */ + stream_socket_tmpl(stream_socket_tmpl&& sock) + : base(std::move(sock)) {} + /** + * Move assignment. + * @param rhs The other socket to move into this one. + * @return A reference to this object. + */ + stream_socket_tmpl& operator=(stream_socket_tmpl&& rhs) { + base::operator=(std::move(rhs)); + return *this; + } + /** + * Cretates a stream socket. + * @param protocol The particular protocol to be used with the sockets + * @return A stream socket + */ + stream_socket_tmpl create(int protocol=0) { + return stream_socket_tmpl(std::move(base::create(ADDRESS_FAMILY, protocol))); + } + /** + * Creates a pair of connected stream sockets. + * + * Whether this will work at all is highly system and domain dependent. + * Currently it is only known to work for Unix-domain sockets on *nix + * systems. + * + * @param protocol The protocol to be used with the socket. (Normally 0) + * + * @return A std::tuple of stream sockets. On error both sockets will be + * in an error state with the last error set. + */ + static std::tuple pair(int protocol=0) { + auto pr = base::pair(ADDRESS_FAMILY, COMM_TYPE, protocol); + return std::make_tuple( + stream_socket_tmpl{std::get<0>(pr).release()}, + stream_socket_tmpl{std::get<1>(pr).release()}); + } + /** + * Gets the local address to which the socket is bound. + * @return The local address to which the socket is bound. + * @throw sys_error on error + */ + addr_t address() const { return addr_t(socket::address()); } + /** + * Gets the address of the remote peer, if this socket is connected. + * @return The address of the remote peer, if this socket is connected. + * @throw sys_error on error + */ + addr_t peer_address() const { return addr_t(socket::peer_address()); } +}; + +///////////////////////////////////////////////////////////////////////////// +// end namespace sockpp +} + +#endif // __sockpp_socket_h + diff --git b/include/socket-cpp/tcp6_acceptor.h a/include/socket-cpp/tcp6_acceptor.h new file mode 100644 index 0000000..230d83a --- /dev/null +++ a/include/socket-cpp/tcp6_acceptor.h @@ -0,0 +1,69 @@ +/// @file tcp6_acceptor.h +/// +/// Class for a TCP v6 server to accept incoming connections. +/// +/// @author Frank Pagliughi +/// @author SoRo Systems, Inc. +/// @author www.sorosys.com +/// +/// @date May 2019 + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#ifndef __sockpp_tcp6_acceptor_h +#define __sockpp_tcp6_acceptor_h + +#include "sockpp/acceptor.h" +#include "sockpp/tcp6_socket.h" + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +/// Class for creating a TCP v6 server. +/// Objects of this class bind and listen on TCP ports for incoming +/// connections. Normally, a server thread creates one of these and blocks +/// on the call to accept incoming connections. The call to accept creates +/// and returns a @ref tcp6_socket which can then be used for the actual +/// communications. + +using tcp6_acceptor = acceptor_tmpl; + +///////////////////////////////////////////////////////////////////////////// +// end namespace sockpp +}; + +#endif // __sockpp_tcp_acceptor_h + diff --git b/include/socket-cpp/tcp6_connector.h a/include/socket-cpp/tcp6_connector.h new file mode 100644 index 0000000..3c37980 --- /dev/null +++ a/include/socket-cpp/tcp6_connector.h @@ -0,0 +1,66 @@ +/** + * @file tcp6_connector.h + * + * Class for creating client-side TCP connections + * + * @author Frank Pagliughi + * @author SoRo Systems, Inc. + * @author www.sorosys.com + * + * @date May 2019 + */ + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2014-2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + + +#ifndef __sockpp_tcp6_connector_h +#define __sockpp_tcp6_connector_h + +#include "sockpp/connector.h" +#include "sockpp/tcp6_socket.h" + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +/** IPv6 active, connector (client) socket. */ +using tcp6_connector = connector_tmpl; + +///////////////////////////////////////////////////////////////////////////// +// end namespace sockpp +} + +#endif // __sockpp_tcp6_connector_h + diff --git b/include/socket-cpp/tcp6_socket.h a/include/socket-cpp/tcp6_socket.h new file mode 100644 index 0000000..04ab99b --- /dev/null +++ a/include/socket-cpp/tcp6_socket.h @@ -0,0 +1,65 @@ +/** + * @file tcp6_socket.h + * + * Class (typedef) for IPv6 TCP socket. + * + * @author Frank Pagliughi + * @author SoRo Systems, Inc. + * @author www.sorosys.com + * + * @date August 2019 + */ + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#ifndef __sockpp_tcp6_socket_h +#define __sockpp_tcp6_socket_h + +#include "sockpp/stream_socket.h" +#include "sockpp/inet6_address.h" + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +/** IPv6 streaming TCP socket */ +using tcp6_socket = stream_socket_tmpl; + +///////////////////////////////////////////////////////////////////////////// +// end namespace sockpp +} + +#endif // __sockpp_tcp6_socket_h + diff --git b/include/socket-cpp/tcp_acceptor.h a/include/socket-cpp/tcp_acceptor.h new file mode 100644 index 0000000..cfd78e4 --- /dev/null +++ a/include/socket-cpp/tcp_acceptor.h @@ -0,0 +1,69 @@ +/// @file tcp_acceptor.h +/// +/// Class for a TCP server to accept incoming connections. +/// +/// @author Frank Pagliughi +/// @author SoRo Systems, Inc. +/// @author www.sorosys.com +/// +/// @date December 2014 + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2014-2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#ifndef __sockpp_tcp_acceptor_h +#define __sockpp_tcp_acceptor_h + +#include "sockpp/acceptor.h" +#include "sockpp/tcp_socket.h" + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +/// Class for creating a TCP server. +/// Objects of this class bind and listen on TCP ports for incoming +/// connections. Normally, a server thread creates one of these and blocks +/// on the call to accept incoming connections. The call to accept creates +/// and returns a @ref tcp_socket which can then be used for the actual +/// communications. + +using tcp_acceptor = acceptor_tmpl; + +///////////////////////////////////////////////////////////////////////////// +// end namespace sockpp +}; + +#endif // __sockpp_tcp_acceptor_h + diff --git b/include/socket-cpp/tcp_connector.h a/include/socket-cpp/tcp_connector.h new file mode 100644 index 0000000..c5088b0 --- /dev/null +++ a/include/socket-cpp/tcp_connector.h @@ -0,0 +1,65 @@ +/** + * @file tcp_connector.h + * + * Class for creating client-side TCP connections + * + * @author Frank Pagliughi + * @author SoRo Systems, Inc. + * @author www.sorosys.com + * + * @date December 2014 + */ + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2014-2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + + +#ifndef __sockpp_tcp_connector_h +#define __sockpp_tcp_connector_h + +#include "sockpp/connector.h" +#include "sockpp/tcp_socket.h" + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +/** IPv4 active, connector (client) socket. */ +using tcp_connector = connector_tmpl; + +///////////////////////////////////////////////////////////////////////////// +// end namespace sockpp +}; + +#endif // __sockpp_tcp_connector_h diff --git b/include/socket-cpp/tcp_socket.h a/include/socket-cpp/tcp_socket.h new file mode 100644 index 0000000..6f1cf64 --- /dev/null +++ a/include/socket-cpp/tcp_socket.h @@ -0,0 +1,65 @@ +/** + * @file tcp_socket.h + * + * Class (typedef) for IPv4 TCP socket. + * + * @author Frank Pagliughi + * @author SoRo Systems, Inc. + * @author www.sorosys.com + * + * @date August 2019 + */ + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#ifndef __sockpp_tcp_socket_h +#define __sockpp_tcp_socket_h + +#include "sockpp/stream_socket.h" +#include "sockpp/inet_address.h" + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +/** IPv4 streaming TCP socket */ +using tcp_socket = stream_socket_tmpl; + +///////////////////////////////////////////////////////////////////////////// +// end namespace sockpp +} + +#endif // __sockpp_tcp_socket_h + diff --git b/include/socket-cpp/udp6_socket.h a/include/socket-cpp/udp6_socket.h new file mode 100644 index 0000000..5c294c5 --- /dev/null +++ a/include/socket-cpp/udp6_socket.h @@ -0,0 +1,65 @@ +/** + * @file udp6_socket.h + * + * Class (typedef) for UDP v6 socket. + * + * @author Frank Pagliughi + * @author SoRo Systems, Inc. + * @author www.sorosys.com + * + * @date August 2019 + */ + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#ifndef __sockpp_udp6_socket_h +#define __sockpp_udp6_socket_h + +#include "sockpp/datagram_socket.h" +#include "sockpp/inet6_address.h" + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +/** UDP datagram socket type for IPv6 */ +using udp6_socket = datagram_socket_tmpl; + +///////////////////////////////////////////////////////////////////////////// +// end namespace sockpp +} + +#endif // __sockpp_udp6_socket_h + diff --git b/include/socket-cpp/udp_socket.h a/include/socket-cpp/udp_socket.h new file mode 100644 index 0000000..c63f299 --- /dev/null +++ a/include/socket-cpp/udp_socket.h @@ -0,0 +1,65 @@ +/** + * @file udp_socket.h + * + * Class (typedef) for UDP v4 socket. + * + * @author Frank Pagliughi + * @author SoRo Systems, Inc. + * @author www.sorosys.com + * + * @date August 2019 + */ + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#ifndef __sockpp_udp_socket_h +#define __sockpp_udp_socket_h + +#include "sockpp/datagram_socket.h" +#include "sockpp/inet_address.h" + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +/** UDP datagram socket type for IPv4 */ +using udp_socket = datagram_socket_tmpl; + +///////////////////////////////////////////////////////////////////////////// +// end namespace sockpp +} + +#endif // __sockpp_udp_socket_h + diff --git b/include/socket-cpp/unix_acceptor.h a/include/socket-cpp/unix_acceptor.h new file mode 100644 index 0000000..5bcc067 --- /dev/null +++ a/include/socket-cpp/unix_acceptor.h @@ -0,0 +1,115 @@ +/// @file unix_acceptor.h +/// +/// Class for a TCP server to accept incoming connections. +/// +/// @author Frank Pagliughi +/// @author SoRo Systems, Inc. +/// @author www.sorosys.com +/// +/// @date December 2014 + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2014-2017 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#ifndef __sockpp_unix_acceptor_h +#define __sockpp_unix_acceptor_h + +#include "sockpp/acceptor.h" +#include "sockpp/unix_stream_socket.h" + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +/// Class for creating a Unix-domain server. +/// Objects of this class bind and listen on Unix-domain ports for +/// connections. Normally, a server thread creates one of these and blocks +/// on the call to accept incoming connections. The call to accept creates +/// and returns a @ref unix_stream_socket which can then be used for the +/// actual communications. + +class unix_acceptor : public acceptor +{ + /** The base class */ + using base = acceptor; + + // Non-copyable + unix_acceptor(const unix_acceptor&) =delete; + unix_acceptor& operator=(const unix_acceptor&) =delete; + +public: + /** + * Creates an unconnected acceptor. + */ + unix_acceptor() {} + /** + * Creates a acceptor and starts it listening on the specified address. + * @param addr The TCP address on which to listen. + * @param queSize The listener queue size. + */ + unix_acceptor(const unix_address& addr, int queSize=DFLT_QUE_SIZE) { + open(addr, queSize); + } + /** + * Gets the local address to which we are bound. + * @return The local address to which we are bound. + */ + unix_address address() const { return unix_address(base::address()); } + /** + * Base open call also work. + */ + using base::open; + /** + * Opens the acceptor socket and binds it to the specified address. + * @param addr The address to which this server should be bound. + * @param queSize The listener queue size. + * @return @em true on success, @em false on error + */ + bool open(const unix_address& addr, int queSize=DFLT_QUE_SIZE) { + return base::open(addr, queSize); + } + /** + * Accepts an incoming UNIX connection and gets the address of the + * client. + * @return A unix_socket to the client. + */ + unix_socket accept() { return unix_socket(base::accept()); } +}; + +///////////////////////////////////////////////////////////////////////////// +// end namespace sockpp +}; + +#endif // __sockpp_unix_acceptor_h + diff --git b/include/socket-cpp/unix_address.h a/include/socket-cpp/unix_address.h new file mode 100644 index 0000000..30e8de7 --- /dev/null +++ a/include/socket-cpp/unix_address.h @@ -0,0 +1,192 @@ +/** + * @file unix_address.h + * + * Class for a UNIX-domain socket address. + * + * @author Frank Pagliughi + * @author SoRo Systems, Inc. + * @author www.sorosys.com + * + * @date February 2014 + */ + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2014-2017 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#ifndef __sockpp_unix_addr_h +#define __sockpp_unix_addr_h + +#include "sockpp/platform.h" +#include "sockpp/sock_address.h" +#include +#include +#include +#include + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +/** + * Class that represents a UNIX domain address. + * This inherits from the UNIX form of a socket address, @em sockaddr_un. + */ +class unix_address : public sock_address +{ + /** The underlying C struct for unix-domain addresses */ + sockaddr_un addr_; + + /** The size of the underlying address struct, in bytes */ + static constexpr size_t SZ = sizeof(sockaddr_un); + +public: + /** The address family for this type of address */ + static constexpr sa_family_t ADDRESS_FAMILY = AF_UNIX; + + // TODO: This only applies to Linux + static constexpr size_t MAX_PATH_NAME = 108; + + /** + * Constructs an empty address. + * The address is initialized to all zeroes. + */ + unix_address() : addr_() {} + /** + * Constructs an address for the specified path. + * @param path The + */ + unix_address(const std::string& path); + /** + * Constructs the address by copying the specified structure. + * @param addr The generic address + * @throws std::invalid_argument if the address is not a UNIX-domain + * address (i.e. family is not AF_UNIX) + */ + explicit unix_address(const sockaddr& addr); + /** + * Constructs the address by copying the specified structure. + * @param addr The other address + */ + unix_address(const sock_address& addr) { + std::memcpy(&addr_, addr.sockaddr_ptr(), SZ); + } + /** + * Constructs the address by copying the specified structure. + * @param addr The other address + * @throws std::invalid_argument if the address is not properly + * initialized as a UNIX-domain address (i.e. family is not + * AF_UNIX) + */ + unix_address(const sockaddr_un& addr) : addr_(addr) {} + /** + * Constructs the address by copying the specified address. + * @param addr The other address + */ + unix_address(const unix_address& addr) : addr_(addr.addr_) {} + /** + * Checks if the address is set to some value. + * This doesn't attempt to determine if the address is valid, simply + * that it's not all zero. + * @return @em true if the address has been set, @em false otherwise. + */ + bool is_set() const { return addr_.sun_path[0] != '\0'; } + /** + * Gets the path to which this address refers. + * @return The path to which this address refers. + */ + std::string path() const { return std::string(addr_.sun_path); } + /** + * Gets the size of the address structure. + * Note: In this implementation, this should return sizeof(this) but + * more convenient in some places, and the implementation might change + * in the future, so it might be more compatible with future revisions + * to use this call. + * @return The size of the address structure. + */ + socklen_t size() const override { return socklen_t(SZ); } + + // TODO: Do we need a: + // create(path) + // to mimic the inet_address behavior? + + /** + * Gets a pointer to this object cast to a const @em sockaddr. + * @return A pointer to this object cast to a const @em sockaddr. + */ + const sockaddr* sockaddr_ptr() const override { + return reinterpret_cast(&addr_); + } + /** + * Gets a pointer to this object cast to a @em sockaddr. + * @return A pointer to this object cast to a @em sockaddr. + */ + sockaddr* sockaddr_ptr() override { + return reinterpret_cast(&addr_); + } + /** + * Gets a const pointer to this object cast to a @em sockaddr_un. + * @return const sockaddr_un pointer to this object. + */ + const sockaddr_un* sockaddr_un_ptr() const { return &addr_; } + /** + * Gets a pointer to this object cast to a @em sockaddr_un. + * @return sockaddr_un pointer to this object. + */ + sockaddr_un* sockaddr_un_ptr() { return &addr_; } + /** + * Gets a printable string for the address. + * @return A string representation of the address in the form + * "unix:" + */ + std::string to_string() const { + return std::string("unix:") + std::string(addr_.sun_path); + } +}; + +// -------------------------------------------------------------------------- + +/** + * Stream inserter for the address. + * @param os The output stream + * @param addr The address + * @return A reference to the output stream. + */ +std::ostream& operator<<(std::ostream& os, const unix_address& addr); + +///////////////////////////////////////////////////////////////////////////// +// end namespace sockpp +} + +#endif // __sockpp_unix_addr_h + diff --git b/include/socket-cpp/unix_connector.h a/include/socket-cpp/unix_connector.h new file mode 100644 index 0000000..8777d6f --- /dev/null +++ a/include/socket-cpp/unix_connector.h @@ -0,0 +1,65 @@ +/** + * @file unix_connector.h + * + * Class for creating client-side UNIX-domain socket connections. + * + * @author Frank Pagliughi + * @author SoRo Systems, Inc. + * @author www.sorosys.com + * + * @date December 2018 + */ + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2014-2017 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#ifndef __sockpp_unix_connector_h +#define __sockpp_unix_connector_h + +#include "sockpp/connector.h" +#include "sockpp/unix_stream_socket.h" + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +/** Unix-domain active connector socket. */ +using unix_connector = connector_tmpl; + +///////////////////////////////////////////////////////////////////////////// +// end namespace sockpp +}; + +#endif // __sockpp_unix_connector_h + diff --git b/include/socket-cpp/unix_dgram_socket.h a/include/socket-cpp/unix_dgram_socket.h new file mode 100644 index 0000000..da171a2 --- /dev/null +++ a/include/socket-cpp/unix_dgram_socket.h @@ -0,0 +1,68 @@ +/** + * @file unix_dgram_socket.h + * + * Class (typedef) for Unix-domain UDP socket. + * + * @author Frank Pagliughi + * @author SoRo Systems, Inc. + * @author www.sorosys.com + * + * @date August 2019 + */ + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#ifndef __sockpp_unix_dgram_socket_h +#define __sockpp_unix_dgram_socket_h + +#include "sockpp/datagram_socket.h" +#include "sockpp/unix_address.h" + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +/** Unix-domain datagram socket */ +using unix_datagram_socket = datagram_socket_tmpl; + +/** Unix-domain datagram socket (same as `unix_datagram_socket`) */ +using unix_dgram_socket = unix_datagram_socket; + +///////////////////////////////////////////////////////////////////////////// +// end namespace sockpp +} + +#endif // __sockpp_unix_dgram_socket_h + diff --git b/include/socket-cpp/unix_stream_socket.h a/include/socket-cpp/unix_stream_socket.h new file mode 100644 index 0000000..fa3863c --- /dev/null +++ a/include/socket-cpp/unix_stream_socket.h @@ -0,0 +1,68 @@ +/** + * @file unix_stream_socket.h + * + * Class (typedef) for Unix-domain streaming socket. + * + * @author Frank Pagliughi + * @author SoRo Systems, Inc. + * @author www.sorosys.com + * + * @date August 2019 + */ + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#ifndef __sockpp_unix_stream_socket_h +#define __sockpp_unix_stream_socket_h + +#include "sockpp/stream_socket.h" +#include "sockpp/unix_address.h" + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +/** Streaming Unix-domain socket */ +using unix_stream_socket = stream_socket_tmpl; + +/** Streaming Unix-domain socket (same as a `unix_stream_socket` */ +using unix_socket = unix_stream_socket; + +///////////////////////////////////////////////////////////////////////////// +// end namespace sockpp +} + +#endif // __sockpp_unix_stream_socket_h + diff --git b/scripts/setup_submodules a/scripts/setup_submodules new file mode 100755 index 0000000..b8c7a6e --- /dev/null +++ a/scripts/setup_submodules @@ -0,0 +1,161 @@ +#!/bin/bash + +# =============================================== +# == Setting some environment variables +# =============================================== +GIT_URL_SUBS="http://gitlab.osdev.nl/open_source" +FUNC_RESULT="-1" + +# Name : print_usage_exit() +# Description : Print the way this script is intended to be used and exit. +# Parameters : None. +# Returns : err_code 1 to the Operating System +# -------------------------------------------------------------------------------------- +function print_usage_exit() +{ + echo "Usage $0 -i|--install|-u|--update" + echo " -i or --install Install the submodules mentioned in the submodules.list" + echo " -u or --update Update the submodules mentioned in the submodules.list" + echo " " + exit 1 +} + +# Name : check_top_or_sub +# Description : Determine if we're running in a "single" lib-build or part of a +# "meta"-repository ( submodule ). +# Parameters : None +# Returns : Updates the value FUNC_RESULT. +# -1 - We're neither a git-repo or submodule. +# 0 - We're a submodule +# 1 - We're a top-repo ( Single library ) +# -------------------------------------------------------------------------------------- +function check_top_or_sub() +{ + # This function checks if we're the top-repository. + # In that case we need the submodules.. If we're already a submodule, + # we simply exit this script with a message + if [ -e ./.git ]; then + FUNC_RESULT="1" + return + elif [ -e ../.git ]; then + if [ -e ../.submodules ]; then + echo "Seems like we're already a submodule. Nothing to do here." + FUNC_RESULT="0" + return + fi + fi + FUNC_RESULT="-1" + return +} + +# Name : check_working_dir +# Description : If we're in the top of our repo, we can run this script further. +# Parameters : None. +# Returns : Updates the value FUNC_RESULT. +# -1 - Not used. +# 0 - We're not on the top-level +# 1 - We're at the top-level. Good to go. +# -------------------------------------------------------------------------------------- +function check_working_dir() +{ + FUNC_RESULT="-1" + # Check if we're in the top-level directory of our repository. + if [ -f ./scripts/submodules.list ]; then + # We're good to go + FUNC_RESULT="1" + return + fi + FUNC_RESULT="0" + return +} + +# Name : read_submodules +# Description : Read the list of submodules needed for this project +# Parameters : None +# Returns : Updates the value FUNC_RESULT +# 0 - Module list was not found +# 1 - Module list was found and read. +# -------------------------------------------------------------------------------------- +function read_submodules() +{ + FUNC_RESULT="-1" + if [ -e ./scripts/submodules.list ]; then + source ./scripts/submodules.list + FUNC_RESULT="1" + return + fi + + echo "Submodules list not found...." + FUNC_RESULT="0" + return +} + +# Name : add_submodules +# Description : Configure the repo to add the submodules. +# Parameters : None. +# Returns : None. +# -------------------------------------------------------------------------------------- +function add_submodules() +{ + echo -e "Adding SubModule(s)." + for SUB_MODULE in ${SUB_MODULES} + do + echo -e "< ${SUB_MODULE} >" + git submodule add -f ${GIT_URL_SUBS}/${SUB_MODULE}.git ${SUB_MODULE} + git config submodule.${SUB_MODULE}.url ${GIT_URL_SUBS}/${SUB_MODULE}.git + done +} + +# Name : get_submodules +# Description : Actually get the submodules from gitlab and add them. +# Parameters : None +# Returns : None +# -------------------------------------------------------------------------------------- +function get_submodules() +{ + git submodule update --init --recursive +} + +# Name : update_submodules +# Description : Update the submodules already added. +# Parameters : None +# Returns : None +# -------------------------------------------------------------------------------------- +function update_submodules() +{ + git submodule update --recursive +} + +# ============================================================================= +# == T H E M A I N E N T R Y O F T H I S S C R I P T == +# ============================================================================= +check_top_or_sub +if [ "${FUNC_RESULT}" == "0" ]; then + echo "Seems like we're a submodule already or not part of a repository." + exit 0 +fi + +check_working_dir +if [ "${FUNC_RESULT}" == "0" ]; then + echo "Go to the top of this repository and type : scripts/setup_submodules [-i|--install]" + exit 0 +fi + +read_submodules + +case "$1" in + -i*|--install*) + echo "Installing submodules for this repository ( ${PWD} )" + add_submodules + get_submodules + ;; + -u*|--update*) + echo "Update submodules : ${SUB_MODULES}" + update_submodules + ;; + *) + echo "No parameters found..." + print_usage_exit + ;; +esac + diff --git b/scripts/submodules.list a/scripts/submodules.list new file mode 100644 index 0000000..109e69b --- /dev/null +++ a/scripts/submodules.list @@ -0,0 +1,2 @@ +SUB_MODULES="versioning +cmake" diff --git b/src/acceptor.cpp a/src/acceptor.cpp new file mode 100644 index 0000000..7d119d7 --- /dev/null +++ a/src/acceptor.cpp @@ -0,0 +1,74 @@ +#include +#include "sockpp/acceptor.h" + +using namespace std; + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +acceptor acceptor::create(int domain) +{ + acceptor acc(create_handle(domain)); + if (!acc) + acc.clear(get_last_error()); + return acc; +} + +// -------------------------------------------------------------------------- + +// This attempts to open the acceptor, bind to the requested address, and +// start listening. On any error it will be sure to leave the underlying +// socket in an unopened/invalid state. +// If the acceptor appears to already be opened, this will quietly succeed +// without doing anything. + +bool acceptor::open(const sock_address& addr, + int queSize /*=DFLT_QUE_SIZE*/, + bool reuseSock /*=true*/) +{ + // TODO: What to do if we are open but bound to a different address? + if (is_open()) + return true; + + sa_family_t domain = addr.family(); + socket_t h = create_handle(domain); + + if (!check_socket_bool(h)) + return false; + + reset(h); + + #if defined(_WIN32) + const int REUSE = SO_REUSEADDR; + #else + const int REUSE = SO_REUSEPORT; + #endif + + if (reuseSock && (domain == AF_INET || domain == AF_INET6)) { + int reuse = 1; + if (!set_option(SOL_SOCKET, REUSE, reuse)) + return close_on_err(); + } + + if (!bind(addr) || !listen(queSize)) + return close_on_err(); + + return true; +} + +// -------------------------------------------------------------------------- + +stream_socket acceptor::accept(sock_address* clientAddr /*=nullptr*/) +{ + sockaddr* p = clientAddr ? clientAddr->sockaddr_ptr() : nullptr; + socklen_t len = clientAddr ? clientAddr->size() : 0; + + socket_t s = check_socket(::accept(handle(), p, clientAddr ? &len : nullptr)); + return stream_socket(s); +} + +///////////////////////////////////////////////////////////////////////////// +// end namespace sockpp +} + diff --git b/src/connector.cpp a/src/connector.cpp new file mode 100644 index 0000000..dbe57dd --- /dev/null +++ a/src/connector.cpp @@ -0,0 +1,23 @@ +#include "sockpp/connector.h" + +namespace sockpp { + +bool connector::connect(const sock_address& addr) +{ + sa_family_t domain = addr.family(); + socket_t h = create_handle(domain); + + if (!check_ret_bool(h)) + return false; + + // This will close the old connection, if any. + reset(h); + + if (!check_ret_bool(::connect(h, addr.sockaddr_ptr(), addr.size()))) + return close_on_err(); + + return true; +} + +} + diff --git b/src/datagram_socket.cpp a/src/datagram_socket.cpp new file mode 100644 index 0000000..41cf832 --- /dev/null +++ a/src/datagram_socket.cpp @@ -0,0 +1,38 @@ +#include "sockpp/datagram_socket.h" +#include "sockpp/exception.h" +#include + +using namespace std::chrono; + +namespace sockpp { + +datagram_socket::datagram_socket(const sock_address& addr) +{ + auto domain = addr.family(); + socket_t h = create_handle(domain); + + if (check_socket_bool(h)) { + reset(h); + // TODO: If the bind fails, should we close the socket and fail completely? + bind(addr); + } +} + +ssize_t datagram_socket::recv_from(void* buf, size_t n, int flags, + sock_address* srcAddr /*=nullptr*/) +{ + sockaddr* p = srcAddr ? srcAddr->sockaddr_ptr() : nullptr; + socklen_t len = srcAddr ? srcAddr->size() : 0; + + // TODO: Check returned length + #if defined(_WIN32) + return check_ret(::recvfrom(handle(), reinterpret_cast(buf), + int(n), flags, p, &len)); + #else + return check_ret(::recvfrom(handle(), buf, n, flags, p, &len)); + #endif +} + + +} + diff --git b/src/exception.cpp a/src/exception.cpp new file mode 100644 index 0000000..7a5b37a --- /dev/null +++ a/src/exception.cpp @@ -0,0 +1,50 @@ +#include "sockpp/exception.h" +#include "sockpp/platform.h" +#include + +// Used to explicitly ignore the returned value of a function call. +#define ignore_result(x) if (x) {} + +using namespace std; + +namespace sockpp { + +sys_error::sys_error(int err) : runtime_error(error_str(err)), errno_(err) +{ +} + +std::string sys_error::error_str(int err) +{ + char buf[1024]; + buf[0] = '\x0'; + + #if defined(_WIN32) + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buf, sizeof(buf), NULL); + #else + #ifdef _GNU_SOURCE + #if !defined(__GLIBC__) + // use the XSI standard behavior. + int e = strerror_r(err, buf, sizeof(buf)); + auto s = strerror(e); + return s ? std::string(s) : std::string(); + #else + // assume GNU exception + auto s = strerror_r(err, buf, sizeof(buf)); + return s ? std::string(s) : std::string(); + #endif + #else + ignore_result(strerror_r(err, buf, sizeof(buf))); + #endif + #endif + return std::string(buf); +} + +getaddrinfo_error::getaddrinfo_error(int err, const string& hostname) + : runtime_error(gai_strerror(err)), error_(err), hostname_(hostname) +{ +} + +} + diff --git b/src/inet6_address.cpp a/src/inet6_address.cpp new file mode 100644 index 0000000..64c7ff8 --- /dev/null +++ a/src/inet6_address.cpp @@ -0,0 +1,80 @@ +#include "sockpp/inet6_address.h" +#include "sockpp/exception.h" + +using namespace std; + +namespace sockpp { + +bool inet6_address::is_set() const +{ + static const auto EMPTY_ADDR = sockaddr_in6{}; + return std::memcmp(&addr_, &EMPTY_ADDR, SZ) != 0; +} + +in6_addr inet6_address::resolve_name(const string& saddr) +{ + #if !defined(_WIN32) + in6_addr ia; + if (::inet_pton(ADDRESS_FAMILY, saddr.c_str(), &ia) == 1) + return ia; + #endif + + addrinfo *res, hints = addrinfo{}; + hints.ai_family = ADDRESS_FAMILY; + hints.ai_socktype = SOCK_STREAM; + + int gai_err = ::getaddrinfo(saddr.c_str(), NULL, &hints, &res); + + #if !defined(_WIN32) + if (gai_err == EAI_SYSTEM) + throw sys_error(); + #endif + + if (gai_err != 0) + throw getaddrinfo_error(gai_err, saddr); + + + auto ipv6 = reinterpret_cast(res->ai_addr); + auto addr = ipv6->sin6_addr; + freeaddrinfo(res); + return addr; +} + +void inet6_address::create(const in6_addr& addr, in_port_t port) +{ + addr_ = sockaddr_in6{}; + addr_.sin6_family = AF_INET6; + addr_.sin6_flowinfo = 0; + addr_.sin6_addr = addr; + addr_.sin6_port = htons(port); +} + +void inet6_address::create(const string& saddr, in_port_t port) +{ + addr_ = sockaddr_in6{}; + addr_.sin6_family = AF_INET6; + addr_.sin6_flowinfo = 0; + addr_.sin6_addr = resolve_name(saddr.c_str()); + addr_.sin6_port = htons(port); +} + +string inet6_address::to_string() const +{ + char buf[INET6_ADDRSTRLEN]; + auto str = inet_ntop(AF_INET6, (void*) &(addr_.sin6_addr), + buf, INET6_ADDRSTRLEN); + return std::string("[") + std::string(str ? str : "") + + "]:" + std::to_string(unsigned(port())); +} + +ostream& operator<<(ostream& os, const inet6_address& addr) +{ + char buf[INET6_ADDRSTRLEN]; + auto str = inet_ntop(AF_INET6, (void*) &(addr.sockaddr_in6_ptr()->sin6_addr), + buf, INET6_ADDRSTRLEN); + os << "[" << (str ? str : "") << "]:" << unsigned(addr.port()); + return os; +} + +} + diff --git b/src/inet_address.cpp a/src/inet_address.cpp new file mode 100644 index 0000000..d14382a --- /dev/null +++ a/src/inet_address.cpp @@ -0,0 +1,75 @@ +#include "sockpp/inet_address.h" +#include "sockpp/exception.h" + +using namespace std; + +namespace sockpp { + +bool inet_address::is_set() const +{ + static const auto EMPTY_ADDR = sockaddr_in{}; + return std::memcmp(&addr_, &EMPTY_ADDR, SZ) != 0; +} + +in_addr_t inet_address::resolve_name(const std::string& saddr) +{ + #if !defined(_WIN32) + in_addr ia; + if (::inet_pton(ADDRESS_FAMILY, saddr.c_str(), &ia) == 1) + return ia.s_addr; + #endif + + addrinfo *res, hints = addrinfo{}; + hints.ai_family = ADDRESS_FAMILY; + hints.ai_socktype = SOCK_STREAM; + + int gai_err = ::getaddrinfo(saddr.c_str(), NULL, &hints, &res); + + #if !defined(_WIN32) + if (gai_err == EAI_SYSTEM) + throw sys_error(); + #endif + + if (gai_err != 0) + throw getaddrinfo_error(gai_err, saddr); + + auto ipv4 = reinterpret_cast(res->ai_addr); + auto addr = ipv4->sin_addr.s_addr; + freeaddrinfo(res); + return addr; +} + +void inet_address::create(uint32_t addr, in_port_t port) +{ + addr_ = sockaddr_in{}; + addr_.sin_family = AF_INET; + addr_.sin_addr.s_addr = htonl(addr); + addr_.sin_port = htons(port); +} + +void inet_address::create(const std::string& saddr, in_port_t port) +{ + addr_ = sockaddr_in{}; + addr_.sin_family = AF_INET; + addr_.sin_addr.s_addr = resolve_name(saddr.c_str()); + addr_.sin_port = htons(port); +} + +string inet_address::to_string() const +{ + char buf[INET_ADDRSTRLEN]; + auto str = inet_ntop(AF_INET, (void*) &(addr_.sin_addr), buf, INET_ADDRSTRLEN); + return std::string(str ? str : "") + + ":" + std::to_string(unsigned(port())); +} + +ostream& operator<<(ostream& os, const inet_address& addr) +{ + char buf[INET_ADDRSTRLEN]; + auto str = inet_ntop(AF_INET, (void*) &(addr.sockaddr_in_ptr()->sin_addr), + buf, INET_ADDRSTRLEN); + os << (str ? str : "") << ":" << unsigned(addr.port()); + return os; +} + +} diff --git b/src/linux/can_address.cpp a/src/linux/can_address.cpp new file mode 100644 index 0000000..7d24064 --- /dev/null +++ a/src/linux/can_address.cpp @@ -0,0 +1,104 @@ +// can_address.cpp +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2014-2021 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include "sockpp/can_address.h" +#include "sockpp/socket.h" +#include +#include +#include +#include + +using namespace std; + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +constexpr sa_family_t can_address::ADDRESS_FAMILY; + +// -------------------------------------------------------------------------- + +can_address::can_address(unsigned ifindex) : addr_{} +{ + addr_.can_family = AF_CAN; + addr_.can_ifindex = ifindex; +} + +can_address::can_address(const string& iface) : addr_{} +{ + unsigned idx = if_nametoindex(iface.c_str()); + + if (idx != 0) { + addr_.can_family = AF_CAN; + addr_.can_ifindex = idx; + } +} + +can_address::can_address(const sockaddr& addr) +{ + auto domain = addr.sa_family; + if (domain != AF_CAN) + throw std::invalid_argument("Not a SocketCAN address"); + + std::memcpy(&addr_, &addr, sizeof(sockaddr)); +} + +string can_address::iface() const +{ + if (addr_.can_family == AF_UNSPEC) + return string("none"); + + if (addr_.can_ifindex == 0) + return string("any"); + + char buf[IF_NAMESIZE]; + const char* iface = if_indextoname(addr_.can_ifindex, buf); + + return string(iface ? iface : "unknown"); +} + + +// -------------------------------------------------------------------------- + +ostream& operator<<(ostream& os, const can_address& addr) +{ + os << "can:" << addr.iface(); + return os; +} + +///////////////////////////////////////////////////////////////////////////// +// End namespace sockpp +} diff --git b/src/linux/can_socket.cpp a/src/linux/can_socket.cpp new file mode 100644 index 0000000..dc7e699 --- /dev/null +++ a/src/linux/can_socket.cpp @@ -0,0 +1,92 @@ +// can_socket.cpp +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2021 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include "sockpp/can_socket.h" +#include "sockpp/socket.h" +#include + +using namespace std; +using namespace std::chrono; + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +can_socket::can_socket(const can_address& addr) +{ + socket_t h = create_handle(SOCK_RAW, CAN_RAW); + + if (check_socket_bool(h)) { + reset(h); + bind(addr); + } +} + +system_clock::time_point can_socket::last_frame_time() +{ + timeval tv {}; + + // TODO: Handle error + ::ioctl(handle(), SIOCGSTAMP, &tv); + return to_timepoint(tv); +} + +double can_socket::last_frame_timestamp() +{ + timeval tv {}; + + // TODO: Handle error + ::ioctl(handle(), SIOCGSTAMP, &tv); + return double(tv.tv_sec) + 1.0e-6 * tv.tv_usec; +} + + +// -------------------------------------------------------------------------- + +ssize_t can_socket::recv_from(can_frame *frame, int flags, + can_address* srcAddr /*=nullptr*/) +{ + sockaddr* p = srcAddr ? srcAddr->sockaddr_ptr() : nullptr; + socklen_t len = srcAddr ? srcAddr->size() : 0; + + // TODO: Check returned length + return check_ret(::recvfrom(handle(), frame, sizeof(can_frame), + flags, p, &len)); +} + +///////////////////////////////////////////////////////////////////////////// +// End namespace sockpp +} diff --git b/src/socket.cpp a/src/socket.cpp new file mode 100644 index 0000000..e055aef --- /dev/null +++ a/src/socket.cpp @@ -0,0 +1,231 @@ +#include "sockpp/socket.h" +#include "sockpp/exception.h" +#include +#include +#include + +// Used to explicitly ignore the returned value of a function call. +#define ignore_result(x) if (x) {} + +using namespace std::chrono; + +namespace sockpp { + +timeval to_timeval(const microseconds& dur) +{ + const seconds sec = duration_cast(dur); + + timeval tv; + #if defined(_WIN32) + tv.tv_sec = long(sec.count()); + #else + tv.tv_sec = time_t(sec.count()); + #endif + tv.tv_usec = suseconds_t(duration_cast(dur - sec).count()); + return tv; +} + +int socket::get_last_error() +{ + #if defined(_WIN32) + return ::WSAGetLastError(); + #else + int err = errno; + return err; + #endif +} + +bool socket::close(socket_t h) +{ + #if defined(_WIN32) + return ::closesocket(h) >= 0; + #else + return ::close(h) >= 0; + #endif +} + +void socket::initialize() +{ + #if defined(_WIN32) + WSADATA wsadata; + ::WSAStartup(MAKEWORD(2, 0), &wsadata); + #else + // Don't signal on socket write errors. + ::signal(SIGPIPE, SIG_IGN); + #endif +} + +void socket::destroy() +{ + #if defined(_WIN32) + ::WSACleanup(); + #endif +} + +socket socket::create(int domain, int type, int protocol /*=0*/) +{ + socket sock(::socket(domain, type, protocol)); + if (!sock) + sock.clear(get_last_error()); + return sock; +} + +socket socket::clone() const +{ + socket_t h = INVALID_SOCKET; + #if defined(_WIN32) + WSAPROTOCOL_INFO protInfo; + if (::WSADuplicateSocket(handle_, ::GetCurrentProcessId(), &protInfo) == 0) + h = ::WSASocket(AF_INET, SOCK_STREAM, 0, &protInfo, 0, WSA_FLAG_OVERLAPPED); + // TODO: Set lastErr_ on failure + #else + h = ::dup(handle_); + #endif + + return socket(h); +} + +std::tuple socket::pair(int domain, int type, int protocol /*=0*/) +{ + socket sock0, sock1; + + #if !defined(_WIN32) + int sv[2]; + int ret = ::socketpair(domain, type, protocol, sv); + + if (ret == 0) { + sock0.reset(sv[0]); + sock1.reset(sv[1]); + } + else { + int err = get_last_error(); + sock0.clear(err); + sock1.clear(err); + } + #else + sock0.clear(ENOTSUP); + sock1.clear(ENOTSUP); + #endif + + // TODO: Should we set an "unsupported" error on Windows? + + return std::make_tuple(std::move(sock0), std::move(sock1)); +} + +void socket::reset(socket_t h /*=INVALID_SOCKET*/) +{ + socket_t oh = handle_; + handle_ = h; + if (oh != INVALID_SOCKET) + close(oh); + clear(); +} + +bool socket::bind(const sock_address& addr) +{ + return check_ret_bool(::bind(handle_, addr.sockaddr_ptr(), addr.size())); +} + +sock_address_any socket::address() const +{ + auto addrStore = sockaddr_storage{}; + socklen_t len = sizeof(sockaddr_storage); + + if (!check_ret_bool(::getsockname(handle_, + reinterpret_cast(&addrStore), &len))) + return sock_address_any{}; + + return sock_address_any(addrStore, len); +} + +sock_address_any socket::peer_address() const +{ + auto addrStore = sockaddr_storage{}; + socklen_t len = sizeof(sockaddr_storage); + + if (!check_ret_bool(::getpeername(handle_, + reinterpret_cast(&addrStore), &len))) + return sock_address_any{}; + + return sock_address_any(addrStore, len); +} + +bool socket::get_option(int level, int optname, void* optval, socklen_t* optlen) const +{ + #if defined(_WIN32) + if (optval && optlen) { + int len = static_cast(*optlen); + if (check_ret_bool(::getsockopt(handle_, level, optname, + static_cast(optval), &len))) { + *optlen = static_cast(len); + return true; + } + } + return false; + #else + return check_ret_bool(::getsockopt(handle_, level, optname, optval, optlen)); + #endif +} + +bool socket::set_option(int level, int optname, const void* optval, socklen_t optlen) +{ + #if defined(_WIN32) + return check_ret_bool(::setsockopt(handle_, level, optname, + static_cast(optval), + static_cast(optlen))); + #else + return check_ret_bool(::setsockopt(handle_, level, optname, optval, optlen)); + #endif +} + +bool socket::set_non_blocking(bool on /*=true*/) +{ + #if defined(_WIN32) + unsigned long mode = on ? 1 : 0; + return check_ret_bool(::ioctlsocket(handle_, FIONBIO, &mode)); + #else + /** + * TODO: Consider a generic function: + * bool set_flag(int flag, bool on=true); + * Used like: + * set_flag(O_NONBLOCK, on); + */ + int flags = ::fcntl(handle_, F_GETFL, 0); + + if (flags == -1) { + set_last_error(); + return false; + } + flags = on ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK); + + if (::fcntl(handle_, F_SETFL, flags) == -1) { + set_last_error(); + return false; + } + return true; + #endif +} + +std::string socket::error_str(int err) +{ + return sys_error::error_str(err); +} + +bool socket::shutdown(int how /*=SHUT_RDWR*/) +{ + return check_ret_bool(::shutdown(handle_, how)); +} + +bool socket::close() +{ + if (handle_ != INVALID_SOCKET) { + if (!close(release())){ + set_last_error(); + return false; + } + } + return true; +} + +} + diff --git b/src/stream_socket.cpp a/src/stream_socket.cpp new file mode 100644 index 0000000..6840afd --- /dev/null +++ a/src/stream_socket.cpp @@ -0,0 +1,152 @@ +#include "socket-cpp/stream_socket.h" +#include "socket-cpp/exception.h" +#include +#include + +using namespace std::chrono; + +namespace socket-cpp { + +stream_socket stream_socket::create(int domain, int protocol /*=0*/) +{ + stream_socket sock(::socket(domain, COMM_TYPE, protocol)); + if (!sock) + sock.clear(get_last_error()); + return sock; +} + +ssize_t stream_socket::read(void *buf, size_t n) +{ + #if defined(_WIN32) + return check_ret(::recv(handle(), reinterpret_cast(buf), + int(n), 0)); + #else + return check_ret(::recv(handle(), buf, n, 0)); + #endif +} + +ssize_t stream_socket::read_n(void *buf, size_t n) +{ + size_t nr = 0; + ssize_t nx = 0; + + uint8_t *b = reinterpret_cast(buf); + + while (nr < n) { + if ((nx = read(b+nr, n-nr)) < 0 && last_error() == EINTR) + continue; + + if (nx <= 0) + break; + + nr += nx; + } + + return (nr == 0 && nx < 0) ? nx : ssize_t(nr); +} + + +ssize_t stream_socket::read(const std::vector& ranges) +{ + if (ranges.empty()) + return 0; + + #if !defined(_WIN32) + return check_ret(::readv(handle(), ranges.data(), int(ranges.size()))); + #else + std::vector bufs; + for (const auto& iovec : ranges) { + bufs.push_back({ + static_cast(iovec.iov_len), + static_cast(iovec.iov_base) + }); + } + + DWORD flags = 0, + nread = 0, + nbuf = DWORD(bufs.size()); + + auto ret = check_ret(::WSARecv(handle(), bufs.data(), nbuf, &nread, &flags, nullptr, nullptr)); + return ssize_t(ret == SOCKET_ERROR ? ret : nread); + #endif +} + +bool stream_socket::read_timeout(const microseconds& to) +{ + auto tv = + #if defined(_WIN32) + DWORD(duration_cast(to).count()); + #else + to_timeval(to); + #endif + return set_option(SOL_SOCKET, SO_RCVTIMEO, tv); +} + +ssize_t stream_socket::write(const void *buf, size_t n) +{ + #if defined(_WIN32) + return check_ret(::send(handle(), reinterpret_cast(buf), + int(n) , 0)); + #else + return check_ret(::send(handle(), buf, n , 0)); + #endif +} + +ssize_t stream_socket::write_n(const void *buf, size_t n) +{ + size_t nw = 0; + ssize_t nx = 0; + + const uint8_t *b = reinterpret_cast(buf); + + while (nw < n) { + if ((nx = write(b+nw, n-nw)) < 0 && last_error() == EINTR) + continue; + + if (nx <= 0) + break; + + nw += nx; + } + + return (nw == 0 && nx < 0) ? nx : ssize_t(nw); +} + +ssize_t stream_socket::write(const std::vector& ranges) +{ + if (ranges.empty()) + return 0; + + #if !defined(_WIN32) + return check_ret(::writev(handle(), ranges.data(), int(ranges.size()))); + #else + std::vector bufs; + for (const auto& iovec : ranges) { + bufs.push_back({ + static_cast(iovec.iov_len), + static_cast(iovec.iov_base) + }); + } + + DWORD nwritten = 0, + nmsg = DWORD(bufs.size()); + + auto ret = check_ret(::WSASend(handle(), bufs.data(), nmsg, &nwritten, 0, nullptr, nullptr)); + return ssize_t(ret == SOCKET_ERROR ? ret : nwritten); + #endif +} + +bool stream_socket::write_timeout(const microseconds& to) +{ + auto tv = + #if defined(_WIN32) + DWORD(duration_cast(to).count()); + #else + to_timeval(to); + #endif + + return set_option(SOL_SOCKET, SO_SNDTIMEO, tv); +} + +} + diff --git b/src/unix/unix_address.cpp a/src/unix/unix_address.cpp new file mode 100644 index 0000000..a4088ed --- /dev/null +++ a/src/unix/unix_address.cpp @@ -0,0 +1,79 @@ +// unix_address.cpp +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2014-2017 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include "sockpp/unix_address.h" +#include +#include + +using namespace std; + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +constexpr sa_family_t unix_address::ADDRESS_FAMILY; +constexpr size_t unix_address::MAX_PATH_NAME; + +// -------------------------------------------------------------------------- + +unix_address::unix_address(const string& path) +{ + addr_.sun_family = ADDRESS_FAMILY; + ::strncpy(addr_.sun_path, path.c_str(), MAX_PATH_NAME); +} + +unix_address::unix_address(const sockaddr& addr) +{ + auto domain = addr.sa_family; + if (domain != AF_UNIX) + throw std::invalid_argument("Not a UNIX-domain address"); + + // TODO: We should check the path, or at least see that it has + // proper NUL termination. + std::memcpy(&addr_, &addr, sizeof(sockaddr)); +} + +// -------------------------------------------------------------------------- + +ostream& operator<<(ostream& os, const unix_address& addr) +{ + os << "unix:" << addr.path(); + return os; +} + +///////////////////////////////////////////////////////////////////////////// +// End namespace sockpp +} diff --git b/tests/udptst.cpp a/tests/udptst.cpp new file mode 100644 index 0000000..f440294 --- /dev/null +++ a/tests/udptst.cpp @@ -0,0 +1,75 @@ +// udptst.cpp + +#include +#include "sockpp/socket.h" + +using namespace std; + +// -------------------------------------------------------------------------- + +int do_recv(sockpp::udp_socket& sock) +{ + char buf[6]; + int n = sock.recv(buf, sizeof(buf)); + + if (n < 0) { + cerr << "Error sending packet: [" + << sock.last_error() << "]" << endl; + return -1; + } + + cout << "Received " << n << " bytes" << flush; + buf[n] = '\0'; + cout << " '" << buf << "'" << endl; + return 0; +} + +// -------------------------------------------------------------------------- + +int main() +{ + in_port_t port = 12345; + + cout << "Testing UDP sockets" << endl; + sockpp::udp_socket srvrSock; + + if (!srvrSock) { + cerr << "Error creating server socket [" + << srvrSock.last_error() << "]" << endl; + return 1; + } + + if (!srvrSock.bind(port)) { + cerr << "Error binding to port: " << port << " [" + << srvrSock.last_error() << "]" << endl; + return 1; + } + + sockpp::udp_socket cliSock; + + if (!cliSock) { + cerr << "Error creating server socket [" + << cliSock.last_error() << "]" << endl; + return 1; + } + + sockpp::inet_address localAddr("localhost", port); + + if (!cliSock.connect(localAddr)) { + cerr << "Error connecting to port: " << port << " [" + << cliSock.last_error() << "]" << endl; + return 1; + } + + cliSock.send("Hello"); + do_recv(srvrSock); + + cliSock.close(); + + sockpp::udp_socket sock; + sock.sendto("bubba", localAddr); + do_recv(srvrSock); + + return 0; +} + diff --git b/tests/unit/test_acceptor.cpp a/tests/unit/test_acceptor.cpp new file mode 100644 index 0000000..2ff5f38 --- /dev/null +++ a/tests/unit/test_acceptor.cpp @@ -0,0 +1,130 @@ +// test_acceptor.cpp +// +// Unit tests for the `acceptor` class(es). +// + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- +// + +#include "sockpp/acceptor.h" +#include "sockpp/inet_address.h" +#include "catch2/catch.hpp" +#include + +using namespace sockpp; + +TEST_CASE("acceptor default constructor", "[acceptor]") { + acceptor sock; + REQUIRE(!sock); + REQUIRE(!sock.is_open()); +} + +TEST_CASE("acceptor handle constructor", "[acceptor]") { + constexpr auto HANDLE = socket_t(3); + + SECTION("valid handle") { + acceptor sock(HANDLE); + REQUIRE(sock); + REQUIRE(sock.is_open()); + } + + SECTION("invalid handle") { + acceptor sock(INVALID_SOCKET); + REQUIRE(!sock); + REQUIRE(!sock.is_open()); + // TODO: Should this set an error? + REQUIRE(sock.last_error() == 0); + } +} + +TEST_CASE("acceptor address constructor", "[acceptor]") { + SECTION("valid address") { + const auto ADDR = inet_address("localhost", 12345); + + acceptor sock(ADDR); + REQUIRE(sock); + REQUIRE(sock.is_open()); + REQUIRE(sock.last_error() == 0); + REQUIRE(sock.address() == ADDR); + } + + SECTION("invalid address") { + const auto ADDR = sock_address_any{}; + + acceptor sock(ADDR); + REQUIRE(!sock); + REQUIRE(!sock.is_open()); + + // Windows returns a different error code than *nix + #if defined(_WIN32) + REQUIRE(sock.last_error() == WSAEINVAL); + #else + REQUIRE(sock.last_error() == EAFNOSUPPORT); + #endif + } +} + +TEST_CASE("acceptor create", "[acceptor]") { + SECTION("valid domain") { + auto sock = acceptor::create(AF_INET); + + REQUIRE(sock); + REQUIRE(sock.is_open()); + REQUIRE(sock.last_error() == 0); + + // Windows returns unknown family for unbound socket + // Windows returns a different error code than *nix + #if defined(_WIN32) + REQUIRE(sock.family() == AF_UNSPEC); + #else + REQUIRE(sock.family() == AF_INET); + #endif + } + + SECTION("invalid domain") { + auto sock = acceptor::create(AF_UNSPEC); + + REQUIRE(!sock); + REQUIRE(!sock.is_open()); + + // Windows returns a different error code than *nix + #if defined(_WIN32) + REQUIRE(sock.last_error() == WSAEINVAL); + #else + REQUIRE(sock.last_error() == EAFNOSUPPORT); + #endif + } +} + diff --git b/tests/unit/test_connector.cpp a/tests/unit/test_connector.cpp new file mode 100644 index 0000000..0ea0faa --- /dev/null +++ a/tests/unit/test_connector.cpp @@ -0,0 +1,66 @@ +// test_connector.cpp +// +// Unit tests for the `connector` class(es). +// + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- +// + +#include "sockpp/connector.h" +#include "sockpp/sock_address.h" +#include "catch2/catch.hpp" +#include + +using namespace sockpp; + +// Test that connector errors properly when given an empty address. +TEST_CASE("connector unspecified address", "[connector]") { + connector conn; + REQUIRE(!conn); + + sock_address_any addr; + + bool ok = conn.connect(addr); + REQUIRE(!ok); + + // Windows returns a different error code than *nix + #if defined(_WIN32) + REQUIRE(conn.last_error() == WSAENOTSOCK); + #else + REQUIRE(conn.last_error() == EAFNOSUPPORT); + #endif +} + + diff --git b/tests/unit/test_datagram_socket.cpp a/tests/unit/test_datagram_socket.cpp new file mode 100644 index 0000000..925872a --- /dev/null +++ a/tests/unit/test_datagram_socket.cpp @@ -0,0 +1,98 @@ +// test_datagram_socket.cpp +// +// Unit tests for the `datagram_socket` class(es). +// + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- +// + +#include "sockpp/datagram_socket.h" +#include "sockpp/inet_address.h" +#include "catch2/catch.hpp" +#include + +using namespace sockpp; + +TEST_CASE("datagram_socket default constructor", "[datagram_socket]") { + datagram_socket sock; + REQUIRE(!sock); + REQUIRE(!sock.is_open()); +} + +TEST_CASE("datagram_socket handle constructor", "[datagram_socket]") { + constexpr auto HANDLE = socket_t(3); + + SECTION("valid handle") { + datagram_socket sock(HANDLE); + REQUIRE(sock); + REQUIRE(sock.is_open()); + } + + SECTION("invalid handle") { + datagram_socket sock(INVALID_SOCKET); + REQUIRE(!sock); + REQUIRE(!sock.is_open()); + // TODO: Should this set an error? + REQUIRE(sock.last_error() == 0); + } +} + +TEST_CASE("datagram_socket address constructor", "[datagram_socket]") { + SECTION("valid address") { + const auto ADDR = inet_address("localhost", 12345); + + datagram_socket sock(ADDR); + REQUIRE(sock); + REQUIRE(sock.is_open()); + REQUIRE(sock.last_error() == 0); + REQUIRE(sock.address() == ADDR); + } + + SECTION("invalid address") { + const auto ADDR = sock_address_any(); + + datagram_socket sock(ADDR); + REQUIRE(!sock); + REQUIRE(!sock.is_open()); + + // Windows returns a different error code than *nix + #if defined(_WIN32) + REQUIRE(sock.last_error() == WSAEINVAL); + #else + REQUIRE(sock.last_error() == EAFNOSUPPORT); + #endif + } +} + diff --git b/tests/unit/test_inet_address.cpp a/tests/unit/test_inet_address.cpp new file mode 100644 index 0000000..e56835d --- /dev/null +++ a/tests/unit/test_inet_address.cpp @@ -0,0 +1,144 @@ +// test_inet_address.cpp +// +// Unit tests for the `inet_address` class. +// + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2018 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- +// + +#include "sockpp/inet_address.h" +#include "catch2/catch.hpp" +#include + +using namespace sockpp; + +const uint32_t ANY_ADDR { INADDR_ANY }; // Any iface 0x00000000 +const uint32_t LOCALHOST_ADDR { INADDR_LOOPBACK }; // Localhost 0x7F000001 +const std::string LOCALHOST_STR { "localhost" }; +const in_port_t PORT { 12345 }; + +TEST_CASE("inet_address default constructor", "[address]") { + inet_address addr; + + REQUIRE(!addr.is_set()); + REQUIRE(0 == addr.address()); + REQUIRE(0 == addr.port()); + REQUIRE(sizeof(sockaddr_in) == addr.size()); + + SECTION("creating address from int32") { + addr.create(LOCALHOST_ADDR, PORT); + + REQUIRE(addr.is_set()); + REQUIRE(LOCALHOST_ADDR == addr.address()); + REQUIRE(PORT == addr.port()); + + REQUIRE(uint8_t((LOCALHOST_ADDR >> 0) &0xFF) == addr[0]); + REQUIRE(uint8_t((LOCALHOST_ADDR >> 8) &0xFF) == addr[1]); + REQUIRE(uint8_t((LOCALHOST_ADDR >> 16) &0xFF) == addr[2]); + REQUIRE(uint8_t((LOCALHOST_ADDR >> 24) &0xFF) == addr[3]); + + // Check the low-level struct + REQUIRE(AF_INET == addr.sockaddr_in_ptr()->sin_family); + REQUIRE(LOCALHOST_ADDR == ntohl(addr.sockaddr_in_ptr()->sin_addr.s_addr)); + REQUIRE(PORT == ntohs(addr.sockaddr_in_ptr()->sin_port)); + } + + SECTION("creating address from name") { + addr.create(LOCALHOST_STR, PORT); + + REQUIRE(addr.is_set()); + REQUIRE(LOCALHOST_ADDR == addr.address()); + REQUIRE(PORT == addr.port()); + + // Check the low-level struct + REQUIRE(AF_INET == addr.sockaddr_in_ptr()->sin_family); + REQUIRE(LOCALHOST_ADDR == ntohl(addr.sockaddr_in_ptr()->sin_addr.s_addr)); + REQUIRE(PORT == ntohs(addr.sockaddr_in_ptr()->sin_port)); + } +} + +// When created using only a port number this should use the +// "any" address to bind to all interfaces (typ for server) +TEST_CASE("inet_address port-only constructor", "[address]") { + inet_address addr(PORT); + + REQUIRE(addr.is_set()); + REQUIRE(ANY_ADDR == addr.address()); + REQUIRE(PORT == addr.port()); + + // Check the low-level struct + REQUIRE(AF_INET == addr.sockaddr_in_ptr()->sin_family); +} + +TEST_CASE("inet_address int32_t constructor", "[address]") { + inet_address addr(LOCALHOST_ADDR, PORT); + + REQUIRE(addr.is_set()); + REQUIRE(LOCALHOST_ADDR == addr.address()); + REQUIRE(PORT == addr.port()); + + REQUIRE(uint8_t((LOCALHOST_ADDR >> 0) &0xFF) == addr[0]); + REQUIRE(uint8_t((LOCALHOST_ADDR >> 8) &0xFF) == addr[1]); + REQUIRE(uint8_t((LOCALHOST_ADDR >> 16) &0xFF) == addr[2]); + REQUIRE(uint8_t((LOCALHOST_ADDR >> 24) &0xFF) == addr[3]); + + // Check the low-level struct + REQUIRE(AF_INET == addr.sockaddr_in_ptr()->sin_family); + REQUIRE(LOCALHOST_ADDR == ntohl(addr.sockaddr_in_ptr()->sin_addr.s_addr)); + REQUIRE(PORT == ntohs(addr.sockaddr_in_ptr()->sin_port)); +} + +TEST_CASE("inet_address name constructor", "[address]") { + inet_address addr(LOCALHOST_STR, PORT); + + REQUIRE(addr.is_set()); + REQUIRE(LOCALHOST_ADDR == addr.address()); + REQUIRE(PORT == addr.port()); + + REQUIRE(uint8_t((LOCALHOST_ADDR >> 0) &0xFF) == addr[0]); + REQUIRE(uint8_t((LOCALHOST_ADDR >> 8) &0xFF) == addr[1]); + REQUIRE(uint8_t((LOCALHOST_ADDR >> 16) &0xFF) == addr[2]); + REQUIRE(uint8_t((LOCALHOST_ADDR >> 24) &0xFF) == addr[3]); + + // Check the low-level struct + REQUIRE(AF_INET == addr.sockaddr_in_ptr()->sin_family); + REQUIRE(LOCALHOST_ADDR == ntohl(addr.sockaddr_in_ptr()->sin_addr.s_addr)); + REQUIRE(PORT == ntohs(addr.sockaddr_in_ptr()->sin_port)); +} + +TEST_CASE("IPv4 resolve_address", "[address]") { + REQUIRE(inet_address::resolve_name("127.0.0.1") == htonl(LOCALHOST_ADDR)); + REQUIRE(inet_address::resolve_name(LOCALHOST_STR) == htonl(LOCALHOST_ADDR)); +} diff --git b/tests/unit/test_socket.cpp a/tests/unit/test_socket.cpp new file mode 100644 index 0000000..8669e89 --- /dev/null +++ a/tests/unit/test_socket.cpp @@ -0,0 +1,332 @@ +// test_socket.cpp +// +// Unit tests for the base `socket` class. +// + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- +// + +#include "sockpp/socket.h" +#include "sockpp/inet_address.h" +#include "catch2/catch.hpp" +#include + +using namespace sockpp; +using namespace std::chrono; + +///////////////////////////////////////////////////////////////////////////// +// Aux functions + +TEST_CASE("test to_timeval", "aux") { + SECTION("concrete function") { + timeval tv = to_timeval(microseconds(500)); + REQUIRE(tv.tv_sec == 0); + REQUIRE(tv.tv_usec == 500); + + tv = to_timeval(microseconds(2500000)); + REQUIRE(tv.tv_sec == 2); + REQUIRE(tv.tv_usec == 500000); + } + + SECTION("template") { + timeval tv = to_timeval(milliseconds(1)); + REQUIRE(tv.tv_sec == 0); + REQUIRE(tv.tv_usec == 1000); + + tv = to_timeval(milliseconds(2500)); + REQUIRE(tv.tv_sec == 2); + REQUIRE(tv.tv_usec == 500000); + + tv = to_timeval(seconds(5)); + REQUIRE(tv.tv_sec == 5); + REQUIRE(tv.tv_usec == 0); + } +} + +///////////////////////////////////////////////////////////////////////////// +// socket class + +constexpr in_port_t INET_TEST_PORT = 12346; + +TEST_CASE("socket constructors", "[socket]") { + SECTION("default constructor") { + sockpp::socket sock; + + REQUIRE(!sock); + REQUIRE(!sock.is_open()); + REQUIRE(sock.handle() == INVALID_SOCKET); + REQUIRE(sock.last_error() == 0); + } + + SECTION("handle constructor") { + constexpr auto HANDLE = socket_t(3); + sockpp::socket sock(HANDLE); + + REQUIRE(sock); + REQUIRE(sock.is_open()); + REQUIRE(sock.handle() == HANDLE); + REQUIRE(sock.last_error() == 0); + } + + + SECTION("move constructor") { + constexpr auto HANDLE = socket_t(3); + sockpp::socket org_sock(HANDLE); + + sockpp::socket sock(std::move(org_sock)); + + // Make sure the new socket got the handle + REQUIRE(sock); + REQUIRE(sock.handle() == HANDLE); + REQUIRE(sock.last_error() == 0); + + // Make sure the handle was moved out of the org_sock + REQUIRE(!org_sock); + REQUIRE(org_sock.handle() == INVALID_SOCKET); + } +} + +// Test the socket error behavior +TEST_CASE("socket errors", "[socket]") { + SECTION("basic errors") { + sockpp::socket sock; + + // Operations on an unopened socket should give an error + int reuse = 1; + socklen_t len = sizeof(int); + bool ok = sock.get_option(SOL_SOCKET, SO_REUSEADDR, &reuse, &len); + + // Socket should be in error state + REQUIRE(!ok); + REQUIRE(!sock); + + int err = sock.last_error(); + REQUIRE(err != 0); + + // last_error() is sticky, unlike `errno` + REQUIRE(sock.last_error() == err); + + // We can clear the error + sock.clear(); + REQUIRE(sock.last_error() == 0); + + // Test arbitrary clear value + sock.clear(42); + REQUIRE(sock.last_error() == 42); + REQUIRE(!sock); + } + + SECTION("clear error") { + auto sock = sockpp::socket::create(AF_INET, SOCK_STREAM); + REQUIRE(sock); + + sock.clear(42); + REQUIRE(!sock); + + sock.clear(); + REQUIRE(sock); + } +} + +TEST_CASE("socket handles", "[socket]") { + + constexpr auto HANDLE = socket_t(3); + + SECTION("test release") { + sockpp::socket sock(HANDLE); + + REQUIRE(sock.handle() == HANDLE); + REQUIRE(sock.release() == HANDLE); + + // Make sure the handle was moved out of the sock + REQUIRE(!sock); + REQUIRE(sock.handle() == INVALID_SOCKET); + } + + SECTION("test reset") { + sockpp::socket sock(HANDLE); + REQUIRE(sock.handle() == HANDLE); + + sock.reset(); // Default reset acts like release w/o return + + // Make sure the handle was moved out of the sock + REQUIRE(!sock); + REQUIRE(sock.handle() == INVALID_SOCKET); + + // Now reset with a "valid" handle + sock.reset(HANDLE); + REQUIRE(sock); + REQUIRE(sock.handle() == HANDLE); + } +} + +TEST_CASE("socket family", "[socket]") { + SECTION("uninitialized socket") { + // Uninitialized socket should have unspecified family + sockpp::socket sock; + REQUIRE(sock.family() == AF_UNSPEC); + } + + SECTION("unbound socket") { + // Unbound socket should have creation family + auto sock = socket::create(AF_INET, SOCK_STREAM); + + // Windows and *nix behave differently + #if defined(_WIN32) + REQUIRE(sock.family() == AF_UNSPEC); + #else + REQUIRE(sock.family() == AF_INET); + #endif + } + + SECTION("bound socket") { + // Bound socket should have same family as + // address to which it's bound + auto sock = socket::create(AF_INET, SOCK_STREAM); + inet_address addr(INET_TEST_PORT); + + int reuse = 1; + REQUIRE(sock.set_option(SOL_SOCKET, SO_REUSEADDR, reuse)); + REQUIRE(sock.bind(addr)); + REQUIRE(sock.family() == addr.family()); + } +} + +TEST_CASE("socket address", "[socket]") { + SECTION("uninitialized socket") { + // Uninitialized socket should have empty address + sockpp::socket sock; + REQUIRE(sock.address() == sock_address_any{}); + } + + // The address has the specified family but all zeros + SECTION("unbound socket") { + auto sock = socket::create(AF_INET, SOCK_STREAM); + auto addr = inet_address(sock.address()); + + // Windows and *nix behave differently for family + #if defined(_WIN32) + REQUIRE(sock.family() == AF_UNSPEC); + #else + REQUIRE(sock.family() == AF_INET); + #endif + + REQUIRE(addr.address() == 0); + REQUIRE(addr.port() == 0); + } + + SECTION("bound socket") { + // Bound socket should have same family as + // address to which it's bound + auto sock = socket::create(AF_INET, SOCK_STREAM); + const inet_address ADDR(INET_TEST_PORT); + + int reuse = 1; + REQUIRE(sock.set_option(SOL_SOCKET, SO_REUSEADDR, reuse)); + + REQUIRE(sock.bind(ADDR)); + REQUIRE(sock.address() == ADDR); + } +} + +// Socket pair shouldn't work for TCP sockets on any known platform. +// So this should fail, but fail gracefully and retain the error +// in both sockets. +TEST_CASE("failed socket pair", "[socket]") { + sockpp::socket sock1, sock2; + std::tie(sock1, sock2) = std::move(socket::pair(AF_INET, SOCK_STREAM)); + + REQUIRE(!sock1); + REQUIRE(!sock2); + + REQUIRE(sock1.last_error() != 0); + REQUIRE(sock1.last_error() == sock2.last_error()); +} + +// -------------------------------------------------------------------------- + +// Test that the "last error" call to a socket gives the proper result +// for the current thread. +// Here we share a socket across two threads, force an error in one +// thread, and then check to make sure that the error did not propagate +// to the other thread. +// +#if 0 +TEST_CASE("thread-safe last error", "[socket]") { + sockpp::socket sock; + + int state = 0; + std::mutex m; + std::condition_variable cv; + + std::thread thr([&] { + // Test #1 + REQUIRE(sock.last_error() == 0); + { + // Wait for Test #2 + std::unique_lock lk(m); + state = 1; + cv.notify_one(); + cv.wait(lk, [&state]{return state >= 2;}); + } + + // Test #3 + REQUIRE(sock.last_error() == 0); + }); + + { + // Wait for Test #1 + std::unique_lock lk(m); + cv.wait(lk, [&state]{return state >= 1;}); + } + + // Test #2 + // Setting options on an un-opened socket should generate an error + int reuse = 1; + socklen_t len = sizeof(int); + bool ok = sock.get_option(SOL_SOCKET, SO_REUSEADDR, &reuse, &len); + + REQUIRE(!ok); + REQUIRE(sock.last_error() != 0); + + { + std::unique_lock lk(m); + state = 2; + cv.notify_one(); + } + thr.join(); +} +#endif + diff --git b/tests/unit/test_stream_socket.cpp a/tests/unit/test_stream_socket.cpp new file mode 100644 index 0000000..6c59e12 --- /dev/null +++ a/tests/unit/test_stream_socket.cpp @@ -0,0 +1,100 @@ +// test_stream_socket.cpp +// +// Unit tests for the `stream_socket` class(es). +// + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- +// + +#include "sockpp/stream_socket.h" +#include "sockpp/inet_address.h" +#include "catch2/catch.hpp" +#include + +using namespace sockpp; + +TEST_CASE("stream_socket default constructor", "[stream_socket]") { + stream_socket sock; + REQUIRE(!sock); + REQUIRE(!sock.is_open()); +} + +TEST_CASE("stream_socket handle constructor", "[stream_socket]") { + constexpr auto HANDLE = socket_t(3); + + SECTION("valid handle") { + stream_socket sock(HANDLE); + REQUIRE(sock); + REQUIRE(sock.is_open()); + } + + SECTION("invalid handle") { + stream_socket sock(INVALID_SOCKET); + REQUIRE(!sock); + REQUIRE(!sock.is_open()); + // TODO: Should this set an error? + REQUIRE(sock.last_error() == 0); + } +} + +#if 0 +TEST_CASE("stream_socket address constructor", "[stream_socket]") { + SECTION("valid address") { + const auto ADDR = inet_address("localhost", 12345); + + stream_socket sock(ADDR); + REQUIRE(sock); + REQUIRE(sock.is_open()); + REQUIRE(sock.last_error() == 0); + REQUIRE(sock.address() == ADDR); + } + + SECTION("invalid address") { + const auto ADDR = sock_address_any(); + + stream_socket sock(ADDR); + REQUIRE(!sock); + REQUIRE(!sock.is_open()); + REQUIRE(sock.last_error() == EAFNOSUPPORT); + } +} +#endif + +// -------------------------------------------------------------------------- +// Connected tests + +TEST_CASE("stream_socket readn, writen", "[stream_socket]") { + //auto lsock = stream_socket::create(AF +} diff --git b/tests/unit/test_tcp_socket.cpp a/tests/unit/test_tcp_socket.cpp new file mode 100644 index 0000000..8aa26d4 --- /dev/null +++ a/tests/unit/test_tcp_socket.cpp @@ -0,0 +1,154 @@ +// test_tcp_socket.cpp +// +// Unit tests for the `tcp_socket` class(es). +// + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- +// + +#include "sockpp/tcp_socket.h" +#include "sockpp/tcp_connector.h" +#include "sockpp/tcp_acceptor.h" +#include "catch2/catch.hpp" +#include + +using namespace sockpp; + +static const in_port_t TEST_PORT = 12345; + +TEST_CASE("tcp_socket default constructor", "[tcp_socket]") { + tcp_socket sock; + REQUIRE(!sock); + REQUIRE(!sock.is_open()); + + //REQUIRE(sock.family() == AF_INET); +} + +TEST_CASE("tcp_socket handle constructor", "[tcp_socket]") { + constexpr auto HANDLE = socket_t(3); + + SECTION("valid handle") { + tcp_socket sock(HANDLE); + REQUIRE(sock); + REQUIRE(sock.is_open()); + + //REQUIRE(sock.family() == AF_INET); + } + + SECTION("invalid handle") { + tcp_socket sock(INVALID_SOCKET); + REQUIRE(!sock); + REQUIRE(!sock.is_open()); + // TODO: Should this set an error? + REQUIRE(sock.last_error() == 0); + + //REQUIRE(sock.family() == AF_INET); + } +} + +// -------------------------------------------------------------------------- +// Connected tests + +TEST_CASE("tcp_socket read/write", "[stream_socket]") { + const std::string STR { "This is a test. This is only a test." }; + const size_t N = STR.length(); + + inet_address addr { "localhost", TEST_PORT }; + tcp_acceptor asock{ addr }; + + tcp_connector csock; + csock.set_non_blocking(); + + REQUIRE(csock.connect(addr)); + + auto ssock = asock.accept(); + REQUIRE(ssock); + + SECTION("read_n/write_n") { + char buf[512]; // N + + REQUIRE(csock.write_n(STR.data(), N) == N); + REQUIRE(ssock.read_n(buf, N) == N); + + std::string str { buf, buf+N }; + REQUIRE(str == STR); + + char buf2[512]; // N + + // string write is a write_n() + REQUIRE(csock.write(STR) == N); + REQUIRE(ssock.read_n(buf2, N) == N); + + std::string str2 { buf2, buf2+N }; + REQUIRE(str2 == STR); + } + + SECTION("scatter/gather") { + const std::string HEADER { "" }, + FOOTER { "" }; + + const size_t N_HEADER = HEADER.length(), + N_FOOTER = FOOTER.length(), + N_TOT = N_HEADER + N + N_FOOTER; + + std::vector outv { + iovec { (void*) HEADER.data(), N_HEADER }, + iovec { (void*) STR.data(), N }, + iovec { (void*) FOOTER.data(), N_FOOTER } + }; + + char hbuf[512], // N_HEADER + buf[512], // N + fbuf[512]; // N_FOOTER + + std::vector inv { + iovec { (void*) hbuf, N_HEADER }, + iovec { (void*) buf, N }, + iovec { (void*) fbuf, N_FOOTER } + }; + + REQUIRE(csock.write(outv) == N_TOT); + REQUIRE(csock.write(outv) == N_TOT); + REQUIRE(csock.write(outv) == N_TOT); + + REQUIRE(ssock.read(inv) == N_TOT); + + REQUIRE(std::string(hbuf, N_HEADER) == HEADER); + REQUIRE(std::string(buf, N) == STR); + REQUIRE(std::string(fbuf, N_FOOTER) == FOOTER); + } +} + + diff --git b/tests/unit/test_unix_address.cpp a/tests/unit/test_unix_address.cpp new file mode 100644 index 0000000..11ca6b5 --- /dev/null +++ a/tests/unit/test_unix_address.cpp @@ -0,0 +1,124 @@ +// test_unix_address.cpp +// +// Unit tests for the `unix_address` class. +// + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2018 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- +// + +#include "sockpp/unix_address.h" +#include "catch2/catch.hpp" +#include + +using namespace sockpp; + +std::string PATH { "/tmp/sock" }; + +TEST_CASE("unix_address default constructor", "[address]") { + unix_address addr; + + REQUIRE(!addr.is_set()); + REQUIRE(addr.path().empty()); + REQUIRE(sizeof(sockaddr_un) == addr.size()); + + // TODO: Do we have a unix_address::create() method yet? +} + +TEST_CASE("unix_address path constructor", "[address]") { + unix_address addr(PATH); + + REQUIRE(addr.is_set()); + REQUIRE(PATH == addr.path()); + REQUIRE(sizeof(sockaddr_un) == addr.size()); + + // Check the low-level struct + REQUIRE(AF_UNIX == addr.sockaddr_un_ptr()->sun_family); + REQUIRE(0 == strcmp(PATH.c_str(), + (const char*) &addr.sockaddr_un_ptr()->sun_path)); + + SECTION("copy constructor") { + unix_address addr2(addr); + + REQUIRE(addr2.is_set()); + REQUIRE(PATH == addr2.path()); + REQUIRE(sizeof(sockaddr_un) == addr2.size()); + + // Check the low-level struct + REQUIRE(AF_UNIX == addr2.sockaddr_un_ptr()->sun_family); + REQUIRE(0 == strcmp(PATH.c_str(), + (const char*) &addr2.sockaddr_un_ptr()->sun_path)); + } + + SECTION("sockaddr conversions") { + auto sa = addr.sockaddr_ptr(); + unix_address addr2(*sa); + + REQUIRE(addr2.is_set()); + REQUIRE(PATH == addr2.path()); + REQUIRE(sizeof(sockaddr_un) == addr2.size()); + + // Check the low-level struct + REQUIRE(AF_UNIX == addr2.sockaddr_un_ptr()->sun_family); + REQUIRE(0 == strcmp(PATH.c_str(), + (const char*) &(addr2.sockaddr_un_ptr()->sun_path))); + } +} + +TEST_CASE("unix_address sockaddr_un constructor", "[address]") { + sockaddr_un unaddr; + unaddr.sun_family = AF_UNIX; + strcpy(unaddr.sun_path, PATH.c_str()); + + unix_address addr(unaddr); + + REQUIRE(addr.is_set()); + REQUIRE(PATH == addr.path()); + REQUIRE(sizeof(sockaddr_un) == addr.size()); + + // Check the low-level struct + REQUIRE(AF_UNIX == addr.sockaddr_un_ptr()->sun_family); + REQUIRE(0 == strcmp(PATH.c_str(), + (const char*) &addr.sockaddr_un_ptr()->sun_path)); + + // TODO: Restore this when all address checks in place + /* + SECTION("reject bad sockaddr_un") { + unaddr.sun_family = AF_INET; + REQUIRE_THROWS_AS([&] { + unix_address addr2(unaddr); + }(), std::invalid_argument); + } + */ +} diff --git b/tests/unit/test_unix_dgram_socket.cpp a/tests/unit/test_unix_dgram_socket.cpp new file mode 100644 index 0000000..95e97d0 --- /dev/null +++ a/tests/unit/test_unix_dgram_socket.cpp @@ -0,0 +1,71 @@ +// test_unix_dgram_socket.cpp +// +// Unit tests for the `unix_dgram_socket` class. +// + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- +// + +#include "sockpp/unix_dgram_socket.h" +#include "catch2/catch.hpp" +#include + +using namespace sockpp; + +// Test that we can create a Unix-domain datagram socket pair and send data +// from one of the sockets to the other. +TEST_CASE("unix dgram socket pair", "[unix_dgram_socket]") { + unix_dgram_socket sock1, sock2; + std::tie(sock1, sock2) = std::move(unix_dgram_socket::pair()); + + REQUIRE(sock1); + REQUIRE(sock2); + + REQUIRE(sock1.is_open()); + REQUIRE(sock2.is_open()); + + const std::string MSG { "Hello there!" }; + const size_t N = MSG.length(); + + char buf[512]; + + REQUIRE(sock1.send(MSG) == N); + REQUIRE(sock2.recv(buf, N) == N); + + std::string msg { buf, buf+N }; + REQUIRE(msg == MSG); +} + + diff --git b/tests/unit/test_unix_stream_socket.cpp a/tests/unit/test_unix_stream_socket.cpp new file mode 100644 index 0000000..77b5960 --- /dev/null +++ a/tests/unit/test_unix_stream_socket.cpp @@ -0,0 +1,71 @@ +// test_unix_stream_socket.cpp +// +// Unit tests for the `unix_stream_socket` class. +// + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2019 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- +// + +#include "sockpp/unix_stream_socket.h" +#include "catch2/catch.hpp" +#include + +using namespace sockpp; + +// Test that we can create a Unix-domain stream socket pair and send +// data from one of the sockets to the other. +TEST_CASE("unix stream socket pair", "[unix_stream_socket]") { + unix_stream_socket sock1, sock2; + std::tie(sock1, sock2) = std::move(unix_stream_socket::pair()); + + REQUIRE(sock1); + REQUIRE(sock2); + + REQUIRE(sock1.is_open()); + REQUIRE(sock2.is_open()); + + const std::string MSG { "Hello there!" }; + const size_t N = MSG.length(); + + char buf[512]; + + REQUIRE(sock1.write(MSG) == N); + REQUIRE(sock2.read_n(buf, N) == N); + + std::string msg { buf, buf+N }; + REQUIRE(msg == MSG); +} + + diff --git b/tests/unit/unit_tests.cpp a/tests/unit/unit_tests.cpp new file mode 100644 index 0000000..020a01d --- /dev/null +++ a/tests/unit/unit_tests.cpp @@ -0,0 +1,71 @@ +// unit_tests.cpp +// +// Main for unit tests. +// + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2018 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- +// + +// Normally, we would just tell Catch2 to define main() like this... +// This tells Catch to provide a main() - only do this in one cpp file +//#define CATCH_CONFIG_MAIN + +// ...but we need to run the sockpp global initialization before running +// any of the tests. Defining a main() is described here: +// https://github.com/catchorg/Catch2/blob/master/docs/own-main.md +// + +#include "sockpp/socket.h" + +// This seems to be required, at least for MSVS 2015 on Win7, +// using Catch2 v2.9.2 +#if defined(_WIN32) + #define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#define CATCH_CONFIG_RUNNER +#include "catch2/catch.hpp" + +int main(int argc, char* argv[]) +{ + // global setup... + sockpp::socket_initializer sockInit; + + int result = Catch::Session().run(argc, argv); + + // global clean-up... + + return result; +}