3#include <readline/readline.h>
4#include <readline/history.h>
19pthread_mutex_t
cli_mutex = PTHREAD_MUTEX_INITIALIZER;
26 .cli_command_name =
"clear",
27 .cli_command_brief_desc =
"Clears CLI screen.",
28 .cli_command_detailed_desc =
29 " * clear - Clears the command line interface screen for better readability.",
30 .cli_command_usage =
"clear"
34 .cli_command_name =
"history",
35 .cli_command_brief_desc =
"Prints command history.",
36 .cli_command_detailed_desc =
37 " * history - Displays the history of all entered commands for reference.",
38 .cli_command_usage =
"history"
42 .cli_command_name =
"info",
43 .cli_command_brief_desc =
"Prints program information.",
44 .cli_command_detailed_desc =
45 " * info - Displays information about BitLab program and the host machine.",
46 .cli_command_usage =
"info"
50 .cli_command_name =
"echo",
51 .cli_command_brief_desc =
"Echoes the input.",
52 .cli_command_detailed_desc =
53 " * echo - Outputs the text or arguments provided as input.",
54 .cli_command_usage =
"echo [Text to be echoed]"
58 .cli_command_name =
"exit",
59 .cli_command_brief_desc =
"Stops the server.",
60 .cli_command_detailed_desc =
61 " * exit - Stops the server gracefully and terminates the session.",
62 .cli_command_usage =
"exit [-f | --force]"
66 .cli_command_name =
"getip",
67 .cli_command_brief_desc =
"Obtains IP address of given URL.",
68 .cli_command_detailed_desc =
69 " * getip - Retrieves and displays the remote IP address of a specified URL or host if not specified.",
70 .cli_command_usage =
"getip [URL 1] [URL 2] ..."
74 .cli_command_name =
"help",
75 .cli_command_brief_desc =
"Prints command descriptions.",
76 .cli_command_detailed_desc =
77 " * help - Lists all available commands with their descriptions.",
78 .cli_command_usage =
"help [command]"
82 .cli_command_name =
"peerdiscovery",
83 .cli_command_brief_desc =
"Starts peer discovery.",
84 .cli_command_detailed_desc =
85 " * peerdiscovery - Initiates the peer discovery proces. Use daemon argument to detach and run in the background. Run again to connect and wait for results. Use without arguments to run default DNS lookup. Add domain after -d or --dns to use custom DNS lookup. Use -h or --hardcoded to use hardcoded seeds of Bitcoin network IPs. Running without arguments will wait for results and running with other arguments before previous are generated will wait for the previous results.",
87 "peerdiscovery [-d | --daemon] [-h | --hardcoded] [-l | --dns-lookup]"
91 .cli_command_name =
"connect",
92 .cli_command_brief_desc =
"Connects to the specified IP address.",
93 .cli_command_detailed_desc =
94 " * connect - Connects to the specified IP address to establish a peer-to-peer connection.",
95 .cli_command_usage =
"connect [IP address]"
99 .cli_command_name =
"ping",
100 .cli_command_brief_desc =
"Pings the specified IP address.",
101 .cli_command_detailed_desc =
102 " * ping - Pings the specified IP address to check for connectivity.",
103 .cli_command_usage =
"ping [-c | --count]"
107 .cli_command_name =
"whoami",
108 .cli_command_brief_desc =
"Prints basic information about user.",
109 .cli_command_detailed_desc =
110 " * whoami - Displays username or full user information, including username, local IP, and public IP address when --full argument provided.",
111 .cli_command_usage =
"whoami [-f | --full]",
115 .cli_command_name =
"list",
116 .cli_command_brief_desc =
"Lists connected nodes.",
117 .cli_command_detailed_desc =
" * list - Lists nodes connected with 'connect' command. Shows IP address, port, socket FD, thread ID, connection status, operation status, compact blocks, and fee rate.",
118 .cli_command_usage =
"list"
122 .cli_command_name =
"getaddr",
123 .cli_command_brief_desc =
"Gets addresses from the specified node.",
124 .cli_command_detailed_desc =
" * getaddr - Sends 'getaddr' command to peer and wait for response. Use with node index to specify the node. Prints IP addresses of returned nodes.",
125 .cli_command_usage =
"getaddr [idx of node]"
129 .cli_command_name =
"disconnect",
130 .cli_command_brief_desc =
"Disconnect from specified node.",
131 .cli_command_detailed_desc =
" * disconnect - Disconnects from node specified by the given node ID. Closes the socket, terminates the thread, and logs the disconnection.",
132 .cli_command_usage =
"disconnect [idx of node]"
136 .cli_command_name =
"getheaders",
137 .cli_command_brief_desc =
"Gets blockchain headers from the specified node.",
138 .cli_command_detailed_desc =
" * getheaders - Sends 'getheaders' and prints decoded 'headers' as a chain of block headers.",
139 .cli_command_usage =
"getheaders [idx of node]"
143 .cli_command_name =
"getblocks",
144 .cli_command_brief_desc =
"Gets block headers from given range",
145 .cli_command_detailed_desc =
" * getblocks - Sends 'getblocks' message with fixed (dummy) interval to ask for 'inv' message. Obtains block hashes.",
146 .cli_command_usage =
"getblocks [idx of node]"
150 .cli_command_name =
"inv",
151 .cli_command_brief_desc =
"Let's answer 'getblocks' manually or verbosely send advertisement about known blocks.",
152 .cli_command_detailed_desc =
" * inv - Sends an 'inv' message to the specified peer using the existing blocks.dat file to advertise known blocks.",
153 .cli_command_usage =
"inv [idx of node]"
157 .cli_command_name =
"getdata",
158 .cli_command_brief_desc =
"Obtains transactions from given block.",
159 .cli_command_detailed_desc =
" * getdata - Sends 'getdata' to a idx-th node with given block hashes. Retrieves transaction data from returned 'blocks' message.",
160 .cli_command_usage =
"getdata [idx of node] [block hash 1] ..."
164 .cli_command_name =
"tx",
165 .cli_command_brief_desc =
"Sends a transaction to a specified node.",
166 .cli_command_detailed_desc =
" * tx - Sends a 'tx' message to the specified node with the provided transaction data.",
167 .cli_command_usage =
"tx [idx of node] [transaction data in hex]"
173 int longest_command_length = 0;
174 int longest_parameters_length = 0;
178 int param_length = 0;
180 char* space_pos = strchr(
cli_commands[i].cli_command_usage,
' ');
185 param_length = strlen(space_pos + 1);
192 if (cmd_length > longest_command_length)
193 longest_command_length = cmd_length;
194 if (param_length > longest_parameters_length)
195 longest_parameters_length = param_length;
198 int longest_description_length = 0;
201 int length = strlen(
cli_commands[i].cli_command_brief_desc);
202 if (length > longest_description_length)
203 longest_description_length = length;
206 char* dashes_command = (
char*)malloc(longest_command_length + 1);
207 memset(dashes_command,
'-', longest_command_length);
208 dashes_command[longest_command_length] =
'\0';
210 char* dashes_params = (
char*)malloc(longest_parameters_length + 1);
211 memset(dashes_params,
'-', longest_parameters_length);
212 dashes_params[longest_parameters_length] =
'\0';
214 char* dashes_description = (
char*)malloc(longest_description_length + 1);
215 memset(dashes_description,
'-', longest_description_length);
216 dashes_description[longest_description_length] =
'\0';
219 longest_command_length,
"Command",
220 longest_parameters_length,
"Parameters",
223 dashes_command, dashes_params, dashes_description);
231 char command[longest_command_length + 1];
232 char parameters[longest_parameters_length + 1];
233 char* params = strchr(usage,
' ');
237 int cmd_length = params - usage;
238 strncpy(command, usage, cmd_length);
239 command[cmd_length] =
'\0';
240 strncpy(parameters, params + 1, longest_parameters_length);
241 parameters[longest_parameters_length] =
'\0';
245 strncpy(command, usage, longest_command_length);
246 command[longest_command_length] =
'\0';
247 parameters[0] =
'\0';
252 longest_command_length, command,
253 longest_parameters_length, parameters,
257 free(dashes_command);
259 free(dashes_description);
266 if (strcmp(command_name,
cli_commands[i].cli_command_name) == 0)
286 if (strcmp(args[0],
"-f") == 0 || strcmp(args[0],
"--force") == 0)
291 "Unknown argument for exit command: \"%s\"", args[1]);
301 "Unknown argument for exit command: \"%s\"", args[0]);
318 "Unknown argument for history command: \"%s\"", args[0]);
323 HIST_ENTRY** history_entries = history_list();
326 for (
int i = 0; history_entries[i] != NULL; ++i)
338 if (args[1] != NULL && args[2] == NULL)
341 "Too many arguments for help command");
348 if (strcmp(args[0],
cli_commands[i].cli_command_name) == 0)
373 "No arguments provided for echo command");
378 for (
int i = 0; args[i] != NULL; ++i)
388 bool full_information =
false;
391 if (strcmp(args[0],
"-f") == 0 || strcmp(args[0],
"--full") == 0)
396 "Unknown argument for whoami command: \"%s\"", args[1]);
401 full_information =
true;
406 "Unknown argument for whoami command: \"%s\"", args[0]);
413 if (!full_information)
420 if (getenv(
"USER") == NULL)
422 else if (strcmp(getenv(
"USER"),
"root") == 0)
448 if (args[0] != NULL && args[1] == NULL)
461 while (args[i] != NULL)
463 ip_address[0] =
'\0';
487 "Unknown argument for info command: %s", args[0]);
537 bool daemon_set =
false;
538 bool hardcoded_seeds_set =
false;
539 bool dns_lookup_set =
false;
544 for (
int i = 0; args[i] != NULL; ++i)
546 if (strcmp(args[i],
"-d") == 0 || strcmp(args[i],
"--daemon") == 0)
551 "Daemon flag already set for peerdiscovery command");
559 else if (strcmp(args[i],
"-h") == 0 || strcmp(args[i],
"--hardcoded") == 0)
561 if (hardcoded_seeds_set || dns_lookup_set)
564 "Hardcoded seeds or DNS lookup flag already set for peerdiscovery command");
569 hardcoded_seeds =
true;
572 hardcoded_seeds_set =
true;
574 else if (strcmp(args[i],
"-l") == 0 || strcmp(args[i],
"--dns-lookup") == 0)
576 if (dns_lookup_set || hardcoded_seeds_set)
579 "DNS lookup or hardcoded seeds flag already set for peerdiscovery command");
585 if (args[i + 1] == NULL)
587 "Missing domain argument for DNS lookup flag for peerdiscovery command, default will be used");
588 if (args[i + 2] != NULL)
591 "Too many arguments for DNS lookup flag for peerdiscovery command");
598 hardcoded_seeds =
false;
600 dns_lookup_set =
true;
608 "Set DNS domain for peerdiscovery command: %s",
613 "Failed to set DNS domain for peerdiscovery command");
621 "Unknown argument for peerdiscovery command: %s",
637 "Peer discovery already in progress");
639 "Peer discovery already in progress. Use \"peerdiscovery\" to check results later or \"info\" to see the status. Any additional arguments will be ignored until the results are cleared.");
646 "Connected to peer discovery daemon. Arguments ignored if provided. Waiting for results...");
658 "Peer discovery previous attempt failed. Allowing new attempt...");
660 if (!results_successful)
672 "Peer discovery in background");
674 "Peer discovery started as daemon. Use \"peerdiscovery\" to check results or \"info\" to see the status.");
702 while (args[i] != NULL)
704 if (strcmp(args[i],
"-c") == 0 || strcmp(args[i],
"--count") == 0)
706 if (args[i + 1] == NULL)
709 "Missing count value for ping command.");
715 count = strtol(args[i + 1], NULL, 10);
726 else if (ip_address[0] ==
'\0')
731 "Invalid IP address for ping command.");
742 "Too many arguments for ping command: %s", args[i]);
749 if (ip_address[0] ==
'\0')
752 "Missing IP address for ping command.");
760 snprintf(command,
sizeof(command),
"ping -c %d -i %d %s", count, sleep_time,
774 "No arguments provided for connect command");
782 "Too many arguments for connect command");
797 "Connect command uses numeric address for peer connection. Supplied argument: %s",
810 "No arguments provided for getaddr command");
818 "Too many arguments for getaddr command");
823 int idx = atoi(args[0]);
836 "No arguments provided for disconnect command");
844 "Too many arguments for disconnect command");
849 int idx = atoi(args[0]);
862 "No arguments provided for getheaders command");
870 "Too many arguments for getheaders command");
875 int idx = atoi(args[0]);
888 "No arguments provided for getblocks command");
896 "Too many arguments for getblocks command");
901 int idx = atoi(args[0]);
914 "No arguments provided for getdata command");
920 int idx = atoi(args[0]);
924 "Invalid node index for getdata command");
931 size_t hash_count = 0;
932 for (
int i = 1; args[i] != NULL; i++)
940 "No hashes provided for getdata command");
946 if (hash_count > 50000)
949 "Too many hashes provided for getdata command");
956 unsigned char* hashes = malloc(hash_count * 32);
960 "Failed to allocate memory for hashes");
966 for (
size_t i = 0; i < hash_count; i++)
968 if (strlen(args[i + 1]) != 64)
971 "Invalid hash length for getdata command");
978 for (
size_t j = 0; j < 32; j++)
980 sscanf(&args[i + 1][j * 2],
"%2hhx", &hashes[i * 32 + j]);
998 "Insufficient arguments provided for inv command");
1003 if (args[1] != NULL)
1006 "Too many arguments for inv command");
1012 int idx = atoi(args[0]);
1020 "Failed to load inventory data from blocks.dat");
1025 size_t inv_count = inv_len / 36;
1026 printf(
"inv_len: %zu, inv_count: %zu\n", inv_len, inv_count);
1039 if (args[0] == NULL || args[1] == NULL)
1042 "No arguments provided for tx command");
1048 int idx = atoi(args[0]);
1052 "Invalid node index for tx command");
1059 size_t tx_size = strlen(args[1]) / 2;
1060 unsigned char* tx_data = malloc(tx_size);
1061 if (tx_data == NULL)
1064 "Failed to allocate memory for transaction data");
1069 for (
size_t i = 0; i < tx_size; i++)
1071 sscanf(&args[1][i * 2],
"%2hhx", &tx_data[i]);
1075 send_tx(idx, tx_data, tx_size);
1085 if (args[0] != NULL)
1088 "Too many arguments for getaddr command");
1101 if (args[0] != NULL)
1104 "Unknown argument for clear command: %s", args[0]);
1120 if (lineptr == NULL || n == NULL)
1134 ptr = strchr(line,
'\n');
1149 strcpy(*lineptr, line);
1167 char** tokens = malloc(buffer_size *
sizeof(
char*));
1180 while (token != NULL)
1185 if (i >= buffer_size)
1188 tokens = realloc(tokens, buffer_size *
sizeof(
char*));
1192 "cli_exec_line: malloc error");
1201 command = tokens[0];
1204 if (command == NULL)
1212 if (!strcmp(command,
cli_commands[i].cli_command_name))
1227 rl_attempted_completion_over = 1;
1230 char* line = rl_line_buffer;
1234 if (strlen(line) == 0)
1243 for (
long unsigned i = 0; i < strlen(line); ++i)
1248 if (((strncmp(line,
"help", 4) == 0) && spaces <= 1))
1260 static int list_index, len;
1272 if (strncmp(name, text, len) == 0)
1282 struct stat st = { 0 };
1286 "Failed to create history directory");
1290 read_history(full_path);
1293 rl_basic_word_break_characters =
" \t\n\"\\'`@$><=;|&{(";
1294 rl_completer_word_break_characters =
" \t\n\"\\'`@$><=;|&{(";
1295 rl_completer_quote_characters =
"\"\\'`";
1296 rl_completion_append_character =
'\0';
1297 rl_attempted_completion_over = 1;
1299 rl_getc_function = getc;
1300 rl_catch_signals = 0;
1308 write_history(full_path);
1330 const char* home = getenv(
"HOME");
1333 const char* suffix =
"/.bitlab/history";
1334 char*
logs_dir = malloc(strlen(home) + strlen(suffix) + 1);
char * cli_command_generator(const char *text, int state)
CLI command generator.
void print_usage(const char *command_name)
Prints CLI command usage.
void print_commands()
Prints CLI commands.
int cli_history(char **args)
Prints CLI command history.
const char * create_history_dir()
Create history directory.
int cli_clear(char **args)
Clears CLI window.
int cli_help(char **args)
Prints CLI command help.
static cli_command cli_commands[]
int cli_whoami(char **args)
Prints the user name.
char ** cli_completion(const char *text, int start, int end)
CLI completion function.
int cli_peer_discovery(char **args)
Discovers Bitcoin peers.
int cli_getdata(char **args)
Sends a 'getdata' message to specified peer.
int cli_tx(char **args)
Sends a 'tx' message to specified peer.
static const char * cli_history_dir
int cli_connect(char **args)
Connects to the specified IP address.
char * cli_read_line(void)
Reads input from user.
int cli_disconnect(char **args)
Disconnects from the specified node.
void * handle_cli(void *arg)
CLI handler thread.
void print_help()
Prints CLI command help.
int cli_get_line(char **lineptr, size_t *n, FILE *stream)
Gets the line from the file stream.
int cli_getblocks(char **args)
Sends a 'getblocks' message to specified peer.
int cli_info(char **args)
Prints program information.
int cli_get_ip(char **args)
Gets remote IP address on an URL or host machine if no URL is provided.
int cli_getheaders(char **args)
Sends a 'getheaders' message to specified peer.
int cli_getaddr(char **args)
Sends a 'getaddr' message to the specified peer.
int cli_inv(char **args)
Sends a 'inv' message to specified peer.
int cli_exit(char **args)
Exits BitLab CLI command.
int cli_exec_line(char *line)
Execute command.
int cli_echo(char **args)
Echoes the input.
int cli_ping(char **args)
Pings the specified IP address.
pthread_mutex_t cli_mutex
int cli_list(char **args)
Lists all connected nodes.
void get_local_ip_address(char *ip_addr)
Get the local IP address of the machine (e.g.
int lookup_address(const char *lookup_addr, char *ip_addr)
Perform lookup of given IP address by the domain address (e.g.
int is_numeric_address(const char *ip_addr)
Check if the IP address is a numeric address (e.g.
int is_valid_domain_address(const char *domain_addr)
Check if the IP address is a valid domain address (e.g.
void get_remote_ip_address(char *ip_addr)
Get the remote IP address of the machine (e.g.
static const char * logs_dir
void log_message(log_level level, const char *filename, const char *source_file, const char *format,...)
Log a message used to log a message to the console or a file.
void list_connected_nodes()
Lists all connected nodes and their details.
void send_inv_and_wait(int idx, const unsigned char *inv_data, size_t inv_count)
Sends an 'inv' message to the peer and waits for a response.
void send_getheaders_and_wait(int idx)
Sends a 'getheaders' message to the peer and waits for a response.
void send_getblocks_and_wait(int idx)
Sends a 'getblocks' message to the peer and waits for a response.
unsigned char * load_blocks_from_file(const char *filename, size_t *payload_len)
void send_tx(int idx, const unsigned char *tx_data, size_t tx_size)
Sends a 'tx' message to the specified node.
void send_getaddr_and_wait(int idx)
Sends a 'getaddr' message to the peer and waits for a response.
int connect_to_peer(const char *ip_addr)
Connects to a peer using the specified IP address.
void disconnect(int node_id)
Disconnects from the node specified by the node ID.
void send_getdata_and_wait(int idx, const unsigned char *hashes, size_t hash_count)
Sends a 'getdata' message to the peer and waits for a response.
void print_peer_queue()
Print the peer queue.
bool set_peer_discovery_daemon(bool value)
Set the peer discovery daemon state.
bool get_peer_discovery()
Get the peer discovery operation.
bool set_peer_discovery_hardcoded_seeds(bool value)
Set the peer discovery hardcoded seeds state.
bool get_peer_discovery_in_progress()
Get the peer discovery in progress state.
bool set_peer_discovery_dns_lookup(bool value)
Set the peer discovery DNS lookup state.
bool set_peer_discovery_dns_domain(const char *domain)
Set the peer discovery DNS domain.
void print_program_state()
Print the program state.
program_state state
The program state used to store the state of the program.
void set_exit_flag(volatile sig_atomic_t flag)
Set the exit flag.
bool get_peer_discovery_succeeded()
Get the peer discovery succeeded state.
#define PEER_DISCOVERY_DEFAULT_DNS_LOOKUP
program_operation operation
The program operation used to store all current operations of the program.
bool set_peer_discovery(bool value)
Set the peer discovery operation.
sig_atomic_t get_exit_flag()
Get the exit flag.
#define PEER_DISCOVERY_DEFAULT_DAEMON
#define PEER_DISCOVERY_DEFAULT_HARDCODED_SEEDS
const char * cli_command_brief_desc
int(* cli_command)(char **args)
const char * cli_command_usage
const char * cli_command_name
bool peer_discovery_succeeded
bool peer_discovery_in_progress
bool peer_discovery_daemon
bool peer_discovery_dns_lookup
bool peer_discovery_hardcoded_seeds
char * strdup(const char *str1)
void clear_cli()
Clear the CLI window.
void guarded_print_line(const char *format,...)
Guarded print line function.
void usleep(unsigned int usec)
void guarded_print(const char *format,...)
Guarded print function.
char * strtok(char *str, const char *delimiters)