[ledger] Allow getting a part of the object as VMO.

TEST=PageStorageTest

LE-239 #comment the mapping part is done, still need  partial download.

Change-Id: I93c5679b864b8da1cd582257177da17d7eccef73
diff --git a/bin/ledger/app/page_utils.cc b/bin/ledger/app/page_utils.cc
index c6fada3..7f0ed50 100644
--- a/bin/ledger/app/page_utils.cc
+++ b/bin/ledger/app/page_utils.cc
@@ -16,22 +16,6 @@
 #include "peridot/bin/ledger/storage/public/types.h"
 
 namespace ledger {
-namespace {
-Status ToBuffer(convert::ExtendedStringView value, int64_t offset,
-                int64_t max_size, fsl::SizedVmo* buffer) {
-  size_t start = value.size();
-  // Valid indices are between -N and N-1.
-  if (offset >= -static_cast<int64_t>(value.size()) &&
-      offset < static_cast<int64_t>(value.size())) {
-    start = offset < 0 ? value.size() + offset : offset;
-  }
-  size_t length = max_size < 0 ? value.size() : max_size;
-
-  bool result = fsl::VmoFromString(value.substr(start, length), buffer);
-  return result ? Status::OK : Status::UNKNOWN_ERROR;
-}
-
-}  // namespace
 
 void PageUtils::ResolveObjectIdentifierAsStringView(
     storage::PageStorage* storage, storage::ObjectIdentifier object_identifier,
@@ -85,21 +69,12 @@
     int64_t offset, int64_t max_size, storage::PageStorage::Location location,
     Status not_found_status,
     fit::function<void(Status, fsl::SizedVmo)> callback) {
-  ResolveObjectIdentifierAsStringView(
-      storage, object_identifier, location, not_found_status,
-      [offset, max_size, callback = std::move(callback)](Status status,
-                                                         fxl::StringView data) {
-        if (status != Status::OK) {
-          callback(status, nullptr);
-          return;
-        }
-        fsl::SizedVmo buffer;
-        Status buffer_status = ToBuffer(data, offset, max_size, &buffer);
-        if (buffer_status != Status::OK) {
-          callback(buffer_status, nullptr);
-          return;
-        }
-        callback(Status::OK, std::move(buffer));
+  storage->GetObjectPart(
+      object_identifier, offset, max_size, location,
+      [not_found_status, callback = std::move(callback)](
+          storage::Status status, fsl::SizedVmo object_part) {
+        callback(ConvertStatus(status, not_found_status),
+                 std::move(object_part));
       });
 }
 
diff --git a/bin/ledger/storage/fake/fake_page_storage.cc b/bin/ledger/storage/fake/fake_page_storage.cc
index e06372a..7fba43e 100644
--- a/bin/ledger/storage/fake/fake_page_storage.cc
+++ b/bin/ledger/storage/fake/fake_page_storage.cc
@@ -12,6 +12,7 @@
 #include <lib/async/default.h>
 #include <lib/fit/function.h>
 #include <lib/fsl/socket/strings.h>
+#include <lib/fsl/vmo/strings.h>
 #include <lib/fxl/logging.h>
 #include <lib/fxl/strings/concatenate.h>
 #include <lib/zx/time.h>
@@ -25,6 +26,23 @@
 namespace storage {
 namespace fake {
 
+namespace {
+
+Status ToBuffer(convert::ExtendedStringView value, int64_t offset,
+                int64_t max_size, fsl::SizedVmo* buffer) {
+  size_t start = value.size();
+  // Valid indices are between -N and N-1.
+  if (offset >= -static_cast<int64_t>(value.size()) &&
+      offset < static_cast<int64_t>(value.size())) {
+    start = offset < 0 ? value.size() + offset : offset;
+  }
+  size_t length = max_size < 0 ? value.size() : max_size;
+  bool result = fsl::VmoFromString(value.substr(start, length), buffer);
+  return result ? Status::OK : Status::INTERNAL_IO_ERROR;
+}
+
+}  // namespace
+
 FakePageStorage::FakePageStorage(ledger::Environment* environment,
                                  PageId page_id)
     : page_id_(std::move(page_id)),
@@ -175,6 +193,32 @@
   GetPiece(object_identifier, std::move(callback));
 }
 
+void FakePageStorage::GetObjectPart(
+    ObjectIdentifier object_identifier, int64_t offset, int64_t max_size,
+    Location location, fit::function<void(Status, fsl::SizedVmo)> callback) {
+  GetPiece(object_identifier,
+           [offset, max_size, callback = std::move(callback)](
+               Status status, std::unique_ptr<const Object> piece) {
+             if (status != Status::OK) {
+               callback(status, nullptr);
+               return;
+             }
+             fxl::StringView data;
+             Status data_status = piece->GetData(&data);
+             if (data_status != Status::OK) {
+               callback(data_status, nullptr);
+               return;
+             }
+             fsl::SizedVmo buffer;
+             Status buffer_status = ToBuffer(data, offset, max_size, &buffer);
+             if (buffer_status != Status::OK) {
+               callback(buffer_status, nullptr);
+               return;
+             }
+             callback(Status::OK, std::move(buffer));
+           });
+}
+
 void FakePageStorage::GetPiece(
     ObjectIdentifier object_identifier,
     fit::function<void(Status, std::unique_ptr<const Object>)> callback) {
diff --git a/bin/ledger/storage/fake/fake_page_storage.h b/bin/ledger/storage/fake/fake_page_storage.h
index bc6f3a1..836a413 100644
--- a/bin/ledger/storage/fake/fake_page_storage.h
+++ b/bin/ledger/storage/fake/fake_page_storage.h
@@ -57,6 +57,10 @@
   void AddObjectFromLocal(
       ObjectType object_type, std::unique_ptr<DataSource> data_source,
       fit::function<void(Status, ObjectIdentifier)> callback) override;
+  void GetObjectPart(
+      ObjectIdentifier object_identifier, int64_t offset, int64_t max_size,
+      Location location,
+      fit::function<void(Status, fsl::SizedVmo)> callback) override;
   void GetObject(ObjectIdentifier object_identifier, Location location,
                  fit::function<void(Status, std::unique_ptr<const Object>)>
                      callback) override;
diff --git a/bin/ledger/storage/impl/page_storage_impl.cc b/bin/ledger/storage/impl/page_storage_impl.cc
index 8809962..fe3d9d7 100644
--- a/bin/ledger/storage/impl/page_storage_impl.cc
+++ b/bin/ledger/storage/impl/page_storage_impl.cc
@@ -19,6 +19,7 @@
 #include <lib/callback/trace_callback.h>
 #include <lib/callback/waiter.h>
 #include <lib/fit/function.h>
+#include <lib/fsl/vmo/strings.h>
 #include <lib/fxl/arraysize.h>
 #include <lib/fxl/files/directory.h>
 #include <lib/fxl/files/file.h>
@@ -71,6 +72,22 @@
   }
 };
 
+size_t GetObjectPartStart(int64_t offset, size_t object_size) {
+  size_t start = object_size;
+  // Valid indices are between -N and N-1.
+  if (offset >= -static_cast<int64_t>(object_size) &&
+      offset < static_cast<int64_t>(object_size)) {
+    start = offset < 0 ? object_size + offset : offset;
+  }
+  return start;
+}
+
+size_t GetObjectPartLength(int64_t max_size, size_t object_size, size_t start) {
+  size_t adjusted_max_size = max_size < 0 ? object_size : max_size;
+  return start > object_size ? 0
+                             : std::min(adjusted_max_size, object_size - start);
+}
+
 }  // namespace
 
 PageStorageImpl::PageStorageImpl(
@@ -457,17 +474,19 @@
       });
 }
 
-void PageStorageImpl::GetObject(
-    ObjectIdentifier object_identifier, Location location,
-    fit::function<void(Status, std::unique_ptr<const Object>)> callback) {
+void PageStorageImpl::GetObjectPart(
+    ObjectIdentifier object_identifier, int64_t offset, int64_t max_size,
+    Location location, fit::function<void(Status, fsl::SizedVmo)> callback) {
   FXL_DCHECK(IsDigestValid(object_identifier.object_digest()));
   GetPiece(
       object_identifier,
-      [this, object_identifier, location, callback = std::move(callback)](
+      [this, object_identifier, offset, max_size, location,
+       callback = std::move(callback)](
           Status status, std::unique_ptr<const Object> object) mutable {
         if (status == Status::NOT_FOUND) {
           if (location == Location::NETWORK) {
-            GetObjectFromSync(object_identifier, std::move(callback));
+            GetObjectPartFromSync(object_identifier, offset, max_size,
+                                  std::move(callback));
           } else {
             callback(Status::NOT_FOUND, nullptr);
           }
@@ -482,38 +501,50 @@
         FXL_DCHECK(object);
         ObjectDigestInfo digest_info =
             GetObjectDigestInfo(object_identifier.object_digest());
+        fxl::StringView data;
+        Status get_data_status = object->GetData(&data);
+        if (get_data_status != Status::OK) {
+          callback(get_data_status, nullptr);
+          return;
+        }
 
         if (digest_info.is_inlined() || digest_info.is_chunk()) {
-          callback(status, std::move(object));
+          fsl::SizedVmo buffer;
+          size_t start = GetObjectPartStart(offset, data.size());
+          size_t length = GetObjectPartLength(max_size, data.size(), start);
+
+          if (!fsl::VmoFromString(data.substr(start, length), &buffer)) {
+            callback(Status::INTERNAL_IO_ERROR, nullptr);
+            return;
+          }
+          callback(Status::OK, std::move(buffer));
           return;
         }
 
         FXL_DCHECK(digest_info.piece_type == PieceType::INDEX);
-
-        fxl::StringView content;
-        status = object->GetData(&content);
-        if (status != Status::OK) {
-          callback(status, nullptr);
-          return;
-        }
         const FileIndex* file_index;
-        status = FileIndexSerialization::ParseFileIndex(content, &file_index);
+        status = FileIndexSerialization::ParseFileIndex(data, &file_index);
         if (status != Status::OK) {
           callback(Status::FORMAT_ERROR, nullptr);
           return;
         }
+        size_t start = GetObjectPartStart(offset, file_index->size());
+        size_t length =
+            GetObjectPartLength(max_size, file_index->size(), start);
 
         zx::vmo raw_vmo;
-        zx_status_t zx_status =
-            zx::vmo::create(file_index->size(), 0, &raw_vmo);
+        zx_status_t zx_status = zx::vmo::create(length, 0, &raw_vmo);
         if (zx_status != ZX_OK) {
-          FXL_LOG(WARNING) << "Unable to create VMO of size: "
-                           << file_index->size();
+          FXL_LOG(WARNING) << "Unable to create VMO of size: " << length;
           callback(Status::INTERNAL_IO_ERROR, nullptr);
           return;
         }
+        fsl::SizedVmo vmo(std::move(raw_vmo), length);
 
-        fsl::SizedVmo vmo(std::move(raw_vmo), file_index->size());
+        if (length == 0) {
+          callback(Status::OK, std::move(vmo));
+        }
+
         fsl::SizedVmo vmo_copy;
         zx_status = vmo.Duplicate(ZX_RIGHTS_BASIC | ZX_RIGHT_WRITE, &vmo_copy);
         if (zx_status != ZX_OK) {
@@ -522,17 +553,26 @@
           return;
         }
         FillBufferWithObjectContent(
-            std::move(object), std::move(vmo_copy), 0, file_index->size(),
-            [object_identifier = std::move(object_identifier),
-             vmo = std::move(vmo),
-             callback = std::move(callback)](Status status) mutable {
-              callback(status,
-                       std::make_unique<VmoObject>(std::move(object_identifier),
-                                                   std::move(vmo)));
-            });
+            std::move(object), std::move(vmo_copy), start, length, 0,
+            file_index->size(),
+            [vmo = std::move(vmo), callback = std::move(callback)](
+                Status status) mutable { callback(status, std::move(vmo)); });
       });
 }
 
+void PageStorageImpl::GetObject(
+    ObjectIdentifier object_identifier, Location location,
+    fit::function<void(Status, std::unique_ptr<const Object>)> callback) {
+  GetObjectPart(object_identifier, 0, -1, location,
+                [object_identifier = std::move(object_identifier),
+                 callback = std::move(callback)](Status status,
+                                                 fsl::SizedVmo vmo) mutable {
+                  callback(status,
+                           std::make_unique<VmoObject>(
+                               std::move(object_identifier), std::move(vmo)));
+                });
+}
+
 void PageStorageImpl::GetPiece(
     ObjectIdentifier object_identifier,
     fit::function<void(Status, std::unique_ptr<const Object>)> callback) {
@@ -892,24 +932,26 @@
       });
 }
 
-void PageStorageImpl::GetObjectFromSync(
-    ObjectIdentifier object_identifier,
-    fit::function<void(Status, std::unique_ptr<const Object>)> callback) {
+void PageStorageImpl::GetObjectPartFromSync(
+    ObjectIdentifier object_identifier, size_t offset, size_t size,
+    fit::function<void(Status, fsl::SizedVmo)> callback) {
   if (!page_sync_) {
     callback(Status::NOT_CONNECTED_ERROR, nullptr);
     return;
   }
 
-  DownloadFullObject(object_identifier, [this, object_identifier,
-                                         callback = std::move(callback)](
-                                            Status status) mutable {
-    if (status != Status::OK) {
-      callback(status, nullptr);
-      return;
-    }
+  // TODO(LE-239): download only the needed part.
+  DownloadFullObject(object_identifier,
+                     [this, object_identifier, offset, size,
+                      callback = std::move(callback)](Status status) mutable {
+                       if (status != Status::OK) {
+                         callback(status, nullptr);
+                         return;
+                       }
 
-    GetObject(object_identifier, Location::LOCAL, std::move(callback));
-  });
+                       GetObjectPart(object_identifier, offset, size,
+                                     Location::LOCAL, std::move(callback));
+                     });
 }
 
 void PageStorageImpl::ObjectIsUntracked(
@@ -934,11 +976,12 @@
 }
 
 void PageStorageImpl::FillBufferWithObjectContent(
-    ObjectIdentifier object_identifier, fsl::SizedVmo vmo, size_t offset,
-    size_t size, fit::function<void(Status)> callback) {
+    ObjectIdentifier object_identifier, fsl::SizedVmo vmo,
+    int64_t global_offset, size_t global_size, int64_t current_position,
+    size_t object_size, fit::function<void(Status)> callback) {
   GetPiece(object_identifier,
-           [this, vmo = std::move(vmo), offset, size,
-            callback = std::move(callback)](
+           [this, vmo = std::move(vmo), global_offset, global_size,
+            current_position, object_size, callback = std::move(callback)](
                Status status, std::unique_ptr<const Object> object) mutable {
              if (status != Status::OK) {
                callback(status);
@@ -946,32 +989,48 @@
              }
 
              FXL_DCHECK(object);
-             FillBufferWithObjectContent(std::move(object), std::move(vmo),
-                                         offset, size, std::move(callback));
+             FillBufferWithObjectContent(
+                 std::move(object), std::move(vmo), global_offset, global_size,
+                 current_position, object_size, std::move(callback));
            });
 }
 
 void PageStorageImpl::FillBufferWithObjectContent(
-    std::unique_ptr<const Object> object, fsl::SizedVmo vmo, size_t offset,
-    size_t size, fit::function<void(Status)> callback) {
+    std::unique_ptr<const Object> object, fsl::SizedVmo vmo,
+    int64_t global_offset, size_t global_size, int64_t current_position,
+    size_t object_size, fit::function<void(Status)> callback) {
   fxl::StringView content;
   Status status = object->GetData(&content);
   if (status != Status::OK) {
     callback(status);
     return;
   }
-
   ObjectDigestInfo digest_info =
       GetObjectDigestInfo(object->GetIdentifier().object_digest());
   if (digest_info.is_inlined() || digest_info.is_chunk()) {
-    if (size != content.size()) {
+    if (object_size != content.size()) {
       FXL_LOG(ERROR) << "Error in serialization format. Expecting object: "
-                     << object->GetIdentifier() << " to have size: " << size
+                     << object->GetIdentifier()
+                     << " to have size: " << object_size
                      << ", but found an object of size: " << content.size();
       callback(Status::FORMAT_ERROR);
       return;
     }
-    zx_status_t zx_status = vmo.vmo().write(content.data(), offset, size);
+    // Distance is negative if the offset is ahead and positive if behind.
+    int64_t distance_from_global_offset = current_position - global_offset;
+    // Read offset can be non-zero on first read; in that case, we need to skip
+    // bytes coming before global offset.
+    size_t read_offset = std::max(-distance_from_global_offset, 0L);
+    // Write offset is zero on the first write; otherwise we need to skip number
+    // of bytes corresponding to what we have already written.
+    size_t write_offset = std::max(distance_from_global_offset, 0L);
+    // Read and write until reaching either size of the object, or global size.
+    size_t read_write_size =
+        std::min(content.size() - read_offset, global_size - write_offset);
+    FXL_DCHECK(read_write_size > 0);
+    fxl::StringView read_substr = content.substr(read_offset, read_write_size);
+    zx_status_t zx_status =
+        vmo.vmo().write(read_substr.data(), write_offset, read_write_size);
     if (zx_status != ZX_OK) {
       FXL_LOG(ERROR) << "Unable to write to vmo. Status: " << zx_status;
       callback(Status::INTERNAL_IO_ERROR);
@@ -987,22 +1046,32 @@
     callback(Status::FORMAT_ERROR);
     return;
   }
-  if (file_index->size() != size) {
+  if (file_index->size() != object_size) {
     FXL_LOG(ERROR) << "Error in serialization format. Expecting object: "
-                   << object->GetIdentifier() << " to have size: " << size
+                   << object->GetIdentifier() << " to have size " << object_size
                    << ", but found an index object of size: "
                    << file_index->size();
     callback(Status::FORMAT_ERROR);
     return;
   }
 
-  size_t sub_offset = 0;
+  int64_t sub_offset = 0;
   auto waiter = fxl::MakeRefCounted<callback::StatusWaiter<Status>>(Status::OK);
   for (const auto* child : *file_index->children()) {
     if (sub_offset + child->size() > file_index->size()) {
       callback(Status::FORMAT_ERROR);
       return;
     }
+    int64_t child_position = current_position + sub_offset;
+    ObjectIdentifier child_identifier =
+        ToObjectIdentifier(child->object_identifier());
+    if (child_position + static_cast<int64_t>(child->size()) < global_offset) {
+      sub_offset += child->size();
+      continue;
+    }
+    if (global_offset + static_cast<int64_t>(global_size) < child_position) {
+      break;
+    }
     fsl::SizedVmo vmo_copy;
     zx_status_t zx_status =
         vmo.Duplicate(ZX_RIGHTS_BASIC | ZX_RIGHT_WRITE, &vmo_copy);
@@ -1011,18 +1080,11 @@
       callback(Status::INTERNAL_IO_ERROR);
       return;
     }
-    FillBufferWithObjectContent(ToObjectIdentifier(child->object_identifier()),
-                                std::move(vmo_copy), offset + sub_offset,
+    FillBufferWithObjectContent(child_identifier, std::move(vmo_copy),
+                                global_offset, global_size, child_position,
                                 child->size(), waiter->NewCallback());
     sub_offset += child->size();
   }
-
-  if (sub_offset != file_index->size()) {
-    FXL_LOG(ERROR) << "Built file size doesn't add up.";
-    callback(Status::FORMAT_ERROR);
-    return;
-  }
-
   waiter->Finalize(std::move(callback));
 }
 
diff --git a/bin/ledger/storage/impl/page_storage_impl.h b/bin/ledger/storage/impl/page_storage_impl.h
index 141288b..1210757 100644
--- a/bin/ledger/storage/impl/page_storage_impl.h
+++ b/bin/ledger/storage/impl/page_storage_impl.h
@@ -98,6 +98,10 @@
   void AddObjectFromLocal(
       ObjectType object_type, std::unique_ptr<DataSource> data_source,
       fit::function<void(Status, ObjectIdentifier)> callback) override;
+  void GetObjectPart(
+      ObjectIdentifier object_identifier, int64_t offset, int64_t max_size,
+      Location location,
+      fit::function<void(Status, fsl::SizedVmo)> callback) override;
   void GetObject(ObjectIdentifier object_identifier, Location location,
                  fit::function<void(Status, std::unique_ptr<const Object>)>
                      callback) override;
@@ -172,17 +176,27 @@
   void DownloadFullObject(ObjectIdentifier object_identifier,
                           fit::function<void(Status)> callback);
 
-  void GetObjectFromSync(
-      ObjectIdentifier object_identifier,
-      fit::function<void(Status, std::unique_ptr<const Object>)> callback);
+  void GetObjectPartFromSync(
+      ObjectIdentifier object_identifier, size_t offset, size_t size,
+      fit::function<void(Status, fsl::SizedVmo)> callback);
 
+  // Reads the content of an object into a provided VMO. Takes into
+  // account the global offset and size in order to be able to read only the
+  // requested part of an object.
+  // |global_offset| is the offset from the beginning of the full object in
+  // bytes. |global_size| is the maximum size requested to be read into the vmo.
+  // |current_position| is the position of the currently read piece (defined by
+  // |object_identifier|) in the full object. |object_size| is the size of the
+  // currently read piece.
   void FillBufferWithObjectContent(ObjectIdentifier object_identifier,
-                                   fsl::SizedVmo vmo, size_t offset,
-                                   size_t size,
+                                   fsl::SizedVmo vmo, int64_t global_offset,
+                                   size_t global_size, int64_t current_position,
+                                   size_t object_size,
                                    fit::function<void(Status)> callback);
   void FillBufferWithObjectContent(std::unique_ptr<const Object> object,
-                                   fsl::SizedVmo vmo, size_t offset,
-                                   size_t size,
+                                   fsl::SizedVmo vmo, int64_t global_offset,
+                                   size_t global_size, int64_t current_position,
+                                   size_t object_size,
                                    fit::function<void(Status)> callback);
 
   // Notifies the registered watchers of |new_commits|.
diff --git a/bin/ledger/storage/impl/page_storage_unittest.cc b/bin/ledger/storage/impl/page_storage_unittest.cc
index e5e7731..a4384b2 100644
--- a/bin/ledger/storage/impl/page_storage_unittest.cc
+++ b/bin/ledger/storage/impl/page_storage_unittest.cc
@@ -13,6 +13,7 @@
 #include <lib/callback/set_when_called.h>
 #include <lib/fit/function.h>
 #include <lib/fsl/socket/strings.h>
+#include <lib/fsl/vmo/strings.h>
 #include <lib/fxl/arraysize.h>
 #include <lib/fxl/files/directory.h>
 #include <lib/fxl/files/file.h>
@@ -388,6 +389,22 @@
     return object;
   }
 
+  fsl::SizedVmo TryGetObjectPart(const ObjectIdentifier& object_identifier,
+                                 size_t offset, size_t max_size,
+                                 PageStorage::Location location,
+                                 Status expected_status = Status::OK) {
+    bool called;
+    Status status;
+    fsl::SizedVmo vmo;
+    storage_->GetObjectPart(
+        object_identifier, offset, max_size, location,
+        callback::Capture(callback::SetWhenCalled(&called), &status, &vmo));
+    RunLoopUntilIdle();
+    EXPECT_TRUE(called);
+    EXPECT_EQ(expected_status, status);
+    return vmo;
+  }
+
   std::unique_ptr<const Object> TryGetPiece(
       const ObjectIdentifier& object_identifier,
       Status expected_status = Status::OK) {
@@ -1270,6 +1287,117 @@
   });
 }
 
+TEST_F(PageStorageTest, GetObjectPart) {
+  RunInCoroutine([this](CoroutineHandler* handler) {
+    ObjectData data("_Some data_");
+    ASSERT_EQ(Status::OK, WriteObject(handler, &data));
+
+    fsl::SizedVmo object_part = TryGetObjectPart(
+        data.object_identifier, 1, data.size - 2, PageStorage::Location::LOCAL);
+    std::string object_part_data;
+    ASSERT_TRUE(fsl::StringFromVmo(object_part, &object_part_data));
+    EXPECT_EQ(data.value.substr(1, data.size - 2),
+              convert::ToString(object_part_data));
+  });
+}
+
+TEST_F(PageStorageTest, GetObjectPartLargeOffset) {
+  RunInCoroutine([this](CoroutineHandler* handler) {
+    ObjectData data("_Some data_");
+    ASSERT_EQ(Status::OK, WriteObject(handler, &data));
+
+    fsl::SizedVmo object_part =
+        TryGetObjectPart(data.object_identifier, data.size * 2, data.size,
+                         PageStorage::Location::LOCAL);
+    std::string object_part_data;
+    ASSERT_TRUE(fsl::StringFromVmo(object_part, &object_part_data));
+    EXPECT_EQ("", convert::ToString(object_part_data));
+  });
+}
+
+TEST_F(PageStorageTest, GetObjectPartLargeMaxSize) {
+  RunInCoroutine([this](CoroutineHandler* handler) {
+    ObjectData data("_Some data_");
+    ASSERT_EQ(Status::OK, WriteObject(handler, &data));
+
+    fsl::SizedVmo object_part = TryGetObjectPart(
+        data.object_identifier, 0, data.size * 2, PageStorage::Location::LOCAL);
+    std::string object_part_data;
+    ASSERT_TRUE(fsl::StringFromVmo(object_part, &object_part_data));
+    EXPECT_EQ(data.value, convert::ToString(object_part_data));
+  });
+}
+
+TEST_F(PageStorageTest, GetObjectPartNegativeArgs) {
+  RunInCoroutine([this](CoroutineHandler* handler) {
+    ObjectData data("_Some data_");
+    ASSERT_EQ(Status::OK, WriteObject(handler, &data));
+
+    fsl::SizedVmo object_part =
+        TryGetObjectPart(data.object_identifier, -data.size + 1, -1,
+                         PageStorage::Location::LOCAL);
+    std::string object_part_data;
+    ASSERT_TRUE(fsl::StringFromVmo(object_part, &object_part_data));
+    EXPECT_EQ(data.value.substr(1, data.size - 1),
+              convert::ToString(object_part_data));
+  });
+}
+
+TEST_F(PageStorageTest, GetLargeObjectPart) {
+  std::string data_str = RandomString(environment_.random(), 65536);
+  size_t offset = 6144;
+  size_t size = 49152;
+
+  ObjectData data(std::move(data_str), ObjectType::TREE_NODE,
+                  InlineBehavior::PREVENT);
+
+  ASSERT_EQ(
+      PieceType::INDEX,
+      GetObjectDigestInfo(data.object_identifier.object_digest()).piece_type);
+
+  bool called;
+  Status status;
+  ObjectIdentifier object_identifier;
+  storage_->AddObjectFromLocal(
+      ObjectType::TREE_NODE, data.ToDataSource(),
+      callback::Capture(callback::SetWhenCalled(&called), &status,
+                        &object_identifier));
+  RunLoopUntilIdle();
+  ASSERT_TRUE(called);
+
+  EXPECT_EQ(Status::OK, status);
+  EXPECT_EQ(data.object_identifier, object_identifier);
+
+  fsl::SizedVmo object_part = TryGetObjectPart(object_identifier, offset, size,
+                                               PageStorage::Location::LOCAL);
+  std::string object_part_data;
+  ASSERT_TRUE(fsl::StringFromVmo(object_part, &object_part_data));
+  std::string result_str = convert::ToString(object_part_data);
+  EXPECT_EQ(size, result_str.size());
+  EXPECT_EQ(data.value.substr(offset, size), result_str);
+}
+
+TEST_F(PageStorageTest, GetObjectPartFromSync) {
+  ObjectData data("_Some data_", InlineBehavior::PREVENT);
+  FakeSyncDelegate sync;
+  sync.AddObject(data.object_identifier, data.value);
+  storage_->SetSyncDelegate(&sync);
+
+  fsl::SizedVmo object_part = TryGetObjectPart(
+      data.object_identifier, 1, data.size - 2, PageStorage::Location::NETWORK);
+  std::string object_part_data;
+  ASSERT_TRUE(fsl::StringFromVmo(object_part, &object_part_data));
+  EXPECT_EQ(data.value.substr(1, data.size - 2),
+            convert::ToString(object_part_data));
+
+  storage_->SetSyncDelegate(nullptr);
+  ObjectData other_data("_Some other data_", InlineBehavior::PREVENT);
+  TryGetObjectPart(other_data.object_identifier, 1, other_data.size - 2,
+                   PageStorage::Location::LOCAL, Status::NOT_FOUND);
+  TryGetObjectPart(other_data.object_identifier, 1, other_data.size - 2,
+                   PageStorage::Location::NETWORK, Status::NOT_CONNECTED_ERROR);
+}
+
 TEST_F(PageStorageTest, GetObjectFromSync) {
   ObjectData data("Some data", InlineBehavior::PREVENT);
   FakeSyncDelegate sync;
diff --git a/bin/ledger/storage/public/page_storage.h b/bin/ledger/storage/public/page_storage.h
index 255688a..cd083d5 100644
--- a/bin/ledger/storage/public/page_storage.h
+++ b/bin/ledger/storage/public/page_storage.h
@@ -157,6 +157,17 @@
   virtual void GetObject(
       ObjectIdentifier object_identifier, Location location,
       fit::function<void(Status, std::unique_ptr<const Object>)> callback) = 0;
+
+  // Retrieve a part of an object starting at |offset| with a maximum size of
+  // |max_size| and map it to a VMO.
+  // If |offset| is less than 0, starts from |-offset| from the end of the
+  // value. If |max_size| is less than 0, retrieves everything untill the end of
+  // an object.
+  virtual void GetObjectPart(
+      ObjectIdentifier object_identifier, int64_t offset, int64_t max_size,
+      Location location,
+      fit::function<void(Status, fsl::SizedVmo)> callback) = 0;
+
   // Finds the piece associated with the given |object_identifier|. The result
   // or an error will be returned through the given |callback|. Only local
   // storage is checked, and if the object is an index, is it returned as is,
diff --git a/bin/ledger/storage/testing/page_storage_empty_impl.cc b/bin/ledger/storage/testing/page_storage_empty_impl.cc
index 5e61aa6..dfe4ec4 100644
--- a/bin/ledger/storage/testing/page_storage_empty_impl.cc
+++ b/bin/ledger/storage/testing/page_storage_empty_impl.cc
@@ -138,6 +138,13 @@
   callback(Status::NOT_IMPLEMENTED, {});
 }
 
+void PageStorageEmptyImpl::GetObjectPart(
+    ObjectIdentifier object_identifier, int64_t offset, int64_t max_size,
+    Location location, fit::function<void(Status, fsl::SizedVmo)> callback) {
+  FXL_NOTIMPLEMENTED();
+  callback(Status::NOT_IMPLEMENTED, nullptr);
+}
+
 void PageStorageEmptyImpl::GetObject(
     ObjectIdentifier /*object_identifier*/, Location /*location*/,
     fit::function<void(Status, std::unique_ptr<const Object>)> callback) {
diff --git a/bin/ledger/storage/testing/page_storage_empty_impl.h b/bin/ledger/storage/testing/page_storage_empty_impl.h
index cb0695c..5136d11 100644
--- a/bin/ledger/storage/testing/page_storage_empty_impl.h
+++ b/bin/ledger/storage/testing/page_storage_empty_impl.h
@@ -86,6 +86,11 @@
       ObjectType object_type, std::unique_ptr<DataSource> data_source,
       fit::function<void(Status, ObjectIdentifier)> callback) override;
 
+  void GetObjectPart(
+      ObjectIdentifier object_identifier, int64_t offset, int64_t max_size,
+      Location location,
+      fit::function<void(Status, fsl::SizedVmo)> callback) override;
+
   void GetObject(ObjectIdentifier object_identifier, Location location,
                  fit::function<void(Status, std::unique_ptr<const Object>)>
                      callback) override;