changeset 469:bcfb05fa961c

Socket: - Add new option system in namespace option, - Do not throw on invalid Ip/Local constructs with storage.
author David Demelier <markand@malikania.fr>
date Thu, 05 Nov 2015 08:58:12 +0100
parents becd06089e8f
children bc3a211c5e33
files C++/modules/Socket/Sockets.cpp C++/modules/Socket/Sockets.h C++/tests/Socket/main.cpp
diffstat 3 files changed, 306 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/C++/modules/Socket/Sockets.cpp	Wed Nov 04 21:21:30 2015 +0100
+++ b/C++/modules/Socket/Sockets.cpp	Thu Nov 05 08:58:12 2015 +0100
@@ -169,6 +169,8 @@
 Ip::Ip(int domain) noexcept
 	: m_domain{domain}
 {
+	assert(domain == AF_INET6 || domain == AF_INET);
+
 	if (m_domain == AF_INET6) {
 		std::memset(&m_sin6, 0, sizeof (sockaddr_in6));
 	} else {
@@ -179,6 +181,8 @@
 Ip::Ip(int domain, const std::string &host, int port)
 	: m_domain{domain}
 {
+	assert(domain == AF_INET6 || domain == AF_INET);
+
 	if (host == "*") {
 		if (m_domain == AF_INET6) {
 			std::memset(&m_sin6, 0, sizeof (sockaddr_in6));
@@ -217,22 +221,27 @@
 	}
 }
 
-Ip::Ip(const sockaddr_storage *ss, socklen_t length)
+Ip::Ip(const sockaddr_storage *ss, socklen_t length) noexcept
 	: m_length{length}
 	, m_domain{ss->ss_family}
 {
+	assert(ss->ss_family == AF_INET6 || ss->ss_family == AF_INET);
+
 	if (ss->ss_family == AF_INET6) {
 		std::memcpy(&m_sin6, ss, length);
 	} else if (ss->ss_family == AF_INET) {
 		std::memcpy(&m_sin, ss, length);
-	} else {
-		throw std::invalid_argument{"invalid domain for Ip constructor"};
 	}
 }
 
 #if !defined(_WIN32)
 
-Local::Local(std::string path, bool rm)
+Local::Local()
+{
+	std::memset(m_sun, 0, sizeof (sockaddr_un));
+}
+
+Local::Local(std::string path, bool rm) noexcept
 	: m_path{std::move(path)}
 {
 	/* Silently remove the file even if it fails */
@@ -248,13 +257,13 @@
 	m_sun.sun_family = AF_LOCAL;
 }
 
-Local::Local(const sockaddr_storage *ss, socklen_t length)
+Local::Local(const sockaddr_storage *ss, socklen_t length) noexcept
 {
+	assert(ss->ss_family == AF_LOCAL);
+
 	if (ss->ss_family == AF_LOCAL) {
 		std::memcpy(&m_sun, ss, length);
 		m_path = reinterpret_cast<const sockaddr_un &>(m_sun).sun_path;
-	} else {
-		throw std::invalid_argument{"invalid domain for Local constructor"};
 	}
 }
 
--- a/C++/modules/Socket/Sockets.h	Wed Nov 04 21:21:30 2015 +0100
+++ b/C++/modules/Socket/Sockets.h	Thu Nov 05 08:58:12 2015 +0100
@@ -117,6 +117,7 @@
 #  include <arpa/inet.h>
 
 #  include <netinet/in.h>
+#  include <netinet/tcp.h>
 
 #  include <fcntl.h>
 #  include <netdb.h>
@@ -772,7 +773,7 @@
 	}
 
 	/**
-	 * Set an option for the socket.
+	 * Set an option for the socket. Wrapper of setsockopt(2).
 	 *
 	 * @param level the setting level
 	 * @param name the name
@@ -788,7 +789,22 @@
 	}
 
 	/**
-	 * Get an option for the socket.
+	 * Object-oriented option setter.
+	 *
+	 * The object must have `set(Socket<Address, Protocol> &) const`.
+	 *
+	 * @param option the option
+	 * @throw Error on errors
+	 * @example socket.set(option::ReuseAddress{true});
+	 */
+	template <typename Option>
+	inline void set(const Option &option)
+	{
+		option.set(*this);
+	}
+
+	/**
+	 * Get an option for the socket. Wrapper of getsockopt(2).
 	 *
 	 * @param level the setting level
 	 * @param name the name
@@ -810,6 +826,22 @@
 	}
 
 	/**
+	 * Object-oriented option getter.
+	 *
+	 * The object must have `T get(Socket<Address, Protocol> &) const`, T can be any type and it is the value
+	 * returned from this function.
+	 *
+	 * @return the same value as get() in the option
+	 * @throw Error on errors
+	 * @example socket.get<option::ReuseAddress>();
+	 */
+	template <typename Option>
+	inline auto get() -> decltype(std::declval<Option>().get(*this))
+	{
+		return Option{}.get(*this);
+	}
+
+	/**
 	 * Get the native handle.
 	 *
 	 * @return the handle
@@ -826,6 +858,7 @@
 	 *
 	 * @param block set to false to mark **non-blocking**
 	 * @throw Error on any error
+	 * @deprecated see option::BlockMode and set()
 	 */
 	void setBlockMode(bool block)
 	{
@@ -1337,7 +1370,200 @@
 /* }}} */
 
 /*
- * Predefine addressed to be used
+ * Predefined options
+ * ------------------------------------------------------------------
+ */
+
+/* {{{ Options */
+
+namespace option {
+
+/**
+ * @class BlockMode
+ * @brief Set or get the blocking-mode for a socket.
+ * @warning On Windows, it's not possible to check if the socket is blocking or not.
+ */
+class BlockMode {
+public:
+	/**
+	 * Set to false if you want non-blocking socket.
+	 */
+	bool value{false};
+
+	/**
+	 * Set the option.
+	 *
+	 * @param sc the socket
+	 * @throw Error on errors
+	 */
+	template <typename Address, typename Protocol>
+	void set(Socket<Address, Protocol> &sc) const
+	{
+#if defined(O_NONBLOCK) && !defined(_WIN32)
+		int flags;
+
+		if ((flags = fcntl(sc.handle(), F_GETFL, 0)) < 0) {
+			flags = 0;
+		}
+
+		if (value) {
+			flags &= ~(O_NONBLOCK);
+		} else {
+			flags |= O_NONBLOCK;
+		}
+
+		if (fcntl(sc.handle(), F_SETFL, flags) < 0) {
+			throw Error{Error::System, "fcntl"};
+		}
+#else
+		unsigned long flags = (value) ? 0 : 1;
+
+		if (ioctlsocket(sc.handle(), FIONBIO, &flags) == Failure) {
+			throw Error{Error::System, "fcntl"};
+		}
+#endif
+	}
+
+	/**
+	 * Get the option.
+	 *
+	 * @return the value
+	 * @throw Error on errors
+	 */
+	template <typename Address, typename Protocol>
+	bool get(Socket<Address, Protocol> &sc) const
+	{
+#if defined(O_NONBLOCK) && !defined(_WIN32)
+		int flags = fcntl(sc.handle(), F_GETFL, 0);
+
+		if (flags < 0) {
+			throw Error{Error::System, "fcntl"};
+		}
+
+		return !(flags & O_NONBLOCK);
+#else
+		throw Error{Error::Other, "get", "Windows API cannot let you get the blocking status of a socket"};
+#endif
+	}
+};
+
+/**
+ * @class TcpNoDelay
+ * @brief Set this option if you want to disable nagle's algorithm.
+ */
+class TcpNoDelay {
+public:
+	/**
+	 * Set to true to set TCP_NODELAY option.
+	 */
+	bool value{true};
+
+	/**
+	 * Set the option.
+	 *
+	 * @param sc the socket
+	 * @throw Error on errors
+	 */
+	template <typename Address, typename Protocol>
+	inline void set(Socket<Address, Protocol> &sc) const
+	{
+		sc.set(IPPROTO_TCP, TCP_NODELAY, value ? 1 : 0);
+	}
+
+	/**
+	 * Get the option.
+	 *
+	 * @return the value
+	 * @throw Error on errors
+	 */
+	template <typename Address, typename Protocol>
+	inline bool get(Socket<Address, Protocol> &sc) const
+	{
+		return static_cast<bool>(sc.get<int>(IPPROTO_TCP, TCP_NODELAY));
+	}
+};
+
+/**
+ * @class ReuseAddress
+ * @brief Reuse address, must be used before calling Socket::bind
+ */
+class ReuseAddress {
+public:
+	/**
+	 * Set to true if you want to set the SOL_SOCKET/SO_REUSEADDR option.
+	 */
+	bool value{true};
+
+	/**
+	 * Set the option.
+	 *
+	 * @param sc the socket
+	 * @throw Error on errors
+	 */
+	template <typename Address, typename Protocol>
+	inline void set(Socket<Address, Protocol> &sc) const
+	{
+		sc.set(SOL_SOCKET, SO_REUSEADDR, value ? 1 : 0);
+	}
+
+	/**
+	 * Get the option.
+	 *
+	 * @return the value
+	 * @throw Error on errors
+	 */
+	template <typename Address, typename Protocol>
+	inline bool get(Socket<Address, Protocol> &sc) const
+	{
+		return static_cast<bool>(sc.get<int>(SOL_SOCKET, SO_REUSEADDR));
+	}
+};
+
+/**
+ * @class Ipv6Only
+ * @brief Control IPPROTO_IPV6/IPV6_V6ONLY
+ *
+ * Note: some systems may or not set this option by default so it's a good idea to set it in any case to either
+ * false or true if portability is a concern.
+ */
+class Ipv6Only {
+public:
+	/**
+	 * Set this to use only IPv6.
+	 */
+	bool value{true};
+
+	/**
+	 * Set the option.
+	 *
+	 * @param sc the socket
+	 * @throw Error on errors
+	 */
+	template <typename Address, typename Protocol>
+	inline void set(Socket<Address, Protocol> &sc) const
+	{
+		sc.set(IPPROTO_IPV6, IPV6_V6ONLY, value ? 1 : 0);
+	}
+
+	/**
+	 * Get the option.
+	 *
+	 * @return the value
+	 * @throw Error on errors
+	 */
+	template <typename Address, typename Protocol>
+	inline bool get(Socket<Address, Protocol> &sc) const
+	{
+		return static_cast<bool>(sc.get<int>(IPPROTO_IPV6, IPV6_V6ONLY));
+	}
+};
+
+} // !option
+
+/* }}} */
+
+/*
+ * Predefined addressed to be used
  * ------------------------------------------------------------------
  *
  * - Ipv6,
@@ -1365,6 +1591,7 @@
 	/**
 	 * Default initialize the Ip domain.
 	 *
+	 * @pre domain must be AF_INET or AF_INET6 only
 	 * @param domain the domain (AF_INET or AF_INET6)
 	 */
 	Ip(int domain = AF_INET6) noexcept;
@@ -1372,6 +1599,7 @@
 	/**
 	 * Construct an address suitable for bind() or connect().
 	 *
+	 * @pre domain must be AF_INET or AF_INET6 only
 	 * @param domain the domain (AF_INET or AF_INET6)
 	 * @param host the host (* for any)
 	 * @param port the port number
@@ -1382,10 +1610,11 @@
 	/**
 	 * Construct an address from a storage.
 	 *
+	 * @pre storage's domain must be AF_INET or AF_INET6 only
 	 * @param ss the storage
 	 * @param length the length
 	 */
-	Ip(const sockaddr_storage *ss, socklen_t length);
+	Ip(const sockaddr_storage *ss, socklen_t length) noexcept;
 
 	/**
 	 * Get the domain (AF_INET or AF_INET6).
@@ -1468,7 +1697,7 @@
 	 * @param ss the storage
 	 * @param length the length
 	 */
-	inline Ipv6(const sockaddr_storage *ss, socklen_t length)
+	inline Ipv6(const sockaddr_storage *ss, socklen_t length) noexcept
 		: Ip{ss, length}
 	{
 	}
@@ -1506,7 +1735,7 @@
 	 * @param ss the storage
 	 * @param length the length
 	 */
-	inline Ipv4(const sockaddr_storage *ss, socklen_t length)
+	inline Ipv4(const sockaddr_storage *ss, socklen_t length) noexcept
 		: Ip{ss, length}
 	{
 	}
@@ -1539,7 +1768,7 @@
 	/**
 	 * Default constructor.
 	 */
-	Local() = default;
+	Local() noexcept;
 
 	/**
 	 * Construct an address to a path.
@@ -1547,15 +1776,16 @@
 	 * @param path the path
 	 * @param rm remove the file before (default: false)
 	 */
-	Local(std::string path, bool rm = false);
+	Local(std::string path, bool rm = false) noexcept;
 
 	/**
 	 * Construct an unix address from a storage address.
 	 *
+	 * @pre storage's domain must be AF_LOCAL
 	 * @param ss the storage
 	 * @param length the length
 	 */
-	Local(const sockaddr_storage *ss, socklen_t length);
+	Local(const sockaddr_storage *ss, socklen_t length) noexcept;
 
 	/**
 	 * Get the sockaddr_un.
@@ -3219,7 +3449,7 @@
 	 * @throw Error on errors
 	 */
 	StreamServer(const Address &address, Protocol protocol = {}, int max = 128)
-		: m_master{address, std::move(type)}
+		: m_master{protocol, std::move(address)}
 	{
 		// TODO: m_onError
 		m_master.set(SOL_SOCKET, SO_REUSEADDR, 1);
--- a/C++/tests/Socket/main.cpp	Wed Nov 04 21:21:30 2015 +0100
+++ b/C++/tests/Socket/main.cpp	Thu Nov 05 08:58:12 2015 +0100
@@ -29,6 +29,56 @@
 using namespace net;
 using namespace std::literals::chrono_literals;
 
+/*
+ * Options
+ * ------------------------------------------------------------------
+ */
+
+TEST(Options, reuse)
+{
+	SocketTcpIp s;
+
+	try {
+		s.set(option::ReuseAddress{true});
+		ASSERT_TRUE(s.get<option::ReuseAddress>());
+
+		s.set(option::ReuseAddress{false});
+		ASSERT_FALSE(s.get<option::ReuseAddress>());
+	} catch (const std::exception &ex) {
+		FAIL() << ex.what();
+	}
+}
+
+TEST(Options, nodelay)
+{
+	SocketTcpIp s;
+
+	try {
+		s.set(option::TcpNoDelay{true});
+		ASSERT_TRUE(s.get<option::TcpNoDelay>());
+
+		s.set(option::TcpNoDelay{false});
+		ASSERT_FALSE(s.get<option::TcpNoDelay>());
+	} catch (const std::exception &ex) {
+		FAIL() << ex.what();
+	}
+}
+
+TEST(Options, v6only)
+{
+	SocketTcpIp s;
+
+	try {
+		s.set(option::Ipv6Only{true});
+		ASSERT_TRUE(s.get<option::Ipv6Only>());
+
+		s.set(option::Ipv6Only{false});
+		ASSERT_FALSE(s.get<option::Ipv6Only>());
+	} catch (const std::exception &ex) {
+		FAIL() << ex.what();
+	}
+}
+
 /* --------------------------------------------------------
  * TCP tests
  * -------------------------------------------------------- */