sk v1
This commit is contained in:
1 parent
71d6a233a5
commit
e4d24f5486
9 files changed
+3585
-7
No files matched your search
+2
-1
@@ -4,4 +4,5 @@ build
|
||||
sdkconfig
|
||||
sdkconfig.ci
|
||||
managed_components
|
||||
dependencies.lock
|
||||
dependencies.lock
|
||||
sdkconfig.old
|
||||
@@ -0,0 +1,470 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DarkMesh</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg-color: #121212;
|
||||
--panel-color: #1e1e1e;
|
||||
--text-color: #e0e0e0;
|
||||
--primary-color: #00ff7f;
|
||||
--border-color: #333;
|
||||
--danger-color: #ff4136;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
header {
|
||||
grid-column: 1 / -1;
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
color: var(--primary-color);
|
||||
text-shadow: 0 0 5px var(--primary-color);
|
||||
}
|
||||
|
||||
.panel {
|
||||
background-color: var(--panel-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.panel.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: var(--primary-color);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: inherit;
|
||||
background-color: var(--primary-color);
|
||||
color: var(--bg-color);
|
||||
border: none;
|
||||
padding: 0.75rem 1rem;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
border-radius: 5px;
|
||||
transition: all 0.2s ease;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
box-shadow: 0 0 10px var(--primary-color);
|
||||
}
|
||||
|
||||
button#stop-attack-btn {
|
||||
background-color: var(--danger-color);
|
||||
}
|
||||
|
||||
button#stop-attack-btn:hover {
|
||||
box-shadow: 0 0 10px var(--danger-color);
|
||||
}
|
||||
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
font-family: inherit;
|
||||
background-color: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-color);
|
||||
padding: 0.5rem;
|
||||
border-radius: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.param-group {
|
||||
display: none;
|
||||
padding: 1rem;
|
||||
border: 1px dashed var(--border-color);
|
||||
border-radius: 5px;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.param-group.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.coord-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.config-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
#status-value {
|
||||
font-weight: bold;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 0.5rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: var(--panel-color);
|
||||
}
|
||||
|
||||
#debug-log {
|
||||
background-color: #000;
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
padding: 1rem;
|
||||
border-radius: 5px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>💀 DarkMesh 💀</h1>
|
||||
<p>Meshtastic Pen-Testing Suite</p>
|
||||
</header>
|
||||
|
||||
<div class="panel">
|
||||
<h2>Radio Configuration</h2>
|
||||
|
||||
<label for="radio-freq">Frequency (MHz)</label>
|
||||
<input type="number" id="radio-freq" value="868.0">
|
||||
|
||||
<label for="lora-preset-selector">LoRa Presets</label>
|
||||
<select id="lora-preset-selector">
|
||||
<option value="long_fast">LongFast (Default)</option>
|
||||
<option value="long_slow">LongSlow</option>
|
||||
<option value="very_long_slow">VeryLongSlow</option>
|
||||
<option value="medium_slow">MediumSlow</option>
|
||||
<option value="medium_fast">MediumFast</option>
|
||||
<option value="short_slow">ShortSlow</option>
|
||||
<option value="short_fast">ShortFast</option>
|
||||
<option value="custom">Custom</option>
|
||||
</select>
|
||||
|
||||
<div class="config-grid">
|
||||
<div>
|
||||
<label for="lora-bw">Bandwidth</label>
|
||||
<input type="number" id="lora-bw" value="250.0" step="0.1">
|
||||
</div>
|
||||
<div>
|
||||
<label for="lora-sf">Spread Factor</label>
|
||||
<input type="number" id="lora-sf" value="11" min="7" max="12">
|
||||
</div>
|
||||
<div>
|
||||
<label for="lora-cr">Coding Rate</label>
|
||||
<input type="number" id="lora-cr" value="5" min="5" max="8">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label for="radio-power">Output Power (dBm)</label>
|
||||
<input type="number" id="radio-power" value="22" min="2" max="22">
|
||||
|
||||
<button id="apply-config-btn">Apply Configuration</button>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>Continuous Attacks</h2>
|
||||
<p>Status: <span id="status-value">Idle</span></p>
|
||||
|
||||
<select id="attack-selector">
|
||||
<option value="none">-- Select Attack --</option>
|
||||
<option value="node_flood">Flood with Nodes</option>
|
||||
<option value="name_change">Namechange - Emoji</option>
|
||||
<option value="pos_poison">Position Poison</option>
|
||||
<option value="pki_poison">PKI Poison</option>
|
||||
<option value="ddos">DDoS</option>
|
||||
</select>
|
||||
|
||||
<div id="params-name_change" class="param-group">
|
||||
<label for="emoji-input">Emoji to Append</label>
|
||||
<input type="text" id="emoji-input" value="😈" maxlength="2">
|
||||
</div>
|
||||
|
||||
<div id="params-pos_poison" class="param-group">
|
||||
<div class="coord-grid">
|
||||
<div><label for="min-lat">Min Latitude</label><input type="number" id="min-lat" value="47.0"></div>
|
||||
<div><label for="max-lat">Max Latitude</label><input type="number" id="max-lat" value="48.0"></div>
|
||||
<div><label for="min-lon">Min Longitude</label><input type="number" id="min-lon" value="18.0"></div>
|
||||
<div><label for="max-lon">Max Longitude</label><input type="number" id="max-lon" value="20.0"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="start-attack-btn">Start Selected Attack</button>
|
||||
<button id="stop-attack-btn">Stop Current Attack</button>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>Send Message As (One-Time)</h2>
|
||||
<label for="source-node-id">Source Node ID</label>
|
||||
<input type="text" id="source-node-id" placeholder="e.g., !a1b2c3d4">
|
||||
|
||||
<label for="message-text">Message</label>
|
||||
<textarea id="message-text" placeholder="Enter your message here..."></textarea>
|
||||
|
||||
<button id="send-message-btn">Send Message</button>
|
||||
</div>
|
||||
|
||||
<div class="panel full-width">
|
||||
<h2>Nodes Detected</h2>
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Node ID</th>
|
||||
<th>Name</th>
|
||||
<th>Position</th>
|
||||
<th>Last Heard</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="node-list-body">
|
||||
<tr>
|
||||
<td colspan="4">Connecting to device...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel full-width">
|
||||
<h2>Debug Console</h2>
|
||||
<div id="debug-log"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// --- Data ---
|
||||
const loraPresets = {
|
||||
'long_fast': { bw: 250.0, sf: 11, cr: 5 },
|
||||
'long_slow': { bw: 125.0, sf: 12, cr: 5 },
|
||||
'very_long_slow': { bw: 31.25, sf: 12, cr: 8 },
|
||||
'medium_slow': { bw: 250.0, sf: 10, cr: 8 },
|
||||
'medium_fast': { bw: 250.0, sf: 9, cr: 7 },
|
||||
'short_slow': { bw: 250.0, sf: 8, cr: 7 },
|
||||
'short_fast': { bw: 250.0, sf: 7, cr: 5 },
|
||||
};
|
||||
|
||||
const statusValue = document.getElementById('status-value');
|
||||
const nodeListBody = document.getElementById('node-list-body');
|
||||
const debugLog = document.getElementById('debug-log');
|
||||
const attackSelector = document.getElementById('attack-selector');
|
||||
const startAttackBtn = document.getElementById('start-attack-btn');
|
||||
const stopAttackBtn = document.getElementById('stop-attack-btn');
|
||||
const sendMessageBtn = document.getElementById('send-message-btn');
|
||||
const applyConfigBtn = document.getElementById('apply-config-btn');
|
||||
const loraPresetSelector = document.getElementById('lora-preset-selector');
|
||||
const loraBw = document.getElementById('lora-bw');
|
||||
const loraSf = document.getElementById('lora-sf');
|
||||
const loraCr = document.getElementById('lora-cr');
|
||||
let socket;
|
||||
function initWebSocket() {
|
||||
console.log("Attempting to connect WebSocket...");
|
||||
const gateway = `ws://${window.location.hostname}/ws`;
|
||||
socket = new WebSocket(gateway);
|
||||
socket.onopen = () => { logToDebug("🔗 Connected to DarkMesh ESP32."); };
|
||||
socket.onclose = () => {
|
||||
logToDebug("⛔ Connection lost. Retrying in 3 seconds...");
|
||||
setTimeout(initWebSocket, 3000);
|
||||
};
|
||||
socket.onerror = (error) => { logToDebug(`❌ WebSocket Error: ${error}`); };
|
||||
socket.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
handleWebSocketMessage(data);
|
||||
} catch (e) {
|
||||
logToDebug(`Received non-JSON message: ${event.data}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
function handleWebSocketMessage(data) {
|
||||
switch (data.type) {
|
||||
case 'status_update':
|
||||
statusValue.textContent = data.current_attack || 'Idle';
|
||||
break;
|
||||
case 'node_update':
|
||||
updateNodeList(data.nodes);
|
||||
break;
|
||||
case 'debug':
|
||||
logToDebug(data.message);
|
||||
break;
|
||||
default:
|
||||
logToDebug(`Unknown message type: ${data.type}`);
|
||||
}
|
||||
}
|
||||
function updateNodeList(nodes) {
|
||||
nodeListBody.innerHTML = '';
|
||||
if (nodes && nodes.length > 0) {
|
||||
nodes.forEach(node => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${node.id || 'N/A'}</td>
|
||||
<td>${node.name || 'N/A'}</td>
|
||||
<td>${node.pos ? `${node.pos.lat}, ${node.pos.lon}` : 'N/A'}</td>
|
||||
<td>${node.last_heard || 'N/A'}</td>
|
||||
`;
|
||||
nodeListBody.appendChild(row);
|
||||
});
|
||||
} else {
|
||||
nodeListBody.innerHTML = '<tr><td colspan="4">No nodes detected yet...</td></tr>';
|
||||
}
|
||||
}
|
||||
function logToDebug(message) {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
debugLog.innerHTML += `[${timestamp}] ${message}\n`;
|
||||
debugLog.scrollTop = debugLog.scrollHeight;
|
||||
}
|
||||
attackSelector.addEventListener('change', () => {
|
||||
document.querySelectorAll('.param-group').forEach(group => group.classList.remove('active'));
|
||||
const selectedAttack = attackSelector.value;
|
||||
const paramGroup = document.getElementById(`params-${selectedAttack}`);
|
||||
if (paramGroup) {
|
||||
paramGroup.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
loraPresetSelector.addEventListener('change', () => {
|
||||
const preset = loraPresetSelector.value;
|
||||
if (preset in loraPresets) {
|
||||
const values = loraPresets[preset];
|
||||
loraBw.value = values.bw;
|
||||
loraSf.value = values.sf;
|
||||
loraCr.value = values.cr;
|
||||
}
|
||||
});
|
||||
|
||||
[loraBw, loraSf, loraCr].forEach(input => {
|
||||
input.addEventListener('input', () => {
|
||||
loraPresetSelector.value = 'custom';
|
||||
});
|
||||
});
|
||||
|
||||
function sendWebSocketMessage(data) {
|
||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||
socket.send(JSON.stringify(data));
|
||||
} else {
|
||||
logToDebug("❌ WebSocket not connected. Cannot send command.");
|
||||
}
|
||||
}
|
||||
|
||||
startAttackBtn.addEventListener('click', () => {
|
||||
const attack = attackSelector.value;
|
||||
if (attack === 'none') {
|
||||
alert('Please select an attack first.');
|
||||
return;
|
||||
}
|
||||
const command = { action: 'start_attack', attack_type: attack, params: {} };
|
||||
if (attack === 'name_change') {
|
||||
command.params.emoji = document.getElementById('emoji-input').value;
|
||||
} else if (attack === 'pos_poison') {
|
||||
command.params.min_lat = parseFloat(document.getElementById('min-lat').value);
|
||||
command.params.max_lat = parseFloat(document.getElementById('max-lat').value);
|
||||
command.params.min_lon = parseFloat(document.getElementById('min-lon').value);
|
||||
command.params.max_lon = parseFloat(document.getElementById('max-lon').value);
|
||||
}
|
||||
logToDebug(`▶️ Starting attack: ${attack}`);
|
||||
sendWebSocketMessage(command);
|
||||
});
|
||||
|
||||
stopAttackBtn.addEventListener('click', () => {
|
||||
logToDebug("⏹️ Stopping current attack.");
|
||||
sendWebSocketMessage({ action: 'stop_attack' });
|
||||
});
|
||||
|
||||
sendMessageBtn.addEventListener('click', () => {
|
||||
const sourceId = document.getElementById('source-node-id').value;
|
||||
const message = document.getElementById('message-text').value;
|
||||
if (!sourceId || !message) {
|
||||
alert('Source Node ID and message cannot be empty.');
|
||||
return;
|
||||
}
|
||||
logToDebug(`✉️ Sending message as ${sourceId}`);
|
||||
sendWebSocketMessage({ action: 'send_message', source_id: sourceId, message: message });
|
||||
});
|
||||
|
||||
applyConfigBtn.addEventListener('click', () => {
|
||||
const config = {
|
||||
action: 'set_config',
|
||||
params: {
|
||||
frequency: parseFloat(document.getElementById('radio-freq').value),
|
||||
bandwidth: parseFloat(loraBw.value),
|
||||
spreading_factor: parseInt(loraSf.value),
|
||||
coding_rate: parseInt(loraCr.value),
|
||||
power: parseInt(document.getElementById('radio-power').value)
|
||||
}
|
||||
};
|
||||
logToDebug(`⚙️ Applying new radio configuration...`);
|
||||
sendWebSocketMessage(config);
|
||||
});
|
||||
initWebSocket();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+3
-3
@@ -1,3 +1,3 @@
|
||||
idf_component_register(SRCS "main.cpp"
|
||||
PRIV_REQUIRES spi_flash
|
||||
INCLUDE_DIRS "")
|
||||
idf_component_register(SRCS "main.cpp" "parson.c"
|
||||
REQUIRES spi_flash esp_wifi spiffs mdns nvs_flash esp_http_server app_update EspMeshtasticCompact
|
||||
INCLUDE_DIRS "" EMBED_FILES ../data/web/index.html )
|
||||
@@ -1,4 +1,5 @@
|
||||
dependencies:
|
||||
EspMeshtasticCompact:
|
||||
git: https://github.com/htotoo/EspMeshtasticCompact
|
||||
|
||||
espressif/mdns: 1.*
|
||||
|
||||
+32
-1
@@ -5,12 +5,19 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "esp_random.h"
|
||||
#include "MeshtasticCompact.hpp"
|
||||
|
||||
#include "webserver.h"
|
||||
static const char* TAG = "DarkMesh";
|
||||
|
||||
static const char* ssid = "DarkMesh";
|
||||
static const char* password = "1234Dark";
|
||||
|
||||
extern "C" {
|
||||
void app_main();
|
||||
}
|
||||
@@ -30,6 +37,30 @@ LoraConfig lora_config = {
|
||||
MeshtasticCompact meshtasticCompact;
|
||||
|
||||
void app_main(void) {
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
esp_netif_create_default_wifi_ap();
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
wifi_config_t wifi_config = {};
|
||||
strcpy((char*)wifi_config.ap.ssid, ssid);
|
||||
wifi_config.ap.ssid_len = strlen(ssid);
|
||||
strcpy((char*)wifi_config.ap.password, password);
|
||||
wifi_config.ap.max_connection = 4;
|
||||
wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK;
|
||||
if (strlen(password) == 0) {
|
||||
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
|
||||
}
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
ESP_LOGI(TAG, "WiFi AP initialized. SSID: %s, Password: %s", ssid, password);
|
||||
|
||||
init_httpd();
|
||||
|
||||
ESP_LOGI(TAG, "Radio initializing...");
|
||||
meshtasticCompact.RadioInit(RadioType::SX1262, radio_pins, lora_config);
|
||||
ESP_LOGI(TAG, "Radio initialized.");
|
||||
|
||||
+2578
File diff suppressed because it is too large.
Load diff
+273
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Parson 1.5.3 (https://github.com/kgabis/parson)
|
||||
Copyright (c) 2012 - 2023 Krzysztof Gabis
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#ifndef parson_parson_h
|
||||
#define parson_parson_h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#if 0
|
||||
} /* unconfuse xcode */
|
||||
#endif
|
||||
|
||||
#define PARSON_VERSION_MAJOR 1
|
||||
#define PARSON_VERSION_MINOR 5
|
||||
#define PARSON_VERSION_PATCH 3
|
||||
|
||||
#define PARSON_VERSION_STRING "1.5.3"
|
||||
|
||||
#include <stddef.h> /* size_t */
|
||||
|
||||
/* Types and enums */
|
||||
typedef struct json_object_t JSON_Object;
|
||||
typedef struct json_array_t JSON_Array;
|
||||
typedef struct json_value_t JSON_Value;
|
||||
|
||||
enum json_value_type {
|
||||
JSONError = -1,
|
||||
JSONNull = 1,
|
||||
JSONString = 2,
|
||||
JSONNumber = 3,
|
||||
JSONObject = 4,
|
||||
JSONArray = 5,
|
||||
JSONBoolean = 6
|
||||
};
|
||||
typedef int JSON_Value_Type;
|
||||
|
||||
enum json_result_t {
|
||||
JSONSuccess = 0,
|
||||
JSONFailure = -1
|
||||
};
|
||||
typedef int JSON_Status;
|
||||
|
||||
typedef void* (*JSON_Malloc_Function)(size_t);
|
||||
typedef void (*JSON_Free_Function)(void*);
|
||||
|
||||
/* A function used for serializing numbers (see json_set_number_serialization_function).
|
||||
If 'buf' is null then it should return number of bytes that would've been written
|
||||
(but not more than PARSON_NUM_BUF_SIZE).
|
||||
*/
|
||||
typedef int (*JSON_Number_Serialization_Function)(double num, char* buf);
|
||||
|
||||
/* Call only once, before calling any other function from parson API. If not called, malloc and free
|
||||
from stdlib will be used for all allocations */
|
||||
void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun);
|
||||
|
||||
/* Sets if slashes should be escaped or not when serializing JSON. By default slashes are escaped.
|
||||
This function sets a global setting and is not thread safe. */
|
||||
void json_set_escape_slashes(int escape_slashes);
|
||||
|
||||
/* Sets float format used for serialization of numbers.
|
||||
Make sure it can't serialize to a string longer than PARSON_NUM_BUF_SIZE.
|
||||
If format is null then the default format is used. */
|
||||
void json_set_float_serialization_format(const char* format);
|
||||
|
||||
/* Sets a function that will be used for serialization of numbers.
|
||||
If function is null then the default serialization function is used. */
|
||||
void json_set_number_serialization_function(JSON_Number_Serialization_Function fun);
|
||||
|
||||
/* Parses first JSON value in a file, returns NULL in case of error */
|
||||
JSON_Value* json_parse_file(const char* filename);
|
||||
|
||||
/* Parses first JSON value in a file and ignores comments (/ * * / and //),
|
||||
returns NULL in case of error */
|
||||
JSON_Value* json_parse_file_with_comments(const char* filename);
|
||||
|
||||
/* Parses first JSON value in a string, returns NULL in case of error */
|
||||
JSON_Value* json_parse_string(const char* string);
|
||||
|
||||
/* Parses first JSON value in a string and ignores comments (/ * * / and //),
|
||||
returns NULL in case of error */
|
||||
JSON_Value* json_parse_string_with_comments(const char* string);
|
||||
|
||||
/* Serialization */
|
||||
size_t json_serialization_size(const JSON_Value* value); /* returns 0 on fail */
|
||||
JSON_Status json_serialize_to_buffer(const JSON_Value* value, char* buf, size_t buf_size_in_bytes);
|
||||
JSON_Status json_serialize_to_file(const JSON_Value* value, const char* filename);
|
||||
char* json_serialize_to_string(const JSON_Value* value);
|
||||
|
||||
/* Pretty serialization */
|
||||
size_t json_serialization_size_pretty(const JSON_Value* value); /* returns 0 on fail */
|
||||
JSON_Status json_serialize_to_buffer_pretty(const JSON_Value* value, char* buf, size_t buf_size_in_bytes);
|
||||
JSON_Status json_serialize_to_file_pretty(const JSON_Value* value, const char* filename);
|
||||
char* json_serialize_to_string_pretty(const JSON_Value* value);
|
||||
|
||||
void json_free_serialized_string(char* string); /* frees string from json_serialize_to_string and json_serialize_to_string_pretty */
|
||||
|
||||
/* Comparing */
|
||||
int json_value_equals(const JSON_Value* a, const JSON_Value* b);
|
||||
|
||||
/* Validation
|
||||
This is *NOT* JSON Schema. It validates json by checking if object have identically
|
||||
named fields with matching types.
|
||||
For example schema {"name":"", "age":0} will validate
|
||||
{"name":"Joe", "age":25} and {"name":"Joe", "age":25, "gender":"m"},
|
||||
but not {"name":"Joe"} or {"name":"Joe", "age":"Cucumber"}.
|
||||
In case of arrays, only first value in schema is checked against all values in tested array.
|
||||
Empty objects ({}) validate all objects, empty arrays ([]) validate all arrays,
|
||||
null validates values of every type.
|
||||
*/
|
||||
JSON_Status json_validate(const JSON_Value* schema, const JSON_Value* value);
|
||||
|
||||
/*
|
||||
* JSON Object
|
||||
*/
|
||||
JSON_Value* json_object_get_value(const JSON_Object* object, const char* name);
|
||||
const char* json_object_get_string(const JSON_Object* object, const char* name);
|
||||
size_t json_object_get_string_len(const JSON_Object* object, const char* name); /* doesn't account for last null character */
|
||||
JSON_Object* json_object_get_object(const JSON_Object* object, const char* name);
|
||||
JSON_Array* json_object_get_array(const JSON_Object* object, const char* name);
|
||||
double json_object_get_number(const JSON_Object* object, const char* name); /* returns 0 on fail */
|
||||
int json_object_get_boolean(const JSON_Object* object, const char* name); /* returns -1 on fail */
|
||||
|
||||
/* dotget functions enable addressing values with dot notation in nested objects,
|
||||
just like in structs or c++/java/c# objects (e.g. objectA.objectB.value).
|
||||
Because valid names in JSON can contain dots, some values may be inaccessible
|
||||
this way. */
|
||||
JSON_Value* json_object_dotget_value(const JSON_Object* object, const char* name);
|
||||
const char* json_object_dotget_string(const JSON_Object* object, const char* name);
|
||||
size_t json_object_dotget_string_len(const JSON_Object* object, const char* name); /* doesn't account for last null character */
|
||||
JSON_Object* json_object_dotget_object(const JSON_Object* object, const char* name);
|
||||
JSON_Array* json_object_dotget_array(const JSON_Object* object, const char* name);
|
||||
double json_object_dotget_number(const JSON_Object* object, const char* name); /* returns 0 on fail */
|
||||
int json_object_dotget_boolean(const JSON_Object* object, const char* name); /* returns -1 on fail */
|
||||
|
||||
/* Functions to get available names */
|
||||
size_t json_object_get_count(const JSON_Object* object);
|
||||
const char* json_object_get_name(const JSON_Object* object, size_t index);
|
||||
JSON_Value* json_object_get_value_at(const JSON_Object* object, size_t index);
|
||||
JSON_Value* json_object_get_wrapping_value(const JSON_Object* object);
|
||||
|
||||
/* Functions to check if object has a value with a specific name. Returned value is 1 if object has
|
||||
* a value and 0 if it doesn't. dothas functions behave exactly like dotget functions. */
|
||||
int json_object_has_value(const JSON_Object* object, const char* name);
|
||||
int json_object_has_value_of_type(const JSON_Object* object, const char* name, JSON_Value_Type type);
|
||||
|
||||
int json_object_dothas_value(const JSON_Object* object, const char* name);
|
||||
int json_object_dothas_value_of_type(const JSON_Object* object, const char* name, JSON_Value_Type type);
|
||||
|
||||
/* Creates new name-value pair or frees and replaces old value with a new one.
|
||||
* json_object_set_value does not copy passed value so it shouldn't be freed afterwards. */
|
||||
JSON_Status json_object_set_value(JSON_Object* object, const char* name, JSON_Value* value);
|
||||
JSON_Status json_object_set_string(JSON_Object* object, const char* name, const char* string);
|
||||
JSON_Status json_object_set_string_with_len(JSON_Object* object, const char* name, const char* string, size_t len); /* length shouldn't include last null character */
|
||||
JSON_Status json_object_set_number(JSON_Object* object, const char* name, double number);
|
||||
JSON_Status json_object_set_boolean(JSON_Object* object, const char* name, int boolean);
|
||||
JSON_Status json_object_set_null(JSON_Object* object, const char* name);
|
||||
|
||||
/* Works like dotget functions, but creates whole hierarchy if necessary.
|
||||
* json_object_dotset_value does not copy passed value so it shouldn't be freed afterwards. */
|
||||
JSON_Status json_object_dotset_value(JSON_Object* object, const char* name, JSON_Value* value);
|
||||
JSON_Status json_object_dotset_string(JSON_Object* object, const char* name, const char* string);
|
||||
JSON_Status json_object_dotset_string_with_len(JSON_Object* object, const char* name, const char* string, size_t len); /* length shouldn't include last null character */
|
||||
JSON_Status json_object_dotset_number(JSON_Object* object, const char* name, double number);
|
||||
JSON_Status json_object_dotset_boolean(JSON_Object* object, const char* name, int boolean);
|
||||
JSON_Status json_object_dotset_null(JSON_Object* object, const char* name);
|
||||
|
||||
/* Frees and removes name-value pair */
|
||||
JSON_Status json_object_remove(JSON_Object* object, const char* name);
|
||||
|
||||
/* Works like dotget function, but removes name-value pair only on exact match. */
|
||||
JSON_Status json_object_dotremove(JSON_Object* object, const char* key);
|
||||
|
||||
/* Removes all name-value pairs in object */
|
||||
JSON_Status json_object_clear(JSON_Object* object);
|
||||
|
||||
/*
|
||||
*JSON Array
|
||||
*/
|
||||
JSON_Value* json_array_get_value(const JSON_Array* array, size_t index);
|
||||
const char* json_array_get_string(const JSON_Array* array, size_t index);
|
||||
size_t json_array_get_string_len(const JSON_Array* array, size_t index); /* doesn't account for last null character */
|
||||
JSON_Object* json_array_get_object(const JSON_Array* array, size_t index);
|
||||
JSON_Array* json_array_get_array(const JSON_Array* array, size_t index);
|
||||
double json_array_get_number(const JSON_Array* array, size_t index); /* returns 0 on fail */
|
||||
int json_array_get_boolean(const JSON_Array* array, size_t index); /* returns -1 on fail */
|
||||
size_t json_array_get_count(const JSON_Array* array);
|
||||
JSON_Value* json_array_get_wrapping_value(const JSON_Array* array);
|
||||
|
||||
/* Frees and removes value at given index, does nothing and returns JSONFailure if index doesn't exist.
|
||||
* Order of values in array may change during execution. */
|
||||
JSON_Status json_array_remove(JSON_Array* array, size_t i);
|
||||
|
||||
/* Frees and removes from array value at given index and replaces it with given one.
|
||||
* Does nothing and returns JSONFailure if index doesn't exist.
|
||||
* json_array_replace_value does not copy passed value so it shouldn't be freed afterwards. */
|
||||
JSON_Status json_array_replace_value(JSON_Array* array, size_t i, JSON_Value* value);
|
||||
JSON_Status json_array_replace_string(JSON_Array* array, size_t i, const char* string);
|
||||
JSON_Status json_array_replace_string_with_len(JSON_Array* array, size_t i, const char* string, size_t len); /* length shouldn't include last null character */
|
||||
JSON_Status json_array_replace_number(JSON_Array* array, size_t i, double number);
|
||||
JSON_Status json_array_replace_boolean(JSON_Array* array, size_t i, int boolean);
|
||||
JSON_Status json_array_replace_null(JSON_Array* array, size_t i);
|
||||
|
||||
/* Frees and removes all values from array */
|
||||
JSON_Status json_array_clear(JSON_Array* array);
|
||||
|
||||
/* Appends new value at the end of array.
|
||||
* json_array_append_value does not copy passed value so it shouldn't be freed afterwards. */
|
||||
JSON_Status json_array_append_value(JSON_Array* array, JSON_Value* value);
|
||||
JSON_Status json_array_append_string(JSON_Array* array, const char* string);
|
||||
JSON_Status json_array_append_string_with_len(JSON_Array* array, const char* string, size_t len); /* length shouldn't include last null character */
|
||||
JSON_Status json_array_append_number(JSON_Array* array, double number);
|
||||
JSON_Status json_array_append_boolean(JSON_Array* array, int boolean);
|
||||
JSON_Status json_array_append_null(JSON_Array* array);
|
||||
|
||||
/*
|
||||
*JSON Value
|
||||
*/
|
||||
JSON_Value* json_value_init_object(void);
|
||||
JSON_Value* json_value_init_array(void);
|
||||
JSON_Value* json_value_init_string(const char* string); /* copies passed string */
|
||||
JSON_Value* json_value_init_string_with_len(const char* string, size_t length); /* copies passed string, length shouldn't include last null character */
|
||||
JSON_Value* json_value_init_number(double number);
|
||||
JSON_Value* json_value_init_boolean(int boolean);
|
||||
JSON_Value* json_value_init_null(void);
|
||||
JSON_Value* json_value_deep_copy(const JSON_Value* value);
|
||||
void json_value_free(JSON_Value* value);
|
||||
|
||||
JSON_Value_Type json_value_get_type(const JSON_Value* value);
|
||||
JSON_Object* json_value_get_object(const JSON_Value* value);
|
||||
JSON_Array* json_value_get_array(const JSON_Value* value);
|
||||
const char* json_value_get_string(const JSON_Value* value);
|
||||
size_t json_value_get_string_len(const JSON_Value* value); /* doesn't account for last null character */
|
||||
double json_value_get_number(const JSON_Value* value);
|
||||
int json_value_get_boolean(const JSON_Value* value);
|
||||
JSON_Value* json_value_get_parent(const JSON_Value* value);
|
||||
|
||||
/* Same as above, but shorter */
|
||||
JSON_Value_Type json_type(const JSON_Value* value);
|
||||
JSON_Object* json_object(const JSON_Value* value);
|
||||
JSON_Array* json_array(const JSON_Value* value);
|
||||
const char* json_string(const JSON_Value* value);
|
||||
size_t json_string_len(const JSON_Value* value); /* doesn't account for last null character */
|
||||
double json_number(const JSON_Value* value);
|
||||
int json_boolean(const JSON_Value* value);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,220 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <esp_http_server.h>
|
||||
#include <freertos/task.h>
|
||||
#include <esp_ota_ops.h>
|
||||
#include <esp_system.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <sys/param.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include "esp_spiffs.h"
|
||||
#include "spi_flash_mmap.h"
|
||||
#include "parson.h"
|
||||
|
||||
static httpd_handle_t server = NULL;
|
||||
static bool disable_esp_async = false; // for example while in file transfer mode, don't send anything else
|
||||
|
||||
extern const char index_start[] asm("_binary_index_html_start");
|
||||
extern const char index_end[] asm("_binary_index_html_end");
|
||||
|
||||
void handle_start_attack(const char* attack_type, JSON_Object* params) {
|
||||
ESP_LOGI("WEB", "Handling 'start_attack': %s", attack_type);
|
||||
if (strcmp(attack_type, "pos_poison") == 0 && params != NULL) {
|
||||
double min_lat = json_object_get_number(params, "min_lat");
|
||||
double max_lat = json_object_get_number(params, "max_lat");
|
||||
// ... get other params
|
||||
ESP_LOGI("WEB", "Position Poison Params: min_lat=%.2f, max_lat=%.2f", min_lat, max_lat);
|
||||
// TODO: Your logic here
|
||||
}
|
||||
}
|
||||
|
||||
void handle_set_config(JSON_Object* params) {
|
||||
ESP_LOGI("WEB", "Handling 'set_config'");
|
||||
double frequency = json_object_get_number(params, "frequency");
|
||||
double bandwidth = json_object_get_number(params, "bandwidth");
|
||||
double sf = json_object_get_number(params, "spreading_factor"); // Numbers are doubles in parson
|
||||
// ... get other params
|
||||
ESP_LOGI("WEB", "Config: Freq=%.1f, BW=%.1f, SF=%.0f", frequency, bandwidth, sf);
|
||||
// TODO: Your logic here
|
||||
}
|
||||
// ... other handlers
|
||||
void handle_stop_attack() { /* ... */ }
|
||||
void handle_send_message(const char* source_id, const char* message) { /* ... */ }
|
||||
|
||||
static int hex_to_int(char c) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
}
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
if (c >= 'A' && c <= 'F') {
|
||||
return c - 'A' + 10;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Function to decode a URL-encoded string
|
||||
std::string url_decode(const std::string encoded_string) {
|
||||
std::string decoded_string;
|
||||
for (size_t i = 0; i < encoded_string.length(); ++i) {
|
||||
if (encoded_string[i] == '%' && i + 2 < encoded_string.length() && isxdigit(encoded_string[i + 1]) && isxdigit(encoded_string[i + 2])) {
|
||||
int high = hex_to_int(encoded_string[++i]);
|
||||
int low = hex_to_int(encoded_string[++i]);
|
||||
decoded_string += static_cast<char>((high << 4) | low);
|
||||
} else if (encoded_string[i] == '+') {
|
||||
decoded_string += ' ';
|
||||
} else {
|
||||
decoded_string += encoded_string[i];
|
||||
}
|
||||
}
|
||||
return decoded_string;
|
||||
}
|
||||
|
||||
// root / get handler.
|
||||
static esp_err_t get_req_handler(httpd_req_t* req) {
|
||||
const uint32_t index_len = index_end - index_start;
|
||||
int response = httpd_resp_send(req, index_start, index_len);
|
||||
return response;
|
||||
}
|
||||
|
||||
// send data to all ws clients.
|
||||
bool ws_sendall(uint8_t* data, size_t len, bool asyncmsg = false) {
|
||||
if (disable_esp_async && asyncmsg) {
|
||||
return false;
|
||||
}
|
||||
httpd_ws_frame_t ws_pkt;
|
||||
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||
ws_pkt.payload = (uint8_t*)data;
|
||||
ws_pkt.len = len;
|
||||
ws_pkt.type = HTTPD_WS_TYPE_BINARY;
|
||||
|
||||
static size_t max_clients = CONFIG_LWIP_MAX_LISTENING_TCP;
|
||||
size_t fds = max_clients;
|
||||
int client_fds[max_clients];
|
||||
|
||||
esp_err_t ret = httpd_get_client_list(server, &fds, client_fds);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < fds; i++) {
|
||||
int client_info = httpd_ws_get_fd_info(server, client_fds[i]);
|
||||
if (client_info == HTTPD_WS_CLIENT_WEBSOCKET) {
|
||||
httpd_ws_send_frame_async(server, client_fds[i], &ws_pkt);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// websocket handler
|
||||
static esp_err_t handle_ws_req(httpd_req_t* req) {
|
||||
if (req->method == HTTP_GET) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
httpd_ws_frame_t ws_pkt;
|
||||
uint8_t* buf = NULL;
|
||||
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
|
||||
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE("WEBS", "httpd_ws_recv_frame failed to get frame len with %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ws_pkt.len) {
|
||||
buf = (uint8_t*)calloc(1, ws_pkt.len + 1);
|
||||
if (buf == NULL) {
|
||||
ESP_LOGE("WEBS", "Failed to calloc memory for buf");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
ws_pkt.payload = buf;
|
||||
ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE("WEB", "httpd_ws_recv_frame failed with %d", ret);
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSON_Value* root_value = json_parse_string((const char*)buf);
|
||||
if (root_value == NULL) {
|
||||
ESP_LOGE("WEB", "Error parsing JSON.");
|
||||
free(buf);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
JSON_Object* root_object = json_value_get_object(root_value);
|
||||
const char* action = json_object_get_string(root_object, "action");
|
||||
|
||||
if (action) {
|
||||
ESP_LOGI("WEB", "Action found: %s", action);
|
||||
|
||||
if (strcmp(action, "start_attack") == 0) {
|
||||
const char* attack_type = json_object_get_string(root_object, "attack_type");
|
||||
JSON_Object* params = json_object_get_object(root_object, "params");
|
||||
if (attack_type) {
|
||||
handle_start_attack(attack_type, params);
|
||||
}
|
||||
} else if (strcmp(action, "stop_attack") == 0) {
|
||||
handle_stop_attack();
|
||||
} else if (strcmp(action, "send_message") == 0) {
|
||||
const char* source_id = json_object_get_string(root_object, "source_id");
|
||||
const char* message = json_object_get_string(root_object, "message");
|
||||
if (source_id && message) {
|
||||
handle_send_message(source_id, message);
|
||||
}
|
||||
} else if (strcmp(action, "set_config") == 0) {
|
||||
JSON_Object* params = json_object_get_object(root_object, "params");
|
||||
if (params) {
|
||||
handle_set_config(params);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE("WEB", "Could not find 'action' in JSON.");
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
json_value_free(root_value);
|
||||
free(buf);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// config web server part
|
||||
static httpd_handle_t setup_websocket_server(void) {
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.max_open_sockets = 10;
|
||||
|
||||
httpd_uri_t uri_get = {.uri = "/",
|
||||
.method = HTTP_GET,
|
||||
.handler = get_req_handler,
|
||||
.user_ctx = NULL,
|
||||
.is_websocket = false,
|
||||
.handle_ws_control_frames = false,
|
||||
.supported_subprotocol = NULL};
|
||||
|
||||
httpd_uri_t ws = {.uri = "/ws",
|
||||
.method = HTTP_GET,
|
||||
.handler = handle_ws_req,
|
||||
.user_ctx = NULL,
|
||||
.is_websocket = true,
|
||||
.handle_ws_control_frames = false,
|
||||
.supported_subprotocol = NULL};
|
||||
|
||||
if (httpd_start(&server, &config) == ESP_OK) {
|
||||
httpd_register_uri_handler(server, &uri_get);
|
||||
httpd_register_uri_handler(server, &ws);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
// start web server, set it up,..
|
||||
extern "C" void init_httpd() {
|
||||
server = setup_websocket_server();
|
||||
}
|
||||
+5
-1
@@ -1 +1,5 @@
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000
|
||||
CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH=y
|
||||
CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
|
||||
CONFIG_HTTPD_MAX_URI_LEN=1024
|
||||
CONFIG_HTTPD_WS_SUPPORT=y
|
||||
Reference in new issue
Block a user