diff --git a/garnet/bin/tee_manager/BUILD.gn b/garnet/bin/tee_manager/BUILD.gn
index ea853ffacac9a9379e678a611723487cf3d9b485..79cfeec435810f2be7a79a71076e1b9464fb44fd 100644
--- a/garnet/bin/tee_manager/BUILD.gn
+++ b/garnet/bin/tee_manager/BUILD.gn
@@ -5,6 +5,8 @@
 import("//build/config.gni")
 import("//build/package.gni")
 import("//build/rust/rustc_binary.gni")
+import("//build/test/test_package.gni")
+import("//build/testing/environments.gni")
 
 package("tee_manager") {
   meta = [
@@ -52,3 +54,39 @@ config_data("config") {
     "tee_manager.config",
   ]
 }
+
+test_package("optee_test") {
+  deps = [
+    ":optee_test_bin",
+  ]
+
+  binaries = [
+    {
+      name = "optee_test"
+    },
+  ]
+
+  tests = [
+    {
+      name = "optee_test"
+      environments = [ astro_env ]
+    },
+  ]
+}
+
+executable("optee_test_bin") {
+  testonly = true
+  output_name = "optee_test"
+
+  sources = [
+    "test/optee_test.cc",
+  ]
+
+  deps = [
+    "//garnet/public/lib/fxl/test:gtest_main",
+    "//sdk/lib/sys/cpp/testing:integration",
+    "//src/lib/fxl",
+    "//zircon/public/fidl/fuchsia-tee",
+    "//zircon/public/lib/tee-client-api",
+  ]
+}
diff --git a/garnet/bin/tee_manager/meta/optee_test.cmx b/garnet/bin/tee_manager/meta/optee_test.cmx
new file mode 100644
index 0000000000000000000000000000000000000000..bd8f166c2ecf56224dda56cc692b6852b3e2b6eb
--- /dev/null
+++ b/garnet/bin/tee_manager/meta/optee_test.cmx
@@ -0,0 +1,21 @@
+{
+    "facets": {
+        "fuchsia.test": {
+            "injected-services": {
+                "fuchsia.tee.Device": "fuchsia-pkg://fuchsia.com/tee_manager#meta/tee_manager.cmx"
+            }
+        }
+    },
+    "program": {
+        "binary": "bin/optee_test"
+    },
+    "sandbox": {
+        "dev": [],
+        "features": [],
+        "services": [
+            "fuchsia.sys.Environment",
+            "fuchsia.sys.Loader",
+            "fuchsia.tee.Device"
+        ]
+    }
+}
diff --git a/garnet/bin/tee_manager/test/optee_test.cc b/garnet/bin/tee_manager/test/optee_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1e162f8c04f64c0145b33d3d0cbf278e36d60896
--- /dev/null
+++ b/garnet/bin/tee_manager/test/optee_test.cc
@@ -0,0 +1,564 @@
+// Copyright 2019 The Fuchsia 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 <fuchsia/tee/cpp/fidl.h>
+#include <gtest/gtest.h>
+#include <lib/sys/cpp/testing/test_with_environment.h>
+#include <src/security/tee/third_party/optee_test/ta_storage.h>
+#include <tee-client-api/tee_client_api.h>
+#include <zircon/syscalls.h>
+#include <zircon/types.h>
+
+#include <iostream>
+#include <limits>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace optee {
+
+namespace test {
+
+namespace {
+constexpr uint32_t kPrivateStorage = 0x1;
+constexpr uint32_t kFlagRead = 0x1;
+constexpr uint32_t kFlagWrite = 0x2;
+constexpr uint32_t kFlagWriteMetadata = 0x4;
+
+class OpteeFileHandleGuard {
+ public:
+  OpteeFileHandleGuard() = default;
+
+  constexpr OpteeFileHandleGuard(TEEC_Session* session, uint32_t handle)
+      : session_(session), handle_(handle) {}
+
+  ~OpteeFileHandleGuard() { Close(); }
+
+  OpteeFileHandleGuard(OpteeFileHandleGuard&& other)
+      : session_(other.session_), handle_(std::move(other.handle_)) {
+    other.session_ = nullptr;
+    other.handle_ = std::nullopt;
+  }
+
+  OpteeFileHandleGuard& operator=(OpteeFileHandleGuard&& other) {
+    if (&other == this) {
+      return *this;
+    }
+
+    Close();
+    if (other.IsValid()) {
+      session_ = other.session_;
+      handle_ = std::make_optional(other.Release());
+    }
+    return *this;
+  }
+
+  OpteeFileHandleGuard(const OpteeFileHandleGuard&) = delete;
+  OpteeFileHandleGuard& operator=(const OpteeFileHandleGuard&) = delete;
+
+  bool IsValid() const { return session_ != nullptr && handle_.has_value(); }
+
+  uint32_t GetHandle() const {
+    EXPECT_TRUE(IsValid());
+    return *handle_;
+  }
+
+  void Close();  // Forward-declare and implement after `CloseFile` definition
+
+  uint32_t Release() {
+    EXPECT_TRUE(IsValid());
+
+    uint32_t released = *handle_;
+    session_ = nullptr;
+    handle_ = std::nullopt;
+    return released;
+  }
+
+ private:
+  TEEC_Session* session_;
+  std::optional<uint32_t> handle_;
+};
+
+struct OperationResult {
+  TEEC_Result result;
+  uint32_t return_origin;
+};
+
+// Helper class to print numeric values in hex for gtest
+template <typename NumericType>
+class Hex {
+ public:
+  constexpr explicit Hex(NumericType number) : number_(number) {}
+
+  friend std::ostream& operator<<(std::ostream& os,
+                                  const Hex<NumericType>& obj) {
+    return os << "0x" << std::hex << obj.number_;
+  }
+
+ private:
+  NumericType number_;
+};
+
+::testing::AssertionResult IsTeecSuccess(TEEC_Result result) {
+  if (result == TEEC_SUCCESS) {
+    return ::testing::AssertionSuccess();
+  } else {
+    return ::testing::AssertionFailure() << "result: " << Hex(result);
+  }
+}
+
+::testing::AssertionResult IsTeecSuccess(const OperationResult& op_result) {
+  if (op_result.result == TEEC_SUCCESS) {
+    return ::testing::AssertionSuccess();
+  } else {
+    return ::testing::AssertionFailure()
+           << "result: " << Hex(op_result.result)
+           << ", return origin: " << Hex(op_result.return_origin);
+  }
+}
+
+static std::vector<uint8_t> StringToBuffer(const std::string& s) {
+  return std::vector<uint8_t>(s.cbegin(), s.cend());
+}
+
+static std::string BufferToString(const std::vector<uint8_t>& buf) {
+  return std::string(buf.cbegin(), buf.cend());
+}
+
+enum class SeekFrom : uint32_t { kBeginning = 0x0, kCurrent = 0x1, kEnd = 0x2 };
+
+// Invokes the storage TA to create a file. Returns an object handle, if
+// successful.
+static void CreateFile(TEEC_Session* session, std::string name,
+                       std::vector<uint8_t>* init_data, uint32_t flags,
+                       OpteeFileHandleGuard* out_handle_guard) {
+  ASSERT_NE(session, nullptr);
+  ASSERT_NE(init_data, nullptr);
+  ASSERT_GT(init_data->size(), 0u)
+      << "the trusted application does not support zero-sized initial data";
+  ASSERT_NE(out_handle_guard, nullptr);
+
+  TEEC_Operation op = {};
+  op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, TEEC_VALUE_INOUT,
+                                   TEEC_VALUE_INPUT, TEEC_MEMREF_TEMP_INPUT);
+  op.params[2].value.b = kPrivateStorage;
+
+  op.params[0].tmpref.buffer = static_cast<void*>(name.data());
+  op.params[0].tmpref.size = name.size();
+
+  op.params[1].value.a = flags;
+
+  constexpr uint32_t kNullHandle = 0x0;
+  op.params[2].value.a = kNullHandle;
+
+  op.params[3].tmpref.buffer = static_cast<void*>(init_data->data());
+  op.params[3].tmpref.size = static_cast<uint32_t>(init_data->size());
+
+  OperationResult op_result;
+  op_result.result = TEEC_InvokeCommand(session, TA_STORAGE_CMD_CREATE, &op,
+                                        &op_result.return_origin);
+  ASSERT_TRUE(IsTeecSuccess(op_result));
+
+  *out_handle_guard = OpteeFileHandleGuard(session, op.params[1].value.b);
+}
+
+// Invokes the storage TA to open a file. Returns an object handle, if
+// successful.
+static void OpenFile(TEEC_Session* session, std::string name, uint32_t flags,
+                     OpteeFileHandleGuard* out_handle_guard) {
+  ASSERT_NE(session, nullptr);
+  ASSERT_NE(out_handle_guard, nullptr);
+
+  TEEC_Operation op = {};
+  op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, TEEC_VALUE_INOUT,
+                                   TEEC_VALUE_INPUT, TEEC_NONE);
+  op.params[2].value.a = kPrivateStorage;
+
+  op.params[0].tmpref.buffer = static_cast<void*>(name.data());
+  op.params[0].tmpref.size = name.size();
+
+  op.params[1].value.a = flags;
+
+  OperationResult op_result;
+  op_result.result = TEEC_InvokeCommand(session, TA_STORAGE_CMD_OPEN, &op,
+                                        &op_result.return_origin);
+  ASSERT_TRUE(IsTeecSuccess(op_result));
+
+  *out_handle_guard = OpteeFileHandleGuard(session, op.params[1].value.b);
+}
+
+// Invokes the storage TA to close a file.
+static void CloseFile(TEEC_Session* session,
+                      OpteeFileHandleGuard* handle_guard) {
+  ASSERT_NE(session, nullptr);
+  ASSERT_NE(handle_guard, nullptr);
+
+  TEEC_Operation op = {};
+  op.paramTypes =
+      TEEC_PARAM_TYPES(TEEC_VALUE_INPUT, TEEC_NONE, TEEC_NONE, TEEC_NONE);
+
+  op.params[0].value.a = handle_guard->GetHandle();
+
+  OperationResult op_result;
+  op_result.result = TEEC_InvokeCommand(session, TA_STORAGE_CMD_CLOSE, &op,
+                                        &op_result.return_origin);
+  EXPECT_TRUE(IsTeecSuccess(op_result));  // Okay to continue on failure
+
+  handle_guard->Release();
+}
+
+// Invokes the storage TA to read from a file. Returns the number of bytes read,
+// if successful.
+static void ReadFile(TEEC_Session* session,
+                     const OpteeFileHandleGuard& handle_guard,
+                     std::vector<uint8_t>* buffer) {
+  ASSERT_NE(session, nullptr);
+  ASSERT_NE(buffer, nullptr);
+
+  TEEC_Operation op = {};
+  op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_OUTPUT, TEEC_VALUE_INOUT,
+                                   TEEC_NONE, TEEC_NONE);
+  op.params[0].tmpref.buffer = static_cast<void*>(buffer->data());
+  op.params[0].tmpref.size = static_cast<uint32_t>(buffer->size());
+
+  op.params[1].value.a = handle_guard.GetHandle();
+
+  OperationResult op_result;
+  op_result.result = TEEC_InvokeCommand(session, TA_STORAGE_CMD_READ, &op,
+                                        &op_result.return_origin);
+  ASSERT_TRUE(IsTeecSuccess(op_result));
+
+  size_t bytes = static_cast<size_t>(op.params[1].value.b);
+  EXPECT_LE(bytes, buffer->size());
+
+  buffer->resize(bytes);
+}
+
+// Invokes the storage TA to write to a file. Returns the number of bytes
+// written, if successful.
+static void WriteFile(TEEC_Session* session,
+                      const OpteeFileHandleGuard& handle_guard,
+                      std::vector<uint8_t>* buffer) {
+  ASSERT_NE(session, nullptr);
+  ASSERT_NE(buffer, nullptr);
+
+  TEEC_Operation op = {};
+  op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, TEEC_VALUE_INPUT,
+                                   TEEC_NONE, TEEC_NONE);
+  op.params[0].tmpref.buffer = static_cast<void*>(buffer->data());
+  op.params[0].tmpref.size = static_cast<uint32_t>(buffer->size());
+
+  op.params[1].value.a = handle_guard.GetHandle();
+
+  OperationResult op_result;
+  op_result.result = TEEC_InvokeCommand(session, TA_STORAGE_CMD_WRITE, &op,
+                                        &op_result.return_origin);
+  EXPECT_TRUE(IsTeecSuccess(op_result));  // Okay to continue on failure
+}
+
+// Invokes the storage TA to seek in a file. If successful, returns the offset
+// from the beginning of the file.
+static void SeekFile(TEEC_Session* session,
+                     const OpteeFileHandleGuard& handle_guard, int32_t offset,
+                     SeekFrom whence, uint32_t* out_absolute_offset) {
+  ASSERT_NE(session, nullptr);
+
+  TEEC_Operation op = {};
+  op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INPUT, TEEC_VALUE_INOUT,
+                                   TEEC_NONE, TEEC_NONE);
+  op.params[0].value.a = handle_guard.GetHandle();
+
+  // Intentionally copy this int32_t to an uint32_t field, as the TA
+  // reinterprets these bits as an int32_t
+  static_assert(sizeof(offset) == sizeof(op.params[0].value.b));
+  std::memcpy(&op.params[0].value.b, &offset, sizeof(uint32_t));
+
+  op.params[1].value.a = static_cast<uint32_t>(whence);
+
+  OperationResult op_result;
+  op_result.result = TEEC_InvokeCommand(session, TA_STORAGE_CMD_SEEK, &op,
+                                        &op_result.return_origin);
+  ASSERT_TRUE(IsTeecSuccess(op_result));
+
+  // Since there are scenarios where the client may ignore this, check if null
+  if (out_absolute_offset != nullptr) {
+    *out_absolute_offset = op.params[1].value.b;
+  }
+}
+
+// Invokes the storage TA to unlink a file.
+static void UnlinkFile(TEEC_Session* session,
+                       OpteeFileHandleGuard* handle_guard) {
+  ASSERT_NE(session, nullptr);
+  ASSERT_NE(handle_guard, nullptr);
+
+  TEEC_Operation op = {};
+  op.paramTypes =
+      TEEC_PARAM_TYPES(TEEC_VALUE_INPUT, TEEC_NONE, TEEC_NONE, TEEC_NONE);
+  op.params[0].value.a = handle_guard->GetHandle();
+
+  OperationResult op_result;
+  op_result.result = TEEC_InvokeCommand(session, TA_STORAGE_CMD_UNLINK, &op,
+                                        &op_result.return_origin);
+  EXPECT_TRUE(IsTeecSuccess(op_result));  // Okay to continue on failure
+
+  handle_guard->Release();
+}
+
+void OpteeFileHandleGuard::Close() {
+  if (IsValid()) {
+    CloseFile(session_, this);
+    session_ = nullptr;
+    handle_ = std::nullopt;
+  }
+}
+
+class ContextGuard {
+ public:
+  ContextGuard() : context_(nullptr) {}
+
+  explicit ContextGuard(TEEC_Context* context) : context_(context) {}
+
+  ~ContextGuard() { Close(); }
+
+  ContextGuard(ContextGuard&& other) : context_(other.context_) {
+    other.context_ = nullptr;
+  }
+
+  ContextGuard& operator=(ContextGuard&& other) {
+    if (&other == this) {
+      return *this;
+    }
+
+    Close();
+    context_ = other.Release();
+    return *this;
+  }
+
+  ContextGuard(const ContextGuard&) = delete;
+  ContextGuard& operator=(const ContextGuard&) = delete;
+
+  constexpr bool IsValid() const { return context_ != nullptr; }
+
+  TEEC_Context* Get() const { return context_; }
+
+  void Close() {
+    if (IsValid()) {
+      TEEC_FinalizeContext(context_);
+      context_ = nullptr;
+    }
+  }
+
+  TEEC_Context* Release() {
+    TEEC_Context* released = context_;
+    context_ = nullptr;
+    return released;
+  }
+
+ private:
+  TEEC_Context* context_;
+};
+
+class SessionGuard {
+ public:
+  constexpr SessionGuard() : session_(nullptr) {}
+
+  constexpr explicit SessionGuard(TEEC_Session* session) : session_(session) {}
+
+  ~SessionGuard() { Close(); }
+
+  SessionGuard(SessionGuard&& other) : session_(other.session_) {
+    other.session_ = nullptr;
+  }
+
+  SessionGuard& operator=(SessionGuard&& other) {
+    if (&other == this) {
+      return *this;
+    }
+
+    Close();
+    session_ = other.Release();
+    return *this;
+  }
+
+  SessionGuard(const SessionGuard&) = delete;
+  SessionGuard& operator=(const SessionGuard&) = delete;
+
+  constexpr bool IsValid() const { return session_ != nullptr; }
+
+  TEEC_Session* Get() const { return session_; }
+
+  void Close() {
+    if (IsValid()) {
+      TEEC_CloseSession(session_);
+      session_ = nullptr;
+    }
+  }
+
+  TEEC_Session* Release() {
+    TEEC_Session* released = session_;
+    session_ = nullptr;
+    return released;
+  }
+
+ private:
+  TEEC_Session* session_;
+};
+
+}  // namespace
+
+class OpteeTest : public sys::testing::TestWithEnvironment {
+ protected:
+  void SetUp() override {
+    auto services = CreateServices();
+
+    fuchsia::sys::LaunchInfo launch_info{
+        "fuchsia-pkg://fuchsia.com/tee_manager#meta/tee_manager.cmx"};
+    zx_status_t status = services->AddServiceWithLaunchInfo(
+        std::move(launch_info), fuchsia::tee::Device::Name_);
+    ASSERT_EQ(status, ZX_OK);
+
+    environment_ =
+        CreateNewEnclosingEnvironment("optee_test", std::move(services));
+    WaitForEnclosingEnvToStart(environment_.get());
+
+    TEEC_Result result = TEEC_InitializeContext(nullptr, &context_);
+    ASSERT_TRUE(IsTeecSuccess(result));
+    context_guard_ = ContextGuard(&context_);
+
+    OperationResult op_result;
+    op_result.result =
+        TEEC_OpenSession(&context_, &session_, &kStorageUuid, TEEC_LOGIN_PUBLIC,
+                         nullptr, nullptr, &op_result.return_origin);
+    ASSERT_TRUE(IsTeecSuccess(op_result));
+    session_guard_ = SessionGuard(&session_);
+
+    std::vector<uint8_t> buffer = StringToBuffer(GetInitialFileContents());
+    OpteeFileHandleGuard handle_guard;
+    CreateFile(&session_, GetFileName(), &buffer, kFlagRead, &handle_guard);
+  }
+
+  void TearDown() override {
+    constexpr uint32_t kOpenFlags = kFlagRead | kFlagWrite | kFlagWriteMetadata;
+    OpteeFileHandleGuard handle_guard;
+    OpenFile(&session_, GetFileName(), kOpenFlags, &handle_guard);
+
+    UnlinkFile(&session_, &handle_guard);
+  }
+
+  static std::string GetFileName() {
+    static const std::string kFileName = "optee_test_file";
+    return kFileName;
+  }
+
+  static std::string GetInitialFileContents() {
+    static const std::string kInitialContents =
+        "the quick brown fox jumped over the lazy dog";
+    return kInitialContents;
+  }
+
+  TEEC_Session* GetSession() { return &session_; }
+
+ private:
+  static constexpr TEEC_UUID kStorageUuid = TA_STORAGE_UUID;
+
+  std::unique_ptr<sys::testing::EnclosingEnvironment> environment_;
+  TEEC_Context context_;
+  ContextGuard context_guard_;
+  TEEC_Session session_;
+  SessionGuard session_guard_;
+};
+
+TEST_F(OpteeTest, OpenFile) {
+  OpteeFileHandleGuard handle_guard;
+  OpenFile(GetSession(), GetFileName(), kFlagRead, &handle_guard);
+}
+
+TEST_F(OpteeTest, ReadFile) {
+  OpteeFileHandleGuard handle_guard;
+  OpenFile(GetSession(), GetFileName(), kFlagRead, &handle_guard);
+
+  constexpr size_t kBufferSize = 128;
+  std::vector<uint8_t> buffer(kBufferSize, 0);
+  ReadFile(GetSession(), handle_guard, &buffer);
+
+  std::string read_contents = BufferToString(buffer);
+  EXPECT_EQ(read_contents, GetInitialFileContents());
+}
+
+TEST_F(OpteeTest, WriteFile) {
+  constexpr uint32_t kOpenFlags = kFlagRead | kFlagWrite | kFlagWriteMetadata;
+  OpteeFileHandleGuard handle_guard;
+  OpenFile(GetSession(), GetFileName(), kOpenFlags, &handle_guard);
+
+  static const std::string kNewFileContents(
+      "how much wood would a woodchuck chuck if a woodchuck could chuck wood?");
+  ASSERT_GE(kNewFileContents.size(), GetInitialFileContents().size());
+
+  std::vector<uint8_t> buffer = StringToBuffer(kNewFileContents);
+  WriteFile(GetSession(), handle_guard, &buffer);
+}
+
+TEST_F(OpteeTest, WriteAndReadFile) {
+  constexpr uint32_t kOpenFlags = kFlagRead | kFlagWrite | kFlagWriteMetadata;
+  static const std::string kNewFileContents(
+      "how much wood would a woodchuck chuck if a woodchuck could chuck wood?");
+  ASSERT_GE(kNewFileContents.size(), GetInitialFileContents().size());
+  std::vector<uint8_t> buffer;
+
+  {
+    OpteeFileHandleGuard handle_guard;
+    OpenFile(GetSession(), GetFileName(), kOpenFlags, &handle_guard);
+
+    buffer = StringToBuffer(kNewFileContents);
+    WriteFile(GetSession(), handle_guard, &buffer);
+  }
+
+  {
+    OpteeFileHandleGuard handle_guard;
+    OpenFile(GetSession(), GetFileName(), kOpenFlags, &handle_guard);
+    constexpr size_t kBufferSize = 128;
+    buffer.assign(kBufferSize, 0);
+    ReadFile(GetSession(), handle_guard, &buffer);
+
+    std::string read_contents = BufferToString(buffer);
+    EXPECT_EQ(read_contents, kNewFileContents);
+  }
+}
+
+TEST_F(OpteeTest, SeekWriteReadFile) {
+  constexpr uint32_t kOpenFlags = kFlagRead | kFlagWrite | kFlagWriteMetadata;
+  static const std::string kStringToAppend = "!";
+
+  OpteeFileHandleGuard handle_guard;
+  OpenFile(GetSession(), GetFileName(), kOpenFlags, &handle_guard);
+
+  // Seek to the end of the file
+  uint32_t absolute_offset = 0;
+  SeekFile(GetSession(), handle_guard, 0, SeekFrom::kEnd, &absolute_offset);
+  EXPECT_EQ(absolute_offset, GetInitialFileContents().size());
+
+  // Append an exclamation point to the file
+  std::vector<uint8_t> buffer = StringToBuffer(kStringToAppend);
+  WriteFile(GetSession(), handle_guard, &buffer);
+
+  // Seek to the beginning of the file
+  absolute_offset = std::numeric_limits<decltype(absolute_offset)>::max();
+  SeekFile(GetSession(), handle_guard, 0, SeekFrom::kBeginning,
+           &absolute_offset);
+  EXPECT_EQ(absolute_offset, 0u);
+
+  // Check the new string
+  const std::string kNewFileContents =
+      GetInitialFileContents() + kStringToAppend;
+  constexpr size_t kBufferSize = 128;
+  buffer.assign(kBufferSize, 0);  // Zero out and resize the buffer
+  ReadFile(GetSession(), handle_guard, &buffer);
+  std::string read_contents = BufferToString(buffer);
+  EXPECT_EQ(read_contents, kNewFileContents);
+}
+
+}  // namespace test
+}  // namespace optee
\ No newline at end of file
diff --git a/zircon/system/dev/tee/optee/optee-client.cpp b/zircon/system/dev/tee/optee/optee-client.cpp
index 1aee96a79e9a4c6184bb812e8a29c870d137b1d6..003eb15e38956bee6e25d3fedb0dee28f272edf4 100644
--- a/zircon/system/dev/tee/optee/optee-client.cpp
+++ b/zircon/system/dev/tee/optee/optee-client.cpp
@@ -2,13 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <algorithm>
+#include <libgen.h>
+#include <string>
 #include <utility>
 
 #include <ddk/debug.h>
 #include <fbl/string_buffer.h>
-#include <fbl/string_piece.h>
 #include <fbl/vector.h>
+#include <fuchsia/io/c/fidl.h>
+#include <fuchsia/tee/manager/c/fidl.h>
 #include <lib/fidl-utils/bind.h>
+#include <lib/fidl/coding.h>
+#include <lib/zx/handle.h>
 #include <lib/zx/time.h>
 #include <lib/zx/vmo.h>
 #include <tee-client-api/tee-client-types.h>
@@ -122,6 +128,227 @@ static zx_status_t ConvertOpteeToZxResult(uint32_t optee_return_code, uint32_t o
     return ZX_OK;
 }
 
+static std::filesystem::path GetPathFromRawMemory(void* mem, size_t max_size) {
+    ZX_DEBUG_ASSERT(mem != nullptr);
+    ZX_DEBUG_ASSERT(max_size > 0);
+
+    auto path = static_cast<char*>(mem);
+
+    // Copy the string out from raw memory first
+    std::string result(path, max_size);
+
+    // Trim string to first null terminating character
+    auto null_pos = result.find('\0');
+    if (null_pos != std::string::npos) {
+        result.resize(null_pos);
+    }
+
+    return std::filesystem::path(std::move(result)).lexically_relative("/");
+}
+
+// Awaits the `fuchsia.io.Node/OnOpen` event that is fired when opening with
+// `fuchsia.io.OPEN_FLAG_DESCRIBE` flag and returns the status contained in the event.
+//
+// This is useful for synchronously awaiting the result of an `Open` request.
+//
+// TODO(godtamit): Transition to receiving an event normally once this client code can use FIDL C++
+// generated bindings that support events directly.
+static zx_status_t AwaitIoOnOpenStatus(const zx::channel& channel) {
+    zx_signals_t observed_signals = 0;
+    zx_status_t status = channel.wait_one(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED,
+                                          zx::time::infinite(), &observed_signals);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "optee::%s: failed to wait on channel (status: %d)\n", __FUNCTION__, status);
+        return status;
+    }
+
+    // Intentionally allow `ZX_CHANNEL_PEER_CLOSED` to take precedence over `ZX_CHANNEL_READABLE`
+    // since it indicates an error occurred.
+    if ((observed_signals & ZX_CHANNEL_PEER_CLOSED) == ZX_CHANNEL_PEER_CLOSED) {
+        zxlogf(ERROR, "optee::%s: channel closed\n", __FUNCTION__);
+
+        // TODO(godtamit): check for an epitaph here once `fuchsia.io` supports it
+        return ZX_ERR_NOT_FOUND;
+    }
+
+    // Sanity check to make sure `ZX_CHANNEL_READABLE` was the signal observed
+    ZX_DEBUG_ASSERT((observed_signals & ZX_CHANNEL_READABLE) == ZX_CHANNEL_READABLE);
+
+    // Test to see how big the message is
+    uint32_t actual_bytes;
+    zx::handle handle;
+    status = channel.read(0,             // flags
+                          nullptr,       // bytes
+                          nullptr,       // handles
+                          0,             // num_bytes
+                          0,             // num_handles
+                          &actual_bytes, // actual_bytes
+                          nullptr);      // actual_handles
+    if (status != ZX_ERR_BUFFER_TOO_SMALL) {
+        zxlogf(ERROR,
+               "optee::%s: received unexpecting error while testing for channel message size "
+               "(status: %d)\n",
+               __FUNCTION__, status);
+        return status == ZX_OK ? ZX_ERR_INTERNAL : status;
+    }
+
+    uint32_t buffer_size = actual_bytes;
+    std::unique_ptr<uint8_t[]> buffer(new uint8_t[static_cast<size_t>(buffer_size)]);
+    status = channel.read(0,                              // flags
+                          buffer.get(),                   // bytes
+                          handle.reset_and_get_address(), // handles
+                          buffer_size,                    // num_bytes
+                          1,                              // num_handles
+                          &actual_bytes,                  // actual_bytes
+                          nullptr);                       // actual_handles
+    if (status != ZX_OK) {
+        zxlogf(ERROR,
+               "optee::%s: received unexpecting error while reading channel message (status: %d)\n",
+               __FUNCTION__, status);
+        return status;
+    }
+
+    auto header = reinterpret_cast<fidl_message_header_t*>(buffer.get());
+    if (header->ordinal != fuchsia_io_NodeOnOpenOrdinal) {
+        // The `OnOpen` event should be the first event fired. See the function description for
+        // preconditions and details.
+        zxlogf(ERROR, "optee::%s: received unexpected message ordinal %x\n",
+               __FUNCTION__, header->ordinal);
+        return ZX_ERR_PROTOCOL_NOT_SUPPORTED;
+    }
+
+    const char* err = nullptr;
+    status =
+        fidl_decode(&fuchsia_io_NodeOnOpenEventTable, buffer.get(), actual_bytes, nullptr, 0, &err);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "optee::%s: failed to decode fuchsia.io.Node/OnOpen event: %s (status: %d)\n",
+               __FUNCTION__, err != nullptr ? err : "", status);
+        return status;
+    }
+
+    auto on_open_event = reinterpret_cast<fuchsia_io_NodeOnOpenEvent*>(buffer.get());
+    return on_open_event->s;
+}
+
+// Calls `fuchsia.io.Directory/Open` on a channel and awaits the result.
+static zx_status_t OpenObjectInDirectory(const zx::channel& root_channel,
+                                         uint32_t flags,
+                                         uint32_t mode,
+                                         std::string path,
+                                         zx::channel* out_channel_node) {
+    ZX_DEBUG_ASSERT(out_channel_node != nullptr);
+
+    // Ensure `OPEN_FLAG_DESCRIBE` is passed
+    flags |= fuchsia_io_OPEN_FLAG_DESCRIBE;
+
+    // Create temporary channel ends to make FIDL call
+    zx::channel channel_client_end;
+    zx::channel channel_server_end;
+    zx_status_t status = zx::channel::create(0, &channel_client_end, &channel_server_end);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "optee::%s: failed to create channel pair (status: %d)\n",
+               __FUNCTION__, status);
+        return status;
+    }
+
+    status = fuchsia_io_DirectoryOpen(root_channel.get(),            // _channel
+                                      flags,                         // flags
+                                      mode,                          // mode
+                                      path.data(),                   // path_data
+                                      path.size(),                   // path_size
+                                      channel_server_end.release()); // object
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "optee::%s: could not call fuchsia.io.Directory/Open (status: %d)\n",
+               __FUNCTION__, status);
+        return status;
+    }
+
+    status = AwaitIoOnOpenStatus(channel_client_end);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    *out_channel_node = std::move(channel_client_end);
+    return ZX_OK;
+}
+
+// Recursively walks down a multi-part path, opening and outputting the final destination.
+//
+// Template Parameters:
+//  * kOpenFlags: The flags to call `fuchsia.io.Directory/Open` with. This must not contain
+//               `OPEN_FLAG_NOT_DIRECTORY`.
+// Parameters:
+//  * root_channel:     The channel to the directory to start the walk from.
+//  * path:             The path relative to `root_channel` to open.
+//  * out_node_channel: Where to store the resulting `fuchsia.io.Node` channel opened.
+template <uint32_t kOpenFlags>
+static zx_status_t RecursivelyWalkPath(const zx::unowned_channel& root_channel,
+                                       std::filesystem::path path,
+                                       zx::channel* out_node_channel) {
+    static_assert((kOpenFlags & fuchsia_io_OPEN_FLAG_NOT_DIRECTORY) == 0,
+                  "kOpenFlags must not include fuchsia_io_OPEN_FLAG_NOT_DIRECTORY");
+    ZX_DEBUG_ASSERT(root_channel->is_valid());
+    ZX_DEBUG_ASSERT(!path.empty());
+    ZX_DEBUG_ASSERT(out_node_channel != nullptr);
+
+    zx_status_t status;
+    zx::channel result_channel;
+
+    if (path == std::filesystem::path(".")) {
+        // If the path is lexicographically equivalent to the (relative) root directory, clone the
+        // root channel instead of opening the path
+        zx::channel server_channel;
+        status = zx::channel::create(0, &result_channel, &server_channel);
+        if (status != ZX_OK) {
+            return status;
+        }
+
+        status = fuchsia_io_DirectoryClone(root_channel->get(),
+                                           fuchsia_io_CLONE_FLAG_SAME_RIGHTS,
+                                           server_channel.release());
+        if (status != ZX_OK) {
+            return status;
+        }
+    } else {
+        zx::unowned_channel current_channel(root_channel);
+        for (const auto& component : path) {
+            zx::channel temporary_channel;
+            static constexpr uint32_t kOpenMode = fuchsia_io_MODE_TYPE_DIRECTORY;
+            status = OpenObjectInDirectory(*current_channel,
+                                           kOpenFlags,
+                                           kOpenMode,
+                                           component.string(),
+                                           &temporary_channel);
+            if (status != ZX_OK) {
+                return status;
+            }
+
+            result_channel = std::move(temporary_channel);
+            current_channel = zx::unowned(result_channel);
+        }
+    }
+
+    *out_node_channel = std::move(result_channel);
+    return ZX_OK;
+}
+
+template <typename... Args>
+static inline zx_status_t CreateDirectory(Args&&... args) {
+    static constexpr uint32_t kCreateFlags = fuchsia_io_OPEN_RIGHT_READABLE |
+                                             fuchsia_io_OPEN_RIGHT_WRITABLE |
+                                             fuchsia_io_OPEN_FLAG_CREATE |
+                                             fuchsia_io_OPEN_FLAG_DIRECTORY;
+    return RecursivelyWalkPath<kCreateFlags>(std::forward<Args>(args)...);
+}
+
+template <typename... Args>
+static inline zx_status_t OpenDirectory(Args&&... args) {
+    static constexpr uint32_t kOpenFlags = fuchsia_io_OPEN_RIGHT_READABLE |
+                                           fuchsia_io_OPEN_RIGHT_WRITABLE |
+                                           fuchsia_io_OPEN_FLAG_DIRECTORY;
+    return RecursivelyWalkPath<kOpenFlags>(std::forward<Args>(args)...);
+}
+
 } // namespace
 
 namespace optee {
@@ -373,6 +600,97 @@ OpteeClient::SharedMemoryList::iterator OpteeClient::FindSharedMemory(uint64_t m
         });
 }
 
+std::optional<SharedMemoryView> OpteeClient::GetMemoryReference(SharedMemoryList::iterator mem_iter,
+                                                                zx_paddr_t base_paddr,
+                                                                size_t size) {
+
+    std::optional<SharedMemoryView> result;
+    if (!mem_iter.IsValid() ||
+        !(result = mem_iter->SliceByPaddr(base_paddr, base_paddr + size)).has_value()) {
+        zxlogf(ERROR, "optee: received invalid shared memory region reference\n");
+    }
+    return result;
+}
+
+zx_status_t OpteeClient::GetRootStorageChannel(zx::unowned_channel* out_root_channel) {
+    ZX_DEBUG_ASSERT(out_root_channel != nullptr);
+
+    if (!service_provider_channel_.is_valid()) {
+        return ZX_ERR_UNAVAILABLE;
+    }
+    if (root_storage_channel_.is_valid()) {
+        *out_root_channel = zx::unowned_channel(root_storage_channel_);
+        return ZX_OK;
+    }
+
+    zx::channel client_channel;
+    zx::channel server_channel;
+    zx_status_t status = zx::channel::create(0, &client_channel, &server_channel);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    status = fuchsia_tee_manager_ServiceProviderRequestPersistentStorage(
+        service_provider_channel_.get(),
+        server_channel.release());
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    root_storage_channel_ = std::move(client_channel);
+    *out_root_channel = zx::unowned_channel(root_storage_channel_);
+    return ZX_OK;
+}
+
+zx_status_t OpteeClient::GetStorageDirectory(std::filesystem::path path,
+                                             bool create,
+                                             zx::channel* out_storage_channel) {
+    ZX_DEBUG_ASSERT(out_storage_channel != nullptr);
+
+    zx::unowned_channel root_channel;
+    zx_status_t status = GetRootStorageChannel(&root_channel);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    zx::channel storage_channel;
+
+    if (create) {
+        status = CreateDirectory(root_channel, path, &storage_channel);
+    } else {
+        status = OpenDirectory(root_channel, path, &storage_channel);
+    }
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    *out_storage_channel = std::move(storage_channel);
+    return ZX_OK;
+}
+
+uint64_t OpteeClient::TrackFileSystemObject(zx::channel io_node_channel) {
+    uint64_t object_id = next_file_system_object_id_.fetch_add(1, std::memory_order_relaxed);
+    // TODO(godtamit): Move to `std::unordered_map` when BLD-413 is complete
+    open_file_system_objects_.insert(
+        std::make_unique<FileSystemObject>(object_id, std::move(io_node_channel)));
+
+    return object_id;
+}
+
+std::optional<zx::unowned_channel>
+OpteeClient::GetFileSystemObjectChannel(uint64_t identifier) {
+    auto iter = open_file_system_objects_.find(identifier);
+    if (iter == open_file_system_objects_.end()) {
+        return std::nullopt;
+    }
+    return zx::unowned_channel(iter->channel);
+}
+
+bool OpteeClient::UntrackFileSystemObject(uint64_t identifier) {
+    std::unique_ptr<FileSystemObject> erased_object = open_file_system_objects_.erase(identifier);
+    return erased_object != nullptr;
+}
+
 zx_status_t OpteeClient::HandleRpc(const RpcFunctionArgs& args, RpcFunctionResult* out_result) {
     zx_status_t status;
     uint32_t func_code = GetRpcFunctionCode(args.generic.status);
@@ -474,7 +792,7 @@ zx_status_t OpteeClient::HandleRpcCommand(const RpcFunctionExecuteCommandsArgs&
         if (!fs_msg.is_valid()) {
             return ZX_ERR_INVALID_ARGS;
         }
-        return HandleRpcCommandFileSystem(&fs_msg);
+        return HandleRpcCommandFileSystem(std::move(fs_msg));
     }
     case RpcMessage::Command::kGetTime: {
         GetTimeRpcMessage get_time_msg(std::move(message));
@@ -530,12 +848,10 @@ zx_status_t OpteeClient::HandleRpcCommandLoadTa(LoadTaRpcMessage* message) {
     std::optional<SharedMemoryView> out_ta_mem; // Where to write the TA in memory
 
     if (message->memory_reference_id() != 0) {
-        SharedMemoryList::iterator mem_iter = FindSharedMemory(message->memory_reference_id());
-        zx_paddr_t lower_paddr = message->memory_reference_paddr();
-        zx_paddr_t upper_paddr = lower_paddr + message->memory_reference_size();
-        if (!mem_iter.IsValid() ||
-            !(out_ta_mem = mem_iter->SliceByPaddr(lower_paddr, upper_paddr)).has_value()) {
-            zxlogf(ERROR, "optee: received invalid shared memory region reference\n");
+        out_ta_mem = GetMemoryReference(FindSharedMemory(message->memory_reference_id()),
+                                        message->memory_reference_paddr(),
+                                        message->memory_reference_size());
+        if (!out_ta_mem.has_value()) {
             message->set_return_code(TEEC_ERROR_BAD_PARAMETERS);
             return ZX_ERR_INVALID_ARGS;
         }
@@ -614,8 +930,8 @@ zx_status_t OpteeClient::HandleRpcCommandGetTime(GetTimeRpcMessage* message) {
         return status;
     }
 
-    static constexpr const zx::duration kDurationSecond = zx::duration(zx_duration_from_sec(1));
-    static constexpr const zx::time_utc kUtcEpoch = zx::time_utc(0);
+    static constexpr zx::duration kDurationSecond = zx::sec(1);
+    static constexpr zx::time_utc kUtcEpoch = zx::time_utc(0);
 
     zx::duration now_since_epoch = now - kUtcEpoch;
     auto seconds = static_cast<uint64_t>(now_since_epoch / kDurationSecond);
@@ -686,35 +1002,75 @@ zx_status_t OpteeClient::HandleRpcCommandFreeMemory(FreeMemoryRpcMessage* messag
     return status;
 }
 
-zx_status_t OpteeClient::HandleRpcCommandFileSystem(FileSystemRpcMessage* message) {
-    ZX_DEBUG_ASSERT(message != nullptr);
-    ZX_DEBUG_ASSERT(message->is_valid());
+zx_status_t OpteeClient::HandleRpcCommandFileSystem(FileSystemRpcMessage&& message) {
+    ZX_DEBUG_ASSERT(message.is_valid());
 
-    switch (message->command()) {
-    case FileSystemRpcMessage::FileSystemCommand::kOpenFile:
-        zxlogf(ERROR, "optee: RPC command to open file recognized but not implemented\n");
-        break;
-    case FileSystemRpcMessage::FileSystemCommand::kCreateFile:
-        zxlogf(ERROR, "optee: RPC command to create file recognized but not implemented\n");
-        break;
-    case FileSystemRpcMessage::FileSystemCommand::kCloseFile:
-        zxlogf(ERROR, "optee: RPC command to close file recognized but not implemented\n");
-        break;
-    case FileSystemRpcMessage::FileSystemCommand::kReadFile:
-        zxlogf(ERROR, "optee: RPC command to read file recognized but not implemented\n");
-        break;
-    case FileSystemRpcMessage::FileSystemCommand::kWriteFile:
-        zxlogf(ERROR, "optee: RPC command to write file recognized but not implemented\n");
-        break;
-    case FileSystemRpcMessage::FileSystemCommand::kTruncateFile:
-        zxlogf(ERROR, "optee: RPC command to truncate file recognized but not implemented\n");
-        break;
-    case FileSystemRpcMessage::FileSystemCommand::kRemoveFile:
-        zxlogf(ERROR, "optee: RPC command to remove file recognized but not implemented\n");
-        break;
-    case FileSystemRpcMessage::FileSystemCommand::kRenameFile:
-        zxlogf(ERROR, "optee: RPC command to rename file recognized but not implemented\n");
-        break;
+    // Mark that the return code will originate from driver
+    message.set_return_origin(TEEC_ORIGIN_COMMS);
+
+    if (!service_provider_channel_.is_valid()) {
+        // Client did not connect with a ServiceProvider so none of these RPCs can be serviced
+        message.set_return_code(TEEC_ERROR_BAD_STATE);
+        return ZX_ERR_UNAVAILABLE;
+    }
+
+    switch (message.file_system_command()) {
+    case FileSystemRpcMessage::FileSystemCommand::kOpenFile: {
+        OpenFileFileSystemRpcMessage open_file_msg(std::move(message));
+        if (!open_file_msg.is_valid()) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+        return HandleRpcCommandFileSystemOpenFile(&open_file_msg);
+    }
+    case FileSystemRpcMessage::FileSystemCommand::kCreateFile: {
+        CreateFileFileSystemRpcMessage create_file_msg(std::move(message));
+        if (!create_file_msg.is_valid()) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+        return HandleRpcCommandFileSystemCreateFile(&create_file_msg);
+    }
+    case FileSystemRpcMessage::FileSystemCommand::kCloseFile: {
+        CloseFileFileSystemRpcMessage close_file_msg(std::move(message));
+        if (!close_file_msg.is_valid()) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+        return HandleRpcCommandFileSystemCloseFile(&close_file_msg);
+    }
+    case FileSystemRpcMessage::FileSystemCommand::kReadFile: {
+        ReadFileFileSystemRpcMessage read_file_msg(std::move(message));
+        if (!read_file_msg.is_valid()) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+        return HandleRpcCommandFileSystemReadFile(&read_file_msg);
+    }
+    case FileSystemRpcMessage::FileSystemCommand::kWriteFile: {
+        WriteFileFileSystemRpcMessage write_file_msg(std::move(message));
+        if (!write_file_msg.is_valid()) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+        return HandleRpcCommandFileSystemWriteFile(&write_file_msg);
+    }
+    case FileSystemRpcMessage::FileSystemCommand::kTruncateFile: {
+        TruncateFileFileSystemRpcMessage truncate_file_msg(std::move(message));
+        if (!truncate_file_msg.is_valid()) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+        return HandleRpcCommandFileSystemTruncateFile(&truncate_file_msg);
+    }
+    case FileSystemRpcMessage::FileSystemCommand::kRemoveFile: {
+        RemoveFileFileSystemRpcMessage remove_file_msg(std::move(message));
+        if (!remove_file_msg.is_valid()) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+        return HandleRpcCommandFileSystemRemoveFile(&remove_file_msg);
+    }
+    case FileSystemRpcMessage::FileSystemCommand::kRenameFile: {
+        RenameFileFileSystemRpcMessage rename_file_msg(std::move(message));
+        if (!rename_file_msg.is_valid()) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+        return HandleRpcCommandFileSystemRenameFile(&rename_file_msg);
+    }
     case FileSystemRpcMessage::FileSystemCommand::kOpenDirectory:
         zxlogf(ERROR, "optee: RPC command to open directory recognized but not implemented\n");
         break;
@@ -727,7 +1083,419 @@ zx_status_t OpteeClient::HandleRpcCommandFileSystem(FileSystemRpcMessage* messag
         break;
     }
 
-    message->set_return_code(TEEC_ERROR_NOT_SUPPORTED);
+    message.set_return_code(TEEC_ERROR_NOT_SUPPORTED);
+    return ZX_OK;
+}
+
+zx_status_t OpteeClient::HandleRpcCommandFileSystemOpenFile(OpenFileFileSystemRpcMessage* message) {
+    ZX_DEBUG_ASSERT(message != nullptr);
+    ZX_DEBUG_ASSERT(message->is_valid());
+    ZX_DEBUG_ASSERT(service_provider_channel_.is_valid());
+
+    zxlogf(SPEW, "optee: received RPC to open file\n");
+
+    SharedMemoryList::iterator mem_iter = FindSharedMemory(message->path_memory_identifier());
+    std::optional<SharedMemoryView> path_mem = GetMemoryReference(mem_iter,
+                                                                  message->path_memory_paddr(),
+                                                                  message->path_memory_size());
+    if (!path_mem.has_value()) {
+        message->set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    std::filesystem::path path = GetPathFromRawMemory(reinterpret_cast<void*>(path_mem->vaddr()),
+                                                      message->path_memory_size());
+
+    zx::channel storage_channel;
+    constexpr bool kNoCreate = false;
+    zx_status_t status = GetStorageDirectory(path.parent_path(), kNoCreate, &storage_channel);
+    if (status != ZX_OK) {
+        message->set_return_code(TEEC_ERROR_BAD_STATE);
+        return status;
+    }
+
+    zx::channel file_channel;
+    static constexpr uint32_t kOpenFlags = fuchsia_io_OPEN_RIGHT_READABLE |
+                                           fuchsia_io_OPEN_RIGHT_WRITABLE |
+                                           fuchsia_io_OPEN_FLAG_NOT_DIRECTORY |
+                                           fuchsia_io_OPEN_FLAG_DESCRIBE;
+    static constexpr uint32_t kOpenMode = fuchsia_io_MODE_TYPE_FILE;
+    status = OpenObjectInDirectory(storage_channel, kOpenFlags, kOpenMode, path.filename().string(),
+                                   &file_channel);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "optee::%s: unable to open file (status: %d)\n", __FUNCTION__, status);
+        message->set_return_code(TEEC_ERROR_GENERIC);
+        return status;
+    }
+
+    uint64_t object_id = TrackFileSystemObject(std::move(file_channel));
+
+    message->set_output_file_system_object_identifier(object_id);
+    message->set_return_code(TEEC_SUCCESS);
+    return ZX_OK;
+}
+
+zx_status_t OpteeClient::HandleRpcCommandFileSystemCreateFile(
+    CreateFileFileSystemRpcMessage* message) {
+    ZX_DEBUG_ASSERT(message != nullptr);
+    ZX_DEBUG_ASSERT(message->is_valid());
+
+    zxlogf(SPEW, "optee: received RPC to create file\n");
+
+    std::optional<SharedMemoryView> path_mem = GetMemoryReference(
+        FindSharedMemory(message->path_memory_identifier()),
+        message->path_memory_paddr(),
+        message->path_memory_size());
+    if (!path_mem.has_value()) {
+        message->set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    std::filesystem::path path = GetPathFromRawMemory(reinterpret_cast<void*>(path_mem->vaddr()),
+                                                      message->path_memory_size());
+
+    zx::channel storage_channel;
+    constexpr bool kCreate = true;
+    zx_status_t status = GetStorageDirectory(path.parent_path(), kCreate, &storage_channel);
+    if (status != ZX_OK) {
+        message->set_return_code(TEEC_ERROR_BAD_STATE);
+        return status;
+    }
+
+    zx::channel file_channel;
+    static constexpr uint32_t kCreateFlags = fuchsia_io_OPEN_RIGHT_READABLE |
+                                             fuchsia_io_OPEN_RIGHT_WRITABLE |
+                                             fuchsia_io_OPEN_FLAG_CREATE |
+                                             fuchsia_io_OPEN_FLAG_CREATE_IF_ABSENT |
+                                             fuchsia_io_OPEN_FLAG_DESCRIBE;
+    static constexpr uint32_t kCreateMode = fuchsia_io_MODE_TYPE_FILE;
+    status = OpenObjectInDirectory(storage_channel, kCreateFlags, kCreateMode,
+                                   path.filename().string(), &file_channel);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "optee::%s: unable to create file (status: %d)\n", __FUNCTION__, status);
+        message->set_return_code(status == ZX_ERR_ALREADY_EXISTS ? TEEC_ERROR_ACCESS_CONFLICT
+                                                                 : TEEC_ERROR_GENERIC);
+        return status;
+    }
+
+    uint64_t object_id = TrackFileSystemObject(std::move(file_channel));
+
+    message->set_output_file_system_object_identifier(object_id);
+    message->set_return_code(TEEC_SUCCESS);
+    return ZX_OK;
+}
+
+zx_status_t OpteeClient::HandleRpcCommandFileSystemCloseFile(
+    CloseFileFileSystemRpcMessage* message) {
+    ZX_DEBUG_ASSERT(message != nullptr);
+    ZX_DEBUG_ASSERT(message->is_valid());
+
+    zxlogf(SPEW, "optee: received RPC to close file\n");
+
+    if (!UntrackFileSystemObject(message->file_system_object_identifier())) {
+        zxlogf(ERROR, "optee: could not find the requested file to close\n");
+        message->set_return_code(TEEC_ERROR_ITEM_NOT_FOUND);
+        return ZX_ERR_NOT_FOUND;
+    }
+
+    message->set_return_code(TEEC_SUCCESS);
+    return ZX_OK;
+}
+
+zx_status_t OpteeClient::HandleRpcCommandFileSystemReadFile(ReadFileFileSystemRpcMessage* message) {
+    ZX_DEBUG_ASSERT(message != nullptr);
+    ZX_DEBUG_ASSERT(message->is_valid());
+
+    zxlogf(SPEW, "optee: received RPC to read from file\n");
+
+    auto maybe_file_channel = GetFileSystemObjectChannel(message->file_system_object_identifier());
+    if (!maybe_file_channel.has_value()) {
+        message->set_return_code(TEEC_ERROR_ITEM_NOT_FOUND);
+        return ZX_ERR_NOT_FOUND;
+    }
+
+    zx::unowned_channel file_channel(std::move(*maybe_file_channel));
+
+    std::optional<SharedMemoryView> buffer_mem = GetMemoryReference(
+        FindSharedMemory(message->file_contents_memory_identifier()),
+        message->file_contents_memory_paddr(),
+        message->file_contents_memory_size());
+    if (!buffer_mem.has_value()) {
+        message->set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    zx_status_t status = ZX_OK;
+    zx_status_t io_status = ZX_OK;
+    uint8_t* buffer = reinterpret_cast<uint8_t*>(buffer_mem->vaddr());
+    uint64_t offset = message->file_offset();
+    size_t bytes_left = buffer_mem->size();
+    size_t bytes_read = 0;
+    while (bytes_left > 0) {
+        uint64_t read_chunk_request = std::min(bytes_left, fuchsia_io_MAX_BUF);
+        uint64_t read_chunk_actual = 0;
+        status = fuchsia_io_FileReadAt(file_channel->get(), // _channel
+                                       read_chunk_request,  // count
+                                       offset,              // offset
+                                       &io_status,          // out_s
+                                       buffer,              // data_buffer
+                                       read_chunk_request,  // data_capacity
+                                       &read_chunk_actual); // out_actual
+        buffer += read_chunk_actual;
+        offset += read_chunk_actual;
+        bytes_left -= read_chunk_actual;
+        bytes_read += read_chunk_actual;
+
+        if (status != ZX_OK || io_status != ZX_OK) {
+            zxlogf(ERROR, "optee::%s failed to read from file (FIDL status: %d, IO status: %d)\n",
+                   __FUNCTION__, status, io_status);
+            message->set_return_code(TEEC_ERROR_GENERIC);
+            return status;
+        }
+
+        if (read_chunk_actual == 0) {
+            break;
+        }
+    }
+
+    message->set_output_file_contents_size(bytes_read);
+    message->set_return_code(TEEC_SUCCESS);
+    return ZX_OK;
+}
+
+zx_status_t OpteeClient::HandleRpcCommandFileSystemWriteFile(
+    WriteFileFileSystemRpcMessage* message) {
+    ZX_DEBUG_ASSERT(message != nullptr);
+    ZX_DEBUG_ASSERT(message->is_valid());
+
+    zxlogf(SPEW, "optee: received RPC to write file\n");
+
+    auto maybe_file_channel = GetFileSystemObjectChannel(message->file_system_object_identifier());
+    if (!maybe_file_channel.has_value()) {
+        message->set_return_code(TEEC_ERROR_ITEM_NOT_FOUND);
+        return ZX_ERR_NOT_FOUND;
+    }
+
+    zx::unowned_channel file_channel(std::move(*maybe_file_channel));
+
+    std::optional<SharedMemoryView> buffer_mem = GetMemoryReference(
+        FindSharedMemory(message->file_contents_memory_identifier()),
+        message->file_contents_memory_paddr(),
+        message->file_contents_memory_size());
+    if (!buffer_mem.has_value()) {
+        message->set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    zx_status_t status = ZX_OK;
+    zx_status_t io_status = ZX_OK;
+    uint8_t* buffer = reinterpret_cast<uint8_t*>(buffer_mem->vaddr());
+    uint64_t offset = message->file_offset();
+    size_t bytes_left = message->file_contents_memory_size();
+    while (bytes_left > 0) {
+        uint64_t write_chunk_request = std::min(bytes_left, fuchsia_io_MAX_BUF);
+        uint64_t write_chunk_actual = 0;
+        status = fuchsia_io_FileWriteAt(file_channel->get(),  // _channel
+                                        buffer,               // data_data
+                                        write_chunk_request,  // data_count
+                                        offset,               // offset
+                                        &io_status,           // out_s
+                                        &write_chunk_actual); // out_actual
+        buffer += write_chunk_actual;
+        offset += write_chunk_actual;
+        bytes_left -= write_chunk_actual;
+
+        if (status != ZX_OK || io_status != ZX_OK) {
+            zxlogf(ERROR, "optee::%s failed to write to file (FIDL status: %d, IO status: %d)\n",
+                   __FUNCTION__, status, io_status);
+            message->set_return_code(TEEC_ERROR_GENERIC);
+            return status;
+        }
+    }
+
+    message->set_return_code(TEEC_SUCCESS);
+    return ZX_OK;
+}
+
+zx_status_t OpteeClient::HandleRpcCommandFileSystemTruncateFile(
+    TruncateFileFileSystemRpcMessage* message) {
+    ZX_DEBUG_ASSERT(message != nullptr);
+    ZX_DEBUG_ASSERT(message->is_valid());
+
+    zxlogf(SPEW, "optee: received RPC to truncate file\n");
+
+    auto maybe_file_channel = GetFileSystemObjectChannel(message->file_system_object_identifier());
+    if (!maybe_file_channel.has_value()) {
+        message->set_return_code(TEEC_ERROR_ITEM_NOT_FOUND);
+        return ZX_ERR_NOT_FOUND;
+    }
+
+    zx::unowned_channel file_channel(std::move(*maybe_file_channel));
+
+    zx_status_t io_status = ZX_OK;
+    zx_status_t status = fuchsia_io_FileTruncate(file_channel->get(),         // _channel
+                                                 message->target_file_size(), // length
+                                                 &io_status);                 // out_s
+    if (status != ZX_OK || io_status != ZX_OK) {
+        zxlogf(ERROR, "optee::%s failed to truncate file (FIDL status: %d, IO status: %d)\n",
+               __FUNCTION__, status, io_status);
+        message->set_return_code(TEEC_ERROR_GENERIC);
+        return status;
+    }
+
+    message->set_return_code(TEEC_SUCCESS);
+    return ZX_OK;
+}
+
+zx_status_t OpteeClient::HandleRpcCommandFileSystemRemoveFile(
+    RemoveFileFileSystemRpcMessage* message) {
+    ZX_DEBUG_ASSERT(message != nullptr);
+    ZX_DEBUG_ASSERT(message->is_valid());
+
+    zxlogf(SPEW, "optee: received RPC to remove file\n");
+
+    std::optional<SharedMemoryView> path_mem = GetMemoryReference(
+        FindSharedMemory(message->path_memory_identifier()),
+        message->path_memory_paddr(),
+        message->path_memory_size());
+    if (!path_mem.has_value()) {
+        message->set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    std::filesystem::path path = GetPathFromRawMemory(reinterpret_cast<void*>(path_mem->vaddr()),
+                                                      message->path_memory_size());
+
+    zx::channel storage_channel;
+    constexpr bool kNoCreate = false;
+    zx_status_t status = GetStorageDirectory(path.parent_path(), kNoCreate, &storage_channel);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "optee::%s: failed to get storage directory (status %d)\n",
+               __FUNCTION__, status);
+        message->set_return_code(TEEC_ERROR_BAD_STATE);
+        return status;
+    }
+
+    zx_status_t io_status;
+    std::string filename = path.filename().string();
+    status = fuchsia_io_DirectoryUnlink(storage_channel.get(), filename.data(), filename.length(),
+                                        &io_status);
+    if (status != ZX_OK || io_status != ZX_OK) {
+        zxlogf(ERROR, "optee::%s failed to remove file (FIDL status: %d, IO status: %d)\n",
+               __FUNCTION__, status, io_status);
+        message->set_return_code(TEEC_ERROR_GENERIC);
+        return status;
+    }
+
+    message->set_return_code(TEEC_SUCCESS);
+    return ZX_OK;
+}
+
+zx_status_t OpteeClient::HandleRpcCommandFileSystemRenameFile(
+    RenameFileFileSystemRpcMessage* message) {
+    ZX_DEBUG_ASSERT(message != nullptr);
+    ZX_DEBUG_ASSERT(message->is_valid());
+
+    zxlogf(SPEW, "optee: received RPC to rename file\n");
+
+    std::optional<SharedMemoryView> old_path_mem = GetMemoryReference(
+        FindSharedMemory(message->old_file_name_memory_identifier()),
+        message->old_file_name_memory_paddr(),
+        message->old_file_name_memory_size());
+    if (!old_path_mem.has_value()) {
+        message->set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    std::filesystem::path old_path = GetPathFromRawMemory(
+        reinterpret_cast<void*>(old_path_mem->vaddr()), message->old_file_name_memory_size());
+    std::string old_name = old_path.filename().string();
+
+    std::optional<SharedMemoryView> new_path_mem = GetMemoryReference(
+        FindSharedMemory(message->new_file_name_memory_identifier()),
+        message->new_file_name_memory_paddr(),
+        message->new_file_name_memory_size());
+    if (!new_path_mem.has_value()) {
+        message->set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    std::filesystem::path new_path = GetPathFromRawMemory(
+        reinterpret_cast<void*>(new_path_mem->vaddr()), message->new_file_name_memory_size());
+    std::string new_name = new_path.filename().string();
+
+    zx::channel new_storage_channel;
+    constexpr bool kNoCreate = false;
+    zx_status_t status = GetStorageDirectory(new_path.parent_path(), kNoCreate,
+                                             &new_storage_channel);
+    if (status != ZX_OK) {
+        message->set_return_code(TEEC_ERROR_BAD_STATE);
+        return status;
+    }
+
+    if (!message->should_overwrite()) {
+        zx::channel destination_channel;
+        static constexpr uint32_t kCheckRenameFlags = fuchsia_io_OPEN_RIGHT_READABLE |
+                                                      fuchsia_io_OPEN_FLAG_DESCRIBE;
+        static constexpr uint32_t kCheckRenameMode = fuchsia_io_MODE_TYPE_FILE |
+                                                     fuchsia_io_MODE_TYPE_DIRECTORY;
+        status = OpenObjectInDirectory(new_storage_channel,
+                                       kCheckRenameFlags,
+                                       kCheckRenameMode,
+                                       new_name,
+                                       &destination_channel);
+        if (status == ZX_OK) {
+            // The file exists but shouldn't be overwritten
+            zxlogf(INFO,
+                   "optee::%s: refusing to rename file to path that already exists with overwrite "
+                   "set to false\n",
+                   __FUNCTION__);
+            message->set_return_code(TEEC_ERROR_ACCESS_CONFLICT);
+            return ZX_OK;
+        } else if (status != ZX_ERR_NOT_FOUND) {
+            zxlogf(ERROR, "optee::%s: could not check file existence before renaming (status %d)\n",
+                   __FUNCTION__, status);
+            message->set_return_code(TEEC_ERROR_GENERIC);
+            return status;
+        }
+    }
+
+    zx::channel old_storage_channel;
+    status = GetStorageDirectory(old_path.parent_path(), kNoCreate, &old_storage_channel);
+    if (status != ZX_OK) {
+        message->set_return_code(TEEC_ERROR_BAD_STATE);
+        return status;
+    }
+
+    zx_status_t io_status = ZX_OK;
+    zx::handle new_storage_token;
+    status = fuchsia_io_DirectoryGetToken(new_storage_channel.get(),
+                                          &io_status,
+                                          new_storage_token.reset_and_get_address());
+    if (status != ZX_OK || io_status != ZX_OK) {
+        zxlogf(ERROR,
+               "optee::%s: could not get destination directory's storage token "
+               "(FIDL status: %d, IO status: %d)\n",
+               __FUNCTION__, status, io_status);
+        message->set_return_code(TEEC_ERROR_GENERIC);
+        return status;
+    }
+
+    status = fuchsia_io_DirectoryRename(old_storage_channel.get(),   // _channel
+                                        old_name.data(),             // src_data
+                                        old_name.length(),           // src_size
+                                        new_storage_token.release(), // dst_parent_token
+                                        new_name.data(),             // dst_data
+                                        new_name.length(),           // dst_size
+                                        &io_status);                 // out_s
+    if (status != ZX_OK || io_status != ZX_OK) {
+        zxlogf(ERROR, "optee::%s failed to rename file (FIDL status: %d, IO status: %d)\n",
+               __FUNCTION__, status, io_status);
+        message->set_return_code(TEEC_ERROR_GENERIC);
+        return status;
+    }
+
+    message->set_return_code(TEEC_SUCCESS);
     return ZX_OK;
 }
 
diff --git a/zircon/system/dev/tee/optee/optee-client.h b/zircon/system/dev/tee/optee/optee-client.h
index e017a54975022e4d9644fec3632d5e0a273c0cb7..41391445fcf7086a0ceb789f4951414bda39c4ed 100644
--- a/zircon/system/dev/tee/optee/optee-client.h
+++ b/zircon/system/dev/tee/optee/optee-client.h
@@ -4,7 +4,10 @@
 
 #pragma once
 
+#include <atomic>
+#include <filesystem>
 #include <optional>
+#include <string_view>
 
 #include <ddktl/device.h>
 #include <ddktl/protocol/empty-protocol.h>
@@ -69,6 +72,17 @@ private:
     };
     using OpenSessionsTable = fbl::HashTable<uint32_t, fbl::unique_ptr<OpteeSession>>;
 
+    // TODO(godtamit): Move to `std::unordered_map` when BLD-413 is complete
+    struct FileSystemObject : fbl::SinglyLinkedListable<std::unique_ptr<FileSystemObject>> {
+        FileSystemObject(uint64_t id, zx::channel&& channel)
+            : id(id), channel(std::move(channel)) {}
+        uint64_t GetKey() const { return id; }
+        static size_t GetHash(uint64_t key) { return static_cast<size_t>(key); }
+        uint64_t id;
+        zx::channel channel;
+    };
+    using FileSystemObjectTable = fbl::HashTable<uint64_t, std::unique_ptr<FileSystemObject>>;
+
     zx_status_t CloseSession(uint32_t session_id);
 
     // Attempts to allocate a block of SharedMemory from a designated memory pool.
@@ -105,6 +119,75 @@ private:
     //  * Otherwise, an iterator object pointing to the end of allocated_shared_memory_.
     SharedMemoryList::iterator FindSharedMemory(uint64_t mem_id);
 
+    // Attempts to get a slice of `SharedMemory` representing an OP-TEE memory reference.
+    //
+    // Parameters:
+    //  * mem_iter:   The `SharedMemoryList::iterator` object pointing to the `SharedMemory`.
+    //                This may point to the end of `allocated_shared_memory_`.
+    //  * base_paddr: The starting base physical address of the slice.
+    //  * size:       The size of the slice.
+    //
+    // Returns:
+    //  * If `mem_iter` is valid and the slice bounds are valid, an initialized `std::optional` with
+    //    the `SharedMemoryView`.
+    //  * Otherwise, an uninitialized `std::optional`.
+    std::optional<SharedMemoryView> GetMemoryReference(SharedMemoryList::iterator mem_iter,
+                                                       zx_paddr_t base_paddr,
+                                                       size_t size);
+
+    // Requests the root storage channel from the `ServiceProvider` and caches it in
+    // `root_storage_channel_`.
+    //
+    // Subsequent calls to the function will return the cached channel.
+    //
+    // Returns:
+    //  * ZX_OK:                The operation was successful.
+    //  * ZX_ERR_UNAVAILABLE:   The current client does not have access to a `ServiceProvider`.
+    //  * `zx_status_t` codes from `zx::channel::create` or requesting the `ServiceProvider` over
+    //    FIDL.
+    zx_status_t GetRootStorageChannel(zx::unowned_channel* out_root_channel);
+
+    // Requests a connection to the storage directory pointed to by the path.
+    //
+    // Parameters:
+    //  * path:                 The path of the directory, relative to the root storage directory.
+    //  * create:               Flag specifying whether to create directories if they don't exist.
+    //  * out_storage_channel:  Where to output the `fuchsia.io.Directory` client end channel.
+    zx_status_t GetStorageDirectory(std::filesystem::path path, bool create,
+                                    zx::channel* out_storage_channel);
+
+    // Tracks a new file system object associated with the current client.
+    //
+    // This occurs when the trusted world creates or opens a file system object.
+    //
+    // Parameters:
+    //  * io_node_channel:  The channel to the `fuchsia.io.Node` file system object.
+    //
+    // Returns:
+    //  * The identifier for the trusted world to refer to the object.
+    __WARN_UNUSED_RESULT uint64_t TrackFileSystemObject(zx::channel io_node_channel);
+
+    // Gets the channel to the file system object associated with the given identifier.
+    //
+    // Parameters:
+    //  * identifier: The identifier to find the file system object by.
+    //
+    // Returns:
+    //  * A `std::optional` containing a reference to the `zx::channel`, if it was found.
+    std::optional<zx::unowned_channel>
+    GetFileSystemObjectChannel(uint64_t identifier);
+
+    // Untracks a file system object associated with the current client.
+    //
+    // This occurs when the trusted world closes a previously open file system object.
+    //
+    // Parameters:
+    //  * identifier:  The identifier to refer to the object.
+    //
+    // Returns:
+    //  * Whether a file system object associated with the identifier was untracked.
+    bool UntrackFileSystemObject(uint64_t identifier);
+
     //
     // OP-TEE RPC Function Handlers
     //
@@ -144,18 +227,35 @@ private:
     zx_status_t HandleRpcCommandGetTime(GetTimeRpcMessage* message);
     zx_status_t HandleRpcCommandAllocateMemory(AllocateMemoryRpcMessage* message);
     zx_status_t HandleRpcCommandFreeMemory(FreeMemoryRpcMessage* message);
-    zx_status_t HandleRpcCommandFileSystem(FileSystemRpcMessage* message);
 
+    // Move in the FileSystemRpcMessage since it'll be moved into a sub-type in this function.
+    zx_status_t HandleRpcCommandFileSystem(FileSystemRpcMessage&& message);
+    zx_status_t HandleRpcCommandFileSystemOpenFile(OpenFileFileSystemRpcMessage* message);
+    zx_status_t HandleRpcCommandFileSystemCreateFile(CreateFileFileSystemRpcMessage* message);
+    zx_status_t HandleRpcCommandFileSystemCloseFile(CloseFileFileSystemRpcMessage* message);
+    zx_status_t HandleRpcCommandFileSystemReadFile(ReadFileFileSystemRpcMessage* message);
+    zx_status_t HandleRpcCommandFileSystemWriteFile(WriteFileFileSystemRpcMessage* message);
+    zx_status_t HandleRpcCommandFileSystemTruncateFile(TruncateFileFileSystemRpcMessage* message);
+    zx_status_t HandleRpcCommandFileSystemRemoveFile(RemoveFileFileSystemRpcMessage* message);
+    zx_status_t HandleRpcCommandFileSystemRenameFile(RenameFileFileSystemRpcMessage* message);
     static fuchsia_tee_Device_ops_t kFidlOps;
 
     OpteeController* controller_;
     bool needs_to_close_ = false;
     SharedMemoryList allocated_shared_memory_;
     OpenSessionsTable open_sessions_;
+    std::atomic<uint64_t> next_file_system_object_id_{1};
+
+    // TODO(godtamit): Move to `std::unordered_map` when BLD-413 is complete
+    FileSystemObjectTable open_file_system_objects_;
 
     // The client end of a channel to the `fuchsia.tee.manager.ServiceProvider` protocol.
     // This may be an invalid channel, which indicates the client has no service provider support.
     zx::channel service_provider_channel_;
+
+    // A lazily-initialized, cached channel to the root storage channel.
+    // This may be an invalid channel, which indicates it has not been initialized yet.
+    zx::channel root_storage_channel_;
 };
 
 } // namespace optee
diff --git a/zircon/system/dev/tee/optee/optee-message.cpp b/zircon/system/dev/tee/optee/optee-message.cpp
index 61de9aa64d81236b6d458798b568e67ecae6d813..065ac44cbf43df579e15b723c860f7b885eb9a97 100644
--- a/zircon/system/dev/tee/optee/optee-message.cpp
+++ b/zircon/system/dev/tee/optee/optee-message.cpp
@@ -630,8 +630,9 @@ bool FreeMemoryRpcMessage::TryInitializeMembers() {
 bool FileSystemRpcMessage::TryInitializeMembers() {
     if (header()->num_params < kMinNumParams) {
         zxlogf(ERROR,
-               "optee: RPC command to access file system received unexpected number of parameters!"
-               "\n");
+               "optee: RPC command to access file system received unexpected number of parameters "
+               "(%u)\n",
+               header()->num_params);
         set_return_origin(TEEC_ORIGIN_COMMS);
         set_return_code(TEEC_ERROR_BAD_PARAMETERS);
         return false;
@@ -662,4 +663,347 @@ bool FileSystemRpcMessage::TryInitializeMembers() {
     return true;
 }
 
+bool OpenFileFileSystemRpcMessage::TryInitializeMembers() {
+    if (header()->num_params != kNumParams) {
+        zxlogf(ERROR,
+               "optee: RPC command to open file received unexpected number of parameters (%u)\n",
+               header()->num_params);
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    // Parse the file name parameter
+    MessageParam& path_param = params()[kPathParamIndex];
+    switch (path_param.attribute) {
+    case MessageParam::kAttributeTypeTempMemInput: {
+        MessageParam::TemporaryMemory& temp_mem = path_param.payload.temporary_memory;
+        path_mem_id_ = temp_mem.shared_memory_reference;
+        path_mem_size_ = temp_mem.size;
+        path_mem_paddr_ = static_cast<zx_paddr_t>(temp_mem.buffer);
+        break;
+    }
+    case MessageParam::kAttributeTypeRegMemInput:
+        zxlogf(ERROR,
+               "optee: received unsupported registered memory parameter\n");
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_NOT_IMPLEMENTED);
+        return false;
+    default:
+        zxlogf(ERROR,
+               "optee: RPC command to open file received unexpected second parameter\n");
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    // Parse the output file identifier parameter
+    MessageParam& out_fs_object_id_param = params()[kOutFileSystemObjectIdParamIndex];
+    if (out_fs_object_id_param.attribute != MessageParam::kAttributeTypeValueOutput) {
+        zxlogf(ERROR,
+               "optee: RPC command to open file received unexpected third parameter\n");
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    out_fs_object_id_ = &out_fs_object_id_param.payload.value.file_system_object.identifier;
+
+    return true;
+}
+
+bool CreateFileFileSystemRpcMessage::TryInitializeMembers() {
+    if (header()->num_params != kNumParams) {
+        zxlogf(ERROR,
+               "optee: RPC command to create file received unexpected number of parameters (%u)\n",
+               header()->num_params);
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    // Parse the file name parameter
+    MessageParam& path_param = params()[kPathParamIndex];
+    switch (path_param.attribute) {
+    case MessageParam::kAttributeTypeTempMemInput: {
+        MessageParam::TemporaryMemory& temp_mem = path_param.payload.temporary_memory;
+        path_mem_id_ = temp_mem.shared_memory_reference;
+        path_mem_size_ = temp_mem.size;
+        path_mem_paddr_ = static_cast<zx_paddr_t>(temp_mem.buffer);
+        break;
+    }
+    case MessageParam::kAttributeTypeRegMemInput:
+        zxlogf(ERROR,
+               "optee: received unsupported registered memory parameter\n");
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_NOT_IMPLEMENTED);
+        return false;
+    default:
+        zxlogf(ERROR,
+               "optee: RPC command to create file received unexpected second parameter\n");
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    // Parse the output file identifier parameter
+    MessageParam& out_fs_object_param = params()[kOutFileSystemObjectIdParamIndex];
+    if (out_fs_object_param.attribute != MessageParam::kAttributeTypeValueOutput) {
+        zxlogf(ERROR,
+               "optee: RPC command to create file received unexpected third parameter\n");
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    out_fs_object_id_ = &out_fs_object_param.payload.value.file_system_object.identifier;
+
+    return true;
+}
+
+bool CloseFileFileSystemRpcMessage::TryInitializeMembers() {
+    if (header()->num_params != kNumParams) {
+        zxlogf(ERROR,
+               "optee: RPC command to close file received unexpected number of parameters (%u)\n",
+               header()->num_params);
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    // Parse the file name parameter
+    MessageParam& command_param = params()[kFileSystemCommandParamIndex];
+
+    // The attribute was already validated by FileSystemRpcMessage
+    ZX_DEBUG_ASSERT(command_param.attribute == MessageParam::kAttributeTypeValueInput ||
+                    command_param.attribute == MessageParam::kAttributeTypeValueInOut);
+
+    fs_object_id_ = command_param.payload.value.file_system_command.object_identifier;
+
+    return true;
+}
+
+bool ReadFileFileSystemRpcMessage::TryInitializeMembers() {
+    if (header()->num_params != kNumParams) {
+        zxlogf(ERROR,
+               "optee: RPC command to read file received unexpected number of parameters (%u)\n",
+               header()->num_params);
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    // Parse the file name parameter
+    MessageParam& command_param = params()[kFileSystemCommandParamIndex];
+
+    // The attribute was already validated by FileSystemRpcMessage
+    ZX_DEBUG_ASSERT(command_param.attribute == MessageParam::kAttributeTypeValueInput ||
+                    command_param.attribute == MessageParam::kAttributeTypeValueInOut);
+
+    fs_object_id_ = command_param.payload.value.file_system_command.object_identifier;
+    file_offset_ = command_param.payload.value.file_system_command.object_offset;
+
+    // Parse the output memory parameter
+    MessageParam& out_mem_param = params()[kOutReadBufferMemoryParamIndex];
+    switch (out_mem_param.attribute) {
+    case MessageParam::kAttributeTypeTempMemOutput: {
+        MessageParam::TemporaryMemory& temp_mem = out_mem_param.payload.temporary_memory;
+        file_contents_memory_identifier_ = temp_mem.shared_memory_reference;
+        file_contents_memory_size_ = static_cast<size_t>(temp_mem.size);
+        file_contents_memory_paddr_ = static_cast<zx_paddr_t>(temp_mem.buffer);
+        out_file_contents_size_ = &temp_mem.size;
+        break;
+    }
+    case MessageParam::kAttributeTypeRegMemInput:
+        zxlogf(ERROR,
+               "optee: received unsupported registered memory parameter\n");
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_NOT_IMPLEMENTED);
+        return false;
+    default:
+        zxlogf(ERROR,
+               "optee: RPC command to read file received unexpected second parameter\n");
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    return true;
+}
+
+bool WriteFileFileSystemRpcMessage::TryInitializeMembers() {
+    if (header()->num_params != kNumParams) {
+        zxlogf(ERROR,
+               "optee: RPC command to write file received unexpected number of parameters (%u)\n",
+               header()->num_params);
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    // Parse the file name parameter
+    MessageParam& command_param = params()[kFileSystemCommandParamIndex];
+
+    // The attribute was already validated by FileSystemRpcMessage
+    ZX_DEBUG_ASSERT(command_param.attribute == MessageParam::kAttributeTypeValueInput ||
+                    command_param.attribute == MessageParam::kAttributeTypeValueInOut);
+
+    fs_object_id_ = command_param.payload.value.file_system_command.object_identifier;
+    file_offset_ = command_param.payload.value.file_system_command.object_offset;
+
+    // Parse the write memory parameter
+    MessageParam& mem_param = params()[kWriteBufferMemoryParam];
+    switch (mem_param.attribute) {
+    case MessageParam::kAttributeTypeTempMemInput: {
+        MessageParam::TemporaryMemory& temp_mem = mem_param.payload.temporary_memory;
+        file_contents_memory_identifier_ = temp_mem.shared_memory_reference;
+        file_contents_memory_size_ = static_cast<size_t>(temp_mem.size);
+        file_contents_memory_paddr_ = static_cast<zx_paddr_t>(temp_mem.buffer);
+        break;
+    }
+    case MessageParam::kAttributeTypeRegMemInput:
+        zxlogf(ERROR,
+               "optee: received unsupported registered memory parameter\n");
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_NOT_IMPLEMENTED);
+        return false;
+    default:
+        zxlogf(ERROR,
+               "optee: RPC command to write file received unexpected second parameter\n");
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    return true;
+}
+
+bool TruncateFileFileSystemRpcMessage::TryInitializeMembers() {
+    if (header()->num_params != kNumParams) {
+        zxlogf(ERROR,
+               "optee: RPC command to truncate file received unexpected number of parameters "
+               "(%u)\n",
+               header()->num_params);
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    // Parse the file command parameter
+    MessageParam& command_param = params()[kFileSystemCommandParamIndex];
+
+    // The attribute was already validated by FileSystemRpcMessage
+    ZX_DEBUG_ASSERT(command_param.attribute == MessageParam::kAttributeTypeValueInput ||
+                    command_param.attribute == MessageParam::kAttributeTypeValueInOut);
+
+    fs_object_id_ = command_param.payload.value.file_system_command.object_identifier;
+    target_file_size_ = command_param.payload.value.file_system_command.object_offset;
+
+    return true;
+}
+
+bool RemoveFileFileSystemRpcMessage::TryInitializeMembers() {
+    if (header()->num_params != kNumParams) {
+        zxlogf(ERROR,
+               "optee: RPC command to remove file received unexpected number of parameters (%u)\n",
+               header()->num_params);
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    // Parse the file name parameter
+    MessageParam& path_param = params()[kFileNameParamIndex];
+    switch (path_param.attribute) {
+    case MessageParam::kAttributeTypeTempMemInput: {
+        MessageParam::TemporaryMemory& temp_mem = path_param.payload.temporary_memory;
+        path_mem_id_ = temp_mem.shared_memory_reference;
+        path_mem_size_ = temp_mem.size;
+        path_mem_paddr_ = static_cast<zx_paddr_t>(temp_mem.buffer);
+        break;
+    }
+    case MessageParam::kAttributeTypeRegMemInput:
+        zxlogf(ERROR,
+               "optee: received unsupported registered memory parameter\n");
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_NOT_IMPLEMENTED);
+        return false;
+    default:
+        zxlogf(ERROR,
+               "optee: RPC command to remove file received unexpected second parameter\n");
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    return true;
+}
+
+bool RenameFileFileSystemRpcMessage::TryInitializeMembers() {
+    if (header()->num_params != kNumParams) {
+        zxlogf(ERROR,
+               "optee: RPC command to rename file received unexpected number of parameters (%u)\n",
+               header()->num_params);
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    // Parse the overwrite value
+    MessageParam& command_param = params()[kFileSystemCommandParamIndex];
+
+    // The attribute was already validated by FileSystemRpcMessage
+    ZX_DEBUG_ASSERT(command_param.attribute == MessageParam::kAttributeTypeValueInput ||
+                    command_param.attribute == MessageParam::kAttributeTypeValueInOut);
+
+    should_overwrite_ = (command_param.payload.value.generic.b != 0);
+
+    // Parse the old file name parameter
+    MessageParam& old_file_name_param = params()[kOldFileNameParamIndex];
+    switch (old_file_name_param.attribute) {
+    case MessageParam::kAttributeTypeTempMemInput: {
+        MessageParam::TemporaryMemory& temp_mem = old_file_name_param.payload.temporary_memory;
+        old_file_name_mem_id_ = temp_mem.shared_memory_reference;
+        old_file_name_mem_size_ = temp_mem.size;
+        old_file_name_mem_paddr_ = static_cast<zx_paddr_t>(temp_mem.buffer);
+        break;
+    }
+    case MessageParam::kAttributeTypeRegMemInput:
+        zxlogf(ERROR, "optee: received unsupported registered memory parameter\n");
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_NOT_IMPLEMENTED);
+        return false;
+    default:
+        zxlogf(ERROR,
+               "optee: RPC command to rename file received unexpected second parameter\n");
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    // Parse the new file name parameter
+    MessageParam& new_file_name_param = params()[kNewFileNameParamIndex];
+    switch (new_file_name_param.attribute) {
+    case MessageParam::kAttributeTypeTempMemInput: {
+        MessageParam::TemporaryMemory& temp_mem = new_file_name_param.payload.temporary_memory;
+        new_file_name_mem_id_ = temp_mem.shared_memory_reference;
+        new_file_name_mem_size_ = temp_mem.size;
+        new_file_name_mem_paddr_ = static_cast<zx_paddr_t>(temp_mem.buffer);
+        break;
+    }
+    case MessageParam::kAttributeTypeRegMemInput:
+        zxlogf(ERROR, "optee: received unsupported registered memory parameter\n");
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_NOT_IMPLEMENTED);
+        return false;
+    default:
+        zxlogf(ERROR, "optee: RPC command to rename file received unexpected third parameter\n");
+        set_return_origin(TEEC_ORIGIN_COMMS);
+        set_return_code(TEEC_ERROR_BAD_PARAMETERS);
+        return false;
+    }
+
+    return true;
+}
+
 } // namespace optee
diff --git a/zircon/system/dev/tee/optee/optee-message.h b/zircon/system/dev/tee/optee/optee-message.h
index 3775b6c5307342b9336ec6715e0359fb4ef264f9..c4132aa38edbaf9bd0d1053cb6517f75ef4eef22 100644
--- a/zircon/system/dev/tee/optee/optee-message.h
+++ b/zircon/system/dev/tee/optee/optee-message.h
@@ -95,7 +95,12 @@ struct MessageParam {
         } free_memory_specs;
         struct {
             uint64_t command_number;
+            uint64_t object_identifier;
+            uint64_t object_offset;
         } file_system_command;
+        struct {
+            uint64_t identifier;
+        } file_system_object;
     };
 
     uint64_t attribute;
@@ -632,7 +637,7 @@ public:
 
     // FileSystemRpcMessage
     //
-    // Move constructor for FileSystemRpcMessage. Uses the default implicit implementation.
+    // Move constructor for `FileSystemRpcMessage`. Uses the default implicit implementation.
     FileSystemRpcMessage(FileSystemRpcMessage&&) = default;
 
     // FileSystemRpcMessage
@@ -662,4 +667,465 @@ private:
     bool TryInitializeMembers();
 };
 
+// OpenFileFileSystemRpcMessage
+//
+// A `FileSystemRpcMessage` that should be interpreted with the command of opening a file.
+// A `FileSystemRpcMessage` can be converted into a `OpenFileFileSystemRpcMessage` via a
+// constructor.
+class OpenFileFileSystemRpcMessage : public FileSystemRpcMessage {
+public:
+    // OpenFileFileSystemRpcMessage
+    //
+    // Move constructor for `OpenFileFileSystemRpcMessage`. Uses the default implicit
+    // implementation.
+    OpenFileFileSystemRpcMessage(OpenFileFileSystemRpcMessage&&) = default;
+
+    // OpenFileFileSystemRpcMessage
+    //
+    // Constructs a `OpenFileFileSystemRpcMessage` from a moved-in `FileSystemRpcMessage`.
+    explicit OpenFileFileSystemRpcMessage(FileSystemRpcMessage&& fs_message)
+        : FileSystemRpcMessage(std::move(fs_message)) {
+        ZX_DEBUG_ASSERT(is_valid()); // The file system message passed in should've been valid
+        ZX_DEBUG_ASSERT(file_system_command() == FileSystemCommand::kOpenFile);
+
+        is_valid_ = is_valid_ && TryInitializeMembers();
+    }
+
+    uint64_t path_memory_identifier() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return path_mem_id_;
+    }
+
+    size_t path_memory_size() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return path_mem_size_;
+    }
+
+    zx_paddr_t path_memory_paddr() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return path_mem_paddr_;
+    }
+
+    void set_output_file_system_object_identifier(uint64_t object_id) {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        ZX_DEBUG_ASSERT(out_fs_object_id_ != nullptr);
+        *out_fs_object_id_ = object_id;
+    }
+
+protected:
+    static constexpr size_t kNumParams = 3;
+    static constexpr size_t kPathParamIndex = 1;
+    static constexpr size_t kOutFileSystemObjectIdParamIndex = 2;
+
+    uint64_t path_mem_id_;
+    size_t path_mem_size_;
+    zx_paddr_t path_mem_paddr_;
+    uint64_t* out_fs_object_id_;
+
+private:
+    bool TryInitializeMembers();
+};
+
+// CreateFileFileSystemRpcMessage
+//
+// A `FileSystemRpcMessage` that should be interpreted with the command of creating a file.
+// A `FileSystemRpcMessage` can be converted into a `CreateFileFileSystemRpcMessage` via a
+// constructor.
+class CreateFileFileSystemRpcMessage : public FileSystemRpcMessage {
+public:
+    // CreateFileFileSystemRpcMessage
+    //
+    // Move constructor for `CreateFileFileSystemRpcMessage`. Uses the default implicit
+    // implementation.
+    CreateFileFileSystemRpcMessage(CreateFileFileSystemRpcMessage&&) = default;
+
+    // CreateFileFileSystemRpcMessage
+    //
+    // Constructs a `CreateFileFileSystemRpcMessage` from a moved-in `FileSystemRpcMessage`.
+    explicit CreateFileFileSystemRpcMessage(FileSystemRpcMessage&& fs_message)
+        : FileSystemRpcMessage(std::move(fs_message)) {
+        ZX_DEBUG_ASSERT(is_valid()); // The file system message passed in should've been valid
+        ZX_DEBUG_ASSERT(file_system_command() == FileSystemCommand::kCreateFile);
+
+        is_valid_ = is_valid_ && TryInitializeMembers();
+    }
+
+    uint64_t path_memory_identifier() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return path_mem_id_;
+    }
+
+    size_t path_memory_size() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return path_mem_size_;
+    }
+
+    zx_paddr_t path_memory_paddr() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return path_mem_paddr_;
+    }
+
+    void set_output_file_system_object_identifier(uint64_t object_id) {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        ZX_DEBUG_ASSERT(out_fs_object_id_ != nullptr);
+        *out_fs_object_id_ = object_id;
+    }
+
+protected:
+    static constexpr size_t kNumParams = 3;
+    static constexpr size_t kPathParamIndex = 1;
+    static constexpr size_t kOutFileSystemObjectIdParamIndex = 2;
+
+    uint64_t path_mem_id_;
+    size_t path_mem_size_;
+    zx_paddr_t path_mem_paddr_;
+    uint64_t* out_fs_object_id_;
+
+private:
+    bool TryInitializeMembers();
+};
+
+// CloseFileFileSystemRpcMessage
+//
+// A `FileSystemRpcMessage` that should be interpreted with the command of closing a file.
+// A `FileSystemRpcMessage` can be converted into a `CloseFileFileSystemRpcMessage` via a
+// constructor.
+class CloseFileFileSystemRpcMessage : public FileSystemRpcMessage {
+public:
+    // CloseFileFileSystemRpcMessage
+    //
+    // Move constructor for `CloseFileFileSystemRpcMessage`. Uses the default implicit
+    // implementation.
+    CloseFileFileSystemRpcMessage(CloseFileFileSystemRpcMessage&&) = default;
+
+    // CloseFileFileSystemRpcMessage
+    //
+    // Constructs a `CloseFileFileSystemRpcMessage` from a moved-in `FileSystemRpcMessage`.
+    explicit CloseFileFileSystemRpcMessage(FileSystemRpcMessage&& fs_message)
+        : FileSystemRpcMessage(std::move(fs_message)) {
+        ZX_DEBUG_ASSERT(is_valid()); // The file system message passed in should've been valid
+        ZX_DEBUG_ASSERT(file_system_command() == FileSystemCommand::kCloseFile);
+
+        is_valid_ = is_valid_ && TryInitializeMembers();
+    }
+
+    uint64_t file_system_object_identifier() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return fs_object_id_;
+    }
+
+protected:
+    static constexpr size_t kNumParams = 1;
+
+    uint64_t fs_object_id_;
+
+private:
+    bool TryInitializeMembers();
+};
+
+// ReadFileFileSystemRpcMessage
+//
+// A `FileSystemRpcMessage` that should be interpreted with the command of reading an open file.
+// A `FileSystemRpcMessage` can be converted into a `ReadFileFileSystemRpcMessage` via a
+// constructor.
+class ReadFileFileSystemRpcMessage : public FileSystemRpcMessage {
+public:
+    // ReadFileFileSystemRpcMessage
+    //
+    // Move constructor for `ReadFileFileSystemRpcMessage`. Uses the default implicit
+    // implementation.
+    ReadFileFileSystemRpcMessage(ReadFileFileSystemRpcMessage&&) = default;
+
+    // ReadFileFileSystemRpcMessage
+    //
+    // Constructs a `ReadFileFileSystemRpcMessage` from a moved-in `FileSystemRpcMessage`.
+    explicit ReadFileFileSystemRpcMessage(FileSystemRpcMessage&& fs_message)
+        : FileSystemRpcMessage(std::move(fs_message)) {
+        ZX_DEBUG_ASSERT(is_valid()); // The file system message passed in should've been valid
+        ZX_DEBUG_ASSERT(file_system_command() == FileSystemCommand::kReadFile);
+
+        is_valid_ = is_valid_ && TryInitializeMembers();
+    }
+
+    uint64_t file_system_object_identifier() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return fs_object_id_;
+    }
+
+    uint64_t file_offset() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return file_offset_;
+    }
+
+    uint64_t file_contents_memory_identifier() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return file_contents_memory_identifier_;
+    }
+
+    size_t file_contents_memory_size() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return file_contents_memory_size_;
+    }
+
+    zx_paddr_t file_contents_memory_paddr() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return file_contents_memory_paddr_;
+    }
+
+    void set_output_file_contents_size(size_t size) const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        ZX_DEBUG_ASSERT(out_file_contents_size_ != nullptr);
+
+        *out_file_contents_size_ = static_cast<uint64_t>(size);
+    }
+
+protected:
+    static constexpr size_t kNumParams = 2;
+    static constexpr size_t kOutReadBufferMemoryParamIndex = 1;
+
+    uint64_t fs_object_id_;
+    uint64_t file_offset_;
+    uint64_t file_contents_memory_identifier_;
+    size_t file_contents_memory_size_;
+    zx_paddr_t file_contents_memory_paddr_;
+    uint64_t* out_file_contents_size_;
+
+private:
+    bool TryInitializeMembers();
+};
+
+// WriteFileFileSystemRpcMessage
+//
+// A `FileSystemRpcMessage` that should be interpreted with the command of writing to an open file.
+// A `FileSystemRpcMessage` can be converted into a `WriteFileFileSystemRpcMessage` via a
+// constructor.
+class WriteFileFileSystemRpcMessage : public FileSystemRpcMessage {
+public:
+    // WriteFileFileSystemRpcMessage
+    //
+    // Move constructor for `WriteFileFileSystemRpcMessage`. Uses the default implicit
+    // implementation.
+    WriteFileFileSystemRpcMessage(WriteFileFileSystemRpcMessage&&) = default;
+
+    // WriteFileFileSystemRpcMessage
+    //
+    // Constructs a `WriteFileFileSystemRpcMessage` from a moved-in `FileSystemRpcMessage`.
+    explicit WriteFileFileSystemRpcMessage(FileSystemRpcMessage&& fs_message)
+        : FileSystemRpcMessage(std::move(fs_message)) {
+        ZX_DEBUG_ASSERT(is_valid()); // The file system message passed in should've been valid
+        ZX_DEBUG_ASSERT(file_system_command() == FileSystemCommand::kWriteFile);
+
+        is_valid_ = is_valid_ && TryInitializeMembers();
+    }
+
+    uint64_t file_system_object_identifier() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return fs_object_id_;
+    }
+
+    zx_off_t file_offset() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return file_offset_;
+    }
+
+    uint64_t file_contents_memory_identifier() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return file_contents_memory_identifier_;
+    }
+
+    size_t file_contents_memory_size() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return file_contents_memory_size_;
+    }
+
+    zx_paddr_t file_contents_memory_paddr() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return file_contents_memory_paddr_;
+    }
+
+protected:
+    static constexpr size_t kNumParams = 2;
+    static constexpr size_t kWriteBufferMemoryParam = 1;
+
+    uint64_t fs_object_id_;
+    zx_off_t file_offset_;
+    uint64_t file_contents_memory_identifier_;
+    size_t file_contents_memory_size_;
+    zx_paddr_t file_contents_memory_paddr_;
+
+private:
+    bool TryInitializeMembers();
+};
+
+// TruncateFileFileSystemRpcMessage
+//
+// A `FileSystemRpcMessage` that should be interpreted with the command of truncating a file.
+// A `FileSystemRpcMessage` can be converted into a `TruncateFileFileSystemRpcMessage` via a
+// constructor.
+class TruncateFileFileSystemRpcMessage : public FileSystemRpcMessage {
+public:
+    // TruncateFileFileSystemRpcMessage
+    //
+    // Move constructor for `TruncateFileFileSystemRpcMessage`. Uses the default implicit
+    // implementation.
+    TruncateFileFileSystemRpcMessage(TruncateFileFileSystemRpcMessage&&) = default;
+
+    // TruncateFileFileSystemRpcMessage
+    //
+    // Constructs a `TruncateFileFileSystemRpcMessage` from a moved-in `FileSystemRpcMessage`.
+    explicit TruncateFileFileSystemRpcMessage(FileSystemRpcMessage&& fs_message)
+        : FileSystemRpcMessage(std::move(fs_message)) {
+        ZX_DEBUG_ASSERT(is_valid()); // The file system message passed in should've been valid
+        ZX_DEBUG_ASSERT(file_system_command() == FileSystemCommand::kTruncateFile);
+
+        is_valid_ = is_valid_ && TryInitializeMembers();
+    }
+
+    uint64_t file_system_object_identifier() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return fs_object_id_;
+    }
+
+    uint64_t target_file_size() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return target_file_size_;
+    }
+
+protected:
+    static constexpr size_t kNumParams = 1;
+
+    uint64_t fs_object_id_;
+    uint64_t target_file_size_;
+
+private:
+    bool TryInitializeMembers();
+};
+
+// RemoveFileFileSystemRpcMessage
+//
+// A `FileSystemRpcMessage` that should be interpreted with the command of removing a file.
+// A `FileSystemRpcMessage` can be converted into a `RemoveFileFileSystemRpcMessage` via a
+// constructor.
+class RemoveFileFileSystemRpcMessage : public FileSystemRpcMessage {
+public:
+    // RemoveFileFileSystemRpcMessage
+    //
+    // Move constructor for `RemoveFileFileSystemRpcMessage`. Uses the default implicit
+    // implementation.
+    RemoveFileFileSystemRpcMessage(RemoveFileFileSystemRpcMessage&&) = default;
+
+    // RemoveFileFileSystemRpcMessage
+    //
+    // Constructs a `RemoveFileFileSystemRpcMessage` from a moved-in `FileSystemRpcMessage`.
+    explicit RemoveFileFileSystemRpcMessage(FileSystemRpcMessage&& fs_message)
+        : FileSystemRpcMessage(std::move(fs_message)) {
+        ZX_DEBUG_ASSERT(is_valid()); // The file system message passed in should've been valid
+        ZX_DEBUG_ASSERT(file_system_command() == FileSystemCommand::kRemoveFile);
+
+        is_valid_ = is_valid_ && TryInitializeMembers();
+    }
+
+    uint64_t path_memory_identifier() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return path_mem_id_;
+    }
+
+    size_t path_memory_size() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return path_mem_size_;
+    }
+
+    zx_paddr_t path_memory_paddr() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return path_mem_paddr_;
+    }
+
+protected:
+    static constexpr size_t kNumParams = 2;
+    static constexpr size_t kFileNameParamIndex = 1;
+
+    uint64_t path_mem_id_;
+    size_t path_mem_size_;
+    zx_paddr_t path_mem_paddr_;
+
+private:
+    bool TryInitializeMembers();
+};
+
+// RenameFileFileSystemRpcMessage
+//
+// A `FileSystemRpcMessage` that should be interpreted with the command of renaming a file.
+// A `FileSystemRpcMessage` can be converted into a `RenameFileFileSystemRpcMessage` via a
+// constructor.
+class RenameFileFileSystemRpcMessage : public FileSystemRpcMessage {
+public:
+    // RenameFileFileSystemRpcMessage
+    //
+    // Move constructor for `RenameFileFileSystemRpcMessage`. Uses the default implicit
+    // implementation.
+    RenameFileFileSystemRpcMessage(RenameFileFileSystemRpcMessage&&) = default;
+
+    // RenameFileFileSystemRpcMessage
+    //
+    // Constructs a `RenameFileFileSystemRpcMessage` from a moved-in `FileSystemRpcMessage`.
+    explicit RenameFileFileSystemRpcMessage(FileSystemRpcMessage&& fs_message)
+        : FileSystemRpcMessage(std::move(fs_message)) {
+        ZX_DEBUG_ASSERT(is_valid()); // The file system message passed in should've been valid
+        ZX_DEBUG_ASSERT(file_system_command() == FileSystemCommand::kRenameFile);
+
+        is_valid_ = is_valid_ && TryInitializeMembers();
+    }
+
+    bool should_overwrite() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return should_overwrite_;
+    }
+
+    uint64_t old_file_name_memory_identifier() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return old_file_name_mem_id_;
+    }
+
+    size_t old_file_name_memory_size() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return old_file_name_mem_size_;
+    }
+
+    zx_paddr_t old_file_name_memory_paddr() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return old_file_name_mem_paddr_;
+    }
+
+    uint64_t new_file_name_memory_identifier() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return new_file_name_mem_id_;
+    }
+
+    size_t new_file_name_memory_size() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return new_file_name_mem_size_;
+    }
+
+    zx_paddr_t new_file_name_memory_paddr() const {
+        ZX_DEBUG_ASSERT_MSG(is_valid(), "Accessing invalid OP-TEE RPC message");
+        return new_file_name_mem_paddr_;
+    }
+
+protected:
+    static constexpr size_t kNumParams = 3;
+    static constexpr size_t kOldFileNameParamIndex = 1;
+    static constexpr size_t kNewFileNameParamIndex = 2;
+
+    bool should_overwrite_;
+    uint64_t old_file_name_mem_id_;
+    size_t old_file_name_mem_size_;
+    zx_paddr_t old_file_name_mem_paddr_;
+    uint64_t new_file_name_mem_id_;
+    size_t new_file_name_mem_size_;
+    zx_paddr_t new_file_name_mem_paddr_;
+
+private:
+    bool TryInitializeMembers();
+};
+
 } // namespace optee