Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f2c9a490 authored by Jack He's avatar Jack He
Browse files

GD: Add strings library

* StringTrim: trim whitespaces around string
* StringSplit: split string using delimiters
* FromHexString: parse hex string to vector of uint8_t
* ToHexString: convert vector of uint8_t to string

Bug: 157533831
Tag: #gd-refactor
Test: atest bluetooth_test_gd
Change-Id: I1d1c4239c739bc6e0653f1818f067ad8dde62a74
parent a82513e8
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ filegroup {
    srcs: [
        "link_key.cc",
        "init_flags.cc",
        "strings.cc",
    ],
}

@@ -16,5 +17,6 @@ filegroup {
        "init_flags_test.cc",
        "list_map_test.cc",
        "lru_cache_test.cc",
        "strings_test.cc",
    ],
}
+102 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 The Android Open Source Project
 *
 * 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
 *
 *      http://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.
 */

#include "common/strings.h"

#include <charconv>
#include <functional>
#include <iomanip>
#include <sstream>
#include <system_error>

#include "os/log.h"

namespace {

struct IsSpace : std::unary_function<std::string::value_type, bool> {
  bool operator()(std::string::value_type v) {
    return isspace(static_cast<int>(v));
  }
};

struct IsHexDigit : std::unary_function<std::string::value_type, bool> {
  bool operator()(std::string::value_type v) {
    return isxdigit(static_cast<int>(v));
  }
};

}  // namespace

namespace bluetooth {
namespace common {

std::string ToHexString(const std::vector<uint8_t>& value) {
  std::stringstream ss;
  for (const auto& byte : value) {
    ss << std::hex << std::setw(2) << std::setfill('0') << +byte;
  }
  return ss.str();
}

std::optional<std::vector<uint8_t>> FromHexString(const std::string& str) {
  if (str.size() % 2 != 0) {
    LOG_DEBUG("str size is not divisible by 2, size is %zu", str.size());
    return std::nullopt;
  }
  if (std::find_if_not(str.begin(), str.end(), IsHexDigit{}) != str.end()) {
    LOG_DEBUG("value contains none hex digit");
    return std::nullopt;
  }
  std::vector<uint8_t> value;
  value.reserve(str.size() / 2);
  for (size_t i = 0; i < str.size(); i += 2) {
    uint8_t v = 0;
    auto ret = std::from_chars(str.c_str() + i, str.c_str() + i + 2, v, 16);
    if (std::make_error_code(ret.ec)) {
      LOG_DEBUG("failed to parse hex char at index %zu", i);
      return std::nullopt;
    }
    value.push_back(v);
  }
  return value;
}

std::string StringTrim(std::string str) {
  str.erase(str.begin(), std::find_if_not(str.begin(), str.end(), IsSpace{}));
  str.erase(std::find_if_not(str.rbegin(), str.rend(), IsSpace{}).base(), str.end());
  return str;
}

std::vector<std::string> StringSplit(const std::string& str, const std::string& delim, size_t max_token) {
  ASSERT_LOG(!delim.empty(), "delim cannot be empty");
  std::vector<std::string> tokens;
  // Use std::string::find and std::string::substr to avoid copying str into a stringstream
  std::string::size_type starting_index = 0;
  auto index_of_delim = str.find(delim);
  while ((max_token == 0 || tokens.size() < (max_token - 1)) && index_of_delim != std::string::npos) {
    tokens.push_back(str.substr(starting_index, index_of_delim - starting_index));
    starting_index = index_of_delim + delim.size();
    index_of_delim = str.find(delim, starting_index);
  }
  // Append last item to the vector if there are anything left
  if (starting_index < (str.size() + 1)) {
    tokens.push_back(str.substr(starting_index));
  }
  return tokens;
}

}  // namespace common
}  // namespace bluetooth
 No newline at end of file
+39 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 The Android Open Source Project
 *
 * 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
 *
 *      http://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 <optional>
#include <string>
#include <vector>

namespace bluetooth {
namespace common {

// Convert value into a hex decimal formatted string in lower case
std::string ToHexString(const std::vector<uint8_t>& value);

// Parse |str| into a std::vector<uint8_t>, |str| must contains only hex decimal
std::optional<std::vector<uint8_t>> FromHexString(const std::string& str);

// Remove whitespace from both ends of the |str|, returning a copy
std::string StringTrim(std::string str);

// Split |str| into at most |max_token| tokens delimited by |delim|, unlimited tokens when |max_token| is 0
std::vector<std::string> StringSplit(const std::string& str, const std::string& delim, size_t max_token = 0);

}  // namespace common
}  // namespace bluetooth
 No newline at end of file
+85 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 The Android Open Source Project
 *
 * 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
 *
 *      http://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.
 */

#include "common/strings.h"

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <memory>

namespace testing {

using bluetooth::common::FromHexString;
using bluetooth::common::StringSplit;
using bluetooth::common::StringTrim;
using bluetooth::common::ToHexString;

TEST(StringsTest, trim_string_test) {
  EXPECT_EQ(StringTrim("  aa bb"), "aa bb");
  EXPECT_EQ(StringTrim("aa bb "), "aa bb");
  EXPECT_EQ(StringTrim("  aa bb "), "aa bb");
  EXPECT_EQ(StringTrim("  aa bb \n"), "aa bb");
  EXPECT_EQ(StringTrim("  \raa bb\t \n"), "aa bb");
}

TEST(StringsTest, split_string_test) {
  EXPECT_THAT(StringSplit("", ","), ElementsAre(""));
  EXPECT_THAT(StringSplit("1,2,3", ","), ElementsAre("1", "2", "3"));
  EXPECT_THAT(StringSplit("1,2,3", "!"), ElementsAre("1,2,3"));
  EXPECT_THAT(StringSplit("1,2,3", ",", 2), ElementsAre("1", "2,3"));
  EXPECT_THAT(StringSplit("a,b,", ","), ElementsAre("a", "b", ""));
  EXPECT_THAT(StringSplit("ab,", ",", 2), ElementsAre("ab", ""));
  EXPECT_THAT(StringSplit("ab,,", ",", 2), ElementsAre("ab", ","));
  EXPECT_THAT(StringSplit("ab,,", ",", 1), ElementsAre("ab,,"));
  EXPECT_THAT(StringSplit("1,,2,,3", ",,"), ElementsAre("1", "2", "3"));
  EXPECT_THAT(StringSplit("1,,2,,3,,", ",,"), ElementsAre("1", "2", "3", ""));
  EXPECT_THAT(StringSplit("1,,2,,3,,", ",,", 2), ElementsAre("1", "2,,3,,"));
  EXPECT_THAT(StringSplit("1", ",,", 2), ElementsAre("1"));
  EXPECT_DEATH({ StringSplit("1,2,3", ""); }, "delim cannot be empty");
}

TEST(StringsTest, to_hex_string_test) {
  // normal
  EXPECT_THAT(ToHexString({0x12, 0x34, 0x56, 0xab}), Eq("123456ab"));
  // empty
  EXPECT_THAT(ToHexString({}), Eq(""));
  // unary
  EXPECT_THAT(ToHexString({0x12}), Eq("12"));
  // half
  EXPECT_THAT(ToHexString({0x6, 0x5, 0x56, 0xb}), Eq("0605560b"));
}

TEST(StringsTest, from_hex_string_test) {
  // normal
  EXPECT_THAT(FromHexString("aabbccdd1122"), Optional(ElementsAre(0xaa, 0xbb, 0xcc, 0xdd, 0x11, 0x22)));
  // empty
  EXPECT_THAT(FromHexString(""), Optional(IsEmpty()));
  // unary
  EXPECT_THAT(FromHexString("aa"), Optional(ElementsAre(0xaa)));
  // half
  EXPECT_THAT(FromHexString("0605560b"), Optional(ElementsAre(0x6, 0x5, 0x56, 0xb)));
  // upper case letter
  EXPECT_THAT(FromHexString("AABBCC"), Optional(ElementsAre(0xaa, 0xbb, 0xcc)));
  // upper and lower case letter mixed
  EXPECT_THAT(FromHexString("aAbbCC"), Optional(ElementsAre(0xaa, 0xbb, 0xcc)));
  // Error: odd length
  EXPECT_FALSE(FromHexString("0605560"));
  // Error: non hex char
  EXPECT_FALSE(FromHexString("060u560b"));
}

}  // namespace testing
 No newline at end of file