diff --git a/.vscode/launch.json b/.vscode/launch.json index 77a7e3e..a06c04d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,7 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { "name": "(gdb) pipedaltest Launch", "type": "cppdbg", @@ -111,7 +112,41 @@ "ignoreFailures": true } ] - } + }, + { + "name": "(gdb) pipedalconfig", + "type": "cppdbg", + "request": "launch", + // Resolved by CMake Tools: + "program": "${command:cmake.launchTargetPath}", + "args": [ + + ], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [ + { + // add the directory where our target was built to the PATHs + // it gets resolved by CMake Tools: + "name": "PATH", + "value": "$PATH:${command:cmake.launchTargetDirectory}" + }, + { + "name": "OTHER_VALUE", + "value": "Something something" + } + ], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index ff17ea6..4b81cf2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,10 +14,14 @@ enable_testing() # Frameworks # catch +add_subdirectory("submodules/pipedal_p2pd") + add_subdirectory("react") add_subdirectory("src") +install (TARGETS pipedal_p2pd DESTINATION ${CMAKE_INSTALL_PREFIX}/bin + ) # select LV2 source directory for the current build architecture @@ -51,6 +55,9 @@ install (FILES ${PROJECT_SOURCE_DIR}/src/template.service ${PROJECT_SOURCE_DIR}/src/templateAdmin.service ${PROJECT_SOURCE_DIR}/src/templateJack.service ${PROJECT_SOURCE_DIR}/src/defaultFavorites.json + ${PROJECT_SOURCE_DIR}/src/templateP2pd.service + ${PROEJCT_SOURCE_DIR}/src/template_p2pd.conf + DESTINATION /etc/pipedal/config ) install (DIRECTORY ${LV2_SOURCE_DIRECTORY} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0ef0f22..ccc2df0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -102,6 +102,7 @@ add_custom_command( set (PIPEDAL_SOURCES + P2pConfigFiles.hpp StdErrorCapture.hpp StdErrorCapture.cpp Ipv6Helpers.cpp Ipv6Helpers.hpp PluginPreset.cpp PluginPreset.hpp @@ -110,6 +111,7 @@ set (PIPEDAL_SOURCES SysExec.cpp SysExec.hpp BeastServer.cpp BeastServer.hpp HtmlHelper.cpp HtmlHelper.hpp pch.h Uri.cpp Uri.hpp WifiConfigSettings.hpp WifiConfigSettings.cpp + WifiDirectConfigSettings.hpp WifiDirectConfigSettings.cpp RequestHandler.hpp json.cpp json.hpp Scratch.cpp Lv2Host.hpp Lv2Host.cpp PluginType.hpp PluginType.cpp Lv2Log.hpp Lv2Log.cpp @@ -219,19 +221,24 @@ add_test(NAME DevTest COMMAND pipedaltest "[Dev]") ################################# -add_executable(pipedalconfig json.cpp json.hpp +add_executable(pipedalconfig + PrettyPrinter.hpp + json.cpp json.hpp HtmlHelper.cpp HtmlHelper.hpp CommandLineParser.hpp WriteTemplateFile.hpp WriteTemplateFile.cpp PiPedalException.hpp ConfigMain.cpp WifiConfigSettings.cpp WifiConfigSettings.hpp + WifiDirectConfigSettings.cpp WifiDirectConfigSettings.hpp JackServerSettings.hpp JackServerSettings.cpp SetWifiConfig.hpp SetWifiConfig.cpp SystemConfigFile.hpp SystemConfigFile.cpp SysExec.hpp SysExec.cpp asan_options.cpp Lv2Log.cpp Lv2Log.hpp + P2pConfigFiles.hpp + ) target_link_libraries(pipedalconfig PRIVATE pthread atomic stdc++fs @@ -242,11 +249,14 @@ add_executable(pipedaladmind ShutdownMain.cpp CommandLineParser.hpp json.hpp json.cpp HtmlHelper.hpp HtmlHelper.cpp Lv2Log.hpp Lv2Log.cpp Lv2SystemdLogger.hpp Lv2SystemdLogger.cpp WifiConfigSettings.cpp WifiConfigSettings.hpp + WifiDirectConfigSettings.cpp WifiDirectConfigSettings.hpp SetWifiConfig.hpp SetWifiConfig.cpp SystemConfigFile.hpp SystemConfigFile.cpp SysExec.hpp SysExec.cpp CpuGovernor.cpp CpuGovernor.hpp asan_options.cpp + P2pConfigFiles.hpp + ) target_link_libraries(pipedaladmind PRIVATE pthread atomic stdc++fs systemd) diff --git a/src/ConfigMain.cpp b/src/ConfigMain.cpp index 24a187e..bd088d2 100644 --- a/src/ConfigMain.cpp +++ b/src/ConfigMain.cpp @@ -31,8 +31,10 @@ #include #include #include "JackServerSettings.hpp" +#include "P2pConfigFiles.hpp" +#include "PrettyPrinter.hpp" -#define SS(x) ( ((std::stringstream&)(std::stringstream() << x )).str()) +#define SS(x) (((std::stringstream &)(std::stringstream() << x)).str()) using namespace std; using namespace pipedal; @@ -54,6 +56,7 @@ using namespace pipedal; #define SERVICE_PATH "/usr/lib/systemd/system" #define NATIVE_SERVICE "pipedald" #define ADMIN_SERVICE "pipedaladmind" +#define PIPEDAL_P2PD_SERVICE "pipedal_p2pd" #define JACK_SERVICE "jack" #define REMOVE_OLD_SERVICE 1 // Grandfathering: whether to remove the old shutdown service (now pipedaladmind) @@ -110,12 +113,12 @@ void DisableService() { cout << "Error: Failed to disable the " ADMIN_SERVICE " service."; } - #if REMOVE_OLD_SERVICE +#if REMOVE_OLD_SERVICE if (sysExec(SYSTEMCTL_BIN " disable " OLD_SHUTDOWN_SERVICE ".service") != EXIT_SUCCESS) { cout << "Error: Failed to disable the " OLD_SHUTDOWN_SERVICE " service."; } - #endif +#endif } void StopService(bool excludeShutdownService = false) @@ -130,12 +133,12 @@ void StopService(bool excludeShutdownService = false) { cout << "Error: Failed to stop the " ADMIN_SERVICE " service."; } - #if REMOVE_OLD_SERVICE +#if REMOVE_OLD_SERVICE if (sysExec(SYSTEMCTL_BIN " stop " OLD_SHUTDOWN_SERVICE ".service") != EXIT_SUCCESS) { cout << "Error: Failed to stop the " OLD_SHUTDOWN_SERVICE " service."; } - #endif +#endif } if (sysExec(SYSTEMCTL_BIN " stop " JACK_SERVICE ".service") != EXIT_SUCCESS) { @@ -202,7 +205,7 @@ static bool userExists(const char *userName) return true; } -static void RemoveLine(const std::string&path, const std::string lineToRemove) +static void RemoveLine(const std::string &path, const std::string lineToRemove) { std::vector lines; try @@ -234,16 +237,16 @@ static void RemoveLine(const std::string&path, const std::string lineToRemove) { throw PiPedalException(SS("Can't write to " << path)); } - for (auto&line: lines) + for (auto &line : lines) { - f << line << endl; + f << line << "\n"; } } } } catch (const std::exception &e) { - cout << "Failed to update " << path << ". " << e.what(); + cout << "Failed to update " << path << ". " << e.what(); } } @@ -252,8 +255,7 @@ void UninstallPamEnv() { RemoveLine( "/etc/security/pam_env.conf", - PAM_LINE - ); + PAM_LINE); } void InstallPamEnv() { @@ -295,16 +297,16 @@ void InstallPamEnv() { throw PiPedalException(SS("Can't write to " << path)); } - for (auto&line: lines) + for (auto &line : lines) { - f << line << endl; + f << line << "\n"; } } } } catch (const std::exception &e) { - cout << "Failed to update " << path << ". " << e.what(); + cout << "Failed to update " << path << ". " << e.what(); } } @@ -320,9 +322,9 @@ void InstallLimits() if (!std::filesystem::exists(limitsConfig)) { ofstream output(limitsConfig); - output << "# Realtime priority group used by pipedal/jack daemon" << endl; - output << "@audio - rtprio 95" << endl; - output << "@audio - memlock unlimited" << endl; + output << "# Realtime priority group used by pipedal/jack daemon" << "\n"; + output << "@audio - rtprio 95" << "\n"; + output << "@audio - memlock unlimited" << "\n"; } } @@ -365,15 +367,15 @@ void InstallJackService() } // lock account for login. silentSysExec("passwd -l " JACK_SERVICE_ACCOUNT_NAME); - } + } // Add to audio groups. // sysExec(USERMOD_BIN " -a -G " JACK_SERVICE_GROUP_NAME " " JACK_SERVICE_ACCOUNT_NAME); sysExec(USERMOD_BIN " -a -G" AUDIO_SERVICE_GROUP_NAME " " JACK_SERVICE_ACCOUNT_NAME); - // If ONBOARDING, add to bluetooth group. - #ifdef ONBOARDING - sysExec(USERMOD_BIN " -a -G bluetooth " JACK_SERVICE_ACCOUNT_NAME); - #endif +// If ONBOARDING, add to bluetooth group. +#ifdef ONBOARDING + sysExec(USERMOD_BIN " -a -G bluetooth " JACK_SERVICE_ACCOUNT_NAME); +#endif // deploy the systemd service file std::map map; // nothing to customize. @@ -383,7 +385,6 @@ void InstallJackService() MaybeStartJackService(); } - int SudoExec(char **argv) { int pbPid; @@ -404,21 +405,25 @@ int SudoExec(char **argv) void Uninstall() { + + OnWifiUninstall(); + StopService(); DisableService(); silentSysExec(SYSTEMCTL_BIN " stop jack"); silentSysExec(SYSTEMCTL_BIN " disable jack"); + OnWifiUninstall(); + std::filesystem::remove("/usr/bin/systemd/system/" OLD_SHUTDOWN_SERVICE ".service"); std::filesystem::remove("/usr/bin/systemd/system/" ADMIN_SERVICE ".service"); + std::filesystem::remove("/usr/bin/systemd/system/" PIPEDAL_P2PD_SERVICE ".service"); std::filesystem::remove("/usr/bin/systemd/system/" NATIVE_SERVICE ".service"); std::filesystem::remove("/usr/bin/systemd/system/" JACK_SERVICE ".service"); UninstallPamEnv(); UninstallLimits(); sysExec(SYSTEMCTL_BIN " daemon-reload"); - } - void Install(const std::filesystem::path &programPrefix, const std::string endpointAddress) { if (sysExec(GROUPADD_BIN " -f " AUDIO_SERVICE_GROUP_NAME) != EXIT_SUCCESS) @@ -470,7 +475,7 @@ void Install(const std::filesystem::path &programPrefix, const std::string endpo } // lock account for login. silentSysExec("passwd -l " SERVICE_ACCOUNT_NAME); - } + } // Add to audio groups. sysExec(USERMOD_BIN " -a -G " AUDIO_SERVICE_GROUP_NAME " " SERVICE_ACCOUNT_NAME); @@ -512,7 +517,8 @@ void Install(const std::filesystem::path &programPrefix, const std::string endpo { // create it. std::ofstream f(portAuthFile); - if (!f.is_open()) { + if (!f.is_open()) + { throw PiPedalException("Failed to create " + portAuthFile.string()); } } @@ -535,7 +541,7 @@ void Install(const std::filesystem::path &programPrefix, const std::string endpo } } - cout << "Creating Systemd file." << endl; + cout << "Creating Systemd file." << "\n"; std::map map; @@ -568,15 +574,147 @@ void Install(const std::filesystem::path &programPrefix, const std::string endpo } WriteTemplateFile(map, std::filesystem::path("/etc/pipedal/config/templateAdmin.service"), GetServiceFileName(ADMIN_SERVICE)); + // /usr/bin/pipedal_p2pd --config-file /etc/pipedal/config/template_p2pd.conf + + std::string pipedal_p2pd_cmd = SS( + (programPrefix / "bin" / "pipedal_p2pd").string() + << " --config-file " << PIPEDAL_P2PD_CONF_PATH); + map["COMMAND"] = pipedal_p2pd_cmd; + + WriteTemplateFile(map, std::filesystem::path("/etc/pipedal/config/templateP2pd.service"), GetServiceFileName(PIPEDAL_P2PD_SERVICE)); + sysExec(SYSTEMCTL_BIN " daemon-reload"); - cout << "Starting services" << endl; + cout << "Starting services" << "\n"; RestartService(false); EnableService(); - cout << "Complete" << endl; + + cout << "Complete" << "\n"; } +static void PrintHelp() +{ + PrettyPrinter pp; + pp << "pipedalconfig - Command-line post-install configuration for PiPedal" << "\n" + << "Copyright (c) 2022 Robin Davies. All rights reserved." << "\n" + << "\n" + << "See https://rerdavies.github.io/pipedal/Documentation.html for " + << "online documentation." << "\n" + << "\n" + << "Syntax:" << "\n"; + pp << Indent(4) + << "pipedalconfig [Options...]" << "\n" + << "\n"; + pp.Indent(0) + << "Options: " << "\n\n"; + pp.Indent(20); + + pp + << HangingIndent() << " -h --help\t" + << "Display this message." << "\n" + << "\n" + + << HangingIndent() << " --install [--port ]\t" + << "Install or re-install services and service accounts." << "\n" + << "\n" + << "The --port option controls which port the web server uses." + << "\n\n" + + << HangingIndent() << " --uninstall\t" + << "Remove installed services." << "\n" + << "\n" + + << HangingIndent() << " --enable\t" + << "Start the pipedal service at boot time." << "\n" + << "\n" + + << HangingIndent() << " --disable\tDo not start the pipedal service at boot time." << "\n" + << "\n" + << HangingIndent() << " --start\tStart the pipedal services." << "\n" + << "\n" + << HangingIndent() << " --stop\tStop the pipedal services." << "\n" + << "\n" + << HangingIndent() << " --restart\tRestart the pipedal services." << "\n" + << "\n" + + << HangingIndent() << " --enable-p2p [[] ]\t" + << "Enable the P2P (Wi-Fi Direct) hotspot." << "\n\n" + << " is the 2-letter ISO-3166 country code for " + "the country you are in. see below for further notes." + << "\n\n" + << " is the name you see when connecting. " + << "\n\n" + << " is an exactly-eight-digit pin number that you must " + << "enter when connecting to the hotspot. If you don't " + << "provide a pin, pipedalconfig will generate and " + << "display a random pin for you. The pin is a " + << "so-called \"label\" pin, which is the same every " + << "time you are asked to enter it (unlike a keypad pin " + << "which changes every time you need to enter it." + << "\n\n" + << "Consider attaching a label to the bottom of your device " + << "so you can can remember the pin if you wan't to connect a new " + << "device to PiPedal. (It's also available on the Settings page of PiPedal, if you have access to PiPedal UI on another device.)" + << "\n\n" + << "For best performance, the channel number should be 1, 6, or 11 (the Wifi Direct \"social\" channels). " + << "If you don't supply a channel number, PiPedal will select the least-busy of channels 1, 6 and 11." + << "\n\n" + + << HangingIndent() << " --disable-p2p\tDisabled Wi-Fi Direct access." + << "\n\n" + + << HangingIndent() << " --enable-legacy-ap\t \tEnable a legacy Wi-Fi access point." + << "\n\n" + << "Enable a legacy Wi-Fi access point. \n\n" + << "country_code is the 2-letter ISO-3166 country code for " + << "the country you are in. see below for further notes." + << "\n\n" + << "See below for an explanation of when you might want to use a legacy Wi-Fi access point instead of Wifi-Direct access." + << "an explanation of when you might want to use a legacy Access Point instead of " + << "a P2P (Wi-Fi Direct) connection. Generally, you should prefer a P2p connection " + << "to an ordinary Hotspot connection." + << "\n\n" + + << HangingIndent() << " --disable-legacy-ap\tDisabled the legacy Wi-Fi access point." + << "\n\n" + + + << Indent(0) << "Country codes:" + << "\n\n" + << Indent(4) + << "Country codes are used to determine Wi-Fi-regulatory regime - the " + << "channels you are legally allowed to use, and the features which must " + << "be enabled or disabled on the channels you can use. " + << "\n\n" + << "Without a country code, Wi-Fi must be restricted to channels 1 through 11 " + << "with reduced amplitude and feature sets." + << "\n\n" + << "For the most part, Wi-Fi country codes are taken from the list of ISO 3661 " + << "2-letter country codes; although there are a handful of exceptions for small " + << "countries and islands. See the Alpha-2 code column of " + << "\n\n" + << Indent(8) << "https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes." + << "\n\n" + + << Indent(0) << "Legacy Wi-Fi Access Points:" + << "\n\n" + << Indent(4) + << "Some older devices may not be able to connect to PiPedal using Wi-Fi Direct connections. " + "Old Apple devices, for example, do not support Wi-Fi Direct. In theory, Wi-Fi Direct should " + "allow legacy Wi-Fi devices to connect to a Wi-Fi Direct access points as if it were an " + "ordinary Access Point. If this turns out " + "not to be the case, you can configure PiPedal to provide a legacy Wi-Fi access point instead. " + "\n\n" + "Unlike Wi-Fi Direct connections, legacy Access Points will prevent both the connecting device " + "and the PiPedal host machine from connecting to the Internet over a simultanous Wi-Fi connection Access " + "Point. On a connecting Android device, you won't be able to use the data connection either when a legacy " + "Wi-Fi connection is active." + "\n\n" + "Wi-Fi Direct connections are " + "therefore preferrable under almost all circumstances.\n\n" + ; +} int main(int argc, char **argv) { @@ -588,6 +726,7 @@ int main(int argc, char **argv) bool stop = false, start = false; bool enable = false, disable = false, restart = false; bool enable_ap = false, disable_ap = false; + bool enable_p2p = false, disable_p2p = false; bool nopkexec = false; bool excludeShutdownService = false; std::string prefixOption; @@ -605,14 +744,17 @@ int main(int argc, char **argv) parser.AddOption("--help", &help); parser.AddOption("--prefix", &prefixOption); parser.AddOption("--port", &portOption); - parser.AddOption("--enable_ap", &enable_ap); - parser.AddOption("--disable_ap", &disable_ap); + parser.AddOption("--enable-ap", &enable_ap); + parser.AddOption("--disable-ap", &disable_ap); + parser.AddOption("--enable-p2p", &enable_p2p); + parser.AddOption("--disable-p2p", &disable_p2p); + parser.AddOption("--excludeShutdownService", &excludeShutdownService); // private (unstable) option used by shutdown service. try { parser.Parse(argc, (const char **)argv); - int actionCount = install + uninstall + stop + start + enable + disable + enable_ap + disable_ap + restart; + int actionCount = install + uninstall + stop + start + enable + disable + enable_ap + disable_ap + restart + enable_p2p + disable_p2p; if (actionCount > 1) { throw PiPedalException("Please provide only one action."); @@ -621,20 +763,11 @@ int main(int argc, char **argv) { help = true; } - if (enable_ap) - { - if (parser.Arguments().size() != 4) - { - cout << "Error: " - << "Incorrect number of arguments supplied for --enable_ap option." << endl; - helpError = true; - } - } - else + if ((!enable_p2p) && (!enable_ap)) { if (parser.Arguments().size() != 0) { - cout << "Error: Unexpected argument : " << parser.Arguments()[0] << endl; + cout << "Error: Unexpected argument : " << parser.Arguments()[0] << "\n"; helpError = true; } } @@ -642,64 +775,23 @@ int main(int argc, char **argv) catch (const std::exception &e) { std::string s = e.what(); - cout << "Error: " << s << endl; + cout << "Error: " << s << "\n"; helpError = true; } - - if (help || helpError) + if (helpError) { - if (helpError) - cout << endl; + cout << "\n"; + return EXIT_FAILURE; // don't scroll the error off the screen. + } - cout << "pipedalconfig - Post-install configuration for pipdeal" << endl - << "Copyright (c) 2022 Robin Davies. All rights reserved." << endl - << endl - << "Syntax:" << endl - << " pipedalconfig [Options...]" << endl - << "Options: " << endl - << " -h --help Display this message." << endl - << endl - << " --install Install services and service accounts." << endl - << endl - << " --prefix Either /usr/local or /usr as appropriate for the install" << endl - << " (only valid with the -install option). Usually determined " << endl - << " automatically." << endl - << endl - << " --uninstall Remove installed services." << endl - << endl - << " --enable Start the pipedal service at boot time." << endl - << endl - << " --disable Do not start the pipedal service at boot time." << endl - << endl - << " --stop Stop the pipedal services." << endl - << endl - << " --start Start the pipedal services." << endl - << endl - << " --restart Restart the pipedal services." << endl - << endl - << " --port Port for the web server (only with -install option)." << endl - << " Either a port (e.g. '81'), or a full endpoint (e.g.: " << endl - << " '127.0.0.1:8080'). If no address is specified, the " << endl - << " web server will listen on 0.0.0.0 (any)." << endl - << endl - << " --enable_ap " << endl - << " Enable the Wi-Fi access point. is the " << endl - << " 2-letter ISO-3166 country code for the country in " << endl - << " which you are currently located. The country code " << endl - << " determines which channels may be legally used in (and " << endl - << " which features need to be enabled) in order to use a " << endl - << " Wi-Fi channel in your legislative regime." << endl - << endl - << " --disable_ap Disabled the Wi-Fi access point." << endl - << endl - << "Description:" << endl - << " pipedalconfig performs post-install configuration of pipedal." << endl - << endl; - exit(helpError ? EXIT_FAILURE : EXIT_SUCCESS); + if (help) + { + PrintHelp(); + return EXIT_SUCCESS; } if (portOption.size() != 0 && !install) { - cout << "Error: -port option can only be specified with the -install option." << endl; + cout << "Error: -port option can only be specified with the -install option." << "\n"; exit(EXIT_FAILURE); } @@ -780,17 +872,27 @@ int main(int argc, char **argv) { DisableService(); } + else if (enable_p2p) + { + auto argv = parser.Arguments(); + WifiDirectConfigSettings settings; + settings.ParseArguments(argv); + settings.valid_ = true; + settings.enable_ = false; + SetWifiDirectConfig(settings); + } + else if (disable_p2p) + { + WifiDirectConfigSettings settings; + settings.valid_ = true; + settings.enable_ = false; + SetWifiDirectConfig(settings); + } else if (enable_ap) { auto argv = parser.Arguments(); WifiConfigSettings settings; - settings.valid_ = true; - settings.enable_ = true; - settings.countryCode_ = argv[0]; - settings.hotspotName_ = argv[1]; - settings.password_ = argv[2]; - settings.channel_ = argv[3]; - settings.hasPassword_ = settings.password_.length() != 0; + settings.ParseArguments(argv); SetWifiConfig(settings); } @@ -804,7 +906,7 @@ int main(int argc, char **argv) } catch (const std::exception &e) { - cout << "Error: " << e.what() << endl; + cout << "Error: " << e.what() << "\n"; exit(EXIT_FAILURE); } } \ No newline at end of file diff --git a/src/P2pConfigFiles.hpp b/src/P2pConfigFiles.hpp new file mode 100644 index 0000000..a565a3d --- /dev/null +++ b/src/P2pConfigFiles.hpp @@ -0,0 +1,31 @@ +/* + * MIT License + * + * Copyright (c) 2022 Robin E. R. Davies + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + + +#define DEVICE_GUID_FILE "/etc/pipedal/config/device_uuid" + +#define PIPEDAL_P2PD_CONF_PATH "/etc/pipedal/config/template_p2pd.conf" + diff --git a/src/PrettyPrinter.hpp b/src/PrettyPrinter.hpp new file mode 100644 index 0000000..66d2169 --- /dev/null +++ b/src/PrettyPrinter.hpp @@ -0,0 +1,239 @@ +/* + * MIT License + * + * Copyright (c) 2022 Robin E. R. Davies + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once +#include +#include +#include +#include + + +namespace pipedal +{ + + using namespace std; + + class PrettyPrinter + { + private: + std::ostream &s; + size_t lineWidth = 80; + std::vector lineBuffer; + size_t column = 0; + size_t indent = 0; + size_t tabSize = 4; + bool hangingIndent = false; + bool hungIndent = false; + + public: + PrettyPrinter() + : s(std::cout) + { + lineBuffer.resize(lineWidth * 2); + } + std::ostream&Stream() const { return s;} + + PrettyPrinter& Indent(size_t indent) + { + this->indent = indent; + return *this; + } + + void WriteLine() { + lineBuffer.push_back('\n'); + s.write(&lineBuffer[0],lineBuffer.size()); + lineBuffer.clear(); + column = 0; + } + + PrettyPrinter& HangingIndent() + { + hangingIndent = true; + return *this; + } + void Write(const std::string &text) + { + for (char c : text) + { + if (c == '\n') + { + WriteLine(); + hungIndent = false; + hangingIndent = false; + } + else + { + if (column <= indent) + { + if (hangingIndent) + { + hangingIndent = false; + hungIndent = true; + + } else if (!hungIndent) { + if (c == ' ') + { + continue; + } + while (column < indent) + { + lineBuffer.push_back(' '); + ++column; + } + } + } + if (c == '\t') + { + if (hungIndent) + { + if (column < indent-1) + { + while (column < indent) + { + lineBuffer.push_back(' '); + ++column; + } + } else { + WriteLine(); + column = 0; + while (column < indent) + { + lineBuffer.push_back(' '); + ++column; + } + } + hungIndent = false; + } else { + lineBuffer.push_back(' '); + ++column; + while ((column % tabSize) != 0) + { + lineBuffer.push_back(' '); + ++column; + } + } + } + else if ((c & 0x80) == 0) + { + lineBuffer.push_back(c); + ++column; + } + else + { + //utf-8 sequence. + lineBuffer.push_back(c); + if ((c & 0xC0) == 0x80) + { + ++column; + } + } + + if (column >= lineWidth - 1) + { + + size_t breakPos = FindBreak(); + + size_t startOfOverflow = breakPos; + while (startOfOverflow < lineBuffer.size() && lineBuffer[startOfOverflow] == ' ') + { + ++startOfOverflow; + } + std::string overflow = string(&lineBuffer[startOfOverflow], lineBuffer.size() - startOfOverflow); + lineBuffer.resize(breakPos); + + WriteLine(); + + this->Write(overflow); + } + } + } + } + + private: + size_t FindBreak() + { + size_t i = lineBuffer.size(); + while (i > indent && lineBuffer[i-1] != ' ') + { + --i; + } + if (i > indent) + return i; + return lineBuffer.size(); + } + }; + + + template + PrettyPrinter& operator<< (PrettyPrinter &p, T &v) + { + std::stringstream s; + s << v; + p.Write(s.str()); + return p; + } + + inline PrettyPrinter& operator << (PrettyPrinter& p,const std::string&s) + { + p.Write(s); + return p; + } + + // iomanip + + // template + // inline basic_ostream<_CharT, _Traits>& + // endl(basic_ostream<_CharT, _Traits>& __os) + // { return flush(__os.put(__os.widen('\n'))); } + + inline PrettyPrinter& operator << (PrettyPrinter& p,PrettyPrinter& (*iomanip)(PrettyPrinter&os)) + { + return (*iomanip)(p); + } + + using pp_manip = std::function; + + inline + PrettyPrinter& operator << (PrettyPrinter&pp,pp_manip manip) + { + return manip(pp); + } + + pp_manip Indent(int n) + { + return [n] (PrettyPrinter&pp) ->PrettyPrinter& { + pp.Indent(n); + return pp; + }; + } + pp_manip HangingIndent() + { + return [] (PrettyPrinter&pp) ->PrettyPrinter&{ + pp.HangingIndent(); + return pp; + }; + } + + +} \ No newline at end of file diff --git a/src/SetWifiConfig.cpp b/src/SetWifiConfig.cpp index 12b36d7..3e4cf79 100644 --- a/src/SetWifiConfig.cpp +++ b/src/SetWifiConfig.cpp @@ -18,22 +18,91 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "pch.h" +#include "ss.hpp" +#include "P2pConfigFiles.hpp" #include "SetWifiConfig.hpp" #include "PiPedalException.hpp" #include "SystemConfigFile.hpp" #include "SysExec.hpp" +#include "WriteTemplateFile.hpp" + using namespace pipedal; +using namespace std; -#define IP_RANGE "172.22.0" -#define PIPEDAL_IP IP_RANGE ".1" +#define APD_IP_PREFIX "172.22.0" +#define APD_IP_ADDRESS APD_IP_PREFIX ".1" +#define APD_INTERFACE_ADDR APD_IP_ADDRESS "/24" -#define PIPEDAL_NETWORK PIPEDAL_IP "/24" +#define P2P_IP_PREFIX "172.23.0" +#define P2P_IP_ADDRESS P2P_IP_PREFIX ".2" // no gateway! +#define P2P_INTERFACE_ADDR P2P_IP_ADDRESS "/24" #define SYSTEMCTL_BIN "/usr/bin/systemctl" -static void restoreDhcpcdConfFile() + +static char DNSMASQ_APD_PATH[] = "/etc/dnsmasq.d/30-pipedal-apd.conf"; +static char DNSMASQ_P2P_PATH[] = "/etc/dnsmasqdnsmaqsq.d/30-pipedal-p2p.conf"; + + +static bool IsApdInstalled() { + return std::filesystem::exists(DNSMASQ_APD_PATH); +} +static bool IsP2pInstalled() { + return std::filesystem::exists(DNSMASQ_P2P_PATH); +} + + +uint32_t pipedal::ChannelToWifiFrequency(const std::string &channel) +{ + std::string t = channel; + // remove dprecated band specs. + if (t.size() > 1 && t[0] == 'a' || t[0] == 'g') + { + t = t.substr(1); + } + size_t size = t.length(); + + unsigned long long lChannel = std::stoull(t,&size); + if (size != t.length()) + { + throw invalid_argument("Expecting a number: '" + t + "'."); + } + return ChannelToWifiFrequency((uint32_t)lChannel); +} + +uint32_t pipedal::ChannelToWifiFrequency(uint32_t channel) +{ + // 2.4GHz. + if (channel >= 1 && channel <= 13) + { + return 2412 + 5*(channel-1); + } + if (channel == 14) + { + return 2484; + } + // 802.11y + if (channel >= 131 && channel < 137) + { + return 3660 + (channel-131)*5; + } + if (channel >= 32 && channel <= 68 && (channel & 1) == 0) + { + return 5160 + (channel-32)/2*10; + } + if (channel == 96) return 5480; + + if (channel >= 100 && channel <= 196) + { + return 5500 + (channel-100)/5; + } + throw invalid_argument(SS("Invalid channel: " << channel)); + +} + +static void restoreApdDhcpdConfFile() { // remove the interface wlan0 section. std::filesystem::path dhcpcdConfig("/etc/dhcpcd.conf"); @@ -71,35 +140,48 @@ static void restoreDhcpcdConfFile() } } -static void restoreDnsmasqConfFile() + +static void restoreApdDnsmasqConfFile() { - std::filesystem::path path("/etc/dnsmasq.c/pipedal.conf"); + std::filesystem::path path(DNSMASQ_APD_PATH); if (std::filesystem::exists(path)) { std::filesystem::remove(path); } } +static void UninstallHostApd() +{ + if (IsApdInstalled()) + { + sysExec(SYSTEMCTL_BIN " stop hostapd"); + sysExec(SYSTEMCTL_BIN " disable hostapd"); + sysExec(SYSTEMCTL_BIN " stop dnsmasq"); + sysExec(SYSTEMCTL_BIN " disable dnsmasq"); + + restoreApdDhcpdConfFile(); + restoreApdDnsmasqConfFile(); + + sysExec(SYSTEMCTL_BIN " unmask wpa_supplicant"); // TODO: Do we need this now we have no_hook line. + sysExec(SYSTEMCTL_BIN " enable wpa_supplicant"); + sysExec(SYSTEMCTL_BIN " start wpa_supplicant"); + } + +} + +static void UninstallP2p(); + void pipedal::SetWifiConfig(const WifiConfigSettings &settings) { char band; if (!settings.enable_) { - sysExec(SYSTEMCTL_BIN " stop hostapd"); - sysExec(SYSTEMCTL_BIN " disable hostapd"); - - sysExec(SYSTEMCTL_BIN " stop dnsmasq"); - sysExec(SYSTEMCTL_BIN " disable dnsmasq"); - - restoreDhcpcdConfFile(); - restoreDnsmasqConfFile(); - - sysExec(SYSTEMCTL_BIN " unmask wpa_supplicant"); - sysExec(SYSTEMCTL_BIN " enable wpa_supplicant"); - sysExec(SYSTEMCTL_BIN " start wpa_supplicant"); + UninstallHostApd(); } else { + UninstallP2p(); + std::filesystem::path path("/etc/hostapd/hostapd.conf"); SystemConfigFile apdConfig; if (std::filesystem::exists(path)) @@ -160,7 +242,7 @@ void pipedal::SetWifiConfig(const WifiConfigSettings &settings) apdConfig.Set("channel", channel); // ******************** dsnmasq ****** - std::filesystem::path dnsMasqPath("/etc/dnsmasq.d/pipedal.conf"); + std::filesystem::path dnsMasqPath(DNSMASQ_APD_PATH); SystemConfigFile dnsMasq; try @@ -173,12 +255,12 @@ void pipedal::SetWifiConfig(const WifiConfigSettings &settings) } dnsMasq.Set("interface", "wlan0", "Name of the Wi-Fi interface"); - dnsMasq.Set("listen-address", PIPEDAL_IP); + dnsMasq.Set("listen-address", APD_IP_ADDRESS); - dnsMasq.SetDefault("dhcp-range", IP_RANGE ".3," IP_RANGE ".127,12h", "dhcp configuration"); + dnsMasq.SetDefault("dhcp-range", APD_IP_PREFIX ".3," APD_IP_PREFIX ".127,12h", "dhcp configuration"); dnsMasq.SetDefault("domain", "local"); std::stringstream sAddress; - sAddress << "/" << settings.mdnsName_ << ".local/" IP_RANGE << ".1"; + sAddress << "/" << settings.mdnsName_ << ".local/" APD_IP_PREFIX << ".1"; dnsMasq.Set("address", sAddress.str()); // ****** dhcpd.conf ***/ @@ -228,7 +310,7 @@ void pipedal::SetWifiConfig(const WifiConfigSettings &settings) line = dhcpcd.GetLineCount(); } dhcpcd.InsertLine(line++, "interface wlan0"); - dhcpcd.InsertLine(line++, " static ip_address=" PIPEDAL_NETWORK); + dhcpcd.InsertLine(line++, " static ip_address=" APD_INTERFACE_ADDR); dhcpcd.InsertLine(line++, " nohook wpa_supplicant"); dhcpcd.Save(dhcpcdConfig); } @@ -263,3 +345,231 @@ void pipedal::SetWifiConfig(const WifiConfigSettings &settings) sysExec(SYSTEMCTL_BIN " enable dnsmasq"); } } + + +/********************************************************************************* + +p2p configuration: + + +dnsmaqsq.d/30-pipedal-p2p.conf: + +interface=p2p-wlan0-0 + dhcp-range=p2p-wlan0,172.24.0.3,172.24.0.127,1h + domain=local + address=/pipedal.local/172.24.0.1 # do this through DNS-SD? + +except-interface=eth0 +except-interface=wlan0 +except-interface=lo + + +dhcpcd.conf: +====== +interface p2p-wlan0-0 + static ip_address=172.24.0.1/16 + domain_name_server=172.24.0.1 + +Watch resolv.conf to make sure we don't lose DNS servers. +************************************************************************************/ + + +void UninstallP2p() +{ + if (IsP2pInstalled()) + { + sysExec(SYSTEMCTL_BIN " stop pipidal_p2pd"); + sysExec(SYSTEMCTL_BIN " disable pipidal_p2pd"); + + sysExec(SYSTEMCTL_BIN " stop dnsmasq"); + sysExec(SYSTEMCTL_BIN " disable dnsmasq"); + + restoreApdDhcpdConfFile(); + restoreApdDnsmasqConfFile(); + + sysExec(SYSTEMCTL_BIN " enable wpa_supplicant"); + sysExec(SYSTEMCTL_BIN " start wpa_supplicant"); + } +} + + +void pipedal::SetWifiDirectConfig(const WifiDirectConfigSettings &settings) +{ + char band; + if (!settings.enable_) + { + UninstallP2p(); + } + else + { + + // ******************* pipedal_p2pd ****** + { + // ${COUNTRY_CODE} + //${PIN} + // ${DEVICE_NAME} + // ${WIFI_GROUP_FREQUENCY} + // ${INSTANCE_ID_FILE} /var/pipedal/instance_id + + std::map map; + map["COUNTRY_CODE"] = settings.countryCode_; + map["PIN"] = settings.pin_; + map["DEVICE_NAME"] = settings.hotspotName_; + map["WIFI_GROUP_FREQUENCY"] = SS(ChannelToWifiFrequency(settings.channel_)); + map["INSTANCE_ID_FILE"] = DEVICE_GUID_FILE; + + + WriteTemplateFile(map, std::filesystem::path("/etc/pipedal/config/template_p2pd.conf"), PIPEDAL_P2PD_CONF_PATH); + + } + + // ******************** dsnmasq ****** + std::filesystem::path dnsMasqPath(DNSMASQ_P2P_PATH); + SystemConfigFile dnsMasq; + + try + { + dnsMasq.Load(dnsMasqPath); + } + catch (const std::exception &) + { + // ignore. + } + // interface=p2p-wlan0-0 + // dhcp-range=172.24.0.3,172.24.0.127,1h + // domain=local + // address=/pipedal.local/172.24.0.1 # do this through DNS-SD? + + // except-interface=eth0 + // except-interface=wlan0 + // except-interface=lo + + + + dnsMasq.Set("interface", "p2p-wlan0-0", "Name of the Wi-Fi interface"); + dnsMasq.Set("listen-address", APD_IP_ADDRESS); + + dnsMasq.SetDefault("dhcp-range", P2P_IP_PREFIX ".3," P2P_IP_PREFIX ".127,1h", "dhcp configuration"); + dnsMasq.SetDefault("domain", "local"); + std::string strAddress = SS("/" << settings.mdnsName_ << ".local/" P2P_IP_PREFIX << ".2"); + dnsMasq.Set("address", strAddress); + dnsMasq.AppendLine("except-interface=eth0"); + dnsMasq.AppendLine("except-interface=wlan0"); + + + // ****** dhcpd.conf ***/ + std::filesystem::path dhcpcdConfig("/etc/dhcpcd.conf"); + if (std::filesystem::exists(dhcpcdConfig)) + { + SystemConfigFile dhcpcd(dhcpcdConfig); + + // dhcpcd.conf: + // ====== + // interface p2p-wlan0-0 + // static ip_address=172.24.0.1/16 + // domain_name_server=172.24.0.1 + + + int line = dhcpcd.GetLineThatStartsWith("hostname"); + std::string hostNameLine; + if (settings.mdnsName_ != "") + { + hostNameLine = "hostname " + settings.mdnsName_; + } + else + { + hostNameLine = "hostname"; // the default value. + } + if (line != -1) + { + dhcpcd.SetLineValue(line, hostNameLine); + } + else + { + dhcpcd.AppendLine(hostNameLine); + } + + // erase any existing interface for p2p-wlan0-0 + line = dhcpcd.GetLineNumber("interface p2p-wlan0-0"); + if (line != -1) + { + dhcpcd.EraseLine(line); + while (line < dhcpcd.GetLineCount()) + { + const std::string &lineValue = dhcpcd.GetLineValue(line); + if (lineValue.length() > 0 && (lineValue[0] == ' ' || lineValue[0] == '\t')) + { + dhcpcd.EraseLine(line); + } + else + { + break; + } + } + } + if (line == -1) + { + dhcpcd.AppendLine(""); + line = dhcpcd.GetLineCount(); + } + // interface p2p-wlan0-0 + // static ip_address=172.24.0.1/16 (trying int on .1.) + // domain_name_server=172.24.0.1 + + dhcpcd.InsertLine(line++, "interface p2p-wlan0-0"); + dhcpcd.InsertLine(line++, " static ip_address=" P2P_INTERFACE_ADDR); + dhcpcd.InsertLine(line++, " domain_name_servier=" P2P_IP_ADDRESS); + dhcpcd.Save(dhcpcdConfig); + } + + // ***** save the config files *** + + dnsMasq.Save(dnsMasqPath); + + // **************** start services ************ + sysExec("rfkill unblock wlan"); + sysExec(SYSTEMCTL_BIN " daemon-reload"); + + sysExec(SYSTEMCTL_BIN " restart wpa_supplicant"); + + sysExec(SYSTEMCTL_BIN " unmask pipedal_p2pd"); + if (sysExec(SYSTEMCTL_BIN " restart pipedal_p2pd") != 0) + { + throw PiPedalException("Unable to start the pipedal_p2pd."); + } + if (sysExec("systemctl is-active --quiet pipedal_p2pd") != 0) + { + throw PiPedalException("Unable to start the access point."); + } + sysExec(SYSTEMCTL_BIN " enable pipedal_p2pd"); + + sysExec(SYSTEMCTL_BIN " restart dnsmasq"); + sysExec(SYSTEMCTL_BIN " enable dnsmasq"); + } +} + +void pipedal::OnWifiUninstall() +{ + // intaller hook + if (IsApdInstalled()) + { + UninstallHostApd(); + } else if (IsP2pInstalled()) + { + UninstallP2p(); + } + +} +void pipedal::OnWifiInstallComplete() +{ + // install has already done a daemon-reload. + // I can't think of anything that needs to be done. + // if (IsApdInstalled()) + // { + // sysExec(SYSTEMCTL_BIN " daemon-reload"); + // } else if (IsP2pInstalled()) + // { + // sysExec(SYSTEMCTL_BIN " daemon-reload"); + // } + +} diff --git a/src/SetWifiConfig.hpp b/src/SetWifiConfig.hpp index 755f3f0..4dd2ec5 100644 --- a/src/SetWifiConfig.hpp +++ b/src/SetWifiConfig.hpp @@ -19,7 +19,16 @@ #pragma once #include "WifiConfigSettings.hpp" +#include "WifiDirectConfigSettings.hpp" + namespace pipedal { + uint32_t ChannelToWifiFrequency(const std::string &channel); + uint32_t ChannelToWifiFrequency(uint32_t channel); void SetWifiConfig(const WifiConfigSettings&settings); + void SetWifiDirectConfig(const WifiDirectConfigSettings&settings); + + void OnWifiUninstall(); + void OnWifiInstallComplete(); + } // namespace. \ No newline at end of file diff --git a/src/WifiConfigSettings.cpp b/src/WifiConfigSettings.cpp index c778562..ea90587 100644 --- a/src/WifiConfigSettings.cpp +++ b/src/WifiConfigSettings.cpp @@ -19,8 +19,10 @@ #include "pch.h" #include "WifiConfigSettings.hpp" +#include using namespace pipedal; +using namespace std; JSON_MAP_BEGIN(WifiConfigSettings) JSON_MAP_REFERENCE(WifiConfigSettings,valid) @@ -35,3 +37,48 @@ JSON_MAP_BEGIN(WifiConfigSettings) JSON_MAP_REFERENCE(WifiConfigSettings,channel) JSON_MAP_END() +bool WifiConfigSettings::ValidateCountryCode(const std::string&value) +{ + if (value.size() < 2 || value.size() > 3) return false; + return true; +} + + +bool WifiConfigSettings::ValidateChannel(const std::string&arguments) +{ + // 1) frequency in khz. + // 2) unadorned channel number 1, 2,3 &c. + // 3) With band annotated: g1, a51. + // come back to this. + return true; +} + + void WifiConfigSettings::ParseArguments(const std::vector &argv) + { + this->valid_ = false; + if (argv.size() != 4) { + throw invalid_argument("Invalid number of arguments."); + } + this->enable_ = true; + this->countryCode_ = argv[0]; + this->hotspotName_ = argv[1]; + this->mdnsName_ = this->hotspotName_; + this->password_ = argv[2]; + this->channel_ = argv[3]; + this->hasPassword_ = this->password_.length() != 0; + + if (!ValidateCountryCode(this->countryCode_)) + { + throw invalid_argument("Invalid country code."); + } + if (this->hotspotName_.length() > 31) throw invalid_argument("Hotspot name is too long."); + if (this->hotspotName_.length() < 1) throw invalid_argument("Hotspot name is too short."); + if (this->password_.size() != 0 && this->password_.size() < 8) throw invalid_argument("Passphrase must be at least 8 characters long."); + + if (!ValidateChannel(this->channel_)) + { + throw invalid_argument("Channel is not valid."); + } + this->valid_ = true; + + } diff --git a/src/WifiConfigSettings.hpp b/src/WifiConfigSettings.hpp index 1cdcf6b..2559ba1 100644 --- a/src/WifiConfigSettings.hpp +++ b/src/WifiConfigSettings.hpp @@ -35,6 +35,10 @@ namespace pipedal { bool hasPassword_ = false; std::string password_; std::string channel_ = "g6"; + + void ParseArguments(const std::vector &arguments); + static bool ValidateChannel(const std::string&value); + static bool ValidateCountryCode(const std::string&value); public: DECLARE_JSON_MAP(WifiConfigSettings); }; diff --git a/src/WifiDirectConfigSettings.cpp b/src/WifiDirectConfigSettings.cpp new file mode 100644 index 0000000..e9defe9 --- /dev/null +++ b/src/WifiDirectConfigSettings.cpp @@ -0,0 +1,101 @@ +// Copyright (c) 2022 Robin Davies +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "pch.h" +#include "WifiDirectConfigSettings.hpp" +#include "WifiConfigSettings.hpp" +#include +#include + +using namespace pipedal; +using namespace std; + +JSON_MAP_BEGIN(WifiDirectConfigSettings) + JSON_MAP_REFERENCE(WifiDirectConfigSettings,valid) + JSON_MAP_REFERENCE(WifiDirectConfigSettings,rebootRequired) + JSON_MAP_REFERENCE(WifiDirectConfigSettings,enable) + JSON_MAP_REFERENCE(WifiDirectConfigSettings,hotspotName) + JSON_MAP_REFERENCE(WifiDirectConfigSettings,hasPin) + JSON_MAP_REFERENCE(WifiDirectConfigSettings,pin) + JSON_MAP_REFERENCE(WifiDirectConfigSettings,countryCode) + JSON_MAP_REFERENCE(WifiDirectConfigSettings,channel) + JSON_MAP_REFERENCE(WifiDirectConfigSettings,mdnsName) +JSON_MAP_END() + +static std::string MakePin() +{ + std::random_device rand_dev; + + std::mt19937 gen(rand_dev()); + std::uniform_int_distribution dist { 0,9}; + + std::stringstream s; + for (size_t i = 0; i < 8; ++i) + { + int r = dist(gen); + s << (char)('0' + r); + } + return s.str(); +} +static void ValidatePin(const std::string&pin) +{ + for (char c : pin) + { + if (c < '0' || c > '9') + { + throw invalid_argument("Pin must consist of digits only."); + } + } + if (pin.length() != 8) + { + throw invalid_argument("Pin must have exctly 8 digits."); + } +} + +void WifiDirectConfigSettings::ParseArguments(const std::vector &arguments) +{ + this->valid_ = false; + if (arguments.size() < 2 || arguments.size() > 4) + { + throw invalid_argument("Incorrect number of arguments supplied"); + } + + this->enable_ = true; + + this->countryCode_ = arguments[0]; + this->hotspotName_ = arguments[1]; + this->pin_ = arguments.size() >= 3 ? arguments[2] : ""; + this->channel_ = arguments.size() >= 4? arguments[3]: ""; + size_t maxLength = 32-string("DIRECT-XX-").length(); + if (hotspotName_.size() >= maxLength) { throw invalid_argument("Device name is too long."); } + if (hotspotName_.size() < 1) { throw invalid_argument("Device name is too short."); } + if (!WifiConfigSettings::ValidateCountryCode(countryCode_)) + { + throw invalid_argument("Invalid country code."); + } + if (pin_.size() == 0) + { + pin_ = MakePin(); + } + ValidatePin(pin_); // throws. + if (!WifiConfigSettings::ValidateChannel(channel_)) + { + throw invalid_argument("Invalid channel."); + } +} diff --git a/src/WifiDirectConfigSettings.hpp b/src/WifiDirectConfigSettings.hpp new file mode 100644 index 0000000..5f29461 --- /dev/null +++ b/src/WifiDirectConfigSettings.hpp @@ -0,0 +1,47 @@ +/* + * MIT License + * + * Copyright (c) 2022 Robin E. R. Davies + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include "json.hpp" + +namespace pipedal { + + class WifiDirectConfigSettings { + public: + bool valid_ = false; + bool rebootRequired_ = false; + bool enable_ = false; + std::string countryCode_ = "US"; // iso 3661 + std::string hotspotName_ = "pipedal"; + std::string mdnsName_ = "pipedal"; + bool hasPin_ = false; + std::string pin_; + std::string channel_ = "g6"; + void ParseArguments(const std::vector &arguments); + + public: + DECLARE_JSON_MAP(WifiDirectConfigSettings); + }; +} \ No newline at end of file diff --git a/src/templateAdmin.service b/src/templateAdmin.service index 6e64288..57ef089 100644 --- a/src/templateAdmin.service +++ b/src/templateAdmin.service @@ -10,4 +10,4 @@ WorkingDirectory=/var/pipedal [Install] -WantedBy=multi-user.target \ No newline at end of file +WantedBy=multi-user.target diff --git a/src/templateP2pd.service b/src/templateP2pd.service new file mode 100644 index 0000000..7c8190c --- /dev/null +++ b/src/templateP2pd.service @@ -0,0 +1,38 @@ +/* + * MIT License + * + * Copyright (c) 2022 Robin E. R. Davies + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +[Unit] +Description=PiPedal P2P Session Manager +After=network.target + +[Service] +ExecStart=${COMMAND} +Type=notify +Restart=always +RestartSec=60 +WorkingDirectory=/var/pipedal + + +[Install] +WantedBy=multi-user.target diff --git a/src/template_p2pd.conf b/src/template_p2pd.conf new file mode 100644 index 0000000..6fc4c12 --- /dev/null +++ b/src/template_p2pd.conf @@ -0,0 +1,48 @@ +${COUNTRY_CODE} +${PIN} +${DEVICE_NAME} +${WIFI_GROUP_FREQUENCY} +${INSTANCE_ID_FILE} /var/pipedal/instance_id +# WiFi regdomain 2-letter country code. +# see: http://www.davros.org/misc/iso3166.txt +country_code=${COUNTRY_CODE} + +# keypad/label pin +p2p_pin=${PIN} + +# Name that appears when you connect. +p2p_device_name=${DEVICE_NAME} + +# DIRECT-XX-postfix (appears on Android p2p Group names) +p2p_ssid_postfix=${DEVICE_NAME} + +# Wifi frequency (kHz). +# Should almost always be 2412 (ch1),2437 (ch6), or 2462 (ch11). +wifiGroupFrequency=${WIFI_GROUP_FREQUENCY} + +# P2P Device info +p2p_model_name=PiPedal +p2p_model_number=1 +p2p_manufacturer="The PiPedal Project" +p2p_serial_number=1 +p2p_device_type=1-0050F204-1 + +# Wi-Fi configuration +wlanInterface=wlan0 +p2pInterface=p2p-dev-wlan0 +p2p_go_ht40=0 +p2p_go_vht=0 +p2p_go_he=0 + +# Ipv4 address for the P2P group interface +p2p_ip_address=172.24.0.2/16 + +# File containing the a globally-unique id identifying the service on this machine. +# Syntax: 0a6045b0-1753-4104-b3e4-b9713b9cc356 +# +service_guid_file=${INSTANCE_ID_FILE} + + +# GUID identifying the PiPedal service +# (if service_guid_file is not provided.) +service_guid=