use crate::error;
use crate::error::StatusCode;
use crate::parcel::*;
use crate::parcelable::*;
use std::fmt::{Debug, Display, Formatter};
pub type Result<T> = std::result::Result<T, Status>;
pub type BinderResult<T> = std::result::Result<T, Status>;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(i32)]
pub enum ExceptionCode {
None = 0,
Security = -1,
BadParcelable = -2,
IllegalArgument = -3,
NullPointer = -4,
IllegalState = -5,
NetworkMainThread = -6,
UnsupportedOperation = -7,
ServiceSpecific = -8,
Parcelable = -9,
HasNotedAppOpsReplyHeader = -127,
HasReplyHeader = -128,
TransactionFailed = -129,
JustError = -256,
}
impl Display for ExceptionCode {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self {
ExceptionCode::None => write!(f, "None"),
ExceptionCode::Security => write!(f, "Security"),
ExceptionCode::BadParcelable => write!(f, "BadParcelable"),
ExceptionCode::IllegalArgument => write!(f, "IllegalArgument"),
ExceptionCode::NullPointer => write!(f, "NullPointer"),
ExceptionCode::IllegalState => write!(f, "IllegalState"),
ExceptionCode::NetworkMainThread => write!(f, "NetworkMainThread"),
ExceptionCode::UnsupportedOperation => write!(f, "UnsupportedOperation"),
ExceptionCode::ServiceSpecific => write!(f, "ServiceSpecific"),
ExceptionCode::Parcelable => write!(f, "Parcelable"),
ExceptionCode::HasNotedAppOpsReplyHeader => write!(f, "HasNotedAppOpsReplyHeader"),
ExceptionCode::HasReplyHeader => write!(f, "HasReplyHeader"),
ExceptionCode::TransactionFailed => write!(f, "TransactionFailed"),
ExceptionCode::JustError => write!(f, "JustError"),
}
}
}
impl Serialize for ExceptionCode {
fn serialize(&self, parcel: &mut Parcel) -> error::Result<()> {
parcel.write::<i32>(&(*self as i32))
}
}
impl Deserialize for ExceptionCode {
fn deserialize(parcel: &mut Parcel) -> error::Result<Self> {
let exception = parcel.read::<i32>()?;
let code = match exception {
exception if exception == ExceptionCode::None as i32 => ExceptionCode::None,
exception if exception == ExceptionCode::Security as i32 => ExceptionCode::Security,
exception if exception == ExceptionCode::BadParcelable as i32 => {
ExceptionCode::BadParcelable
}
exception if exception == ExceptionCode::IllegalArgument as i32 => {
ExceptionCode::IllegalArgument
}
exception if exception == ExceptionCode::NullPointer as i32 => {
ExceptionCode::NullPointer
}
exception if exception == ExceptionCode::IllegalState as i32 => {
ExceptionCode::IllegalState
}
exception if exception == ExceptionCode::NetworkMainThread as i32 => {
ExceptionCode::NetworkMainThread
}
exception if exception == ExceptionCode::UnsupportedOperation as i32 => {
ExceptionCode::UnsupportedOperation
}
exception if exception == ExceptionCode::ServiceSpecific as i32 => {
ExceptionCode::ServiceSpecific
}
exception if exception == ExceptionCode::Parcelable as i32 => ExceptionCode::Parcelable,
exception if exception == ExceptionCode::HasNotedAppOpsReplyHeader as i32 => {
ExceptionCode::HasNotedAppOpsReplyHeader
}
exception if exception == ExceptionCode::HasReplyHeader as i32 => {
ExceptionCode::HasReplyHeader
}
exception if exception == ExceptionCode::TransactionFailed as i32 => {
ExceptionCode::TransactionFailed
}
_ => ExceptionCode::JustError,
};
Ok(code)
}
}
#[derive(Clone)]
pub struct Status {
code: StatusCode,
exception: ExceptionCode,
message: Option<String>,
}
impl PartialEq for Status {
fn eq(&self, other: &Self) -> bool {
self.code == other.code && self.exception == other.exception
}
}
impl Status {
fn new(exception: ExceptionCode, status: StatusCode, message: Option<String>) -> Self {
Status {
code: status,
exception,
message,
}
}
pub fn new_service_specific_error(err: i32, message: Option<String>) -> Self {
Self::new(
ExceptionCode::ServiceSpecific,
StatusCode::ServiceSpecific(err),
message,
)
}
pub fn is_ok(&self) -> bool {
self.exception == ExceptionCode::None
}
pub fn exception_code(&self) -> ExceptionCode {
self.exception
}
pub fn transaction_error(&self) -> StatusCode {
if self.exception == ExceptionCode::TransactionFailed {
self.code
} else {
StatusCode::Ok
}
}
pub fn service_specific_error(&self) -> i32 {
if let StatusCode::ServiceSpecific(err) = self.code {
err
} else {
0
}
}
}
impl std::error::Error for Status {}
impl Display for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.exception == ExceptionCode::None {
write!(f, "{}", self.code)
} else {
write!(
f,
"{} / {}: {}",
self.exception,
self.code,
self.message.as_ref().unwrap_or(&"".to_owned())
)
}
}
}
impl Debug for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}
impl From<ExceptionCode> for StatusCode {
fn from(exception: ExceptionCode) -> Self {
match exception {
ExceptionCode::TransactionFailed => StatusCode::FailedTransaction,
_ => StatusCode::Ok,
}
}
}
impl From<Status> for StatusCode {
fn from(status: Status) -> Self {
match status.code {
StatusCode::Ok if status.exception != ExceptionCode::None => {
StatusCode::FailedTransaction
}
code => code,
}
}
}
impl From<StatusCode> for ExceptionCode {
fn from(status: StatusCode) -> Self {
match status {
StatusCode::Ok => ExceptionCode::None,
StatusCode::UnexpectedNull => ExceptionCode::NullPointer,
StatusCode::ServiceSpecific(_) => ExceptionCode::ServiceSpecific,
_ => ExceptionCode::TransactionFailed,
}
}
}
impl From<ExceptionCode> for Status {
fn from(exception: ExceptionCode) -> Self {
Status::new(exception, exception.into(), None)
}
}
impl From<(ExceptionCode, &str)> for Status {
fn from(arg: (ExceptionCode, &str)) -> Self {
Status::new(arg.0, arg.0.into(), Some(arg.1.to_owned()))
}
}
impl From<StatusCode> for Status {
fn from(status: StatusCode) -> Self {
Status::new(status.into(), status, None)
}
}
impl From<std::io::Error> for Status {
fn from(err: std::io::Error) -> Self {
StatusCode::from(err).into()
}
}
impl From<std::array::TryFromSliceError> for Status {
fn from(err: std::array::TryFromSliceError) -> Self {
StatusCode::from(err).into()
}
}
impl Serialize for Status {
fn serialize(&self, parcel: &mut Parcel) -> error::Result<()> {
if self.exception == ExceptionCode::TransactionFailed {
return Err(self.code);
}
parcel.write::<i32>(&(self.exception as _))?;
if self.exception == ExceptionCode::None {
return Ok(());
}
parcel.write::<String>(self.message.as_ref().unwrap_or(&"".to_owned()))?;
parcel.write::<i32>(&0)?;
if self.exception == ExceptionCode::ServiceSpecific {
parcel.write::<i32>(&(self.code.into()))?;
} else if self.exception == ExceptionCode::Parcelable {
parcel.write::<i32>(&0)?;
}
Ok(())
}
}
fn read_check_header_size(parcel: &mut Parcel) -> error::Result<()> {
let header_start = parcel.data_position();
let header_avail = parcel.data_avail();
let header_size = parcel.read::<i32>()?;
if header_size < 0 {
log::error!("0x534e4554:132650049 Negative header_size({header_size}).");
return Err(StatusCode::Unknown);
}
let header_size_usize = header_size as usize;
if header_size_usize > header_avail {
log::error!(
"0x534e4554:132650049 Invalid header_size({header_size}) exceeds available({header_avail})."
);
return Err(StatusCode::Unknown);
}
let new_position = header_start.checked_add(header_size_usize).ok_or_else(|| {
log::error!("0x534e4554:132650049 Position overflow with header_size({header_size})");
StatusCode::Unknown
})?;
parcel.set_data_position(new_position);
Ok(())
}
impl Deserialize for Status {
fn deserialize(parcel: &mut Parcel) -> error::Result<Self> {
let mut exception = parcel.read::<ExceptionCode>()?;
if exception == ExceptionCode::HasNotedAppOpsReplyHeader {
read_check_header_size(parcel)?;
exception = parcel.read::<ExceptionCode>()?;
}
if exception == ExceptionCode::HasReplyHeader {
read_check_header_size(parcel)?;
exception = ExceptionCode::None;
}
let status = if exception == ExceptionCode::None {
exception.into()
} else {
let message: String = parcel.read::<String>()?;
let header_start = parcel.data_position();
let header_avail = parcel.data_avail();
let remote_stack_trace_header_size = parcel.read::<i32>()?;
if remote_stack_trace_header_size < 0 {
log::error!(
"0x534e4554:132650049 Negative remote_stack_trace_header_size({remote_stack_trace_header_size})."
);
return Err(StatusCode::Unknown);
}
let trace_size_usize = remote_stack_trace_header_size as usize;
if trace_size_usize > header_avail {
log::error!(
"0x534e4554:132650049 Invalid remote_stack_trace_header_size({remote_stack_trace_header_size}) exceeds available({header_avail})."
);
return Err(StatusCode::Unknown);
}
if trace_size_usize != 0 {
let new_position = header_start.checked_add(trace_size_usize).ok_or_else(|| {
log::error!(
"0x534e4554:132650049 Position overflow with remote_stack_trace_header_size({remote_stack_trace_header_size})"
);
StatusCode::Unknown
})?;
parcel.set_data_position(new_position);
}
let code = if exception == ExceptionCode::ServiceSpecific {
let code = parcel.read::<i32>()?;
StatusCode::ServiceSpecific(code)
} else if exception == ExceptionCode::Parcelable {
read_check_header_size(parcel)?;
StatusCode::Ok
} else {
StatusCode::Ok
};
Status::new(exception, code, Some(message))
};
Ok(status)
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn test_status() -> Result<()> {
let _status = Status::from(StatusCode::Unknown);
Ok(())
}
#[test]
fn test_status_display() -> Result<()> {
let unknown = Status::from(StatusCode::Unknown);
assert_eq!(format!("{unknown}"), "TransactionFailed / Unknown: ");
let service_specific =
Status::new_service_specific_error(1, Some("Service specific error".to_owned()));
assert_eq!(
format!("{service_specific}"),
"ServiceSpecific / ServiceSpecific(1): Service specific error"
);
let exception = Status::new(
ExceptionCode::BadParcelable,
StatusCode::Unknown,
Some("Bad parcelable".to_owned()),
);
assert_eq!(
format!("{exception}"),
"BadParcelable / Unknown: Bad parcelable"
);
Ok(())
}
#[test]
fn binder_result_question_marks_io_and_slice_errors() {
fn open_missing() -> BinderResult<std::fs::File> {
Ok(std::fs::File::open("/nonexistent/rsbinder/regression")?)
}
assert_eq!(
StatusCode::from(open_missing().unwrap_err()),
StatusCode::NameNotFound,
);
fn take_four(bytes: &[u8]) -> BinderResult<[u8; 4]> {
Ok(bytes.try_into()?)
}
assert_eq!(
StatusCode::from(take_four(&[1, 2, 3]).unwrap_err()),
StatusCode::NotEnoughData,
);
}
#[test]
fn deserialize_skips_nonzero_remote_stack_trace_header() {
let mut parcel = Parcel::new();
parcel
.write::<i32>(&(ExceptionCode::ServiceSpecific as i32))
.unwrap();
parcel.write::<String>(&"boom".to_owned()).unwrap();
parcel.write::<i32>(&8i32).unwrap();
parcel.write::<i32>(&0x5555_5555i32).unwrap(); parcel.write::<i32>(&777i32).unwrap();
parcel.set_data_position(0);
let status = Status::deserialize(&mut parcel).expect("deserialize");
assert_eq!(
StatusCode::from(status),
StatusCode::ServiceSpecific(777),
"non-zero size-inclusive stack-trace header must be skipped exactly"
);
}
#[test]
fn test_status_serialize() -> Result<()> {
let status = Status::from(StatusCode::ServiceSpecific(1));
let mut parcel = Parcel::new();
status.serialize(&mut parcel).unwrap();
parcel.set_data_position(0);
let deserialized = Status::deserialize(&mut parcel).unwrap();
assert_eq!(status, deserialized);
let status = Status::new(
ExceptionCode::Parcelable,
StatusCode::Ok,
Some("Parcelable".to_owned()),
);
let mut parcel = Parcel::new();
status.serialize(&mut parcel).unwrap();
parcel.set_data_position(0);
let deserialized = Status::deserialize(&mut parcel).unwrap();
assert_eq!(status, deserialized);
Ok(())
}
#[test]
fn status_with_exception_never_maps_to_ok() {
let status = Status::new(ExceptionCode::IllegalArgument, StatusCode::Ok, None);
assert_eq!(
StatusCode::from(status),
StatusCode::FailedTransaction,
"exception + Ok code must surface as FailedTransaction, not Ok"
);
let ok = Status::new(ExceptionCode::None, StatusCode::Ok, None);
assert_eq!(StatusCode::from(ok), StatusCode::Ok);
let svc = Status::from(StatusCode::ServiceSpecific(7));
assert_eq!(StatusCode::from(svc), StatusCode::ServiceSpecific(7));
let txn = Status::new(
ExceptionCode::TransactionFailed,
StatusCode::DeadObject,
None,
);
assert_eq!(StatusCode::from(txn), StatusCode::DeadObject);
}
#[test]
fn serialize_transaction_failed_returns_err_without_writing() {
let status = Status::new(
ExceptionCode::TransactionFailed,
StatusCode::DeadObject,
None,
);
let mut parcel = Parcel::new();
assert_eq!(status.serialize(&mut parcel), Err(StatusCode::DeadObject));
assert_eq!(
parcel.data_size(),
0,
"nothing must be written on the failed path"
);
}
#[test]
fn appops_header_exception_codes_match_aosp_wire() {
assert_eq!(
ExceptionCode::HasNotedAppOpsReplyHeader as i32,
-127,
"EX_HAS_NOTED_APPOPS_REPLY_HEADER must be -127 (AOSP Status.h:71)"
);
assert_eq!(
ExceptionCode::HasReplyHeader as i32,
-128,
"EX_HAS_REPLY_HEADER must be -128 (AOSP Status.h:74)"
);
}
fn write_header_blob(parcel: &mut Parcel, header_payload_bytes: usize) {
let aligned_payload = header_payload_bytes.div_ceil(4) * 4;
let size_field = 4 + aligned_payload;
parcel.write::<i32>(&(size_field as i32)).unwrap();
for _ in 0..(aligned_payload / 4) {
parcel.write::<i32>(&0i32).unwrap();
}
}
#[test]
fn deserialize_skips_appops_header_then_reads_ex_none() {
let mut parcel = Parcel::new();
parcel
.write::<i32>(&(ExceptionCode::HasNotedAppOpsReplyHeader as i32))
.unwrap();
write_header_blob(&mut parcel, 16); parcel.write::<i32>(&(ExceptionCode::None as i32)).unwrap();
parcel.set_data_position(0);
let status = Status::deserialize(&mut parcel).expect("deserialize");
assert_eq!(status.exception_code(), ExceptionCode::None);
assert!(status.is_ok());
}
#[test]
fn deserialize_skips_appops_then_reply_header_collapses_to_none() {
let mut parcel = Parcel::new();
parcel
.write::<i32>(&(ExceptionCode::HasNotedAppOpsReplyHeader as i32))
.unwrap();
write_header_blob(&mut parcel, 8);
parcel
.write::<i32>(&(ExceptionCode::HasReplyHeader as i32))
.unwrap();
write_header_blob(&mut parcel, 4);
parcel.set_data_position(0);
let status = Status::deserialize(&mut parcel).expect("deserialize");
assert_eq!(
status.exception_code(),
ExceptionCode::None,
"EX_HAS_REPLY_HEADER (-128) must collapse to None"
);
}
#[test]
fn deserialize_skips_appops_header_then_surfaces_real_exception() {
let mut parcel = Parcel::new();
parcel
.write::<i32>(&(ExceptionCode::HasNotedAppOpsReplyHeader as i32))
.unwrap();
write_header_blob(&mut parcel, 12);
parcel
.write::<i32>(&(ExceptionCode::Security as i32))
.unwrap();
parcel.write::<String>(&"denied".to_owned()).unwrap();
parcel.write::<i32>(&0i32).unwrap();
parcel.set_data_position(0);
let status = Status::deserialize(&mut parcel).expect("deserialize");
assert_eq!(status.exception_code(), ExceptionCode::Security);
}
#[test]
fn flag_update_txn_matches_kernel_wire() {
assert_eq!(
crate::FLAG_UPDATE_TXN,
0x40,
"FLAG_UPDATE_TXN must be the kernel-defined 0x40"
);
}
#[test]
fn flag_collect_noted_app_ops_matches_aosp_userspace() {
assert_eq!(
crate::FLAG_COLLECT_NOTED_APP_OPS,
0x80,
"FLAG_COLLECT_NOTED_APP_OPS must be 0x80 (AOSP userspace)"
);
}
#[test]
fn extended_error_struct_layout_matches_kernel() {
use std::mem::{align_of, size_of};
type Ee = crate::sys::binder_extended_error;
assert_eq!(size_of::<Ee>(), 12);
assert_eq!(align_of::<Ee>(), 4);
}
}