[libc-commits] [libc] [libc] Implement ether_aton and ether_aton_r (PR #235886)
Pavel Labath via libc-commits
libc-commits at lists.llvm.org
Thu Oct 29 01:14:30 PDT 2026
https://github.com/labath updated https://github.com/llvm/llvm-project/pull/235886
>From 7b22167dd194284074600ad070a512dc956d49c7 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Mon, 26 Oct 2026 15:25:22 +0000
Subject: [PATCH 1/2] [libc] Implement ether_aton and ether_aton_r
The parsing is done with strtointeger, limited to two characters per
octet. Since strtointeger also accepts leading whitespace and a sign,
I check that each octet starts with an alphanumeric character first.
A few implementation notes:
- each octet can have one or two hex digits, so the output of
ether_ntoa ("1:2:a:...") can be parsed back
- following musl (but unlike glibc), the address must not be followed by
any trailing characters, not even whitespace. glibc accepts trailing
whitespace (and ignores anything after it), and even garbage directly
after a two-digit final octet (e.g. "...:abfoo").
- the internal function does not take a string_view, as that would
require us to scan the entire string needlessly (we stop at the first
invalid character). In the worst case, the string might not be
terminated, and we'd crash trying to find the end.
- the output struct is not modified if parsing fails (glibc can leave
it partially written)
- like ether_ntoa, ether_aton uses a thread-local static buffer
Assisted-by: Gemini
---
libc/config/linux/aarch64/entrypoints.txt | 2 +
libc/config/linux/riscv/entrypoints.txt | 2 +
libc/config/linux/x86_64/entrypoints.txt | 2 +
libc/include/netinet/ether.yaml | 13 +++
libc/src/netinet/ether/CMakeLists.txt | 32 ++++++
libc/src/netinet/ether/ether_aton.cpp | 34 ++++++
libc/src/netinet/ether/ether_aton.h | 26 +++++
libc/src/netinet/ether/ether_aton_r.cpp | 35 ++++++
libc/src/netinet/ether/ether_aton_r.h | 26 +++++
libc/src/netinet/ether/ether_utils.h | 26 +++++
libc/test/src/netinet/ether/CMakeLists.txt | 24 ++++
.../src/netinet/ether/ether_aton_r_test.cpp | 107 ++++++++++++++++++
.../src/netinet/ether/ether_aton_test.cpp | 59 ++++++++++
13 files changed, 388 insertions(+)
create mode 100644 libc/src/netinet/ether/ether_aton.cpp
create mode 100644 libc/src/netinet/ether/ether_aton.h
create mode 100644 libc/src/netinet/ether/ether_aton_r.cpp
create mode 100644 libc/src/netinet/ether/ether_aton_r.h
create mode 100644 libc/test/src/netinet/ether/ether_aton_r_test.cpp
create mode 100644 libc/test/src/netinet/ether/ether_aton_test.cpp
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 93e4a812f39c53..f9fd49fb7f36a8 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1165,6 +1165,8 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.net.if_nametoindex
# netinet/ether.h entrypoints
+ libc.src.netinet.ether.ether_aton
+ libc.src.netinet.ether.ether_aton_r
libc.src.netinet.ether.ether_ntoa
libc.src.netinet.ether.ether_ntoa_r
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 89560e59f10c52..03b6129710064d 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -73,6 +73,8 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.net.if_nametoindex
# netinet/ether.h entrypoints
+ libc.src.netinet.ether.ether_aton
+ libc.src.netinet.ether.ether_aton_r
libc.src.netinet.ether.ether_ntoa
libc.src.netinet.ether.ether_ntoa_r
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 7fa1fb4bb69da5..65a963d602cd37 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -73,6 +73,8 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.net.if_nametoindex
# netinet/ether.h entrypoints
+ libc.src.netinet.ether.ether_aton
+ libc.src.netinet.ether.ether_aton_r
libc.src.netinet.ether.ether_ntoa
libc.src.netinet.ether.ether_ntoa_r
diff --git a/libc/include/netinet/ether.yaml b/libc/include/netinet/ether.yaml
index 99a65a781c11b9..1f52a93273b292 100644
--- a/libc/include/netinet/ether.yaml
+++ b/libc/include/netinet/ether.yaml
@@ -6,6 +6,19 @@ types: []
enums: []
objects: []
functions:
+ - name: ether_aton
+ standards:
+ - bsd
+ return_type: struct ether_addr *
+ arguments:
+ - type: const char *
+ - name: ether_aton_r
+ standards:
+ - gnu
+ return_type: struct ether_addr *
+ arguments:
+ - type: const char *
+ - type: struct ether_addr *
- name: ether_ntoa
standards:
- bsd
diff --git a/libc/src/netinet/ether/CMakeLists.txt b/libc/src/netinet/ether/CMakeLists.txt
index 4b92f54f6b22a0..09819b83a2f3b6 100644
--- a/libc/src/netinet/ether/CMakeLists.txt
+++ b/libc/src/netinet/ether/CMakeLists.txt
@@ -6,10 +6,42 @@ add_header_library(
libc.hdr.stdint_proxy
libc.hdr.types.size_t
libc.hdr.types.struct_ether_addr
+ libc.src.__support.CPP.optional
libc.src.__support.CPP.span
libc.src.__support.common
libc.src.__support.ctype_utils
libc.src.__support.macros.config
+ libc.src.__support.str_to_integer
+)
+
+add_entrypoint_object(
+ ether_aton
+ SRCS
+ ether_aton.cpp
+ HDRS
+ ether_aton.h
+ DEPENDS
+ .ether_utils
+ libc.hdr.types.struct_ether_addr
+ libc.src.__support.CPP.optional
+ libc.src.__support.common
+ libc.src.__support.macros.config
+ libc.src.__support.macros.null_check
+)
+
+add_entrypoint_object(
+ ether_aton_r
+ SRCS
+ ether_aton_r.cpp
+ HDRS
+ ether_aton_r.h
+ DEPENDS
+ .ether_utils
+ libc.hdr.types.struct_ether_addr
+ libc.src.__support.CPP.optional
+ libc.src.__support.common
+ libc.src.__support.macros.config
+ libc.src.__support.macros.null_check
)
add_entrypoint_object(
diff --git a/libc/src/netinet/ether/ether_aton.cpp b/libc/src/netinet/ether/ether_aton.cpp
new file mode 100644
index 00000000000000..93890126210850
--- /dev/null
+++ b/libc/src/netinet/ether/ether_aton.cpp
@@ -0,0 +1,34 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of ether_aton function.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/netinet/ether/ether_aton.h"
+#include "hdr/types/struct_ether_addr.h"
+#include "src/__support/CPP/optional.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/netinet/ether/ether_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(struct ether_addr *, ether_aton, (const char *asc)) {
+ LIBC_CRASH_ON_NULLPTR(asc);
+ static LIBC_THREAD_LOCAL struct ether_addr buffer;
+ cpp::optional<struct ether_addr> addr = net::ether_aton(asc);
+ if (!addr)
+ return nullptr;
+ buffer = *addr;
+ return &buffer;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/netinet/ether/ether_aton.h b/libc/src/netinet/ether/ether_aton.h
new file mode 100644
index 00000000000000..b1fa4431961043
--- /dev/null
+++ b/libc/src/netinet/ether/ether_aton.h
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation header of ether_aton.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_NETINET_ETHER_ETHER_ATON_H
+#define LLVM_LIBC_SRC_NETINET_ETHER_ETHER_ATON_H
+
+#include "hdr/types/struct_ether_addr.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+struct ether_addr *ether_aton(const char *asc);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_NETINET_ETHER_ETHER_ATON_H
diff --git a/libc/src/netinet/ether/ether_aton_r.cpp b/libc/src/netinet/ether/ether_aton_r.cpp
new file mode 100644
index 00000000000000..fa7746f674b13a
--- /dev/null
+++ b/libc/src/netinet/ether/ether_aton_r.cpp
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of ether_aton_r function.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/netinet/ether/ether_aton_r.h"
+#include "hdr/types/struct_ether_addr.h"
+#include "src/__support/CPP/optional.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/netinet/ether/ether_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(struct ether_addr *, ether_aton_r,
+ (const char *asc, struct ether_addr *addr)) {
+ LIBC_CRASH_ON_NULLPTR(asc);
+ LIBC_CRASH_ON_NULLPTR(addr);
+ cpp::optional<struct ether_addr> result = net::ether_aton(asc);
+ if (!result)
+ return nullptr;
+ *addr = *result;
+ return addr;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/netinet/ether/ether_aton_r.h b/libc/src/netinet/ether/ether_aton_r.h
new file mode 100644
index 00000000000000..7dee1e165449aa
--- /dev/null
+++ b/libc/src/netinet/ether/ether_aton_r.h
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation header of ether_aton_r.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_NETINET_ETHER_ETHER_ATON_R_H
+#define LLVM_LIBC_SRC_NETINET_ETHER_ETHER_ATON_R_H
+
+#include "hdr/types/struct_ether_addr.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+struct ether_addr *ether_aton_r(const char *asc, struct ether_addr *addr);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_NETINET_ETHER_ETHER_ATON_R_H
diff --git a/libc/src/netinet/ether/ether_utils.h b/libc/src/netinet/ether/ether_utils.h
index da906c87e6e97b..3fac94bc46d55c 100644
--- a/libc/src/netinet/ether/ether_utils.h
+++ b/libc/src/netinet/ether/ether_utils.h
@@ -17,10 +17,12 @@
#include "hdr/stdint_proxy.h"
#include "hdr/types/size_t.h"
#include "hdr/types/struct_ether_addr.h"
+#include "src/__support/CPP/optional.h"
#include "src/__support/CPP/span.h"
#include "src/__support/common.h"
#include "src/__support/ctype_utils.h"
#include "src/__support/macros/config.h"
+#include "src/__support/str_to_integer.h"
namespace LIBC_NAMESPACE_DECL {
namespace net {
@@ -44,6 +46,30 @@ LIBC_INLINE char *ether_ntoa_r(const struct ether_addr &addr,
return buf.data();
}
+/// Parses \p str as an Ethernet address in the colon-separated hexadecimal
+/// notation ("xx:xx:xx:xx:xx:xx"), where each octet consists of one or two
+/// hexadecimal digits. Like musl (but unlike glibc), trailing characters are
+/// not allowed. Returns nullopt if the string is not a valid address.
+LIBC_INLINE cpp::optional<struct ether_addr> ether_aton(const char *str) {
+ struct ether_addr addr;
+ for (size_t i = 0; i < 6; ++i) {
+ if (i > 0 && *str++ != ':')
+ return cpp::nullopt;
+ // strtointeger skips leading whitespace and accepts a sign. We don't.
+ // subtle: "0xff" passes this check, but gets rejected by src_len=2.
+ if (!internal::isalnum(*str))
+ return cpp::nullopt;
+ auto result = internal::strtointeger<uint8_t>(str, 16, /*src_len=*/2);
+ if (result.parsed_len == 0)
+ return cpp::nullopt;
+ addr.ether_addr_octet[i] = result.value;
+ str += result.parsed_len;
+ }
+ if (*str != '\0')
+ return cpp::nullopt;
+ return addr;
+}
+
} // namespace net
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/netinet/ether/CMakeLists.txt b/libc/test/src/netinet/ether/CMakeLists.txt
index bcb8bab6c00f88..abb0e875f3dff9 100644
--- a/libc/test/src/netinet/ether/CMakeLists.txt
+++ b/libc/test/src/netinet/ether/CMakeLists.txt
@@ -1,5 +1,29 @@
add_libc_testsuite(libc-netinet-ether-tests PARENT libc-netinet-tests)
+add_libc_test(
+ ether_aton_test
+ SUITE
+ libc-netinet-ether-tests
+ SRCS
+ ether_aton_test.cpp
+ DEPENDS
+ libc.hdr.stdint_proxy
+ libc.hdr.types.struct_ether_addr
+ libc.src.netinet.ether.ether_aton
+)
+
+add_libc_test(
+ ether_aton_r_test
+ SUITE
+ libc-netinet-ether-tests
+ SRCS
+ ether_aton_r_test.cpp
+ DEPENDS
+ libc.hdr.stdint_proxy
+ libc.hdr.types.struct_ether_addr
+ libc.src.netinet.ether.ether_aton_r
+)
+
add_libc_test(
ether_ntoa_test
SUITE
diff --git a/libc/test/src/netinet/ether/ether_aton_r_test.cpp b/libc/test/src/netinet/ether/ether_aton_r_test.cpp
new file mode 100644
index 00000000000000..a0ae502338e260
--- /dev/null
+++ b/libc/test/src/netinet/ether/ether_aton_r_test.cpp
@@ -0,0 +1,107 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unittests for ether_aton_r.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/stdint_proxy.h"
+#include "hdr/types/struct_ether_addr.h"
+#include "src/netinet/ether/ether_aton_r.h"
+#include "test/UnitTest/Test.h"
+
+namespace {
+
+// Packs the address into an integer to get readable test failure messages.
+uint64_t to_u64(const struct ether_addr &addr) {
+ uint64_t result = 0;
+ for (uint8_t octet : addr.ether_addr_octet)
+ result = result << 8 | octet;
+ return result;
+}
+
+} // namespace
+
+TEST(LlvmLibcEtherAtonRTest, ValidAddresses) {
+ struct ether_addr addr;
+
+ EXPECT_EQ(&addr, LIBC_NAMESPACE::ether_aton_r("00:00:00:00:00:00", &addr));
+ EXPECT_EQ(uint64_t{0x000000000000}, to_u64(addr));
+
+ EXPECT_EQ(&addr, LIBC_NAMESPACE::ether_aton_r("ff:ff:ff:ff:ff:ff", &addr));
+ EXPECT_EQ(uint64_t{0xffffffffffff}, to_u64(addr));
+
+ EXPECT_EQ(&addr, LIBC_NAMESPACE::ether_aton_r("01:23:45:67:89:ab", &addr));
+ EXPECT_EQ(uint64_t{0x0123456789ab}, to_u64(addr));
+
+ // Uppercase and mixed-case digits.
+ EXPECT_EQ(&addr, LIBC_NAMESPACE::ether_aton_r("CD:EF:aB:Cd:eF:00", &addr));
+ EXPECT_EQ(uint64_t{0xcdefabcdef00}, to_u64(addr));
+
+ // Single-digit octets, as produced by ether_ntoa.
+ EXPECT_EQ(&addr, LIBC_NAMESPACE::ether_aton_r("0:0:0:0:0:0", &addr));
+ EXPECT_EQ(uint64_t{0x000000000000}, to_u64(addr));
+
+ EXPECT_EQ(&addr, LIBC_NAMESPACE::ether_aton_r("1:2:3:4:5:6", &addr));
+ EXPECT_EQ(uint64_t{0x010203040506}, to_u64(addr));
+
+ EXPECT_EQ(&addr, LIBC_NAMESPACE::ether_aton_r("cd:ef:a:10:0:9f", &addr));
+ EXPECT_EQ(uint64_t{0xcdef0a10009f}, to_u64(addr));
+}
+
+TEST(LlvmLibcEtherAtonRTest, InvalidAddresses) {
+ struct ether_addr addr;
+
+ const char *invalid[] = {
+ "",
+ ":",
+ "01:23:45:67:89",
+ "01:23:45:67:89:",
+ "a",
+ "1:2:3:4:5",
+ "1:2:3:4:5:",
+ "01:23:45:67:89:ab:",
+ "01:23:45:67:89:ab:cd",
+ "001:23:45:67:89:ab",
+ "01:23:45:67:89:abc",
+ "01:23:45:67::ab",
+ ":01:23:45:67:89:ab",
+ " 01:23:45:67:89:ab",
+ "01 :23:45:67:89:ab",
+ "01: 23:45:67:89:ab",
+ "01-23-45-67-89-ab",
+ "01.23.45.67.89.ab",
+ "0123456789ab",
+ "0g:23:45:67:89:ab",
+ "01:23:45:67:89:ag",
+ "0x1:23:45:67:89:ab",
+ "0X1:23:45:67:89:ab",
+ "+1:23:45:67:89:ab",
+ "-1:23:45:67:89:ab",
+ "01:23:45:67:89: ab",
+ "01:23:45:67:89:+b",
+ "01:23:45:67:89:-b",
+ "01:23:45:67:89:0x",
+ "01:23:45:67:89:abfoo",
+ "01:23:45:67:89:ab ",
+ "1:2:3:4:5:6 ",
+ "1:2:3:4:5:6\tfoo",
+ "1:2:3:4:5:6\n",
+ };
+ for (const char *str : invalid)
+ EXPECT_EQ(static_cast<struct ether_addr *>(nullptr),
+ LIBC_NAMESPACE::ether_aton_r(str, &addr));
+}
+
+TEST(LlvmLibcEtherAtonRTest, OutputUnchangedOnFailure) {
+ struct ether_addr addr = {{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}};
+ EXPECT_EQ(static_cast<struct ether_addr *>(nullptr),
+ LIBC_NAMESPACE::ether_aton_r("01:23:45:67:89:xy", &addr));
+ EXPECT_EQ(uint64_t{0x112233445566}, to_u64(addr));
+}
diff --git a/libc/test/src/netinet/ether/ether_aton_test.cpp b/libc/test/src/netinet/ether/ether_aton_test.cpp
new file mode 100644
index 00000000000000..287e193ebab40a
--- /dev/null
+++ b/libc/test/src/netinet/ether/ether_aton_test.cpp
@@ -0,0 +1,59 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unittests for ether_aton.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/stdint_proxy.h"
+#include "hdr/types/struct_ether_addr.h"
+#include "src/netinet/ether/ether_aton.h"
+#include "test/UnitTest/Test.h"
+
+namespace {
+
+// Packs the address into an integer to get readable test failure messages.
+uint64_t to_u64(const struct ether_addr &addr) {
+ uint64_t result = 0;
+ for (uint8_t octet : addr.ether_addr_octet)
+ result = result << 8 | octet;
+ return result;
+}
+
+} // namespace
+
+TEST(LlvmLibcEtherAtonTest, BasicConversion) {
+ struct ether_addr *addr = LIBC_NAMESPACE::ether_aton("01:23:45:67:89:ab");
+ ASSERT_NE(static_cast<struct ether_addr *>(nullptr), addr);
+ EXPECT_EQ(uint64_t{0x0123456789ab}, to_u64(*addr));
+
+ addr = LIBC_NAMESPACE::ether_aton("cd:ef:a:10:0:9F");
+ ASSERT_NE(static_cast<struct ether_addr *>(nullptr), addr);
+ EXPECT_EQ(uint64_t{0xcdef0a10009f}, to_u64(*addr));
+
+ EXPECT_EQ(static_cast<struct ether_addr *>(nullptr),
+ LIBC_NAMESPACE::ether_aton("01:23:45:67:89"));
+ EXPECT_EQ(static_cast<struct ether_addr *>(nullptr),
+ LIBC_NAMESPACE::ether_aton("01:23:45:67:89:abc"));
+}
+
+TEST(LlvmLibcEtherAtonTest, BufferReuseAndOverwrite) {
+ struct ether_addr *ptr1 = LIBC_NAMESPACE::ether_aton("11:22:33:44:55:66");
+ ASSERT_NE(static_cast<struct ether_addr *>(nullptr), ptr1);
+ EXPECT_EQ(uint64_t{0x112233445566}, to_u64(*ptr1));
+
+ struct ether_addr *ptr2 = LIBC_NAMESPACE::ether_aton("1:2:3:4:5:6");
+ EXPECT_EQ(ptr1, ptr2);
+ EXPECT_EQ(uint64_t{0x010203040506}, to_u64(*ptr1));
+
+ // A failed conversion leaves the buffer untouched.
+ EXPECT_EQ(static_cast<struct ether_addr *>(nullptr),
+ LIBC_NAMESPACE::ether_aton("a:b:c:d:e:g"));
+ EXPECT_EQ(uint64_t{0x010203040506}, to_u64(*ptr1));
+}
>From 1a6033222be154f99735e976f37286ca9f5d9b19 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Thu, 29 Oct 2026 08:14:00 +0000
Subject: [PATCH 2/2] drop str_to_integer
---
libc/src/netinet/ether/CMakeLists.txt | 1 -
libc/src/netinet/ether/ether_utils.h | 34 ++++++++++++++++++---------
2 files changed, 23 insertions(+), 12 deletions(-)
diff --git a/libc/src/netinet/ether/CMakeLists.txt b/libc/src/netinet/ether/CMakeLists.txt
index 09819b83a2f3b6..a8cf47fcb803b9 100644
--- a/libc/src/netinet/ether/CMakeLists.txt
+++ b/libc/src/netinet/ether/CMakeLists.txt
@@ -11,7 +11,6 @@ add_header_library(
libc.src.__support.common
libc.src.__support.ctype_utils
libc.src.__support.macros.config
- libc.src.__support.str_to_integer
)
add_entrypoint_object(
diff --git a/libc/src/netinet/ether/ether_utils.h b/libc/src/netinet/ether/ether_utils.h
index 3fac94bc46d55c..3653282795ca5d 100644
--- a/libc/src/netinet/ether/ether_utils.h
+++ b/libc/src/netinet/ether/ether_utils.h
@@ -22,7 +22,6 @@
#include "src/__support/common.h"
#include "src/__support/ctype_utils.h"
#include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
namespace LIBC_NAMESPACE_DECL {
namespace net {
@@ -46,6 +45,15 @@ LIBC_INLINE char *ether_ntoa_r(const struct ether_addr &addr,
return buf.data();
}
+/// Returns the value of the hexadecimal digit \p ch, or -1 if \p ch is not a
+/// hexadecimal digit.
+LIBC_INLINE constexpr int hex_digit_value(char ch) {
+ if (!internal::isalnum(ch))
+ return -1;
+ int val = internal::b36_char_to_int(ch);
+ return val < 16 ? val : -1;
+}
+
/// Parses \p str as an Ethernet address in the colon-separated hexadecimal
/// notation ("xx:xx:xx:xx:xx:xx"), where each octet consists of one or two
/// hexadecimal digits. Like musl (but unlike glibc), trailing characters are
@@ -53,17 +61,21 @@ LIBC_INLINE char *ether_ntoa_r(const struct ether_addr &addr,
LIBC_INLINE cpp::optional<struct ether_addr> ether_aton(const char *str) {
struct ether_addr addr;
for (size_t i = 0; i < 6; ++i) {
- if (i > 0 && *str++ != ':')
- return cpp::nullopt;
- // strtointeger skips leading whitespace and accepts a sign. We don't.
- // subtle: "0xff" passes this check, but gets rejected by src_len=2.
- if (!internal::isalnum(*str))
- return cpp::nullopt;
- auto result = internal::strtointeger<uint8_t>(str, 16, /*src_len=*/2);
- if (result.parsed_len == 0)
+ if (i > 0) {
+ if (*str != ':')
+ return cpp::nullopt;
+ ++str;
+ }
+ int val = hex_digit_value(*str);
+ if (val < 0)
return cpp::nullopt;
- addr.ether_addr_octet[i] = result.value;
- str += result.parsed_len;
+ ++str;
+ int low = hex_digit_value(*str);
+ if (low >= 0) {
+ val = val << 4 | low;
+ ++str;
+ }
+ addr.ether_addr_octet[i] = static_cast<uint8_t>(val);
}
if (*str != '\0')
return cpp::nullopt;
More information about the libc-commits
mailing list