shill: vpn: Parse INFO and STATE management interface messages.

This adds support for reconnect as well as missing OpenVPN management interface
unit tests.

BUG=chromium-os:26994
TEST=unit tests

Change-Id: Ib2c9911412154a81c8881618bf8b170346a21da0
Reviewed-on: https://gerrit.chromium.org/gerrit/19155
Commit-Ready: Darin Petkov <petkov@chromium.org>
Reviewed-by: Darin Petkov <petkov@chromium.org>
Tested-by: Darin Petkov <petkov@chromium.org>
diff --git a/mock_openvpn_driver.h b/mock_openvpn_driver.h
index 393c71b..7213582 100644
--- a/mock_openvpn_driver.h
+++ b/mock_openvpn_driver.h
@@ -16,6 +16,8 @@
   MockOpenVPNDriver(const KeyValueStore &args);
   virtual ~MockOpenVPNDriver();
 
+  MOCK_METHOD0(OnReconnecting, void());
+
  private:
   DISALLOW_COPY_AND_ASSIGN(MockOpenVPNDriver);
 };
diff --git a/openvpn_driver.cc b/openvpn_driver.cc
index 6852cd4..ec78a8b 100644
--- a/openvpn_driver.cc
+++ b/openvpn_driver.cc
@@ -532,9 +532,20 @@
 }
 
 void OpenVPNDriver::Disconnect() {
+  VLOG(2) << __func__;
   Cleanup(Service::kStateIdle);
 }
 
+void OpenVPNDriver::OnReconnecting() {
+  VLOG(2) << __func__;
+  if (device_) {
+    device_->OnDisconnected();
+  }
+  if (service_) {
+    service_->SetState(Service::kStateAssociating);
+  }
+}
+
 bool OpenVPNDriver::Load(StoreInterface *storage, const string &storage_id) {
   for (size_t i = 0; i < arraysize(kProperties); i++) {
     const string property = kProperties[i].property;
diff --git a/openvpn_driver.h b/openvpn_driver.h
index 91a9ff6..377ccd6 100644
--- a/openvpn_driver.h
+++ b/openvpn_driver.h
@@ -55,6 +55,8 @@
   virtual bool Load(StoreInterface *storage, const std::string &storage_id);
   virtual bool Save(StoreInterface *storage, const std::string &storage_id);
 
+  virtual void OnReconnecting();
+
   virtual void InitPropertyStore(PropertyStore *store);
   void ClearMappedProperty(const size_t &index, Error *error);
   std::string GetMappedProperty(const size_t &index, Error *error);
@@ -77,6 +79,7 @@
   FRIEND_TEST(OpenVPNDriverTest, Notify);
   FRIEND_TEST(OpenVPNDriverTest, NotifyFail);
   FRIEND_TEST(OpenVPNDriverTest, OnOpenVPNDied);
+  FRIEND_TEST(OpenVPNDriverTest, OnReconnecting);
   FRIEND_TEST(OpenVPNDriverTest, ParseForeignOption);
   FRIEND_TEST(OpenVPNDriverTest, ParseForeignOptions);
   FRIEND_TEST(OpenVPNDriverTest, ParseIPConfiguration);
diff --git a/openvpn_driver_unittest.cc b/openvpn_driver_unittest.cc
index a4c531e..2032ef9 100644
--- a/openvpn_driver_unittest.cc
+++ b/openvpn_driver_unittest.cc
@@ -480,6 +480,15 @@
   EXPECT_FALSE(driver_->service_);
 }
 
+TEST_F(OpenVPNDriverTest, OnReconnecting) {
+  driver_->OnReconnecting();  // Expect no crash.
+  driver_->device_ = device_;
+  driver_->service_ = service_;
+  EXPECT_CALL(*device_, OnDisconnected());
+  EXPECT_CALL(*service_, SetState(Service::kStateAssociating));
+  driver_->OnReconnecting();
+}
+
 MATCHER_P(IsIPAddress, address, "") {
   IPAddress ip_address(IPAddress::kFamilyIPv4);
   EXPECT_TRUE(ip_address.SetAddressFromString(address));
diff --git a/openvpn_management_server.cc b/openvpn_management_server.cc
index 71bff83..9c13ebd 100644
--- a/openvpn_management_server.cc
+++ b/openvpn_management_server.cc
@@ -13,6 +13,7 @@
 #include <base/stringprintf.h>
 
 #include "shill/event_dispatcher.h"
+#include "shill/openvpn_driver.h"
 #include "shill/sockets.h"
 
 using base::Bind;
@@ -46,7 +47,7 @@
     return true;
   }
 
-  int socket = sockets_->Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+  int socket = sockets->Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
   if (socket < 0) {
     PLOG(ERROR) << "Unable to create management server socket.";
     return false;
@@ -68,6 +69,7 @@
   }
 
   VLOG(2) << "Listening socket: " << socket;
+  sockets_ = sockets;
   socket_ = socket;
   ready_handler_.reset(
       dispatcher->CreateReadyHandler(
@@ -121,22 +123,59 @@
 
 void OpenVPNManagementServer::ProcessMessage(const string &message) {
   VLOG(2) << __func__ << "(" << message << ")";
-  if (StartsWithASCII(message, ">INFO:", true)) {
-    return;
+  LOG_IF(WARNING,
+         !ProcessInfoMessage(message) &&
+         !ProcessNeedPasswordMessage(message) &&
+         !ProcessFailedPasswordMessage(message) &&
+         !ProcessStateMessage(message))
+      << "OpenVPN management message ignored: " << message;
+}
+
+bool OpenVPNManagementServer::ProcessInfoMessage(const string &message) {
+  return StartsWithASCII(message, ">INFO:", true);
+}
+
+bool OpenVPNManagementServer::ProcessNeedPasswordMessage(
+    const string &message) {
+  if (!StartsWithASCII(message, ">PASSWORD:Need ", true)) {
+    return false;
   }
-  if (StartsWithASCII(message, ">PASSWORD:Need ", true)) {
-    NOTIMPLEMENTED();
-    return;
+  NOTIMPLEMENTED();
+  return true;
+}
+
+bool OpenVPNManagementServer::ProcessFailedPasswordMessage(
+    const string &message) {
+  if (!StartsWithASCII(message, ">PASSWORD:Verification Failed:", true)) {
+    return false;
   }
-  if (StartsWithASCII(message, ">PASSWORD:Verification Failed:", true)) {
-    NOTIMPLEMENTED();
-    return;
+  NOTIMPLEMENTED();
+  return true;
+}
+
+// >STATE:* message support. State messages are of the form:
+//    >STATE:<date>,<state>,<detail>,<local-ip>,<remote-ip>
+// where:
+// <date> is the current time (since epoch) in seconds
+// <state> is one of:
+//    INITIAL, CONNECTING, WAIT, AUTH, GET_CONFIG, ASSIGN_IP, ADD_ROUTES,
+//    CONNECTED, RECONNECTING, EXITING, RESOLVE, TCP_CONNECT
+// <detail> is a free-form string giving details about the state change
+// <local-ip> is a dotted-quad for the local IPv4 address (when available)
+// <remote-ip> is a dotted-quad for the remote IPv4 address (when available)
+bool OpenVPNManagementServer::ProcessStateMessage(const string &message) {
+  if (!StartsWithASCII(message, ">STATE:", true)) {
+    return false;
   }
-  if (StartsWithASCII(message, ">STATE:", true)) {
-    NOTIMPLEMENTED();
-    return;
+  vector<string> details;
+  SplitString(message, ',', &details);
+  if (details.size() > 1) {
+    if (details[1] == "RECONNECTING") {
+      driver_->OnReconnecting();
+    }
+    // The rest of the states are currently ignored.
   }
-  LOG(WARNING) << "OpenVPN management message ignored: " << message;
+  return true;
 }
 
 void OpenVPNManagementServer::Send(const string &data) {
diff --git a/openvpn_management_server.h b/openvpn_management_server.h
index 9263f12..4ff41b7 100644
--- a/openvpn_management_server.h
+++ b/openvpn_management_server.h
@@ -8,6 +8,7 @@
 #include <base/basictypes.h>
 #include <base/cancelable_callback.h>
 #include <base/memory/weak_ptr.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
 namespace shill {
 
@@ -28,6 +29,18 @@
   void Stop();
 
  private:
+  friend class OpenVPNManagementServerTest;
+  FRIEND_TEST(OpenVPNManagementServerTest, OnInput);
+  FRIEND_TEST(OpenVPNManagementServerTest, OnReady);
+  FRIEND_TEST(OpenVPNManagementServerTest, OnReadyAcceptFail);
+  FRIEND_TEST(OpenVPNManagementServerTest, ProcessInfoMessage);
+  FRIEND_TEST(OpenVPNManagementServerTest, ProcessMessage);
+  FRIEND_TEST(OpenVPNManagementServerTest, ProcessStateMessage);
+  FRIEND_TEST(OpenVPNManagementServerTest, Send);
+  FRIEND_TEST(OpenVPNManagementServerTest, SendState);
+  FRIEND_TEST(OpenVPNManagementServerTest, Start);
+  FRIEND_TEST(OpenVPNManagementServerTest, Stop);
+
   // IO handler callbacks.
   void OnReady(int fd);
   void OnInput(InputData *data);
@@ -36,6 +49,10 @@
   void SendState(const std::string &state);
 
   void ProcessMessage(const std::string &message);
+  bool ProcessInfoMessage(const std::string &message);
+  bool ProcessNeedPasswordMessage(const std::string &message);
+  bool ProcessFailedPasswordMessage(const std::string &message);
+  bool ProcessStateMessage(const std::string &message);
 
   OpenVPNDriver *driver_;
   base::WeakPtrFactory<OpenVPNManagementServer> weak_ptr_factory_;
diff --git a/openvpn_management_server_unittest.cc b/openvpn_management_server_unittest.cc
index 7ebcae7..2e8b037 100644
--- a/openvpn_management_server_unittest.cc
+++ b/openvpn_management_server_unittest.cc
@@ -4,13 +4,32 @@
 
 #include "shill/openvpn_management_server.h"
 
+#include <netinet/in.h>
+
 #include <gtest/gtest.h>
 
 #include "shill/key_value_store.h"
+#include "shill/mock_event_dispatcher.h"
 #include "shill/mock_openvpn_driver.h"
+#include "shill/mock_sockets.h"
+
+using std::string;
+using testing::_;
+using testing::Return;
+using testing::ReturnNew;
 
 namespace shill {
 
+namespace {
+MATCHER_P(CallbackEq, callback, "") {
+  return arg.Equals(callback);
+}
+
+MATCHER_P(VoidStringEq, value, "") {
+  return value == reinterpret_cast<const char *>(arg);
+}
+}  // namespace {}
+
 class OpenVPNManagementServerTest : public testing::Test {
  public:
   OpenVPNManagementServerTest()
@@ -19,14 +38,171 @@
 
   virtual ~OpenVPNManagementServerTest() {}
 
+  void SetSockets() { server_.sockets_ = &sockets_; }
+  void SetDispatcher() { server_.dispatcher_ = &dispatcher_; }
+  void ExpectNotStarted() { EXPECT_TRUE(server_.sockets_ == NULL); }
+
+  void ExpectSend(int socket, const string &value) {
+    EXPECT_CALL(sockets_, Send(socket, VoidStringEq(value), value.size(), 0))
+        .WillOnce(Return(value.size()));
+  }
+
+  InputData CreateInputDataFromString(const string &str) {
+    InputData data(
+        reinterpret_cast<unsigned char *>(const_cast<char *>(str.data())),
+        str.size());
+    return data;
+  }
+
  protected:
   KeyValueStore args_;
   MockOpenVPNDriver driver_;
   OpenVPNManagementServer server_;
+  MockSockets sockets_;
+  MockEventDispatcher dispatcher_;
 };
 
+TEST_F(OpenVPNManagementServerTest, StartStarted) {
+  SetSockets();
+  EXPECT_TRUE(server_.Start(NULL, NULL));
+}
+
+TEST_F(OpenVPNManagementServerTest, StartSocketFail) {
+  EXPECT_CALL(sockets_, Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))
+      .WillOnce(Return(-1));
+  EXPECT_FALSE(server_.Start(NULL, &sockets_));
+  ExpectNotStarted();
+}
+
+TEST_F(OpenVPNManagementServerTest, StartGetSockNameFail) {
+  const int kSocket = 123;
+  EXPECT_CALL(sockets_, Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))
+      .WillOnce(Return(kSocket));
+  EXPECT_CALL(sockets_, Bind(kSocket, _, _)).WillOnce(Return(0));
+  EXPECT_CALL(sockets_, Listen(kSocket, 1)).WillOnce(Return(0));
+  EXPECT_CALL(sockets_, GetSockName(kSocket, _, _)).WillOnce(Return(-1));
+  EXPECT_CALL(sockets_, Close(kSocket)).WillOnce(Return(0));
+  EXPECT_FALSE(server_.Start(NULL, &sockets_));
+  ExpectNotStarted();
+}
+
 TEST_F(OpenVPNManagementServerTest, Start) {
-  // TODO(petkov): Add unit tests.
+  const int kSocket = 123;
+  EXPECT_CALL(sockets_, Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))
+      .WillOnce(Return(kSocket));
+  EXPECT_CALL(sockets_, Bind(kSocket, _, _)).WillOnce(Return(0));
+  EXPECT_CALL(sockets_, Listen(kSocket, 1)).WillOnce(Return(0));
+  EXPECT_CALL(sockets_, GetSockName(kSocket, _, _)).WillOnce(Return(0));
+  EXPECT_CALL(dispatcher_,
+              CreateReadyHandler(kSocket, IOHandler::kModeInput,
+                                 CallbackEq(server_.ready_callback_)))
+      .WillOnce(ReturnNew<IOHandler>());
+  EXPECT_TRUE(server_.Start(&dispatcher_, &sockets_));
+  EXPECT_EQ(&sockets_, server_.sockets_);
+  EXPECT_EQ(kSocket, server_.socket_);
+  EXPECT_TRUE(server_.ready_handler_.get());
+  EXPECT_EQ(&dispatcher_, server_.dispatcher_);
+}
+
+TEST_F(OpenVPNManagementServerTest, Stop) {
+  SetSockets();
+  server_.input_handler_.reset(new IOHandler());
+  const int kConnectedSocket = 234;
+  server_.connected_socket_ = kConnectedSocket;
+  EXPECT_CALL(sockets_, Close(kConnectedSocket)).WillOnce(Return(0));
+  SetDispatcher();
+  server_.ready_handler_.reset(new IOHandler());
+  const int kSocket = 345;
+  server_.socket_ = kSocket;
+  EXPECT_CALL(sockets_, Close(kSocket)).WillOnce(Return(0));
+  server_.Stop();
+  EXPECT_FALSE(server_.input_handler_.get());
+  EXPECT_EQ(-1, server_.connected_socket_);
+  EXPECT_FALSE(server_.dispatcher_);
+  EXPECT_FALSE(server_.ready_handler_.get());
+  EXPECT_EQ(-1, server_.socket_);
+  ExpectNotStarted();
+}
+
+TEST_F(OpenVPNManagementServerTest, OnReadyAcceptFail) {
+  const int kSocket = 333;
+  SetSockets();
+  EXPECT_CALL(sockets_, Accept(kSocket, NULL, NULL)).WillOnce(Return(-1));
+  server_.OnReady(kSocket);
+  EXPECT_EQ(-1, server_.connected_socket_);
+}
+
+TEST_F(OpenVPNManagementServerTest, OnReady) {
+  const int kSocket = 111;
+  const int kConnectedSocket = 112;
+  SetSockets();
+  SetDispatcher();
+  EXPECT_CALL(sockets_, Accept(kSocket, NULL, NULL))
+      .WillOnce(Return(kConnectedSocket));
+  server_.ready_handler_.reset(new IOHandler());
+  EXPECT_CALL(dispatcher_,
+              CreateInputHandler(kConnectedSocket,
+                                 CallbackEq(server_.input_callback_)))
+      .WillOnce(ReturnNew<IOHandler>());
+  ExpectSend(kConnectedSocket, "state on\n");
+  server_.OnReady(kSocket);
+  EXPECT_EQ(kConnectedSocket, server_.connected_socket_);
+  EXPECT_FALSE(server_.ready_handler_.get());
+  EXPECT_TRUE(server_.input_handler_.get());
+}
+
+TEST_F(OpenVPNManagementServerTest, OnInput) {
+  {
+    string s;
+    InputData data = CreateInputDataFromString(s);
+    server_.OnInput(&data);
+  }
+  {
+    string s = "foo\n"
+        ">INFO:...\n"
+        ">STATE:123,RECONNECTING,detail,...,...";
+    InputData data = CreateInputDataFromString(s);
+    EXPECT_CALL(driver_, OnReconnecting());
+    server_.OnInput(&data);
+  }
+}
+
+TEST_F(OpenVPNManagementServerTest, ProcessMessage) {
+  server_.ProcessMessage("foo");
+  server_.ProcessMessage(">INFO:");
+
+  EXPECT_CALL(driver_, OnReconnecting());
+  server_.ProcessMessage(">STATE:123,RECONNECTING,detail,...,...");
+}
+
+TEST_F(OpenVPNManagementServerTest, ProcessInfoMessage) {
+  EXPECT_FALSE(server_.ProcessInfoMessage("foo"));
+  EXPECT_TRUE(server_.ProcessInfoMessage(">INFO:"));
+}
+
+TEST_F(OpenVPNManagementServerTest, ProcessStateMessage) {
+  EXPECT_FALSE(server_.ProcessStateMessage("foo"));
+  EXPECT_TRUE(server_.ProcessStateMessage(">STATE:123,WAIT,detail,...,..."));
+  EXPECT_CALL(driver_, OnReconnecting());
+  EXPECT_TRUE(
+      server_.ProcessStateMessage(">STATE:123,RECONNECTING,detail,...,..."));
+}
+
+TEST_F(OpenVPNManagementServerTest, Send) {
+  const int kSocket = 555;
+  const char kMessage[] = "foo\n";
+  server_.connected_socket_ = kSocket;
+  SetSockets();
+  ExpectSend(kSocket, kMessage);
+  server_.Send(kMessage);
+}
+
+TEST_F(OpenVPNManagementServerTest, SendState) {
+  const int kSocket = 555;
+  server_.connected_socket_ = kSocket;
+  SetSockets();
+  ExpectSend(kSocket, "state off\n");
+  server_.SendState("off");
 }
 
 }  // namespace shill