Project 1: WiFi Packet Sniffer and Network Analyzer

Build a pocket-sized 802.11 capture appliance on the Cardputer that logs valid PCAP files, renders live channel statistics, and survives real-time capture without dropped frames or corrupt storage.

Quick Reference

Attribute Value
Difficulty Advanced
Time Estimate 2–3 weeks
Main Programming Language C/C++ (ESP-IDF)
Alternative Programming Languages Arduino, Rust (esp-rs)
Coolness Level Very High
Business Potential Medium (field diagnostics, site surveys, incident response tooling)
Prerequisites C pointers/structs, ESP32 toolchain basics, SPI display basics
Key Topics 802.11 frames, promiscuous capture, backpressure, PCAP, FAT/SD writes

1. Learning Objectives

By completing this project, you will:

  1. Configure ESP32-S3 promiscuous mode safely and measure capture loss.
  2. Parse 802.11 frame headers to extract SSIDs, BSSIDs, RSSI, and frame types.
  3. Design a non-blocking capture pipeline with bounded buffers and backpressure.
  4. Write standards-compliant PCAP files on microSD and validate them in Wireshark.
  5. Build a UI that updates smoothly while capture and storage run concurrently.
  6. Implement ethical guardrails and clear warnings for capture tools.

2. All Theory Needed (Per-Concept Breakdown)

2.1 802.11 Promiscuous Capture and Frame Anatomy

Fundamentals

WiFi is not Ethernet. The frames you will see on-air are 802.11 MAC frames that can be management, control, or data types, and the meaning of each byte depends on the Frame Control field. Promiscuous mode on the ESP32-S3 changes the driver behavior so that it delivers all frames seen on the current channel, not just those addressed to the device. That makes it possible to discover networks, count clients, and measure channel activity, but it also means you have to parse frames defensively. Many frames are encrypted or truncated; some are malformed; others include variable-length headers or optional fields. A sniffer must be able to detect type/subtype, handle variable address fields, and ignore what it cannot parse. Your goal is not to decode everything, but to extract safe, reliable metadata: RSSI, channel, BSSID, SSID (when present), and frame counts.

Deep Dive into the concept

The 802.11 MAC header begins with a 2-byte Frame Control (FC) field, and that FC field dictates everything else. The type bits determine whether the frame is management (beacons, probes, association), control (RTS/CTS/ACK), or data. The subtype bits determine the exact purpose, and the ToDS/FromDS flags define how to interpret the address fields. Unlike Ethernet, WiFi frames can have up to four address fields. For example, a typical infrastructure data frame might use Address 1 as the receiver, Address 2 as the transmitter, and Address 3 as the BSSID. In a WDS or mesh scenario, Address 4 appears. The header length is not fixed: QoS data frames include a QoS control field, and some frames can include HT control. This means your parser cannot assume a constant header size. Instead, it must compute the header length by reading the FC field, checking QoS and ToDS/FromDS bits, and then safely advancing through the buffer.

In promiscuous mode, the ESP32 driver gives you a wifi_promiscuous_pkt_t structure with rx_ctrl metadata and a pointer to the raw frame bytes. This callback executes in the WiFi task context, so you must not parse deeply or write to SD here. The correct approach is to copy only a small slice (the first N bytes) or a pointer into a preallocated buffer and return immediately. If you parse inside the callback, you will starve the driver and lose frames. A sniffer is a pipeline: capture fast, parse later. You also need to understand channel hopping: you can only capture one channel at a time, so your dataset is a time series. If you hop every 200 ms across 13 channels, each channel only gets a small observation window. That means your “counts” are relative, not absolute. A robust UI should display packets-per-second and channel utilization rather than raw totals that imply complete coverage.

Finally, be mindful of legal and ethical constraints. Promiscuous capture is powerful and should only be used on networks you own or are authorized to test. A professional tool includes explicit warnings and a confirmation step to enable capture. This is not just about compliance; it also forces you to build a safer UX pattern that avoids accidental capture in the field.

How this fits in projects

This is the core capture theory for this project. You will reuse the same 802.11 parsing discipline in P05-wardriving-wifi-mapper.md and in the capstone P08-complete-cardputer-security-toolkit.md.

Definitions & key terms

  • Frame Control (FC) → 2-byte header that encodes type, subtype, flags, and direction bits
  • Management frame → discovery/association frames (beacons, probes, auth)
  • Control frame → MAC control frames (RTS/CTS/ACK)
  • Data frame → carries actual payload (often encrypted)
  • BSSID → MAC address identifying the access point
  • SSID → network name carried in management information elements
  • Information Element (IE) → TLV field inside management frames
  • Promiscuous mode → radio/driver mode that delivers all frames on the channel

Mental model diagram (ASCII)

[RF Channel] -> [WiFi PHY] -> [MAC] -> [Promisc Callback]
                                   |
                                   v
                          [Frame Slice + Meta]
                                   |
                                   v
                             [Parser Task]
                                   |
                        +----------+-----------+
                        |                      |
                  [Stats Store]          [PCAP Writer]

How it works (step-by-step, with invariants and failure modes)

  1. Configure WiFi driver for promiscuous capture on a specific channel.
  2. The radio receives frames and the driver invokes your callback for each one.
  3. The callback copies a bounded slice and metadata into a preallocated ring buffer.
  4. A parsing task dequeues slices and reads the FC field to determine type/subtype.
  5. For management frames, parse IEs to extract SSID and capabilities safely.
  6. Update counters and UI state in a thread-safe structure.
  7. Write full frames (or slices) into PCAP buffers for SD write batching.

Invariants: callback runtime must be constant and short; ring buffers must not allocate; parsing must validate lengths before reading IEs. Failure modes: buffer overflow leads to dropped frames; malformed frames cause crashes if bounds checks are missing; channel hopping without dwell causes skewed stats.

Minimal concrete example

void wifi_promisc_cb(void *buf, wifi_promiscuous_pkt_type_t type) {
    const wifi_promiscuous_pkt_t *pkt = (wifi_promiscuous_pkt_t *)buf;
    capture_meta_t meta = {
        .rssi = pkt->rx_ctrl.rssi,
        .channel = pkt->rx_ctrl.channel,
        .len = pkt->rx_ctrl.sig_len
    };
    ringbuf_push(&meta, pkt->payload, MIN(pkt->rx_ctrl.sig_len, 96));
}

Common misconceptions

  • “Promiscuous mode captures all channels.” → It captures only the current channel.
  • “I can parse in the callback.” → Heavy parsing will drop frames.
  • “Frame headers are fixed-size.” → QoS and direction flags change length.

Check-your-understanding questions

  1. Why does channel hopping create sampling bias in your stats?
  2. What does the ToDS/FromDS flag pair tell you about address fields?
  3. Why must you bounds-check Information Elements?
  4. Which frame types are most useful for SSID discovery?

Check-your-understanding answers

  1. Because you only observe a fraction of time on each channel, so counts are relative.
  2. They determine whether addresses represent receiver/transmitter/BSSID or WDS.
  3. Malformed or truncated IEs can cause out-of-bounds reads and crashes.
  4. Management frames like beacons and probe responses carry SSIDs.

Real-world applications

  • Wireless site surveys and channel utilization reports.
  • Incident response and rogue AP detection.
  • Field diagnostics for IoT and industrial deployments.

Where you’ll apply it

  • This project: see §4.1 and §5.4 for pipeline design and parsing constraints.
  • Also used in: P05-wardriving-wifi-mapper.md and P08-complete-cardputer-security-toolkit.md.

References

  • IEEE 802.11 MAC header overview (Wireshark Wiki, RFC-style references).
  • ESP-IDF WiFi promiscuous mode guide (ESP-IDF docs).
  • Computer Networks by Tanenbaum – Ch. 6 (Wireless).

Key insight

Promiscuous capture is not about decoding everything; it is about extracting reliable metadata without disturbing the radio pipeline.

Summary

A WiFi sniffer lives or dies by how it parses the 802.11 header. The Frame Control field and address layout are the map. Keep parsing minimal and defensive, and never block the capture path.

Homework/Exercises to practice the concept

  1. Write a parser that prints type/subtype and address fields for captured frames.
  2. Simulate malformed IEs and verify your parser safely skips them.
  3. Measure the difference in frame counts when hopping vs. static channel.

Solutions to the homework/exercises

  1. Use FC bits to branch; only parse enough to identify management vs data.
  2. Before reading each IE, check that offset + 2 + len <= frame_len.
  3. Log packets-per-second on a fixed channel, then repeat with hopping and compare.

2.2 Real-Time Capture Pipeline: Callbacks, Ring Buffers, and Backpressure

Fundamentals

A sniffer is a real-time pipeline: frames arrive at unpredictable rates, the UI and SD card are comparatively slow, and the CPU is limited. The only way to survive is to decouple capture, parsing, rendering, and storage into separate stages connected by bounded queues or ring buffers. The key idea is backpressure: if the consumer cannot keep up, the producer must drop or throttle. On microcontrollers, you cannot afford unbounded growth. Therefore, you preallocate buffers, implement drop counters, and design the UI to display loss statistics so you can tune performance. The capture callback must be constant time; everything else can be slower as long as it never blocks that callback.

Deep Dive into the concept

ESP32-S3 has dual cores, but the WiFi stack already occupies substantial CPU time. A naive single-task design—capture, parse, UI, and write in one loop—will fail under real load. The correct architecture uses a capture callback that enqueues frames, a parser task that builds metadata, a writer task that batches PCAP writes, and a UI task that renders statistics. Each task should have explicit priorities and clear ownership of data. For example, the parser task can consume raw slices and produce compact statistics (counts, most-recent SSID, RSSI histogram), while the writer task uses full frames and assembles PCAP blocks in memory before flushing to SD. The UI task should never read raw frames; it should read snapshots of aggregated stats.

Backpressure is a policy decision. If the ring buffer fills, you can drop oldest frames (keeping the newest) or drop newest frames (preserving early data). For a sniffer, dropping newest frames may create a “frozen” view. Dropping oldest frames keeps the display current but may distort total counts. A good compromise is to maintain two pipelines: a statistics pipeline that favors recency and a capture pipeline that favors completeness within a rolling window. You should track drop counts per pipeline and expose them on the UI so users can understand data quality.

Another key detail is memory discipline. WiFi frames can be large, and if you copy full frames for every packet, you will blow RAM. A common strategy is to copy only the first N bytes for parsing, but store full frames only when you are actively recording to SD. This requires a mode flag that controls whether the writer task is active. When capture is active, allocate a fixed pool of frame buffers sized for the maximum allowed frame length you will store. Use a free-list and avoid malloc in the hot path. When a buffer is exhausted, increment a drop counter and return.

Timing also matters for UI updates. A 30 FPS UI update is visually smooth but expensive; 10 FPS is often enough. If you update UI too frequently, you will starve the capture pipeline. Use a timer to update UI at a fixed cadence and decouple UI updates from capture rate. For storage, never write each frame individually; always batch. Use a 4–16 KB buffer, fill it with PCAP records, then flush. This reduces FAT writes, increases speed, and lowers wear. Finally, watchdogs matter: if any task stalls, you want a log, a counter, and a recovery path.

How this fits in projects

This is the concurrency backbone for this project. The same pipeline pattern powers the audio analyzer in P03-real-time-audio-spectrum-analyzer.md and the multi-tool architecture in P08-complete-cardputer-security-toolkit.md.

Definitions & key terms

  • Backpressure → a policy for handling overflow when consumers lag
  • Ring buffer → fixed-size circular queue optimized for high-rate data
  • Task priority → scheduling weight that determines preemption order
  • Batching → grouping writes to reduce I/O overhead and wear
  • Drop counter → telemetry for data loss events

Mental model diagram (ASCII)

[Promisc CB] -> [Ring Buffer] -> [Parser Task] -> [Stats Snapshot] -> [UI]
                      |
                      v
                [Frame Pool] -> [Writer Task] -> [PCAP Buffer] -> [SD]

How it works (step-by-step, with invariants and failure modes)

  1. Callback enqueues a frame descriptor into a ring buffer.
  2. Parser task dequeues descriptors and extracts metadata.
  3. Writer task drains full-frame buffers into a PCAP aggregation buffer.
  4. UI task reads stats snapshots at a fixed cadence (e.g., 10 Hz).
  5. If buffers fill, drop policy is applied and drop counters increment.

Invariants: no dynamic allocation in the callback; queues must be bounded; UI must never block writer. Failure modes: SD write stalls cause backpressure and drops; UI refresh too high causes capture loss; unbounded buffers cause memory exhaustion.

Minimal concrete example

if (!ringbuf_push(&desc)) {
    stats.drops++;
    return; // backpressure applied
}

Common misconceptions

  • “If I log everything, I won’t lose data.” → You will lose everything if you block.
  • “UI should update every frame.” → Human eyes do not need 200 FPS.
  • “I can use malloc in the callback if it’s small.” → Allocators are nondeterministic.

Check-your-understanding questions

  1. Why is batching critical for SD writes?
  2. What happens when your ring buffer fills and you have no drop policy?
  3. How does UI refresh rate impact capture reliability?

Check-your-understanding answers

  1. It reduces FAT overhead and wear, and avoids per-frame write latency.
  2. You either overwrite memory or block the callback, causing packet loss.
  3. High refresh steals CPU from capture tasks and increases drop rate.

Real-world applications

  • Real-time telemetry pipelines in IoT gateways.
  • High-rate sensor capture on embedded data loggers.
  • Software-defined radio capture front-ends.

Where you’ll apply it

  • This project: see §4.2 and §5.10 for task topology and phases.
  • Also used in: P03-real-time-audio-spectrum-analyzer.md, P08-complete-cardputer-security-toolkit.md.

References

  • Making Embedded Systems by Elecia White – Ch. 5–7 (timing, concurrency).
  • ESP-IDF FreeRTOS documentation (queues, tasks, priorities).

Key insight

If the capture callback ever waits, the sniffer is already losing data.

Summary

The sniffer is a pipeline. Separate capture, parse, UI, and storage tasks; bound all buffers; and measure drops as a first-class metric.

Homework/Exercises to practice the concept

  1. Build a producer/consumer demo with a ring buffer and drop counter.
  2. Measure UI update cost by toggling refresh rates and logging FPS vs drops.

Solutions to the homework/exercises

  1. Use a fixed-size queue and increment a drop counter on enqueue failure.
  2. Add a timer that logs stats.drops at 5 Hz, 10 Hz, and 30 Hz and compare.

2.3 PCAP Format and Reliable Storage on microSD

Fundamentals

A PCAP file is a binary container for captured packets that tools like Wireshark understand. It starts with a global header that defines endianness, capture link type, and snapshot length, followed by a sequence of packet records. Each record includes a timestamp and the raw frame bytes. On microcontrollers, writing PCAP data correctly is more than just writing bytes. You must ensure the global header is correct, timestamps are monotonic, and packet lengths match the data you store. Storage adds complexity: microSD cards use FAT filesystems, which are slow and sensitive to many small writes. You must batch writes, flush safely, and handle power loss without corrupting the file.

Deep Dive into the concept

The PCAP global header is 24 bytes and includes a magic number that indicates byte order. If you write the wrong magic or link type, Wireshark will reject the file. For WiFi capture, the link type is usually 105 (LINKTYPE_IEEE802_11) or 127 (LINKTYPE_IEEE802_11_RADIOTAP). On the ESP32-S3 you likely capture raw 802.11 frames without radiotap headers, so you should use LINKTYPE_IEEE802_11. If you only store a slice of each frame, you must still report the captured length and original length correctly; otherwise Wireshark will misparse frames or show them truncated unexpectedly.

Timestamps are another pitfall. PCAP uses seconds and microseconds (or nanoseconds in PCAPNG). Your ESP32 can use esp_timer_get_time() to get microseconds since boot. For deterministic demos, you can pin timestamps to a synthetic start time, but for real captures you should use actual wall clock if available (from SNTP). If you lack wall time, you can still provide relative timestamps; Wireshark will still display them. What matters is monotonicity and consistency. If your timestamps go backwards due to overflow or improper conversion, tools will mis-order frames.

Storage reliability is a systems problem. microSD cards are optimized for large sequential writes; small writes create FAT overhead and wear. The correct pattern is to maintain a PCAP buffer in RAM, append packet records until you reach a threshold (e.g., 8 KB), then flush. If you lose power mid-write, the file may be truncated but should still be recoverable if headers are intact. To help recovery, you can flush periodically and maintain a simple “index” file that records the last flush offset. Another safeguard is to pre-allocate the file size if you know your capture duration, which reduces fragmentation. If pre-allocation is not feasible, keep the file open and avoid frequent open/close cycles.

You must also handle the SD card’s variable latency. A single write can block for tens of milliseconds, which is catastrophic if done in the capture path. That is why a dedicated writer task is mandatory. The writer task can block, but it must read from a queue with bounded capacity so you can measure drops when storage falls behind. When storage is too slow, you may switch to “metadata-only” mode: keep UI stats but stop writing frames. This is an example of graceful degradation.

Finally, remember that FAT file systems require synchronization. Always close the file cleanly to ensure the directory entry is updated. If your device crashes, the file may appear zero-length even though data exists. A recovery tool or a “safe close” feature can help, but the best fix is regular flushes and a clean stop sequence.

How this fits in projects

PCAP writing is unique to this project, but the same SD durability techniques are reused in P05-wardriving-wifi-mapper.md and P08-complete-cardputer-security-toolkit.md for CSV and log files.

Definitions & key terms

  • PCAP global header → 24-byte file header describing capture format
  • Packet record header → per-packet timestamp and length metadata
  • Snaplen → maximum bytes captured per packet
  • Link type → identifier for the data link layer (802.11, Ethernet)
  • Flush → writing buffered data to storage to ensure persistence

Mental model diagram (ASCII)

[Frames] -> [PCAP Builder] -> [8KB Buffer] -> [SD Write Task]
                   |
                   v
           [Global Header once]

How it works (step-by-step, with invariants and failure modes)

  1. On capture start, create file and write the 24-byte PCAP global header.
  2. For each frame, build a record header with timestamp and lengths.
  3. Append record header + frame bytes into a RAM buffer.
  4. When buffer reaches threshold, enqueue it for SD write.
  5. Writer task flushes to SD and updates counters.

Invariants: global header written once; record length fields match data; buffer size bounded. Failure modes: wrong link type yields unreadable file; blocking writes cause dropped frames; power loss before flush causes truncation.

Minimal concrete example

pcap_hdr_t hdr = {.magic = 0xa1b2c3d4, .linktype = 105, .snaplen = 256};
file_write(&hdr, sizeof(hdr));

Common misconceptions

  • “PCAP is just raw bytes.” → Without correct headers, tools can’t parse it.
  • “I can write each packet immediately.” → SD latency will kill capture.
  • “Snaplen doesn’t matter.” → If too small, you lose essential frame fields.

Check-your-understanding questions

  1. Why must captured length and original length be correct in each record?
  2. What does the PCAP magic number represent?
  3. Why is batching essential for microSD reliability?

Check-your-understanding answers

  1. Wireshark uses them to determine how many bytes to read and display.
  2. It encodes endianness and PCAP format identification.
  3. It reduces FAT overhead and avoids blocking the capture pipeline.

Real-world applications

  • Forensic capture devices in incident response.
  • Field diagnostics for wireless deployments.
  • Automated testing of WiFi firmware in labs.

Where you’ll apply it

  • This project: see §3.5 and §4.4 for format and storage architecture.
  • Also used in: P05-wardriving-wifi-mapper.md, P08-complete-cardputer-security-toolkit.md.

References

  • Wireshark PCAP format reference (Wireshark Wiki).
  • ESP-IDF SDMMC/FATFS docs.
  • Making Embedded Systems – Ch. 8 (storage and logging).

Key insight

A sniffer that cannot write trustworthy PCAP files is just a toy, not a tool.

Summary

PCAP files have strict headers and record formats. Buffer writes, validate lengths, and treat SD latency as a real-time hazard.

Homework/Exercises to practice the concept

  1. Write a PCAP file with two fake frames and open it in Wireshark.
  2. Simulate an SD write delay and measure drop counts.

Solutions to the homework/exercises

  1. Use a known sample frame and verify that Wireshark shows it as 802.11.
  2. Insert a vTaskDelay(50) in the writer task and log drop rates.

3. Project Specification

3.1 What You Will Build

A handheld WiFi sniffer that:

  • captures 802.11 frames in promiscuous mode,
  • renders live statistics (channels, RSSI, SSID counts, frame types),
  • records a PCAP file on microSD,
  • includes a safe UI with explicit authorization prompts.

Out of scope: decryption of protected WiFi payloads, active attacks, or long-range RF hardware modifications.

3.2 Functional Requirements

  1. Promiscuous capture: enable capture on a selected channel and optionally hop channels on a fixed schedule.
  2. Live statistics: show packets-per-second, top SSIDs/BSSIDs, RSSI histogram, and drop counters.
  3. PCAP logging: write valid PCAP files with a correct global header and per-packet record headers.
  4. Storage management: create a new file per capture session with a deterministic name and metadata header.
  5. User safety: show a warning banner and require a long-press to start capture.

3.3 Non-Functional Requirements

  • Performance: sustain 100+ packets/sec without UI jitter or crashes.
  • Reliability: no file corruption after a clean stop; drop counter always accurate.
  • Usability: capture starts in under 3 seconds; UI updates at least 5 Hz.

3.4 Example Usage / Output

1) Boot device
2) Select channel 6
3) Long-press [OK] to arm capture
4) Observe live stats on screen
5) Press [MENU] to stop and flush
6) Remove SD and open PCAP in Wireshark

3.5 Data Formats / Schemas / Protocols

PCAP Global Header (little-endian):

magic: 0xa1b2c3d4
version: 2.4
snaplen: 256
linktype: 105 (IEEE 802.11)

Per-packet Record Header:

ts_sec, ts_usec, incl_len, orig_len

3.6 Edge Cases

  • SD card missing or read-only.
  • WiFi driver init fails (RF off).
  • Ring buffer overflow while SD is slow.
  • Channel hop interval too short to capture beacons.
  • Frames smaller than the expected header length.

3.7 Real World Outcome

A successful build produces a PCAP file that opens in Wireshark and shows recognizable 802.11 management frames and data frames, with timestamps in order, along with a smooth on-device UI that tracks packet rate and drop counts.

3.7.1 How to Run (Copy/Paste)

idf.py set-target esp32s3
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor

3.7.2 Golden Path Demo (Deterministic)

  • Set channel to 6 and disable hopping.
  • Place device 2 meters from a known AP.
  • Capture for exactly 30 seconds.
  • Expect at least 20 beacon frames and a stable packets/sec rate.

Failure demo (deterministic):

  • Remove the SD card, start capture, then stop after 10 seconds. Expected: UI shows “SD ERROR,” capture statistics still update, and the device reports logging disabled. Exit code on stop: 2.

3.7.3 If CLI: exact terminal transcript

I (1020) sniffer: channel=6 mode=static
I (1021) sniffer: armed=YES
I (1050) stats: pps=180 drops=0 ssids=14
I (1080) pcap: wrote 512 packets (96 KB)
I (1110) sniffer: stop requested
I (1111) pcap: flush ok file=/sd/sniff_2025-01-01_120000.pcap

Exit codes: 0 = success, 2 = SD error (write/flush), 3 = WiFi init failure.

3.7.4 If Web App

Not applicable.

3.7.5 If API

Not applicable.

3.7.6 If Library

Not applicable.

3.7.7 If GUI / Desktop / Mobile

Not applicable.

3.7.8 If TUI

+------------------------------+
| WiFi Sniffer  CH:6  RSSI:-42 |
| PPS: 180   Drops: 0          |
| SSIDs: 14  BSSIDs: 21        |
| Top: CoffeeShop_Free (-43)   |
| [OK] Stop  [*] Hop           |
+------------------------------+

4. Solution Architecture

4.1 High-Level Design

[WiFi Driver] -> [Promisc Callback] -> [Ring Buffer]
                                         |
                                         v
                                 [Parser Task]
                                         |
                       +-----------------+-----------------+
                       |                                   |
                [Stats Aggregator]                 [PCAP Writer]
                       |                                   |
                       v                                   v
                    [UI Task]                         [SD Writer]

4.2 Key Components

Component Responsibility Key Decisions
Promisc callback Fast capture + metadata copy Must not block or allocate
Parser task Frame classification + SSID parsing Bounded parsing, defensive checks
Stats store Aggregated counters and histograms Lock-free snapshots to avoid stalls
PCAP writer Record assembly and buffering Batched writes and snaplen policy
UI task Render stats at fixed cadence 5–10 Hz to balance performance

4.3 Data Structures (No Full Code)

typedef struct {
    int8_t rssi;
    uint8_t channel;
    uint16_t len;
    uint32_t ts_us;
    uint8_t slice[96];
} frame_slice_t;

typedef struct {
    uint32_t pps;
    uint32_t drops;
    uint16_t ssid_count;
    int8_t rssi_hist[10];
} stats_snapshot_t;

4.4 Algorithm Overview

Key Algorithm: Management Frame SSID Extraction

  1. Read FC field and confirm management frame.
  2. Skip fixed header to IE region.
  3. Iterate TLVs until SSID tag found.
  4. Validate length and copy SSID safely.

Complexity Analysis:

  • Time: O(n) per frame (n = number of IEs)
  • Space: O(1) per frame (fixed buffers)

5. Implementation Guide

5.1 Development Environment Setup

# ESP-IDF v5.x recommended
idf.py set-target esp32s3
idf.py build

5.2 Project Structure

project-root/
├── main/
│   ├── app_main.c
│   ├── capture.c
│   ├── parser.c
│   ├── pcap_writer.c
│   ├── ui.c
│   └── stats.c
├── components/
│   └── sdcard/
├── CMakeLists.txt
└── README.md

5.3 The Core Question You’re Answering

“How do I capture high-rate WiFi frames without dropping data or corrupting storage?”

5.4 Concepts You Must Understand First

Stop and research these before coding:

  1. Promiscuous mode callback timing
  2. 802.11 frame headers and IEs
  3. Ring buffers and backpressure
  4. PCAP global and record headers

5.5 Questions to Guide Your Design

  1. What is your snaplen and why?
  2. What is your drop policy when buffers are full?
  3. How will you show data quality to the user?

5.6 Thinking Exercise

Design a minimal PCAP writer that records only beacon frames. Sketch the header layout and decide how you will store timestamps.

5.7 The Interview Questions They Will Ask

  1. Why can’t you parse frames inside the WiFi callback?
  2. What’s the difference between SSID and BSSID?
  3. How do you decide a snaplen value?
  4. How does channel hopping affect data accuracy?

5.8 Hints in Layers

Hint 1: Start with statistics only Log RSSI and frame counts without writing to SD.

Hint 2: Add PCAP header Write only the global header and verify Wireshark opens the file.

Hint 3: Add packet records Append a few frame slices and check Wireshark decodes them.

Hint 4: Add batching Use a RAM buffer to reduce SD writes.

5.9 Books That Will Help

Topic Book Chapter
WiFi basics Computer Networks Ch. 6
Embedded timing Making Embedded Systems Ch. 5–7
Defensive C parsing Effective C Ch. 10

5.10 Implementation Phases

Phase 1: Capture Skeleton (2–3 days)

Goals:

  • Enable promiscuous mode and see callback activity.
  • Display packet counts on screen.

Tasks:

  1. Configure WiFi and register promiscuous callback.
  2. Add a drop counter and log RSSI histogram.

Checkpoint: packet counts increase without crashes.

Phase 2: Parsing + UI (4–6 days)

Goals:

  • Parse management frames.
  • Show SSID list and RSSI stats.

Tasks:

  1. Implement IE parser for SSID.
  2. Add UI list with top SSIDs.

Checkpoint: SSIDs visible in UI and match real networks.

Phase 3: PCAP Logging (5–7 days)

Goals:

  • Write valid PCAP files.
  • Verify with Wireshark.

Tasks:

  1. Implement PCAP global + record headers.
  2. Batch writes and flush at stop.

Checkpoint: Wireshark opens and decodes captured file.

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
Snaplen 96, 256, 1024 256 Captures full headers without huge RAM cost
Drop policy Drop oldest, drop newest Drop oldest Keep UI responsive and current
UI refresh 30 Hz, 10 Hz, 5 Hz 10 Hz Balanced smoothness vs CPU

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Unit Tests Validate parsing logic IE parsing, FC decoding
Integration Tests Validate pipeline capture -> parse -> UI
Edge Case Tests Handle malformed frames truncated IE, short frame

6.2 Critical Test Cases

  1. Malformed IE: parser skips and continues without crash.
  2. SD card removed: capture continues but logging disabled.
  3. High traffic: drop counter increments, UI remains responsive.

6.3 Test Data

Beacon frame (hex) with SSID="TestAP"
Truncated frame (<24 bytes)
Large burst of frames at 500 PPS

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

Pitfall Symptom Solution
Parsing inside callback Massive drops Move parsing to task
Wrong linktype Wireshark rejects file Use 105 for 802.11
Unbounded buffers Heap exhaustion Pre-allocate pools

7.2 Debugging Strategies

  • Use drop counters to quantify loss and tune queues.
  • Capture on fixed channel to validate parser before hopping.

7.3 Performance Traps

  • Updating UI too often.
  • Writing to SD for every packet.

8. Extensions & Challenges

8.1 Beginner Extensions

  • Add a channel hop toggle.
  • Add a top-5 SSID view.

8.2 Intermediate Extensions

  • Add RSSI histograms per channel.
  • Add pcap file rotation by size.

8.3 Advanced Extensions

  • Add deauth detection heuristics.
  • Add capture filters by BSSID.

9. Real-World Connections

9.1 Industry Applications

  • Site surveys and channel occupancy reports.
  • Incident response for rogue AP detection.
  • Kismet: full-featured WiFi sniffer (desktop class).
  • Aircrack-ng: WiFi analysis toolkit.

9.3 Interview Relevance

  • Packet parsing, concurrency, and I/O reliability questions are standard in embedded roles.

10. Resources

10.1 Essential Reading

  • Computer Networks by Tanenbaum – Wireless chapters.
  • Making Embedded Systems by Elecia White – timing and logging chapters.

10.2 Video Resources

  • Wireshark PCAP format overview (Wireshark community videos).

10.3 Tools & Documentation

  • ESP-IDF WiFi Promiscuous Mode docs.
  • Wireshark PCAP file format spec.
  • P05-wardriving-wifi-mapper.md – same capture pipeline with GPS fusion.
  • P08-complete-cardputer-security-toolkit.md – integrates this sniffer.

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain the 802.11 Frame Control field.
  • I can describe why callbacks must be fast.
  • I can explain PCAP record headers.

11.2 Implementation

  • All functional requirements are met.
  • PCAP opens in Wireshark without errors.
  • Drops are measured and displayed.

11.3 Growth

  • I can describe one optimization for higher packet rates.
  • I can explain tradeoffs of channel hopping.

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Promiscuous mode captures frames and shows counts.
  • A valid PCAP file is written and opens in Wireshark.
  • UI is stable and shows drop counter.

Full Completion:

  • Channel hopping works with stable UI.
  • PCAP files include correct timestamps.
  • SD logging survives a 30-minute capture.

Excellence (Going Above & Beyond):

  • Adds capture filters and file rotation.
  • Provides calibration mode with known AP test.