Commit 35a0237e authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[1954] CommandOptions member initialization and validation. Exceptions.

parent 434ca154
SUBDIRS = . tests
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
AM_CXXFLAGS = $(B10_CXXFLAGS)
AM_LDFLAGS = $(CLOCK_GETTIME_LDFLAGS)
......@@ -11,3 +14,5 @@ endif
pkglibexec_PROGRAMS = perfdhcp
perfdhcp_SOURCES = perfdhcp.c
perfdhcp_SOURCES += command_options.cc command_options.h
perfdhcp_LDADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
......@@ -12,10 +12,21 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <inttypes.h>
#define __STDC_LIMIT_MACROS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <boost/algorithm/string.hpp>
#include <boost/tokenizer.hpp>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include "exceptions/exceptions.h"
#include "command_options.h"
using namespace std;
......@@ -30,23 +41,21 @@ CommandOptions::reset() {
uint8_t mac[6] = { 0x0, 0xC, 0x1, 0x2, 0x3, 0x4 };
double lt[2] = { 1., 1. };
ipversion_ = 4;
ipversion_ = 0;
exchange_mode_ = DORR_SARR;
rate_ = 0;
report_delay_ = 0;
random_range_ = 0;
max_random_ = 0;
mac_prefix_.assign(mac, mac+6);
mac_prefix_.assign(mac, mac + 6);
base_.resize(0);
num_request_.resize(0);
period_ = 0;
lost_time_set_ = 0;
lost_time_.assign(lt, lt+2);
lost_time_.assign(lt, lt + 2);
max_drop_set_ = 0;
max_drop_.push_back(0);
max_drop_.push_back(0);
max_pdrop_.push_back(0.);
max_pdrop_.push_back(0.);
max_drop_.resize(0);
max_pdrop_.resize(0);
localname_.resize(0);
is_interface_ = false;
preload_ = 0;
......@@ -58,8 +67,8 @@ CommandOptions::reset() {
rapid_commit_ = false;
use_first_ = false;
template_file_.resize(0);
xid_offset_.resize(0);
rnd_offset_.resize(0);
xid_offset_.resize(0);
elp_offset_ = -1;
sid_offset_ = -1;
rip_offset_ = -1;
......@@ -68,24 +77,338 @@ CommandOptions::reset() {
server_name_.resize(0);
}
int
CommandOptions::parse(int argc, char** argv, bool force_reset /*=false */) {
int ch;
void
CommandOptions::parse(int argc, char** const argv, bool force_reset /*=false */) {
if (force_reset) {
reset();
}
// Reset internal variable used by getopt to index elements
// Reset internal variables used by getopt
// to eliminate underfined behavior when
// parsing different command lines multiple times
optind = 1;
while((ch = getopt(argc, argv, "hv46r:t:R:b:n:p:d:D:l:P:a:L:s:iBc1T:X:O:E:S:I:x:w:")) != -1) {
switch (ch) {
case 'h':
usage();
opterr = 0;
initialize(argc, argv);
validate();
}
void
CommandOptions::initialize(int argc, char** const argv) {
char opt;
char* pc;
int nr, of;
int di = 0;
float dp = 0;
long long r;
while((opt = getopt(argc, argv, "hv46r:t:R:b:n:p:d:D:l:P:a:L:s:iBc1T:X:O:E:S:I:x:w:")) != -1) {
switch (opt) {
case 'h':
usage();
case 'v':
// version();
;
case '4':
check(ipversion_ == 6, "IP version already set to 6");
ipversion_ = 4;
break;
case '6':
check(ipversion_ == 4, "IP version already set to 4");
ipversion_ = 6;
break;
case 'r':
rate_ = atoi(optarg);
check(rate_ <= 0, "rate_ must be a positive integer");
break;
case 't':
report_delay_ = atoi(optarg);
check(report_delay_ <= 0, "report_delay_ must be a positive integer");
break;
case 'R':
r = atoll(optarg);
check(r < 0, "random_range_ must not be a negative integer");
random_range_ = (uint32_t) r;
if ((random_range_ != 0) && (random_range_ != UINT32_MAX)) {
uint32_t s = random_range_ + 1;
uint64_t b = UINT32_MAX + 1, m;
m = (b / s) * s;
if (m == b)
max_random_ = 0;
else
max_random_ = (uint32_t) m;
}
break;
case 'b':
check(base_.size() > 3, "too many bases");
base_.push_back(optarg);
decodeBase(base_.back());
break;
case 'n':
nr = atoi(optarg);
check(nr <= 0, "num-request must be a positive integer");
if (num_request_.size() >= 2) {
isc_throw(isc::InvalidParameter,
"too many num-request values");
}
num_request_.push_back(nr);
break;
case 'p':
period_ = atoi(optarg);
check(period_ <= 0, "test-period must be a positive integer");
break;
case 'd':
lost_time_[lost_time_set_] = atof(optarg);
check(lost_time_[lost_time_set_] <= 0., "drop-time must be a positive number");
lost_time_set_ = 1;
break;
case 'D':
pc = strchr(optarg, '%');
if (pc != NULL) {
*pc = '\0';
dp = atof(optarg);
max_pdrop_[max_drop_set_] = atof(optarg);
check((dp <= 0) || (dp >= 100), "invalid drop-time percentage");
max_pdrop_.push_back(dp);
break;
}
di = atoi(optarg);
check(di <= 0, "max-drop must be a positive integer");
max_drop_.push_back(di);
break;
case 'l':
localname_ = optarg;
break;
case 'P':
preload_ = atoi(optarg);
check(preload_ < 0, "preload must not be a negative integer");
break;
case 'a':
aggressivity_ = atoi(optarg);
check(aggressivity_ <= 0, "aggressivity must be a positive integer");
break;
case 'L':
local_port_ = atoi(optarg);
check(local_port_ < 0, "local-port must not be a negative integer");
check(local_port_ > (int) UINT16_MAX, "local-port must be lower than UINT16_MAX");
break;
case 's':
seeded_ = true;
seed_ = (unsigned int) atol(optarg);
break;
case 'i':
exchange_mode_ = DO_SA;
break;
case 'B':
broadcast_ = 1;
break;
case 'c':
rapid_commit_ = 1;
break;
case '1':
use_first_ = 1;
break;
case 'T':
switch (template_file_.size()) {
case 0:
case 1:
template_file_.push_back(std::string(optarg));
break;
default:
;
isc_throw(isc::InvalidParameter,
"template-files are already set");
}
break;
case 'X':
of = atoi(optarg);
check(of <= 0, "xid-offset must be a positive integer");
if (xid_offset_.size() >= 2) {
xid_offset_.resize(0);
}
xid_offset_.push_back(of);
break;
case 'O':
of = atoi(optarg);
check(of < 3, "random-offset must be greater than 3");
if (rnd_offset_.size() >= 2) {
rnd_offset_.resize(0);
}
rnd_offset_.push_back(of);
break;
case 'E':
elp_offset_ = atoi(optarg);
check(elp_offset_ < 0, "time-offset must not be a negative integer");
break;
case 'S':
sid_offset_ = atoi(optarg);
check(sid_offset_ <= 0, "srvid-offset must be a positive integer");
break;
case 'I':
rip_offset_ = atoi(optarg);
check(rip_offset_ <= 0, "ip-offset must be a positive integer");
break;
case 'x':
diags_.assign(optarg);
break;
case 'w':
wrapped_.assign(optarg);
break;
default:
isc_throw(isc::InvalidParameter,
"unknown command line option");
}
}
if (ipversion_ == 0)
ipversion_ = 4;
if (template_file_.size() > 1) {
if (xid_offset_.size() == 1)
xid_offset_.push_back(xid_offset_[0]);
if (rnd_offset_.size() == 1)
rnd_offset_.push_back(rnd_offset_[0]);
}
// TODO HADNLE SERVER ARG
}
void
CommandOptions::decodeBase(const std::string& base) {
std::string b(base);
boost::algorithm::to_lower(b);
if ((b.substr(0, 4) == "mac=") || (b.substr(0, 6) == "ether=")) {
decodeMac(b);
} else if (b.substr(0, 5) == "duid=") {
decodeDuid(b);
}
}
void
CommandOptions::decodeMac(const std::string& base) {
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
size_t found = base.find('=');
check(found == std::string::npos, "expected -b<base> format for MAC address is -b MAC=00::0C::01::02::03::04");
boost::char_separator<char> sep(":-");
tokenizer tokens(base.substr(found + 1), sep);
std::vector<std::string> stokens(tokens.begin(), tokens.end());
check(stokens.size() != 6, "expected -b<base> format for MAC address is -b MAC=00::0C::01::02::03::04");
mac_prefix_.resize(0);
BOOST_FOREACH(std::string t, stokens) {
std::istringstream ss(t);
unsigned int ui = 0;
ss >> std::hex >> ui >> std::dec;
check(ss.fail() || (ui > 0xFF),
"expected -b<base> format for MAC address is -b MAC=00::0C::01::02::03::04");
mac_prefix_.push_back(static_cast<uint8_t>(ui));
}
}
void
CommandOptions::decodeDuid(const std::string& base) {
size_t found = base.find('=');
check(found == std::string::npos, "expected -b<base> format for DUID is -b DUID=<duid>");
std::string b = base.substr(found + 1);
check(b.length() & 1, "odd number of hexadecimal digits in duid");
check(b.length() > 128, "duid too large");
check(b.length() == 0, "no duid specified");
for (int i = 0; i < b.length(); i += 2) {
unsigned int ui = 0;
std::istringstream ss(b.substr(i, 2));
check(!(ss >> std::hex >> ui >> std::dec) || (ui > 0xFF),
"illegal characters " + b + " in duid");
duid_prefix_.push_back(static_cast<uint8_t>(ui));
}
}
void
CommandOptions::validate() const {
check((getIpVersion() != 4) && (isBroadcast() != 0),
"-B is not compatible with IPv6 (-6)");
check((getIpVersion() != 6) && (isRapidCommit() != 0),
"-6 (IPv6) must be set to use -c");
check((getExchangeMode() == DO_SA) && (getNumRequests().size() > 1),
"second -n<num-request> is not compatible with -i");
check((getExchangeMode() == DO_SA) && (getLostTime()[1] != 1.),
"second -d<drop-time> is not compatible with -i");
check((getExchangeMode() == DO_SA) &&
((getMaxDrop().size() > 1) || (getMaxDropPercentage().size() > 1)),
"second -D<max-drop> is not compatible with -i\n");
check((getExchangeMode() == DO_SA) && (isUseFirst()),
"-1 is not compatible with -i\n");
check((getExchangeMode() == DO_SA) && (getTemplateFiles().size() > 1),
"second -T<template-file> is not compatible with -i\n");
check((getExchangeMode() == DO_SA) && (getXidOffset().size() > 1),
"second -X<xid-offset> is not compatible with -i\n");
check((getExchangeMode() == DO_SA) && (getRndOffset().size() > 1),
"second -O<random-offset is not compatible with -i\n");
check((getExchangeMode() == DO_SA) && (getElpOffset() >= 0),
"-E<time-offset> is not compatible with -i\n");
check((getExchangeMode() == DO_SA) && (getSidOffset() >= 0),
"-S<srvid-offset> is not compatible with -i\n");
check((getExchangeMode() == DO_SA) && (getRipOffset() >= 0),
"-I<ip-offset> is not compatible with -i\n");
check((getExchangeMode() != DO_SA) && (isRapidCommit() != 0),
"-i must be set to use -c\n");
check((getRate() == 0) && (getReportDelay() != 0),
"-r<rate> must be set to use -t<report>\n");
check((getRate() == 0) && (getNumRequests().size() > 0),
"-r<getRate()> must be set to use -n<num-request>\n");
check((getRate() == 0) && (getPeriod() != 0),
"-r<rate> must be set to use -p<test-period>\n");
check((getRate() == 0) &&
((getMaxDrop().size() > 0) || getMaxDropPercentage().size() > 0),
"-r<rate> must be set to use -D<max-drop>\n");
check((getTemplateFiles().size() < getXidOffset().size()),
"-T<template-file> must be set to use -X<xid-offset>\n");
check((getTemplateFiles().size() < getRndOffset().size()),
"-T<template-file> must be set to use -O<random-offset>\n");
check((getTemplateFiles().size() < 2) && (getElpOffset() >= 0),
"second/request -T<template-file> must be set to use -E<time-offset>\n");
check((getTemplateFiles().size() < 2) && (getSidOffset() >= 0),
"second/request -T<template-file> must be set to "
"use -S<srvid-offset>\n");
check((getTemplateFiles().size() < 2) && (getRipOffset() >= 0),
"second/request -T<template-file> must be set to "
"use -I<ip-offset>\n");
}
void
CommandOptions::check(bool condition, const std::string errmsg) const {
if (condition) {
isc_throw(isc::InvalidParameter, errmsg);
}
return(0);
}
void
......
......@@ -52,8 +52,8 @@ public:
/// \param argc Argument count passed to main().
/// \param argv Argument value array passed to main().
/// \param force_reset Force reset of state variables
/// return non-zero value if parse failed
int parse(int argc, char** argv, bool force_reset = false);
/// \throw BadValue if fails to parse
void parse(int argc, char** const argv, bool force_reset = false);
/// \brief Returns IP version
///
......@@ -198,7 +198,7 @@ public:
/// \brief Returns template offset for requested IP
///
/// \return template offset for requested IP
int getRipOffset() const { return elp_offset_; }
int getRipOffset() const { return rip_offset_; }
/// \brief Returns diagnostic selectors
///
......@@ -221,6 +221,46 @@ public:
void usage(void);
private:
/// \brief Initializes class members based command line
///
/// Reads each command line parameter and sets class member values
///
/// \param argc Argument count passed to main().
/// \param argv Argument value array passed to main().
/// \throw InvalidParameter if bad command line option values
void initialize(int argc, char** const argv);
/// \brief Validates initialized options
///
/// \throw InvalidPrameter if validation fails
void validate() const;
/// \brief Checks given condition
///
/// \param condition Condition to be checked
/// \param errmsg Error message in exception
/// \throw InvalidParameter if check fails
inline void check(bool condition, const std::string errmsg) const;
/// \brief Decodes base provided with -b
///
/// \param base Base in string format
/// \throw InvalidParameter if base is invalid
void decodeBase(const std::string& base);
/// \brief Decodes base MAC address provided with -b
///
/// \param base MAC address in string format
/// \throw InvalidParameter if base is invalid
void decodeMac(const std::string& base);
/// \brief Decodes base DUID provided with -b
///
/// \param base DUID in string format
/// \throw InvalidParameter if base is invalid
void decodeDuid(const std::string& base);
uint8_t ipversion_; ///< IP version
ExchangeMode exchange_mode_ ; ///< Packet exchange mode (e.g. DORR/SARR)
int rate_; ///< rate in exchange per second
......
......@@ -42,16 +42,15 @@ protected:
///
/// \param s Command line to parse
/// \return non-zero if parsing failed
int process(const std::string& s) {
void process(const std::string& s) {
int argc = 0;
char** argv = tokenizeString(s, &argc);
int r = parse(argc, argv, true);
parse(argc, argv, true);
for(int i = 0; i < argc; ++i) {
free(argv[i]);
argv[i] = NULL;
}
free(argv);
return (r);
}
/// \brief Check initialized values
......@@ -135,12 +134,12 @@ TEST_F(CommandOptionsTest, Defaults) {
}
TEST_F(CommandOptionsTest, UseFirst) {
process("perfdhcp -l ethx -1 -O 3");
process("perfdhcp -l ethx -1 -B");
EXPECT_TRUE(isUseFirst());
}
TEST_F(CommandOptionsTest, IpVersion) {
process("perfdhcp -6 -l ethx -c");
process("perfdhcp -6 -l ethx -c -i");
EXPECT_EQ(6, getIpVersion());
EXPECT_EQ("ethx", getLocalName());
EXPECT_TRUE(isRapidCommit());
......@@ -149,18 +148,18 @@ TEST_F(CommandOptionsTest, IpVersion) {
EXPECT_EQ(4, getIpVersion());
EXPECT_TRUE(isBroadcast());
EXPECT_FALSE(isRapidCommit());
EXPECT_NE(0, process("perfdhcp -6 -B -l ethx"));
EXPECT_NE(0, process("perfdhcp -c -l ethx"));
EXPECT_THROW(process("perfdhcp -6 -B -l ethx"), isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -c -l ethx"), isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, Rate) {
process("perfdhcp -4 -r 10 -l ethx");
EXPECT_EQ(10, getRate());
EXPECT_NE(0, process("perfdhcp -4 -r 0 -l ethx"));
EXPECT_NE(0, process("perfdhcp -6 -t 5 -l ethx"));
EXPECT_NE(0, process("perfdhcp -4 -n 150 -l ethx"));
EXPECT_NE(0, process("perfdhcp -6 -p 120 -l ethx"));
EXPECT_NE(0, process("perfdhcp -4 -D 1400 -l ethx"));
EXPECT_THROW(process("perfdhcp -4 -r 0 -l ethx"), isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -6 -t 5 -l ethx"), isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -4 -n 150 -l ethx"), isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -6 -p 120 -l ethx"), isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -4 -D 1400 -l ethx"), isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, ReportDelay) {
......@@ -174,11 +173,13 @@ TEST_F(CommandOptionsTest, RandomRange) {
}
TEST_F(CommandOptionsTest, Base) {
process("perfdhcp -6 -b MAC=10:20:30:40:50:60 -l ethx");
process("perfdhcp -6 -b MAC=10::20::30::40::50::60 -l ethx -b duiD=1AB7F5670901FF");
uint8_t mac[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60 };
std::vector<uint8_t> v(mac, mac+6);
EXPECT_EQ(v, getMacPrefix());
// TODO - test for DUID
uint8_t duid[7] = { 0x1A, 0xB7, 0xF5, 0x67, 0x09, 0x01, 0xFF };
std::vector<uint8_t> v1(mac, mac + 6);
std::vector<uint8_t> v2(duid, duid + 7);
EXPECT_EQ(v1, getMacPrefix());
EXPECT_EQ(v2, getDuidPrefix());
}
TEST_F(CommandOptionsTest, DropTime) {
......@@ -194,33 +195,34 @@ TEST_F(CommandOptionsTest, DropTime) {
}
TEST_F(CommandOptionsTest, TimeOffset) {
process("perfdhcp -l ethx -E 4");
EXPECT_EQ(5, getElpOffset());
EXPECT_NE(0, process("perfdhcp -l ethx -E 3 -i"));
process("perfdhcp -l ethx -T file1.x -T file2.x -E 4");
EXPECT_EQ(4, getElpOffset());
EXPECT_THROW(process("perfdhcp -l ethx -E 3 -i"), isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, ExchangeMode) {
process("perfdhcp -i -l ethx");
EXPECT_EQ(DO_SA, getExchangeMode());
EXPECT_NE(0, process("perfdhcp -i -l ethx -X 3"));
EXPECT_NE(0, process("perfdhcp -i -l ethx -O 2"));
EXPECT_NE(0, process("perfdhcp -i -l ethx -E 3"));
EXPECT_NE(0, process("perfdhcp -i -l ethx -S 1"));
EXPECT_NE(0, process("perfdhcp -i -l ethx -I 2"));
process("perfdhcp -l ethx -i");
EXPECT_EQ(CommandOptions::DO_SA, getExchangeMode());
EXPECT_THROW(process("perfdhcp -i -l ethx -X 3"), isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -i -l ethx -O 2"), isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -i -l ethx -E 3"), isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -i -l ethx -S 1"), isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -i -l ethx -I 2"), isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, Offsets) {