diff --git a/include/sipp.hpp b/include/sipp.hpp index 6f2c7450..cf4c76bf 100644 --- a/include/sipp.hpp +++ b/include/sipp.hpp @@ -165,6 +165,12 @@ cmd messages are received */ #define DEFAULT_BEHAVIOR_PINGREPLY 4 #define DEFAULT_BEHAVIOR_BADCSEQ 8 +#define CID_MODE_FORMAT 0 +#define CID_MODE_UUID 1 +#define CID_MODE_UUID_COMPACT 2 +#define CID_MODE_RANDOM 3 +#define CID_MODE_TIMESTAMP 4 + #define DEFAULT_BEHAVIOR_ALL (DEFAULT_BEHAVIOR_BYE | DEFAULT_BEHAVIOR_ABORTUNEXP | DEFAULT_BEHAVIOR_PINGREPLY | DEFAULT_BEHAVIOR_BADCSEQ) #define DEFAULT_MIN_RTP_PORT DEFAULT_MEDIA_PORT @@ -290,6 +296,7 @@ MAYBE_EXTERN int currentRepartitionToDisplay DEFVAL(1); MAYBE_EXTERN unsigned int base_cseq DEFVAL(0); MAYBE_EXTERN char * auth_uri DEFVAL(0); MAYBE_EXTERN const char * call_id_string DEFVAL("%u-%p@%s"); +MAYBE_EXTERN int call_id_mode DEFVAL(CID_MODE_FORMAT); typedef std::unordered_map ParamMap; MAYBE_EXTERN ParamMap generic; diff --git a/src/call.cpp b/src/call.cpp index da80d5c0..91cce311 100644 --- a/src/call.cpp +++ b/src/call.cpp @@ -39,6 +39,7 @@ */ #include +#include #include #include #include @@ -116,6 +117,170 @@ static const int SM_UNUSED = -1; static unsigned int next_number = 1; +static void append_call_id_char(char *call_id, int &count, char value) +{ + if (count < MAX_HEADER_LEN - 1) { + call_id[count++] = value; + } + call_id[count] = '\0'; +} + +static void append_call_id_text(char *call_id, int &count, const char *fmt, ...) +{ + if (count >= MAX_HEADER_LEN - 1) { + call_id[MAX_HEADER_LEN - 1] = '\0'; + return; + } + + va_list ap; + va_start(ap, fmt); + int written = vsnprintf(&call_id[count], MAX_HEADER_LEN - count, fmt, ap); + va_end(ap); + + if (written < 0) { + call_id[count] = '\0'; + return; + } + + if (written >= MAX_HEADER_LEN - count) { + count = MAX_HEADER_LEN - 1; + } else { + count += written; + } +} + +static void fill_call_id_bytes(unsigned char *bytes, size_t size, unsigned int call_number) +{ + using clock = std::chrono::system_clock; + uint64_t seed = static_cast( + std::chrono::duration_cast( + clock::now().time_since_epoch()).count()); + + seed ^= static_cast(call_number) << 32; + seed ^= static_cast(pid) << 8; + + for (size_t i = 0; i < size; ++i) { + seed ^= static_cast(rand()) << ((i % 4) * 8); + seed = seed * 2862933555777941757ULL + 3037000493ULL + i; + bytes[i] = static_cast((seed >> ((i % 8) * 8)) & 0xff); + } +} + +static void build_default_call_id(char *call_id, unsigned int call_number) +{ + const char *src = call_id_string; + int count = 0; + + while (*src && count < MAX_HEADER_LEN - 1) { + if (*src == '%') { + ++src; + switch (*src++) { + case 'u': + append_call_id_text(call_id, count, "%u", call_number); + break; + case 'p': + append_call_id_text(call_id, count, "%u", pid); + break; + case 's': + append_call_id_text(call_id, count, "%s", local_ip); + break; + case 'r': + append_call_id_text(call_id, count, "%u", rand()); + break; + default: + append_call_id_char(call_id, count, '%'); + break; + } + } else { + append_call_id_char(call_id, count, *src++); + } + } + call_id[count] = '\0'; +} + +static void build_uuid_call_id(char *call_id, unsigned int call_number, bool compact) +{ + unsigned char bytes[16]; + int count = 0; + + fill_call_id_bytes(bytes, sizeof(bytes), call_number); + bytes[6] = static_cast((bytes[6] & 0x0f) | 0x40); + bytes[8] = static_cast((bytes[8] & 0x3f) | 0x80); + + if (compact) { + append_call_id_text(call_id, count, + "%02x%02x%02x%02x%02x%02x%02x%02x" + "%02x%02x%02x%02x%02x%02x%02x%02x", + bytes[0], bytes[1], bytes[2], bytes[3], + bytes[4], bytes[5], bytes[6], bytes[7], + bytes[8], bytes[9], bytes[10], bytes[11], + bytes[12], bytes[13], bytes[14], bytes[15]); + } else { + append_call_id_text(call_id, count, + "%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x-%02x%02x%02x%02x%02x%02x", + bytes[0], bytes[1], bytes[2], bytes[3], + bytes[4], bytes[5], bytes[6], bytes[7], + bytes[8], bytes[9], bytes[10], bytes[11], + bytes[12], bytes[13], bytes[14], bytes[15]); + } + + append_call_id_text(call_id, count, "@%s", local_ip); +} + +static void build_random_call_id(char *call_id, unsigned int call_number) +{ + unsigned char bytes[16]; + int count = 0; + + fill_call_id_bytes(bytes, sizeof(bytes), call_number); + append_call_id_text(call_id, count, "%u-", call_number); + append_call_id_text(call_id, count, + "%02x%02x%02x%02x%02x%02x%02x%02x" + "%02x%02x%02x%02x%02x%02x%02x%02x", + bytes[0], bytes[1], bytes[2], bytes[3], + bytes[4], bytes[5], bytes[6], bytes[7], + bytes[8], bytes[9], bytes[10], bytes[11], + bytes[12], bytes[13], bytes[14], bytes[15]); + append_call_id_text(call_id, count, "@%s", local_ip); +} + +static void build_timestamp_call_id(char *call_id, unsigned int call_number) +{ + using clock = std::chrono::system_clock; + int count = 0; + unsigned long long micros = static_cast( + std::chrono::duration_cast( + clock::now().time_since_epoch()).count()); + + append_call_id_text(call_id, count, "%llu-%u-%u@%s", + micros, call_number, pid, local_ip); +} + +static void build_call_id(char *call_id, unsigned int call_number) +{ + call_id[0] = '\0'; + + switch (call_id_mode) { + case CID_MODE_UUID: + build_uuid_call_id(call_id, call_number, false); + break; + case CID_MODE_UUID_COMPACT: + build_uuid_call_id(call_id, call_number, true); + break; + case CID_MODE_RANDOM: + build_random_call_id(call_id, call_number); + break; + case CID_MODE_TIMESTAMP: + build_timestamp_call_id(call_id, call_number); + break; + case CID_MODE_FORMAT: + default: + build_default_call_id(call_id, call_number); + break; + } +} + static unsigned int get_tdm_map_number() { unsigned int nb = 0; @@ -791,38 +956,11 @@ call *call::add_call(int userId, bool ipv6, struct sockaddr_storage *dest) { static char call_id[MAX_HEADER_LEN]; - const char * src = call_id_string; - int count = 0; - if(!next_number) { next_number ++; } - while (*src && count < MAX_HEADER_LEN-1) { - if (*src == '%') { - ++src; - switch(*src++) { - case 'u': - count += snprintf(&call_id[count], MAX_HEADER_LEN-count-1, "%u", next_number); - break; - case 'p': - count += snprintf(&call_id[count], MAX_HEADER_LEN-count-1, "%u", pid); - break; - case 's': - count += snprintf(&call_id[count], MAX_HEADER_LEN-count-1, "%s", local_ip); - break; - case 'r': - count += snprintf(&call_id[count], MAX_HEADER_LEN-count-1, "%u", rand()); - break; - default: // treat all unknown sequences as %% - call_id[count++] = '%'; - break; - } - } else { - call_id[count++] = *src++; - } - } - call_id[count] = 0; + build_call_id(call_id, next_number); return new call(main_scenario, nullptr, dest, call_id, userId, ipv6, false /* Not Auto. */, false); } @@ -6837,6 +6975,105 @@ const std::string test_sdp_v6 = "v=0\r\n" "m=audio 12345 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n"; +static std::string call_id_body(const char *call_id) +{ + const char *separator = strchr(call_id, '@'); + if (!separator) { + return call_id; + } + return std::string(call_id, separator - call_id); +} + +static bool is_lower_hex_string(const std::string &value) +{ + return std::all_of(value.begin(), value.end(), [](unsigned char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); + }); +} + +TEST(call_id, default_mode_uses_cid_str_template) { + char call_id[MAX_HEADER_LEN]; + + call_id_mode = CID_MODE_FORMAT; + call_id_string = "%u-%p@%s"; + pid = 4321; + strcpy(local_ip, "192.0.2.10"); + + build_call_id(call_id, 77); + EXPECT_STREQ("77-4321@192.0.2.10", call_id); +} + +TEST(call_id, uuid_mode_generates_rfc4122_value) { + char call_id[MAX_HEADER_LEN]; + + call_id_mode = CID_MODE_UUID; + pid = 9001; + strcpy(local_ip, "198.51.100.25"); + + build_call_id(call_id, 15); + + std::string body = call_id_body(call_id); + ASSERT_EQ(body.size(), 36U); + EXPECT_EQ(body[8], '-'); + EXPECT_EQ(body[13], '-'); + EXPECT_EQ(body[18], '-'); + EXPECT_EQ(body[23], '-'); + EXPECT_EQ(body[14], '4'); + EXPECT_TRUE(body[19] == '8' || body[19] == '9' || body[19] == 'a' || body[19] == 'b'); + EXPECT_TRUE(is_lower_hex_string(body.substr(0, 8))); + EXPECT_TRUE(is_lower_hex_string(body.substr(9, 4))); + EXPECT_TRUE(is_lower_hex_string(body.substr(14, 4))); + EXPECT_TRUE(is_lower_hex_string(body.substr(19, 4))); + EXPECT_TRUE(is_lower_hex_string(body.substr(24, 12))); + EXPECT_NE(std::string(call_id).find("@198.51.100.25"), std::string::npos); +} + +TEST(call_id, uuid_compact_mode_generates_hex_value) { + char call_id[MAX_HEADER_LEN]; + + call_id_mode = CID_MODE_UUID_COMPACT; + pid = 1337; + strcpy(local_ip, "203.0.113.8"); + + build_call_id(call_id, 22); + + std::string body = call_id_body(call_id); + ASSERT_EQ(body.size(), 32U); + EXPECT_TRUE(is_lower_hex_string(body)); + EXPECT_EQ(body[12], '4'); + EXPECT_TRUE(body[16] == '8' || body[16] == '9' || body[16] == 'a' || body[16] == 'b'); + EXPECT_NE(std::string(call_id).find("@203.0.113.8"), std::string::npos); +} + +TEST(call_id, random_mode_generates_unique_token_with_call_number) { + char call_id[MAX_HEADER_LEN]; + + call_id_mode = CID_MODE_RANDOM; + pid = 5150; + strcpy(local_ip, "203.0.113.44"); + + build_call_id(call_id, 31); + + std::string body = call_id_body(call_id); + ASSERT_EQ(body.rfind("31-", 0), 0U); + ASSERT_EQ(body.size(), 35U); + EXPECT_TRUE(is_lower_hex_string(body.substr(3))); + EXPECT_NE(std::string(call_id).find("@203.0.113.44"), std::string::npos); +} + +TEST(call_id, timestamp_mode_embeds_call_number_and_pid) { + char call_id[MAX_HEADER_LEN]; + + call_id_mode = CID_MODE_TIMESTAMP; + pid = 2718; + strcpy(local_ip, "192.0.2.44"); + + build_call_id(call_id, 52); + + std::string full_call_id(call_id); + EXPECT_NE(full_call_id.find("-52-2718@192.0.2.44"), std::string::npos); +} + TEST(sdp, parse_valid_sdp_msg) { ASSERT_EQ(find_in_sdp("c=IN IP4 ", test_sdp_v4), "127.0.0.1"); ASSERT_EQ(find_in_sdp("c=IN IP6 ", test_sdp_v6), "::1"); diff --git a/src/sipp.cpp b/src/sipp.cpp index b4ed127a..3e5c47f2 100644 --- a/src/sipp.cpp +++ b/src/sipp.cpp @@ -40,11 +40,15 @@ #include #include #include +#include #include +#include +#include +#include #include -#include -#include #include +#include +#include #ifdef __APPLE__ /* Provide OSX version of extern char **environ; */ @@ -122,8 +126,368 @@ struct sipp_option { #define SIPP_OPTION_NEED_SCTP 39 #define SIPP_OPTION_RX_SCENARIO 40 #define SIPP_OPTION_RX_INPUT_FILE 41 +#define SIPP_OPTION_CID_TYPE 42 #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}, +}; + +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(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_call_id_mode(const std::string &value, int *mode) +{ + std::string lowered = lowercase_copy(trim_copy(value)); + + if (lowered == "default" || lowered == "format" || lowered == "legacy") { + *mode = CID_MODE_FORMAT; + return true; + } + if (lowered == "uuid") { + *mode = CID_MODE_UUID; + return true; + } + if (lowered == "uuid-compact" || lowered == "uuidcompact" || lowered == "uuid32") { + *mode = CID_MODE_UUID_COMPACT; + return true; + } + if (lowered == "random" || lowered == "random-hex") { + *mode = CID_MODE_RANDOM; + return true; + } + if (lowered == "timestamp" || lowered == "time") { + *mode = CID_MODE_TIMESTAMP; + return true; + } + + return false; +} + +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 split_simple_args(const std::string &input) +{ + std::vector 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 launch_startup_wizard(const char *program_name) +{ + std::vector 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; + int selected_call_id_mode = CID_MODE_FORMAT; + 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(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 {}; + } + } + + while (true) { + if (!wizard_prompt_line("Call-ID mode [default/uuid/uuid-compact/random/timestamp] (default default): ", + &input, "default")) { + return {}; + } + if (parse_call_id_mode(input, &selected_call_id_mode)) { + break; + } + std::cout << "Unknown Call-ID mode. Use default, uuid, uuid-compact, random, or timestamp.\n"; + } + + 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 (selected_call_id_mode != CID_MODE_FORMAT) { + static const char *mode_names[] = { + "default", + "uuid", + "uuid-compact", + "random", + "timestamp", + }; + args.push_back("-cid_type"); + args.push_back(mode_names[selected_call_id_mode]); + } + if (!remote_host_value.empty()) { + args.push_back(remote_host_value); + } + + std::vector 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}, @@ -234,6 +598,7 @@ struct sipp_option options_table[] = { {"aa", "Enable automatic 200 OK answer for INFO, NOTIFY, OPTIONS and UPDATE.", SIPP_OPTION_SETFLAG, &auto_answer, 1}, {"base_cseq", "Start value of [cseq] for each call.", SIPP_OPTION_CSEQ, nullptr, 1}, {"cid_str", "Call ID string (default %u-%p@%s). %u=call_number, %s=ip_address, %p=process_number, %r=random_integer, %%=% (in any order).", SIPP_OPTION_STRING, &call_id_string, 1}, + {"cid_type", "Call ID generation mode. Values: default, uuid, uuid-compact, random, timestamp. Modes other than default ignore -cid_str.", SIPP_OPTION_CID_TYPE, nullptr, 1}, {"d", "Controls the length of calls. More precisely, this controls the duration of 'pause' instructions in the scenario, if they do not have a 'milliseconds' section. Default value is 0 and default unit is milliseconds.", SIPP_OPTION_TIME_MS, &duration, 1}, {"deadcall_wait", "How long the Call-ID and final status of calls should be kept to improve message and error logs (default unit is ms).", SIPP_OPTION_TIME_MS, &deadcall_wait, 1}, {"auth_uri", "Force the value of the URI for authentication.\n" @@ -864,9 +1229,12 @@ static void help() "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" @@ -1361,17 +1729,32 @@ int main(int argc, char *argv[]) bool slave_masterSet = false; int rtp_errors; int echo_errors; + std::vector wizard_args_storage; + std::vector 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(wizard_argv.size()); + argv = wizard_argv.data(); + } else if (argc < 2) { help(); exit(EXIT_OTHER); } + { /* Ignore the SIGPIPE signal */ struct sigaction action_pipe; @@ -1515,6 +1898,13 @@ int main(int argc, char *argv[]) CHECK_PASS(); *((char**)option->data) = argv[argi]; break; + case SIPP_OPTION_CID_TYPE: + REQUIRE_ARG(); + CHECK_PASS(); + if (!parse_call_id_mode(argv[argi], &call_id_mode)) { + ERROR("Unknown Call-ID mode '%s'. Use default, uuid, uuid-compact, random, or timestamp.", argv[argi]); + } + break; case SIPP_OPTION_ARGI: REQUIRE_ARG(); CHECK_PASS();