Skip to content

Commit

Permalink
C++ OakSessionClient/OakSessionServer implementations
Browse files Browse the repository at this point in the history
OakServer provides initialized channels using an underlying
OakServerSession.

OakClient provides initialized channels using an underlying
OakClientSession.

Both implementations use an underlying OakChannel implementation. The
channel implementation is templatized on send/receive types, so the same
implementation can be used for both by switching the send/receive
message types, and changing the underlying session type.

I wanted to add error contexts in many places, so I created a simple
absl::Status annotater to help as part of this CR.

Fixed: b/382733114
Fixed: b/356398548
Fixed: b/382481589

Change-Id: I0da73930e9dd9005be2c5803c875f7bb0092196b
  • Loading branch information
jblebrun committed Dec 10, 2024
1 parent 9f44ffa commit f149dc7
Show file tree
Hide file tree
Showing 13 changed files with 796 additions and 0 deletions.
29 changes: 29 additions & 0 deletions cc/client/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
)
78 changes: 78 additions & 0 deletions cc/client/session_client.cc
Original file line number Diff line number Diff line change
@@ -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<std::unique_ptr<OakSessionClient::Channel>>
OakSessionClient::NewChannel(std::unique_ptr<Channel::Transport> transport) {
absl::StatusOr<std::unique_ptr<session::ClientSession>> session =
session::ClientSession::Create();

while (!(*session)->IsOpen()) {
absl::StatusOr<std::optional<session::v1::SessionRequest>> 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<session::v1::SessionResponse> 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
54 changes: 54 additions & 0 deletions cc/client/session_client.h
Original file line number Diff line number Diff line change
@@ -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<session::v1::SessionRequest,
session::v1::SessionResponse,
session::ClientSession>;

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<std::unique_ptr<OakSessionClient::Channel>> NewChannel(
std::unique_ptr<OakSessionClient::Channel::Transport> transport);
};

} // namespace oak::client

#endif // CC_CLIENT_SESSION_CLIENT_H_
101 changes: 101 additions & 0 deletions cc/client/session_client_test.cc
Original file line number Diff line number Diff line change
@@ -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<session::ServerSession> server_session)
: server_session_(std::move(server_session)) {}
absl::Status Send(const session::v1::SessionRequest& request) override {
return server_session_->PutIncomingMessage(request);
}
absl::StatusOr<session::v1::SessionResponse> Receive() override {
absl::StatusOr<std::optional<session::v1::SessionResponse>> 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<session::ServerSession> 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<TestTransport>(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<TestTransport>(std::move(*server_session)));

std::string test_send_msg = "Testing Send";
ASSERT_THAT((*channel)->Send(test_send_msg), IsOk());
absl::StatusOr<std::optional<std::string>> 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<TestTransport>(std::move(*server_session)));

std::string test_recv_msg = "Testing Receive";
ASSERT_THAT(server_session_ptr->Write(test_recv_msg), IsOk());

absl::StatusOr<std::string> server_read = (*channel)->Receive();
EXPECT_THAT(server_read, IsOk());
EXPECT_THAT(*server_read, Eq(test_recv_msg));
}
} // namespace
} // namespace oak::client
31 changes: 31 additions & 0 deletions cc/oak_session/channel/BUILD
Original file line number Diff line number Diff line change
@@ -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",
],
)
Loading

0 comments on commit f149dc7

Please sign in to comment.