shill: Add SocketInfoReader class to read TCP socket information.

BUG=chromium:225907
TEST=Build and run unit tests.

Change-Id: I9b4f7258a0eca67d3e6260078ca010df148859e9
Reviewed-on: https://gerrit.chromium.org/gerrit/47300
Reviewed-by: Thieu Le <thieule@chromium.org>
Tested-by: Ben Chan <benchan@chromium.org>
Commit-Queue: Ben Chan <benchan@chromium.org>
diff --git a/Makefile b/Makefile
index 22b3dcb..388c982 100644
--- a/Makefile
+++ b/Makefile
@@ -284,6 +284,7 @@
 	shill_test_config.o \
 	shill_time.o \
 	socket_info.o \
+	socket_info_reader.o \
 	sockets.o \
 	static_ip_parameters.o \
 	supplicant_bss_proxy.o \
@@ -402,6 +403,7 @@
 	mock_routing_table.o \
 	mock_rtnl_handler.o \
 	mock_service.o \
+	mock_socket_info_reader.o \
 	mock_sockets.o \
 	mock_store.o \
 	mock_supplicant_bss_proxy.o \
@@ -445,6 +447,7 @@
 	shims/certificates_unittest.o \
 	shims/netfilter_queue_processor_unittest.o \
 	socket_info_unittest.o \
+	socket_info_reader_unittest.o \
 	static_ip_parameters_unittest.o \
 	technology_unittest.o \
 	traffic_monitor_unittest.o \
diff --git a/mock_socket_info_reader.cc b/mock_socket_info_reader.cc
new file mode 100644
index 0000000..23c3bfd
--- /dev/null
+++ b/mock_socket_info_reader.cc
@@ -0,0 +1,13 @@
+// Copyright (c) 2013 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/mock_socket_info_reader.h"
+
+namespace shill {
+
+MockSocketInfoReader::MockSocketInfoReader() {}
+
+MockSocketInfoReader::~MockSocketInfoReader() {}
+
+}  // namespace shill
diff --git a/mock_socket_info_reader.h b/mock_socket_info_reader.h
new file mode 100644
index 0000000..1ced570
--- /dev/null
+++ b/mock_socket_info_reader.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2013 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.
+
+#ifndef SHILL_MOCK_SOCKET_INFO_READER_H_
+#define SHILL_MOCK_SOCKET_INFO_READER_H_
+
+#include <vector>
+
+#include <base/basictypes.h>
+#include <gmock/gmock.h>
+
+#include "shill/socket_info_reader.h"
+
+namespace shill {
+
+class SocketInfo;
+
+class MockSocketInfoReader : public SocketInfoReader {
+ public:
+  MockSocketInfoReader();
+  virtual ~MockSocketInfoReader();
+
+  MOCK_METHOD1(LoadTcpSocketInfo, bool(std::vector<SocketInfo> *info_list));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockSocketInfoReader);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_MOCK_SOCKET_INFO_READER_H_
diff --git a/socket_info_reader.cc b/socket_info_reader.cc
new file mode 100644
index 0000000..26f7db7
--- /dev/null
+++ b/socket_info_reader.cc
@@ -0,0 +1,205 @@
+// Copyright (c) 2013 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/socket_info_reader.h"
+
+#include <algorithm>
+#include <limits>
+
+#include <base/file_path.h>
+#include <base/string_number_conversions.h>
+#include <base/string_split.h>
+
+#include "shill/file_reader.h"
+#include "shill/logging.h"
+
+using base::FilePath;
+using std::string;
+using std::vector;
+
+namespace shill {
+
+namespace {
+
+const char kTcpSocketInfoFilePath[] = "/proc/net/tcp";
+
+}  // namespace
+
+SocketInfoReader::SocketInfoReader() {}
+
+SocketInfoReader::~SocketInfoReader() {}
+
+bool SocketInfoReader::LoadTcpSocketInfo(vector<SocketInfo> *info_list) {
+  return LoadSocketInfo(FilePath(kTcpSocketInfoFilePath), info_list);
+}
+
+bool SocketInfoReader::LoadSocketInfo(const FilePath &info_file_path,
+                                      vector<SocketInfo> *info_list) {
+  FileReader file_reader;
+  if (!file_reader.Open(info_file_path)) {
+    SLOG(Link, 2) << __func__ << ": Failed to open '"
+                  << info_file_path.value() << "'.";
+    return false;
+  }
+
+  info_list->clear();
+  string line;
+  while (file_reader.ReadLine(&line)) {
+    SocketInfo socket_info;
+    if (ParseSocketInfo(line, &socket_info))
+      info_list->push_back(socket_info);
+  }
+  return true;
+}
+
+bool SocketInfoReader::ParseSocketInfo(const string &input,
+                                       SocketInfo *socket_info) {
+  vector<string> tokens;
+  base::SplitStringAlongWhitespace(input, &tokens);
+  if (tokens.size() < 10) {
+    return false;
+  }
+
+  IPAddress ip_address(IPAddress::kFamilyUnknown);
+  uint16 port = 0;
+
+  if (!ParseIPAddressAndPort(tokens[1], &ip_address, &port)) {
+    return false;
+  }
+  socket_info->set_local_ip_address(ip_address);
+  socket_info->set_local_port(port);
+
+  if (!ParseIPAddressAndPort(tokens[2], &ip_address, &port)) {
+    return false;
+  }
+  socket_info->set_remote_ip_address(ip_address);
+  socket_info->set_remote_port(port);
+
+  SocketInfo::ConnectionState connection_state =
+      SocketInfo::kConnectionStateUnknown;
+  if (!ParseConnectionState(tokens[3], &connection_state)) {
+    return false;
+  }
+  socket_info->set_connection_state(connection_state);
+
+  uint64 transmit_queue_value = 0, receive_queue_value = 0;
+  if (!ParseTransimitAndReceiveQueueValues(
+      tokens[4], &transmit_queue_value, &receive_queue_value)) {
+    return false;
+  }
+  socket_info->set_transmit_queue_value(transmit_queue_value);
+  socket_info->set_receive_queue_value(receive_queue_value);
+
+  SocketInfo::TimerState timer_state = SocketInfo::kTimerStateUnknown;
+  if (!ParseTimerState(tokens[5], &timer_state)) {
+    return false;
+  }
+  socket_info->set_timer_state(timer_state);
+
+  return true;
+}
+
+bool SocketInfoReader::ParseIPAddressAndPort(
+    const string &input, IPAddress *ip_address, uint16 *port) {
+  vector<string> tokens;
+
+  base::SplitString(input, ':', &tokens);
+  if (tokens.size() != 2 ||
+      !ParseIPAddress(tokens[0], ip_address) ||
+      !ParsePort(tokens[1], port)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool SocketInfoReader::ParseIPAddress(const string &input,
+                                      IPAddress *ip_address) {
+  vector<uint8> bytes;
+  if (!base::HexStringToBytes(input, &bytes))
+    return false;
+
+  IPAddress::Family family;
+  if (bytes.size() == 4)
+    family = IPAddress::kFamilyIPv4;
+  else if (bytes.size() == 6)
+    family = IPAddress::kFamilyIPv6;
+  else
+    return false;
+
+  // TODO(benchan): This doesn't work with IPv6 addresses. Fix it
+  // by introducing a proper method in ByteString to handle byte order
+  // conversion.
+  std::reverse(bytes.begin(), bytes.end());
+
+  *ip_address = IPAddress(family, ByteString(&bytes[0], bytes.size()));
+  return true;
+}
+
+bool SocketInfoReader::ParsePort(const string &input, uint16 *port) {
+  int result = 0;
+
+  if (input.size() != 4 || !base::HexStringToInt(input, &result) ||
+      result < 0 || result > std::numeric_limits<uint16>::max()) {
+    return false;
+  }
+
+  *port = result;
+  return true;
+}
+
+bool SocketInfoReader::ParseTransimitAndReceiveQueueValues(
+    const string &input,
+    uint64 *transmit_queue_value, uint64 *receive_queue_value) {
+  vector<string> tokens;
+  int64 signed_transmit_queue_value = 0, signed_receive_queue_value = 0;
+
+  base::SplitString(input, ':', &tokens);
+  if (tokens.size() != 2 ||
+      !base::HexStringToInt64(tokens[0], &signed_transmit_queue_value) ||
+      !base::HexStringToInt64(tokens[1], &signed_receive_queue_value)) {
+    return false;
+  }
+
+  *transmit_queue_value = static_cast<uint64>(signed_transmit_queue_value);
+  *receive_queue_value = static_cast<uint64>(signed_receive_queue_value);
+  return true;
+}
+
+bool SocketInfoReader::ParseConnectionState(
+    const string &input, SocketInfo::ConnectionState *connection_state) {
+  int result = 0;
+
+  if (input.size() != 2 || !base::HexStringToInt(input, &result)) {
+    return false;
+  }
+
+  if (result > 0 && result < SocketInfo::kConnectionStateMax) {
+    *connection_state = static_cast<SocketInfo::ConnectionState>(result);
+  } else {
+    *connection_state = SocketInfo::kConnectionStateUnknown;
+  }
+  return true;
+}
+
+bool SocketInfoReader::ParseTimerState(
+    const string &input, SocketInfo::TimerState *timer_state) {
+  vector<string> tokens;
+  int result = 0;
+
+  base::SplitString(input, ':', &tokens);
+  if (tokens.size() != 2 || tokens[0].size() != 2 ||
+      !base::HexStringToInt(tokens[0], &result)) {
+    return false;
+  }
+
+  if (result < SocketInfo::kTimerStateMax) {
+    *timer_state = static_cast<SocketInfo::TimerState>(result);
+  } else {
+    *timer_state = SocketInfo::kTimerStateUnknown;
+  }
+  return true;
+}
+
+}  // namespace shill
diff --git a/socket_info_reader.h b/socket_info_reader.h
new file mode 100644
index 0000000..af32525
--- /dev/null
+++ b/socket_info_reader.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2013 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.
+
+#ifndef SHILL_SOCKET_INFO_READER_H_
+#define SHILL_SOCKET_INFO_READER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/basictypes.h>
+#include <gtest/gtest_prod.h>
+
+#include "shill/socket_info.h"
+
+namespace base {
+
+class FilePath;
+
+}  // namespace base
+
+namespace shill {
+
+class SocketInfoReader {
+ public:
+  SocketInfoReader();
+  virtual ~SocketInfoReader();
+
+  virtual bool LoadTcpSocketInfo(std::vector<SocketInfo> *info_list);
+
+ private:
+  FRIEND_TEST(SocketInfoReaderTest, LoadSocketInfo);
+  FRIEND_TEST(SocketInfoReaderTest, ParseConnectionState);
+  FRIEND_TEST(SocketInfoReaderTest, ParseIPAddress);
+  FRIEND_TEST(SocketInfoReaderTest, ParseIPAddressAndPort);
+  FRIEND_TEST(SocketInfoReaderTest, ParsePort);
+  FRIEND_TEST(SocketInfoReaderTest, ParseSocketInfo);
+  FRIEND_TEST(SocketInfoReaderTest, ParseTimerState);
+  FRIEND_TEST(SocketInfoReaderTest, ParseTransimitAndReceiveQueueValues);
+
+  bool LoadSocketInfo(const base::FilePath &info_file_path,
+                      std::vector<SocketInfo> *info_list);
+  bool ParseSocketInfo(const std::string &input, SocketInfo *socket_info);
+  bool ParseIPAddressAndPort(
+      const std::string &input, IPAddress *ip_address, uint16 *port);
+  bool ParseIPAddress(const std::string &input, IPAddress *ip_address);
+  bool ParsePort(const std::string &input, uint16 *port);
+  bool ParseTransimitAndReceiveQueueValues(
+      const std::string &input,
+      uint64 *transmit_queue_value, uint64 *receive_queue_value);
+  bool ParseConnectionState(const std::string &input,
+                            SocketInfo::ConnectionState *connection_state);
+  bool ParseTimerState(const std::string &input,
+                       SocketInfo::TimerState *timer_state);
+
+  DISALLOW_COPY_AND_ASSIGN(SocketInfoReader);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_SOCKET_INFO_READER_H_
diff --git a/socket_info_reader_unittest.cc b/socket_info_reader_unittest.cc
new file mode 100644
index 0000000..1fda67b
--- /dev/null
+++ b/socket_info_reader_unittest.cc
@@ -0,0 +1,278 @@
+// Copyright (c) 2013 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/socket_info_reader.h"
+
+#include <base/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/stringprintf.h>
+#include <gtest/gtest.h>
+
+using base::FilePath;
+using base::ScopedTempDir;
+using std::string;
+using std::vector;
+
+// TODO(benchan): Test IPv6 addresses.
+
+namespace shill {
+
+namespace {
+
+const char kIPAddress_0_0_0_0[] = "0.0.0.0";
+const char kIPAddress_127_0_0_1[] = "127.0.0.1";
+const char kIPAddress_192_168_1_10[] = "192.168.1.10";
+const char kIPAddress_255_255_255_255[] = "255.255.255.255";
+const char *kSocketInfoLines[] = {
+    "  sl  local_address rem_address   st tx_queue rx_queue tr tm->when "
+    "retrnsmt   uid  timeout inode                                      ",
+    "   0: 0100007F:0019 00000000:0000 0A 0000000A:00000005 00:00000000 "
+    "00000000     0        0 36948 1 0000000000000000 100 0 0 10 -1     ",
+    "   1: 0A01A8C0:0050 0100007F:03FC 01 00000000:00000000 00:00000000 "
+    "00000000 65534        0 2787034 1 0000000000000000 100 0 0 10 -1   ",
+};
+
+}  // namespace
+
+class SocketInfoReaderTest : public testing::Test {
+ protected:
+  IPAddress StringToIPv4Address(const string &address_string) {
+    IPAddress ip_address(IPAddress::kFamilyIPv4);
+    EXPECT_TRUE(ip_address.SetAddressFromString(address_string));
+    return ip_address;
+  }
+
+  void CreateSocketInfoFile(const char **lines, size_t num_lines,
+                            const FilePath &dir_path, FilePath *file_path) {
+    ASSERT_TRUE(file_util::CreateTemporaryFileInDir(dir_path, file_path));
+    for (size_t i = 0; i < num_lines; ++i) {
+      string line = lines[i];
+      line += '\n';
+      ASSERT_EQ(line.size(),
+                file_util::AppendToFile(*file_path, line.data(), line.size()));
+    }
+  }
+
+  void ExpectSocketInfoEqual(const SocketInfo &info1, const SocketInfo &info2) {
+    EXPECT_EQ(info1.connection_state(), info2.connection_state());
+    EXPECT_TRUE(info1.local_ip_address().Equals(info2.local_ip_address()));
+    EXPECT_EQ(info1.local_port(), info2.local_port());
+    EXPECT_TRUE(info1.remote_ip_address().Equals(info2.remote_ip_address()));
+    EXPECT_EQ(info1.remote_port(), info2.remote_port());
+    EXPECT_EQ(info1.transmit_queue_value(), info2.transmit_queue_value());
+    EXPECT_EQ(info1.receive_queue_value(), info2.receive_queue_value());
+    EXPECT_EQ(info1.timer_state(), info2.timer_state());
+  }
+
+  SocketInfoReader reader_;
+};
+
+TEST_F(SocketInfoReaderTest, LoadSocketInfo) {
+  FilePath file_path("/non-existent-file");
+  vector<SocketInfo> info_list;
+
+  EXPECT_FALSE(reader_.LoadSocketInfo(file_path, &info_list));
+  EXPECT_TRUE(info_list.empty());
+
+  ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  CreateSocketInfoFile(kSocketInfoLines, 1, temp_dir.path(), &file_path);
+  EXPECT_TRUE(reader_.LoadSocketInfo(file_path, &info_list));
+  EXPECT_TRUE(info_list.empty());
+
+  CreateSocketInfoFile(kSocketInfoLines, arraysize(kSocketInfoLines),
+                       temp_dir.path(), &file_path);
+  EXPECT_TRUE(reader_.LoadSocketInfo(file_path, &info_list));
+  EXPECT_EQ(arraysize(kSocketInfoLines) - 1, info_list.size());
+  ExpectSocketInfoEqual(SocketInfo(SocketInfo::kConnectionStateListen,
+                                   StringToIPv4Address(kIPAddress_127_0_0_1),
+                                   25,
+                                   StringToIPv4Address(kIPAddress_0_0_0_0),
+                                   0,
+                                   10,
+                                   5,
+                                   SocketInfo::kTimerStateNoTimerPending),
+                        info_list[0]);
+  ExpectSocketInfoEqual(SocketInfo(SocketInfo::kConnectionStateEstablished,
+                                   StringToIPv4Address(kIPAddress_192_168_1_10),
+                                   80,
+                                   StringToIPv4Address(kIPAddress_127_0_0_1),
+                                   1020,
+                                   0,
+                                   0,
+                                   SocketInfo::kTimerStateNoTimerPending),
+                        info_list[1]);
+}
+
+TEST_F(SocketInfoReaderTest, ParseSocketInfo) {
+  SocketInfo info;
+
+  EXPECT_FALSE(reader_.ParseSocketInfo("", &info));
+  EXPECT_FALSE(reader_.ParseSocketInfo(kSocketInfoLines[0], &info));
+
+  EXPECT_TRUE(reader_.ParseSocketInfo(kSocketInfoLines[1], &info));
+  ExpectSocketInfoEqual(SocketInfo(SocketInfo::kConnectionStateListen,
+                                   StringToIPv4Address(kIPAddress_127_0_0_1),
+                                   25,
+                                   StringToIPv4Address(kIPAddress_0_0_0_0),
+                                   0,
+                                   10,
+                                   5,
+                                   SocketInfo::kTimerStateNoTimerPending),
+                        info);
+}
+
+TEST_F(SocketInfoReaderTest, ParseIPAddressAndPort) {
+  IPAddress ip_address(IPAddress::kFamilyUnknown);
+  uint16 port = 0;
+
+  EXPECT_FALSE(reader_.ParseIPAddressAndPort("", &ip_address, &port));
+  EXPECT_FALSE(reader_.ParseIPAddressAndPort("00000000", &ip_address, &port));
+  EXPECT_FALSE(reader_.ParseIPAddressAndPort("00000000:", &ip_address, &port));
+  EXPECT_FALSE(reader_.ParseIPAddressAndPort(":0000", &ip_address, &port));
+  EXPECT_FALSE(reader_.ParseIPAddressAndPort("0000000Y:0000",
+                                             &ip_address, &port));
+  EXPECT_FALSE(reader_.ParseIPAddressAndPort("00000000:000Y", &ip_address,
+                                             &port));
+
+  EXPECT_TRUE(reader_.ParseIPAddressAndPort("0a01A8c0:0050",
+                                            &ip_address, &port));
+  EXPECT_TRUE(ip_address.Equals(StringToIPv4Address(kIPAddress_192_168_1_10)));
+  EXPECT_EQ(80, port);
+}
+
+TEST_F(SocketInfoReaderTest, ParseIPAddress) {
+  IPAddress ip_address(IPAddress::kFamilyUnknown);
+
+  EXPECT_FALSE(reader_.ParseIPAddress("", &ip_address));
+  EXPECT_FALSE(reader_.ParseIPAddress("0", &ip_address));
+  EXPECT_FALSE(reader_.ParseIPAddress("00", &ip_address));
+  EXPECT_FALSE(reader_.ParseIPAddress("0000000Y", &ip_address));
+
+  EXPECT_TRUE(reader_.ParseIPAddress("00000000", &ip_address));
+  EXPECT_TRUE(ip_address.Equals(StringToIPv4Address(kIPAddress_0_0_0_0)));
+
+  EXPECT_TRUE(reader_.ParseIPAddress("0100007F", &ip_address));
+  EXPECT_TRUE(ip_address.Equals(StringToIPv4Address(kIPAddress_127_0_0_1)));
+
+  EXPECT_TRUE(reader_.ParseIPAddress("0a01A8c0", &ip_address));
+  EXPECT_TRUE(ip_address.Equals(StringToIPv4Address(kIPAddress_192_168_1_10)));
+
+  EXPECT_TRUE(reader_.ParseIPAddress("ffffffff", &ip_address));
+  EXPECT_TRUE(ip_address.Equals(
+      StringToIPv4Address(kIPAddress_255_255_255_255)));
+}
+
+TEST_F(SocketInfoReaderTest, ParsePort) {
+  uint16 port = 0;
+
+  EXPECT_FALSE(reader_.ParsePort("", &port));
+  EXPECT_FALSE(reader_.ParsePort("0", &port));
+  EXPECT_FALSE(reader_.ParsePort("00", &port));
+  EXPECT_FALSE(reader_.ParsePort("000", &port));
+  EXPECT_FALSE(reader_.ParsePort("000Y", &port));
+
+  EXPECT_TRUE(reader_.ParsePort("0000", &port));
+  EXPECT_EQ(0, port);
+
+  EXPECT_TRUE(reader_.ParsePort("0050", &port));
+  EXPECT_EQ(80, port);
+
+  EXPECT_TRUE(reader_.ParsePort("abCD", &port));
+  EXPECT_EQ(43981, port);
+
+  EXPECT_TRUE(reader_.ParsePort("ffff", &port));
+  EXPECT_EQ(65535, port);
+}
+
+TEST_F(SocketInfoReaderTest, ParseTransimitAndReceiveQueueValues) {
+  uint64 transmit_queue_value = 0, receive_queue_value = 0;
+
+  EXPECT_FALSE(reader_.ParseTransimitAndReceiveQueueValues(
+      "", &transmit_queue_value, &receive_queue_value));
+  EXPECT_FALSE(reader_.ParseTransimitAndReceiveQueueValues(
+      "00000000", &transmit_queue_value, &receive_queue_value));
+  EXPECT_FALSE(reader_.ParseTransimitAndReceiveQueueValues(
+      "00000000:", &transmit_queue_value, &receive_queue_value));
+  EXPECT_FALSE(reader_.ParseTransimitAndReceiveQueueValues(
+      ":00000000", &transmit_queue_value, &receive_queue_value));
+  EXPECT_FALSE(reader_.ParseTransimitAndReceiveQueueValues(
+      "0000000Y:00000000", &transmit_queue_value, &receive_queue_value));
+  EXPECT_FALSE(reader_.ParseTransimitAndReceiveQueueValues(
+      "00000000:0000000Y", &transmit_queue_value, &receive_queue_value));
+
+  EXPECT_TRUE(reader_.ParseTransimitAndReceiveQueueValues(
+      "00000001:FFFFFFFF", &transmit_queue_value, &receive_queue_value));
+  EXPECT_EQ(1, transmit_queue_value);
+  EXPECT_EQ(0xffffffff, receive_queue_value);
+}
+
+TEST_F(SocketInfoReaderTest, ParseConnectionState) {
+  SocketInfo::ConnectionState connection_state =
+      SocketInfo::kConnectionStateUnknown;
+
+  EXPECT_FALSE(reader_.ParseConnectionState("", &connection_state));
+  EXPECT_FALSE(reader_.ParseConnectionState("0", &connection_state));
+  EXPECT_FALSE(reader_.ParseConnectionState("X", &connection_state));
+
+  EXPECT_TRUE(reader_.ParseConnectionState("00", &connection_state));
+  EXPECT_EQ(SocketInfo::kConnectionStateUnknown, connection_state);
+  EXPECT_TRUE(reader_.ParseConnectionState("01", &connection_state));
+  EXPECT_EQ(SocketInfo::kConnectionStateEstablished, connection_state);
+  EXPECT_TRUE(reader_.ParseConnectionState("02", &connection_state));
+  EXPECT_EQ(SocketInfo::kConnectionStateSynSent, connection_state);
+  EXPECT_TRUE(reader_.ParseConnectionState("03", &connection_state));
+  EXPECT_EQ(SocketInfo::kConnectionStateSynRecv, connection_state);
+  EXPECT_TRUE(reader_.ParseConnectionState("04", &connection_state));
+  EXPECT_EQ(SocketInfo::kConnectionStateFinWait1, connection_state);
+  EXPECT_TRUE(reader_.ParseConnectionState("05", &connection_state));
+  EXPECT_EQ(SocketInfo::kConnectionStateFinWait2, connection_state);
+  EXPECT_TRUE(reader_.ParseConnectionState("06", &connection_state));
+  EXPECT_EQ(SocketInfo::kConnectionStateTimeWait, connection_state);
+  EXPECT_TRUE(reader_.ParseConnectionState("07", &connection_state));
+  EXPECT_EQ(SocketInfo::kConnectionStateClose, connection_state);
+  EXPECT_TRUE(reader_.ParseConnectionState("08", &connection_state));
+  EXPECT_EQ(SocketInfo::kConnectionStateCloseWait, connection_state);
+  EXPECT_TRUE(reader_.ParseConnectionState("09", &connection_state));
+  EXPECT_EQ(SocketInfo::kConnectionStateLastAck, connection_state);
+  EXPECT_TRUE(reader_.ParseConnectionState("0A", &connection_state));
+  EXPECT_EQ(SocketInfo::kConnectionStateListen, connection_state);
+  EXPECT_TRUE(reader_.ParseConnectionState("0B", &connection_state));
+  EXPECT_EQ(SocketInfo::kConnectionStateClosing, connection_state);
+
+  for (int i = SocketInfo::kConnectionStateMax; i < 256; ++i) {
+    EXPECT_TRUE(reader_.ParseConnectionState(
+        base::StringPrintf("%02X", i), &connection_state));
+    EXPECT_EQ(SocketInfo::kConnectionStateUnknown, connection_state);
+  }
+}
+
+TEST_F(SocketInfoReaderTest, ParseTimerState) {
+  SocketInfo::TimerState timer_state = SocketInfo::kTimerStateUnknown;
+
+  EXPECT_FALSE(reader_.ParseTimerState("", &timer_state));
+  EXPECT_FALSE(reader_.ParseTimerState("0", &timer_state));
+  EXPECT_FALSE(reader_.ParseTimerState("X", &timer_state));
+  EXPECT_FALSE(reader_.ParseTimerState("00", &timer_state));
+
+  EXPECT_TRUE(reader_.ParseTimerState("00:00000000", &timer_state));
+  EXPECT_EQ(SocketInfo::kTimerStateNoTimerPending, timer_state);
+  EXPECT_TRUE(reader_.ParseTimerState("01:00000000", &timer_state));
+  EXPECT_EQ(SocketInfo::kTimerStateRetransmitTimerPending, timer_state);
+  EXPECT_TRUE(reader_.ParseTimerState("02:00000000", &timer_state));
+  EXPECT_EQ(SocketInfo::kTimerStateAnotherTimerPending, timer_state);
+  EXPECT_TRUE(reader_.ParseTimerState("03:00000000", &timer_state));
+  EXPECT_EQ(SocketInfo::kTimerStateInTimeWaitState, timer_state);
+  EXPECT_TRUE(reader_.ParseTimerState("04:00000000", &timer_state));
+  EXPECT_EQ(SocketInfo::kTimerStateZeroWindowProbeTimerPending, timer_state);
+
+  for (int i = SocketInfo::kTimerStateMax; i < 256; ++i) {
+    EXPECT_TRUE(reader_.ParseTimerState(
+        base::StringPrintf("%02X:00000000", i), &timer_state));
+    EXPECT_EQ(SocketInfo::kTimerStateUnknown, timer_state);
+  }
+}
+
+}  // namespace shill