cleanup ip address code

Wed, 27 Jan 2021 18:55:03 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Wed, 27 Jan 2021 18:55:03 +0200
changeset 186
9330b93d9946
parent 185
e83ec58cc458
child 187
53f9c7b2c068

cleanup ip address code

sources/interface.cpp file | annotate | diff | comparison | revisions
sources/interface.h file | annotate | diff | comparison | revisions
sources/main.h file | annotate | diff | comparison | revisions
sources/network/ipaddress.cpp file | annotate | diff | comparison | revisions
sources/network/ipaddress.h file | annotate | diff | comparison | revisions
sources/network/rconsession.cpp file | annotate | diff | comparison | revisions
sources/network/rconsession.h file | annotate | diff | comparison | revisions
sources/network/udpsocket.cpp file | annotate | diff | comparison | revisions
sources/network/udpsocket.h file | annotate | diff | comparison | revisions
--- a/sources/interface.cpp	Wed Jan 27 14:16:58 2021 +0200
+++ b/sources/interface.cpp	Wed Jan 27 18:55:03 2021 +0200
@@ -132,7 +132,7 @@
 	{
 	case INPUTSTATE_ADDRESS:
 		if (m_remoteAddress.host != 0)
-			getEditableInput() = m_remoteAddress.to_string(IPAddress::WITH_PORT);
+			getEditableInput() = net::ip_address_to_string(m_remoteAddress);
 		break;
 
 	default:
@@ -488,7 +488,7 @@
 
 	case RCON_CONNECTING:
 	case RCON_AUTHENTICATING:
-		text = "Connecting to " + m_session.address().to_string(IPAddress::WITH_PORT) + "...";
+		text = "Connecting to " + net::ip_address_to_string(m_session.address()) + "...";
 		break;
 
 	case RCON_CONNECTED:
@@ -506,7 +506,7 @@
 			}
 
 			text = zfc::sprintf("%s | %s | %s",
-				m_session.address().to_string(IPAddress::WITH_PORT).data(),
+				net::ip_address_to_string(m_session.address()).data(),
 				m_session.getLevel().data(),
 				adminText.data());
 		}
@@ -606,6 +606,26 @@
 	m_needInputRender = true;
 }
 
+bool Interface::tryResolveAddress(const std::string &address_string, net::ip_address* target)
+{
+	std::stringstream errors;
+	const std::optional<net::ip_address> address_opt = net::ip_resolve(address_string, errors);
+	if (address_opt.has_value())
+	{
+		*target = address_opt.value();
+		if (target->port == 0)
+		{
+			target->port = 10666;
+		}
+		return true;
+	}
+	else
+	{
+		this->printError("%s\n", errors.str().data());
+		return false;
+	}
+}
+
 // -------------------------------------------------------------------------------------------------
 //
 void Interface::handleInput()
@@ -794,20 +814,10 @@
 			break; // handled above
 
 		case INPUTSTATE_ADDRESS:
-			try
-			{
-				m_remoteAddress = IPAddress::from_string(getCurrentInput());
-			}
-			catch (std::exception& e)
+			if (this->tryResolveAddress(this->getCurrentInput(), &this->m_remoteAddress))
 			{
-				print("%s\n", e.what());
-				return;
+				setInputState(INPUTSTATE_PASSWORD);
 			}
-
-			if (m_remoteAddress.port == 0)
-				m_remoteAddress.port = 10666;
-
-			setInputState(INPUTSTATE_PASSWORD);
 			break;
 
 		case INPUTSTATE_PASSWORD:
@@ -996,24 +1006,14 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::connect(std::string address, std::string password)
+void Interface::connect(std::string address_string, std::string password)
 {
-	try
-	{
-		m_remoteAddress = IPAddress::from_string(address);
-	}
-	catch (std::exception& e)
+	if (this->tryResolveAddress(address_string, &this->m_remoteAddress))
 	{
-		print("%s\n", e.what());
-		return;
+		m_session.disconnect();
+		m_session.setPassword(password);
+		m_session.connect(m_remoteAddress);
 	}
-
-	if (m_remoteAddress.port == 0)
-		m_remoteAddress.port = 10666;
-
-	m_session.disconnect();
-	m_session.setPassword(password);
-	m_session.connect(m_remoteAddress);
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -1069,24 +1069,7 @@
 		}
 		else
 		{
-			IPAddress address;
-
-			try
-			{
-				address = IPAddress::from_string(args[0]);
-			}
-			catch (std::exception& e)
-			{
-				printError("%s\n", e.what());
-				return;
-			}
-
-			if (address.port == 0)
-				address.port = 10666;
-
-			m_session.setPassword(args[1]);
-			m_session.disconnect();
-			m_session.connect(m_remoteAddress = address);
+			this->connect(args[0], args[1]);
 		}
 	}
 	else if (command == "disconnect")
@@ -1107,7 +1090,7 @@
 //
 void Interface::disconnected()
 {
-	print("Disconnected from %s\n", m_session.address().to_string(IPAddress::WITH_PORT).data());
+	print("Disconnected from %s\n", net::ip_address_to_string(m_session.address()).data());
 	resetTitle();
 	renderFull();
 }
--- a/sources/interface.h	Wed Jan 27 14:16:58 2021 +0200
+++ b/sources/interface.h	Wed Jan 27 18:55:03 2021 +0200
@@ -83,7 +83,7 @@
 	std::string m_title;
 	InputState m_inputState;
 	std::function<void(bool)> m_disconnectCallback;
-	IPAddress m_remoteAddress;
+	net::ip_address m_remoteAddress;
 	std::string m_statusBarText;
 	std::vector<ColoredLine> m_playerNames;
 	std::string m_pasteBuffer;
@@ -111,6 +111,7 @@
 	void safeDisconnect(std::function<void(bool)> afterwards);
 	void setInputState(InputState newstate);
 	void yank(int a, int b);
+	bool tryResolveAddress(const std::string& address_string, net::ip_address *target);
 };
 
 END_ZFC_NAMESPACE
\ No newline at end of file
--- a/sources/main.h	Wed Jan 27 14:16:58 2021 +0200
+++ b/sources/main.h	Wed Jan 27 18:55:03 2021 +0200
@@ -31,6 +31,8 @@
 #pragma once
 #include <algorithm>
 #include <functional>
+#include <string>
+#include <sstream>
 #include <cstdio>
 #include <cstdlib>
 #include <cstdint>
--- a/sources/network/ipaddress.cpp	Wed Jan 27 14:16:58 2021 +0200
+++ b/sources/network/ipaddress.cpp	Wed Jan 27 18:55:03 2021 +0200
@@ -46,133 +46,139 @@
 typedef struct addrinfo AddrInfo;
 #endif
 
-// -----------------------------------------------------------------------------
-//
-std::string IPAddress::to_string (WithPort withport) const
+net::octet_t net::ip_octet(const net::ip_address& address, unsigned char n)
 {
-	std::string val;
+	assert(n < 4);
+	return (address.host >> ((3 - n) * 8)) & 0xFF;
+}
 
-	if (withport == WITH_PORT)
-		val = sprintf ("%u.%u.%u.%u:%u", octet (0), octet (1), octet (2), octet (3), port);
-	else
-		val = sprintf ("%u.%u.%u.%u", octet (0), octet (1), octet (2), octet (3));
+void net::ip_set_octet(net::ip_address* address, unsigned char n, net::octet_t octet)
+{
+	// TODO: make a big-endian version
+	assert(n < 4);
+	address->host &= ~(0xFF << (3 - n) * 8);
+	address->host |= octet << ((3 - n) * 8);
+}
 
+std::string net::ip_address_to_string(const net::ip_address& address)
+{
+	const auto octet = [&](unsigned char n) { return ip_octet(address, n); };
+	std::string val = sprintf ("%u.%u.%u.%u:%u", octet(0), octet(1), octet(2), octet(3), address.port);
 	return val;
 }
 
-// -----------------------------------------------------------------------------
-//
-unsigned char IPAddress::octet (int n) const
+int net::ip_compare(const net::ip_address &one, const net::ip_address &other)
 {
-	return (host >> ((3 - n) * 8)) & 0xFF;
-}
-
-// -----------------------------------------------------------------------------
-//
-void IPAddress::set_octet (int n, unsigned char oct)
-{
-	// TODO: make a big-endian version
-	host &= ~(0xFF << (3 - n) * 8);
-	host |= oct << ((3 - n) * 8);
+	if (one.host != other.host)
+	{
+		return one.host - other.host;
+	}
+	return one.port - other.port;
 }
 
-// -----------------------------------------------------------------------------
-//
-bool IPAddress::compare (const IPAddress& other) const
+bool net::ip_address::operator<(const ip_address& other) const
 {
-	for (int i = 0; i < 4; i += 1)
-	{
-		if (octet (i) != other.octet (i))
-			return false;
-	}
-
-	if (port != 0
-		and other.port != 0
-		and port != other.port)
-	{
-		return false;
-	}
-
-	return true;
+	return ip_compare(*this, other) < 0;
 }
 
-// -----------------------------------------------------------------------------
-//
-bool IPAddress::operator< (const IPAddress& other) const
+bool net::ip_address::operator==(const net::ip_address &other) const
 {
-	for (int i = 0; i < 4; i += 1)
-	{
-		if (octet (i) != other.octet (i))
-			return octet (i) < other.octet (i);
-	}
-
-	return port < other.port;
+	return this->host == other.host and this->port == other.port;
 }
 
 // -----------------------------------------------------------------------------
 //
-sockaddr_in IPAddress::to_sockaddr_in() const
+sockaddr_in net::ip_address_to_sockaddr_in(const net::ip_address& address)
 {
 	sockaddr_in claddr;
 	memset (&claddr, 0, sizeof claddr);
-	claddr.sin_addr.s_addr = htonl (host);
-	claddr.sin_port = htons (port);
+	claddr.sin_addr.s_addr = htonl(address.host);
+	claddr.sin_port = htons(address.port);
 	claddr.sin_family = AF_INET;
 	return claddr;
 }
 
-// -----------------------------------------------------------------------------
-//
-IPAddress IPAddress::from_string (std::string input)
+std::optional<unsigned short> net::ip_parse_port(const char* port_string, std::ostream& errorStream)
 {
-	unsigned int parts[4];
-	int colonpos = input.find (":");
-	std::string addressString = colonpos == -1 ? input : mid(input, 0, colonpos);
-	IPAddress value;
-
-	// Try scanf the IPv4 host first
-	if (sscanf(addressString.data(), "%u.%u.%u.%u", &parts[0], &parts[1], &parts[2], &parts[3]))
+	std::optional<long> opt = to_int(port_string);
+	if (opt.has_value())
 	{
-		for (int i = 0; i < 4; i += 1)
-			value.set_octet (i, parts[i]);
+		if (*opt >= 0 and *opt < 65536)
+		{
+			return static_cast<unsigned short>(opt.value());
+		}
+		else
+		{
+			return {};
+			errorStream << "port is not in range"s;
+		}
 	}
 	else
 	{
-		// Possibly a hostname, try resolve it
-		value = IPAddress::resolve (addressString);
+		return {};
+		errorStream << "could not parse port"s;
 	}
+}
 
-	if (colonpos != -1)
+std::optional<net::ip_address> net::ip_scan_octets(const char* address_string)
+{
+	std::optional<net::ip_address> value;
+	net::octet_t parts[4];
+	if (std::sscanf(address_string, "%hhu.%hhu.%hhu.%hhu", &parts[0], &parts[1], &parts[2], &parts[3]))
 	{
-		std::optional<long> opt = to_int(mid(input, colonpos + 1, -1).data());
-		if (opt.has_value())
-		{
-			value.port = opt.value();
-		}
+		value = net::ip_address{};
+		for (unsigned char i = 0; i < 4; i += 1)
+			ip_set_octet(&*value, i, parts[i]);
 	}
-
 	return value;
 }
 
-// -----------------------------------------------------------------------------
-//
-IPAddress IPAddress::resolve (std::string node)
+std::optional<net::ip_address> net::ip_resolve_hostname(const std::string& node, std::ostream& errorStream)
 {
 	AddrInfo hints;
 	AddrInfo* lookup;
-	memset (&hints, 0, sizeof hints);
+	memset(&hints, 0, sizeof hints);
 	hints.ai_family = AF_INET;
-
-	if (getaddrinfo(node.data(), nullptr, &hints, &lookup) != 0)
-		throw StringParseError ("unknown host " + node);
-
-	IPAddress result;
-	assert (lookup != nullptr);
-	sockaddr_in* addr = reinterpret_cast<sockaddr_in*> (lookup[0].ai_addr);
-	result.host = ntohl (addr->sin_addr.s_addr);
-	result.port = ntohs (addr->sin_port);
-	freeaddrinfo (lookup);
+	std::optional<net::ip_address> result = {};
+	if (::getaddrinfo(node.data(), nullptr, &hints, &lookup) != 0)
+	{
+		errorStream << "unknown host "s + node;
+	}
+	else
+	{
+		assert (lookup != nullptr);
+		sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(lookup[0].ai_addr);
+		result = net::ip_address{ntohl(addr->sin_addr.s_addr), ntohs(addr->sin_port)};
+		::freeaddrinfo(lookup);
+	}
 	return result;
 }
 
+std::optional<net::ip_address> net::ip_resolve(const std::string& input_string, std::ostream& errorStream)
+{
+	int colonpos = input_string.find(":");
+	std::string addressString = colonpos == -1 ? input_string : mid(input_string, 0, colonpos);
+	std::optional<net::ip_address> value;
+	// Try scanf the IPv4 host first
+	value = ip_scan_octets(addressString.data());
+	if (not value.has_value())
+	{
+		// Possibly a hostname, try resolve it
+		value = ip_resolve_hostname(addressString, errorStream);
+	}
+	if (value.has_value() and colonpos != -1)
+	{
+		std::optional<unsigned short> port_opt = ip_parse_port(&input_string.data()[colonpos + 1], errorStream);
+		if (port_opt.has_value())
+		{
+			value->port = *port_opt;
+		}
+		else
+		{
+			value.reset();
+		}
+	}
+	return value;
+}
+
 END_ZFC_NAMESPACE
\ No newline at end of file
--- a/sources/network/ipaddress.h	Wed Jan 27 14:16:58 2021 +0200
+++ b/sources/network/ipaddress.h	Wed Jan 27 18:55:03 2021 +0200
@@ -29,6 +29,7 @@
 */
 
 #pragma once
+#include <ostream>
 #include "../main.h"
 
 struct sockaddr;
@@ -36,48 +37,26 @@
 
 BEGIN_ZFC_NAMESPACE
 
-struct IPAddress
+namespace net
 {
-	enum
-	{
-		LOCALHOST = 0x7f000001
-	};
-
-	enum WithPort
-	{
-		WITH_PORT,
-		NO_PORT
-	};
-
-	class StringParseError : public std::exception
+	using octet_t = std::uint8_t;
+	struct ip_address
 	{
-		std::string m_message;
-
-	public:
-		StringParseError (std::string message) :
-			m_message (message) {}
-
-		const char* what() const throw()
-		{
-			return m_message.data();
-		}
+		std::uint32_t host;
+		std::uint16_t port;
+		bool operator<(const ip_address& other) const;
+		bool operator==(const ip_address& other) const;
 	};
-
-	unsigned long host;
-	unsigned short port;
-
-	bool compare (const IPAddress& other) const;
-	unsigned char octet (int n) const;
-	void set_octet (int n, unsigned char oct);
-	std::string to_string (WithPort withport = NO_PORT) const;
-	sockaddr_in to_sockaddr_in() const;
-	bool operator< (const IPAddress& other) const;
-	bool operator== (const IPAddress& other) const { return compare (other); }
-	bool operator!= (const IPAddress& other) const { return not compare (other); }
-	unsigned char operator[] (int n) const { return octet (n); }
-
-	static IPAddress from_string (std::string input);
-	static IPAddress resolve (std::string node);
-};
+	constexpr ip_address localhost = {0x7f000001, 0};
+	int ip_compare(const ip_address& one, const ip_address& other);
+	net::octet_t ip_octet(const ip_address& address, unsigned char n);
+	void ip_set_octet(ip_address* address, unsigned char n, net::octet_t octet);
+	std::optional<ip_address> ip_scan_octets(const char* address_string);
+	std::optional<unsigned short> ip_parse_port(const char* port_string, std::ostream& errorStream);
+	std::optional<ip_address> ip_resolve_hostname(const std::string& node, std::ostream& errorStream);
+	std::optional<ip_address> ip_resolve(const std::string& input_string, std::ostream &errorStream);
+	sockaddr_in ip_address_to_sockaddr_in(const ip_address& address);
+	std::string ip_address_to_string(const ip_address& address);
+}
 
 END_ZFC_NAMESPACE
--- a/sources/network/rconsession.cpp	Wed Jan 27 14:16:58 2021 +0200
+++ b/sources/network/rconsession.cpp	Wed Jan 27 18:55:03 2021 +0200
@@ -56,7 +56,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-void RCONSession::connect(IPAddress address)
+void RCONSession::connect(net::ip_address address)
 {
 	m_address = address;
 	m_state = RCON_CONNECTING;
@@ -277,7 +277,7 @@
 //
 void RCONSession::sendHello()
 {
-	m_interface->print("Connecting to %s...\n", m_address.to_string(IPAddress::WITH_PORT).data());
+	m_interface->print("Connecting to %s...\n", net::ip_address_to_string(m_address).data());
 	send({CLRC_BEGINCONNECTION, RCON_PROTOCOL_VERSION});
 	bumpLastPing();
 }
@@ -344,7 +344,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-const IPAddress& RCONSession::address() const
+const net::ip_address& RCONSession::address() const
 {
 	return m_address;
 }
--- a/sources/network/rconsession.h	Wed Jan 27 14:16:58 2021 +0200
+++ b/sources/network/rconsession.h	Wed Jan 27 18:55:03 2021 +0200
@@ -95,9 +95,9 @@
 	RCONSession();
 	~RCONSession();
 
-	const IPAddress&            address() const;
+	const net::ip_address&            address() const;
 	void                        bumpLastPing();
-	void                        connect(IPAddress address);
+	void                        connect(net::ip_address address);
 	void                        disconnect();
 	int                         getAdminCount() const;
 	const std::string&               getLevel() const;
@@ -117,7 +117,7 @@
 
 private:
 	RCONSessionState m_state;
-	IPAddress m_address;
+	net::ip_address m_address;
 	UDPSocket m_socket;
 	time_t m_lastPing;
 	std::string m_password;
--- a/sources/network/udpsocket.cpp	Wed Jan 27 14:16:58 2021 +0200
+++ b/sources/network/udpsocket.cpp	Wed Jan 27 18:55:03 2021 +0200
@@ -141,11 +141,11 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-bool UDPSocket::send (const IPAddress& address, const ByteArray& data)
+bool UDPSocket::send (const net::ip_address& address, const ByteArray& data)
 {
 	int encodedlength = sizeof HuffmanBuffer;
 	HUFFMAN_Encode (data.data(), reinterpret_cast<unsigned char*> (HuffmanBuffer), data.size(), &encodedlength);
-	sockaddr_in claddr = address.to_sockaddr_in();
+	sockaddr_in claddr = net::ip_address_to_sockaddr_in(address);
 	int res = ::sendto (m_socket, HuffmanBuffer, encodedlength, 0,
 		reinterpret_cast<sockaddr*> (&claddr), sizeof claddr);
 
--- a/sources/network/udpsocket.h	Wed Jan 27 14:16:58 2021 +0200
+++ b/sources/network/udpsocket.h	Wed Jan 27 18:55:03 2021 +0200
@@ -39,7 +39,7 @@
 struct Datagram
 {
 	ByteArray message;
-	IPAddress address;
+	net::ip_address address;
 };
 
 // -------------------------------------------------------------------------------------------------
@@ -52,7 +52,7 @@
 
 	bool bind (unsigned short port);
 	bool read (Datagram& datagram);
-	bool send (const IPAddress& address, const ByteArray& data);
+	bool send (const net::ip_address& address, const ByteArray& data);
 	bool set_blocking (bool a);
 	const std::string& error_string() const { return m_error; }
 	int file_descriptor() const { return m_socket; }

mercurial