1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 use crate::binder::AsNative;
18 use crate::sys;
19 
20 use std::error;
21 use std::ffi::{CStr, CString};
22 use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
23 use std::ptr;
24 use std::result;
25 
26 pub use sys::binder_status_t as status_t;
27 
28 /// Low-level status codes from Android `libutils`.
29 // All error codes are negative integer values. Derived from the anonymous enum
30 // in utils/Errors.h
31 pub use sys::android_c_interface_StatusCode as StatusCode;
32 
33 /// A specialized [`Result`](result::Result) for binder operations.
34 pub type Result<T> = result::Result<T, StatusCode>;
35 
36 /// Convert a low-level status code into an empty result.
37 ///
38 /// An OK status is converted into an `Ok` result, any other status is converted
39 /// into an `Err` result holding the status code.
status_result(status: status_t) -> Result<()>40 pub fn status_result(status: status_t) -> Result<()> {
41     match parse_status_code(status) {
42         StatusCode::OK => Ok(()),
43         e => Err(e),
44     }
45 }
46 
parse_status_code(code: i32) -> StatusCode47 fn parse_status_code(code: i32) -> StatusCode {
48     match code {
49         e if e == StatusCode::OK as i32 => StatusCode::OK,
50         e if e == StatusCode::NO_MEMORY as i32 => StatusCode::NO_MEMORY,
51         e if e == StatusCode::INVALID_OPERATION as i32 => StatusCode::INVALID_OPERATION,
52         e if e == StatusCode::BAD_VALUE as i32 => StatusCode::BAD_VALUE,
53         e if e == StatusCode::BAD_TYPE as i32 => StatusCode::BAD_TYPE,
54         e if e == StatusCode::NAME_NOT_FOUND as i32 => StatusCode::NAME_NOT_FOUND,
55         e if e == StatusCode::PERMISSION_DENIED as i32 => StatusCode::PERMISSION_DENIED,
56         e if e == StatusCode::NO_INIT as i32 => StatusCode::NO_INIT,
57         e if e == StatusCode::ALREADY_EXISTS as i32 => StatusCode::ALREADY_EXISTS,
58         e if e == StatusCode::DEAD_OBJECT as i32 => StatusCode::DEAD_OBJECT,
59         e if e == StatusCode::FAILED_TRANSACTION as i32 => StatusCode::FAILED_TRANSACTION,
60         e if e == StatusCode::BAD_INDEX as i32 => StatusCode::BAD_INDEX,
61         e if e == StatusCode::NOT_ENOUGH_DATA as i32 => StatusCode::NOT_ENOUGH_DATA,
62         e if e == StatusCode::WOULD_BLOCK as i32 => StatusCode::WOULD_BLOCK,
63         e if e == StatusCode::TIMED_OUT as i32 => StatusCode::TIMED_OUT,
64         e if e == StatusCode::UNKNOWN_TRANSACTION as i32 => StatusCode::UNKNOWN_TRANSACTION,
65         e if e == StatusCode::FDS_NOT_ALLOWED as i32 => StatusCode::FDS_NOT_ALLOWED,
66         e if e == StatusCode::UNEXPECTED_NULL as i32 => StatusCode::UNEXPECTED_NULL,
67         _ => StatusCode::UNKNOWN_ERROR,
68     }
69 }
70 
71 pub use sys::android_c_interface_ExceptionCode as ExceptionCode;
72 
parse_exception_code(code: i32) -> ExceptionCode73 fn parse_exception_code(code: i32) -> ExceptionCode {
74     match code {
75         e if e == ExceptionCode::NONE as i32 => ExceptionCode::NONE,
76         e if e == ExceptionCode::SECURITY as i32 => ExceptionCode::SECURITY,
77         e if e == ExceptionCode::BAD_PARCELABLE as i32 => ExceptionCode::BAD_PARCELABLE,
78         e if e == ExceptionCode::ILLEGAL_ARGUMENT as i32 => ExceptionCode::ILLEGAL_ARGUMENT,
79         e if e == ExceptionCode::NULL_POINTER as i32 => ExceptionCode::NULL_POINTER,
80         e if e == ExceptionCode::ILLEGAL_STATE as i32 => ExceptionCode::ILLEGAL_STATE,
81         e if e == ExceptionCode::NETWORK_MAIN_THREAD as i32 => ExceptionCode::NETWORK_MAIN_THREAD,
82         e if e == ExceptionCode::UNSUPPORTED_OPERATION as i32 => {
83             ExceptionCode::UNSUPPORTED_OPERATION
84         }
85         e if e == ExceptionCode::SERVICE_SPECIFIC as i32 => ExceptionCode::SERVICE_SPECIFIC,
86         _ => ExceptionCode::TRANSACTION_FAILED,
87     }
88 }
89 
90 // Safety: `Status` always contains a owning pointer to a valid `AStatus`. The
91 // lifetime of the contained pointer is the same as the `Status` object.
92 /// High-level binder status object that encapsulates a standard way to keep
93 /// track of and chain binder errors along with service specific errors.
94 ///
95 /// Used in AIDL transactions to represent failed transactions.
96 pub struct Status(ptr::NonNull<sys::AStatus>);
97 
98 // Safety: The `AStatus` that the `Status` points to must have an entirely thread-safe API for the
99 // duration of the `Status` object's lifetime. We ensure this by not allowing mutation of a `Status`
100 // in Rust, and the NDK API says we're the owner of our `AStatus` objects so outside code should not
101 // be mutating them underneath us.
102 unsafe impl Sync for Status {}
103 
104 // Safety: `Status` always contains an owning pointer to a global, immutable, interned `AStatus`.
105 // A thread-local `AStatus` would not be valid.
106 unsafe impl Send for Status {}
107 
to_cstring<T: AsRef<str>>(message: T) -> Option<CString>108 fn to_cstring<T: AsRef<str>>(message: T) -> Option<CString> {
109     CString::new(message.as_ref()).ok()
110 }
111 
112 impl Status {
113     /// Create a status object representing a successful transaction.
ok() -> Self114     pub fn ok() -> Self {
115         // Safety: `AStatus_newOk` always returns a new, heap allocated
116         // pointer to an `ASTatus` object, so we know this pointer will be
117         // valid.
118         //
119         // Rust takes ownership of the returned pointer.
120         let ptr = unsafe { sys::AStatus_newOk() };
121         Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer"))
122     }
123 
124     /// Create a status object from a service specific error
new_service_specific_error(err: i32, message: Option<&CStr>) -> Status125     pub fn new_service_specific_error(err: i32, message: Option<&CStr>) -> Status {
126         let ptr = if let Some(message) = message {
127             // Safety: Any i32 is a valid service specific error for the
128             // error code parameter. We construct a valid, null-terminated
129             // `CString` from the message, which must be a valid C-style
130             // string to pass as the message. This function always returns a
131             // new, heap allocated pointer to an `AStatus` object, so we
132             // know the returned pointer will be valid.
133             //
134             // Rust takes ownership of the returned pointer.
135             unsafe { sys::AStatus_fromServiceSpecificErrorWithMessage(err, message.as_ptr()) }
136         } else {
137             // Safety: Any i32 is a valid service specific error for the
138             // error code parameter. This function always returns a new,
139             // heap allocated pointer to an `AStatus` object, so we know the
140             // returned pointer will be valid.
141             //
142             // Rust takes ownership of the returned pointer.
143             unsafe { sys::AStatus_fromServiceSpecificError(err) }
144         };
145         Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer"))
146     }
147 
148     /// Creates a status object from a service specific error.
new_service_specific_error_str<T: AsRef<str>>(err: i32, message: Option<T>) -> Status149     pub fn new_service_specific_error_str<T: AsRef<str>>(err: i32, message: Option<T>) -> Status {
150         Self::new_service_specific_error(err, message.and_then(to_cstring).as_deref())
151     }
152 
153     /// Create a status object from an exception code
new_exception(exception: ExceptionCode, message: Option<&CStr>) -> Status154     pub fn new_exception(exception: ExceptionCode, message: Option<&CStr>) -> Status {
155         if let Some(message) = message {
156             // Safety: the C string pointer is valid and not retained by the
157             // function.
158             let ptr = unsafe {
159                 sys::AStatus_fromExceptionCodeWithMessage(exception as i32, message.as_ptr())
160             };
161             Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer"))
162         } else {
163             exception.into()
164         }
165     }
166 
167     /// Creates a status object from an exception code and message.
new_exception_str<T: AsRef<str>>( exception: ExceptionCode, message: Option<T>, ) -> Status168     pub fn new_exception_str<T: AsRef<str>>(
169         exception: ExceptionCode,
170         message: Option<T>,
171     ) -> Status {
172         Self::new_exception(exception, message.and_then(to_cstring).as_deref())
173     }
174 
175     /// Create a status object from a raw `AStatus` pointer.
176     ///
177     /// # Safety
178     ///
179     /// This constructor is safe iff `ptr` is a valid pointer to an `AStatus`.
from_ptr(ptr: *mut sys::AStatus) -> Self180     pub(crate) unsafe fn from_ptr(ptr: *mut sys::AStatus) -> Self {
181         Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer"))
182     }
183 
184     /// Returns `true` if this status represents a successful transaction.
is_ok(&self) -> bool185     pub fn is_ok(&self) -> bool {
186         // Safety: `Status` always contains a valid `AStatus` pointer, so we
187         // are always passing a valid pointer to `AStatus_isOk` here.
188         unsafe { sys::AStatus_isOk(self.as_native()) }
189     }
190 
191     /// Returns a description of the status.
get_description(&self) -> String192     pub fn get_description(&self) -> String {
193         // Safety: `Status` always contains a valid `AStatus` pointer, so we
194         // are always passing a valid pointer to `AStatus_getDescription`
195         // here.
196         //
197         // `AStatus_getDescription` always returns a valid pointer to a null
198         // terminated C string. Rust is responsible for freeing this pointer
199         // via `AStatus_deleteDescription`.
200         let description_ptr = unsafe { sys::AStatus_getDescription(self.as_native()) };
201         // Safety: `AStatus_getDescription` always returns a valid C string,
202         // which can be safely converted to a `CStr`.
203         let description = unsafe { CStr::from_ptr(description_ptr) };
204         let description = description.to_string_lossy().to_string();
205         // Safety: `description_ptr` was returned from
206         // `AStatus_getDescription` above, and must be freed via
207         // `AStatus_deleteDescription`. We must not access the pointer after
208         // this call, so we copy it into an owned string above and return
209         // that string.
210         unsafe {
211             sys::AStatus_deleteDescription(description_ptr);
212         }
213         description
214     }
215 
216     /// Returns the exception code of the status.
exception_code(&self) -> ExceptionCode217     pub fn exception_code(&self) -> ExceptionCode {
218         // Safety: `Status` always contains a valid `AStatus` pointer, so we
219         // are always passing a valid pointer to `AStatus_getExceptionCode`
220         // here.
221         let code = unsafe { sys::AStatus_getExceptionCode(self.as_native()) };
222         parse_exception_code(code)
223     }
224 
225     /// Return a status code representing a transaction failure, or
226     /// `StatusCode::OK` if there was no transaction failure.
227     ///
228     /// If this method returns `OK`, the status may still represent a different
229     /// exception or a service specific error. To find out if this transaction
230     /// as a whole is okay, use [`is_ok`](Self::is_ok) instead.
transaction_error(&self) -> StatusCode231     pub fn transaction_error(&self) -> StatusCode {
232         // Safety: `Status` always contains a valid `AStatus` pointer, so we
233         // are always passing a valid pointer to `AStatus_getStatus` here.
234         let code = unsafe { sys::AStatus_getStatus(self.as_native()) };
235         parse_status_code(code)
236     }
237 
238     /// Return a service specific error if this status represents one.
239     ///
240     /// This function will only ever return a non-zero result if
241     /// [`exception_code`](Self::exception_code) returns
242     /// `ExceptionCode::SERVICE_SPECIFIC`. If this function returns 0, the
243     /// status object may still represent a different exception or status. To
244     /// find out if this transaction as a whole is okay, use
245     /// [`is_ok`](Self::is_ok) instead.
service_specific_error(&self) -> i32246     pub fn service_specific_error(&self) -> i32 {
247         // Safety: `Status` always contains a valid `AStatus` pointer, so we
248         // are always passing a valid pointer to
249         // `AStatus_getServiceSpecificError` here.
250         unsafe { sys::AStatus_getServiceSpecificError(self.as_native()) }
251     }
252 
253     /// Calls `op` if the status was ok, otherwise returns an `Err` value of
254     /// `self`.
and_then<T, F>(self, op: F) -> result::Result<T, Status> where F: FnOnce() -> result::Result<T, Status>,255     pub fn and_then<T, F>(self, op: F) -> result::Result<T, Status>
256     where
257         F: FnOnce() -> result::Result<T, Status>,
258     {
259         <result::Result<(), Status>>::from(self)?;
260         op()
261     }
262 }
263 
264 impl error::Error for Status {}
265 
266 impl Display for Status {
fmt(&self, f: &mut Formatter) -> FmtResult267     fn fmt(&self, f: &mut Formatter) -> FmtResult {
268         f.write_str(&self.get_description())
269     }
270 }
271 
272 impl Debug for Status {
fmt(&self, f: &mut Formatter) -> FmtResult273     fn fmt(&self, f: &mut Formatter) -> FmtResult {
274         f.write_str(&self.get_description())
275     }
276 }
277 
278 impl PartialEq for Status {
eq(&self, other: &Status) -> bool279     fn eq(&self, other: &Status) -> bool {
280         let self_code = self.exception_code();
281         let other_code = other.exception_code();
282 
283         match (self_code, other_code) {
284             (ExceptionCode::NONE, ExceptionCode::NONE) => true,
285             (ExceptionCode::TRANSACTION_FAILED, ExceptionCode::TRANSACTION_FAILED) => {
286                 self.transaction_error() == other.transaction_error()
287                     && self.get_description() == other.get_description()
288             }
289             (ExceptionCode::SERVICE_SPECIFIC, ExceptionCode::SERVICE_SPECIFIC) => {
290                 self.service_specific_error() == other.service_specific_error()
291                     && self.get_description() == other.get_description()
292             }
293             (e1, e2) => e1 == e2 && self.get_description() == other.get_description(),
294         }
295     }
296 }
297 
298 impl Eq for Status {}
299 
300 impl From<StatusCode> for Status {
from(status: StatusCode) -> Status301     fn from(status: StatusCode) -> Status {
302         (status as status_t).into()
303     }
304 }
305 
306 impl From<status_t> for Status {
from(status: status_t) -> Status307     fn from(status: status_t) -> Status {
308         // Safety: `AStatus_fromStatus` expects any `status_t` integer, so
309         // this is a safe FFI call. Unknown values will be coerced into
310         // UNKNOWN_ERROR.
311         let ptr = unsafe { sys::AStatus_fromStatus(status) };
312         Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer"))
313     }
314 }
315 
316 impl From<ExceptionCode> for Status {
from(code: ExceptionCode) -> Status317     fn from(code: ExceptionCode) -> Status {
318         // Safety: `AStatus_fromExceptionCode` expects any
319         // `binder_exception_t` (i32) integer, so this is a safe FFI call.
320         // Unknown values will be coerced into EX_TRANSACTION_FAILED.
321         let ptr = unsafe { sys::AStatus_fromExceptionCode(code as i32) };
322         Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer"))
323     }
324 }
325 
326 // TODO: impl Try for Status when try_trait is stabilized
327 // https://github.com/rust-lang/rust/issues/42327
328 impl From<Status> for result::Result<(), Status> {
from(status: Status) -> result::Result<(), Status>329     fn from(status: Status) -> result::Result<(), Status> {
330         if status.is_ok() {
331             Ok(())
332         } else {
333             Err(status)
334         }
335     }
336 }
337 
338 impl From<Status> for status_t {
from(status: Status) -> status_t339     fn from(status: Status) -> status_t {
340         status.transaction_error() as status_t
341     }
342 }
343 
344 impl Drop for Status {
drop(&mut self)345     fn drop(&mut self) {
346         // Safety: `Status` manages the lifetime of its inner `AStatus`
347         // pointee, so we need to delete it here. We know that the pointer
348         // will be valid here since `Status` always contains a valid pointer
349         // while it is alive.
350         unsafe {
351             sys::AStatus_delete(self.0.as_mut());
352         }
353     }
354 }
355 
356 /// Safety: `Status` always contains a valid pointer to an `AStatus` object, so
357 /// we can trivially convert it to a correctly-typed raw pointer.
358 ///
359 /// Care must be taken that the returned pointer is only dereferenced while the
360 /// `Status` object is still alive.
361 unsafe impl AsNative<sys::AStatus> for Status {
as_native(&self) -> *const sys::AStatus362     fn as_native(&self) -> *const sys::AStatus {
363         self.0.as_ptr()
364     }
365 
as_native_mut(&mut self) -> *mut sys::AStatus366     fn as_native_mut(&mut self) -> *mut sys::AStatus {
367         // Safety: The pointer will be valid here since `Status` always contains
368         // a valid and initialized pointer while it is alive.
369         unsafe { self.0.as_mut() }
370     }
371 }
372 
373 /// A conversion from `std::result::Result<T, E>` to `binder::Result<T>`. If this type is `Ok(T)`,
374 /// it's returned as is. If this type is `Err(E)`, `E` is converted into `Status` which can be
375 /// either a general binder exception, or a service-specific exception.
376 ///
377 /// # Examples
378 ///
379 /// ```
380 /// // std::io::Error is formatted as the exception's message
381 /// fn file_exists(name: &str) -> binder::Result<bool> {
382 ///     std::fs::metadata(name)
383 ///         .or_service_specific_exception(NOT_FOUND)?
384 /// }
385 ///
386 /// // A custom function is used to create the exception's message
387 /// fn file_exists(name: &str) -> binder::Result<bool> {
388 ///     std::fs::metadata(name)
389 ///         .or_service_specific_exception_with(NOT_FOUND,
390 ///             |e| format!("file {} not found: {:?}", name, e))?
391 /// }
392 ///
393 /// // anyhow::Error is formatted as the exception's message
394 /// use anyhow::{Context, Result};
395 /// fn file_exists(name: &str) -> binder::Result<bool> {
396 ///     std::fs::metadata(name)
397 ///         .context("file {} not found")
398 ///         .or_service_specific_exception(NOT_FOUND)?
399 /// }
400 ///
401 /// // General binder exceptions can be created similarly
402 /// fn file_exists(name: &str) -> binder::Result<bool> {
403 ///     std::fs::metadata(name)
404 ///         .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT)?
405 /// }
406 /// ```
407 pub trait IntoBinderResult<T, E> {
408     /// Converts the embedded error into a general binder exception of code `exception`. The
409     /// message of the exception is set by formatting the error for debugging.
or_binder_exception(self, exception: ExceptionCode) -> result::Result<T, Status>410     fn or_binder_exception(self, exception: ExceptionCode) -> result::Result<T, Status>;
411 
412     /// Converts the embedded error into a general binder exception of code `exception`. The
413     /// message of the exception is set by lazily evaluating the `op` function.
or_binder_exception_with<M: AsRef<str>, O: FnOnce(E) -> M>( self, exception: ExceptionCode, op: O, ) -> result::Result<T, Status>414     fn or_binder_exception_with<M: AsRef<str>, O: FnOnce(E) -> M>(
415         self,
416         exception: ExceptionCode,
417         op: O,
418     ) -> result::Result<T, Status>;
419 
420     /// Converts the embedded error into a service-specific binder exception. `error_code` is used
421     /// to distinguish different service-specific binder exceptions. The message of the exception
422     /// is set by formatting the error for debugging.
or_service_specific_exception(self, error_code: i32) -> result::Result<T, Status>423     fn or_service_specific_exception(self, error_code: i32) -> result::Result<T, Status>;
424 
425     /// Converts the embedded error into a service-specific binder exception. `error_code` is used
426     /// to distinguish different service-specific binder exceptions. The message of the exception
427     /// is set by lazily evaluating the `op` function.
or_service_specific_exception_with<M: AsRef<str>, O: FnOnce(E) -> M>( self, error_code: i32, op: O, ) -> result::Result<T, Status>428     fn or_service_specific_exception_with<M: AsRef<str>, O: FnOnce(E) -> M>(
429         self,
430         error_code: i32,
431         op: O,
432     ) -> result::Result<T, Status>;
433 }
434 
435 impl<T, E: std::fmt::Debug> IntoBinderResult<T, E> for result::Result<T, E> {
or_binder_exception(self, exception: ExceptionCode) -> result::Result<T, Status>436     fn or_binder_exception(self, exception: ExceptionCode) -> result::Result<T, Status> {
437         self.or_binder_exception_with(exception, |e| format!("{:?}", e))
438     }
439 
or_binder_exception_with<M: AsRef<str>, O: FnOnce(E) -> M>( self, exception: ExceptionCode, op: O, ) -> result::Result<T, Status>440     fn or_binder_exception_with<M: AsRef<str>, O: FnOnce(E) -> M>(
441         self,
442         exception: ExceptionCode,
443         op: O,
444     ) -> result::Result<T, Status> {
445         self.map_err(|e| Status::new_exception_str(exception, Some(op(e))))
446     }
447 
or_service_specific_exception(self, error_code: i32) -> result::Result<T, Status>448     fn or_service_specific_exception(self, error_code: i32) -> result::Result<T, Status> {
449         self.or_service_specific_exception_with(error_code, |e| format!("{:?}", e))
450     }
451 
or_service_specific_exception_with<M: AsRef<str>, O: FnOnce(E) -> M>( self, error_code: i32, op: O, ) -> result::Result<T, Status>452     fn or_service_specific_exception_with<M: AsRef<str>, O: FnOnce(E) -> M>(
453         self,
454         error_code: i32,
455         op: O,
456     ) -> result::Result<T, Status> {
457         self.map_err(|e| Status::new_service_specific_error_str(error_code, Some(op(e))))
458     }
459 }
460 
461 #[cfg(test)]
462 mod tests {
463     use super::*;
464 
465     #[test]
make_service_specific_error()466     fn make_service_specific_error() {
467         let status = Status::new_service_specific_error_str(-42, Some("message"));
468 
469         assert!(!status.is_ok());
470         assert_eq!(status.exception_code(), ExceptionCode::SERVICE_SPECIFIC);
471         assert_eq!(status.service_specific_error(), -42);
472         assert_eq!(
473             status.get_description(),
474             "Status(-8, EX_SERVICE_SPECIFIC): '-42: message'".to_string()
475         );
476     }
477 
478     #[test]
make_exception()479     fn make_exception() {
480         let status = Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some("message"));
481 
482         assert!(!status.is_ok());
483         assert_eq!(status.exception_code(), ExceptionCode::ILLEGAL_STATE);
484         assert_eq!(status.service_specific_error(), 0);
485         assert_eq!(status.get_description(), "Status(-5, EX_ILLEGAL_STATE): 'message'".to_string());
486     }
487 
488     #[test]
make_exception_null()489     fn make_exception_null() {
490         let status = Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some("one\0two"));
491 
492         assert!(!status.is_ok());
493         assert_eq!(status.exception_code(), ExceptionCode::ILLEGAL_STATE);
494         assert_eq!(status.service_specific_error(), 0);
495         assert_eq!(status.get_description(), "Status(-5, EX_ILLEGAL_STATE): ''".to_string());
496     }
497 
498     #[test]
convert_to_service_specific_exception()499     fn convert_to_service_specific_exception() {
500         let res: std::result::Result<(), Status> =
501             Err("message").or_service_specific_exception(-42);
502 
503         assert!(res.is_err());
504         let status = res.unwrap_err();
505         assert_eq!(status.exception_code(), ExceptionCode::SERVICE_SPECIFIC);
506         assert_eq!(status.service_specific_error(), -42);
507         assert_eq!(
508             status.get_description(),
509             "Status(-8, EX_SERVICE_SPECIFIC): '-42: \"message\"'".to_string()
510         );
511     }
512 
513     #[test]
convert_to_service_specific_exception_with()514     fn convert_to_service_specific_exception_with() {
515         let res: std::result::Result<(), Status> = Err("message")
516             .or_service_specific_exception_with(-42, |e| format!("outer message: {:?}", e));
517 
518         assert!(res.is_err());
519         let status = res.unwrap_err();
520         assert_eq!(status.exception_code(), ExceptionCode::SERVICE_SPECIFIC);
521         assert_eq!(status.service_specific_error(), -42);
522         assert_eq!(
523             status.get_description(),
524             "Status(-8, EX_SERVICE_SPECIFIC): '-42: outer message: \"message\"'".to_string()
525         );
526     }
527 
528     #[test]
convert_to_binder_exception()529     fn convert_to_binder_exception() {
530         let res: std::result::Result<(), Status> =
531             Err("message").or_binder_exception(ExceptionCode::ILLEGAL_STATE);
532 
533         assert!(res.is_err());
534         let status = res.unwrap_err();
535         assert_eq!(status.exception_code(), ExceptionCode::ILLEGAL_STATE);
536         assert_eq!(status.service_specific_error(), 0);
537         assert_eq!(
538             status.get_description(),
539             "Status(-5, EX_ILLEGAL_STATE): '\"message\"'".to_string()
540         );
541     }
542 
543     #[test]
convert_to_binder_exception_with()544     fn convert_to_binder_exception_with() {
545         let res: std::result::Result<(), Status> = Err("message")
546             .or_binder_exception_with(ExceptionCode::ILLEGAL_STATE, |e| {
547                 format!("outer message: {:?}", e)
548             });
549 
550         assert!(res.is_err());
551         let status = res.unwrap_err();
552         assert_eq!(status.exception_code(), ExceptionCode::ILLEGAL_STATE);
553         assert_eq!(status.service_specific_error(), 0);
554         assert_eq!(
555             status.get_description(),
556             "Status(-5, EX_ILLEGAL_STATE): 'outer message: \"message\"'".to_string()
557         );
558     }
559 }
560