summaryrefslogtreecommitdiff
path: root/trusty/libtrusty-rs/src/lib.rs
blob: 28ea07505b0f14b3f4c7a702f03120e4d5b7f18b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
// 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 = Vec::new();
//! let read_len = stream.recv(&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::prelude::*;
use std::io::{ErrorKind, Result};
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";

/// The maximum size an incoming TIPC message can be.
///
/// This can be used to pre-allocate buffer space in order to ensure that your
/// read buffer can always hold an incoming message.
pub const MAX_MESSAGE_SIZE: usize = 4096;

/// 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<Path>, service: &str) -> Result<Self> {
        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]) -> 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(())
    }

    /// Reads the next incoming message.
    ///
    /// Attempts to read the next incoming message from the connected service if any
    /// exist. If the initial capacity of `buf` is not enough to hold the incoming
    /// message the function repeatedly attempts to reserve additional space until
    /// it is able to fully read the message.
    ///
    /// Blocks until there is an incoming message if there is not already a message
    /// ready to be received.
    ///
    /// # Errors
    ///
    /// If this function encounters an error of the kind [`ErrorKind::Interrupted`]
    /// then the error is ignored and the operation will be tried again.
    ///
    /// If this function encounters an error with the error code `EMSGSIZE` then
    /// additional space will be reserved in `buf` and the operation will be tried
    /// again.
    ///
    /// If any other read error is encountered then this function immediately
    /// returns the error to the caller, and the length of `buf` is set to 0.
    pub fn recv(&mut self, buf: &mut Vec<u8>) -> Result<()> {
        // If no space has been allocated in the buffer reserve enough space to hold any
        // incoming message.
        if buf.capacity() == 0 {
            buf.reserve(MAX_MESSAGE_SIZE);
        }

        loop {
            // Resize the vec to make its full capacity available to write into.
            buf.resize(buf.capacity(), 0);

            match self.0.read(buf.as_mut_slice()) {
                Ok(len) => {
                    buf.truncate(len);
                    return Ok(());
                }

                Err(err) => {
                    if let Some(libc::EMSGSIZE) = err.raw_os_error() {
                        // Ensure that we didn't get `EMSGSIZE` when we already had enough capacity
                        // to contain the maximum message size. This should never happen, but if it
                        // does we don't want to hang by looping infinitely.
                        assert!(
                            buf.capacity() < MAX_MESSAGE_SIZE,
                            "Received `EMSGSIZE` error when buffer capacity was already at maximum",
                        );

                        // If we didn't have enough space to hold the incoming message, reserve
                        // enough space to fit the maximum message size regardless of how much
                        // capacity the buffer already had.
                        buf.reserve(MAX_MESSAGE_SIZE - buf.capacity());
                    } else if err.kind() == ErrorKind::Interrupted {
                        // If we get an interrupted error the operation can be retried as-is, i.e.
                        // we don't need to allocate additional space.
                        continue;
                    } else {
                        buf.truncate(0);
                        return Err(err);
                    }
                }
            }
        }
    }

    /// Reads the next incoming message without allocating.
    ///
    /// Returns the number of bytes in the received message, or any error that
    /// occurred when reading the message.
    ///
    /// Blocks until there is an incoming message if there is not already a message
    /// ready to be received.
    ///
    /// # Errors
    ///
    /// Returns an error with native error code `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`. If error code
    /// `EMSGSIZE` is returned the incoming message will not be dropped, and a
    /// subsequent call to `recv_no_alloc` can still read it.
    ///
    /// An error of the [`ErrorKind::Interrupted`] kind is non-fatal and the read
    /// operation should be retried if there is nothing else to do.
    pub fn recv_no_alloc(&mut self, buf: &mut [u8]) -> Result<usize> {
        self.0.read(buf)
    }

    // TODO: Add method that is equivalent to `tipc_send`, i.e. that supports
    // sending shared memory buffers.
}