Files
nearby/connections/implementation/mediums/webrtc_test.cc
T
2024-12-10 20:20:39 -08:00

645 lines
24 KiB
C++

// 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/mediums/webrtc.h"
#include <memory>
#include <string>
#include <utility>
#include "gmock/gmock.h"
#include "protobuf-matchers/protocol-buffer-matchers.h"
#include "gtest/gtest.h"
#include "connections/implementation/mediums/webrtc_peer_id.h"
#include "connections/implementation/mediums/webrtc_socket.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/cancellation_flag.h"
#include "internal/platform/exception.h"
#include "internal/platform/expected.h"
#include "internal/platform/feature_flags.h"
#include "internal/platform/future.h"
#include "internal/platform/medium_environment.h"
#include "internal/platform/webrtc.h"
#include "internal/test/fake_webrtc.h"
namespace nearby {
namespace connections {
namespace mediums {
namespace {
using FeatureFlags = FeatureFlags::Flags;
using ::location::nearby::connections::LocationHint;
struct WebRtcTestParams {
FeatureFlags feature_flags;
bool non_cellular;
};
class TestWebRtc : public WebRtc {
public:
explicit TestWebRtc(std::unique_ptr<WebRtcMedium> medium)
: WebRtc(std::move(medium)) {}
int connect_attempts_count(std::string service_id) {
return service_id_to_connect_attempts_count_map_[service_id];
}
};
class WebRtcTest : public ::testing::TestWithParam<WebRtcTestParams> {
protected:
using MockAcceptedCallback = testing::MockFunction<void(
const std::string& service_id, WebRtcSocketWrapper socket)>;
MediumEnvironment& env_{MediumEnvironment::Instance()};
};
// Tests the flow when the two devices exchange SDP messages and connect to each
// other but the signaling channel is closed before sending the data.
TEST_P(WebRtcTest, ConnectBothDevices_ShutdownSignaling_SendData) {
env_.Start({.webrtc_enabled = true});
WebRtcTestParams params = GetParam();
env_.SetFeatureFlags(params.feature_flags);
WebRtc receiver, sender;
WebRtcSocketWrapper receiver_socket;
const WebrtcPeerId self_id("self_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
Future<bool> connected;
ByteArray message("message xyz");
receiver.StartAcceptingConnections(
service_id, self_id, location_hint,
[&receiver_socket, connected](const std::string& service_id,
WebRtcSocketWrapper wrapper) mutable {
receiver_socket = wrapper;
connected.Set(receiver_socket.IsValid());
},
params.non_cellular);
CancellationFlag flag;
ErrorOr<WebRtcSocketWrapper> sender_socket_result = sender.Connect(
service_id, self_id, location_hint, &flag, params.non_cellular);
EXPECT_TRUE(sender_socket_result.has_value());
EXPECT_TRUE(sender_socket_result.value().IsValid());
ExceptionOr<bool> devices_connected = connected.Get();
ASSERT_TRUE(devices_connected.ok());
EXPECT_TRUE(devices_connected.result());
// Only shuts down signaling channel.
receiver.StopAcceptingConnections(service_id);
sender_socket_result.value().GetOutputStream().Write(message);
ExceptionOr<ByteArray> received_msg =
receiver_socket.GetInputStream().Read(/*size=*/32);
ASSERT_TRUE(received_msg.ok());
EXPECT_EQ(message, received_msg.result());
env_.Stop();
}
TEST_P(WebRtcTest, CanCancelConnect) {
env_.Start({.webrtc_enabled = true});
WebRtcTestParams params = GetParam();
env_.SetFeatureFlags(params.feature_flags);
WebRtc receiver, sender;
WebRtcSocketWrapper receiver_socket;
const WebrtcPeerId self_id("self_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
Future<bool> connected;
ByteArray message("message");
receiver.StartAcceptingConnections(
service_id, self_id, location_hint,
[&receiver_socket, connected](const std::string& service_id,
WebRtcSocketWrapper wrapper) mutable {
receiver_socket = wrapper;
connected.Set(receiver_socket.IsValid());
},
params.non_cellular);
CancellationFlag flag(true);
ErrorOr<WebRtcSocketWrapper> sender_socket_result = sender.Connect(
service_id, self_id, location_hint, &flag, params.non_cellular);
// If FeatureFlag is disabled, Cancelled is false as no-op.
if (!params.feature_flags.enable_cancellation_flag) {
EXPECT_TRUE(sender_socket_result.has_value());
EXPECT_TRUE(sender_socket_result.value().IsValid());
ExceptionOr<bool> devices_connected = connected.Get();
ASSERT_TRUE(devices_connected.ok());
EXPECT_TRUE(devices_connected.result());
sender_socket_result.value().GetOutputStream().Write(message);
ExceptionOr<ByteArray> received_msg =
receiver_socket.GetInputStream().Read(/*size=*/32);
ASSERT_TRUE(received_msg.ok());
EXPECT_EQ(message, received_msg.result());
receiver_socket.Close();
} else {
EXPECT_TRUE(sender_socket_result.has_error());
}
env_.Stop();
}
// Basic test to check that device is accepting connections when initialized.
TEST_P(WebRtcTest, NotAcceptingConnections) {
env_.Start({.webrtc_enabled = true});
WebRtc webrtc;
ASSERT_TRUE(webrtc.IsAvailable());
EXPECT_FALSE(webrtc.IsAcceptingConnections(std::string{}));
env_.Stop();
}
// Tests the flow when the device tries to accept connections twice. In this
// case, only the first call is successful and subsequent calls fail.
TEST_P(WebRtcTest, StartAcceptingConnectionTwice) {
env_.Start({.webrtc_enabled = true});
WebRtcTestParams params = GetParam();
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
WebRtc webrtc;
WebrtcPeerId self_id("peer_id");
const std::string service_id("NearbySharing");
LocationHint location_hint{};
ASSERT_TRUE(webrtc.IsAvailable());
ASSERT_TRUE(webrtc.StartAcceptingConnections(
service_id, self_id, location_hint,
mock_accepted_callback_.AsStdFunction(), params.non_cellular));
EXPECT_FALSE(webrtc.StartAcceptingConnections(
service_id, self_id, location_hint,
mock_accepted_callback_.AsStdFunction(), params.non_cellular));
EXPECT_TRUE(webrtc.IsAcceptingConnections(service_id));
EXPECT_FALSE(webrtc.IsAcceptingConnections(std::string{}));
env_.Stop();
}
// Tests the flow when the device tries to connect but there is no peer
// accepting connections at the given peer ID.
TEST_P(WebRtcTest, Connect_NoPeer) {
env_.Start({.webrtc_enabled = true});
WebRtcTestParams params = GetParam();
WebRtc webrtc;
WebrtcPeerId peer_id("peer_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
ASSERT_TRUE(webrtc.IsAvailable());
CancellationFlag flag;
ErrorOr<WebRtcSocketWrapper> wrapper_1_result = webrtc.Connect(
service_id, peer_id, location_hint, &flag, params.non_cellular);
EXPECT_TRUE(wrapper_1_result.has_error());
EXPECT_TRUE(webrtc.StartAcceptingConnections(
service_id, peer_id, location_hint, nullptr, params.non_cellular));
env_.Stop();
}
// Tests the flow when the device calls Connect() after calling
// StartAcceptingConnections() without StopAcceptingConnections().
TEST_P(WebRtcTest, StartAcceptingConnection_ThenConnect) {
env_.Start({.webrtc_enabled = true});
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
WebRtcTestParams params = GetParam();
WebRtc webrtc;
WebrtcPeerId self_id("peer_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
ASSERT_TRUE(webrtc.IsAvailable());
ASSERT_TRUE(webrtc.StartAcceptingConnections(
service_id, self_id, location_hint,
mock_accepted_callback_.AsStdFunction(), params.non_cellular));
CancellationFlag flag;
ErrorOr<WebRtcSocketWrapper> wrapper_result =
webrtc.Connect(service_id, WebrtcPeerId("random_peer_id"), location_hint,
&flag, params.non_cellular);
EXPECT_TRUE(webrtc.IsAcceptingConnections(service_id));
EXPECT_TRUE(wrapper_result.has_error());
EXPECT_FALSE(webrtc.StartAcceptingConnections(
service_id, self_id, location_hint,
mock_accepted_callback_.AsStdFunction(), params.non_cellular));
env_.Stop();
}
// Tests the flow when the device calls StartAcceptingConnections but the medium
// is closed before a peer device can connect to it.
TEST_P(WebRtcTest, StartAndStopAcceptingConnections) {
env_.Start({.webrtc_enabled = true});
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
WebRtcTestParams params = GetParam();
WebRtc webrtc;
WebrtcPeerId self_id("peer_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
ASSERT_TRUE(webrtc.IsAvailable());
ASSERT_TRUE(webrtc.StartAcceptingConnections(
service_id, self_id, location_hint,
mock_accepted_callback_.AsStdFunction(), params.non_cellular));
EXPECT_TRUE(webrtc.IsAcceptingConnections(service_id));
webrtc.StopAcceptingConnections(service_id);
EXPECT_FALSE(webrtc.IsAcceptingConnections(service_id));
env_.Stop();
}
// Tests the flow when the device tries to connect to two different peers
// without disconnecting in between.
TEST_P(WebRtcTest, ConnectTwice) {
env_.Start({.webrtc_enabled = true});
WebRtc receiver, sender, device_c;
WebRtcSocketWrapper receiver_socket;
WebRtcTestParams params = GetParam();
const WebrtcPeerId self_id("self_id"), other_id("other_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
Future<bool> connected;
ByteArray message("message xyz");
receiver.StartAcceptingConnections(
service_id, self_id, location_hint,
[&receiver_socket, connected](const std::string& service_id,
WebRtcSocketWrapper wrapper) mutable {
receiver_socket = wrapper;
connected.Set(receiver_socket.IsValid());
},
params.non_cellular);
device_c.StartAcceptingConnections(
service_id, other_id, location_hint,
[](const std::string& service_id, WebRtcSocketWrapper wrapper) {},
params.non_cellular);
CancellationFlag flag;
ErrorOr<WebRtcSocketWrapper> sender_socket_result = sender.Connect(
service_id, self_id, location_hint, &flag, params.non_cellular);
EXPECT_TRUE(sender_socket_result.has_value());
EXPECT_TRUE(sender_socket_result.value().IsValid());
ExceptionOr<bool> devices_connected = connected.Get();
ASSERT_TRUE(devices_connected.ok());
EXPECT_TRUE(devices_connected.result());
ErrorOr<WebRtcSocketWrapper> socket_result = sender.Connect(
service_id, other_id, location_hint, &flag, params.non_cellular);
EXPECT_TRUE(socket_result.has_value());
EXPECT_TRUE(socket_result.value().IsValid());
socket_result.value().Close();
EXPECT_TRUE(receiver_socket.IsValid());
EXPECT_TRUE(sender_socket_result.has_value());
EXPECT_TRUE(sender_socket_result.value().IsValid());
sender_socket_result.value().GetOutputStream().Write(message);
ExceptionOr<ByteArray> received_msg =
receiver_socket.GetInputStream().Read(/*size=*/32);
ASSERT_TRUE(received_msg.ok());
EXPECT_EQ(message, received_msg.result());
receiver_socket.Close();
env_.Stop();
}
// Tests the flow when the two devices exchange SDP messages and connect to each
// other but disconnect before being able to send/receive the actual data.
TEST_P(WebRtcTest, ConnectBothDevicesAndAbort) {
env_.Start({.webrtc_enabled = true});
WebRtc receiver, sender;
WebRtcSocketWrapper receiver_socket, sender_socket;
WebRtcTestParams params = GetParam();
const WebrtcPeerId self_id("self_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
Future<bool> connected;
ByteArray message("message xyz");
receiver.StartAcceptingConnections(
service_id, self_id, location_hint,
[&receiver_socket, connected](const std::string& service_id,
WebRtcSocketWrapper wrapper) mutable {
receiver_socket = wrapper;
connected.Set(receiver_socket.IsValid());
},
params.non_cellular);
CancellationFlag flag;
ErrorOr<WebRtcSocketWrapper> sender_socket_result = sender.Connect(
service_id, self_id, location_hint, &flag, params.non_cellular);
EXPECT_TRUE(sender_socket_result.has_value());
EXPECT_TRUE(sender_socket_result.value().IsValid());
ExceptionOr<bool> devices_connected = connected.Get();
ASSERT_TRUE(devices_connected.ok());
EXPECT_TRUE(devices_connected.result());
receiver_socket.Close();
env_.Stop();
}
// Tests the flow when the two devices exchange SDP messages and connect to each
// other and the actual data is exchanged successfully between the devices.
TEST_P(WebRtcTest, ConnectBothDevicesAndSendData) {
env_.Start({.webrtc_enabled = true});
WebRtc receiver, sender;
WebRtcSocketWrapper receiver_socket;
WebRtcTestParams params = GetParam();
const WebrtcPeerId self_id("self_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
Future<bool> connected;
ByteArray message("message");
receiver.StartAcceptingConnections(
service_id, self_id, location_hint,
[&receiver_socket, connected](const std::string& service_id,
WebRtcSocketWrapper wrapper) mutable {
receiver_socket = wrapper;
connected.Set(receiver_socket.IsValid());
},
params.non_cellular);
CancellationFlag flag;
ErrorOr<WebRtcSocketWrapper> sender_socket_result = sender.Connect(
service_id, self_id, location_hint, &flag, params.non_cellular);
EXPECT_TRUE(sender_socket_result.has_value());
EXPECT_TRUE(sender_socket_result.value().IsValid());
ExceptionOr<bool> devices_connected = connected.Get();
ASSERT_TRUE(devices_connected.ok());
EXPECT_TRUE(devices_connected.result());
sender_socket_result.value().GetOutputStream().Write(message);
ExceptionOr<ByteArray> received_msg =
receiver_socket.GetInputStream().Read(/*size=*/32);
ASSERT_TRUE(received_msg.ok());
EXPECT_EQ(message, received_msg.result());
receiver_socket.Close();
env_.Stop();
}
TEST_P(WebRtcTest, Connect_NullPeerConnection) {
env_.Start({.webrtc_enabled = true});
WebRtcTestParams params = GetParam();
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
env_.SetUseValidPeerConnection(
/*use_valid_peer_connection=*/false);
WebRtc webrtc;
const std::string service_id("NearbySharing");
WebrtcPeerId self_id("peer_id");
LocationHint location_hint;
ASSERT_TRUE(webrtc.IsAvailable());
CancellationFlag flag;
ErrorOr<WebRtcSocketWrapper> wrapper_result =
webrtc.Connect(service_id, WebrtcPeerId("random_peer_id"), location_hint,
&flag, params.non_cellular);
EXPECT_TRUE(wrapper_result.has_error());
env_.Stop();
}
// Tests the flow when the device calls StartAcceptingConnections and the
// receive messages stream fails.
TEST_P(WebRtcTest, ContinueAcceptingConnectionsOnComplete) {
env_.Start({.webrtc_enabled = true});
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
WebRtcTestParams params = GetParam();
WebRtc webrtc;
WebrtcPeerId self_id("peer_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
ASSERT_TRUE(webrtc.IsAvailable());
ASSERT_TRUE(webrtc.StartAcceptingConnections(
service_id, self_id, location_hint,
mock_accepted_callback_.AsStdFunction(), params.non_cellular));
EXPECT_TRUE(webrtc.IsAcceptingConnections(service_id));
// Simulate a failure in receiving messages stream, WebRtc should restart
// accepting connections.
env_.SendWebRtcSignalingComplete(self_id.GetId(),
/*success=*/false);
EXPECT_TRUE(webrtc.IsAcceptingConnections(service_id));
// And a "success" message should not cause accepting connections to stop.
env_.SendWebRtcSignalingComplete(self_id.GetId(),
/*success=*/true);
EXPECT_TRUE(webrtc.IsAcceptingConnections(service_id));
webrtc.StopAcceptingConnections(service_id);
EXPECT_FALSE(webrtc.IsAcceptingConnections(service_id));
env_.Stop();
}
// Tests when a CancellationFlag is cancelled during an attempt to
// `WebRtc::AttemptToConnect` triggered by `WebRtc::Connect`.
TEST_P(WebRtcTest, CancelDuringConnect) {
env_.Start({.webrtc_enabled = true});
WebRtcTestParams params = GetParam();
// Enable cancellation flags.
env_.SetFeatureFlags(FeatureFlags{
.enable_cancellation_flag = true,
});
WebRtcSocketWrapper receiver_socket, sender_socket;
const WebrtcPeerId self_id("self_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
Future<bool> connected;
ByteArray message("message");
CancellationFlag receiver_flag;
std::unique_ptr<WebRtc> receiver = std::make_unique<TestWebRtc>(
std::make_unique<FakeWebRtcMedium>(&receiver_flag));
CancellationFlag sender_flag;
std::unique_ptr<WebRtcMedium> sender_medium =
std::make_unique<FakeWebRtcMedium>(&sender_flag);
FakeWebRtcMedium* fake_sender_medium =
static_cast<FakeWebRtcMedium*>(sender_medium.get());
auto sender = std::make_unique<TestWebRtc>(std::move(sender_medium));
// Calls `CancellationFlag::Cancel` during a call to `GetSignalingMessenger`
// to simulate the cancellation occuring during an `AttemptToConnect`.
fake_sender_medium->TriggerCancellationDuringGetSignalingMessenger();
receiver->StartAcceptingConnections(
service_id, self_id, location_hint,
[&receiver_socket, connected](const std::string& service_id,
WebRtcSocketWrapper wrapper) mutable {
receiver_socket = wrapper;
connected.Set(receiver_socket.IsValid());
},
params.non_cellular);
ErrorOr<WebRtcSocketWrapper> sender_socket_result = sender->Connect(
service_id, self_id, location_hint, &sender_flag, params.non_cellular);
// Since the flag was cancelled during the initial `AttemptToConnect`, except
// only one attempt instead of the usual three, because the cancellation flag
// should short-circuit the lengthy connection attempts during shutdown.
// Because of the way the iteration happens, the check for is cancelled
// happens after the counter has already been incremented, but before the
// attempt actually occurs.
EXPECT_TRUE(sender_socket_result.has_error());
EXPECT_EQ(2, sender->connect_attempts_count(service_id));
env_.Stop();
}
// Tests when a CancellationFlag is cancelled before `WebRtc::Connect` is
// called.
TEST_P(WebRtcTest, CancelBeforeConnect) {
env_.Start({.webrtc_enabled = true});
WebRtcTestParams params = GetParam();
// Enable cancellation flags.
env_.SetFeatureFlags(FeatureFlags{
.enable_cancellation_flag = true,
});
WebRtcSocketWrapper receiver_socket;
const WebrtcPeerId self_id("self_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
Future<bool> connected;
ByteArray message("message");
CancellationFlag receiver_flag;
std::unique_ptr<WebRtc> receiver = std::make_unique<TestWebRtc>(
std::make_unique<FakeWebRtcMedium>(&receiver_flag));
CancellationFlag sender_flag(true);
auto sender = std::make_unique<TestWebRtc>(
std::make_unique<FakeWebRtcMedium>(&sender_flag));
receiver->StartAcceptingConnections(
service_id, self_id, location_hint,
[&receiver_socket, connected](const std::string& service_id,
WebRtcSocketWrapper wrapper) mutable {
receiver_socket = wrapper;
connected.Set(receiver_socket.IsValid());
},
params.non_cellular);
ErrorOr<WebRtcSocketWrapper> sender_socket_result = sender->Connect(
service_id, self_id, location_hint, &sender_flag, params.non_cellular);
// Expect an invalid socket from stopping during the first attempt to connect,
// because `Connect` returned immediatley when it checked for cancellation.
EXPECT_TRUE(sender_socket_result.has_error());
EXPECT_EQ(1, sender->connect_attempts_count(service_id));
env_.Stop();
}
// Tests when a CancellationFlag is cancelled during an attempt to
// `WebRtc::AttemptToConnect` triggered by `WebRtc::Connect` when multiple
// `WebRTC::Connect` calls are in flight for multiple service ids.
TEST_P(WebRtcTest, CancelDuringConnect_MultipleConnect) {
env_.Start({.webrtc_enabled = true});
WebRtcTestParams params = GetParam();
// Enable cancellation flags.
env_.SetFeatureFlags(FeatureFlags{
.enable_cancellation_flag = true,
});
WebRtcSocketWrapper receiver_socket;
const WebrtcPeerId self_id("self_id");
const std::string ns_service_id("NearbySharing");
const std::string ph_service_id("PhoneHub");
LocationHint location_hint;
Future<bool> connected;
ByteArray message("message xyz");
CancellationFlag receiver_flag;
std::unique_ptr<WebRtc> receiver = std::make_unique<TestWebRtc>(
std::make_unique<FakeWebRtcMedium>(&receiver_flag));
CancellationFlag flag;
auto sender_medium = std::make_unique<FakeWebRtcMedium>(&flag);
FakeWebRtcMedium* fake_sender_medium = sender_medium.get();
auto sender = std::make_unique<TestWebRtc>(std::move(sender_medium));
receiver->StartAcceptingConnections(
ns_service_id, self_id, location_hint,
[&receiver_socket, connected](const std::string& ns_service_id,
WebRtcSocketWrapper wrapper) mutable {
receiver_socket = wrapper;
connected.Set(receiver_socket.IsValid());
},
params.non_cellular);
// Simulate a successful connect for the endpoint of NearbySharing.
ErrorOr<WebRtcSocketWrapper> sender_socket_result = sender->Connect(
ns_service_id, self_id, location_hint, &flag, params.non_cellular);
EXPECT_TRUE(sender_socket_result.has_value());
EXPECT_TRUE(sender_socket_result.value().IsValid());
// Calls `CancellationFlag::Cancel` during a call to `GetSignalingMessenger`
// to simulate the cancellation occuring during an `AttemptToConnect` for the
// endpoint of Phone Hub.
fake_sender_medium->TriggerCancellationDuringGetSignalingMessenger();
sender_socket_result = sender->Connect(ph_service_id, self_id, location_hint,
&flag, params.non_cellular);
EXPECT_TRUE(sender_socket_result.has_error());
// Since the flag was cancelled during the initial `AttemptToConnect`, except
// only one attempt instead of the usual three, because the cancellation flag
// should short-circuit the lengthy connection attempts during shutdown.
// Because of the way the iteration happens, the check for is cancelled
// happens after the counter has already been incremented, but before the
// attempt actually occurs. For the successful connect, expect only one
// attempt.
EXPECT_EQ(1, sender->connect_attempts_count(ns_service_id));
EXPECT_EQ(2, sender->connect_attempts_count(ph_service_id));
env_.Stop();
}
INSTANTIATE_TEST_SUITE_P(ParametrisedWebRtcTest, WebRtcTest,
testing::ValuesIn<WebRtcTestParams>({
{.feature_flags =
FeatureFlags{
.enable_cancellation_flag = true,
},
.non_cellular = true},
{.feature_flags =
FeatureFlags{
.enable_cancellation_flag = true,
},
.non_cellular = false},
{.feature_flags =
FeatureFlags{
.enable_cancellation_flag = false,
},
.non_cellular = true},
{.feature_flags =
FeatureFlags{
.enable_cancellation_flag = false,
},
.non_cellular = false},
}));
} // namespace
} // namespace mediums
} // namespace connections
} // namespace nearby