Files
op-pedal/NetworkManagerP2P/src/P2PManager.cpp
T
2024-08-08 12:53:02 -04:00

1442 lines
45 KiB
C++

#ifndef USE_NETWORKMANAGER
#include "P2PManager.hpp"
#include "ss.hpp"
#include <future>
#include <chrono>
#include <poll.h>
#include "WpaInterfaces.hpp"
#include "NMP2pSettings.hpp"
#include <thread>
#include <chrono>
#include <memory>
#include "DBusDispatcher.hpp"
#include "NetworkManagerInterfaces.hpp"
#include "DBusVariantHelper.hpp"
#include "SignalHandler.hpp"
static const char CONFIG_METHODS[] = "keypad";
static const char CONFIG_CONNECT_METHOD[] = "display";
enum class p2p_status_code
{
P2P_SC_SUCCESS = 0,
P2P_SC_FAIL_INFO_CURRENTLY_UNAVAILABLE = 1,
P2P_SC_FAIL_INCOMPATIBLE_PARAMS = 2,
P2P_SC_FAIL_LIMIT_REACHED = 3,
P2P_SC_FAIL_INVALID_PARAMS = 4,
P2P_SC_FAIL_UNABLE_TO_ACCOMMODATE = 5,
P2P_SC_FAIL_PREV_PROTOCOL_ERROR = 6,
P2P_SC_FAIL_NO_COMMON_CHANNELS = 7,
P2P_SC_FAIL_UNKNOWN_GROUP = 8,
P2P_SC_FAIL_BOTH_GO_INTENT_15 = 9,
P2P_SC_FAIL_INCOMPATIBLE_PROV_METHOD = 10,
P2P_SC_FAIL_REJECTED_BY_USER = 11,
P2P_SC_SUCCESS_DEFERRED = 12,
};
std::string InvitationStatusToString(int32_t status_code_)
{
if (status_code_ == -1)
{
return "FAIL";
}
p2p_status_code status_code = (p2p_status_code)status_code_;
#define STATUS_CODE_CASE(x) \
case p2p_status_code::x: \
return #x;
switch (status_code)
{
STATUS_CODE_CASE(P2P_SC_SUCCESS)
STATUS_CODE_CASE(P2P_SC_FAIL_INFO_CURRENTLY_UNAVAILABLE)
STATUS_CODE_CASE(P2P_SC_FAIL_INCOMPATIBLE_PARAMS)
STATUS_CODE_CASE(P2P_SC_FAIL_LIMIT_REACHED)
STATUS_CODE_CASE(P2P_SC_FAIL_INVALID_PARAMS)
STATUS_CODE_CASE(P2P_SC_FAIL_UNABLE_TO_ACCOMMODATE)
STATUS_CODE_CASE(P2P_SC_FAIL_PREV_PROTOCOL_ERROR)
STATUS_CODE_CASE(P2P_SC_FAIL_NO_COMMON_CHANNELS)
STATUS_CODE_CASE(P2P_SC_FAIL_UNKNOWN_GROUP)
STATUS_CODE_CASE(P2P_SC_FAIL_BOTH_GO_INTENT_15)
STATUS_CODE_CASE(P2P_SC_FAIL_INCOMPATIBLE_PROV_METHOD)
STATUS_CODE_CASE(P2P_SC_FAIL_REJECTED_BY_USER)
STATUS_CODE_CASE(P2P_SC_SUCCESS_DEFERRED)
default:
return "UNKNOWN_STATUS";
}
#undef STATUS_CODE_CASE
}
class P2PManagerImpl : public P2PManager
{
public:
P2PManagerImpl() : dispatcher(true) {}
virtual ~P2PManagerImpl();
bool Init() override;
void Run() override;
void Stop() override;
bool IsFinished() override;
void Wait() override;
bool ReloadRequested() override;
bool TerminatedNormally() override;
private:
using clock = std::chrono::steady_clock;
void OnServiceDiscoveryRequest(const std::map<std::string, sdbus::Variant> &sd_request);
void CancelDelayedConnectDevice();
void DelayedConnectDevice(const sdbus::ObjectPath&peer_object);
void OnInvitationResult(const sdbus::ObjectPath &objectPath, const std::map<std::string, sdbus::Variant> &invite_result);
sdbus::ObjectPath GetActivePersistentGroup();
void StartListen();
void SignalStop();
void SignalHangup();
void ExecuteOrderlyShutdown();
void ShutdownStage2();
void TurnOnWifi();
void UnInitialize();
void StartWaitUntilDone();
void PostWaitUntilDone();
void MaybeRemoveGroup();
void CancelMaybeRemoveGroup();
void MaybeCancel();
void StartGroup();
bool HasPersistentGroup();
void AddExistingInterfaces();
void RemoveExistingInterfaces();
void HandleInterfaceAdded(const std::string &objectPath);
void HandleInterfaceRemoved(const std::string &objectPath);
void InitWpaP2PDevice(WpaP2PDevice::ptr&&device);
void InitWpaP2PGoDevice(WpaP2PDevice::ptr&&device);
void ConnectDevice(const sdbus::ObjectPath &peer_object, bool enterRequest);
bool HasClient();
std::string UpnpServiceName();
void StartServiceBroadcast();
void StopServiceBroadcast();
std::atomic<bool> reloadRequested{false};
std::atomic<bool> signalStop{false};
std::atomic<bool> normalTermination{false};
bool signalStopStarted = false;
bool shutdownExecuted = false;
bool waited = false;
bool serviceBroadcastStarted = false;
DBusDispatcher dispatcher;
DBusDispatcher::PostHandle maybeCloseHandle = 0;
DBusDispatcher::PostHandle delayedConnectHandle = 0;
std::mutex postMutex;
std::atomic<bool> stopping;
std::vector<std::string> pendingAddedInterfaces;
using PostCallback = DBusDispatcher::PostCallback;
using PostHandle = DBusDispatcher::PostHandle;
void Post(PostCallback &&fn);
WpaSupplicant::ptr wpaSupplicant;
WpaP2PDevice::ptr p2pDevice;
WpaP2PDevice::ptr p2pGoDevice;
WpaWPS::ptr wpsDevice;
WpaGroup::ptr wpaGroup;
std::vector<WpaPeer::ptr> connectedPeers;
sdbus::ObjectPath persistentGroupPath;
P2pSettings settings{};
std::string oldWpasDebugLevel;
clock::time_point stage2Timeout;
clock::time_point waitUntilDoneTimeout;
};
P2PManager::ptr P2PManager::Create()
{
return std::make_unique<P2PManagerImpl>();
}
P2PManagerImpl::~P2PManagerImpl()
{
CancelDelayedConnectDevice();
Stop();
}
static const char hexDigits[] = "0123456789abcdef";
static std::string AddressToString(const std::vector<uint8_t> &address)
{
std::stringstream ss;
bool firstTime = true;
for (auto byte : address)
{
if (!firstTime)
{
ss << ":";
}
firstTime = false;
ss << hexDigits[(byte >> 4) & 0x0F] << hexDigits[byte & 0x0F];
}
return ss.str();
}
void P2PManagerImpl::OnInvitationResult(const sdbus::ObjectPath &objectPath, const std::map<std::string, sdbus::Variant> &invite_result)
{
int32_t status;
try
{
status = invite_result.at("status");
}
catch (const std::exception &)
{
status = -1;
}
std::string strStatus = InvitationStatusToString(status);
if (status == 0)
{
LogDebug(objectPath, "OnInvitationResult", strStatus);
}
else
{
LogInfo(objectPath, "OnInvitationResult", strStatus);
}
}
void P2PManagerImpl::RemoveExistingInterfaces()
{
try
{
while (true)
{
if (!this->wpaSupplicant)
break;
auto interfaces = this->wpaSupplicant->Interfaces();
if (interfaces.size() == 0)
break;
wpaSupplicant->RemoveInterface(interfaces[0]);
}
}
catch (const std::exception &e)
{
LogWarning("P2PManagerImpl", "RemoveExistingInterfaces", e.what());
}
}
bool P2PManagerImpl::Init()
{
settings.Load();
if (!settings.valid())
{
return false;
}
LogInfo(
"P2PManager",
"Init",
SS(
"Configuration:"
<< " " << settings.country()
<< " " << settings.wlan()
<< " " << settings.device_name()
<< " " << settings.web_server_port()
<< " " << settings.listen_channel()
<< " " << settings.op_channel()));
SignalHandler::HandleSignal(
SignalHandler::Interrupt,
[this]()
{
this->normalTermination = true; // don't restart service
this->SignalStop();
});
SignalHandler::HandleSignal(
SignalHandler::Terminate,
[this]()
{
this->normalTermination = true; // don't restart service
this->SignalStop();
});
SignalHandler::HandleSignal(
SignalHandler::Hangup,
[this]()
{
this->normalTermination = false; // don't restart service
this->SignalStop(); // we can't restart, but at lease we can do an orderly shutdown.
// disable this, because sdbus::dispatcher doesn't seem to be able to handle a restart.
// this->SignalHangup();
});
#ifdef DEBUG
dispatcher.Connection().setMethodCallTimeout(500 * 1000 * 1000);
#endif
for (int retry = 0; retry < 4; ++retry) // defend against slow starup of wpa_supplicant
{
try
{
this->wpaSupplicant = WpaSupplicant::Create(dispatcher.Connection());
this->wpaSupplicant->DebugLevel();
if (retry != 0)
{
LogInfo("P2PManager", "Init", "wpa_supplicant DBus connection established.");
}
break;
}
catch (std::exception &e)
{
if (retry == 0)
{
LogInfo("P2PManager", "Init", "Waiting for wpa_supplicant DBus connection.");
}
this->wpaSupplicant = nullptr;
std::this_thread::sleep_for(std::chrono::seconds(5));
}
}
if (!this->wpaSupplicant)
{
throw std::runtime_error("wpa_supplicant service is not reachable by DBus.");
}
std::string wpaLogLevel = "info";
switch (GetDBusLogLevel())
{
default:
case DBusLogLevel::Info:
wpaLogLevel = "info";
break;
case DBusLogLevel::Debug:
wpaLogLevel = "debug";
break;
case DBusLogLevel::Warning:
wpaLogLevel = "warning";
break;
case DBusLogLevel::Error:
wpaLogLevel = "error";
break;
case DBusLogLevel::Trace:
wpaLogLevel = "debug";
break;
}
oldWpasDebugLevel = wpaSupplicant->DebugLevel();
wpaSupplicant->DebugLevel(wpaLogLevel);
TurnOnWifi();
this->wpaSupplicant->OnInterfaceAdded.add(
[this](const sdbus::ObjectPath &path, const std::map<std::string, sdbus::Variant> &properties)
{
this->HandleInterfaceAdded(path);
});
this->wpaSupplicant->OnInterfaceRemoved.add(
[this](const sdbus::ObjectPath &path)
{
this->HandleInterfaceRemoved(path);
});
RemoveExistingInterfaces();
if (wpaSupplicant->Interfaces().size() == 0)
{
std::map<std::string, sdbus::Variant> createArgs;
createArgs["Ifname"] = settings.wlan();
createArgs["Driver"] = settings.driver();
createArgs["ConfigFile"] = settings.wpas_config_file();
wpaSupplicant->CreateInterface(createArgs);
}
return true;
}
void P2PManagerImpl::AddExistingInterfaces()
{
if (!this->p2pDevice)
{
for (const auto &objectPath : this->wpaSupplicant->Interfaces())
{
HandleInterfaceAdded(objectPath);
}
}
}
void P2PManagerImpl::HandleInterfaceRemoved(const std::string &objectPath)
{
bool deleted = false;
// removal from pending list can be done in a handler.
for (auto i = this->pendingAddedInterfaces.begin(); i != pendingAddedInterfaces.end(); ++i)
{
if (*i == objectPath)
{
pendingAddedInterfaces.erase(i);
return;
}
}
// removal of existing objects needs to be posted.
Post([this, objectPath]()
{
if (this->p2pDevice && this->p2pDevice->getObjectPath() == objectPath)
{
CancelDelayedConnectDevice();
this->p2pDevice = nullptr;
this->wpsDevice = nullptr;
dispatcher.SignalStop(
[this]()
{
UnInitialize(); // shut down, wpa_supplicant has stopped.
});
}
if (this->p2pGoDevice && this->p2pGoDevice->getObjectPath() == objectPath)
{
this->wpaGroup = nullptr;
this->p2pGoDevice = nullptr;
StartListen();
} });
}
void P2PManagerImpl::HandleInterfaceAdded(const std::string &objectPath)
{
// Create the objects in the callback, but defer processing.
// Guard against the interface being synchronously removed before the async post handler excecutes./
this->pendingAddedInterfaces.push_back(objectPath.c_str());
{
std::string msg = SS("Device added: " << objectPath.c_str());
LogDebug("P2PManagerImpl", "HandleInterfaceAdded", msg);
}
Post([this,
objectPath]() mutable
{
// check to see whether the new interface was removed from pending list.
auto found = std::find(pendingAddedInterfaces.begin(),pendingAddedInterfaces.end(),objectPath);
if (found != pendingAddedInterfaces.end())
{
pendingAddedInterfaces.erase(found);
try {
auto p2pDevice = WpaP2PDevice::Create(dispatcher.Connection(), objectPath);
LogDebug(objectPath,"HandleInterfaceAdded",SS("Role: " << p2pDevice->Role()));
if (p2pDevice->Role() == "device" && ((!this->p2pDevice) || this->p2pDevice->getObjectPath() != objectPath))
{
LogDebug("P2PManagerImpl", "HandleInterfaceAdded", "P2P device found.");
InitWpaP2PDevice(std::move(p2pDevice));
}
else if (p2pDevice->Role() == "GO" && ((!this->p2pGoDevice) || this->p2pGoDevice->getObjectPath() != objectPath))
{
LogDebug("P2PManagerImpl", "HandleInterfaceAdded", "P2P GO group interface found.");
InitWpaP2PGoDevice(std::move(p2pDevice));
}
} catch (const std::exception&e)
{
LogError("P2PManagerImpl","HandleInterfaceAdded",SS("Failed to add " << objectPath.c_str() << " (" << e.what() << ")"));
}
} });
}
void P2PManagerImpl::InitWpaP2PGoDevice(WpaP2PDevice::ptr&&device)
{
this->p2pGoDevice = std::move(device);
if (IsDBusLoggingEnabled(DBusLogLevel::Trace))
{
auto deviceConfig = p2pGoDevice->P2PDeviceConfig();
std::stringstream s;
s << deviceConfig;
LogTrace("P2PManagerImpl", "InitWpaP2PGoDevice", SS("GO Device config: " << s.str()));
}
p2pGoDevice->ConfigMethods(CONFIG_METHODS);
LogTrace("P2PManagerImpl", "InitWpaP2PGoDevice", SS("ConfigMethods: " << p2pGoDevice->ConfigMethods()));
if (IsDBusLoggingEnabled(DBusLogLevel::Trace))
{
auto deviceConfig = p2pGoDevice->P2PDeviceConfig();
std::stringstream s;
s << deviceConfig;
LogTrace("P2PManager", "InitWpaP2PGoDevice", SS("P2PDeviceConfig: " << s.str()));
}
p2pGoDevice->OnInvitationResult.add(
[this](const std::map<std::string, sdbus::Variant> &invite_result)
{
OnInvitationResult(p2pDevice->getObjectPath(), invite_result);
});
p2pGoDevice->OnStaAuthorized.add(
[this](const std::string &name)
{
LogInfo(
p2pGoDevice->getObjectPath(),
"OnStationAuthorized",
SS("Station authorized: " << name));
});
p2pGoDevice->OnStaDeauthorized.add(
[this](const std::string &name)
{
LogInfo(
p2pGoDevice->getObjectPath(),
"OnStationDeauthorized",
SS("Station deauthorized: " << name));
});
p2pGoDevice->OnWpsFailed.add(
[this](const std::string &name, const std::map<std::string, sdbus::Variant> &args)
{
{
std::stringstream ss;
ss << name << " " << args << " ";
LogError("P2PManagerImp", "OnWpsFailed", ss.str());
}
try
{
this->p2pGoDevice->Cancel();
}
catch (const std::exception &)
{
}
MaybeRemoveGroup();
});
p2pGoDevice->OnGONegotiationFailure.add(
[this](const std::map<std::string, sdbus::Variant> &info)
{
std::stringstream ss;
ss << info;
LogError(p2pGoDevice->getObjectPath().c_str(),"OnGoNegotiationFailure",ss.str()); });
p2pGoDevice->OnGroupFormationFailure.add(
[this](const std::string &message)
{
std::stringstream ss;
ss << message;
LogError(p2pGoDevice->getObjectPath().c_str(), "OnGroupFormationFailure", ss.str());
MaybeRemoveGroup();
});
p2pGoDevice->OnStationAdded.add(
[this](const sdbus::ObjectPath &path, const std::map<std::string, sdbus::Variant> &properties) {
});
p2pGoDevice->OnStationRemoved.add(
[this](const sdbus::ObjectPath &)
{
MaybeRemoveGroup();
});
LogTrace("P2PManagerImpl", "InitWpaP2PGoDevice", SS("GoCountry: " << p2pGoDevice->Country()));
p2pGoDevice->OnProvisionDiscoveryRequestDisplayPin.add(
[this](const sdbus::ObjectPath &peer_object, const std::string &pin)
{
LogTrace("P2PManagerImpl", "InitWpaP2PGoDevice",
SS("GO OnProvisionDiscoveryRequestDisplayPin - " << peer_object.c_str() << " pin: " << pin));
});
}
bool P2PManagerImpl::HasClient()
{
return this->p2pDevice->Stations().size() != 0;
}
void P2PManagerImpl::CancelDelayedConnectDevice()
{
if (delayedConnectHandle) {
dispatcher.CancelPost(delayedConnectHandle);
delayedConnectHandle = 0;
}
}
void P2PManagerImpl::DelayedConnectDevice(const sdbus::ObjectPath&peer_object)
{
delayedConnectHandle = 0;
if (dispatcher.IsStopping()) return; // brief race window during shutdown.
if (!p2pGoDevice)
{
ConnectDevice(peer_object,false);
} else {
delayedConnectHandle = dispatcher.PostDelayed(
std::chrono::milliseconds(500),
[this,peer_object]()
{
DelayedConnectDevice(peer_object);
}
);
}
}
void P2PManagerImpl::ConnectDevice(const sdbus::ObjectPath &peer_object, bool enterRequest)
{
// The implemented behaviour: if a second connection request comes in, disconnect the first connection
// before establishing the second connection.
//
// I've tried every way I can think of to allow a second group member, without success.
// if we received a connection request with a a currently active group,
// disconnect the group, asynchronously, waiting for the group intefrace to close, and THEN issue the connect call.
#if 1
CancelDelayedConnectDevice();
if (this->p2pGoDevice)
{
try {
this->p2pGoDevice->Disconnect();
} catch (const std::exception &e)
{
LogDebug("P2PManager","ConnectDevice",SS("Disconnect failed. " << e.what()));
try {
this->wpaSupplicant->RemoveInterface(p2pGoDevice->getObjectPath()); // draconian!
} catch (const std::exception&e)
{
LogDebug("P2PManager","ConnectDevice",SS("RemoveInterface failed. " << e.what()));
}
}
DelayedConnectDevice(peer_object);
return;
}
#endif
#if 1
try
{
MaybeCancel();
if (!this->p2pGoDevice)
{
auto groups = p2pDevice->PersistentGroups();
if (false && groups.size() >= 2)
{
// p2pDevice, groups[1] -> OnInvitationResult(P2P_SC_FAIL_UNKNOWN_GROUP)
auto groupPath = groups[1];
// start a new group using an existing persistent group.
std::map<std::string, sdbus::Variant> inviteArgs;
inviteArgs["peer"] = peer_object;
inviteArgs["persistent_group_object"] = groupPath;
p2pDevice->Invite(inviteArgs);
} else {
// 2nd connection, join = true: EAP auth failure, PIN_NEEDED.
// 2nd connection, join = false: immedate failure with device busy.
std::map<std::string, sdbus::Variant> connectArgs;
connectArgs["peer"] = peer_object;
connectArgs["persistent"] = true;
connectArgs["frequency"] = (int32_t)this->settings.op_frequency();
connectArgs["go_intent"] = (int32_t)this->settings.p2p_go_intent();
connectArgs["wps_method"] = "display",
connectArgs["pin"] = this->settings.pin();
connectArgs["authorize_only"] = false;
connectArgs["join"] = false; //false; // HasClient();
this->p2pDevice->Connect(connectArgs);
// // start a new group.
// std::map<std::string, sdbus::Variant> inviteArgs;
// inviteArgs["peer"] = peer_object;
// sdbus::ObjectPath groupPath;
// auto persistentGroups = p2pDevice->PersistentGroups();
// if (persistentGroups.size() >= 2)
// {
// groupPath = persistentGroups[0];
// // inviteArgs["persistent_group_object"] = groupPath;
// p2pDevice->Invite(inviteArgs);
// } else {
// throw std::runtime_error("NOT IMPLEMENTED");
// }
}
}
else
{
// p2pDevice, Connect,join=true. Bizarre extra group devices. Connected device gets orphaned. borked.
// std::map<std::string, sdbus::Variant> connectArgs;
// connectArgs["peer"] = peer_object;
// connectArgs["persistent"] = true;
// connectArgs["frequency"] = (int32_t)this->settings.op_frequency();
// connectArgs["go_intent"] = (int32_t)this->settings.p2p_go_intent();
// connectArgs["wps_method"] = "display",
// connectArgs["pin"] = this->settings.pin();
// connectArgs["authorize_only"] = false;
// connectArgs["join"] = true; // HasClient();
// this->p2pDevice->Connect(connectArgs);
// join the existing group.
// 2pGoDevice Join("") -> NO_INFO error.
// p2pDevice Join("") -> Failed to join an active group. looking for wlan0 interfacpe when it should be looking for p2p-wlan-N
// p2pDevice join("GetActivePersistentGroup") -> Failed to create interface p2p-wlan0-2: -16 (Device or resource busy)
std::map<std::string, sdbus::Variant> inviteArgs;
inviteArgs["peer"] = peer_object;
// inviteArgs["persistent_group_object"] = GetActivePersistentGroup();
p2pGoDevice->Invite(inviteArgs);
}
}
catch (const std::exception &e)
{
LogError("P2pManager", "ConnectDevice", e.what());
return;
}
#else
std::map<std::string, sdbus::Variant> inviteArgs;
inviteArgs["peer"] = peer_object;
inviteArgs["persistent_group_object"] = this->persistentGroupPath;
p2pDevice->Invite(inviteArgs);
#endif
}
static bool ValidateSetting(const std::map<std::string, sdbus::Variant> &properties, const std::string &key, int32_t value)
{
auto iter = properties.find(key);
if (iter == properties.end())
return false;
if (!iter->second.containsValueOfType<int32_t>())
return false;
return iter->second.get<int32_t>() == value;
}
static bool ValidateSetting(const std::map<std::string, sdbus::Variant> &properties, const std::string &key, const std::string &value)
{
auto iter = properties.find(key);
if (iter == properties.end())
return false;
if (!iter->second.containsValueOfType<std::string>())
return false;
return iter->second.get<std::string>() == value;
}
bool P2PManagerImpl::HasPersistentGroup()
{
auto persistentGroups = p2pDevice->PersistentGroups();
return persistentGroups.size() != 0;
}
void P2PManagerImpl::InitWpaP2PDevice(WpaP2PDevice::ptr&&device)
{
p2pDevice = std::move(device);
this->wpsDevice = WpaWPS::Create(dispatcher.Connection(), p2pDevice->getObjectPath());
p2pDevice->Country(settings.country());
std::map<std::string, sdbus::Variant> deviceConfig;
deviceConfig["DeviceName"] = SS(settings.device_name());
deviceConfig["PrimaryDeviceType"] = std::vector<uint8_t>{0x00, 0x01, 0x00, 0x50, 0xF2, 0x04, 0x00, 0x01};
deviceConfig["GOIntent"] = (uint32_t)15;
deviceConfig["ListenChannel"] = (uint32_t)settings.listen_channel();
deviceConfig["ListenRegClass"] = (uint32_t)settings.listen_reg_class();
deviceConfig["OperChannel"] = (uint32_t)settings.op_channel();
deviceConfig["OperRegClass"] = (uint32_t)settings.op_reg_class();
//
// deviceConfig["ListenChannel"] = (uint32_t)settings.listen_channel();
deviceConfig["SsidPostfix"] = SS("-" << settings.device_name());
deviceConfig["IpAddrGo"] = std::vector<uint8_t>{172, 23, 0, 2};
deviceConfig["IpAddrMask"] = std::vector<uint8_t>{255, 255, 0, 0};
deviceConfig["IpAddrStart"] = std::vector<uint8_t>{172, 23, 0, 4};
deviceConfig["IpAddrEnd"] = std::vector<uint8_t>{172, 23, 0, 9};
// deviceConfig["IntraBss"] = false;
deviceConfig["PersistentReconnect"] = true;
// per_sta_psk
//p2pDevice->MeshFwding("0");
p2pDevice->P2PDeviceConfig(deviceConfig);
if (IsDBusLoggingEnabled(DBusLogLevel::Trace))
{
auto deviceConfig = p2pDevice->P2PDeviceConfig();
std::stringstream s;
s << deviceConfig;
LogTrace("P2PManager", "InitWpaP2PDevice", SS("P2PDeviceConfig: " << s.str()));
}
LogDebug("P2PManager", "InitWpaP2PDevice", SS("ConfigMethods" << p2pDevice->ConfigMethods()));
p2pDevice->ConfigMethods(CONFIG_METHODS);
// p2pDevice->P2pGoHt40(settings.p2p_go_ht40() ? "1" : "0");
std::string groupName = p2pDevice->Group().c_str();
auto t = wpsDevice->DeviceType();
wpsDevice->DeviceName(settings.device_name());
wpsDevice->Manufacturer(settings.manufacturer());
wpsDevice->ModelName(settings.model_name());
wpsDevice->ModelNumber(settings.model_number());
wpsDevice->SerialNumber(settings.serial_number());
wpsDevice->DeviceType(settings.device_type());
p2pDevice->OnServiceDiscoveryRequest.add(
[this](const std::map<std::string, sdbus::Variant> &sd_request)
{
OnServiceDiscoveryRequest(sd_request);
});
p2pDevice->OnInvitationResult.add(
[this](const std::map<std::string, sdbus::Variant> &invite_result)
{
OnInvitationResult(p2pDevice->getObjectPath(), invite_result);
});
p2pDevice->OnGroupStarted.add(
[this](const std::map<std::string, sdbus::Variant> &args)
{
try
{
LogInfo("P2PManager", "OnGroupStarted", "GroupStarted");
const sdbus::Variant &vGroup = args.at("group_object");
sdbus::ObjectPath group = vGroup;
if (!group.empty())
{
wpaGroup = WpaGroup::Create(dispatcher.Connection(), group);
wpaGroup->OnPeerJoined.add(
[this](const sdbus::ObjectPath &path)
{
Post(
[this, path]()
{
try
{
LogDebug("P2PManager", "OnPeerJoined", path.c_str());
this->connectedPeers.push_back(WpaPeer::Create(dispatcher.Connection(), path));
}
catch (const std::exception &e)
{
LogError("P2PManager", "OnPeerJoined", e.what());
}
});
});
wpaGroup->OnPeerDisconnected.add(
[this](const sdbus::ObjectPath &path)
{
Post(
[this, path]()
{
LogDebug("P2PManager","OnPeerDisconnected",path.c_str());
this->connectedPeers.push_back(WpaPeer::Create(dispatcher.Connection(),path));
for (auto iter = this->connectedPeers.begin(); iter != this->connectedPeers.end(); iter++)
{
if ((*iter)->getObjectPath() == path)
{
this->connectedPeers.erase(iter);
break;
}
} });
});
}
}
catch (const std::exception &e)
{
LogError("InitWpaP2PDevice", "OnGroupAdded", e.what());
}
});
p2pDevice->OnGONegotiationFailure.add(
[this](const std::map<std::string, sdbus::Variant> &info)
{
std::stringstream ss;
ss << info;
LogError(p2pDevice->getObjectPath().c_str(), "OnGoNegotiationFailure", ss.str());
});
p2pDevice->OnGroupFormationFailure.add(
[this](const std::string &reason)
{
LogError(p2pDevice->getObjectPath().c_str(), "OnGroupFormationFailure", reason);
});
p2pDevice->OnGroupFinished.add([this](const std::map<std::string, sdbus::Variant> &properties)
{
LogInfo("P2PManager","OnGroupFinished","");
wpaGroup = nullptr; });
p2pDevice->OnGroupFormationFailure.add(
[this](const std::string &reason)
{
Post([this]()
{
if (this->p2pDevice)
{
try {
StartListen();
} catch (const std::exception&e)
{
LogError("P2PManager","OnGroupFormationFailure", SS("Listen failed: " << e.what()));
}
} });
});
p2pDevice->OnGONegotiationFailure.add(
[this](const std::map<std::string, sdbus::Variant> &info)
{
Post([this]()
{
if (this->p2pDevice)
{
try {
StartListen();
} catch (const std::exception&e)
{
LogError("P2PManager","OnGONegotiationFailure", SS("Listen failed: " << e.what()));
}
} });
});
p2pDevice->OnProvisionDiscoveryEnterPin.add(
[this](const sdbus::ObjectPath &peer_object)
{
// old android device picks wrong method. pretend they asked for OnProvisionDiscoveryRequestDisplayPin instead
LogDebug(p2pDevice->getObjectPath(), "OnProvisionDiscoveryEnterPin",
peer_object.c_str());
Post(
[this, peer_object]()
{
if (!this->p2pDevice)
return;
ConnectDevice(peer_object, true);
});
});
p2pDevice->OnProvisionDiscoveryRequestDisplayPin.add(
[this](const sdbus::ObjectPath &peer_object, const std::string &pin)
{
LogDebug(p2pDevice->getObjectPath(), "OnProvisionDiscoveryRequestDisplayPin",
SS(peer_object.c_str()));
Post(
[this, peer_object]()
{
if (!this->p2pDevice)
return;
ConnectDevice(peer_object, false);
});
});
if (!HasPersistentGroup() || settings.auth_changed())
{
if (settings.auth_changed())
{
LogInfo("P2PManagerImpl", "InitWpaP2PDevice", "Auth has changed. Resetting persistent groups");
}
p2pDevice->RemoveAllPersistentGroups();
std::map<std::string, sdbus::Variant> persistentGroupAddParams;
persistentGroupAddParams["bssid"] = settings.bssid();
persistentGroupAddParams["ssid"] = settings.device_name();
persistentGroupAddParams["psk"] = settings.pin();
persistentGroupAddParams["mode"] = (uint32_t)3; //"3";
sdbus::ObjectPath persistentGroupPath = p2pDevice->AddPersistentGroup(persistentGroupAddParams);
this->persistentGroupPath = persistentGroupPath;
if (settings.auth_changed())
{
settings.auth_changed(false);
settings.Save();
}
}
else
{
this->persistentGroupPath = p2pDevice->PersistentGroups()[0];
}
StartListen();
StartServiceBroadcast();
LogInfo(this->p2pDevice->getObjectPath(), "InitWpaP2PDevice", "Listening");
dispatcher.Post(
[this]()
{
// StartGroup();
});
}
void P2PManagerImpl::Post(PostCallback &&fn)
{
dispatcher.Post(std::move(fn));
}
void P2PManagerImpl::Run()
{
dispatcher.Run();
}
void P2PManagerImpl::ExecuteOrderlyShutdown()
{
dispatcher.Post(
[this]()
{
if (this->shutdownExecuted)
return;
this->shutdownExecuted = true;
StopServiceBroadcast();
CancelMaybeRemoveGroup();
if (this->p2pDevice)
{
// no parameters cancels Extended listen.
std::map<std::string, sdbus::Variant> listenArgs;
this->p2pDevice->ExtendedListen(listenArgs);
}
if (this->p2pGoDevice)
{
try
{
this->p2pGoDevice->Disconnect();
}
catch (const std::exception &e)
{
LogWarning("P2PManagerImpl", "Stop", SS("Can't disconnect " << this->p2pGoDevice->Ifname()));
}
}
stage2Timeout = clock::now() + std::chrono::duration_cast<clock::duration>(std::chrono::milliseconds(1500));
ShutdownStage2();
});
}
void P2PManagerImpl::ShutdownStage2()
{
if (clock::now() > stage2Timeout)
{
LogError("P2PManagerImpl", "ShutdownStage2", "Stage2 shutdown timeout exipred.");
}
else
{
// wait until the Disconnect has completed.
if (this->p2pGoDevice)
{
dispatcher.PostDelayed(std::chrono::milliseconds(50),
[this]()
{
ShutdownStage2();
});
return;
}
}
LogDebug("P2PManagerImpl", "ShutdownStage2", "Sutdown Stage2 complete.");
if (this->p2pDevice)
{
this->wpaSupplicant->RemoveInterface(p2pDevice->getObjectPath());
}
StartWaitUntilDone();
}
void P2PManagerImpl::Stop()
{
SignalStop();
Wait();
}
bool P2PManagerImpl::IsFinished()
{
return dispatcher.IsFinished();
}
bool P2PManagerImpl::ReloadRequested()
{
return this->reloadRequested;
}
void P2PManagerImpl::Wait()
{
if (!waited)
{
waited = true;
dispatcher.Wait();
LogInfo("P2PManager", "Stop", "Shutdown complete.");
}
}
void P2PManagerImpl::StartWaitUntilDone()
{
this->waitUntilDoneTimeout = clock::now() + std::chrono::duration_cast<clock::duration>(std::chrono::milliseconds(1000));
PostWaitUntilDone();
}
void P2PManagerImpl::PostWaitUntilDone()
{
if (clock::now() > this->waitUntilDoneTimeout)
{
LogError("P2PManager", "PostWaitUntilDone", "WaitUntilDone timeout expired.");
}
else
{
auto interfaces = this->wpaSupplicant->Interfaces();
if (interfaces.size() > 0)
{
dispatcher.PostDelayed(
std::chrono::milliseconds(100),
[this]()
{
PostWaitUntilDone();
});
}
}
// the dispatcher won't exit while there's a pending post.
dispatcher.PostDelayed(
std::chrono::milliseconds(100),
[this]()
{
UnInitialize();
});
}
void P2PManagerImpl::StartGroup()
{
auto groups = p2pDevice->PersistentGroups();
if (groups.size() == 0)
throw std::runtime_error("StartGroup() called with no persistent groups.");
std::map<std::string, sdbus::Variant> args;
args["persistent_group_object"] = groups[0];
this->p2pDevice->GroupAdd(args);
}
void P2PManagerImpl::CancelMaybeRemoveGroup()
{
if (maybeCloseHandle)
{
dispatcher.CancelPost(maybeCloseHandle);
maybeCloseHandle = 0;
}
}
void P2PManagerImpl::MaybeRemoveGroup()
{
CancelMaybeRemoveGroup();
if (dispatcher.IsStopping())
{
return;
}
maybeCloseHandle = dispatcher.PostDelayed(
std::chrono::milliseconds(100),
[this]()
{
maybeCloseHandle = 0;
if (this->p2pDevice->Stations().size() == 0)
{
if (this->p2pGoDevice)
{
try
{
p2pGoDevice->Disconnect();
}
catch (const std::exception &e)
{
LogError(p2pGoDevice->getObjectPath(), "MaybeRemoveGroup", "Failed to disconnect.");
}
}
}
});
}
std::vector<std::string> s_idleStates{
"disconnected",
"inactive",
"interface_disabled",
"scanning",
//"authenticating",
//"associating",
//"associated",
//"4way_handshake",
//"group_handshake",
"completed",
//"unknown",
};
void P2PManagerImpl::MaybeCancel()
{
std::string state = this->p2pDevice->State();
for (const auto &idleState : s_idleStates)
{
if (idleState == state)
{
return;
}
}
try
{
LogWarning("P2PManager","MaybeCancel",SS("Cancelling (State=" << state << ")"));
this->p2pDevice->Cancel();
}
catch (const std::exception &)
{
}
}
std::string P2PManagerImpl::UpnpServiceName()
{
return SS("uuid:" << settings.service_uuid() << "::urn:schemas-twoplay-com:service:PiPedal:1"
<< "::port:" << settings.web_server_port());
}
void P2PManagerImpl::StartServiceBroadcast()
{
// Upnp service advertising allows the android client to automatically discover pipedal Wifi P2P devices.
try
{
try {
p2pDevice->FlushService();
} catch (const std::exception&)
{
}
std::map<std::string, sdbus::Variant> args;
args["service_type"] = "upnp";
args["version"] = (int32_t)0x10;
args["service"] = UpnpServiceName();
std::vector<std::uint8_t> response;
response.push_back(0);
args["response"] = response;
p2pDevice->AddService(
args);
serviceBroadcastStarted = true;
}
catch (const std::exception &e)
{
LogError(
p2pDevice->getObjectPath(),
"StartServiceBroadcast",
SS("Failed to start Wi-Fi service broadcast. " << e.what()));
}
}
void P2PManagerImpl::StopServiceBroadcast()
{
try
{
if (serviceBroadcastStarted)
{
serviceBroadcastStarted = false;
std::map<std::string, sdbus::Variant> args;
args["service_type"] = "upnp";
args["version"] = (int32_t)10;
args["service"] = UpnpServiceName();
p2pDevice->DeleteService(args);
}
}
catch (std::exception &e)
{
LogDebug("P2PManager", "StopServiceBroadcast", e.what());
}
}
void P2PManagerImpl::UnInitialize()
{
// should all have been released at this point,
// but still best to be certain.
// the must NOT survive destruction of the dispatcher.
this->connectedPeers.clear();
this->wpaGroup = nullptr;
this->p2pGoDevice = nullptr;
this->wpsDevice = nullptr;
this->p2pDevice = nullptr;
this->wpsDevice = nullptr;
if (!oldWpasDebugLevel.empty())
{
wpaSupplicant->DebugLevel(oldWpasDebugLevel);
}
wpaSupplicant = nullptr; // completely shut down after this.
}
void P2PManagerImpl::TurnOnWifi()
{
bool wirelessEnabled;
auto networkManager = NetworkManager::Create(dispatcher);
try
{
wirelessEnabled = networkManager->WirelessEnabled();
}
catch (const std::exception &e)
{
LogDebug("P2PManager", "TurnOnWifi", "NetworkManager is not available.");
return;
}
if (!wirelessEnabled)
{
networkManager->WirelessEnabled(true);
}
}
void P2PManagerImpl::SignalHangup()
{
if (signalStop)
return;
// reloadRequested = true;
dispatcher.SignalStop([this]()
{
dispatcher.Post([this]()
{
LogInfo("P2PManager","SignalHangup","Reload requested.");
ExecuteOrderlyShutdown();
});
ExecuteOrderlyShutdown(); });
signalStop = true;
}
void P2PManagerImpl::SignalStop()
{
if (signalStop)
return;
dispatcher.SignalStop([this]()
{ ExecuteOrderlyShutdown(); });
signalStop = true;
}
void P2PManagerImpl::StartListen()
{
if (!dispatcher.IsStopping())
{
try {
p2pDevice->Listen((int32_t)5);
std::map<std::string, sdbus::Variant> listenArgs;
listenArgs["interval"] = (int32_t)3000; // listen for...
listenArgs["period"] = (int32_t)1000; // check every...
this->p2pDevice->ExtendedListen(listenArgs);
} catch (const std::exception&)
{
}
}
}
static std::string UnquoteSsid(const std::string &ssid)
{
if (ssid[0] != '\"') return ssid;
return ssid.substr(1,ssid.length()-2);
}
sdbus::ObjectPath P2PManagerImpl::GetActivePersistentGroup()
{
if (!wpaGroup)
{
throw std::runtime_error("No active group.");
}
auto groupSsidBytes = wpaGroup->SSID();
std::string groupSsid { (const char*)(&groupSsidBytes[0]),groupSsidBytes.size()};
for (const auto&persistentGroupPath: p2pDevice->PersistentGroups())
{
auto persistentGroup = WpaPersistentGroup::Create(dispatcher.Connection(),persistentGroupPath);
auto properties = persistentGroup->Properties();
std::string persistentGroupSsid = properties["ssid"];
persistentGroupSsid = UnquoteSsid(persistentGroupSsid);
if (persistentGroupSsid == groupSsid)
{
return persistentGroupPath;
}
}
throw std::runtime_error("Active persistent group not found.");
}
class ServiceTLV {
public:
enum class ProtocolTypes: uint8_t { All = 0, Bonjour = 1, UPnP = 2, WSDiscovery = 3, WifiDisplay = 4, P2Ps = 11};
ServiceTLV() { }
ServiceTLV(const std::vector<uint8_t> &tlv) {
size_t length = tlv[1] *256 + tlv[0];
protocolType = (ProtocolTypes)tlv[2];
transactionId = tlv[3];
for (size_t i = 4; i <= length; ++i)
{
queryData.push_back(tlv[i]);
}
}
ProtocolTypes ProtocolType() const { return protocolType; }
void ProtocolType(ProtocolTypes type) { protocolType = type;}
uint8_t TransactionId() const { return transactionId; }
void TransactionId(uint8_t value) { transactionId = value; }
const std::vector<uint8_t> QueryData() const { return queryData; }
std::string UpnpQueryString() const { return std::string((char*)&(queryData[1]),queryData.size()-1);}
void QueryData(const std::vector<uint8_t> &value) { queryData = value; }
uint8_t UpnpVersion() const { return queryData[0]; }
void UpnpQueryString(const std::string &value) {
queryData.resize(0);
queryData.reserve(value.length()+1);
queryData.push_back(10);
for (char c: value)
{
queryData.push_back((uint8_t)c);
}
}
std::vector<uint8_t> Build() const {
std::vector<uint8_t> result;
size_t length = queryData.size()+4;
result.reserve(length);
result.push_back((uint8_t)length);
result.push_back((uint8_t)(length >> 8));
result.push_back((uint8_t) protocolType);
result.push_back(transactionId);
for (uint8_t v: queryData)
{
result.push_back(v);
}
return result;
}
private:
ProtocolTypes protocolType;
uint8_t transactionId;
uint16_t length;
std::vector<uint8_t> queryData;
};
static bool IsDigit(char c)
{
return c >= '0' && c <= '9';
}
static std::string StripVersion(const std::string&v)
{
size_t len = v.length();
while (len > 0 && IsDigit(v[len-1]))
{
--len;
}
if (len > 0 && v[len-1] == '.')
{
--len;
}
return v.substr(0,len);
}
void P2PManagerImpl::OnServiceDiscoveryRequest(const std::map<std::string, sdbus::Variant> &sd_request)
{
// try {
// sdbus::ObjectPath peer_object = sd_request.at("peeer_object");
// int32_t frequency = sd_request.at("frequency");
// int32_t dialog_token = sd_request.at("dialog_token");
// std::vector<std::uint8_t> request_tlvs = sd_request.at("tlvs");
// ServiceTLV request { request_tlvs};
// if (request.ProtocolType() != ServiceTLV::ProtocolTypes::Any && request.ProtocolType() != ServiceTLV::ProtocolTypes::UPnP )
// {
// return;
// }
// std::string requestUrn = request.UpnpQueryString();
// requestUrn = StripVersion(requestUrn);
// if (requestUrn != "urn:schemas-twoplay-com:service:PiPedal")
// {
// return;
// }
// ServiceTLV response;
// response.ProtocolType(ServiceTLV::ProtocolTypes::UPnP);
// response.TransactionId(request.TransactionId());
// response.UpnpQueryString(UpnpServiceName());
// // [44, 0, 2, 2, 16, 117, 114, 110, 58, 115, 99, 104, 101, 109, 97, 115, 45, 116, 119, 111, 112, 108, 97, 121, 45, 99, 111, 109, 58, 115, 101, 114, 118, 105, 99, 101, 58, 80, 105, 80, 101, 100, 97, 108, 58, 49
// std::map<std::string, sdbus::Variant> args;
// args["peer_object"] = peer_object;
// args["frequency"] = frequency;
// this->p2pDevice->ServiceDiscoveryResponse(args);
// } catch (const std::exception&e)
// {
// LogError("P2PManager","OnServiceDiscoveryRequest",e.what());
// }
}
bool P2PManagerImpl::TerminatedNormally()
{
return normalTermination;
}
#endif