Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
338 changes: 334 additions & 4 deletions src/sipp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,15 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <algorithm>
#include <atomic>
#include <cctype>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <thread>
#include <chrono>
#include <thread>
#include <vector>

#ifdef __APPLE__
/* Provide OSX version of extern char **environ; */
Expand Down Expand Up @@ -124,6 +128,314 @@
#define SIPP_OPTION_RX_INPUT_FILE 41
#define SIPP_HELP_TEXT_HEADER 255

struct wizard_scenario_option {
const char *scenario_name;
const char *description;
bool needs_remote_host;
bool use_scenario_file;
};

static const wizard_scenario_option wizard_scenarios[] = {
{"uac", "Embedded UAC client scenario.", true, false},
{"uas", "Embedded UAS server scenario.", false, false},
{"regexp", "Embedded UAC scenario with regexp and variables.", true, false},
{"branchc", "Embedded client branching scenario.", true, false},
{"branchs", "Embedded server branching scenario.", false, false},
{"uac_pcap", "Embedded UAC scenario with PCAP media playback.", true, false},
{"custom", "Custom XML scenario file.", false, true},
Comment on lines +143 to +145
};

static std::string trim_copy(const std::string &value)
{
size_t first = value.find_first_not_of(" \t\r\n");
if (first == std::string::npos) {
return "";
}

size_t last = value.find_last_not_of(" \t\r\n");
return value.substr(first, last - first + 1);
}

static std::string lowercase_copy(std::string value)
{
std::transform(value.begin(), value.end(), value.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return value;
}

static bool wizard_cancelled(const std::string &value)
{
std::string lowered = lowercase_copy(trim_copy(value));
return lowered == "q" || lowered == "quit" || lowered == "exit";
}

static bool wizard_prompt_line(const std::string &prompt, std::string *result, const char *default_value = nullptr)
{
std::cout << prompt;
std::cout.flush();

if (!std::getline(std::cin, *result)) {
std::cout << "\n";
return false;
}

*result = trim_copy(*result);
if (wizard_cancelled(*result)) {
return false;
}

if (result->empty() && default_value) {
*result = default_value;
}

return true;
}

static bool parse_transport_choice(const std::string &value, std::string *transport_arg)
{
std::string lowered = lowercase_copy(trim_copy(value));

if (lowered == "udp" || lowered == "u1") {
*transport_arg = "u1";
return true;
}
if (lowered == "tcp" || lowered == "t1") {
*transport_arg = "t1";
return true;
}
if (lowered == "tls" || lowered == "l1") {
*transport_arg = "l1";
return true;
}
#ifdef USE_SCTP
if (lowered == "sctp" || lowered == "s1") {
*transport_arg = "s1";
return true;
}
#endif

return false;
}

static std::vector<std::string> split_simple_args(const std::string &input)
{
std::vector<std::string> result;
std::istringstream words(input);
std::string word;

while (words >> word) {
result.push_back(word);
}

return result;
}

static bool should_launch_startup_wizard(int argc)
{
return argc < 2 && isatty(STDIN_FILENO) && isatty(STDOUT_FILENO);
}

static std::vector<std::string> launch_startup_wizard(const char *program_name)

Check warning

Code scanning / CodeQL

Poorly documented large function Warning

Poorly documented function: fewer than 2% comments for a function of 200 lines.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
{
std::vector<std::string> args;
const wizard_scenario_option *scenario_choice = nullptr;
std::string input;
std::string transport_arg = "u1";
std::string scenario_path;
std::string remote_host_value;
std::string service_value;
std::string rate_value;
std::string max_calls_value;
std::string concurrent_calls_value;
std::string extra_args;
bool custom_scenario_uses_remote_host = false;

std::cout
<< "\nSIPp startup wizard\n"
<< "Press Enter to accept defaults. Type 'q' to quit.\n\n";

while (!scenario_choice) {
std::cout << "Scenario:\n";
for (size_t i = 0; i < sizeof(wizard_scenarios) / sizeof(wizard_scenarios[0]); ++i) {
std::cout << " " << (i + 1) << ") " << wizard_scenarios[i].scenario_name
<< " - " << wizard_scenarios[i].description << "\n";
}
std::cout << "\n";

if (!wizard_prompt_line("Choose a scenario [1]: ", &input, "1")) {
return {};
}

std::string lowered = lowercase_copy(input);
size_t numeric_choice = 0;
if (!lowered.empty() &&
std::all_of(lowered.begin(), lowered.end(),
[](unsigned char c) { return std::isdigit(c) != 0; })) {
numeric_choice = static_cast<size_t>(strtoul(lowered.c_str(), nullptr, 10));
}

if (numeric_choice >= 1 &&
numeric_choice <= (sizeof(wizard_scenarios) / sizeof(wizard_scenarios[0]))) {
scenario_choice = &wizard_scenarios[numeric_choice - 1];
break;
}

for (const wizard_scenario_option &candidate : wizard_scenarios) {
if (lowered == lowercase_copy(candidate.scenario_name)) {
scenario_choice = &candidate;
break;
}
}

if (!scenario_choice) {
std::cout << "Unknown scenario choice. Please select one of the listed items.\n\n";
}
}

if (scenario_choice->use_scenario_file) {
while (true) {
if (!wizard_prompt_line("Path to XML scenario file: ", &scenario_path)) {
return {};
}
if (scenario_path.empty()) {
std::cout << "A scenario path is required.\n";
continue;
}
if (access(scenario_path.c_str(), R_OK) != 0) {
std::cout << "Unable to read '" << scenario_path << "'. Try another path.\n";
continue;
}
break;
}

while (true) {
if (!wizard_prompt_line("Does this scenario send calls to a remote host? [y/N]: ",
&input, "n")) {
return {};
}

std::string lowered = lowercase_copy(input);
if (lowered == "y" || lowered == "yes") {
custom_scenario_uses_remote_host = true;
break;
}
if (lowered == "n" || lowered == "no") {
custom_scenario_uses_remote_host = false;
break;
}

std::cout << "Please answer y or n.\n";
}
}

if (scenario_choice->needs_remote_host || custom_scenario_uses_remote_host) {
while (remote_host_value.empty()) {
if (!wizard_prompt_line("Remote host[:port] [127.0.0.1]: ", &remote_host_value, "127.0.0.1")) {
return {};
}
if (remote_host_value.empty()) {
std::cout << "A remote host is required for this scenario.\n";
}
}
}

while (true) {
#ifdef USE_SCTP
const char *transport_prompt = "Transport [udp/tcp/tls/sctp] (default udp): ";
#else
const char *transport_prompt = "Transport [udp/tcp/tls] (default udp): ";
#endif
if (!wizard_prompt_line(transport_prompt, &input, "udp")) {
return {};
}
if (parse_transport_choice(input, &transport_arg)) {
break;
}
std::cout << "Unknown transport. Use udp, tcp, tls";
#ifdef USE_SCTP
std::cout << ", or sctp";
#endif
std::cout << ".\n";
}

if (scenario_choice->needs_remote_host) {
if (!wizard_prompt_line("Request URI user (-s) [service]: ", &service_value, DEFAULT_SERVICE)) {
return {};
}
}

if (scenario_choice->needs_remote_host || custom_scenario_uses_remote_host) {
if (!wizard_prompt_line("Call rate (-r) [10]: ", &rate_value, "10")) {
return {};
}
if (!wizard_prompt_line("Max calls (-m, blank for unlimited): ", &max_calls_value)) {
return {};
}
if (!wizard_prompt_line("Max simultaneous calls (-l, optional): ", &concurrent_calls_value)) {
return {};
}
}

if (!wizard_prompt_line("Extra SIPp options (optional, space-separated): ", &extra_args)) {
return {};
}

args.push_back(program_name);
if (scenario_choice->use_scenario_file) {
args.push_back("-sf");
args.push_back(scenario_path);
} else {
args.push_back("-sn");
args.push_back(scenario_choice->scenario_name);
}

args.push_back("-t");
args.push_back(transport_arg);

if (!service_value.empty()) {
args.push_back("-s");
args.push_back(service_value);
}
if (!rate_value.empty()) {
args.push_back("-r");
args.push_back(rate_value);
}
if (!max_calls_value.empty()) {
args.push_back("-m");
args.push_back(max_calls_value);
}
if (!concurrent_calls_value.empty()) {
args.push_back("-l");
args.push_back(concurrent_calls_value);
}
if (!remote_host_value.empty()) {
args.push_back(remote_host_value);
}
Comment on lines +426 to +428

std::vector<std::string> extra_words = split_simple_args(extra_args);
args.insert(args.end(), extra_words.begin(), extra_words.end());

std::cout << "\nCommand:\n ";
for (size_t i = 0; i < args.size(); ++i) {
if (i != 0) {
std::cout << " ";
}
std::cout << args[i];
}
std::cout << "\n\n";

if (!wizard_prompt_line("Run this command now? [Y/n]: ", &input, "y")) {
return {};
}
std::string lowered = lowercase_copy(input);
if (!(lowered == "y" || lowered == "yes")) {
std::cout << "Wizard cancelled.\n";
return {};
}

return args;
}

/* Put each option, its help text, and type in this table. */
struct sipp_option options_table[] = {
{"h", nullptr, SIPP_OPTION_HELP, nullptr, 0},
Expand Down Expand Up @@ -864,9 +1176,12 @@
"Usage:\n"
"\n"
" sipp remote_host[:remote_port] [options]\n"
" sipp\n"
"\n"
"Example:\n"
"\n"
" Launch the interactive startup wizard:\n"
" ./sipp\n"
" Run SIPp with embedded server (uas) scenario:\n"
" ./sipp -sn uas\n"
" On the same host, run SIPp with embedded client (uac) scenario:\n"
Expand Down Expand Up @@ -1361,17 +1676,32 @@
bool slave_masterSet = false;
int rtp_errors;
int echo_errors;
std::vector<std::string> wizard_args_storage;
std::vector<char *> wizard_argv;

rtp_errors = 0;
echo_errors = 0;

randomseed();

/* At least one argument is needed */
if (argc < 2) {
if (should_launch_startup_wizard(argc)) {
wizard_args_storage = launch_startup_wizard(argv[0]);
if (wizard_args_storage.empty()) {
exit(EXIT_OTHER);
}

wizard_argv.reserve(wizard_args_storage.size());
for (std::string &value : wizard_args_storage) {
wizard_argv.push_back(value.data());
}

argc = static_cast<int>(wizard_argv.size());
argv = wizard_argv.data();
} else if (argc < 2) {
help();
exit(EXIT_OTHER);
}

{
/* Ignore the SIGPIPE signal */
struct sigaction action_pipe;
Expand Down
Loading