1 // Copyright (C) 2014-2020 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 #include <asiolink/io_address.h>
9 #include <dhcp/duid.h>
10 #include <dhcpsrv/csv_lease_file4.h>
11 #include <dhcpsrv/lease.h>
12 #include <dhcpsrv/testutils/lease_file_io.h>
13 #include <gtest/gtest.h>
14 #include <ctime>
15 #include <sstream>
16 
17 using namespace isc;
18 using namespace isc::asiolink;
19 using namespace isc::data;
20 using namespace isc::dhcp;
21 using namespace isc::dhcp::test;
22 using namespace isc::util;
23 
24 namespace {
25 
26 // HWADDR values used by unit tests.
27 const uint8_t HWADDR0[] = { 0, 1, 2, 3, 4, 5 };
28 const uint8_t HWADDR1[] = { 0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf };
29 
30 const uint8_t CLIENTID[] = { 1, 2, 3, 4 };
31 
32 /// @brief Test fixture class for @c CSVLeaseFile4 validation.
33 class CSVLeaseFile4Test : public ::testing::Test {
34 public:
35 
36     /// @brief Constructor.
37     ///
38     /// Initializes IO for lease file used by unit tests.
39     CSVLeaseFile4Test();
40 
41     /// @brief Prepends the absolute path to the file specified
42     /// as an argument.
43     ///
44     /// @param filename Name of the file.
45     /// @return Absolute path to the test file.
46     static std::string absolutePath(const std::string& filename);
47 
48     /// @brief Creates the lease file to be parsed by unit tests.
49     void writeSampleFile() const;
50 
51     /// @brief Checks the stats for the file
52     ///
53     /// This method is passed a leasefile and the values for the statistics it
54     /// should have for comparison.
55     ///
56     /// @param lease_file A reference to the file we are using
57     /// @param reads the number of attempted reads
58     /// @param read_leases the number of valid leases read
59     /// @param read_errs the number of errors while reading leases
60     /// @param writes the number of attempted writes
61     /// @param write_leases the number of leases successfully written
62     /// @param write_errs the number of errors while writing
checkStats(CSVLeaseFile4 & lease_file,uint32_t reads,uint32_t read_leases,uint32_t read_errs,uint32_t writes,uint32_t write_leases,uint32_t write_errs) const63     void checkStats(CSVLeaseFile4& lease_file,
64                     uint32_t reads, uint32_t read_leases,
65                     uint32_t read_errs, uint32_t writes,
66                     uint32_t write_leases, uint32_t write_errs) const {
67         EXPECT_EQ(reads, lease_file.getReads());
68         EXPECT_EQ(read_leases, lease_file.getReadLeases());
69         EXPECT_EQ(read_errs, lease_file.getReadErrs());
70         EXPECT_EQ(writes, lease_file.getWrites());
71         EXPECT_EQ(write_leases, lease_file.getWriteLeases());
72         EXPECT_EQ(write_errs, lease_file.getWriteErrs());
73     }
74 
75     /// @brief Name of the test lease file.
76     std::string filename_;
77 
78     /// @brief Object providing access to lease file IO.
79     LeaseFileIO io_;
80 
81     /// @brief hardware address 0 (corresponds to HWADDR0 const)
82     HWAddrPtr hwaddr0_;
83 
84     /// @brief hardware address 1 (corresponds to HWADDR1 const)
85     HWAddrPtr hwaddr1_;
86 
87 };
88 
CSVLeaseFile4Test()89 CSVLeaseFile4Test::CSVLeaseFile4Test()
90     : filename_(absolutePath("leases4.csv")), io_(filename_) {
91     hwaddr0_.reset(new HWAddr(HWADDR0, sizeof(HWADDR0), HTYPE_ETHER));
92     hwaddr1_.reset(new HWAddr(HWADDR1, sizeof(HWADDR1), HTYPE_ETHER));
93 }
94 
95 std::string
absolutePath(const std::string & filename)96 CSVLeaseFile4Test::absolutePath(const std::string& filename) {
97     std::ostringstream s;
98     s << DHCP_DATA_DIR << "/" << filename;
99     return (s.str());
100 }
101 
102 void
writeSampleFile() const103 CSVLeaseFile4Test::writeSampleFile() const {
104     io_.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
105                   "fqdn_fwd,fqdn_rev,hostname,state,user_context\n"
106                   "192.0.2.1,06:07:08:09:0a:bc,,200,200,8,1,1,"
107                   "host.example.com,0,\n"
108                   "192.0.2.2,,,200,200,8,1,1,host.example.com,0,\n"
109                   "192.0.2.3,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,100,100,7,"
110                   "0,0,,1,{ \"foobar\": true }\n"
111                   "192.0.2.4,,11:22:33:44:55:66,200,200,8,1,1,host.example.com,0,\n"
112                   "192.0.2.5,,,200,200,8,1,1,,1,\n");
113 }
114 
115 // This test checks the capability to read and parse leases from the file.
TEST_F(CSVLeaseFile4Test,parse)116 TEST_F(CSVLeaseFile4Test, parse) {
117     // Create a file to be parsed.
118     writeSampleFile();
119 
120     // Open the lease file.
121     CSVLeaseFile4 lf(filename_);
122     ASSERT_NO_THROW(lf.open());
123 
124     // Verify the counters are cleared
125     {
126     SCOPED_TRACE("Check stats are empty");
127     checkStats(lf, 0, 0, 0, 0, 0, 0);
128     }
129 
130     Lease4Ptr lease;
131     // Reading first read should be successful.
132     {
133     SCOPED_TRACE("First lease valid");
134     EXPECT_TRUE(lf.next(lease));
135     ASSERT_TRUE(lease);
136     checkStats(lf, 1, 1, 0, 0, 0, 0);
137 
138     // Verify that the lease attributes are correct.
139     EXPECT_EQ("192.0.2.1", lease->addr_.toText());
140     HWAddr hwaddr1(*lease->hwaddr_);
141     EXPECT_EQ("06:07:08:09:0a:bc", hwaddr1.toText(false));
142     EXPECT_FALSE(lease->client_id_);
143     EXPECT_EQ(200, lease->valid_lft_);
144     EXPECT_EQ(0, lease->cltt_);
145     EXPECT_EQ(8, lease->subnet_id_);
146     EXPECT_TRUE(lease->fqdn_fwd_);
147     EXPECT_TRUE(lease->fqdn_rev_);
148     EXPECT_EQ("host.example.com", lease->hostname_);
149     EXPECT_EQ(Lease::STATE_DEFAULT, lease->state_);
150     EXPECT_FALSE(lease->getContext());
151     }
152 
153     // Second lease is malformed - has no HW address or client id and state
154     // is not declined.
155     {
156     SCOPED_TRACE("Second lease malformed");
157     EXPECT_FALSE(lf.next(lease));
158     EXPECT_FALSE(lease);
159     checkStats(lf, 2, 1, 1, 0, 0, 0);
160     }
161 
162     // Even though parsing previous lease failed, reading the next lease should be
163     // successful.
164     {
165     SCOPED_TRACE("Third lease valid");
166     EXPECT_TRUE(lf.next(lease));
167     ASSERT_TRUE(lease);
168     checkStats(lf, 3, 2, 1, 0, 0, 0);
169 
170     // Verify that the third lease is correct.
171     EXPECT_EQ("192.0.2.3", lease->addr_.toText());
172     HWAddr hwaddr3(*lease->hwaddr_);
173     EXPECT_EQ("dd:de:ba:0d:1b:2e:3e:4f", hwaddr3.toText(false));
174     ASSERT_TRUE(lease->client_id_);
175     EXPECT_EQ("0a:00:01:04", lease->client_id_->toText());
176     EXPECT_EQ(100, lease->valid_lft_);
177     EXPECT_EQ(0, lease->cltt_);
178     EXPECT_EQ(7, lease->subnet_id_);
179     EXPECT_FALSE(lease->fqdn_fwd_);
180     EXPECT_FALSE(lease->fqdn_rev_);
181     EXPECT_TRUE(lease->hostname_.empty());
182     EXPECT_EQ(Lease::STATE_DECLINED, lease->state_);
183     ASSERT_TRUE(lease->getContext());
184     EXPECT_EQ("{ \"foobar\": true }", lease->getContext()->str());
185     }
186 
187     // Fourth lease has no hardware address but has client id
188     {
189     SCOPED_TRACE("Fourth lease valid");
190     EXPECT_TRUE(lf.next(lease));
191     ASSERT_TRUE(lease);
192     checkStats(lf, 4, 3, 1, 0, 0, 0);
193 
194     EXPECT_EQ("192.0.2.4", lease->addr_.toText());
195     ASSERT_TRUE(lease->hwaddr_);
196     EXPECT_TRUE(lease->hwaddr_->hwaddr_.empty());
197     ASSERT_TRUE(lease->client_id_);
198     EXPECT_EQ("11:22:33:44:55:66", lease->client_id_->toText());
199     }
200 
201     // Fifth lease has no hardware address or client id but is declined
202     {
203     SCOPED_TRACE("Fifth lease valid");
204     EXPECT_TRUE(lf.next(lease));
205     ASSERT_TRUE(lease);
206     checkStats(lf, 5, 4, 1, 0, 0, 0);
207 
208     EXPECT_EQ("192.0.2.5", lease->addr_.toText());
209     ASSERT_TRUE(lease->hwaddr_);
210     EXPECT_TRUE(lease->hwaddr_->hwaddr_.empty());
211     ASSERT_FALSE(lease->client_id_);
212     EXPECT_EQ(lease->state_, Lease::STATE_DECLINED);
213     }
214 
215     // There are no more leases. Reading should cause no error, but the returned
216     // lease pointer should be NULL.
217     {
218     SCOPED_TRACE("Sixth read empty");
219     EXPECT_TRUE(lf.next(lease));
220     EXPECT_FALSE(lease);
221     checkStats(lf, 6, 4, 1, 0, 0, 0);
222     }
223 
224     // We should be able to do it again.
225     {
226     SCOPED_TRACE("Seventh read empty");
227     EXPECT_TRUE(lf.next(lease));
228     EXPECT_FALSE(lease);
229     checkStats(lf, 7, 4, 1, 0, 0, 0);
230     }
231 }
232 
233 // This test checks creation of the lease file and writing leases.
TEST_F(CSVLeaseFile4Test,recreate)234 TEST_F(CSVLeaseFile4Test, recreate) {
235     CSVLeaseFile4 lf(filename_);
236     ASSERT_NO_THROW(lf.recreate());
237     ASSERT_TRUE(io_.exists());
238 
239     // Verify the counters are cleared
240     checkStats(lf, 0, 0, 0, 0, 0, 0);
241 
242     // Create first lease, with NULL client id.
243     Lease4Ptr lease(new Lease4(IOAddress("192.0.3.2"),
244                                hwaddr0_,
245                                NULL, 0,
246                                200, 0, 8, true, true,
247                                "host.example.com"));
248     lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
249     {
250     SCOPED_TRACE("First write");
251     ASSERT_NO_THROW(lf.append(*lease));
252     checkStats(lf, 0, 0, 0, 1, 1, 0);
253     }
254 
255     // Create second lease, with non-NULL client id and user context.
256     lease.reset(new Lease4(IOAddress("192.0.3.10"),
257                            hwaddr1_,
258                            CLIENTID, sizeof(CLIENTID),
259                            100, 0, 7));
260     lease->setContext(Element::fromJSON("{ \"foobar\": true }"));
261     {
262     SCOPED_TRACE("Second write");
263     ASSERT_NO_THROW(lf.append(*lease));
264     checkStats(lf, 0, 0, 0, 2, 2, 0);
265     }
266 
267     // Close the lease file.
268     lf.close();
269     // Check that the contents of the csv file are correct.
270     EXPECT_EQ("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
271               "fqdn_fwd,fqdn_rev,hostname,state,user_context\n"
272               "192.0.3.2,00:01:02:03:04:05,,200,200,8,1,1,host.example.com,"
273               "2,\n"
274               "192.0.3.10,0d:0e:0a:0d:0b:0e:0e:0f,01:02:03:04,100,100,7,0,"
275               "0,,0,{ \"foobar\": true }\n",
276               io_.readFile());
277 }
278 
279 // Verifies that a schema 1.0 file with records from
280 // schema 1.0 and 2.0 loads correctly.
TEST_F(CSVLeaseFile4Test,mixedSchemaload)281 TEST_F(CSVLeaseFile4Test, mixedSchemaload) {
282     // Create mixed schema file
283     io_.writeFile(
284                   // schema 1.0 header
285                   "address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
286                   "fqdn_fwd,fqdn_rev,hostname\n"
287                   // schema 1.0 record
288                   "192.0.2.1,06:07:08:09:1a:bc,,200,200,8,1,1,"
289                   "one.example.com\n"
290                   // schema 2.0 record - has state
291                   "192.0.2.2,06:07:08:09:2a:bc,,200,200,8,1,1,"
292                   "two.example.com,1\n"
293                   // schema 2.1 record - has state and user context
294                   "192.0.2.3,06:07:08:09:3a:bc,,200,200,8,1,1,"
295                   "three.example.com,2,{ \"foobar\": true }\n"
296                    );
297 
298     // Open the lease file.
299     CSVLeaseFile4 lf(filename_);
300     ASSERT_NO_THROW(lf.open());
301 
302     Lease4Ptr lease;
303 
304     // Reading first read should be successful.
305     {
306     SCOPED_TRACE("First lease valid");
307     EXPECT_TRUE(lf.next(lease));
308     ASSERT_TRUE(lease);
309 
310     // Verify that the lease attributes are correct.
311     EXPECT_EQ("192.0.2.1", lease->addr_.toText());
312     HWAddr hwaddr1(*lease->hwaddr_);
313     EXPECT_EQ("06:07:08:09:1a:bc", hwaddr1.toText(false));
314     EXPECT_FALSE(lease->client_id_);
315     EXPECT_EQ(200, lease->valid_lft_);
316     EXPECT_EQ(0, lease->cltt_);
317     EXPECT_EQ(8, lease->subnet_id_);
318     EXPECT_TRUE(lease->fqdn_fwd_);
319     EXPECT_TRUE(lease->fqdn_rev_);
320     EXPECT_EQ("one.example.com", lease->hostname_);
321     // Verify that added state is DEFAULT
322     EXPECT_EQ(Lease::STATE_DEFAULT, lease->state_);
323     EXPECT_FALSE(lease->getContext());
324     }
325 
326     {
327     SCOPED_TRACE("Second lease valid");
328     EXPECT_TRUE(lf.next(lease));
329     ASSERT_TRUE(lease);
330 
331     // Verify that the lease attributes are correct.
332     EXPECT_EQ("192.0.2.2", lease->addr_.toText());
333     HWAddr hwaddr1(*lease->hwaddr_);
334     EXPECT_EQ("06:07:08:09:2a:bc", hwaddr1.toText(false));
335     EXPECT_FALSE(lease->client_id_);
336     EXPECT_EQ(200, lease->valid_lft_);
337     EXPECT_EQ(0, lease->cltt_);
338     EXPECT_EQ(8, lease->subnet_id_);
339     EXPECT_TRUE(lease->fqdn_fwd_);
340     EXPECT_TRUE(lease->fqdn_rev_);
341     EXPECT_EQ("two.example.com", lease->hostname_);
342     EXPECT_EQ(Lease::STATE_DECLINED, lease->state_);
343     EXPECT_FALSE(lease->getContext());
344     }
345 
346     {
347     SCOPED_TRACE("Third lease valid");
348     EXPECT_TRUE(lf.next(lease));
349     ASSERT_TRUE(lease);
350 
351     // Verify that the third lease is correct.
352     EXPECT_EQ("192.0.2.3", lease->addr_.toText());
353     HWAddr hwaddr1(*lease->hwaddr_);
354     EXPECT_EQ("06:07:08:09:3a:bc", hwaddr1.toText(false));
355     EXPECT_FALSE(lease->client_id_);
356     EXPECT_EQ(200, lease->valid_lft_);
357     EXPECT_EQ(0, lease->cltt_);
358     EXPECT_EQ(8, lease->subnet_id_);
359     EXPECT_TRUE(lease->fqdn_fwd_);
360     EXPECT_TRUE(lease->fqdn_rev_);
361     EXPECT_EQ("three.example.com", lease->hostname_);
362     EXPECT_EQ(Lease::STATE_EXPIRED_RECLAIMED, lease->state_);
363     ASSERT_TRUE(lease->getContext());
364     EXPECT_EQ("{ \"foobar\": true }", lease->getContext()->str());
365     }
366 }
367 
368 
369 // Verifies that a lease file with fewer header columns than the
370 // minimum allowed will not open.
TEST_F(CSVLeaseFile4Test,tooFewHeaderColumns)371 TEST_F(CSVLeaseFile4Test, tooFewHeaderColumns) {
372     // Create 1.0 file
373     io_.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
374                   "fqdn_fwd,fqdn_rev\n");
375 
376     // Open the lease file.
377     CSVLeaseFile4 lf(filename_);
378     ASSERT_THROW(lf.open(), CSVFileError);
379 }
380 
381 // Verifies that a lease file with an unrecognized column header
382 // will not open.
TEST_F(CSVLeaseFile4Test,invalidHeaderColumn)383 TEST_F(CSVLeaseFile4Test, invalidHeaderColumn) {
384     // Create 1.0 file
385     io_.writeFile("address,hwaddr,BOGUS,valid_lifetime,expire,subnet_id,"
386                   "fqdn_fwd,fqdn_rev,hostname,state,user_context\n");
387 
388     // Open the lease file.
389     CSVLeaseFile4 lf(filename_);
390     ASSERT_THROW(lf.open(), CSVFileError);
391 }
392 
393 // Verifies that a lease file with more header columns than defined
394 // columns will downgrade.
TEST_F(CSVLeaseFile4Test,downGrade)395 TEST_F(CSVLeaseFile4Test, downGrade) {
396     // Create 2.0 PLUS a column file
397     io_.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
398                   "fqdn_fwd,fqdn_rev,hostname,state,user_context,FUTURE_COL\n"
399 
400                   "192.0.2.3,06:07:08:09:3a:bc,,200,200,8,1,1,"
401                   "three.example.com,2,,BOGUS\n");
402 
403     // Lease file should open and report as needing downgrade.
404     CSVLeaseFile4 lf(filename_);
405     ASSERT_NO_THROW(lf.open());
406     EXPECT_TRUE(lf.needsConversion());
407     EXPECT_EQ(util::VersionedCSVFile::NEEDS_DOWNGRADE,
408               lf.getInputSchemaState());
409     Lease4Ptr lease;
410 
411     {
412     SCOPED_TRACE("First lease valid");
413     EXPECT_TRUE(lf.next(lease));
414     ASSERT_TRUE(lease);
415 
416     // Verify that the third lease is correct.
417     EXPECT_EQ("192.0.2.3", lease->addr_.toText());
418     HWAddr hwaddr1(*lease->hwaddr_);
419     EXPECT_EQ("06:07:08:09:3a:bc", hwaddr1.toText(false));
420     EXPECT_FALSE(lease->client_id_);
421     EXPECT_EQ(200, lease->valid_lft_);
422     EXPECT_EQ(0, lease->cltt_);
423     EXPECT_EQ(8, lease->subnet_id_);
424     EXPECT_TRUE(lease->fqdn_fwd_);
425     EXPECT_TRUE(lease->fqdn_rev_);
426     EXPECT_EQ("three.example.com", lease->hostname_);
427     EXPECT_EQ(Lease::STATE_EXPIRED_RECLAIMED, lease->state_);
428     EXPECT_FALSE(lease->getContext());
429     }
430 }
431 
432 // Verifies that leases with no hardware address are only permitted
433 // if they are in the declined state.
TEST_F(CSVLeaseFile4Test,declinedLeaseTest)434 TEST_F(CSVLeaseFile4Test, declinedLeaseTest) {
435     io_.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
436                   "fqdn_fwd,fqdn_rev,hostname,state,user_context\n"
437                   "192.0.2.1,,,200,200,8,1,1,host.example.com,0,\n"
438                   "192.0.2.1,,,200,200,8,1,1,host.example.com,1,\n");
439 
440     CSVLeaseFile4 lf(filename_);
441     ASSERT_NO_THROW(lf.open());
442     EXPECT_FALSE(lf.needsConversion());
443     EXPECT_EQ(util::VersionedCSVFile::CURRENT, lf.getInputSchemaState());
444     Lease4Ptr lease;
445 
446     {
447     SCOPED_TRACE("No hardware and not declined, invalid");
448     EXPECT_FALSE(lf.next(lease));
449     ASSERT_FALSE(lease);
450     EXPECT_EQ(lf.getReadErrs(),1);
451     }
452 
453     {
454     SCOPED_TRACE("No hardware and declined, valid");
455     EXPECT_TRUE(lf.next(lease));
456     ASSERT_TRUE(lease);
457     EXPECT_EQ(lf.getReadErrs(),1);
458     }
459 }
460 
461 // Verifies that it is possible to output a lease with very high valid
462 // lifetime (infinite in RFC2131 terms) and current time, and then read
463 // back this lease.
TEST_F(CSVLeaseFile4Test,highLeaseLifetime)464 TEST_F(CSVLeaseFile4Test, highLeaseLifetime) {
465     CSVLeaseFile4 lf(filename_);
466     ASSERT_NO_THROW(lf.recreate());
467     ASSERT_TRUE(io_.exists());
468 
469     // Write lease with very high lease lifetime and current time.
470     Lease4Ptr lease(new Lease4(IOAddress("192.0.3.2"),
471                                hwaddr0_,
472                                NULL, 0,
473                                0xFFFFFFFF, time(0),
474                                8, true, true,
475                                "host.example.com"));
476     // Write this lease out to the lease file.
477     ASSERT_NO_THROW(lf.append(*lease));
478 
479     // Close the lease file.
480     lf.close();
481 
482     Lease4Ptr lease_read;
483 
484     // Re-open the file for reading.
485     ASSERT_NO_THROW(lf.open());
486 
487     // Read the lease and make sure it is successful.
488     EXPECT_TRUE(lf.next(lease_read));
489     ASSERT_TRUE(lease_read);
490 
491     // The valid lifetime and the cltt should match with the original lease.
492     EXPECT_EQ(lease->valid_lft_, lease_read->valid_lft_);
493     EXPECT_EQ(lease->cltt_, lease_read->cltt_);
494 }
495 
496 // Verifies that it is not possible to output a lease with empty hwaddr in other
497 // than the declined state
TEST_F(CSVLeaseFile4Test,emptyHWAddrDefaultStateOnly)498 TEST_F(CSVLeaseFile4Test, emptyHWAddrDefaultStateOnly) {
499     CSVLeaseFile4 lf(filename_);
500     ASSERT_NO_THROW(lf.recreate());
501     ASSERT_TRUE(io_.exists());
502 
503     HWAddrPtr hwaddr;
504 
505     // Create lease with null hwaddr and default state
506     Lease4Ptr lease_null_hwaddr(new Lease4(IOAddress("192.0.3.2"),
507                                 hwaddr,
508                                 NULL, 0,
509                                 0xFFFFFFFF, time(0),
510                                 8, true, true,
511                                 "host.example.com"));
512     // Try to write this lease out to the lease file.
513     ASSERT_THROW(lf.append(*lease_null_hwaddr), BadValue);
514 
515     hwaddr.reset(new HWAddr());
516 
517     // Create lease with empty hwaddr and default state
518     Lease4Ptr lease_empty_hwaddr(new Lease4(IOAddress("192.0.3.2"),
519                                  hwaddr,
520                                  NULL, 0,
521                                  0xFFFFFFFF, time(0),
522                                  8, true, true,
523                                  "host.example.com"));
524     // Try to write this lease out to the lease file.
525     ASSERT_THROW(lf.append(*lease_empty_hwaddr), BadValue);
526 
527     // Create lease with hwaddr and current time.
528     Lease4Ptr lease(new Lease4(IOAddress("192.0.3.2"),
529                                hwaddr0_,
530                                NULL, 0,
531                                0xFFFFFFFF, time(0),
532                                8, true, true,
533                                "host.example.com"));
534 
535     // Decline the lease
536     lease->decline(1000);
537     ASSERT_TRUE(lease->hwaddr_);
538     EXPECT_EQ(lease->hwaddr_->toText(false), "");
539 
540     // Write this lease out to the lease file.
541     ASSERT_NO_THROW(lf.append(*lease));
542 
543     // Close the lease file.
544     lf.close();
545 
546     Lease4Ptr lease_read;
547 
548     // Re-open the file for reading.
549     ASSERT_NO_THROW(lf.open());
550 
551     // Read the lease and make sure it is successful.
552     EXPECT_TRUE(lf.next(lease_read));
553     ASSERT_TRUE(lease_read);
554 
555     // The valid lifetime and the cltt should match with the original lease.
556     EXPECT_EQ(lease->valid_lft_, lease_read->valid_lft_);
557     EXPECT_EQ(lease->cltt_, lease_read->cltt_);
558 }
559 
560 // Verifies that it is possible to write and read a lease with commas
561 // in hostname and user context.
TEST_F(CSVLeaseFile4Test,embeddedCommas)562 TEST_F(CSVLeaseFile4Test, embeddedCommas) {
563     CSVLeaseFile4 lf(filename_);
564     ASSERT_NO_THROW(lf.recreate());
565     ASSERT_TRUE(io_.exists());
566 
567     std::string hostname("host,example,com");
568     std::string context_str("{ \"bar\": true, \"foo\": false, \"x\": \"factor\" }");
569 
570     // Create a lease with commas in the hostname.
571     Lease4Ptr lease(new Lease4(IOAddress("192.0.3.2"),
572                                hwaddr0_,
573                                NULL, 0,
574                                0xFFFFFFFF, time(0),
575                                8, true, true,
576                                hostname));
577 
578     // Add the user context with commas.
579     lease->setContext(Element::fromJSON(context_str));
580 
581     // Write this lease out to the lease file.
582     ASSERT_NO_THROW(lf.append(*lease));
583 
584     // Close the lease file.
585     lf.close();
586 
587     Lease4Ptr lease_read;
588 
589     // Re-open the file for reading.
590     ASSERT_NO_THROW(lf.open());
591 
592     // Read the lease and make sure it is successful.
593     EXPECT_TRUE(lf.next(lease_read));
594     ASSERT_TRUE(lease_read);
595 
596     // Expect the hostname and user context to retain the commas
597     // they started with.
598     EXPECT_EQ(hostname, lease->hostname_);
599     EXPECT_EQ(context_str, lease->getContext()->str());
600 }
601 
602 // Verifies that it is possible to write and read a lease with
603 // escape tags and sequences in hostname and user context.
TEST_F(CSVLeaseFile4Test,embeddedEscapes)604 TEST_F(CSVLeaseFile4Test, embeddedEscapes) {
605     CSVLeaseFile4 lf(filename_);
606     ASSERT_NO_THROW(lf.recreate());
607     ASSERT_TRUE(io_.exists());
608 
609     std::string hostname("host&#xexample&#x2ccom");
610     std::string context_str("{ \"&#xbar\": true, \"foo\": false, \"x\": \"fac&#x2ctor\" }");
611 
612     // Create a lease with commas in the hostname.
613     Lease4Ptr lease(new Lease4(IOAddress("192.0.3.2"),
614                                hwaddr0_,
615                                NULL, 0,
616                                0xFFFFFFFF, time(0),
617                                8, true, true,
618                                hostname));
619 
620     // Add the user context with commas.
621     lease->setContext(Element::fromJSON(context_str));
622 
623     // Write this lease out to the lease file.
624     ASSERT_NO_THROW(lf.append(*lease));
625 
626     // Close the lease file.
627     lf.close();
628 
629     Lease4Ptr lease_read;
630 
631     // Re-open the file for reading.
632     ASSERT_NO_THROW(lf.open());
633 
634     // Read the lease and make sure it is successful.
635     EXPECT_TRUE(lf.next(lease_read));
636     ASSERT_TRUE(lease_read);
637 
638     // Expect the hostname and user context to retain the commas
639     // they started with.
640     EXPECT_EQ(hostname, lease->hostname_);
641     EXPECT_EQ(context_str, lease->getContext()->str());
642 }
643 
644 /// @todo Currently we don't check invalid lease attributes, such as invalid
645 /// lease type, invalid preferred lifetime vs valid lifetime etc. The Lease6
646 /// should be extended with the function that validates lease attributes. Once
647 /// this is implemented we should provide more tests for malformed leases
648 /// in the CSV file. See http://oldkea.isc.org/ticket/2405.
649 
650 } // end of anonymous namespace
651