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.
This commit is contained in:
Max Kellermann 2024-07-05 18:37:03 +02:00
parent fcddab84c6
commit d3ef4ab234
3 changed files with 178 additions and 0 deletions

141
src/net/FormatAddress.cxx Normal file
View File

@ -0,0 +1,141 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright CM4all GmbH
// author: Max Kellermann <mk@cm4all.com>
#include "FormatAddress.hxx"
#include "Features.hxx"
#include "SocketAddress.hxx"
#include "IPv4Address.hxx"
#include <algorithm>
#include <cassert>
#include <cstring>
#ifdef _WIN32
#include <ws2tcpip.h>
#else
#include <netdb.h>
#ifdef HAVE_TCP
#include <netinet/in.h>
#endif
#endif
#ifdef HAVE_UN
#include <sys/un.h>
#endif
#ifdef HAVE_UN
static bool
LocalToString(std::span<char> 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<char> 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<char> buffer, SocketAddress address,
const char *fallback) noexcept
{
return ToString(buffer, address)
? buffer.data()
: fallback;
}
bool
HostToString(std::span<char> 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;
}

36
src/net/FormatAddress.hxx Normal file
View File

@ -0,0 +1,36 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright CM4all GmbH
// author: Max Kellermann <mk@cm4all.com>
#pragma once
#include <cstddef>
#include <span>
class SocketAddress;
/**
* Generates the string representation of a #SocketAddress into the
* specified buffer.
*
* @return true on success
*/
bool
ToString(std::span<char> 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<char> 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<char> buffer, SocketAddress address) noexcept;

View File

@ -41,6 +41,7 @@ endif
net = static_library( net = static_library(
'net', 'net',
net_sources, net_sources,
'FormatAddress.cxx',
'ToString.cxx', 'ToString.cxx',
'HostParser.cxx', 'HostParser.cxx',
'Resolver.cxx', 'Resolver.cxx',