style: adjust clang-format rules (#2186)

Co-authored-by: Vithorio Polten <reach@vithor.io>
This commit is contained in:
ReenigneArcher 2025-01-19 22:34:47 -05:00 committed by GitHub
commit c2420427b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
158 changed files with 8754 additions and 9994 deletions

View file

@ -7,16 +7,16 @@
// standard includes
#include <filesystem>
#include <string>
#include <utility>
// lib includes
#include <Simple-Web-Server/server_http.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/ssl/context_base.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <string>
#include <Simple-Web-Server/server_http.hpp>
// local includes
#include "config.h"
@ -36,6 +36,7 @@
#include "video.h"
using namespace std::literals;
namespace nvhttp {
namespace fs = std::filesystem;
@ -61,8 +62,7 @@ namespace nvhttp {
protected:
boost::asio::ssl::context context;
void
after_bind() override {
void after_bind() override {
if (verify) {
context.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once);
context.set_verify_callback([](int verified, boost::asio::ssl::verify_context &ctx) {
@ -73,17 +73,18 @@ namespace nvhttp {
}
// This is Server<HTTPS>::accept() with SSL validation support added
void
accept() override {
void accept() override {
auto connection = create_connection(*io_service, context);
acceptor->async_accept(connection->socket->lowest_layer(), [this, connection](const SimpleWeb::error_code &ec) {
auto lock = connection->handler_runner->continue_lock();
if (!lock)
if (!lock) {
return;
}
if (ec != SimpleWeb::error::operation_aborted)
if (ec != SimpleWeb::error::operation_aborted) {
this->accept();
}
auto session = std::make_shared<Session>(config.max_request_streambuf_size, connection);
@ -96,20 +97,22 @@ namespace nvhttp {
session->connection->socket->async_handshake(boost::asio::ssl::stream_base::server, [this, session](const SimpleWeb::error_code &ec) {
session->connection->cancel_timeout();
auto lock = session->connection->handler_runner->continue_lock();
if (!lock)
if (!lock) {
return;
if (!ec) {
if (verify && !verify(session->connection->socket->native_handle()))
this->write(session, on_verify_failed);
else
this->read(session);
}
else if (this->on_error)
if (!ec) {
if (verify && !verify(session->connection->socket->native_handle())) {
this->write(session, on_verify_failed);
} else {
this->read(session);
}
} else if (this->on_error) {
this->on_error(session->request, ec);
}
});
}
else if (this->on_error)
} else if (this->on_error) {
this->on_error(session->request, ec);
}
});
}
};
@ -148,8 +151,7 @@ namespace nvhttp {
REMOVE ///< Remove certificate
};
std::string
get_arg(const args_t &args, const char *name, const char *default_value = nullptr) {
std::string get_arg(const args_t &args, const char *name, const char *default_value = nullptr) {
auto it = args.find(name);
if (it == std::end(args)) {
if (default_value != NULL) {
@ -161,15 +163,13 @@ namespace nvhttp {
return it->second;
}
void
save_state() {
void save_state() {
pt::ptree root;
if (fs::exists(config::nvhttp.file_state)) {
try {
pt::read_json(config::nvhttp.file_state, root);
}
catch (std::exception &e) {
} catch (std::exception &e) {
BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what();
return;
}
@ -193,15 +193,13 @@ namespace nvhttp {
try {
pt::write_json(config::nvhttp.file_state, root);
}
catch (std::exception &e) {
} catch (std::exception &e) {
BOOST_LOG(error) << "Couldn't write "sv << config::nvhttp.file_state << ": "sv << e.what();
return;
}
}
void
load_state() {
void load_state() {
if (!fs::exists(config::nvhttp.file_state)) {
BOOST_LOG(info) << "File "sv << config::nvhttp.file_state << " doesn't exist"sv;
http::unique_id = uuid_util::uuid_t::generate().string();
@ -211,8 +209,7 @@ namespace nvhttp {
pt::ptree tree;
try {
pt::read_json(config::nvhttp.file_state, tree);
}
catch (std::exception &e) {
} catch (std::exception &e) {
BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what();
return;
@ -266,8 +263,7 @@ namespace nvhttp {
client_root = client;
}
void
add_authorized_client(const std::string &name, std::string &&cert) {
void add_authorized_client(const std::string &name, std::string &&cert) {
client_t &client = client_root;
named_cert_t named_cert;
named_cert.name = name;
@ -280,8 +276,7 @@ namespace nvhttp {
}
}
std::shared_ptr<rtsp_stream::launch_session_t>
make_launch_session(bool host_audio, const args_t &args) {
std::shared_ptr<rtsp_stream::launch_session_t> make_launch_session(bool host_audio, const args_t &args) {
auto launch_session = std::make_shared<rtsp_stream::launch_session_t>();
launch_session->id = ++session_id_counter;
@ -295,9 +290,15 @@ namespace nvhttp {
int x = 0;
std::string segment;
while (std::getline(mode, segment, 'x')) {
if (x == 0) launch_session->width = atoi(segment.c_str());
if (x == 1) launch_session->height = atoi(segment.c_str());
if (x == 2) launch_session->fps = atoi(segment.c_str());
if (x == 0) {
launch_session->width = atoi(segment.c_str());
}
if (x == 1) {
launch_session->height = atoi(segment.c_str());
}
if (x == 2) {
launch_session->fps = atoi(segment.c_str());
}
x++;
}
launch_session->unique_id = (get_arg(args, "uniqueid", "unknown"));
@ -312,7 +313,8 @@ namespace nvhttp {
auto corever = util::from_view(get_arg(args, "corever", "0"));
if (corever >= 1) {
launch_session->rtsp_cipher = crypto::cipher::gcm_t {
launch_session->gcm_key, false
launch_session->gcm_key,
false
};
launch_session->rtsp_iv_counter = 0;
}
@ -331,21 +333,18 @@ namespace nvhttp {
return launch_session;
}
void
remove_session(const pair_session_t &sess) {
void remove_session(const pair_session_t &sess) {
map_id_sess.erase(sess.client.uniqueID);
}
void
fail_pair(pair_session_t &sess, pt::ptree &tree, const std::string status_msg) {
void fail_pair(pair_session_t &sess, pt::ptree &tree, const std::string status_msg) {
tree.put("root.paired", 0);
tree.put("root.<xmlattr>.status_code", 400);
tree.put("root.<xmlattr>.status_message", status_msg);
remove_session(sess); // Security measure, delete the session when something went wrong and force a re-pair
}
void
getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) {
void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) {
if (sess.last_phase != PAIR_PHASE::NONE) {
fail_pair(sess, tree, "Out of order call to getservercert");
return;
@ -357,7 +356,7 @@ namespace nvhttp {
return;
}
std::string_view salt_view { sess.async_insert_pin.salt.data(), 32 };
std::string_view salt_view {sess.async_insert_pin.salt.data(), 32};
auto salt = util::from_hex<std::array<uint8_t, 16>>(salt_view, true);
@ -369,8 +368,7 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200);
}
void
clientchallenge(pair_session_t &sess, pt::ptree &tree, const std::string &challenge) {
void clientchallenge(pair_session_t &sess, pt::ptree &tree, const std::string &challenge) {
if (sess.last_phase != PAIR_PHASE::GETSERVERCERT) {
fail_pair(sess, tree, "Out of order call to clientchallenge");
return;
@ -393,7 +391,7 @@ namespace nvhttp {
decrypted.insert(std::end(decrypted), std::begin(sign), std::end(sign));
decrypted.insert(std::end(decrypted), std::begin(serversecret), std::end(serversecret));
auto hash = crypto::hash({ (char *) decrypted.data(), decrypted.size() });
auto hash = crypto::hash({(char *) decrypted.data(), decrypted.size()});
auto serverchallenge = crypto::rand(16);
std::string plaintext;
@ -413,8 +411,7 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200);
}
void
serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const std::string &encrypted_response) {
void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const std::string &encrypted_response) {
if (sess.last_phase != PAIR_PHASE::CLIENTCHALLENGE) {
fail_pair(sess, tree, "Out of order call to serverchallengeresp");
return;
@ -443,8 +440,7 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200);
}
void
clientpairingsecret(pair_session_t &sess, std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, pt::ptree &tree, const std::string &client_pairing_secret) {
void clientpairingsecret(pair_session_t &sess, std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, pt::ptree &tree, const std::string &client_pairing_secret) {
if (sess.last_phase != PAIR_PHASE::SERVERCHALLENGERESP) {
fail_pair(sess, tree, "Out of order call to clientpairingsecret");
return;
@ -458,8 +454,8 @@ namespace nvhttp {
return;
}
std::string_view secret { client_pairing_secret.data(), 16 };
std::string_view sign { client_pairing_secret.data() + secret.size(), client_pairing_secret.size() - secret.size() };
std::string_view secret {client_pairing_secret.data(), 16};
std::string_view sign {client_pairing_secret.data() + secret.size(), client_pairing_secret.size() - secret.size()};
auto x509 = crypto::x509(client.cert);
if (!x509) {
@ -486,8 +482,7 @@ namespace nvhttp {
// The client is now successfully paired and will be authorized to connect
add_authorized_client(client.name, std::move(client.cert));
}
else {
} else {
tree.put("root.paired", 0);
}
@ -495,22 +490,21 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200);
}
template <class T>
template<class T>
struct tunnel;
template <>
template<>
struct tunnel<SunshineHTTPS> {
static auto constexpr to_string = "HTTPS"sv;
};
template <>
template<>
struct tunnel<SimpleWeb::HTTP> {
static auto constexpr to_string = "NONE"sv;
};
template <class T>
void
print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
template<class T>
void print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
BOOST_LOG(debug) << "TUNNEL :: "sv << tunnel<T>::to_string;
BOOST_LOG(debug) << "METHOD :: "sv << request->method;
@ -529,9 +523,8 @@ namespace nvhttp {
BOOST_LOG(debug) << " [--] "sv;
}
template <class T>
void
not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
template<class T>
void not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request);
pt::ptree tree;
@ -549,9 +542,8 @@ namespace nvhttp {
response->close_connection_after_response = true;
}
template <class T>
void
pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
template<class T>
void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request);
pt::ptree tree;
@ -572,7 +564,7 @@ namespace nvhttp {
return;
}
auto uniqID { get_arg(args, "uniqueid") };
auto uniqID {get_arg(args, "uniqueid")};
args_t::const_iterator it;
if (it = args.find("phrase"); it != std::end(args)) {
@ -593,8 +585,7 @@ namespace nvhttp {
std::getline(std::cin, pin);
getservercert(ptr->second, tree, pin);
}
else {
} else {
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
system_tray::update_tray_require_pin();
#endif
@ -603,8 +594,7 @@ namespace nvhttp {
fg.disable();
return;
}
}
else if (it->second == "pairchallenge"sv) {
} else if (it->second == "pairchallenge"sv) {
tree.put("root.paired", 1);
tree.put("root.<xmlattr>.status_code", 200);
return;
@ -622,23 +612,19 @@ namespace nvhttp {
if (it = args.find("clientchallenge"); it != std::end(args)) {
auto challenge = util::from_hex_vec(it->second, true);
clientchallenge(sess_it->second, tree, challenge);
}
else if (it = args.find("serverchallengeresp"); it != std::end(args)) {
} else if (it = args.find("serverchallengeresp"); it != std::end(args)) {
auto encrypted_response = util::from_hex_vec(it->second, true);
serverchallengeresp(sess_it->second, tree, encrypted_response);
}
else if (it = args.find("clientpairingsecret"); it != std::end(args)) {
} else if (it = args.find("clientpairingsecret"); it != std::end(args)) {
auto pairingsecret = util::from_hex_vec(it->second, true);
clientpairingsecret(sess_it->second, add_cert, tree, pairingsecret);
}
else {
} else {
tree.put("root.<xmlattr>.status_code", 404);
tree.put("root.<xmlattr>.status_message", "Invalid pairing request");
}
}
bool
pin(std::string pin, std::string name) {
bool pin(std::string pin, std::string name) {
pt::ptree tree;
if (map_id_sess.empty()) {
return false;
@ -649,7 +635,9 @@ namespace nvhttp {
tree.put("root.paired", 0);
tree.put("root.<xmlattr>.status_code", 400);
tree.put(
"root.<xmlattr>.status_message", "Pin must be 4 digits, " + std::to_string(pin.size()) + " provided");
"root.<xmlattr>.status_message",
"Pin must be 4 digits, " + std::to_string(pin.size()) + " provided"
);
return false;
}
@ -672,11 +660,9 @@ namespace nvhttp {
auto &async_response = sess.async_insert_pin.response;
if (async_response.has_left() && async_response.left()) {
async_response.left()->write(data.str());
}
else if (async_response.has_right() && async_response.right()) {
} else if (async_response.has_right() && async_response.right()) {
async_response.right()->write(data.str());
}
else {
} else {
return false;
}
@ -686,9 +672,8 @@ namespace nvhttp {
return true;
}
template <class T>
void
serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
template<class T>
void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request);
int pair_status = 0;
@ -719,8 +704,7 @@ namespace nvhttp {
// For HTTP requests, use a placeholder MAC address that Moonlight knows to ignore.
if constexpr (std::is_same_v<SunshineHTTPS, T>) {
tree.put("root.mac", platf::get_mac_address(net::addr_to_normalized_string(local_endpoint.address())));
}
else {
} else {
tree.put("root.mac", "00:00:00:00:00:00");
}
@ -735,8 +719,7 @@ namespace nvhttp {
// support know to ignore this bogus address.
if (local_endpoint.address().is_v6() && !local_endpoint.address().to_v6().is_v4_mapped()) {
tree.put("root.LocalIP", "127.0.0.1");
}
else {
} else {
tree.put("root.LocalIP", net::addr_to_normalized_string(local_endpoint.address()));
}
@ -782,8 +765,7 @@ namespace nvhttp {
response->close_connection_after_response = true;
}
pt::ptree
get_all_clients() {
pt::ptree get_all_clients() {
pt::ptree named_cert_nodes;
client_t &client = client_root;
for (auto &named_cert : client.named_devices) {
@ -796,8 +778,7 @@ namespace nvhttp {
return named_cert_nodes;
}
void
applist(resp_https_t response, req_https_t request) {
void applist(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);
pt::ptree tree;
@ -825,12 +806,11 @@ namespace nvhttp {
}
}
void
launch(bool &host_audio, resp_https_t response, req_https_t request) {
void launch(bool &host_audio, resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);
pt::ptree tree;
bool revert_display_configuration { false };
bool revert_display_configuration {false};
auto g = util::fail_guard([&]() {
std::ostringstream data;
@ -848,7 +828,8 @@ namespace nvhttp {
args.find("rikey"s) == std::end(args) ||
args.find("rikeyid"s) == std::end(args) ||
args.find("localAudioPlayMode"s) == std::end(args) ||
args.find("appid"s) == std::end(args)) {
args.find("appid"s) == std::end(args)
) {
tree.put("root.resume", 0);
tree.put("root.<xmlattr>.status_code", 400);
tree.put("root.<xmlattr>.status_message", "Missing a required launch parameter");
@ -915,9 +896,7 @@ namespace nvhttp {
}
tree.put("root.<xmlattr>.status_code", 200);
tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme +
net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' +
std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
tree.put("root.gamesession", 1);
rtsp_stream::launch_session_raise(launch_session);
@ -926,8 +905,7 @@ namespace nvhttp {
revert_display_configuration = false;
}
void
resume(bool &host_audio, resp_https_t response, req_https_t request) {
void resume(bool &host_audio, resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);
pt::ptree tree;
@ -951,7 +929,8 @@ namespace nvhttp {
auto args = request->parse_query_string();
if (
args.find("rikey"s) == std::end(args) ||
args.find("rikeyid"s) == std::end(args)) {
args.find("rikeyid"s) == std::end(args)
) {
tree.put("root.resume", 0);
tree.put("root.<xmlattr>.status_code", 400);
tree.put("root.<xmlattr>.status_message", "Missing a required resume parameter");
@ -962,7 +941,7 @@ namespace nvhttp {
// Newer Moonlight clients send localAudioPlayMode on /resume too,
// so we should use it if it's present in the args and there are
// no active sessions we could be interfering with.
const bool no_active_sessions { rtsp_stream::session_count() == 0 };
const bool no_active_sessions {rtsp_stream::session_count() == 0};
if (no_active_sessions && args.find("localAudioPlayMode"s) != std::end(args)) {
host_audio = util::from_view(get_arg(args, "localAudioPlayMode"));
}
@ -999,16 +978,13 @@ namespace nvhttp {
}
tree.put("root.<xmlattr>.status_code", 200);
tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme +
net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' +
std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
tree.put("root.resume", 1);
rtsp_stream::launch_session_raise(launch_session);
}
void
cancel(resp_https_t response, req_https_t request) {
void cancel(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);
pt::ptree tree;
@ -1033,8 +1009,7 @@ namespace nvhttp {
display_device::revert_configuration();
}
void
appasset(resp_https_t response, req_https_t request) {
void appasset(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);
auto args = request->parse_query_string();
@ -1047,14 +1022,12 @@ namespace nvhttp {
response->close_connection_after_response = true;
}
void
setup(const std::string &pkey, const std::string &cert) {
void setup(const std::string &pkey, const std::string &cert) {
conf_intern.pkey = pkey;
conf_intern.servercert = cert;
}
void
start() {
void start() {
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
auto port_http = net::map_port(PORT_HTTP);
@ -1077,7 +1050,7 @@ namespace nvhttp {
// launch will store it in host_audio
bool host_audio {};
https_server_t https_server { config::nvhttp.cert, config::nvhttp.pkey };
https_server_t https_server {config::nvhttp.cert, config::nvhttp.pkey};
http_server_t http_server;
// Verify certificates after establishing connection
@ -1143,11 +1116,17 @@ namespace nvhttp {
https_server.default_resource["GET"] = not_found<SunshineHTTPS>;
https_server.resource["^/serverinfo$"]["GET"] = serverinfo<SunshineHTTPS>;
https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair<SunshineHTTPS>(add_cert, resp, req); };
https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) {
pair<SunshineHTTPS>(add_cert, resp, req);
};
https_server.resource["^/applist$"]["GET"] = applist;
https_server.resource["^/appasset$"]["GET"] = appasset;
https_server.resource["^/launch$"]["GET"] = [&host_audio](auto resp, auto req) { launch(host_audio, resp, req); };
https_server.resource["^/resume$"]["GET"] = [&host_audio](auto resp, auto req) { resume(host_audio, resp, req); };
https_server.resource["^/launch$"]["GET"] = [&host_audio](auto resp, auto req) {
launch(host_audio, resp, req);
};
https_server.resource["^/resume$"]["GET"] = [&host_audio](auto resp, auto req) {
resume(host_audio, resp, req);
};
https_server.resource["^/cancel$"]["GET"] = cancel;
https_server.config.reuse_address = true;
@ -1156,7 +1135,9 @@ namespace nvhttp {
http_server.default_resource["GET"] = not_found<SimpleWeb::HTTP>;
http_server.resource["^/serverinfo$"]["GET"] = serverinfo<SimpleWeb::HTTP>;
http_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair<SimpleWeb::HTTP>(add_cert, resp, req); };
http_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) {
pair<SimpleWeb::HTTP>(add_cert, resp, req);
};
http_server.config.reuse_address = true;
http_server.config.address = net::af_to_any_address_string(address_family);
@ -1165,8 +1146,7 @@ namespace nvhttp {
auto accept_and_run = [&](auto *http_server) {
try {
http_server->start();
}
catch (boost::system::system_error &err) {
} catch (boost::system::system_error &err) {
// It's possible the exception gets thrown after calling http_server->stop() from a different thread
if (shutdown_event->peek()) {
return;
@ -1177,8 +1157,8 @@ namespace nvhttp {
return;
}
};
std::thread ssl { accept_and_run, &https_server };
std::thread tcp { accept_and_run, &http_server };
std::thread ssl {accept_and_run, &https_server};
std::thread tcp {accept_and_run, &http_server};
// Wait for any event
shutdown_event->view();
@ -1190,24 +1170,21 @@ namespace nvhttp {
tcp.join();
}
void
erase_all_clients() {
void erase_all_clients() {
client_t client;
client_root = client;
cert_chain.clear();
save_state();
}
int
unpair_client(std::string uuid) {
int unpair_client(std::string uuid) {
int removed = 0;
client_t &client = client_root;
for (auto it = client.named_devices.begin(); it != client.named_devices.end();) {
if ((*it).uuid == uuid) {
it = client.named_devices.erase(it);
removed++;
}
else {
} else {
++it;
}
}