changeset 380:06b0f405c58f

Socket: initial support for object-oriented addresses
author David Demelier <markand@malikania.fr>
date Fri, 19 Jun 2015 13:56:12 +0200
parents 57ce1a6293b9
children 9fd636045546
files C++/modules/Socket/Socket.cpp C++/modules/Socket/Socket.h C++/modules/Socket/SocketAddress.cpp C++/modules/Socket/SocketAddress.h C++/modules/Socket/SocketTcp.cpp C++/modules/Socket/SocketUdp.cpp C++/modules/Socket/SocketUdp.h C++/tests/Socket/main.cpp
diffstat 8 files changed, 226 insertions(+), 131 deletions(-) [+]
line wrap: on
line diff
--- a/C++/modules/Socket/Socket.cpp	Fri Jun 19 11:18:53 2015 +0200
+++ b/C++/modules/Socket/Socket.cpp	Fri Jun 19 13:56:12 2015 +0200
@@ -142,26 +142,21 @@
 	close();
 }
 
-SocketAddress Socket::address() const
+std::unique_ptr<SocketAddress> Socket::address() const
 {
-#if defined(_WIN32)
-	int length;
-#else
 	socklen_t length;
-#endif
-
 	sockaddr_storage ss;
 
 	if (getsockname(m_handle, (sockaddr *)&ss, &length) == Error)
 		throw SocketError(SocketError::System, "getsockname");
 
-	return SocketAddress(ss, length);
+	return SocketAddress::decode(ss, length);
 }
 
-void Socket::bind(const SocketAddress &address)
+void Socket::bind(const std::unique_ptr<SocketAddress> &address)
 {
-	const auto &sa = address.address();
-	const auto addrlen = address.length();
+	const auto &sa = address->address();
+	const auto addrlen = address->length();
 
 	if (::bind(m_handle, reinterpret_cast<const sockaddr *>(&sa), addrlen) == Error) {
 		throw SocketError(SocketError::System, "bind");
--- a/C++/modules/Socket/Socket.h	Fri Jun 19 11:18:53 2015 +0200
+++ b/C++/modules/Socket/Socket.h	Fri Jun 19 13:56:12 2015 +0200
@@ -38,6 +38,7 @@
 #include <cstdlib>
 #include <cstring>
 #include <exception>
+#include <memory>
 #include <string>
 
 #if defined(_WIN32)
@@ -327,7 +328,7 @@
 	 *
 	 * @param handle the native descriptor
 	 */
-	inline Socket(Handle handle)
+	inline Socket(Handle handle) noexcept
 		: m_handle{handle}
 		, m_state{SocketState::Opened}
 	{
@@ -366,7 +367,7 @@
 	 * @return the address
 	 * @throw SocketError on failures
 	 */
-	SocketAddress address() const;
+	std::unique_ptr<SocketAddress> address() const;
 
 	/**
 	 * Set an option for the socket.
@@ -439,7 +440,7 @@
 	 * @param address the address
 	 * @throw SocketError on any error
 	 */
-	void bind(const SocketAddress &address);
+	void bind(const std::unique_ptr<SocketAddress> &address);
 
 	/**
 	 * Set the blocking mode, if set to false, the socket will be marked
--- a/C++/modules/Socket/SocketAddress.cpp	Fri Jun 19 11:18:53 2015 +0200
+++ b/C++/modules/Socket/SocketAddress.cpp	Fri Jun 19 13:56:12 2015 +0200
@@ -22,6 +22,23 @@
 #include "Socket.h"
 #include "SocketAddress.h"
 
+std::unique_ptr<SocketAddress> SocketAddress::decode(const struct sockaddr_storage &ss, socklen_t length)
+{
+	switch (ss.ss_family) {
+	case AF_INET:
+	case AF_INET6:
+		return std::make_unique<address::Internet>(ss, length);
+#if !defined(_WIN32)
+	case AF_UNIX:
+		return std::make_unique<address::Unix>(ss, length);
+#endif
+	default:
+		break;
+	}
+
+	throw std::invalid_argument{"not a valid address type"};
+}
+
 namespace address {
 
 /* --------------------------------------------------------
@@ -29,24 +46,21 @@
  * -------------------------------------------------------- */
 
 Internet::Internet(const std::string &host, unsigned port, int domain)
+	: m_domain{domain}
 {
 	if (host == "*") {
-		if (domain == AF_INET6) {
-			sockaddr_in6 *ptr = (sockaddr_in6 *)&m_addr;
-
-			ptr->sin6_addr = in6addr_any;
-			ptr->sin6_family = AF_INET6;
-			ptr->sin6_port = htons(port);
+		if (m_domain == AF_INET6) {
+			std::memset(&m_sin6, 0, sizeof (struct sockaddr_in6));
 
-			m_addrlen = sizeof (sockaddr_in6);
+			m_sin6.sin6_addr = in6addr_any;
+			m_sin6.sin6_family = AF_INET6;
+			m_sin6.sin6_port = htons(port);
 		} else {
-			sockaddr_in *ptr = (sockaddr_in *)&m_addr;
+			std::memset(&m_sin, 0, sizeof (struct sockaddr_in));
 
-			ptr->sin_addr.s_addr = INADDR_ANY;
-			ptr->sin_family = AF_INET;
-			ptr->sin_port = htons(port);
-
-			m_addrlen = sizeof (sockaddr_in);
+			m_sin.sin_addr.s_addr = INADDR_ANY;
+			m_sin.sin_family = AF_INET;
+			m_sin.sin_port = htons(port);
 		}
 	} else {
 		addrinfo hints, *res;
@@ -59,80 +73,100 @@
 			throw SocketError(SocketError::System, "getaddrinfo", gai_strerror(error));
 		}
 
-		std::memcpy(&m_addr, res->ai_addr, res->ai_addrlen);
-		m_addrlen = res->ai_addrlen;
+		if (m_domain == AF_INET6) {
+			std::memcpy(&m_sin6, res->ai_addr, sizeof (struct sockaddr_in6));
+		} else {
+			std::memcpy(&m_sin, res->ai_addr, sizeof (struct sockaddr_in));
+		}
+
 		freeaddrinfo(res);
 	}
 }
 
+Internet::Internet(const sockaddr_storage &ss, socklen_t length)
+	: m_domain{ss.ss_family}
+{
+	if (ss.ss_family == AF_INET6) {
+		std::memcpy(&m_sin6, &ss, length);
+	} else {
+		std::memcpy(&m_sin, &ss, length);
+	}
+}
+
+socklen_t Internet::length() const noexcept
+{
+	return (m_domain == AF_INET6) ? sizeof (struct sockaddr_in6) : sizeof (struct sockaddr_in);
+}
+
+const sockaddr &Internet::address() const noexcept
+{
+	// Can't get a ternary operator to work here.
+	if (m_domain == AF_INET6)
+		return reinterpret_cast<const sockaddr &>(m_sin6);
+
+	return reinterpret_cast<const sockaddr &>(m_sin);
+}
+
+SocketAddressInfo Internet::info() const
+{
+	std::string type = (m_domain == AF_INET6) ? "ipv6" : "ipv4";
+	std::string port = std::to_string(m_domain == AF_INET6 ? ntohs(m_sin6.sin6_port) : ntohs(m_sin.sin_port));
+
+	return SocketAddressInfo{
+		{ "type",	type	},
+		{ "port",	port	}
+	};
+}
+
 /* --------------------------------------------------------
  * Unix implementation
  * -------------------------------------------------------- */
 
 #if !defined(_WIN32)
 
-#include <sys/un.h>
-
-Unix::Unix(const std::string &path, bool rm)
+Unix::Unix(std::string path, bool rm)
+	: m_path{std::move(path)}
 {
-	sockaddr_un *sun = (sockaddr_un *)&m_addr;
-
 	// Silently remove the file even if it fails
 	if (rm) {
-		::remove(path.c_str());
+		::remove(m_path.c_str());
 	}
 
 	// Copy the path
-	memset(sun->sun_path, 0, sizeof (sun->sun_path));
-	strncpy(sun->sun_path, path.c_str(), sizeof (sun->sun_path) - 1);
+	memset(m_sun.sun_path, 0, sizeof (m_sun.sun_path));
+	strncpy(m_sun.sun_path, path.c_str(), sizeof (m_sun.sun_path) - 1);
 
 	// Set the parameters
-	sun->sun_family = AF_UNIX;
-	m_addrlen = SUN_LEN(sun);
+	m_sun.sun_family = AF_UNIX;
+}
+
+Unix::Unix(const sockaddr_storage &ss, socklen_t)
+{
+	m_path = reinterpret_cast<const struct sockaddr_un &>(ss).sun_path;
+}
+
+socklen_t Unix::length() const noexcept
+{
+#if defined(SOCKET_HAVE_SUN_LEN)
+	return SUN_LEN(m_sun);
+#else
+	return sizeof (m_sun);
+#endif
+}
+
+const sockaddr &Unix::address() const noexcept
+{
+	return reinterpret_cast<const sockaddr &>(m_sun);
+}
+
+SocketAddressInfo Unix::info() const
+{
+	return SocketAddressInfo{
+		{ "type",	"unix"	},
+		{ "path",	m_path	}
+	};
 }
 
 #endif // _WIN32
 
 } // !address
-
-/* --------------------------------------------------------
- * SocketAddress implementation
- * -------------------------------------------------------- */
-
-SocketAddress::SocketAddress()
-	: m_addrlen(0)
-{
-	memset(&m_addr, 0, sizeof (m_addr));
-}
-
-SocketAddress::SocketAddress(const sockaddr_storage &addr, socklen_t length)
-	: m_addr(addr)
-	, m_addrlen(length)
-{
-}
-
-const sockaddr_storage &SocketAddress::address() const
-{
-	return m_addr;
-}
-
-socklen_t SocketAddress::length() const
-{
-	return m_addrlen;
-}
-
-bool operator<(const SocketAddress &s1, const SocketAddress &s2)
-{
-	const auto &array1 = reinterpret_cast<const unsigned char *>(&s1.address());
-	const auto &array2 = reinterpret_cast<const unsigned char *>(&s2.address());
-
-	return std::lexicographical_compare(array1, array1 + s1.length(), array2, array2 + s2.length());
-}
-
-bool operator==(const SocketAddress &s1, const SocketAddress &s2)
-{
-	const auto &array1 = reinterpret_cast<const unsigned char *>(&s1.address());
-	const auto &array2 = reinterpret_cast<const unsigned char *>(&s2.address());
-
-	return std::equal(array1, array1 + s1.length(), array2, array2 + s2.length());
-}
--- a/C++/modules/Socket/SocketAddress.h	Fri Jun 19 11:18:53 2015 +0200
+++ b/C++/modules/Socket/SocketAddress.h	Fri Jun 19 13:56:12 2015 +0200
@@ -19,15 +19,37 @@
 #ifndef _SOCKET_ADDRESS_NG_H_
 #define _SOCKET_ADDRESS_NG_H_
 
+/**
+ * @file SocketAddress.h
+ * @brief Describe addresses
+ *
+ * User may set the following variables before compiling these files:
+ *
+ * SOCKET_HAVE_SUN_LEN	- (bool) Some systems do not have SUN_LEN which is the preferred way to
+ *			  compute the address size for a Unix address. Otherwise, sizeof is used.
+ */
+
+
+#include <memory>
 #include <string>
+#include <unordered_map>
 
 #if defined(_WIN32)
 #  include <Winsock2.h>
 #  include <Ws2tcpip.h>
 #else
 #  include <sys/socket.h>
+#  include <sys/un.h>
+#  include <arpa/inet.h>
+#  include <netinet/in.h>
 #endif
 
+
+/**
+ * Generic information table for an address.
+ */
+using SocketAddressInfo = std::unordered_map<std::string, std::string>;
+
 /**
  * @class SocketAddress
  * @brief base class for socket addresses
@@ -39,23 +61,21 @@
  * @see Unix
  */
 class SocketAddress {
-protected:
-	sockaddr_storage m_addr;
-	socklen_t m_addrlen;
+public:
+	/**
+	 * Decode an address into the appropriate object. Useful for recvfrom, accept.
+	 *
+	 * @param ss the storage address
+	 * @param length the address length
+	 * @return the real object
+	 * @throw std::invalid_argument on error
+	 */
+	static std::unique_ptr<SocketAddress> decode(const struct sockaddr_storage &ss, socklen_t length);
 
-public:
 	/**
 	 * Default constructor.
 	 */
-	SocketAddress();
-
-	/**
-	 * Constructor with address and size.
-	 *
-	 * @param addr the address
-	 * @param length the address length
-	 */
-	SocketAddress(const sockaddr_storage &addr, socklen_t length);
+	SocketAddress() = default;
 
 	/**
 	 * Default destructor.
@@ -67,32 +87,21 @@
 	 *
 	 * @return the length
 	 */
-	socklen_t length() const;
+	virtual socklen_t length() const noexcept = 0;
 
 	/**
 	 * Get the address.
 	 *
 	 * @return the address
 	 */
-	const sockaddr_storage &address() const;
+	virtual const sockaddr &address() const noexcept = 0;
 
 	/**
-	 * Compare the addresses. The check is lexicographical.
+	 * Get the information about the address.
 	 *
-	 * @param s1 the first address
-	 * @param s2 the second address
-	 * @return true if s1 is less than s2
+	 * @return the information
 	 */
-	friend bool operator<(const SocketAddress &s1, const SocketAddress &s2);
-
-	/**
-	 * Compare the addresses.
-	 *
-	 * @param s1 the first address
-	 * @param s2 the second address
-	 * @return true if s1 == s2
-	 */
-	friend bool operator==(const SocketAddress &s1, const SocketAddress &s2);
+	virtual SocketAddressInfo info() const = 0;
 };
 
 namespace address {
@@ -105,16 +114,47 @@
  * using getaddrinfo(3).
  */
 class Internet : public SocketAddress {
+private:
+	union {
+		struct sockaddr_in m_sin;
+		struct sockaddr_in6 m_sin6;
+	};
+
+	int m_domain{AF_INET};
+
 public:
 	/**
 	 * Create an IPv4 or IPV6 end point.
 	 *
 	 * @param host the hostname
 	 * @param port the port
-	 * @param family AF_INET, AF_INET6, ...
+	 * @param domain AF_INET or AF_INET6
 	 * @throw SocketError on error
 	 */
-	Internet(const std::string &host, unsigned port, int family);
+	Internet(const std::string &host, unsigned port, int domain);
+
+	/**
+	 * Construct an internet address from a storage address.
+	 *
+	 * @param ss the storage
+	 * @param length the length
+	 */
+	Internet(const struct sockaddr_storage &ss, socklen_t length);
+
+	/**
+	 * @copydoc SocketAddress::length
+	 */
+	socklen_t length() const noexcept override;
+
+	/**
+	 * @copydoc SocketAddress::address
+	 */
+	const sockaddr &address() const noexcept override;
+
+	/**
+	 * @copydoc SocketAddress::info
+	 */
+	SocketAddressInfo info() const override;
 };
 
 #if !defined(_WIN32)
@@ -126,6 +166,10 @@
  * Create an address to a specific path. Only available on Unix.
  */
 class Unix : public SocketAddress {
+private:
+	struct sockaddr_un m_sun;
+	std::string m_path;
+
 public:
 	/**
 	 * Construct an address to a path.
@@ -133,7 +177,30 @@
 	 * @param path the path
 	 * @param rm remove the file before (default: false)
 	 */
-	Unix(const std::string &path, bool rm = false);
+	Unix(std::string path, bool rm = false);
+
+	/**
+	 * Construct an unix address from a storage address.
+	 *
+	 * @param ss the storage
+	 * @param length the length
+	 */
+	Unix(const struct sockaddr_storage &ss, socklen_t length);
+
+	/**
+	 * @copydoc SocketAddress::length
+	 */
+	socklen_t length() const noexcept override;
+
+	/**
+	 * @copydoc SocketAddress::address
+	 */
+	const sockaddr &address() const noexcept override;
+
+	/**
+	 * @copydoc SocketAddress::info
+	 */
+	SocketAddressInfo info() const override;
 };
 
 #endif // ! !_WIN32
--- a/C++/modules/Socket/SocketTcp.cpp	Fri Jun 19 11:18:53 2015 +0200
+++ b/C++/modules/Socket/SocketTcp.cpp	Fri Jun 19 13:56:12 2015 +0200
@@ -62,8 +62,7 @@
 #endif
 	}
 
-	// TODO: add it
-	//info = SocketAddress(address, addrlen);
+	info = SocketAddress::decode(address, addrlen);
 
 	return std::make_unique<SocketTcp>(handle);
 }
--- a/C++/modules/Socket/SocketUdp.cpp	Fri Jun 19 11:18:53 2015 +0200
+++ b/C++/modules/Socket/SocketUdp.cpp	Fri Jun 19 13:56:12 2015 +0200
@@ -24,7 +24,7 @@
 {
 }
 
-unsigned SocketUdp::recvfrom(void *data, unsigned length, SocketAddress &info)
+unsigned SocketUdp::recvfrom(void *data, unsigned length, std::unique_ptr<SocketAddress> &info)
 {
 	int nbread;
 
@@ -35,7 +35,7 @@
 	addrlen = sizeof (struct sockaddr_storage);
 	nbread = ::recvfrom(m_handle, (Socket::Arg)data, length, 0, (sockaddr *)&address, &addrlen);
 
-	info = SocketAddress(address, addrlen);
+	info = SocketAddress::decode(address, addrlen);
 
 	if (nbread == Error) {
 #if defined(_WIN32)
@@ -58,11 +58,11 @@
 	return (unsigned)nbread;
 }
 
-unsigned SocketUdp::sendto(const void *data, unsigned length, const SocketAddress &info)
+unsigned SocketUdp::sendto(const void *data, unsigned length, const std::unique_ptr<SocketAddress> &info)
 {
 	int nbsent;
 
-	nbsent = ::sendto(m_handle, (Socket::ConstArg)data, length, 0, (const sockaddr *)&info.address(), info.length());
+	nbsent = ::sendto(m_handle, (Socket::ConstArg)data, length, 0, (const sockaddr *)&info->address(), info->length());
 	if (nbsent == Error) {
 #if defined(_WIN32)
 		int error = WSAGetLastError();
--- a/C++/modules/Socket/SocketUdp.h	Fri Jun 19 11:18:53 2015 +0200
+++ b/C++/modules/Socket/SocketUdp.h	Fri Jun 19 13:56:12 2015 +0200
@@ -43,7 +43,7 @@
 	 * @return the number of bytes sent
 	 * @throw SocketError on error
 	 */
-	inline unsigned sendto(const std::string &data, const SocketAddress &address)
+	inline unsigned sendto(const std::string &data, const std::unique_ptr<SocketAddress> &address)
 	{
 		return sendto(data.c_str(), data.length(), address);
 	}
@@ -56,7 +56,7 @@
 	 * @return the string
 	 * @throw SocketError on error
 	 */
-	inline std::string recvfrom(unsigned count, SocketAddress &info)
+	inline std::string recvfrom(unsigned count, std::unique_ptr<SocketAddress> &info)
 	{
 		std::string result;
 
@@ -76,7 +76,7 @@
 	 * @return the number of bytes received
 	 * @throw SocketError on error
 	 */
-	virtual unsigned recvfrom(void *data, unsigned length, SocketAddress &info);
+	virtual unsigned recvfrom(void *data, unsigned length, std::unique_ptr<SocketAddress> &info);
 
 	/**
 	 * Send data to an end point.
@@ -87,7 +87,7 @@
 	 * @return the number of bytes sent
 	 * @throw SocketError on error
 	 */
-	virtual unsigned sendto(const void *data, unsigned length, const SocketAddress &address);
+	virtual unsigned sendto(const void *data, unsigned length, const std::unique_ptr<SocketAddress> &address);
 };
 
 #endif // !_SOCKET_UDP_NG_H_
--- a/C++/tests/Socket/main.cpp	Fri Jun 19 11:18:53 2015 +0200
+++ b/C++/tests/Socket/main.cpp	Fri Jun 19 13:56:12 2015 +0200
@@ -64,7 +64,7 @@
 TEST_F(TcpServerTest, connect)
 {
 	m_tserver = std::thread([this] () {
-		m_server.bind(Internet("*", 16000, AF_INET));
+		m_server.bind(std::make_unique<Internet>("*", 16000, AF_INET));
 
 		ASSERT_EQ(SocketState::Bound, m_server.state());
 
@@ -87,7 +87,7 @@
 TEST_F(TcpServerTest, io)
 {
 	m_tserver = std::thread([this] () {
-		m_server.bind(Internet("*", 16000, AF_INET));
+		m_server.bind(std::make_unique<Internet>("*", 16000, AF_INET));
 		m_server.listen();
 
 		auto client = m_server.accept();
@@ -138,10 +138,9 @@
 TEST_F(UdpServerTest, io)
 {
 	m_tserver = std::thread([this] () {
-		SocketAddress info;
+		std::unique_ptr<SocketAddress> info = std::make_unique<Internet>("*", 16000, AF_INET);
 
-		m_server.bind(Internet("*", 16000, AF_INET));
-
+		m_server.bind(info);
 		auto msg = m_server.recvfrom(512, info);
 
 		ASSERT_EQ("hello world", msg);
@@ -153,7 +152,7 @@
 	std::this_thread::sleep_for(100ms);
 
 	m_tclient = std::thread([this] () {
-		Internet info("127.0.0.1", 16000, AF_INET);
+		std::unique_ptr<SocketAddress> info = std::make_unique<Internet>("127.0.0.1", 16000, AF_INET);
 
 		m_client.sendto("hello world", info);
 
@@ -397,7 +396,7 @@
 		, m_clientTcp{std::make_shared<SocketTcp>(AF_INET, 0)}
 	{
 		m_masterTcp->set(SOL_SOCKET, SO_REUSEADDR, 1);
-		m_masterTcp->bind(Internet("*", 16000, AF_INET));
+		m_masterTcp->bind(std::make_unique<Internet>("*", 16000, AF_INET));
 		m_masterTcp->listen();
 	}
 
@@ -498,7 +497,7 @@
 	TcpAcceptTest()
 	{
 		m_server.set(SOL_SOCKET, SO_REUSEADDR, 1);
-		m_server.bind(Internet("*", 16000, AF_INET));
+		m_server.bind(std::make_unique<Internet>("*", 16000, AF_INET));
 		m_server.listen();
 	}
 
@@ -527,7 +526,7 @@
 	TcpRecvTest()
 	{
 		m_server.set(SOL_SOCKET, SO_REUSEADDR, 1);
-		m_server.bind(Internet("*", 16000, AF_INET));
+		m_server.bind(std::make_unique<Internet>("*", 16000, AF_INET));
 		m_server.listen();
 	}