blob: 78ff4e6ee14d4f3c93847ff8c03fa4aa9b6e25b9 [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_service_controller.h"
#include <array>
#include "gmock/gmock.h"
#include "protobuf-matchers/protocol-buffer-matchers.h"
#include "gtest/gtest.h"
#include "connections/implementation/offline_simulation_user.h"
#include "internal/platform/medium_environment.h"
#include "internal/platform/output_stream.h"
#include "internal/platform/count_down_latch.h"
#include "internal/platform/logging.h"
#include "internal/platform/pipe.h"
#include "internal/platform/system_clock.h"
namespace location {
namespace nearby {
namespace connections {
namespace {
using ::testing::Eq;
constexpr std::array<char, 6> kFakeMacAddress = {'a', 'b', 'c', 'd', 'e', 'f'};
constexpr absl::string_view kServiceId = "service-id";
constexpr absl::string_view kDeviceA = "device-a";
constexpr absl::string_view kDeviceB = "device-b";
constexpr absl::string_view kMessage = "message";
constexpr absl::Duration kProgressTimeout = absl::Milliseconds(1500);
constexpr absl::Duration kDefaultTimeout = absl::Milliseconds(1500);
constexpr absl::Duration kDisconnectTimeout = absl::Milliseconds(15000);
constexpr BooleanMediumSelector kTestCases[] = {
BooleanMediumSelector{
.bluetooth = true,
},
BooleanMediumSelector{
.wifi_lan = true,
},
BooleanMediumSelector{
.bluetooth = true,
.wifi_lan = true,
},
};
class OfflineServiceControllerTest
: public ::testing::TestWithParam<BooleanMediumSelector> {
protected:
OfflineServiceControllerTest() { env_.Stop(); }
bool SetupConnection(OfflineSimulationUser& user_a,
OfflineSimulationUser& user_b) {
user_a.StartAdvertising(std::string(kServiceId), &connect_latch_);
user_b.StartDiscovery(std::string(kServiceId), &discover_latch_);
EXPECT_TRUE(discover_latch_.Await(kDefaultTimeout).result());
EXPECT_EQ(user_b.GetDiscovered().service_id, kServiceId);
EXPECT_EQ(user_b.GetDiscovered().endpoint_info, user_a.GetInfo());
EXPECT_FALSE(user_b.GetDiscovered().endpoint_id.empty());
NEARBY_LOG(INFO, "EP-B: [discovered] %s",
user_b.GetDiscovered().endpoint_id.c_str());
user_b.RequestConnection(&connect_latch_);
EXPECT_TRUE(connect_latch_.Await(kDefaultTimeout).result());
EXPECT_FALSE(user_a.GetDiscovered().endpoint_id.empty());
NEARBY_LOG(INFO, "EP-A: [discovered] %s",
user_a.GetDiscovered().endpoint_id.c_str());
NEARBY_LOG(INFO, "Both users discovered their peers.");
user_a.AcceptConnection(&accept_latch_);
user_b.AcceptConnection(&accept_latch_);
EXPECT_TRUE(accept_latch_.Await(kDefaultTimeout).result());
NEARBY_LOG(INFO, "Both users reached connected state.");
return user_a.IsConnected() && user_b.IsConnected();
}
CountDownLatch discover_latch_{1};
CountDownLatch lost_latch_{1};
CountDownLatch connect_latch_{2};
CountDownLatch accept_latch_{2};
CountDownLatch payload_latch_{1};
MediumEnvironment& env_ = MediumEnvironment::Instance();
};
TEST_P(OfflineServiceControllerTest, CanCreateOne) {
env_.Start();
OfflineSimulationUser user_a(kDeviceA, GetParam());
env_.Stop();
}
TEST_P(OfflineServiceControllerTest, CanCreateMany) {
env_.Start();
OfflineSimulationUser user_a(kDeviceA, GetParam());
OfflineSimulationUser user_b(kDeviceB, GetParam());
env_.Stop();
}
TEST_P(OfflineServiceControllerTest, CanStartAdvertising) {
env_.Start();
OfflineSimulationUser user_a(kDeviceA, GetParam());
OfflineSimulationUser user_b(kDeviceB, GetParam());
EXPECT_FALSE(user_a.IsAdvertising());
EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), nullptr),
Eq(Status{Status::kSuccess}));
EXPECT_TRUE(user_a.IsAdvertising());
env_.Stop();
}
TEST_P(OfflineServiceControllerTest, CanStartDiscoveryBeforeAdvertising) {
env_.Start();
OfflineSimulationUser user_a(kDeviceA, GetParam());
OfflineSimulationUser user_b(kDeviceB, GetParam());
EXPECT_FALSE(user_b.IsDiscovering());
EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_),
Eq(Status{Status::kSuccess}));
EXPECT_TRUE(user_b.IsDiscovering());
EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), nullptr),
Eq(Status{Status::kSuccess}));
EXPECT_TRUE(discover_latch_.Await(kDefaultTimeout).result());
user_a.Stop();
user_b.Stop();
env_.Stop();
}
TEST_P(OfflineServiceControllerTest, CanStartDiscoveryAfterAdvertising) {
env_.Start();
OfflineSimulationUser user_a(kDeviceA, GetParam());
OfflineSimulationUser user_b(kDeviceB, GetParam());
EXPECT_FALSE(user_b.IsDiscovering());
EXPECT_FALSE(user_b.IsAdvertising());
EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), nullptr),
Eq(Status{Status::kSuccess}));
EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_),
Eq(Status{Status::kSuccess}));
EXPECT_TRUE(user_a.IsAdvertising());
EXPECT_TRUE(user_b.IsDiscovering());
EXPECT_TRUE(discover_latch_.Await(kDefaultTimeout).result());
user_a.Stop();
user_b.Stop();
env_.Stop();
}
TEST_P(OfflineServiceControllerTest, CanStopAdvertising) {
env_.Start();
OfflineSimulationUser user_a(kDeviceA, GetParam());
OfflineSimulationUser user_b(kDeviceB, GetParam());
EXPECT_FALSE(user_a.IsAdvertising());
EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), nullptr),
Eq(Status{Status::kSuccess}));
EXPECT_TRUE(user_a.IsAdvertising());
user_a.StopAdvertising();
EXPECT_FALSE(user_a.IsAdvertising());
EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_,
&lost_latch_),
Eq(Status{Status::kSuccess}));
EXPECT_TRUE(user_b.IsDiscovering());
auto discover_none = discover_latch_.Await(kDefaultTimeout).GetResult();
if (!discover_none) {
EXPECT_TRUE(true);
} else {
// There are rare cases (1/1000) that advertisment data has been captured by
// discovery device before advertising is stopped. So we need to check if
// lost_cb has grabbed the event in the end to prove the advertising service
// is stopped.
EXPECT_TRUE(lost_latch_.Await(kDefaultTimeout).result());
}
user_a.Stop();
user_b.Stop();
env_.Stop();
}
TEST_P(OfflineServiceControllerTest, CanStopDiscovery) {
env_.Start();
OfflineSimulationUser user_a(kDeviceA, GetParam());
OfflineSimulationUser user_b(kDeviceB, GetParam());
EXPECT_FALSE(user_b.IsDiscovering());
EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_),
Eq(Status{Status::kSuccess}));
EXPECT_TRUE(user_b.IsDiscovering());
user_b.StopDiscovery();
EXPECT_FALSE(user_b.IsDiscovering());
EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), nullptr),
Eq(Status{Status::kSuccess}));
EXPECT_FALSE(discover_latch_.Await(kDefaultTimeout).result());
user_a.Stop();
user_b.Stop();
env_.Stop();
}
TEST_P(OfflineServiceControllerTest, CanConnect) {
env_.Start();
OfflineSimulationUser user_a(kDeviceA, GetParam());
OfflineSimulationUser user_b(kDeviceB, GetParam());
EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), &connect_latch_),
Eq(Status{Status::kSuccess}));
EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_),
Eq(Status{Status::kSuccess}));
EXPECT_TRUE(discover_latch_.Await(kDefaultTimeout).result());
EXPECT_THAT(user_b.RequestConnection(&connect_latch_),
Eq(Status{Status::kSuccess}));
EXPECT_TRUE(connect_latch_.Await(kDefaultTimeout).result());
user_a.Stop();
user_b.Stop();
env_.Stop();
}
TEST_P(OfflineServiceControllerTest, CanAcceptConnection) {
env_.Start();
OfflineSimulationUser user_a(kDeviceA, GetParam());
OfflineSimulationUser user_b(kDeviceB, GetParam());
EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), &connect_latch_),
Eq(Status{Status::kSuccess}));
EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_),
Eq(Status{Status::kSuccess}));
EXPECT_TRUE(discover_latch_.Await(kDefaultTimeout).result());
EXPECT_THAT(user_b.RequestConnection(&connect_latch_),
Eq(Status{Status::kSuccess}));
EXPECT_TRUE(connect_latch_.Await(kDefaultTimeout).result());
EXPECT_THAT(user_a.AcceptConnection(&accept_latch_),
Eq(Status{Status::kSuccess}));
EXPECT_THAT(user_b.AcceptConnection(&accept_latch_),
Eq(Status{Status::kSuccess}));
EXPECT_TRUE(accept_latch_.Await(kDefaultTimeout).result());
EXPECT_TRUE(user_a.IsConnected());
EXPECT_TRUE(user_b.IsConnected());
user_a.Stop();
user_b.Stop();
env_.Stop();
}
TEST_P(OfflineServiceControllerTest, CanRejectConnection) {
env_.Start();
OfflineSimulationUser user_a(kDeviceA, GetParam());
OfflineSimulationUser user_b(kDeviceB, GetParam());
CountDownLatch reject_latch(1);
EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), &connect_latch_),
Eq(Status{Status::kSuccess}));
EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_),
Eq(Status{Status::kSuccess}));
EXPECT_TRUE(discover_latch_.Await(kDefaultTimeout).result());
EXPECT_THAT(user_b.RequestConnection(&connect_latch_),
Eq(Status{Status::kSuccess}));
EXPECT_TRUE(connect_latch_.Await(kDefaultTimeout).result());
user_a.ExpectRejectedConnection(reject_latch);
EXPECT_THAT(user_b.RejectConnection(nullptr), Eq(Status{Status::kSuccess}));
EXPECT_TRUE(reject_latch.Await(kDefaultTimeout).result());
user_a.Stop();
user_b.Stop();
env_.Stop();
}
TEST_P(OfflineServiceControllerTest, CanSendBytePayload) {
env_.Start();
OfflineSimulationUser user_a(kDeviceA, GetParam());
OfflineSimulationUser user_b(kDeviceB, GetParam());
ASSERT_TRUE(SetupConnection(user_a, user_b));
ByteArray message(std::string{kMessage});
user_a.SendPayload(Payload(message));
user_b.ExpectPayload(payload_latch_);
EXPECT_TRUE(payload_latch_.Await(kDefaultTimeout).result());
EXPECT_EQ(user_b.GetPayload().AsBytes(), message);
user_a.Stop();
user_b.Stop();
env_.Stop();
}
TEST_P(OfflineServiceControllerTest, CanSendStreamPayload) {
env_.Start();
OfflineSimulationUser user_a(kDeviceA, GetParam());
OfflineSimulationUser user_b(kDeviceB, GetParam());
ASSERT_TRUE(SetupConnection(user_a, user_b));
ByteArray message(std::string{kMessage});
auto pipe = std::make_shared<Pipe>();
OutputStream& tx = pipe->GetOutputStream();
user_a.SendPayload(Payload([pipe]() -> InputStream& {
return pipe->GetInputStream(); // NOLINT
}));
user_b.ExpectPayload(payload_latch_);
tx.Write(message);
EXPECT_TRUE(payload_latch_.Await(kDefaultTimeout).result());
EXPECT_NE(user_b.GetPayload().AsStream(), nullptr);
InputStream& rx = *user_b.GetPayload().AsStream();
ASSERT_TRUE(user_b.WaitForProgress(
[size = message.size()](const PayloadProgressInfo& info) -> bool {
return info.bytes_transferred >= size;
},
kProgressTimeout));
EXPECT_EQ(rx.Read(Pipe::kChunkSize).result(), message);
user_a.Stop();
user_b.Stop();
env_.Stop();
}
TEST_P(OfflineServiceControllerTest, CanCancelStreamPayload) {
env_.Start();
OfflineSimulationUser user_a(kDeviceA, GetParam());
OfflineSimulationUser user_b(kDeviceB, GetParam());
ASSERT_TRUE(SetupConnection(user_a, user_b));
ByteArray message(std::string{kMessage});
auto pipe = std::make_shared<Pipe>();
OutputStream& tx = pipe->GetOutputStream();
user_a.SendPayload(Payload([pipe]() -> InputStream& {
return pipe->GetInputStream(); // NOLINT
}));
user_b.ExpectPayload(payload_latch_);
tx.Write(message);
EXPECT_TRUE(payload_latch_.Await(kDefaultTimeout).result());
EXPECT_NE(user_b.GetPayload().AsStream(), nullptr);
InputStream& rx = *user_b.GetPayload().AsStream();
ASSERT_TRUE(user_b.WaitForProgress(
[size = message.size()](const PayloadProgressInfo& info) -> bool {
return info.bytes_transferred >= size;
},
kProgressTimeout));
EXPECT_EQ(rx.Read(Pipe::kChunkSize).result(), message);
user_b.CancelPayload();
int count = 0;
while (true) {
count++;
if (!tx.Write(message).Ok()) break;
SystemClock::Sleep(kDefaultTimeout);
}
EXPECT_TRUE(user_a.WaitForProgress(
[](const PayloadProgressInfo& info) -> bool {
return info.status == PayloadProgressInfo::Status::kCanceled;
},
kProgressTimeout));
EXPECT_LT(count, 10);
user_a.Stop();
user_b.Stop();
env_.Stop();
}
TEST_P(OfflineServiceControllerTest, CanDisconnect) {
env_.Start();
CountDownLatch disconnect_latch(1);
OfflineSimulationUser user_a(kDeviceA, GetParam());
OfflineSimulationUser user_b(kDeviceB, GetParam());
ASSERT_TRUE(SetupConnection(user_a, user_b));
NEARBY_LOGS(INFO) << "Disconnecting";
user_b.ExpectDisconnect(disconnect_latch);
user_b.Disconnect();
EXPECT_TRUE(disconnect_latch.Await(kDisconnectTimeout).result());
NEARBY_LOGS(INFO) << "Disconnected";
EXPECT_FALSE(user_b.IsConnected());
user_a.Stop();
user_b.Stop();
env_.Stop();
}
INSTANTIATE_TEST_SUITE_P(ParametrisedOfflineServiceControllerTest,
OfflineServiceControllerTest,
::testing::ValuesIn(kTestCases));
// Verifies that InjectEndpoint() can be run successfully; does not test the
// full connection flow given that normal discovery/advertisement is skipped.
// Note: Not parameterized because InjectEndpoint only works over Bluetooth.
TEST_F(OfflineServiceControllerTest, InjectEndpoint) {
env_.Start();
OfflineSimulationUser user_a(kDeviceA,
BooleanMediumSelector{.bluetooth = true});
EXPECT_THAT(user_a.StartDiscovery(std::string(kServiceId),
/*found_latch=*/nullptr),
Eq(Status{Status::kSuccess}));
EXPECT_TRUE(user_a.IsDiscovering());
user_a.InjectEndpoint(
std::string(kServiceId),
OutOfBandConnectionMetadata{
.medium = Medium::BLUETOOTH,
.remote_bluetooth_mac_address = ByteArray(kFakeMacAddress),
});
user_a.Stop();
env_.Stop();
}
} // namespace
} // namespace connections
} // namespace nearby
} // namespace location