From c8e5c3bd8b353092c7fd5b7ed0327d044ee53b74 Mon Sep 17 00:00:00 2001 From: RogueMaster Date: Fri, 28 Jul 2023 20:40:02 -0400 Subject: [PATCH] Add Adv WifiSniffer --- ReadMe.md | 8 +- .../advanced_wifisniff/application.fam | 15 + .../assets/DolphinWait_61x59.png | Bin 0 -> 3896 bytes .../advanced_wifisniff/assets/down.png | Bin 0 -> 156 bytes .../external/advanced_wifisniff/assets/up.png | Bin 0 -> 155 bytes .../advanced_wifisniff/helpers/minmea.c | 640 ++++++++++++++ .../advanced_wifisniff/helpers/minmea.h | 295 +++++++ .../external/advanced_wifisniff/sniff.png | Bin 0 -> 160 bytes .../external/advanced_wifisniff/sniffer.c | 817 ++++++++++++++++++ 9 files changed, 1771 insertions(+), 4 deletions(-) create mode 100644 applications/external/advanced_wifisniff/application.fam create mode 100644 applications/external/advanced_wifisniff/assets/DolphinWait_61x59.png create mode 100644 applications/external/advanced_wifisniff/assets/down.png create mode 100644 applications/external/advanced_wifisniff/assets/up.png create mode 100644 applications/external/advanced_wifisniff/helpers/minmea.c create mode 100644 applications/external/advanced_wifisniff/helpers/minmea.h create mode 100644 applications/external/advanced_wifisniff/sniff.png create mode 100644 applications/external/advanced_wifisniff/sniffer.c diff --git a/ReadMe.md b/ReadMe.md index d96cb715a44..4b17fb79596 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -31,20 +31,20 @@ This software is for experimental purposes only and is not meant for any illegal ## Latest Updates - [PATREON: Latest Release RM0728-0240-0.87.1-31c0a6c](https://www.patreon.com/RogueMaster?filters[tag]=Latest%20Release) -- Last Synced/Checked Unleashed, changes in [changelog](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/blob/420/CHANGELOG.md) and in [commits](https://github.com/DarkFlippers/unleashed-firmware/commits/dev): `2023-07-28 20:15 EST` -- Last Synced/Checked OFW, changes in [commits](https://github.com/flipperdevices/flipperzero-firmware/commits/dev): `2023-07-28 20:15 EST` +- Last Synced/Checked Unleashed, changes in [changelog](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/blob/420/CHANGELOG.md) and in [commits](https://github.com/DarkFlippers/unleashed-firmware/commits/dev): `2023-07-28 20:40 EST` +- Last Synced/Checked OFW, changes in [commits](https://github.com/flipperdevices/flipperzero-firmware/commits/dev): `2023-07-28 20:40 EST` - [Fixed Memory leak/Pointer issue with CFW Settings app (By ESurge)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/724) - [Configurable SPI & UART Channels (By Sil333033)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/commit/fc4c6bdacd7aac9ba8377fddcb966a9ae17da36a) - Updated: [ESP32 Evil Portal v0.2 (By bigbrodude6119)](https://github.com/bigbrodude6119/flipper-zero-evil-portal) [Changes By leedave]( https://github.com/leedave/flipper-zero-evil-portal/tree/leedave/ap_rename/flipper/flipper-evil-portal) - [DSi Layout for Main Menu (By Willy-JL)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/commit/de5a8d222d138a7d01f82a265b088b37576b7a0b) - Updated: [Authenticator/TOTP v3.2 (By akopachov)](https://github.com/akopachov/flipper-zero_authenticator) - OFW: [Fix fbtenv restore #2924 (By drunkbatya)](https://github.com/flipperdevices/flipperzero-firmware/pull/2924) -- [Vertical Layout for Main Menu & Menu Code Style Improvements (By Willy-JL)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/commit/0e54531d1c8bc2fb454345debd9dbc25857c1d0c) - Updated: [Enhanced Sub-Ghz Chat (By twisted-pear)](https://github.com/twisted-pear/esubghz_chat) - Added: [GPIO Controller (By Lokno)](https://github.com/Lokno/gpio_controller) - Updated: [USB Mass Storage (By nminaylov)](https://github.com/flipperdevices/flipperzero-good-faps/tree/nm/usb_mass_storage_app/mass_storage) [Based on OFW#1060 (By kevinwallace)](https://github.com/flipperdevices/flipperzero-firmware/pull/1060) - Added: [u-blox GPS (By liamhays)](https://github.com/liamhays/ublox) -- Updates to Menu styles (By Willy-JL) +- [Vertical Layout for Main Menu & Menu Code Style Improvements (By Willy-JL)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/commit/0e54531d1c8bc2fb454345debd9dbc25857c1d0c) +- Added: [Advanced Wifi Sniffer (By Sil333033)] diff --git a/applications/external/advanced_wifisniff/application.fam b/applications/external/advanced_wifisniff/application.fam new file mode 100644 index 00000000000..eec2e67cf00 --- /dev/null +++ b/applications/external/advanced_wifisniff/application.fam @@ -0,0 +1,15 @@ +App( + appid="gps_adv_wifi_sniffer", + name="[GPS] Advanced Wifi Sniffer", + apptype=FlipperAppType.EXTERNAL, + entry_point="wifisniffer_app", + stack_size=2 * 1024, + fap_category="GPIO/ESP32", + fap_icon="sniff.png", + fap_icon_assets="assets", + fap_icon_assets_symbol="wifisniffer", + fap_author="Sil333033", + fap_weburl="https://github.com/Sil333033", + fap_version=(1, 0), + fap_description="Advanced wifi sniffer for ESP32 GPS", +) \ No newline at end of file diff --git a/applications/external/advanced_wifisniff/assets/DolphinWait_61x59.png b/applications/external/advanced_wifisniff/assets/DolphinWait_61x59.png new file mode 100644 index 0000000000000000000000000000000000000000..bb0c3840038a0470ea1830779c1f1ebf78b839e3 GIT binary patch literal 3896 zcmd5<2~-nj9*^e=h*-tyTI)`O0zR2!j%1PyM1_RQHI@R4csrQ`Qj$!V8AynVN3G{- zU33=@T5A>STV1>8dQhvh+lq=pE!MR@73{Vi$g`jvM%eDlrk z`=7u6|NF8kX=;3+|44s@LJ^plU`_^iBlt!R=na13rw`i!Zho$W>7qgrFjW5bQfw(2 z0S1IEHC;-#Od)CBp`sYx%BnmL7eFf%Q4>8biq2vs$jWBeoJM8C(K01uV~omaS_@)v znb=HQf>&Tuyi-$YZx(G}loKaFQ63TqaIg{uc^r19NP3LQHn}7i%hPHl)CQ5VjLK+P zA(U=Of=s-?LRu98)2K!Z=?p5Az%>R#1cV`|UXAG0C=R0psX<5#gE}rH&?Yb(nQV^h z&;_21%1lXek!rQu?N+&Q6)$9{QG>ytMldyo!2kh^c}|J)z)mrwQ-YZlX~E`_Y`hba zB~n&CM=~mbrR^>_Tt2Z*u_H_%U}_KLQllzFcBu`>&_0|iN3gdkXJ|ERXC16l5&;(V zVL_BE79aenVjT`2T9jgQfkhp@Jrymc=DApPGAr^q0?o$e0*xV^hKW)#`y@0ySPtO6 z#jZ?V;>Aq<8A0&9_JzVlngo`Tcp;VN?VVXp>XZUus0xF^r`eng?-s|&CUz`f&6LC% zl^BAdFoJ!^a%Qw?vslPPrzlZQnoTqtmB3V$&Bl-nj%g4asO1bg6xJAY9Be=} z8rZ6*bp{&IFdVM$TyN&-969~+`u0j-cpAv?#f?PuT0O<#EX*M~3dT_l4O1MVfms?e z5VXdCavaks?RCKhijlH+l`31s0EsNCCva3pz%0V)VPKCHwh~$l)*(8UC3ITWs-u* z&F&c4ZBQFsNs5*$%BZAeHn6~j_G#Nw0@^dv+t<4@Ss?T|ENO>{JSVv+fsM`p!S9xZ z>Sq}*Qn}AJonf?kPKQu1VbHU%hNTeLisGP?;h0XRV;O?ba-FXGO#dloL38WiY>!Mg zndG);fWj>lZF9=C396`lC7nq*Gg#20)yijjMDxyER_Kxt7bQ@jHM4?fRB{6EfP6Io zNjyZkTy`5x$*EN5I+>34=VQ^c^)Rep>k_-JT6`uzFHm2%nZg;Iu z4O$Ha8V#;TtPG_i^q>xYMza59rn>crKsbW7%1uLqYJo?b76wmRScf4vrO_Y+jvKo1 z2=n#J{}1kULpPa`K6<_g?!6d&-Daw1JH~Ul_g~ADT0Zl%_iy#jTYLMnxxJL*P$>HBPBcfSW-h6mVNAx+ffs&2#+@2Cd|oDA+9x*Dc=3Jfa&hP6ir~$u zf7xl-GV8S~OWs=@@L>Ge4?Sb2|6x&#=j|0&4_?2Sk@z>Ru1ItlrP*OMukXLT?^NWN zSF0QUnPYu;J@dr5hbzj=`H!lanhul=y}Wu`mDXFoEA~v$KI^Qyln-<-O=&1s*Vfb& ziI4c&<7H*0$^G7p`Ze#I@ypSZw{Dy+Z6&-Lkb6l#4n3Ju)x7YX!s8DgJXpN4`r9}A zVR6Uq^_S>udb(LW$pPo>uraAX} zFD_nRe&)}EO6I@OhsTfqH9LRQri}%B(86QiBoeK^@SC{o@VBA2HgK(jwzpWZJ>h3e zyx+sVg<-cB(iss$#-5H`&~)82NysXDWtn$~UyZjvkoPJFRM%CbcB!>5s*-@t{4&qY`O{ken3yZuRKjcr68c)2k}h z9R2+Kp?y-?>A^F17VGOiR^6^$P=BBZTXpX4dP_^x?hS{&jJGT2^GiltavrWKJ$W*I zLfWIHp8SZsfPq1H&7JCVt>(T4$v+{Z_|++TMPqZb*0ZrF`R=udo2qqvzxsMoLqh|v z@OZtw7hkg9;Fr%Cu`bwHAPv|q-DsSN-_cQ=g*T$I9}iHAJw|XxN-JklAZSI^oh-v1u5%ae-J?MAg zOx3(UjW!Se*gW6u9=<$n-=upJ!^1Dk{cO}%rQ?a*M&sHsFIV3i7cirU)r`Eb_wlc4 Y#^x8E+Pk44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`1M1^9%x{{R0U$P7JnxeiEi7I;J!GcfQS0Aa?gYn_}xL3vLX#}J9j$q5Mp sDG3KWjkth-VP=y+AA{4h#zsa4nNN%ihm{hR0hKa%y85}Sb4q9e0N(f~O#lD@ literal 0 HcmV?d00001 diff --git a/applications/external/advanced_wifisniff/assets/up.png b/applications/external/advanced_wifisniff/assets/up.png new file mode 100644 index 0000000000000000000000000000000000000000..1b7d469043593f9318f6f955c22007cfc626465c GIT binary patch literal 155 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2ZGmxy8xzq=w7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`1M1^9%x{{R0U$P7JnxeiEi7I;J!GcfQS0Aa?gYn_}xK{-zs#}J9j$qkH- r0tYoT7?QZSnr1W!@HvT1O-TSse_&)_Tl_o|sFK0c)z4*}Q$iB}r+6ll literal 0 HcmV?d00001 diff --git a/applications/external/advanced_wifisniff/helpers/minmea.c b/applications/external/advanced_wifisniff/helpers/minmea.c new file mode 100644 index 00000000000..1b7a84b1c3e --- /dev/null +++ b/applications/external/advanced_wifisniff/helpers/minmea.c @@ -0,0 +1,640 @@ +/* + * Copyright © 2014 Kosma Moczek + * This program is free software. It comes without any warranty, to the extent + * permitted by applicable law. You can redistribute it and/or modify it under + * the terms of the Do What The Fuck You Want To Public License, Version 2, as + * published by Sam Hocevar. See the COPYING file for more details. + */ + +#include "minmea.h" + +#include +#include +#include + +#define boolstr(s) ((s) ? "true" : "false") + +static int hex2int(char c) { + if(c >= '0' && c <= '9') return c - '0'; + if(c >= 'A' && c <= 'F') return c - 'A' + 10; + if(c >= 'a' && c <= 'f') return c - 'a' + 10; + return -1; +} + +uint8_t minmea_checksum(const char* sentence) { + // Support senteces with or without the starting dollar sign. + if(*sentence == '$') sentence++; + + uint8_t checksum = 0x00; + + // The optional checksum is an XOR of all bytes between "$" and "*". + while(*sentence && *sentence != '*') checksum ^= *sentence++; + + return checksum; +} + +bool minmea_check(const char* sentence, bool strict) { + uint8_t checksum = 0x00; + + // A valid sentence starts with "$". + if(*sentence++ != '$') return false; + + // The optional checksum is an XOR of all bytes between "$" and "*". + while(*sentence && *sentence != '*' && isprint((unsigned char)*sentence)) + checksum ^= *sentence++; + + // If checksum is present... + if(*sentence == '*') { + // Extract checksum. + sentence++; + int upper = hex2int(*sentence++); + if(upper == -1) return false; + int lower = hex2int(*sentence++); + if(lower == -1) return false; + int expected = upper << 4 | lower; + + // Check for checksum mismatch. + if(checksum != expected) return false; + } else if(strict) { + // Discard non-checksummed frames in strict mode. + return false; + } + + // The only stuff allowed at this point is a newline. + while(*sentence == '\r' || *sentence == '\n') { + sentence++; + } + + if(*sentence) { + return false; + } + + return true; +} + +bool minmea_scan(const char* sentence, const char* format, ...) { + bool result = false; + bool optional = false; + + if(sentence == NULL) return false; + + va_list ap; + va_start(ap, format); + + const char* field = sentence; +#define next_field() \ + do { \ + /* Progress to the next field. */ \ + while(minmea_isfield(*sentence)) sentence++; \ + /* Make sure there is a field there. */ \ + if(*sentence == ',') { \ + sentence++; \ + field = sentence; \ + } else { \ + field = NULL; \ + } \ + } while(0) + + while(*format) { + char type = *format++; + + if(type == ';') { + // All further fields are optional. + optional = true; + continue; + } + + if(!field && !optional) { + // Field requested but we ran out if input. Bail out. + goto parse_error; + } + + switch(type) { + case 'c': { // Single character field (char). + char value = '\0'; + + if(field && minmea_isfield(*field)) value = *field; + + *va_arg(ap, char*) = value; + } break; + + case 'd': { // Single character direction field (int). + int value = 0; + + if(field && minmea_isfield(*field)) { + switch(*field) { + case 'N': + case 'E': + value = 1; + break; + case 'S': + case 'W': + value = -1; + break; + default: + goto parse_error; + } + } + + *va_arg(ap, int*) = value; + } break; + + case 'f': { // Fractional value with scale (struct minmea_float). + int sign = 0; + int_least32_t value = -1; + int_least32_t scale = 0; + + if(field) { + while(minmea_isfield(*field)) { + if(*field == '+' && !sign && value == -1) { + sign = 1; + } else if(*field == '-' && !sign && value == -1) { + sign = -1; + } else if(isdigit((unsigned char)*field)) { + int digit = *field - '0'; + if(value == -1) value = 0; + if(value > (INT_LEAST32_MAX - digit) / 10) { + /* we ran out of bits, what do we do? */ + if(scale) { + /* truncate extra precision */ + break; + } else { + /* integer overflow. bail out. */ + goto parse_error; + } + } + value = (10 * value) + digit; + if(scale) scale *= 10; + } else if(*field == '.' && scale == 0) { + scale = 1; + } else if(*field == ' ') { + /* Allow spaces at the start of the field. Not NMEA + * conformant, but some modules do this. */ + if(sign != 0 || value != -1 || scale != 0) goto parse_error; + } else { + goto parse_error; + } + field++; + } + } + + if((sign || scale) && value == -1) goto parse_error; + + if(value == -1) { + /* No digits were scanned. */ + value = 0; + scale = 0; + } else if(scale == 0) { + /* No decimal point. */ + scale = 1; + } + if(sign) value *= sign; + + *va_arg(ap, struct minmea_float*) = (struct minmea_float){value, scale}; + } break; + + case 'i': { // Integer value, default 0 (int). + int value = 0; + + if(field) { + char* endptr; + value = strtol(field, &endptr, 10); + if(minmea_isfield(*endptr)) goto parse_error; + } + + *va_arg(ap, int*) = value; + } break; + + case 's': { // String value (char *). + char* buf = va_arg(ap, char*); + + if(field) { + while(minmea_isfield(*field)) *buf++ = *field++; + } + + *buf = '\0'; + } break; + + case 't': { // NMEA talker+sentence identifier (char *). + // This field is always mandatory. + if(!field) goto parse_error; + + if(field[0] != '$') goto parse_error; + for(int f = 0; f < 5; f++) + if(!minmea_isfield(field[1 + f])) goto parse_error; + + char* buf = va_arg(ap, char*); + memcpy(buf, field + 1, 5); + buf[5] = '\0'; + } break; + + case 'D': { // Date (int, int, int), -1 if empty. + struct minmea_date* date = va_arg(ap, struct minmea_date*); + + int d = -1, m = -1, y = -1; + + if(field && minmea_isfield(*field)) { + // Always six digits. + for(int f = 0; f < 6; f++) + if(!isdigit((unsigned char)field[f])) goto parse_error; + + char dArr[] = {field[0], field[1], '\0'}; + char mArr[] = {field[2], field[3], '\0'}; + char yArr[] = {field[4], field[5], '\0'}; + d = strtol(dArr, NULL, 10); + m = strtol(mArr, NULL, 10); + y = strtol(yArr, NULL, 10); + } + + date->day = d; + date->month = m; + date->year = y; + } break; + + case 'T': { // Time (int, int, int, int), -1 if empty. + struct minmea_time* time_ = va_arg(ap, struct minmea_time*); + + int h = -1, i = -1, s = -1, u = -1; + + if(field && minmea_isfield(*field)) { + // Minimum required: integer time. + for(int f = 0; f < 6; f++) + if(!isdigit((unsigned char)field[f])) goto parse_error; + + char hArr[] = {field[0], field[1], '\0'}; + char iArr[] = {field[2], field[3], '\0'}; + char sArr[] = {field[4], field[5], '\0'}; + h = strtol(hArr, NULL, 10); + i = strtol(iArr, NULL, 10); + s = strtol(sArr, NULL, 10); + field += 6; + + // Extra: fractional time. Saved as microseconds. + if(*field++ == '.') { + uint32_t value = 0; + uint32_t scale = 1000000LU; + while(isdigit((unsigned char)*field) && scale > 1) { + value = (value * 10) + (*field++ - '0'); + scale /= 10; + } + u = value * scale; + } else { + u = 0; + } + } + + time_->hours = h; + time_->minutes = i; + time_->seconds = s; + time_->microseconds = u; + } break; + + case '_': { // Ignore the field. + } break; + + default: { // Unknown. + goto parse_error; + } + } + + next_field(); + } + + result = true; + +parse_error: + va_end(ap); + return result; +} + +bool minmea_talker_id(char talker[3], const char* sentence) { + char type[6]; + if(!minmea_scan(sentence, "t", type)) return false; + + talker[0] = type[0]; + talker[1] = type[1]; + talker[2] = '\0'; + + return true; +} + +enum minmea_sentence_id minmea_sentence_id(const char* sentence, bool strict) { + if(!minmea_check(sentence, strict)) return MINMEA_INVALID; + + char type[6]; + if(!minmea_scan(sentence, "t", type)) return MINMEA_INVALID; + + if(!strcmp(type + 2, "GBS")) return MINMEA_SENTENCE_GBS; + if(!strcmp(type + 2, "GGA")) return MINMEA_SENTENCE_GGA; + if(!strcmp(type + 2, "GLL")) return MINMEA_SENTENCE_GLL; + if(!strcmp(type + 2, "GSA")) return MINMEA_SENTENCE_GSA; + if(!strcmp(type + 2, "GST")) return MINMEA_SENTENCE_GST; + if(!strcmp(type + 2, "GSV")) return MINMEA_SENTENCE_GSV; + if(!strcmp(type + 2, "RMC")) return MINMEA_SENTENCE_RMC; + if(!strcmp(type + 2, "VTG")) return MINMEA_SENTENCE_VTG; + if(!strcmp(type + 2, "ZDA")) return MINMEA_SENTENCE_ZDA; + + return MINMEA_UNKNOWN; +} + +bool minmea_parse_gbs(struct minmea_sentence_gbs* frame, const char* sentence) { + // $GNGBS,170556.00,3.0,2.9,8.3,,,,*5C + char type[6]; + if(!minmea_scan( + sentence, + "tTfffifff", + type, + &frame->time, + &frame->err_latitude, + &frame->err_longitude, + &frame->err_altitude, + &frame->svid, + &frame->prob, + &frame->bias, + &frame->stddev)) + return false; + if(strcmp(type + 2, "GBS")) return false; + + return true; +} + +bool minmea_parse_rmc(struct minmea_sentence_rmc* frame, const char* sentence) { + // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62 + char type[6]; + char validity; + int latitude_direction; + int longitude_direction; + int variation_direction; + if(!minmea_scan( + sentence, + "tTcfdfdffDfd", + type, + &frame->time, + &validity, + &frame->latitude, + &latitude_direction, + &frame->longitude, + &longitude_direction, + &frame->speed, + &frame->course, + &frame->date, + &frame->variation, + &variation_direction)) + return false; + if(strcmp(type + 2, "RMC")) return false; + + frame->valid = (validity == 'A'); + frame->latitude.value *= latitude_direction; + frame->longitude.value *= longitude_direction; + frame->variation.value *= variation_direction; + + return true; +} + +bool minmea_parse_gga(struct minmea_sentence_gga* frame, const char* sentence) { + // $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47 + char type[6]; + int latitude_direction; + int longitude_direction; + + if(!minmea_scan( + sentence, + "tTfdfdiiffcfcf_", + type, + &frame->time, + &frame->latitude, + &latitude_direction, + &frame->longitude, + &longitude_direction, + &frame->fix_quality, + &frame->satellites_tracked, + &frame->hdop, + &frame->altitude, + &frame->altitude_units, + &frame->height, + &frame->height_units, + &frame->dgps_age)) + return false; + if(strcmp(type + 2, "GGA")) return false; + + frame->latitude.value *= latitude_direction; + frame->longitude.value *= longitude_direction; + + return true; +} + +bool minmea_parse_gsa(struct minmea_sentence_gsa* frame, const char* sentence) { + // $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39 + char type[6]; + + if(!minmea_scan( + sentence, + "tciiiiiiiiiiiiifff", + type, + &frame->mode, + &frame->fix_type, + &frame->sats[0], + &frame->sats[1], + &frame->sats[2], + &frame->sats[3], + &frame->sats[4], + &frame->sats[5], + &frame->sats[6], + &frame->sats[7], + &frame->sats[8], + &frame->sats[9], + &frame->sats[10], + &frame->sats[11], + &frame->pdop, + &frame->hdop, + &frame->vdop)) + return false; + if(strcmp(type + 2, "GSA")) return false; + + return true; +} + +bool minmea_parse_gll(struct minmea_sentence_gll* frame, const char* sentence) { + // $GPGLL,3723.2475,N,12158.3416,W,161229.487,A,A*41$; + char type[6]; + int latitude_direction; + int longitude_direction; + + if(!minmea_scan( + sentence, + "tfdfdTc;c", + type, + &frame->latitude, + &latitude_direction, + &frame->longitude, + &longitude_direction, + &frame->time, + &frame->status, + &frame->mode)) + return false; + if(strcmp(type + 2, "GLL")) return false; + + frame->latitude.value *= latitude_direction; + frame->longitude.value *= longitude_direction; + + return true; +} + +bool minmea_parse_gst(struct minmea_sentence_gst* frame, const char* sentence) { + // $GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58 + char type[6]; + + if(!minmea_scan( + sentence, + "tTfffffff", + type, + &frame->time, + &frame->rms_deviation, + &frame->semi_major_deviation, + &frame->semi_minor_deviation, + &frame->semi_major_orientation, + &frame->latitude_error_deviation, + &frame->longitude_error_deviation, + &frame->altitude_error_deviation)) + return false; + if(strcmp(type + 2, "GST")) return false; + + return true; +} + +bool minmea_parse_gsv(struct minmea_sentence_gsv* frame, const char* sentence) { + // $GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74 + // $GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D + // $GPGSV,4,2,11,08,51,203,30,09,45,215,28*75 + // $GPGSV,4,4,13,39,31,170,27*40 + // $GPGSV,4,4,13*7B + char type[6]; + + if(!minmea_scan( + sentence, + "tiii;iiiiiiiiiiiiiiii", + type, + &frame->total_msgs, + &frame->msg_nr, + &frame->total_sats, + &frame->sats[0].nr, + &frame->sats[0].elevation, + &frame->sats[0].azimuth, + &frame->sats[0].snr, + &frame->sats[1].nr, + &frame->sats[1].elevation, + &frame->sats[1].azimuth, + &frame->sats[1].snr, + &frame->sats[2].nr, + &frame->sats[2].elevation, + &frame->sats[2].azimuth, + &frame->sats[2].snr, + &frame->sats[3].nr, + &frame->sats[3].elevation, + &frame->sats[3].azimuth, + &frame->sats[3].snr)) { + return false; + } + if(strcmp(type + 2, "GSV")) return false; + + return true; +} + +bool minmea_parse_vtg(struct minmea_sentence_vtg* frame, const char* sentence) { + // $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48 + // $GPVTG,156.1,T,140.9,M,0.0,N,0.0,K*41 + // $GPVTG,096.5,T,083.5,M,0.0,N,0.0,K,D*22 + // $GPVTG,188.36,T,,M,0.820,N,1.519,K,A*3F + char type[6]; + char c_true, c_magnetic, c_knots, c_kph, c_faa_mode; + + if(!minmea_scan( + sentence, + "t;fcfcfcfcc", + type, + &frame->true_track_degrees, + &c_true, + &frame->magnetic_track_degrees, + &c_magnetic, + &frame->speed_knots, + &c_knots, + &frame->speed_kph, + &c_kph, + &c_faa_mode)) + return false; + if(strcmp(type + 2, "VTG")) return false; + // values are only valid with the accompanying characters + if(c_true != 'T') frame->true_track_degrees.scale = 0; + if(c_magnetic != 'M') frame->magnetic_track_degrees.scale = 0; + if(c_knots != 'N') frame->speed_knots.scale = 0; + if(c_kph != 'K') frame->speed_kph.scale = 0; + frame->faa_mode = (enum minmea_faa_mode)c_faa_mode; + + return true; +} + +bool minmea_parse_zda(struct minmea_sentence_zda* frame, const char* sentence) { + // $GPZDA,201530.00,04,07,2002,00,00*60 + char type[6]; + + if(!minmea_scan( + sentence, + "tTiiiii", + type, + &frame->time, + &frame->date.day, + &frame->date.month, + &frame->date.year, + &frame->hour_offset, + &frame->minute_offset)) + return false; + if(strcmp(type + 2, "ZDA")) return false; + + // check offsets + if(abs(frame->hour_offset) > 13 || frame->minute_offset > 59 || frame->minute_offset < 0) + return false; + + return true; +} + +int minmea_getdatetime( + struct tm* tm, + const struct minmea_date* date, + const struct minmea_time* time_) { + if(date->year == -1 || time_->hours == -1) return -1; + + memset(tm, 0, sizeof(*tm)); + if(date->year < 80) { + tm->tm_year = 2000 + date->year - 1900; // 2000-2079 + } else if(date->year >= 1900) { + tm->tm_year = date->year - 1900; // 4 digit year, use directly + } else { + tm->tm_year = date->year; // 1980-1999 + } + tm->tm_mon = date->month - 1; + tm->tm_mday = date->day; + tm->tm_hour = time_->hours; + tm->tm_min = time_->minutes; + tm->tm_sec = time_->seconds; + + return 0; +} + +int minmea_gettime( + struct timespec* ts, + const struct minmea_date* date, + const struct minmea_time* time_) { + struct tm tm; + if(minmea_getdatetime(&tm, date, time_)) return -1; + + time_t timestamp = mktime(&tm); /* See README.md if your system lacks timegm(). */ + if(timestamp != (time_t)-1) { + ts->tv_sec = timestamp; + ts->tv_nsec = time_->microseconds * 1000; + return 0; + } else { + return -1; + } +} + +/* vim: set ts=4 sw=4 et: */ diff --git a/applications/external/advanced_wifisniff/helpers/minmea.h b/applications/external/advanced_wifisniff/helpers/minmea.h new file mode 100644 index 00000000000..88eec4ae985 --- /dev/null +++ b/applications/external/advanced_wifisniff/helpers/minmea.h @@ -0,0 +1,295 @@ +/* + * Copyright © 2014 Kosma Moczek + * This program is free software. It comes without any warranty, to the extent + * permitted by applicable law. You can redistribute it and/or modify it under + * the terms of the Do What The Fuck You Want To Public License, Version 2, as + * published by Sam Hocevar. See the COPYING file for more details. + */ + +#ifndef MINMEA_H +#define MINMEA_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +#ifdef MINMEA_INCLUDE_COMPAT +#include +#endif + +#ifndef MINMEA_MAX_SENTENCE_LENGTH +#define MINMEA_MAX_SENTENCE_LENGTH 80 +#endif + +enum minmea_sentence_id { + MINMEA_INVALID = -1, + MINMEA_UNKNOWN = 0, + MINMEA_SENTENCE_GBS, + MINMEA_SENTENCE_GGA, + MINMEA_SENTENCE_GLL, + MINMEA_SENTENCE_GSA, + MINMEA_SENTENCE_GST, + MINMEA_SENTENCE_GSV, + MINMEA_SENTENCE_RMC, + MINMEA_SENTENCE_VTG, + MINMEA_SENTENCE_ZDA, +}; + +struct minmea_float { + int_least32_t value; + int_least32_t scale; +}; + +struct minmea_date { + int day; + int month; + int year; +}; + +struct minmea_time { + int hours; + int minutes; + int seconds; + int microseconds; +}; + +struct minmea_sentence_gbs { + struct minmea_time time; + struct minmea_float err_latitude; + struct minmea_float err_longitude; + struct minmea_float err_altitude; + int svid; + struct minmea_float prob; + struct minmea_float bias; + struct minmea_float stddev; +}; + +struct minmea_sentence_rmc { + struct minmea_time time; + bool valid; + struct minmea_float latitude; + struct minmea_float longitude; + struct minmea_float speed; + struct minmea_float course; + struct minmea_date date; + struct minmea_float variation; +}; + +struct minmea_sentence_gga { + struct minmea_time time; + struct minmea_float latitude; + struct minmea_float longitude; + int fix_quality; + int satellites_tracked; + struct minmea_float hdop; + struct minmea_float altitude; + char altitude_units; + struct minmea_float height; + char height_units; + struct minmea_float dgps_age; +}; + +enum minmea_gll_status { + MINMEA_GLL_STATUS_DATA_VALID = 'A', + MINMEA_GLL_STATUS_DATA_NOT_VALID = 'V', +}; + +// FAA mode added to some fields in NMEA 2.3. +enum minmea_faa_mode { + MINMEA_FAA_MODE_AUTONOMOUS = 'A', + MINMEA_FAA_MODE_DIFFERENTIAL = 'D', + MINMEA_FAA_MODE_ESTIMATED = 'E', + MINMEA_FAA_MODE_MANUAL = 'M', + MINMEA_FAA_MODE_SIMULATED = 'S', + MINMEA_FAA_MODE_NOT_VALID = 'N', + MINMEA_FAA_MODE_PRECISE = 'P', +}; + +struct minmea_sentence_gll { + struct minmea_float latitude; + struct minmea_float longitude; + struct minmea_time time; + char status; + char mode; +}; + +struct minmea_sentence_gst { + struct minmea_time time; + struct minmea_float rms_deviation; + struct minmea_float semi_major_deviation; + struct minmea_float semi_minor_deviation; + struct minmea_float semi_major_orientation; + struct minmea_float latitude_error_deviation; + struct minmea_float longitude_error_deviation; + struct minmea_float altitude_error_deviation; +}; + +enum minmea_gsa_mode { + MINMEA_GPGSA_MODE_AUTO = 'A', + MINMEA_GPGSA_MODE_FORCED = 'M', +}; + +enum minmea_gsa_fix_type { + MINMEA_GPGSA_FIX_NONE = 1, + MINMEA_GPGSA_FIX_2D = 2, + MINMEA_GPGSA_FIX_3D = 3, +}; + +struct minmea_sentence_gsa { + char mode; + int fix_type; + int sats[12]; + struct minmea_float pdop; + struct minmea_float hdop; + struct minmea_float vdop; +}; + +struct minmea_sat_info { + int nr; + int elevation; + int azimuth; + int snr; +}; + +struct minmea_sentence_gsv { + int total_msgs; + int msg_nr; + int total_sats; + struct minmea_sat_info sats[4]; +}; + +struct minmea_sentence_vtg { + struct minmea_float true_track_degrees; + struct minmea_float magnetic_track_degrees; + struct minmea_float speed_knots; + struct minmea_float speed_kph; + enum minmea_faa_mode faa_mode; +}; + +struct minmea_sentence_zda { + struct minmea_time time; + struct minmea_date date; + int hour_offset; + int minute_offset; +}; + +/** + * Calculate raw sentence checksum. Does not check sentence integrity. + */ +uint8_t minmea_checksum(const char* sentence); + +/** + * Check sentence validity and checksum. Returns true for valid sentences. + */ +bool minmea_check(const char* sentence, bool strict); + +/** + * Determine talker identifier. + */ +bool minmea_talker_id(char talker[3], const char* sentence); + +/** + * Determine sentence identifier. + */ +enum minmea_sentence_id minmea_sentence_id(const char* sentence, bool strict); + +/** + * Scanf-like processor for NMEA sentences. Supports the following formats: + * c - single character (char *) + * d - direction, returned as 1/-1, default 0 (int *) + * f - fractional, returned as value + scale (struct minmea_float *) + * i - decimal, default zero (int *) + * s - string (char *) + * t - talker identifier and type (char *) + * D - date (struct minmea_date *) + * T - time stamp (struct minmea_time *) + * _ - ignore this field + * ; - following fields are optional + * Returns true on success. See library source code for details. + */ +bool minmea_scan(const char* sentence, const char* format, ...); + +/* + * Parse a specific type of sentence. Return true on success. + */ +bool minmea_parse_gbs(struct minmea_sentence_gbs* frame, const char* sentence); +bool minmea_parse_rmc(struct minmea_sentence_rmc* frame, const char* sentence); +bool minmea_parse_gga(struct minmea_sentence_gga* frame, const char* sentence); +bool minmea_parse_gsa(struct minmea_sentence_gsa* frame, const char* sentence); +bool minmea_parse_gll(struct minmea_sentence_gll* frame, const char* sentence); +bool minmea_parse_gst(struct minmea_sentence_gst* frame, const char* sentence); +bool minmea_parse_gsv(struct minmea_sentence_gsv* frame, const char* sentence); +bool minmea_parse_vtg(struct minmea_sentence_vtg* frame, const char* sentence); +bool minmea_parse_zda(struct minmea_sentence_zda* frame, const char* sentence); + +/** + * Convert GPS UTC date/time representation to a UNIX calendar time. + */ +int minmea_getdatetime( + struct tm* tm, + const struct minmea_date* date, + const struct minmea_time* time_); + +/** + * Convert GPS UTC date/time representation to a UNIX timestamp. + */ +int minmea_gettime( + struct timespec* ts, + const struct minmea_date* date, + const struct minmea_time* time_); + +/** + * Rescale a fixed-point value to a different scale. Rounds towards zero. + */ +static inline int_least32_t minmea_rescale(const struct minmea_float* f, int_least32_t new_scale) { + if(f->scale == 0) return 0; + if(f->scale == new_scale) return f->value; + if(f->scale > new_scale) + return (f->value + ((f->value > 0) - (f->value < 0)) * f->scale / new_scale / 2) / + (f->scale / new_scale); + else + return f->value * (new_scale / f->scale); +} + +/** + * Convert a fixed-point value to a floating-point value. + * Returns NaN for "unknown" values. + */ +static inline float minmea_tofloat(const struct minmea_float* f) { + if(f->scale == 0) return NAN; + return (float)f->value / (float)f->scale; +} + +/** + * Convert a raw coordinate to a floating point DD.DDD... value. + * Returns NaN for "unknown" values. + */ +static inline float minmea_tocoord(const struct minmea_float* f) { + if(f->scale == 0) return NAN; + if(f->scale > (INT_LEAST32_MAX / 100)) return NAN; + if(f->scale < (INT_LEAST32_MIN / 100)) return NAN; + int_least32_t degrees = f->value / (f->scale * 100); + int_least32_t minutes = f->value % (f->scale * 100); + return (float)degrees + (float)minutes / (60 * f->scale); +} + +/** + * Check whether a character belongs to the set of characters allowed in a + * sentence data field. + */ +static inline bool minmea_isfield(char c) { + return isprint((unsigned char)c) && c != ',' && c != '*'; +} + +#ifdef __cplusplus +} +#endif + +#endif /* MINMEA_H */ + +/* vim: set ts=4 sw=4 et: */ diff --git a/applications/external/advanced_wifisniff/sniff.png b/applications/external/advanced_wifisniff/sniff.png new file mode 100644 index 0000000000000000000000000000000000000000..c74df70913348ab84934af6cedea25d6470bd5cb GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2ZGmxy8xzq=w7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`1M1^9%x{{R0U$P7JnxeiEi7I;J!GcfQS1YyP<6SLm}1(iKr977~7CnqF` wh$JK=2qYw=Feq{`_qG@*Xi2h29#COsW{{|0IBO{R8DtBCr>mdKI;Vst0EsLpH2?qr literal 0 HcmV?d00001 diff --git a/applications/external/advanced_wifisniff/sniffer.c b/applications/external/advanced_wifisniff/sniffer.c new file mode 100644 index 00000000000..37493976915 --- /dev/null +++ b/applications/external/advanced_wifisniff/sniffer.c @@ -0,0 +1,817 @@ +#include +#include +#include +#include +#include +#include + +#include "helpers/minmea.h" +#include "wifisniffer_icons.h" + +#define appname "wifisniffer" + +#define RX_BUF_SIZE 2048 +#define MAX_ACCESS_POINTS 2048 // imagine getting this many access points + +#define MAX_SSID_LENGTH 32 +#define MAX_BSSID_LENGTH 18 + +#define UART_CH_ESP \ + (CFW_SETTINGS()->uart_esp_channel == UARTDefault ? FuriHalUartIdUSART1 : FuriHalUartIdLPUART1) + +#define UART_CH_GPS \ + (CFW_SETTINGS()->uart_nmea_channel == UARTDefault ? FuriHalUartIdUSART1 : FuriHalUartIdLPUART1) + +#define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone) + +typedef enum { + WorkerEvtStop = (1 << 0), + WorkerEvtRxDone = (1 << 1), +} WorkerEvtFlags; + +typedef enum { + EventTypeKey, + EventTypeTick, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} Event; + +typedef struct { + char* recievedMac; + char* sentMac; +} Packet; + +typedef struct { + char* ssid; + char* bssid; + int8_t rssi; + uint8_t channel; + FuriHalRtcDateTime datetime; + uint16_t packetRxCount; + uint16_t packetTxCount; + float latitude; + float longitude; +} AccessPoint; + +typedef struct { + FuriMessageQueue* queue; + FuriMutex* mutex; + FuriString* buffer; + FuriString* buffer2; + NotificationApp* notifications; + FuriThread* thread_esp; + FuriStreamBuffer* rx_stream_esp; + uint8_t rx_buf_esp[2048]; + FuriThread* thread_gps; + FuriStreamBuffer* rx_stream_gps; + uint8_t rx_buf_gps[2048]; + File* file; + char* dataString; + uint16_t access_points_count; + AccessPoint access_points[MAX_ACCESS_POINTS]; + int16_t access_points_index; + AccessPoint active_access_point; + bool extra_info; + bool pressedButton; + float last_latitude; + float last_longitude; +} Context; + +static void tick_callback(void* ctx_q) { + furi_assert(ctx_q); + FuriMessageQueue* queue = ctx_q; + Event event = {.type = EventTypeTick}; + furi_message_queue_put(queue, &event, 0); +} + +static void input_callback(InputEvent* input_event, FuriMessageQueue* queue) { + furi_assert(queue); + Event event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(queue, &event, FuriWaitForever); +} + +static void show_access_point(Canvas* canvas, Context* context) { + Context* ctx = context; + + AccessPoint ap = ctx->active_access_point; + + // canvas_draw_str_aligned(canvas, 3, 25, AlignLeft, AlignBottom, ap.ssid); + canvas_draw_str_aligned(canvas, 62, 25, AlignCenter, AlignBottom, ap.ssid); + + canvas_set_font(canvas, FontSecondary); + + // canvas_draw_str_aligned(canvas, 43, 12, AlignLeft, AlignBottom, ap.bssid); + canvas_draw_str_aligned( + canvas, 38 + (ctx->access_points_count > 99 ? 5 : 0), 12, AlignLeft, AlignBottom, ap.bssid); + + furi_string_printf(ctx->buffer, "Signal strength: %ddBm", ap.rssi); + canvas_draw_str_aligned( + canvas, 3, 35, AlignLeft, AlignBottom, furi_string_get_cstr(ctx->buffer)); + + furi_string_printf(ctx->buffer, "CH: %d", ap.channel); + canvas_draw_str_aligned( + canvas, 3, 47, AlignLeft, AlignBottom, furi_string_get_cstr(ctx->buffer)); + + if(ap.latitude == 0 && ap.longitude == 0) { + canvas_draw_str_aligned(canvas, 29, 47, AlignLeft, AlignBottom, "X"); + } else { + canvas_draw_str_aligned(canvas, 29, 47, AlignLeft, AlignBottom, "O"); + } + + furi_string_printf(ctx->buffer, "%d", ap.packetRxCount); + canvas_draw_icon(canvas, 35, 39, &I_down); + canvas_draw_str_aligned( + canvas, 45, 47, AlignLeft, AlignBottom, furi_string_get_cstr(ctx->buffer)); + + furi_string_printf(ctx->buffer, "%d", ap.packetTxCount); + canvas_draw_icon(canvas, 85, 38, &I_up); + canvas_draw_str_aligned( + canvas, 95, 47, AlignLeft, AlignBottom, furi_string_get_cstr(ctx->buffer)); + + furi_string_printf( + ctx->buffer, + "Seen: %d:%d:%d (%lds ago)", + ap.datetime.hour, + ap.datetime.minute, + ap.datetime.second, + furi_hal_rtc_get_timestamp() - furi_hal_rtc_datetime_to_timestamp(&ap.datetime)); + canvas_draw_str_aligned( + canvas, 3, 59, AlignLeft, AlignBottom, furi_string_get_cstr(ctx->buffer)); +} + +static void render_callback(Canvas* canvas, void* context) { + Context* ctx = context; + + // hier teken je hoe je projectje eruit ziet + + canvas_draw_frame(canvas, 0, 0, 128, 64); + + canvas_set_font(canvas, FontPrimary); // groot + + if(ctx->access_points_count >= MAX_ACCESS_POINTS) { + canvas_draw_str(canvas, 118, 10, "!"); + } + + if(ctx->access_points_count == 0) { + canvas_draw_str(canvas, 80, 30, "No AP's"); + canvas_draw_str(canvas, 80, 40, "Found!"); + canvas_draw_icon(canvas, 1, 4, &I_DolphinWait_61x59); + } else { + // canvas_draw_frame(canvas, 0, 0, 35, 15); + canvas_draw_frame(canvas, 0, 0, 35 + (ctx->access_points_count > 99 ? 5 : 0), 15); + + furi_string_printf( + ctx->buffer, "%d/%d", ctx->access_points_index + 1, ctx->access_points_count); + + canvas_draw_str(canvas, 3, 12, furi_string_get_cstr(ctx->buffer)); + // canvas_draw_str_aligned( + // canvas, 20, 12, AlignCenter, AlignBottom, furi_string_get_cstr(ctx->buffer)); + + show_access_point(canvas, ctx); + } + // canvas_clear(canvas); + furi_mutex_release(ctx->mutex); +} + +// order ctx->access_points by ssid alphabetically +static void sort_access_points(Context* ctx) { + for(int i = 0; i < ctx->access_points_count; i++) { + for(int j = i + 1; j < ctx->access_points_count; j++) { + if(strcmp(ctx->access_points[i].ssid, ctx->access_points[j].ssid) > 0) { + AccessPoint temp = ctx->access_points[i]; + ctx->access_points[i] = ctx->access_points[j]; + ctx->access_points[j] = temp; + } + } + } +} + +// set the index from the active access point +static void set_index_from_access_points(Context* ctx) { + for(int i = 0; i < ctx->access_points_count; i++) { + if(ctx->access_points[i].bssid == ctx->active_access_point.bssid) { + ctx->access_points_index = i; + break; + } + } +} + +static void removeSpaces(char* str) { + // Remove spaces from the beginning of the string + int i = 0; + while(isspace((unsigned char)str[i])) { + i++; + } + + // Move the remaining characters to the beginning of the string + int j = 0; + while(str[i] != '\0') { + str[j++] = str[i++]; + } + str[j] = '\0'; + + // Remove spaces from the end of the string + int len = strlen(str); + while(len > 0 && isspace((unsigned char)str[len - 1])) { + str[--len] = '\0'; + } +} + +static void parseLine(void* context, char* line) { + Context* ctx = context; + + AccessPoint ap = {.ssid = malloc(MAX_SSID_LENGTH + 1), .bssid = malloc(MAX_BSSID_LENGTH + 1)}; + + Packet pkt = {.recievedMac = malloc(18 + 1), .sentMac = malloc(18 + 1)}; + + char* token = strtok(line, ","); + int i = 0; + bool isAp = false; + bool isValid = true; + UNUSED(isValid); + while(token != NULL) { + switch(i) { + case 0: + // strcpy(ap.ssid, token); + if(strcmp(token, "AR") == 0) { + isAp = true; + isValid = true; + } else if(strcmp(token, "PK") == 0) { + isAp = false; + isValid = true; + } + break; + case 1: + if(isAp && isValid) { + removeSpaces(token); + strcpy(ap.ssid, token); + } else if(!isAp && isValid) { + strncpy(pkt.recievedMac, token, 18); + pkt.recievedMac[18] = '\0'; + } + break; + case 2: + if(isAp && isValid) { + strcpy(ap.bssid, token); + } else if(!isAp && isValid) { + strncpy(pkt.sentMac, token, 18); + pkt.sentMac[18] = '\0'; + } + break; + case 3: + if(isAp && isValid) { + ap.rssi = atoi(token); + } + break; + case 4: + if(isAp && isValid) { + ap.channel = atoi(token); + } + break; + } + + token = strtok(NULL, ","); + i++; + } + + if(isAp && isValid) { + // free the packet + free(pkt.recievedMac); + free(pkt.sentMac); + + // check if values are valid + // bssid needs an ":" + // rssi needs to be negative + // channel needs to be between 1 and 14 + // ssid needs to be at least 1 character long + if(ap.bssid[2] != ':' || ap.bssid[5] != ':' || ap.bssid[8] != ':' || ap.bssid[11] != ':' || + ap.bssid[14] != ':' || ap.rssi > 0 || ap.channel < 1 || ap.channel > 14 || + strlen(ap.ssid) < 1) { + free(ap.ssid); + free(ap.bssid); + return; + } + + furi_hal_light_set(LightBlue, 0); + furi_hal_light_set(LightGreen, 255); + + furi_hal_rtc_get_datetime(&ap.datetime); + + if(isnan(ctx->last_latitude) || isnan(ctx->last_longitude)) { + ctx->last_latitude = 0; + ctx->last_longitude = 0; + } else { + ap.latitude = ctx->last_latitude; + ap.longitude = ctx->last_longitude; + } + + // check if ap is already in the list otherwise add it but update the rssi + bool found = false; + for(size_t i = 0; i < ctx->access_points_count; i++) { + if(strcmp(ctx->access_points[i].bssid, ap.bssid) == 0) { + found = true; + //update rssi channel datetime + ctx->access_points[i].rssi = ap.rssi; + ctx->access_points[i].channel = ap.channel; + ctx->access_points[i].datetime = ap.datetime; + ctx->access_points[i].latitude = ap.latitude; + ctx->access_points[i].longitude = ap.longitude; + + if(strcmp(ctx->active_access_point.bssid, ap.bssid) == 0) { + ctx->active_access_point.rssi = ap.rssi; + ctx->active_access_point.channel = ap.channel; + ctx->active_access_point.datetime = ap.datetime; + ctx->active_access_point.latitude = ap.latitude; + ctx->active_access_point.longitude = ap.longitude; + } + + free(ap.ssid); + free(ap.bssid); + + break; + } + } + + if(!found) { + memcpy(&ctx->access_points[ctx->access_points_count], &ap, sizeof(AccessPoint)); + ctx->access_points_count++; + } + + sort_access_points(ctx); + set_index_from_access_points(ctx); + } else { + // it is a packet so screw the ap + free(ap.ssid); + free(ap.bssid); + + // check if values are valid + // mac needs to be 6 characters long + if(strlen(pkt.recievedMac) != 17 || strlen(pkt.sentMac) != 17 || + ctx->access_points_count == 0) { + free(pkt.recievedMac); + free(pkt.sentMac); + return; + } + + furi_hal_light_set(LightGreen, 0); + furi_hal_light_set(LightBlue, 255); + + for(size_t i = 0; i < ctx->access_points_count; i++) { + if(strcmp(ctx->access_points[i].bssid, pkt.recievedMac) == 0) { + ctx->access_points[i].packetRxCount++; + break; + } + } + + for(size_t i = 0; i < ctx->access_points_count; i++) { + if(strcmp(ctx->access_points[i].bssid, pkt.sentMac) == 0) { + ctx->access_points[i].packetTxCount++; + break; + } + } + + free(pkt.recievedMac); + free(pkt.sentMac); + } +} + +static void uart_cb_esp(UartIrqEvent ev, uint8_t data, void* context) { + Context* ctx = (Context*)context; + + if(ev == UartIrqEventRXNE) { + furi_stream_buffer_send(ctx->rx_stream_esp, &data, 1, 0); + furi_thread_flags_set(furi_thread_get_id(ctx->thread_esp), WorkerEvtRxDone); + } +} + +static int32_t uart_worker_esp(void* context) { + Context* ctx = (Context*)context; + + size_t rx_offset = 0; + + while(1) { + uint32_t events = + furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever); + furi_check((events & FuriFlagError) == 0); + + if(events & WorkerEvtStop) { + break; + } + + if(events & WorkerEvtRxDone) { + size_t len = 0; + do { + // receive serial bytes into rx_buf, starting at rx_offset from the start of the buffer + // the maximum we can receive is RX_BUF_SIZE - 1 - rx_offset + len = furi_stream_buffer_receive( + ctx->rx_stream_esp, + ctx->rx_buf_esp + rx_offset, + RX_BUF_SIZE - 1 - rx_offset, + 0); + + if(len > 0) { + // increase rx_offset by the number of bytes received, and null-terminate rx_buf + rx_offset += len; + ctx->rx_buf_esp[rx_offset] = '\0'; + + // look for strings ending in newlines, starting at the start of rx_buf + char* line_current = (char*)ctx->rx_buf_esp; + while(1) { + // skip null characters + while(*line_current == '\0' && + line_current < (char*)ctx->rx_buf_esp + rx_offset - 1) { + line_current++; + } + + // find the next newline + char* newline = strchr(line_current, '\n'); + if(newline) // newline found + { + // put a null terminator in place of the newline, to delimit the line string + *newline = '\0'; + + // FURI_LOG_I(appname, "Received line: %s", line_current); + + parseLine(ctx, line_current); + + // move the cursor to the character after the newline + line_current = newline + 1; + } else // no more newlines found + { + if(line_current > + (char*)ctx->rx_buf_esp) // at least one line was found + { + // clear parsed lines, and move any leftover bytes to the start of rx_buf + rx_offset = 0; + while( + *line_current) // stop when the original rx_offset terminator is reached + { + ctx->rx_buf_esp[rx_offset++] = *(line_current++); + } + } + break; // go back to receiving bytes from the serial stream + } + } + } + } while(len > 0); + } + } + + furi_hal_uart_set_irq_cb(UART_CH_ESP, NULL, NULL); + + furi_stream_buffer_free(ctx->rx_stream_esp); + + return 0; +} + +static void gps_uart_parse_nmea(Context* ctx, char* line) { + switch(minmea_sentence_id(line, false)) { + case MINMEA_SENTENCE_RMC: { + struct minmea_sentence_rmc frame; + if(minmea_parse_rmc(&frame, line)) { + ctx->last_latitude = minmea_tocoord(&frame.latitude); + ctx->last_longitude = minmea_tocoord(&frame.longitude); + } + } break; + + case MINMEA_SENTENCE_GGA: { + struct minmea_sentence_gga frame; + if(minmea_parse_gga(&frame, line)) { + ctx->last_latitude = minmea_tocoord(&frame.latitude); + ctx->last_longitude = minmea_tocoord(&frame.longitude); + } + } break; + + case MINMEA_SENTENCE_GLL: { + struct minmea_sentence_gll frame; + if(minmea_parse_gll(&frame, line)) { + ctx->last_latitude = minmea_tocoord(&frame.latitude); + ctx->last_longitude = minmea_tocoord(&frame.longitude); + } + } break; + + default: + break; + } +} + +static void uart_cb_gps(UartIrqEvent ev, uint8_t data, void* context) { + Context* ctx = (Context*)context; + + if(ev == UartIrqEventRXNE) { + furi_stream_buffer_send(ctx->rx_stream_gps, &data, 1, 0); + furi_thread_flags_set(furi_thread_get_id(ctx->thread_gps), WorkerEvtRxDone); + } +} + +static int32_t uart_worker_gps(void* context) { + Context* ctx = (Context*)context; + + size_t rx_offset = 0; + + while(1) { + uint32_t events = + furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever); + furi_check((events & FuriFlagError) == 0); + + if(events & WorkerEvtStop) { + break; + } + + if(events & WorkerEvtRxDone) { + size_t len = 0; + do { + // receive serial bytes into rx_buf, starting at rx_offset from the start of the buffer + // the maximum we can receive is RX_BUF_SIZE - 1 - rx_offset + len = furi_stream_buffer_receive( + ctx->rx_stream_gps, + ctx->rx_buf_gps + rx_offset, + RX_BUF_SIZE - 1 - rx_offset, + 0); + + if(len > 0) { + // increase rx_offset by the number of bytes received, and null-terminate rx_buf + rx_offset += len; + ctx->rx_buf_gps[rx_offset] = '\0'; + + // look for strings ending in newlines, starting at the start of rx_buf + char* line_current = (char*)ctx->rx_buf_gps; + while(1) { + // skip null characters + while(*line_current == '\0' && + line_current < (char*)ctx->rx_buf_gps + rx_offset - 1) { + line_current++; + } + + // find the next newline + char* newline = strchr(line_current, '\n'); + if(newline) // newline found + { + // put a null terminator in place of the newline, to delimit the line string + *newline = '\0'; + + // FURI_LOG_I(appname, "Received line: %s", line_current); + + gps_uart_parse_nmea(ctx, line_current); + + // move the cursor to the character after the newline + line_current = newline + 1; + } else // no more newlines found + { + if(line_current > + (char*)ctx->rx_buf_gps) // at least one line was found + { + // clear parsed lines, and move any leftover bytes to the start of rx_buf + rx_offset = 0; + while( + *line_current) // stop when the original rx_offset terminator is reached + { + ctx->rx_buf_gps[rx_offset++] = *(line_current++); + } + } + break; // go back to receiving bytes from the serial stream + } + } + } + } while(len > 0); + } + } + + furi_hal_uart_set_irq_cb(UART_CH_GPS, NULL, NULL); + + furi_stream_buffer_free(ctx->rx_stream_gps); + + return 0; +} + +int32_t wifisniffer_app(void* p) { + UNUSED(p); + + // alloc everything + + Context* ctx = malloc(sizeof(Context)); + ctx->queue = furi_message_queue_alloc(8, sizeof(Event)); + ctx->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + ctx->buffer = furi_string_alloc(); + ctx->buffer2 = furi_string_alloc(); + ctx->notifications = furi_record_open(RECORD_NOTIFICATION); + + ctx->access_points_count = 0; + ctx->access_points_index = 0; + + ctx->pressedButton = false; + + //esp uart + + ctx->rx_stream_esp = furi_stream_buffer_alloc(RX_BUF_SIZE * 5, 1); + + ctx->thread_esp = furi_thread_alloc(); + furi_thread_set_name(ctx->thread_esp, "LLwifiSnifferUartWorkerESP"); + furi_thread_set_stack_size(ctx->thread_esp, 2048); + furi_thread_set_context(ctx->thread_esp, ctx); + furi_thread_set_callback(ctx->thread_esp, uart_worker_esp); + + furi_thread_start(ctx->thread_esp); + + if(UART_CH_ESP == FuriHalUartIdUSART1) { + furi_hal_console_disable(); + } else if(UART_CH_ESP == FuriHalUartIdLPUART1) { + furi_hal_uart_init(UART_CH_ESP, 9600); + } + furi_hal_uart_set_br(UART_CH_ESP, 9600); + furi_hal_uart_set_irq_cb(UART_CH_ESP, uart_cb_esp, ctx); + //end esp uart + + //gps uart + + ctx->rx_stream_gps = furi_stream_buffer_alloc(RX_BUF_SIZE * 5, 1); + + ctx->thread_gps = furi_thread_alloc(); + furi_thread_set_name(ctx->thread_gps, "LLwifiSnifferUartWorkerGPS"); + furi_thread_set_stack_size(ctx->thread_gps, 2048); + furi_thread_set_context(ctx->thread_gps, ctx); + furi_thread_set_callback(ctx->thread_gps, uart_worker_gps); + + furi_thread_start(ctx->thread_gps); + + if(UART_CH_GPS == FuriHalUartIdUSART1) { + furi_hal_console_disable(); + } else if(UART_CH_GPS == FuriHalUartIdLPUART1) { + furi_hal_uart_init(UART_CH_GPS, 9600); + } + furi_hal_uart_set_br(UART_CH_GPS, 9600); + furi_hal_uart_set_irq_cb(UART_CH_GPS, uart_cb_gps, ctx); + //end gps uart + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, ctx); + view_port_input_callback_set(view_port, input_callback, ctx->queue); + + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + FuriTimer* timer = furi_timer_alloc(tick_callback, FuriTimerTypePeriodic, ctx->queue); + furi_timer_start(timer, 100); + + // application loop + Event event; + bool processing = true; + do { + if(furi_message_queue_get(ctx->queue, &event, FuriWaitForever) == FuriStatusOk) { + furi_mutex_acquire(ctx->mutex, FuriWaitForever); + switch(event.type) { + case EventTypeKey: + // applicatie verlaten + if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) { + processing = false; + } else if(event.input.type == InputTypeLong && event.input.key == InputKeyBack) { + processing = false; + } else if(event.input.type == InputTypeLong && event.input.key == InputKeyOk) { + // remove accespoint + if(ctx->access_points_count > 0) { + for(int i = ctx->access_points_index; i < ctx->access_points_count - 1; + i++) { + ctx->access_points[i] = ctx->access_points[i + 1]; + } + ctx->access_points_count--; + if(ctx->access_points_index >= ctx->access_points_count) { + ctx->access_points_index = ctx->access_points_count - 1; + } + } + } else if(event.input.type == InputTypePress && event.input.key == InputKeyDown) { + ctx->access_points_index--; + if(ctx->access_points_index < 0) { + ctx->access_points_index = ctx->access_points_count - 1; + } + ctx->active_access_point = ctx->access_points[ctx->access_points_index]; + } else if(event.input.type == InputTypePress && event.input.key == InputKeyUp) { + ctx->access_points_index++; + if(ctx->access_points_index >= ctx->access_points_count) { + ctx->access_points_index = 0; + } + ctx->active_access_point = ctx->access_points[ctx->access_points_index]; + } else if(event.input.type == InputTypePress && event.input.key == InputKeyLeft) { + } else if(event.input.type == InputTypePress && event.input.key == InputKeyRight) { + } + ctx->pressedButton = true; + break; + case EventTypeTick: + + // fix for the empty active access point when there was no interaction + if(!ctx->pressedButton) { + ctx->access_points_index = 0; + ctx->active_access_point = ctx->access_points[ctx->access_points_index]; + } + + break; + + default: + break; + } + + view_port_update(view_port); + } else { + processing = false; + } + } while(processing); + + // save the data to the file + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriHalRtcDateTime datetime; + furi_hal_rtc_get_datetime(&datetime); + + FuriString* filename = furi_string_alloc(); + furi_string_printf( + filename, + "%d_%d_%d_%d_%d_%d.txt", + datetime.year, + datetime.month, + datetime.day, + datetime.hour, + datetime.minute, + datetime.second); + + FuriString* path = furi_string_alloc(); + furi_string_printf(path, "/ext/apps_data/llsniffer/%s", furi_string_get_cstr(filename)); + + // open file + ctx->file = storage_file_alloc(storage); + + if(!storage_common_exists(storage, EXT_PATH("apps_data/llsniffer"))) { + storage_common_mkdir(storage, EXT_PATH("apps_data/llsniffer")); + } + + if(!storage_file_open(ctx->file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_ALWAYS)) { + FURI_LOG_E(appname, "Failed to open file"); + } + + for(int i = 0; i < ctx->access_points_count; i++) { + AccessPoint ap = ctx->access_points[i]; + furi_string_printf( + ctx->buffer2, + "%s,%s,%s,%d,%d,%d,%d,%d,%d,%d,%d,%f,%f\r\n", + "Accesspoint", + ap.ssid, + ap.bssid, + ap.rssi, + ap.channel, + ap.datetime.year, + ap.datetime.month, + ap.datetime.day, + ap.datetime.hour, + ap.datetime.minute, + ap.datetime.second, + (double)ap.latitude, + (double)ap.longitude); + + if(!storage_file_write( + ctx->file, + furi_string_get_cstr(ctx->buffer2), + strlen(furi_string_get_cstr(ctx->buffer2)))) { + FURI_LOG_E(appname, "Failed to write AP to file"); + } + } + + // free everything + furi_record_close(RECORD_NOTIFICATION); + furi_timer_free(timer); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_record_close(RECORD_GUI); + furi_message_queue_free(ctx->queue); + furi_mutex_free(ctx->mutex); + + furi_thread_flags_set(furi_thread_get_id(ctx->thread_esp), WorkerEvtStop); + furi_thread_join(ctx->thread_esp); + furi_thread_free(ctx->thread_esp); + + furi_thread_flags_set(furi_thread_get_id(ctx->thread_gps), WorkerEvtStop); + furi_thread_join(ctx->thread_gps); + furi_thread_free(ctx->thread_gps); + + storage_file_close(ctx->file); + storage_file_free(ctx->file); + furi_record_close(RECORD_STORAGE); + free(ctx); + + furi_hal_light_set(LightBlue, 0); + furi_hal_light_set(LightGreen, 0); + + if(UART_CH_ESP == FuriHalUartIdLPUART1) { + furi_hal_uart_deinit(UART_CH_ESP); + } else if(UART_CH_ESP == FuriHalUartIdUSART1) { + furi_hal_console_enable(); + } + + if(UART_CH_GPS == FuriHalUartIdLPUART1) { + furi_hal_uart_deinit(UART_CH_GPS); + } else if(UART_CH_GPS == FuriHalUartIdUSART1) { + furi_hal_console_enable(); + } + + return 0; +}