blob: 00260f55c1f8945f06a60c4a4e10b13b3cc16dd2 [file] [log] [blame]
// 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 "connections/implementation/offline_frames_validator.h"
#include <regex> //NOLINT
#include "connections/implementation/proto/offline_wire_formats.pb.h"
#include "connections/implementation/internal_payload.h"
#include "connections/implementation/offline_frames.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;
using Medium = location::nearby::connections::Medium;
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,
std::int64_t 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, std::int64_t 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 (static_cast<Medium>(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