Key-value store: Baseline example

This page details how to create an example write-only key-value store — defining interface definitions and a test harness as well as client/server implementations.

Getting started

This baseline case details an end-to-end implementation of a write-only key-value store implemented using FIDL. This case allows each client to have its own bespoke key-value store on the server, which it can connect to using the Store protocol, and write to using the WriteItem method.

The following FIDL, CML, and realm interface definitions set up a scaffold that arbitrary implementations can use:

FIDL

// Copyright 2022 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.
library examples.keyvaluestore.baseline;

/// An item in the store. The key must match the regex `^[A-z][A-z0-9_\.\/]{2,62}[A-z0-9]$`. That
/// is, it must start with a letter, end with a letter or number, contain only letters, numbers,
/// periods, and slashes, and be between 4 and 64 characters long.
type Item = struct {
    key string:128;
    value vector<byte>:64000;
};

/// An enumeration of things that may go wrong when trying to write a value to our store.
type WriteError = flexible enum {
    UNKNOWN = 0;
    INVALID_KEY = 1;
    INVALID_VALUE = 2;
    ALREADY_EXISTS = 3;
};

/// A very basic key-value store - so basic, in fact, that one may only write to it, never read!
@discoverable
open protocol Store {
    /// Writes an item to the store.
    flexible WriteItem(struct {
        attempt Item;
    }) -> () error WriteError;
};

CML

Client

// Copyright 2022 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: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/client_bin",
    },
    use: [
        { protocol: "examples.keyvaluestore.baseline.Store" },
    ],
    config: {
        write_items: {
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
        },
    },
}

Server

// Copyright 2022 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: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/server_bin",
    },
    capabilities: [
        { protocol: "examples.keyvaluestore.baseline.Store" },
    ],
    expose: [
        {
            protocol: "examples.keyvaluestore.baseline.Store",
            from: "self",
        },
    ],
}

Realm

// Copyright 2022 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.
{
    children: [
        {
            name: "client",
            url: "#meta/client.cm",
        },
        {
            name: "server",
            url: "#meta/server.cm",
        },
    ],
    offer: [
        // Route the protocol under test from the server to the client.
        {
            protocol: "examples.keyvaluestore.baseline.Store",
            from: "#server",
            to: "#client",
        },
        {
            dictionary: "diagnostics",
            from: "parent",
            to: "all",
        },

        // Route diagnostics support to all children.
        {
            protocol: [
                "fuchsia.inspect.InspectSink",
                "fuchsia.logger.LogSink",
            ],
            from: "parent",
            to: [
                "#client",
                "#server",
            ],
        },
    ],
}

Client and server implementations can then be written in any supported language:

Rust

Client

// Copyright 2022 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.

use anyhow::{Context as _, Error};
use config::Config;
use fidl_examples_keyvaluestore_baseline::{Item, StoreMarker};
use fuchsia_component::client::connect_to_protocol;
use std::{thread, time};

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Load the structured config values passed to this component at startup.
    let config = Config::take_from_startup_handle();

    // Use the Component Framework runtime to connect to the newly spun up server component. We wrap
    // our retained client end in a proxy object that lets us asynchronously send `Store` requests
    // across the channel.
    let store = connect_to_protocol::<StoreMarker>()?;
    println!("Outgoing connection enabled");

    // This client's structured config has one parameter, a vector of strings. Each string is the
    // path to a resource file whose filename is a key and whose contents are a value. We iterate
    // over them and try to write each key-value pair to the remote store.
    for key in config.write_items.into_iter() {
        let path = format!("/pkg/data/{}.txt", key);
        let value = std::fs::read_to_string(path.clone())
            .with_context(|| format!("Failed to load {path}"))?;
        match store.write_item(&Item { key: key, value: value.into_bytes() }).await? {
            Ok(_) => println!("WriteItem Success"),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the
    // referenced bug has been resolved, we can remove the sleep.
    thread::sleep(time::Duration::from_secs(2));
    Ok(())
}

Server

// Copyright 2022 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.

use anyhow::{Context as _, Error};
use fidl_examples_keyvaluestore_baseline::{Item, StoreRequest, StoreRequestStream, WriteError};
use fuchsia_component::server::ServiceFs;
use futures::prelude::*;
use lazy_static::lazy_static;
use regex::Regex;
use std::cell::RefCell;
use std::collections::hash_map::Entry;
use std::collections::HashMap;

lazy_static! {
    static ref KEY_VALIDATION_REGEX: Regex =
        Regex::new(r"^[A-Za-z]\w+[A-Za-z0-9]$").expect("Key validation regex failed to compile");
}

/// Handler for the `WriteItem` method.
fn write_item(store: &mut HashMap<String, Vec<u8>>, attempt: Item) -> Result<(), WriteError> {
    // Validate the key.
    if !KEY_VALIDATION_REGEX.is_match(attempt.key.as_str()) {
        println!("Write error: INVALID_KEY, For key: {}", attempt.key);
        return Err(WriteError::InvalidKey);
    }

    // Validate the value.
    if attempt.value.is_empty() {
        println!("Write error: INVALID_VALUE, For key: {}", attempt.key);
        return Err(WriteError::InvalidValue);
    }

    // Write to the store, validating that the key did not already exist.
    match store.entry(attempt.key) {
        Entry::Occupied(entry) => {
            println!("Write error: ALREADY_EXISTS, For key: {}", entry.key());
            Err(WriteError::AlreadyExists)
        }
        Entry::Vacant(entry) => {
            println!("Wrote value at key: {}", entry.key());
            entry.insert(attempt.value);
            Ok(())
        }
    }
}

/// Creates a new instance of the server. Each server has its own bespoke, per-connection instance
/// of the key-value store.
async fn run_server(stream: StoreRequestStream) -> Result<(), Error> {
    // Create a new in-memory key-value store. The store will live for the lifetime of the
    // connection between the server and this particular client.
    let store = RefCell::new(HashMap::<String, Vec<u8>>::new());

    // Serve all requests on the protocol sequentially - a new request is not handled until its
    // predecessor has been processed.
    stream
        .map(|result| result.context("failed request"))
        .try_for_each(|request| async {
            // Match based on the method being invoked.
            match request {
                StoreRequest::WriteItem { attempt, responder } => {
                    println!("WriteItem request received");

                    // The `responder` parameter is a special struct that manages the outgoing reply
                    // to this method call. Calling `send` on the responder exactly once will send
                    // the reply.
                    responder
                        .send(write_item(&mut store.borrow_mut(), attempt))
                        .context("error sending reply")?;
                    println!("WriteItem response sent");
                }
                StoreRequest::_UnknownMethod { ordinal, .. } => {
                    println!("Received an unknown method with ordinal {ordinal}");
                }
            }
            Ok(())
        })
        .await
}

// A helper enum that allows us to treat a `Store` service instance as a value.
enum IncomingService {
    Store(StoreRequestStream),
}

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Add a discoverable instance of our `Store` protocol - this will allow the client to see the
    // server and connect to it.
    let mut fs = ServiceFs::new_local();
    fs.dir("svc").add_fidl_service(IncomingService::Store);
    fs.take_and_serve_directory_handle()?;
    println!("Listening for incoming connections");

    // The maximum number of concurrent clients that may be served by this process.
    const MAX_CONCURRENT: usize = 10;

    // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit.
    fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Store(stream)| {
        run_server(stream).unwrap_or_else(|e| println!("{:?}", e))
    })
    .await;

    Ok(())
}

C++ (Natural)

Client

// Copyright 2022 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 <fidl/examples.keyvaluestore.baseline/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/syslog/cpp/macros.h>
#include <unistd.h>

#include <examples/fidl/new/key_value_store/baseline/cpp_natural/client/config.h>
#include <src/lib/files/file.h>
#include <src/lib/fxl/strings/string_printf.h>

int main(int argc, const char** argv) {
  FX_LOGS(INFO) << "Started";

  // Retrieve component configuration.
  auto conf = config::Config::TakeFromStartupHandle();

  // Start up an async loop and dispatcher.
  async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
  async_dispatcher_t* dispatcher = loop.dispatcher();

  // Connect to the protocol inside the component's namespace. This can fail so it's wrapped in a
  // |zx::result| and it must be checked for errors.
  zx::result client_end = component::Connect<examples_keyvaluestore_baseline::Store>();
  if (!client_end.is_ok()) {
    FX_LOGS(ERROR) << "Synchronous error when connecting to the |Store| protocol: "
                   << client_end.status_string();
    return -1;
  }

  // Create an asynchronous client using the newly-established connection.
  fidl::Client client(std::move(*client_end), dispatcher);
  FX_LOGS(INFO) << "Outgoing connection enabled";

  for (const auto& action : conf.write_items()) {
    std::string text;
    if (!files::ReadFileToString(fxl::StringPrintf("/pkg/data/%s.txt", action.c_str()), &text)) {
      FX_LOGS(ERROR) << "It looks like the correct `resource` dependency has not been packaged";
      break;
    }

    auto value = std::vector<uint8_t>(text.begin(), text.end());
    client->WriteItem(examples_keyvaluestore_baseline::Item(action, value))
        .ThenExactlyOnce(
            [&](fidl::Result<examples_keyvaluestore_baseline::Store::WriteItem> result) {
              // Check if the FIDL call succeeded or not.
              if (!result.is_ok()) {
                if (result.error_value().is_framework_error()) {
                  FX_LOGS(ERROR) << "Unexpected FIDL framework error: " << result.error_value();
                } else {
                  FX_LOGS(INFO) << "WriteItem Error: "
                                << fidl::ToUnderlying(result.error_value().domain_error());
                }
              } else {
                FX_LOGS(INFO) << "WriteItem Success";
              }

              // Quit the loop, thereby handing control back to the outer loop of actions being
              // iterated over.
              loop.Quit();
            });

    // Run the loop until the callback is resolved, at which point we can continue from here.
    loop.Run();
    loop.ResetQuit();
  }

  // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the
  // referenced bug has been resolved, we can remove the sleep.
  sleep(2);
  return 0;
}

Server

// Copyright 2022 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 <fidl/examples.keyvaluestore.baseline/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/cpp/task.h>
#include <lib/component/outgoing/cpp/outgoing_directory.h>
#include <lib/fidl/cpp/wire/channel.h>
#include <lib/syslog/cpp/macros.h>
#include <unistd.h>

#include <re2/re2.h>

// An implementation of the |Store| protocol.
class StoreImpl final : public fidl::Server<examples_keyvaluestore_baseline::Store> {
 public:
  // Bind this implementation to a channel.
  StoreImpl(async_dispatcher_t* dispatcher,
            fidl::ServerEnd<examples_keyvaluestore_baseline::Store> server_end)
      : binding_(fidl::BindServer(
            dispatcher, std::move(server_end), this,
            [this](StoreImpl* impl, fidl::UnbindInfo info,
                   fidl::ServerEnd<examples_keyvaluestore_baseline::Store> server_end) {
              if (info.reason() != ::fidl::Reason::kPeerClosedWhileReading) {
                FX_LOGS(ERROR) << "Shutdown unexpectedly";
              }
              delete this;
            })) {}

  void WriteItem(WriteItemRequest& request, WriteItemCompleter::Sync& completer) override {
    FX_LOGS(INFO) << "WriteItem request received";
    auto key = request.attempt().key();
    auto value = request.attempt().value();

    // Validate the key.
    if (!RE2::FullMatch(key, "^[A-Za-z]\\w+[A-Za-z0-9]$")) {
      FX_LOGS(INFO) << "Write error: INVALID_KEY, For key: " << key;
      FX_LOGS(INFO) << "WriteItem response sent";
      return completer.Reply(fit::error(examples_keyvaluestore_baseline::WriteError::kInvalidKey));
    }

    // Validate the value.
    if (value.empty()) {
      FX_LOGS(INFO) << "Write error: INVALID_VALUE, For key: " << key;
      FX_LOGS(INFO) << "WriteItem response sent";
      return completer.Reply(
          fit::error(examples_keyvaluestore_baseline::WriteError::kInvalidValue));
    }

    if (key_value_store_.find(key) != key_value_store_.end()) {
      FX_LOGS(INFO) << "Write error: ALREADY_EXISTS, For key: " << key;
      FX_LOGS(INFO) << "WriteItem response sent";
      return completer.Reply(
          fit::error(examples_keyvaluestore_baseline::WriteError::kAlreadyExists));
    }

    // Ensure that the value does not already exist in the store.
    key_value_store_.insert({key, value});
    FX_LOGS(INFO) << "Wrote value at key: " << key;
    FX_LOGS(INFO) << "WriteItem response sent";
    return completer.Reply(fit::ok());
  }

  void handle_unknown_method(
      fidl::UnknownMethodMetadata<examples_keyvaluestore_baseline::Store> metadata,
      fidl::UnknownMethodCompleter::Sync& completer) override {
    FX_LOGS(WARNING) << "Received an unknown method with ordinal " << metadata.method_ordinal;
  }

 private:
  fidl::ServerBindingRef<examples_keyvaluestore_baseline::Store> binding_;

  // The map that serves as the per-connection instance of the key-value store.
  std::unordered_map<std::string, std::vector<uint8_t>> key_value_store_ = {};
};

int main(int argc, char** argv) {
  FX_LOGS(INFO) << "Started";

  // The event loop is used to asynchronously listen for incoming connections and requests from the
  // client. The following initializes the loop, and obtains the dispatcher, which will be used when
  // binding the server implementation to a channel.
  async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
  async_dispatcher_t* dispatcher = loop.dispatcher();

  // Create an |OutgoingDirectory| instance.
  //
  // The |component::OutgoingDirectory| class serves the outgoing directory for our component. This
  // directory is where the outgoing FIDL protocols are installed so that they can be provided to
  // other components.
  component::OutgoingDirectory outgoing = component::OutgoingDirectory(dispatcher);

  // The `ServeFromStartupInfo()` function sets up the outgoing directory with the startup handle.
  // The startup handle is a handle provided to every component by the system, so that they can
  // serve capabilities (e.g. FIDL protocols) to other components.
  zx::result result = outgoing.ServeFromStartupInfo();
  if (result.is_error()) {
    FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string();
    return -1;
  }

  // Register a handler for components trying to connect to |Store|.
  result = outgoing.AddUnmanagedProtocol<examples_keyvaluestore_baseline::Store>(
      [dispatcher](fidl::ServerEnd<examples_keyvaluestore_baseline::Store> server_end) {
        // Create an instance of our StoreImpl that destroys itself when the connection closes.
        new StoreImpl(dispatcher, std::move(server_end));
      });
  if (result.is_error()) {
    FX_LOGS(ERROR) << "Failed to add Store protocol: " << result.status_string();
    return -1;
  }

  // Everything is wired up. Sit back and run the loop until an incoming connection wakes us up.
  FX_LOGS(INFO) << "Listening for incoming connections";
  loop.Run();
  return 0;
}

C++ (Wire)

Client

// Copyright 2022 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 <fidl/examples.keyvaluestore.baseline/cpp/wire.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/syslog/cpp/macros.h>
#include <unistd.h>

#include <examples/fidl/new/key_value_store/baseline/cpp_wire/client/config.h>
#include <src/lib/files/file.h>
#include <src/lib/fxl/strings/string_printf.h>

int main(int argc, const char** argv) {
  FX_LOGS(INFO) << "Started";

  // Retrieve component configuration.
  auto conf = config::Config::TakeFromStartupHandle();

  // Start up an async loop and dispatcher.
  async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
  async_dispatcher_t* dispatcher = loop.dispatcher();

  // Connect to the protocol inside the component's namespace. This can fail so it's wrapped in a
  // |zx::result| and it must be checked for errors.
  zx::result client_end = component::Connect<examples_keyvaluestore_baseline::Store>();
  if (!client_end.is_ok()) {
    FX_LOGS(ERROR) << "Synchronous error when connecting to the |Store| protocol: "
                   << client_end.status_string();
    return -1;
  }

  // Create an asynchronous client using the newly-established connection.
  fidl::WireClient client(std::move(*client_end), dispatcher);
  FX_LOGS(INFO) << "Outgoing connection enabled";

  for (const auto& key : conf.write_items()) {
    std::string text;
    if (!files::ReadFileToString(fxl::StringPrintf("/pkg/data/%s.txt", key.c_str()), &text)) {
      FX_LOGS(ERROR) << "It looks like the correct `resource` dependency has not been packaged";
      break;
    }

    auto value = std::vector<uint8_t>(text.begin(), text.end());
    client
        ->WriteItem(
            {fidl::StringView::FromExternal(key), fidl::VectorView<uint8_t>::FromExternal(value)})
        .ThenExactlyOnce(
            [&](fidl::WireUnownedResult<examples_keyvaluestore_baseline::Store::WriteItem>&
                    result) {
              if (!result.ok()) {
                FX_LOGS(ERROR) << "Unexpected framework error";
              } else if (result->is_error()) {
                FX_LOGS(INFO) << "WriteItem Error: " << fidl::ToUnderlying(result->error_value());
              } else {
                FX_LOGS(INFO) << "WriteItem Success";
              }

              // Quit the loop, thereby handing control back to the outer loop of actions being
              // iterated over.
              loop.Quit();
            });

    // Run the loop until the callback is resolved, at which point we can continue from here.
    loop.Run();
    loop.ResetQuit();
  }

  // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the
  // referenced bug has been resolved, we can remove the sleep.
  sleep(2);
  return 0;
}

Server

// Copyright 2022 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 <fidl/examples.keyvaluestore.baseline/cpp/wire.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/cpp/task.h>
#include <lib/component/outgoing/cpp/outgoing_directory.h>
#include <lib/fidl/cpp/wire/channel.h>
#include <lib/syslog/cpp/macros.h>
#include <unistd.h>

#include <re2/re2.h>

// An implementation of the |Store| protocol.
class StoreImpl final : public fidl::WireServer<examples_keyvaluestore_baseline::Store> {
 public:
  // Bind this implementation to a channel.
  StoreImpl(async_dispatcher_t* dispatcher,
            fidl::ServerEnd<examples_keyvaluestore_baseline::Store> server_end)
      : binding_(fidl::BindServer(
            dispatcher, std::move(server_end), this,
            [this](StoreImpl* impl, fidl::UnbindInfo info,
                   fidl::ServerEnd<examples_keyvaluestore_baseline::Store> server_end) {
              if (info.reason() != ::fidl::Reason::kPeerClosedWhileReading) {
                FX_LOGS(ERROR) << "Shutdown unexpectedly";
              }
              delete this;
            })) {}

  void WriteItem(WriteItemRequestView request, WriteItemCompleter::Sync& completer) override {
    FX_LOGS(INFO) << "WriteItem request received";
    std::string key{request->attempt.key.get()};
    std::vector<uint8_t> value{request->attempt.value.begin(), request->attempt.value.end()};

    // Validate the key.
    if (!RE2::FullMatch(key, "^[A-Za-z]\\w+[A-Za-z0-9]$")) {
      FX_LOGS(INFO) << "Write error: INVALID_KEY, For key: " << key;
      FX_LOGS(INFO) << "WriteItem response sent";
      return completer.Reply(fit::error(examples_keyvaluestore_baseline::WriteError::kInvalidKey));
    }

    // Validate the value.
    if (value.empty()) {
      FX_LOGS(INFO) << "Write error: INVALID_VALUE, For key: " << key;
      FX_LOGS(INFO) << "WriteItem response sent";
      return completer.Reply(
          fit::error(examples_keyvaluestore_baseline::WriteError::kInvalidValue));
    }

    if (key_value_store_.find(key) != key_value_store_.end()) {
      FX_LOGS(INFO) << "Write error: ALREADY_EXISTS, For key: " << key;
      FX_LOGS(INFO) << "WriteItem response sent";
      return completer.Reply(
          fit::error(examples_keyvaluestore_baseline::WriteError::kAlreadyExists));
    }

    // Ensure that the value does not already exist in the store.
    key_value_store_.insert({key, value});
    FX_LOGS(INFO) << "Wrote value at key: " << key;
    FX_LOGS(INFO) << "WriteItem response sent";
    return completer.Reply(fit::success());
  }

  void handle_unknown_method(
      fidl::UnknownMethodMetadata<examples_keyvaluestore_baseline::Store> metadata,
      fidl::UnknownMethodCompleter::Sync& completer) override {
    FX_LOGS(WARNING) << "Received an unknown method with ordinal " << metadata.method_ordinal;
  }

 private:
  fidl::ServerBindingRef<examples_keyvaluestore_baseline::Store> binding_;

  // The map that serves as the per-connection instance of the key-value store.
  std::unordered_map<std::string, const std::vector<uint8_t>> key_value_store_ = {};
};

int main(int argc, char** argv) {
  FX_LOGS(INFO) << "Started";

  // The event loop is used to asynchronously listen for incoming connections and requests from the
  // client. The following initializes the loop, and obtains the dispatcher, which will be used when
  // binding the server implementation to a channel.
  async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
  async_dispatcher_t* dispatcher = loop.dispatcher();

  // Create an |OutgoingDirectory| instance.
  //
  // The |component::OutgoingDirectory| class serves the outgoing directory for our component. This
  // directory is where the outgoing FIDL protocols are installed so that they can be provided to
  // other components.
  component::OutgoingDirectory outgoing = component::OutgoingDirectory(dispatcher);

  // The `ServeFromStartupInfo()` function sets up the outgoing directory with the startup handle.
  // The startup handle is a handle provided to every component by the system, so that they can
  // serve capabilities (e.g. FIDL protocols) to other components.
  zx::result result = outgoing.ServeFromStartupInfo();
  if (result.is_error()) {
    FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string();
    return -1;
  }

  // Register a handler for components trying to connect to |Store|.
  result = outgoing.AddUnmanagedProtocol<examples_keyvaluestore_baseline::Store>(
      [dispatcher](fidl::ServerEnd<examples_keyvaluestore_baseline::Store> server_end) {
        // Create an instance of our StoreImpl that destroys itself when the connection closes.
        new StoreImpl(dispatcher, std::move(server_end));
      });
  if (result.is_error()) {
    FX_LOGS(ERROR) << "Failed to add Store protocol: " << result.status_string();
    return -1;
  }

  // Everything is wired up. Sit back and run the loop until an incoming connection wakes us up.
  FX_LOGS(INFO) << "Listening for incoming connections";
  loop.Run();
  return 0;
}

HLCPP

Client

// TODO(https://fxbug.dev/42060656): HLCPP implementation.

Server

// TODO(https://fxbug.dev/42060656): HLCPP implementation.

What's next?

Key-value store: Improving the design details how to add to this baseline key-value store. Specifically, this details how to complete the following:

  • Adding support for reading from the store
  • Using generic values
  • Supporting nested key-value stores
  • Adding support for iterating the store
  • Enabling exporting backups