Only allow message_bridge to connect with matching config sha256
We've been seeing a ton of crashes becasue the config doesn't match, and
a node is out of range, or the client is asking for a channel which
doesn't exist. Honestly, there is no real use case at this point in
time for accepting connections from clients who aren't running the same
code. We can't read the logs if we were to allow it, and the effort
required to support that is massive. We'll probably run into send too
fast issues, would run into flatbuffer version problems (maybe), and all
sorts of other problems. The cost to reward ratio doesn't work.
So, as part of connecting, send the sha256 sum of the config. The
server will disconnect any clients who don't have a matching config, and
increment a counter in the status message.
Change-Id: I99520713efc644252f2c7cf5dc53720c4fc19974
Signed-off-by: Austin Schuh <austin.schuh@bluerivertech.com>
diff --git a/aos/network/BUILD b/aos/network/BUILD
index 88df785..527fd46 100644
--- a/aos/network/BUILD
+++ b/aos/network/BUILD
@@ -274,6 +274,7 @@
":message_bridge_server_lib",
"//aos:init",
"//aos:json_to_flatbuffer",
+ "//aos:sha256",
"//aos/events:shm_event_loop",
"//aos/logging:dynamic_logging",
],
@@ -356,6 +357,7 @@
":message_bridge_client_lib",
"//aos:init",
"//aos:json_to_flatbuffer",
+ "//aos:sha256",
"//aos/events:shm_event_loop",
"//aos/logging:dynamic_logging",
],
@@ -422,6 +424,7 @@
":message_bridge_client_lib",
":message_bridge_server_lib",
"//aos:json_to_flatbuffer",
+ "//aos:sha256",
"//aos/events:ping_fbs",
"//aos/events:pong_fbs",
"//aos/events:shm_event_loop",
diff --git a/aos/network/connect.fbs b/aos/network/connect.fbs
index 7ff2fbe..5c7fb21 100644
--- a/aos/network/connect.fbs
+++ b/aos/network/connect.fbs
@@ -13,6 +13,9 @@
// The UUID that this node booted with.
boot_uuid: string (id: 2);
+
+ // Sha256 of the AOS config that this node is running with.
+ config_sha256: string (id: 3);
}
root_type Connect;
diff --git a/aos/network/message_bridge_client.cc b/aos/network/message_bridge_client.cc
index 8aad867..c3f55ba 100644
--- a/aos/network/message_bridge_client.cc
+++ b/aos/network/message_bridge_client.cc
@@ -2,6 +2,7 @@
#include "aos/init.h"
#include "aos/logging/dynamic_logging.h"
#include "aos/network/message_bridge_client_lib.h"
+#include "aos/sha256.h"
DEFINE_string(config, "aos_config.json", "Path to the config.");
DEFINE_int32(rt_priority, -1, "If > 0, run as this RT priority");
@@ -18,7 +19,7 @@
event_loop.SetRuntimeRealtimePriority(FLAGS_rt_priority);
}
- MessageBridgeClient app(&event_loop);
+ MessageBridgeClient app(&event_loop, Sha256(config.span()));
logging::DynamicLogging dynamic_logging(&event_loop);
// TODO(austin): Save messages into a vector to be logged. One file per
diff --git a/aos/network/message_bridge_client_lib.cc b/aos/network/message_bridge_client_lib.cc
index b7ee3cc..0c04244 100644
--- a/aos/network/message_bridge_client_lib.cc
+++ b/aos/network/message_bridge_client_lib.cc
@@ -99,11 +99,11 @@
aos::ShmEventLoop *const event_loop, std::string_view remote_name,
const Node *my_node, std::string_view local_host,
std::vector<SctpClientChannelState> *channels, int client_index,
- MessageBridgeClientStatus *client_status)
+ MessageBridgeClientStatus *client_status, std::string_view config_sha256)
: event_loop_(event_loop),
connect_message_(MakeConnectMessage(event_loop->configuration(), my_node,
- remote_name,
- event_loop->boot_uuid())),
+ remote_name, event_loop->boot_uuid(),
+ config_sha256)),
message_reception_reply_(MakeMessageHeaderReply()),
remote_node_(CHECK_NOTNULL(
configuration::GetNode(event_loop->configuration(), remote_name))),
@@ -342,8 +342,11 @@
<< " cumtsn=" << message->header.rcvinfo.rcv_cumtsn << ")";
}
-MessageBridgeClient::MessageBridgeClient(aos::ShmEventLoop *event_loop)
- : event_loop_(event_loop), client_status_(event_loop_) {
+MessageBridgeClient::MessageBridgeClient(aos::ShmEventLoop *event_loop,
+ std::string config_sha256)
+ : event_loop_(event_loop),
+ client_status_(event_loop_),
+ config_sha256_(std::move(config_sha256)) {
std::string_view node_name = event_loop->node()->name()->string_view();
// Find all the channels which are supposed to be delivered to us.
@@ -390,7 +393,8 @@
// Open an unspecified connection (:: in ipv6 terminology)
connections_.emplace_back(new SctpClientConnection(
event_loop, source_node, event_loop->node(), "", &channels_,
- client_status_.FindClientIndex(source_node), &client_status_));
+ client_status_.FindClientIndex(source_node), &client_status_,
+ config_sha256_));
}
}
diff --git a/aos/network/message_bridge_client_lib.h b/aos/network/message_bridge_client_lib.h
index 23c982d..6e108df 100644
--- a/aos/network/message_bridge_client_lib.h
+++ b/aos/network/message_bridge_client_lib.h
@@ -37,7 +37,8 @@
std::string_view local_host,
std::vector<SctpClientChannelState> *channels,
int client_index,
- MessageBridgeClientStatus *client_status);
+ MessageBridgeClientStatus *client_status,
+ std::string_view config_sha256);
~SctpClientConnection() { event_loop_->epoll()->DeleteFd(client_.fd()); }
@@ -101,7 +102,7 @@
// node.
class MessageBridgeClient {
public:
- MessageBridgeClient(aos::ShmEventLoop *event_loop);
+ MessageBridgeClient(aos::ShmEventLoop *event_loop, std::string config_sha256);
~MessageBridgeClient() {}
@@ -116,6 +117,8 @@
// List of connections. These correspond to the nodes in source_node_names_
std::vector<std::unique_ptr<SctpClientConnection>> connections_;
+
+ std::string config_sha256_;
};
} // namespace message_bridge
diff --git a/aos/network/message_bridge_protocol.cc b/aos/network/message_bridge_protocol.cc
index 87114ed..a0c68d4 100644
--- a/aos/network/message_bridge_protocol.cc
+++ b/aos/network/message_bridge_protocol.cc
@@ -13,7 +13,8 @@
aos::FlatbufferDetachedBuffer<aos::message_bridge::Connect> MakeConnectMessage(
const Configuration *config, const Node *my_node,
- std::string_view remote_name, const UUID &boot_uuid) {
+ std::string_view remote_name, const UUID &boot_uuid,
+ std::string_view config_sha256) {
CHECK(config->has_nodes()) << ": Config must have nodes to transfer.";
flatbuffers::FlatBufferBuilder fbb;
@@ -46,10 +47,14 @@
flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
channels_offset = fbb.CreateVector(channel_offsets);
+ flatbuffers::Offset<flatbuffers::String> config_sha256_offset =
+ fbb.CreateString(config_sha256);
+
Connect::Builder connect_builder(fbb);
connect_builder.add_channels_to_transfer(channels_offset);
connect_builder.add_node(node_offset);
connect_builder.add_boot_uuid(boot_uuid_offset);
+ connect_builder.add_config_sha256(config_sha256_offset);
fbb.Finish(connect_builder.Finish());
return fbb.Release();
diff --git a/aos/network/message_bridge_protocol.h b/aos/network/message_bridge_protocol.h
index d0a28a1..485defe 100644
--- a/aos/network/message_bridge_protocol.h
+++ b/aos/network/message_bridge_protocol.h
@@ -34,7 +34,8 @@
// Builds up a subscription request for my_node to remote_name.
aos::FlatbufferDetachedBuffer<aos::message_bridge::Connect> MakeConnectMessage(
const Configuration *config, const Node *my_node,
- std::string_view remote_name, const UUID &boot_uuid);
+ std::string_view remote_name, const UUID &boot_uuid,
+ std::string_view config_sha256);
} // namespace message_bridge
} // namespace aos
diff --git a/aos/network/message_bridge_server.cc b/aos/network/message_bridge_server.cc
index 3b5d30b..ec3cdc4 100644
--- a/aos/network/message_bridge_server.cc
+++ b/aos/network/message_bridge_server.cc
@@ -2,6 +2,7 @@
#include "aos/init.h"
#include "aos/logging/dynamic_logging.h"
#include "aos/network/message_bridge_server_lib.h"
+#include "aos/sha256.h"
#include "gflags/gflags.h"
#include "glog/logging.h"
@@ -20,7 +21,7 @@
event_loop.SetRuntimeRealtimePriority(FLAGS_rt_priority);
}
- MessageBridgeServer app(&event_loop);
+ MessageBridgeServer app(&event_loop, Sha256(config.span()));
logging::DynamicLogging dynamic_logging(&event_loop);
diff --git a/aos/network/message_bridge_server.fbs b/aos/network/message_bridge_server.fbs
index 031f801..30017c4 100644
--- a/aos/network/message_bridge_server.fbs
+++ b/aos/network/message_bridge_server.fbs
@@ -41,6 +41,10 @@
// Number of times we've established a connection to the server.
connection_count:uint (id: 8);
+
+ // Number of times we've had an invalid connection with something wrong in
+ // the connection message, but we were able to match which node it was.
+ invalid_connection_count:uint (id: 9);
}
// Statistics for all connections to all the clients.
@@ -49,6 +53,11 @@
// Count of timestamp send failures
timestamp_send_failures:uint64 (id: 1);
+
+ // Number of times we've had an invalid connection with something wrong in
+ // the connection message. The most likely cause is that the config sha256
+ // doesn't match between nodes.
+ invalid_connection_count:uint (id: 2);
}
root_type ServerStatistics;
diff --git a/aos/network/message_bridge_server_lib.cc b/aos/network/message_bridge_server_lib.cc
index b0973ac..54a5d53 100644
--- a/aos/network/message_bridge_server_lib.cc
+++ b/aos/network/message_bridge_server_lib.cc
@@ -261,14 +261,17 @@
return -1;
}
-MessageBridgeServer::MessageBridgeServer(aos::ShmEventLoop *event_loop)
+MessageBridgeServer::MessageBridgeServer(aos::ShmEventLoop *event_loop,
+ std::string config_sha256)
: event_loop_(event_loop),
timestamp_loggers_(event_loop_),
server_(max_channels() + kControlStreams(), "",
event_loop->node()->port()),
- server_status_(event_loop, [this](const Context &context) {
- timestamp_state_->SendData(&server_, context);
- }) {
+ server_status_(event_loop,
+ [this](const Context &context) {
+ timestamp_state_->SendData(&server_, context);
+ }),
+ config_sha256_(std::move(config_sha256)) {
CHECK(event_loop_->node() != nullptr) << ": No nodes configured.";
size_t max_size = 0;
@@ -288,7 +291,7 @@
configuration::GetNode(event_loop->configuration(),
destination_node_name),
event_loop->node()->name()->string_view(),
- UUID::Zero())
+ UUID::Zero(), config_sha256_)
.span()
.size();
VLOG(1) << "Connection to " << destination_node_name << " has size "
@@ -467,6 +470,34 @@
}
}
+void MessageBridgeServer::MaybeIncrementInvalidConnectionCount(
+ const Node *node) {
+ server_status_.increment_invalid_connection_count();
+
+ if (node == nullptr) {
+ return;
+ }
+
+ if (!node->has_name()) {
+ return;
+ }
+
+ const aos::Node *client_node = configuration::GetNode(
+ event_loop_->configuration(), node->name()->string_view());
+
+ if (client_node == nullptr) {
+ return;
+ }
+
+ const int node_index =
+ configuration::GetNodeIndex(event_loop_->configuration(), client_node);
+
+ ServerConnection *connection = server_status_.server_connection()[node_index];
+
+ connection->mutate_invalid_connection_count(
+ connection->invalid_connection_count() + 1);
+}
+
void MessageBridgeServer::HandleData(const Message *message) {
VLOG(2) << "Received data of length " << message->size;
@@ -475,13 +506,47 @@
const Connect *connect = flatbuffers::GetRoot<Connect>(message->data());
{
flatbuffers::Verifier verifier(message->data(), message->size);
- CHECK(connect->Verify(verifier));
+ if (!connect->Verify(verifier)) {
+ LOG_EVERY_T(WARNING, 1.0)
+ << "Failed to verify message, disconnecting client";
+ server_.Abort(message->header.rcvinfo.rcv_assoc_id);
+
+ MaybeIncrementInvalidConnectionCount(nullptr);
+ return;
+ }
}
VLOG(1) << FlatbufferToJson(connect);
- CHECK_LE(connect->channels_to_transfer()->size(),
- static_cast<size_t>(max_channels()))
- << ": Client has more channels than we do";
+ if (!connect->has_config_sha256()) {
+ LOG_EVERY_T(WARNING, 1.0)
+ << "Client missing config_sha256, disconnecting client";
+ server_.Abort(message->header.rcvinfo.rcv_assoc_id);
+
+ MaybeIncrementInvalidConnectionCount(connect->node());
+ return;
+ }
+
+ if (connect->config_sha256()->string_view() != config_sha256_) {
+ LOG_EVERY_T(WARNING, 1.0) << "Client config sha256 of "
+ << connect->config_sha256()->string_view()
+ << " doesn't match our config sha256 of "
+ << config_sha256_ << ", disconnecting client";
+ server_.Abort(message->header.rcvinfo.rcv_assoc_id);
+
+ MaybeIncrementInvalidConnectionCount(connect->node());
+ return;
+ }
+
+ if (connect->channels_to_transfer()->size() >
+ static_cast<size_t>(max_channels())) {
+ LOG_EVERY_T(WARNING, 1.0)
+ << "Client has more channels than we do, disconnecting client";
+ server_.Abort(message->header.rcvinfo.rcv_assoc_id);
+
+ MaybeIncrementInvalidConnectionCount(connect->node());
+ return;
+ }
+
monotonic_clock::time_point monotonic_now = event_loop_->monotonic_now();
// Account for the control channel and delivery times channel.
diff --git a/aos/network/message_bridge_server_lib.h b/aos/network/message_bridge_server_lib.h
index 6f2be08..0938094 100644
--- a/aos/network/message_bridge_server_lib.h
+++ b/aos/network/message_bridge_server_lib.h
@@ -109,7 +109,7 @@
// node. It handles the session and dispatches data to the ChannelState.
class MessageBridgeServer {
public:
- MessageBridgeServer(aos::ShmEventLoop *event_loop);
+ MessageBridgeServer(aos::ShmEventLoop *event_loop, std::string config_sha256);
~MessageBridgeServer() { event_loop_->epoll()->DeleteFd(server_.fd()); }
@@ -126,6 +126,10 @@
// received.
void HandleData(const Message *message);
+ // Increments the invalid connection count overall, and per node if we know
+ // which node (ie, node is not nullptr).
+ void MaybeIncrementInvalidConnectionCount(const Node *node);
+
// The maximum number of channels we support on a single connection. We need
// to configure the SCTP socket with this before any clients connect, so we
// need an upper bound on the number of channels any of them will use.
@@ -147,6 +151,8 @@
// List of channels. The entries that aren't sent from this node are left
// null.
std::vector<std::unique_ptr<ChannelState>> channels_;
+
+ std::string config_sha256_;
};
} // namespace message_bridge
diff --git a/aos/network/message_bridge_server_status.cc b/aos/network/message_bridge_server_status.cc
index 4f6abff..8b186b7 100644
--- a/aos/network/message_bridge_server_status.cc
+++ b/aos/network/message_bridge_server_status.cc
@@ -40,6 +40,7 @@
connection_builder.add_connected_since_time(
monotonic_clock::min_time.time_since_epoch().count());
connection_builder.add_connection_count(0);
+ connection_builder.add_invalid_connection_count(0);
connection_offsets.emplace_back(connection_builder.Finish());
}
flatbuffers::Offset<
@@ -231,6 +232,11 @@
connection->connection_count());
}
+ if (connection->invalid_connection_count() != 0) {
+ server_connection_builder.add_invalid_connection_count(
+ connection->invalid_connection_count());
+ }
+
// TODO(austin): If it gets stale, drop it too.
if (!filters_[node_index].MissingSamples()) {
server_connection_builder.add_monotonic_offset(
@@ -254,6 +260,8 @@
server_statistics_builder.add_connections(server_connections_offset);
server_statistics_builder.add_timestamp_send_failures(
timestamp_failure_counter_.failures());
+ server_statistics_builder.add_invalid_connection_count(
+ invalid_connection_count_);
builder.CheckOk(builder.Send(server_statistics_builder.Finish()));
}
diff --git a/aos/network/message_bridge_server_status.h b/aos/network/message_bridge_server_status.h
index 327930c..d513a0e 100644
--- a/aos/network/message_bridge_server_status.h
+++ b/aos/network/message_bridge_server_status.h
@@ -77,6 +77,10 @@
// Enables sending out any statistics messages.
void EnableStatistics();
+ // Increments invalid_connection_count_, marking that we had another bad
+ // connection that got rejected.
+ void increment_invalid_connection_count() { ++invalid_connection_count_; }
+
private:
static constexpr std::chrono::nanoseconds kStatisticsPeriod =
std::chrono::seconds(1);
@@ -126,6 +130,8 @@
bool send_ = true;
std::vector<uint32_t> partial_deliveries_;
+
+ size_t invalid_connection_count_ = 0u;
};
} // namespace message_bridge
diff --git a/aos/network/message_bridge_test.cc b/aos/network/message_bridge_test.cc
index 226daf7..00033f9 100644
--- a/aos/network/message_bridge_test.cc
+++ b/aos/network/message_bridge_test.cc
@@ -8,6 +8,7 @@
#include "aos/network/message_bridge_client_lib.h"
#include "aos/network/message_bridge_server_lib.h"
#include "aos/network/team_number.h"
+#include "aos/sha256.h"
#include "aos/testing/path.h"
#include "aos/util/file.h"
#include "gtest/gtest.h"
@@ -53,6 +54,7 @@
MessageBridgeParameterizedTest()
: config(aos::configuration::ReadConfig(
ArtifactPath(absl::StrCat("aos/network/", GetParam().config)))),
+ config_sha256(Sha256(config.span())),
pi1_boot_uuid_(UUID::Random()),
pi2_boot_uuid_(UUID::Random()) {
util::UnlinkRecursive(ShmBase("pi1"));
@@ -73,14 +75,16 @@
FLAGS_boot_uuid = pi2_boot_uuid_.ToString();
}
- void MakePi1Server() {
+ void MakePi1Server(std::string server_config_sha256 = "") {
OnPi1();
FLAGS_application_name = "pi1_message_bridge_server";
pi1_server_event_loop =
std::make_unique<aos::ShmEventLoop>(&config.message());
pi1_server_event_loop->SetRuntimeRealtimePriority(1);
- pi1_message_bridge_server =
- std::make_unique<MessageBridgeServer>(pi1_server_event_loop.get());
+ pi1_message_bridge_server = std::make_unique<MessageBridgeServer>(
+ pi1_server_event_loop.get(), server_config_sha256.size() == 0
+ ? config_sha256
+ : server_config_sha256);
}
void RunPi1Server(chrono::nanoseconds duration) {
@@ -118,8 +122,8 @@
pi1_client_event_loop =
std::make_unique<aos::ShmEventLoop>(&config.message());
pi1_client_event_loop->SetRuntimeRealtimePriority(1);
- pi1_message_bridge_client =
- std::make_unique<MessageBridgeClient>(pi1_client_event_loop.get());
+ pi1_message_bridge_client = std::make_unique<MessageBridgeClient>(
+ pi1_client_event_loop.get(), config_sha256);
}
void StartPi1Client() {
@@ -183,8 +187,8 @@
pi2_server_event_loop =
std::make_unique<aos::ShmEventLoop>(&config.message());
pi2_server_event_loop->SetRuntimeRealtimePriority(1);
- pi2_message_bridge_server =
- std::make_unique<MessageBridgeServer>(pi2_server_event_loop.get());
+ pi2_message_bridge_server = std::make_unique<MessageBridgeServer>(
+ pi2_server_event_loop.get(), config_sha256);
}
void RunPi2Server(chrono::nanoseconds duration) {
@@ -222,8 +226,8 @@
pi2_client_event_loop =
std::make_unique<aos::ShmEventLoop>(&config.message());
pi2_client_event_loop->SetRuntimeRealtimePriority(1);
- pi2_message_bridge_client =
- std::make_unique<MessageBridgeClient>(pi2_client_event_loop.get());
+ pi2_message_bridge_client = std::make_unique<MessageBridgeClient>(
+ pi2_client_event_loop.get(), config_sha256);
}
void RunPi2Client(chrono::nanoseconds duration) {
@@ -297,6 +301,8 @@
}
aos::FlatbufferDetachedBuffer<aos::Configuration> config;
+ std::string config_sha256;
+
const UUID pi1_boot_uuid_;
const UUID pi2_boot_uuid_;
@@ -1342,6 +1348,108 @@
pi1_remote_timestamp_thread.join();
}
+// Test that differing config sha256's result in no connection.
+TEST_P(MessageBridgeParameterizedTest, MismatchedSha256) {
+ // This is rather annoying to set up. We need to start up a client and
+ // server, on the same node, but get them to think that they are on different
+ // nodes.
+ //
+ // We need the client to not post directly to "/test" like it would in a
+ // real system, otherwise we will re-send the ping message... So, use an
+ // application specific map to have the client post somewhere else.
+ //
+ // To top this all off, each of these needs to be done with a ShmEventLoop,
+ // which needs to run in a separate thread... And it is really hard to get
+ // everything started up reliably. So just be super generous on timeouts and
+ // hope for the best. We can be more generous in the future if we need to.
+ //
+ // We are faking the application names by passing in --application_name=foo
+ OnPi1();
+
+ MakePi1Server("dummy sha256");
+ MakePi1Client();
+
+ // And build the app for testing.
+ MakePi1Test();
+ aos::Fetcher<ServerStatistics> pi1_server_statistics_fetcher =
+ pi1_test_event_loop->MakeFetcher<ServerStatistics>("/pi1/aos");
+ aos::Fetcher<ClientStatistics> pi1_client_statistics_fetcher =
+ pi1_test_event_loop->MakeFetcher<ClientStatistics>("/pi1/aos");
+
+ // Now do it for "raspberrypi2", the client.
+ OnPi2();
+ MakePi2Server();
+
+ // And build the app for testing.
+ MakePi2Test();
+ aos::Fetcher<ServerStatistics> pi2_server_statistics_fetcher =
+ pi2_test_event_loop->MakeFetcher<ServerStatistics>("/pi2/aos");
+ aos::Fetcher<ClientStatistics> pi2_client_statistics_fetcher =
+ pi2_test_event_loop->MakeFetcher<ClientStatistics>("/pi2/aos");
+
+ // Wait until we are connected, then send.
+
+ StartPi1Test();
+ StartPi2Test();
+ StartPi1Server();
+ StartPi1Client();
+ StartPi2Server();
+
+ {
+ MakePi2Client();
+
+ RunPi2Client(chrono::milliseconds(3050));
+
+ // Now confirm we are synchronized.
+ EXPECT_TRUE(pi1_server_statistics_fetcher.Fetch());
+ EXPECT_TRUE(pi1_client_statistics_fetcher.Fetch());
+ EXPECT_TRUE(pi2_server_statistics_fetcher.Fetch());
+ EXPECT_TRUE(pi2_client_statistics_fetcher.Fetch());
+
+ const ServerConnection *const pi1_connection =
+ pi1_server_statistics_fetcher->connections()->Get(0);
+ const ClientConnection *const pi1_client_connection =
+ pi1_client_statistics_fetcher->connections()->Get(0);
+ const ServerConnection *const pi2_connection =
+ pi2_server_statistics_fetcher->connections()->Get(0);
+ const ClientConnection *const pi2_client_connection =
+ pi2_client_statistics_fetcher->connections()->Get(0);
+
+ // Make sure one direction is disconnected with a bunch of connection
+ // attempts and failures.
+ EXPECT_EQ(pi1_connection->state(), State::DISCONNECTED);
+ EXPECT_EQ(pi1_connection->connection_count(), 0u);
+ EXPECT_GT(pi1_connection->invalid_connection_count(), 10u);
+
+ EXPECT_EQ(pi2_client_connection->state(), State::DISCONNECTED);
+ EXPECT_GT(pi2_client_connection->connection_count(), 10u);
+
+ // And the other direction is happy.
+ EXPECT_EQ(pi2_connection->state(), State::CONNECTED);
+ EXPECT_EQ(pi2_connection->connection_count(), 1u);
+ EXPECT_TRUE(pi2_connection->has_connected_since_time());
+ EXPECT_FALSE(pi2_connection->has_monotonic_offset());
+ EXPECT_TRUE(pi2_connection->has_boot_uuid());
+
+ EXPECT_EQ(pi1_client_connection->state(), State::CONNECTED);
+ EXPECT_EQ(pi1_client_connection->connection_count(), 1u);
+
+ VLOG(1) << aos::FlatbufferToJson(pi2_server_statistics_fetcher.get());
+ VLOG(1) << aos::FlatbufferToJson(pi1_server_statistics_fetcher.get());
+ VLOG(1) << aos::FlatbufferToJson(pi2_client_statistics_fetcher.get());
+ VLOG(1) << aos::FlatbufferToJson(pi1_client_statistics_fetcher.get());
+
+ StopPi2Client();
+ }
+
+ // Shut everyone else down
+ StopPi1Server();
+ StopPi1Client();
+ StopPi2Server();
+ StopPi1Test();
+ StopPi2Test();
+}
+
INSTANTIATE_TEST_SUITE_P(
MessageBridgeTests, MessageBridgeParameterizedTest,
::testing::Values(