4#error "PoliticianStorage.h requires the Arduino framework. Use ESP-IDF VFS and nvs_flash APIs directly."
9#include <Preferences.h>
24 if (out < maxLen - 1) output[out++] =
'"';
25 for (
size_t i = 0; input[i] && out < maxLen - 2; i++) {
26 if (input[i] ==
'"' && out < maxLen - 2) output[out++] =
'"';
27 output[out++] = input[i];
29 if (out < maxLen - 1) output[out++] =
'"';
58 bool open(fs::FS &fs,
const char *path) {
62 bool isNew = !fs.exists(path);
64 fs::File check = fs.open(path, FILE_READ);
65 if (check) { isNew = (check.size() == 0); check.close(); }
68 _file = fs.open(path, FILE_APPEND);
69 if (!_file) { _fs =
nullptr;
return false; }
85 if (!_open)
return false;
88 if (len > 0) { _file.write(buf, len); _file.flush(); }
96 bool writePacket(
const uint8_t *payload, uint16_t len, int8_t rssi, uint8_t channel, uint32_t ts_usec) {
97 if (!_open)
return false;
100 if (wlen > 0) { _file.write(buf, wlen); _file.flush(); }
106 if (_open) { _file.close(); _open =
false; _fs =
nullptr; }
120 return logger.
open(fs, path) && logger.
write(rec);
127 static bool appendPacket(fs::FS &fs,
const char *path,
const uint8_t *payload, uint16_t len,
128 int8_t rssi, uint8_t channel, uint32_t ts_usec) {
130 return logger.
open(fs, path) && logger.
writePacket(payload, len, rssi, channel, ts_usec);
154 bool open(fs::FS &fs,
const char *path) {
156 _file = fs.open(path, FILE_APPEND);
157 if (!_file)
return false;
167 if (!_open)
return false;
169 if (!str.empty()) { _file.println(str.c_str()); _file.flush(); }
175 if (_open) { _file.close(); _open =
false; }
187 return logger.
open(fs, path) && logger.
write(rec);
216 float lat,
float lon,
float alt = 0.0,
float acc = 10.0,
217 const char* timestamp =
nullptr) {
218 fs::File file = _openWithHeader(fs, path);
219 if (!file)
return false;
221 char ssidEscaped[72];
225 snprintf(line,
sizeof(line),
"%02X:%02X:%02X:%02X:%02X:%02X,%s,%s,%s,%d,%d,%.6f,%.6f,%.1f,%.1f,WIFI",
227 ssidEscaped, _authStr(rec.
enc), timestamp ? timestamp :
"1970-01-01 00:00:00",
250 float lat,
float lon,
float alt = 0.0,
float acc = 10.0,
251 const char* timestamp =
nullptr) {
252 fs::File file = _openWithHeader(fs, path);
253 if (!file)
return false;
255 char ssidEscaped[72];
259 snprintf(line,
sizeof(line),
"%02X:%02X:%02X:%02X:%02X:%02X,%s,%s,%s,%d,%d,%.6f,%.6f,%.1f,%.1f,WIFI",
261 ssidEscaped, _authStr(ap.
enc), timestamp ? timestamp :
"1970-01-01 00:00:00",
271 static const char* _authStr(uint8_t enc) {
273 case 1:
return "[WEP][ESS]";
274 case 2:
return "[WPA-PSK-TKIP][ESS]";
275 case 3:
return "[WPA2-PSK-CCMP][ESS]";
276 case 4:
return "[WPA2-EAP-CCMP][ESS]";
277 default:
return "[ESS]";
281 static fs::File _openWithHeader(fs::FS &fs,
const char* path) {
282 bool isNew = !fs.exists(path);
284 fs::File check = fs.open(path, FILE_READ);
285 if (check) { isNew = (check.size() == 0); check.close(); }
287 fs::File file = fs.open(path, FILE_APPEND);
288 if (!file)
return file;
290 file.println(
"WigleWifi-1.4,appRelease=1.0,model=Politician,release=1.0,device=ESP32,display=1.0,board=ESP32,brand=Espressif");
291 file.println(
"MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type");
304 bool isNew = !fs.exists(path);
306 fs::File file = fs.open(path, FILE_APPEND);
307 if (!file)
return false;
310 file.println(
"Enterprise BSSID,Client MAC,Plaintext Identity,Channel,RSSI");
313 char identityEscaped[136];
317 snprintf(line,
sizeof(line),
"%02X:%02X:%02X:%02X:%02X:%02X,%02X:%02X:%02X:%02X:%02X:%02X,%s,%d,%d",
337 static const int MAX_STORED = 128;
338 uint8_t _cache[MAX_STORED][6];
345 Serial.println(
"[NvsBssidCache] WARNING: namespace name exceeds 15 chars and will be truncated");
346 strncpy(_ns, ns,
sizeof(_ns) - 1);
347 _ns[
sizeof(_ns) - 1] =
'\0';
348 memset(_cache, 0,
sizeof(_cache));
355 _prefs.begin(_ns,
false);
356 size_t bytes = _prefs.getBytes(
"bssids", _cache,
sizeof(_cache));
358 if (_count > MAX_STORED) _count = MAX_STORED;
367 for (
size_t i = 0; i < _count; i++) {
368 engine.markCaptured(_cache[i]);
378 bool add(
const uint8_t* bssid) {
379 for (
size_t i = 0; i < _count; i++) {
380 if (memcmp(_cache[i], bssid, 6) == 0)
return false;
382 if (_count >= MAX_STORED)
return false;
384 memcpy(_cache[_count], bssid, 6);
396 if (!_dirty)
return false;
397 _prefs.putBytes(
"bssids", _cache, _count * 6);
408 size_t count()
const {
return _count; }
416 _prefs.remove(
"bssids");
The core WiFi handshake capturing engine.
Helper for logging harvested 802.1X Enterprise Credentials.
static bool append(fs::FS &fs, const char *path, const EapIdentityRecord &rec)
Helper for writing HandshakeRecords to an HC22000 text file.
bool open(fs::FS &fs, const char *path)
Opens the HC22000 file for streaming writes.
void close()
Closes the underlying file handle.
bool isOpen() const
Returns true if the file is currently open for streaming.
bool write(const HandshakeRecord &rec)
Writes a HandshakeRecord to the open file.
static bool append(fs::FS &fs, const char *path, const HandshakeRecord &rec)
Appends a HandshakeRecord to a file as an HC22000 string (opens and closes per call).
Helper for persistently storing captured BSSIDs in NVS memory.
NvsBssidCache(const char *ns="wardrive")
bool isDirty() const
Returns true if there are in-RAM changes not yet written to NVS.
bool add(const uint8_t *bssid)
Adds a newly captured BSSID to the in-RAM cache.
bool flush()
Writes any pending in-RAM changes to NVS.
size_t count() const
Returns the number of BSSIDs currently stored.
void loadInto(Politician &engine)
Feeds the loaded BSSIDs into the Politician engine so it knows to ignore them.
void begin()
Initializes the NVS memory and loads the cached BSSIDs into RAM.
void clear()
Clears the entire cache from both RAM and NVS immediately.
Helper for writing HandshakeRecords and raw packets to a standard PCAPNG file.
static bool appendPacket(fs::FS &fs, const char *path, const uint8_t *payload, uint16_t len, int8_t rssi, uint8_t channel, uint32_t ts_usec)
Appends a raw 802.11 sniffer frame to a PCAPNG file (opens and closes per call).
bool isOpen() const
Returns true if the file is currently open for streaming.
void close()
Closes the underlying file handle.
bool open(fs::FS &fs, const char *path)
Opens the PCAPNG file for streaming writes.
static bool append(fs::FS &fs, const char *path, const HandshakeRecord &rec)
Appends a HandshakeRecord to a file as PCAPNG (opens and closes per call).
bool write(const HandshakeRecord &rec)
Writes a HandshakeRecord to the open file.
bool writePacket(const uint8_t *payload, uint16_t len, int8_t rssi, uint8_t channel, uint32_t ts_usec)
Writes a raw 802.11 sniffer frame to the open file.
Helper for writing precise GPS location coordinates to a Wigle.net compatible CSV file.
static bool append(fs::FS &fs, const char *path, const HandshakeRecord &rec, float lat, float lon, float alt=0.0, float acc=10.0, const char *timestamp=nullptr)
Appends a HandshakeRecord's details alongside GPS coordinates to a Wigle CSV.
static bool appendAp(fs::FS &fs, const char *path, const ApRecord &ap, float lat, float lon, float alt=0.0, float acc=10.0, const char *timestamp=nullptr)
Appends any discovered ApRecord alongside GPS coordinates to a Wigle CSV.
void escapeCsvField(const char *input, char *output, size_t maxLen)
Writes a CSV-safe quoted field into output (RFC 4180).
Snapshot of a discovered Access Point from the internal cache.
A harvested 802.1X Enterprise plaintext identity, delivered to the IdentityCb callback.
A captured handshake or PMKID record delivered to the EapolCb callback.