token_unittest.cc 116 KB
Newer Older
1
// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
2
//
3
4
5
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7

#include <config.h>
8
#include <fstream>
9
#include <eval/token.h>
10
#include <eval/eval_context.h>
11
12
13
14
15
#include <dhcp/pkt4.h>
#include <dhcp/pkt6.h>
#include <dhcp/dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/option_string.h>
16
17
#include <dhcp/option_vendor.h>
#include <dhcp/option_vendor_class.h>
18
19
20
#include <log/logger_manager.h>
#include <log/logger_name.h>
#include <log/logger_support.h>
21
#include <testutils/log_utils.h>
22
23
24
25
26

#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>

Francis Dupont's avatar
Francis Dupont committed
27
28
#include <arpa/inet.h>

29
30
using namespace std;
using namespace isc::dhcp;
31
using namespace isc::asiolink;
32
using namespace isc::log;
33
using namespace isc::dhcp::test;
34
35
36
37
38
39
40

namespace {

/// @brief Test fixture for testing Tokens.
///
/// This class provides several convenience objects to be used during testing
/// of the Token family of classes.
41

42
class TokenTest : public LogContentTest {
43
44
public:

45
    /// @brief Initializes Pkt4, Pkt6 and options that can be useful for
46
47
48
49
50
51
52
53
54
55
56
    ///        evaluation tests.
    TokenTest() {
        pkt4_.reset(new Pkt4(DHCPDISCOVER, 12345));
        pkt6_.reset(new Pkt6(DHCPV6_SOLICIT, 12345));

        // Add options with easily identifiable strings in them
        option_str4_.reset(new OptionString(Option::V4, 100, "hundred4"));
        option_str6_.reset(new OptionString(Option::V6, 100, "hundred6"));

        pkt4_->addOption(option_str4_);
        pkt6_->addOption(option_str6_);
57
58
59
60

        // Change this to true if you need extra information about logging
        // checks to be printed.
        logCheckVerbose(false);
61
62
    }

63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
    /// @brief Inserts RAI option with several suboptions
    ///
    /// The structure inserted is:
    ///  - RAI (option 82)
    ///      - option 1 (containing string "one")
    ///      - option 13 (containing string "thirteen")
    void insertRelay4Option() {

        // RAI (Relay Agent Information) option
        OptionPtr rai(new Option(Option::V4, DHO_DHCP_AGENT_OPTIONS));
        OptionPtr sub1(new OptionString(Option::V4, 1, "one"));
        OptionPtr sub13(new OptionString(Option::V4, 13, "thirteen"));

        rai->addOption(sub1);
        rai->addOption(sub13);
        pkt4_->addOption(rai);
    }

81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
    /// @brief Adds relay encapsulations with some suboptions
    ///
    /// This will add 2 relay encapsulations all will have
    /// msg_type of RELAY_FORW
    /// Relay 0 (closest to server) will have
    /// linkaddr = peeraddr = 0, hop-count = 1
    /// option 100 "hundred.zero", option 101 "hundredone.zero"
    /// Relay 1 (closest to client) will have
    /// linkaddr 1::1= peeraddr = 1::2, hop-count = 0
    /// option 100 "hundred.one", option 102 "hundredtwo.one"
    void addRelay6Encapsulations() {
        // First relay
        Pkt6::RelayInfo relay0;
        relay0.msg_type_ = DHCPV6_RELAY_FORW;
        relay0.hop_count_ = 1;
        relay0.linkaddr_ = isc::asiolink::IOAddress("::");
        relay0.peeraddr_ = isc::asiolink::IOAddress("::");
        OptionPtr optRelay01(new OptionString(Option::V6, 100,
                                              "hundred.zero"));
        OptionPtr optRelay02(new OptionString(Option::V6, 101,
                                              "hundredone.zero"));

        relay0.options_.insert(make_pair(optRelay01->getType(), optRelay01));
        relay0.options_.insert(make_pair(optRelay02->getType(), optRelay02));

        pkt6_->addRelayInfo(relay0);
        // Second relay
        Pkt6::RelayInfo relay1;
        relay1.msg_type_ = DHCPV6_RELAY_FORW;
        relay1.hop_count_ = 0;
        relay1.linkaddr_ = isc::asiolink::IOAddress("1::1");
        relay1.peeraddr_ = isc::asiolink::IOAddress("1::2");
        OptionPtr optRelay11(new OptionString(Option::V6, 100,
                                              "hundred.one"));
        OptionPtr optRelay12(new OptionString(Option::V6, 102,
                                              "hundredtwo.one"));

        relay1.options_.insert(make_pair(optRelay11->getType(), optRelay11));
        relay1.options_.insert(make_pair(optRelay12->getType(), optRelay12));
        pkt6_->addRelayInfo(relay1);
    }

    /// @brief Verify that the relay6 option evaluatiosn work properly
    ///
    /// Given the nesting level and option code extract the option
    /// and compare it to the expected string.
    ///
    /// @param test_level The nesting level
    /// @param test_code The code of the option to extract
    /// @param result_addr The expected result of the address as a string
Francis Dupont's avatar
Francis Dupont committed
131
    void verifyRelay6Option(const int8_t test_level,
132
                            const uint16_t test_code,
Thomas Markwalder's avatar
Thomas Markwalder committed
133
                            const TokenOption::RepresentationType& test_rep,
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
                            const std::string& result_string) {
        // Create the token
        ASSERT_NO_THROW(t_.reset(new TokenRelay6Option(test_level,
                                                       test_code,
                                                       test_rep)));

        // We should be able to evaluate it
        EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_));

        // We should have one value on the stack
        ASSERT_EQ(1, values_.size());

        // And it should match the expected result
        // Invalid nesting levels result in a 0 length string
        EXPECT_EQ(result_string, values_.top());

        // Then we clear the stack
        clearStack();
    }

    /// @brief Verify that the relay6 field evaluations work properly
    ///
    /// Given the nesting level, the field to extract and the expected
    /// address create a token and evaluate it then compare the addresses
    ///
    /// @param test_level The nesting level
    /// @param test_field The type of the field to extract
    /// @param result_addr The expected result of the address as a string
Francis Dupont's avatar
Francis Dupont committed
162
    void verifyRelay6Eval(const int8_t test_level,
163
                          const TokenRelay6Field::FieldType test_field,
164
165
166
                          const int result_len,
                          const uint8_t result_addr[]) {
        // Create the token
167
        ASSERT_NO_THROW(t_.reset(new TokenRelay6Field(test_level, test_field)));
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185

        // We should be able to evaluate it
        EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_));

        // We should have one value on the stack
        ASSERT_EQ(1, values_.size());

        // And it should match the expected result
        // Invalid nesting levels result in a 0 length string
        EXPECT_EQ(result_len, values_.top().size());
        if (result_len != 0) {
            EXPECT_EQ(0, memcmp(result_addr, &values_.top()[0], result_len));
        }

        // Then we clear the stack
        clearStack();
    }

186
    /// @brief Convenience function. Removes token and values stacks.
187
188
    /// @param token specifies if the convenience token should be removed or not
    void clearStack(bool token = true) {
189
190
191
        while (!values_.empty()) {
            values_.pop();
        }
192
193
194
        if (token) {
            t_.reset();
        }
195
196
    }

197
    /// @brief Aux. function that stores integer values as 4 byte string.
198
199
    ///
    /// @param value integer value to be stored
200
    /// @return 4 byte long string with encoded value.
201
    string encode(uint32_t value) {
202
        return EvalContext::fromUint32(value);
203
204
    }

205
206
207
208
209
210
211
212
213
214
    TokenPtr t_; ///< Just a convenience pointer

    ValueStack values_; ///< evaluated values will be stored here

    Pkt4Ptr pkt4_; ///< A stub DHCPv4 packet
    Pkt6Ptr pkt6_; ///< A stub DHCPv6 packet

    OptionPtr option_str4_; ///< A string option for DHCPv4
    OptionPtr option_str6_; ///< A string option for DHCPv6

215
216
    OptionVendorPtr vendor_; ///< Vendor option used during tests
    OptionVendorClassPtr vendor_class_; ///< Vendor class option used during tests
217
218
219
220
221
222
223

    /// @brief Verify that the substring eval works properly
    ///
    /// This function takes the parameters and sets up the value
    /// stack then executes the eval and checks the results.
    ///
    /// @param test_string The string to operate on
Vincent Legout's avatar
Vincent Legout committed
224
    /// @param test_start The position to start when getting a substring
225
226
    /// @param test_length The length of the substring to get
    /// @param result_string The expected result of the eval
227
    /// @param should_throw The eval will throw
228
229
230
    void verifySubstringEval(const std::string& test_string,
                             const std::string& test_start,
                             const std::string& test_length,
231
232
                             const std::string& result_string,
                             bool should_throw = false) {
233
234
235
236
237
238
239
240
241
242

        // create the token
        ASSERT_NO_THROW(t_.reset(new TokenSubstring()));

        // push values on stack
        values_.push(test_string);
        values_.push(test_start);
        values_.push(test_length);

        // evaluate the token
243
244
245
246
247
248
249
250
251
252
253
254
255
        if (should_throw) {
            EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError);
            ASSERT_EQ(0, values_.size());
        } else {
            EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));

            // verify results
            ASSERT_EQ(1, values_.size());
            EXPECT_EQ(result_string, values_.top());

            // remove result
            values_.pop();
        }
256
257
    }

258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
    /// @brief Creates vendor-option with specified value and adds it to packet
    ///
    /// This method creates specified vendor option, removes any existing
    /// vendor options and adds the new one to v4 or v6 packet.
    ///
    /// @param u universe (V4 or V6)
    /// @param vendor_id specifies enterprise-id value.
    void setVendorOption(Option::Universe u, uint32_t vendor_id) {
        vendor_.reset(new OptionVendor(u, vendor_id));
        switch (u) {
        case Option::V4:
            pkt4_->delOption(DHO_VIVSO_SUBOPTIONS);
            pkt4_->addOption(vendor_);
            break;
        case Option::V6:
            pkt6_->delOption(D6O_VENDOR_OPTS);
            pkt6_->addOption(vendor_);
            break;
        }
    }

    /// @brief Creates vendor-class option with specified values and adds it to packet
    ///
    /// This method creates specified vendor-class option, removes any existing
    /// vendor class options and adds the new one to v4 or v6 packet.
    /// It also creates data tuples with greek alphabet names.
    ///
    /// @param u universe (V4 or V6)
    /// @param vendor_id specifies enterprise-id value.
    /// @param tuples_size number of data tuples to create.
    void setVendorClassOption(Option::Universe u, uint32_t vendor_id,
                              size_t tuples_size = 0) {
        // Create the option first.
        vendor_class_.reset(new OptionVendorClass(u, vendor_id));

        // Now let's add specified number of data tuples
        OpaqueDataTuple::LengthFieldType len = (u == Option::V4?OpaqueDataTuple::LENGTH_1_BYTE:
                                                OpaqueDataTuple::LENGTH_2_BYTES);
296
        const char* content[] = { "alpha", "beta", "delta", "gamma", "epsilon",
Francis Dupont's avatar
Francis Dupont committed
297
298
                                  "zeta", "eta", "theta", "iota", "kappa" };
        const size_t nb_content = sizeof(content) / sizeof(char*);
299
        ASSERT_TRUE(tuples_size < nb_content);
Francis Dupont's avatar
Francis Dupont committed
300
        for (size_t i = 0; i < tuples_size; ++i) {
301
302
303
            OpaqueDataTuple tuple(len);
            tuple.assign(string(content[i]));
            if (u == Option::V4 && i == 0) {
Andrei Pavel's avatar
Andrei Pavel committed
304
                // vendor-class for v4 has a peculiar quirk. The first tuple is being
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
                // added, even if there's no data at all.
                vendor_class_->setTuple(0, tuple);
            } else {
                vendor_class_->addTuple(tuple);
            }
        }

        switch (u) {
        case Option::V4:
            pkt4_->delOption(DHO_VIVCO_SUBOPTIONS);
            pkt4_->addOption(vendor_class_);
            break;
        case Option::V6:
            pkt6_->delOption(D6O_VENDOR_CLASS);
            pkt6_->addOption(vendor_class_);
            break;
        }
    }

    /// @brief Auxiliary function that evaluates tokens and checks result
    ///
    /// Depending on the universe, either pkt4_ or pkt6_ are supposed to have
    /// all the necessary values and options set. The result is checked
    /// on the values_ stack.
    ///
    /// @param u universe (V4 or V6)
    /// @param expected_result text representation of the expected outcome
    void evaluate(Option::Universe u, std::string expected_result) {
        switch (u) {
        case Option::V4:
            EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
            break;
        case Option::V6:
            EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_));
            break;
        default:
            ADD_FAILURE() << "Invalid universe specified.";
        }
        ASSERT_EQ(1, values_.size());
        EXPECT_EQ(expected_result, values_.top());
    }

    /// @brief Tests if vendor token behaves properly.
    ///
    /// @param u universe (V4 or V6)
    /// @param token_vendor_id enterprise-id used in the token
    /// @param option_vendor_id enterprise-id used in option (0 means don't
    ///        create the option)
    /// @param expected_result text representation of the expected outcome
    void testVendorExists(Option::Universe u, uint32_t token_vendor_id,
355
356
                          uint32_t option_vendor_id,
                          const std::string& expected_result) {
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
        // Let's clear any old values, so we can run multiple cases in each test
        clearStack();

        // Create the token
        ASSERT_NO_THROW(t_.reset(new TokenVendor(u, token_vendor_id,
                                                 TokenOption::EXISTS)));

        // If specified option is non-zero, create it.
        if (option_vendor_id) {
            setVendorOption(u, option_vendor_id);
        }

        evaluate(u, expected_result);
    }

    /// @brief Tests if vendor token properly returns enterprise-id.
    ///
    /// @param u universe (V4 or V6)
    /// @param option_vendor_id enterprise-id used in option (0 means don't
    ///        create the option)
    /// @param expected_result text representation of the expected outcome
    void testVendorEnterprise(Option::Universe u, uint32_t option_vendor_id,
379
                              const std::string& expected_result) {
380
381
382
383
384
385
386
387
388
389
        // Let's clear any old values, so we can run multiple cases in each test
        clearStack();

        ASSERT_NO_THROW(t_.reset(new TokenVendor(u, 0, TokenVendor::ENTERPRISE_ID)));
        if (option_vendor_id) {
            setVendorOption(u, option_vendor_id);
        }

        evaluate(u, expected_result);
    }
390

391
392
393
394
395
396
397
    /// @brief Tests if vendor class token properly returns enterprise-id.
    ///
    /// @param u universe (V4 or V6)
    /// @param option_vendor_id enterprise-id used in option (0 means don't
    ///        create the option)
    /// @param expected_result text representation of the expected outcome
    void testVendorClassEnterprise(Option::Universe u, uint32_t option_vendor_id,
398
                                   const std::string& expected_result) {
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
        // Let's clear any old values, so we can run multiple cases in each test
        clearStack();

        ASSERT_NO_THROW(t_.reset(new TokenVendorClass(u, 0, TokenVendor::ENTERPRISE_ID)));
        if (option_vendor_id) {
            setVendorClassOption(u, option_vendor_id);
        }

        evaluate(u, expected_result);
    }

    /// @brief Tests if vendor class token can report existence properly.
    ///
    /// @param u universe (V4 or V6)
    /// @param token_vendor_id enterprise-id used in the token
    /// @param option_vendor_id enterprise-id used in option (0 means don't
    ///        create the option)
    /// @param expected_result text representation of the expected outcome
    void testVendorClassExists(Option::Universe u, uint32_t token_vendor_id,
418
419
                               uint32_t option_vendor_id,
                               const std::string& expected_result) {
420
421
422
423
424
425
426
427
428
429
430
431
432
        // Let's clear any old values, so we can run multiple cases in each test
        clearStack();

        ASSERT_NO_THROW(t_.reset(new TokenVendorClass(u, token_vendor_id,
                                                      TokenOption::EXISTS)));

        if (option_vendor_id) {
            setVendorClassOption(u, option_vendor_id);
        }

        evaluate(u, expected_result);
    }

433
    /// @brief Tests if vendor token can handle sub-options properly.
434
435
436
437
438
439
440
    ///
    /// @param u universe (V4 or V6)
    /// @param token_vendor_id enterprise-id used in the token
    /// @param token_option_code option code in the token
    /// @param option_vendor_id enterprise-id used in option (0 means don't
    ///        create the option)
    /// @param option_code sub-option code (0 means don't create suboption)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
441
    /// @param repr representation (TokenOption::EXISTS or HEXADECIMAL)
442
443
444
445
    /// @param expected_result text representation of the expected outcome
    void testVendorSuboption(Option::Universe u,
                             uint32_t token_vendor_id, uint16_t token_option_code,
                             uint32_t option_vendor_id, uint16_t option_code,
446
447
                             TokenOption::RepresentationType repr,
                             const std::string& expected) {
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
        // Let's clear any old values, so we can run multiple cases in each test
        clearStack();

        ASSERT_NO_THROW(t_.reset(new TokenVendor(u, token_vendor_id, repr,
                                                 token_option_code)));
        if (option_vendor_id) {
            setVendorOption(u, option_vendor_id);
            if (option_code) {
                ASSERT_TRUE(vendor_);
                OptionPtr subopt(new OptionString(u, option_code, "alpha"));
                vendor_->addOption(subopt);
            }
        }

        evaluate(u, expected);
    }

    /// @brief Tests if vendor class token can handle data chunks properly.
    ///
    /// @param u universe (V4 or V6)
    /// @param token_vendor_id enterprise-id used in the token
    /// @param token_index data index used in the token
    /// @param option_vendor_id enterprise-id used in option (0 means don't
    ///        create the option)
    /// @param data_tuples number of data tuples in the option
    /// @param expected_result text representation of the expected outcome
    void testVendorClassData(Option::Universe u,
                             uint32_t token_vendor_id, uint16_t token_index,
                             uint32_t option_vendor_id, uint16_t data_tuples,
477
                             const std::string& expected) {
478
479
480
481
482
483
484
485
486
487
488
        // Let's clear any old values, so we can run multiple cases in each test
        clearStack();

        ASSERT_NO_THROW(t_.reset(new TokenVendorClass(u, token_vendor_id,
                                                      TokenVendor::DATA, token_index)));
        if (option_vendor_id) {
            setVendorClassOption(u, option_vendor_id, data_tuples);
        }

        evaluate(u, expected);
    }
489

490
    /// @brief Tests if TokenInteger evaluates to the proper value
491
492
    ///
    /// @param expected expected string representation on stack after evaluation
493
    /// @param value integer value passed to constructor
494
    void testInteger(const std::string& expected, uint32_t value) {
495
496
497
498
499
500
501
502
503
504
505
506
507
508

        clearStack();

        ASSERT_NO_THROW(t_.reset(new TokenInteger(value)));

        // The universe (v4 or v6) shouldn't have any impact on this,
        // but let's check it anyway.
        evaluate(Option::V4, expected);

        clearStack(false);
        evaluate(Option::V6, expected);

        clearStack(true);
    }
509
510
};

511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
// This tests the toBool() conversions
TEST_F(TokenTest, toBool) {

    ASSERT_NO_THROW(Token::toBool("true"));
    EXPECT_TRUE(Token::toBool("true"));
    ASSERT_NO_THROW(Token::toBool("false"));
    EXPECT_FALSE(Token::toBool("false"));

    // Token::toBool() is case-sensitive
    EXPECT_THROW(Token::toBool("True"), EvalTypeError);
    EXPECT_THROW(Token::toBool("TRUE"), EvalTypeError);

    // Proposed aliases
    EXPECT_THROW(Token::toBool("1"), EvalTypeError);
    EXPECT_THROW(Token::toBool("0"), EvalTypeError);
    EXPECT_THROW(Token::toBool(""), EvalTypeError);
}

529
530
531
532
533
534
535
536
537
538
539
540
541
// This simple test checks that a TokenString, representing a constant string,
// can be used in Pkt4 evaluation. (The actual packet is not used)
TEST_F(TokenTest, string4) {

    // Store constant string "foo" in the TokenString object.
    ASSERT_NO_THROW(t_.reset(new TokenString("foo")));

    // Make sure that the token can be evaluated without exceptions.
    ASSERT_NO_THROW(t_->evaluate(*pkt4_, values_));

    // Check that the evaluation put its value on the values stack.
    ASSERT_EQ(1, values_.size());
    EXPECT_EQ("foo", values_.top());
542

543
544
545
546
547
    // Check that the debug output was correct.  Add the strings
    // to the test vector in the class and then call checkFile
    // for comparison
    addString("EVAL_DEBUG_STRING Pushing text string 'foo'");
    EXPECT_TRUE(checkFile());
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
}

// This simple test checks that a TokenString, representing a constant string,
// can be used in Pkt6 evaluation. (The actual packet is not used)
TEST_F(TokenTest, string6) {

    // Store constant string "foo" in the TokenString object.
    ASSERT_NO_THROW(t_.reset(new TokenString("foo")));

    // Make sure that the token can be evaluated without exceptions.
    ASSERT_NO_THROW(t_->evaluate(*pkt6_, values_));

    // Check that the evaluation put its value on the values stack.
    ASSERT_EQ(1, values_.size());
    EXPECT_EQ("foo", values_.top());
563

564
565
566
567
568
    // Check that the debug output was correct.  Add the strings
    // to the test vector in the class and then call checkFile
    // for comparison
    addString("EVAL_DEBUG_STRING Pushing text string 'foo'");
    EXPECT_TRUE(checkFile());
569
570
}

Francis Dupont's avatar
Francis Dupont committed
571
572
573
574
575
576
// This simple test checks that a TokenHexString, representing a constant
// string coded in hexadecimal, can be used in Pkt4 evaluation.
// (The actual packet is not used)
TEST_F(TokenTest, hexstring4) {
    TokenPtr empty;
    TokenPtr bad;
577
578
    TokenPtr nodigit;
    TokenPtr baddigit;
Francis Dupont's avatar
Francis Dupont committed
579
580
581
582
583
584
    TokenPtr bell;
    TokenPtr foo;
    TokenPtr cookie;

    // Store constant empty hexstring "" ("") in the TokenHexString object.
    ASSERT_NO_THROW(empty.reset(new TokenHexString("")));
585
586
587
588
589
590
591
592
593
594
595
596
    // Store bad encoded hexstring "0abc" ("").
    ASSERT_NO_THROW(bad.reset(new TokenHexString("0abc")));
    // Store hexstring with no digits "0x" ("").
    ASSERT_NO_THROW(nodigit.reset(new TokenHexString("0x")));
    // Store hexstring with a bad hexdigit "0xxabc" ("").
    ASSERT_NO_THROW(baddigit.reset(new TokenHexString("0xxabc")));
    // Store hexstring with an odd number of hexdigits "0x7" ("\a").
    ASSERT_NO_THROW(bell.reset(new TokenHexString("0x7")));
    // Store constant hexstring "0x666f6f" ("foo").
    ASSERT_NO_THROW(foo.reset(new TokenHexString("0x666f6f")));
    // Store constant hexstring "0x63825363" (DHCP_OPTIONS_COOKIE).
    ASSERT_NO_THROW(cookie.reset(new TokenHexString("0x63825363")));
Francis Dupont's avatar
Francis Dupont committed
597

598
599
    // Make sure that tokens can be evaluated without exceptions,
    // and verify the debug output
Francis Dupont's avatar
Francis Dupont committed
600
601
    ASSERT_NO_THROW(empty->evaluate(*pkt4_, values_));
    ASSERT_NO_THROW(bad->evaluate(*pkt4_, values_));
602
603
    ASSERT_NO_THROW(nodigit->evaluate(*pkt4_, values_));
    ASSERT_NO_THROW(baddigit->evaluate(*pkt4_, values_));
Francis Dupont's avatar
Francis Dupont committed
604
605
606
607
608
    ASSERT_NO_THROW(bell->evaluate(*pkt4_, values_));
    ASSERT_NO_THROW(foo->evaluate(*pkt4_, values_));
    ASSERT_NO_THROW(cookie->evaluate(*pkt4_, values_));

    // Check that the evaluation put its value on the values stack.
609
    ASSERT_EQ(7, values_.size());
Francis Dupont's avatar
Francis Dupont committed
610
611
    uint32_t expected = htonl(DHCP_OPTIONS_COOKIE);
    EXPECT_EQ(4, values_.top().size());
612
    EXPECT_EQ(0, memcmp(&expected, &values_.top()[0], 4));
Francis Dupont's avatar
Francis Dupont committed
613
614
615
616
617
618
619
620
    values_.pop();
    EXPECT_EQ("foo", values_.top());
    values_.pop();
    EXPECT_EQ("\a", values_.top());
    values_.pop();
    EXPECT_EQ("", values_.top());
    values_.pop();
    EXPECT_EQ("", values_.top());
621
622
623
624
    values_.pop();
    EXPECT_EQ("", values_.top());
    values_.pop();
    EXPECT_EQ("", values_.top());
625

626
627
628
629
630
631
632
633
634
635
636
    // Check that the debug output was correct.  Add the strings
    // to the test vector in the class and then call checkFile
    // for comparison
    addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x");
    addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x");
    addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x");
    addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x");
    addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x07");
    addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x666F6F");
    addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x63825363");
    EXPECT_TRUE(checkFile());
Francis Dupont's avatar
Francis Dupont committed
637
638
639
640
641
642
643
644
}

// This simple test checks that a TokenHexString, representing a constant
// string coded in hexadecimal, can be used in Pkt6 evaluation.
// (The actual packet is not used)
TEST_F(TokenTest, hexstring6) {
    TokenPtr empty;
    TokenPtr bad;
645
646
    TokenPtr nodigit;
    TokenPtr baddigit;
Francis Dupont's avatar
Francis Dupont committed
647
648
649
650
651
652
    TokenPtr bell;
    TokenPtr foo;
    TokenPtr cookie;

    // Store constant empty hexstring "" ("") in the TokenHexString object.
    ASSERT_NO_THROW(empty.reset(new TokenHexString("")));
653
654
655
656
657
658
659
660
661
662
663
664
    // Store bad encoded hexstring "0abc" ("").
    ASSERT_NO_THROW(bad.reset(new TokenHexString("0abc")));
    // Store hexstring with no digits "0x" ("").
    ASSERT_NO_THROW(nodigit.reset(new TokenHexString("0x")));
    // Store hexstring with a bad hexdigit "0xxabc" ("").
    ASSERT_NO_THROW(baddigit.reset(new TokenHexString("0xxabc")));
    // Store hexstring with an odd number of hexdigits "0x7" ("\a").
    ASSERT_NO_THROW(bell.reset(new TokenHexString("0x7")));
    // Store constant hexstring "0x666f6f" ("foo").
    ASSERT_NO_THROW(foo.reset(new TokenHexString("0x666f6f")));
    // Store constant hexstring "0x63825363" (DHCP_OPTIONS_COOKIE).
    ASSERT_NO_THROW(cookie.reset(new TokenHexString("0x63825363")));
Francis Dupont's avatar
Francis Dupont committed
665
666
667
668

    // Make sure that tokens can be evaluated without exceptions.
    ASSERT_NO_THROW(empty->evaluate(*pkt6_, values_));
    ASSERT_NO_THROW(bad->evaluate(*pkt6_, values_));
669
670
    ASSERT_NO_THROW(nodigit->evaluate(*pkt6_, values_));
    ASSERT_NO_THROW(baddigit->evaluate(*pkt6_, values_));
Francis Dupont's avatar
Francis Dupont committed
671
672
673
674
675
    ASSERT_NO_THROW(bell->evaluate(*pkt6_, values_));
    ASSERT_NO_THROW(foo->evaluate(*pkt6_, values_));
    ASSERT_NO_THROW(cookie->evaluate(*pkt6_, values_));

    // Check that the evaluation put its value on the values stack.
676
    ASSERT_EQ(7, values_.size());
Francis Dupont's avatar
Francis Dupont committed
677
678
    uint32_t expected = htonl(DHCP_OPTIONS_COOKIE);
    EXPECT_EQ(4, values_.top().size());
679
    EXPECT_EQ(0, memcmp(&expected, &values_.top()[0], 4));
Francis Dupont's avatar
Francis Dupont committed
680
681
682
683
684
685
686
687
    values_.pop();
    EXPECT_EQ("foo", values_.top());
    values_.pop();
    EXPECT_EQ("\a", values_.top());
    values_.pop();
    EXPECT_EQ("", values_.top());
    values_.pop();
    EXPECT_EQ("", values_.top());
688
689
690
691
    values_.pop();
    EXPECT_EQ("", values_.top());
    values_.pop();
    EXPECT_EQ("", values_.top());
692

693
694
695
696
697
698
699
700
701
702
703
    // Check that the debug output was correct.  Add the strings
    // to the test vector in the class and then call checkFile
    // for comparison
    addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x");
    addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x");
    addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x");
    addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x");
    addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x07");
    addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x666F6F");
    addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x63825363");
    EXPECT_TRUE(checkFile());
Francis Dupont's avatar
Francis Dupont committed
704
705
}

Francis Dupont's avatar
Francis Dupont committed
706
707
708
// This test checks that a TokenIpAddress, representing an IP address as
// a constant string, can be used in Pkt4/Pkt6 evaluation.
// (The actual packet is not used)
Francis Dupont's avatar
Francis Dupont committed
709
TEST_F(TokenTest, ipaddress) {
Francis Dupont's avatar
Francis Dupont committed
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
    TokenPtr bad4;
    TokenPtr bad6;
    TokenPtr ip4;
    TokenPtr ip6;

    // Bad IP addresses
    ASSERT_NO_THROW(bad4.reset(new TokenIpAddress("10.0.0.0.1")));
    ASSERT_NO_THROW(bad6.reset(new TokenIpAddress(":::")));

    // IP addresses
    ASSERT_NO_THROW(ip4.reset(new TokenIpAddress("10.0.0.1")));
    ASSERT_NO_THROW(ip6.reset(new TokenIpAddress("2001:db8::1")));

    // Make sure that tokens can be evaluated without exceptions.
    ASSERT_NO_THROW(ip4->evaluate(*pkt4_, values_));
    ASSERT_NO_THROW(ip6->evaluate(*pkt6_, values_));
    ASSERT_NO_THROW(bad4->evaluate(*pkt4_, values_));
    ASSERT_NO_THROW(bad6->evaluate(*pkt6_, values_));

    // Check that the evaluation put its value on the values stack.
    ASSERT_EQ(4, values_.size());

    // Check bad addresses (they pushed '' on the value stack)
    EXPECT_EQ(0, values_.top().size());
    values_.pop();
    EXPECT_EQ(0, values_.top().size());
    values_.pop();

    // Check IPv6 address
    uint8_t expected6[] = { 0x20, 1, 0xd, 0xb8, 0, 0, 0, 0,
                            0, 0, 0, 0, 0, 0, 0, 1 };
    EXPECT_EQ(16, values_.top().size());
    EXPECT_EQ(0, memcmp(expected6, &values_.top()[0], 16));
    values_.pop();

    // Check IPv4 address
    uint8_t expected4[] = { 10, 0, 0, 1 };
    EXPECT_EQ(4, values_.top().size());
    EXPECT_EQ(0, memcmp(expected4, &values_.top()[0], 4));
749

750
751
752
753
754
755
756
757
758
    // Check that the debug output was correct.  Add the strings
    // to the test vector in the class and then call checkFile
    // for comparison
    addString("EVAL_DEBUG_IPADDRESS Pushing IPAddress 0x0A000001");
    addString("EVAL_DEBUG_IPADDRESS Pushing IPAddress "
              "0x20010DB8000000000000000000000001");
    addString("EVAL_DEBUG_IPADDRESS Pushing IPAddress 0x");
    addString("EVAL_DEBUG_IPADDRESS Pushing IPAddress 0x");
    EXPECT_TRUE(checkFile());
Francis Dupont's avatar
Francis Dupont committed
759
760
}

761
// This test checks if a token representing an option value is able to extract
Tomek Mrugalski's avatar
Tomek Mrugalski committed
762
763
// the option from an IPv4 packet and properly store the option's value.
TEST_F(TokenTest, optionString4) {
764
765
766
767
    TokenPtr found;
    TokenPtr not_found;

    // The packets we use have option 100 with a string in them.
768
769
    ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::TEXTUAL)));
    ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::TEXTUAL)));
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786

    // This should evaluate to the content of the option 100 (i.e. "hundred4")
    ASSERT_NO_THROW(found->evaluate(*pkt4_, values_));

    // This should evaluate to "" as there is no option 101.
    ASSERT_NO_THROW(not_found->evaluate(*pkt4_, values_));

    // There should be 2 values evaluated.
    ASSERT_EQ(2, values_.size());

    // This is a stack, so the pop order is inversed. We should get the empty
    // string first.
    EXPECT_EQ("", values_.top());
    values_.pop();

    // Then the content of the option 100.
    EXPECT_EQ("hundred4", values_.top());
787

788
789
790
791
792
793
    // Check that the debug output was correct.  Add the strings
    // to the test vector in the class and then call checkFile
    // for comparison
    addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'hundred4'");
    addString("EVAL_DEBUG_OPTION Pushing option 101 with value ''");
    EXPECT_TRUE(checkFile());
794
795
}

796
797
// This test checks if a token representing option value is able to extract
// the option from an IPv4 packet and properly store its value in a
798
799
// hexadecimal format.
TEST_F(TokenTest, optionHexString4) {
800
801
802
803
    TokenPtr found;
    TokenPtr not_found;

    // The packets we use have option 100 with a string in them.
804
805
    ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::HEXADECIMAL)));
    ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::HEXADECIMAL)));
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821

    // This should evaluate to the content of the option 100 (i.e. "hundred4")
    ASSERT_NO_THROW(found->evaluate(*pkt4_, values_));

    // This should evaluate to "" as there is no option 101.
    ASSERT_NO_THROW(not_found->evaluate(*pkt4_, values_));

    // There should be 2 values evaluated.
    ASSERT_EQ(2, values_.size());

    // This is a stack, so the pop order is inversed. We should get the empty
    // string first.
    EXPECT_EQ("", values_.top());
    values_.pop();

    // Then the content of the option 100.
822
    EXPECT_EQ("hundred4", values_.top());
823

824
825
826
827
828
829
    // Check that the debug output was correct.  Add the strings
    // to the test vector in the class and then call checkFile
    // for comparison
    addString("EVAL_DEBUG_OPTION Pushing option 100 with value 0x68756E6472656434");
    addString("EVAL_DEBUG_OPTION Pushing option 101 with value 0x");
    EXPECT_TRUE(checkFile());
830
831
}

832
// This test checks if a token representing an option value is able to check
833
// the existence of the option from an IPv4 packet.
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
TEST_F(TokenTest, optionExistsString4) {
    TokenPtr found;
    TokenPtr not_found;

    // The packets we use have option 100 with a string in them.
    ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::EXISTS)));
    ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::EXISTS)));

    ASSERT_NO_THROW(found->evaluate(*pkt4_, values_));
    ASSERT_NO_THROW(not_found->evaluate(*pkt4_, values_));

    // There should be 2 values evaluated.
    ASSERT_EQ(2, values_.size());

    // This is a stack, so the pop order is inversed.
    EXPECT_EQ("false", values_.top());
    values_.pop();
    EXPECT_EQ("true", values_.top());
852

853
854
855
856
857
858
    // Check that the debug output was correct.  Add the strings
    // to the test vector in the class and then call checkFile
    // for comparison
    addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'true'");
    addString("EVAL_DEBUG_OPTION Pushing option 101 with value 'false'");
    EXPECT_TRUE(checkFile());
859
860
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
861
862
863
864
865
866
867
// This test checks if a token representing an option value is able to extract
// the option from an IPv6 packet and properly store the option's value.
TEST_F(TokenTest, optionString6) {
    TokenPtr found;
    TokenPtr not_found;

    // The packets we use have option 100 with a string in them.
868
869
    ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::TEXTUAL)));
    ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::TEXTUAL)));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886

    // This should evaluate to the content of the option 100 (i.e. "hundred6")
    ASSERT_NO_THROW(found->evaluate(*pkt6_, values_));

    // This should evaluate to "" as there is no option 101.
    ASSERT_NO_THROW(not_found->evaluate(*pkt6_, values_));

    // There should be 2 values evaluated.
    ASSERT_EQ(2, values_.size());

    // This is a stack, so the pop order is inversed. We should get the empty
    // string first.
    EXPECT_EQ("", values_.top());
    values_.pop();

    // Then the content of the option 100.
    EXPECT_EQ("hundred6", values_.top());
887

888
889
890
891
892
893
    // Check that the debug output was correct.  Add the strings
    // to the test vector in the class and then call checkFile
    // for comparison
    addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'hundred6'");
    addString("EVAL_DEBUG_OPTION Pushing option 101 with value ''");
    EXPECT_TRUE(checkFile());
Tomek Mrugalski's avatar
Tomek Mrugalski committed
894
895
}

896
// This test checks if a token representing an option value is able to extract
897
// the option from an IPv6 packet and properly store its value in hexadecimal
898
// format.
899
TEST_F(TokenTest, optionHexString6) {
900
901
902
903
    TokenPtr found;
    TokenPtr not_found;

    // The packets we use have option 100 with a string in them.
904
905
    ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::HEXADECIMAL)));
    ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::HEXADECIMAL)));
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921

    // This should evaluate to the content of the option 100 (i.e. "hundred6")
    ASSERT_NO_THROW(found->evaluate(*pkt6_, values_));

    // This should evaluate to "" as there is no option 101.
    ASSERT_NO_THROW(not_found->evaluate(*pkt6_, values_));

    // There should be 2 values evaluated.
    ASSERT_EQ(2, values_.size());

    // This is a stack, so the pop order is inversed. We should get the empty
    // string first.
    EXPECT_EQ("", values_.top());
    values_.pop();

    // Then the content of the option 100.
922
    EXPECT_EQ("hundred6", values_.top());
923

924
925
926
927
928
929
    // Check that the debug output was correct.  Add the strings
    // to the test vector in the class and then call checkFile
    // for comparison
    addString("EVAL_DEBUG_OPTION Pushing option 100 with value 0x68756E6472656436");
    addString("EVAL_DEBUG_OPTION Pushing option 101 with value 0x");
    EXPECT_TRUE(checkFile());
930
931
}

932
// This test checks if a token representing an option value is able to check
933
// the existence of the option from an IPv6 packet.
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
TEST_F(TokenTest, optionExistsString6) {
    TokenPtr found;
    TokenPtr not_found;

    // The packets we use have option 100 with a string in them.
    ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::EXISTS)));
    ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::EXISTS)));

    ASSERT_NO_THROW(found->evaluate(*pkt6_, values_));
    ASSERT_NO_THROW(not_found->evaluate(*pkt6_, values_));

    // There should be 2 values evaluated.
    ASSERT_EQ(2, values_.size());

    // This is a stack, so the pop order is inversed.
    EXPECT_EQ("false", values_.top());
    values_.pop();
    EXPECT_EQ("true", values_.top());
952

953
954
955
956
957
958
    // Check that the debug output was correct.  Add the strings
    // to the test vector in the class and then call checkFile
    // for comparison
    addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'true'");
    addString("EVAL_DEBUG_OPTION Pushing option 101 with value 'false'");
    EXPECT_TRUE(checkFile());
959
960
}

961
962
// This test checks that the existing relay4 option can be found.
TEST_F(TokenTest, relay4Option) {
963
964
965
966
967
968
969
970
971
972
973
974
975

    // Insert relay option with sub-options 1 and 13
    insertRelay4Option();

    // Creating the token should be safe.
    ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(13, TokenOption::TEXTUAL)));

    // We should be able to evaluate it.
    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));

    // we should have one value on the stack
    ASSERT_EQ(1, values_.size());

976
    // The option should be found and relay4[13] should evaluate to the
977
978
    // content of that sub-option, i.e. "thirteen"
    EXPECT_EQ("thirteen", values_.top());
979

980
981
982
983
984
    // Check that the debug output was correct.  Add the strings
    // to the test vector in the class and then call checkFile
    // for comparison
    addString("EVAL_DEBUG_OPTION Pushing option 13 with value 'thirteen'");
    EXPECT_TRUE(checkFile());
985
986
987
988
}

// This test checks that the code properly handles cases when
// there is a RAI option, but there's no requested sub-option.
989
TEST_F(TokenTest, relay4OptionNoSuboption) {
990
991
992
993
994
995
996
997
998
999
1000

    // Insert relay option with sub-options 1 and 13
    insertRelay4Option();

    // Creating the token should be safe.
    ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(15, TokenOption::TEXTUAL)));

    // We should be able to evaluate it.
    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));

    // we should have one value on the stack