context_unittest.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. // Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <config.h>
  15. #include <eval/token.h>
  16. #include <eval/eval_context.h>
  17. #include <eval/token.h>
  18. #include <dhcp/option.h>
  19. #include <dhcp/pkt4.h>
  20. #include <boost/shared_ptr.hpp>
  21. #include <boost/scoped_ptr.hpp>
  22. #include <gtest/gtest.h>
  23. using namespace std;
  24. using namespace isc::dhcp;
  25. namespace {
  26. /// @brief Test class for testing EvalContext aka class test parsing
  27. class EvalContextTest : public ::testing::Test {
  28. public:
  29. /// @brief constructor to initialize members
  30. EvalContextTest() : ::testing::Test() {
  31. parsed_ = false;
  32. universe_ = Option::V4;
  33. }
  34. /// @brief checks if the given token is a string with the expected value
  35. void checkTokenString(const TokenPtr& token, const std::string& expected) {
  36. ASSERT_TRUE(token);
  37. boost::shared_ptr<TokenString> str =
  38. boost::dynamic_pointer_cast<TokenString>(token);
  39. ASSERT_TRUE(str);
  40. Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
  41. ValueStack values;
  42. EXPECT_NO_THROW(token->evaluate(*pkt4, values));
  43. ASSERT_EQ(1, values.size());
  44. EXPECT_EQ(expected, values.top());
  45. }
  46. /// @brief checks if the given token is a hex string with the expected value
  47. void checkTokenHexString(const TokenPtr& token,
  48. const std::string& expected) {
  49. ASSERT_TRUE(token);
  50. boost::shared_ptr<TokenHexString> hex =
  51. boost::dynamic_pointer_cast<TokenHexString>(token);
  52. ASSERT_TRUE(hex);
  53. Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
  54. ValueStack values;
  55. EXPECT_NO_THROW(token->evaluate(*pkt4, values));
  56. ASSERT_EQ(1, values.size());
  57. EXPECT_EQ(expected, values.top());
  58. }
  59. /// @brief checks if the given token is an equal operator
  60. void checkTokenEq(const TokenPtr& token) {
  61. ASSERT_TRUE(token);
  62. boost::shared_ptr<TokenEqual> eq =
  63. boost::dynamic_pointer_cast<TokenEqual>(token);
  64. EXPECT_TRUE(eq);
  65. }
  66. /// @brief checks if the given token is an option with the expected code
  67. void checkTokenOption(const TokenPtr& token, uint16_t expected_code) {
  68. ASSERT_TRUE(token);
  69. boost::shared_ptr<TokenOption> opt =
  70. boost::dynamic_pointer_cast<TokenOption>(token);
  71. ASSERT_TRUE(opt);
  72. EXPECT_EQ(expected_code, opt->getCode());
  73. }
  74. /// @brief checks if the given token is a substring operator
  75. void checkTokenSubstring(const TokenPtr& token) {
  76. ASSERT_TRUE(token);
  77. boost::shared_ptr<TokenSubstring> sub =
  78. boost::dynamic_pointer_cast<TokenSubstring>(token);
  79. EXPECT_TRUE(sub);
  80. }
  81. /// @brief checks if the given expression raises the expected message
  82. /// when it is parsed.
  83. void checkError(const string& expr, const string& msg) {
  84. EvalContext eval(universe_);
  85. parsed_ = false;
  86. try {
  87. parsed_ = eval.parseString(expr);
  88. FAIL() << "Expected EvalParseError but nothing was raised";
  89. }
  90. catch (const EvalParseError& ex) {
  91. EXPECT_EQ(msg, ex.what());
  92. EXPECT_FALSE(parsed_);
  93. }
  94. catch (...) {
  95. FAIL() << "Expected EvalParseError but something else was raised";
  96. }
  97. }
  98. /// @brief sets the universe
  99. /// @note the default universe is DHCPv4
  100. void setUniverse(const Option::Universe& universe) {
  101. universe_ = universe;
  102. }
  103. Option::Universe universe_;
  104. bool parsed_; ///< Parsing status
  105. };
  106. // Test the parsing of a basic expression
  107. TEST_F(EvalContextTest, basic) {
  108. EvalContext eval(Option::V4);
  109. EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].text == 'MSFT'"));
  110. EXPECT_TRUE(parsed_);
  111. }
  112. // Test the parsing of a string terminal
  113. TEST_F(EvalContextTest, string) {
  114. EvalContext eval(Option::V4);
  115. EXPECT_NO_THROW(parsed_ = eval.parseString("'foo' == 'bar'"));
  116. EXPECT_TRUE(parsed_);
  117. ASSERT_EQ(3, eval.expression.size());
  118. TokenPtr tmp1 = eval.expression.at(0);
  119. TokenPtr tmp2 = eval.expression.at(1);
  120. checkTokenString(tmp1, "foo");
  121. checkTokenString(tmp2, "bar");
  122. }
  123. // Test the parsing of a basic expression using integers
  124. TEST_F(EvalContextTest, integer) {
  125. EvalContext eval(Option::V4);
  126. EXPECT_NO_THROW(parsed_ =
  127. eval.parseString("substring(option[123].text, 0, 2) == '42'"));
  128. EXPECT_TRUE(parsed_);
  129. }
  130. // Test the parsing of a hexstring terminal
  131. TEST_F(EvalContextTest, hexstring) {
  132. EvalContext eval(Option::V4);
  133. EXPECT_NO_THROW(parsed_ = eval.parseString("0x666f6f == 'foo'"));
  134. EXPECT_TRUE(parsed_);
  135. ASSERT_EQ(3, eval.expression.size());
  136. TokenPtr tmp = eval.expression.at(0);
  137. checkTokenHexString(tmp, "foo");
  138. }
  139. // Test the parsing of a hexstring terminal with an odd number of
  140. // hexadecimal digits
  141. TEST_F(EvalContextTest, oddHexstring) {
  142. EvalContext eval(Option::V4);
  143. EXPECT_NO_THROW(parsed_ = eval.parseString("0X7 == 'foo'"));
  144. EXPECT_TRUE(parsed_);
  145. ASSERT_EQ(3, eval.expression.size());
  146. TokenPtr tmp = eval.expression.at(0);
  147. checkTokenHexString(tmp, "\a");
  148. }
  149. // Test the parsing of an equal expression
  150. TEST_F(EvalContextTest, equal) {
  151. EvalContext eval(Option::V4);
  152. EXPECT_NO_THROW(parsed_ = eval.parseString("'foo' == 'bar'"));
  153. EXPECT_TRUE(parsed_);
  154. ASSERT_EQ(3, eval.expression.size());
  155. TokenPtr tmp1 = eval.expression.at(0);
  156. TokenPtr tmp2 = eval.expression.at(1);
  157. TokenPtr tmp3 = eval.expression.at(2);
  158. checkTokenString(tmp1, "foo");
  159. checkTokenString(tmp2, "bar");
  160. checkTokenEq(tmp3);
  161. }
  162. // Test the parsing of an option terminal
  163. TEST_F(EvalContextTest, option) {
  164. EvalContext eval(Option::V4);
  165. EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].text == 'foo'"));
  166. EXPECT_TRUE(parsed_);
  167. ASSERT_EQ(3, eval.expression.size());
  168. checkTokenOption(eval.expression.at(0), 123);
  169. }
  170. // Test parsing of an option identified by name.
  171. TEST_F(EvalContextTest, optionWithName) {
  172. EvalContext eval(Option::V4);
  173. // Option 'host-name' is a standard DHCPv4 option defined in the libdhcp++.
  174. EXPECT_NO_THROW(parsed_ = eval.parseString("option[host-name].text == 'foo'"));
  175. EXPECT_TRUE(parsed_);
  176. ASSERT_EQ(3, eval.expression.size());
  177. checkTokenOption(eval.expression.at(0), 12);
  178. }
  179. // Test checking that whitespace can surround option name.
  180. TEST_F(EvalContextTest, optionWithNameAndWhitespace) {
  181. EvalContext eval(Option::V4);
  182. // Option 'host-name' is a standard DHCPv4 option defined in the libdhcp++.
  183. EXPECT_NO_THROW(parsed_ = eval.parseString("option[ host-name ].text == 'foo'"));
  184. EXPECT_TRUE(parsed_);
  185. ASSERT_EQ(3, eval.expression.size());
  186. checkTokenOption(eval.expression.at(0), 12);
  187. }
  188. // Test checking that newlines can surround option name.
  189. TEST_F(EvalContextTest, optionWithNameAndNewline) {
  190. EvalContext eval(Option::V4);
  191. // Option 'host-name' is a standard DHCPv4 option defined in the libdhcp++.
  192. EXPECT_NO_THROW(parsed_ =
  193. eval.parseString("option[\n host-name \n ].text == \n'foo'"));
  194. EXPECT_TRUE(parsed_);
  195. ASSERT_EQ(3, eval.expression.size());
  196. checkTokenOption(eval.expression.at(0), 12);
  197. }
  198. // Test parsing of an option represented as hexadecimal string.
  199. TEST_F(EvalContextTest, optionHex) {
  200. EvalContext eval(Option::V4);
  201. EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].hex == 0x666F6F"));
  202. EXPECT_TRUE(parsed_);
  203. ASSERT_EQ(3, eval.expression.size());
  204. checkTokenOption(eval.expression.at(0), 123);
  205. }
  206. // Test the parsing of a substring expression
  207. TEST_F(EvalContextTest, substring) {
  208. EvalContext eval(Option::V4);
  209. EXPECT_NO_THROW(parsed_ =
  210. eval.parseString("substring('foobar',2,all) == 'obar'"));
  211. EXPECT_TRUE(parsed_);
  212. ASSERT_EQ(6, eval.expression.size());
  213. TokenPtr tmp1 = eval.expression.at(0);
  214. TokenPtr tmp2 = eval.expression.at(1);
  215. TokenPtr tmp3 = eval.expression.at(2);
  216. TokenPtr tmp4 = eval.expression.at(3);
  217. checkTokenString(tmp1, "foobar");
  218. checkTokenString(tmp2, "2");
  219. checkTokenString(tmp3, "all");
  220. checkTokenSubstring(tmp4);
  221. }
  222. // Test some scanner error cases
  223. TEST_F(EvalContextTest, scanErrors) {
  224. checkError("'", "<string>:1.1: Invalid character: '");
  225. checkError("'\''", "<string>:1.3: Invalid character: '");
  226. checkError("'\n'", "<string>:1.1: Invalid character: '");
  227. checkError("0x123h", "<string>:1.6: Invalid character: h");
  228. checkError("=", "<string>:1.1: Invalid character: =");
  229. checkError("subtring", "<string>:1.1: Invalid character: s");
  230. checkError("foo", "<string>:1.1: Invalid character: f");
  231. checkError(" bar", "<string>:1.2: Invalid character: b");
  232. }
  233. // Tests some scanner/parser error cases
  234. TEST_F(EvalContextTest, scanParseErrors) {
  235. checkError("", "<string>:1.1: syntax error, unexpected end of file");
  236. checkError(" ", "<string>:1.2: syntax error, unexpected end of file");
  237. checkError("0x", "<string>:1.1: syntax error, unexpected integer");
  238. checkError("0abc",
  239. "<string>:1.1: syntax error, unexpected integer");
  240. checkError("===", "<string>:1.1-2: syntax error, unexpected ==");
  241. checkError("option[-1].text",
  242. "<string>:1.8-9: Option code has invalid "
  243. "value in -1. Allowed range: 0..255");
  244. checkError("option[256].text",
  245. "<string>:1.8-10: Option code has invalid "
  246. "value in 256. Allowed range: 0..255");
  247. setUniverse(Option::V6);
  248. checkError("option[65536].text",
  249. "<string>:1.8-12: Option code has invalid "
  250. "value in 65536. Allowed range: 0..65535");
  251. setUniverse(Option::V4);
  252. checkError("option[12345678901234567890].text",
  253. "<string>:1.8-27: Failed to convert 12345678901234567890 "
  254. "to an integer.");
  255. checkError("option[123]",
  256. "<string>:1.12: syntax error, unexpected end of file,"
  257. " expecting .");
  258. checkError("option[123].text < 'foo'", "<string>:1.18: Invalid"
  259. " character: <");
  260. checkError("option[-ab].text", "<string>:1.8: Invalid character: -");
  261. checkError("option[0ab].text",
  262. "<string>:1.9-10: syntax error, unexpected option name, "
  263. "expecting ]");
  264. checkError("option[ab_].hex", "<string>:1.8: Invalid character: a");
  265. checkError("option[\nhost-name\n].hex =\n= 'foo'",
  266. "<string>:3.7: Invalid character: =");
  267. checkError("substring('foo',12345678901234567890,1)",
  268. "<string>:1.17-36: Failed to convert 12345678901234567890 "
  269. "to an integer.");
  270. }
  271. // Tests some parser error cases
  272. TEST_F(EvalContextTest, parseErrors) {
  273. checkError("'foo''bar'",
  274. "<string>:1.6-10: syntax error, unexpected constant string, "
  275. "expecting ==");
  276. checkError("== 'ab'", "<string>:1.1-2: syntax error, unexpected ==");
  277. checkError("'foo' ==",
  278. "<string>:1.9: syntax error, unexpected end of file");
  279. checkError("option 'ab'",
  280. "<string>:1.8-11: syntax error, unexpected "
  281. "constant string, expecting [");
  282. checkError("option(10) == 'ab'",
  283. "<string>:1.7: syntax error, "
  284. "unexpected (, expecting [");
  285. checkError("option['ab'].text == 'foo'",
  286. "<string>:1.8-11: syntax error, "
  287. "unexpected constant string, "
  288. "expecting integer or option name");
  289. checkError("option[ab].text == 'foo'",
  290. "<string>:1.8-9: option 'ab' is not defined");
  291. checkError("option[0xa].text == 'ab'",
  292. "<string>:1.8-10: syntax error, "
  293. "unexpected constant hexstring, "
  294. "expecting integer or option name");
  295. checkError("option[10].bin", "<string>:1.12: Invalid character: b");
  296. checkError("option[boot-size].bin", "<string>:1.19: Invalid character: b");
  297. checkError("substring('foobar') == 'f'",
  298. "<string>:1.19: syntax error, "
  299. "unexpected ), expecting \",\"");
  300. checkError("substring('foobar',3) == 'bar'",
  301. "<string>:1.21: syntax error, unexpected ), expecting \",\"");
  302. checkError("substring('foobar','3',3) == 'bar'",
  303. "<string>:1.20-22: syntax error, unexpected constant string, "
  304. "expecting integer");
  305. checkError("substring('foobar',1,a) == 'foo'",
  306. "<string>:1.22: Invalid character: a");
  307. }
  308. // Tests some type error cases (caught only by the strongly typed parser)
  309. TEST_F(EvalContextTest, typeErrors) {
  310. checkError("'foobar'",
  311. "<string>:1.9: syntax error, unexpected end of file, "
  312. "expecting ==");
  313. checkError("substring('foobar',all,1) == 'foo'",
  314. "<string>:1.20-22: syntax error, unexpected all, "
  315. "expecting integer");
  316. checkError("substring('foobar',0x32,1) == 'foo'",
  317. "<string>:1.20-23: syntax error, unexpected constant "
  318. "hexstring, expecting integer");
  319. }
  320. };