[
  {
    "path": ".gitignore",
    "content": "# Compiled source #\n###################\n*.com\n*.class\n*.dll\n*.exe\n*.o\n*.so\n\n# Packages #\n############\n# it's better to unpack these files and commit the raw source\n# git has its own built in compression methods\n*.7z\n*.dmg\n*.gz\n*.iso\n*.jar\n*.rar\n*.tar\n*.zip\n*.img\n\n# Logs and databases #\n######################\n*.log\n*.sql\n*.sqlite\n*.pcap\n*.csv\n\n# OS generated files #\n######################\n.DS_Store\n.DS_Store?\n._*\n.Spotlight-V100\n.Trashes\nehthumbs.db\nThumbs.db\n"
  },
  {
    "path": "LICENSE",
    "content": "This file is part of Pi Sniffer.\n\nPi Sniffer is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nPi Sniffer is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with Foobar.  If not, see <https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "README.md",
    "content": "# Pi Sniffer\n\nPi Sniffer is a Wi-Fi sniffer built on the Raspberry Pi Zero W. While there are many excellent sniffing platforms out there, Pi Sniffer is unique for it's small size, real time display of captured data, and handling of user input.\n\n<image src=\"https://user-images.githubusercontent.com/787916/75169212-291e0c00-56f6-11ea-8ae9-13e4a2762276.jpg\" height=\"66%\" width=\"66%\">\n\n## Current Release Image\nYou can download an an RPI image of this project from the \"Releases\" page. If you don't trust that, you can generate your own release by using the image_gen/create_image.sh script.\n\n## Project Goals\nThe goal of this project was to create a Wi-Fi sniffer that I could carry around in my pocket, easily view real time status, decrypt packets on the fly, and change antenna channels as needed. Also, I wanted this project to be cheap (less than $100) and require no soldering.\n  \n## Hardware\n\nThe project was conceived with the goal to avoid any type of soldering. While Pi Sniffer does require the GPIO header on the Raspberry Pi Zero W, you can buy that pre-soldered. So I'm gonna claim no soldering required.\n\nThe base install requires:\n\n* [Raspberry Pi Zero WH](https://www.adafruit.com/product/3708)\n* [Adafruit 128x64 OLED Bonnet](https://www.adafruit.com/product/3531)\n* A power source. I suggest one of the following:\n    * [Anker PowerCore 5000](https://www.amazon.com/dp/B01CU1EC6Y)\n    * [Anker E1 Astro](https://www.amazon.com/Anker-bar-Sized-Portable-High-Speed-Technology/dp/B00P7N0320)\n    * [UPS-Lite](https://www.tindie.com/products/rachel/ups-lite-for-raspberry-pi-zero/)\n* Any SD card 8GB or larger\n\nAdditionally, you can configure the device with any of the following add-ons (and still reasonably be called pocket sized):\n* [Secondary antenna by CanaKit](https://www.amazon.com/CanaKit-Raspberry-Wireless-Adapter-Dongle/dp/B00GFAN498)\n* [Ublox-7 GPS](https://www.amazon.com/WINGONEER%C2%AE%C2%AE-Antenna-VK-172-Receiver-Windows/dp/B07F6TJG9L)\n* [MicroUSB to USB adapters](https://www.amazon.com/Ksmile%C2%AE-Female-Adapter-SamSung-tablets/dp/B01C6032G0)\n* [USB MiniHub](https://www.adafruit.com/product/2991)\n\n\n## Software\nDownload the release image and flash it to an SD card. Stick the SD card into your RPI Zero WH and you should be good to go! By default, SSH should be enabled. Use the default pi:raspberry credentials. The device's hostname is pisniffer so something along the following lines should get you in:\n\n```sh\nssh pi@pisniffer.local\n```\n\n## Controls\nPi Sniffer isn't unique just due to it's size but it also offers controls. The user can start and stop sniffing. Change channels. Deauth clients. And more. Here are some images showing how to use the controls.\n\n### Start, Stop, and Shutdown\nTo start sniffing hit the #6 button. To stop sniffing hit the #5 button. To shutdown the device hold #5 and #6.\n\n![start_stop](https://user-images.githubusercontent.com/787916/79753616-3580a880-82e4-11ea-934b-fc65fc3d9e78.png)\n\n### Channel Hoppping\nTo change to a specific channel, rotate to the antenna screen and hit #6. This will cycle you through the available channels plus hopping.\n\n![channel_change](https://user-images.githubusercontent.com/787916/79753590-30235e00-82e4-11ea-98a2-65b8e8a419f8.png)\n\n### Deauth Attack\nTo deauth a client, find them in the client view and hit #6.\n\n![deauth](https://user-images.githubusercontent.com/787916/79753602-3285b800-82e4-11ea-85b2-065e194b8387.png)\n\n### Lock display\nSometimes it's beneficial to lock the screen and controls. To do so, rotate to the lock screen and hit #6. To unlock you need to hit #5 and push up on the joystick at the same time.\n\n![lock](https://user-images.githubusercontent.com/787916/79753610-33b6e500-82e4-11ea-846d-62177edc5385.png)\n\n## Issues and Pull Requests\nIssues and pull requests are welcome. I only ask that you provide enough information to recreate the issue or information about why the pull request should be accepted.\n"
  },
  {
    "path": "configs/kismet.conf",
    "content": "# Version of Kismet config\nversion=2009-newcore\n\n# Do we process the contents of data frames?  If this is enabled, data\n# frames will be truncated to the headers only immediately after frame type\n# detection.  This will disable IP detection, etc, however it is likely\n# safer (and definitely more polite) if monitoring networks you do not own.\nhidedata=true\n\n# Do we allow plugins to be used?  This will load plugins from the system\n# and user plugin directiories when set to true (See the README for the default\n# plugin locations).\nallowplugins=false\n\n# See the README for full information on the new source format\n# ncsource=interface:options\n# for example:\nncsource=wlan0:uuid=00000000-0000-0000-0000-000000000001\nncsource=wlan1:uuid=00000000-0000-0000-0000-000000000002\n# ncsource=wifi0:type=madwifi\n# ncsource=wlan0:name=intel,hop=false,channel=11\n\n# Control which channels we like to spend more time on.  By default, the list\n# of channels is pulled from the driver automatically.  By setting preferred channels,\n# if they are present in the channel list, they'll be set with a timing delay so that\n# more time is spent on them.  Since 1, 6, 11 are the common default channels, it makes\n# sense to spend more time monitoring them.\n# For finer control, see further down in the config for the channellist= directives.\npreferredchannels=1,6,11\n\n# How many channels per second do we hop?  (1-10)\nchannelvelocity=3\n\n# Default channel lists\n# These channel lists MUST BE PRESENT for Kismet to work properly.  While it is\n# possible to change these, it is not recommended.  These are used when the supported\n# channel list can not be found for the source; to force using these instead of\n# the detected supported channels, override with channellist= in the source defintion\n#\n# IN GENERAL, if you think you want to modify these, what you REALLY want to do is\n# copy them and use channellist= in the packet source.\nchannellist=IEEE80211b:1:3,6:3,11:3,2,7,3,8,4,9,5,10\nchannellist=IEEE80211a:36,40,44,48,52,56,60,64,149,153,157,161,165\nchannellist=IEEE80211ab:1:3,6:3,11:3,2,7,3,8,4,9,5,10,36,40,44,48,52,56,60,64,149,153,157,161,165\n\n# Client/server listen config\nlisten=tcp://127.0.0.1:2501\n# People allowed to connect, comma seperated IP addresses or network/mask\n# blocks.  Netmasks can be expressed as dotted quad (/255.255.255.0) or as\n# numbers (/24)\nallowedhosts=127.0.0.1\n# Maximum number of concurrent GUI's\nmaxclients=1\n# Maximum backlog before we start throwing out or killing clients.  The\n# bigger this number, the more memory and the more power it will use.\nmaxbacklog=50\n\n# Server + Drone config options.  To have a Kismet server export live packets\n# as if it were a drone, uncomment these.\ndronelisten=tcp://127.0.0.1:3501\ndroneallowedhosts=127.0.0.1\ndronemaxclients=1\ndroneringlen=65535\n\n# Do we have a GPS?\ngps=true\n# Do we use a locally serial attached GPS, or use a gpsd server, or\n# use a fixed virtual gps?\n# (Pick only one)\ngpstype=gpsd\n# Host:port that GPSD is running on.  This can be localhost OR remote!\ngpshost=localhost:2947\n\n\n# gpstype=serial\n# What serial device do we look for the GPS on?\n# gpsdevice=/dev/rfcomm0\n\n# gpstype=virtual\n# gpsposition=100,-50\n# gpsaltitude=1234\n\n# Do we lock the mode?  This overrides coordinates of lock \"0\", which will\n# generate some bad information until you get a GPS lock, but it will \n# fix problems with GPS units with broken NMEA that report lock 0\ngpsmodelock=false\n# Do we try to reconnect if we lose our link to the GPS, or do we just\n# let it die and be disabled?\ngpsreconnect=true\n\n# Do we export packets over tun/tap virtual interfaces?\ntuntap_export=false\ntuntap_device=kistap0\n\n# Is transmission of the keys to the client allowed?  This may be a security\n# risk for some.  If you disable this, you will not be able to query keys from\n# a client.\nallowkeytransmit=true\n\n# How often (in seconds) do we write all our data files (0 to disable)\nwriteinterval=0\n\n# Do we use sound?\n# Not to be confused with GUI sound parameter, this controls wether or not the\n# server itself will play sound.  Primarily for headless or automated systems.\nenablesound=false\n\n# Format of the pcap dump (PPI or 80211)\npcapdumpformat=ppi\n\n# Default log title\nlogdefault=Kismet\n\n# logtemplate - Filename logging template.\n# This is, at first glance, really nasty and ugly, but you'll hardly ever\n# have to touch it so don't complain too much.\n#\n# %p is replaced by the logging prefix + '/'\n# %n is replaced by the logging instance name\n# %d is replaced by the starting date as Mon-DD-YYYY\n# %D is replaced by the current date as YYYYMMDD\n# %t is replaced by the starting time as HH-MM-SS\n# %i is replaced by the increment log in the case of multiple logs\n# %l is replaced by the log type (pcapdump, strings, etc)\n# %h is replaced by the home directory\n\nlogtemplate=%p%n-%D-%t-%i.%l\n\n# Where state info, etc, is stored.  You shouldnt ever need to change this.\n# This is a directory.\nconfigdir=/tmp/.kismet/\n\n"
  },
  {
    "path": "configs/ntp.conf",
    "content": "# /etc/ntp.conf, configuration for ntpd; see ntp.conf(5) for help\n\ndriftfile /var/lib/ntp/ntp.drift\n\n# Leap seconds definition provided by tzdata\nleapfile /usr/share/zoneinfo/leap-seconds.list\n\n# Enable this if you want statistics to be logged.\n#statsdir /var/log/ntpstats/\n\nstatistics loopstats peerstats clockstats\nfilegen loopstats file loopstats type day enable\nfilegen peerstats file peerstats type day enable\nfilegen clockstats file clockstats type day enable\n\n\n# You do need to talk to an NTP server or two (or three).\n#server ntp.your-provider.example\n\n# pool.ntp.org maps to about 1000 low-stratum NTP servers.  Your server will\n# pick a different set every time it starts up.  Please consider joining the\n# pool: <http://www.pool.ntp.org/join.html>\npool 0.debian.pool.ntp.org iburst\npool 1.debian.pool.ntp.org iburst\npool 2.debian.pool.ntp.org iburst\npool 3.debian.pool.ntp.org iburst\n\n\n# Access control configuration; see /usr/share/doc/ntp-doc/html/accopt.html for\n# details.  The web page <http://support.ntp.org/bin/view/Support/AccessRestrictions>\n# might also be helpful.\n#\n# Note that \"restrict\" applies to both servers and clients, so a configuration\n# that might be intended to block requests from certain clients could also end\n# up blocking replies from your own upstream servers.\n\n# By default, exchange time with everybody, but don't allow configuration.\nrestrict -4 default kod notrap nomodify nopeer noquery limited\nrestrict -6 default kod notrap nomodify nopeer noquery limited\n\n# Local users may interrogate the ntp server more closely.\nrestrict 127.0.0.1\nrestrict ::1\n\n# Needed for adding pool entries\nrestrict source notrap nomodify noquery\n\n# Clients from this (example!) subnet have unlimited access, but only if\n# cryptographically authenticated.\n#restrict 192.168.123.0 mask 255.255.255.0 notrust\n\n\n# If you want to provide time to your local subnet, change the next line.\n# (Again, the address is an example only.)\n#broadcast 192.168.123.255\n\n# If you want to listen to time broadcasts on your local subnet, de-comment the\n# next lines.  Please do this only if you trust everybody on the network!\n#disable auth\n#broadcastclient\n\n# GPS Serial data reference\nserver 127.127.28.0 minpoll 4 maxpoll 4\nfudge 127.127.28.0 time1 0.0 refid GPS\n\n# GPS PPS reference\nserver 127.127.28.1 minpoll 4 maxpoll 4 prefer\nfudge 127.127.28.1 refid PPS\n\n"
  },
  {
    "path": "configs/rc.local",
    "content": "#!/bin/sh -e\n#\n# rc.local\n#\n# This script is executed at the end of each multiuser runlevel.\n# Make sure that the script will \"exit 0\" on success or any other\n# value on error.\n#\n# In order to enable or disable this script just change the execution\n# bits.\n#\n# By default this script does nothing.\n\n# Print the IP address\n_IP=$(hostname -I) || true\nif [ \"$_IP\" ]; then\n  printf \"My IP address is %s\\n\" \"$_IP\"\nfi\n\n(python3 /home/pi/display_handler.py > /dev/null)&\n\nexit 0\n"
  },
  {
    "path": "image_gen/create_image.sh",
    "content": "#!/usr/bin/env bash\n# based on: https://wiki.debian.org/RaspberryPi/qemu-user-static\n# and https://z4ziggy.wordpress.com/2015/05/04/from-bochs-to-chroot/\n#\n# and pwnagotchi's old create_sibling script:\n# https://raw.githubusercontent.com/evilsocket/pwnagotchi/55bac8a8b913c158132953d8186d278621df032a/scripts/create_sibling.sh\n\nset -eu\n\nif [[ \"$EUID\" -ne 0 ]]; then\n   echo \"Run this script as root!\"\n   exit 1\nfi\n\nREQUIREMENTS=( wget gunzip git dd e2fsck resize2fs parted losetup qemu-system-x86_64 )\nDEBREQUIREMENTS=( wget gzip git parted qemu-system-x86 qemu-user-static )\nREPO_DIR=\"$(dirname \"$(dirname \"$(realpath \"$0\")\")\")\"\nTMP_DIR=\"${REPO_DIR}/tmp\"\nMNT_DIR=\"${TMP_DIR}/mnt\"\nTHIS_DIR=$(pwd)\n\nHOST_NAME=\"pisniffer\"\nOUTPUT_NAME=\"pi_sniffer.img\"\nIMAGE_SIZE=\"7\"\n\nfunction check_dependencies() {\n  if [ -f /etc/debian_version ];\n  then\n    echo \"[+] Checking Debian dependencies\"\n\n    for REQ in \"${DEBREQUIREMENTS[@]}\"; do\n      if ! dpkg -s \"$REQ\" >/dev/null 2>&1; then\n        echo \"Dependency check failed for ${REQ}; use 'apt install ${REQ}' to install\"\n        exit 1\n      fi\n    done\n  fi\n\n  echo \"[+] Checking dependencies\"\n  for REQ in \"${REQUIREMENTS[@]}\"; do\n    if ! type \"$REQ\" >/dev/null 2>&1; then\n      echo \"Dependency check failed for ${REQ}\"\n      exit 1\n    fi\n  done\n\n  if ! test -e /usr/bin/qemu-arm-static; then\n    echo \"[-] You need the package \\\"qemu-user-static\\\" for this to work.\"\n    exit 1\n  fi\n\n  if ! systemctl is-active systemd-binfmt.service >/dev/null 2>&1; then\n     mkdir -p \"/lib/binfmt.d\"\n     echo ':qemu-arm:M::\\x7fELF\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x28\\x00:\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xfe\\xff\\xff\\xff:/usr/bin/qemu-arm-static:F' > /lib/binfmt.d/qemu-arm-static.conf\n     systemctl restart systemd-binfmt.service\n  fi\n}\n\nfunction get_raspbian() {\n  URL=\"https://downloads.raspberrypi.org/raspbian_lite_latest\"\n  echo \"[+] Downloading raspbian lite latest to raspbian.zip\"\n  mkdir -p \"${TMP_DIR}\"\n  wget --show-progress -qcO \"${TMP_DIR}/raspbian.zip\" \"$URL\"\n  echo \"[+] Unpacking raspbian.zip to raspbian.img\"\n  gunzip -c \"${TMP_DIR}/raspbian.zip\" > \"${TMP_DIR}/raspbian.img\"\n}\n\nfunction setup_raspbian(){\n  # Note that we 'extend' the raspbian.img\n  echo \"[+] Resizing full image to ${IMAGE_SIZE}G\"\n\n  # Full disk-space using image (appends to raspbian image)\n  dd if=/dev/zero bs=1G count=\"${IMAGE_SIZE}\" >> \"${TMP_DIR}/raspbian.img\"\n  truncate --size=\"${IMAGE_SIZE}\"G \"${TMP_DIR}/raspbian.img\"\n\n  echo \"[+] Setup loop device\"\n  mkdir -p \"${MNT_DIR}\"\n  LOOP_PATH=\"$(losetup --find --partscan --show \"${TMP_DIR}/raspbian.img\")\"\n  PART2_START=\"$(parted -s \"$LOOP_PATH\" -- print | awk '$1==2{ print $2 }')\"\n  parted -s \"$LOOP_PATH\" rm 2\n  parted -s \"$LOOP_PATH\" mkpart primary \"$PART2_START\" 100%\n  echo \"[+] Check FS\"\n  e2fsck -y -f \"${LOOP_PATH}p2\"\n  echo \"[+] Resize FS\"\n  resize2fs \"${LOOP_PATH}p2\"\n  echo \"[+] Device is ${LOOP_PATH}\"\n  echo \"[+] Unmount if already mounted with other img\"\n  mountpoint -q \"${MNT_DIR}\" && umount -R \"${MNT_DIR}\"\n  echo \"[+] Mount /\"\n  mount -o rw \"${LOOP_PATH}p2\" \"${MNT_DIR}\"\n  echo \"[+] Mount /boot\"\n  mount -o rw \"${LOOP_PATH}p1\" \"${MNT_DIR}/boot\"\n  mount --bind /dev \"${MNT_DIR}/dev/\"\n  mount --bind /sys \"${MNT_DIR}/sys/\"\n  mount --bind /proc \"${MNT_DIR}/proc/\"\n  mount --bind /dev/pts \"${MNT_DIR}/dev/pts\"\n  cp /usr/bin/qemu-arm-static \"${MNT_DIR}/usr/bin\"\n  cp /etc/resolv.conf \"${MNT_DIR}/etc/resolv.conf\"\n}\n\nfunction provision_raspbian() {\n\n  # copy in pi sniffer\n  cd ${MNT_DIR}/home/pi/\n  cp -r ${REPO_DIR}/pi_sniffer .\n  cp ${REPO_DIR}/ui/* .\n  cp ${REPO_DIR}/configs/rc.local ../../etc/\n  chmod +x /etc/rc.local\n  cp ${REPO_DIR}/configs/kismet.conf .\n  cp ${REPO_DIR}/configs/ntp.conf ../../etc/\n\n  cd ${MNT_DIR}\n  sed -i'' 's/^\\([^#]\\)/#\\1/g' etc/ld.so.preload # add comments\n  echo \"[+] Run chroot commands\"\n  LANG=C LC_ALL=C LC_CTYPE=C chroot . bin/bash -x <<EOF\n  set -eu\n  export PATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"\n\n  uname -a\n\n  apt -y update\n  apt -y upgrade\n  apt -y install git build-essential python3-pip tcpdump\n  apt -y install libpcap-dev cmake gpsd gpsd-clients\n  apt -y install aircrack-ng kismet libtins-dev libpugixml-dev\n  apt -y install libssl-dev python3-pil python-smbus i2c-tools python-gi \n  apt -y install libboost-all-dev \n  apt -y install ntp\n\n  # setup dphys-swapfile\n  echo \"CONF_SWAPSIZE=1024\" >/etc/dphys-swapfile\n  systemctl enable dphys-swapfile.service\n\n  # compile pi sniffer\n  cd home/pi/pi_sniffer\n  mkdir build\n  cd build\n  cmake ..\n  make\n\n  cd /\n\n  # configure pwnagotchi\n  echo -e \"$HOST_NAME\" > /etc/hostname\n  sed -i \"s@^127\\.0\\.0\\.1 .*@127.0.0.1 localhost \"$HOST_NAME\" \"$HOST_NAME\".local@g\" /etc/hosts\n\n  # interface dependencies\n  pip3 install adafruit-circuitpython-ssd1306 spidev RPI.GPIO adafruit-blinka\n\n  echo \"dtparam=i2c_arm=on\" >> boot/config.txt\n  echo \"dtparam=spi=on\" >> boot/config.txt\n  echo \"i2c-dev\" >> etc/modules\n  echo \"hi\" >> boot/ssh\n\n  # Re4son-Kernel\n  echo \"deb http://http.re4son-kernel.com/re4son/ kali-pi main\" > /etc/apt/sources.list.d/re4son.list\n  wget -O - https://re4son-kernel.com/keys/http/archive-key.asc | apt-key add -\n  apt update\n  apt install -y kalipi-kernel kalipi-bootloader kalipi-re4son-firmware kalipi-kernel-headers libraspberrypi0 libraspberrypi-dev libraspberrypi-doc libraspberrypi-bin\n\n  # Fix PARTUUID\n  PUUID_ROOT=\"\\$(blkid \"\\$(df / --output=source | tail -1)\" | grep -Po 'PARTUUID=\"\\K[^\"]+')\"\n  PUUID_BOOT=\"\\$(blkid \"\\$(df /boot --output=source | tail -1)\" | grep -Po 'PARTUUID=\"\\K[^\"]+')\"\n\n  # sed regex info: search for line containing / followed by whitespace or /boot (second sed)\n  #                 in this line, search for PARTUUID= followed by letters, numbers or \"-\"\n  #                 replace that match with the new PARTUUID\n  sed -i \"/\\/[ ]\\+/s/PARTUUID=[A-Za-z0-9-]\\+/PARTUUID=\\$PUUID_ROOT/g\" /etc/fstab\n  sed -i \"/\\/boot/s/PARTUUID=[A-Za-z0-9-]\\+/PARTUUID=\\$PUUID_BOOT/g\" /etc/fstab\n\n  sed -i \"s/root=[^ ]\\+/root=PARTUUID=\\${PUUID_ROOT}/g\" /boot/cmdline.txt\n\n  # delete keys\n  find /etc/ssh/ -name \"ssh_host_*key*\" -delete\n\n  # slows down boot\n  systemctl disable apt-daily.timer apt-daily.service apt-daily-upgrade.timer apt-daily-upgrade.service\n\n  # unecessary services\n  systemctl disable triggerhappy bluetooth wpa_supplicant\n\nEOF\n  sed -i'' 's/^#//g' etc/ld.so.preload\n  cd \"${REPO_DIR}\"\n  umount -R \"${MNT_DIR}\"\n  losetup -D \"$(losetup -l | awk '/raspbian\\.img/{print $1}')\"\n  mv \"${TMP_DIR}/raspbian.img\" \"${OUTPUT_NAME}\"\n}\n\ncheck_dependencies\nget_raspbian \"latest\"\nsetup_raspbian\nprovision_raspbian\n\necho -e \"[+] Done.\"\n"
  },
  {
    "path": "pi_sniffer/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 2.8)\n\n# Options\noption(debug \"Build with debug flags.\" Off)\n\nset(PROJECT_NAME pi_sniffer)\nproject(${PROJECT_NAME})\n\n# compiler flags\nif (debug)\n    set(CMAKE_CXX_FLAGS \"-g -std=c++0x -Wall -Wextra -Weffc++ -Wshadow -pedantic -Wcast-align -Wcast-qual -Woverloaded-virtual -Wstrict-null-sentinel -Wswitch-default -Winit-self -Wlogical-op -Wno-deprecated-declarations\")\nelse()\n    set(CMAKE_CXX_FLAGS \"-O2 -std=c++0x -Wall -Wextra -Wno-deprecated-declarations\")\nendif()\n\n#package locations\nfind_package(Boost 1.67 COMPONENTS system thread program_options filesystem atomic REQUIRED)\nfind_package(OpenSSL REQUIRED)\n\n# includes\ninclude_directories(SYSTEM ${Boost_INCLUDE_DIR})\ninclude_directories(SYSTEM ${OPENSSL_INCLUDE_DIR})\ninclude_directories(SYSTEM \"src/\")\n\n# compilation units\nadd_executable(${PROJECT_NAME} \n               src/main.cpp\n               src/ap.cpp\n               src/client.cpp\n               src/probed_network.cpp\n               src/configuration.cpp\n               src/packet.cpp\n               src/stats.cpp\n               src/input/kismet_drone.cpp\n               src/input/pcap.cpp\n               src/protocols/ieee80211.cpp\n               src/protocols/llcsnap.cpp\n               src/protocols/eapol11.cpp\n               src/util/kml_maker.cpp\n               src/util/pcap_output.cpp\n               src/util/convert.cpp)\n\n# linking comp / libs\ntarget_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} -lpugixml -ltins)\n"
  },
  {
    "path": "pi_sniffer/pi_sniffer.conf",
    "content": "<pi_sniffer>\n    <wifidecrypt>\n        <key type=\"wep\" bssid=\"00:12:bf:12:32:29\" key=\"1f1f1f1f1f\" />\n        <key type=\"wpa\" ssid=\"Coherer\" key=\"Induction\" />\n    </wifidecrypt>\n    <output>\n        <dir path=\"/home/pi/pi_sniffer_output/\" />\n        <file type=\"pcap\" enabled=\"true\" />\n        <file type=\"wigle\" enabled=\"true\" />\n        <file type=\"kml\" enabled=\"true\" />\n        <file type=\"client_csv\" enabled=\"true\"/>\n        <file type=\"probe_csv\" enabled=\"true\"/>\n        <file type=\"ap_clients_csv\" enabled=\"true\"/>\n    </output>\n</pi_sniffer>\n"
  },
  {
    "path": "pi_sniffer/src/ap.cpp",
    "content": "#include \"ap.hpp\"\n#include \"util/convert.hpp\"\n\nAP::AP() :\n    m_bssid(),\n    m_lastseen(0),\n    m_firstseen(0),\n    m_lastSignal(0),\n    m_bestSignal(-100),\n    m_channel(0),\n    m_wps(false),\n    m_parsed_beacon(false),\n    m_ssid(),\n    m_mac(),\n    m_encryption(),\n    m_clients(0),\n    m_data(0),\n    m_accesslock(),\n    m_lat(0),\n    m_long(0),\n    m_alt(0),\n    m_bestLat(0),\n    m_bestLong(0),\n    m_bestAlt(0)\n{\n}\n\nAP::~AP()\n{\n}\n\nbool AP::has_wps() const\n{\n    return m_wps;\n}\n\nvoid AP::set_wps(bool p_wps)\n{\n    m_wps = p_wps;\n}\n\nvoid AP::set_last_seen(boost::uint32_t p_epoch_time)\n{\n    if (m_firstseen == 0)\n    {\n        m_firstseen = p_epoch_time;\n    }\n\n    m_lastseen = p_epoch_time;\n}\n\nboost::uint32_t AP::get_last_seen() const\n{\n    return m_lastseen;\n}\n\nboost::uint32_t AP::get_first_seen() const\n{\n    return m_firstseen;\n}\n\nvoid AP::set_location_info(boost::int8_t p_signal,\n                                       double p_lat, double p_long,\n                                       double p_alt, bool p_gps)\n{\n    if (p_signal == 0)\n    {\n        return;\n    }\n\n    m_lastSignal = p_signal;\n    if (p_gps)\n    {\n        boost::mutex::scoped_lock routerLock(m_accesslock);\n        m_lat = p_lat;\n        m_long = p_long;\n        m_alt = p_alt;\n\n        if (m_lastSignal > m_bestSignal)\n        {\n            m_bestSignal = m_lastSignal;\n            m_bestLat = p_lat;\n            m_bestLong = p_long;\n            m_bestAlt = p_alt;\n        }\n    }\n    else\n    {\n        if (m_lastSignal > m_bestSignal)\n        {\n            m_bestSignal = m_lastSignal;\n        }\n    }\n}\n\nboost::int8_t AP::get_last_signal() const\n{\n    return m_lastSignal;\n}\n\nboost::int8_t AP::get_best_signal() const\n{\n    return m_bestSignal;\n}\n\ndouble AP::get_latitude()\n{\n    boost::mutex::scoped_lock routerLock(m_accesslock);\n    return m_lat;\n}\n\ndouble AP::get_best_latitude()\n{\n    boost::mutex::scoped_lock routerLock(m_accesslock);\n    return m_bestLat;\n}\n\ndouble AP::get_longitude()\n{\n    boost::mutex::scoped_lock routerLock(m_accesslock);\n    return m_long;\n}\n\ndouble AP::get_best_longitude()\n{\n    boost::mutex::scoped_lock routerLock(m_accesslock);\n    return m_bestLong;\n}\n\ndouble AP::get_altitude()\n{\n    boost::mutex::scoped_lock routerLock(m_accesslock);\n    return m_alt;\n}\n\ndouble AP::get_best_altitude() \n{\n    boost::mutex::scoped_lock routerLock(m_accesslock);\n    return m_bestAlt;\n}\n\nvoid AP::set_ssid(const std::string& p_ssid)\n{\n    // only accept ascii, I guess\n    for (unsigned int i = 0; i < p_ssid.size(); i++)\n    {\n        if (p_ssid[i] > 0x7e || p_ssid[i] < 0x20)\n        {\n            return;\n        }\n    }\n    boost::mutex::scoped_lock routerLock(m_accesslock);\n    m_ssid.assign(p_ssid);\n}\n\nstd::string AP::get_ssid()\n{\n    boost::mutex::scoped_lock routerLock(m_accesslock);\n    return m_ssid;\n}\n\nboost::uint64_t AP::get_bssid()\n{\n    boost::mutex::scoped_lock routerLock(m_accesslock);\n    return m_bssid;\n}\n\nvoid AP::set_mac(const std::string& p_mac)\n{\n    boost::mutex::scoped_lock routerLock(m_accesslock);\n    m_mac.assign(p_mac);\n    m_bssid = string_mac_to_int(p_mac);\n}\n\nstd::string AP::get_mac()\n{\n    boost::mutex::scoped_lock routerLock(m_accesslock);\n    return m_mac;\n}\n\nvoid AP::set_channel(boost::uint8_t p_channel)\n{\n    m_channel = p_channel;\n}\n\nboost::uint8_t AP::get_channel() const\n{\n    return m_channel;\n}\n\nvoid AP::set_encryption(const std::string& p_encryption)\n{\n    boost::mutex::scoped_lock routerLock(m_accesslock);\n    m_encryption.assign(p_encryption);\n}\n\nstd::string AP::get_encryption()\n{\n    boost::mutex::scoped_lock routerLock(m_accesslock);\n    return m_encryption;\n}\n\nvoid AP::increment_client()\n{\n    ++m_clients;\n}\n\nboost::uint32_t AP::get_client_count() const\n{\n    return m_clients;\n}\n\nvoid AP::increment_data_packet()\n{\n    ++m_data;\n}\n\nboost::uint32_t AP::get_data_count() const\n{\n    return m_data;\n}\n\n\nvoid AP::set_beacon_parsed()\n{\n    m_parsed_beacon = true;\n}\n\nbool AP::get_beacon_parsed() const\n{\n    return m_parsed_beacon;\n}"
  },
  {
    "path": "pi_sniffer/src/ap.hpp",
    "content": "#ifndef AP_HPP\n#define AP_HPP\n\n#include <string>\n#include <boost/cstdint.hpp>\n#include <boost/thread/mutex.hpp>\n\n/*!\n * This object represents an access point, typically a standard router but sometimes more\n * interesting things like cars, doorbells, tablets, etc. This object attempts to track the\n * general state and location of the the AP.\n *\n * Note that this object is r/w on at least two threads. I've made the assumption that\n * int32 assignment is atomic, otherwise all other accesses require lock access.\n */\nclass AP\n{\npublic:\n\n    AP();\n    ~AP();\n\n    // Set to true if the WPS tag indicates the AP has WPS configured (0x02)\n    void set_wps(bool p_wps);\n    bool has_wps() const;   \n\n    // Updated to track where we've seen the AP\n    void set_last_seen(boost::uint32_t p_time);\n    boost::uint32_t get_last_seen() const;\n    boost::uint32_t get_first_seen() const;\n\n    // Signal strength and GPS location information. We make no attempt to\n    // triangulate the actual location of the AP. Instead, we just call the \"best\"\n    // GPS coordinates whereever the signal strength is strongest. I think that\n    // is a fine enough solution.\n    void set_location_info(boost::int8_t p_signal, double p_lat, double p_long, double p_alt, bool p_gps);\n    boost::int8_t get_last_signal() const;\n    boost::int8_t get_best_signal() const;\n    double get_latitude();\n    double get_best_latitude();\n    double get_longitude();\n    double get_best_longitude();\n    double get_altitude();\n    double get_best_altitude();\n\n    // Store the broadcasted name. Currently only accepting ascii names.\n    void set_ssid(const std::string& p_ssid);\n    std::string get_ssid();\n\n    // Store the devices mac address\n    void set_mac(const std::string& p_mac);\n    std::string get_mac();\n    boost::uint64_t get_bssid();\n\n    // Store the channel we observed this device on\n    void set_channel(boost::uint8_t p_channel);\n    boost::uint8_t get_channel() const;\n\n    // Store a string representation of the encryption used (Open, WEP, WPA-EAP/PSK, WPA2-EAP/PSK)\n    void set_encryption(const std::string& p_encryption);\n    std::string get_encryption();\n\n    // Increment observed associated clients. This relies on a mechanism outside of the\n    // object to ensure that duplicate clients aren't being tracked.\n    void increment_client();\n    boost::uint32_t get_client_count() const;\n\n    // Track how much data we've seen go across this AP\n    void increment_data_packet();\n    boost::uint32_t get_data_count() const;\n\n    // We only want to parse this devices beacon/probe response once in order to save\n    // processing time. \n    void set_beacon_parsed();\n    bool get_beacon_parsed() const;\n\nprivate:\n\n    AP(const AP& p_rhs);\n    AP& operator=(const AP& p_rhs);\n\nprivate:\n\n    boost::uint64_t m_bssid;\n    boost::uint32_t m_lastseen;\n    boost::uint32_t m_firstseen;\n    boost::int8_t m_lastSignal;\n    boost::int8_t m_bestSignal;\n    boost::uint8_t m_channel;\n    bool m_wps;\n    bool m_parsed_beacon;\n    std::string m_ssid;\n    std::string m_mac;\n    std::string m_encryption;\n    boost::uint32_t m_clients;\n    boost::uint32_t m_data;\n    boost::mutex m_accesslock;\n    double m_lat;\n    double m_long;\n    double m_alt;\n    double m_bestLat;\n    double m_bestLong;\n    double m_bestAlt;\n};\n\n#endif\n"
  },
  {
    "path": "pi_sniffer/src/client.cpp",
    "content": "#include \"client.hpp\"\n#include \"util/convert.hpp\"\n\nClient::Client() :\n    m_lastseen(0),\n    m_firstseen(0),\n    m_associated_mac(0),\n    m_lastSignal(0),\n    m_bestSignal(-100),\n    m_accesslock(),\n    m_mac(),\n    m_lat(0),\n    m_long(0),\n    m_alt(0),\n    m_bestLat(0),\n    m_bestLong(0),\n    m_bestAlt(0)\n{\n}\n\nClient::~Client()\n{\n}\n\nvoid Client::set_last_seen(uint64_t p_epoch_time)\n{\n    if (m_firstseen == 0)\n    {\n        m_firstseen = p_epoch_time;\n    }\n    m_lastseen = p_epoch_time;\n}\n\nboost::uint64_t Client::get_last_seen()\n{\n    boost::mutex::scoped_lock clientLock(m_accesslock);\n    return m_lastseen;\n}\n\nboost::uint64_t Client::get_first_seen()\n{\n    boost::mutex::scoped_lock clientLock(m_accesslock);\n    return m_firstseen;\n}\n\nvoid Client::set_location_info(boost::int8_t p_signal, double p_lat, double p_long, double p_alt, bool p_gps_on)\n{\n    if (p_signal == 0)\n    {\n        return;\n    }\n\n    m_lastSignal = p_signal;\n    if (p_gps_on)\n    {\n        m_lat = p_lat;\n        m_long = p_long;\n        m_alt = p_alt;\n\n        if (m_lastSignal > m_bestSignal)\n        {\n            m_bestLat = p_lat;\n            m_bestLong = p_long;\n            m_bestAlt = p_alt;\n            m_bestSignal = m_lastSignal;\n        }\n    }\n    else if (m_lastSignal != 0)\n    {\n        if (m_lastSignal > m_bestSignal)\n        {\n            m_bestSignal = m_lastSignal;\n        }\n    }\n}\n\nboost::uint64_t Client::get_associated()\n{\n    boost::mutex::scoped_lock clientLock(m_accesslock);\n    return m_associated_mac;\n}\n\nstd::string Client::get_associated_str()\n{\n    boost::mutex::scoped_lock clientLock(m_accesslock);\n    return printable_mac(reinterpret_cast<const unsigned char*>(&m_associated_mac), 6, true);\n}\n\nvoid Client::set_associated(boost::uint64_t p_associated_mac)\n{\n    boost::mutex::scoped_lock clientLock(m_accesslock);\n    m_associated_mac = p_associated_mac;\n}\n\nboost::int8_t Client::get_last_signal() const\n{\n    return m_lastSignal;\n}\n\nboost::int8_t Client::get_best_signal() const\n{\n    return m_bestSignal;\n}\n\ndouble Client::get_latitude()\n{\n    boost::mutex::scoped_lock clientLock(m_accesslock);\n    return m_lat;\n}\n\ndouble Client::get_best_latitude()\n{\n    boost::mutex::scoped_lock clientLock(m_accesslock);\n    return m_bestLat;\n}\n\ndouble Client::get_longitude()\n{\n    boost::mutex::scoped_lock clientLock(m_accesslock);\n    return m_long;\n}\n\ndouble Client::get_best_longitude()\n{\n    boost::mutex::scoped_lock clientLock(m_accesslock);\n    return m_bestLong;\n}\n\ndouble Client::get_altitude()\n{\n    boost::mutex::scoped_lock clientLock(m_accesslock);\n    return m_alt;\n}\n\ndouble Client::get_best_altitude()\n{\n    boost::mutex::scoped_lock clientLock(m_accesslock);\n    return m_bestAlt;\n}\n\nvoid Client::set_mac(const std::string& p_mac)\n{\n    boost::mutex::scoped_lock clientLock(m_accesslock);\n    m_mac.assign(p_mac);\n}\n\nstd::string Client::get_mac()\n{\n    boost::mutex::scoped_lock clientLock(m_accesslock);\n    return m_mac;\n}\n"
  },
  {
    "path": "pi_sniffer/src/client.hpp",
    "content": "#ifndef CLIENT_HPP\n#define CLIENT_HPP\n\n#include <string>\n#include <boost/thread.hpp>\n\n/**\n * This object represents an observed client. Clients should only be created if\n * we observe it associated with an AP.\n *\n * Note that this object is r/w on at least two threads. I've made the assumption that\n * int32 assignment is atomic, otherwise all other accesses require lock access.\n */\nclass Client\n{\npublic:\n\n    Client();\n    ~Client();\n\n    // Updated to track where we've seen the AP\n    void set_last_seen(boost::uint64_t p_epoch_time);\n    boost::uint64_t get_last_seen();\n    boost::uint64_t get_first_seen();\n\n    // Signal strength and GPS location information. We make no attempt to\n    // triangulate the actual location of the AP. Instead, we just call the \"best\"\n    // GPS coordinates whereever the signal strength is strongest. I think that\n    // is a fine enough solution.\n    void set_location_info(boost::int8_t p_signal, double p_lat, double p_long, double p_alt, bool p_gps_on);\n    boost::int8_t get_last_signal() const;\n    boost::int8_t get_best_signal() const;\n    double get_latitude();\n    double get_best_latitude();\n    double get_longitude();\n    double get_best_longitude();\n    double get_altitude();\n    double get_best_altitude();\n\n    // store the devices mac address\n    void set_mac(const std::string& p_mac);\n    std::string get_mac();\n\n    // store the mac of the station we are associated with\n    void set_associated(boost::uint64_t p_associated_mac);\n    boost::uint64_t get_associated();\n    std::string get_associated_str();\n\nprivate:\n\n    Client(const Client& p_rhs);\n    Client& operator=(const Client& p_rhs);\n\nprivate:\n\n    boost::uint64_t m_lastseen;\n    boost::uint64_t m_firstseen;\n    boost::uint64_t m_associated_mac;\n    boost::int8_t m_lastSignal;\n    boost::int8_t m_bestSignal;\n    boost::mutex m_accesslock;\n    std::string m_mac;\n    double m_lat;\n    double m_long;\n    double m_alt;\n    double m_bestLat;\n    double m_bestLong;\n    double m_bestAlt;\n};\n\n#endif\n"
  },
  {
    "path": "pi_sniffer/src/configuration.cpp",
    "content": "#include \"configuration.hpp\"\n\n#include \"util/convert.hpp\"\n\n#include <boost/filesystem/operations.hpp>\n#include <boost/filesystem/path.hpp>\n#include <pugixml.hpp>\n\nConfiguration::Configuration() :\n    m_wep_decrypter(),\n    m_wpa_decrypter(),\n    m_output_path(),\n    m_wep_keys(),\n    m_wpa_keys(),\n    m_pcap(false),\n    m_wigle(false),\n    m_kml(false),\n    m_client_csv(false),\n    m_probe_csv(false),\n    m_ap_clients_csv(false)\n{\n}\n\nConfiguration::~Configuration()\n{\n}\n\nvoid Configuration::parse_configuration(const std::string& p_config_location)\n{\n    pugi::xml_document config;\n    if (!config.load_file(p_config_location.c_str()))\n    {\n        throw std::runtime_error(\"Failed to load the configuration file: \" + p_config_location);\n    }\n\n    const pugi::xml_node fullConfig = config.child(\"pi_sniffer\");\n    if (fullConfig.empty())\n    {\n        throw std::runtime_error(\"Failed to find the root node: <pi_sniffer>\");\n    }\n\n    const pugi::xml_node wifi_decrypt = fullConfig.child(\"wifidecrypt\");\n    for (pugi::xml_node_iterator it = wifi_decrypt.begin();\n         it != wifi_decrypt.end(); ++it)\n    {\n        parse_wifi_key(*it);\n    }\n\n    const pugi::xml_node output = fullConfig.child(\"output\");\n    for (pugi::xml_node_iterator it = output.begin();\n         it != output.end(); ++it)\n    {\n        parse_output(*it);\n    }\n}\n\nvoid Configuration::parse_wifi_key(const pugi::xml_node& p_key)\n{\n    std::string key_type(p_key.attribute(\"type\").as_string());\n    if (key_type.empty())\n    {\n        throw std::runtime_error(\"key missing the type attribute.\");\n    }\n\n    std::string key(p_key.attribute(\"key\").as_string());\n    if (key.empty())\n    {\n        throw std::runtime_error(\"key missing the key attribute.\");\n    }\n\n    if (key_type == \"wep\")\n    {\n\n        std::string bssid(p_key.attribute(\"bssid\").as_string());\n        if (bssid.empty())\n        {\n            throw std::runtime_error(\"key missing the bssid attribute.\");\n        }\n\n        boost::uint64_t index = string_mac_to_int(bssid);\n\n        // convert to actual hex\n        std::string hex_key(string_to_hex(key));\n        if (hex_key.size() != 5 && hex_key.size() != 13 && hex_key.size() != 16)\n        {\n            throw std::runtime_error(\"The WEP key must be 5, 13, or 16 bytes long.\");\n        }\n\n        m_wep_decrypter.add_password(int_mac_to_array(index), hex_key);\n        m_wep_keys.insert(bssid);\n    }\n    else if (key_type == \"wpa\")\n    {\n        std::string ssid(p_key.attribute(\"ssid\").as_string());\n        if (ssid.empty())\n        {\n            throw std::runtime_error(\"key missing the ssid attribute.\");\n        }\n        m_wpa_decrypter.add_ap_data(key, ssid);\n        m_wpa_keys.insert(ssid);\n    }\n    else\n    {\n        throw std::runtime_error(\"Unknown key type: \" + key_type + \". Options are wep or wpa\");\n    }\n}\n\nvoid Configuration::parse_output(const pugi::xml_node& p_output)\n{\n    std::string type(p_output.attribute(\"type\").as_string());\n    std::string path(p_output.attribute(\"path\").as_string());\n    if (!type.empty())\n    {\n        if (type.compare(\"pcap\") == 0)\n        {\n            std::string enabled(p_output.attribute(\"enabled\").as_string());\n            if (enabled.compare(\"true\") == 0)\n            {\n                m_pcap = true;\n            }\n        }\n        else if (type.compare(\"wigle\") == 0)\n        {\n            std::string enabled(p_output.attribute(\"enabled\").as_string());\n            if (enabled.compare(\"true\") == 0)\n            {\n                m_wigle = true;\n            }\n        }\n        else if (type.compare(\"kml\") == 0)\n        {\n            std::string enabled(p_output.attribute(\"enabled\").as_string());\n            if (enabled.compare(\"true\") == 0)\n            {\n                m_kml = true;\n            }\n        }\n        else if (type.compare(\"client_csv\") == 0)\n        {\n            std::string enabled(p_output.attribute(\"enabled\").as_string());\n            if (enabled.compare(\"true\") == 0)\n            {\n                m_client_csv = true;\n            }\n        }\n        else if (type.compare(\"probe_csv\") == 0)\n        {\n            std::string enabled(p_output.attribute(\"enabled\").as_string());\n            if (enabled.compare(\"true\") == 0)\n            {\n                m_probe_csv = true;\n            }\n        }\n        else if (type.compare(\"ap_clients_csv\") == 0)\n        {\n            std::string enabled(p_output.attribute(\"enabled\").as_string());\n            if (enabled.compare(\"true\") == 0)\n            {\n                m_ap_clients_csv = true;\n            }\n        }\n    }\n    else if (!path.empty())\n    {\n        m_output_path.assign(p_output.attribute(\"path\").as_string());\n        if (!boost::filesystem::exists(m_output_path))\n        {\n            // create directory\n            try\n            {\n                boost::filesystem::create_directories(m_output_path);\n            }\n            catch (const std::exception&)\n            {\n                // ignore it\n            }\n        }\n\n        if (!boost::filesystem::is_directory(m_output_path))\n        {\n            throw std::runtime_error(\"The output path is not a directory.\");\n        }\n    }\n}\n\nbool Configuration::has_wep_key(const std::string& p_bssid) const\n{\n    return m_wep_keys.find(p_bssid) != m_wep_keys.end();\n}\n\nbool Configuration::has_wpa_key(const std::string& p_ssid) const\n{\n    return m_wpa_keys.find(p_ssid) != m_wpa_keys.end();\n}\n"
  },
  {
    "path": "pi_sniffer/src/configuration.hpp",
    "content": "#ifndef CONFIG_HPP\n#define CONFIG_HPP\n\n#include <boost/unordered_map.hpp>\n#include <boost/cstdint.hpp>\n\n#include <string>\n\n#include <tins/crypto.h>\n\nnamespace pugi\n{\n    struct xml_node;\n}\n\n/**\n * Parses the configuration file passed on the command line. Also holds the information.\n * The configuration information is largely:\n * \n * 1. What type of files to output.\n * 2. Where to output the files.\n * 3. Decryption keys.\n */\nclass Configuration\n{\npublic:\n\n    Configuration();\n\n    ~Configuration();\n\n    void parse_configuration(const std::string& p_config_location);\n\n    const std::string& get_output_path() const\n    {\n        return m_output_path;\n    }\n\n    bool get_pcap() const\n    {\n        return m_pcap;\n    }\n\n    bool get_wigle() const\n    {\n        return m_wigle;\n    }\n\n    bool get_kml() const\n    {\n        return m_kml;\n    }\n\n    bool get_client_csv() const\n    {\n        return m_client_csv;\n    }\n\n    bool get_probe_csv() const\n    {\n        return m_probe_csv;\n    }\n\n    bool get_ap_clients_csv() const\n    {\n        return m_ap_clients_csv;\n    }\n\n    bool has_wep_key(const std::string& p_bssid) const;\n\n    bool has_wpa_key(const std::string& p_ssid) const;\n\nprivate:\n\n    void parse_wifi_key(const pugi::xml_node& p_key);\n\n    void parse_output(const pugi::xml_node& p_output);\n\npublic:\n\n    // Note: its a touch odd that configuration holds the decryptors but here we are\n    Tins::Crypto::WEPDecrypter m_wep_decrypter;\n\n    Tins::Crypto::WPA2Decrypter m_wpa_decrypter;\n\nprivate:\n\n    //! The file path to the output directory\n    std::string m_output_path;\n\n    //! AP we have wep keys for\n    std::set<std::string> m_wep_keys;\n\n    //! AP we have wpa keys for\n    std::set<std::string> m_wpa_keys;\n\n    //! indicates if we should write ppi pcap file to disk\n    bool m_pcap;\n\n    //! indicates if we should write wigle csv to disk\n    bool m_wigle;\n\n    //! indicates if we should write out the kml information for routers\n    bool m_kml;\n\n    //! indicates if we should write out the clients to a csv file\n    bool m_client_csv;\n\n    //! indicates if we should write out the probes to a csv file\n    bool m_probe_csv;\n\n    //! indicates if we should write out the ap client csv file\n    bool m_ap_clients_csv;\n};\n\n#endif\n"
  },
  {
    "path": "pi_sniffer/src/input/kismet_drone.cpp",
    "content": "#include \"kismet_drone.hpp\"\n\n#include \"packet.hpp\"\n#include \"stats.hpp\"\n\n#include <iostream>\n#include <boost/lambda/bind.hpp>\n#include <boost/lambda/lambda.hpp>\n\nnamespace\n{\n    // hold the packet data that goes down to the protocols\n    std::string s_drone_data;\n\n    #pragma pack(push, 1)\n    struct drone_trans_double\n    {\n        uint32_t mantissal;\n        uint32_t mantissah;\n        uint16_t exponent;\n        uint16_t sign;\n    };\n\n    struct ieee_double_t\n    {\n        unsigned int mantissal:32;\n        unsigned int mantissah:20;\n        unsigned int exponent:11;\n        unsigned int sign:1;\n    };\n    #pragma pack(pop)\n\n    void double_conversion_drone(double& x, drone_trans_double* y)\n    {\n        ieee_double_t* locfl = (ieee_double_t *)&(x);\n        (locfl)->mantissal = ntohl((y)->mantissal);\n        (locfl)->mantissah = ntohl((y)->mantissah);\n        (locfl)->exponent = ntohs((y)->exponent);\n        (locfl)->sign = ntohs((y)->sign);\n    }\n}\n\nKismetDrone::KismetDrone(const std::string& p_address, std::size_t p_port) :\n    m_ip(p_address),\n    m_port(),\n    m_io_service(),\n    m_socket(m_io_service),\n    m_deadline(m_io_service)\n{\n    std::stringstream portString;\n    portString << p_port;\n    m_port = portString.str();\n\n    m_deadline.expires_at(boost::posix_time::pos_infin);\n    check_deadline();\n}\n\nKismetDrone::~KismetDrone()\n{\n    close();\n}\n\nvoid KismetDrone::close()\n{\n    try\n    {\n        m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both);\n        m_socket.close();\n    }\n    catch (...)\n    {\n    }\n}\n\nvoid KismetDrone::check_deadline()\n{\n    if (m_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now())\n    {\n      boost::system::error_code ignored_ec;\n      m_socket.close(ignored_ec);\n      m_deadline.expires_at(boost::posix_time::pos_infin);\n    }\n\n    m_deadline.async_wait(boost::lambda::bind(&KismetDrone::check_deadline, this));\n}\n\nbool KismetDrone::connect()\n{\n    std::cout << \"Drone connecting...\" << std::endl;\n    try\n    {\n        boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string(m_ip), atoi(m_port.c_str()));\n\n        // set a 2 second deadline for the connection   \n        boost::system::error_code ec = boost::asio::error::would_block;\n        m_deadline.expires_from_now(boost::posix_time::seconds(5));\n        m_socket.async_connect(endpoint, boost::lambda::var(ec) = boost::lambda::_1);\n\n        do\n        {\n            m_io_service.run_one();\n        }\n        while (ec == boost::asio::error::would_block);\n\n        if (ec || !m_socket.is_open())\n        {\n            return false;\n        }\n    }\n    catch(const std::exception& e)\n    {\n        return false;\n    }\n    return true;\n}\n\nbool KismetDrone::read(boost::uint32_t p_length, std::string& p_data)\n{\n    boost::system::error_code ec = boost::asio::error::would_block;\n    m_deadline.expires_from_now(boost::posix_time::seconds(5));\n\n    boost::asio::streambuf response;\n    boost::asio::async_read(m_socket, response, boost::asio::transfer_exactly(p_length), boost::lambda::var(ec) = boost::lambda::_1);\n    do\n    {\n        m_io_service.run_one();\n    }\n    while (ec == boost::asio::error::would_block);\n\n    if (ec)\n    {\n        return false;\n    }\n\n    p_data.assign(std::istreambuf_iterator<char>(&response), std::istreambuf_iterator<char>());\n    response.consume(p_length);\n    return true;\n}\n\nbool KismetDrone::get_packet(Packet& p_packet)\n{\n    boost::uint32_t type = 0;\n    boost::uint32_t read_length = 0;\n    boost::uint32_t offset = 0;\n    boost::uint32_t bitmap = 0;\n    while (type != 3)\n    {\n        if (!read(12, s_drone_data) || s_drone_data.size() != 12)\n        {\n            return false;\n        }\n\n        // check for sentinel\n        if (static_cast<boost::uint8_t>(s_drone_data[0]) != 0xde ||\n            static_cast<boost::uint8_t>(s_drone_data[3]) != 0xef)\n        {\n            return false;\n        }\n\n        // get the packet type (will indicate if we have a packet or not)\n        type = *reinterpret_cast<boost::uint32_t*>(&s_drone_data[0] + 4);\n        type = ntohl(type);\n\n        // get length of the packet and read it all in\n        read_length = *reinterpret_cast<boost::uint32_t*>(&s_drone_data[0] + 8);\n        read_length = ntohl(read_length);\n\n        if (!read(read_length, s_drone_data) || s_drone_data.size() != read_length)\n        {\n            return false;\n        }\n\n        bitmap = *reinterpret_cast<boost::uint32_t*>(&s_drone_data[0]);\n        bitmap = ntohl(bitmap);\n        if ((bitmap & 1) == 0)\n        {\n            // missing radio header.\n            type = 2;\n            continue;\n        }\n\n        offset = *reinterpret_cast<boost::uint32_t*>(&s_drone_data[0] + 4);\n        offset = ntohl(offset) + 8;\n        if (read_length == 12 || offset == 8)\n        {\n            // This is an empty packet.\n            type = 2;\n        }\n    }\n\n    if ((offset + 44) >= read_length)\n    {\n        // invalid packet\n        return false;\n    }\n\n    if ((bitmap & 2) == 2)\n    {\n        // gps data present\n        boost::uint16_t gps_size = *reinterpret_cast<boost::uint16_t*>(&s_drone_data[0] + 38);\n        gps_size = ntohs(gps_size);\n        if (gps_size == 68)\n        {\n            p_packet.m_gps_on = true;\n            double_conversion_drone(p_packet.m_lat, reinterpret_cast<drone_trans_double*>(&s_drone_data[0] + 46));\n            double_conversion_drone(p_packet.m_long, reinterpret_cast<drone_trans_double*>(&s_drone_data[0] + 46 + sizeof(drone_trans_double)));\n            double_conversion_drone(p_packet.m_alt, reinterpret_cast<drone_trans_double*>(&s_drone_data[0] + 46 + (sizeof(drone_trans_double) * 2)));\n        }\n    }\n    // grab the signal metadata\n    boost::int16_t dbm = *reinterpret_cast<boost::int16_t*>(&s_drone_data[0] + 18);\n    dbm = ntohs(dbm);\n\n    // grab the metadata in the radio header\n    boost::uint32_t time = *reinterpret_cast<boost::uint32_t*>(&s_drone_data[0] + (offset + 28));\n    time = ntohl(time);\n\n    // skip over the metadata\n    p_packet.m_data = reinterpret_cast<const boost::uint8_t*>(&s_drone_data[0]) + (offset + 44);\n    p_packet.m_length = read_length - (offset + 44);\n\n    // update stats\n    p_packet.m_stats.increment_packets();\n    p_packet.m_time = time;\n    p_packet.m_signal = dbm;\n\n    return true;\n}\n"
  },
  {
    "path": "pi_sniffer/src/input/kismet_drone.hpp",
    "content": "#ifndef KISMET_DRONE_HPP\n#define KISMET_DRONE_HPP\n\n#include <cstddef>\n#include <string>\n#include <boost/asio.hpp>\n\nclass Packet;\n\nclass KismetDrone\n{\npublic:\n    KismetDrone(const std::string& p_address, std::size_t p_port);\n    ~KismetDrone();\n\n    bool get_packet(Packet& p_packet);\n    bool connect();\n\nprivate:\n\n    bool read(boost::uint32_t p_length, std::string& p_data);\n    void check_deadline();\n    void close();\n\nprivate:\n\n    KismetDrone(const KismetDrone& p_rhs);\n    KismetDrone& operator=(const KismetDrone& p_rhs);\n\nprivate:\n\n    // the ip address to connect to\n    std::string m_ip;\n\n    // the port to connect to\n    std::string m_port;\n\n    // the IO service associated with our blocking socket\n    boost::asio::io_service m_io_service;\n\n    // the blocking socket we use for communication\n    boost::asio::ip::tcp::socket m_socket;\n\n    // Timer to use with async socket operations\n    boost::asio::deadline_timer m_deadline;\n};\n\n#endif"
  },
  {
    "path": "pi_sniffer/src/input/pcap.cpp",
    "content": "#include \"pcap.hpp\"\n#include \"packet.hpp\"\n\n#include <boost/lexical_cast.hpp>\n#include <boost/concept_check.hpp>\n#include <boost/static_assert.hpp>\n\nnamespace\n{\n    unsigned char data[65535] = {0};\n\n    #pragma pack(push, 1)\n    struct pcap_header\n    {\n        boost::uint32_t magic_number;\n        boost::uint16_t version_major;\n        boost::uint16_t version_minor;\n        boost::uint32_t thiszone;\n        boost::uint32_t sigfigs;\n        boost::uint32_t snaplen;\n        boost::uint32_t network;\n    };\n\n    struct packet_header\n    {\n        boost::uint32_t ts_sec;\n        boost::uint32_t ts_usec;\n        boost::uint32_t incl_len;\n        boost::uint32_t orig_len;\n    };\n\n    struct ppi_packetheader\n    {\n        boost::uint8_t pph_version;\n        boost::uint8_t pph_flags;\n        boost::uint16_t pph_len;\n        boost::uint32_t pph_dlt;\n    };\n\n    struct ppi_fieldheader\n    {\n        boost::uint16_t pfh_type;\n        boost::uint16_t pfh_datalen;\n    };\n\n    struct gps_fields\n    {\n        boost::uint8_t gps_revision;\n        boost::uint8_t gps_pad;\n        boost::uint16_t gps_length;\n        boost::uint32_t gps_present;\n        boost::uint32_t gps_lat;\n        boost::uint32_t gps_long;\n        boost::uint32_t gps_alt;\n        boost::uint32_t gps_app;\n    };\n\n    struct radiotap_header\n    {\n        boost::uint8_t version;\n        boost::uint8_t pad;\n        boost::uint16_t len;\n        boost::uint32_t present;\n    };\n\n    struct ppi_common\n    {\n        boost::uint64_t tsft;\n        boost::uint16_t flags;\n        boost::uint16_t rate;\n        boost::uint16_t frequency;\n        boost::uint16_t channel_type;\n        boost::uint8_t hopset;\n        boost::uint8_t pattern;\n        boost::uint8_t rssi;\n        boost::uint8_t noise;\n    };\n\n    #pragma pack(pop)\n\n    // harris crap\n    double fixed_3_7_to_flt(boost::uint32_t in)\n    {\n        boost::int32_t remapped = in - (180 * 10000000);\n        return static_cast<double>(remapped) / 10000000;\n    }\n\n    double fixed_6_4_to_flt(boost::uint32_t in)\n    {\n        boost::int32_t remapped_in = in - (180000 * 10000);\n        double ret = (double) ((double) remapped_in / 10000);\n        return ret;\n    }\n}\n\nPCAP::PCAP(std::string p_filename) :\n    m_file(p_filename.c_str(), std::ios::binary),\n    m_linktype(0)\n{\n}\n\nPCAP::~PCAP()\n{\n}\n\nbool PCAP::initialize()\n{\n    if (!m_file.is_open())\n    {\n        return false;\n    }\n\n    m_file.read(reinterpret_cast<char*>(&data[0]), sizeof(pcap_header));\n    if (m_file.gcount() != sizeof(pcap_header))\n    {\n        return false;\n    }\n\n    struct pcap_header* header = reinterpret_cast<struct pcap_header*>(data);\n    if (header->magic_number != 0xa1b2c3d4)\n    {\n        return false;\n    }\n\n    if (header->network != 192 && header->network != 127 && header->network != 105)\n    {\n        return false;\n    }\n\n    m_linktype = header->network;\n    return true;\n}\n\nbool PCAP::eof() const\n{\n    return m_file.eof() && m_file.good();\n}\n\nbool PCAP::get_packet(Packet& p_packet)\n{\n    m_file.read(reinterpret_cast<char*>(&data[0]), sizeof(packet_header));\n    if (m_file.gcount() != sizeof(packet_header))\n    {\n        return false;\n    }\n\n    struct packet_header* header = reinterpret_cast<struct packet_header*>(data);\n    p_packet.m_time = header->ts_sec;\n    p_packet.m_stats.increment_packets();\n\n    boost::uint32_t length = header->incl_len;\n    m_file.read(reinterpret_cast<char*>(&data[0]), length);\n    if (m_file.gcount() != static_cast<std::streamsize>(length) ||\n        length < static_cast<std::size_t>(4))\n    {\n        return false;\n    }\n\n    switch (m_linktype)\n    {\n        case 127:\n            return do_radiotap(p_packet, length);\n        case 192:\n            return do_ppi(p_packet, length);\n        default:\n            //uhm... ok\n            break;\n    }\n\n    p_packet.m_data = data;\n    p_packet.m_length = length;\n    return true;\n}\n\nbool PCAP::do_radiotap(Packet& p_packet, boost::uint32_t p_length)\n{\n    struct radiotap_header* radio_header = reinterpret_cast<struct radiotap_header*>(data);\n    if (radio_header->version != 0)\n    {\n        return false;\n    }\n\n    if (p_length < radio_header->len)\n    {\n        return false;\n    }\n\n    bool has_fcs = false;\n    unsigned char* radio_tags = data + sizeof(radiotap_header);\n    if ((radio_header->present & 0x01) != 0)\n    {\n        // mac timestamp\n        radio_tags += 8;\n    }\n    if ((radio_header->present & 0x02) != 0)\n    {\n        // flags\n        unsigned char flags = radio_tags[0];\n        if (flags & 0x10)\n        {\n            has_fcs = true;\n        }\n        radio_tags += 1;\n    }\n    if ((radio_header->present & 0x04) != 0)\n    {\n        // rate\n        radio_tags += 1;\n    }\n    if ((radio_header->present & 0x08) != 0)\n    {\n        // channel frequency / type\n        radio_tags += 4;\n    }\n    if ((radio_header->present & 0x10) != 0)\n    {\n        // fhss\n        radio_tags += 2;\n    }\n    if ((radio_header->present & 0x20) != 0)\n    {\n        //signal\n        p_packet.m_signal = radio_tags[0];\n    }\n\n    p_packet.m_data = data + radio_header->len;\n    p_packet.m_length = p_length - radio_header->len;\n    if (has_fcs)\n    {\n        p_packet.m_length -= 4;\n    }\n\n    return true;\n}\n\nbool PCAP::do_ppi(Packet& p_packet, boost::uint32_t p_length)\n{\n    struct ppi_packetheader* ppi_header = reinterpret_cast<struct ppi_packetheader*>(data);\n    if (ppi_header->pph_version != 0)\n    {\n        return false;\n    }\n\n    if (ppi_header->pph_dlt != 105)\n    {\n        return false;\n    }\n\n    if (p_length < ppi_header->pph_len )\n    {\n        return false;\n    }\n\n    // check the next field type\n    struct ppi_fieldheader* field_header = reinterpret_cast<struct ppi_fieldheader*>(data + sizeof(ppi_packetheader));\n    if (field_header->pfh_type == 0x7532)\n    {\n        // gps info\n        struct gps_fields* gps = reinterpret_cast<struct gps_fields*>(data + sizeof(ppi_packetheader) + sizeof(ppi_fieldheader));\n        if (gps->gps_length == field_header->pfh_datalen)\n        {\n            if (gps->gps_present == 0x2000000e)\n            {\n                p_packet.m_gps_on = true;\n                p_packet.m_lat = fixed_3_7_to_flt(gps->gps_lat);\n                p_packet.m_long = fixed_3_7_to_flt(gps->gps_long);\n                p_packet.m_alt = fixed_6_4_to_flt(gps->gps_alt);\n\n                // try the next field\n                field_header = reinterpret_cast<struct ppi_fieldheader*>(\n                    reinterpret_cast<char*>(gps) + gps->gps_length);    \n            }\n        }\n    }\n\n    if (field_header->pfh_type == 0x0002)\n    {\n        // 802.11 common\n        struct ppi_common* common = reinterpret_cast<struct ppi_common*>(reinterpret_cast<char*>(field_header) + 4);\n        p_packet.m_signal = common->rssi;\n    }\n\n    p_packet.m_data = data + ppi_header->pph_len;\n    p_packet.m_length = p_length - ppi_header->pph_len;\n    return true;\n}\n"
  },
  {
    "path": "pi_sniffer/src/input/pcap.hpp",
    "content": "#ifndef PCAP_HPP\n#define PCAP_HPP\n\n#include <fstream>\n#include <string>\n\n#include <boost/cstdint.hpp>\n\nclass Packet;\n\nclass PCAP\n{\npublic:\n\n    PCAP(std::string p_filename);\n    ~PCAP();\n\n    bool initialize();\n    bool eof() const;\n    bool get_packet(Packet& p_packet);\n\nprivate:\n\n    bool do_radiotap(Packet& p_packet, boost::uint32_t p_length);\n    bool do_ppi(Packet& p_packet, boost::uint32_t p_length);\n\nprivate:\n\n    std::ifstream m_file;\n    std::size_t m_linktype;\n};\n\n#endif"
  },
  {
    "path": "pi_sniffer/src/main.cpp",
    "content": "#include <cstdlib>\n#include <iostream>\n#include <boost/thread.hpp>\n#include <boost/program_options.hpp>\n\n#include \"util/convert.hpp\"\n#include \"ap.hpp\"\n#include \"client.hpp\"\n#include \"packet.hpp\"\n#include \"protocols/ieee80211.hpp\"\n#include \"input/kismet_drone.hpp\"\n#include \"input/pcap.hpp\"\n\nnamespace\n{\n    bool parseCommandLine(int p_argCount, char* p_argArray[],\n        std::string& p_server_address, std::size_t& p_server_port, std::string& p_file, Configuration& p_config)\n    {\n        boost::program_options::options_description description(\"options\");\n        description.add_options()(\"help,h\", \"A list of command line options\")\n                                 (\"version,v\", \"Display version information\")\n                                 (\"config,c\", boost::program_options::value<std::string>()->default_value(std::string(\"/home/pi/pi_sniffer/pi_sniffer.conf\"), \"\"), \"The path to the configuration file\")\n                                 (\"file,f\", boost::program_options::value<std::string>(),\"The file to parse.\")\n                                 (\"kismet-address,k\", boost::program_options::value<std::string>(), \"The address of the kismet server.\")\n                                 (\"kismet-port,p\", boost::program_options::value<std::size_t>(), \"The port of the kismet server.\");\n\n        boost::program_options::variables_map argv_map;\n        try\n        {\n            boost::program_options::store(\n                boost::program_options::parse_command_line(\n                    p_argCount, p_argArray, description), argv_map);\n        }\n        catch (const std::exception& e)\n        {\n            std::cerr << e.what() << \"\\n\" << std::endl;\n            std::cout << description << std::endl;\n            return false;\n        }\n\n        boost::program_options::notify(argv_map);\n        if (argv_map.empty() || argv_map.count(\"help\"))\n        {\n            std::cout << description << std::endl;\n            return false;\n        }\n\n        if (argv_map.count(\"version\"))\n        {\n            std::cout << \"🦞  Pi Sniffer Alpha  🦞\" << std::endl;\n            return false;\n        }\n\n        if (argv_map.count(\"config\"))\n        {\n            try\n            {\n                p_config.parse_configuration(argv_map[\"config\"].as<std::string>());\n            }\n            catch (const std::runtime_error& e)\n            {\n                std::cerr << \"Failed config parsing: \" << e.what() << std::endl;\n                return false;\n            }\n        }\n\n        if (argv_map.count(\"file\"))\n        {\n            p_file = argv_map[\"file\"].as<std::string>();\n            return true;\n        }\n\n        if (argv_map.count(\"kismet-port\") && argv_map.count(\"kismet-address\"))\n        {\n            p_server_port = argv_map[\"kismet-port\"].as<std::size_t>();\n            p_server_address = argv_map[\"kismet-address\"].as<std::string>();\n            return true;\n        }\n\n        return false;\n    }\n\n    void fileThread(Packet& p_packet, const std::string& p_file)\n    {\n        IEEE80211 link_layer;\n        PCAP file_input(p_file);\n        file_input.initialize();\n        \n        std::cout << \"Reading: \" << p_file << std::endl;\n        while (file_input.get_packet(p_packet))\n        {\n            link_layer.handle_packet(p_packet);\n            p_packet.reset();\n        }\n    }\n\n    void protocolThread(Packet& p_packet, const std::string& p_kismet_address, const std::size_t p_kismet_port)\n    {\n        try\n        {\n            IEEE80211 link_layer;\n            while (!p_packet.m_shutdown)\n            {\n                KismetDrone input(p_kismet_address, p_kismet_port);\n                input.connect();\n\n                while (!p_packet.m_shutdown && input.get_packet(p_packet))\n                {\n                    link_layer.handle_packet(p_packet);\n                    p_packet.reset();\n                }\n\n                if (!p_packet.m_shutdown)\n                {\n                    // for some reason we lost sync with kismet? I've seen kismet simply stop sending data as\n                    // well. Which is strange, but I think not our fault. Sleep 5 seconds and let try to\n                    // reconnect.\n                    sleep(5);\n                }\n            }\n        }\n        catch (const std::runtime_error& e)\n        {\n        }\n    }\n}\n\nint main(int p_argCount, char* p_argArray[])\n{\n    /* for better or worse, holds on global structures used by protocol\n     * processing and the user interface */\n    try\n    {\n        Packet packet;\n        std::stringstream timeStream;\n        boost::uint32_t start = time(NULL);\n        timeStream << start;\n        packet.m_startTime.assign(timeStream.str());\n\n        std::string file;\n        std::string interface;\n        std::string kismet_address;\n        std::size_t kismet_port = 0;\n        if (!parseCommandLine(p_argCount, p_argArray, kismet_address, kismet_port, file, packet.get_config()))\n        {\n            return EXIT_FAILURE;\n        }\n\n        // spawn protocol thread\n        boost::thread protoThread;\n        if (file.empty())\n        {\n            protoThread = boost::thread(protocolThread, boost::ref(packet), kismet_address, kismet_port);\n        }\n        else\n        {\n            protoThread = boost::thread(fileThread, boost::ref(packet), file);\n        }\n\n        // ui server will be on the main thread\n        boost::asio::io_service io_service;\n        boost::asio::ip::udp::socket ui_sock(io_service, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 1270));\n        while (packet.m_shutdown == false)\n        {\n            boost::array<char, 128> recv_buf;\n            boost::system::error_code error;\n            boost::asio::ip::udp::endpoint remote_endpoint;\n            int length = ui_sock.receive_from(boost::asio::buffer(recv_buf), remote_endpoint, 0, error);\n            if (error && error != boost::asio::error::message_size)\n            {\n                packet.m_shutdown = true;\n                continue;\n            }\n\n            if (length == 2 && recv_buf.data()[0] == 's')\n            {\n                packet.m_shutdown = true;\n                continue;\n            }\n            else if (length == 2 && recv_buf.data()[0] == 'o')\n            {\n                std::stringstream response;\n                response << (time(NULL) - start) << \",\"\n                         << (packet.m_stats.get_unencrypted() + packet.m_stats.get_wep() + packet.m_stats.get_wpa()) << \",\"\n                         << packet.m_stats.get_unencrypted() << \",\"\n                         << packet.m_stats.get_wep() << \",\"\n                         << packet.m_stats.get_wpa() << \",\"\n                         << packet.m_stats.get_packets() << \",\"\n                         << packet.m_stats.get_beacons() << \",\"\n                         << packet.m_stats.get_data_packets() << \",\"\n                         << packet.m_stats.get_encrypted() << \",\"\n                         << packet.m_stats.get_eapol() << std::endl;\n                boost::system::error_code ignored_error;\n                ui_sock.send_to(boost::asio::buffer(response.str()), remote_endpoint, 0, ignored_error);\n            }\n            else if (length == 2 && recv_buf.data()[0] == 'l')\n            {\n                std::stringstream response;\n\n                std::vector<AP*> recent;\n                packet.get_recent_ap(30, recent);\n                for (std::vector<AP*>::iterator it = recent.begin();\n                     it != recent.end(); ++it)\n                {\n                    response << (*it)->get_ssid() << \",\" << (*it)->get_mac() << std::endl;\n                }\n\n                response << std::endl;\n                std::string output(response.str());\n                if (output.size() == 1)\n                {\n                    output.push_back('\\n');\n                }\n                boost::system::error_code ignored_error;\n                ui_sock.send_to(boost::asio::buffer(output), remote_endpoint, 0, ignored_error);\n            }\n            else if (length == 2 && recv_buf.data()[0] == 'c')\n            {\n                std::stringstream response;\n\n                std::vector<Client*> recent;\n                packet.get_recent_client(30, recent);\n                for (std::vector<Client*>::iterator it = recent.begin();\n                     it != recent.end(); ++it)\n                {\n                    response << (*it)->get_mac() << std::endl;\n                }\n                response << std::endl;\n                std::string output(response.str());\n                if (output.size() == 1)\n                {\n                    output.push_back('\\n');\n                }\n                boost::system::error_code ignored_error;\n                ui_sock.send_to(boost::asio::buffer(output), remote_endpoint, 0, ignored_error);\n            }\n            else if (length == 19 && recv_buf.data()[0] == 'r')\n            {\n                std::string mac(recv_buf.data() + 1, 17);\n                boost::uint64_t lookup_mac = string_mac_to_int(mac);\n                AP* router = packet.find_ap(lookup_mac);\n                if (router != NULL)\n                {\n                    std::stringstream result;\n                    result << ((int)router->get_channel() & 0xff) << \",\"\n                          << router->get_encryption() << \",\"\n                          << ((int)router->get_last_signal()) << \",\"\n                          << router->get_client_count() << std::endl << std::endl;\n\n                    boost::system::error_code ignored_error;\n                    ui_sock.send_to(boost::asio::buffer(result.str()), remote_endpoint, 0, ignored_error);\n                }\n            }\n            else if (length == 19 && recv_buf.data()[0] == 'c')\n            {\n                std::string mac(recv_buf.data() + 1, 17);\n                boost::uint64_t lookup_mac = string_mac_to_int(mac);\n                Client* client = packet.find_client(lookup_mac, false);\n                if (client != NULL)\n                {\n                    std::stringstream result;\n                    result << ((int)client->get_last_signal()) << \",\"\n                           << client->get_associated_str() << std::endl << std::endl;\n\n                    boost::system::error_code ignored_error;\n                    ui_sock.send_to(boost::asio::buffer(result.str()), remote_endpoint, 0, ignored_error);\n                }\n            }\n            else if (length == '2' && recv_buf.data()[0] == 'f')\n            {\n                // flash output\n                if (packet.get_const_config().get_wigle())\n                {\n                    packet.write_wigle_output(packet.m_startTime);\n                }\n                if (packet.get_const_config().get_kml())\n                {\n                    packet.write_kml_output(packet.m_startTime);\n                }\n                if (packet.get_const_config().get_client_csv())\n                {\n                    packet.write_client_csv_output(packet.m_startTime);\n                }\n                if (packet.get_const_config().get_probe_csv())\n                {\n                    packet.write_probe_csv_output(packet.m_startTime);\n                }\n                if (packet.get_const_config().get_ap_clients_csv())\n                {\n                    packet.write_ap_clients_csv_output(packet.m_startTime);\n                }\n            }\n        }\n\n        // join the protocol thread back in\n        packet.m_shutdown = true;\n        protoThread.interrupt();\n        protoThread.join();\n\n        if (packet.get_const_config().get_wigle())\n        {\n            packet.write_wigle_output(packet.m_startTime);\n        }\n        if (packet.get_const_config().get_kml())\n        {\n            packet.write_kml_output(packet.m_startTime);\n        }\n        if (packet.get_const_config().get_client_csv())\n        {\n            packet.write_client_csv_output(packet.m_startTime);\n        }\n        if (packet.get_const_config().get_probe_csv())\n        {\n            packet.write_probe_csv_output(packet.m_startTime);\n        }\n        if (packet.get_const_config().get_ap_clients_csv())\n        {\n            packet.write_ap_clients_csv_output(packet.m_startTime);\n        }\n    }\n    catch (const std::runtime_error& e)\n    {\n        std::cerr << e.what() << std::endl;\n        return EXIT_FAILURE;\n    }\n    catch (const std::exception& e)\n    {\n        return EXIT_FAILURE;\n    }\n\n    return EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "pi_sniffer/src/packet.cpp",
    "content": "#include \"packet.hpp\"\n\n#include \"client.hpp\"\n#include \"ap.hpp\"\n#include \"probed_network.hpp\"\n#include \"util/convert.hpp\"\n#include \"util/kml_maker.hpp\"\n\n#include <ctime>\n#include <iostream>\n#include <fstream>\n\nPacket::Packet() :\n    m_devices(),\n    m_clients(),\n    m_probed_networks(),\n    m_router_mutex(),\n    m_client_mutex(),\n    m_probe_mutex(),\n    m_configuration(),\n    m_stats(),\n    m_data(NULL),\n    m_length(0),\n    m_time(0),\n    m_lat(0),\n    m_long(0),\n    m_alt(0),\n    m_signal(0),\n    m_gps_on(false),\n    m_current_client(NULL),\n    m_current_router(NULL),\n    m_from_client(false),\n    m_startTime(),\n    m_shutdown(false)\n{\n}\n\nPacket::~Packet()\n{\n}\n\nvoid Packet::reset()\n{\n    // note: don't zero out m_time\n\n    m_data = NULL;\n    m_length = 0;\n    m_signal = 0;\n    m_lat = 0;\n    m_long = 0;\n    m_alt = 0;\n    m_gps_on = false;\n}\n\nconst Configuration& Packet::get_const_config() const\n{\n    return m_configuration;\n}\n\nConfiguration& Packet::get_config()\n{\n    return m_configuration;\n}\n\nvoid Packet::get_recent_ap(boost::uint32_t p_seconds, std::vector<AP*>& p_routers)\n{\n    // this approach has the short coming that we need packets to be regurlarly flowing.\n    // although hopefully that's the case.\n    boost::uint32_t cutoff = m_time - p_seconds;\n\n    // loop over everything. yes there are better approaches but they require much more code\n    // and I don't think the router map should ever get big enough for that to pay off\n    \n    // get read lock for router lookup\n    boost::upgrade_lock<boost::shared_mutex> readLock(m_router_mutex);\n    for (boost::ptr_unordered_map<boost::uint64_t, AP>::iterator iter = m_devices.begin();\n         iter != m_devices.end(); ++iter)\n    {\n        if (iter->second->get_last_seen() >= cutoff)\n        {\n            std::vector<AP*>::iterator v_iter = p_routers.begin();\n            for ( ; v_iter != p_routers.end(); ++v_iter)\n            {\n                if ((*v_iter)->get_last_seen() < iter->second->get_last_seen())\n                {\n                    break;\n                }\n            }\n            p_routers.insert(v_iter, iter->second);\n        }\n    }\n}\n\nvoid Packet::get_recent_client(boost::uint32_t p_seconds, std::vector<Client*>& p_clients)\n{\n    // this approach has the short coming that we need packets to be regurlarly flowing.\n    // although hopefully that's the case.\n    boost::uint32_t cutoff = m_time - p_seconds;\n\n    // loop over everything. yes there are better approaches but they require much more code\n    // and I don't think the router map should ever get big enough for that to pay off\n    \n    // get read lock for router lookup\n    boost::upgrade_lock<boost::shared_mutex> readLock(m_client_mutex);\n    for (boost::ptr_unordered_map<boost::uint64_t, Client>::iterator iter = m_clients.begin();\n         iter != m_clients.end(); ++iter)\n    {\n        if (iter->second->get_last_seen() >= cutoff)\n        {\n            std::vector<Client*>::iterator v_iter = p_clients.begin();\n            for ( ; v_iter != p_clients.end(); ++v_iter)\n            {\n                if ((*v_iter)->get_last_seen() < iter->second->get_last_seen())\n                {\n                    break;\n                }\n            }\n            p_clients.insert(v_iter, iter->second);\n        }\n    }\n}\n\nAP* Packet::find_ap(boost::uint64_t p_mac)\n{\n    {\n        // get read lock for router lookup\n        boost::upgrade_lock<boost::shared_mutex> readLock(m_router_mutex);\n\n        // find the router and return it if it exists\n        boost::ptr_unordered_map<boost::uint64_t, AP>::iterator iter = m_devices.find(p_mac);\n        if (iter != m_devices.end())\n        {\n            m_current_router = (*iter).second;\n            m_current_router->set_last_seen(m_time);\n            if (m_gps_on)\n            {\n                m_current_router->set_location_info(m_signal, m_lat, m_long, m_alt, m_gps_on);\n            }\n            else if (m_signal != 0)\n            {\n                m_current_router->set_location_info(m_signal, m_lat, m_long, m_alt, m_gps_on);\n            }\n\n            return m_current_router;\n        }\n\n        // upgrade to a write lock and create the new router object\n        boost::upgrade_to_unique_lock<boost::shared_mutex> writeLock(readLock);\n        m_current_router = &m_devices[p_mac];\n    }\n\n    // this is a new router so update it.\n    unsigned char* mac = reinterpret_cast<unsigned char*>(&p_mac);\n    std::string mac_string(printable_mac(mac, 6));\n    m_current_router->set_mac(mac_string);\n    m_current_router->set_last_seen(m_time);\n    if (m_gps_on)\n    {\n        m_current_router->set_location_info(m_signal, m_lat, m_long, m_alt, m_gps_on);\n    }\n    else if (m_signal != 0)\n    {\n        m_current_router->set_location_info(m_signal, m_lat, m_long, m_alt, m_gps_on);\n    }\n\n    return m_current_router;\n}\n\nClient* Packet::find_client(boost::uint64_t p_src_mac, bool p_associated)\n{\n    {\n        // get read lock for client lookup\n        boost::upgrade_lock<boost::shared_mutex> readLock(m_client_mutex);\n\n        // if the client already exists we can simply return it\n        boost::ptr_unordered_map<boost::uint64_t, Client>::iterator iter = m_clients.find(p_src_mac);\n        if (iter != m_clients.end())\n        {\n            m_current_client = (*iter)->second;\n            m_current_client->set_last_seen(m_time);\n            m_current_client->set_location_info(m_signal, m_lat, m_long, m_alt, m_gps_on);\n\n            if (m_current_router && p_associated && m_current_client->get_associated() == 0)\n            {\n                m_current_client->set_associated(m_current_router->get_bssid());\n                m_current_router->increment_client();\n            }\n\n            return m_current_client;\n        }\n\n        // upgrade to a write lock and create the new client object\n        boost::upgrade_to_unique_lock<boost::shared_mutex> writeLock(readLock);\n        m_current_client = &m_clients[p_src_mac];\n    }\n\n    if (m_current_router && p_associated)\n    {\n        m_current_client->set_associated(m_current_router->get_bssid());\n        m_current_router->increment_client();\n    }\n\n    unsigned char* mac = reinterpret_cast<unsigned char*>(&p_src_mac);\n    std::string mac_address(printable_mac(mac, 6));\n    m_current_client->set_mac(mac_address);\n    m_current_client->set_last_seen(m_time);\n    m_current_client->set_location_info(m_signal, m_lat, m_long, m_alt, m_gps_on);\n\n    return m_current_client;\n}\n\nvoid Packet::write_wigle_output(const std::string& p_time)\n{\n    std::string filename(m_configuration.get_output_path() + \"pi_sniffer_wigle_\" + p_time + \".csv\");\n\n    // create the file\n    std::filebuf wigle_output;\n    wigle_output.open(filename, std::ios::out);\n    if (!wigle_output.is_open())\n    {\n        std::cerr << \"Failed to write \" << filename << std::endl;\n        return;\n    }\n    std::ostream os(&wigle_output);\n\n    // header\n    os << \"WigleWifi-1.4\\n\";\n\n    // data fields\n    os << \"MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type\\n\";\n\n    // time\n    char buffer[32] = {0};\n    std::string time;\n\n    // loop over the router\n    boost::upgrade_lock<boost::shared_mutex> readLock(m_router_mutex);\n    for (boost::ptr_unordered_map<boost::uint64_t, AP>::iterator it = m_devices.begin();\n         it != m_devices.end(); ++it)\n    {\n        os << it->second->get_mac() << \",\";\n        if (it->second->get_ssid() == \"<Unknown>\")\n        {\n            os << \",\";\n        }\n        else\n        {\n            os << it->second->get_ssid() << \",\";\n        }\n\n        if (it->second->get_encryption().find(\"/\") != std::string::npos)\n        {\n            os << \"[WPA-PSK][WPA2-PSK]\";\n        }\n        else if (it->second->get_encryption() == \"None\")\n        {\n            //leave it blank\n        }\n        else\n        {\n            os << \"[\" << it->second->get_encryption() << \"]\";\n        }\n        if (it->second->has_wps())\n        {\n            os << \"[WPS]\";\n        }\n        os << \",\";\n\n        time_t start = it->second->get_first_seen();\n        strftime(buffer, sizeof(buffer), \"%Y-%m-%d %H:%M:%S\", localtime(&start));\n        time.assign(buffer);\n        os << time << \",\" << (int)it->second->get_channel() << \",\";\n        os << static_cast<int>(it->second->get_best_signal()) << \",\";\n        os << it->second->get_best_latitude() << \",\";\n        os << it->second->get_best_longitude() << \",\";\n        os << it->second->get_best_altitude() << \",\";\n        os << \",\"; // don't really have accuracy info\n        os << \"WIFI\" << \"\\n\";\n    }\n\n    // close it\n    wigle_output.close();\n}\n\nvoid Packet::write_kml_output(const std::string& p_time)\n{\n    std::string filename(m_configuration.get_output_path() + \"pi_sniffer_map_\" + p_time);\n\n    KML_Maker kml_maker;\n    boost::upgrade_lock<boost::shared_mutex> readLock(m_router_mutex);\n    kml_maker.load_aps(m_devices);\n    kml_maker.write_all(filename);\n}\n\nvoid Packet::write_client_csv_output(const std::string& p_time)\n{\n    std::string filename(m_configuration.get_output_path() + \"pi_sniffer_clients_\" + p_time + \".csv\");\n\n    // create the file\n    std::filebuf client_output;\n    client_output.open(filename, std::ios::out);\n    if (!client_output.is_open())\n    {\n        std::cerr << \"Failed to write \" << filename << std::endl;\n        return;\n    }\n    std::ostream os(&client_output);\n\n    // data fields\n    os << \"MAC,BSSID,RSSI,Lat,Long,FirstSeen,LastSeen\" << std::endl;\n\n    // loop over the router\n    boost::upgrade_lock<boost::shared_mutex> readLock(m_router_mutex);\n    for (boost::ptr_unordered_map<boost::uint64_t, Client>::iterator it = m_clients.begin();\n         it != m_clients.end(); ++it)\n    {\n        os << it->second->get_mac() << \",\";\n        os << it->second->get_associated_str() << \",\";\n        os << static_cast<int>(it->second->get_best_signal()) << \",\";\n        os << it->second->get_best_latitude() << \",\";\n        os << it->second->get_best_longitude() << \",\";\n        os << it->second->get_first_seen() << \",\";\n        os << it->second->get_last_seen() << std::endl;\n    }\n\n    client_output.close();\n}\n\nvoid Packet::write_probe_csv_output(const std::string& p_time)\n{\n    std::string filename(m_configuration.get_output_path() + \"pi_sniffer_probes_\" + p_time + \".csv\");\n\n    // create the file\n    std::filebuf client_output;\n    client_output.open(filename, std::ios::out);\n    if (!client_output.is_open())\n    {\n        std::cerr << \"Failed to write \" << filename << std::endl;\n        return;\n    }\n    std::ostream os(&client_output);\n    os << \"Probe,Count\" << std::endl;\n\n    boost::upgrade_lock<boost::shared_mutex> readLock(m_probe_mutex);\n\n    for (boost::ptr_map<std::string, Probed_Network>::iterator it = m_probed_networks.begin();\n         it != m_probed_networks.end(); ++it)\n    {\n        os << it->first << \",\" << it->second->get_clients_count() << std::endl;\n    }\n    client_output.close();\n}\n\nvoid Packet::write_ap_clients_csv_output(const std::string& p_time)\n{\n    std::string filename(m_configuration.get_output_path() + \"pi_sniffer_ap_clients_\" + p_time + \".csv\");\n\n    // create the file\n    std::filebuf ap_clients_output;\n    ap_clients_output.open(filename, std::ios::out);\n    if (!ap_clients_output.is_open())\n    {\n        std::cerr << \"Failed to write \" << filename << std::endl;\n        return;\n    }\n    std::ostream os(&ap_clients_output);\n\n    // data fields\n    os << \"Clients,SSID,MAC,\\n\";\n\n    // loop over the router\n    boost::upgrade_lock<boost::shared_mutex> readLock(m_router_mutex);\n    for (boost::ptr_unordered_map<boost::uint64_t, AP>::iterator it = m_devices.begin();\n         it != m_devices.end(); ++it)\n    {\n        if (it->second->get_mac() == \"00:00:00:00:00:00\")\n        {\n            continue;\n        }\n\n        os << it->second->get_client_count() << \",\";\n        if (it->second->get_ssid() == \"<Unknown>\")\n        {\n            os << \",\";\n        }\n        else\n        {\n            os << it->second->get_ssid() << \",\";\n        }\n\n        os << it->second->get_mac() << std::endl;\n    }\n\n    // close it\n    ap_clients_output.close();\n}\n\nvoid Packet::add_probe_network(const std::string& p_network, const std::string& p_client)\n{\n    if (p_network.size() < 3)\n    {\n        return;\n    }\n\n    // only accept ascii, I guess\n    for (unsigned int i = 0; i < p_network.size(); i++)\n    {\n        if (p_network[i] > 0x7e || p_network[i] < 0x20)\n        {\n            return;\n        }\n    }\n\n    {\n        // get read lock for client lookup\n        boost::upgrade_lock<boost::shared_mutex> readLock(m_probe_mutex);\n        if (m_probed_networks.find(p_network) == m_probed_networks.end())\n        {\n            // upgrade to a write lock and insert the new probe network\n            boost::upgrade_to_unique_lock<boost::shared_mutex> writeLock(readLock);\n            Probed_Network* new_probe = &m_probed_networks[p_network];\n            new_probe->set_name(p_network);\n            new_probe->add_client(string_mac_to_int(p_client));\n        }\n        else\n        {\n            // upgrade to a write lock and add the client to the prexisting probe network\n            boost::upgrade_to_unique_lock<boost::shared_mutex> writeLock(readLock);\n            m_probed_networks[p_network].add_client(string_mac_to_int(p_client));\n        }\n    }\n}\n"
  },
  {
    "path": "pi_sniffer/src/packet.hpp",
    "content": "#ifndef PACKET_HPP\n#define PACKET_HPP\n\n#include <string>\n#include <vector>\n#include <boost/thread.hpp>\n#include <boost/cstdint.hpp>\n#include <boost/ptr_container/ptr_map.hpp>\n#include <boost/ptr_container/ptr_unordered_map.hpp>\n\n#include \"stats.hpp\"\n#include \"configuration.hpp\"\n\nclass Client;\nclass AP;\nclass Probed_Network;\n\n/**\n * Packet is an unfortunate design decision. It, more or less, holds or owns all the\n * objects in the system. This is done for convience so that all protocols have access\n * to all data. It's also manipulated by both the UI thread and the packet thread.\n *\n * On the packet side, packets are read into the data member variable and the object\n * is sent down the protocol stack (80211 -> snap -> eapol).\n *\n * On the UI side, the packet's devices/client maps are queried as the user sees fit.\n *\n * As mentioned, this object (in particularly the maps) is accessed via two threads\n * so be cautious.\n */\nclass Packet\n{\npublic:\n    Packet();\n\n    ~Packet();\n\n    // resets the member variables associated with packet data\n    void reset();\n\n    // find the access point with the given mac in m_devices\n    AP* find_ap(boost::uint64_t p_mac);\n\n    // find the client with the given mac in m_clients\n    Client* find_client(boost::uint64_t p_mac, bool p_associated);\n\n    // store a probe request\n    void add_probe_network(const std::string& p_network, const std::string& p_client);\n\n    // get a const reference to the configuration\n    const Configuration& get_const_config() const;\n\n    // get the configuration\n    Configuration& get_config();\n\n    // get all access points seen in the last p_seconds seconds\n    void get_recent_ap(boost::uint32_t p_seconds, std::vector<AP*>& p_routers);\n\n    // get all clients seen in the last p_seconds seconds\n    void get_recent_client(boost::uint32_t p_seconds, std::vector<Client*>& p_clients);\n\n    // file output functions\n    void write_wigle_output(const std::string& p_time);\n    void write_kml_output(const std::string& p_time);\n    void write_client_csv_output(const std::string& p_time);\n    void write_probe_csv_output(const std::string& p_time);\n    void write_ap_clients_csv_output(const std::string& p_time);\n\nprivate:\n\n    Packet(const Packet& p_rhs);\n    Packet& operator=(const Packet& p_rhs);\n\nprivate:\n\n    // All observered routers: mac to router mapping\n    boost::ptr_unordered_map<boost::uint64_t, AP> m_devices;\n\n    // All observed clients: mac to client mapping\n    boost::ptr_unordered_map<boost::uint64_t, Client> m_clients;\n\n    // All the probe requests seend (ssid -> Probed object)\n    boost::ptr_map<std::string, Probed_Network> m_probed_networks;\n\n    // mutex for accessing m_devices\n    boost::shared_mutex m_router_mutex;\n\n    // mutex for accessing m_clients\n    boost::shared_mutex m_client_mutex;\n\n    // mutex for accessing probes\n    boost::shared_mutex m_probe_mutex;\n\n    // holds the parsed configuration data\n    Configuration m_configuration;\n\npublic:\n\n    // Holds basic counter statistics\n    Stats m_stats;\n\n    // the below should *only* be accessed via the packet thread (except m_shudown)\n    \n    // packet information provided by the input method (pcap or kismet)\n    const unsigned char* m_data;\n    std::size_t m_length;\n    boost::uint32_t m_time;  \n    double m_lat;\n    double m_long;\n    double m_alt;\n    boost::int8_t m_signal;\n    bool m_gps_on;\n\n    // cached result of the current devices we are operating on\n    Client* m_current_client;\n    AP* m_current_router;\n    bool m_from_client;\n\n    // created when the program starts up. largely used for output\n    std::string m_startTime;\n    \n    // cross thread indicator that its time to shutdown\n    bool m_shutdown;\n    \n};\n\n#endif\n"
  },
  {
    "path": "pi_sniffer/src/probed_network.cpp",
    "content": "#include \"probed_network.hpp\"\n\nProbed_Network::Probed_Network() :\n    m_accesslock(),\n    m_name(),\n    m_clients()\n{\n}\n\nProbed_Network::~Probed_Network()\n{\n}\n\nvoid Probed_Network::set_name(const std::string& p_name)\n{\n    m_name.assign(p_name);\n}\n\nvoid Probed_Network::add_client(boost::uint64_t p_mac)\n{\n    m_clients.insert(p_mac);\n}\n"
  },
  {
    "path": "pi_sniffer/src/probed_network.hpp",
    "content": "#ifndef PROBED_NETWORK_HPP\n#define PROBED_NETWORK_HPP\n\n#include <set>\n#include <string>\n#include <boost/cstdint.hpp>\n#include <boost/thread/mutex.hpp>\n\n/**\n * A simple object that stores an SSID that was probed and all the client macs\n * that probed it. This *can be* prob\n */\nclass Probed_Network\n{\npublic:\n\n    Probed_Network();\n    \n    ~Probed_Network();\n\n    // stores the name probed for\n    void set_name(const std::string& p_name);\n\n    //  add a client to the set of STA that probed this ssid\n    void add_client(boost::uint64_t p_mac);\n\n    std::size_t get_clients_count() const\n    {\n        return m_clients.size();\n    }\n\nprivate:\n\n    boost::mutex m_accesslock;\n    std::string m_name;\n    std::set<boost::uint64_t> m_clients;\n};\n#endif"
  },
  {
    "path": "pi_sniffer/src/protocols/eapol11.cpp",
    "content": "#include \"eapol11.hpp\"\n\n#include \"packet.hpp\"\n#include \"ap.hpp\"\n\n#include <netinet/in.h>\n#include <boost/static_assert.hpp>\n\nnamespace\n{\n    #pragma pack(push, 1)\n    struct key_struct\n    {\n        boost::uint8_t type;\n        boost::uint16_t key_info;\n        boost::uint16_t key_length;\n        boost::uint8_t replay_counter[8];\n        boost::uint8_t key_nonce[32];\n        boost::uint8_t key_iv[16];\n        boost::uint8_t key_rsc[8];\n        boost::uint8_t key_id[8];\n        boost::uint8_t key_mic[16];\n        boost::uint16_t key_data_length;\n    };\n\n    BOOST_STATIC_ASSERT(sizeof(key_struct) == 95);\n    #pragma pack(pop)\n}\n\nEAPOL::EAPOL()\n{\n}\n\nEAPOL::~EAPOL()\n{\n}\n\nbool EAPOL::handle_packet(Packet& p_packet)\n{\n    // too short to be an eapol (or an interesting one at least)\n    if (p_packet.m_length < 4)\n    {\n        return false;\n    }\n\n    const boost::uint16_t length =\n        ntohs(*reinterpret_cast<const boost::uint16_t*>(p_packet.m_data + 2));\n\n    if (static_cast<unsigned int>(length + 4) > p_packet.m_length)\n    {\n        return false;\n    }\n\n    if (p_packet.m_data[1] == 0)\n    {\n        return false;\n    }\n\n    p_packet.m_data += 4;\n    p_packet.m_length -= 4;\n\n    if (p_packet.m_length < sizeof(key_struct))\n    {\n        return false;\n    }\n\n    const struct key_struct* key_data =\n        reinterpret_cast<const struct key_struct*>(p_packet.m_data);\n\n    if (key_data->type != 1 && key_data->type != 2)\n    {\n        // these aren't key types we are interested in\n        return false;\n    }\n\n    // Used to build HCCAPX here. However, it appears that cap2hccapx is\n    // significantly better than my crap. Maybe think of a way to isolate eapol\n    // and a beacon for each auth attempt? Otherwise its fine to just convert\n    // to hccapx at the end of the run.\n    p_packet.m_stats.increment_eapol();\n\n    return true;\n}\n"
  },
  {
    "path": "pi_sniffer/src/protocols/eapol11.hpp",
    "content": "#ifndef EAPOL_HPP\n#define EAPOL_HPP\n\nclass Packet;\n\nclass EAPOL\n{\npublic:\n\n    EAPOL();\n\n    ~EAPOL();\n\n    bool handle_packet(Packet& p_packet);\n};\n\n#endif //EAPOL_HPP\n"
  },
  {
    "path": "pi_sniffer/src/protocols/ieee80211.cpp",
    "content": "#include \"ieee80211.hpp\"\n\n#include \"packet.hpp\"\n#include \"client.hpp\"\n#include \"ap.hpp\"\n#include \"probed_network.hpp\"\n#include \"util/convert.hpp\"\n\n#include <tins/dot11/dot11_data.h>\n\n#include <boost/cstdint.hpp>\n#include <boost/foreach.hpp>\n\n#include <cctype>\n#include <cstdio>\n#include <cstring>\n#include <iostream>\n#include <netinet/in.h>\n\nIEEE80211::IEEE80211() :\n    m_snap(),\n    m_pcap_out()\n{\n}\n\nIEEE80211::~IEEE80211()\n{\n}\n\nbool IEEE80211::handle_packet(Packet& p_packet)\n{\n    if (p_packet.m_length < 8)\n    {\n        return false;\n    }\n\n    switch (p_packet.m_data[0])\n    {\n        case 0x00:\n            do_association(p_packet);\n            break;\n        case 0x40:\n            do_probe_request(p_packet);\n            break;\n        case 0x50:\n            do_probe_response(p_packet);\n            break;\n        case 0x80:\n            do_beacon(p_packet);\n            break;\n        case 0x08:\n            do_data(p_packet);\n            break;\n        case 0x88:\n            do_qos(p_packet);\n            break;\n        default:\n            break;\n    }\n\n    return true;\n}\n\nAP* IEEE80211::get_ap(Packet& p_packet, std::size_t p_ssid_offset, int p_depth)\n{\n    boost::uint64_t bssid_mac = (*reinterpret_cast<const boost::uint64_t*>(\n        p_packet.m_data + p_ssid_offset));\n\n    bssid_mac = (bssid_mac >> 16);\n    bssid_mac = (bssid_mac << 16);\n    bssid_mac = be64toh(bssid_mac);\n\n    if (bssid_mac == 0 && p_depth == 1)\n    {\n        return get_ap(p_packet, p_ssid_offset - 6, 0);\n    }\n    \n    return p_packet.find_ap(bssid_mac);\n}\n\nClient* IEEE80211::get_client(Packet& p_packet, std::size_t p_src_offset, bool p_associated)\n{\n    boost::uint64_t src_mac = (*reinterpret_cast<const boost::uint64_t*>(\n        p_packet.m_data + p_src_offset));\n\n    src_mac = (src_mac >> 16);\n    src_mac = (src_mac << 16);\n    src_mac = be64toh(src_mac);\n    if (src_mac == 0x0000ffffffffffffULL)\n    {\n        return NULL;\n    }\n    return p_packet.find_client(src_mac, p_associated);\n}\n\nvoid IEEE80211::do_probe_request(Packet& p_packet)\n{\n    if (p_packet.get_const_config().get_pcap())\n    {\n        m_pcap_out.add_packet(p_packet);\n    }\n\n    if (p_packet.m_length <= 26)\n    {\n        return;\n    }\n\n    // don't want to allocate a client for this since we only track associated\n    // clients. However, we want to track probbed networks, so extract the transmitter\n    boost::uint64_t src_mac = (*reinterpret_cast<const boost::uint64_t*>(\n        p_packet.m_data + 8));\n    src_mac = (src_mac >> 16);\n    src_mac = (src_mac << 16);\n    src_mac = be64toh(src_mac);\n    unsigned char* mac = reinterpret_cast<unsigned char*>(&src_mac);\n    std::string mac_address(printable_mac(mac, 6));\n\n    // TODO this should really reuse the tagged parameters loop\n    if (p_packet.m_data[24] == 0)\n    {\n        if (p_packet.m_length < static_cast<std::size_t>(24 + p_packet.m_data[25]))\n        {\n            return;\n        }\n\n        std::string ssid(reinterpret_cast<const char*>(p_packet.m_data + 26), p_packet.m_data[25]);\n        p_packet.add_probe_network(ssid, mac_address);\n    }\n}\n\nvoid IEEE80211::do_probe_response(Packet& p_packet)\n{\n    do_beacon(p_packet);\n}\n\nvoid IEEE80211::do_beacon(Packet& p_packet)\n{\n    if (p_packet.get_const_config().get_pcap())\n    {\n        m_pcap_out.add_packet(p_packet);\n    }\n\n    AP* found = get_ap(p_packet, 14, 1);\n\n    p_packet.m_stats.increment_beacons();\n\n    if (found->get_beacon_parsed())\n    {\n        // let's only do this once\n        return;\n    }\n\n    if (p_packet.m_length < 36)\n    {\n        return;\n    }\n\n    const unsigned char* management_frame = p_packet.m_data + 24;\n    boost::uint16_t capabilities = *reinterpret_cast<const boost::uint16_t*>(management_frame + 10);\n\n    if ((capabilities & 0x0010) != 0)\n    {\n        found->set_encryption(\"WEP\");\n    }\n    else\n    {\n        found->set_encryption(\"None\");\n        p_packet.m_stats.increment_unencrypted();\n    }\n\n    boost::uint16_t tagged_length = p_packet.m_length - 36;\n    if (tagged_length == 0)\n    {\n        return;\n    }\n\n    bool found_ssid = false;\n    bool wpa = false;\n    bool wpa2 = false;\n    bool psk = false;\n    bool eap = false;\n\n    const unsigned char* tagged_data = management_frame + 12;\n    while (tagged_length > 2)\n    {\n        if (tagged_length < (tagged_data[1] + 2))\n        {\n            break;\n        }\n\n        boost::uint8_t tag = tagged_data[0];\n        boost::uint8_t length = tagged_data[1];\n        const unsigned char* value = tagged_data + 2;\n\n        // skip over this data\n        tagged_length -= (tagged_data[1] + 2);\n        tagged_data += (tagged_data[1] + 2);\n\n        switch (tag)\n        {\n            case 0:\n                if (found_ssid)\n                {\n                    break;\n                }\n                found_ssid = true;\n                if (length != 0)\n                {\n                    if (value[0] != 0)\n                    {\n                        std::string ssid(reinterpret_cast<const char*>(value), length);\n                        found->set_ssid(ssid);\n                    }\n                }\n                else\n                {\n                    found->set_ssid(\"<Unknown>\");\n                }\n                break;\n            case 3:\n                found->set_channel(value[0]);\n                break;\n            case 0x30: // RSN\n            {\n                if (length <= 8)\n                {\n                    break;\n                }\n                // jump version and oui lead in\n                value += 6;\n                boost::uint16_t pairwise = *reinterpret_cast<const boost::uint16_t*>(value);\n\n                if (length <= (8 + (pairwise * 4)))\n                {\n                    break;\n                }\n                value += 2;\n                for (boost::uint16_t pair_loop = pairwise ; pair_loop > 0; --pair_loop)\n                {\n\n                    if (value[3] == 0x04)\n                    {\n                        wpa2 = true;\n                    }\n                    value += 4;\n                }\n\n                if (!wpa2)\n                {\n                    wpa = true;\n                }\n\n                boost::uint16_t auth = *reinterpret_cast<const boost::uint16_t*>(value);\n                if (length <= (10 + (pairwise * 4) + (auth * 4)))\n                {\n                    break;\n                }\n                value += 2;\n                for ( ; auth > 0; --auth)\n                {\n                    if (value[3] == 0x02)\n                    {\n                        psk = true;\n                    }\n                    else if(value[3] == 1)\n                    {\n                        eap = true;\n                    }\n                    value += 4;\n                }\n                break;\n            }\n            case 0xdd:\n                if (length > 4 && memcmp(value, \"\\x00\\x50\\xf2\", 3) == 0) // MS\n                {\n                    switch(value[3])\n                    {\n                        case 1: // WPA IE\n                        {\n                            if (length <= 12)\n                            {\n                                break;\n                            }\n                            wpa = true;\n\n                            // jump version and oui lead in\n                            value += 10;\n                            boost::uint16_t pairwise = *reinterpret_cast<const boost::uint16_t*>(value);\n                            if (length <= (12 + (pairwise * 4)))\n                            {\n                                break;\n                            }\n\n                            value += 2;\n                            for (boost::uint16_t pair_loop = pairwise; pair_loop > 0; --pair_loop)\n                            {\n                                value += 4;\n                            }\n\n                            boost::uint16_t auth = *reinterpret_cast<const boost::uint16_t*>(value);\n                            if (length <= (14 + (pairwise * 4) + (auth * 4)))\n                            {\n                                break;\n                            }\n                            for ( ; auth > 0; --auth)\n                            {\n                                if (value[3] == 0x02)\n                                {\n                                    psk = true;\n                                }\n                                else if (value[3] == 1)\n                                {\n                                    eap = true;\n                                }\n                                value += 4;\n                            }\n                            break;\n                        }\n                        case 4: // wps\n                            // skip over oui and type\n                            value += 4;\n                            length -= 4;\n\n                            while (length > 4)\n                            {\n                                boost::uint16_t type = ntohs(*reinterpret_cast<const boost::uint16_t*>(value));\n                                value += 2;\n                                length -= 2;\n                                boost::uint16_t inner_length = ntohs(*reinterpret_cast<const boost::uint16_t*>(value));\n                                value += 2;\n                                length -= 2;\n\n                                if (inner_length > length)\n                                {\n                                    break;\n                                }\n\n                                switch (type)\n                                {\n                                    case 0x1011:\n                                        if (found->get_ssid().empty())\n                                        {\n                                            found->set_ssid(std::string(reinterpret_cast<const char*>(value), inner_length));\n                                        }\n                                        break;\n                                    case 0x1044:\n                                        if (*value == 0x02)\n                                        {\n                                            found->set_wps(true);\n                                            p_packet.m_stats.increment_wps();\n                                        }\n                                        break;\n                                    default:\n                                        break;\n                                }\n\n                                value += inner_length;\n                                length -= inner_length;\n                            }\n                            break;\n                        default:\n                            break;\n                    }\n                }\n                break;\n            default:\n                break;\n        }\n    }\n\n    // libtins wants a shot at the beacon... honestly we should just rewrite to use libtins\n    if (p_packet.m_current_router &&\n        p_packet.get_const_config().has_wpa_key(p_packet.m_current_router->get_ssid()))\n    {\n        try\n        {\n            boost::scoped_ptr<Tins::Dot11> tinsPacket(Tins::Dot11::from_bytes(p_packet.m_data, p_packet.m_length));\n            if (tinsPacket.get() != NULL)\n            {\n                p_packet.get_config().m_wpa_decrypter.decrypt(*tinsPacket);\n            }\n        }\n        catch (const std::exception&)\n        {\n        }\n    }\n\n    found->set_beacon_parsed();\n    std::string encryption;\n    if (wpa)\n    {\n        encryption.append(\"WPA\");\n    }\n\n    if (wpa2)\n    {\n        if (!encryption.empty())\n        {\n            encryption.push_back('/');\n        }\n\n        encryption.append(\"WPA2\");\n    }\n\n    if (wpa || wpa2)\n    {\n        p_packet.m_stats.increment_wpa();\n    }\n    else if (found->get_encryption() == \"WEP\")\n    {\n        p_packet.m_stats.increment_wep();\n    }\n\n    if (psk)\n    {\n        encryption.append(\"-PSK\");\n    }\n    else if (eap)\n    {\n        encryption.append(\"-EAP\");\n    }\n\n    if (!encryption.empty())\n    {\n        found->set_encryption(encryption);\n    }\n}\n\nvoid IEEE80211::do_association(Packet& p_packet)\n{\n    if (p_packet.get_const_config().get_pcap())\n    {\n        m_pcap_out.add_packet(p_packet);\n    }\n\n    AP* found = get_ap(p_packet, 14);\n\n    if (p_packet.m_length < 36)\n    {\n        return;\n    }\n\n    const unsigned char* management_frame = p_packet.m_data + 24;\n    boost::uint16_t tagged_length = p_packet.m_length - 36;\n    if (tagged_length == 0)\n    {\n        return;\n    }\n\n    bool found_ssid = false;\n    const unsigned char* tagged_data = management_frame + 4;\n\n    while (tagged_length > 2)\n    {\n        if (tagged_length < (tagged_data[1] + 2))\n        {\n            break;\n        }\n\n        boost::uint8_t tag = tagged_data[0];\n        boost::uint8_t length = tagged_data[1];\n        const unsigned char* value = tagged_data + 2;\n\n        tagged_length -= (tagged_data[1] + 2);\n        tagged_data += (tagged_data[1] + 2);\n\n        switch (tag)\n        {\n            case 0:\n                if (found_ssid)\n                {\n                    break;\n                }\n                found_ssid = true;\n                if (length != 0)\n                {\n                    if (value[0] != 0)\n                    {\n                        std::string ssid(reinterpret_cast<const char*>(value), length);\n                        found->set_ssid(ssid);\n                    }\n                }\n                else\n                {\n                    found->set_ssid(\"<Unknown>\");\n                }\n                break;\n            case 3:\n                found->set_channel(value[0]);\n                break;\n            default:\n                break;\n        }\n    }\n}\n\nvoid IEEE80211::do_data(Packet& p_packet)\n{\n    AP* found = NULL;\n    Client* client = NULL;\n    if ((p_packet.m_data[1] & 0x03) == 0x03)\n    {\n        found = get_ap(p_packet, 8);\n        client = get_client(p_packet, 22, true);\n        if (client == NULL)\n        {\n            return;\n        }\n        p_packet.m_from_client = true;\n        p_packet.m_length -= 6;\n        p_packet.m_data += 6;\n    }\n    else if ((p_packet.m_data[1] & 0x02) == 2)\n    {\n        found = get_ap(p_packet, 8);\n        if (memcmp(p_packet.m_data + 10, p_packet.m_data + 16, 6) != 0)\n        {\n            client = get_client(p_packet, 14, true);\n            if (client == NULL)\n            {\n                return;\n            }\n            p_packet.m_from_client = true;\n        }\n        else\n        {\n            p_packet.m_from_client = false;\n        }\n    }\n    else if((p_packet.m_data[1] & 0x01) == 1)\n    {\n        found = get_ap(p_packet, 2);\n        client = get_client(p_packet, 8, true);\n        if (client == NULL)\n        {\n            return;\n        }\n        p_packet.m_from_client = true;\n    }\n    else\n    {\n        found = get_ap(p_packet, 14);\n        client = get_client(p_packet, 8, true);\n        if (client == NULL)\n        {\n            return;\n        }\n        p_packet.m_from_client = true;\n    }\n\n    found->increment_data_packet();\n    p_packet.m_stats.increment_data_packets();\n\n    if (p_packet.m_length > 24)\n    {\n        handle_data(p_packet, 24);\n    }\n}\n\nvoid IEEE80211::do_qos(Packet& p_packet)\n{\n    AP* found = NULL;\n    Client* client = NULL;\n    if ((p_packet.m_data[1] & 0x03) == 0x03)\n    {\n        found = get_ap(p_packet, 8);\n        client = get_client(p_packet, 22, true);\n        if (client == NULL)\n        {\n            return;\n        }\n        p_packet.m_from_client = true;\n        p_packet.m_length -= 6;\n        p_packet.m_data += 6;\n    }\n    else if ((p_packet.m_data[1] & 0x02) == 2)\n    {\n        found = get_ap(p_packet, 8);\n        client = get_client(p_packet, 14, true);\n        if (client == NULL)\n        {\n            return;\n        }\n        p_packet.m_from_client = false;\n    }\n    else if ((p_packet.m_data[1] & 0x01) == 1)\n    {\n        found = get_ap(p_packet, 2);\n        client = get_client(p_packet, 8, true);\n        if (client == NULL)\n        {\n            return;\n        }\n        p_packet.m_from_client = true;\n    }\n    else\n    {\n        found = get_ap(p_packet, 14);\n        client = get_client(p_packet, 8, true);\n        if (client == NULL)\n        {\n            return;\n        }\n        p_packet.m_from_client = true;\n    }\n\n    found->increment_data_packet();\n    p_packet.m_stats.increment_data_packets();\n\n    if (p_packet.m_length > 26)\n    {\n        handle_data(p_packet, 26);\n    }\n}\n\nvoid IEEE80211::handle_data(Packet& p_packet, boost::uint32_t p_increment)\n{\n    // check if we have a snap header\n    if (memcmp(p_packet.m_data + p_increment, \"\\xaa\\xaa\\x03\", 3) == 0)\n    {\n        if (p_packet.get_const_config().get_pcap())\n        {\n            m_pcap_out.add_packet(p_packet);\n        }\n\n        // libtins wants a shot at the handshake... honestly we should just rewrite to use libtins\n        if (p_packet.m_current_router &&\n            p_packet.get_const_config().has_wpa_key(p_packet.m_current_router->get_ssid()))\n        {\n            try\n            {\n                boost::scoped_ptr<Tins::Dot11> tinsPacket(Tins::Dot11::from_bytes(p_packet.m_data, p_packet.m_length));\n                if (tinsPacket.get() != NULL)\n                {\n                    p_packet.get_config().m_wpa_decrypter.decrypt(*tinsPacket);\n                }\n            }\n            catch (const std::exception&)\n            {\n            }\n        }\n\n        p_packet.m_length -= p_increment;\n        p_packet.m_data += p_increment;\n        m_snap.handle_packet(p_packet);\n    }\n    else\n    {\n        // is this wep or wpa?\n        const std::string& encryption(p_packet.m_current_router->get_encryption());\n        if (encryption == \"WEP\")\n        {\n            p_packet.m_stats.increment_encrypted();\n            handle_wep(p_packet);\n        }\n        else if (!encryption.empty())\n        {\n            p_packet.m_stats.increment_encrypted();\n            handle_wpa(p_packet);\n        }\n        else if (p_packet.get_config().get_pcap())\n        {\n            // cleartext that isn't SNAP\n            m_pcap_out.add_packet(p_packet);\n        }\n    }\n}\n\nvoid IEEE80211::handle_wep(Packet& p_packet)\n{\n    if (p_packet.m_current_router &&\n        p_packet.get_const_config().has_wep_key(p_packet.m_current_router->get_mac()))\n    {\n        try\n        {\n            boost::scoped_ptr<Tins::Dot11> tinsPacket(Tins::Dot11::from_bytes(p_packet.m_data, p_packet.m_length));\n            if (tinsPacket.get() != NULL)\n            {\n                bool return_value = p_packet.get_config().m_wep_decrypter.decrypt(*tinsPacket);\n                if (return_value)\n                {\n                    std::vector<unsigned char> decrypted = tinsPacket->serialize();\n                    if (!decrypted.empty())\n                    {\n                        p_packet.m_stats.increment_decrypted();\n                        p_packet.m_data = &decrypted[0];\n                        p_packet.m_length = decrypted.size();\n\n                        if (p_packet.get_const_config().get_pcap())\n                        {\n                            // write the decrypted version to the pcap\n                            m_pcap_out.add_packet(p_packet);\n                        }\n\n                        if (memcmp(p_packet.m_data, \"\\xaa\\xaa\\x03\\x00\\x00\", 5) == 0)\n                        {\n                            // we only really handle snap at this point\n                            m_snap.handle_packet(p_packet);\n                        }\n                        return;\n                    }\n                }\n\n                // decryption failed for some reason\n                p_packet.m_stats.increment_failed_decrypt();\n            }\n        }\n        catch (std::exception&)\n        {\n        }\n    }\n\n    if (p_packet.get_const_config().get_pcap())\n    {\n        m_pcap_out.add_packet(p_packet);\n    }\n\n}\n\n void IEEE80211::handle_wpa(Packet& p_packet)\n {\n    if (p_packet.m_current_router &&\n        p_packet.get_const_config().has_wpa_key(p_packet.m_current_router->get_ssid()))\n    {\n        try\n        {\n            boost::scoped_ptr<Tins::Dot11> tinsPacket(Tins::Dot11::from_bytes(p_packet.m_data, p_packet.m_length));\n            if (tinsPacket.get() != NULL)\n            {\n                bool return_value = p_packet.get_config().m_wpa_decrypter.decrypt(*tinsPacket);\n                if (return_value)\n                {\n                    std::vector<unsigned char> decrypted = tinsPacket->serialize();\n                    if (!decrypted.empty())\n                    {\n                        p_packet.m_stats.increment_decrypted();\n                        p_packet.m_data = &decrypted[0];\n                        p_packet.m_length = decrypted.size();\n\n                        if (p_packet.get_const_config().get_pcap())\n                        {\n                            // write the decrypted version to the pcap\n                            m_pcap_out.add_packet(p_packet);\n                        }\n\n                        if (memcmp(p_packet.m_data, \"\\xaa\\xaa\\x03\\x00\\x00\", 5) == 0)\n                        {\n                            // we only really handle snap at this point\n                            m_snap.handle_packet(p_packet);\n                        }\n                        return;\n                    }\n                }\n                // decryption failed for some reason\n                p_packet.m_stats.increment_failed_decrypt();\n            }\n        }\n        catch (std::exception&)\n        {\n        }\n    }\n\n    if (p_packet.get_const_config().get_pcap())\n    {\n        m_pcap_out.add_packet(p_packet);\n    }\n }\n"
  },
  {
    "path": "pi_sniffer/src/protocols/ieee80211.hpp",
    "content": "#include <cstddef>\n#include <boost/scoped_ptr.hpp>\n#include <boost/cstdint.hpp>\n\n#include \"llcsnap.hpp\"\n#include \"util/pcap_output.hpp\"\n\nclass AP;\nclass Client;\n\nclass IEEE80211\n{\npublic:\n\n    IEEE80211();\n\n    ~IEEE80211();\n\n    bool handle_packet(Packet& p_packet);\n\nprivate:\n\n    AP* get_ap(Packet& p_packet, std::size_t p_ssid_offset, int p_depth = 0);\n\n    Client* get_client(Packet& p_packet, std::size_t p_src_offset, bool p_associated);\n\n    void do_probe_request(Packet& p_packet);\n    void do_probe_response(Packet& p_packet);\n    void do_beacon(Packet& p_packet);\n    void do_data(Packet& p_packet);\n    void do_qos(Packet& p_packet);\n    void do_association(Packet& p_packet);\n\n    void handle_data(Packet& p_packet, boost::uint32_t p_increment);\n    void handle_wep(Packet& p_packet);\n    void handle_wpa(Packet& p_packet);\n\nprivate:\n\n    LLCSNAP m_snap;\n    PcapOutput m_pcap_out;\n};\n"
  },
  {
    "path": "pi_sniffer/src/protocols/llcsnap.cpp",
    "content": "#include \"llcsnap.hpp\"\n\n#include <sstream>\n#include <netinet/in.h>\n\n#include \"packet.hpp\"\n\nLLCSNAP::LLCSNAP() :\n    m_eapol()\n{\n}\n\nLLCSNAP::~LLCSNAP()\n{\n}\n\nbool LLCSNAP::handle_packet(Packet& p_packet)\n{\n    if (p_packet.m_length < 8)\n    {\n        return false;\n    }\n\n    boost::uint16_t next_proto = *reinterpret_cast<const boost::uint16_t*>(p_packet.m_data + 6);\n    next_proto = ntohs(next_proto);\n\n    p_packet.m_data += 8;\n    p_packet.m_length -= 8;\n\n    if (next_proto == 0x888e)\n    {\n        m_eapol.handle_packet(p_packet);\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "pi_sniffer/src/protocols/llcsnap.hpp",
    "content": "#ifndef SNAP_HPP\n#define SNAP_HPP\n\nclass Packet;\n\n#include \"eapol11.hpp\"\n\nclass LLCSNAP\n{\npublic:\n    LLCSNAP();\n\n    ~LLCSNAP();\n\n    bool handle_packet(Packet& p_packet);\n\nprivate:\n\n    EAPOL m_eapol;\n};\n\n#endif\n"
  },
  {
    "path": "pi_sniffer/src/stats.cpp",
    "content": "#include \"stats.hpp\"\n\nStats::Stats() :\n    m_accesslock(),\n    m_unecrypted(0),\n    m_wep(0),\n    m_wpa(0),\n    m_wps(0),\n    m_data(0),\n    m_encrypted(0),\n    m_decrypted(0),\n    m_failed_decrypt(0),\n    m_packets(0),\n    m_beacons(0),\n    m_eapol(0)\n{\n}\n\nStats::~Stats()\n{\n}\n\nvoid Stats::increment_unencrypted()\n{\n    ++m_unecrypted;\n}\n\nboost::uint32_t Stats::get_unencrypted() const\n{\n    return m_unecrypted;\n}\n\nvoid Stats::increment_wep()\n{\n    ++m_wep;\n}\n\nboost::uint32_t Stats::get_wep() const\n{\n    return m_wep;\n}\n\nvoid Stats::increment_wpa()\n{\n    ++m_wpa;\n}\n\nboost::uint32_t Stats::get_wpa() const\n{\n    return m_wpa;\n}\n\nvoid Stats::increment_wps()\n{\n    ++m_wps;\n}\n\nvoid Stats::increment_data_packets()\n{\n    ++m_data;\n}\n\nboost::uint32_t Stats::get_data_packets() const\n{\n    return m_data;\n}\n\nvoid Stats::increment_packets()\n{\n    ++m_packets;\n}\n\nboost::uint32_t Stats::get_packets() const\n{\n    return m_packets;\n}\n\nvoid Stats::increment_beacons()\n{\n    ++m_beacons;\n}\n\nboost::uint32_t Stats::get_beacons() const\n{\n    return m_beacons;\n}\n\nvoid Stats::increment_encrypted()\n{\n    ++m_encrypted;\n}\n\nboost::uint32_t Stats::get_encrypted() const\n{\n    return m_encrypted;\n}\n\nboost::uint32_t Stats::get_decrypted() const\n{\n    return m_decrypted;\n}\n\nvoid Stats::increment_decrypted()\n{\n    ++m_decrypted;\n}\n\nboost::uint32_t Stats::get_failed_decrypt() const\n{\n    return m_failed_decrypt;\n}\n\nvoid Stats::increment_failed_decrypt()\n{\n    ++m_failed_decrypt;\n}\n\nboost::uint32_t Stats::get_eapol() const\n{\n    return m_eapol;\n}\n\nvoid Stats::increment_eapol()\n{\n    ++m_eapol;\n}\n"
  },
  {
    "path": "pi_sniffer/src/stats.hpp",
    "content": "#ifndef STATS_HPP\n#define STATS_HPP\n\n#include <boost/thread.hpp>\n#include <boost/cstdint.hpp>\n\n/**\n * Stats is a an object that is largely a wrapper simple counters. The information\n * in stats is tracked to get a high level overview of what the engine has seen.\n * Most useful in a overview type screen or shutdown stats.\n */\nclass Stats\n{\npublic:\n\n    Stats();\n    ~Stats();\n\n    void increment_unencrypted();\n    boost::uint32_t get_unencrypted() const;\n\n    void increment_wep();\n    boost::uint32_t get_wep() const;\n\n    void increment_wpa();\n    boost::uint32_t get_wpa() const;\n\n    void increment_wps();\n\n    void increment_data_packets();\n    boost::uint32_t get_data_packets() const;\n\n    void increment_packets();\n    boost::uint32_t get_packets() const;\n\n    void increment_beacons();\n    boost::uint32_t get_beacons() const;\n\n    void increment_probe_requests();\n\n    boost::uint32_t get_encrypted() const;\n    void increment_encrypted();\n\n    boost::uint32_t get_decrypted() const;\n    void increment_decrypted();\n\n    boost::uint32_t get_failed_decrypt() const;\n    void increment_failed_decrypt();\n\n    boost::uint32_t get_eapol() const;\n    void increment_eapol();\n\nprivate:\n\n    Stats(const Stats& p_rhs);\n    Stats& operator=(const Stats& p_rhs);\n\nprivate:\n\n    boost::mutex m_accesslock;\n\n    boost::uint32_t m_unecrypted;\n    boost::uint32_t m_wep;\n    boost::uint32_t m_wpa;\n    boost::uint32_t m_wps;\n    boost::uint32_t m_data;\n    boost::uint32_t m_encrypted;\n    boost::uint32_t m_decrypted;\n    boost::uint32_t m_failed_decrypt;\n    boost::uint32_t m_packets;\n    boost::uint32_t m_beacons;\n    boost::uint32_t m_eapol;\n};\n\n#endif\n"
  },
  {
    "path": "pi_sniffer/src/util/convert.cpp",
    "content": "#include \"convert.hpp\"\n\n#include <string>\n#include <cassert>\n#include <stdexcept>\n#include <cctype>\n#include <algorithm>\n#include <vector>\n\n#include <boost/foreach.hpp>\n#include <boost/lexical_cast.hpp>\n#include <boost/algorithm/string.hpp>\n#include <boost/asio/ip/address_v4.hpp>\n#include <boost/asio/ip/address_v6.hpp>\n\n#ifdef OS_WINDOWS\n#include <stdlib.h>\n#endif\n\nnamespace\n{\n    const unsigned char k_hex[] =\n    {\n        '0','1','2','3','4','5','6','7',\n        '8','9','a','b','c','d','e','f'\n    };\n\n    const unsigned char k_hex_int[] =\n    {\n        0, 1, 2, 3, 4, 5, 6, 7,\n        8, 9, 10, 11, 12, 13, 14, 15\n    };\n\n    const char b64_table[65] = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\n    static const char reverse_table[128] =\n    {\n        64, 64, 64, 64, 64, 64, 64, 64,\n        64, 64, 64, 64, 64, 64, 64, 64,\n        64, 64, 64, 64, 64, 64, 64, 64,\n        64, 64, 64, 64, 64, 64, 64, 64,\n        64, 64, 64, 64, 64, 64, 64, 64,\n        64, 64, 64, 62, 64, 64, 64, 63,\n        52, 53, 54, 55, 56, 57, 58, 59,\n        60, 61, 64, 64, 64, 64, 64, 64,\n        64,  0,  1,  2,  3,  4,  5,  6,\n        7,  8,  9, 10, 11, 12, 13, 14,\n        15, 16, 17, 18, 19, 20, 21, 22,\n        23, 24, 25, 64, 64, 64, 64, 64,\n        64, 26, 27, 28, 29, 30, 31, 32,\n        33, 34, 35, 36, 37, 38, 39, 40,\n        41, 42, 43, 44, 45, 46, 47, 48,\n        49, 50, 51, 64, 64, 64, 64, 64\n    };\n\n}\n\nstd::string printable_mac(const unsigned char* p_data, std::size_t p_length, bool p_reverse)\n{\n    std::string return_string;\n\n    for(std::size_t byte = 0; byte < p_length; ++byte)\n    {\n        if (p_reverse)\n        {\n            return_string.push_back(k_hex[p_data[byte] & 0x0F]);\n            return_string.push_back(k_hex[(p_data[byte] >> 4) & 0x0F]);\n        }\n        else\n        {\n            return_string.push_back(k_hex[(p_data[byte] >> 4) & 0x0F]);\n            return_string.push_back(k_hex[p_data[byte] & 0x0F]);\n        }\n\n        if(byte + 1 != p_length)\n        {\n            return_string.push_back(':');\n        }\n    }\n\n    if (p_reverse)\n    {\n        std::reverse(return_string.begin(), return_string.end());\n    }\n\n    return return_string;\n}\n\nboost::uint64_t string_mac_to_int(const std::string& p_mac)\n{\n    std::vector<std::string> octets;\n    boost::algorithm::split(octets, p_mac, boost::is_any_of(\":\"));\n\n    if (octets.size() != 6)\n    {\n        throw std::runtime_error(\"Malformed MAC address\");\n    }\n\n    boost::uint64_t return_value = 0;\n    BOOST_FOREACH(std::string& octet, octets)\n    {\n        unsigned int convert = 0;\n        std::stringstream in;\n        in << std::hex << octet;\n        in >> convert;\n        convert = (convert & 0xff);\n        return_value = (return_value << 8);\n        return_value |= (convert & 0xff);\n    }\n\n    return return_value;\n}\n\nTins::HWAddress<6> int_mac_to_array(boost::uint64_t p_mac)\n{\n    boost::array<unsigned char, 6> address;\n    for (int i = 5, j=0; i >= 0; --i, ++j)\n    {\n        address[i] = reinterpret_cast<unsigned char*>(&p_mac)[j];\n    }\n\n    Tins::HWAddress<6> return_value(address.c_array());\n    return return_value;\n}\n\nstd::string string_to_hex(const std::string& p_mac1)\n{\n    std::string p_mac(p_mac1);\n    std::string return_value;\n    unsigned char hex_value = 0;\n\n    if ((p_mac.size() % 2) != 0)\n    {\n        throw std::runtime_error(\"Hex strings must have both nibbles\");\n    }\n\n    // replace the a-f values\n    for (std::size_t i = 0; i < p_mac.size(); ++i)\n    {\n        switch (p_mac[i])\n        {\n            case '0':\n                p_mac[i] = 0x00;\n                break;\n            case '1':\n                p_mac[i] = 0x01;\n                break;\n            case '2':\n                p_mac[i] = 0x02;\n                break;\n            case '3':\n                p_mac[i] = 0x03;\n                break;\n            case '4':\n                p_mac[i] = 0x04;\n                break;\n            case '5':\n                p_mac[i] = 0x05;\n                break;\n            case '6':\n                p_mac[i] = 0x06;\n                break;\n            case '7':\n                p_mac[i] = 0x07;\n                break;\n            case '8':\n                p_mac[i] = 0x08;\n                break;\n            case '9':\n                p_mac[i] = 0x09;\n                break;\n            case 'a':\n                p_mac[i] = 0x0a;\n                break;\n            case 'b':\n                p_mac[i] = 0x0b;\n                break;\n            case 'c':\n                p_mac[i] = 0x0c;\n                break;\n            case 'd':\n                p_mac[i] = 0x0d;\n                break;\n            case 'e':\n                p_mac[i] = 0x0e;\n                break;\n            case 'f':\n                p_mac[i] = 0x0f;\n                break;\n            default:\n                std::stringstream error;\n                error << \"Non hex value in decrypt key: \" << p_mac[i];\n                throw std::runtime_error(error.str());\n        }\n\n        if ((i % 2) == 0)\n        {\n            hex_value = p_mac[i];\n            hex_value = (hex_value << 4) & 0xf0;\n        }\n        else\n        {\n            hex_value |= (p_mac[i] & 0x0f);\n            return_value.push_back(hex_value);\n            hex_value = 0;\n        }\n    }\n\n \n    return return_value;\n}\n\n"
  },
  {
    "path": "pi_sniffer/src/util/convert.hpp",
    "content": "#include <string>\n#include <cstddef>\n#include <boost/array.hpp>\n#include <boost/cstdint.hpp>\n#include <tins/hw_address.h>\n\nstd::string printable_mac(const unsigned char* p_data, std::size_t p_length, bool p_reverse = true);\nboost::uint64_t string_mac_to_int(const std::string& p_mac);\nTins::HWAddress<6> int_mac_to_array(boost::uint64_t p_mac);\nstd::string string_to_hex(const std::string& p_hex);\n"
  },
  {
    "path": "pi_sniffer/src/util/kml_maker.cpp",
    "content": "#include \"kml_maker.hpp\"\n\n#include <algorithm>\n#include <iostream>\n#include <fstream>\n#include <sstream>\n#include <iomanip>\n\n#include <boost/foreach.hpp>\n\n#include \"ap.hpp\"\n\nnamespace\n{\n    std::string s_header(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n<kml xmlns=\\\"http://www.opengis.net/kml/2.2\\\">\\n\\t<Document>\\n\");\n    std::string s_footer(\"\\t\\t</Folder>\\n\\t</Document>\\n</kml>\");\n}\n\nKML_Maker::KML_Maker() :\n    m_open(),\n    m_wep(),\n    m_wpa()\n{\n}\n\nKML_Maker::~KML_Maker()\n{\n}\n\nvoid KML_Maker::load_aps(boost::ptr_unordered_map<boost::uint64_t, AP>& p_ap)\n{\n    for (boost::ptr_unordered_map<boost::uint64_t, AP>::iterator current_ap = p_ap.begin();\n            current_ap != p_ap.end(); ++current_ap)\n    {\n        if (current_ap->second->get_best_longitude() > 1.0 || current_ap->second->get_best_longitude() < -1.0)\n        {\n            if (current_ap->second->get_encryption() == \"WEP\")\n            {\n                m_wep[current_ap->first] = current_ap->second;\n            }\n            else if (current_ap->second->get_encryption().find(\"WPA\") != std::string::npos)\n            {\n                m_wpa[current_ap->first] = current_ap->second;\n            }\n            else\n            {\n                m_open[current_ap->first] = current_ap->second;\n            }\n        }\n    }\n}\n\nstd::string KML_Maker::write_color(const std::string& p_color) const\n{\n    std::string return_value;\n    return_value.append(\"\\t\\t<Style id=\\\"\");\n    return_value.append(p_color);\n    return_value.append(\"\\\">\\n\");\n    return_value.append(\"\\t\\t\\t<IconStyle>\\n\");\n    return_value.append(\"\\t\\t\\t\\t<Icon><href>http://maps.google.com/mapfiles/ms/icons/\");\n    return_value.append(p_color);\n    return_value.append(\"-dot.png</href></Icon>\\n\");\n    return_value.append(\"\\t\\t\\t</IconStyle>\\n\\t\\t</Style>\\n\");\n    return_value.append(\"\\t\\t<Folder>\\n\");\n    return return_value;\n}\n\nstd::string KML_Maker::write_ap(const std::map<boost::uint64_t, AP*>& p_aps) const\n{\n    std::string return_value;\n    for (std::map<boost::uint64_t, AP*>::const_iterator current_ap = p_aps.begin();\n            current_ap != p_aps.end(); ++current_ap)\n    {\n        return_value.append(\"\\t\\t\\t<Placemark>\\n\\t\\t\\t\\t<name><![CDATA[\");\n        return_value.append(current_ap->second->get_ssid());\n        return_value.append(\"]]></name>\\n\\t\\t\\t\\t<description>\\n\\t\\t\\t\\t\\t<![CDATA[BSSID: <b>\");\n        return_value.append(current_ap->second->get_mac());\n        return_value.append(\"</b><br/>RSSI: <b>\");\n        std::stringstream rssi;\n        rssi << static_cast<int>(current_ap->second->get_best_signal());\n        return_value.append(rssi.str());\n        return_value.append(\"</b><br/>Channel: <b>\");\n        std::stringstream channel;\n        channel << (int)current_ap->second->get_channel();\n        return_value.append(channel.str());\n        return_value.append(\"</b><br/>Encryption: <b>\");\n        return_value.append(current_ap->second->get_encryption());\n        return_value.append(\"</b><br/>First Seen: <b>\");\n        const long int timestamp = current_ap->second->get_first_seen();\n        std::tm* t = std::localtime(&timestamp);\n        std::stringstream firstTime;\n        firstTime << std::put_time(t, \"%Y-%m-%d %I:%M:%S %p\");\n        return_value.append(firstTime.str());\n        return_value.append(\"</b>]]>\\n\\t\\t\\t\\t</description>\\n\");\n        return_value.append(\"\\t\\t\\t\\t<styleUrl>#\");\n        if (current_ap->second->get_encryption() == \"WEP\")\n        {\n            return_value.append(\"pink\");\n        }\n        else if (current_ap->second->get_encryption().find(\"WPA\") != std::string::npos)\n        {\n            return_value.append(\"green\");\n        }\n        else\n        {\n            return_value.append(\"blue\");\n        }\n        return_value.append(\"</styleUrl>\\n\\t\\t\\t\\t<Point>\\n\\t\\t\\t\\t\\t<coordinates>\");\n        std::stringstream longitude;\n        longitude << current_ap->second->get_best_longitude();\n        return_value.append(longitude.str());\n        return_value.append(\",\");\n        std::stringstream latitude;\n        latitude << current_ap->second->get_best_latitude();\n        return_value.append(latitude.str());\n        return_value.append(\"</coordinates>\\n\");\n        return_value.append(\"\\t\\t\\t\\t</Point>\\n\\t\\t\\t</Placemark>\\n\");\n    }\n    return return_value;\n}\n\nvoid KML_Maker::write_all(const std::string& p_filename) const\n{\n    write_open(p_filename);\n    write_wep(p_filename);\n    write_wpa(p_filename);\n}\n\nvoid KML_Maker::write_open(const std::string& p_filename) const\n{\n    if (m_open.empty())\n    {\n        return;\n    }\n\n    std::string filename(p_filename + \"_open.kml\");\n\n    std::ofstream open_ap;\n    open_ap.open(filename);\n    if (!open_ap.is_open())\n    {\n        return;\n    }\n    open_ap << s_header;\n    open_ap << write_color(\"blue\");\n    open_ap << \"\\t\\t<name>\" << filename << \"</name>\\n\";\n    open_ap << write_ap(m_open);\n    open_ap << s_footer;\n    open_ap.close();\n}\n\nvoid KML_Maker::write_wep(const std::string& p_filename) const\n{\n    if (m_wep.empty())\n    {\n        return;\n    }\n\n    std::string filename(p_filename + \"_wep.kml\");\n\n    std::ofstream wep_ap;\n    wep_ap.open(filename);\n    if (!wep_ap.is_open())\n    {\n        return;\n    }\n    wep_ap << s_header;\n    wep_ap << write_color(\"pink\");\n    wep_ap << \"\\t\\t<name>\" << filename << \"</name>\\n\";\n    wep_ap << write_ap(m_wep);\n    wep_ap << s_footer;\n    wep_ap.close();\n}\n\nvoid KML_Maker::write_wpa(const std::string& p_filename) const\n{\n    if (m_wpa.empty())\n    {\n        return;\n    }\n\n    std::string filename(p_filename + \"_wpa.kml\");\n\n    std::ofstream wpa_ap;\n    wpa_ap.open(filename);\n    if (!wpa_ap.is_open())\n    {\n        return;\n    }\n    wpa_ap << s_header;\n    wpa_ap << write_color(\"green\");\n    wpa_ap << \"\\t\\t<name>\" << filename << \"</name>\\n\";\n    wpa_ap << write_ap(m_wpa);\n    wpa_ap << s_footer;\n    wpa_ap.close();\n}\n\n\n"
  },
  {
    "path": "pi_sniffer/src/util/kml_maker.hpp",
    "content": "#ifndef KML_MAKER_HPP\n#define KML_MAKER_HPP\n\n#include <map>\n#include <string>\n#include <vector>\n#include <boost/ptr_container/ptr_unordered_map.hpp>\n\nclass AP;\n\nclass KML_Maker\n{\npublic:\n\n    KML_Maker();\n    ~KML_Maker();\n\n    void load_aps(boost::ptr_unordered_map<boost::uint64_t, AP>& p_ap);\n    void write_all(const std::string& p_filename) const;\n\nprivate:\n\n    void write_open(const std::string& p_filename) const;\n    void write_wep(const std::string& p_filename) const;\n    void write_wpa(const std::string& p_filename) const;\n\n    std::string write_color(const std::string& p_color) const;\n    std::string write_ap(const std::map<boost::uint64_t, AP*>& p_aps) const;\n\nprivate:\n    std::map<boost::uint64_t, AP*> m_open;\n    std::map<boost::uint64_t, AP*> m_wep;\n    std::map<boost::uint64_t, AP*> m_wpa;\n};\n\n#endif"
  },
  {
    "path": "pi_sniffer/src/util/pcap_output.cpp",
    "content": "#include \"pcap_output.hpp\"\n\n#include <boost/cstdint.hpp>\n\n#include \"../packet.hpp\"\n\nnamespace\n{\n    #pragma pack(push, 1)\n    struct pcap_header\n    {\n        boost::uint32_t magic_number;\n        boost::uint16_t version_major;\n        boost::uint16_t version_minor;\n        boost::int32_t  thiszone;\n        boost::uint32_t sigfigs;\n        boost::uint32_t snaplen;\n        boost::uint32_t network;\n    };\n\n    struct packet_header\n    {\n        uint32_t ts_sec;\n        uint32_t ts_usec;\n        uint32_t incl_len;\n        uint32_t orig_len;\n    };\n\n    #pragma pack(pop)\n}\n\nPcapOutput::PcapOutput() :\n    m_pcapOut()\n{\n}\n\nPcapOutput::~PcapOutput()\n{\n    if (m_pcapOut.is_open())\n    {\n        m_pcapOut.close();\n    }\n}\n\nbool PcapOutput::create_header(const std::string& p_path)\n{\n    pcap_header newHeader = {};\n    newHeader.magic_number = 0xa1b2c3d4;\n    newHeader.version_major = 2;\n    newHeader.version_minor = 4;\n    newHeader.thiszone = 0;\n    newHeader.sigfigs = 0;\n    newHeader.snaplen = 0xffff;\n    newHeader.network = 105; // ieee 802.11\n\n    // create the file\n    m_pcapOut.open(p_path, std::ofstream::binary);\n    m_pcapOut.write(reinterpret_cast<const char*>(&newHeader), sizeof(pcap_header));\n\n    return true;\n}\n\nvoid PcapOutput::add_packet(Packet& p_packet)\n{\n    if (!m_pcapOut.is_open())\n    {\n        create_header(p_packet.get_const_config().get_output_path() + \"pi_sniffer_\" + p_packet.m_startTime + \".pcap\");\n    }\n    packet_header packetHeader = {};\n    packetHeader.incl_len = p_packet.m_length;\n    packetHeader.orig_len = p_packet.m_length;\n    packetHeader.ts_sec = p_packet.m_time;\n    packetHeader.ts_usec = 0;\n    m_pcapOut.write(reinterpret_cast<const char*>(&packetHeader), sizeof(packet_header));\n    m_pcapOut.write(reinterpret_cast<const char*>(&p_packet.m_data[0]), p_packet.m_length);\n    m_pcapOut.flush();\n}\n"
  },
  {
    "path": "pi_sniffer/src/util/pcap_output.hpp",
    "content": "#ifndef PCAP_OUTPUT_HPP\n#define PCAP_OUTPUT_HPP\n\n#include <string>\n#include <fstream>\n\nclass Packet;\n\n/*! Create PPI pcap file */\nclass PcapOutput\n{\npublic:\n    PcapOutput();\n    ~PcapOutput();\n\n    bool create_header(const std::string& p_path);\n    void add_packet(Packet& p_packet);\n\nprivate:\n\n    std::ofstream m_pcapOut;\n};\n\n#endif"
  },
  {
    "path": "ui/display_handler.py",
    "content": "import socket\nimport time\nimport board\nimport busio\nimport re\nimport subprocess\nfrom enum import Enum\nimport adafruit_ssd1306\nfrom digitalio import DigitalInOut, Direction, Pull\nfrom PIL import Image, ImageDraw, ImageFont\n\n###\n# This monolithic madness is the entire UI of pi sniffer. It communicates with\n# the pi_sniffer engine over UDP and with Kismet over TCP. It issues various\n# shell commands in order to interact with the system. It checks to see if the\n# UI needs repainting every 0.1ish seconds. An enterprising individual might\n# break this thing up.\n###\n\n###\n# Hooray for globals!\n###\n\n# Create the I2C interface.\ni2c = busio.I2C(board.SCL, board.SDA)\n\n# Create a \"display\" that represents the OLED screen\ndisp = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c)\n \n# Input pins\nbutton_A = DigitalInOut(board.D5)\nbutton_A.direction = Direction.INPUT\nbutton_A.pull = Pull.UP\n \nbutton_B = DigitalInOut(board.D6)\nbutton_B.direction = Direction.INPUT\nbutton_B.pull = Pull.UP\n \nbutton_L = DigitalInOut(board.D27)\nbutton_L.direction = Direction.INPUT\nbutton_L.pull = Pull.UP\n \nbutton_R = DigitalInOut(board.D23)\nbutton_R.direction = Direction.INPUT\nbutton_R.pull = Pull.UP\n \nbutton_U = DigitalInOut(board.D17)\nbutton_U.direction = Direction.INPUT\nbutton_U.pull = Pull.UP\n \nbutton_D = DigitalInOut(board.D22)\nbutton_D.direction = Direction.INPUT\nbutton_D.pull = Pull.UP\n \nbutton_C = DigitalInOut(board.D4)\nbutton_C.direction = Direction.INPUT\nbutton_C.pull = Pull.UP\n\n# views\nstatus_view = 0\noverview = 1\nap_view = 2\nclient_view = 3\nantenna = 4\nsystem_view = 5\ngps_view = 6\nlock_screen = 7\nrotate = 8 # place holder\n\n# ap view\nselected_ap = 0\nselected_ant = 0\nselected_client = 0\n\n# current view\ncurrent_view = status_view\n\n# lock controls\nlocked = False\n\n# do we need to update the view?\nredraw = True\n\n# last ap list\nap_list = []\nclients_list = []\n\n# last update time\nlast_update = 0\nlast_stats = None\n\n# observed some curious behavior from kismet. After many hours it sometimes\n# just stops sending data in the kismet packets. I'm 90% certain it isn't me.\n# who knows. Poor man's solution: watchdog that restarts kismet\nwatch_dog = time.time()\n\n# flush output every five minutes just in case of catestrophic error\nflush_time = time.time()\n\n# the font all text writing will use\nfont = ImageFont.truetype(\"/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf\", 9)\n\n# create a UDP socket to talk to pi_sniffer engine\nbackend_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n\n###\n# Globals over. I am appropriately embarrassed.\n### \n\n##\n# Issue a command to the pi sniffer UDP interface. Not all commands require\n# responses (e.g. 'S\\n' for shutdown does not require a response)\n##\ndef pi_sniff_command(command, get_response):\n    data = None\n\n    pi_sniffer = subprocess.run([\"ps\", \"-C\", \"pi_sniffer\"], capture_output=True)\n    if (pi_sniffer.stdout.find(b\"pi_sniffer\") != -1):\n        backend_sock.sendto(command + b\"\\n\", (\"127.0.0.1\", 1270))\n        if (get_response == True):\n            data = backend_sock.recvfrom(65535)[0]\n\n    return data\n\n##\n# Issue a generic kismet command (e.g. shutdown) and return\n##\ndef do_kismet_command(command):\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.connect((\"127.0.0.1\", 2501))\n    s.sendall(b\"!0 \" + command + b\"\\n\")\n    s.close()\n\n##\n# Grab the current channel list, hop status, and current channel for the\n# provided antenna (uuid defined in kismet.conf. wlan0 == 01 and wlan1 == 02)\n##\ndef kismet_ant_info(uuid):\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.connect((\"127.0.0.1\", 2501))\n    s.sendall(b\"!0 ENABLE SOURCE uuid,channellist,hop,channel\\n\")\n    data = b\"\"\n\n    try:\n        data = s.recv(1024)\n        s.close()\n    except:\n        pass\n\n    channel_info = re.search(b\"SOURCE: \" + uuid + b\" ([0-9,]+) ([0-9]+) ([0-9]+)\", data)\n    if channel_info == None:\n        return (b'',b'',b'')\n    else:\n        # channel list, hop status, current channel\n        return (channel_info.group(1), channel_info.group(2), channel_info.group(3))    \n\n##\n# Set the channel of the provided antenna (uuid defined in kismet.conf). If\n# the provided channel == \"0\" than switch to channel hopping.\n##\ndef kismet_set_channel(uuid, channel):\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.connect((\"127.0.0.1\", 2501))\n    if (channel == b\"0\"):\n        s.sendall(b\"!0 HOPSOURCE \" + uuid + b\" HOP 3\\n\")\n    else:\n        s.sendall(b\"!0 HOPSOURCE \" + uuid + b\" LOCK \" + channel + b\"\\n\")\n    s.close()\n\n\n##\n# Every 60 seconds check if kismet is still sending us data. I've observed it\n# sending us empty packets for some reason... If we observe that behavior just\n# restart it.\n#\n# Every 300 seconds tell pi_sniffer to flush output to disk.\n##\ndef do_watchdog():\n\n    global watch_dog\n    global last_stats\n    global flush_time\n\n    if ((current_time - 60) > watch_dog):\n        watch_dog = current_time\n        overview_stats = pi_sniff_command(b\"o\", True)\n        if overview_stats != None:\n            stats = overview_stats.split(b\",\")\n            if last_stats == None:\n                last_stats = stats[5]\n            elif last_stats == stats[5]:\n                # we are either getting no data or kismet is behaving odd.\n                # knock it over and set it back up\n                last_stats = None\n                kismet = subprocess.run([\"ps\", \"-C\", \"kismet_server\"], capture_output=True)\n                if (kismet.stdout.find(b\"kismet_server\") != -1):\n                    do_kismet_command(b\"SHUTDOWN\")\n                    subprocess.run([\"airmon-ng\", \"stop\", \"wlan0mon\"])\n                    subprocess.run([\"airmon-ng\", \"stop\", \"wlan1mon\"])\n                    time.sleep(3)\n                    subprocess.Popen([\"kismet_server\", \"-f\", \"/home/pi/kismet.conf\", \"-n\", \"--daemonize\"])\n            else:\n                last_stats = stats[5]\n\n    # let's check if we should flush output too\n    if ((current_time - 300) > flush_time):\n        flush_time = current_time\n        # only send the command if it's running\n        pi_sniffer = subprocess.run([\"ps\", \"-C\", \"pi_sniffer\"], capture_output=True)\n        if (pi_sniffer.stdout.find(b\"pi_sniffer\") != -1):\n            pi_sniff_command(b\"f\", False)\n\n###\n# Have the client attempt to rotate to the next screen\n###\ndef check_view():\n    \n    global redraw\n    global current_view\n    global selected_ap\n    global selected_ant\n    global selected_client\n\n    # Right joystick controls screen movement\n    if not button_R.value:\n        redraw = True\n\n        # reset screen specific items\n        # todo move this until ap_view\n        selected_ap = 0\n        selected_ant = 0\n        selected_client = 0\n\n        # move to the next screen\n        current_view = current_view + 1\n        current_view = current_view % rotate\n    # Left joystick controls screen movement too\n    elif not button_L.value:\n        redraw = True\n\n        # reset screen specific items\n        # todo move this until ap_view\n        selected_ap = 0\n        selected_ant = 0\n        selected_client = 0\n\n        if current_view == 0:\n            current_view = lock_screen\n        else:\n            current_view = current_view - 1\n\n##\n# Populate the start/status view\n##\ndef do_status_view():\n\n    global redraw\n\n    if not button_A.value and not button_B.value:\n        # attempt a clean shutdown\n        pi_sniff_command(b\"s\", False)\n        time.sleep(5)\n        subprocess.run([\"shutdown\", \"-h\", \"now\"])\n        return False\n\n    elif not button_B.value:\n        # start kismet and pi sniffer\n        kismet = subprocess.run([\"ps\", \"-C\", \"kismet_server\"], capture_output=True)\n        if (kismet.stdout.find(b\"kismet_server\") == -1):\n            redraw = True\n            subprocess.Popen([\"kismet_server\", \"-f\", \"/home/pi/kismet.conf\", \"-n\", \"--daemonize\"])\n            time.sleep(3) # give it a second to get established\n\n        pi_sniffer = subprocess.run([\"ps\", \"-C\", \"pi_sniffer\"], capture_output=True)\n        if (pi_sniffer.stdout.find(b\"pi_sniffer\") == -1):\n            redraw = True\n            subprocess.Popen([\"/home/pi/pi_sniffer/build/pi_sniffer\", \"-c\", \"/home/pi/pi_sniffer/pi_sniffer.conf\", \"-k\", \"127.0.0.1\", \"-p\", \"3501\"])\n    elif not button_A.value:\n        # shutdown kismet and pi sniffer\n        redraw = True\n        pi_sniff_command(b\"s\", False)\n        kismet = subprocess.run([\"ps\", \"-C\", \"kismet_server\"], capture_output=True)\n        if (kismet.stdout.find(b\"kismet_server\") != -1):\n            do_kismet_command(b\"SHUTDOWN\")\n            subprocess.run([\"airmon-ng\", \"stop\", \"wlan0mon\"])\n            subprocess.run([\"airmon-ng\", \"stop\", \"wlan1mon\"])\n\n    if redraw == True:\n        draw.rectangle((0,0,width,10),outline=1,fill=1)\n        draw.text(((width/2)-12,0), \"Status\", fill=0)\n\n        kismet = subprocess.run([\"ps\", \"-C\", \"kismet_server\"], capture_output=True)\n        if (kismet.stdout.find(b\"kismet_server\") != -1):\n            draw.text((0,10), \"Kismet: Running\", font=font, fill=1)\n        else:\n            draw.text((0,10), \"Kismet: Stopped\", font=font, fill=1)\n\n        pi_sniffer = subprocess.run([\"ps\", \"-C\", \"pi_sniffer\"], capture_output=True)\n        if (pi_sniffer.stdout.find(b\"pi_sniffer\") != -1):\n            draw.text((0,20), \"PiSniff: Running\", font=font, fill=1)\n        else:\n            draw.text((0,20), \"PiSniff: Stopped\", font=font, fill=1)\n\n        wlan0mon = subprocess.run([\"ifconfig\", \"wlan0mon\"], capture_output=True)\n        if (wlan0mon.stdout.find(b\"RUNNING,PROMISC\") != -1):\n            draw.text((0,30), \"wlan0mon: Up\", font=font, fill=1)\n        else:\n            draw.text((0,30), \"wlan0mon: Down\", font=font, fill=1)\n\n        wlan1mon = subprocess.run([\"ifconfig\", \"wlan1mon\"], capture_output=True)\n        if (wlan1mon.stdout.find(b\"RUNNING,PROMISC\") != -1):\n            draw.text((0,40), \"wlan1mon: Up\", font=font, fill=1)\n        else:\n            draw.text((0,40), \"wlan1mon: Down\", font=font, fill=1)         \n\n        gps_found = subprocess.run([\"ls\", \"/dev/ttyACM0\"], capture_output=True)\n        if (len(gps_found.stdout) > 0):\n            draw.text((0,50), \"GPS: Available\", font=font, fill=1)\n        else:\n            draw.text((0,50), \"GPS: Not Found\", font=font, fill=1)\n\n    return True\n\n##\n# Populate the overview screen\n##\ndef do_overview():\n    if redraw == True:\n        draw.rectangle((0,0,width,10),outline=1,fill=1)\n        draw.text(((width/2)-16,0), \"Overview\", fill=0)\n        draw.line((width/2,10,width/2, height), fill=1)\n        overview_stats = pi_sniff_command(b\"o\", True)\n        if overview_stats != None:\n            stats = overview_stats.split(b\",\")\n            draw.text((0,10), \"Time: \" + stats[0].decode(\"utf-8\"), font=font, fill=1)\n            draw.text((0,20), \"APs: \" + stats[1].decode(\"utf-8\"), font=font, fill=1)\n            draw.text((0,30), \"Open: \" + stats[2].decode(\"utf-8\"), font=font, fill=1)\n            draw.text((0,40), \"WEP: \" + stats[3].decode(\"utf-8\"), font=font, fill=1)\n            draw.text((0,50), \"WPA: \" + stats[4].decode(\"utf-8\"), font=font, fill=1)\n            draw.text((width/2+2,10), \"Pkts: \" + stats[5].decode(\"utf-8\"), font=font, fill=1)\n            draw.text((width/2+2,20), \"Bcns: \" + stats[6].decode(\"utf-8\"), font=font, fill=1)\n            draw.text((width/2+2,30), \"Data: \" + stats[7].decode(\"utf-8\"), font=font, fill=1)\n            draw.text((width/2+2,40), \"Enc: \" + stats[8].decode(\"utf-8\"), font=font, fill=1)\n            draw.text((width/2+2,50), \"EAPOL: \" + stats[9].decode(\"utf-8\"), font=font, fill=1)\n\n##\n# Handle antenna view and input\n##\ndef do_ant_view():\n\n    global redraw\n    global selected_ant\n\n    if not button_D.value: # down arrow\n        if (selected_ant < 2):\n            redraw = True\n            selected_ant = selected_ant + 1\n    elif not button_U.value: # up arrow\n        if (selected_ant > 0):\n            selected_ant = selected_ant - 1\n            redraw = True\n    elif not button_B.value and selected_ant != 0:\n        if selected_ant == 1:\n            wlan0mon = subprocess.run([\"ifconfig\", \"wlan0mon\"], capture_output=True)\n            if (wlan0mon.stdout.find(b\"RUNNING,PROMISC\") == -1):\n                # if the antenna doesn't exist do nothing\n                return\n            uid = b\"00000000-0000-0000-0000-000000000001\"\n        elif selected_ant == 2:\n            wlan1mon = subprocess.run([\"ifconfig\", \"wlan1mon\"], capture_output=True)\n            if (wlan1mon.stdout.find(b\"RUNNING,PROMISC\") == -1):\n                # if the antenna doesn't exist do nothing\n                return\n            uid = b\"00000000-0000-0000-0000-000000000002\"\n        else:\n            #ignore\n            return\n\n        (channellist, hopping, channel) = kismet_ant_info(uid)\n        channels = channellist.split(b\",\")\n        if (len(channels) > 0):\n            if hopping != b\"0\":\n                kismet_set_channel(uid, channels[0])\n            else:\n                current = channels.index(channel)\n                current = current + 1\n                if (current >= len(channels)):\n                    kismet_set_channel(uid, b\"0\")\n                else:\n                    kismet_set_channel(uid, channels[current])\n\n            # kismet needs a little before we slam it with more requests\n            time.sleep(0.3)\n            redraw = True\n\n    if redraw == True:\n        draw.rectangle((0,0,width,10),outline=1,fill=1)\n        draw.text(((width/2)-8,0), \"Antenna\", fill=0)\n        draw.line((width/2,10,width/2, height), fill=1)\n\n        if (selected_ant == 1):\n            draw.rectangle((0,10,width/2,20),outline=1,fill=1)\n            draw.text((0,10), \"wlan0mon\", font=font, fill=0)\n\n            wlan0mon = subprocess.run([\"ifconfig\", \"wlan0mon\"], capture_output=True)\n            if (wlan0mon.stdout.find(b\"RUNNING,PROMISC\") == -1):\n                draw.text((width/2+2,10), \"Disabled\", font=font,fill=1)\n            else:\n                (channellist, hopping, channel) = kismet_ant_info(b\"00000000-0000-0000-0000-000000000001\")\n                if hopping == b'':\n                    draw.text((width/2+2,10), \"Channel:\\nTransitioning\", font=font,fill=1)\n                elif hopping != b\"0\":\n                    draw.text((width/2+2,10), \"Channel:\\nHopping\", font=font,fill=1)\n                else:\n                    draw.text((width/2+2,10), \"Channel:\\n\" + channel.decode(\"utf-8\"), font=font, fill=1)\n        else:\n            draw.text((0,10), \"wlan0mon\", font=font, fill=1)\n\n        if (selected_ant == 2):\n            draw.rectangle((0,20,width/2,30),outline=1,fill=1)\n            draw.text((0,20), \"wlan1mon\", font=font, fill=0)\n\n            wlan1mon = subprocess.run([\"ifconfig\", \"wlan1mon\"], capture_output=True)\n            if (wlan1mon.stdout.find(b\"RUNNING,PROMISC\") == -1):\n                draw.text((width/2+2,10), \"Disabled\", font=font,fill=1)\n            else:\n                (channellist, hopping, channel) = kismet_ant_info(b\"00000000-0000-0000-0000-000000000002\")\n                if hopping == b'':\n                    draw.text((width/2+2,10), \"Channel:\\nTransitioning\", font=font,fill=1)\n                elif hopping != b\"0\":\n                    draw.text((width/2+2,10), \"Channel:\\nHopping\", font=font,fill=1)\n                else:\n                    draw.text((width/2+2,10), \"Channel:\\n\" + channel.decode(\"utf-8\"), font=font, fill=1)\n        else:\n            draw.text((0,20), \"wlan1mon\", font=font, fill=1)\n\n##\n# Draw the system view\n##\ndef do_system_view():\n    if redraw == True:   \n        draw.rectangle((0,0,width,10),outline=1,fill=1)\n        draw.text(((width/2)-36,0), \"System Status\", fill=0)\n\n        cpu = subprocess.run([\"top\", \"-b\", \"-n\", \"1\"], capture_output=True)\n        result = re.search(b\"%Cpu\\(s\\):[ ]+[0-9\\.]+[ ]+us,[ ]+[0-9\\.]+[ ]+sy,[ ]+[0-9\\.]+[ ]+ni,[ ]+([0-9\\.]+)[ ]+id\", cpu.stdout)\n        if (result == None):\n            draw.text((0,10), \"CPU: Unknown%\", font=font, fill=1)\n        else:\n            value = 100 - int(float(result.group(1)))\n            draw.text((0,10), \"CPU: \" + str(value) + \"%\", font=font, fill=1)\n\n        mem = subprocess.run([\"vmstat\", \"-s\"], capture_output=True)\n        total_mem = re.search(b\"([0-9]+) K total memory\\n\", mem.stdout)\n        total_free= re.search(b\"([0-9]+) K free memory\\n\", mem.stdout)\n        if (total_mem == None or total_free == None):\n            draw.text((0,20), \"Memory: Unknown%\", font=font, fill=1)\n        else:\n            value = 100 - ((float(total_free.group(1)) / float(total_mem.group(1))) *100)\n            draw.text((0,20), \"Memory: \" + str(int(value)) + \"%\", font=font, fill=1)\n\n        disk = subprocess.run([\"df\"], capture_output=True)\n        disk_usage = re.search(b\"/dev/root[ ]+[A-Z0-9\\.]+[ ]+[A-Z0-9\\.]+[ ]+[A-Z0-9\\.]+[ ]+([0-9]+)% /\", disk.stdout)\n        if (disk_usage == None):\n            draw.text((0,30), \"Disk: Unknown%\", font=font, fill=1)\n        else:\n            draw.text((0,30), \"Disk: \" + disk_usage.group(1).decode(\"utf-8\") + \"%\", font=font, fill=1)\n\n        temp = subprocess.run([\"vcgencmd\", \"measure_temp\"], capture_output=True)\n        temp_C = re.search(b\"temp=(.*)\", temp.stdout)\n        if (temp_C == None):\n            draw.text((0,40), \"Temp: Unknown\", font=font, fill=1)\n        else:\n            draw.text((0,40), \"Temp: \" + temp_C.group(1).decode(\"utf-8\"), font=font, fill=1)\n\n##\n# Populate the GPS view\n##\ndef do_gps_view():\n    if redraw == True:\n        draw.rectangle((0,0,width,10),outline=1,fill=1)\n        draw.text(((width/2)-28,0), \"GPS Status\", fill=0)\n\n        gps_found = subprocess.run([\"ls\", \"/dev/ttyACM0\"], capture_output=True)\n        if (len(gps_found.stdout) == 0):\n            draw.text((0,10), \"Hardware: Not Found\", font=font, fill=1)\n        else:\n            draw.text((0,10), \"Hardware: Available\", font=font, fill=1)\n\n            # does gpsd see the device?\n            gps_found = subprocess.run([\"gpspipe\", \"-w\", \"-n\", \"2\"], capture_output=True)\n            if (gps_found.stdout.find(b'\"devices\":[]') != -1):\n                draw.text((0,20), \"Hardware: Not Recognized\", font=font, fill=1)\n            else:\n                draw.text((0,20), \"Hardware: Recognized\", font=font, fill=1)\n\n                # grab lat long\n                gps_found = subprocess.run([\"gpspipe\", \"-w\", \"-n\", \"4\"], capture_output=True)\n                result = re.search(b'\"lat\":([0-9\\.-]+),\"lon\":([0-9\\.-]+),', gps_found.stdout)\n                if (result == None):\n                    draw.text((0,30), \"No sync\", font=font, fill=1)\n                else:\n                    draw.text((0,30), \"Lat: \" + result.group(1).decode(\"utf-8\"), font=font, fill=1)\n                    draw.text((0,40), \"Lon: \" + result.group(2).decode(\"utf-8\"), font=font, fill=1)\n\n##\n# Populate the client view and handle user input\n##\ndef do_client_view():\n    global redraw\n    global selected_client\n    global clients_list\n\n    if not button_D.value: # down arrow\n        if (selected_client < len(clients_list)):\n            redraw = True\n            selected_client = selected_client + 1\n    elif not button_U.value: # up arrow\n        if (selected_client > 0):\n            selected_client = selected_client - 1\n            redraw = True\n    elif not button_B.value:\n        if (selected_client > 0):\n            # grab the client's bssid\n            data = pi_sniff_command(b\"c\" + clients_list[selected_client - 1], True)\n            if data != None:\n                split_info = data.split(b\",\")\n                subprocess.run([\"aireplay-ng\", \"-0\", \"1\", \"-a\", split_info[1].decode(\"utf-8\"), \"-c\", clients_list[selected_client - 1].decode(\"utf-8\"), \"wlan0mon\"])\n\n    if redraw == True and selected_client == 0:\n        # get the list from the back end\n        clients = pi_sniff_command(b\"c\", True)\n        if (clients != None):\n            clients_list = clients.splitlines()\n            # trim the \\n\\n\n            clients_list = clients_list[:len(clients_list)-1]\n\n    if redraw == True:\n        # divide screen\n        draw.rectangle((0,0,width,10),outline=1,fill=1)\n        draw.text(((width/2)-30,0), \"Client View\", fill=0)\n        draw.line((width/2+22,10,width/2+22, height), fill=1)\n        \n        if selected_client > 3:\n            i = selected_client - 4\n        else:\n            i = 0\n\n        location = 0          \n        while (location < 5 and i < len(clients_list)):\n            if selected_client == (i+1):\n                draw.rectangle((0,(location*10)+10,width/2+22,(location*10)+20),outline=1,fill=1)\n                draw.text((0,(location*10)+10), clients_list[i].decode(\"utf-8\"), font=font, fill=0)\n                data = pi_sniff_command(b\"c\" + clients_list[i], True)\n                if (data != None):\n                    split_info = data.split(b\",\")\n                    draw.text((width/2+22,10), split_info[1].decode(\"utf-8\")[:9], font=font,fill=1)\n                    draw.text((width/2+22,20), split_info[1].decode(\"utf-8\")[9:], font=font,fill=1)\n                    draw.text((width/2+22,30), \"Sig: \" + split_info[0].decode(\"utf-8\"), font=font,fill=1)\n            else:\n                draw.text((0,(location*10)+10), clients_list[i].decode(\"utf-8\"), font=font, fill=255)\n\n            i = i + 1\n            location = location + 1\n\ndef do_ap_view():\n\n    global redraw\n    global selected_ap\n    global ap_list\n\n    if not button_D.value: # down arrow\n        if (selected_ap < len(ap_list)):\n            redraw = True\n            selected_ap = selected_ap + 1\n    elif not button_U.value: # up arrow\n        if (selected_ap > 0):\n            selected_ap = selected_ap - 1\n            redraw = True\n    elif redraw == True and selected_ap == 0:\n        # get the list from the back end\n        access_points = pi_sniff_command(b\"l\", True)\n        if (access_points != None):\n            ap_list = access_points.splitlines()\n            ap_list = ap_list[:len(ap_list)-1]\n\n    if redraw == True:   \n        # divide screen\n        draw.rectangle((0,0,width,10),outline=1,fill=1)\n        draw.text(((width/2)-18,0), \"Live View\", fill=0)\n        draw.line((width/2,10,width/2, height), fill=1)\n\n        # this supports forever scrolling... I hope\n        if selected_ap > 3:\n            i = selected_ap - 4\n        else:\n            i = 0\n\n        location = 0          \n        while (location < 5 and i < len(ap_list)):\n            ap = ap_list[i].split(b\",\")\n            if (len(ap[0]) > 11):\n                shorten = ap[0][:8]\n                shorten = shorten + b\"...\"\n                ap[0] = shorten\n\n            if selected_ap == (i+1):\n                draw.rectangle((0,(location*10)+10,width/2,(location*10)+20),outline=1,fill=1)\n                draw.text((0,(location*10)+10), ap[0].decode(\"utf-8\"), font=font, fill=0)\n                \n                data = pi_sniff_command(b\"r\" + ap[1], True)\n                if (data != None):\n                    split_info = data.split(b\",\")\n                    draw.text((width/2+2,10), ap[1].decode(\"utf-8\")[:9], font=font,fill=1)\n                    draw.text((width/2+2,20), ap[1].decode(\"utf-8\")[9:], font=font,fill=1)\n                    draw.text((width/2+2,30), \"Ch: \" + split_info[0].decode(\"utf-8\"),font=font, fill=1)\n                    draw.text((width/2+2,40), split_info[1].decode(\"utf-8\"), font=font,fill=1)\n                    draw.text((width/2+2,50), \"Sig: \" + split_info[2].decode(\"utf-8\"),font=font, fill=1)\n                    draw.text((width/2+2,60), \"Clnts: \" + split_info[3].decode(\"utf-8\"),font=font, fill=1)\n            else:\n                draw.text((0,(location*10)+10), ap[0].decode(\"utf-8\"), font=font, fill=255)\n\n            i = i + 1\n            location = location + 1\n\n##\n# Handle the lock screen drawing and locking input (unlocked handled elsewhere)\n##\ndef do_lock_screen():\n\n    global redraw\n    global locked\n\n    if not button_B.value: # button 6\n        locked = True\n        redraw = True\n\n    if redraw == True:   \n        draw.rectangle((0,0,width,10),outline=1,fill=1)\n        draw.text(((width/2)-26,0), \"Lock Status\", fill=0)\n\n        if (locked == True):\n            draw.text((0,10), \"Locked\", font=font, fill=1)\n        else:\n            draw.text((0,10), \"Unlocked\", font=font, fill=1)\n\n# ensure the echo is disabled on the gps tty. Really annoying this needs to be done.\nsubprocess.run([\"stty\", \"-F\", \"/dev/ttyACM0\", \"-echo\"])\n\n# kill hdmi. power saving.\nsubprocess.run([\"/usr/bin/tvservice\", \"-o\"])\n\n# loop until the user hits the break\n# Clear display.\ndisp.fill(0)\ndisp.show()\n\nwhile True:\n\n    if locked == True:\n        # the user can lock the display in the lock screen. If they have, don't\n        # do any other UI processing. We will have to still do the watch dog\n        # logic though\n        if not button_A.value and not button_U.value:\n            locked = False\n            redraw = True\n        else:\n            current_time = time.time()\n            if ((current_time - 6) > last_update):\n                redraw = True\n            do_watchdog()\n            time.sleep(0.1)\n            continue\n\n    # check if the user is changing the view\n    check_view()\n\n    # see if we should be refreshing\n    if redraw == False:\n        current_time = time.time()\n        if ((current_time - 6) > last_update):\n            redraw = True\n\n        # while we have current time let's kick the dog\n        do_watchdog()\n\n    # we might draw! Create a blank canvas\n    width = disp.width\n    height = disp.height\n    image = Image.new('1', (width, height))\n    draw = ImageDraw.Draw(image)\n    draw.rectangle((0, 0, width, height), outline=0, fill=0)\n\n    # which view to draw to the screen\n    if (current_view == status_view):\n        if (do_status_view() == False):\n            # user has requested shutdown\n            break\n    elif (current_view == overview):\n        do_overview()\n    elif (current_view == antenna):\n        do_ant_view()   \n    elif (current_view == system_view):\n        do_system_view()\n    elif (current_view == gps_view):\n        do_gps_view()\n    elif (current_view == client_view):\n        do_client_view()\n    elif (current_view == ap_view):\n        do_ap_view()\n    elif (current_view == lock_screen):\n        do_lock_screen()       \n    else:\n        print(\"oh no! Why are we here?\")\n\n    if (redraw == True):\n        last_update = time.time()\n        disp.image(image)\n        disp.show()\n        redraw = False\n\n    time.sleep(0.1)\n\ndisp.fill(0)\ndisp.show()\n\n"
  }
]