diff --git a/esp32_marauder/WiFiScan.cpp b/esp32_marauder/WiFiScan.cpp index 10c1fb8..b60372d 100644 --- a/esp32_marauder/WiFiScan.cpp +++ b/esp32_marauder/WiFiScan.cpp @@ -2256,8 +2256,6 @@ void WiFiScan::StartScan(uint8_t scan_mode, uint16_t color) { #endif RunProbeScan(scan_mode, color); } - else if (scan_mode == WIFI_SCAN_STATION_WAR_DRIVE) - RunProbeScan(scan_mode, color); else if (scan_mode == WIFI_SCAN_EVIL_PORTAL) RunEvilPortal(scan_mode, color); else if (scan_mode == WIFI_SCAN_EAPOL) @@ -2554,7 +2552,6 @@ void WiFiScan::StopScan(uint8_t scan_mode) { (currentScanMode == WIFI_SCAN_SAE_COMMIT) || (currentScanMode == WIFI_SCAN_AP) || (currentScanMode == WIFI_SCAN_WAR_DRIVE) || - (currentScanMode == WIFI_SCAN_STATION_WAR_DRIVE) || (currentScanMode == WIFI_SCAN_EVIL_PORTAL) || (currentScanMode == WIFI_SCAN_RAW_CAPTURE) || (currentScanMode == WIFI_SCAN_STATION) || @@ -2646,10 +2643,8 @@ void WiFiScan::StopScan(uint8_t scan_mode) { } // Close POI file if wardrive was active - if ((currentScanMode == WIFI_SCAN_WAR_DRIVE) || - (currentScanMode == WIFI_SCAN_STATION_WAR_DRIVE)) { + if (currentScanMode == WIFI_SCAN_WAR_DRIVE) this->closePoiFile(); - } if ((currentScanMode == BT_SCAN_ALL) || @@ -5134,8 +5129,7 @@ void WiFiScan::RunBeaconScan(uint8_t scan_mode, uint16_t color) { #ifdef HAS_GPS if (gps_obj.getGpsModuleStatus()) { startLog(F("wardrive")); - String header_line = "WigleWifi-1.4,appRelease=" + (String)MARAUDER_VERSION + ",model=ESP32 Marauder,release=" + (String)MARAUDER_VERSION + ",device=ESP32 Marauder,display=SPI TFT,board=ESP32 Marauder,brand=JustCallMeKoko\nMAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type\n"; - buffer_obj.append(header_line); + buffer_obj.append(this->header_line); this->openPoiFile(); } else { return; @@ -5320,19 +5314,6 @@ void WiFiScan::RunProbeScan(uint8_t scan_mode, uint16_t color) { startPcap(F("flock")); else if (scan_mode == WIFI_SCAN_DETECT_FOLLOW) startPcap(F("mac_track")); - else if (scan_mode == WIFI_SCAN_STATION_WAR_DRIVE) { - #ifdef HAS_GPS - if (gps_obj.getGpsModuleStatus()) { - startLog(F("station_wardrive")); - String header_line = "WigleWifi-1.4,appRelease=" + (String)MARAUDER_VERSION + ",model=ESP32 Marauder,release=" + (String)MARAUDER_VERSION + ",device=ESP32 Marauder,display=SPI TFT,board=ESP32 Marauder,brand=JustCallMeKoko\nMAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type\n"; - buffer_obj.append(header_line); - } else { - return; - } - #else - return; - #endif - } this->setLEDMode(MODE_SNIFF); @@ -5344,8 +5325,6 @@ void WiFiScan::RunProbeScan(uint8_t scan_mode, uint16_t color) { display_obj.tft.drawCentreString(text_table4[40],TFT_WIDTH / 2,16,2); else if (scan_mode == WIFI_SCAN_DETECT_FOLLOW) display_obj.tft.drawCentreString("MAC Monitor",TFT_WIDTH / 2,16,2); - else if (scan_mode == WIFI_SCAN_STATION_WAR_DRIVE) - display_obj.tft.drawCentreString("Station Wardrive",TFT_WIDTH / 2,16,2); else { display_obj.tft.drawCentreString("Flock Sniff",TFT_WIDTH / 2,16,2); } @@ -5356,7 +5335,6 @@ void WiFiScan::RunProbeScan(uint8_t scan_mode, uint16_t color) { #endif display_obj.tft.setTextColor(TFT_GREEN, TFT_BLACK); if ((scan_mode != WIFI_SCAN_DETECT_FOLLOW) && - (scan_mode != WIFI_SCAN_STATION_WAR_DRIVE) && (scan_mode != BT_SCAN_FLOCK)) { display_obj.tftDrawChannelScaleButtons(set_channel, false); display_obj.tftDrawExitScaleButtons(false); @@ -5472,8 +5450,7 @@ void WiFiScan::RunBluetoothScan(uint8_t scan_mode, uint16_t color) { this->startWardriverWiFi(); this->wifi_initialized = true; } - String header_line = "WigleWifi-1.4,appRelease=" + (String)MARAUDER_VERSION + ",model=ESP32 Marauder,release=" + (String)MARAUDER_VERSION + ",device=ESP32 Marauder,display=SPI TFT,board=ESP32 Marauder,brand=JustCallMeKoko\nMAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type\n"; - buffer_obj.append(header_line); + buffer_obj.append(this->header_line); } #endif @@ -6199,14 +6176,14 @@ String WiFiScan::processPwnagotchiBeacon(const uint8_t* frame, int length) { bool deauth = doc["policy"]["deauth"]; int uptime = doc["uptime"]; - Serial.print(F("Pwnagotchi Name: ")); + Serial.print(F("Name: ")); Serial.println(name); - Serial.print(F("Pwnd Totals: ")); + Serial.print(F("Pwnd #: ")); Serial.println(pwnd_tot); #ifdef HAS_SCREEN - display_obj.display_buffer->add(String("Pwnagotchi: ") + name + ", "); + display_obj.display_buffer->add(String("Name: ") + name + ", "); display_obj.display_buffer->add(" Pwnd: " + String(pwnd_tot) + ", "); display_obj.display_buffer->add(" Uptime: " + String(uptime) + ", "); if (deauth) @@ -6217,7 +6194,7 @@ String WiFiScan::processPwnagotchiBeacon(const uint8_t* frame, int length) { display_obj.display_buffer->add(String(" Ver: ") + ver + " "); #endif - return String("Pwnagotchi: ") + name + ", \nPwnd: " + String(pwnd_tot) + ", \nVer: " + ver; + return String("Name: ") + name + ", \nPwnd: " + String(pwnd_tot) + ", \nVer: " + ver; } else return ""; @@ -7135,7 +7112,6 @@ void WiFiScan::beaconSnifferCallback(void* buf, wifi_promiscuous_pkt_type_t type String essid = ""; if ((wifi_scan_obj.currentScanMode == WIFI_SCAN_PROBE) || - (wifi_scan_obj.currentScanMode == WIFI_SCAN_STATION_WAR_DRIVE) || (wifi_scan_obj.currentScanMode == WIFI_SCAN_AP) || (wifi_scan_obj.currentScanMode == WIFI_SCAN_PWN)) { if (type == WIFI_PKT_MGMT) { @@ -9982,7 +9958,6 @@ void WiFiScan::main(uint32_t currentTime) (currentScanMode == WIFI_SCAN_PINESCAN) || (currentScanMode == WIFI_SCAN_MULTISSID) || (currentScanMode == WIFI_SCAN_DEAUTH) || - (currentScanMode == WIFI_SCAN_STATION_WAR_DRIVE) || (currentScanMode == WIFI_SCAN_ALL)) { if (currentTime - initTime >= this->channel_hop_delay * HOP_DELAY) { diff --git a/esp32_marauder/WiFiScan.h b/esp32_marauder/WiFiScan.h index 5e5bd1d..83222d0 100644 --- a/esp32_marauder/WiFiScan.h +++ b/esp32_marauder/WiFiScan.h @@ -682,6 +682,8 @@ class WiFiScan static MacEntry mac_entries[mac_history_len_half]; static uint8_t mac_entry_state[mac_history_len_half]; + String header_line = "WigleWifi-1.4,appRelease=" + (String)MARAUDER_VERSION + ",model=ESP32 Marauder,release=" + (String)MARAUDER_VERSION + ",device=ESP32 Marauder,display=SPI TFT,board=ESP32 Marauder,brand=JustCallMeKoko\nMAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type\n"; + uint8_t dual_band_channels[DUAL_BAND_CHANNELS] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 149, 153, 157, 161, 165, 169, 173, 177}; uint8_t dual_band_channel_index = 0; diff --git a/esp32_marauder/settings.cpp b/esp32_marauder/settings.cpp index 8dcf438..c81194f 100644 --- a/esp32_marauder/settings.cpp +++ b/esp32_marauder/settings.cpp @@ -1,410 +1,456 @@ -#include "settings.h" - -String Settings::getSettingsString() { - return this->json_settings_string; -} - -bool Settings::begin() { - if(!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)){ - return false; - } - - File settingsFile; - - //SPIFFS.remove("/settings.json"); // NEED TO REMOVE THIS LINE - - if (SPIFFS.exists("/settings.json")) { - settingsFile = SPIFFS.open("/settings.json", FILE_READ); - - if (!settingsFile) { - settingsFile.close(); - if (this->createDefaultSettings(SPIFFS)) - return true; - else - return false; - } - } - else { - if (this->createDefaultSettings(SPIFFS)) - return true; - else - return false; - } - - String json_string; - DynamicJsonDocument jsonBuffer(JSON_SETTING_SIZE); - DeserializationError error = deserializeJson(jsonBuffer, settingsFile); - if (error) { - Serial.print("Could not parse json during setup: "); - Serial.println(error.f_str()); - } - serializeJson(jsonBuffer, json_string); - - this->json_settings_string = json_string; - - return true; -} - -template -T Settings::loadSetting(String key) {} - -// Get type int settings -template<> -int Settings::loadSetting(String key) { - DynamicJsonDocument json(JSON_SETTING_SIZE); // ArduinoJson v6 - - if (deserializeJson(json, this->json_settings_string)) { - Serial.println("\nCould not parse json"); - } - - for (int i = 0; i < json["Settings"].size(); i++) { - if (json["Settings"][i]["name"].as() == key) - return json["Settings"][i]["value"]; - } - - return 0; -} - -// Get type string settings -template<> -String Settings::loadSetting(String key) { - - DynamicJsonDocument json(JSON_SETTING_SIZE); // ArduinoJson v6 - - if (deserializeJson(json, this->json_settings_string)) { - Serial.println("\nCould not parse json"); - } - - for (int i = 0; i < json["Settings"].size(); i++) { - if (json["Settings"][i]["name"].as() == key) - return json["Settings"][i]["value"]; - } - - Serial.println("Did not find setting named " + (String)key + ". Creating..."); - if (this->createDefaultSettings(SPIFFS, true, json["Settings"].size(), "String", key)) - return ""; - - return ""; -} - -// Get type bool settings -template<> -bool Settings::loadSetting(String key) { - DynamicJsonDocument json(JSON_SETTING_SIZE); // ArduinoJson v6 - - if (deserializeJson(json, this->json_settings_string)) { - Serial.println("Could not parse json to load"); - } - - for (int i = 0; i < json["Settings"].size(); i++) { - if (json["Settings"][i]["name"].as() == key) - return json["Settings"][i]["value"]; - } - - Serial.println("Did not find setting named " + (String)key + ". Creating..."); - if (this->createDefaultSettings(SPIFFS, true, json["Settings"].size(), "bool", key)) - return true; - - return false; -} - -//Get type uint8_t settings -template<> -uint8_t Settings::loadSetting(String key) { - DynamicJsonDocument json(JSON_SETTING_SIZE); // ArduinoJson v6 - - if (deserializeJson(json, this->json_settings_string)) { - Serial.println("\nCould not parse json"); - } - - for (int i = 0; i < json["Settings"].size(); i++) { - if (json["Settings"][i]["name"].as() == key) - return json["Settings"][i]["value"]; - } - - return 0; -} - -template -T Settings::saveSetting(String key, bool value) {} - -template<> -bool Settings::saveSetting(String key, bool value) { - DynamicJsonDocument json(JSON_SETTING_SIZE); // ArduinoJson v6 - - if (deserializeJson(json, this->json_settings_string)) { - Serial.println("\nCould not parse json"); - } - - String settings_string; - - for (int i = 0; i < json["Settings"].size(); i++) { - if (json["Settings"][i]["name"].as() == key) { - json["Settings"][i]["value"] = value; - - Serial.println("Saving setting..."); - - File settingsFile = SPIFFS.open("/settings.json", FILE_WRITE); - - if (!settingsFile) { - Serial.println(F("Failed to create settings file")); - return false; - } - - if (serializeJson(json, settingsFile) == 0) { - Serial.println(F("Failed to write to file")); - } - if (serializeJson(json, settings_string) == 0) { - Serial.println(F("Failed to write to string")); - } - - // Close the file - settingsFile.close(); - - this->json_settings_string = settings_string; - - this->printJsonSettings(settings_string); - - return true; - } - } - return false; -} - -template -T Settings::saveSetting(String key, String value) {} - -template<> -bool Settings::saveSetting(String key, String value) { - DynamicJsonDocument json(JSON_SETTING_SIZE); // ArduinoJson v6 - - if (deserializeJson(json, this->json_settings_string)) { - Serial.println("\nCould not parse json"); - } - - String settings_string; - - for (int i = 0; i < json["Settings"].size(); i++) { - if (json["Settings"][i]["name"].as() == key) { - json["Settings"][i]["value"] = value; - - File settingsFile = SPIFFS.open("/settings.json", FILE_WRITE); - - if (!settingsFile) { - return false; - } - - if (serializeJson(json, settingsFile) == 0) { - Serial.println(F("Failed to write to file")); - } - if (serializeJson(json, settings_string) == 0) { - Serial.println(F("Failed to write to string")); - } - - // Close the file - settingsFile.close(); - - this->json_settings_string = settings_string; - - this->printJsonSettings(settings_string); - - return true; - } - } - return false; -} - -bool Settings::toggleSetting(String key) { - DynamicJsonDocument json(JSON_SETTING_SIZE); // ArduinoJson v6 - - if (deserializeJson(json, this->json_settings_string)) { - Serial.println("\nCould not parse json"); - } - - for (int i = 0; i < json["Settings"].size(); i++) { - if (json["Settings"][i]["name"].as() == key) { - if (json["Settings"][i]["value"]) { - saveSetting(key, false); - return false; - } - else { - saveSetting(key, true); - return true; - } - - return false; - } - } -} - -String Settings::setting_index_to_name(int i) { - DynamicJsonDocument json(JSON_SETTING_SIZE); // ArduinoJson v6 - - if (deserializeJson(json, this->json_settings_string)) { - Serial.println("\nCould not parse json"); - } - - return json["Settings"][i]["name"]; -} - -int Settings::getNumberSettings() { - DynamicJsonDocument json(JSON_SETTING_SIZE); // ArduinoJson v6 - - if (deserializeJson(json, this->json_settings_string)) { - Serial.println("\nCould not parse json"); - } - - return json["Settings"].size(); -} - -String Settings::getSettingType(String key) { - DynamicJsonDocument json(JSON_SETTING_SIZE); // ArduinoJson v6 - - DeserializationError error = deserializeJson(json, this->json_settings_string); - - if (error) { - Serial.print("\nCould not parse json: "); - Serial.println(error.f_str()); - } - - for (int i = 0; i < json["Settings"].size(); i++) { - if (json["Settings"][i]["name"].as() == key) - return json["Settings"][i]["type"]; - } - - return ""; -} - -void Settings::printJsonSettings(String json_string) { - DynamicJsonDocument json(JSON_SETTING_SIZE); // ArduinoJson v6 - - if (deserializeJson(json, json_string)) { - Serial.println("\nCould not parse json"); - } - - for (int i = 0; i < json["Settings"].size(); i++) { - Serial.println("Name: " + json["Settings"][i]["name"].as()); - Serial.println("Type: " + json["Settings"][i]["type"].as()); - Serial.println("Value: " + json["Settings"][i]["value"].as() + "\n"); - } -} - -bool Settings::createDefaultSettings(fs::FS &fs, bool spec, uint8_t index, String typeStr, String name) { - Serial.println(F("Creating default settings file: settings.json")); - - File settingsFile = fs.open("/settings.json", FILE_WRITE); - - if (!settingsFile) { - Serial.println(F("Failed to create settings file")); - return false; - } - - String settings_string; - - if (!spec) { - DynamicJsonDocument jsonBuffer(JSON_SETTING_SIZE); - - jsonBuffer["Settings"][0]["name"] = "ForcePMKID"; - jsonBuffer["Settings"][0]["type"] = "bool"; - jsonBuffer["Settings"][0]["value"] = false; - jsonBuffer["Settings"][0]["range"]["min"] = false; - jsonBuffer["Settings"][0]["range"]["max"] = true; - - jsonBuffer["Settings"][1]["name"] = "ForceProbe"; - jsonBuffer["Settings"][1]["type"] = "bool"; - jsonBuffer["Settings"][1]["value"] = false; - jsonBuffer["Settings"][1]["range"]["min"] = false; - jsonBuffer["Settings"][1]["range"]["max"] = true; - - jsonBuffer["Settings"][2]["name"] = "SavePCAP"; - jsonBuffer["Settings"][2]["type"] = "bool"; - jsonBuffer["Settings"][2]["value"] = true; - jsonBuffer["Settings"][2]["range"]["min"] = false; - jsonBuffer["Settings"][2]["range"]["max"] = true; - - jsonBuffer["Settings"][3]["name"] = "EnableLED"; - jsonBuffer["Settings"][3]["type"] = "bool"; - jsonBuffer["Settings"][3]["value"] = true; - jsonBuffer["Settings"][3]["range"]["min"] = false; - jsonBuffer["Settings"][3]["range"]["max"] = true; - - jsonBuffer["Settings"][4]["name"] = "EPDeauth"; - jsonBuffer["Settings"][4]["type"] = "bool"; - jsonBuffer["Settings"][4]["value"] = false; - jsonBuffer["Settings"][4]["range"]["min"] = false; - jsonBuffer["Settings"][4]["range"]["max"] = true; - - jsonBuffer["Settings"][5]["name"] = "ChanHop"; - jsonBuffer["Settings"][5]["type"] = "bool"; - jsonBuffer["Settings"][5]["value"] = false; - jsonBuffer["Settings"][5]["range"]["min"] = false; - jsonBuffer["Settings"][5]["range"]["max"] = true; - - jsonBuffer["Settings"][6]["name"] = "ClientSSID"; - jsonBuffer["Settings"][6]["type"] = "String"; - jsonBuffer["Settings"][6]["value"] = ""; - jsonBuffer["Settings"][6]["range"]["min"] = ""; - jsonBuffer["Settings"][6]["range"]["max"] = ""; - - jsonBuffer["Settings"][7]["name"] = "ClientPW"; - jsonBuffer["Settings"][7]["type"] = "String"; - jsonBuffer["Settings"][7]["value"] = ""; - jsonBuffer["Settings"][7]["range"]["min"] = ""; - jsonBuffer["Settings"][7]["range"]["max"] = ""; - - //jsonBuffer.printTo(settingsFile); - if (serializeJson(jsonBuffer, settingsFile) == 0) { - } - if (serializeJson(jsonBuffer, settings_string) == 0) { - } - } - - else { - DynamicJsonDocument json(JSON_SETTING_SIZE); // ArduinoJson v6 - - if (deserializeJson(json, this->json_settings_string)) { - Serial.println("Could not parse json to create new setting"); - return false; - } - - Serial.println("Creating " + typeStr + " setting..."); - - if (typeStr == "bool") { - - json["Settings"][index]["name"] = name; - json["Settings"][index]["type"] = typeStr; - json["Settings"][index]["value"] = true; - json["Settings"][index]["range"]["min"] = false; - json["Settings"][index]["range"]["max"] = true; - - if (serializeJson(json, settings_string) == 0) { - } - - if (serializeJson(json, settingsFile) == 0) { - } - } - - else if (typeStr == "String") { - json["Settings"][index]["name"] = name; - json["Settings"][index]["type"] = typeStr; - json["Settings"][index]["value"] = ""; - json["Settings"][index]["range"]["min"] = ""; - json["Settings"][index]["range"]["max"] = ""; - - if (serializeJson(json, settings_string) == 0) { - } - - if (serializeJson(json, settingsFile) == 0) { - } - } - } - - // Close the file - settingsFile.close(); - - this->json_settings_string = settings_string; - - this->printJsonSettings(settings_string); - - return true; -} +#include "settings.h" + +// --------------------------------------------------------------------------- +// _buildCache — called once after json_settings_string is loaded/updated. +// Parses the JSON exactly once and fills every field of _cache. +// All loadSetting() reads hit the cache; no heap is allocated on read. +// --------------------------------------------------------------------------- +void Settings::_buildCache() { + DynamicJsonDocument json(JSON_SETTING_SIZE); + if (deserializeJson(json, this->json_settings_string)) { + Serial.println(F("_buildCache: could not parse json")); + return; + } + + for (int i = 0; i < (int)json["Settings"].size(); i++) { + String name = json["Settings"][i]["name"].as(); + + if (name == "ForcePMKID") + _cache.ForcePMKID = json["Settings"][i]["value"].as(); + else if (name == "ForceProbe") + _cache.ForceProbe = json["Settings"][i]["value"].as(); + else if (name == "SavePCAP") + _cache.SavePCAP = json["Settings"][i]["value"].as(); + else if (name == "EnableLED") + _cache.EnableLED = json["Settings"][i]["value"].as(); + else if (name == "EPDeauth") + _cache.EPDeauth = json["Settings"][i]["value"].as(); + else if (name == "ChanHop") + _cache.ChanHop = json["Settings"][i]["value"].as(); + else if (name == "ClientSSID") + _cache.ClientSSID = json["Settings"][i]["value"].as(); + else if (name == "ClientPW") + _cache.ClientPW = json["Settings"][i]["value"].as(); + } +} + +// --------------------------------------------------------------------------- + +String Settings::getSettingsString() { return this->json_settings_string; } + +bool Settings::begin() { + if (!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) { + return false; + } + + File settingsFile; + + // SPIFFS.remove("/settings.json"); // NEED TO REMOVE THIS LINE + + if (SPIFFS.exists("/settings.json")) { + settingsFile = SPIFFS.open("/settings.json", FILE_READ); + + if (!settingsFile) { + settingsFile.close(); + if (this->createDefaultSettings(SPIFFS)) + return true; + else + return false; + } + } else { + if (this->createDefaultSettings(SPIFFS)) + return true; + else + return false; + } + + String json_string; + DynamicJsonDocument jsonBuffer(JSON_SETTING_SIZE); + DeserializationError error = deserializeJson(jsonBuffer, settingsFile); + if (error) + Serial.println(error.f_str()); + + serializeJson(jsonBuffer, json_string); + + this->json_settings_string = json_string; + + // Populate the flat cache from the freshly loaded JSON. + this->_buildCache(); + + return true; +} + +// --------------------------------------------------------------------------- +// loadSetting — O(1), zero heap, reads straight from the cache struct. +// --------------------------------------------------------------------------- + +template T Settings::loadSetting(String key) {} + +// Get type int settings +template <> int Settings::loadSetting(String key) { + // No int settings are defined currently; fall back to parsing if ever added. + DynamicJsonDocument json(JSON_SETTING_SIZE); + deserializeJson(json, this->json_settings_string); + for (int i = 0; i < (int)json["Settings"].size(); i++) { + if (json["Settings"][i]["name"].as() == key) + return json["Settings"][i]["value"]; + } + return 0; +} + +// Get type String settings — read from cache, no JSON parse +template <> String Settings::loadSetting(String key) { + if (key == "ClientSSID") + return _cache.ClientSSID; + if (key == "ClientPW") + return _cache.ClientPW; + + // Unknown String key: fall back to JSON so the setting can be auto-created. + DynamicJsonDocument json(JSON_SETTING_SIZE); + deserializeJson(json, this->json_settings_string); + for (int i = 0; i < (int)json["Settings"].size(); i++) { + if (json["Settings"][i]["name"].as() == key) + return json["Settings"][i]["value"].as(); + } + + //Serial.println("Did not find setting named " + (String)key + ". Creating..."); + if (this->createDefaultSettings(SPIFFS, true, json["Settings"].size(), + "String", key)) + return ""; + + return ""; +} + +// Get type bool settings — read from cache, no JSON parse +template <> bool Settings::loadSetting(String key) { + if (key == "ForcePMKID") + return _cache.ForcePMKID; + if (key == "ForceProbe") + return _cache.ForceProbe; + if (key == "SavePCAP") + return _cache.SavePCAP; + if (key == "EnableLED") + return _cache.EnableLED; + if (key == "EPDeauth") + return _cache.EPDeauth; + if (key == "ChanHop") + return _cache.ChanHop; + + // Unknown bool key: fall back to JSON so the setting can be auto-created. + DynamicJsonDocument json(JSON_SETTING_SIZE); + deserializeJson(json, this->json_settings_string); + for (int i = 0; i < (int)json["Settings"].size(); i++) { + if (json["Settings"][i]["name"].as() == key) + return json["Settings"][i]["value"].as(); + } + + //Serial.println("Did not find setting named " + (String)key + ". Creating..."); + if (this->createDefaultSettings(SPIFFS, true, json["Settings"].size(), "bool", + key)) + return true; + + return false; +} + +// Get type uint8_t settings — read from cache, no JSON parse +template <> uint8_t Settings::loadSetting(String key) { + // uint8_t settings reuse bool cache fields where applicable. + if (key == "ForcePMKID") + return (uint8_t)_cache.ForcePMKID; + if (key == "ForceProbe") + return (uint8_t)_cache.ForceProbe; + if (key == "SavePCAP") + return (uint8_t)_cache.SavePCAP; + if (key == "EnableLED") + return (uint8_t)_cache.EnableLED; + if (key == "EPDeauth") + return (uint8_t)_cache.EPDeauth; + if (key == "ChanHop") + return (uint8_t)_cache.ChanHop; + + DynamicJsonDocument json(JSON_SETTING_SIZE); + deserializeJson(json, this->json_settings_string); + for (int i = 0; i < (int)json["Settings"].size(); i++) { + if (json["Settings"][i]["name"].as() == key) + return json["Settings"][i]["value"].as(); + } + return 0; +} + +// --------------------------------------------------------------------------- +// saveSetting — writes SPIFFS, updates json_settings_string, rebuilds cache. +// --------------------------------------------------------------------------- + +template T Settings::saveSetting(String key, bool value) {} + +template <> bool Settings::saveSetting(String key, bool value) { + DynamicJsonDocument json(JSON_SETTING_SIZE); + + deserializeJson(json, this->json_settings_string); + + String settings_string; + + for (int i = 0; i < (int)json["Settings"].size(); i++) { + if (json["Settings"][i]["name"].as() == key) { + json["Settings"][i]["value"] = value; + + File settingsFile = SPIFFS.open("/settings.json", FILE_WRITE); + + if (!settingsFile) { + return false; + } + + serializeJson(json, settingsFile); + serializeJson(json, settings_string); + + settingsFile.close(); + + this->json_settings_string = settings_string; + + // Keep the cache in sync — no re-parse needed, just update the field. + if (key == "ForcePMKID") + _cache.ForcePMKID = value; + else if (key == "ForceProbe") + _cache.ForceProbe = value; + else if (key == "SavePCAP") + _cache.SavePCAP = value; + else if (key == "EnableLED") + _cache.EnableLED = value; + else if (key == "EPDeauth") + _cache.EPDeauth = value; + else if (key == "ChanHop") + _cache.ChanHop = value; + + this->printJsonSettings(settings_string); + + return true; + } + } + return false; +} + +template T Settings::saveSetting(String key, String value) {} + +template <> bool Settings::saveSetting(String key, String value) { + DynamicJsonDocument json(JSON_SETTING_SIZE); + + deserializeJson(json, this->json_settings_string); + + String settings_string; + + for (int i = 0; i < (int)json["Settings"].size(); i++) { + if (json["Settings"][i]["name"].as() == key) { + json["Settings"][i]["value"] = value; + + //Serial.println("Saving setting..."); + + File settingsFile = SPIFFS.open("/settings.json", FILE_WRITE); + + if (!settingsFile) + return false; + + serializeJson(json, settingsFile); + serializeJson(json, settings_string); + + settingsFile.close(); + + this->json_settings_string = settings_string; + + // Keep the cache in sync for String fields. + if (key == "ClientSSID") + _cache.ClientSSID = value; + else if (key == "ClientPW") + _cache.ClientPW = value; + + this->printJsonSettings(settings_string); + + return true; + } + } + return false; +} + +// --------------------------------------------------------------------------- +// toggleSetting — reads current bool value from cache (fast), then delegates +// to saveSetting which will update the cache again. +// --------------------------------------------------------------------------- +bool Settings::toggleSetting(String key) { + // Use the cached value to decide direction — avoids an extra JSON parse. + bool current = this->loadSetting(key); + if (current) { + saveSetting(key, false); + //Serial.println("Setting value to false"); + return false; + } else { + saveSetting(key, true); + //Serial.println("Setting value to true"); + return true; + } +} + +// --------------------------------------------------------------------------- +// Rarely-called utility functions — keep JSON parsing, they are not hot paths. +// --------------------------------------------------------------------------- + +String Settings::setting_index_to_name(int i) { + DynamicJsonDocument json(JSON_SETTING_SIZE); + + deserializeJson(json, this->json_settings_string); + + return json["Settings"][i]["name"]; +} + +int Settings::getNumberSettings() { + DynamicJsonDocument json(JSON_SETTING_SIZE); + + deserializeJson(json, this->json_settings_string); + + return json["Settings"].size(); +} + +String Settings::getSettingType(String key) { + DynamicJsonDocument json(JSON_SETTING_SIZE); + + deserializeJson(json, this->json_settings_string); + + for (int i = 0; i < (int)json["Settings"].size(); i++) { + if (json["Settings"][i]["name"].as() == key) + return json["Settings"][i]["type"]; + } + + return ""; +} + +void Settings::printJsonSettings(String json_string) { + DynamicJsonDocument json(JSON_SETTING_SIZE); + + deserializeJson(json, json_string); + + Serial.println("Settings\n----------------------------------------------"); + for (int i = 0; i < (int)json["Settings"].size(); i++) { + Serial.println("Name: " + json["Settings"][i]["name"].as()); + Serial.println("Type: " + json["Settings"][i]["type"].as()); + Serial.println("Value: " + json["Settings"][i]["value"].as() + "\n"); + } +} + +// --------------------------------------------------------------------------- +// createDefaultSettings — sets json_settings_string then rebuilds the cache. +// --------------------------------------------------------------------------- +bool Settings::createDefaultSettings(fs::FS &fs, bool spec, uint8_t index, String typeStr, String name) { + File settingsFile = fs.open("/settings.json", FILE_WRITE); + + if (!settingsFile) { + //Serial.println(F("Failed to create settings file")); + return false; + } + + String settings_string; + + if (!spec) { + DynamicJsonDocument jsonBuffer(JSON_SETTING_SIZE); + + jsonBuffer["Settings"][0]["name"] = "ForcePMKID"; + jsonBuffer["Settings"][0]["type"] = "bool"; + jsonBuffer["Settings"][0]["value"] = false; + jsonBuffer["Settings"][0]["range"]["min"] = false; + jsonBuffer["Settings"][0]["range"]["max"] = true; + + jsonBuffer["Settings"][1]["name"] = "ForceProbe"; + jsonBuffer["Settings"][1]["type"] = "bool"; + jsonBuffer["Settings"][1]["value"] = false; + jsonBuffer["Settings"][1]["range"]["min"] = false; + jsonBuffer["Settings"][1]["range"]["max"] = true; + + jsonBuffer["Settings"][2]["name"] = "SavePCAP"; + jsonBuffer["Settings"][2]["type"] = "bool"; + jsonBuffer["Settings"][2]["value"] = true; + jsonBuffer["Settings"][2]["range"]["min"] = false; + jsonBuffer["Settings"][2]["range"]["max"] = true; + + jsonBuffer["Settings"][3]["name"] = "EnableLED"; + jsonBuffer["Settings"][3]["type"] = "bool"; + jsonBuffer["Settings"][3]["value"] = true; + jsonBuffer["Settings"][3]["range"]["min"] = false; + jsonBuffer["Settings"][3]["range"]["max"] = true; + + jsonBuffer["Settings"][4]["name"] = "EPDeauth"; + jsonBuffer["Settings"][4]["type"] = "bool"; + jsonBuffer["Settings"][4]["value"] = false; + jsonBuffer["Settings"][4]["range"]["min"] = false; + jsonBuffer["Settings"][4]["range"]["max"] = true; + + jsonBuffer["Settings"][5]["name"] = "ChanHop"; + jsonBuffer["Settings"][5]["type"] = "bool"; + jsonBuffer["Settings"][5]["value"] = false; + jsonBuffer["Settings"][5]["range"]["min"] = false; + jsonBuffer["Settings"][5]["range"]["max"] = true; + + jsonBuffer["Settings"][6]["name"] = "ClientSSID"; + jsonBuffer["Settings"][6]["type"] = "String"; + jsonBuffer["Settings"][6]["value"] = ""; + jsonBuffer["Settings"][6]["range"]["min"] = ""; + jsonBuffer["Settings"][6]["range"]["max"] = ""; + + jsonBuffer["Settings"][7]["name"] = "ClientPW"; + jsonBuffer["Settings"][7]["type"] = "String"; + jsonBuffer["Settings"][7]["value"] = ""; + jsonBuffer["Settings"][7]["range"]["min"] = ""; + jsonBuffer["Settings"][7]["range"]["max"] = ""; + + //jsonBuffer.printTo(settingsFile); + if (serializeJson(jsonBuffer, settingsFile) == 0) { + } + if (serializeJson(jsonBuffer, settings_string) == 0) { + } + } + + else { + DynamicJsonDocument json(JSON_SETTING_SIZE); // ArduinoJson v6 + + if (deserializeJson(json, this->json_settings_string)) { + return false; + } + + if (typeStr == "bool") { + + json["Settings"][index]["name"] = name; + json["Settings"][index]["type"] = typeStr; + json["Settings"][index]["value"] = true; + json["Settings"][index]["range"]["min"] = false; + json["Settings"][index]["range"]["max"] = true; + + if (serializeJson(json, settings_string) == 0) { + } + + if (serializeJson(json, settingsFile) == 0) { + } + } + + else if (typeStr == "String") { + json["Settings"][index]["name"] = name; + json["Settings"][index]["type"] = typeStr; + json["Settings"][index]["value"] = ""; + json["Settings"][index]["range"]["min"] = ""; + json["Settings"][index]["range"]["max"] = ""; + + if (serializeJson(json, settings_string) == 0) { + } + + if (serializeJson(json, settingsFile) == 0) { + } + } + } + + // Close the file + settingsFile.close(); + + this->json_settings_string = settings_string; + + // Rebuild cache from the newly written settings. + this->_buildCache(); + + this->printJsonSettings(settings_string); + + return true; +} diff --git a/esp32_marauder/settings.h b/esp32_marauder/settings.h index 1d4cd5e..d083687 100644 --- a/esp32_marauder/settings.h +++ b/esp32_marauder/settings.h @@ -22,6 +22,21 @@ class Settings { private: String json_settings_string; + // Flat cache populated once at begin() and kept in sync by saveSetting(). + // All loadSetting() reads hit this struct — zero heap, zero JSON parse. + struct SettingsCache { + bool ForcePMKID = false; + bool ForceProbe = false; + bool SavePCAP = true; + bool EnableLED = true; + bool EPDeauth = false; + bool ChanHop = false; + String ClientSSID = ""; + String ClientPW = ""; + } _cache; + + void _buildCache(); // parse json_settings_string -> _cache + public: bool begin();