mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-15 07:06:11 -04:00
282 lines
9.1 KiB
C++
282 lines
9.1 KiB
C++
// Copyright 2023 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 <arpa/inet.h>
|
||
#include <netinet/in.h>
|
||
#include <sys/socket.h>
|
||
#include <cerrno>
|
||
#include <cstdint>
|
||
#include <cstring>
|
||
#include <memory>
|
||
#include <utility>
|
||
|
||
#include <sdbus-c++/Error.h>
|
||
#include <sdbus-c++/IConnection.h>
|
||
#include <sdbus-c++/Types.h>
|
||
|
||
#include "absl/strings/substitute.h"
|
||
#include "internal/platform/implementation/linux/avahi.h"
|
||
#include "internal/platform/implementation/linux/dbus.h"
|
||
#include "internal/platform/implementation/linux/wifi_lan.h"
|
||
#include "internal/platform/implementation/linux/wifi_lan_server_socket.h"
|
||
#include "internal/platform/implementation/linux/wifi_lan_socket.h"
|
||
#include "internal/platform/implementation/linux/wifi_medium.h"
|
||
#include "internal/platform/implementation/wifi_lan.h"
|
||
#include "internal/platform/logging.h"
|
||
|
||
namespace nearby {
|
||
namespace linux {
|
||
WifiLanMedium::WifiLanMedium(sdbus::IConnection &system_bus)
|
||
: system_bus_(system_bus),
|
||
network_manager_(std::make_shared<NetworkManager>(system_bus)),
|
||
avahi_(std::make_shared<avahi::Server>(system_bus)) {}
|
||
|
||
bool WifiLanMedium::IsNetworkConnected() const {
|
||
auto state = network_manager_->getState();
|
||
return state == NetworkManager::kNMStateConnectedLocal ||
|
||
state == NetworkManager::kNMStateConnectedSite ||
|
||
state == NetworkManager::kNMStateConnectedGlobal;
|
||
}
|
||
|
||
std::optional<std::pair<std::string, std::string>> entry_group_key(
|
||
const NsdServiceInfo &nsd_service_info) {
|
||
auto name = nsd_service_info.GetServiceName();
|
||
if (name.empty()) {
|
||
NEARBY_LOGS(ERROR) << __func__ << ": service name cannot be empty";
|
||
return std::nullopt;
|
||
}
|
||
|
||
auto type = nsd_service_info.GetServiceType();
|
||
if (type.empty()) {
|
||
NEARBY_LOGS(ERROR) << __func__ << ": service type cannot be empty";
|
||
return std::nullopt;
|
||
}
|
||
|
||
return std::make_pair(std::move(name), std::move(type));
|
||
}
|
||
|
||
bool WifiLanMedium::StartAdvertising(const NsdServiceInfo &nsd_service_info) {
|
||
auto key = entry_group_key(nsd_service_info);
|
||
if (!key.has_value()) {
|
||
return false;
|
||
}
|
||
|
||
{
|
||
absl::ReaderMutexLock l(&entry_groups_mutex_);
|
||
if (entry_groups_.count(*key) == 1) {
|
||
NEARBY_LOGS(ERROR) << __func__
|
||
<< ": advertising is already active for this service";
|
||
return false;
|
||
}
|
||
}
|
||
|
||
auto txt_records_map = nsd_service_info.GetTxtRecords();
|
||
std::vector<std::vector<std::uint8_t>> txt_records(txt_records_map.size());
|
||
std::size_t i = 0;
|
||
|
||
for (auto [key, value] : nsd_service_info.GetTxtRecords()) {
|
||
std::string entry = absl::Substitute("$0=$1", key, value);
|
||
txt_records[i++] = std::vector<std::uint8_t>(entry.begin(), entry.end());
|
||
}
|
||
|
||
sdbus::ObjectPath entry_group_path;
|
||
try {
|
||
entry_group_path = avahi_->EntryGroupNew();
|
||
} catch (const sdbus::Error &e) {
|
||
DBUS_LOG_METHOD_CALL_ERROR(avahi_, "EntryGroupNew", e);
|
||
return false;
|
||
}
|
||
|
||
auto entry_group =
|
||
std::make_unique<avahi::EntryGroup>(system_bus_, entry_group_path);
|
||
|
||
try {
|
||
entry_group->AddService(
|
||
-1, // AVAHI_IF_UNSPEC. This will advertise on all possible interfaces
|
||
-1, // AVAHI_PROTO_UNSPED
|
||
0, nsd_service_info.GetServiceName(), nsd_service_info.GetServiceType(),
|
||
std::string(), std::string(), nsd_service_info.GetPort(), txt_records);
|
||
entry_group->Commit();
|
||
} catch (const sdbus::Error &e) {
|
||
NEARBY_LOGS(ERROR) << __func__ << ": Got error '" << e.getName()
|
||
<< "' with message '" << e.getMessage()
|
||
<< "' while adding service";
|
||
return false;
|
||
}
|
||
|
||
absl::MutexLock l(&entry_groups_mutex_);
|
||
entry_groups_.insert({*key, std::move(entry_group)});
|
||
|
||
return true;
|
||
}
|
||
|
||
bool WifiLanMedium::StopAdvertising(const NsdServiceInfo &nsd_service_info) {
|
||
auto key = entry_group_key(nsd_service_info);
|
||
if (!key.has_value()) {
|
||
return false;
|
||
}
|
||
|
||
absl::MutexLock l(&entry_groups_mutex_);
|
||
if (entry_groups_.count(*key) == 0) {
|
||
NEARBY_LOGS(ERROR) << __func__
|
||
<< ": Advertising is already inactive for this service.";
|
||
return false;
|
||
}
|
||
|
||
entry_groups_.erase(*key);
|
||
return true;
|
||
}
|
||
|
||
bool WifiLanMedium::StartDiscovery(
|
||
const std::string &service_type,
|
||
api::WifiLanMedium::DiscoveredServiceCallback callback) {
|
||
{
|
||
// set up and start a new service discovery browser
|
||
// using Avahi's D-Bus API if a browser for the
|
||
// specified service type doesn't already exist
|
||
|
||
absl::ReaderMutexLock l(&service_browsers_mutex_);
|
||
if (service_browsers_.count(service_type) != 0) {
|
||
auto &object = service_browsers_[service_type];
|
||
NEARBY_LOGS(ERROR) << __func__ << ": A service browser for service type "
|
||
<< service_type << " already exists at "
|
||
<< object->getObjectPath();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
try {
|
||
sdbus::ObjectPath browser_object_path =
|
||
avahi_->ServiceBrowserPrepare(-1, // AVAHI_IF_UNSPEC
|
||
-1, // AVAHI_PROTO_UNSPED
|
||
service_type, std::string(), 0);
|
||
NEARBY_VLOG(1)
|
||
<< __func__
|
||
<< ": Created a new org.freedesktop.Avahi.ServiceBrowser object at "
|
||
<< browser_object_path;
|
||
|
||
absl::MutexLock l(&service_browsers_mutex_);
|
||
service_browsers_.emplace(
|
||
service_type,
|
||
std::make_unique<avahi::ServiceBrowser>(
|
||
system_bus_, browser_object_path, std::move(callback), avahi_));
|
||
} catch (const sdbus::Error &e) {
|
||
DBUS_LOG_METHOD_CALL_ERROR(avahi_, "ServiceBrowserPrepare", e);
|
||
return false;
|
||
}
|
||
|
||
service_browsers_mutex_.ReaderLock();
|
||
auto &browser = service_browsers_[service_type];
|
||
service_browsers_mutex_.ReaderUnlock();
|
||
|
||
try {
|
||
NEARBY_VLOG(1) << __func__ << ": Starting service discovery for "
|
||
<< browser->getObjectPath();
|
||
browser->Start();
|
||
} catch (const sdbus::Error &e) {
|
||
DBUS_LOG_METHOD_CALL_ERROR(browser, "Start", e);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
bool WifiLanMedium::StopDiscovery(const std::string &service_type) {
|
||
absl::MutexLock l(&service_browsers_mutex_);
|
||
|
||
if (service_browsers_.count(service_type) == 0) {
|
||
NEARBY_LOGS(ERROR) << __func__ << ": Service type " << service_type
|
||
<< " has not been registered for discovery";
|
||
return false;
|
||
}
|
||
service_browsers_.erase(service_type);
|
||
|
||
return true;
|
||
}
|
||
|
||
std::unique_ptr<api::WifiLanSocket> WifiLanMedium::ConnectToService(
|
||
const std::string &ip_address, int port,
|
||
CancellationFlag *cancellation_flag) {
|
||
//creates a TCP socket, connects it to a specified
|
||
//IP address and port, and then wraps the socket’s file descriptor in an
|
||
//sdbus::UnixFd object to pass it to another component
|
||
|
||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||
if (sock < 0) {
|
||
NEARBY_LOGS(ERROR) << __func__
|
||
<< ": Error opening socket: " << std::strerror(errno);
|
||
return nullptr;
|
||
}
|
||
|
||
NEARBY_VLOG(1) << __func__ << ": Connecting to " << ip_address << ":"
|
||
<< port;
|
||
struct sockaddr_in addr;
|
||
addr.sin_addr.s_addr = inet_addr(ip_address.c_str());
|
||
addr.sin_family = AF_INET;
|
||
addr.sin_port = htons(port);
|
||
|
||
auto ret =
|
||
connect(sock, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr));
|
||
if (ret < 0) {
|
||
NEARBY_LOGS(ERROR) << __func__ << ": Error connecting to socket: "
|
||
<< std::strerror(errno);
|
||
return nullptr;
|
||
}
|
||
//creating a unix file descriptor representing the opened socket and passing it on
|
||
sdbus::UnixFd fd(sock);
|
||
return std::make_unique<WifiLanSocket>(std::move(fd));
|
||
}
|
||
|
||
std::unique_ptr<api::WifiLanServerSocket> WifiLanMedium::ListenForService(
|
||
int port) {
|
||
auto sock = socket(AF_INET, SOCK_STREAM, 0);
|
||
if (sock < 0) {
|
||
NEARBY_LOGS(ERROR) << __func__
|
||
<< ": Error opening socket: " << std::strerror(errno);
|
||
return nullptr;
|
||
}
|
||
|
||
struct sockaddr_in addr;
|
||
addr.sin_family = AF_INET;
|
||
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||
addr.sin_port = htons(port);
|
||
|
||
auto ret =
|
||
bind(sock, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr));
|
||
if (ret < 0) {
|
||
NEARBY_LOGS(ERROR) << __func__
|
||
<< ": Error binding to socket: " << std::strerror(errno);
|
||
return nullptr;
|
||
}
|
||
|
||
ret = listen(sock, 0);
|
||
if (ret < 0) {
|
||
NEARBY_LOGS(ERROR) << __func__ << ": Error listening on socket: "
|
||
<< std::strerror(errno);
|
||
return nullptr;
|
||
}
|
||
|
||
NEARBY_VLOG(1) << __func__ << "Listening for services on port " << port;
|
||
|
||
return std::make_unique<WifiLanServerSocket>(sock, network_manager_,
|
||
system_bus_);
|
||
}
|
||
|
||
absl::optional<std::pair<std::int32_t, std::int32_t>> GetDynamicPortRange() {
|
||
return absl::nullopt;
|
||
}
|
||
|
||
} // namespace linux
|
||
} // namespace nearby
|