diff --git a/cc/client/BUILD b/cc/client/BUILD index 375c68f0f4..770208fea4 100644 --- a/cc/client/BUILD +++ b/cc/client/BUILD @@ -79,3 +79,32 @@ cc_binary( "@com_google_absl//absl/log", ], ) + +cc_library( + name = "session_client", + srcs = ["session_client.cc"], + hdrs = ["session_client.h"], + deps = [ + "//cc/oak_session:client_session", + "//cc/oak_session/channel", + "//cc/utils/status", + "//proto/session:session_cc_proto", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + ], +) + +cc_test( + name = "session_client_test", + srcs = ["session_client_test.cc"], + deps = [ + ":session_client", + "//cc/oak_session:client_session", + "//cc/oak_session:server_session", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:status_matchers", + "@com_google_absl//absl/status:statusor", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/cc/client/session_client.cc b/cc/client/session_client.cc new file mode 100644 index 0000000000..28df340651 --- /dev/null +++ b/cc/client/session_client.cc @@ -0,0 +1,78 @@ +/* + * Copyright 2024 The Project Oak Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cc/client/session_client.h" + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "cc/oak_session/client_session.h" +#include "cc/utils/status/status.h" +#include "proto/session/session.pb.h" + +namespace oak::client { + +absl::StatusOr> +OakSessionClient::NewChannel(std::unique_ptr transport) { + absl::StatusOr> session = + session::ClientSession::Create(); + + while (!(*session)->IsOpen()) { + absl::StatusOr> init_request = + (*session)->GetOutgoingMessage(); + if (!init_request.ok()) { + return util::status::Annotate( + init_request.status(), + "Failed to get outgoing message from state machine"); + } + + if (*init_request == std::nullopt) { + return absl::InternalError("No outgoing message but session not open"); + } + + absl::Status send_result = transport->Send(**init_request); + if (!send_result.ok()) { + return util::status::Annotate(send_result, + "Failed to send outgoing message"); + } + + // Some initialization seqeuences may end with the client sending a final + // request but not expecting any response from the server. + if ((*session)->IsOpen()) { + break; + } + + absl::StatusOr init_response = + transport->Receive(); + if (!init_response.ok()) { + return util::status::Annotate( + init_request.status(), + "Failed to get next init response from server"); + } + + absl::Status put_result = (*session)->PutIncomingMessage(*init_response); + if (!put_result.ok()) { + return util::status::Annotate( + put_result, + "Failed to put next init response in session state machine"); + } + } + + // Need to call private constructor, so WrapUnique instead of make_unique. + return absl::WrapUnique( + new Channel(std::move(*session), std::move(transport))); +} + +} // namespace oak::client diff --git a/cc/client/session_client.h b/cc/client/session_client.h new file mode 100644 index 0000000000..acec71737c --- /dev/null +++ b/cc/client/session_client.h @@ -0,0 +1,54 @@ +/* + * Copyright 2024 The Project Oak Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CC_CLIENT_SESSION_CLIENT_H_ +#define CC_CLIENT_SESSION_CLIENT_H_ + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "cc/oak_session/channel/oak_session_channel.h" +#include "cc/oak_session/client_session.h" +#include "proto/session/session.pb.h" + +namespace oak::client { + +class OakSessionClient { + public: + // OakClientChannel manages an established connection between a client and + // server that communicate using the Noise Protocol via an Oak Session. + using Channel = + session::channel::OakSessionChannel; + + OakSessionClient() = default; + + // Create a new OakClientChannel instance with the provided session and + // transport. + // + // client_session should be a newly created ClientSession instance with a + // configuration that matches the configuration of the target server. + // + // The call will block during the initialization sequence, and return an open + // channel that is ready to use, or return an error if the handshake didn't + // succeed. + absl::StatusOr> NewChannel( + std::unique_ptr transport); +}; + +} // namespace oak::client + +#endif // CC_CLIENT_SESSION_CLIENT_H_ diff --git a/cc/client/session_client_test.cc b/cc/client/session_client_test.cc new file mode 100644 index 0000000000..5dd877b78b --- /dev/null +++ b/cc/client/session_client_test.cc @@ -0,0 +1,101 @@ +/* + * Copyright 2024 The Project Oak Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cc/client/session_client.h" + +#include "absl/status/status_matchers.h" +#include "cc/oak_session/client_session.h" +#include "cc/oak_session/server_session.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace oak::client { +namespace { + +using ::absl_testing::IsOk; +using ::testing::Eq; +using ::testing::Ne; +using ::testing::Optional; + +class TestTransport : public OakSessionClient::Channel::Transport { + public: + TestTransport(std::unique_ptr server_session) + : server_session_(std::move(server_session)) {} + absl::Status Send(const session::v1::SessionRequest& request) override { + return server_session_->PutIncomingMessage(request); + } + absl::StatusOr Receive() override { + absl::StatusOr> msg = + server_session_->GetOutgoingMessage(); + if (!msg.ok()) { + return msg.status(); + } + if (*msg == std::nullopt) { + return absl::FailedPreconditionError("expected outgoing server message"); + } + return **msg; + } + + private: + std::unique_ptr server_session_; +}; + +TEST(OakSessionClientTest, CreateSuccessFullyHandshakes) { + auto server_session = session::ServerSession::Create(); + ASSERT_THAT(server_session, IsOk()); + auto client_session = session::ClientSession::Create(); + ASSERT_THAT(client_session, IsOk()); + auto _ = OakSessionClient().NewChannel( + std::make_unique(std::move(*server_session))); +} + +TEST(OakSessionClientTest, CreatedSessionCanSend) { + auto server_session = session::ServerSession::Create(); + // Hold a pointer for testing behavior below. + session::ServerSession* server_session_ptr = server_session->get(); + ASSERT_THAT(server_session, IsOk()); + auto client_session = session::ClientSession::Create(); + ASSERT_THAT(client_session, IsOk()); + auto channel = OakSessionClient().NewChannel( + std::make_unique(std::move(*server_session))); + + std::string test_send_msg = "Testing Send"; + ASSERT_THAT((*channel)->Send(test_send_msg), IsOk()); + absl::StatusOr> test_send_read_back = + server_session_ptr->Read(); + EXPECT_THAT(test_send_read_back, IsOk()); + EXPECT_THAT(*test_send_read_back, Optional(Eq(test_send_msg))); +} + +TEST(OakSessionClientTest, CreatedSessionCanReceive) { + auto server_session = session::ServerSession::Create(); + // Hold a pointer for testing behavior below. + session::ServerSession* server_session_ptr = server_session->get(); + ASSERT_THAT(server_session, IsOk()); + auto client_session = session::ClientSession::Create(); + ASSERT_THAT(client_session, IsOk()); + auto channel = OakSessionClient().NewChannel( + std::make_unique(std::move(*server_session))); + + std::string test_recv_msg = "Testing Receive"; + ASSERT_THAT(server_session_ptr->Write(test_recv_msg), IsOk()); + + absl::StatusOr server_read = (*channel)->Receive(); + EXPECT_THAT(server_read, IsOk()); + EXPECT_THAT(*server_read, Eq(test_recv_msg)); +} +} // namespace +} // namespace oak::client diff --git a/cc/oak_session/channel/BUILD b/cc/oak_session/channel/BUILD new file mode 100644 index 0000000000..0a69b2805c --- /dev/null +++ b/cc/oak_session/channel/BUILD @@ -0,0 +1,31 @@ +# +# Copyright 2022 The Project Oak Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package( + default_visibility = ["//:default_visibility"], + licenses = ["notice"], +) + +cc_library( + name = "channel", + hdrs = ["oak_session_channel.h"], + deps = [ + "//cc/utils/status", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + ], +) diff --git a/cc/oak_session/channel/oak_session_channel.h b/cc/oak_session/channel/oak_session_channel.h new file mode 100644 index 0000000000..674e95aeb7 --- /dev/null +++ b/cc/oak_session/channel/oak_session_channel.h @@ -0,0 +1,141 @@ +/* + * Copyright 2024 The Project Oak Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CC_SERVER_OAK_SESSION_CHANNEL_H_ +#define CC_SERVER_OAK_SESSION_CHANNEL_H_ + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/substitute.h" +#include "cc/utils/status/status.h" + +namespace oak::session::channel { + +// OakSessionChannel manages an established connection between a client and +// server that communicate using the Noise Protocol via an Oak Session. +// +// This parameterized class provides the implementation for channels managed on +// both the client and server sides. +// +// SendMessage is the type that will be sent out on the wire, ReceiveMessage is +// the type that will be coming in on the wire, and Session is the type of the +// underlying oak session that manages the encrypted channel state. +template +class OakSessionChannel { + public: + // Instances of `OakSessionChannel` need an implementation of this interface, + // which provides the means for receiving requests from a client, and sending + // responses back to that client. + class Transport { + public: + virtual ~Transport() = default; + virtual absl::Status Send(const SendMessage& message) = 0; + + // Implementations should block until a new message is available to return. + // Blocking semantics, deadlines, etc should be defined by the particular + // implementation. + virtual absl::StatusOr Receive() = 0; + }; + + OakSessionChannel(std::unique_ptr session, + std::unique_ptr transport) + : session_(std::move(session)), transport_(std::move(transport)) {} + + // Encrypt and send a message back to the other party. + absl::Status Send(absl::string_view unencrypted_message) { + absl::Status write_result = session_->Write(unencrypted_message); + if (!write_result.ok()) { + return util::status::Annotate(write_result, + "Failed to write message for encryption"); + } + + absl::StatusOr> outgoing_message = + session_->GetOutgoingMessage(); + if (!outgoing_message.ok()) { + return util::status::Annotate(outgoing_message.status(), + "Failed to get encrypted outgoing message"); + } + if (*outgoing_message == std::nullopt) { + return absl::InternalError( + "Reading back encrypted message returned null result"); + } + + absl::Status send_result = transport_->Send(**outgoing_message); + if (!send_result.ok()) { + return util::status::Annotate( + send_result, "Failed to send outgoing message on provided transport"); + } + + return absl::OkStatus(); + } + + // Receive and decrypt a message from the other party. + // The call will block until a message is available, as defined by the + // provided Transport. + absl::StatusOr Receive() { + absl::StatusOr next_request = transport_->Receive(); + + if (!next_request.ok()) { + return util::status::Annotate(next_request.status(), + "Failed to receive request from transport"); + } + + absl::Status put_result = session_->PutIncomingMessage(*next_request); + + if (!put_result.ok()) { + return util::status::Annotate( + put_result, "Failed to put incoming request onto state machine"); + } + + absl::StatusOr> decrypted_message = + session_->Read(); + + if (!decrypted_message.ok()) { + return util::status::Annotate( + decrypted_message.status(), + "Failed to read decrypted message from state machine"); + } + + if (*decrypted_message == std::nullopt) { + return absl::InternalError( + "Reading back decrypted message from state machine returned null " + "result"); + } + + return **decrypted_message; + } + + // Create a new OakSessionChannel instance with the provided session and + // transport. + // + // session should be a newly created Session instance with a + // configuration that matches the configuration of the corresponding client or + // server on the other side.. + // + // The call will block during the initialization sequence, and return an open + // channel that is ready to use, or return an error if the handshake didn't + // succeed. + static absl::StatusOr> Create( + std::unique_ptr session, std::unique_ptr transport); + + private: + std::unique_ptr session_; + std::unique_ptr transport_; +}; + +} // namespace oak::session::channel + +#endif // CC_SERVER_OAK_SESSION_CHANNEL_H_ diff --git a/cc/server/BUILD b/cc/server/BUILD new file mode 100644 index 0000000000..8b4dbc2f3c --- /dev/null +++ b/cc/server/BUILD @@ -0,0 +1,49 @@ +# +# Copyright 2022 The Project Oak Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package( + default_visibility = ["//:default_visibility"], + licenses = ["notice"], +) + +cc_library( + name = "session_server", + srcs = ["session_server.cc"], + hdrs = ["session_server.h"], + deps = [ + "//cc/oak_session:server_session", + "//cc/oak_session/channel", + "//cc/utils/status", + "//proto/session:session_cc_proto", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + ], +) + +cc_test( + name = "server_test", + srcs = ["session_server_test.cc"], + deps = [ + ":session_server", + "//cc/oak_session:client_session", + "//cc/oak_session:server_session", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:status_matchers", + "@com_google_absl//absl/status:statusor", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/cc/server/session_server.cc b/cc/server/session_server.cc new file mode 100644 index 0000000000..edc5f3ea82 --- /dev/null +++ b/cc/server/session_server.cc @@ -0,0 +1,74 @@ +/* + * Copyright 2024 The Project Oak Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cc/server/session_server.h" + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/substitute.h" +#include "cc/oak_session/channel/oak_session_channel.h" +#include "cc/oak_session/server_session.h" +#include "cc/utils/status/status.h" +#include "proto/session/session.pb.h" + +namespace oak::server { + +absl::StatusOr> +OakSessionServer::NewChannel(std::unique_ptr transport) { + auto session = session::ServerSession::Create(); + if (!session.ok()) { + return util::status::Annotate(session.status(), + "Failed to create server session"); + } + + while (!(*session)->IsOpen()) { + absl::StatusOr init_request = + transport->Receive(); + if (!init_request.ok()) { + return util::status::Annotate(init_request.status(), + "Failed to get next init message"); + } + + absl::Status put_result = (*session)->PutIncomingMessage(*init_request); + if (!put_result.ok()) { + return util::status::Annotate( + put_result, + "Failed to put next init message in session state machine"); + } + + absl::StatusOr> init_response = + (*session)->GetOutgoingMessage(); + if (!init_response.ok()) { + return util::status::Annotate( + init_response.status(), + "Failed to get outgoing message from state machine"); + } + + if (*init_response != std::nullopt) { + absl::Status send_result = transport->Send(**init_response); + if (!send_result.ok()) { + return util::status::Annotate(send_result, + "Failed to send outgoing message"); + } + } + } + + // Need to call private constructor, so WrapUnique instead of make_unique. + return absl::WrapUnique( + new Channel(std::move(*session), std::move(transport))); +} + +} // namespace oak::server diff --git a/cc/server/session_server.h b/cc/server/session_server.h new file mode 100644 index 0000000000..705c11a095 --- /dev/null +++ b/cc/server/session_server.h @@ -0,0 +1,52 @@ +/* + * Copyright 2024 The Project Oak Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CC_SERVER_OAK_SERVER_CHANNEL_H_ +#define CC_SERVER_OAK_SERVER_CHANNEL_H_ + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "cc/oak_session/channel/oak_session_channel.h" +#include "cc/oak_session/server_session.h" +#include "proto/session/session.pb.h" + +namespace oak::server { + +class OakSessionServer { + public: + using Channel = + session::channel::OakSessionChannel; + + OakSessionServer() = default; + + // Create a new OakServerChannel instance with the provided session and + // transport. + // + // server_session should be a newly created ServerSession instance with a + // configuration that matches the configuration of the target server. + // + // The call will block during the initialization sequence, and return an open + // channel that is ready to use, or return an error if the handshake didn't + // succeed. + absl::StatusOr> NewChannel( + std::unique_ptr transport); +}; + +} // namespace oak::server + +#endif // CC_SERVER_OAK_SERVER_CHANNEL_H_ diff --git a/cc/server/session_server_test.cc b/cc/server/session_server_test.cc new file mode 100644 index 0000000000..70b3cf8495 --- /dev/null +++ b/cc/server/session_server_test.cc @@ -0,0 +1,95 @@ +/* + * Copyright 2024 The Project Oak Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cc/server/session_server.h" + +#include "absl/status/status_matchers.h" +#include "cc/oak_session/client_session.h" +#include "cc/oak_session/server_session.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace oak::server { +namespace { + +using ::absl_testing::IsOk; +using ::testing::Eq; +using ::testing::Ne; +using ::testing::Optional; + +class TestTransport : public OakSessionServer::Channel::Transport { + public: + TestTransport(std::unique_ptr client_session) + : client_session_(std::move(client_session)) {} + absl::Status Send(const session::v1::SessionResponse& request) override { + return client_session_->PutIncomingMessage(request); + } + absl::StatusOr Receive() override { + absl::StatusOr> msg = + client_session_->GetOutgoingMessage(); + if (!msg.ok()) { + return msg.status(); + } + if (*msg == std::nullopt) { + return absl::FailedPreconditionError("expected outgoing client message"); + } + return **msg; + } + + private: + std::unique_ptr client_session_; +}; + +TEST(OakSessionServerTest, CreateSuccessFullyHandshakes) { + auto client_session = session::ClientSession::Create(); + ASSERT_THAT(client_session, IsOk()); + auto _ = OakSessionServer().NewChannel( + std::make_unique(std::move(*client_session))); +} + +TEST(OakSessionServerTest, CreatedSessionCanSend) { + auto client_session = session::ClientSession::Create(); + // Hold a pointer for testing behavior below. + session::ClientSession* client_session_ptr = client_session->get(); + ASSERT_THAT(client_session, IsOk()); + auto channel = OakSessionServer().NewChannel( + std::make_unique(std::move(*client_session))); + + std::string test_send_msg = "Testing Send"; + ASSERT_THAT((*channel)->Send(test_send_msg), IsOk()); + absl::StatusOr> test_send_read_back = + client_session_ptr->Read(); + EXPECT_THAT(test_send_read_back, IsOk()); + EXPECT_THAT(*test_send_read_back, Optional(Eq(test_send_msg))); +} + +TEST(OakSessionServerTest, CreatedSessionCanReceive) { + auto client_session = session::ClientSession::Create(); + // Hold a pointer for testing behavior below. + session::ClientSession* client_session_ptr = client_session->get(); + ASSERT_THAT(client_session, IsOk()); + auto channel = OakSessionServer().NewChannel( + std::make_unique(std::move(*client_session))); + + std::string test_recv_msg = "Testing Receive"; + ASSERT_THAT(client_session_ptr->Write(test_recv_msg), IsOk()); + + absl::StatusOr server_read = (*channel)->Receive(); + EXPECT_THAT(server_read, IsOk()); + EXPECT_THAT(*server_read, Eq(test_recv_msg)); +} +} // namespace +} // namespace oak::server diff --git a/cc/utils/status/BUILD b/cc/utils/status/BUILD new file mode 100644 index 0000000000..02e09576a8 --- /dev/null +++ b/cc/utils/status/BUILD @@ -0,0 +1,30 @@ +# +# Copyright 2024 The Project Oak Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package( + default_visibility = ["//:default_visibility"], + licenses = ["notice"], +) + +cc_library( + name = "status", + srcs = ["status.cc"], + hdrs = ["status.h"], + deps = [ + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + ], +) diff --git a/cc/utils/status/status.cc b/cc/utils/status/status.cc new file mode 100644 index 0000000000..1076c036e4 --- /dev/null +++ b/cc/utils/status/status.cc @@ -0,0 +1,32 @@ +/* + * Copyright 2024 The Project Oak Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cc/utils/status/status.h" + +#include "absl/status/status.h" +#include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" + +namespace oak::util::status { + +absl::Status Annotate(const absl::Status& s, absl::string_view msg) { + if (s.ok() || msg.empty()) return s; + + std::string new_msg = absl::Substitute("$0: $1", msg, s.message()); + return absl::Status(s.code(), new_msg); +} + +} // namespace oak::util::status diff --git a/cc/utils/status/status.h b/cc/utils/status/status.h new file mode 100644 index 0000000000..a5642f4afc --- /dev/null +++ b/cc/utils/status/status.h @@ -0,0 +1,30 @@ +/* + * Copyright 2024 The Project Oak Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CC_UTILS_STATUS_H_ +#define CC_UTILS_STATUS_H_ + +#include "absl/status/status.h" +#include "absl/strings/string_view.h" + +namespace oak::util::status { + +// Annotate an existing status message by prepending the provided msg. +absl::Status Annotate(const absl::Status& s, absl::string_view msg); + +} // namespace oak::util::status + +#endif // CC_UTILS_STATUS_H_