Add Transport-Neutral Serial Session Broker
This commit is contained in:
@@ -0,0 +1,447 @@
|
||||
#include "session_console.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_console.h"
|
||||
#include "esp_err.h"
|
||||
#include "session_broker.h"
|
||||
|
||||
#define CONSOLE_HEX_MAX_BYTES 48
|
||||
#define CONSOLE_READ_MAX_BYTES 512
|
||||
|
||||
static void print_usage(void)
|
||||
{
|
||||
printf("Usage:\n");
|
||||
printf(" broker status|clients|counters|clear-counters\n");
|
||||
printf(" broker connect <name>\n");
|
||||
printf(" broker disconnect <client-id>\n");
|
||||
printf(" broker request-writer <client-id>\n");
|
||||
printf(" broker release-writer <client-id>\n");
|
||||
printf(" broker force-writer <client-id|none>\n");
|
||||
printf(" broker send-hex <client-id> <hex-bytes>\n");
|
||||
printf(" broker read <client-id> [maximum-bytes]\n");
|
||||
printf(" broker events <client-id>\n");
|
||||
}
|
||||
|
||||
static bool parse_u32(const char *text, uint32_t minimum, uint32_t maximum, uint32_t *value)
|
||||
{
|
||||
if (text == NULL || *text == '\0') {
|
||||
return false;
|
||||
}
|
||||
for (const char *character = text; *character != '\0'; ++character) {
|
||||
if (*character < '0' || *character > '9') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
char *end = NULL;
|
||||
errno = 0;
|
||||
unsigned long parsed = strtoul(text, &end, 10);
|
||||
if (errno != 0 || end == text || *end != '\0' ||
|
||||
parsed < minimum || parsed > maximum) {
|
||||
return false;
|
||||
}
|
||||
*value = (uint32_t)parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_client_id(const char *text, session_broker_client_id_t *client_id)
|
||||
{
|
||||
uint32_t value;
|
||||
if (!parse_u32(text, 1, UINT32_MAX, &value)) {
|
||||
return false;
|
||||
}
|
||||
*client_id = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *client_type_name(session_broker_client_type_t type)
|
||||
{
|
||||
switch (type) {
|
||||
case SESSION_BROKER_CLIENT_CONSOLE:
|
||||
return "console";
|
||||
case SESSION_BROKER_CLIENT_USB:
|
||||
return "usb";
|
||||
case SESSION_BROKER_CLIENT_WEB:
|
||||
return "web";
|
||||
case SESSION_BROKER_CLIENT_SSH:
|
||||
return "ssh";
|
||||
case SESSION_BROKER_CLIENT_INTERNAL:
|
||||
return "internal";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *event_type_name(session_broker_event_type_t type)
|
||||
{
|
||||
switch (type) {
|
||||
case SESSION_BROKER_EVENT_CONNECT:
|
||||
return "connect";
|
||||
case SESSION_BROKER_EVENT_DISCONNECT:
|
||||
return "disconnect";
|
||||
case SESSION_BROKER_EVENT_WRITER_GRANTED:
|
||||
return "writer-granted";
|
||||
case SESSION_BROKER_EVENT_WRITER_RELEASED:
|
||||
return "writer-released";
|
||||
case SESSION_BROKER_EVENT_WRITER_REVOKED:
|
||||
return "writer-revoked";
|
||||
case SESSION_BROKER_EVENT_WRITER_DENIED:
|
||||
return "writer-denied";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static int show_status(void)
|
||||
{
|
||||
session_broker_global_snapshot_t snapshot;
|
||||
esp_err_t err = session_broker_get_global_snapshot(&snapshot);
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not read broker status: %s\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Broker clients=%" PRIu32 " writer=%lu latest-event=%" PRIu64 "\n",
|
||||
snapshot.connected_clients,
|
||||
(unsigned long)snapshot.writer_id,
|
||||
snapshot.latest_event_sequence);
|
||||
printf("UART-RX=%" PRIu64 " unobserved=%" PRIu64
|
||||
" observer-queued=%" PRIu64 " observer-read=%" PRIu64
|
||||
" observer-dropped=%" PRIu64 "\n",
|
||||
snapshot.counters.uart_rx_bytes,
|
||||
snapshot.counters.unobserved_rx_bytes,
|
||||
snapshot.counters.output_queued_bytes,
|
||||
snapshot.counters.output_read_bytes,
|
||||
snapshot.counters.output_dropped_bytes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int show_clients(void)
|
||||
{
|
||||
session_broker_client_snapshot_t clients[SESSION_BROKER_MAX_CLIENTS];
|
||||
size_t count = session_broker_list_clients(clients, SESSION_BROKER_MAX_CLIENTS);
|
||||
if (count == 0) {
|
||||
printf("No broker clients connected.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("ID type writer pending events name\n");
|
||||
for (size_t index = 0; index < count; ++index) {
|
||||
const session_broker_client_snapshot_t *client = &clients[index];
|
||||
printf("%-10lu %-9s %-6s %-7u %-6" PRIu32 " %s\n",
|
||||
(unsigned long)client->id,
|
||||
client_type_name(client->type),
|
||||
client->is_writer ? "yes" : "no",
|
||||
(unsigned int)client->output_bytes_pending,
|
||||
client->events_pending,
|
||||
client->name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int show_counters(void)
|
||||
{
|
||||
session_broker_global_snapshot_t snapshot;
|
||||
esp_err_t err = session_broker_get_global_snapshot(&snapshot);
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not read broker counters: %s\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
const session_broker_global_counters_t *counter = &snapshot.counters;
|
||||
printf("RX: UART=%" PRIu64 " unobserved=%" PRIu64
|
||||
" queued=%" PRIu64 " read=%" PRIu64 " dropped=%" PRIu64 "\n",
|
||||
counter->uart_rx_bytes,
|
||||
counter->unobserved_rx_bytes,
|
||||
counter->output_queued_bytes,
|
||||
counter->output_read_bytes,
|
||||
counter->output_dropped_bytes);
|
||||
printf("TX: accepted=%" PRIu64 " rejected=%" PRIu64 "\n",
|
||||
counter->tx_accepted_bytes,
|
||||
counter->tx_rejected_bytes);
|
||||
printf("Sessions: connect=%" PRIu64 " disconnect=%" PRIu64
|
||||
" writer-requests=%" PRIu64 " grants=%" PRIu64
|
||||
" releases=%" PRIu64 " revocations=%" PRIu64
|
||||
" denials=%" PRIu64 " changes=%" PRIu64 "\n",
|
||||
counter->connections,
|
||||
counter->disconnections,
|
||||
counter->writer_requests,
|
||||
counter->writer_grants,
|
||||
counter->writer_releases,
|
||||
counter->writer_revocations,
|
||||
counter->writer_denials,
|
||||
counter->writer_changes);
|
||||
printf("Events: generated=%" PRIu64 " queued=%" PRIu64
|
||||
" popped=%" PRIu64 " dropped=%" PRIu64 "\n",
|
||||
counter->events_generated,
|
||||
counter->events_queued,
|
||||
counter->events_popped,
|
||||
counter->event_drops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int connect_client(const char *name)
|
||||
{
|
||||
session_broker_client_id_t client_id;
|
||||
esp_err_t err = session_broker_connect(
|
||||
SESSION_BROKER_CLIENT_CONSOLE,
|
||||
name,
|
||||
&client_id);
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not connect broker client: %s\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
printf("Connected console client '%s' with ID %lu.\n", name, (unsigned long)client_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int disconnect_client(const char *id_text)
|
||||
{
|
||||
session_broker_client_id_t client_id;
|
||||
if (!parse_client_id(id_text, &client_id)) {
|
||||
printf("Invalid client ID.\n");
|
||||
return 1;
|
||||
}
|
||||
esp_err_t err = session_broker_disconnect(client_id);
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not disconnect client %lu: %s\n", (unsigned long)client_id, esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
printf("Disconnected client %lu.\n", (unsigned long)client_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int change_writer(const char *operation, const char *id_text)
|
||||
{
|
||||
session_broker_client_id_t client_id;
|
||||
if (!parse_client_id(id_text, &client_id)) {
|
||||
printf("Invalid client ID.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
esp_err_t err;
|
||||
if (strcmp(operation, "request-writer") == 0) {
|
||||
err = session_broker_request_writer(client_id);
|
||||
} else {
|
||||
err = session_broker_release_writer(client_id);
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
printf("Writer operation failed for client %lu: %s\n",
|
||||
(unsigned long)client_id, esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
printf("Writer is now %lu.\n", (unsigned long)session_broker_get_writer_id());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int force_writer(const char *id_text)
|
||||
{
|
||||
session_broker_client_id_t client_id = SESSION_BROKER_NO_CLIENT;
|
||||
if (strcmp(id_text, "none") != 0 && !parse_client_id(id_text, &client_id)) {
|
||||
printf("Writer must be a connected client ID or 'none'.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
esp_err_t err = session_broker_force_writer(client_id);
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not force writer: %s\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
printf("Writer is now %lu.\n", (unsigned long)session_broker_get_writer_id());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hexadecimal_value(char character)
|
||||
{
|
||||
if (character >= '0' && character <= '9') {
|
||||
return character - '0';
|
||||
}
|
||||
if (character >= 'a' && character <= 'f') {
|
||||
return character - 'a' + 10;
|
||||
}
|
||||
if (character >= 'A' && character <= 'F') {
|
||||
return character - 'A' + 10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int send_hexadecimal(const char *id_text, const char *text)
|
||||
{
|
||||
session_broker_client_id_t client_id;
|
||||
if (!parse_client_id(id_text, &client_id)) {
|
||||
printf("Invalid client ID.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t data[CONSOLE_HEX_MAX_BYTES];
|
||||
size_t text_length = strlen(text);
|
||||
if (text_length == 0 || (text_length % 2) != 0 || text_length > sizeof(data) * 2) {
|
||||
printf("Provide 1..%d bytes as hexadecimal digits without separators.\n", CONSOLE_HEX_MAX_BYTES);
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t data_length = text_length / 2;
|
||||
for (size_t index = 0; index < data_length; ++index) {
|
||||
int high = hexadecimal_value(text[index * 2]);
|
||||
int low = hexadecimal_value(text[index * 2 + 1]);
|
||||
if (high < 0 || low < 0) {
|
||||
size_t invalid_index = index * 2 + (high < 0 ? 0 : 1);
|
||||
printf("Invalid hexadecimal digit at character %u.\n", (unsigned int)invalid_index);
|
||||
return 1;
|
||||
}
|
||||
data[index] = (uint8_t)((high << 4) | low);
|
||||
}
|
||||
|
||||
size_t accepted = 0;
|
||||
esp_err_t err = session_broker_write(client_id, data, data_length, &accepted);
|
||||
if (err != ESP_OK) {
|
||||
printf("Client %lu could not write: %s\n", (unsigned long)client_id, esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
printf("Broker accepted %u of %u bytes from client %lu.\n",
|
||||
(unsigned int)accepted,
|
||||
(unsigned int)data_length,
|
||||
(unsigned long)client_id);
|
||||
return accepted == data_length ? 0 : 1;
|
||||
}
|
||||
|
||||
static int read_hexadecimal(int argc, char **argv)
|
||||
{
|
||||
session_broker_client_id_t client_id;
|
||||
if (!parse_client_id(argv[2], &client_id)) {
|
||||
printf("Invalid client ID.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32_t maximum = CONSOLE_READ_MAX_BYTES;
|
||||
if (argc == 4 && !parse_u32(argv[3], 1, CONSOLE_READ_MAX_BYTES, &maximum)) {
|
||||
printf("Read size must be 1..%d bytes.\n", CONSOLE_READ_MAX_BYTES);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t data[CONSOLE_READ_MAX_BYTES];
|
||||
size_t received = 0;
|
||||
esp_err_t err = session_broker_read(client_id, data, maximum, &received);
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not read client %lu: %s\n", (unsigned long)client_id, esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Client %lu read %u byte%s",
|
||||
(unsigned long)client_id,
|
||||
(unsigned int)received,
|
||||
received == 1 ? "" : "s");
|
||||
if (received > 0) {
|
||||
printf(": ");
|
||||
for (size_t index = 0; index < received; ++index) {
|
||||
printf("%02x", data[index]);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int show_events(const char *id_text)
|
||||
{
|
||||
session_broker_client_id_t client_id;
|
||||
if (!parse_client_id(id_text, &client_id)) {
|
||||
printf("Invalid client ID.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
session_broker_client_snapshot_t snapshot;
|
||||
esp_err_t err = session_broker_get_client_snapshot(client_id, &snapshot);
|
||||
if (err != ESP_OK) {
|
||||
printf("Unknown client %lu: %s\n", (unsigned long)client_id, esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned int count = 0;
|
||||
session_broker_event_t event;
|
||||
while (true) {
|
||||
err = session_broker_pop_event(client_id, &event);
|
||||
if (err == ESP_ERR_TIMEOUT) {
|
||||
break;
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not pop events for client %lu: %s\n",
|
||||
(unsigned long)client_id, esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
printf("#%" PRIu64 " %-16s subject=%lu writer=%lu\n",
|
||||
event.sequence,
|
||||
event_type_name(event.type),
|
||||
(unsigned long)event.client_id,
|
||||
(unsigned long)event.writer_id);
|
||||
++count;
|
||||
}
|
||||
if (count == 0) {
|
||||
printf("No pending events for client %lu.\n", (unsigned long)client_id);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int command_broker(int argc, char **argv)
|
||||
{
|
||||
if (argc == 1 || (argc == 2 && strcmp(argv[1], "status") == 0)) {
|
||||
return show_status();
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "clients") == 0) {
|
||||
return show_clients();
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "counters") == 0) {
|
||||
return show_counters();
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "clear-counters") == 0) {
|
||||
esp_err_t err = session_broker_clear_counters();
|
||||
if (err != ESP_OK) {
|
||||
printf("Could not clear broker counters: %s\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
printf("Broker counters cleared.\n");
|
||||
return 0;
|
||||
}
|
||||
if (argc == 3 && strcmp(argv[1], "connect") == 0) {
|
||||
return connect_client(argv[2]);
|
||||
}
|
||||
if (argc == 3 && strcmp(argv[1], "disconnect") == 0) {
|
||||
return disconnect_client(argv[2]);
|
||||
}
|
||||
if (argc == 3 &&
|
||||
(strcmp(argv[1], "request-writer") == 0 ||
|
||||
strcmp(argv[1], "release-writer") == 0)) {
|
||||
return change_writer(argv[1], argv[2]);
|
||||
}
|
||||
if (argc == 3 && strcmp(argv[1], "force-writer") == 0) {
|
||||
return force_writer(argv[2]);
|
||||
}
|
||||
if (argc == 4 && strcmp(argv[1], "send-hex") == 0) {
|
||||
return send_hexadecimal(argv[2], argv[3]);
|
||||
}
|
||||
if ((argc == 3 || argc == 4) && strcmp(argv[1], "read") == 0) {
|
||||
return read_hexadecimal(argc, argv);
|
||||
}
|
||||
if (argc == 3 && strcmp(argv[1], "events") == 0) {
|
||||
return show_events(argv[2]);
|
||||
}
|
||||
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
esp_err_t session_console_register_commands(void)
|
||||
{
|
||||
const esp_console_cmd_t command = {
|
||||
.command = "broker",
|
||||
.help = "Manage Phase 2 broker clients, writer ownership, events, and binary data",
|
||||
.hint = NULL,
|
||||
.func = &command_broker,
|
||||
.argtable = NULL,
|
||||
};
|
||||
return esp_console_cmd_register(&command);
|
||||
}
|
||||
Reference in New Issue
Block a user