1 // Copyright (C) 2022 The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //! Functionality for communicating with Trusty services. 16 //! 17 //! This crate provides the [`TipcChannel`] type, which allows you to establish a 18 //! connection to a Trusty service and then communicate with that service. 19 //! 20 //! # Usage 21 //! 22 //! To connect to a Trusty service you need two things: 23 //! 24 //! * The filesystem path to the Trusty IPC device. This is usually 25 //! `/dev/trusty-ipc-dev0`, which is exposed in the constant [`DEFAULT_DEVICE`]. 26 //! * The port name defined by the service, e.g. `com.android.ipc-unittest.srv.echo`. 27 //! 28 //! Pass these values to [`TipcChannel::connect`] to establish a connection to a 29 //! service. 30 //! 31 //! Once connected use the [`send`][TipcChannel::send] and [`recv`][TipcChannel::recv] 32 //! methods to communicate with the service. Messages are passed as byte buffers, and 33 //! each Trusty service has its own protocol for what data messages are expected to 34 //! contain. Consult the documentation for the service you are communicating with to 35 //! determine how to format outgoing messages and interpret incoming ones. 36 //! 37 //! The connection is closed automatically when [`TipcChannel`] is dropped. 38 //! 39 //! # Examples 40 //! 41 //! This example is a simplified version of the echo test from `tipc-test-rs`: 42 //! 43 //! ```no_run 44 //! use trusty::{DEFAULT_DEVICE, TipcChannel}; 45 //! use std::io::{Read, Write}; 46 //! 47 //! let mut chann = TipcChannel::connect( 48 //! DEFAULT_DEVICE, 49 //! "com.android.ipc-unittest.srv.echo", 50 //! ).unwrap(); 51 //! 52 //! chann.send("Hello, world!".as_bytes()).unwrap(); 53 //! 54 //! let mut read_buf = Vec::new(); 55 //! let read_len = stream.recv(&mut read_buf).unwrap(); 56 //! 57 //! let response = std::str::from_utf8(&read_buf[..read_len]).unwrap(); 58 //! assert_eq!("Hello, world!", response); 59 //! 60 //! // The connection is closed here. 61 //! ``` 62 63 use crate::sys::tipc_connect; 64 use std::ffi::CString; 65 use std::fs::File; 66 use std::io::prelude::*; 67 use std::io::{ErrorKind, Result}; 68 use std::os::unix::prelude::AsRawFd; 69 use std::path::Path; 70 71 mod sys; 72 73 /// The default filesystem path for the Trusty IPC device. 74 pub const DEFAULT_DEVICE: &str = "/dev/trusty-ipc-dev0"; 75 76 /// The maximum size an incoming TIPC message can be. 77 /// 78 /// This can be used to pre-allocate buffer space in order to ensure that your 79 /// read buffer can always hold an incoming message. 80 pub const MAX_MESSAGE_SIZE: usize = 4096; 81 82 /// A channel for communicating with a Trusty service. 83 /// 84 /// See the [crate-level documentation][crate] for usage details and examples. 85 #[derive(Debug)] 86 pub struct TipcChannel(File); 87 88 impl TipcChannel { 89 /// Attempts to establish a connection to the specified Trusty service. 90 /// 91 /// The first argument is the path of the Trusty device in the local filesystem, 92 /// e.g. `/dev/trusty-ipc-dev0`. The second argument is the name of the service 93 /// to connect to, e.g. `com.android.ipc-unittest.srv.echo`. 94 /// 95 /// # Panics 96 /// 97 /// This function will panic if `service` contains any intermediate `NUL` 98 /// bytes. This is handled with a panic because the service names are all 99 /// hard-coded constants, and so such an error should always be indicative of a 100 /// bug in the calling code. connect(device: impl AsRef<Path>, service: &str) -> Result<Self>101 pub fn connect(device: impl AsRef<Path>, service: &str) -> Result<Self> { 102 let file = File::options().read(true).write(true).open(device)?; 103 104 let srv_name = CString::new(service).expect("Service name contained null bytes"); 105 unsafe { 106 tipc_connect(file.as_raw_fd(), srv_name.as_ptr())?; 107 } 108 109 Ok(TipcChannel(file)) 110 } 111 112 /// Sends a message to the connected service. 113 /// 114 /// The entire contents of `buf` will be sent as a single message to the 115 /// connected service. send(&mut self, buf: &[u8]) -> Result<()>116 pub fn send(&mut self, buf: &[u8]) -> Result<()> { 117 let write_len = self.0.write(buf)?; 118 119 // Verify that the expected number of bytes were written. The entire message 120 // should always be written with a single `write` call, or an error should have 121 // been returned if the message couldn't be written. An assertion failure here 122 // potentially means a bug in the kernel driver. 123 assert_eq!( 124 buf.len(), 125 write_len, 126 "Failed to send full message ({} of {} bytes written)", 127 write_len, 128 buf.len(), 129 ); 130 131 Ok(()) 132 } 133 134 /// Reads the next incoming message. 135 /// 136 /// Attempts to read the next incoming message from the connected service if any 137 /// exist. If the initial capacity of `buf` is not enough to hold the incoming 138 /// message the function repeatedly attempts to reserve additional space until 139 /// it is able to fully read the message. 140 /// 141 /// Blocks until there is an incoming message if there is not already a message 142 /// ready to be received. 143 /// 144 /// # Errors 145 /// 146 /// If this function encounters an error of the kind [`ErrorKind::Interrupted`] 147 /// then the error is ignored and the operation will be tried again. 148 /// 149 /// If this function encounters an error with the error code `EMSGSIZE` then 150 /// additional space will be reserved in `buf` and the operation will be tried 151 /// again. 152 /// 153 /// If any other read error is encountered then this function immediately 154 /// returns the error to the caller, and the length of `buf` is set to 0. recv(&mut self, buf: &mut Vec<u8>) -> Result<()>155 pub fn recv(&mut self, buf: &mut Vec<u8>) -> Result<()> { 156 // If no space has been allocated in the buffer reserve enough space to hold any 157 // incoming message. 158 if buf.capacity() == 0 { 159 buf.reserve(MAX_MESSAGE_SIZE); 160 } 161 162 loop { 163 // Resize the vec to make its full capacity available to write into. 164 buf.resize(buf.capacity(), 0); 165 166 match self.0.read(buf.as_mut_slice()) { 167 Ok(len) => { 168 buf.truncate(len); 169 return Ok(()); 170 } 171 172 Err(err) => { 173 if let Some(libc::EMSGSIZE) = err.raw_os_error() { 174 // Ensure that we didn't get `EMSGSIZE` when we already had enough capacity 175 // to contain the maximum message size. This should never happen, but if it 176 // does we don't want to hang by looping infinitely. 177 assert!( 178 buf.capacity() < MAX_MESSAGE_SIZE, 179 "Received `EMSGSIZE` error when buffer capacity was already at maximum", 180 ); 181 182 // If we didn't have enough space to hold the incoming message, reserve 183 // enough space to fit the maximum message size regardless of how much 184 // capacity the buffer already had. 185 buf.reserve(MAX_MESSAGE_SIZE - buf.capacity()); 186 } else if err.kind() == ErrorKind::Interrupted { 187 // If we get an interrupted error the operation can be retried as-is, i.e. 188 // we don't need to allocate additional space. 189 continue; 190 } else { 191 buf.truncate(0); 192 return Err(err); 193 } 194 } 195 } 196 } 197 } 198 199 /// Reads the next incoming message without allocating. 200 /// 201 /// Returns the number of bytes in the received message, or any error that 202 /// occurred when reading the message. 203 /// 204 /// Blocks until there is an incoming message if there is not already a message 205 /// ready to be received. 206 /// 207 /// # Errors 208 /// 209 /// Returns an error with native error code `EMSGSIZE` if `buf` isn't large 210 /// enough to contain the incoming message. Use 211 /// [`raw_os_error`][std::io::Error::raw_os_error] to check the error code to 212 /// determine if you need to increase the size of `buf`. If error code 213 /// `EMSGSIZE` is returned the incoming message will not be dropped, and a 214 /// subsequent call to `recv_no_alloc` can still read it. 215 /// 216 /// An error of the [`ErrorKind::Interrupted`] kind is non-fatal and the read 217 /// operation should be retried if there is nothing else to do. recv_no_alloc(&mut self, buf: &mut [u8]) -> Result<usize>218 pub fn recv_no_alloc(&mut self, buf: &mut [u8]) -> Result<usize> { 219 self.0.read(buf) 220 } 221 222 // TODO: Add method that is equivalent to `tipc_send`, i.e. that supports 223 // sending shared memory buffers. 224 } 225