- `<byte>`: 1 byte
- `<u8>`: unsigned 8-bit integer (1 byte)
- `<u64>`: unsigned 64-bit integer in host byte order (8 bytes)
+- `<bool>`: 1 byte (0x00: false, 0x01: true)
### Common types
- 0x00: `get`/`put`/`remove` requests
- 0x01: `info` request
+- 0x02: `exists` request
### Server greeting (server to client)
<diag_num> ::= <u8>
<diag> ::= <msg> ; message to be logged by the client
```
+
+#### Exists (capability 0x02)
+
+Check if remote storage has a value.
+
+```
+<exists_request> ::= 0x05 <key>
+<exists_response> ::= <ok> <exists> | <err>
+<exists> ::= <bool>
+```
constexpr uint8_t k_request_remove = 0x02;
constexpr uint8_t k_request_stop = 0x03;
constexpr uint8_t k_request_info = 0x04;
+constexpr uint8_t k_request_exists = 0x05;
} // namespace
return {};
}
+tl::expected<bool, Client::Error>
+Client::exists(std::span<const uint8_t> key)
+{
+ TRY(verify_connected());
+ TRY(verify_key_size(key));
+
+ m_request_start_time = std::chrono::steady_clock::now();
+
+ util::Bytes msg;
+ msg.reserve(2 + key.size());
+ msg.push_back(k_request_exists);
+ msg.push_back(static_cast<uint8_t>(key.size()));
+ msg.insert(msg.end(), key.data(), key.size());
+ TRY(send_bytes(msg));
+ return receive_response_exists();
+}
+
tl::expected<std::optional<util::Bytes>, Client::Error>
Client::get(std::span<const uint8_t> key)
{
header.insert(header.end(), len_bytes, sizeof(uint64_t));
TRY(send_bytes(header));
TRY(send_bytes(value));
- return receive_response_bool();
+ return receive_response_ok_noop_error();
}
tl::expected<bool, Client::Error>
msg.push_back(static_cast<uint8_t>(key.size()));
msg.insert(msg.end(), key.data(), key.size());
TRY(send_bytes(msg));
- return receive_response_bool();
+ return receive_response_ok_noop_error();
}
tl::expected<void, Client::Error>
return std::string(msg_bytes.begin(), msg_bytes.end());
}
+tl::expected<bool, Client::Error>
+Client::receive_response_exists()
+{
+ TRY_ASSIGN(uint8_t status_byte, receive_u8());
+ auto status = static_cast<Status>(status_byte);
+
+ switch (status) {
+ case Status::ok: {
+ TRY_ASSIGN(uint8_t exists, receive_u8());
+ return exists;
+ }
+
+ case Status::error: {
+ TRY_ASSIGN(auto err_msg, receive_error_string());
+ return tl::unexpected(Error(Failure::error, err_msg));
+ }
+
+ default:
+ return tl::unexpected(
+ Error(Failure::error, FMT("Invalid status code: {}", status_byte)));
+ }
+}
+
tl::expected<std::optional<util::Bytes>, Client::Error>
Client::receive_response_get()
{
}
tl::expected<bool, Client::Error>
-Client::receive_response_bool()
+Client::receive_response_ok_noop_error()
{
TRY_ASSIGN(uint8_t status_byte, receive_u8());
auto status = static_cast<Status>(status_byte);
static constexpr uint8_t k_protocol_version = 0x01;
enum class Capability : uint8_t {
- get_put_remove = 0x00, // get/put/remove operations
- info = 0x01, // info operation
+ get_put_remove = 0x00,
+ info = 0x01,
+ exists = 0x02,
};
enum class Status : uint8_t {
const std::vector<Capability>& capabilities() const;
bool has_capability(Capability cap) const;
+ tl::expected<bool, Error> exists(std::span<const uint8_t> key);
+
tl::expected<std::optional<util::Bytes>, Error>
get(std::span<const uint8_t> key);
tl::expected<void, Error> verify_connected() const;
static tl::expected<void, Error>
verify_key_size(std::span<const uint8_t> key);
+ tl::expected<bool, Error> receive_response_exists();
tl::expected<std::optional<util::Bytes>, Error> receive_response_get();
- tl::expected<bool, Error> receive_response_bool();
+ tl::expected<bool, Error> receive_response_ok_noop_error();
tl::expected<void, Error> receive_response_void();
};
return "get/put/remove";
case Client::Capability::info:
return "info";
+ case Client::Capability::exists:
+ return "exists";
}
return FMT("{}", static_cast<int>(capability));
}
{
TRY(ensure_connected());
- Client::PutFlags flags;
- flags.overwrite = (overwrite == Overwrite::yes);
+ Client::PutFlags flags{.overwrite = true};
+
+ if (overwrite == Overwrite::no) {
+ if (m_client.has_capability(Client::Capability::exists)) {
+ // Prefer asking with exists instead of setting overwrite=false to avoid
+ // having to send the payload to the helper in case the key already
+ // exists.
+ auto result = m_client.exists(key);
+ if (!result) {
+ const auto& error = result.error();
+ LOG("Remote storage exists failed: {}", error.message);
+ auto failure = (error.failure == Client::Failure::timeout)
+ ? Failure::timeout
+ : Failure::error;
+ return tl::unexpected(failure);
+ }
+ bool exists = *result;
+ if (exists) {
+ return false;
+ }
+ } else {
+ flags.overwrite = false;
+ }
+ }
auto result = m_client.put(key, value, flags);
if (!result) {
Commands:
ping check if helper is reachable
info print helper info
+ stop tell the helper to stop
+
+ exists KEY check if a value exists in storage
get KEY -o FILE get a value and output to file
get KEY -o - get a value and output to stdout
put [--overwrite] KEY -i FILE put a value from file
put [--overwrite] KEY -i - put a value from stdin
put [--overwrite] KEY -v VALUE put a literal value
remove KEY remove a value from storage
- stop tell the helper to stop
Notes:
KEY must be a hexadecimal string (0-9, a-f, A-F).
PRINT(stream, USAGE_TEXT, program_name);
}
+tl::expected<int, std::string>
+cmd_exists(Client& client, const std::vector<std::string>& args)
+{
+ if (args.size() != 1) {
+ return tl::unexpected("exists requires exactly 1 argument: KEY");
+ }
+
+ auto key_result = util::parse_base16(args[0]);
+ if (!key_result) {
+ PRINT(stderr, "Error: Invalid hex key: {}\n", key_result.error());
+ return 1;
+ }
+ const auto& key = *key_result;
+
+ auto result = client.exists(key);
+
+ if (!result) {
+ PRINT(stderr, "Error: {}\n", result.error().message);
+ return 1;
+ }
+
+ if (*result) {
+ PRINT(stdout, "yes\n");
+ return 0;
+ } else {
+ PRINT(stderr, "no\n");
+ return 2;
+ }
+}
+
tl::expected<int, std::string>
cmd_get(Client& client, const std::vector<std::string>& args)
{
if (command == "ping") {
TRY_ASSIGN(result, cmd_ping(args));
+ } else if (command == "exists") {
+ TRY(require_capability(client, Client::Capability::exists));
+ TRY_ASSIGN(result, cmd_exists(client, args));
} else if (command == "get") {
TRY(require_capability(client, Client::Capability::get_put_remove));
TRY_ASSIGN(result, cmd_get(client, args));
PRINT(stderr, "Error: {}\n", result.error());
return 1;
}
+
+ return 0;
}
constexpr uint8_t PROTOCOL_VERSION = 0x01;
constexpr uint8_t CAP_GET_PUT_REMOVE = 0x00;
constexpr uint8_t CAP_INFO = 0x01;
+constexpr uint8_t CAP_EXISTS = 0x02;
constexpr uint8_t STATUS_OK = 0x00;
constexpr uint8_t STATUS_NOOP = 0x01;
constexpr uint8_t REQ_REMOVE = 0x02;
constexpr uint8_t REQ_STOP = 0x03;
constexpr uint8_t REQ_INFO = 0x04;
+constexpr uint8_t REQ_EXISTS = 0x05;
constexpr uint8_t PUT_FLAG_OVERWRITE = 0x01;
+constexpr uint8_t GREETING[] = {
+ PROTOCOL_VERSION,
+ 3,
+ CAP_GET_PUT_REMOVE,
+ CAP_INFO,
+ CAP_EXISTS,
+};
+
#ifdef _WIN32
using ConnHandle = HANDLE;
#else
bool recv_exact(ConnHandle conn, uint8_t* buf, size_t count);
void send_data(ConnHandle conn, const uint8_t* data, size_t len);
void send_error(ConnHandle conn, const char* message);
+ void handle_exists(ConnHandle conn);
void handle_get(ConnHandle conn);
void handle_info(ConnHandle conn);
void handle_put(ConnHandle conn);
send_data(conn, response.data(), response.size());
}
+void
+IpcServer::handle_exists(ConnHandle conn)
+{
+ uint8_t key_len;
+ if (!recv_exact(conn, &key_len, 1)) {
+ return;
+ }
+
+ std::string key;
+ if (key_len > 0) {
+ key.resize(key_len);
+ if (!recv_exact(conn, reinterpret_cast<uint8_t*>(key.data()), key_len)) {
+ return;
+ }
+ }
+
+ log_msg(FMT("EXISTS: key_len={}", key_len));
+
+ auto it = m_storage.find(key);
+ if (it != m_storage.end()) {
+ static uint8_t response[] = {STATUS_OK, 0x01};
+ send_data(conn, response, sizeof(response));
+ log_msg(" -> exists");
+ } else {
+ static uint8_t response[] = {STATUS_OK, 0x00};
+ send_data(conn, response, sizeof(response));
+ log_msg(" -> does not exist");
+ }
+}
+
void
IpcServer::handle_get(ConnHandle conn)
{
void
IpcServer::handle_client(ConnHandle conn)
{
- uint8_t greeting[] = {PROTOCOL_VERSION, 2, CAP_GET_PUT_REMOVE, CAP_INFO};
- send_data(conn, greeting, sizeof(greeting));
+ send_data(conn, GREETING, sizeof(GREETING));
while (true) {
uint8_t request_type;
update_activity();
switch (request_type) {
+ case REQ_EXISTS:
+ handle_exists(conn);
+ break;
+
case REQ_GET:
handle_get(conn);
break;