From d3ef4ab234572e7ffac5918f6aad9c0e5b82a2f9 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Jul 2024 18:37:03 +0200 Subject: [PATCH] net/FormatAddress: new library to replace ToString.cxx This library writes to a caller-specified buffer instead of allocating a std::string which can be faster by avoiding heap allocations. --- src/net/FormatAddress.cxx | 141 ++++++++++++++++++++++++++++++++++++++ src/net/FormatAddress.hxx | 36 ++++++++++ src/net/meson.build | 1 + 3 files changed, 178 insertions(+) create mode 100644 src/net/FormatAddress.cxx create mode 100644 src/net/FormatAddress.hxx diff --git a/src/net/FormatAddress.cxx b/src/net/FormatAddress.cxx new file mode 100644 index 000000000..20a9ef33b --- /dev/null +++ b/src/net/FormatAddress.cxx @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: BSD-2-Clause +// Copyright CM4all GmbH +// author: Max Kellermann + +#include "FormatAddress.hxx" +#include "Features.hxx" +#include "SocketAddress.hxx" +#include "IPv4Address.hxx" + +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#ifdef HAVE_TCP +#include +#endif +#endif + +#ifdef HAVE_UN +#include +#endif + +#ifdef HAVE_UN + +static bool +LocalToString(std::span buffer, std::string_view raw) noexcept +{ + if (raw.empty()) + return false; + + if (raw.size() >= buffer.size()) + /* truncate to the buffer size */ + raw = raw.substr(0, buffer.size() - 1); + + if (raw.front() != '\0' && raw.back() == '\0') + /* don't convert the null terminator of a non-abstract socket + to a '@' */ + raw.remove_suffix(1); + + *std::copy(raw.begin(), raw.end(), buffer.begin()) = '\0'; + + /* replace all null bytes with '@'; this also handles abstract + addresses (Linux specific) */ + const auto result = buffer.first(raw.size()); + std::replace(result.begin(), result.end(), '\0', '@'); + + return true; +} + +#endif + +bool +ToString(std::span buffer, SocketAddress address) noexcept +{ + if (address.IsNull() || address.GetSize() == 0) + return false; + +#ifdef HAVE_UN + if (address.GetFamily() == AF_LOCAL) + /* return path of local socket */ + return LocalToString(buffer, address.GetLocalRaw()); +#endif + +#if defined(HAVE_IPV6) && defined(IN6_IS_ADDR_V4MAPPED) + IPv4Address ipv4_buffer; + if (address.IsV4Mapped()) + address = ipv4_buffer = address.UnmapV4(); +#endif + + char serv[NI_MAXSERV]; + int ret = getnameinfo(address.GetAddress(), address.GetSize(), + buffer.data(), buffer.size(), + serv, sizeof(serv), + NI_NUMERICHOST | NI_NUMERICSERV); + if (ret != 0) + return false; + + if (serv[0] != 0 && (serv[0] != '0' || serv[1] != 0)) { +#ifdef HAVE_IPV6 + if (address.GetFamily() == AF_INET6) { + /* enclose IPv6 address in square brackets */ + + std::size_t length = strlen(buffer.data()); + if (length + 4 >= buffer.size()) + /* no more room */ + return false; + + memmove(buffer.data() + 1, buffer.data(), length); + buffer[0] = '['; + buffer[++length] = ']'; + buffer[++length] = 0; + } +#endif + + if (strlen(buffer.data()) + 1 + strlen(serv) >= buffer.size()) + /* no more room */ + return false; + + strcat(buffer.data(), ":"); + strcat(buffer.data(), serv); + } + + return true; +} + +const char * +ToString(std::span buffer, SocketAddress address, + const char *fallback) noexcept +{ + return ToString(buffer, address) + ? buffer.data() + : fallback; +} + +bool +HostToString(std::span buffer, SocketAddress address) noexcept +{ + if (address.IsNull() || address.GetSize() == 0) + return false; + +#ifdef HAVE_UN + if (address.GetFamily() == AF_LOCAL) + /* return path of local socket */ + return LocalToString(buffer, address.GetLocalRaw()); +#endif + +#if defined(HAVE_IPV6) && defined(IN6_IS_ADDR_V4MAPPED) + IPv4Address ipv4_buffer; + if (address.IsV4Mapped()) + address = ipv4_buffer = address.UnmapV4(); +#endif + + return getnameinfo(address.GetAddress(), address.GetSize(), + buffer.data(), buffer.size(), + nullptr, 0, + NI_NUMERICHOST | NI_NUMERICSERV) == 0; +} diff --git a/src/net/FormatAddress.hxx b/src/net/FormatAddress.hxx new file mode 100644 index 000000000..a5fabc7dd --- /dev/null +++ b/src/net/FormatAddress.hxx @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: BSD-2-Clause +// Copyright CM4all GmbH +// author: Max Kellermann + +#pragma once + +#include +#include + +class SocketAddress; + +/** + * Generates the string representation of a #SocketAddress into the + * specified buffer. + * + * @return true on success + */ +bool +ToString(std::span buffer, SocketAddress address) noexcept; + +/** + * Like ToString() above, but return the string pointer (or on error: + * return the given fallback pointer). + */ +const char * +ToString(std::span buffer, SocketAddress address, + const char *fallback) noexcept; + +/** + * Generates the string representation of a #SocketAddress into the + * specified buffer, without the port number. + * + * @return true on success + */ +bool +HostToString(std::span buffer, SocketAddress address) noexcept; diff --git a/src/net/meson.build b/src/net/meson.build index 1faa5ed54..cd817850a 100644 --- a/src/net/meson.build +++ b/src/net/meson.build @@ -41,6 +41,7 @@ endif net = static_library( 'net', net_sources, + 'FormatAddress.cxx', 'ToString.cxx', 'HostParser.cxx', 'Resolver.cxx',