handle data element

PiperOrigin-RevId: 728727545
This commit is contained in:
Guogang Li
2025-02-19 10:38:28 -08:00
committed by Copybara-Service
parent eea3441da9
commit 46c0f153fe
5 changed files with 357 additions and 0 deletions
+2
View File
@@ -406,6 +406,7 @@ let package = Package(
// build files
"connections/implementation/analytics/BUILD",
"connections/implementation/flags/BUILD",
"connections/implementation/mediums/advertisements/BUILD",
"connections/implementation/mediums/ble_v2/BUILD",
"connections/implementation/mediums/multiplex/BUILD",
"connections/implementation/mediums/BUILD",
@@ -454,6 +455,7 @@ let package = Package(
"connections/implementation/wifi_hotspot_bwu_test.cc",
"connections/implementation/analytics/analytics_recorder_test.cc",
"connections/implementation/analytics/throughput_recorder_test.cc",
"connections/implementation/mediums/advertisements/data_element_test.cc",
"connections/implementation/mediums/ble_v2_test.cc",
"connections/implementation/mediums/ble_v2/bloom_filter_test.cc",
"connections/implementation/mediums/ble_v2/ble_packet_test.cc",
@@ -0,0 +1,39 @@
# Copyright 2020 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.
licenses(["notice"])
cc_library(
name = "common",
srcs = ["data_element.cc"],
hdrs = ["data_element.h"],
deps = [
"//internal/platform:base",
"//internal/platform:logging",
"//internal/platform:util",
"@com_google_absl//absl/strings",
],
)
cc_test(
name = "data_element_test",
srcs = ["data_element_test.cc"],
deps = [
":common",
"//internal/platform:base",
"//internal/platform:util",
"@com_github_protobuf_matchers//protobuf-matchers",
"@com_google_googletest//:gtest_main",
],
)
@@ -0,0 +1,159 @@
// 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 "connections/implementation/mediums/advertisements/data_element.h"
#include <cstdint>
#include <optional>
#include <string>
#include "internal/platform/byte_array.h"
#include "internal/platform/logging.h"
#include "internal/platform/stream_reader.h"
#include "internal/platform/stream_writer.h"
namespace nearby::connections::advertisements {
std::optional<DataElement> DataElement::From(uint8_t type, std::string value) {
if (type > kMaxTypeValue || type == 0 || value.size() > kMaxLengthValue) {
LOG(WARNING) << "Invalid data element: type=" << type
<< ", length=" << value.size();
return std::nullopt;
}
DataElement data_element;
data_element.type_ = type;
data_element.value_ = value;
return data_element;
}
std::optional<DataElement> DataElement::FromStreamReader(StreamReader& reader) {
std::optional<uint8_t> is_two_bytes = reader.ReadBits(1);
if (!is_two_bytes.has_value()) {
LOG(WARNING) << "Failed to read type";
return std::nullopt;
}
uint8_t type;
uint8_t length;
std::string value;
if (*is_two_bytes) {
// two bytes DE header
std::optional<uint8_t> length_result = reader.ReadBits(7);
if (!length_result.has_value()) {
LOG(WARNING) << "Failed to read length";
return std::nullopt;
}
std::optional<uint8_t> type_result = reader.ReadUint8();
if (!type_result.has_value() || *type_result == 0) {
LOG(WARNING) << "Failed to read type";
return std::nullopt;
}
type = *type_result;
length = *length_result;
} else {
// one byte DE header
std::optional<uint8_t> length_result = reader.ReadBits(3);
if (!length_result.has_value()) {
LOG(WARNING) << "Failed to read length";
return std::nullopt;
}
std::optional<uint8_t> type_result = reader.ReadBits(4);
if (!type_result.has_value() || *type_result == 0) {
LOG(WARNING) << "Failed to read type";
return std::nullopt;
}
type = *type_result;
length = *length_result;
}
if (length > 0) {
std::optional<ByteArray> bytes = reader.ReadBytes(length);
if (!bytes.has_value()) {
LOG(WARNING) << "Failed to read bytes";
return std::nullopt;
}
value = bytes->string_data();
}
DataElement data_element;
data_element.type_ = type;
data_element.value_ = value;
return data_element;
}
std::optional<DataElement> DataElement::FromData(const std::string& data) {
ByteArray bytes(data);
StreamReader reader(bytes);
return FromStreamReader(reader);
}
std::optional<std::string> DataElement::ToData() const {
StreamWriter writer;
if (!ToStreamWriter(writer)) {
LOG(WARNING) << "Failed to write data element";
return std::nullopt;
}
return writer.GetData();
}
bool DataElement::ToStreamWriter(StreamWriter& writer) const {
bool is_one_byte = true;
if (type_ > 15 || value_.size() > 7) {
is_one_byte = false;
}
if (is_one_byte) {
if (!writer.WriteBits(0, 1).Ok()) {
LOG(WARNING) << "Failed to write is_one_byte";
return false;
}
if (!writer.WriteBits(value_.size(), 3).Ok()) {
LOG(WARNING) << "Failed to write length";
return false;
}
if (!writer.WriteBits(type_, 4).Ok()) {
LOG(WARNING) << "Failed to write type";
return false;
}
} else {
if (!writer.WriteBits(1, 1).Ok()) {
LOG(WARNING) << "Failed to write is_one_byte";
return false;
}
if (!writer.WriteBits(value_.size(), 7).Ok()) {
LOG(WARNING) << "Failed to write length";
return false;
}
if (!writer.WriteUint8(type_).Ok()) {
LOG(WARNING) << "Failed to write type";
return false;
}
}
if (!writer.WriteBytes(value_).Ok()) {
LOG(WARNING) << "Failed to write value";
return false;
}
return true;
}
} // namespace nearby::connections::advertisements
@@ -0,0 +1,53 @@
// 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_CONNECTIONS_IMPLEMENTATION_MEDIUMS_ADVERTISEMENTS_DATA_ELEMENT_H_
#define THIRD_PARTY_NEARBY_CONNECTIONS_IMPLEMENTATION_MEDIUMS_ADVERTISEMENTS_DATA_ELEMENT_H_
#include <cstdint>
#include <optional>
#include <string>
#include "internal/platform/stream_reader.h"
#include "internal/platform/stream_writer.h"
namespace nearby::connections::advertisements {
class DataElement {
public:
constexpr static uint8_t kMaxLengthValue = 0x7f;
constexpr static uint8_t kMaxTypeValue = 0xff;
~DataElement() = default;
static std::optional<DataElement> From(uint8_t type, std::string value);
static std::optional<DataElement> FromStreamReader(StreamReader& reader);
static std::optional<DataElement> FromData(const std::string& data);
uint8_t type() const { return type_; }
uint8_t length() const { return value_.size(); }
std::string value() const { return value_; }
std::optional<std::string> ToData() const;
bool ToStreamWriter(StreamWriter& writer) const;
private:
// Each DE has a defined meaning and follows a common TLV structure.
uint8_t type_{0};
std::string value_;
};
} // namespace nearby::connections::advertisements
#endif // THIRD_PARTY_NEARBY_CONNECTIONS_IMPLEMENTATION_MEDIUMS_ADVERTISEMENTS_DATA_ELEMENT_H_
@@ -0,0 +1,104 @@
// 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 "connections/implementation/mediums/advertisements/data_element.h"
#include <optional>
#include <string>
#include "gtest/gtest.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/stream_reader.h"
#include "internal/platform/stream_writer.h"
namespace nearby::connections::advertisements {
namespace {
TEST(DataElementTest, From) {
std::optional<DataElement> data_element =
DataElement::From(0x01, "test_value");
ASSERT_TRUE(data_element.has_value());
EXPECT_EQ(data_element->type(), 0x01);
EXPECT_EQ(data_element->length(), 10);
EXPECT_EQ(data_element->value(), "test_value");
}
TEST(DataElementTest, FromInvalidType) {
std::optional<DataElement> data_element =
DataElement::From(0x00, "test_value");
EXPECT_FALSE(data_element.has_value());
}
TEST(DataElementTest, FromInvalidLength) {
std::optional<DataElement> data_element =
DataElement::From(0x01, std::string(128, 'a'));
EXPECT_FALSE(data_element.has_value());
}
TEST(DataElementTest, OneByteFromStreamReader) {
ByteArray bytes("\x21\x01\x02");
StreamReader reader(bytes);
std::optional<DataElement> data_element =
DataElement::FromStreamReader(reader);
ASSERT_TRUE(data_element.has_value());
EXPECT_EQ(data_element->type(), 1);
EXPECT_EQ(data_element->length(), 2);
EXPECT_EQ(data_element->value(), "\x01\x02");
}
TEST(DataElementTest, TwoByteFromStreamReader) {
ByteArray bytes("\x84\x01\x01\x02\x03\x04");
StreamReader reader(bytes);
std::optional<DataElement> data_element =
DataElement::FromStreamReader(reader);
ASSERT_TRUE(data_element.has_value());
EXPECT_EQ(data_element->type(), 1);
EXPECT_EQ(data_element->length(), 4);
EXPECT_EQ(data_element->value(), "\x01\x02\x03\x04");
}
TEST(DataElementTest, FromData) {
std::string data = "\x21\x01\x02";
std::optional<DataElement> data_element = DataElement::FromData(data);
ASSERT_TRUE(data_element.has_value());
EXPECT_EQ(data_element->type(), 1);
EXPECT_EQ(data_element->length(), 2);
EXPECT_EQ(data_element->value(), "\x01\x02");
}
TEST(DataElementTest, InvalidData) {
EXPECT_FALSE(DataElement::FromData("").has_value());
EXPECT_FALSE(DataElement::FromData("\x20").has_value());
EXPECT_FALSE(DataElement::FromData("\x21\x00").has_value());
EXPECT_FALSE(DataElement::FromData("\x91\x00").has_value());
EXPECT_FALSE(DataElement::FromData("\x91\x01").has_value());
EXPECT_TRUE(DataElement::FromData("\x81\x01\x03").has_value());
}
TEST(DataElementTest, ToData) {
std::string data = "\x21\x01\x03";
EXPECT_EQ(DataElement::FromData(data)->ToData(), data);
data = "\x81\x23\x21";
EXPECT_EQ(DataElement::FromData(data)->ToData(), data);
}
TEST(DataElementTest, ToStreamWriter) {
StreamWriter writer;
std::string data = "\x21\x01\x03";
DataElement::FromData(data)->ToStreamWriter(writer);
EXPECT_EQ(writer.GetData(), data);
}
} // namespace
} // namespace nearby::connections::advertisements