dhcp client: add classes for parsing dhcp options

Add class DHCPOptionsParser and its descendant classes
to provide an unified interface for parsing dhcp options.
Also add unittests for these classes.

Bug: 25642025
TEST=compile and unittests

Change-Id: I02dccef5ba1ee7a094a1b03b75fe8b987b16d2c3
diff --git a/dhcp_client.gyp b/dhcp_client.gyp
index fc96e48..dad1248 100644
--- a/dhcp_client.gyp
+++ b/dhcp_client.gyp
@@ -28,6 +28,7 @@
       '-Wno-unused-parameter',  # base/lazy_instance.h, etc.
     ],
     'cflags_cc': [
+      '-fno-strict-aliasing',
       '-Wno-missing-field-initializers', # for LAZY_INSTANCE_INITIALIZER
       '-Wno-unused-const-variable',
     ],
@@ -58,6 +59,7 @@
         'daemon.cc',
         'device_info.cc',
         'dhcp_message.cc',
+        'dhcp_options_parser.cc',
         'dhcpv4.cc',
         'message_loop_event_dispatcher.cc',
         'manager.cc',
@@ -83,6 +85,7 @@
           'includes': ['../../../../platform2/common-mk/common_test.gypi'],
           'sources': [
             'device_info_unittest.cc',
+            'dhcp_options_parser_unittest.cc',
             'testrunner.cc',
           ],
         },
diff --git a/dhcp_options_parser.cc b/dhcp_options_parser.cc
new file mode 100644
index 0000000..075a61e
--- /dev/null
+++ b/dhcp_options_parser.cc
@@ -0,0 +1,175 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "dhcp_client/dhcp_options_parser.h"
+
+#include <netinet/in.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/macros.h>
+
+namespace dhcp_client {
+
+bool UInt8Parser::GetOption(const uint8_t* buffer,
+                            uint8_t length,
+                            void* value) {
+  if (length != sizeof(uint8_t)) {
+    LOG(ERROR) << "Invalid option length field";
+    return false;
+  }
+  uint8_t* value_uint8 = static_cast<uint8_t*>(value);
+  *value_uint8 = *buffer;
+  return true;
+}
+
+bool UInt16Parser::GetOption(const uint8_t* buffer,
+                             uint8_t length,
+                             void* value) {
+  if (length != sizeof(uint16_t)) {
+    LOG(ERROR) << "Invalid option length field";
+    return false;
+  }
+  uint16_t* value_uint16 = static_cast<uint16_t*>(value);
+  *value_uint16 = ntohs(*reinterpret_cast<const uint16_t*>(buffer));
+  return true;
+}
+
+bool UInt32Parser::GetOption(const uint8_t* buffer,
+                             uint8_t length,
+                             void* value) {
+  if (length != sizeof(uint32_t)) {
+    LOG(ERROR) << "Invalid option length field";
+    return false;
+  }
+  uint32_t* value_uint32 = static_cast<uint32_t*>(value);
+  *value_uint32 = ntohl(*reinterpret_cast<const uint32_t*>(buffer));
+  return true;
+}
+
+bool UInt8ListParser::GetOption(const uint8_t* buffer,
+                                uint8_t length,
+                                void* value) {
+  if (length == 0) {
+    LOG(ERROR) << "Invalid option length field";
+    return false;
+  }
+  std::vector<uint8_t>* value_vector =
+      static_cast<std::vector<uint8_t>*>(value);
+  for (int i = 0; i < length; i++) {
+    uint8_t content = *reinterpret_cast<const uint8_t*>(buffer);
+    value_vector->push_back(content);
+    buffer += sizeof(uint8_t);
+  }
+  return true;
+}
+
+bool UInt16ListParser::GetOption(const uint8_t* buffer,
+                                 uint8_t length,
+                                 void* value) {
+  if (length == 0 || length % sizeof(uint16_t)) {
+    LOG(ERROR) << "Invalid option length field";
+    return false;
+  }
+  int num_int16s = length / sizeof(uint16_t);
+  std::vector<uint16_t>* value_vector =
+      static_cast<std::vector<uint16_t>*>(value);
+  for (int i = 0; i < num_int16s; i++) {
+    uint16_t content = *reinterpret_cast<const uint16_t*>(buffer);
+    content = ntohs(content);
+    value_vector->push_back(content);
+    buffer += sizeof(uint16_t);
+  }
+  return true;
+}
+
+bool UInt32ListParser::GetOption(const uint8_t* buffer,
+                                 uint8_t length,
+                                 void* value) {
+  if (length == 0 || length % sizeof(uint32_t)) {
+    LOG(ERROR) << "Invalid option length field";
+    return false;
+  }
+  int num_int32s = length / sizeof(uint32_t);
+  std::vector<uint32_t>* value_vector =
+      static_cast<std::vector<uint32_t>*>(value);
+  for (int i = 0; i < num_int32s; i++) {
+    uint32_t content = *reinterpret_cast<const uint32_t*>(buffer);
+    content = ntohl(content);
+    value_vector->push_back(content);
+    buffer += sizeof(uint32_t);
+  }
+  return true;
+}
+
+bool UInt32PairListParser::GetOption(const uint8_t* buffer,
+                                     uint8_t length,
+                                     void* value) {
+  if (length == 0 || length % (2 * sizeof(uint32_t))) {
+    LOG(ERROR) << "Invalid option length field";
+    return false;
+  }
+  int num_int32pairs = length / (2 * sizeof(uint32_t));
+  std::vector<std::pair<uint32_t, uint32_t>>* value_vector =
+      static_cast<std::vector<std::pair<uint32_t, uint32_t>>*>(value);
+  for (int i = 0; i < num_int32pairs; i++) {
+    uint32_t first = *reinterpret_cast<const uint32_t*>(buffer);
+    first = ntohl(first);
+    buffer += sizeof(uint32_t);
+    uint32_t second = *reinterpret_cast<const uint32_t*>(buffer);
+    second = ntohl(second);
+    value_vector->push_back(std::pair<uint32_t, uint32_t>(first, second));
+    buffer += sizeof(uint32_t);
+  }
+  return true;
+}
+
+bool BoolParser::GetOption(const uint8_t* buffer,
+                           uint8_t length,
+                           void* value) {
+  if (length != sizeof(uint8_t)) {
+    LOG(ERROR) << "Invalid option length field";
+    return false;
+  }
+  uint8_t content = *buffer;
+  bool* enable = static_cast<bool*>(value);
+  if (content == 1) {
+    *enable = true;
+  } else if (content == 0) {
+    *enable = false;
+  } else {
+    LOG(ERROR) << "Invalid option value field";
+    return false;
+  }
+  return true;
+}
+
+bool StringParser::GetOption(const uint8_t* buffer,
+                             uint8_t length,
+                             void* value) {
+  if (length == 0) {
+    LOG(ERROR) << "Invalid option length field";
+    return false;
+  }
+  std::string* option_string = static_cast<std::string*>(value);
+  option_string->assign(reinterpret_cast<const char*>(buffer), length);
+  return true;
+}
+
+}  // namespace dhcp_client
diff --git a/dhcp_options_parser.h b/dhcp_options_parser.h
new file mode 100644
index 0000000..f8e6786
--- /dev/null
+++ b/dhcp_options_parser.h
@@ -0,0 +1,106 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef DHCP_CLIENT_PARSER_H_
+#define DHCP_CLIENT_PARSER_H_
+
+#include <cstdint>
+
+namespace dhcp_client {
+
+class DHCPOptionsParser {
+ public:
+  virtual bool GetOption(const uint8_t* buffer,
+                         uint8_t length,
+                         void* value) = 0;
+  virtual ~DHCPOptionsParser() {}
+};
+
+class UInt8Parser : public DHCPOptionsParser {
+ public:
+  UInt8Parser() {}
+  bool GetOption(const uint8_t* buffer,
+                 uint8_t length,
+                 void* value) override;
+};
+
+class UInt16Parser : public DHCPOptionsParser {
+ public:
+  UInt16Parser() {}
+  bool GetOption(const uint8_t* buffer,
+                 uint8_t length,
+                 void* value) override;
+};
+
+class UInt32Parser : public DHCPOptionsParser {
+ public:
+  UInt32Parser() {}
+  bool GetOption(const uint8_t* buffer,
+                 uint8_t length,
+                 void* value) override;
+};
+
+class UInt8ListParser : public DHCPOptionsParser {
+ public:
+  UInt8ListParser() {}
+  bool GetOption(const uint8_t* buffer,
+                 uint8_t length,
+                 void* value) override;
+};
+
+class UInt16ListParser : public DHCPOptionsParser {
+ public:
+  UInt16ListParser() {}
+  bool GetOption(const uint8_t* buffer,
+                 uint8_t length,
+                 void* value) override;
+};
+
+class UInt32ListParser : public DHCPOptionsParser {
+ public:
+  UInt32ListParser() {}
+  bool GetOption(const uint8_t* buffer,
+                 uint8_t length,
+                 void* value) override;
+};
+
+class UInt32PairListParser : public DHCPOptionsParser {
+ public:
+  UInt32PairListParser() {}
+  bool GetOption(const uint8_t* buffer,
+                 uint8_t length,
+                 void* value) override;
+};
+
+class BoolParser : public DHCPOptionsParser {
+ public:
+  BoolParser() {}
+  bool GetOption(const uint8_t* buffer,
+                 uint8_t length,
+                 void* value) override;
+};
+
+class StringParser : public DHCPOptionsParser {
+ public:
+  StringParser() {}
+  bool GetOption(const uint8_t* buffer,
+                 uint8_t length,
+                 void* value) override;
+};
+
+}  // namespace dhcp_client
+
+#endif  // DHCP_CLIENT_PARSER_H_
diff --git a/dhcp_options_parser_unittest.cc b/dhcp_options_parser_unittest.cc
new file mode 100644
index 0000000..c701099
--- /dev/null
+++ b/dhcp_options_parser_unittest.cc
@@ -0,0 +1,206 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <dhcp_client/dhcp_options_parser.h>
+
+#include <netinet/in.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace {
+const uint8_t kFakeUInt8Option[] = {0x02};
+const uint8_t kFakeUInt8OptionLength = 1;
+const uint8_t kFakeUInt16Option[] = {0x2a, 0x01};
+const uint8_t kFakeUInt16OptionLength = 2;
+const uint8_t kFakeUInt32Option[] = {0x01, 0x02, 0x00, 0xfa};
+const uint8_t kFakeUInt32OptionLength = 4;
+const uint8_t kFakeUInt8ListOption[] =
+    {0x01, 0x02, 0x00, 0xfa, 0x23, 0xae, 0x1f, 0x00};
+const uint8_t kFakeUInt8ListOptionLength = 8;
+
+const uint8_t kFakeUInt16ListOption[] =
+    {0xaa, 0x26, 0x4b, 0x00, 0xff, 0xc2, 0xcf, 0x0d, 0xe0, 0x01};
+const uint8_t kFakeUInt16ListOptionLength = 10;
+
+const uint8_t kFakeUInt32ListOption[] = {0x01, 0x02, 0x00, 0xfa,
+                                         0x23, 0xae, 0x1f, 0x00,
+                                         0x0c, 0x53, 0x33, 0x10,
+                                         0x47, 0x80, 0xb3, 0xff};
+const uint8_t kFakeUInt32ListOptionLength = 16;
+
+const uint8_t kFakeUInt32PairListOption[] = {0x21, 0xa0, 0xeb, 0x73,
+                                             0x01, 0x00, 0x1f, 0x10,
+                                             0xc9, 0x22, 0x3a, 0x37,
+                                             0xff, 0x00, 0xbe, 0xd0};
+const uint8_t kFakeUInt32PairListOptionLength = 16;
+
+const unsigned char kFakeStringOption[] =
+    {'f', 'a', 'k', 'e', 's', 't', 'r', 'i', 'n', 'g'};
+const uint8_t kFakeStringOptionLength = 10;
+
+const uint8_t kFakeBoolOptionEnable[] = {0x01};
+const uint8_t kFakeBoolOptionDisable[] = {0x00};
+const uint8_t kFakeBoolOptionLength = 1;
+}  // namespace
+
+namespace dhcp_client {
+
+class ParserTest : public testing::Test {
+ protected:
+  std::unique_ptr<DHCPOptionsParser> parser_;
+};
+
+TEST_F(ParserTest, ParseUInt8) {
+  parser_.reset(new UInt8Parser());
+  uint8_t value;
+  EXPECT_TRUE(parser_->GetOption(kFakeUInt8Option,
+                                 kFakeUInt8OptionLength,
+                                 &value));
+  EXPECT_EQ(*kFakeUInt8Option, value);
+}
+
+TEST_F(ParserTest, ParseUInt16) {
+  parser_.reset(new UInt16Parser());
+  uint16_t value;
+  uint16_t target_value =
+      *reinterpret_cast<const uint16_t*>(kFakeUInt16Option);
+  target_value = ntohs(target_value);
+  EXPECT_TRUE(parser_->GetOption(kFakeUInt16Option,
+                                 kFakeUInt16OptionLength,
+                                 &value));
+  EXPECT_EQ(target_value, value);
+}
+
+TEST_F(ParserTest, ParseUInt32) {
+  parser_.reset(new UInt32Parser());
+  uint32_t value;
+  uint32_t target_value =
+      *reinterpret_cast<const uint32_t*>(kFakeUInt32Option);
+  target_value = ntohl(target_value);
+  EXPECT_TRUE(parser_->GetOption(kFakeUInt32Option,
+                                 kFakeUInt32OptionLength,
+                                 &value));
+  EXPECT_EQ(target_value, value);
+}
+
+TEST_F(ParserTest, ParseUInt8List) {
+  parser_.reset(new UInt8ListParser());
+  std::vector<uint8_t> value;
+  std::vector<uint8_t> target_value;
+  uint8_t length = kFakeUInt8ListOptionLength;
+  const uint8_t* uint8_list =
+      reinterpret_cast<const uint8_t*>(kFakeUInt8ListOption);
+  target_value = std::vector<uint8_t>(uint8_list, uint8_list + length);
+  EXPECT_TRUE(parser_->GetOption(kFakeUInt8ListOption,
+                                 kFakeUInt8ListOptionLength,
+                                 &value));
+  EXPECT_EQ(target_value, value);
+}
+
+TEST_F(ParserTest, ParseUInt16List) {
+  parser_.reset(new UInt16ListParser());
+  std::vector<uint16_t> value;
+  std::vector<uint16_t> target_value;
+  std::vector<uint16_t> target_value_net_order;
+  int length = kFakeUInt16ListOptionLength / sizeof(uint16_t);
+  const uint16_t* uint16_list =
+      reinterpret_cast<const uint16_t*>(kFakeUInt16ListOption);
+  target_value_net_order =
+      std::vector<uint16_t>(uint16_list, uint16_list + length);
+  for (uint16_t element : target_value_net_order) {
+    target_value.push_back(ntohs(element));
+  }
+  EXPECT_TRUE(parser_->GetOption(kFakeUInt16ListOption,
+                                 kFakeUInt16ListOptionLength,
+                                 &value));
+  EXPECT_EQ(target_value, value);
+}
+
+TEST_F(ParserTest, ParseUInt32List) {
+  parser_.reset(new UInt32ListParser());
+  std::vector<uint32_t> value;
+  std::vector<uint32_t> target_value;
+  std::vector<uint32_t> target_value_net_order;
+  int length = kFakeUInt32ListOptionLength / sizeof(uint32_t);
+  const uint32_t* uint32_list =
+      reinterpret_cast<const uint32_t*>(kFakeUInt32ListOption);
+  target_value_net_order =
+      std::vector<uint32_t>(uint32_list, uint32_list + length);
+  for (uint32_t element : target_value_net_order) {
+    target_value.push_back(ntohl(element));
+  }
+  EXPECT_TRUE(parser_->GetOption(kFakeUInt32ListOption,
+                                 kFakeUInt32ListOptionLength,
+                                 &value));
+  EXPECT_EQ(target_value, value);
+}
+
+TEST_F(ParserTest, ParseUInt32PairList) {
+  parser_.reset(new UInt32PairListParser());
+  int length = kFakeUInt32PairListOptionLength / (2 * sizeof(uint32_t));
+  const uint32_t* uint32_array =
+      reinterpret_cast<const uint32_t*>(kFakeUInt32PairListOption);
+  std::vector<uint32_t> uint32_vector =
+      std::vector<uint32_t>(uint32_array, uint32_array + length * 2);
+  std::vector<std::pair<uint32_t, uint32_t>> target_value;
+  for (int i = 0; i < length; i++) {
+    target_value.push_back(
+        std::pair<uint32_t, uint32_t>(ntohl(uint32_vector[2 * i]),
+                                      ntohl(uint32_vector[2 * i + 1])));
+  }
+  std::vector<std::pair<uint32_t, uint32_t>> value;
+  EXPECT_TRUE(parser_->GetOption(kFakeUInt32PairListOption,
+                                 kFakeUInt32PairListOptionLength,
+                                 &value));
+  EXPECT_EQ(target_value, value);
+}
+
+TEST_F(ParserTest, ParseBoolEnable) {
+  parser_.reset(new BoolParser());
+  bool value;
+  EXPECT_TRUE(parser_->GetOption(kFakeBoolOptionEnable,
+                                 kFakeBoolOptionLength,
+                                 &value));
+  EXPECT_TRUE(value);
+}
+
+TEST_F(ParserTest, ParseBoolDisable) {
+  parser_.reset(new BoolParser());
+  bool value;
+  EXPECT_TRUE(parser_->GetOption(kFakeBoolOptionDisable,
+                                 kFakeBoolOptionLength,
+                                 &value));
+  EXPECT_FALSE(value);
+}
+
+TEST_F(ParserTest, ParseString) {
+  parser_.reset(new StringParser());
+  std::string value;
+  std::string target_value;
+  target_value.assign(reinterpret_cast<const char*>(kFakeStringOption),
+                      kFakeStringOptionLength);
+  EXPECT_TRUE(parser_->GetOption(kFakeStringOption,
+                                 kFakeStringOptionLength,
+                                 &value));
+  EXPECT_EQ(target_value, value);
+}
+
+}  // namespace dhcp_client