mysql_host_data_source.cc 109 KB
Newer Older
1
// Copyright (C) 2015-2016 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
8

#include <config.h>

9
10
11
12
13
#include <dhcp/libdhcp++.h>
#include <dhcp/option.h>
#include <dhcp/option_definition.h>
#include <dhcp/option_space.h>
#include <dhcpsrv/cfg_option.h>
14
#include <dhcpsrv/db_exceptions.h>
15
16
17
#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/mysql_host_data_source.h>
#include <dhcpsrv/db_exceptions.h>
18
19
#include <util/buffer.h>
#include <util/optional_value.h>
20

21
22
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
23
#include <boost/array.hpp>
24
#include <boost/pointer_cast.hpp>
25
#include <boost/static_assert.hpp>
26
27

#include <mysql.h>
28
29
#include <mysqld_error.h>

30
#include <stdint.h>
31
32
33
#include <string>

using namespace isc;
34
using namespace isc::asiolink;
35
36
using namespace isc::dhcp;
using namespace isc::util;
37
38
39
using namespace std;

namespace {
40
41

/// @brief Maximum size of an IPv6 address represented as a text string.
42
///
43
44
45
46
/// This is 32 hexadecimal characters written in 8 groups of four, plus seven
/// colon separators.
const size_t ADDRESS6_TEXT_MAX_LEN = 39;

47
48
/// @brief Maximum length of classes stored in a dhcp4/6_client_classes
/// columns.
49
50
51
52
53
54
55
56
const size_t CLIENT_CLASSES_MAX_LEN = 255;

/// @brief Maximum length of the hostname stored in DNS.
///
/// This length is restricted by the length of the domain-name carried
/// in the Client FQDN %Option (see RFC4702 and RFC4704).
const size_t HOSTNAME_MAX_LEN = 255;

57
58
59
60
61
62
63
64
65
/// @brief Maximum length of option value.
const size_t OPTION_VALUE_MAX_LEN = 4096;

/// @brief Maximum length of option value specified in textual format.
const size_t OPTION_FORMATTED_VALUE_MAX_LEN = 8192;

/// @brief Maximum length of option space name.
const size_t OPTION_SPACE_MAX_LEN = 128;

66
67
68
69
70
71
/// @brief Maximum length of the server hostname.
const size_t SERVER_HOSTNAME_MAX_LEN = 64;

/// @brief Maximum length of the boot file name.
const size_t BOOT_FILE_NAME_MAX_LEN = 128;

72
73
74
75
76
/// @brief Numeric value representing last supported identifier.
///
/// This value is used to validate whether the identifier type stored in
/// a database is within bounds. of supported identifiers.
const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::IDENT_CIRCUIT_ID);
77

78
79
80
/// @brief This class provides mechanisms for sending and retrieving
/// information from the 'hosts' table.
///
81
82
83
84
85
/// This class is used to insert and retrieve entries from the 'hosts' table.
/// The queries used with this class do not retrieve IPv6 reservations or
/// options associated with a host to minimize impact on performance. Other
/// classes derived from @ref MySqlHostExchange should be used to retrieve
/// information about IPv6 reservations and options.
86
87
class MySqlHostExchange {
private:
88

89
    /// @brief Number of columns returned for SELECT queries send by this class.
90
    static const size_t HOST_COLUMNS = 12;
91
92
93
94
95

public:

    /// @brief Constructor
    ///
96
97
98
99
    /// @param additional_columns_num This value is set by the derived classes
    /// to indicate how many additional columns will be returned by SELECT
    /// queries performed by the derived class. This constructor will allocate
    /// resources for these columns, e.g. binding table, error indicators.
100
101
102
103
    MySqlHostExchange(const size_t additional_columns_num = 0)
        : columns_num_(HOST_COLUMNS + additional_columns_num),
          bind_(columns_num_), columns_(columns_num_),
          error_(columns_num_, MLM_FALSE), host_id_(0),
104
105
106
          dhcp_identifier_length_(0), dhcp_identifier_type_(0),
          dhcp4_subnet_id_(0), dhcp6_subnet_id_(0), ipv4_address_(0),
          hostname_length_(0), dhcp4_client_classes_length_(0),
107
          dhcp6_client_classes_length_(0),
108
109
110
          dhcp4_next_server_(0),
          dhcp4_server_hostname_length_(0),
          dhcp4_boot_file_name_length_(0),
111
112
113
114
          dhcp4_subnet_id_null_(MLM_FALSE),
          dhcp6_subnet_id_null_(MLM_FALSE),
          ipv4_address_null_(MLM_FALSE), hostname_null_(MLM_FALSE),
          dhcp4_client_classes_null_(MLM_FALSE),
115
116
117
118
          dhcp6_client_classes_null_(MLM_FALSE),
          dhcp4_next_server_null_(MLM_FALSE),
          dhcp4_server_hostname_null_(MLM_FALSE),
          dhcp4_boot_file_name_null_(MLM_FALSE) {
119

120
        // Fill arrays with 0 so as they don't include any garbage.
121
122
123
124
        memset(dhcp_identifier_buffer_, 0, sizeof(dhcp_identifier_buffer_));
        memset(hostname_, 0, sizeof(hostname_));
        memset(dhcp4_client_classes_, 0, sizeof(dhcp4_client_classes_));
        memset(dhcp6_client_classes_, 0, sizeof(dhcp6_client_classes_));
125
126
        memset(dhcp4_server_hostname_, 0, sizeof(dhcp4_server_hostname_));
        memset(dhcp4_boot_file_name_, 0, sizeof(dhcp4_boot_file_name_));
127

128
129
130
        // Set the column names for use by this class. This only comprises
        // names used by the MySqlHostExchange class. Derived classes will
        // need to set names for the columns they use.
131
132
133
134
135
136
137
138
139
        columns_[0] = "host_id";
        columns_[1] = "dhcp_identifier";
        columns_[2] = "dhcp_identifier_type";
        columns_[3] = "dhcp4_subnet_id";
        columns_[4] = "dhcp6_subnet_id";
        columns_[5] = "ipv4_address";
        columns_[6] = "hostname";
        columns_[7] = "dhcp4_client_classes";
        columns_[8] = "dhcp6_client_classes";
140
141
142
        columns_[9] = "dhcp4_next_server";
        columns_[10] = "dhcp4_server_hostname";
        columns_[11] = "dhcp4_boot_file_name";
143

144
        BOOST_STATIC_ASSERT(11 < HOST_COLUMNS);
145
146
147
148
    };

    /// @brief Virtual destructor.
    virtual ~MySqlHostExchange() {
149
150
    }

151
152
153
154
155
156
157
158
159
160
161
162
163
164
    /// @brief Returns index of the first uninitialized column name.
    ///
    /// This method is called by the derived classes to determine which
    /// column indexes are available for the derived classes within a
    /// binding array, error array and column names. This method
    /// determines the first available index by searching the first
    /// empty value within the columns_ vector. Previously we relied on
    /// the fixed values set for each class, but this was hard to maintain
    /// when new columns were added to the SELECT queries. It required
    /// modifying indexes in all derived classes.
    ///
    /// Derived classes must call this method in their constructors and
    /// use returned value as an index for the first column used by the
    /// derived class and increment this value for each subsequent column.
165
166
167
168
169
170
    size_t findAvailColumn() const {
        std::vector<std::string>::const_iterator empty_column =
            std::find(columns_.begin(), columns_.end(), std::string());
        return (std::distance(columns_.begin(), empty_column));
    }

171
    /// @brief Returns value of host id.
172
173
    ///
    /// This method is used by derived classes.
174
175
    uint64_t getHostId() const {
        return (host_id_);
176
    };
177
178
179
180
181
182
183
184
185
186
187

    /// @brief Set error indicators
    ///
    /// Sets the error indicator for each of the MYSQL_BIND elements. It points
    /// the "error" field within an element of the input array to the
    /// corresponding element of the passed error array.
    ///
    /// @param bind Array of BIND elements
    /// @param error Array of error elements.  If there is an error in getting
    ///        data associated with one of the "bind" elements, the
    ///        corresponding element in the error array is set to MLM_TRUE.
188
189
190
    static void setErrorIndicators(std::vector<MYSQL_BIND>& bind,
                                   std::vector<my_bool>& error) {
        for (size_t i = 0; i < error.size(); ++i) {
191
192
193
            error[i] = MLM_FALSE;
            bind[i].error = reinterpret_cast<char*>(&error[i]);
        }
194
    };
195
196
197
198
199
200
201
202
203
204
205
206
207
208

    /// @brief Return columns in error
    ///
    /// If an error is returned from a fetch (in particular, a truncated
    /// status), this method can be called to get the names of the fields in
    /// error.  It returns a string comprising the names of the fields
    /// separated by commas.  In the case of there being no error indicators
    /// set, it returns the string "(None)".
    ///
    /// @param error Array of error elements.  An element is set to MLM_TRUE
    ///        if the corresponding column in the database is the source of
    ///        the error.
    /// @param names Array of column names, the same size as the error array.
    /// @param count Size of each of the arrays.
209
210
    static std::string getColumnsInError(std::vector<my_bool>& error,
                                         const std::vector<std::string>& names) {
211
212
213
        std::string result = "";

        // Accumulate list of column names
214
        for (size_t i = 0; i < names.size(); ++i) {
215
216
217
218
219
220
221
222
223
224
225
226
227
            if (error[i] == MLM_TRUE) {
                if (!result.empty()) {
                    result += ", ";
                }
                result += names[i];
            }
        }

        if (result.empty()) {
            result = "(None)";
        }

        return (result);
228
    };
229
230
231

    /// @brief Create MYSQL_BIND objects for Host Pointer
    ///
232
233
    /// Fills in the MYSQL_BIND array for sending data stored in the Host object
    /// to the database.
234
235
236
237
238
239
240
241
242
243
244
245
246
    ///
    /// @param host Host object to be added to the database.
    ///        None of the fields in the host reservation are modified -
    ///        the host data is only read.
    ///
    /// @return Vector of MySQL BIND objects representing the data to be added.
    std::vector<MYSQL_BIND> createBindForSend(const HostPtr& host) {
        // Store host object to ensure it remains valid.
        host_ = host;

        // Initialize prior to constructing the array of MYSQL_BIND structures.
        // It sets all fields, including is_null, to zero, so we need to set
        // is_null only if it should be true. This gives up minor performance
247
248
        // benefit while being safe approach.
        memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());
249
250
251
252
253
254
255

        // Set up the structures for the various components of the host structure.

        try {
            // host_id : INT UNSIGNED NOT NULL
            // The host_id is auto_incremented by MySQL database,
            // so we need to pass the NULL value
256
            host_id_ = 0;
257
258
259
260
261
            bind_[0].buffer_type = MYSQL_TYPE_LONG;
            bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
            bind_[0].is_unsigned = MLM_TRUE;

            // dhcp_identifier : VARBINARY(128) NOT NULL
262
263
264
265
266
267
268
269
270
            dhcp_identifier_length_ = host->getIdentifier().size();
            memcpy(static_cast<void*>(dhcp_identifier_buffer_),
                   &(host->getIdentifier())[0],
                   host->getIdentifier().size());

            bind_[1].buffer_type = MYSQL_TYPE_BLOB;
            bind_[1].buffer = dhcp_identifier_buffer_;
            bind_[1].buffer_length = dhcp_identifier_length_;
            bind_[1].length = &dhcp_identifier_length_;
271
272

            // dhcp_identifier_type : TINYINT NOT NULL
273
274
275
276
            dhcp_identifier_type_ = static_cast<uint8_t>(host->getIdentifierType());
            bind_[2].buffer_type = MYSQL_TYPE_TINY;
            bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
            bind_[2].is_unsigned = MLM_TRUE;
277
278
279
280

            // dhcp4_subnet_id : INT UNSIGNED NULL
            // Can't take an address of intermediate object, so let's store it
            // in dhcp4_subnet_id_
281
            dhcp4_subnet_id_ = host->getIPv4SubnetID();
282
283
284
285
286
287
288
            bind_[3].buffer_type = MYSQL_TYPE_LONG;
            bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
            bind_[3].is_unsigned = MLM_TRUE;

            // dhcp6_subnet_id : INT UNSIGNED NULL
            // Can't take an address of intermediate object, so let's store it
            // in dhcp6_subnet_id_
289
            dhcp6_subnet_id_ = host->getIPv6SubnetID();
290
291
292
293
294
295
296
            bind_[4].buffer_type = MYSQL_TYPE_LONG;
            bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
            bind_[4].is_unsigned = MLM_TRUE;

            // ipv4_address : INT UNSIGNED NULL
            // The address in the Host structure is an IOAddress object.  Convert
            // this to an integer for storage.
297
            ipv4_address_ = host->getIPv4Reservation().toUint32();
298
299
300
301
            bind_[5].buffer_type = MYSQL_TYPE_LONG;
            bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
            bind_[5].is_unsigned = MLM_TRUE;
            // bind_[5].is_null = &MLM_FALSE; // commented out for performance
302
                                                      // reasons, see memset() above
303
304

            // hostname : VARCHAR(255) NULL
305
306
            strncpy(hostname_, host->getHostname().c_str(), HOSTNAME_MAX_LEN - 1);
            hostname_length_ = host->getHostname().length();
307
308
309
310
311
312
            bind_[6].buffer_type = MYSQL_TYPE_STRING;
            bind_[6].buffer = reinterpret_cast<char*>(hostname_);
            bind_[6].buffer_length = hostname_length_;

            // dhcp4_client_classes : VARCHAR(255) NULL
            bind_[7].buffer_type = MYSQL_TYPE_STRING;
313
314
            // Override default separator to not include space after comma.
            string classes4_txt = host->getClientClasses4().toText(",");
315
316
317
318
319
320
            strncpy(dhcp4_client_classes_, classes4_txt.c_str(), CLIENT_CLASSES_MAX_LEN - 1);
            bind_[7].buffer = dhcp4_client_classes_;
            bind_[7].buffer_length = classes4_txt.length();

            // dhcp6_client_classes : VARCHAR(255) NULL
            bind_[8].buffer_type = MYSQL_TYPE_STRING;
321
322
            // Override default separator to not include space after comma.
            string classes6_txt = host->getClientClasses6().toText(",");
323
324
325
326
            strncpy(dhcp6_client_classes_, classes6_txt.c_str(), CLIENT_CLASSES_MAX_LEN - 1);
            bind_[8].buffer = dhcp6_client_classes_;
            bind_[8].buffer_length = classes6_txt.length();

327
328
329
            // ipv4_address : INT UNSIGNED NULL
            // The address in the Host structure is an IOAddress object.  Convert
            // this to an integer for storage.
330
            dhcp4_next_server_ = host->getNextServer().toUint32();
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
            bind_[9].buffer_type = MYSQL_TYPE_LONG;
            bind_[9].buffer = reinterpret_cast<char*>(&dhcp4_next_server_);
            bind_[9].is_unsigned = MLM_TRUE;
            // bind_[9].is_null = &MLM_FALSE; // commented out for performance
                                              // reasons, see memset() above

            // dhcp4_server_hostname
            bind_[10].buffer_type = MYSQL_TYPE_STRING;
            std::string server_hostname = host->getServerHostname();
            strncpy(dhcp4_server_hostname_, server_hostname.c_str(),
                    SERVER_HOSTNAME_MAX_LEN - 1);
            bind_[10].buffer = dhcp4_server_hostname_;
            bind_[10].buffer_length = server_hostname.length();

            // dhcp4_boot_file_name
            bind_[11].buffer_type = MYSQL_TYPE_STRING;
            std::string boot_file_name = host->getBootFileName();
            strncpy(dhcp4_boot_file_name_, boot_file_name.c_str(),
                    BOOT_FILE_NAME_MAX_LEN - 1);
            bind_[11].buffer = dhcp4_boot_file_name_;
            bind_[11].buffer_length = boot_file_name.length();

353
354
355
        } catch (const std::exception& ex) {
            isc_throw(DbOperationError,
                      "Could not create bind array from Host: "
356
                      << host->getHostname() << ", reason: " << ex.what());
357
358
359
360
        }

        // Add the data to the vector.  Note the end element is one after the
        // end of the array.
361
        return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[columns_num_]));
362
    };
363

364
    /// @brief Create BIND array to receive Host data.
365
    ///
366
367
368
    /// Creates a MYSQL_BIND array to receive Host data from the database.
    /// After data is successfully received, @ref retrieveHost can be called
    /// to retrieve the Host object.
369
    ///
370
371
    /// @return Vector of MYSQL_BIND objects representing data to be retrieved.
    virtual std::vector<MYSQL_BIND> createBindForReceive() {
372
373
374
375
376
377
        // Initialize MYSQL_BIND array.
        // It sets all fields, including is_null, to zero, so we need to set
        // is_null only if it should be true. This gives up minor performance
        // benefit while being safe approach. For improved readability, the
        // code that explicitly sets is_null is there, but is commented out.
        // This also takes care of seeeting bind_[X].is_null to MLM_FALSE.
378
        memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444

        // host_id : INT UNSIGNED NOT NULL
        bind_[0].buffer_type = MYSQL_TYPE_LONG;
        bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
        bind_[0].is_unsigned = MLM_TRUE;

        // dhcp_identifier : VARBINARY(128) NOT NULL
        dhcp_identifier_length_ = sizeof(dhcp_identifier_buffer_);
        bind_[1].buffer_type = MYSQL_TYPE_BLOB;
        bind_[1].buffer = reinterpret_cast<char*>(dhcp_identifier_buffer_);
        bind_[1].buffer_length = dhcp_identifier_length_;
        bind_[1].length = &dhcp_identifier_length_;

        // dhcp_identifier_type : TINYINT NOT NULL
        bind_[2].buffer_type = MYSQL_TYPE_TINY;
        bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
        bind_[2].is_unsigned = MLM_TRUE;

        // dhcp4_subnet_id : INT UNSIGNED NULL
        dhcp4_subnet_id_null_ = MLM_FALSE;
        bind_[3].buffer_type = MYSQL_TYPE_LONG;
        bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
        bind_[3].is_unsigned = MLM_TRUE;
        bind_[3].is_null = &dhcp4_subnet_id_null_;

        // dhcp6_subnet_id : INT UNSIGNED NULL
        dhcp6_subnet_id_null_ = MLM_FALSE;
        bind_[4].buffer_type = MYSQL_TYPE_LONG;
        bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
        bind_[4].is_unsigned = MLM_TRUE;
        bind_[4].is_null = &dhcp6_subnet_id_null_;

        // ipv4_address : INT UNSIGNED NULL
        ipv4_address_null_ = MLM_FALSE;
        bind_[5].buffer_type = MYSQL_TYPE_LONG;
        bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
        bind_[5].is_unsigned = MLM_TRUE;
        bind_[5].is_null = &ipv4_address_null_;

        // hostname : VARCHAR(255) NULL
        hostname_null_ = MLM_FALSE;
        hostname_length_ = sizeof(hostname_);
        bind_[6].buffer_type = MYSQL_TYPE_STRING;
        bind_[6].buffer = reinterpret_cast<char*>(hostname_);
        bind_[6].buffer_length = hostname_length_;
        bind_[6].length = &hostname_length_;
        bind_[6].is_null = &hostname_null_;

        // dhcp4_client_classes : VARCHAR(255) NULL
        dhcp4_client_classes_null_ = MLM_FALSE;
        dhcp4_client_classes_length_ = sizeof(dhcp4_client_classes_);
        bind_[7].buffer_type = MYSQL_TYPE_STRING;
        bind_[7].buffer = reinterpret_cast<char*>(dhcp4_client_classes_);
        bind_[7].buffer_length = dhcp4_client_classes_length_;
        bind_[7].length = &dhcp4_client_classes_length_;
        bind_[7].is_null = &dhcp4_client_classes_null_;

        // dhcp6_client_classes : VARCHAR(255) NULL
        dhcp6_client_classes_null_ = MLM_FALSE;
        dhcp6_client_classes_length_ = sizeof(dhcp6_client_classes_);
        bind_[8].buffer_type = MYSQL_TYPE_STRING;
        bind_[8].buffer = reinterpret_cast<char*>(dhcp6_client_classes_);
        bind_[8].buffer_length = dhcp6_client_classes_length_;
        bind_[8].length = &dhcp6_client_classes_length_;
        bind_[8].is_null = &dhcp6_client_classes_null_;

445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
        // dhcp4_next_server
        dhcp4_next_server_null_ = MLM_FALSE;
        bind_[9].buffer_type = MYSQL_TYPE_LONG;
        bind_[9].buffer = reinterpret_cast<char*>(&dhcp4_next_server_);
        bind_[9].is_unsigned = MLM_TRUE;
        bind_[9].is_null = &dhcp4_next_server_null_;

        // dhcp4_server_hostname
        dhcp4_server_hostname_null_ = MLM_FALSE;
        dhcp4_server_hostname_length_ = sizeof(dhcp4_server_hostname_);
        bind_[10].buffer_type = MYSQL_TYPE_STRING;
        bind_[10].buffer = reinterpret_cast<char*>(dhcp4_server_hostname_);
        bind_[10].buffer_length = dhcp4_server_hostname_length_;
        bind_[10].length = &dhcp4_server_hostname_length_;
        bind_[10].is_null = &dhcp4_server_hostname_null_;

        // dhcp4_boot_file_name
        dhcp4_boot_file_name_null_ = MLM_FALSE;
        dhcp4_boot_file_name_length_ = sizeof(dhcp4_boot_file_name_);
        bind_[11].buffer_type = MYSQL_TYPE_STRING;
        bind_[11].buffer = reinterpret_cast<char*>(dhcp4_boot_file_name_);
        bind_[11].buffer_length = dhcp4_boot_file_name_length_;
        bind_[11].length = &dhcp4_boot_file_name_length_;
        bind_[11].is_null = &dhcp4_boot_file_name_null_;

470
        // Add the error flags
471
        setErrorIndicators(bind_, error_);
472
473
474

        // Add the data to the vector.  Note the end element is one after the
        // end of the array.
475
476
        return (bind_);
    };
477

478
    /// @brief Copy received data into Host object
479
    ///
480
481
482
    /// This function copies information about the host into a newly created
    /// @ref Host object. This method is called after @ref createBindForReceive.
    /// has been used.
483
    ///
484
485
486
    /// @return Host Pointer to a @ref HostPtr object holding a pointer to the
    /// @ref Host object returned.
    HostPtr retrieveHost() {
487
488
        // Check if the identifier stored in the database is correct.
        if (dhcp_identifier_type_ > MAX_IDENTIFIER_TYPE) {
489
            isc_throw(BadValue, "invalid dhcp identifier type returned: "
490
                      << static_cast<int>(dhcp_identifier_type_));
491
        }
492
493
        // Set the dhcp identifier type in a variable of the appropriate
        // data type.
494
495
        Host::IdentifierType type =
            static_cast<Host::IdentifierType>(dhcp_identifier_type_);
496

497
498
        // Set DHCPv4 subnet ID to the value returned. If NULL returned,
        // set to 0.
499
500
501
502
503
        SubnetID ipv4_subnet_id(0);
        if (dhcp4_subnet_id_null_ == MLM_FALSE) {
            ipv4_subnet_id = static_cast<SubnetID>(dhcp4_subnet_id_);
        }

504
505
        // Set DHCPv6 subnet ID to the value returned. If NULL returned,
        // set to 0.
506
507
508
509
510
        SubnetID ipv6_subnet_id(0);
        if (dhcp6_subnet_id_null_ == MLM_FALSE) {
            ipv6_subnet_id = static_cast<SubnetID>(dhcp6_subnet_id_);
        }

511
512
        // Set IPv4 address reservation if it was given, if not, set IPv4 zero
        // address
513
514
515
516
517
        asiolink::IOAddress ipv4_reservation = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
        if (ipv4_address_null_ == MLM_FALSE) {
            ipv4_reservation = asiolink::IOAddress(ipv4_address_);
        }

518
519
        // Set hostname if non NULL value returned. Otherwise, leave an
        // empty string.
520
521
        std::string hostname;
        if (hostname_null_ == MLM_FALSE) {
522
            hostname = std::string(hostname_, hostname_length_);
523
524
        }

525
526
        // Set DHCPv4 client classes if non NULL value returned.
        std::string dhcp4_client_classes;
527
        if (dhcp4_client_classes_null_ == MLM_FALSE) {
528
529
            dhcp4_client_classes = std::string(dhcp4_client_classes_,
                                               dhcp4_client_classes_length_);
530
531
        }

532
533
        // Set DHCPv6 client classes if non NULL value returned.
        std::string dhcp6_client_classes;
534
        if (dhcp6_client_classes_null_ == MLM_FALSE) {
535
536
            dhcp6_client_classes = std::string(dhcp6_client_classes_,
                                               dhcp6_client_classes_length_);
537
538
        }

539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
        // Set next server value (siaddr) if non NULL value returned.
        asiolink::IOAddress next_server = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
        if (dhcp4_next_server_null_ == MLM_FALSE) {
            next_server = asiolink::IOAddress(dhcp4_next_server_);
        }

        // Set server hostname (sname) if non NULL value returned.
        std::string dhcp4_server_hostname;
        if (dhcp4_server_hostname_null_ == MLM_FALSE) {
            dhcp4_server_hostname = std::string(dhcp4_server_hostname_,
                                                dhcp4_server_hostname_length_);
        }

        // Set boot file name (file) if non NULL value returned.
        std::string dhcp4_boot_file_name;
        if (dhcp4_boot_file_name_null_ == MLM_FALSE) {
            dhcp4_boot_file_name = std::string(dhcp4_boot_file_name_,
                                               dhcp4_boot_file_name_length_);
        }

559
        // Create and return Host object from the data gathered.
560
561
        HostPtr h(new Host(dhcp_identifier_buffer_, dhcp_identifier_length_,
                           type, ipv4_subnet_id, ipv6_subnet_id, ipv4_reservation,
562
563
564
                           hostname, dhcp4_client_classes, dhcp6_client_classes,
                           next_server, dhcp4_server_hostname,
                           dhcp4_boot_file_name));
565
566
567
        h->setHostId(host_id_);

        return (h);
568
569
570
571
572
573
574
575
576
577
    };

    /// @brief Processes one row of data fetched from a database.
    ///
    /// The processed data must contain host id, which uniquely identifies a
    /// host. This method creates a host and inserts it to the hosts collection
    /// only if the last inserted host has a different host id. This prevents
    /// adding duplicated hosts to the collection, assuming that processed
    /// rows are primarily ordered by host id column.
    ///
578
579
    /// This method must be overriden in the derived classes to also
    /// retrieve IPv6 reservations and DHCP options associated with a host.
580
581
582
583
584
585
586
587
588
589
590
591
592
593
    ///
    /// @param [out] hosts Collection of hosts to which a new host created
    ///        from the processed data should be inserted.
    virtual void processFetchedData(ConstHostCollection& hosts) {
        HostPtr host;
        // Add new host only if there are no hosts yet or the host id of the
        // most recently added host is different than the host id of the
        // currently processed host.
        if (hosts.empty() || (hosts.back()->getHostId() != getHostId())) {
            // Create Host object from the fetched data and append it to the
            // collection.
            host = retrieveHost();
            hosts.push_back(host);
        }
594
595
596
597
598
599
600
601
602
603
604
605
606
    }

    /// @brief Return columns in error
    ///
    /// If an error is returned from a fetch (in particular, a truncated
    /// status), this method can be called to get the names of the fields in
    /// error.  It returns a string comprising the names of the fields
    /// separated by commas.  In the case of there being no error indicators
    /// set, it returns the string "(None)".
    ///
    /// @return Comma-separated list of columns in error, or the string
    ///         "(None)".
    std::string getErrorColumns() {
607
608
609
610
611
        return (getColumnsInError(error_, columns_));
    };

protected:

612
613
614
    /// Number of columns returned in queries.
    size_t columns_num_;

615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
    /// Vector of MySQL bindings.
    std::vector<MYSQL_BIND> bind_;

    /// Column names.
    std::vector<std::string> columns_;

    /// Error array.
    std::vector<my_bool> error_;

    /// Pointer to Host object holding information to be inserted into
    /// Hosts table.
    HostPtr host_;

private:

    /// Host identifier (primary key in Hosts table).
    uint64_t host_id_;

    /// Buffer holding client's identifier (e.g. DUID, HW address)
    /// in the binary format.
    uint8_t dhcp_identifier_buffer_[DUID::MAX_DUID_LEN];

    /// Length of a data in the dhcp_identifier_buffer_.
638
    unsigned long dhcp_identifier_length_;
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672

    /// Type of the identifier in the dhcp_identifier_buffer_. This
    /// value corresponds to the @ref Host::IdentifierType value.
    uint8_t dhcp_identifier_type_;

    /// DHCPv4 subnet identifier.
    uint32_t dhcp4_subnet_id_;

    /// DHCPv6 subnet identifier.
    uint32_t dhcp6_subnet_id_;

    /// Reserved IPv4 address.
    uint32_t ipv4_address_;

    /// Name reserved for the host.
    char hostname_[HOSTNAME_MAX_LEN];

    /// Hostname length.
    unsigned long hostname_length_;

    /// A string holding comma separated list of DHCPv4 client classes.
    char dhcp4_client_classes_[CLIENT_CLASSES_MAX_LEN];

    /// A length of the string holding comma separated list of DHCPv4
    /// client classes.
    unsigned long dhcp4_client_classes_length_;

    /// A string holding comma separated list of DHCPv6 client classes.
    char dhcp6_client_classes_[CLIENT_CLASSES_MAX_LEN];

    /// A length of the string holding comma separated list of DHCPv6
    /// client classes.
    unsigned long dhcp6_client_classes_length_;

673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
    /// Next server address (siaddr).
    uint32_t dhcp4_next_server_;

    /// Server hostname (sname).
    char dhcp4_server_hostname_[SERVER_HOSTNAME_MAX_LEN];

    /// A length of the string holding server hostname.
    unsigned long dhcp4_server_hostname_length_;

    /// Boot file name (file).
    char dhcp4_boot_file_name_[BOOT_FILE_NAME_MAX_LEN];

    /// A length of the string holding boot file name.
    unsigned long dhcp4_boot_file_name_length_;

688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
    /// @name Boolean values indicating if values of specific columns in
    /// the database are NULL.
    //@{
    /// Boolean flag indicating if the value of the DHCPv4 subnet is NULL.
    my_bool dhcp4_subnet_id_null_;

    /// Boolean flag indicating if the value of the DHCPv6 subnet is NULL.
    my_bool dhcp6_subnet_id_null_;

    /// Boolean flag indicating if the value of IPv4 reservation is NULL.
    my_bool ipv4_address_null_;

    /// Boolean flag indicating if the value if hostname is NULL.
    my_bool hostname_null_;

    /// Boolean flag indicating if the value of DHCPv4 client classes is
    /// NULL.
    my_bool dhcp4_client_classes_null_;

    /// Boolean flag indicating if the value of DHCPv6 client classes is
    /// NULL.
    my_bool dhcp6_client_classes_null_;

711
712
713
714
715
716
717
718
719
    /// Boolean flag indicating if the value of next server is NULL.
    my_bool dhcp4_next_server_null_;

    /// Boolean flag indicating if the value of server hostname is NULL.
    my_bool dhcp4_server_hostname_null_;

    /// Boolean flag indicating if the value of boot file name is NULL.
    my_bool dhcp4_boot_file_name_null_;

720
721
722
723
    //@}

};

724
725
726
727
728
729
730
731
732
/// @brief Extends base exchange class with ability to retrieve DHCP options
/// from the 'dhcp4_options' and 'dhcp6_options' tables.
///
/// This class provides means to retrieve both DHCPv4 and DHCPv6 options
/// along with the host information. It is not used to retrieve IPv6
/// reservations. The following types of queries are supported:
/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options ...
/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options ...
/// - SELECT ? FROM hosts LEFT JOIN dhcp6_options ...
733
class MySqlHostWithOptionsExchange : public MySqlHostExchange {
734
735
private:

736
    /// @brief Number of columns holding DHCPv4  or DHCPv6 option information.
737
738
    static const size_t OPTION_COLUMNS = 6;

739
740
741
    /// @brief Receives DHCPv4 or DHCPv6 options information from the
    /// dhcp4_options or dhcp6_options tables respectively.
    ///
742
743
744
    /// The MySqlHostWithOptionsExchange class holds two respective instances
    /// of this class, one for receiving DHCPv4 options, one for receiving
    /// DHCPv6 options.
745
746
747
748
749
750
751
752
    ///
    /// The following are the basic functions of this class:
    /// - bind class members to specific columns in MySQL binding tables,
    /// - set DHCP options specific column names,
    /// - create instances of options retrieved from the database.
    ///
    /// The reason for isolating those functions in a separate C++ class is
    /// to prevent code duplication for handling DHCPv4 and DHCPv6 options.
753
754
    class OptionProcessor {
    public:
755
756
757
758
759
760
761

        /// @brief Constructor.
        ///
        /// @param universe V4 or V6. The type of the options' instances
        /// created by this class depends on this parameter.
        /// @param start_column Index of the first column to be used by this
        /// class.
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
        OptionProcessor(const Option::Universe& universe,
                        const size_t start_column)
        : universe_(universe), start_column_(start_column), option_id_(0),
          code_(0), value_length_(0), formatted_value_length_(0),
          space_length_(0), persistent_(false), option_id_null_(MLM_FALSE),
          code_null_(MLM_FALSE), value_null_(MLM_FALSE),
          formatted_value_null_(MLM_FALSE), space_null_(MLM_FALSE),
          option_id_index_(start_column), code_index_(start_column_ + 1),
          value_index_(start_column_ + 2),
          formatted_value_index_(start_column_ + 3),
          space_index_(start_column_ + 4),
          persistent_index_(start_column_ + 5),
          most_recent_option_id_(0) {

            memset(value_, 0, sizeof(value_));
            memset(formatted_value_, 0, sizeof(formatted_value_));
            memset(space_, 0, sizeof(space_));
        }

781
        /// @brief Returns identifier of the currently processed option.
782
783
784
785
786
787
788
        uint64_t getOptionId() const {
            if (option_id_null_ == MLM_FALSE) {
                return (option_id_);
            }
            return (0);
        }

789
790
791
792
793
794
795
796
797
798
799
800
        /// @brief Creates instance of the currently processed option.
        ///
        /// This method detects if the currently processed option is a new
        /// instance. It makes it determination by comparing the identifier
        /// of the currently processed option, with the most recently processed
        /// option. If the current value is greater than the id of the recently
        /// processed option it is assumed that the processed row holds new
        /// option information. In such case the option instance is created and
        /// inserted into the configuration passed as argument.
        ///
        /// @param cfg Pointer to the configuration object into which new
        /// option instances should be inserted.
801
        void retrieveOption(const CfgOptionPtr& cfg) {
802
803
804
805
806
807
808
            // option_id may be NULL if dhcp4_options or dhcp6_options table
            // doesn't contain any options for the particular host. Also, the
            // current option id must be greater than id if the most recent
            // option because options are ordered by option id. Otherwise
            // we assume that this is already processed option.
            if ((option_id_null_ == MLM_TRUE) ||
                (most_recent_option_id_ >= option_id_)) {
809
810
811
                return;
            }

812
813
814
815
816
817
818
819
820
821
822
823
            // Remember current option id as the most recent processed one. We
            // will be comparing it with option ids in subsequent rows.
            most_recent_option_id_ = option_id_;

            // Convert it to string object for easier comparison.
            std::string space;
            if (space_null_ == MLM_FALSE) {
                // Typically, the string values returned by the database are not
                // NULL terminated.
                space_[space_length_] = '\0';
                space.assign(space_);
            }
824

825
826
827
828
829
            // If empty or null space provided, use a default top level space.
            if (space.empty()) {
                space = (universe_ == Option::V4 ? "dhcp4" : "dhcp6");
            }

830
831
832
833
834
835
            // Convert formatted_value to string as well.
            std::string formatted_value;
            if (formatted_value_null_ == MLM_FALSE) {
                formatted_value_[formatted_value_length_] = '\0';
                formatted_value.assign(formatted_value_);
            }
836

837
838
839
840
841
842
            // Options are held in a binary or textual format in the database.
            // This is similar to having an option specified in a server
            // configuration file. Such option is converted to appropriate C++
            // class, using option definition. Thus, we need to find the
            // option definition for this option code and option space.

843
844
            // Check if this is a standard option.
            OptionDefinitionPtr def = LibDHCP::getOptionDef(space, code_);
845

846
847
848
849
850
851
852
            // Otherwise, we may check if this an option encapsulated within the
            // vendor space.
            if (!def && (space != DHCP4_OPTION_SPACE) &&
                (space != DHCP6_OPTION_SPACE)) {
                uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
                if (vendor_id > 0) {
                    def = LibDHCP::getVendorOptionDef(universe_, vendor_id, code_);
853
                }
854
            }
855

856
857
858
859
860
            // In all other cases, we use runtime option definitions, which
            // should be also registered within the libdhcp++.
            if (!def) {
                def = LibDHCP::getRuntimeOptionDef(space, code_);
            }
861

862
            OptionPtr option;
863

864
865
866
867
868
869
870
871
872
873
874
            if (!def) {
                // If no definition found, we use generic option type.
                OptionBuffer buf(value_, value_ + value_length_);
                option.reset(new Option(universe_, code_, buf.begin(),
                                        buf.end()));
            } else {
                // The option value may be specified in textual or binary format
                // in the database. If formatted_value is empty, the binary
                // format is used. Depending on the format we use a different
                // variant of the optionFactory function.
                if (formatted_value.empty()) {
875
                    OptionBuffer buf(value_, value_ + value_length_);
876
877
                    option = def->optionFactory(universe_, code_, buf.begin(),
                                                buf.end());
878
                } else {
879
880
881
882
883
                    // Spit the value specified in comma separated values
                    // format.
                    std::vector<std::string> split_vec;
                    boost::split(split_vec, formatted_value, boost::is_any_of(","));
                    option = def->optionFactory(universe_, code_, split_vec);
884
885
                }
            }
886
887
888

            OptionDescriptor desc(option, persistent_, formatted_value);
            cfg->add(desc, space);
889
890
        }

891
892
893
894
        /// @brief Specify column names.
        ///
        /// @param [out] columns Reference to a vector holding names of option
        /// specific columns.
895
896
897
898
899
900
901
902
903
        void setColumnNames(std::vector<std::string>& columns) {
            columns[option_id_index_] = "option_id";
            columns[code_index_] = "code";
            columns[value_index_] = "value";
            columns[formatted_value_index_] = "formatted_value";
            columns[space_index_] = "space";
            columns[persistent_index_] = "persistent";
        }

904
905
906
907
908
        /// @brief Initialize binding table fields for options.
        ///
        /// Resets most_recent_option_id_ value to 0.
        ///
        /// @param [out] bind Binding table.
909
        void setBindFields(std::vector<MYSQL_BIND>& bind) {
910
911
912
            // This method is called just before making a new query, so we
            // reset the most_recent_option_id_ to start over with options
            // processing.
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
            most_recent_option_id_ = 0;

            // option_id : INT UNSIGNED NOT NULL AUTO_INCREMENT,
            bind[option_id_index_].buffer_type = MYSQL_TYPE_LONG;
            bind[option_id_index_].buffer = reinterpret_cast<char*>(&option_id_);
            bind[option_id_index_].is_unsigned = MLM_TRUE;

            // code : TINYINT OR SHORT UNSIGNED NOT NULL
            bind[code_index_].buffer_type = MYSQL_TYPE_SHORT;
            bind[code_index_].buffer = reinterpret_cast<char*>(&code_);
            bind[code_index_].is_unsigned = MLM_TRUE;
            bind[code_index_].is_null = &code_null_;

            // value : BLOB NULL
            value_length_ = sizeof(value_);
            bind[value_index_].buffer_type = MYSQL_TYPE_BLOB;
            bind[value_index_].buffer = reinterpret_cast<char*>(value_);
            bind[value_index_].buffer_length = value_length_;
            bind[value_index_].length = &value_length_;
            bind[value_index_].is_null = &value_null_;

            // formatted_value : TEXT NULL
            formatted_value_length_ = sizeof(formatted_value_);
            bind[formatted_value_index_].buffer_type = MYSQL_TYPE_STRING;
            bind[formatted_value_index_].buffer = reinterpret_cast<char*>(formatted_value_);
            bind[formatted_value_index_].buffer_length = formatted_value_length_;
            bind[formatted_value_index_].length = &formatted_value_length_;
            bind[formatted_value_index_].is_null = &formatted_value_null_;

            // space : VARCHAR(128) NULL
            space_length_ = sizeof(space_);
            bind[space_index_].buffer_type = MYSQL_TYPE_STRING;
            bind[space_index_].buffer = reinterpret_cast<char*>(space_);
            bind[space_index_].buffer_length = space_length_;
            bind[space_index_].length = &space_length_;
            bind[space_index_].is_null = &space_null_;

            // persistent : TINYINT(1) NOT NULL DEFAULT 0
            bind[persistent_index_].buffer_type = MYSQL_TYPE_TINY;
            bind[persistent_index_].buffer = reinterpret_cast<char*>(&persistent_);
            bind[persistent_index_].is_unsigned = MLM_TRUE;
        }

    private:

958
        /// @brief Universe: V4 or V6.
959
960
        Option::Universe universe_;

961
        /// @brief Index of first column used by this class.
962
963
        size_t start_column_;

964
        /// @brief Option id.
965
        uint32_t option_id_;
966

967
        /// @brief Option code.
968
969
        uint16_t code_;

970
        /// @brief Buffer holding binary value of an option.
971
972
        uint8_t value_[OPTION_VALUE_MAX_LEN];

973
        /// @brief Option value length.
974
975
        unsigned long value_length_;

976
        /// @brief Buffer holding textual value of an option.
977
978
        char formatted_value_[OPTION_FORMATTED_VALUE_MAX_LEN];

979
        /// @brief Formatted option value length.
980
981
        unsigned long formatted_value_length_;

982
        /// @brief Buffer holding option space name.
983
984
        char space_[OPTION_SPACE_MAX_LEN];

985
        /// @brief Option space length.
986
987
        unsigned long space_length_;

988
989
        /// @brief Flag indicating if option is always sent or only if
        /// requested.
990
991
992
993
994
        bool persistent_;

        /// @name Boolean values indicating if values of specific columns in
        /// the database are NULL.
        //@{
995
        /// @brief Boolean flag indicating if the DHCPv4 option id is NULL.
996
997
        my_bool option_id_null_;

998
        /// @brief Boolean flag indicating if the DHCPv4 option code is NULL.
999
1000
        my_bool code_null_;

1001
        /// @brief Boolean flag indicating if the DHCPv4 option value is NULL.
1002
1003
        my_bool value_null_;

1004
        /// @brief Boolean flag indicating if the DHCPv4 formatted option value
1005
1006
1007
        /// is NULL.
        my_bool formatted_value_null_;

1008
        /// @brief Boolean flag indicating if the DHCPv4 option space is NULL.
1009
1010
1011
1012
1013
1014
        my_bool space_null_;

        //@}

        /// @name Indexes of the specific columns
        //@{
1015
        /// @brief Option id
1016
1017
        size_t option_id_index_;

1018
        /// @brief Code
1019
1020
        size_t code_index_;

1021
        /// @brief Value
1022
1023
        size_t value_index_;

1024
        /// @brief Formatted value
1025
1026
        size_t formatted_value_index_;

1027
        /// @brief Space
1028
1029
        size_t space_index_;

1030
        /// @brief Persistent
1031
1032
1033
1034
        size_t persistent_index_;
        //@}

        /// @brief Option id for last processed row.
1035
        uint32_t most_recent_option_id_;
1036
1037
    };

1038
    /// @brief Pointer to the @ref OptionProcessor class.
1039
1040
1041
1042
    typedef boost::shared_ptr<OptionProcessor> OptionProcessorPtr;

public:

1043
1044
1045
1046
1047
1048
    /// @brief DHCP option types to be fetched from the database.
    ///
    /// Supported types are:
    /// - Only DHCPv4 options,
    /// - Only DHCPv6 options,
    /// - Both DHCPv4 and DHCPv6 options.
1049
1050
1051
1052
1053
1054
    enum FetchedOptions {
        DHCP4_ONLY,
        DHCP6_ONLY,
        DHCP4_AND_DHCP6
    };

1055
1056
1057
1058
1059
1060
1061
1062
    /// @brief Constructor.
    ///
    /// @param fetched_options Specifies if DHCPv4, DHCPv6 or both should
    /// be fetched from the database for a host.
    /// @param additional_columns_num Number of additional columns for which
    /// resources should be allocated, e.g. binding table, column names etc.
    /// This parameter should be set to a non zero value by derived classes to
    /// allocate resources for the columns supported by derived classes.
1063
1064
    MySqlHostWithOptionsExchange(const FetchedOptions& fetched_options,
                                 const size_t additional_columns_num = 0)
1065
1066
1067
1068
        : MySqlHostExchange(getRequiredColumnsNum(fetched_options)
                            + additional_columns_num),
          opt_proc4_(), opt_proc6_() {

1069
        // Create option processor for DHCPv4 options, if required.
1070
1071
1072
1073
1074
1075
1076
        if ((fetched_options == DHCP4_ONLY) ||
            (fetched_options == DHCP4_AND_DHCP6)) {
            opt_proc4_.reset(new OptionProcessor(Option::V4,
                                                 findAvailColumn()));
            opt_proc4_->setColumnNames(columns_);
        }

1077
        // Create option processor for DHCPv6 options, if required.
1078
1079
1080
1081
1082
1083
1084
1085
        if ((fetched_options == DHCP6_ONLY) ||
            (fetched_options == DHCP4_AND_DHCP6)) {
            opt_proc6_.reset(new OptionProcessor(Option::V6,
                                                 findAvailColumn()));
            opt_proc6_->setColumnNames(columns_);
        }
    }

1086
1087
1088
1089
1090
1091
1092
1093
    /// @brief Processes the current row.
    ///
    /// The processed row includes both host information and DHCP option
    /// information. Because used SELECT query use LEFT JOIN clause, the
    /// some rows contain duplicated host or options entries. This method
    /// detects duplicated information and discards such entries.
    ///
    /// @param [out] hosts Container holding parsed hosts and options.
1094
    virtual void processFetchedData(ConstHostCollection& hosts) {
1095
        // Holds pointer to the previously parsed host.
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
        HostPtr most_recent_host;
        if (!hosts.empty()) {
            // Const cast is not very elegant way to deal with it, but
            // there is a good reason to use it here. This method is called
            // to build a collection of const hosts to be returned to the
            // caller. If we wanted to use non-const collection we'd need
            // to copy the whole collection before returning it, which has
            // performance implications. Alternatively, we could store the
            // most recently added host in a class member but this would
            // make the code less readable.
            most_recent_host = boost::const_pointer_cast<Host>(hosts.back());
        }

1109
1110
1111
1112
1113
        // If no host has been parsed yet or we're at the row holding next
        // host, we create a new host object and put it at the end of the
        // list.
        if (!most_recent_host || (most_recent_host->getHostId() < getHostId())) {
            HostPtr host = retrieveHost();
1114
1115
1116
1117
            hosts.push_back(host);
            most_recent_host = host;
        }

1118
        // Parse DHCPv4 options if required to do so.
1119
1120
1121
1122
1123
        if (opt_proc4_) {
            CfgOptionPtr cfg = most_recent_host->getCfgOption4();
            opt_proc4_->retrieveOption(cfg);
        }

1124
        // Parse DHCPv6 options if required to do so.
1125
1126
1127
1128
1129
1130
        if (opt_proc6_) {
            CfgOptionPtr cfg = most_recent_host->getCfgOption6();
            opt_proc6_->retrieveOption(cfg);
        }
    }

1131
1132
1133
    /// @brief Bind variables for receiving option data.
    ///
    /// @return Vector of MYSQL_BIND object representing data to be retrieved.
1134
1135
1136
1137
    virtual std::vector<MYSQL_BIND> createBindForReceive() {
        // The following call sets bind_ values between 0 and 8.
        static_cast<void>(MySqlHostExchange::createBindForReceive());

1138
        // Bind variables for DHCPv4 options.
1139
1140
1141
1142
        if (opt_proc4_) {
            opt_proc4_->setBindFields(bind_);
        }

1143
        // Bind variables for DHCPv6 options.
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
        if (opt_proc6_) {
            opt_proc6_->setBindFields(bind_);
        }

        // Add the error flags
        setErrorIndicators(bind_, error_);

        return (bind_);
    };

private:

1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
    /// @brief Returns a number of columns required to retrieve option data.
    ///
    /// Depending if we need DHCPv4/DHCPv6 options only, or both DHCPv4 and
    /// DHCPv6 a different number of columns is required in the binding array.
    /// This method returns the number of required columns, according to the
    /// value of @c fetched_columns passed in the constructor.
    ///
    /// @param fetched_columns A value which specifies whether DHCPv4, DHCPv6 or
    /// both types of options should be retrieved.
    ///
    /// @return Number of required columns.
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
    static size_t getRequiredColumnsNum(const FetchedOptions& fetched_options) {
        return (fetched_options == DHCP4_AND_DHCP6 ? 2 * OPTION_COLUMNS :
                OPTION_COLUMNS);
    }

    /// @brief Pointer to DHCPv4 options processor.
    ///
    /// If this object is NULL, the DHCPv4 options are not fetched.
    OptionProcessorPtr opt_proc4_;

    /// @brief Pointer to DHCPv6 options processor.
    ///
    /// If this object is NULL, the DHCPv6 options are not fetched.
    OptionProcessorPtr opt_proc6_;
};

1183
/// @brief This class provides mechanisms for sending and retrieving
1184
/// host information, DHCPv4 options, DHCPv6 options and IPv6 reservations.
1185
///
1186
/// This class extends the @ref MySqlHostWithOptionsExchange class with the
Andrei Pavel's avatar
Andrei Pavel committed
1187
/// mechanisms to retrieve IPv6 reservations. This class is used in situations
1188
1189
1190
1191
1192
1193
1194
/// when it is desired to retrieve DHCPv6 specific information about the host
/// (DHCPv6 options and reservations), or entire information about the host
/// (DHCPv4 options, DHCPv6 options and reservations). The following are the
/// queries used with this class:
/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options
///   LEFT JOIN ipv6_reservations ...
/// - SELECT ? FROM hosts LEFT JOIN dhcp6_options LEFT JOIN ipv6_reservations ..
1195
class MySqlHostIPv6Exchange : public MySqlHostWithOptionsExchange {
1196
1197
private:

1198
1199
    /// @brief Number of columns holding IPv6 reservation information.
    static const size_t RESERVATION_COLUMNS = 5;
1200
1201
1202
1203
1204
1205
1206

public:

    /// @brief Constructor.
    ///
    /// Apart from initializing the base class data structures it also
    /// initializes values representing IPv6 reservation information.
1207
    MySqlHostIPv6Exchange(const FetchedOptions& fetched_options)
1208
        : MySqlHostWithOptionsExchange(fetched_options, RESERVATION_COLUMNS),
1209
1210
1211
1212
1213
1214
1215
1216
1217
          reservation_id_(0),
          reserv_type_(0), reserv_type_null_(MLM_FALSE),
          ipv6_address_buffer_len_(0), prefix_len_(0), iaid_(0),
          reservation_id_index_(findAvailColumn()),
          address_index_(reservation_id_index_ + 1),
          prefix_len_index_(reservation_id_index_ + 2),
          type_index_(reservation_id_index_ + 3),
          iaid_index_(reservation_id_index_ + 4),
          most_recent_reservation_id_(0) {
1218

1219
1220
        memset(ipv6_address_buffer_, 0, sizeof(ipv6_address_buffer_));

1221
        // Provide names of additional columns returned by the queries.
1222
1223
1224
1225
1226
        columns_[reservation_id_index_] = "reservation_id";
        columns_[address_index_] = "address";
        columns_[prefix_len_index_] = "prefix_len";
        columns_[type_index_] = "type";
        columns_[iaid_index_] = "dhcp6_iaid";
1227
1228
    }

1229
    /// @brief Returns last fetched reservation id.
1230
    ///
1231
    /// @return Reservation id or 0 if no reservation data is fetched.
1232
    uint32_t getReservationId() const {
1233
1234
1235
1236
        if (reserv_type_null_ == MLM_FALSE) {
            return (reservation_id_);
        }
        return (0);
1237
1238
    };

1239
    /// @brief Creates IPv6 reservation from the data contained in the
1240
1241
1242
1243
1244
    /// currently processed row.
    ///
    /// Called after the MYSQL_BIND array created by createBindForReceive().
    ///
    /// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
1245
    IPv6Resrv retrieveReservation() {
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
        // Set the IPv6 Reservation type (0 = IA_NA, 2 = IA_PD)
        IPv6Resrv::Type type = IPv6Resrv::TYPE_NA;

        switch (reserv_type_) {
        case 0:
            type = IPv6Resrv::TYPE_NA;
            break;

        case 2:
            type = IPv6Resrv::TYPE_PD;
            break;

        default:
            isc_throw(BadValue,
1260
1261
1262
                      "invalid IPv6 reservation type returned: "
                      << static_cast<int>(reserv_type_)
                      << ". Only 0 or 2 are allowed.");
1263
1264
        }

1265
        ipv6_address_buffer_[ipv6_address_buffer_len_] = '\0';
1266
1267
1268
        std::string address = ipv6_address_buffer_;
        IPv6Resrv r(type, IOAddress(address), prefix_len_);
        return (r);
1269
    };
1270

1271
    /// @brief Processes one row of data fetched from a database.
1272
    ///
1273
1274
1275
1276
1277
    /// The processed data must contain host id, which uniquely identifies a
    /// host. This method creates a host and inserts it to the hosts collection
    /// only if the last inserted host has a different host id. This prevents
    /// adding duplicated hosts to the collection, assuming that processed
    /// rows are primarily ordered by host id column.
1278
    ///
1279
1280
1281
    /// Depending on the value of the @c fetched_options specified in the
    /// constructor, this method also parses options returned as a result
    /// of SELECT queries.
1282
    ///
1283
1284
1285
1286
    /// For any returned row which contains IPv6 reservation information it
    /// checks if the reservation is not a duplicate of previously parsed
    /// reservation and appends the IPv6Resrv object into the host object
    /// if the parsed row contains new reservation information.
1287
1288
1289
1290
1291
    ///
    /// @param [out] hosts Collection of hosts to which a new host created
    ///        from the processed data should be inserted.
    virtual void processFetchedData(ConstHostCollection& hosts) {

1292
        // Call parent class to fetch host information and options.
1293
        MySqlHostWithOptionsExchange::processFetchedData(hosts);
1294
1295
1296

        if (getReservationId() == 0) {
            return;
1297
1298
        }

1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
        if (hosts.empty()) {
            isc_throw(Unexpected, "no host information while retrieving"
                      " IPv6 reservation");
        }
        HostPtr host = boost::const_pointer_cast<Host>(hosts.back());

        // If we're dealing with a new reservation, let's add it to the
        // host.
        if (getReservationId() > most_recent_reservation_id_) {
            most_recent_reservation_id_ = getReservationId();

            if (most_recent_reservation_id_ > 0) {
1311
                host->addReservation(retrieveReservation());
1312
1313
1314
1315
            }
        }
    }

1316
1317
1318
1319
1320
1321
1322
1323
1324
    /// @brief Create BIND array to receive Host data with IPv6 reservations.
    ///
    /// Creates a MYSQL_BIND array to receive Host data from the database.
    /// After data is successfully received, @ref processedFetchedData is
    /// called for each returned row to build collection of @ref Host
    /// objects with associated IPv6 reservations.
    ///
    /// @return Vector of MYSQL_BIND objects representing data to be retrieved.
    virtual std::vector<MYSQL_BIND> createBindForReceive() {
1325
1326
1327
1328
        // Reset most recent reservation id value because we're now making
        // a new SELECT query.
        most_recent_reservation_id_ = 0;

1329
        // Bind values supported by parent classes.
1330
        static_cast<void>(MySqlHostWithOptionsExchange::createBindForReceive());
1331
1332
1333
1334
1335

        // reservation_id : INT UNSIGNED NOT NULL AUTO_INCREMENT
        bind_[reservation_id_index_].buffer_type = MYSQL_TYPE_LONG;
        bind_[reservation_id_index_].buffer = reinterpret_cast<char*>(&reservation_id_);
        bind_[reservation_id_index_].is_unsigned = MLM_TRUE;
1336
1337
1338

        // IPv6 address/prefix VARCHAR(39)
        ipv6_address_buffer_len_ = sizeof(ipv6_address_buffer_) - 1;
1339
1340
1341
1342
        bind_[address_index_].buffer_type = MYSQL_TYPE_STRING;
        bind_[address_index_].buffer = ipv6_address_buffer_;
        bind_[address_index_].buffer_length = ipv6_address_buffer_len_;
        bind_[address_index_].length = &ipv6_address_buffer_len_;
1343
1344

        // prefix_len : TINYINT
1345
1346
1347
        bind_[prefix_len_index_].buffer_type = MYSQL_TYPE_TINY;
        bind_[prefix_len_index_].buffer = reinterpret_cast<char*>(&prefix_len_);
        bind_[prefix_len_index_].is_unsigned = MLM_TRUE;
1348
1349
1350

        // (reservation) type : TINYINT
        reserv_type_null_ = MLM_FALSE;
1351
1352
1353
1354
        bind_[type_index_].buffer_type = MYSQL_TYPE_TINY;
        bind_[type_index_].buffer = reinterpret_cast<char*>(&reserv_type_);
        bind_[type_index_].is_unsigned = MLM_TRUE;
        bind_[type_index_].is_null = &reserv_type_null_;
1355
1356

        // dhcp6_iaid INT UNSIGNED
1357
1358
1359
        bind_[iaid_index_].buffer_type = MYSQL_TYPE_LONG;
        bind_[iaid_index_].buffer = reinterpret_cast<char*>(&iaid_);
        bind_[iaid_index_].is_unsigned = MLM_TRUE;
1360
1361
1362
1363
1364
1365
1366

        // Add the error flags
        setErrorIndicators(bind_, error_);

        return (bind_);
    };

1367
private:
1368

1369
    /// @brief IPv6 reservation id.
1370
    uint32_t reservation_id_;
1371

1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
    /// @brief IPv6 reservation type.
    uint8_t reserv_type_;

    /// @brief Boolean flag indicating if reservation type field is null.
    ///
    /// This flag is used by the class to determine if the returned row
    /// contains IPv6 reservation information.
    my_bool reserv_type_null_;

    /// @brief Buffer holding IPv6 address/prefix in textual format.
1382
    char ipv6_address_buffer_[ADDRESS6_TEXT_MAX_LEN + 1];