Skip to content

Commit 8fa1c82

Browse files
committed
Try to make the code a lot safer, so maybe it won't crash
1 parent dc55236 commit 8fa1c82

File tree

1 file changed

+93
-37
lines changed

1 file changed

+93
-37
lines changed

main.cpp

Lines changed: 93 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
#include <fstream>
1818
#include "json.hpp"
1919
#include "filesystem.hpp"
20+
#include <mutex>
21+
#include <atomic>
2022

2123
#define VERSION "1.0.0B" // Version of the app.
2224
#define BETA false // If the app is in beta.
@@ -51,12 +53,13 @@ std::vector<std::string> output; // Logs
5153
std::deque<int16_t> signalRssis; // Signal strengths recorded (for graphs)
5254
int positionAway = 0; // How far we have scrolled up in the command line widget
5355
int logScrolledLeft = 0; // How far we've scrolled right in the command line
54-
bool running = false; // If the UI update thread should run
56+
std::atomic<bool> running{true}; // If the UI update thread should run
5557
bool showSaveSettingsPrompt = true; // If we should ask to save a settings file (if it doesn't already exist)
5658
std::thread refresher; // The UI update thread
5759
ghc::filesystem::path exec; // Parent directory of the executable
5860
ghc::filesystem::path settingsfile; // The file path containing our settings (potentially)
5961
json settings; // Our global settings
62+
std::mutex mutex;
6063

6164
enum rssi_stage {
6265
rssi_stage_excellent,
@@ -91,10 +94,10 @@ std::string rssiStageToString(rssi_stage stage) {
9194
Color rssiStageToColor(rssi_stage stage) {
9295
switch (stage) {
9396
case rssi_stage_excellent: return Color::Green;
94-
case rssi_stage_good: return Color::Green;
95-
case rssi_stage_fair: return Color::Green;
96-
case rssi_stage_poor: return Color::Green;
97-
case rssi_stage_unavailable: return Color::Green;
97+
case rssi_stage_good: return Color::Yellow;
98+
case rssi_stage_fair: return Color::Orange;
99+
case rssi_stage_poor: return Color::Red;
100+
case rssi_stage_unavailable: return Color::White;
98101
}
99102
}
100103

@@ -133,6 +136,7 @@ void debug(const std::string& input, Args&&... args) {
133136

134137
// Add to command line widget logs ('output')
135138
void log(std::string input) {
139+
std::lock_guard<std::mutex> lock(mutex);
136140
if (positionAway != 0) positionAway++; // If we're not following the logs, then scroll even farther away from them to stay where we are now
137141
output.push_back(input);
138142
}
@@ -240,7 +244,7 @@ void usage(std::string command = "") {
240244
} else if (command == "save" || command == "unsave") {
241245
log("save/unsave Usage:");
242246
log(1, "save/unsave help Print this help message.");
243-
log(1, "save/unsace password [SSID] [password] Save a password for a WiFi network, for use later.");
247+
log(1, "save/unsave password [SSID] [password] Save a password for a WiFi network, for use later.");
244248
} else if (command.empty()) {
245249
log("Usage:");
246250
log(1, "help [command] Print this help message, or optionally the help message of a different command.");
@@ -260,6 +264,7 @@ void usage(std::string command = "") {
260264

261265
bool processCommand(std::string input) {
262266
trim(input);
267+
if (input.empty()) return true;
263268
auto command = parseCommand(input); // The full list of arguments
264269
auto action = command[0]; // The thing the user is trying to do, the first argument
265270

@@ -279,7 +284,7 @@ bool processCommand(std::string input) {
279284
} else if (action == "echo") { // Debug command, solely for command parsing tests; won't be listed to the user
280285
log(fmt::format("Received command of '{}' with {} extra arguments", action, command.size() - 1));
281286
} else if (action == "power") {
282-
if (command.size() <= 2) {
287+
if (command.size() >= 2) {
283288
const std::string status = command[1];
284289
int result;
285290

@@ -340,7 +345,11 @@ bool processCommand(std::string input) {
340345
return true;
341346
}
342347

343-
settings["savedPasswords"][*ssid] = pswd;
348+
{
349+
std::lock_guard<std::mutex> lock(mutex);
350+
settings["savedPasswords"][*ssid] = pswd;
351+
}
352+
344353
bool saved = saveSettings(settings);
345354
log(fmt::format("Saved SSID '{}' with password '{}'!", ssid.value_or("<unknown>"), pswd.value_or("<unknown>")));
346355
} else if (subcommand == std::nullopt) {
@@ -357,7 +366,7 @@ bool processCommand(std::string input) {
357366
std::optional<std::string> ssid = atOrNull(command, 2);
358367

359368
if (ssid == std::nullopt) {
360-
log("Please provide ab SSID.");
369+
log("Please provide an SSID.");
361370
return true;
362371
}
363372

@@ -385,7 +394,7 @@ bool processCommand(std::string input) {
385394
showSaveSettingsPrompt = false;
386395
_saveSettings(settings);
387396
log("Saved settings!");
388-
} else if (status == "decline") {
397+
} else if (status == "deny") {
389398
showSaveSettingsPrompt = false;
390399
log("Declined to save settings.");
391400
} else {
@@ -425,7 +434,7 @@ int main(int argc, char* argv[]) {
425434
debug("Starting ItlwmCLI version {} {}", VERSION, versionTypeString);
426435

427436
exec = ghc::filesystem::absolute(argv[0]).parent_path();
428-
settingsfile = exec / "ItlwmCLI.settings.json"; // File for settings
437+
settingsfile = exec / "ItlwmCLI.settings.json"; // File for settings, obviously
429438

430439
// We finally get to the good stuff
431440
debug("Loading application...");
@@ -441,7 +450,7 @@ int main(int argc, char* argv[]) {
441450
uint32_t current80211State = 0;
442451

443452
unsigned long iteration = 0; // How many times the refresher thread has iterated
444-
unsigned long pastIteration = -1; // Keeping track of "have we already recorded for this iteration?"
453+
signed long pastIteration = -1; // Keeping track of "have we already recorded for this iteration?"
445454
int minRssi = 0; // Minimum RSSI of the graph
446455
int maxRssi = 0; // Maximum RSSI of the graph
447456
std::string input_str; // What the user has inputted in the command line widget
@@ -458,26 +467,51 @@ int main(int argc, char* argv[]) {
458467
auto input = Input(&input_str, "Type 'help' for available commands. Use up/down, left/right to scroll.", style); // The input provider for the command line widget
459468

460469
auto renderer = Renderer([&] {
470+
std::vector<std::string> localOutput;
471+
std::deque<int16_t> localSignalRssis;
472+
int localPositionAway;
473+
int localLogScrolledLeft;
474+
unsigned long localIteration;
475+
476+
{
477+
// Gotta lock stuff
478+
std::lock_guard<std::mutex> lock(mutex);
479+
480+
localOutput = output;
481+
localSignalRssis = signalRssis;
482+
localPositionAway = positionAway;
483+
localLogScrolledLeft = logScrolledLeft;
484+
localIteration = iteration;
485+
}
486+
461487
Elements output_elements;
462488
Elements networks_elements;
463489

464-
size_t start = (output.size() > VISIBLE_LOG_LINES) ? (output.size() - VISIBLE_LOG_LINES) : 0; // Where should we start rendering command logs?
490+
size_t start = (localOutput.size() > VISIBLE_LOG_LINES) ? (localOutput.size() - VISIBLE_LOG_LINES) : 0; // Where should we start rendering command logs?
465491
bool foundConnected = false; // If one of the networks returned from itlwm is the one we're connected to (it doesn't seem to do this in my testing)
466492

467-
for (size_t i = start - positionAway; i < output.size() - positionAway; ++i) {
493+
int newStart = start - localPositionAway;
494+
if (newStart < 0) newStart = 0;
495+
496+
int toRender = localOutput.size() - localPositionAway;
497+
if (toRender < 0) toRender = 0;
498+
499+
for (size_t i = newStart; i < toRender; ++i) {
468500
std::string index = std::to_string(i + 1);
469501
std::string spaces = "";
470502
while (index.size() + spaces.size() < LOG_INDEX_PADDING) spaces += " "; // Pad so the line numbers line up correctly
471-
output_elements.push_back(text(fmt::format("{}{}. {}", spaces, index, output[i].size() > logScrolledLeft ? output[i].substr(logScrolledLeft) : "")));
503+
output_elements.push_back(text(fmt::format("{}{}. {}", spaces, index, localOutput[i].size() > localLogScrolledLeft ? localOutput[i].substr(localLogScrolledLeft) : "")));
472504
}
473505

474506
while (output_elements.size() < VISIBLE_LOG_LINES) { // Pad with blanks to keep FTXUI consistent
475507
output_elements.insert(output_elements.begin(), text(""));
476508
}
477509

478-
// Make sure to clear each time, in case it changes
510+
// Make sure to clear each time, in case it changes.
511+
// We also clear stationInfo so we know it's at least initialized.
479512
std::memset(currentSsid, 0, sizeof(currentSsid));
480513
std::memset(currentBssid, 0, sizeof(currentBssid));
514+
std::memset(stationInfo, 0, sizeof(*stationInfo));
481515

482516
// Query itlwm
483517
bool network_ssid_available = get_network_ssid(currentSsid);
@@ -489,8 +523,8 @@ int main(int argc, char* argv[]) {
489523
bool station_info_available = get_station_info(stationInfo);
490524

491525
// So uh, station_info_available is always false in my experience for some reason, so we're using a different method
492-
station_info_available = station_info_available || stationInfo ? true : false;
493-
bool rssi_available = station_info_available && stationInfo->rssi < 0 && stationInfo->rssi > RSSI_UNAVAILABLE_THRESHOLD;
526+
station_info_available = station_info_available && stationInfo->rssi < 0 && stationInfo->rssi > RSSI_UNAVAILABLE_THRESHOLD;
527+
bool rssi_available = station_info_available;
494528

495529
// If the WiFi is off, then everything should be off
496530
if (network_power_state_available == false || currentPowerState == false) {
@@ -531,31 +565,30 @@ int main(int argc, char* argv[]) {
531565
networks_elements.push_back(text(""));
532566
}
533567

534-
if (rssi_available && iteration % RSSI_RECORD_INTERVAL == 0 && pastIteration != iteration) { // Every X iterations
535-
signalRssis.push_back(rssi_available ? stationInfo->rssi : RSSI_UNAVAILABLE_THRESHOLD);
536-
if (signalRssis.size() > MAX_RSSI_RECORD_LENGTH) signalRssis.pop_front();
537-
pastIteration = iteration;
538-
}
539-
540568
// Setup stuff for the graph
541-
minRssi = *std::min_element(signalRssis.begin(), signalRssis.end()); // Minimum graph point (based on the entire dataset)
542-
maxRssi = *std::max_element(signalRssis.begin(), signalRssis.end()); // Maximum graph point (based on the entire dataset)
543-
if (minRssi == maxRssi) maxRssi = minRssi + 1;
569+
if (localSignalRssis.empty()) {
570+
minRssi = 0;
571+
maxRssi = 0;
572+
} else {
573+
minRssi = *std::min_element(localSignalRssis.begin(), localSignalRssis.end()); // Minimum graph point (based on the entire dataset)
574+
maxRssi = *std::max_element(localSignalRssis.begin(), localSignalRssis.end()); // Maximum graph point (based on the entire dataset)
575+
if (minRssi == maxRssi) maxRssi = minRssi + 1;
576+
}
544577

545578
// Fancy duplication stuff
546-
int lastIndexLength = output.size();
579+
int lastIndexLength = localOutput.size();
547580
std::stringstream hashtagStream;
548-
hashtagStream << std::setw(LOG_INDEX_PADDING) << std::setfill(' ') << std::string(std::to_string(output.size()).size(), '#');
581+
hashtagStream << std::setw(LOG_INDEX_PADDING) << std::setfill(' ') << std::string(std::to_string(localOutput.size()).size(), '#');
549582

550583
// Get average RSSI
551584
long long rssiSum = 0;
552-
for (int n : signalRssis) rssiSum += abs(n);
553-
int rssiAverage = signalRssis.empty() ? 0 : -static_cast<int>(rssiSum / signalRssis.size());
585+
for (int n : localSignalRssis) rssiSum += abs(n);
586+
int rssiAverage = localSignalRssis.empty() ? 0 : -static_cast<int>(rssiSum / localSignalRssis.size());
554587

555-
auto makeGraph = [rssi_available, minRssi, maxRssi](int width, int height) -> std::vector<int> {
588+
auto makeGraph = [rssi_available, minRssi, maxRssi, localSignalRssis](int width, int height) -> std::vector<int> {
556589
std::vector<int> scaled(width, 0);
557-
if (signalRssis.empty()) return scaled; // Empty, we don't have data yet
558-
std::deque<int16_t> data(signalRssis); // Duplicate the list
590+
if (localSignalRssis.empty()) return scaled; // Empty, we don't have data yet
591+
std::deque<int16_t> data(localSignalRssis); // Duplicate the list
559592
int i;
560593

561594
if (data.size() * BAR_WIDTH > width) { // Truncate the copied list
@@ -626,6 +659,9 @@ int main(int argc, char* argv[]) {
626659
});
627660

628661
auto interactive = CatchEvent(renderer, [&](Event event) { // Catch events, like keystrokes
662+
std::lock_guard<std::mutex> lock(mutex);
663+
int maxScroll = output.size() > VISIBLE_LOG_LINES ? static_cast<int>(output.size() - VISIBLE_LOG_LINES) : 0;
664+
629665
if (event == Event::Return) { // User tried to enter a command
630666
trim(input_str);
631667
if (input_str.empty()) return true;
@@ -643,7 +679,7 @@ int main(int argc, char* argv[]) {
643679

644680
return true;
645681
} else if (event == Event::ArrowUp) { // Scroll up
646-
if (positionAway < output.size() - VISIBLE_LOG_LINES) {
682+
if (positionAway < maxScroll) {
647683
positionAway++;
648684
}
649685
} else if (event == Event::ArrowDown) { // Scroll down
@@ -671,16 +707,36 @@ int main(int argc, char* argv[]) {
671707
refresher = std::thread([&] {
672708
while (running) {
673709
std::this_thread::sleep_for(std::chrono::milliseconds(CONSTANT_REFRESH_INTERVAL));
710+
{
711+
std::lock_guard<std::mutex> lock(mutex);
712+
iteration++;
713+
714+
if (iteration % RSSI_RECORD_INTERVAL == 0) {
715+
int16_t rssiCopy = 0;
716+
bool availableCopy = false;
717+
718+
{
719+
std::lock_guard<std::mutex> lock(mutex);
720+
availableCopy = station_info_available && stationInfo != nullptr;
721+
if (availableCopy) rssiCopy = stationInfo->rssi;
722+
723+
if (availableCopy) {
724+
signalRssis.push_back(rssiCopy);
725+
if (signalRssis.size() > MAX_RSSI_RECORD_LENGTH) signalRssis.pop_front();
726+
}
727+
}
728+
}
729+
}
730+
674731
screen.PostEvent(Event::Custom);
675-
iteration++;
676732
}
677733
});
678734
}
679735

680736
debug("Starting application...");
681-
if (refresher.joinable()) refresher.detach();
682737
screen.Loop(interactive);
683738
running = false;
739+
if (refresher.joinable()) refresher.join();
684740
api_terminate();
685741
return 0;
686742
}

0 commit comments

Comments
 (0)