diff --git a/Package.swift b/Package.swift index 4b08655f..7c5fdc60 100644 --- a/Package.swift +++ b/Package.swift @@ -524,6 +524,7 @@ let package = Package( "internal/platform/bluetooth_utils_test.cc", "internal/platform/credential_storage_impl_test.cc", "internal/platform/input_stream_test.cc", + "internal/platform/mac_address_test.cc", "internal/platform/single_thread_executor_test.cc", "internal/platform/scheduled_executor_test.cc", "internal/platform/stream_reader_test.cc", diff --git a/internal/platform/BUILD b/internal/platform/BUILD index fbe4b4a0..1a36a06b 100644 --- a/internal/platform/BUILD +++ b/internal/platform/BUILD @@ -347,6 +347,21 @@ cc_library( ], ) +cc_library( + name = "mac_address", + srcs = ["mac_address.cc"], + hdrs = ["mac_address.h"], + compatible_with = ["//buildenv/target:non_prod"], + visibility = [ + "//:__subpackages__", + ], + deps = [ + ":logging", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + ], +) + cc_library( name = "test_util", testonly = True, @@ -570,3 +585,13 @@ cc_test( ], }), ) + +cc_test( + name = "mac_address_test", + srcs = ["mac_address_test.cc"], + deps = [ + ":mac_address", + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/internal/platform/implementation/windows/BUILD b/internal/platform/implementation/windows/BUILD index c2dfd5c7..2a601233 100644 --- a/internal/platform/implementation/windows/BUILD +++ b/internal/platform/implementation/windows/BUILD @@ -251,6 +251,7 @@ cc_library( "//internal/platform:base", "//internal/platform:cancellation_flag", "//internal/platform:logging", + "//internal/platform:mac_address", "//internal/platform:types", "//internal/platform:uuid", "//internal/platform/flags:platform_flags", diff --git a/internal/platform/implementation/windows/ble_v2_peripheral.cc b/internal/platform/implementation/windows/ble_v2_peripheral.cc index d0cc47dc..1a9c80a7 100644 --- a/internal/platform/implementation/windows/ble_v2_peripheral.cc +++ b/internal/platform/implementation/windows/ble_v2_peripheral.cc @@ -13,51 +13,26 @@ // limitations under the License. #include "internal/platform/implementation/windows/ble_v2_peripheral.h" - #include #include "absl/strings/string_view.h" -#include "internal/platform/bluetooth_utils.h" #include "internal/platform/logging.h" +#include "internal/platform/mac_address.h" namespace nearby { namespace windows { -namespace { -constexpr int kMacAddressLength = 17; -} BleV2Peripheral::BleV2Peripheral(absl::string_view address) { - if (SetAddress(address)) { - unique_id_ = static_cast(BluetoothUtils::ToNumber(address)); + if (!MacAddress::FromString(address, mac_address_)) { + LOG(WARNING) << "Create BleV2Peripheral with invalid MAC: " << address; } } -bool BleV2Peripheral::SetAddress(absl::string_view address) { - // The address must be in format "00:B0:D0:63:C2:26". - if (address.size() != kMacAddressLength) { - LOG(ERROR) << ": Invalid MAC address length."; - return false; +std::string BleV2Peripheral::GetAddress() const { + if (mac_address_.IsSet()) { + return mac_address_.ToString(); } - - for (int i = 0; i < kMacAddressLength; ++i) { - if ((i % 3) == 0 || (i % 3) == 1) { - if ((address[i] >= '0' && address[i] <= '9') || - (address[i] >= 'a' && address[i] <= 'f') || - (address[i] >= 'A' && address[i] <= 'F')) { - continue; - } - } else { - if (address[i] == ':') { - continue; - } - } - - LOG(ERROR) << ": Invalid MAC address format."; - return false; - } - - address_ = std::string(address); - return true; + return ""; } } // namespace windows diff --git a/internal/platform/implementation/windows/ble_v2_peripheral.h b/internal/platform/implementation/windows/ble_v2_peripheral.h index 6486cb2b..3c750109 100644 --- a/internal/platform/implementation/windows/ble_v2_peripheral.h +++ b/internal/platform/implementation/windows/ble_v2_peripheral.h @@ -19,6 +19,7 @@ #include "absl/strings/string_view.h" #include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/mac_address.h" namespace nearby { namespace windows { @@ -31,21 +32,17 @@ class BleV2Peripheral : public api::ble_v2::BlePeripheral { explicit BleV2Peripheral(absl::string_view address); ~BleV2Peripheral() override = default; - // Returns the MAC address of the peripheral. The format is in + // Returns the MAC address of the peripheral or empty string. The format is in // "00:B0:D0:63:C2:26". - std::string GetAddress() const override { return address_; } + std::string GetAddress() const override; - UniqueId GetUniqueId() const override { return unique_id_; } - // Sets the MAC address of the peripheral. The address format must be in - // pattern of "00:B0:D0:63:C2:26". - bool SetAddress(absl::string_view address); + UniqueId GetUniqueId() const override { return mac_address_.address(); } - bool Ok() const { return unique_id_ != 0; } + bool Ok() const { return mac_address_.IsSet(); } explicit operator bool() const { return Ok(); } private: - std::string address_; - UniqueId unique_id_ = 0; + MacAddress mac_address_; }; } // namespace windows diff --git a/internal/platform/implementation/windows/ble_v2_peripheral_test.cc b/internal/platform/implementation/windows/ble_v2_peripheral_test.cc index 8902a21c..18b90dd0 100644 --- a/internal/platform/implementation/windows/ble_v2_peripheral_test.cc +++ b/internal/platform/implementation/windows/ble_v2_peripheral_test.cc @@ -15,6 +15,7 @@ #include "internal/platform/implementation/windows/ble_v2_peripheral.h" #include "gtest/gtest.h" +#include "absl/strings/string_view.h" namespace nearby { namespace windows { @@ -26,38 +27,10 @@ TEST(BleV2Peripheral, Constructor) { EXPECT_TRUE(ble_peripheral); EXPECT_TRUE(ble_peripheral.Ok()); - EXPECT_NE(ble_peripheral.GetUniqueId(), 0); + EXPECT_EQ(ble_peripheral.GetUniqueId(), 0xf1f2f3f4f5f6); EXPECT_EQ(ble_peripheral.GetAddress(), kAddress); } -TEST(BleV2Peripheral, SetMacAddress) { - BleV2Peripheral ble_peripheral("F1:F2:F3:F4:F5:F6"); - EXPECT_TRUE(ble_peripheral.SetAddress("00:B0:D0:63:C2:26")); - EXPECT_FALSE(ble_peripheral.SetAddress("00:B0:D0:6T:C2:26")); - EXPECT_FALSE(ble_peripheral.SetAddress("00:B0:D0:63:C2:2")); - EXPECT_FALSE(ble_peripheral.SetAddress("0:B0:D0:63:C2:203")); - EXPECT_FALSE(ble_peripheral.SetAddress("0:B0:D0:6P:C2:203")); -} - -TEST(BleV2Peripheral, SetAndGetMacAddress) { - constexpr absl::string_view kChangedAddress = "00:B0:D0:63:C2:26"; - BleV2Peripheral ble_peripheral("F1:F2:F3:F4:F5:F6"); - - EXPECT_TRUE(ble_peripheral.SetAddress(kChangedAddress)); - EXPECT_EQ(ble_peripheral.GetAddress(), kChangedAddress); -} - -TEST(BleV2Peripheral, SetAddressDoesNotChangeUniqueId) { - constexpr absl::string_view kChangedAddress = "00:B0:D0:63:C2:26"; - BleV2Peripheral ble_peripheral("F1:F2:F3:F4:F5:F6"); - BleV2Peripheral::UniqueId unique_id = ble_peripheral.GetUniqueId(); - - EXPECT_NE(unique_id, 0); - EXPECT_TRUE(ble_peripheral.SetAddress(kChangedAddress)); - EXPECT_EQ(ble_peripheral.GetAddress(), kChangedAddress); - EXPECT_EQ(ble_peripheral.GetUniqueId(), unique_id); -} - TEST(BleV2Peripheral, ConstructFromBadAddress) { BleV2Peripheral ble_peripheral("G1:F2:F3:F4:F5:F6"); diff --git a/internal/platform/mac_address.cc b/internal/platform/mac_address.cc new file mode 100644 index 00000000..45e826e4 --- /dev/null +++ b/internal/platform/mac_address.cc @@ -0,0 +1,68 @@ +// Copyright 2025 Google LLC +// +// 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. + +#include "internal/platform/mac_address.h" + +#include +#include + +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" + +namespace nearby { + +bool MacAddress::FromString(absl::string_view address, + MacAddress& mac_address) { + if (address.length() != 17) { + return false; + } + uint64_t address_int = 0; + for (int i = 0; i < 6; ++i) { + // Check for ":" separator. + if (i < 5 && address[i * 3 + 2] != ':') { + return false; + } + absl::string_view component; + component = address.substr(i * 3, 2); + uint64_t component_int = 0; + if (!absl::SimpleHexAtoi(component, &component_int)) { + return false; + } + address_int = (address_int << 8) | component_int; + } + mac_address.address_ = address_int; + return true; +} + + +bool MacAddress::FromUint64(uint64_t address, MacAddress& mac_address) { + if ((address & 0xffff000000000000L) != 0) { + return false; + } + mac_address.address_ = address; + return true; +} + +std::string MacAddress::ToString() const { + return absl::StrCat(absl::StrFormat("%02X", (address_ >> 40) & 0xff), ":", + absl::StrFormat("%02X", (address_ >> 32) & 0xff), ":", + absl::StrFormat("%02X", (address_ >> 24) & 0xff), ":", + absl::StrFormat("%02X", (address_ >> 16) & 0xff), ":", + absl::StrFormat("%02X", (address_ >> 8) & 0xff), ":", + absl::StrFormat("%02X", address_ & 0xff)); +} + +} // namespace nearby diff --git a/internal/platform/mac_address.h b/internal/platform/mac_address.h new file mode 100644 index 00000000..5ddccef8 --- /dev/null +++ b/internal/platform/mac_address.h @@ -0,0 +1,55 @@ +// Copyright 2025 Google LLC +// +// 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. + +#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_MAC_ADDRESS_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_MAC_ADDRESS_H_ + +#include +#include + +#include "absl/strings/string_view.h" + +namespace nearby { + +// Representation of a 48 bit MAC address. +class MacAddress { + public: + // Creates an empty MAC address with all 0s. + MacAddress() = default; + + // Creates a MAC address from a string. + // Returns false if the string is not a valid MAC address. + static bool FromString(absl::string_view address, MacAddress& mac_address); + + // Creates a MAC address from a 64-bit integer. + // The upper 16 bits of the address must be 0. + // Returns false if the integer is not a valid MAC address. + static bool FromUint64(uint64_t address, MacAddress& mac_address); + + // Packs the MAC address into the lower 48 bits of a 64-bit integer. + uint64_t address() const { return address_; } + + // Returns the MAC address in the format of "00:B0:D0:63:C2:26". + std::string ToString() const; + + // Returns true if the MAC address is set. + bool IsSet() const { return address_ != 0; } + + private: + uint64_t address_ = 0; +}; + +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_MAC_ADDRESS_H_ diff --git a/internal/platform/mac_address_test.cc b/internal/platform/mac_address_test.cc new file mode 100644 index 00000000..b208e8a7 --- /dev/null +++ b/internal/platform/mac_address_test.cc @@ -0,0 +1,72 @@ +// Copyright 2025 Google LLC +// +// 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. + +#include "internal/platform/mac_address.h" + +#include "gtest/gtest.h" + +namespace nearby { +namespace { + +TEST(MacAddressTest, FromStringSuccess) { + MacAddress mac_address; + EXPECT_TRUE(MacAddress::FromString("00:B0:D0:63:C2:26", mac_address)); + EXPECT_EQ(mac_address.address(), 0x00B0D063C226); + EXPECT_EQ(mac_address.ToString(), "00:B0:D0:63:C2:26"); +} + +TEST(MacAddressTest, FromStringMissingSeparator) { + MacAddress mac_address; + EXPECT_FALSE(MacAddress::FromString("00:B0:D0,63:C2:26", mac_address)); + EXPECT_EQ(mac_address.address(), 0); +} + +TEST(MacAddressTest, FromStringInvalidHex) { + MacAddress mac_address; + EXPECT_FALSE(MacAddress::FromString("01:BG:D0,63:C2:26", mac_address)); + EXPECT_EQ(mac_address.address(), 0); +} + +TEST(MacAddressTest, FromStringInvalidLength) { + MacAddress mac_address; + EXPECT_FALSE(MacAddress::FromString("01:BG:D0,63:C2:1", mac_address)); + EXPECT_EQ(mac_address.address(), 0); +} + +TEST(MacAddressTest, FromUint64Success) { + MacAddress mac_address; + EXPECT_TRUE(MacAddress::FromUint64(0x00B0D063C226, mac_address)); + EXPECT_EQ(mac_address.address(), 0x00B0D063C226); +} + +TEST(MacAddressTest, FromUint64InvalidAddresss) { + MacAddress mac_address; + EXPECT_FALSE(MacAddress::FromUint64(0x0100B0D063C226, mac_address)); + EXPECT_EQ(mac_address.address(), 0); +} + +TEST(MacAddressTest, IsSetTrue) { + MacAddress mac_address; + EXPECT_TRUE(MacAddress::FromUint64(0x00B0D063C226, mac_address)); + EXPECT_TRUE(mac_address.IsSet()); +} + + +TEST(MacAddressTest, IsSetFalse) { + MacAddress mac_address; + EXPECT_FALSE(mac_address.IsSet()); +} + +} // namespace +} // namespace nearby