353 lines
14 KiB
C
353 lines
14 KiB
C
#include "cli_parse.h"
|
|
#include "interactive.h"
|
|
#include "peer_kcp_client.h"
|
|
|
|
#include <pthread.h>
|
|
#include <string.h>
|
|
|
|
typedef struct kcppeer_receive_ctx {
|
|
kcp_client_t *client;
|
|
const char *inbox_dir;
|
|
volatile int stop_requested;
|
|
int rc;
|
|
} kcppeer_receive_ctx_t;
|
|
|
|
static void kcppeer_usage(FILE *out) {
|
|
fprintf(out, "usage: kcppeer [-id peer-a] [-server 127.0.0.1:9002] [-relay-via addr]\n");
|
|
fprintf(out, " [-to peer] [-text msg | -file path] [-bind-ip ip] [-bind-device dev]\n");
|
|
fprintf(out, " [-inbox-dir dir] [-latency-log path] [-kcp-ts-debug-log path]\n");
|
|
fprintf(out, " [-kcp-session-stats-log path] [-kcp-session-stats-interval 100ms]\n");
|
|
fprintf(out, " [-interactive[=true|false]]\n");
|
|
}
|
|
|
|
static void *kcppeer_receive_thread_main(void *arg) {
|
|
kcppeer_receive_ctx_t *ctx = (kcppeer_receive_ctx_t *) arg;
|
|
|
|
for (;;) {
|
|
message_t msg;
|
|
char persisted_path[512];
|
|
|
|
protocol_message_init(&msg);
|
|
if (kcp_client_receive(ctx->client, &msg) != 0) {
|
|
protocol_message_clear(&msg);
|
|
ctx->rc = ctx->stop_requested ? 0 : -1;
|
|
return NULL;
|
|
}
|
|
|
|
switch (msg.type) {
|
|
case MSG_TYPE_TEXT:
|
|
if (kcp_client_persist_message(ctx->client, &msg, ctx->inbox_dir, persisted_path, sizeof(persisted_path)) != 0) {
|
|
fprintf(stderr, "kcppeer: persist text from %s to %s failed\n", msg.from, msg.to);
|
|
protocol_message_clear(&msg);
|
|
ctx->rc = -1;
|
|
return NULL;
|
|
}
|
|
fprintf(stderr, "received text from %s to %s and persisted to %s\n", msg.from, msg.to, persisted_path);
|
|
break;
|
|
case MSG_TYPE_FILE:
|
|
if (kcp_client_persist_message(ctx->client, &msg, ctx->inbox_dir, persisted_path, sizeof(persisted_path)) != 0) {
|
|
fprintf(stderr, "kcppeer: persist file from %s to %s failed\n", msg.from, msg.to);
|
|
protocol_message_clear(&msg);
|
|
ctx->rc = -1;
|
|
return NULL;
|
|
}
|
|
fprintf(stderr, "received file from %s to %s: %s (%lu bytes) -> %s\n", msg.from, msg.to, msg.file_name, (unsigned long) msg.body_len, persisted_path);
|
|
break;
|
|
case MSG_TYPE_BINARY:
|
|
if (kcp_client_persist_message(ctx->client, &msg, ctx->inbox_dir, persisted_path, sizeof(persisted_path)) != 0) {
|
|
fprintf(stderr, "kcppeer: persist binary payload from %s to %s failed\n", msg.from, msg.to);
|
|
protocol_message_clear(&msg);
|
|
ctx->rc = -1;
|
|
return NULL;
|
|
}
|
|
fprintf(stderr, "received binary payload from %s to %s (%lu bytes) -> %s\n", msg.from, msg.to, (unsigned long) msg.body_len, persisted_path);
|
|
break;
|
|
case MSG_TYPE_ERROR:
|
|
fprintf(stderr, "received error from %s to %s: %.*s\n", msg.from, msg.to, (int) msg.body_len, msg.body == NULL ? "" : (const char *) msg.body);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "received unexpected message type %s from %s\n", protocol_message_type_name(msg.type), msg.from);
|
|
protocol_message_clear(&msg);
|
|
ctx->rc = -1;
|
|
return NULL;
|
|
}
|
|
protocol_message_clear(&msg);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
const char *peer_id = "peer-a";
|
|
const char *server_addr = "127.0.0.1:9002";
|
|
const char *relay_via = "";
|
|
const char *actual_dial_target;
|
|
const char *target_peer = "";
|
|
const char *text = "";
|
|
const char *file_path = "";
|
|
const char *bind_ip = "";
|
|
const char *bind_device = "";
|
|
const char *inbox_dir = "inbox";
|
|
const char *latency_log_path = "";
|
|
const char *packet_log_path = "";
|
|
const char *stats_log_path = "";
|
|
const char *stats_interval_raw = "";
|
|
int stats_interval_ms = KCP_DEFAULT_STATS_INTERVAL_MS;
|
|
int interactive = 1;
|
|
latency_logger_t *latency_logger = NULL;
|
|
kcp_packet_debug_logger_t *packet_logger = NULL;
|
|
kcp_session_stats_logger_t *stats_logger = NULL;
|
|
kcp_client_t *client = NULL;
|
|
kcppeer_receive_ctx_t receive_ctx;
|
|
pthread_t receive_thread;
|
|
int receive_thread_started = 0;
|
|
int i;
|
|
int rc = 1;
|
|
|
|
memset(&receive_ctx, 0, sizeof(receive_ctx));
|
|
|
|
for (i = 1; i < argc; ++i) {
|
|
const char *value = NULL;
|
|
int handled;
|
|
|
|
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-id", &value)) < 0) {
|
|
fprintf(stderr, "kcppeer: flag -id requires a value\n");
|
|
return 1;
|
|
} else if (handled) {
|
|
peer_id = value;
|
|
continue;
|
|
}
|
|
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-server", &value)) < 0) {
|
|
fprintf(stderr, "kcppeer: flag -server requires a value\n");
|
|
return 1;
|
|
} else if (handled) {
|
|
server_addr = value;
|
|
continue;
|
|
}
|
|
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-relay-via", &value)) < 0) {
|
|
fprintf(stderr, "kcppeer: flag -relay-via requires a value\n");
|
|
return 1;
|
|
} else if (handled) {
|
|
relay_via = value;
|
|
continue;
|
|
}
|
|
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-to", &value)) < 0) {
|
|
fprintf(stderr, "kcppeer: flag -to requires a value\n");
|
|
return 1;
|
|
} else if (handled) {
|
|
target_peer = value;
|
|
continue;
|
|
}
|
|
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-text", &value)) < 0) {
|
|
fprintf(stderr, "kcppeer: flag -text requires a value\n");
|
|
return 1;
|
|
} else if (handled) {
|
|
text = value;
|
|
continue;
|
|
}
|
|
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-file", &value)) < 0) {
|
|
fprintf(stderr, "kcppeer: flag -file requires a value\n");
|
|
return 1;
|
|
} else if (handled) {
|
|
file_path = value;
|
|
continue;
|
|
}
|
|
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-bind-ip", &value)) < 0) {
|
|
fprintf(stderr, "kcppeer: flag -bind-ip requires a value\n");
|
|
return 1;
|
|
} else if (handled) {
|
|
bind_ip = value;
|
|
continue;
|
|
}
|
|
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-bind-device", &value)) < 0) {
|
|
fprintf(stderr, "kcppeer: flag -bind-device requires a value\n");
|
|
return 1;
|
|
} else if (handled) {
|
|
bind_device = value;
|
|
continue;
|
|
}
|
|
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-inbox-dir", &value)) < 0) {
|
|
fprintf(stderr, "kcppeer: flag -inbox-dir requires a value\n");
|
|
return 1;
|
|
} else if (handled) {
|
|
inbox_dir = value;
|
|
continue;
|
|
}
|
|
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-latency-log", &value)) < 0) {
|
|
fprintf(stderr, "kcppeer: flag -latency-log requires a value\n");
|
|
return 1;
|
|
} else if (handled) {
|
|
latency_log_path = value;
|
|
continue;
|
|
}
|
|
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-kcp-ts-debug-log", &value)) < 0) {
|
|
fprintf(stderr, "kcppeer: flag -kcp-ts-debug-log requires a value\n");
|
|
return 1;
|
|
} else if (handled) {
|
|
packet_log_path = value;
|
|
continue;
|
|
}
|
|
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-kcp-session-stats-log", &value)) < 0) {
|
|
fprintf(stderr, "kcppeer: flag -kcp-session-stats-log requires a value\n");
|
|
return 1;
|
|
} else if (handled) {
|
|
stats_log_path = value;
|
|
continue;
|
|
}
|
|
if ((handled = cli_parse_value_flag(argc, argv, &i, argv[i], "-kcp-session-stats-interval", &value)) < 0) {
|
|
fprintf(stderr, "kcppeer: flag -kcp-session-stats-interval requires a value\n");
|
|
return 1;
|
|
} else if (handled) {
|
|
stats_interval_raw = value;
|
|
continue;
|
|
}
|
|
if ((handled = cli_parse_bool_flag(argv[i], "-interactive", &interactive)) < 0) {
|
|
fprintf(stderr, "kcppeer: invalid -interactive value\n");
|
|
return 1;
|
|
} else if (handled) {
|
|
continue;
|
|
}
|
|
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
|
kcppeer_usage(stdout);
|
|
return 0;
|
|
}
|
|
fprintf(stderr, "kcppeer: unknown argument %s\n", argv[i]);
|
|
kcppeer_usage(stderr);
|
|
return 1;
|
|
}
|
|
|
|
if (text[0] != '\0' && file_path[0] != '\0') {
|
|
fprintf(stderr, "kcppeer: only one of -text or -file may be specified\n");
|
|
return 1;
|
|
}
|
|
if ((text[0] != '\0' || file_path[0] != '\0') && target_peer[0] == '\0') {
|
|
fprintf(stderr, "kcppeer: flag -to is required when sending text or file\n");
|
|
return 1;
|
|
}
|
|
if (kcp_session_stats_parse_interval_ms(stats_interval_raw, &stats_interval_ms) != 0) {
|
|
fprintf(stderr, "kcppeer: invalid -kcp-session-stats-interval value %s\n", stats_interval_raw);
|
|
return 1;
|
|
}
|
|
|
|
if (latency_log_path[0] != '\0') {
|
|
latency_logger = latencylog_open_jsonl(latency_log_path);
|
|
if (latency_logger == NULL) {
|
|
fprintf(stderr, "kcppeer: open latency logger %s failed\n", latency_log_path);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
if (packet_log_path[0] != '\0') {
|
|
packet_logger = kcp_packet_debug_open_jsonl(packet_log_path);
|
|
if (packet_logger == NULL) {
|
|
fprintf(stderr, "kcppeer: open kcp packet debug logger %s failed\n", packet_log_path);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
if (stats_log_path[0] != '\0') {
|
|
stats_logger = kcp_session_stats_open_jsonl(stats_log_path);
|
|
if (stats_logger == NULL) {
|
|
fprintf(stderr, "kcppeer: open kcp session stats logger %s failed\n", stats_log_path);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
actual_dial_target = relay_via[0] != '\0' ? relay_via : server_addr;
|
|
client = kcp_client_dial(server_addr, relay_via, peer_id, bind_ip, bind_device, latency_logger, packet_logger, stats_logger, stats_interval_ms);
|
|
if (client == NULL) {
|
|
int saved_errno = errno;
|
|
const char *reason = saved_errno != 0 ? strerror(saved_errno) : "unknown error";
|
|
if (relay_via[0] != '\0') {
|
|
fprintf(stderr, "kcppeer: dial target %s failed (logical server %s): %s (errno=%d)\n", actual_dial_target, server_addr, reason, saved_errno);
|
|
} else {
|
|
fprintf(stderr, "kcppeer: dial kcp server %s failed: %s (errno=%d)\n", server_addr, reason, saved_errno);
|
|
}
|
|
goto cleanup;
|
|
}
|
|
if (relay_via[0] != '\0') {
|
|
fprintf(stderr, "opened KCP session as %s; logical server=%s, actual dial target=%s via relay; register not yet confirmed\n", kcp_client_id(client), server_addr, actual_dial_target);
|
|
} else {
|
|
fprintf(stderr, "opened KCP session as %s; logical server=%s, actual dial target=%s; register not yet confirmed\n", kcp_client_id(client), server_addr, actual_dial_target);
|
|
}
|
|
|
|
receive_ctx.client = client;
|
|
receive_ctx.inbox_dir = inbox_dir;
|
|
if (pthread_create(&receive_thread, NULL, kcppeer_receive_thread_main, &receive_ctx) != 0) {
|
|
fprintf(stderr, "kcppeer: create receive thread failed\n");
|
|
goto cleanup;
|
|
}
|
|
receive_thread_started = 1;
|
|
|
|
if (target_peer[0] != '\0' && text[0] != '\0') {
|
|
if (kcp_client_send_text(client, target_peer, text) != 0) {
|
|
fprintf(stderr, "kcppeer: send text to %s failed\n", target_peer);
|
|
goto cleanup;
|
|
}
|
|
fprintf(stderr, "sent text to %s\n", target_peer);
|
|
}
|
|
if (target_peer[0] != '\0' && file_path[0] != '\0') {
|
|
if (kcp_client_send_file_path(client, target_peer, file_path) != 0) {
|
|
fprintf(stderr, "kcppeer: send file %s to %s failed\n", file_path, target_peer);
|
|
goto cleanup;
|
|
}
|
|
fprintf(stderr, "sent file %s to %s\n", file_path, target_peer);
|
|
}
|
|
|
|
if (interactive) {
|
|
char line[2048];
|
|
char prompt[128];
|
|
|
|
snprintf(prompt, sizeof(prompt), "%s> ", kcp_client_id(client));
|
|
interactive_print_help(stdout, "KCP");
|
|
while (fputs(prompt, stdout) >= 0 && fflush(stdout) == 0 && fgets(line, sizeof(line), stdin) != NULL) {
|
|
interactive_command_t command;
|
|
char err[128];
|
|
|
|
omni_trim_newline(line);
|
|
if (interactive_parse_command(line, &command, err, sizeof(err)) != 0) {
|
|
if (strstr(err, "empty command") == NULL) {
|
|
fprintf(stderr, "%s\n", err);
|
|
}
|
|
continue;
|
|
}
|
|
if (command.type == INTERACTIVE_CMD_HELP) {
|
|
interactive_print_help(stdout, "KCP");
|
|
continue;
|
|
}
|
|
if (command.type == INTERACTIVE_CMD_QUIT) {
|
|
break;
|
|
}
|
|
if (command.type == INTERACTIVE_CMD_TEXT) {
|
|
if (kcp_client_send_text(client, command.to, command.value) != 0) {
|
|
fprintf(stderr, "kcppeer: send text to %s failed\n", command.to);
|
|
continue;
|
|
}
|
|
fprintf(stderr, "sent text to %s\n", command.to);
|
|
continue;
|
|
}
|
|
if (command.type == INTERACTIVE_CMD_FILE) {
|
|
if (kcp_client_send_file_path(client, command.to, command.value) != 0) {
|
|
fprintf(stderr, "kcppeer: send file %s to %s failed\n", command.value, command.to);
|
|
continue;
|
|
}
|
|
fprintf(stderr, "sent file %s to %s\n", command.value, command.to);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
rc = 0;
|
|
|
|
cleanup:
|
|
receive_ctx.stop_requested = 1;
|
|
kcp_client_close(client);
|
|
if (receive_thread_started) {
|
|
pthread_join(receive_thread, NULL);
|
|
if (rc == 0 && receive_ctx.rc != 0) {
|
|
rc = 1;
|
|
}
|
|
}
|
|
kcp_client_free(client);
|
|
kcp_session_stats_close(stats_logger);
|
|
kcp_packet_debug_close(packet_logger);
|
|
latencylog_close(latency_logger);
|
|
return rc;
|
|
}
|