shill: Parse dhcpcd Event signal properties.

BUG=chromium-os:16126
TEST=unit test

Change-Id: Idafd3cebc015a5f900fd589c27dfcd1b405da7ef
Reviewed-on: http://gerrit.chromium.org/gerrit/2126
Reviewed-by: Chris Masone <cmasone@chromium.org>
Tested-by: Darin Petkov <petkov@chromium.org>
diff --git a/Makefile b/Makefile
index 35b8a2e..4263d05 100644
--- a/Makefile
+++ b/Makefile
@@ -70,6 +70,7 @@
 TEST_OBJS = \
 	dbus_adaptor_unittest.o \
 	device_info_unittest.o \
+	dhcp_config_unittest.o \
 	ipconfig_unittest.o \
 	manager_unittest.o \
 	mock_control.o \
diff --git a/dhcp_config.cc b/dhcp_config.cc
index 36849a6..14bc9bc 100644
--- a/dhcp_config.cc
+++ b/dhcp_config.cc
@@ -4,16 +4,30 @@
 
 #include "shill/dhcp_config.h"
 
+#include <arpa/inet.h>
+
 #include <base/logging.h>
 #include <glib.h>
 
 #include "shill/dhcpcd_proxy.h"
 #include "shill/dhcp_provider.h"
 
+using std::string;
+using std::vector;
+
 namespace shill {
 
+const char DHCPConfig::kConfigurationKeyBroadcastAddress[] = "BroadcastAddress";
+const char DHCPConfig::kConfigurationKeyDNS[] = "DomainNameServers";
+const char DHCPConfig::kConfigurationKeyDomainName[] = "DomainName";
+const char DHCPConfig::kConfigurationKeyDomainSearch[] = "DomainSearch";
+const char DHCPConfig::kConfigurationKeyIPAddress[] = "IPAddress";
+const char DHCPConfig::kConfigurationKeyMTU[] = "InterfaceMTU";
+const char DHCPConfig::kConfigurationKeyRouters[] = "Routers";
+const char DHCPConfig::kConfigurationKeySubnetCIDR[] = "SubnetCIDR";
 const char DHCPConfig::kDHCPCDPath[] = "/sbin/dhcpcd";
 
+
 DHCPConfig::DHCPConfig(DHCPProvider *provider, const Device &device)
     : IPConfig(device),
       provider_(provider),
@@ -53,6 +67,18 @@
   }
 }
 
+void DHCPConfig::ProcessEventSignal(const std::string &reason,
+                                    const Configuration &configuration) {
+  LOG(INFO) << "Event reason: " << reason;
+
+  IPConfig::Properties properties;
+  if (!ParseConfiguration(configuration, &properties)) {
+    LOG(ERROR) << "Unable to parse the new DHCP configuration -- ignored.";
+    return;
+  }
+  UpdateProperties(properties);
+}
+
 bool DHCPConfig::Start() {
   VLOG(2) << __func__ << ": " << GetDeviceName();
 
@@ -83,4 +109,76 @@
   return true;
 }
 
+string DHCPConfig::GetIPv4AddressString(unsigned int address) {
+  char str[INET_ADDRSTRLEN];
+  if (inet_ntop(AF_INET, &address, str, arraysize(str))) {
+    return str;
+  }
+  LOG(ERROR) << "Unable to convert IPv4 address to string: " << address;
+  return "";
+}
+
+bool DHCPConfig::ParseConfiguration(const Configuration& configuration,
+                                    IPConfig::Properties *properties) {
+  VLOG(2) << __func__;
+  for (Configuration::const_iterator it = configuration.begin();
+       it != configuration.end(); ++it) {
+    const string &key = it->first;
+    const DBus::Variant &value = it->second;
+    VLOG(2) << "Processing key: " << key;
+    if (key == kConfigurationKeyIPAddress) {
+      properties->address = GetIPv4AddressString(value.reader().get_uint32());
+      if (properties->address.empty()) {
+        return false;
+      }
+    } else if (key == kConfigurationKeySubnetCIDR) {
+      properties->subnet_cidr = value.reader().get_byte();
+    } else if (key == kConfigurationKeyBroadcastAddress) {
+      properties->broadcast_address =
+          GetIPv4AddressString(value.reader().get_uint32());
+      if (properties->broadcast_address.empty()) {
+        return false;
+      }
+    } else if (key == kConfigurationKeyRouters) {
+      vector<unsigned int> routers;
+      DBus::MessageIter reader = value.reader();
+      reader >> routers;
+      if (routers.empty()) {
+        LOG(ERROR) << "No routers provided.";
+        return false;
+      }
+      properties->gateway = GetIPv4AddressString(routers[0]);
+      if (properties->gateway.empty()) {
+        return false;
+      }
+    } else if (key == kConfigurationKeyDNS) {
+      vector<unsigned int> servers;
+      DBus::MessageIter reader = value.reader();
+      reader >> servers;
+      vector<string> dns_servers;
+      for (vector<unsigned int>::const_iterator it = servers.begin();
+           it != servers.end(); ++it) {
+        string server = GetIPv4AddressString(*it);
+        if (server.empty()) {
+          return false;
+        }
+        properties->dns_servers.push_back(server);
+      }
+    } else if (key == kConfigurationKeyDomainName) {
+      properties->domain_name = value.reader().get_string();
+    } else if (key == kConfigurationKeyDomainSearch) {
+      DBus::MessageIter reader = value.reader();
+      reader >> properties->domain_search;
+    } else if (key == kConfigurationKeyMTU) {
+      int mtu = value.reader().get_uint16();
+      if (mtu >= 576) {
+        properties->mtu = mtu;
+      }
+    } else {
+      VLOG(2) << "Key ignored.";
+    }
+  }
+  return true;
+}
+
 }  // namespace shill
diff --git a/dhcp_config.h b/dhcp_config.h
index f916971..0707774 100644
--- a/dhcp_config.h
+++ b/dhcp_config.h
@@ -6,6 +6,7 @@
 #define SHILL_DHCP_CONFIG_
 
 #include <base/memory/scoped_ptr.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
 #include <dbus-c++/connection.h>
 
 #include "shill/ipconfig.h"
@@ -20,6 +21,17 @@
 
 class DHCPConfig : public IPConfig {
  public:
+  typedef std::map<std::string, DBus::Variant> Configuration;
+
+  static const char kConfigurationKeyBroadcastAddress[];
+  static const char kConfigurationKeyDNS[];
+  static const char kConfigurationKeyDomainName[];
+  static const char kConfigurationKeyDomainSearch[];
+  static const char kConfigurationKeyIPAddress[];
+  static const char kConfigurationKeyMTU[];
+  static const char kConfigurationKeyRouters[];
+  static const char kConfigurationKeySubnetCIDR[];
+
   DHCPConfig(DHCPProvider *provider, const Device &device);
   virtual ~DHCPConfig();
 
@@ -31,12 +43,28 @@
   // |service|.
   void InitProxy(DBus::Connection *connection, const char *service);
 
+  // Processes an Event signal from dhcpcd.
+  void ProcessEventSignal(const std::string &reason,
+                          const Configuration &configuration);
+
  private:
+  FRIEND_TEST(DHCPConfigTest, GetIPv4AddressString);
+  FRIEND_TEST(DHCPConfigTest, ParseConfiguration);
+
   static const char kDHCPCDPath[];
 
   // Starts dhcpcd, returns true on success and false otherwise.
   bool Start();
 
+  // Parses |configuration| into |properties|. Returns true on success, and
+  // false otherwise.
+  bool ParseConfiguration(const Configuration& configuration,
+                          IPConfig::Properties *properties);
+
+  // Returns the string representation of the IP address |address|, or an
+  // empty string on failure.
+  std::string GetIPv4AddressString(unsigned int address);
+
   DHCPProvider *provider_;
 
   // The PID of the spawned DHCP client. May be 0 if no client has been spawned
diff --git a/dhcp_config_unittest.cc b/dhcp_config_unittest.cc
new file mode 100644
index 0000000..aa043e5
--- /dev/null
+++ b/dhcp_config_unittest.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/dhcp_config.h"
+#include "shill/mock_control.h"
+#include "shill/mock_device.h"
+
+using std::string;
+using std::vector;
+using testing::Test;
+
+namespace shill {
+
+class DHCPConfigTest : public Test {
+ public:
+  DHCPConfigTest() :
+      device_(new MockDevice(&control_interface_, NULL, NULL, "testname", 0)) {}
+
+ protected:
+  MockControl control_interface_;
+  scoped_refptr<MockDevice> device_;
+};
+
+TEST_F(DHCPConfigTest, GetIPv4AddressString) {
+  DHCPConfigRefPtr config(new DHCPConfig(NULL, *device_));
+  EXPECT_EQ("255.255.255.255", config->GetIPv4AddressString(0xffffffff));
+  EXPECT_EQ("0.0.0.0", config->GetIPv4AddressString(0));
+  EXPECT_EQ("1.2.3.4", config->GetIPv4AddressString(0x04030201));
+}
+
+TEST_F(DHCPConfigTest, ParseConfiguration) {
+  DHCPConfig::Configuration conf;
+  conf[DHCPConfig::kConfigurationKeyIPAddress].writer().append_uint32(
+      0x01020304);
+  conf[DHCPConfig::kConfigurationKeySubnetCIDR].writer().append_byte(
+      16);
+  conf[DHCPConfig::kConfigurationKeyBroadcastAddress].writer().append_uint32(
+      0x10203040);
+  {
+    DBus::Variant var;
+    vector<unsigned int> routers;
+    routers.push_back(0x02040608);
+    routers.push_back(0x03050709);
+    DBus::MessageIter writer = var.writer();
+    writer << routers;
+    conf[DHCPConfig::kConfigurationKeyRouters] = var;
+  }
+  {
+    DBus::Variant var;
+    vector<unsigned int> dns;
+    dns.push_back(0x09070503);
+    dns.push_back(0x08060402);
+    DBus::MessageIter writer = var.writer();
+    writer << dns;
+    conf[DHCPConfig::kConfigurationKeyDNS] = var;
+  }
+  conf[DHCPConfig::kConfigurationKeyDomainName].writer().append_string(
+      "domain-name");
+  {
+    DBus::Variant var;
+    vector<string> search;
+    search.push_back("foo.com");
+    search.push_back("bar.com");
+    DBus::MessageIter writer = var.writer();
+    writer << search;
+    conf[DHCPConfig::kConfigurationKeyDomainSearch] = var;
+  }
+  conf[DHCPConfig::kConfigurationKeyMTU].writer().append_uint16(600);
+  conf["UnknownKey"] = DBus::Variant();
+
+  DHCPConfigRefPtr config(new DHCPConfig(NULL, *device_));
+  IPConfig::Properties properties;
+  ASSERT_TRUE(config->ParseConfiguration(conf, &properties));
+  EXPECT_EQ("4.3.2.1", properties.address);
+  EXPECT_EQ(16, properties.subnet_cidr);
+  EXPECT_EQ("64.48.32.16", properties.broadcast_address);
+  EXPECT_EQ("8.6.4.2", properties.gateway);
+  ASSERT_EQ(2, properties.dns_servers.size());
+  EXPECT_EQ("3.5.7.9", properties.dns_servers[0]);
+  EXPECT_EQ("2.4.6.8", properties.dns_servers[1]);
+  EXPECT_EQ("domain-name", properties.domain_name);
+  ASSERT_EQ(2, properties.domain_search.size());
+  EXPECT_EQ("foo.com", properties.domain_search[0]);
+  EXPECT_EQ("bar.com", properties.domain_search[1]);
+  EXPECT_EQ(600, properties.mtu);
+}
+
+}  // namespace shill
diff --git a/dhcpcd_proxy.cc b/dhcpcd_proxy.cc
index 9f577c5..2552a29 100644
--- a/dhcpcd_proxy.cc
+++ b/dhcpcd_proxy.cc
@@ -8,7 +8,9 @@
 
 #include "shill/dhcp_provider.h"
 
+using std::map;
 using std::string;
+using std::vector;
 
 namespace shill {
 
@@ -38,8 +40,12 @@
     return;
   }
   config->InitProxy(&conn(), signal.sender());
-  // TODO(petkov): Process and dispatch the signal to the appropriate DHCP
-  // configuration.
+
+  string reason;
+  ri >> reason;
+  DHCPConfig::Configuration configuration;
+  ri >> configuration;
+  config->ProcessEventSignal(reason, configuration);
 }
 
 void DHCPCDListener::StatusChangedSignal(const DBus::SignalMessage &signal) {
@@ -74,15 +80,14 @@
   Rebind(interface);
 }
 
-void DHCPCDProxy::Event(
-    const uint32_t& pid,
-    const std::string& reason,
-    const std::map< std::string, DBus::Variant >& configuration) {
+void DHCPCDProxy::Event(const uint32_t &pid,
+                        const std::string &reason,
+                        const DHCPConfig::Configuration &configuration) {
   NOTREACHED();
 }
 
-void DHCPCDProxy::StatusChanged(const uint32_t& pid,
-                                const std::string& status) {
+void DHCPCDProxy::StatusChanged(const uint32_t &pid,
+                                const std::string &status) {
   NOTREACHED();
 }
 
diff --git a/dhcpcd_proxy.h b/dhcpcd_proxy.h
index 39ef8a5..a178c93 100644
--- a/dhcpcd_proxy.h
+++ b/dhcpcd_proxy.h
@@ -52,10 +52,10 @@
   // unused because signals are dispatched directly to the DHCP configuration
   // instance by the signal listener.
   virtual void Event(
-      const uint32_t& pid,
-      const std::string& reason,
-      const std::map< std::string, DBus::Variant >& configuration);
-  virtual void StatusChanged(const uint32_t& pid, const std::string& status);
+      const uint32_t &pid,
+      const std::string &reason,
+      const DHCPConfig::Configuration &configuration);
+  virtual void StatusChanged(const uint32_t &pid, const std::string &status);
 
  private:
   DISALLOW_COPY_AND_ASSIGN(DHCPCDProxy);
diff --git a/ipconfig.cc b/ipconfig.cc
index 950eda7..092e279 100644
--- a/ipconfig.cc
+++ b/ipconfig.cc
@@ -24,4 +24,17 @@
   return device().UniqueName();
 }
 
+bool IPConfig::Request() {
+  return false;
+}
+
+bool IPConfig::Renew() {
+  return false;
+}
+
+void IPConfig::UpdateProperties(const Properties &properties) {
+  properties_ = properties;
+  // TODO(petkov): Notify listeners about the new properties.
+}
+
 }  // namespace shill
diff --git a/ipconfig.h b/ipconfig.h
index bd964be..bf860d6 100644
--- a/ipconfig.h
+++ b/ipconfig.h
@@ -6,6 +6,7 @@
 #define SHILL_IPCONFIG_
 
 #include <string>
+#include <vector>
 
 #include <base/memory/ref_counted.h>
 
@@ -17,21 +18,40 @@
 // class.
 class IPConfig : public base::RefCounted<IPConfig> {
  public:
+  struct Properties {
+    Properties() : subnet_cidr(0), mtu(0) {}
+
+    std::string address;
+    int subnet_cidr;
+    std::string broadcast_address;
+    std::string gateway;
+    std::vector<std::string> dns_servers;
+    std::string domain_name;
+    std::vector<std::string> domain_search;
+    int mtu;
+  };
+
   explicit IPConfig(const Device &device);
   virtual ~IPConfig();
 
   const Device &device() const { return device_; }
-
   const std::string &GetDeviceName() const;
 
-  // Request or renew IP configuration. Return true on success, false otherwise.
-  virtual bool Request() { return false; }
-  virtual bool Renew() { return false; }
+  // Updates the IP configuration properties and notifies registered listeners
+  // about the event.
+  void UpdateProperties(const Properties &properties);
+
+  const Properties &properties() const { return properties_; }
+
+  // Request or renew IP configuration. Return true on success, false
+  // otherwise. The default implementation always returns false indicating a
+  // failure.
+  virtual bool Request();
+  virtual bool Renew();
 
  private:
-  friend class base::RefCounted<IPConfig>;
-
   const Device &device_;
+  Properties properties_;
 
   DISALLOW_COPY_AND_ASSIGN(IPConfig);
 };