shill: LinkMonitor: Add ArpPacket code

Add ArpPacket class to parse and compose ARP packets.

BUG=chromium-os:32600
TEST=New unit tests

Change-Id: Icac65cc16e059f07109fc695ba9046cafa0b042c
Reviewed-on: https://gerrit.chromium.org/gerrit/28096
Commit-Ready: Paul Stewart <pstew@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/Makefile b/Makefile
index dfff00a..594fe8d 100644
--- a/Makefile
+++ b/Makefile
@@ -105,6 +105,7 @@
 	$(addprefix $(BUILD_DBUS_BINDINGS_DIR)/, $(DBUS_PROXY_HEADERS))
 
 SHILL_OBJS = $(addprefix $(BUILDDIR)/, \
+	arp_packet.o \
 	async_connection.o \
 	byte_string.o \
 	cellular.o \
@@ -231,6 +232,7 @@
 
 TEST_BIN = shill_unittest
 TEST_OBJS = $(addprefix $(BUILDDIR)/, \
+	arp_packet_unittest.o \
 	async_connection_unittest.o \
 	byte_string_unittest.o \
 	cellular_capability_cdma_unittest.o \
diff --git a/arp_packet.cc b/arp_packet.cc
new file mode 100644
index 0000000..5c3998b
--- /dev/null
+++ b/arp_packet.cc
@@ -0,0 +1,163 @@
+// Copyright (c) 2012 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/arp_packet.h"
+
+#include <net/ethernet.h>
+#include <net/if_arp.h>
+#include <netinet/in.h>
+#include <string.h>
+
+#include <base/logging.h>
+
+namespace shill {
+
+ArpPacket::ArpPacket()
+    : local_ip_address_(IPAddress::kFamilyUnknown),
+      remote_ip_address_(IPAddress::kFamilyUnknown) {}
+
+ArpPacket::ArpPacket(
+    const IPAddress &local_ip, const IPAddress &remote_ip,
+    const ByteString &local_mac, const ByteString &remote_mac)
+    : local_ip_address_(local_ip),
+      remote_ip_address_(remote_ip),
+      local_mac_address_(local_mac),
+      remote_mac_address_(remote_mac) {}
+
+ArpPacket::~ArpPacket() {}
+
+// Format of an ARP packet (all multi-byte values are big-endian):
+//
+//       Byte 0            Byte 1           Byte 2             Byte 3
+// +-----------------+-----------------+-----------------+-----------------+
+// | Format of hardware address (ether)| Format of Protocol Address (IP)   |
+// +-----------------+-----------------+-----------------------------------+
+// | Hardware Length | Protocol Length |       ARP Protocol OpCode         |
+// +-----------------+-----------------+-----------------------------------+
+//
+// plus a variable length section...
+//
+// +-----------------------------------------------------------------------+
+// | Sender Hardware Address (of length "Hardware Length")...              |
+// +-----------------------------------------------------------------------+
+// | Sender IP Address (of length "Protocol Length")...                    |
+// +-----------------------------------------------------------------------+
+// | Target Hardware Address (of length "Hardware Length")...              |
+// +-----------------------------------------------------------------------+
+// | Target IP Address (of length "Protocol Length")...                    |
+// +-----------------------------------------------------------------------+
+bool ArpPacket::ParseReply(const ByteString &packet) {
+  arphdr header;
+  if (packet.GetLength() < sizeof(header)) {
+    LOG(ERROR) << "Packet size " << packet.GetLength()
+               << " is too short to contain ARP header.";
+    return false;
+  }
+
+  memcpy(&header, packet.GetConstData(), sizeof(header));
+
+  const uint16 hardware_type = ntohs(header.ar_hrd);
+  if (hardware_type != ARPHRD_ETHER) {
+    NOTIMPLEMENTED() << "Packet is of unknown ARPHRD type "
+                     << hardware_type;
+    return false;
+  }
+  const uint16 protocol = ntohs(header.ar_pro);
+  IPAddress::Family family = IPAddress::kFamilyUnknown;
+  if (protocol == ETHERTYPE_IP) {
+    family = IPAddress::kFamilyIPv4;
+  } else if (protocol == ETHERTYPE_IPV6) {
+    family = IPAddress::kFamilyIPv6;
+  } else {
+    NOTIMPLEMENTED() << "Packet has unknown protocol "
+                     << protocol;
+    return false;
+  }
+  if (header.ar_hln != ETH_ALEN) {
+    LOG(ERROR) << "Packet has unexpected hardware address length "
+               << static_cast<int>(header.ar_hln) << "; expected " << ETH_ALEN;
+    return false;
+  }
+  size_t ip_address_length = IPAddress::GetAddressLength(family);
+  if (header.ar_pln != ip_address_length) {
+    LOG(ERROR) << "Packet has unexpected protocol address length "
+               << static_cast<int>(header.ar_hln) << "; expected "
+               << ip_address_length;
+    return false;
+  }
+  const uint16 operation = ntohs(header.ar_op);
+  if (operation != ARPOP_REPLY) {
+    NOTIMPLEMENTED() << "Packet is not an ARP reply but of type "
+                     << operation;
+    return false;
+  }
+  size_t min_packet_size =
+      sizeof(header) + 2 * ip_address_length + 2 * ETH_ALEN;
+  if (packet.GetLength() < min_packet_size) {
+    NOTIMPLEMENTED() << "Packet of size "
+                     << packet.GetLength()
+                     << " is too small to contain entire ARP payload; "
+                     << "expected at least "
+                     << min_packet_size;
+    return false;
+  }
+  local_mac_address_ = packet.GetSubstring(sizeof(header), ETH_ALEN);
+  local_ip_address_ = IPAddress(family, packet.GetSubstring(
+      sizeof(header) + ETH_ALEN, ip_address_length));
+  remote_mac_address_ = packet.GetSubstring(
+      sizeof(header) + ETH_ALEN + ip_address_length, ETH_ALEN);
+  remote_ip_address_ = IPAddress(family, packet.GetSubstring(
+      sizeof(header) + ETH_ALEN * 2 + ip_address_length, ip_address_length));
+  return true;
+}
+
+// Output a payload from local parameters.
+bool ArpPacket::FormatRequest(ByteString *packet) const {
+  if (!local_ip_address_.IsValid() || !remote_ip_address_.IsValid()) {
+    LOG(ERROR) << "Local or remote IP address is not valid.";
+    return false;
+  }
+  if (local_ip_address_.family() != remote_ip_address_.family()) {
+    LOG(ERROR) << "Local and remote IP address families do not match!";
+    return false;
+  }
+  uint16 protocol;
+  IPAddress::Family family = local_ip_address_.family();
+  if (family == IPAddress::kFamilyIPv4) {
+    protocol = ETHERTYPE_IP;
+  } else if (family == IPAddress::kFamilyIPv6) {
+    protocol = ETHERTYPE_IPV6;
+  } else {
+    NOTIMPLEMENTED() << "Address family "
+                     << IPAddress::GetAddressFamilyName(family)
+                     << " is not supported.";
+    return false;
+  }
+  size_t ip_address_length = IPAddress::GetAddressLength(family);
+  CHECK(ip_address_length < kuint8max);
+  if (local_mac_address_.GetLength() != ETH_ALEN ||
+      remote_mac_address_.GetLength() != ETH_ALEN) {
+    LOG(ERROR) << "Local or remote MAC address length is incorrect.";
+    return false;
+  }
+
+  arphdr header;
+  header.ar_hrd = htons(ARPHRD_ETHER);
+  header.ar_pro = htons(protocol);
+  header.ar_hln = ETH_ALEN;
+  header.ar_pln = ip_address_length;
+  header.ar_op = htons(ARPOP_REQUEST);
+
+  *packet = ByteString(reinterpret_cast<const unsigned char *>(&header),
+                       sizeof(header));
+
+  packet->Append(local_mac_address_);
+  packet->Append(local_ip_address_.address());
+  packet->Append(remote_mac_address_);
+  packet->Append(remote_ip_address_.address());
+
+  return true;
+}
+
+}  // namespace shill
diff --git a/arp_packet.h b/arp_packet.h
new file mode 100644
index 0000000..58e4bf1
--- /dev/null
+++ b/arp_packet.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 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_ARP_PACKET_H_
+#define SHILL_ARP_PACKET_H_
+
+#include "shill/byte_string.h"
+#include "shill/ip_address.h"
+
+namespace shill {
+
+// ArpPacket encapsulates the task of creating and parsing
+// Address Resolution Protocol (ARP) packets for IP and
+// IPv6 protocols on Ethernet (or Ethernet-like) networks.
+class ArpPacket {
+ public:
+  ArpPacket();
+  ArpPacket(const IPAddress &local_ip, const IPAddress &remote_ip,
+            const ByteString &local_mac, const ByteString &remote_mac);
+  virtual ~ArpPacket();
+
+  // Parse a payload and save to local parameters.
+  bool ParseReply(const ByteString &packet);
+
+  // Output a payload from local parameters.
+  bool FormatRequest(ByteString *packet) const;
+
+  // Getters and seters.
+  const IPAddress &local_ip_address() const { return local_ip_address_; }
+  void set_local_ip_address(const IPAddress &address) {
+    local_ip_address_ = address;
+  }
+
+  const IPAddress &remote_ip_address() const { return remote_ip_address_; }
+  void set_remote_ip_address(const IPAddress &address) {
+    remote_ip_address_ = address;
+  }
+
+  const ByteString &local_mac_address() const { return local_mac_address_; }
+  void set_local_mac_address(const ByteString &address) {
+    local_mac_address_ = address;
+  }
+
+  const ByteString &remote_mac_address() const { return remote_mac_address_; }
+  void set_remote_mac_address(const ByteString &address) {
+    remote_mac_address_ = address;
+  }
+
+ private:
+  friend class ArpPacketTest;
+
+  IPAddress local_ip_address_;
+  IPAddress remote_ip_address_;
+  ByteString local_mac_address_;
+  ByteString remote_mac_address_;
+
+  DISALLOW_COPY_AND_ASSIGN(ArpPacket);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_ARP_PACKET_H_
diff --git a/arp_packet_unittest.cc b/arp_packet_unittest.cc
new file mode 100644
index 0000000..4cdaa76
--- /dev/null
+++ b/arp_packet_unittest.cc
@@ -0,0 +1,267 @@
+// Copyright (c) 2012 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/arp_packet.h"
+
+#include <gtest/gtest.h>
+
+#include "shill/mock_log.h"
+
+using testing::_;
+using testing::HasSubstr;
+using testing::Test;
+
+namespace shill {
+
+namespace {
+const uint8 kArpRequestV4[] =
+    { 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01 };
+const uint8 kArpRequestV6[] =
+    { 0x00, 0x01, 0x86, 0xdd, 0x06, 0x10, 0x00, 0x01 };
+const uint8 kArpReplyV4[] =
+    { 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x02 };
+const uint8 kArpReplyV6[] =
+    { 0x00, 0x01, 0x86, 0xdd, 0x06, 0x10, 0x00, 0x02 };
+const char kIPv4Address0[] = "192.168.0.1";
+const char kIPv4Address1[] = "10.0.12.13";
+const char kIPv6Address0[] = "fe80::1aa9:5ff:7ebf:14c5";
+const char kIPv6Address1[] = "1980:0:0:1000:1b02:1aa9:5ff:7ebf";
+const uint8 kMACAddress0[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+const uint8 kMACAddress1[] = { 0x88, 0x87, 0x86, 0x85, 0x84, 0x83 };
+const uint8 kMACBroadcast[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+const uint8 kInsertedByte[] = { 0x00 };
+}  // namespace {}
+
+class ArpPacketTest : public Test {
+ public:
+  ArpPacketTest()
+      : ipv4_address0_(IPAddress::kFamilyIPv4),
+        ipv4_address1_(IPAddress::kFamilyIPv4),
+        ipv6_address0_(IPAddress::kFamilyIPv6),
+        ipv6_address1_(IPAddress::kFamilyIPv6),
+        mac_address0_(kMACAddress0, arraysize(kMACAddress0)),
+        mac_address1_(kMACAddress1, arraysize(kMACAddress1)),
+        inserted_byte_(kInsertedByte, arraysize(kInsertedByte)) {}
+  virtual ~ArpPacketTest() {}
+
+  virtual void SetUp() {
+    EXPECT_TRUE(ipv4_address0_.SetAddressFromString(kIPv4Address0));
+    EXPECT_TRUE(ipv4_address1_.SetAddressFromString(kIPv4Address1));
+    EXPECT_TRUE(ipv6_address0_.SetAddressFromString(kIPv6Address0));
+    EXPECT_TRUE(ipv6_address1_.SetAddressFromString(kIPv6Address1));
+  }
+
+ protected:
+  IPAddress ipv4_address0_;
+  IPAddress ipv4_address1_;
+  IPAddress ipv6_address0_;
+  IPAddress ipv6_address1_;
+  ByteString mac_address0_;
+  ByteString mac_address1_;
+  ByteString inserted_byte_;
+  ArpPacket packet_;
+};
+
+TEST_F(ArpPacketTest, Constructor) {
+  EXPECT_FALSE(packet_.local_ip_address().IsValid());
+  EXPECT_FALSE(packet_.remote_ip_address().IsValid());
+  EXPECT_TRUE(packet_.local_mac_address().IsEmpty());
+  EXPECT_TRUE(packet_.remote_mac_address().IsEmpty());
+}
+
+TEST_F(ArpPacketTest, GettersAndSetters) {
+  packet_.set_local_ip_address(ipv4_address0_);
+  packet_.set_remote_ip_address(ipv6_address1_);
+  packet_.set_local_mac_address(mac_address0_);
+  packet_.set_remote_mac_address(mac_address1_);
+  EXPECT_TRUE(ipv4_address0_.Equals(packet_.local_ip_address()));
+  EXPECT_TRUE(ipv6_address1_.Equals(packet_.remote_ip_address()));
+  EXPECT_TRUE(mac_address0_.Equals(packet_.local_mac_address()));
+  EXPECT_TRUE(mac_address1_.Equals(packet_.remote_mac_address()));
+}
+
+TEST_F(ArpPacketTest, ParseReplyTinyPacket) {
+  ScopedMockLog log;
+  EXPECT_CALL(log,
+      Log(logging::LOG_ERROR, _,
+          HasSubstr("too short to contain ARP header."))).Times(1);
+
+  ByteString arp_bytes(kArpReplyV4, arraysize(kArpReplyV4));
+  arp_bytes.Resize(arp_bytes.GetLength() - 1);
+  EXPECT_FALSE(packet_.ParseReply(arp_bytes));
+}
+
+TEST_F(ArpPacketTest, ParseReplyBadHRDType) {
+  ScopedMockLog log;
+  EXPECT_CALL(log,
+      Log(logging::LOG_ERROR, _,
+          HasSubstr("Packet is of unknown ARPHRD type 257"))).Times(1);
+
+  ByteString arp_bytes(kArpReplyV4, arraysize(kArpReplyV4));
+  arp_bytes.GetData()[0] = 0x1;
+  EXPECT_FALSE(packet_.ParseReply(arp_bytes));
+}
+
+TEST_F(ArpPacketTest, ParseReplyBadProtocol) {
+  ScopedMockLog log;
+  EXPECT_CALL(log,
+      Log(logging::LOG_ERROR, _,
+          HasSubstr("Packet has unknown protocol 2049"))).Times(1);
+
+  ByteString arp_bytes(kArpReplyV4, arraysize(kArpReplyV4));
+  arp_bytes.GetData()[3] = 0x1;
+  EXPECT_FALSE(packet_.ParseReply(arp_bytes));
+}
+
+TEST_F(ArpPacketTest, ParseReplyBadHardwareLength) {
+  ScopedMockLog log;
+  EXPECT_CALL(log,
+      Log(logging::LOG_ERROR, _,
+          HasSubstr("Packet has unexpected hardware address length"))).Times(1);
+
+  ByteString arp_bytes(kArpReplyV4, arraysize(kArpReplyV4));
+  arp_bytes.GetData()[4] = 0x1;
+  EXPECT_FALSE(packet_.ParseReply(arp_bytes));
+}
+
+TEST_F(ArpPacketTest, ParseReplyBadProtocolLength) {
+  ScopedMockLog log;
+  EXPECT_CALL(log,
+      Log(logging::LOG_ERROR, _,
+          HasSubstr("Packet has unexpected protocol address length"))).Times(1);
+
+  ByteString arp_bytes(kArpReplyV4, arraysize(kArpReplyV4));
+  arp_bytes.GetData()[5] = 0x1;
+  EXPECT_FALSE(packet_.ParseReply(arp_bytes));
+}
+
+TEST_F(ArpPacketTest, ParseReplyBadOpCode) {
+  ScopedMockLog log;
+  EXPECT_CALL(log,
+      Log(logging::LOG_ERROR, _,
+          HasSubstr("Packet is not an ARP reply but of type 258"))).Times(1);
+
+  ByteString arp_bytes(kArpReplyV4, arraysize(kArpReplyV4));
+  arp_bytes.GetData()[6] = 0x1;
+  EXPECT_FALSE(packet_.ParseReply(arp_bytes));
+}
+
+TEST_F(ArpPacketTest, ParseReplyShortPacket) {
+  ScopedMockLog log;
+  EXPECT_CALL(log,
+      Log(logging::LOG_ERROR, _,
+          HasSubstr("is too small to contain entire ARP payload"))).Times(1);
+
+  ByteString arp_bytes(kArpReplyV6, arraysize(kArpReplyV6));
+  arp_bytes.Append(mac_address1_);
+  arp_bytes.Append(ipv6_address0_.address());
+  arp_bytes.Append(mac_address0_);
+  arp_bytes.Append(ipv6_address1_.address());
+  arp_bytes.Resize(arp_bytes.GetLength() - 1);
+  EXPECT_FALSE(packet_.ParseReply(arp_bytes));
+}
+
+TEST_F(ArpPacketTest, ParseReplyIPv4) {
+  ByteString arp_bytes(kArpReplyV4, arraysize(kArpReplyV4));
+  arp_bytes.Append(mac_address0_);
+  arp_bytes.Append(ipv4_address0_.address());
+  arp_bytes.Append(mac_address1_);
+  arp_bytes.Append(ipv4_address1_.address());
+  EXPECT_TRUE(packet_.ParseReply(arp_bytes));
+  EXPECT_TRUE(ipv4_address0_.Equals(packet_.local_ip_address()));
+  EXPECT_TRUE(ipv4_address1_.Equals(packet_.remote_ip_address()));
+  EXPECT_TRUE(mac_address0_.Equals(packet_.local_mac_address()));
+  EXPECT_TRUE(mac_address1_.Equals(packet_.remote_mac_address()));
+}
+
+TEST_F(ArpPacketTest, ParseReplyIPv6) {
+  ByteString arp_bytes(kArpReplyV6, arraysize(kArpReplyV6));
+  arp_bytes.Append(mac_address1_);
+  arp_bytes.Append(ipv6_address0_.address());
+  arp_bytes.Append(mac_address0_);
+  arp_bytes.Append(ipv6_address1_.address());
+  EXPECT_TRUE(packet_.ParseReply(arp_bytes));
+  EXPECT_TRUE(ipv6_address0_.Equals(packet_.local_ip_address()));
+  EXPECT_TRUE(ipv6_address1_.Equals(packet_.remote_ip_address()));
+  EXPECT_TRUE(mac_address1_.Equals(packet_.local_mac_address()));
+  EXPECT_TRUE(mac_address0_.Equals(packet_.remote_mac_address()));
+}
+
+TEST_F(ArpPacketTest, FormatRequestInvalidAddress) {
+  ScopedMockLog log;
+  EXPECT_CALL(log,
+      Log(logging::LOG_ERROR, _,
+          HasSubstr("Local or remote IP address is not valid"))).Times(3);
+
+  ByteString arp_bytes;
+  EXPECT_FALSE(packet_.FormatRequest(&arp_bytes));
+  packet_.set_local_ip_address(ipv4_address0_);
+  EXPECT_FALSE(packet_.FormatRequest(&arp_bytes));
+  packet_.set_local_ip_address(IPAddress(IPAddress::kFamilyUnknown));
+  packet_.set_remote_ip_address(ipv4_address0_);
+  EXPECT_FALSE(packet_.FormatRequest(&arp_bytes));
+}
+
+TEST_F(ArpPacketTest, FormatRequestMismatchedAddresses) {
+  ScopedMockLog log;
+  EXPECT_CALL(log,
+      Log(logging::LOG_ERROR, _,
+          HasSubstr("IP address families do not match"))).Times(1);
+
+  ByteString arp_bytes;
+  packet_.set_local_ip_address(ipv4_address0_);
+  packet_.set_remote_ip_address(ipv6_address1_);
+  EXPECT_FALSE(packet_.FormatRequest(&arp_bytes));
+}
+
+TEST_F(ArpPacketTest, FormatRequestBadMACAddressLength) {
+  ScopedMockLog log;
+  EXPECT_CALL(log,
+      Log(logging::LOG_ERROR, _,
+          HasSubstr("MAC address length is incorrect"))).Times(3);
+
+  ByteString arp_bytes;
+  packet_.set_local_ip_address(ipv4_address0_);
+  packet_.set_remote_ip_address(ipv4_address1_);
+  EXPECT_FALSE(packet_.FormatRequest(&arp_bytes));
+  packet_.set_local_mac_address(mac_address0_);
+  EXPECT_FALSE(packet_.FormatRequest(&arp_bytes));
+  packet_.set_local_mac_address(ByteString());
+  packet_.set_remote_mac_address(mac_address0_);
+  EXPECT_FALSE(packet_.FormatRequest(&arp_bytes));
+}
+
+TEST_F(ArpPacketTest, FormatRequestIPv4) {
+  ByteString arp_bytes;
+  packet_.set_local_ip_address(ipv4_address0_);
+  packet_.set_remote_ip_address(ipv4_address1_);
+  packet_.set_local_mac_address(mac_address0_);
+  packet_.set_remote_mac_address(mac_address1_);
+  EXPECT_TRUE(packet_.FormatRequest(&arp_bytes));
+
+  ByteString expected_bytes(kArpRequestV4, arraysize(kArpRequestV4));
+  expected_bytes.Append(mac_address0_);
+  expected_bytes.Append(ipv4_address0_.address());
+  expected_bytes.Append(mac_address1_);
+  expected_bytes.Append(ipv4_address1_.address());
+  EXPECT_TRUE(expected_bytes.Equals(arp_bytes));
+}
+
+TEST_F(ArpPacketTest, FormatRequestIPv6) {
+  ByteString arp_bytes;
+  packet_.set_local_ip_address(ipv6_address0_);
+  packet_.set_remote_ip_address(ipv6_address1_);
+  packet_.set_local_mac_address(mac_address1_);
+  packet_.set_remote_mac_address(mac_address0_);
+  EXPECT_TRUE(packet_.FormatRequest(&arp_bytes));
+
+  ByteString expected_bytes(kArpRequestV6, arraysize(kArpRequestV6));
+  expected_bytes.Append(mac_address1_);
+  expected_bytes.Append(ipv6_address0_.address());
+  expected_bytes.Append(mac_address0_);
+  expected_bytes.Append(ipv6_address1_.address());
+  EXPECT_TRUE(expected_bytes.Equals(arp_bytes));
+}
+
+}  // namespace shill
diff --git a/byte_string.cc b/byte_string.cc
index f182756..fae4953 100644
--- a/byte_string.cc
+++ b/byte_string.cc
@@ -12,6 +12,16 @@
 
 namespace shill {
 
+ByteString ByteString::GetSubstring(size_t offset, size_t length) const {
+  if (offset > GetLength()) {
+    offset = GetLength();
+  }
+  if (length > GetLength() - offset) {
+    length = GetLength() - offset;
+  }
+  return ByteString(GetConstData() + offset, length);
+}
+
 // static
 ByteString ByteString::CreateFromCPUUInt32(uint32 val) {
   return ByteString(reinterpret_cast<unsigned char *>(&val), sizeof(val));
diff --git a/byte_string.h b/byte_string.h
index a1b76d4..bd370e7 100644
--- a/byte_string.h
+++ b/byte_string.h
@@ -32,15 +32,21 @@
     return *this;
   }
 
+  unsigned char *GetData() { return data_.data(); }
+  const unsigned char *GetConstData() const { return data_.data(); }
+  size_t GetLength() const { return data_.size(); }
+
+  // Returns a ByteString containing |length| bytes from the ByteString
+  // starting at |offset|.  This function truncates the returned string
+  // if part (or all) of this requested data lies outside the bounds of
+  // this ByteString.
+  ByteString GetSubstring(size_t offset, size_t length) const;
+
   // Inserts a uint32 into a ByteString in cpu-order
   static ByteString CreateFromCPUUInt32(uint32 val);
   // Inserts a uint32 into a ByteString in network-order
   static ByteString CreateFromNetUInt32(uint32 val);
 
-  unsigned char *GetData() { return data_.data(); }
-  const unsigned char *GetConstData() const { return data_.data(); }
-  size_t GetLength() const { return data_.size(); }
-
   // Converts to a uint32 from a host-order value stored in the ByteString
   // Returns true on success
   bool ConvertToCPUUInt32(uint32 *val) const;
diff --git a/byte_string_unittest.cc b/byte_string_unittest.cc
index c4692ed..7aeed8b 100644
--- a/byte_string_unittest.cc
+++ b/byte_string_unittest.cc
@@ -81,6 +81,16 @@
   EXPECT_TRUE(bs6.Equals(bs1));
 }
 
+TEST_F(ByteStringTest, SubString) {
+  ByteString bs1(kTest1, sizeof(kTest1));
+  ByteString bs2(kTest1 + 3, 4);
+  EXPECT_TRUE(bs2.Equals(bs1.GetSubstring(3, 4)));
+  const int kMargin = sizeof(kTest1) - 3;
+  ByteString bs3(kTest1 + kMargin, sizeof(kTest1) - kMargin);
+  EXPECT_TRUE(bs3.Equals(bs1.GetSubstring(kMargin, sizeof(kTest1))));
+  EXPECT_TRUE(bs1.GetSubstring(sizeof(kTest1), 10).IsEmpty());
+}
+
 TEST_F(ByteStringTest, UInt32) {
   ByteString bs1 = ByteString::CreateFromNetUInt32(kTest2Uint32);
   uint32 val;