Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Rust client for Trusted Information Retrieval #1110

Merged
merged 8 commits into from
Jun 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cloudbuild.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ steps:
waitFor: ['run_examples']
timeout: 60m
entrypoint: 'bash'
args: ['./scripts/run_examples', '-s', 'base', '-l', 'cpp']
args: ['./scripts/run_examples', '-s', 'base', '-a', 'cpp']

- name: 'gcr.io/oak-ci/oak:latest'
id: build_experimental
Expand Down
2 changes: 1 addition & 1 deletion docs/programming-oak.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ All these steps are implemented as a part of the
The Oak Application is then loaded using the Oak Runner:

```bash
./scripts/run_server -a "${PWD}/config.bin"
./scripts/run_server -f "${PWD}/config.bin"
```

The Oak Runner will launch an [Oak Runtime](concepts.md#oak-runtime), and this
Expand Down
15 changes: 15 additions & 0 deletions examples/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"chat/module/rust",
"hello_world/module/rust",
"machine_learning/module/rust",
"trusted_information_retrieval/client/rust",
"trusted_information_retrieval/module/rust",
"private_set_intersection/module/rust",
"running_average/module/rust",
Expand Down
19 changes: 19 additions & 0 deletions examples/trusted_information_retrieval/client/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "trusted_information_retrieval_client"
version = "0.1.0"
authors = ["Ivan Petrov <[email protected]>"]
edition = "2018"
license = "Apache-2.0"

[dependencies]
env_logger = "*"
log = "*"
oak_abi = "=0.1.0"
prost = "*"
structopt = "*"
tokio = { version = "*", features = ["fs", "macros", "sync", "stream"] }
tonic = { version = "*", features = ["tls"] }

[build-dependencies]
oak_utils = "*"
tonic-build = "*"
33 changes: 33 additions & 0 deletions examples/trusted_information_retrieval/client/rust/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Copyright 2020 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.
//

use std::path::Path;

fn main() -> Result<(), Box<dyn std::error::Error>> {
// TODO(#1120): Add Rust client library function for building Protobuf.
let proto_path = Path::new("../../../../examples/trusted_information_retrieval/proto");
let file_path = proto_path.join("trusted_information_retrieval.proto");

// Tell cargo to rerun this build script if the proto file has changed.
// https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorerun-if-changedpath
println!("cargo:rerun-if-changed={}", file_path.display());

tonic_build::configure()
.build_client(true)
.build_server(false)
.compile(&[file_path.as_path()], &[proto_path])?;
Ok(())
}
125 changes: 125 additions & 0 deletions examples/trusted_information_retrieval/client/rust/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//
// Copyright 2020 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.
//

//! Client for the Trusted Information Retrieval example.

pub mod proto {
tonic::include_proto!("oak.examples.trusted_information_retrieval");
}

use log::info;
use oak_abi::label::Label;
use prost::Message;
use proto::{
trusted_information_retrieval_client::TrustedInformationRetrievalClient,
ListPointsOfInterestRequest, Location,
};
use structopt::StructOpt;
use tonic::{
metadata::MetadataValue,
transport::{Certificate, Channel, ClientTlsConfig},
Request,
};

#[derive(StructOpt, Clone)]
#[structopt(about = "Trusted Information Retrieval Client")]
pub struct Opt {
#[structopt(
long,
help = "URI of the Oak application to connect to",
default_value = "https://localhost:8080"
)]
uri: String,
#[structopt(
long,
help = "PEM encoded X.509 TLS root certificate file used by gRPC client"
)]
root_tls_certificate: String,
#[structopt(long, help = "Requested location's latitude in degrees (WGS84)")]
latitude: f32,
#[structopt(long, help = "Requested location's longitude in degrees (WGS84)")]
longitude: f32,
}

// Metadata key that is used for storing Oak labels in the gRPC request.
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md.
const OAK_LABEL_GRPC_METADATA_KEY: &str = "x-oak-label-bin";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let opt = Opt::from_args();

let uri = opt.uri.parse().expect("Error parsing URI");
let root_tls_certificate = tokio::fs::read(&opt.root_tls_certificate)
.await
.expect("Could not load certificate file");
let latitude = opt.latitude;
if latitude < -90.0 || latitude > 90.0 {
panic!(
"Latitude must be a valid floating point number >=-90 and <= 90, found {}",
latitude
);
}
let longitude = opt.longitude;
if longitude < -180.0 || longitude > 180.0 {
panic!(
"Longitude must be a valid floating point number >= -180 and <= 180, found {}",
longitude
);
}

info!("Connecting to Oak Application: {:?}", uri);
let tls_config =
ClientTlsConfig::new().ca_certificate(Certificate::from_pem(root_tls_certificate));
let channel = Channel::builder(uri)
.tls_config(tls_config)
.connect()
.await
.expect("Could not connect to Oak Application");

// TODO(#1097): Turn the following logic into a proper reusable client library.
let mut label = Vec::new();
Label::public_untrusted()
.encode(&mut label)
.expect("Error encoding label");
let mut client = TrustedInformationRetrievalClient::with_interceptor(
channel,
move |mut request: Request<()>| {
request.metadata_mut().insert_bin(
OAK_LABEL_GRPC_METADATA_KEY,
MetadataValue::from_bytes(label.as_ref()),
);
Ok(request)
},
);

let request = Request::new(ListPointsOfInterestRequest {
location: Some(Location {
latitude,
longitude,
}),
});
info!("Sending request: {:?}", request);

let response = client
.list_points_of_interest(request)
.await
.expect("Could not receive response");
info!("Received response: {:?}", response);

Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
#include "oak/client/application_client.h"

ABSL_FLAG(std::string, address, "localhost:8080", "Address of the Oak application to connect to");
ABSL_FLAG(std::vector<std::string>, location, std::vector<std::string>{},
"Requested location (latitude and longitude separated by comma)");
ABSL_FLAG(std::string, ca_cert, "", "Path to the PEM-encoded CA root certificate");
ABSL_FLAG(float, latitude, float{}, "Requested location's latitude in degrees (WGS84)");
ABSL_FLAG(float, longitude, float{}, "Requested location's longitude in degrees (WGS84)");

using ::oak::examples::trusted_information_retrieval::ListPointsOfInterestRequest;
using ::oak::examples::trusted_information_retrieval::ListPointsOfInterestResponse;
Expand Down Expand Up @@ -73,17 +73,15 @@ int main(int argc, char** argv) {
}

// Parse arguments.
auto location = absl::GetFlag(FLAGS_location);
if (location.size() != 2) {
LOG(FATAL) << "Incorrect number of coordinates: " << location.size() << " (expected 2)";
float latitude = absl::GetFlag(FLAGS_latitude);
if (latitude < -90.0 || latitude > 90.0) {
LOG(FATAL) << "Latitude must be a valid floating point number >=-90 and <= 90, found "
<< latitude;
}
float latitude;
if (!absl::SimpleAtof(location.front(), &latitude) && latitude >= -90.0 && latitude <= 90.0) {
LOG(FATAL) << "Latitude must be a valid floating point number >=-90 and <= 90.";
}
float longitude;
if (!absl::SimpleAtof(location.back(), &longitude) && longitude >= -180.0 && longitude <= 180.0) {
LOG(FATAL) << "Longitude must be a valid floating point number >= -180 and <= 180.";
float longitude = absl::GetFlag(FLAGS_longitude);
if (longitude < -180.0 || longitude > 180.0) {
LOG(FATAL) << "Longitude must be a valid floating point number >= -180 and <= 180, found "
<< longitude;
}

// Get nearest point of interest from the server.
Expand Down
46 changes: 31 additions & 15 deletions scripts/build_example
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,25 @@ readonly SCRIPTS_DIR="$(dirname "$0")"
# shellcheck source=scripts/common
source "${SCRIPTS_DIR}/common"

language="rust"
application_language="rust"
client_language="cpp"
compilation_mode='fastbuild'
docker_config=''
while getopts "e:l:di:h" opt; do
while getopts "e:a:c:di:h" opt; do
case "${opt}" in
h)
echo -e "Usage: ${0} [-h] [-l rust|cpp] [-i base|logless] [-d] -e EXAMPLE
echo -e "Usage: ${0} [-h] [-a rust|cpp] [-c rust|cpp] [-i base|logless] [-d] -e EXAMPLE

Build the given example Oak Application and client.

Options:
-e Example application name (required)
-l Example application variant:
-a Example application variant:
- rust (used by default)
- cpp
-c Example client variant:
- rust
- cpp (used by default)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This isn't quite selecting client implementation language; it's more selecting client build system: cargo (always Rust underneath) vs Bazel (usually C++ underneath, but could also be Go).

I don't think it worth changing just for pedantry's sake, but it may be relevant for thinking about how the Node.js client would fit in here (cc @juliettepretot )

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. I was a bit puzzled by this, since the go client is run as a "cpp" client. :)

-d Build C++ code for example using debug mode
-i This flag enables packaging the application into a Docker image,
and specifies the version of the Oak server, used by the application:
Expand All @@ -28,8 +32,10 @@ Options:
exit 0;;
e)
readonly EXAMPLE="${OPTARG}";;
l)
language="${OPTARG}";;
a)
application_language="${OPTARG}";;
c)
client_language="${OPTARG}";;
d)
compilation_mode='dbg';;
i)
Expand All @@ -45,7 +51,7 @@ if [[ -z "${EXAMPLE+z}" ]]; then
exit 1
fi

case "${language}" in
case "${application_language}" in
rust)
for module in examples/"${EXAMPLE}"/module*/rust/Cargo.toml; do
# Use a separate target dir for Wasm build artifacts. The precise name is not relevant, but it
Expand Down Expand Up @@ -78,7 +84,7 @@ case "${language}" in
fi
;;
*)
echo "Invalid example variant: ${language}"
echo "Invalid example application variant: ${application_language}"
exit 1;;
esac

Expand All @@ -96,11 +102,21 @@ if [[ -n "${docker_config}" ]]; then
fi
fi

bazel_build_flags+=(
'--symlink_prefix=bazel-client-'
"--compilation_mode=${compilation_mode}"
)
case "${client_language}" in
rust)
cargo build --release "--manifest-path=./examples/${EXAMPLE}/client/rust/Cargo.toml"
;;
cpp)
bazel_build_flags+=(
'--symlink_prefix=bazel-client-'
"--compilation_mode=${compilation_mode}"
)

# Build the client with a different output_base so that we don't lose incremental state.
# See https://docs.bazel.build/versions/master/command-line-reference.html#flag--output_base.
bazel --output_base="${CACHE_DIR}/client" build "${bazel_build_flags[@]}" "//examples/${EXAMPLE}/client:all"
# Build the client with a different output_base so that we don't lose incremental state.
# See https://docs.bazel.build/versions/master/command-line-reference.html#flag--output_base.
bazel --output_base="${CACHE_DIR}/client" build "${bazel_build_flags[@]}" "//examples/${EXAMPLE}/client:all"
;;
*)
echo "Invalid example client variant: ${client_language}"
exit 1;;
esac
2 changes: 1 addition & 1 deletion scripts/build_examples
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ source "$SCRIPTS_DIR/common"
for language in rust cpp; do
examples="$(find examples -mindepth 2 -maxdepth 4 -type d -regex '.*/module.*/'"${language}"'$' | cut -d'/' -f2 | uniq)"
for example in ${examples}; do
"${SCRIPTS_DIR}/build_example" -l "${language}" -e "${example}"
"${SCRIPTS_DIR}/build_example" -a "${language}" -e "${example}"
done
done
Loading