pw_string: redo pw::string::Length

Moves and renames the existing pw::string::Length to
pw::string::internal::ClampedLength. Instead two new helpers are added:
1) pw::string::NullTerminatedLength(...) which returns the length IFF
   the string is null terminated.
2) pw::string::ClampedCString(...) which returns a string_view of
   the clamped string. This is considered safer compared to strnlen_s
   and the existing internal::ClampedLength implementation, since
   sting_view does not require null termination.

Change-Id: Ie6486df13b205332633f3970109ca97f578e6993
Requires: pigweed-internal:12460
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/43463
Reviewed-by: Ewout van Bekkum <ewout@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
Pigweed-Auto-Submit: Ewout van Bekkum <ewout@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
diff --git a/pw_assert_basic/BUILD.gn b/pw_assert_basic/BUILD.gn
index 34125d9..b0a82da 100644
--- a/pw_assert_basic/BUILD.gn
+++ b/pw_assert_basic/BUILD.gn
@@ -35,6 +35,7 @@
 # TODO(pwbug/372): Reorganize this.
 group("pw_assert_basic.impl") {
   public_deps = [
+    dir_pw_result,
     dir_pw_string,
     dir_pw_sys_io,
   ]
@@ -72,7 +73,10 @@
   check_includes = false
 
   # Depend on the include path instead of the library to avoid circular deps.
-  configs = [ "$dir_pw_string:public_include_path" ]
+  configs = [
+    "$dir_pw_string:public_include_path",
+    "$dir_pw_result:public_include_path",
+  ]
   deps = [
     ":handler.facade",
     "$dir_pw_assert:facade",
diff --git a/pw_fuzzer/BUILD.gn b/pw_fuzzer/BUILD.gn
index 1359db4..91cbd08 100644
--- a/pw_fuzzer/BUILD.gn
+++ b/pw_fuzzer/BUILD.gn
@@ -80,7 +80,10 @@
 # Sample fuzzer
 pw_fuzzer("toy_fuzzer") {
   sources = [ "examples/toy_fuzzer.cc" ]
-  deps = [ "$dir_pw_string" ]
+  deps = [
+    "$dir_pw_result",
+    "$dir_pw_string",
+  ]
 }
 
 pw_test_group("tests") {
diff --git a/pw_fuzzer/examples/toy_fuzzer.cc b/pw_fuzzer/examples/toy_fuzzer.cc
index bcbaef1..58ae461 100644
--- a/pw_fuzzer/examples/toy_fuzzer.cc
+++ b/pw_fuzzer/examples/toy_fuzzer.cc
@@ -22,7 +22,9 @@
 #include <cstddef>
 #include <cstdint>
 #include <cstring>
+#include <span>
 
+#include "pw_result/result.h"
 #include "pw_string/util.h"
 
 namespace {
@@ -62,24 +64,26 @@
 // The fuzz target function
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   // We want to split our input into two strings.
-  const char* word1 = reinterpret_cast<const char*>(data);
+  const std::span<const char> input(reinterpret_cast<const char*>(data), size);
 
   // If that's not feasible, toss this input. The fuzzer will quickly learn that
   // inputs without null-terminators are uninteresting.
-  size_t offset = pw::string::Length(word1, size) + 1;
-  if (offset >= size) {
+  const pw::Result<size_t> possible_word1_size =
+      pw::string::NullTerminatedLength(input);
+  if (!possible_word1_size.ok()) {
     return 0;
   }
+  const std::span<const char> word1 =
+      input.first(possible_word1_size.value() + 1);
 
   // Actually, inputs without TWO null terminators are uninteresting.
-  const char* word2 = reinterpret_cast<const char*>(&data[offset]);
-  size -= offset;
-  if (pw::string::Length(word2, size) == size) {
+  std::span<const char> remaining_input = input.subspan(word1.size());
+  if (!pw::string::NullTerminatedLength(remaining_input).ok()) {
     return 0;
   }
 
   // Call the code we're targeting!
-  toy_example(word1, word2);
+  toy_example(word1.data(), remaining_input.data());
 
   // By convention, the fuzzer always returns zero.
   return 0;
diff --git a/pw_kvs/public/pw_kvs/key.h b/pw_kvs/public/pw_kvs/key.h
index 62b8532..f449f05 100644
--- a/pw_kvs/public/pw_kvs/key.h
+++ b/pw_kvs/public/pw_kvs/key.h
@@ -21,7 +21,7 @@
 #include <string_view>
 #endif  // __cplusplus >= 201703L
 
-#include "pw_string/util.h"
+#include "pw_string/internal/length.h"
 
 namespace pw {
 namespace kvs {
@@ -35,7 +35,8 @@
   constexpr Key(const Key&) = default;
   constexpr Key(const char* str)
       : str_{str},
-        length_{string::Length(str, std::numeric_limits<size_t>::max())} {}
+        length_{string::internal::ClampedLength(
+            str, std::numeric_limits<size_t>::max())} {}
   constexpr Key(const char* str, size_t len) : str_{str}, length_{len} {}
   Key(const std::string& str) : str_{str.data()}, length_{str.length()} {}
 
diff --git a/pw_result/BUILD.gn b/pw_result/BUILD.gn
index 7192cbf..aa09d75 100644
--- a/pw_result/BUILD.gn
+++ b/pw_result/BUILD.gn
@@ -19,12 +19,12 @@
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
 
-config("default_config") {
+config("public_include_path") {
   include_dirs = [ "public" ]
 }
 
 pw_source_set("pw_result") {
-  public_configs = [ ":default_config" ]
+  public_configs = [ ":public_include_path" ]
   public_deps = [
     "$dir_pw_assert",
     "$dir_pw_preprocessor",
diff --git a/pw_string/BUILD b/pw_string/BUILD
index 572a16d..29d04b4 100644
--- a/pw_string/BUILD
+++ b/pw_string/BUILD
@@ -31,6 +31,7 @@
     ],
     hdrs = [
         "public/pw_string/format.h",
+        "public/pw_string/internal/length.h",
         "public/pw_string/string_builder.h",
         "public/pw_string/to_string.h",
         "public/pw_string/type_to_string.h",
@@ -42,6 +43,7 @@
         "//pw_span",
         "//pw_assert",
         "//pw_status",
+        "//pw_result",
     ],
 )
 
diff --git a/pw_string/BUILD.gn b/pw_string/BUILD.gn
index caba610..0742938 100644
--- a/pw_string/BUILD.gn
+++ b/pw_string/BUILD.gn
@@ -27,6 +27,7 @@
   public_configs = [ ":public_include_path" ]
   public = [
     "public/pw_string/format.h",
+    "public/pw_string/internal/length.h",
     "public/pw_string/string_builder.h",
     "public/pw_string/to_string.h",
     "public/pw_string/type_to_string.h",
@@ -40,6 +41,7 @@
   public_deps = [
     "$dir_pw_assert",
     "$dir_pw_preprocessor",
+    "$dir_pw_result",
     "$dir_pw_status",
   ]
 }
diff --git a/pw_string/CMakeLists.txt b/pw_string/CMakeLists.txt
index 8b66bc0..a4d12d3 100644
--- a/pw_string/CMakeLists.txt
+++ b/pw_string/CMakeLists.txt
@@ -18,6 +18,7 @@
   PUBLIC_DEPS
     pw_assert
     pw_preprocessor
+    pw_result
     pw_span
     pw_status
 )
diff --git a/pw_string/docs.rst b/pw_string/docs.rst
index 07028df..5bb10f0 100644
--- a/pw_string/docs.rst
+++ b/pw_string/docs.rst
@@ -34,16 +34,41 @@
 
 .. include:: format_size_report
 
-pw::string::Length
-==================
-The ``pw::string::Length`` function provides a safer alternative to
-``std::strlen`` in case a string is extremely long and/or potentially not
-null-terminated. It is a constexpr version of C11's ``strnlen_s``.
+Safe Length Checking
+====================
+This module provides two safer alternatives to ``std::strlen`` in case the
+string is extremely long and/or potentially not null-terminated.
 
-.. cpp:function:: constexpr size_t Length(const char* str, size_t max_len)
+First, a constexpr alternative to C11's ``strnlen_s`` is offerred through
+:cpp:func:`pw::string::ClampedCString`. This does not return a length by
+design and instead returns a string_view which does not require
+null-termination.
 
-   Calculates the length of a null-terminated string up to the specified maximum
-   length. If str is nullptr, returns 0.
+Second, a constexpr specialized form is offered where null termination is
+required through :cpp:func:`pw::string::NullTerminatedLength`. This will only
+return a length if the string is null-terminated.
+
+.. cpp:function:: constexpr std::string_view pw::string::ClampedCString(std::span<const char> str)
+.. cpp:function:: constexpr std::string_view pw::string::ClampedCString(const char* str, size_t max_len)
+
+   Safe alternative to the string_view constructor to avoid the risk of an
+   unbounded implicit or explicit use of strlen.
+
+   This is strongly recommended over using something like C11's strnlen_s as
+   a string_view does not require null-termination.
+
+.. cpp:function:: constexpr pw::Result<size_t> pw::string::NullTerminatedLength(std::span<const char> str)
+.. cpp:function:: pw::Result<size_t> pw::string::NullTerminatedLength(const char* str, size_t max_len)
+
+   Safe alternative to strlen to calculate the null-terminated length of the
+   string within the specified span, excluding the null terminator. Like C11's
+   strnlen_s, the scan for the null-terminator is bounded.
+
+   Returns:
+     null-terminated length of the string excluding the null terminator.
+     OutOfRange - if the string is not null-terminated.
+
+   Precondition: The string shall be at a valid pointer.
 
 pw::string::Copy
 ================
diff --git a/pw_string/public/pw_string/internal/length.h b/pw_string/public/pw_string/internal/length.h
new file mode 100644
index 0000000..a338517
--- /dev/null
+++ b/pw_string/public/pw_string/internal/length.h
@@ -0,0 +1,46 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include <cstddef>
+
+namespace pw {
+namespace string {
+namespace internal {
+
+// Calculates the length of a null-terminated string up to the specified maximum
+// length. If str is nullptr, returns 0.
+//
+// This function is a constexpr version of C11's strnlen_s.
+//
+// WARNING: this will return a length even if the string is NOT null-terminated!
+// ClampedCString and NullTerminatedLength are recommended which are built on
+// top of this.
+constexpr size_t ClampedLength(const char* str, size_t max_len) {
+  size_t length = 0;
+
+  if (str != nullptr) {
+    for (; length < max_len; ++length) {
+      if (str[length] == '\0') {
+        break;
+      }
+    }
+  }
+
+  return length;
+}
+
+}  // namespace internal
+}  // namespace string
+}  // namespace pw
diff --git a/pw_string/public/pw_string/util.h b/pw_string/public/pw_string/util.h
index 1909401..6b58308 100644
--- a/pw_string/public/pw_string/util.h
+++ b/pw_string/public/pw_string/util.h
@@ -18,30 +18,53 @@
 #include <string_view>
 
 #include "pw_assert/assert.h"
+#include "pw_result/result.h"
 #include "pw_status/status.h"
 #include "pw_status/status_with_size.h"
+#include "pw_string/internal/length.h"
 
 namespace pw {
 namespace string {
 
-// Calculates the length of a null-terminated string up to the specified maximum
-// length. If str is nullptr, returns 0.
+// Safe alternative to the string_view constructor to avoid the risk of an
+// unbounded implicit or explicit use of strlen.
 //
-// This function is a constexpr version of C11's strnlen_s.
-constexpr size_t Length(const char* str, size_t max_len) {
-  size_t length = 0;
+// This is strongly recommended over using something like C11's strnlen_s as
+// a string_view does not require null-termination.
+constexpr std::string_view ClampedCString(std::span<const char> str) {
+  return std::string_view(str.data(),
+                          internal::ClampedLength(str.data(), str.size()));
+}
 
-  if (str != nullptr) {
-    for (; length < max_len; ++length) {
-      if (str[length] == '\0') {
-        break;
-      }
-    }
+constexpr std::string_view ClampedCString(const char* str, size_t max_len) {
+  return ClampedCString(std::span<const char>(str, max_len));
+}
+
+// Safe alternative to strlen to calculate the null-terminated length of the
+// string within the specified span, excluding the null terminator. Like C11's
+// strnlen_s, the scan for the null-terminator is bounded.
+//
+// Returns:
+//   null-terminated length of the string excluding the null terminator.
+//   OutOfRange - if the string is not null-terminated.
+//
+// Precondition: The string shall be at a valid pointer.
+constexpr pw::Result<size_t> NullTerminatedLength(std::span<const char> str) {
+  PW_DASSERT(str.data() != nullptr);
+
+  const size_t length = internal::ClampedLength(str.data(), str.size());
+  if (length == str.size()) {
+    return Status::OutOfRange();
   }
 
   return length;
 }
 
+constexpr pw::Result<size_t> NullTerminatedLength(const char* str,
+                                                  size_t max_len) {
+  return NullTerminatedLength(std::span<const char>(str, max_len));
+}
+
 // Copies the source string to the dest, truncating if the full string does not
 // fit. Always null terminates if dest.size() or num > 0.
 //
@@ -66,7 +89,7 @@
 
 constexpr StatusWithSize Copy(const char* source, std::span<char> dest) {
   PW_DASSERT(source != nullptr);
-  return Copy(std::string_view(source, Length(source, dest.size())), dest);
+  return Copy(ClampedCString(source, dest.size()), dest);
 }
 
 constexpr StatusWithSize Copy(const char* source, char* dest, size_t num) {
diff --git a/pw_string/string_builder.cc b/pw_string/string_builder.cc
index 60879a9..7eff7d2 100644
--- a/pw_string/string_builder.cc
+++ b/pw_string/string_builder.cc
@@ -44,7 +44,7 @@
   // Use buffer_.size() - size() as the maximum length so that strings too long
   // to fit in the buffer will request one character too many, which sets the
   // status to RESOURCE_EXHAUSTED.
-  return append(str, string::Length(str, buffer_.size() - size()));
+  return append(string::ClampedCString(str, buffer_.size() - size()));
 }
 
 StringBuilder& StringBuilder::append(const std::string_view& str) {
diff --git a/pw_string/util_test.cc b/pw_string/util_test.cc
index 86c8562..b22749c 100644
--- a/pw_string/util_test.cc
+++ b/pw_string/util_test.cc
@@ -19,23 +19,85 @@
 namespace pw::string {
 namespace {
 
-TEST(Length, Nullptr_Returns0) { EXPECT_EQ(0u, Length(nullptr, 100)); }
+using namespace std::literals::string_view_literals;
 
-TEST(Length, EmptyString_Returns0) {
-  EXPECT_EQ(0u, Length("", 0));
-  EXPECT_EQ(0u, Length("", 100));
+TEST(ClampedLength, Nullptr_Returns0) {
+  EXPECT_EQ(0u, internal::ClampedLength(nullptr, 100));
 }
 
-TEST(Length, MaxLongerThanString_ReturnsStrlen) {
-  EXPECT_EQ(5u, Length("12345", 100));
+TEST(ClampedLength, EmptyString_Returns0) {
+  EXPECT_EQ(0u, internal::ClampedLength("", 0));
+  EXPECT_EQ(0u, internal::ClampedLength("", 100));
 }
 
-TEST(Length, StringMaxLongerThanMax_ReturnsMax) {
-  EXPECT_EQ(0u, Length("12345", 0));
-  EXPECT_EQ(4u, Length("12345", 4));
+TEST(ClampedLength, MaxLongerThanString_ReturnsStrlen) {
+  EXPECT_EQ(5u, internal::ClampedLength("12345", 100));
 }
 
-TEST(Length, LengthEqualsMax) { EXPECT_EQ(5u, Length("12345", 5)); }
+TEST(ClampedLength, StringMaxLongerThanMax_ReturnsMax) {
+  EXPECT_EQ(0u, internal::ClampedLength("12345", 0));
+  EXPECT_EQ(4u, internal::ClampedLength("12345", 4));
+}
+
+TEST(ClampedLength, LengthEqualsMax) {
+  EXPECT_EQ(5u, internal::ClampedLength("12345", 5));
+}
+
+TEST(ClampedCString, NullPtr_ReturnsEmpty) {
+  EXPECT_TRUE(ClampedCString(nullptr, 100).empty());
+}
+
+TEST(ClampedCString, EmptyString_Returns0) {
+  EXPECT_TRUE(ClampedCString("", 0).empty());
+  EXPECT_TRUE(ClampedCString("", 100).empty());
+}
+
+TEST(ClampedCString, MaxLongerThanString_ReturnsStr) {
+  static constexpr char kInput[] = "12345";
+  const std::string_view result = ClampedCString(kInput, 100);
+  EXPECT_EQ(result.size(), strlen(kInput));
+  EXPECT_EQ(result.data(), &kInput[0]);
+}
+
+TEST(ClampedCString, StringMaxLongerThanMax_ClampsView) {
+  static constexpr char kInput[] = "12345";
+
+  EXPECT_TRUE(ClampedCString(kInput, 0).empty());
+
+  const std::string_view result = ClampedCString(kInput, 4);
+  EXPECT_EQ(result.size(), 4u);
+  EXPECT_EQ(result.data(), &kInput[0]);
+}
+
+TEST(ClampedCString, FullStringView) {
+  static constexpr char kInput[] = "12345";
+  const std::string_view result = ClampedCString(kInput);
+  EXPECT_EQ(result.size(), strlen(kInput));
+  EXPECT_EQ(result.data(), &kInput[0]);
+}
+
+TEST(NullTerminatedLength, EmptyString_RequiresNullTerminator) {
+  EXPECT_TRUE(NullTerminatedLength("", 0).status().IsOutOfRange());
+
+  ASSERT_TRUE(NullTerminatedLength("", 100).status().ok());
+  EXPECT_EQ(0u, NullTerminatedLength("", 100).value());
+}
+
+TEST(NullTerminatedLength, MaxLongerThanString_ReturnsStrlen) {
+  ASSERT_TRUE(NullTerminatedLength("12345", 100).status().ok());
+  EXPECT_EQ(5u, NullTerminatedLength("12345", 100).value());
+}
+
+TEST(NullTerminatedLength, StringMaxLongerThanMax_Fails) {
+  EXPECT_TRUE(NullTerminatedLength("12345", 0).status().IsOutOfRange());
+  EXPECT_TRUE(NullTerminatedLength("12345", 4).status().IsOutOfRange());
+}
+
+TEST(NullTerminatedLength, LengthEqualsMax) {
+  static constexpr char kInput[] = "12345";
+  ASSERT_TRUE(NullTerminatedLength(kInput).ok());
+  EXPECT_EQ(5u, NullTerminatedLength(kInput).value());
+}
 
 class TestWithBuffer : public ::testing::Test {
  protected:
@@ -48,8 +110,6 @@
 
 class CopyTest : public TestWithBuffer {};
 
-using namespace std::literals::string_view_literals;
-
 TEST_F(CopyTest, EmptyStringView_WritesNullTerminator) {
   EXPECT_EQ(0u, Copy("", buffer_).size());
   EXPECT_EQ('\0', buffer_[0]);