mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2026-11-13 12:37:33 +00:00
808 lines
27 KiB
C
808 lines
27 KiB
C
#include <furi.h>
|
|
#include <gui/gui.h>
|
|
#include <network/network.h>
|
|
#include <string.h>
|
|
|
|
/* Multi-protocol ping over the companion Network bridge.
|
|
*
|
|
* Left/Right switches the probe protocol, OK restarts, Back exits:
|
|
* - DNS/UDP: the firmware builds a real RFC 1035 A query, sends it over a
|
|
* UDP socket and parses the reply (RTT, answer address, record TTL).
|
|
* - TCP+HTTP: each probe opens its own TCP connection (SYN RTT), then a raw
|
|
* "HEAD / HTTP/1.1" written with network_send() and timed to the first
|
|
* response byte.
|
|
* - HTTPS: HEAD request through the companion HTTP API (DNS+TCP+TLS+HTTP).
|
|
*
|
|
* Network API used: network_connect() (TCP and UDP), network_send(),
|
|
* network_close(), network_http_request(), network_set_event_callback().
|
|
* Connection ids are client-assigned; this app never reuses an id after
|
|
* closing it (see id_counter).
|
|
*/
|
|
|
|
#define PROBE_COUNT 4
|
|
#define PROBE_INTERVAL_MS 1000
|
|
#define PROBE_TIMEOUT_MS 3000
|
|
#define HTTPS_TIMEOUT_MS 8000
|
|
|
|
#define TARGET_NAME "one.one.one.one"
|
|
#define DNS_SERVER "1.1.1.1"
|
|
#define DNS_PORT 53
|
|
#define TCP_PORT 80
|
|
#define HTTPS_URL "https://one.one.one.one/"
|
|
|
|
#define CONNECTION_ID_BASE 0x50
|
|
|
|
static const char TCP_HEAD_REQUEST[] = "HEAD / HTTP/1.1\r\n"
|
|
"Host: " TARGET_NAME "\r\n"
|
|
"User-Agent: Flipper-NetPing\r\n"
|
|
"Connection: close\r\n\r\n";
|
|
|
|
typedef enum {
|
|
ProbeModeDns,
|
|
ProbeModeTcp,
|
|
ProbeModeHttps,
|
|
ProbeModeCount,
|
|
} ProbeMode;
|
|
|
|
typedef enum {
|
|
RunStateIdle,
|
|
RunStateNoBridge,
|
|
RunStateOpening,
|
|
RunStateProbing,
|
|
RunStateDone,
|
|
RunStateError,
|
|
} RunState;
|
|
|
|
typedef struct {
|
|
bool done;
|
|
bool ok;
|
|
uint32_t time_ms;
|
|
uint32_t aux_ms;
|
|
size_t rx_bytes;
|
|
NetworkError error;
|
|
char detail[40];
|
|
} ProbeResult;
|
|
|
|
typedef struct {
|
|
FuriMutex* mutex;
|
|
|
|
ProbeMode mode;
|
|
RunState state;
|
|
NetworkError error;
|
|
|
|
uint32_t id_counter;
|
|
uint32_t cur_id;
|
|
|
|
uint32_t active_seq;
|
|
bool probe_active;
|
|
bool start_probe;
|
|
uint32_t next_probe_tick;
|
|
uint32_t started_tick;
|
|
|
|
uint16_t dns_txid;
|
|
bool socket_open;
|
|
|
|
bool send_tcp_request;
|
|
bool tcp_request_sent;
|
|
uint32_t tcp_syn_ms;
|
|
|
|
ProbeResult results[PROBE_COUNT];
|
|
uint32_t sent;
|
|
uint32_t received;
|
|
uint32_t min_ms;
|
|
uint32_t max_ms;
|
|
uint32_t total_ms;
|
|
char resolved_ip[48];
|
|
|
|
uint32_t close_ids[8];
|
|
uint32_t close_count;
|
|
} PingApp;
|
|
|
|
static size_t dns_build_query(uint8_t* buf, size_t cap, uint16_t txid, const char* name) {
|
|
size_t need = 12 + strlen(name) + 2 + 4;
|
|
if(cap < need) return 0;
|
|
|
|
memset(buf, 0, 12);
|
|
buf[0] = txid >> 8;
|
|
buf[1] = txid & 0xFF;
|
|
buf[2] = 0x01; /* recursion desired */
|
|
buf[5] = 0x01; /* one question */
|
|
|
|
size_t pos = 12;
|
|
const char* label = name;
|
|
while(*label) {
|
|
const char* dot = strchr(label, '.');
|
|
size_t len = dot ? (size_t)(dot - label) : strlen(label);
|
|
if(len == 0 || len > 63) return 0;
|
|
buf[pos++] = len;
|
|
memcpy(&buf[pos], label, len);
|
|
pos += len;
|
|
label += len;
|
|
if(*label == '.') label++;
|
|
}
|
|
buf[pos++] = 0;
|
|
buf[pos++] = 0x00;
|
|
buf[pos++] = 0x01; /* QTYPE A */
|
|
buf[pos++] = 0x00;
|
|
buf[pos++] = 0x01; /* QCLASS IN */
|
|
return pos;
|
|
}
|
|
|
|
static bool dns_parse_response(
|
|
const uint8_t* data,
|
|
size_t size,
|
|
uint16_t txid,
|
|
char* ip,
|
|
size_t ip_size,
|
|
uint32_t* ttl) {
|
|
if(size < 12) return false;
|
|
if((((uint16_t)data[0] << 8) | data[1]) != txid) return false;
|
|
uint16_t flags = ((uint16_t)data[2] << 8) | data[3];
|
|
if((flags & 0x8000) == 0) return false; /* not a response */
|
|
if((flags & 0x000F) != 0) return false; /* RCODE != NOERROR */
|
|
uint16_t qdcount = ((uint16_t)data[4] << 8) | data[5];
|
|
uint16_t ancount = ((uint16_t)data[6] << 8) | data[7];
|
|
if(ancount == 0) return false;
|
|
|
|
size_t pos = 12;
|
|
for(uint16_t i = 0; i < qdcount; i++) {
|
|
while(pos < size && data[pos] != 0 && (data[pos] & 0xC0) != 0xC0) {
|
|
pos += (size_t)data[pos] + 1;
|
|
}
|
|
if(pos >= size) return false;
|
|
pos += (data[pos] & 0xC0) == 0xC0 ? 2 : 1;
|
|
pos += 4; /* QTYPE + QCLASS */
|
|
}
|
|
|
|
for(uint16_t i = 0; i < ancount; i++) {
|
|
if(pos >= size) return false;
|
|
if((data[pos] & 0xC0) == 0xC0) {
|
|
pos += 2;
|
|
} else {
|
|
while(pos < size && data[pos] != 0) {
|
|
pos += (size_t)data[pos] + 1;
|
|
}
|
|
pos++;
|
|
}
|
|
if(pos + 10 > size) return false;
|
|
uint16_t type = ((uint16_t)data[pos] << 8) | data[pos + 1];
|
|
uint16_t dns_class = ((uint16_t)data[pos + 2] << 8) | data[pos + 3];
|
|
uint32_t rec_ttl = ((uint32_t)data[pos + 4] << 24) | ((uint32_t)data[pos + 5] << 16) |
|
|
((uint32_t)data[pos + 6] << 8) | data[pos + 7];
|
|
uint16_t rdlen = ((uint16_t)data[pos + 8] << 8) | data[pos + 9];
|
|
pos += 10;
|
|
if(pos + rdlen > size) return false;
|
|
if(type == 1 && dns_class == 1 && rdlen == 4) {
|
|
snprintf(
|
|
ip, ip_size, "%u.%u.%u.%u", data[pos], data[pos + 1], data[pos + 2], data[pos + 3]);
|
|
if(ttl) *ttl = rec_ttl;
|
|
return true;
|
|
}
|
|
pos += rdlen;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static uint32_t http_parse_status(const uint8_t* data, size_t size) {
|
|
if(size < 12) return 0;
|
|
if(memcmp(data, "HTTP/1.", 7) != 0) return 0;
|
|
uint32_t status = 0;
|
|
for(size_t i = 9; i < 12; i++) {
|
|
if(data[i] < '0' || data[i] > '9') return 0;
|
|
status = status * 10 + (data[i] - '0');
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/* Helpers below expect app->mutex to be held by the caller. */
|
|
|
|
static void ping_push_close(PingApp* app, uint32_t id) {
|
|
if(id == 0) return;
|
|
if(app->close_count < COUNT_OF(app->close_ids)) {
|
|
app->close_ids[app->close_count++] = id;
|
|
}
|
|
}
|
|
|
|
static void ping_record(
|
|
PingApp* app,
|
|
bool ok,
|
|
uint32_t time_ms,
|
|
uint32_t aux_ms,
|
|
size_t rx_bytes,
|
|
NetworkError error,
|
|
const char* detail) {
|
|
if(app->active_seq >= PROBE_COUNT) return;
|
|
ProbeResult* result = &app->results[app->active_seq];
|
|
result->done = true;
|
|
result->ok = ok;
|
|
result->time_ms = time_ms ? time_ms : 1;
|
|
result->aux_ms = aux_ms;
|
|
result->rx_bytes = rx_bytes;
|
|
result->error = error;
|
|
strlcpy(result->detail, detail, sizeof(result->detail));
|
|
|
|
if(ok) {
|
|
app->received++;
|
|
app->total_ms += result->time_ms;
|
|
if(app->received == 1 || result->time_ms < app->min_ms) app->min_ms = result->time_ms;
|
|
if(result->time_ms > app->max_ms) app->max_ms = result->time_ms;
|
|
} else if(error != NetworkErrorNone) {
|
|
app->error = error;
|
|
}
|
|
}
|
|
|
|
static void ping_probe_finish(PingApp* app) {
|
|
app->probe_active = false;
|
|
app->send_tcp_request = false;
|
|
if(app->mode == ProbeModeTcp) {
|
|
ping_push_close(app, app->cur_id);
|
|
}
|
|
app->active_seq++;
|
|
app->start_probe = app->active_seq < PROBE_COUNT;
|
|
if(app->active_seq >= PROBE_COUNT) {
|
|
app->state = app->received ? RunStateDone : RunStateError;
|
|
if(!app->received && app->error == NetworkErrorNone) app->error = NetworkErrorTimeout;
|
|
if(app->socket_open) {
|
|
ping_push_close(app, app->cur_id);
|
|
app->socket_open = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ping_handle_dns_event(PingApp* app, const NetworkEvent* event) {
|
|
switch(event->type) {
|
|
/* Answer to network_connect(): event->state/error report the outcome,
|
|
* event->resolved_ip (not used here) carries the peer address. */
|
|
case NetworkEventConnected:
|
|
if(app->state == RunStateOpening) {
|
|
if(event->error == NetworkErrorNone && event->state == NetworkStateConnected) {
|
|
app->state = RunStateProbing;
|
|
app->start_probe = true;
|
|
app->next_probe_tick = furi_get_tick();
|
|
} else {
|
|
app->state = RunStateError;
|
|
app->error = event->error != NetworkErrorNone ? event->error :
|
|
NetworkErrorInternal;
|
|
ping_push_close(app, app->cur_id);
|
|
app->socket_open = false;
|
|
}
|
|
}
|
|
break;
|
|
case NetworkEventReceived:
|
|
if(app->probe_active && event->data && event->size) {
|
|
char ip[16];
|
|
uint32_t ttl = 0;
|
|
if(dns_parse_response(event->data, event->size, app->dns_txid, ip, sizeof(ip), &ttl)) {
|
|
char detail[40];
|
|
snprintf(detail, sizeof(detail), "A %s ttl=%lus", ip, (unsigned long)ttl);
|
|
strlcpy(app->resolved_ip, ip, sizeof(app->resolved_ip));
|
|
ping_record(
|
|
app,
|
|
true,
|
|
furi_get_tick() - app->started_tick,
|
|
0,
|
|
event->size,
|
|
NetworkErrorNone,
|
|
detail);
|
|
ping_probe_finish(app);
|
|
}
|
|
/* Mismatched txid: a late reply to a lost probe, keep waiting. */
|
|
}
|
|
break;
|
|
/* Ack for network_send(): only interesting when the datagram failed. */
|
|
case NetworkEventSent:
|
|
if(app->probe_active && event->error != NetworkErrorNone) {
|
|
ping_record(app, false, 0, 0, 0, event->error, "");
|
|
ping_probe_finish(app);
|
|
}
|
|
break;
|
|
/* Socket died (peer/companion side) or close was acknowledged. */
|
|
case NetworkEventStateChanged:
|
|
case NetworkEventClosed:
|
|
if(app->state == RunStateProbing && network_state_is_terminal(event->state)) {
|
|
NetworkError error = event->error != NetworkErrorNone ? event->error :
|
|
NetworkErrorNotConnected;
|
|
if(app->probe_active) {
|
|
ping_record(app, false, 0, 0, 0, error, "");
|
|
}
|
|
app->probe_active = false;
|
|
app->start_probe = false;
|
|
app->state = RunStateError;
|
|
app->error = error;
|
|
app->socket_open = false;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void ping_handle_tcp_event(PingApp* app, const NetworkEvent* event) {
|
|
if(!app->probe_active) return;
|
|
|
|
switch(event->type) {
|
|
case NetworkEventConnected:
|
|
if(!app->tcp_request_sent) {
|
|
if(event->error == NetworkErrorNone && event->state == NetworkStateConnected) {
|
|
app->tcp_syn_ms = furi_get_tick() - app->started_tick;
|
|
if(event->resolved_ip && event->resolved_ip[0]) {
|
|
strlcpy(app->resolved_ip, event->resolved_ip, sizeof(app->resolved_ip));
|
|
}
|
|
app->send_tcp_request = true;
|
|
} else {
|
|
ping_record(app, false, 0, 0, 0, event->error, "");
|
|
ping_probe_finish(app);
|
|
}
|
|
}
|
|
break;
|
|
case NetworkEventReceived:
|
|
/* First chunk of the response stops the clock; trailing chunks arrive
|
|
* with probe_active already false and are dropped. */
|
|
if(app->tcp_request_sent && event->data && event->size) {
|
|
char detail[40];
|
|
uint32_t status = http_parse_status(event->data, event->size);
|
|
if(status) {
|
|
snprintf(
|
|
detail,
|
|
sizeof(detail),
|
|
"HTTP %lu %.21s",
|
|
(unsigned long)status,
|
|
app->resolved_ip[0] ? app->resolved_ip : TARGET_NAME);
|
|
} else {
|
|
snprintf(
|
|
detail,
|
|
sizeof(detail),
|
|
"raw %.21s",
|
|
app->resolved_ip[0] ? app->resolved_ip : TARGET_NAME);
|
|
}
|
|
ping_record(
|
|
app,
|
|
true,
|
|
furi_get_tick() - app->started_tick,
|
|
app->tcp_syn_ms,
|
|
event->size,
|
|
NetworkErrorNone,
|
|
detail);
|
|
ping_probe_finish(app);
|
|
}
|
|
break;
|
|
case NetworkEventSent:
|
|
if(event->error != NetworkErrorNone) {
|
|
ping_record(app, false, 0, 0, 0, event->error, "");
|
|
ping_probe_finish(app);
|
|
}
|
|
break;
|
|
case NetworkEventStateChanged:
|
|
case NetworkEventClosed:
|
|
if(network_state_is_terminal(event->state)) {
|
|
NetworkError error = event->error != NetworkErrorNone ? event->error :
|
|
NetworkErrorNotConnected;
|
|
ping_record(app, false, 0, 0, 0, error, "");
|
|
ping_probe_finish(app);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void ping_handle_https_event(PingApp* app, const NetworkEvent* event) {
|
|
if(!app->probe_active) return;
|
|
/* HTTP requests produce exactly one terminal event; with no save_path the
|
|
* body would also stream in as NetworkEventReceived (ignored here). */
|
|
if(event->type != NetworkEventHttpResponse) return;
|
|
|
|
if(event->error == NetworkErrorNone) {
|
|
char detail[40];
|
|
snprintf(detail, sizeof(detail), "HTTP %lu via TLS", (unsigned long)event->http_status);
|
|
ping_record(
|
|
app,
|
|
true,
|
|
furi_get_tick() - app->started_tick,
|
|
0,
|
|
event->size,
|
|
NetworkErrorNone,
|
|
detail);
|
|
} else {
|
|
ping_record(app, false, 0, 0, 0, event->error, "");
|
|
}
|
|
ping_probe_finish(app);
|
|
}
|
|
|
|
/* Called from the RPC session thread; events for stale connection ids (closed
|
|
* probes, previous runs) are dropped by the cur_id check. The event payload is
|
|
* valid only for the duration of the call. */
|
|
static void ping_event_callback(const NetworkEvent* event, void* context) {
|
|
PingApp* app = context;
|
|
|
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
|
if(event->connection_id == app->cur_id) {
|
|
switch(app->mode) {
|
|
case ProbeModeDns:
|
|
ping_handle_dns_event(app, event);
|
|
break;
|
|
case ProbeModeTcp:
|
|
ping_handle_tcp_event(app, event);
|
|
break;
|
|
case ProbeModeHttps:
|
|
ping_handle_https_event(app, event);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
furi_mutex_release(app->mutex);
|
|
}
|
|
|
|
static void ping_start_run(PingApp* app, Network* network, ProbeMode mode) {
|
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
|
uint32_t stale_id =
|
|
app->socket_open || (app->mode == ProbeModeTcp && app->probe_active) ? app->cur_id : 0;
|
|
app->mode = mode;
|
|
app->error = NetworkErrorNone;
|
|
memset(app->results, 0, sizeof(app->results));
|
|
app->active_seq = 0;
|
|
app->probe_active = false;
|
|
app->send_tcp_request = false;
|
|
app->tcp_request_sent = false;
|
|
app->sent = 0;
|
|
app->received = 0;
|
|
app->min_ms = 0;
|
|
app->max_ms = 0;
|
|
app->total_ms = 0;
|
|
app->tcp_syn_ms = 0;
|
|
app->resolved_ip[0] = '\0';
|
|
app->socket_open = false;
|
|
app->cur_id = ++app->id_counter;
|
|
uint32_t run_id = app->cur_id;
|
|
|
|
if(mode == ProbeModeDns) {
|
|
app->state = RunStateOpening;
|
|
app->start_probe = false;
|
|
app->started_tick = furi_get_tick();
|
|
} else {
|
|
app->state = RunStateProbing;
|
|
app->start_probe = true;
|
|
app->next_probe_tick = furi_get_tick();
|
|
}
|
|
furi_mutex_release(app->mutex);
|
|
|
|
if(stale_id) network_close(network, stale_id);
|
|
|
|
if(mode == ProbeModeDns) {
|
|
/* One UDP socket for the whole run. The call only queues the request:
|
|
* the outcome comes back as NetworkEventConnected. */
|
|
bool opened = network_connect(
|
|
network, run_id, DNS_SERVER, DNS_PORT, NetworkProtocolUdp, PROBE_TIMEOUT_MS);
|
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
|
if(opened) {
|
|
app->socket_open = true;
|
|
} else {
|
|
app->state = RunStateNoBridge;
|
|
}
|
|
furi_mutex_release(app->mutex);
|
|
}
|
|
}
|
|
|
|
static void ping_process(PingApp* app, Network* network) {
|
|
uint32_t closes[COUNT_OF(app->close_ids)];
|
|
uint32_t close_count;
|
|
bool dns_send = false;
|
|
bool head_send = false;
|
|
bool tcp_connect = false;
|
|
bool https_request = false;
|
|
uint32_t action_id = 0;
|
|
uint16_t txid = 0;
|
|
|
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
|
uint32_t now = furi_get_tick();
|
|
|
|
if(app->state == RunStateOpening &&
|
|
now - app->started_tick > furi_ms_to_ticks(PROBE_TIMEOUT_MS)) {
|
|
app->state = RunStateError;
|
|
app->error = NetworkErrorTimeout;
|
|
if(app->socket_open) {
|
|
ping_push_close(app, app->cur_id);
|
|
app->socket_open = false;
|
|
}
|
|
}
|
|
|
|
if(app->probe_active) {
|
|
uint32_t timeout_ms = app->mode == ProbeModeHttps ? HTTPS_TIMEOUT_MS : PROBE_TIMEOUT_MS;
|
|
if(now - app->started_tick > furi_ms_to_ticks(timeout_ms)) {
|
|
ping_record(app, false, 0, 0, 0, NetworkErrorTimeout, "");
|
|
ping_probe_finish(app);
|
|
}
|
|
}
|
|
|
|
if(app->send_tcp_request && app->probe_active) {
|
|
app->send_tcp_request = false;
|
|
app->tcp_request_sent = true;
|
|
head_send = true;
|
|
action_id = app->cur_id;
|
|
}
|
|
|
|
bool launch = app->state == RunStateProbing && app->start_probe && !app->probe_active &&
|
|
app->active_seq < PROBE_COUNT && now >= app->next_probe_tick;
|
|
if(launch) {
|
|
app->start_probe = false;
|
|
app->probe_active = true;
|
|
app->sent++;
|
|
app->started_tick = now;
|
|
app->next_probe_tick = now + furi_ms_to_ticks(PROBE_INTERVAL_MS);
|
|
app->tcp_request_sent = false;
|
|
app->tcp_syn_ms = 0;
|
|
if(app->mode == ProbeModeDns) {
|
|
app->dns_txid = (uint16_t)((now << 4) | app->active_seq);
|
|
dns_send = true;
|
|
action_id = app->cur_id;
|
|
txid = app->dns_txid;
|
|
} else {
|
|
app->cur_id = ++app->id_counter;
|
|
action_id = app->cur_id;
|
|
if(app->mode == ProbeModeTcp) {
|
|
tcp_connect = true;
|
|
} else {
|
|
https_request = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
close_count = app->close_count;
|
|
memcpy(closes, app->close_ids, close_count * sizeof(uint32_t));
|
|
app->close_count = 0;
|
|
furi_mutex_release(app->mutex);
|
|
|
|
/* Fire-and-forget: the companion confirms each close with
|
|
* NetworkEventClosed. */
|
|
for(uint32_t i = 0; i < close_count; i++) {
|
|
network_close(network, closes[i]);
|
|
}
|
|
|
|
/* Every network_* call below returns false only when there is no live
|
|
* companion session; probe results arrive via ping_event_callback. */
|
|
bool bridge_ok = true;
|
|
if(dns_send) {
|
|
/* One datagram per probe over the open UDP socket
|
|
* (payload <= NETWORK_MAX_DATA_SIZE); reply = NetworkEventReceived. */
|
|
uint8_t query[64];
|
|
size_t query_size = dns_build_query(query, sizeof(query), txid, TARGET_NAME);
|
|
bridge_ok = query_size && network_send(network, action_id, query, query_size);
|
|
} else if(head_send) {
|
|
/* Raw bytes over the connected TCP socket - the service does not
|
|
* interpret them, HTTP framing is this app's own. */
|
|
bridge_ok = network_send(
|
|
network, action_id, (const uint8_t*)TCP_HEAD_REQUEST, strlen(TCP_HEAD_REQUEST));
|
|
} else if(tcp_connect) {
|
|
/* Fresh client-assigned id per probe: each probe is its own TCP
|
|
* connection, timed from here to NetworkEventConnected. */
|
|
bridge_ok = network_connect(
|
|
network, action_id, TARGET_NAME, TCP_PORT, NetworkProtocolTcp, PROBE_TIMEOUT_MS);
|
|
} else if(https_request) {
|
|
/* Full HTTP stack on the companion; concludes with
|
|
* NetworkEventHttpResponse for this request id. */
|
|
const NetworkHttpRequest request = {
|
|
.method = NetworkHttpMethodHead,
|
|
.url = HTTPS_URL,
|
|
.timeout_ms = HTTPS_TIMEOUT_MS,
|
|
};
|
|
bridge_ok = network_http_request(network, action_id, &request);
|
|
}
|
|
|
|
if(!bridge_ok) {
|
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
|
app->state = RunStateNoBridge;
|
|
app->probe_active = false;
|
|
app->start_probe = false;
|
|
app->socket_open = false;
|
|
furi_mutex_release(app->mutex);
|
|
}
|
|
}
|
|
|
|
static const char* ping_mode_title(ProbeMode mode) {
|
|
switch(mode) {
|
|
case ProbeModeDns:
|
|
return "< DNS/UDP 1.1.1.1:53 >";
|
|
case ProbeModeTcp:
|
|
return "< TCP + raw HTTP :80 >";
|
|
case ProbeModeHttps:
|
|
return "< HTTPS request >";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
static void ping_render_callback(Canvas* canvas, void* context) {
|
|
PingApp* app = context;
|
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
|
|
|
char line[64];
|
|
canvas_set_font(canvas, FontSecondary);
|
|
canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignBottom, ping_mode_title(app->mode));
|
|
|
|
uint32_t cursor = app->active_seq;
|
|
if(app->probe_active && cursor < PROBE_COUNT) {
|
|
cursor++;
|
|
}
|
|
uint32_t first = cursor > 2 ? cursor - 2 : 0;
|
|
uint32_t visible = cursor - first;
|
|
if(visible > 2) visible = 2;
|
|
|
|
for(uint32_t slot = 0; slot < visible; slot++) {
|
|
uint32_t i = first + slot;
|
|
const ProbeResult* result = &app->results[i];
|
|
uint8_t y = 21 + slot * 20;
|
|
|
|
if(result->done && result->ok) {
|
|
switch(app->mode) {
|
|
case ProbeModeTcp:
|
|
snprintf(
|
|
line,
|
|
sizeof(line),
|
|
"seq=%lu syn=%lu http=%lums",
|
|
(unsigned long)i,
|
|
(unsigned long)result->aux_ms,
|
|
(unsigned long)result->time_ms);
|
|
break;
|
|
case ProbeModeDns:
|
|
snprintf(
|
|
line,
|
|
sizeof(line),
|
|
"seq=%lu time=%lums rx=%uB",
|
|
(unsigned long)i,
|
|
(unsigned long)result->time_ms,
|
|
(unsigned)result->rx_bytes);
|
|
break;
|
|
default:
|
|
snprintf(
|
|
line,
|
|
sizeof(line),
|
|
"seq=%lu time=%lums",
|
|
(unsigned long)i,
|
|
(unsigned long)result->time_ms);
|
|
break;
|
|
}
|
|
canvas_draw_str(canvas, 0, y, line);
|
|
canvas_draw_str(canvas, 0, y + 9, result->detail);
|
|
} else if(result->done) {
|
|
snprintf(line, sizeof(line), "seq=%lu lost", (unsigned long)i);
|
|
canvas_draw_str(canvas, 0, y, line);
|
|
canvas_draw_str(canvas, 0, y + 9, network_error_to_string(result->error));
|
|
} else if(app->probe_active && app->active_seq == i) {
|
|
snprintf(line, sizeof(line), "seq=%lu", (unsigned long)i);
|
|
canvas_draw_str(canvas, 0, y, line);
|
|
if(app->mode == ProbeModeDns) {
|
|
snprintf(line, sizeof(line), "txid=0x%04X...", app->dns_txid);
|
|
} else if(app->mode == ProbeModeTcp) {
|
|
if(app->tcp_request_sent) {
|
|
snprintf(
|
|
line, sizeof(line), "syn=%lums wait rsp", (unsigned long)app->tcp_syn_ms);
|
|
} else {
|
|
snprintf(line, sizeof(line), "connecting...");
|
|
}
|
|
} else {
|
|
snprintf(line, sizeof(line), "TLS handshake...");
|
|
}
|
|
canvas_draw_str(canvas, 0, y + 9, line);
|
|
}
|
|
}
|
|
|
|
if(app->state == RunStateNoBridge) {
|
|
canvas_draw_str(canvas, 0, 63, "No USB/BLE connection");
|
|
} else if(app->state == RunStateOpening) {
|
|
canvas_draw_str(canvas, 0, 63, "Opening UDP socket...");
|
|
} else if(app->state == RunStateDone || app->state == RunStateError) {
|
|
uint32_t loss = ((PROBE_COUNT - app->received) * 100) / PROBE_COUNT;
|
|
if(app->received) {
|
|
snprintf(
|
|
line,
|
|
sizeof(line),
|
|
"%lu/%u rx %lu%% loss %lu/%lu/%lums",
|
|
(unsigned long)app->received,
|
|
PROBE_COUNT,
|
|
(unsigned long)loss,
|
|
(unsigned long)app->min_ms,
|
|
(unsigned long)(app->total_ms / app->received),
|
|
(unsigned long)app->max_ms);
|
|
} else {
|
|
snprintf(
|
|
line,
|
|
sizeof(line),
|
|
"0/%u rx: %s",
|
|
PROBE_COUNT,
|
|
network_error_to_string(app->error));
|
|
}
|
|
canvas_draw_str(canvas, 0, 63, line);
|
|
} else {
|
|
snprintf(
|
|
line,
|
|
sizeof(line),
|
|
"%.21s %lu/%u",
|
|
app->resolved_ip[0] ? app->resolved_ip : TARGET_NAME,
|
|
(unsigned long)app->sent,
|
|
PROBE_COUNT);
|
|
canvas_draw_str(canvas, 0, 63, line);
|
|
}
|
|
|
|
furi_mutex_release(app->mutex);
|
|
}
|
|
|
|
static void ping_input_callback(InputEvent* input_event, void* context) {
|
|
FuriMessageQueue* event_queue = context;
|
|
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
|
|
}
|
|
|
|
int32_t network_ping_app(void* p) {
|
|
UNUSED(p);
|
|
|
|
PingApp* app = malloc(sizeof(PingApp));
|
|
memset(app, 0, sizeof(PingApp));
|
|
app->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
|
app->state = RunStateIdle;
|
|
app->id_counter = CONNECTION_ID_BASE;
|
|
|
|
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
|
Network* network = furi_record_open(RECORD_NETWORK);
|
|
network_set_event_callback(network, ping_event_callback, app);
|
|
|
|
ViewPort* view_port = view_port_alloc();
|
|
view_port_draw_callback_set(view_port, ping_render_callback, app);
|
|
view_port_input_callback_set(view_port, ping_input_callback, event_queue);
|
|
|
|
Gui* gui = furi_record_open(RECORD_GUI);
|
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
|
|
|
ping_start_run(app, network, ProbeModeDns);
|
|
|
|
InputEvent event;
|
|
for(bool processing = true; processing;) {
|
|
if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk &&
|
|
event.type == InputTypeShort) {
|
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
|
ProbeMode mode = app->mode;
|
|
furi_mutex_release(app->mutex);
|
|
|
|
if(event.key == InputKeyBack) {
|
|
processing = false;
|
|
} else if(event.key == InputKeyOk) {
|
|
ping_start_run(app, network, mode);
|
|
} else if(event.key == InputKeyLeft || event.key == InputKeyRight) {
|
|
uint32_t step = event.key == InputKeyRight ? 1 : ProbeModeCount - 1;
|
|
ping_start_run(app, network, (ProbeMode)((mode + step) % ProbeModeCount));
|
|
}
|
|
}
|
|
|
|
ping_process(app, network);
|
|
view_port_update(view_port);
|
|
}
|
|
|
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
|
if(app->socket_open || app->probe_active) {
|
|
ping_push_close(app, app->cur_id);
|
|
app->socket_open = false;
|
|
}
|
|
uint32_t close_count = app->close_count;
|
|
uint32_t closes[COUNT_OF(app->close_ids)];
|
|
memcpy(closes, app->close_ids, close_count * sizeof(uint32_t));
|
|
app->close_count = 0;
|
|
furi_mutex_release(app->mutex);
|
|
for(uint32_t i = 0; i < close_count; i++) {
|
|
network_close(network, closes[i]);
|
|
}
|
|
|
|
/* Unsubscribe before closing the record so late events cannot reach a
|
|
* freed context. */
|
|
network_set_event_callback(network, NULL, NULL);
|
|
furi_record_close(RECORD_NETWORK);
|
|
|
|
view_port_enabled_set(view_port, false);
|
|
gui_remove_view_port(gui, view_port);
|
|
furi_record_close(RECORD_GUI);
|
|
view_port_free(view_port);
|
|
furi_message_queue_free(event_queue);
|
|
furi_mutex_free(app->mutex);
|
|
free(app);
|
|
|
|
return 0;
|
|
}
|