diff --git a/trusty/libtrusty-rs/Android.bp b/trusty/libtrusty-rs/Android.bp new file mode 100644 index 000000000..0c6349a57 --- /dev/null +++ b/trusty/libtrusty-rs/Android.bp @@ -0,0 +1,28 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// 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_applicable_licenses: ["Android-Apache-2.0"], +} + +rust_library { + name: "libtrusty-rs", + crate_name: "trusty", + srcs: [ + "src/lib.rs" + ], + rustlibs: [ + "libnix", + ], +} diff --git a/trusty/libtrusty-rs/src/lib.rs b/trusty/libtrusty-rs/src/lib.rs new file mode 100644 index 000000000..fe65a27ac --- /dev/null +++ b/trusty/libtrusty-rs/src/lib.rs @@ -0,0 +1,146 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// 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. + +//! Functionality for communicating with Trusty services. +//! +//! This crate provides the [`TipcChannel`] type, which allows you to establish a +//! connection to a Trusty service and then communicate with that service. +//! +//! # Usage +//! +//! To connect to a Trusty service you need two things: +//! +//! * The filesystem path to the Trusty IPC device. This is usually +//! `/dev/trusty-ipc-dev0`, which is exposed in the constant [`DEFAULT_DEVICE`]. +//! * The port name defined by the service, e.g. `com.android.ipc-unittest.srv.echo`. +//! +//! Pass these values to [`TipcChannel::connect`] to establish a connection to a +//! service. +//! +//! Once connected use the [`send`][TipcChannel::send] and [`recv`][TipcChannel::recv] +//! methods to communicate with the service. Messages are passed as byte buffers, and +//! each Trusty service has its own protocol for what data messages are expected to +//! contain. Consult the documentation for the service you are communicating with to +//! determine how to format outgoing messages and interpret incoming ones. +//! +//! The connection is closed automatically when [`TipcChannel`] is dropped. +//! +//! # Examples +//! +//! This example is a simplified version of the echo test from `tipc-test-rs`: +//! +//! ```no_run +//! use trusty::{DEFAULT_DEVICE, TipcChannel}; +//! use std::io::{Read, Write}; +//! +//! let mut chann = TipcChannel::connect( +//! DEFAULT_DEVICE, +//! "com.android.ipc-unittest.srv.echo", +//! ).unwrap(); +//! +//! chann.send("Hello, world!".as_bytes()).unwrap(); +//! +//! let mut read_buf = [0u8; 1024]; +//! let read_len = stream.read(&mut read_buf[..]).unwrap(); +//! +//! let response = std::str::from_utf8(&read_buf[..read_len]).unwrap(); +//! assert_eq!("Hello, world!", response); +//! +//! // The connection is closed here. +//! ``` + +use crate::sys::tipc_connect; +use std::ffi::CString; +use std::fs::File; +use std::io; +use std::io::prelude::*; +use std::os::unix::prelude::AsRawFd; +use std::path::Path; + +mod sys; + +/// The default filesystem path for the Trusty IPC device. +pub const DEFAULT_DEVICE: &str = "/dev/trusty-ipc-dev0"; + +/// A channel for communicating with a Trusty service. +/// +/// See the [crate-level documentation][crate] for usage details and examples. +#[derive(Debug)] +pub struct TipcChannel(File); + +impl TipcChannel { + /// Attempts to establish a connection to the specified Trusty service. + /// + /// The first argument is the path of the Trusty device in the local filesystem, + /// e.g. `/dev/trusty-ipc-dev0`. The second argument is the name of the service + /// to connect to, e.g. `com.android.ipc-unittest.srv.echo`. + /// + /// # Panics + /// + /// This function will panic if `service` contains any intermediate `NUL` + /// bytes. This is handled with a panic because the service names are all + /// hard-coded constants, and so such an error should always be indicative of a + /// bug in the calling code. + pub fn connect(device: impl AsRef, service: &str) -> io::Result { + let file = File::options().read(true).write(true).open(device)?; + + let srv_name = CString::new(service).expect("Service name contained null bytes"); + unsafe { + tipc_connect(file.as_raw_fd(), srv_name.as_ptr())?; + } + + Ok(TipcChannel(file)) + } + + /// Sends a message to the connected service. + /// + /// The entire contents of `buf` will be sent as a single message to the + /// connected service. + pub fn send(&mut self, buf: &[u8]) -> io::Result<()> { + let write_len = self.0.write(buf)?; + + // Verify that the expected number of bytes were written. The entire message + // should always be written with a single `write` call, or an error should have + // been returned if the message couldn't be written. An assertion failure here + // potentially means a bug in the kernel driver. + assert_eq!( + buf.len(), + write_len, + "Failed to send full message ({} of {} bytes written)", + write_len, + buf.len(), + ); + + Ok(()) + } + + /// Receives a message from the connected service. + /// + /// Returns the number of bytes in the received message, or any error that + /// occurred when reading the message. A return value of 0 indicates that + /// there were no incoming messages to receive. + /// + /// # Errors + /// + /// Returns an error with native error code 90 (`EMSGSIZE`) if `buf` isn't + /// large enough to contain the incoming message. Use + /// [`raw_os_error`][std::io::Error::raw_os_error] to check the error code + /// to determine if you need to increase the size of `buf`. + pub fn recv(&mut self, buf: &mut [u8]) -> io::Result { + self.0.read(buf) + } + + // TODO: Add method that is equivalent to `tipc_send`, i.e. that supports + // sending shared memory buffers. +} diff --git a/trusty/libtrusty-rs/src/sys.rs b/trusty/libtrusty-rs/src/sys.rs new file mode 100644 index 000000000..f1c8c5f14 --- /dev/null +++ b/trusty/libtrusty-rs/src/sys.rs @@ -0,0 +1,31 @@ +// NOTE: The ioctl definitions are sequestered into this module because the +// `ioctl_*!` macros provided by the nix crate generate public functions that we +// don't want to be part of this crate's public API. +// +// NOTE: We are manually re-declaring the types and constants here instead of using +// bindgen and a separate `-sys` crate because the defines used for the ioctl +// numbers (`TIPC_IOC_CONNECT` and `TIPC_IOC_SEND_MSG`) can't currently be +// translated by bindgen. + +use std::os::raw::c_char; + +const TIPC_IOC_MAGIC: u8 = b'r'; + +// NOTE: We use `ioctl_write_ptr_bad!` here due to an error in how the ioctl +// code is defined in `trusty/ipc.h`. +// +// If we were to do `ioctl_write_ptr!(TIPC_IOC_MAGIC, 0x80, c_char)` it would +// generate a function that takes a `*const c_char` data arg and would use +// `size_of::()` when generating the ioctl number. However, in +// `trusty/ipc.h` the definition for `TIPC_IOC_CONNECT` declares the ioctl with +// `char*`, meaning we need to use `size_of::<*const c_char>()` to generate an +// ioctl number that matches what Trusty expects. +// +// To maintain compatibility with the `trusty/ipc.h` and the kernel driver we +// use `ioctl_write_ptr_bad!` and manually use `request_code_write!` to generate +// the ioctl number using the correct size. +nix::ioctl_write_ptr_bad!( + tipc_connect, + nix::request_code_write!(TIPC_IOC_MAGIC, 0x80, std::mem::size_of::<*const c_char>()), + c_char +);