diff --git a/rust/oak/src/lib.rs b/rust/oak/src/lib.rs index 293a115c52c..67e546802fa 100644 --- a/rust/oak/src/lib.rs +++ b/rust/oak/src/lib.rs @@ -35,6 +35,7 @@ mod tests; #[macro_use] extern crate assert_matches; +/// Result type that uses a [`proto::status::Status`] type for error values. pub type GrpcResult = Result; /// Trait to allow repeated writing of responses for server-streaming gRPC methods. @@ -64,9 +65,12 @@ where } } +/// Handle used to identify read or write channel halves. +/// +/// These handles are used for all host function calls. pub type Handle = u64; -/// Map an OakStatus to the nearest available std::io::Result. +/// Map an [`OakStatus`] value to the nearest available [`std::io::Result`]. fn result_from_status(status: Option, val: T) -> std::io::Result { match status { Some(OakStatus::OAK_STATUS_UNSPECIFIED) => Err(io::Error::new( @@ -133,6 +137,10 @@ mod wasm { } } +/// Create a new unidirectional channel. +/// +/// On success, returns [`Handle`] values for the write and read halves +/// (respectively). pub fn channel_create() -> Result<(Handle, Handle), OakStatus> { let mut write: Handle = 0; let mut read: Handle = 0; @@ -145,6 +153,7 @@ pub fn channel_create() -> Result<(Handle, Handle), OakStatus> { } } +/// Close the specified channel [`Handle`]. pub fn channel_close(handle: Handle) -> OakStatus { match OakStatus::from_i32(unsafe { wasm::channel_close(handle) }) { Some(s) => s, @@ -152,10 +161,16 @@ pub fn channel_close(handle: Handle) -> OakStatus { } } +/// Determine the [`Handle`] for a pre-defined channel, identified by its +/// `port_name`. pub fn channel_find(port_name: &str) -> Handle { unsafe { wasm::channel_find(port_name.as_ptr(), port_name.len()) } } +/// Number of bytes needed per-handle for channel readiness notifications. +/// +/// The notification space consists of the channel handle (as a little-endian +/// u64) followed by a single byte indicating the channel readiness. pub const SPACE_BYTES_PER_HANDLE: usize = 9; // Build a chunk of memory that is suitable for passing to wasm::wait_on_channels, @@ -178,9 +193,12 @@ fn prep_handle_space(space: &mut [u8]) { } } -// Convenience wrapper around wait_on_channels host function. This version is -// easier to use in Rust but is less efficient (because the notification space -// is re-created on each invocation). +/// Wait for one or more of the provided handles to become ready for reading +/// from. +/// +/// This is a convenience wrapper around the [`wasm::wait_on_channels`] host +/// function. This version is easier to use in Rust but is less efficient +/// (because the notification space is re-created on each invocation). pub fn wait_on_channels(handles: &[Handle]) -> Result, OakStatus> { let mut space = new_handle_space(handles); unsafe { @@ -200,6 +218,8 @@ pub fn wait_on_channels(handles: &[Handle]) -> Result, OakStatus> { } } +/// Convenience wrapper for the send half of a channel, to allow use of the +/// [`std::io::Write`] trait. pub struct SendChannelHalf { handle: Handle, } @@ -225,8 +245,6 @@ impl SendChannelHalf { } } -// Implement the Write trait on a send channel for convenience, particularly for -// the logging channel. impl Write for SendChannelHalf { fn write(&mut self, buf: &[u8]) -> std::io::Result { match self.write_message(buf, &[]) { @@ -239,12 +257,20 @@ impl Write for SendChannelHalf { } } +/// Return an instance of the [`std::io::Write`] trait that emits messages to +/// the Node's logging channel. +/// +/// Assumes that the Node has a pre-configured channel to the logging +/// pseudo-Node that is identified by the default port name (`"log"`). pub fn logging_channel() -> impl Write { let logging_channel = SendChannelHalf::new(channel_find("log")); // Only flush logging channel on newlines. std::io::LineWriter::new(logging_channel) } +/// Convenience wrapper for the receive half of a channel. +/// +/// This helps when the underlying [`Handle`] is known to be for a receive half. pub struct ReceiveChannelHalf { handle: Handle, } @@ -312,15 +338,41 @@ impl ReceiveChannelHalf { } } -/// Trait encapsulating the operations required for an Oak Node. +/// Trait for Oak Nodes that act as a gRPC services. +/// +/// An `OakNode` instance is normally passed to [`grpc_event_loop`], to allow +/// repeated invocation of its `invoke()` method. pub trait OakNode { + /// Construct the (single) instance of the node. + /// + /// This method may choose to initialize logging by invoking + /// [`oak_log::init()`]. + /// + /// [`oak_log::init()`]: ../oak_log/fn.init.html fn new() -> Self where Self: Sized; + + /// Process a single gRPC method invocation. + /// + /// The method name is provided by `method` and the incoming serialized gRPC + /// request is held in `req`. Response messages should be written to `out`, + /// as serialized [`GrpcResponse`] messages encapsulating the service + /// response. + /// + /// [`GrpcResponse`]: proto::grpc_encap::GrpcResponse fn invoke(&mut self, method: &str, req: &[u8], out: &mut SendChannelHalf); } -/// Perform a gRPC event loop on the given channels, invoking the given node. +/// Perform a gRPC event loop for a Node. +/// +/// Invoking the given `node`'s [`invoke`] method for each incoming request that +/// arrives on the inbound channel as a serialized [`GrpcRequest`] message, +/// giving the [`invoke`] method the outbound channel for encapsulated responses +/// to be written to. +/// +/// [`invoke`]: OakNode::invoke +/// [`GrpcRequest`]: proto::grpc_encap::GrpcRequest pub fn grpc_event_loop( mut node: T, grpc_in_handle: Handle, @@ -376,8 +428,11 @@ pub fn grpc_event_loop( } } -/// Install a panic hook so that panics are logged to the logging channel, if one is set. -/// See https://doc.rust-lang.org/std/panic/struct.PanicInfo.html. +/// Install a panic hook that logs [panic information]. +/// +/// Logs panic infomation to the logging channel, if one is set. +/// +/// [panic information]: std::panic::PanicInfo pub fn set_panic_hook() { std::panic::set_hook(Box::new(|panic_info| { let msg = panic_info diff --git a/rust/oak/src/proto/mod.rs b/rust/oak/src/proto/mod.rs index 6bc7c007ba8..9b34d80a9d1 100644 --- a/rust/oak/src/proto/mod.rs +++ b/rust/oak/src/proto/mod.rs @@ -1,3 +1,6 @@ +//! Auto-generated code for processing protocol buffer message definitions and +//! gRPC service definitions. + #[allow(clippy::all)] pub mod grpc_encap; pub mod oak_api; diff --git a/rust/oak/src/storage/mod.rs b/rust/oak/src/storage/mod.rs index 438a026a601..23315620086 100644 --- a/rust/oak/src/storage/mod.rs +++ b/rust/oak/src/storage/mod.rs @@ -14,6 +14,8 @@ // limitations under the License. // +//! Helper library for accessing Oak storage services. + extern crate protobuf; use crate::{GrpcResult, ReceiveChannelHalf, SendChannelHalf}; @@ -23,6 +25,7 @@ use proto::storage_channel::{ }; use protobuf::Message; +/// Local representation of the connection to an external storage service. pub struct Storage { write_channel: crate::SendChannelHalf, wait_space: Vec, @@ -30,12 +33,17 @@ pub struct Storage { } impl Default for Storage { + /// Create a default `Storage` instance assuming the standard port names + /// (`"storage_in"`, `"storage_out"`) for pre-defined channels for storage + /// communication. fn default() -> Storage { Storage::new("storage_in", "storage_out") } } impl Storage { + /// Create a `Storage` instance using the given port names for pre-defined + /// channels for storage communication. pub fn new(in_port_name: &str, out_port_name: &str) -> Storage { let handle = crate::channel_find(in_port_name); let handles = vec![handle]; @@ -82,6 +90,8 @@ impl Storage { response } + /// Read the value associated with the given `name` from the storage + /// instance identified by `name`. pub fn read(&mut self, storage_name: &[u8], name: &[u8]) -> GrpcResult> { let mut read_request = StorageChannelReadRequest::new(); read_request.datum_name = name.to_owned(); @@ -103,6 +113,8 @@ impl Storage { } } + /// Set the value associated with the given `name` from the storage instance + /// identified by `name`. pub fn write(&mut self, storage_name: &[u8], name: &[u8], value: &[u8]) -> GrpcResult<()> { let mut write_request = StorageChannelWriteRequest::new(); write_request.datum_name = name.to_owned(); @@ -121,6 +133,8 @@ impl Storage { } } + /// Delete the value associated with the given `name` from the storage + /// instance identified by `name`. pub fn delete(&mut self, storage_name: &[u8], name: &[u8]) -> GrpcResult<()> { let mut delete_request = StorageChannelDeleteRequest::new(); delete_request.datum_name = name.to_owned(); diff --git a/rust/oak_derive/src/lib.rs b/rust/oak_derive/src/lib.rs index 97f3cc5b4df..4448a4f443b 100644 --- a/rust/oak_derive/src/lib.rs +++ b/rust/oak_derive/src/lib.rs @@ -1,3 +1,21 @@ +// +// Copyright 2019 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. +// + +//! Macro to derive standard boilerplate code for an Oak Node. + extern crate oak; extern crate proc_macro; extern crate protobuf; @@ -6,14 +24,15 @@ extern crate syn; use proc_macro::TokenStream; use quote::quote; -/// Implements the necessary bindings to make the annotated struct act as an Oak -/// Node that processes gRPC method invocations, assuming the default port names -/// are used for gRPC channels. +/// Implements the necessary bindings to make the annotated struct act as a +/// gRPC-processing Oak Node. /// -/// May only be used on struct objects. +/// May only be used on struct objects. Assumes that the default pre-defined +/// port names (`"grpc_in"`, `"grpc_out"`) are used to identify the gRPC +/// channels. /// -/// At most one struct may be annotated with this, as it produces global symbols that would -/// otherwise conflict if implemented multiple times. +/// At most one struct may be annotated with this, as it produces global symbols +/// that would otherwise conflict if implemented multiple times. /// /// ```rust /// extern crate oak; diff --git a/rust/oak_log/src/lib.rs b/rust/oak_log/src/lib.rs index a0abfc448ca..3d49220875c 100644 --- a/rust/oak_log/src/lib.rs +++ b/rust/oak_log/src/lib.rs @@ -14,7 +14,10 @@ // limitations under the License. // -//! A logger that send output to an Oak logging channel. +//! A logger that sends output to an Oak logging channel, for use with +//! the [log facade]. +//! +//! [log facade]: https://crates.io/crates/log extern crate log; extern crate oak; @@ -52,10 +55,26 @@ impl Log for OakChannelLogger { fn flush(&self) {} } +/// Initialize Node-wide default logging. +/// +/// Uses the default level (`Debug`) and the default pre-defined port name +/// (`"log"`) for the logging channel from the current Node. +/// +/// # Panics +/// +/// Panics if a logger has already been set. pub fn init_default() { init(Level::Debug, oak::channel_find("log")).unwrap(); } +/// Initialize Node-wide logging via a channel. +/// +/// Initialize logging at the given level, using the send channel half +/// identified by the given port name. +/// +/// # Errors +/// +/// An error is returned if a logger has already been set. pub fn init(level: Level, channel_handle: oak::Handle) -> Result<(), SetLoggerError> { log::set_boxed_logger(Box::new(OakChannelLogger { channel_handle }))?; log::set_max_level(level.to_level_filter()); diff --git a/rust/oak_tests/src/lib.rs b/rust/oak_tests/src/lib.rs index 1f502226339..bab4d4fb355 100644 --- a/rust/oak_tests/src/lib.rs +++ b/rust/oak_tests/src/lib.rs @@ -1,3 +1,21 @@ +// +// Copyright 2019 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. +// + +//! Test utilities to help with unit testing of Oak SDK code. + extern crate protobuf; use oak::proto::oak_api::OakStatus; @@ -72,6 +90,9 @@ thread_local! { } // Implementations of the Oak API host functions in Rust for testing. + +/// Test-only implementation of channel wait functionality, which always +/// indicates that all provided channels are ready for reading. #[no_mangle] pub unsafe extern "C" fn wait_on_channels(buf: *mut u8, count: u32) -> i32 { // Pretend all channels are readable. @@ -82,6 +103,8 @@ pub unsafe extern "C" fn wait_on_channels(buf: *mut u8, count: u32) -> i32 { OakStatus::OK.value() } +/// Test-only implementation of channel write functionality, which writes (only) the data +/// of the provided message to a (global) test channel. #[no_mangle] pub extern "C" fn channel_write( _handle: u64, @@ -93,6 +116,8 @@ pub extern "C" fn channel_write( CHANNEL.with(|channel| channel.borrow_mut().write_message(buf, size)) } +/// Test-only implementation fo channel read functionality, which reads a +/// message from the (global) test channel. #[no_mangle] pub extern "C" fn channel_read( _handle: u64, @@ -106,34 +131,46 @@ pub extern "C" fn channel_read( CHANNEL.with(|channel| channel.borrow_mut().read_message(buf, size, actual_size)) } +/// Test-only placeholder for channel creation, which always fails. #[no_mangle] pub extern "C" fn channel_create(_write: *mut u64, _read: *mut u64) -> i32 { OakStatus::ERR_INTERNAL.value() } +/// Test-only placeholder for channel creation, which always appears to succeed +/// (but does nothing). #[no_mangle] pub extern "C" fn channel_close(_handle: u64) -> i32 { OakStatus::OK.value() } +/// Test-only placeholder for finding a channel by preconfigured port name, which +/// always returns a hard coded value. #[no_mangle] pub extern "C" fn channel_find(_buf: *const u8, _size: usize) -> u64 { 1 } -// Convenience helpers for tests +/// Convenience test helper which returns the last message on the global test +/// channel as a string. pub fn last_message_as_string() -> String { CHANNEL.with(|channel| match channel.borrow().messages.front() { Some(msg) => unsafe { std::str::from_utf8_unchecked(msg).to_string() }, None => "".to_string(), }) } + +/// Test helper that injects a failure for future channel read operations. pub fn set_read_status(status: Option) { CHANNEL.with(|channel| channel.borrow_mut().read_status = status) } + +/// Test helper that injects a failure for future channel write operations. pub fn set_write_status(status: Option) { CHANNEL.with(|channel| channel.borrow_mut().write_status = status) } + +/// Test helper that sets up a new global test channel. pub fn reset_channels() { CHANNEL.with(|channel| *channel.borrow_mut() = MockChannel::new()) }