diff --git a/cpp/core/internal/BUILD b/cpp/core/internal/BUILD index 9ce2c620..1e649fae 100644 --- a/cpp/core/internal/BUILD +++ b/cpp/core/internal/BUILD @@ -30,6 +30,7 @@ cc_library( "internal_payload.cc", "internal_payload_factory.cc", "offline_frames.cc", + "offline_frames_validator.cc", "offline_service_controller.cc", "p2p_cluster_pcp_handler.cc", "p2p_point_to_point_pcp_handler.cc", @@ -62,6 +63,7 @@ cc_library( "internal_payload.h", "internal_payload_factory.h", "offline_frames.h", + "offline_frames_validator.h", "offline_service_controller.h", "p2p_cluster_pcp_handler.h", "p2p_point_to_point_pcp_handler.h", @@ -163,6 +165,7 @@ cc_test( "endpoint_manager_test.cc", "internal_payload_factory_test.cc", "offline_frames_test.cc", + "offline_frames_validator_test.cc", "offline_service_controller_test.cc", "p2p_cluster_pcp_handler_test.cc", "payload_manager_test.cc", diff --git a/cpp/core/internal/base_pcp_handler.cc b/cpp/core/internal/base_pcp_handler.cc index e7f3d557..98298f54 100644 --- a/cpp/core/internal/base_pcp_handler.cc +++ b/cpp/core/internal/base_pcp_handler.cc @@ -488,8 +488,10 @@ Exception BasePcpHandler::WriteConnectionRequestFrame( EndpointChannel* endpoint_channel, const std::string& local_endpoint_id, const ByteArray& local_endpoint_info, std::int32_t nonce, const std::vector& supported_mediums) { + // TODO(b/172178926): Add WifiLan 5GHz and BSSID support. return endpoint_channel->Write(parser::ForConnectionRequest( - local_endpoint_id, local_endpoint_info, nonce, supported_mediums)); + local_endpoint_id, local_endpoint_info, nonce, /*supports_5_ghz =*/false, + /*bssid=*/std::string{}, supported_mediums)); } void BasePcpHandler::ProcessPreConnectionInitiationFailure( diff --git a/cpp/core/internal/endpoint_manager_test.cc b/cpp/core/internal/endpoint_manager_test.cc index 5a3ca675..4cd64752 100644 --- a/cpp/core/internal/endpoint_manager_test.cc +++ b/cpp/core/internal/endpoint_manager_test.cc @@ -175,9 +175,8 @@ TEST_F(EndpointManagerTest, RegisterFrameProcessorWorks) { auto endpoint_channel = std::make_unique(); auto connect_request = std::make_unique(); ByteArray endpoint_info{"endpoint_name"}; - auto read_data = - parser::ForConnectionRequest("endpoint_id", endpoint_info, - 1234, std::vector{Medium::BLE}); + auto read_data = parser::ForConnectionRequest( + "endpoint_id", endpoint_info, 1234, false, "", std::vector{Medium::BLE}); EXPECT_CALL(*connect_request, OnIncomingFrame); EXPECT_CALL(*connect_request, OnEndpointDisconnect); EXPECT_CALL(*endpoint_channel, Read()) diff --git a/cpp/core/internal/internal_payload.cc b/cpp/core/internal/internal_payload.cc index 079e1f47..b230d548 100644 --- a/cpp/core/internal/internal_payload.cc +++ b/cpp/core/internal/internal_payload.cc @@ -18,6 +18,9 @@ namespace location { namespace nearby { namespace connections { +// The definition is necessary before C++17. +constexpr int InternalPayload::kIndeterminateSize; + InternalPayload::InternalPayload(Payload payload) : payload_(std::move(payload)), payload_id_(payload_.GetId()) {} diff --git a/cpp/core/internal/internal_payload.h b/cpp/core/internal/internal_payload.h index 6ffcde58..7b1cb699 100644 --- a/cpp/core/internal/internal_payload.h +++ b/cpp/core/internal/internal_payload.h @@ -33,6 +33,8 @@ namespace connections { // Payload. class InternalPayload { public: + static constexpr int kIndeterminateSize = -1; + explicit InternalPayload(Payload payload); virtual ~InternalPayload() = default; diff --git a/cpp/core/internal/mediums/utils.cc b/cpp/core/internal/mediums/utils.cc index f57ba8f7..11b581f7 100644 --- a/cpp/core/internal/mediums/utils.cc +++ b/cpp/core/internal/mediums/utils.cc @@ -76,6 +76,8 @@ std::string Utils::UnwrapUpgradeServiceId( LocationHint Utils::BuildLocationHint(const std::string& location) { LocationHint location_hint; + location_hint.set_format(LocationStandard::UNKNOWN); + if (!location.empty()) { location_hint.set_location(location); if (location.at(0) == '+') { diff --git a/cpp/core/internal/mediums/wifi_lan.cc b/cpp/core/internal/mediums/wifi_lan.cc index 5c6954a1..dff0ef8d 100644 --- a/cpp/core/internal/mediums/wifi_lan.cc +++ b/cpp/core/internal/mediums/wifi_lan.cc @@ -257,6 +257,12 @@ WifiLanService WifiLan::GetRemoteWifiLanService(const std::string& ip_address, return medium_.FindRemoteService(ip_address, port); } +std::pair WifiLan::GetServiceAddress( + const std::string& service_id) { + MutexLock lock(&mutex_); + return medium_.GetServiceAddress(service_id); +} + } // namespace connections } // namespace nearby } // namespace location diff --git a/cpp/core/internal/mediums/wifi_lan.h b/cpp/core/internal/mediums/wifi_lan.h index a6f4fca4..4d9b02d7 100644 --- a/cpp/core/internal/mediums/wifi_lan.h +++ b/cpp/core/internal/mediums/wifi_lan.h @@ -88,6 +88,9 @@ class WifiLan { WifiLanService GetRemoteWifiLanService(const std::string& ip_address, int port) ABSL_LOCKS_EXCLUDED(mutex_); + std::pair GetServiceAddress(const std::string& service_id) + ABSL_LOCKS_EXCLUDED(mutex_); + private: struct AdvertisingInfo { bool Empty() const { return service_ids.empty(); } diff --git a/cpp/core/internal/offline_frames.cc b/cpp/core/internal/offline_frames.cc index ad40c23f..0064dcd0 100644 --- a/cpp/core/internal/offline_frames.cc +++ b/cpp/core/internal/offline_frames.cc @@ -18,6 +18,7 @@ #include #include "core/internal/message_lite.h" +#include "core/internal/offline_frames_validator.h" #include "core/status.h" #include "proto/connections/offline_wire_formats.pb.h" #include "platform/base/byte_array.h" @@ -44,6 +45,10 @@ ExceptionOrOfflineFrame FromBytes(const ByteArray& bytes) { OfflineFrame frame; if (frame.ParseFromString(std::string(bytes))) { + Exception validation_exception = EnsureValidOfflineFrame(frame); + if (validation_exception.Raised()) { + return ExceptionOrOfflineFrame(validation_exception); + } return ExceptionOrOfflineFrame(std::move(frame)); } else { return ExceptionOrOfflineFrame(Exception::kInvalidProtocolBuffer); @@ -60,7 +65,8 @@ V1Frame::FrameType GetFrameType(const OfflineFrame& frame) { ByteArray ForConnectionRequest(const std::string& endpoint_id, const ByteArray& endpoint_info, - std::int32_t nonce, + std::int32_t nonce, bool supports_5_ghz, + const std::string& bssid, const std::vector& mediums) { OfflineFrame frame; @@ -68,12 +74,21 @@ ByteArray ForConnectionRequest(const std::string& endpoint_id, auto* v1_frame = frame.mutable_v1(); v1_frame->set_type(V1Frame::CONNECTION_REQUEST); auto* connection_request = v1_frame->mutable_connection_request(); - connection_request->set_endpoint_id(endpoint_id); - connection_request->set_endpoint_name(std::string(endpoint_info)); - connection_request->set_endpoint_info(std::string(endpoint_info)); + if (!endpoint_id.empty()) + connection_request->set_endpoint_id(endpoint_id); + if (!endpoint_info.Empty()) { + connection_request->set_endpoint_name(std::string(endpoint_info)); + connection_request->set_endpoint_info(std::string(endpoint_info)); + } connection_request->set_nonce(nonce); - for (const auto& medium : mediums) { - connection_request->add_mediums(MediumToConnectionRequestMedium(medium)); + auto* medium_metadata = connection_request->mutable_medium_metadata(); + medium_metadata->set_supports_5_ghz(supports_5_ghz); + if (!bssid.empty()) + medium_metadata->set_bssid(bssid); + if (!mediums.empty()) { + for (const auto& medium : mediums) { + connection_request->add_mediums(MediumToConnectionRequestMedium(medium)); + } } return ToBytes(std::move(frame)); @@ -132,7 +147,9 @@ ByteArray ForControlPayloadTransfer( ByteArray ForBwuWifiHotspotPathAvailable(const std::string& ssid, const std::string& password, - std::int32_t port) { + std::int32_t port, + const std::string& gateway, + bool supports_disabling_encryption) { OfflineFrame frame; frame.set_version(OfflineFrame::V1); @@ -143,11 +160,14 @@ ByteArray ForBwuWifiHotspotPathAvailable(const std::string& ssid, BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE); auto* upgrade_path_info = sub_frame->mutable_upgrade_path_info(); upgrade_path_info->set_medium(UpgradePathInfo::WIFI_HOTSPOT); + upgrade_path_info->set_supports_disabling_encryption( + supports_disabling_encryption); auto* wifi_hotspot_credentials = upgrade_path_info->mutable_wifi_hotspot_credentials(); wifi_hotspot_credentials->set_ssid(ssid); wifi_hotspot_credentials->set_password(password); wifi_hotspot_credentials->set_port(port); + wifi_hotspot_credentials->set_gateway(gateway); return ToBytes(std::move(frame)); } @@ -171,6 +191,58 @@ ByteArray ForBwuWifiLanPathAvailable(const std::string& ip_address, return ToBytes(std::move(frame)); } +ByteArray ForBwuWifiAwarePathAvailable(const std::string& service_id, + const std::string& service_info, + const std::string& password, + bool supports_disabling_encryption) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE); + auto* upgrade_path_info = sub_frame->mutable_upgrade_path_info(); + upgrade_path_info->set_medium(UpgradePathInfo::WIFI_AWARE); + upgrade_path_info->set_supports_disabling_encryption( + supports_disabling_encryption); + auto* wifi_aware_credentials = + upgrade_path_info->mutable_wifi_aware_credentials(); + wifi_aware_credentials->set_service_id(service_id); + wifi_aware_credentials->set_service_info(service_info); + if (!password.empty()) wifi_aware_credentials->set_password(password); + + return ToBytes(std::move(frame)); +} + +ByteArray ForBwuWifiDirectPathAvailable(const std::string& ssid, + const std::string& password, + std::int32_t port, + std::int32_t frequency, + bool supports_disabling_encryption) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE); + auto* upgrade_path_info = sub_frame->mutable_upgrade_path_info(); + upgrade_path_info->set_medium(UpgradePathInfo::WIFI_DIRECT); + upgrade_path_info->set_supports_disabling_encryption( + supports_disabling_encryption); + auto* wifi_direct_credentials = + upgrade_path_info->mutable_wifi_direct_credentials(); + wifi_direct_credentials->set_ssid(ssid); + wifi_direct_credentials->set_password(password); + wifi_direct_credentials->set_port(port); + wifi_direct_credentials->set_frequency(frequency); + + return ToBytes(std::move(frame)); +} + ByteArray ForBwuBluetoothPathAvailable(const std::string& service_id, const std::string& mac_address) { OfflineFrame frame; diff --git a/cpp/core/internal/offline_frames.h b/cpp/core/internal/offline_frames.h index 17c83ade..0c5eb21d 100644 --- a/cpp/core/internal/offline_frames.h +++ b/cpp/core/internal/offline_frames.h @@ -45,7 +45,8 @@ V1Frame::FrameType GetFrameType(const OfflineFrame& offline_frame); // Builds Connection Request / Response messages. ByteArray ForConnectionRequest(const std::string& endpoint_id, const ByteArray& endpoint_info, - std::int32_t nonce, + std::int32_t nonce, bool supports_5_ghz, + const std::string& bssid, const std::vector& mediums); ByteArray ForConnectionResponse(std::int32_t status); @@ -61,9 +62,20 @@ ByteArray ForControlPayloadTransfer( ByteArray ForBwuIntroduction(const std::string& endpoint_id); ByteArray ForBwuWifiHotspotPathAvailable(const std::string& ssid, const std::string& password, - std::int32_t port); + std::int32_t port, + const std::string& gateway, + bool supports_disabling_encryption); ByteArray ForBwuWifiLanPathAvailable(const std::string& ip_address, std::int32_t port); +ByteArray ForBwuWifiAwarePathAvailable(const std::string& service_id, + const std::string& service_info, + const std::string& password, + bool supports_disabling_encryption); +ByteArray ForBwuWifiDirectPathAvailable(const std::string& ssid, + const std::string& password, + std::int32_t port, + std::int32_t frequency, + bool supports_disabling_encryption); ByteArray ForBwuBluetoothPathAvailable(const std::string& service_id, const std::string& mac_address); ByteArray ForBwuWebrtcPathAvailable(const std::string& peer_id, diff --git a/cpp/core/internal/offline_frames_test.cc b/cpp/core/internal/offline_frames_test.cc index 3aec5f81..bdc526ad 100644 --- a/cpp/core/internal/offline_frames_test.cc +++ b/cpp/core/internal/offline_frames_test.cc @@ -36,6 +36,8 @@ using ::testing::EqualsProto; constexpr absl::string_view kEndpointId{"ABC"}; constexpr absl::string_view kEndpointName{"XYZ"}; constexpr int kNonce = 1234; +constexpr bool kSupports5ghz = true; +constexpr absl::string_view kBssid{"FF:FF:FF:FF:FF:FF"}; constexpr std::array kMediums = { Medium::MDNS, Medium::BLUETOOTH, Medium::WIFI_HOTSPOT, Medium::BLE, Medium::WIFI_LAN, Medium::WIFI_AWARE, @@ -55,6 +57,11 @@ TEST(OfflineFramesTest, CanParseMessageFromBytes) { sub_frame->set_endpoint_name(kEndpointName); sub_frame->set_endpoint_info(kEndpointName); sub_frame->set_nonce(kNonce); + auto* medium_metadata = sub_frame->mutable_medium_metadata(); + + medium_metadata->set_supports_5_ghz(kSupports5ghz); + medium_metadata->set_bssid(kBssid); + for (auto& medium : kMediums) { sub_frame->add_mediums(MediumToConnectionRequestMedium(medium)); } @@ -81,6 +88,10 @@ TEST(OfflineFramesTest, CanGenerateConnectionRequest) { endpoint_name: "XYZ" endpoint_info: "XYZ" nonce: 1234 + medium_metadata: < + supports_5_ghz: true + bssid: "FF:FF:FF:FF:FF:FF" + > mediums: MDNS mediums: BLUETOOTH mediums: WIFI_HOTSPOT @@ -94,6 +105,7 @@ TEST(OfflineFramesTest, CanGenerateConnectionRequest) { >)pb"; ByteArray bytes = ForConnectionRequest( std::string(kEndpointId), ByteArray{std::string(kEndpointName)}, kNonce, + kSupports5ghz, std::string(kBssid), std::vector(kMediums.begin(), kMediums.end())); auto response = FromBytes(bytes); ASSERT_TRUE(response.ok()); @@ -188,11 +200,14 @@ TEST(OfflineFramesTest, CanGenerateBwuWifiHotspotPathAvailable) { ssid: "ssid" password: "password" port: 1234 + gateway: "0.0.0.0" > + supports_disabling_encryption: false > > >)pb"; - ByteArray bytes = ForBwuWifiHotspotPathAvailable("ssid", "password", 1234); + ByteArray bytes = ForBwuWifiHotspotPathAvailable("ssid", "password", 1234, + "0.0.0.0", false); auto response = FromBytes(bytes); ASSERT_TRUE(response.ok()); OfflineFrame message = FromBytes(bytes).result(); @@ -220,6 +235,61 @@ TEST(OfflineFramesTest, CanGenerateBwuWifiLanPathAvailable) { EXPECT_THAT(message, EqualsProto(kExpected)); } +TEST(OfflineFramesTest, CanGenerateBwuWifiAwarePathAvailable) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: BANDWIDTH_UPGRADE_NEGOTIATION + bandwidth_upgrade_negotiation: < + event_type: UPGRADE_PATH_AVAILABLE + upgrade_path_info: < + medium: WIFI_AWARE + wifi_aware_credentials: < + service_id: "service_id" + service_info: "service_info" + password: "password" + > + supports_disabling_encryption: false + > + > + >)pb"; + ByteArray bytes = ForBwuWifiAwarePathAvailable("service_id", "service_info", + "password", false); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateBwuWifiDirectPathAvailable) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: BANDWIDTH_UPGRADE_NEGOTIATION + bandwidth_upgrade_negotiation: < + event_type: UPGRADE_PATH_AVAILABLE + upgrade_path_info: < + medium: WIFI_DIRECT + wifi_direct_credentials: < + ssid: "DIRECT-A0-0123456789AB" + password: "password" + port: 1000 + frequency: 1000 + > + supports_disabling_encryption: false + > + > + >)pb"; + ByteArray bytes = ForBwuWifiDirectPathAvailable( + "DIRECT-A0-0123456789AB", "password", 1000, 1000, false); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + TEST(OfflineFramesTest, CanGenerateBwuBluetoothPathAvailable) { constexpr char kExpected[] = R"pb( diff --git a/cpp/core/internal/offline_frames_validator.cc b/cpp/core/internal/offline_frames_validator.cc new file mode 100644 index 00000000..807997e2 --- /dev/null +++ b/cpp/core/internal/offline_frames_validator.cc @@ -0,0 +1,380 @@ +// 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. + +#include "core/internal/offline_frames_validator.h" + +#include //NOLINT + +#include "core/internal/internal_payload.h" +#include "core/internal/offline_frames.h" +#include "proto/connections/offline_wire_formats.pb.h" + +namespace location { +namespace nearby { +namespace connections { +namespace parser { +namespace { + +using PayloadChunk = PayloadTransferFrame::PayloadChunk; +using ControlMessage = PayloadTransferFrame::ControlMessage; +using ClientIntroduction = BandwidthUpgradeNegotiationFrame::ClientIntroduction; +using WifiHotspotCredentials = UpgradePathInfo::WifiHotspotCredentials; +using WifiLanSocket = UpgradePathInfo::WifiLanSocket; +using WifiAwareCredentials = UpgradePathInfo::WifiAwareCredentials; +using WifiDirectCredentials = UpgradePathInfo::WifiDirectCredentials; +using BluetoothCredentials = UpgradePathInfo::BluetoothCredentials; +using WebRtcCredentials = UpgradePathInfo::WebRtcCredentials; + +constexpr absl::string_view kIpv4PatternString{ + "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"}; +constexpr absl::string_view kIpv6PatternString{ + "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"}; +constexpr absl::string_view kWifiDirectSsidPatternString{ + "^DIRECT-[a-zA-Z0-9]{2}.*$"}; +constexpr int kWifiDirectSsidMaxLength = 32; +constexpr int kWifiPasswordSsidMinLength = 8; +constexpr int kWifiPasswordSsidMaxLength = 64; + +inline bool WithinRange(int value, int min, int max) { + return value >= min && value < max; +} + +Exception EnsureValidConnectionRequestFrame( + const ConnectionRequestFrame& frame) { + if (!frame.has_endpoint_id()) return {Exception::kInvalidProtocolBuffer}; + if (!frame.has_endpoint_name()) return {Exception::kInvalidProtocolBuffer}; + + // For backwards compatibility reasons, no other fields should be + // null-checked for this frame. Parameter checking (eg. must be within this + // range) is fine. + return {Exception::kSuccess}; +} + +Exception EnsureValidConnectionResponseFrame( + const ConnectionResponseFrame& frame) { + // For backwards compatibility reasons, no other fields should be null-checked + // for this frame. Parameter checking (eg. must be within this range) is fine. + return {Exception::kSuccess}; +} + +Exception EnsureValidPayloadTransferDataFrame(const PayloadChunk& payload_chunk, + int totalSize) { + if (!payload_chunk.has_flags()) return {Exception::kInvalidProtocolBuffer}; + + // Special case. The body can be null iff the chunk is flagged as the last + // chunk. + bool is_last_chunk = (payload_chunk.flags() & + PayloadTransferFrame::PayloadChunk::LAST_CHUNK) != 0; + if (!payload_chunk.has_body() && !is_last_chunk) + return {Exception::kInvalidProtocolBuffer}; + if (!payload_chunk.has_offset() || payload_chunk.offset() < 0) + return {Exception::kInvalidProtocolBuffer}; + if (totalSize != InternalPayload::kIndeterminateSize && + totalSize < payload_chunk.offset()) { + return {Exception::kInvalidProtocolBuffer}; + } + + // For backwards compatibility reasons, no other fields should be null-checked + // for this frame. Parameter checking (eg. must be within this range) is fine. + return {Exception::kSuccess}; +} + +Exception EnsureValidPayloadTransferControlFrame( + const ControlMessage& control_message, int totalSize) { + if (!control_message.has_offset() || control_message.offset() < 0) + return {Exception::kInvalidProtocolBuffer}; + if (totalSize != InternalPayload::kIndeterminateSize && + totalSize < control_message.offset()) { + return {Exception::kInvalidProtocolBuffer}; + } + + // For backwards compatibility reasons, no other fields should be null-checked + // for this frame. Parameter checking (eg. must be within this range) is fine. + return {Exception::kSuccess}; +} + +Exception EnsureValidPayloadTransferFrame(const PayloadTransferFrame& frame) { + if (!frame.has_payload_header()) return {Exception::kInvalidProtocolBuffer}; + if (!frame.payload_header().has_total_size() || + (frame.payload_header().total_size() < 0 && + frame.payload_header().total_size() != + InternalPayload::kIndeterminateSize)) + return {Exception::kInvalidProtocolBuffer}; + if (!frame.has_packet_type()) return {Exception::kInvalidProtocolBuffer}; + + switch (frame.packet_type()) { + case PayloadTransferFrame::DATA: + if (frame.has_payload_chunk()) { + return EnsureValidPayloadTransferDataFrame( + frame.payload_chunk(), frame.payload_header().total_size()); + } + return {Exception::kInvalidProtocolBuffer}; + + case PayloadTransferFrame::CONTROL: + if (frame.has_control_message()) { + return EnsureValidPayloadTransferControlFrame( + frame.control_message(), frame.payload_header().total_size()); + } + return {Exception::kInvalidProtocolBuffer}; + + default: + break; + } + + // For backwards compatibility reasons, no other fields should be null-checked + // for this frame. Parameter checking (eg. must be within this range) is fine. + return {Exception::kSuccess}; +} + +Exception EnsureValidBandwidthUpgradeWifiHotspotPathAvailableFrame( + const WifiHotspotCredentials& wifi_hotspot_credentials) { + if (!wifi_hotspot_credentials.has_ssid()) + return {Exception::kInvalidProtocolBuffer}; + if (!wifi_hotspot_credentials.has_password() || + !WithinRange(wifi_hotspot_credentials.password().length(), + kWifiPasswordSsidMinLength, kWifiPasswordSsidMaxLength)) + return {Exception::kInvalidProtocolBuffer}; + if (!wifi_hotspot_credentials.has_gateway()) + return {Exception::kInvalidProtocolBuffer}; + const std::regex ip4_pattern(std::string(kIpv4PatternString).c_str()); + const std::regex ip6_pattern(std::string(kIpv6PatternString).c_str()); + if (!(std::regex_match(wifi_hotspot_credentials.gateway(), ip4_pattern) || + std::regex_match(wifi_hotspot_credentials.gateway(), ip6_pattern))) + return {Exception::kInvalidProtocolBuffer}; + + // For backwards compatibility reasons, no other fields should be null-checked + // for this frame. Parameter checking (eg. must be within this range) is fine. + return {Exception::kSuccess}; +} + +Exception EnsureValidBandwidthUpgradeWifiLanPathAvailableFrame( + const WifiLanSocket& wifi_lan_socket) { + if (!wifi_lan_socket.has_ip_address()) + return {Exception::kInvalidProtocolBuffer}; + if (!wifi_lan_socket.has_wifi_port() || wifi_lan_socket.wifi_port() < 0) + return {Exception::kInvalidProtocolBuffer}; + + // For backwards compatibility reasons, no other fields should be null-checked + // for this frame. Parameter checking (eg. must be within this range) is fine. + return {Exception::kSuccess}; +} + +Exception EnsureValidBandwidthUpgradeWifiAwarePathAvailableFrame( + const WifiAwareCredentials& wifi_aware_credentials) { + if (!wifi_aware_credentials.has_service_id()) + return {Exception::kInvalidProtocolBuffer}; + if (!wifi_aware_credentials.has_service_info()) + return {Exception::kInvalidProtocolBuffer}; + + // For backwards compatibility reasons, no other fields should be null-checked + // for this frame. Parameter checking (eg. must be within this range) is fine. + return {Exception::kSuccess}; +} + +Exception EnsureValidBandwidthUpgradeWifiDirectPathAvailableFrame( + const WifiDirectCredentials& wifi_direct_credentials) { + const std::regex ssid_pattern( + std::string(kWifiDirectSsidPatternString).c_str()); + if (!wifi_direct_credentials.has_ssid() || + !(wifi_direct_credentials.ssid().length() < kWifiDirectSsidMaxLength && + std::regex_match(wifi_direct_credentials.ssid(), ssid_pattern))) + return {Exception::kInvalidProtocolBuffer}; + + if (!wifi_direct_credentials.has_password() || + !WithinRange(wifi_direct_credentials.password().length(), + kWifiPasswordSsidMinLength, kWifiPasswordSsidMaxLength)) + return {Exception::kInvalidProtocolBuffer}; + + if (!wifi_direct_credentials.has_frequency() || + wifi_direct_credentials.frequency() < -1) + return {Exception::kInvalidProtocolBuffer}; + + // For backwards compatibility reasons, no other fields should be null-checked + // for this frame. Parameter checking (eg. must be within this range) is fine. + return {Exception::kSuccess}; +} + +Exception EnsureValidBandwidthUpgradeBluetoothPathAvailableFrame( + const BluetoothCredentials& bluetooth_credentials) { + if (!bluetooth_credentials.has_service_name()) + return {Exception::kInvalidProtocolBuffer}; + if (!bluetooth_credentials.has_mac_address()) + return {Exception::kInvalidProtocolBuffer}; + + // For backwards compatibility reasons, no other fields should be null-checked + // for this frame. Parameter checking (eg. must be within this range) is fine. + return {Exception::kSuccess}; +} + +Exception EnsureValidBandwidthUpgradeWebRtcPathAvailableFrame( + const WebRtcCredentials& web_rtc_credentials) { + if (!web_rtc_credentials.has_peer_id()) + return {Exception::kInvalidProtocolBuffer}; + + // For backwards compatibility reasons, no other fields should be null-checked + // for this frame. Parameter checking (eg. must be within this range) is fine. + return {Exception::kSuccess}; +} + +Exception EnsureValidBandwidthUpgradePathAvailableFrame( + const UpgradePathInfo& upgrade_path_info) { + if (!upgrade_path_info.has_medium()) + return {Exception::kInvalidProtocolBuffer}; + switch (upgrade_path_info.medium()) { + case Medium::WIFI_HOTSPOT: + if (upgrade_path_info.has_wifi_hotspot_credentials()) { + return EnsureValidBandwidthUpgradeWifiHotspotPathAvailableFrame( + upgrade_path_info.wifi_hotspot_credentials()); + } + return {Exception::kInvalidProtocolBuffer}; + + case Medium::WIFI_LAN: + if (upgrade_path_info.has_wifi_lan_socket()) { + return EnsureValidBandwidthUpgradeWifiLanPathAvailableFrame( + upgrade_path_info.wifi_lan_socket()); + } + return {Exception::kInvalidProtocolBuffer}; + + case Medium::WIFI_AWARE: + if (upgrade_path_info.has_wifi_aware_credentials()) { + return EnsureValidBandwidthUpgradeWifiAwarePathAvailableFrame( + upgrade_path_info.wifi_aware_credentials()); + } + return {Exception::kInvalidProtocolBuffer}; + + case Medium::WIFI_DIRECT: + if (upgrade_path_info.has_wifi_direct_credentials()) { + return EnsureValidBandwidthUpgradeWifiDirectPathAvailableFrame( + upgrade_path_info.wifi_direct_credentials()); + } + return {Exception::kInvalidProtocolBuffer}; + + case Medium::BLUETOOTH: + if (upgrade_path_info.has_bluetooth_credentials()) { + return EnsureValidBandwidthUpgradeBluetoothPathAvailableFrame( + upgrade_path_info.bluetooth_credentials()); + } + return {Exception::kInvalidProtocolBuffer}; + + case Medium::WEB_RTC: + if (upgrade_path_info.has_web_rtc_credentials()) { + return EnsureValidBandwidthUpgradeWebRtcPathAvailableFrame( + upgrade_path_info.web_rtc_credentials()); + } + return {Exception::kInvalidProtocolBuffer}; + + default: + break; + } + + // For backwards compatibility reasons, no other fields should be null-checked + // for this frame. Parameter checking (eg. must be within this range) is fine. + return {Exception::kSuccess}; +} + +Exception EnsureValidBandwidthUpgradeClientIntroductionFrame( + const ClientIntroduction& client_introduction) { + if (!client_introduction.has_endpoint_id()) + return {Exception::kInvalidProtocolBuffer}; + + // For backwards compatibility reasons, no other fields should be null-checked + // for this frame. Parameter checking (eg. must be within this range) is fine. + return {Exception::kSuccess}; +} + +Exception EnsureValidBandwidthUpgradeNegotiationFrame( + const BandwidthUpgradeNegotiationFrame& frame) { + if (!frame.has_event_type()) return {Exception::kInvalidProtocolBuffer}; + + switch (frame.event_type()) { + case BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE: + if (frame.has_upgrade_path_info()) { + return EnsureValidBandwidthUpgradePathAvailableFrame( + frame.upgrade_path_info()); + } + return {Exception::kInvalidProtocolBuffer}; + + case BandwidthUpgradeNegotiationFrame::CLIENT_INTRODUCTION: + if (frame.has_client_introduction()) { + return EnsureValidBandwidthUpgradeClientIntroductionFrame( + frame.client_introduction()); + } + return {Exception::kInvalidProtocolBuffer}; + + default: + break; + } + + // For backwards compatibility reasons, no other fields should be null-checked + // for this frame. Parameter checking (eg. must be within this range) is fine. + return {Exception::kSuccess}; +} + +} // namespace + +Exception EnsureValidOfflineFrame(const OfflineFrame& offline_frame) { + V1Frame::FrameType frame_type = GetFrameType(offline_frame); + switch (frame_type) { + case V1Frame::CONNECTION_REQUEST: + if (offline_frame.has_v1() && + offline_frame.v1().has_connection_request()) { + return EnsureValidConnectionRequestFrame( + offline_frame.v1().connection_request()); + } + return {Exception::kInvalidProtocolBuffer}; + + case V1Frame::CONNECTION_RESPONSE: + if (offline_frame.has_v1() && + offline_frame.v1().has_connection_response()) { + return EnsureValidConnectionResponseFrame( + offline_frame.v1().connection_response()); + } + return {Exception::kInvalidProtocolBuffer}; + + case V1Frame::PAYLOAD_TRANSFER: + if (offline_frame.has_v1() && offline_frame.v1().has_payload_transfer()) { + return EnsureValidPayloadTransferFrame( + offline_frame.v1().payload_transfer()); + } + return {Exception::kInvalidProtocolBuffer}; + + case V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION: + if (offline_frame.has_v1() && + offline_frame.v1().has_bandwidth_upgrade_negotiation()) { + return EnsureValidBandwidthUpgradeNegotiationFrame( + offline_frame.v1().bandwidth_upgrade_negotiation()); + } + return {Exception::kInvalidProtocolBuffer}; + + case V1Frame::KEEP_ALIVE: + case V1Frame::UNKNOWN_FRAME_TYPE: + default: + // Nothing to check for these frames. + break; + } + return {Exception::kSuccess}; +} + +} // namespace parser +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/offline_frames_validator.h b/cpp/core/internal/offline_frames_validator.h new file mode 100644 index 00000000..a673c913 --- /dev/null +++ b/cpp/core/internal/offline_frames_validator.h @@ -0,0 +1,33 @@ +// 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. + +#ifndef CORE_INTERNAL_OFFLINE_FRAMES_VALIDATOR_H_ +#define CORE_INTERNAL_OFFLINE_FRAMES_VALIDATOR_H_ + +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform/base/exception.h" + +namespace location { +namespace nearby { +namespace connections { +namespace parser { + +Exception EnsureValidOfflineFrame(const OfflineFrame& offline_frame); + +} // namespace parser +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_OFFLINE_FRAMES_VALIDATOR_H_ diff --git a/cpp/core/internal/offline_frames_validator_test.cc b/cpp/core/internal/offline_frames_validator_test.cc new file mode 100644 index 00000000..09fecac4 --- /dev/null +++ b/cpp/core/internal/offline_frames_validator_test.cc @@ -0,0 +1,556 @@ +// 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. + +#include "core/internal/offline_frames_validator.h" + +#include "core/internal/offline_frames.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform/base/byte_array.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace parser { +namespace { + +constexpr absl::string_view kEndpointId{"ABC"}; +constexpr absl::string_view kEndpointName{"XYZ"}; +constexpr int kNonce = 1234; +constexpr bool kSupports5ghz = true; +constexpr absl::string_view kBssid{"FF:FF:FF:FF:FF:FF"}; +constexpr int kStatusAccepted = 0; +constexpr absl::string_view kSsid = "ssid"; +constexpr absl::string_view kPassword = "password"; +constexpr absl::string_view kWifiHotspotGateway = "0.0.0.0"; +constexpr absl::string_view kWifiDirectSsid = "DIRECT-A0-0123456789AB"; +constexpr absl::string_view kWifiDirectPassword = "WIFIDIRECT123456"; +constexpr int kWifiDirectFrequency = 1000; +constexpr int kPort = 1000; +constexpr bool kSupportsDisablingEncryption = true; +constexpr std::array kMediums = { + Medium::MDNS, Medium::BLUETOOTH, Medium::WIFI_HOTSPOT, + Medium::BLE, Medium::WIFI_LAN, Medium::WIFI_AWARE, + Medium::NFC, Medium::WIFI_DIRECT, Medium::WEB_RTC, +}; + +TEST(OfflineFramesValidatorTest, ValidatesAsOkWithValidConnectionRequestFrame) { + OfflineFrame offline_frame; + + ByteArray bytes = ForConnectionRequest( + std::string(kEndpointId), ByteArray{std::string(kEndpointName)}, kNonce, + kSupports5ghz, std::string(kBssid), + std::vector(kMediums.begin(), kMediums.end())); + offline_frame.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_TRUE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithNullConnectionRequestFrame) { + OfflineFrame offline_frame; + + ByteArray bytes = ForConnectionRequest( + std::string(kEndpointId), ByteArray{std::string(kEndpointName)}, kNonce, + kSupports5ghz, std::string(kBssid), + std::vector(kMediums.begin(), kMediums.end())); + offline_frame.ParseFromString(std::string(bytes)); + auto* v1_frame = offline_frame.mutable_v1(); + + v1_frame->clear_connection_request(); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithNullEndpointIdInConnectionRequestFrame) { + OfflineFrame offline_frame; + + std::string empty_enpoint_id; + ByteArray bytes = ForConnectionRequest( + empty_enpoint_id, ByteArray{std::string(kEndpointName)}, kNonce, + kSupports5ghz, std::string(kBssid), + std::vector(kMediums.begin(), kMediums.end())); + offline_frame.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithNullEndpointInfoInConnectionRequestFrame) { + OfflineFrame offline_frame; + + ByteArray empty_endpoint_info; + ByteArray bytes = ForConnectionRequest( + std::string(kEndpointId), empty_endpoint_info, kNonce, kSupports5ghz, + std::string(kBssid), std::vector(kMediums.begin(), kMediums.end())); + offline_frame.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsOkWithNullBssidInConnectionRequestFrame) { + OfflineFrame offline_frame; + + std::string empty_bssid; + ByteArray bytes = ForConnectionRequest( + std::string(kEndpointId), ByteArray{std::string(kEndpointName)}, kNonce, + kSupports5ghz, empty_bssid, + std::vector(kMediums.begin(), kMediums.end())); + offline_frame.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_TRUE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsOkWithNullMediumsInConnectionRequestFrame) { + OfflineFrame offline_frame; + + std::vector empty_mediums; + ByteArray bytes = ForConnectionRequest( + std::string(kEndpointId), ByteArray{std::string(kEndpointName)}, kNonce, + kSupports5ghz, std::string(kBssid), empty_mediums); + offline_frame.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_TRUE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsOkWithValidConnectionResponseFrame) { + OfflineFrame offline_frame; + + ByteArray bytes = ForConnectionResponse(kStatusAccepted); + offline_frame.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_TRUE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithNullConnectionResponseFrame) { + OfflineFrame offline_frame; + + ByteArray bytes = ForConnectionResponse(kStatusAccepted); + offline_frame.ParseFromString(std::string(bytes)); + auto* v1_frame = offline_frame.mutable_v1(); + + v1_frame->clear_connection_response(); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithUnexpectedStatusInConnectionResponseFrame) { + OfflineFrame offline_frame; + + ByteArray bytes = ForConnectionResponse(-1); + offline_frame.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + // To maintain forward compatibility, we allow unexpected status codes. + ASSERT_TRUE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, ValidatesAsOkWithValidPayloadTransferFrame) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::PayloadChunk chunk; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + chunk.set_body("payload data"); + chunk.set_offset(150); + chunk.set_flags(1); + + OfflineFrame offline_frame; + + ByteArray bytes = ForDataPayloadTransfer(header, chunk); + offline_frame.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_TRUE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, ValidatesAsFailWithNullPayloadTransferFrame) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::PayloadChunk chunk; + chunk.set_body("payload data"); + chunk.set_offset(150); + chunk.set_flags(1); + + OfflineFrame offline_frame; + + ByteArray bytes = ForDataPayloadTransfer(header, chunk); + offline_frame.ParseFromString(std::string(bytes)); + auto* v1_frame = offline_frame.mutable_v1(); + + v1_frame->clear_payload_transfer(); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithNullPayloadHeaderInPayloadTransferFrame) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::PayloadChunk chunk; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + chunk.set_body("payload data"); + chunk.set_offset(150); + chunk.set_flags(1); + + OfflineFrame offline_frame; + + ByteArray bytes = ForDataPayloadTransfer(header, chunk); + offline_frame.ParseFromString(std::string(bytes)); + auto* v1_frame = offline_frame.mutable_v1(); + auto* payload_transfer = v1_frame->mutable_payload_transfer(); + + payload_transfer->clear_payload_header(); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithInvalidSizeInPayloadHeader) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::PayloadChunk chunk; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(-5); + chunk.set_body("payload data"); + chunk.set_offset(150); + chunk.set_flags(1); + + OfflineFrame offline_frame; + + ByteArray bytes = ForDataPayloadTransfer(header, chunk); + offline_frame.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithNullPayloadChunkInPayloadTransferFrame) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::PayloadChunk chunk; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + chunk.set_body("payload data"); + chunk.set_offset(150); + chunk.set_flags(1); + + OfflineFrame offline_frame; + + ByteArray bytes = ForDataPayloadTransfer(header, chunk); + offline_frame.ParseFromString(std::string(bytes)); + auto* v1_frame = offline_frame.mutable_v1(); + auto* payload_transfer = v1_frame->mutable_payload_transfer(); + + payload_transfer->clear_payload_chunk(); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithInvalidOffsetInPayloadChunk) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::PayloadChunk chunk; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + chunk.set_body("payload data"); + chunk.set_offset(-1); + chunk.set_flags(1); + + OfflineFrame offline_frame; + + ByteArray bytes = ForDataPayloadTransfer(header, chunk); + offline_frame.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithInvalidLargeOffsetInPayloadChunk) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::PayloadChunk chunk; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + chunk.set_body("payload data"); + chunk.set_offset(4999); + chunk.set_flags(1); + + OfflineFrame offline_frame; + + ByteArray bytes = ForDataPayloadTransfer(header, chunk); + offline_frame.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithInvalidFlagsInPayloadChunk) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::PayloadChunk chunk; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + chunk.set_body("payload data"); + chunk.set_offset(150); + chunk.set_flags(1); + + OfflineFrame offline_frame; + + ByteArray bytes = ForDataPayloadTransfer(header, chunk); + offline_frame.ParseFromString(std::string(bytes)); + auto* v1_frame = offline_frame.mutable_v1(); + auto* payload_transfer = v1_frame->mutable_payload_transfer(); + auto* payload_chunk = payload_transfer->mutable_payload_chunk(); + + payload_chunk->clear_flags(); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithNullControlMessageInPayloadTransferFrame) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::ControlMessage control; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + control.set_event(PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED); + control.set_offset(150); + + OfflineFrame offline_frame; + + ByteArray bytes = ForControlPayloadTransfer(header, control); + offline_frame.ParseFromString(std::string(bytes)); + + auto* v1_frame = offline_frame.mutable_v1(); + auto* payload_transfer = v1_frame->mutable_payload_transfer(); + + payload_transfer->clear_control_message(); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithInvalidNegativeOffsetInControlMessage) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::ControlMessage control; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + control.set_event(PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED); + control.set_offset(-1); + + OfflineFrame offline_frame; + + ByteArray bytes = ForControlPayloadTransfer(header, control); + offline_frame.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithInvalidLargeOffsetInControlMessage) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::ControlMessage control; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + control.set_event(PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED); + control.set_offset(4999); + + OfflineFrame offline_frame; + + ByteArray bytes = ForControlPayloadTransfer(header, control); + offline_frame.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsOkWithValidBandwidthUpgradeNegotiationFrame) { + OfflineFrame offline_frame; + + ByteArray bytes = ForBwuWifiHotspotPathAvailable( + std::string(kSsid), std::string(kPassword), kPort, + std::string(kWifiHotspotGateway), kSupportsDisablingEncryption); + offline_frame.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_TRUE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithNullBandwidthUpgradeNegotiationFrame) { + OfflineFrame offline_frame; + + ByteArray bytes = ForBwuWifiHotspotPathAvailable( + std::string(kSsid), std::string(kPassword), kPort, + std::string(kWifiHotspotGateway), kSupportsDisablingEncryption); + offline_frame.ParseFromString(std::string(bytes)); + auto* v1_frame = offline_frame.mutable_v1(); + + v1_frame->clear_bandwidth_upgrade_negotiation(); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, ValidatesAsOkBandwidthUpgradeWifiDirect) { + OfflineFrame offline_frame; + + ByteArray bytes = ForBwuWifiDirectPathAvailable( + std::string(kWifiDirectSsid), std::string(kWifiDirectPassword), kPort, + kWifiDirectFrequency, kSupportsDisablingEncryption); + offline_frame.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + ASSERT_TRUE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesValidFrequencyInBandwidthUpgradeWifiDirect) { + OfflineFrame offline_frame_1; + OfflineFrame offline_frame_2; + + // Anything less than -1 is invalid + ByteArray bytes = ForBwuWifiDirectPathAvailable( + std::string(kWifiDirectSsid), std::string(kWifiDirectPassword), kPort, -2, + kSupportsDisablingEncryption); + offline_frame_1.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame_1); + + ASSERT_FALSE(ret_value.Ok()); + + // But -1 itself is not invalid + bytes = ForBwuWifiDirectPathAvailable(std::string(kWifiDirectSsid), + std::string(kWifiDirectPassword), kPort, + -1, kSupportsDisablingEncryption); + offline_frame_2.ParseFromString(std::string(bytes)); + + ret_value = EnsureValidOfflineFrame(offline_frame_2); + + ASSERT_TRUE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithInvalidSsidInBandwidthUpgradeWifiDirect) { + OfflineFrame offline_frame_1; + OfflineFrame offline_frame_2; + + std::string wifi_direct_ssid{"DIRECT-A*-0123456789AB"}; + ByteArray bytes = ForBwuWifiDirectPathAvailable( + wifi_direct_ssid, std::string(kWifiDirectPassword), kPort, + kWifiDirectFrequency, kSupportsDisablingEncryption); + offline_frame_1.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame_1); + + ASSERT_FALSE(ret_value.Ok()); + + std::string wifi_direct_ssid_wrong_length = + std::string{kWifiDirectSsid} + "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789"; + bytes = ForBwuWifiDirectPathAvailable( + wifi_direct_ssid_wrong_length, std::string(kWifiDirectPassword), kPort, + kWifiDirectFrequency, kSupportsDisablingEncryption); + offline_frame_2.ParseFromString(std::string(bytes)); + + ret_value = EnsureValidOfflineFrame(offline_frame_2); + + ASSERT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailWithInvalidPasswordInBandwidthUpgradeWifiDirect) { + OfflineFrame offline_frame_1; + OfflineFrame offline_frame_2; + + std::string short_wifi_direct_password{"Test"}; + ByteArray bytes = ForBwuWifiDirectPathAvailable( + std::string(kWifiDirectSsid), short_wifi_direct_password, kPort, + kWifiDirectFrequency, kSupportsDisablingEncryption); + offline_frame_1.ParseFromString(std::string(bytes)); + + auto ret_value = EnsureValidOfflineFrame(offline_frame_1); + + ASSERT_FALSE(ret_value.Ok()); + + std::string long_wifi_direct_password = + std::string{kWifiDirectSsid} + + "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789"; + bytes = ForBwuWifiDirectPathAvailable( + std::string(kWifiDirectSsid), long_wifi_direct_password, kPort, + kWifiDirectFrequency, kSupportsDisablingEncryption); + offline_frame_2.ParseFromString(std::string(bytes)); + + ret_value = EnsureValidOfflineFrame(offline_frame_2); + + ASSERT_FALSE(ret_value.Ok()); +} + +} // namespace +} // namespace parser +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/p2p_cluster_pcp_handler.cc b/cpp/core/internal/p2p_cluster_pcp_handler.cc index c1d90b44..24983d9a 100644 --- a/cpp/core/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core/internal/p2p_cluster_pcp_handler.cc @@ -156,6 +156,10 @@ BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartAdvertisingImpl( }; } +// StopAcceptingConnections invokes for webrtc is suppressed for now to +// unblock CrOS dogfood integration. Disconnect will invoke ShutdownSignaling +// to release resources. +// TODO (hais): add corresponding logic back (b/172518506). Status P2pClusterPcpHandler::StopAdvertisingImpl(ClientProxy* client) { bluetooth_medium_.TurnOffDiscoverability(); bluetooth_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); @@ -163,8 +167,6 @@ Status P2pClusterPcpHandler::StopAdvertisingImpl(ClientProxy* client) { ble_medium_.StopAdvertising(client->GetAdvertisingServiceId()); ble_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); - webrtc_medium_.StopAcceptingConnections(); - wifi_lan_medium_.StopAdvertising(client->GetAdvertisingServiceId()); wifi_lan_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); @@ -232,17 +234,13 @@ void P2pClusterPcpHandler::BluetoothDeviceDiscoveredHandler( << "Invoking BasePcpHandler::OnEndpointFound() for BT service=" << service_id << "; id=" << device_name.GetEndpointId() << "; name=" << absl::BytesToHexString(device_name.GetEndpointInfo().data()); - OnEndpointFound(client, - std::make_shared(BluetoothEndpoint{ - { - device_name.GetEndpointId(), - device_name.GetEndpointInfo(), - service_id, - proto::connections::Medium::BLUETOOTH, - device_name.GetWebRtcState() - }, - device, - })); + OnEndpointFound( + client, std::make_shared(BluetoothEndpoint{ + {device_name.GetEndpointId(), device_name.GetEndpointInfo(), + service_id, proto::connections::Medium::BLUETOOTH, + device_name.GetWebRtcState()}, + device, + })); }); } @@ -274,13 +272,11 @@ void P2pClusterPcpHandler::BluetoothDeviceLostHandler( "BT discovery handler (LOST) [client=%p, service=%s]: report " "to client", client, service_id.c_str()); - OnEndpointLost(client, DiscoveredEndpoint{ - device_name.GetEndpointId(), - device_name.GetEndpointInfo(), - service_id, - proto::connections::Medium::BLUETOOTH, - WebRtcState::kUndefined - }); + OnEndpointLost(client, + DiscoveredEndpoint{device_name.GetEndpointId(), + device_name.GetEndpointInfo(), service_id, + proto::connections::Medium::BLUETOOTH, + WebRtcState::kUndefined}); }); } @@ -367,13 +363,10 @@ void P2pClusterPcpHandler::BlePeripheralDiscoveredHandler( << service_id << "; id=" << advertisement.GetEndpointId() << "; name=" << absl::BytesToHexString(advertisement.GetEndpointInfo().data()); OnEndpointFound(client, std::make_shared(BleEndpoint{ - { - advertisement.GetEndpointId(), - advertisement.GetEndpointInfo(), - service_id, - proto::connections::Medium::BLE, - advertisement.GetWebRtcState() - }, + {advertisement.GetEndpointId(), + advertisement.GetEndpointInfo(), service_id, + proto::connections::Medium::BLE, + advertisement.GetWebRtcState()}, peripheral, })); @@ -575,10 +568,8 @@ BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartDiscoveryImpl( // If this is an out-of-band connection, do not start actual discovery, since // this connection is intended to be completed via InjectEndpointImpl(). if (options.is_out_of_band_connection) { - return { - .status = {Status::kSuccess}, - .mediums = options.allowed.GetMediums(true) - }; + return {.status = {Status::kSuccess}, + .mediums = options.allowed.GetMediums(true)}; } std::vector mediums_started_successfully; @@ -657,8 +648,7 @@ Status P2pClusterPcpHandler::StopDiscoveryImpl(ClientProxy* client) { } Status P2pClusterPcpHandler::InjectEndpointImpl( - ClientProxy* client, - const std::string& service_id, + ClientProxy* client, const std::string& service_id, const OutOfBandConnectionMetadata& metadata) { NEARBY_LOG(INFO, "InjectEndpoint"); // Bluetooth is the only supported out-of-band connection medium. @@ -920,8 +910,7 @@ proto::connections::Medium P2pClusterPcpHandler::StartBleAdvertising( service_id, {.accepted_cb = [this, client, local_endpoint_info]( BluetoothSocket socket) { if (!socket.IsValid()) { - NEARBY_LOG(INFO, - "Invalid socket in accept callback: name=%s", + NEARBY_LOG(INFO, "Invalid socket in accept callback: name=%s", std::string(local_endpoint_info).c_str()); return; } @@ -1174,8 +1163,9 @@ BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::WifiLanConnectImpl( proto::connections::Medium P2pClusterPcpHandler::StartListeningForWebRtcConnections( - ClientProxy* client, const string& service_id, - const string& local_endpoint_id, const ByteArray& local_endpoint_info) { + ClientProxy* client, const std::string& service_id, + const std::string& local_endpoint_id, + const ByteArray& local_endpoint_info) { if (!webrtc_medium_.IsAvailable()) { return proto::connections::UNKNOWN_MEDIUM; } @@ -1183,10 +1173,9 @@ P2pClusterPcpHandler::StartListeningForWebRtcConnections( if (!webrtc_medium_.IsAcceptingConnections()) { mediums::PeerId self_id = CreatePeerIdFromAdvertisement( service_id, local_endpoint_id, local_endpoint_info); - LocationHint location_hint; - location_hint.set_format(LocationStandard::UNKNOWN); + std::string empty_country_code; if (!webrtc_medium_.StartAcceptingConnections( - self_id, location_hint, + self_id, Utils::BuildLocationHint(empty_country_code), {[this, client, local_endpoint_info](mediums::WebRtcSocketWrapper socket) { if (!socket.IsValid()) { @@ -1197,7 +1186,7 @@ P2pClusterPcpHandler::StartListeningForWebRtcConnections( RunOnPcpHandlerThread( [this, client, socket = std::move(socket)]() { - string remote_device_name = "WebRtcSocket"; + std::string remote_device_name = "WebRtcSocket"; auto channel = absl::make_unique( remote_device_name, socket); ByteArray remote_device_info{remote_device_name}; @@ -1216,13 +1205,11 @@ P2pClusterPcpHandler::StartListeningForWebRtcConnections( BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::WebRtcConnectImpl( ClientProxy* client, WebRtcEndpoint* webrtc_endpoint) { - LocationHint location_hint; - location_hint.set_format(LocationStandard::UNKNOWN); -mediums::WebRtcSocketWrapper socket_wrapper = - webrtc_medium_.Connect(webrtc_endpoint->peer_id, location_hint); - -if (!socket_wrapper.IsValid()) { - return BasePcpHandler::ConnectImplResult{.status = {Status::kError}}; + std::string empty_country_code; + mediums::WebRtcSocketWrapper socket_wrapper = webrtc_medium_.Connect( + webrtc_endpoint->peer_id, Utils::BuildLocationHint(empty_country_code)); + if (!socket_wrapper.IsValid()) { + return BasePcpHandler::ConnectImplResult{.status = {Status::kError}}; } auto channel = absl::make_unique( diff --git a/cpp/core/internal/webrtc_bwu_handler.cc b/cpp/core/internal/webrtc_bwu_handler.cc index c1de0fef..4d351530 100644 --- a/cpp/core/internal/webrtc_bwu_handler.cc +++ b/cpp/core/internal/webrtc_bwu_handler.cc @@ -23,9 +23,6 @@ #include "core/internal/webrtc_endpoint_channel.h" #include "absl/functional/bind_front.h" -// Manages the Bluetooth-specific methods needed to upgrade an {@link -// EndpointChannel}. - namespace location { namespace nearby { namespace connections { @@ -62,9 +59,9 @@ void WebrtcBwuHandler::OnIncomingWebrtcConnection( bwu_notifications_.incoming_connection_cb(client, std::move(connection)); } -// Called by BWU initiator. BT Medium is set up, and BWU request is prepared, -// with necessary info (service_id, MAC address) for remote party to perform -// discovery. +// Called by BWU initiator. Set up WebRTC upgraded medium for this endpoint, +// and returns a upgrade path info (PeerId, LocationHint) for remote party to +// perform discovery. ByteArray WebrtcBwuHandler::InitializeUpgradedMediumForEndpoint( ClientProxy* client, const std::string& service_id, const std::string& endpoint_id) { diff --git a/cpp/core/internal/wifi_lan_bwu_handler.cc b/cpp/core/internal/wifi_lan_bwu_handler.cc index dd458d0f..d0585d87 100644 --- a/cpp/core/internal/wifi_lan_bwu_handler.cc +++ b/cpp/core/internal/wifi_lan_bwu_handler.cc @@ -61,20 +61,28 @@ ByteArray WifiLanBwuHandler::InitializeUpgradedMediumForEndpoint( endpoint_id.c_str()); return {}; } - NEARBY_LOG(INFO, - "WifiLanBwuHandler successfully started listening for incoming " - "WifiLan connections while upgrading endpoint %s", - endpoint_id.c_str()); + NEARBY_LOGS(INFO) + << "WifiLanBwuHandler successfully started listening for incoming " + "WifiLan connections while upgrading endpoint " + << endpoint_id; } // cache service ID to revert active_service_ids_.emplace(upgrade_service_id); - // TODO(b/169303360): Implements wifiLanCredntials for wif_lan_medium to - // get ip_address and port. - std::string ip_addresss; - std::int32_t port = 0; - return parser::ForBwuWifiLanPathAvailable(ip_addresss, port); + auto service_address = wifi_lan_medium_.GetServiceAddress(upgrade_service_id); + auto ip_address = service_address.first; + auto port = service_address.second; + if (ip_address.empty()) { + NEARBY_LOGS(INFO) + << "WifiLanBwuHandler couldn't initiate the wifi_lan upgrade for " + "endpoint " + << endpoint_id + << " because the wifi_lan ip address were unable to be obtained."; + return {}; + } + + return parser::ForBwuWifiLanPathAvailable(ip_address, port); } void WifiLanBwuHandler::Revert() { @@ -92,8 +100,28 @@ std::unique_ptr WifiLanBwuHandler::CreateUpgradedEndpointChannel( ClientProxy* client, const std::string& service_id, const std::string& endpoint_id, const UpgradePathInfo& upgrade_path_info) { - // TODO(b/169303360): Implements connect WifiLan over ip address and port. - WifiLanSocket socket; + if (!upgrade_path_info.has_wifi_lan_socket()) { + return nullptr; + } + const UpgradePathInfo::WifiLanSocket& wifi_lan_socket = + upgrade_path_info.wifi_lan_socket(); + if (!wifi_lan_socket.has_ip_address() || !wifi_lan_socket.has_wifi_port()) { + return nullptr; + } + + const std::string& ip_address = wifi_lan_socket.ip_address(); + int32 port = wifi_lan_socket.wifi_port(); + + WifiLanService wifi_lan_service = + wifi_lan_medium_.GetRemoteWifiLanService(ip_address, port); + if (!wifi_lan_service.IsValid()) { + return nullptr; + } + WifiLanSocket socket = + wifi_lan_medium_.Connect(wifi_lan_service, service_id); + if (!socket.IsValid()) { + return nullptr; + } // Create a new WifiLanEndpointChannel. auto channel = std::make_unique(service_id, socket); diff --git a/cpp/platform/api/wifi_lan.h b/cpp/platform/api/wifi_lan.h index a2a92b5b..68cc9131 100644 --- a/cpp/platform/api/wifi_lan.h +++ b/cpp/platform/api/wifi_lan.h @@ -124,6 +124,9 @@ class WifiLanMedium { virtual WifiLanService* FindRemoteService(const std::string& ip_address, int port) = 0; + + virtual std::pair GetServiceAddress( + const std::string& service_id) = 0; }; } // namespace api diff --git a/cpp/platform/impl/g3/wifi_lan.cc b/cpp/platform/impl/g3/wifi_lan.cc index 80f9fc8f..4e8b02d4 100644 --- a/cpp/platform/impl/g3/wifi_lan.cc +++ b/cpp/platform/impl/g3/wifi_lan.cc @@ -374,6 +374,13 @@ api::WifiLanService* WifiLanMedium::FindRemoteService( return env.FindWifiLanService(ip_address, port); } +std::pair WifiLanMedium::GetServiceAddress( + const std::string& service_id) { + NEARBY_LOGS(INFO) << "G3 WifiLan GetServiceAddress: service_id=" + << service_id; + return service_.GetServiceAddress(); +} + } // namespace g3 } // namespace nearby } // namespace location diff --git a/cpp/platform/impl/g3/wifi_lan.h b/cpp/platform/impl/g3/wifi_lan.h index 04fa21ee..e4167897 100644 --- a/cpp/platform/impl/g3/wifi_lan.h +++ b/cpp/platform/impl/g3/wifi_lan.h @@ -232,6 +232,9 @@ class WifiLanMedium : public api::WifiLanMedium { api::WifiLanService* FindRemoteService(const std::string& ip_address, int port) override; + std::pair GetServiceAddress( + const std::string& service_id) override ABSL_LOCKS_EXCLUDED(mutex_); + private: static constexpr int kMaxConcurrentAcceptLoops = 5; diff --git a/cpp/platform/public/wifi_lan.cc b/cpp/platform/public/wifi_lan.cc index 13049c3e..6d6e7965 100644 --- a/cpp/platform/public/wifi_lan.cc +++ b/cpp/platform/public/wifi_lan.cc @@ -145,5 +145,10 @@ WifiLanService WifiLanMedium::FindRemoteService(const std::string& ip_address, return WifiLanService(impl_->FindRemoteService(ip_address, port)); } +std::pair WifiLanMedium::GetServiceAddress( + const std::string& service_id) { + return impl_->GetServiceAddress(service_id); +} + } // namespace nearby } // namespace location diff --git a/cpp/platform/public/wifi_lan.h b/cpp/platform/public/wifi_lan.h index 1de32d1e..fa9c2fc2 100644 --- a/cpp/platform/public/wifi_lan.h +++ b/cpp/platform/public/wifi_lan.h @@ -106,6 +106,7 @@ class WifiLanSocket final { class WifiLanMedium final { public: using Platform = api::ImplementationPlatform; + struct DiscoveredServiceCallback { std::function @@ -116,6 +117,7 @@ class WifiLanMedium final { service_lost_cb = DefaultCallback(); }; + struct ServiceDiscoveryInfo { WifiLanService service; }; @@ -124,6 +126,7 @@ class WifiLanMedium final { std::function accepted_cb = DefaultCallback(); }; + struct AcceptedConnectionInfo { WifiLanSocket socket; }; @@ -161,6 +164,8 @@ class WifiLanMedium final { WifiLanService FindRemoteService(const std::string& ip_address, int port); + std::pair GetServiceAddress(const std::string& service_id); + private: Mutex mutex_; std::unique_ptr impl_; diff --git a/proto/connections/offline_wire_formats.proto b/proto/connections/offline_wire_formats.proto index 49be9e8c..76154e6c 100644 --- a/proto/connections/offline_wire_formats.proto +++ b/proto/connections/offline_wire_formats.proto @@ -127,6 +127,7 @@ message PayloadTransferFrame { optional int64 id = 1; optional PayloadType type = 2; optional int64 total_size = 3; + optional bool is_sensitive = 4; } // Accompanies DATA packets.