diff --git a/rust/bridge/ffi/src/lib.rs b/rust/bridge/ffi/src/lib.rs index d978bda06..923ef84c6 100644 --- a/rust/bridge/ffi/src/lib.rs +++ b/rust/bridge/ffi/src/lib.rs @@ -14,9 +14,6 @@ use std::ffi::{c_char, c_uchar, c_uint, CString}; use std::panic::AssertUnwindSafe; pub mod logging; -mod util; - -use crate::util::*; #[no_mangle] pub unsafe extern "C" fn signal_print_ptr(p: *const std::ffi::c_void) { @@ -69,11 +66,8 @@ pub unsafe extern "C" fn signal_error_get_message( out: *mut *const c_char, ) -> *mut SignalFfiError { let result = (|| { - if err.is_null() { - return Err(SignalFfiError::NullPointer); - } - let msg = format!("{}", *err); - write_result_to(out, msg) + let err = err.as_ref().ok_or(NullPointerError)?; + write_result_to(out, err.to_string()) })(); match result { @@ -89,18 +83,17 @@ pub unsafe extern "C" fn signal_error_get_address( ) -> *mut SignalFfiError { let err = AssertUnwindSafe(err); run_ffi_safe(|| { - let err = err.as_ref().ok_or(SignalFfiError::NullPointer)?; - match err { - SignalFfiError::Signal(SignalProtocolError::InvalidRegistrationId(addr, _value)) => { + let err = err.as_ref().ok_or(NullPointerError)?; + match err.downcast_ref::() { + Some(SignalProtocolError::InvalidRegistrationId(addr, _value)) => { write_result_to(out, addr.clone())?; } _ => { - return Err(SignalFfiError::Signal( - SignalProtocolError::InvalidArgument(format!( - "cannot get address from error ({})", - err - )), - )); + return Err(SignalProtocolError::InvalidArgument(format!( + "cannot get address from error ({})", + err + )) + .into()); } } Ok(()) @@ -114,20 +107,17 @@ pub unsafe extern "C" fn signal_error_get_uuid( ) -> *mut SignalFfiError { let err = AssertUnwindSafe(err); run_ffi_safe(|| { - let err = err.as_ref().ok_or(SignalFfiError::NullPointer)?; - match err { - SignalFfiError::Signal(SignalProtocolError::InvalidSenderKeySession { - distribution_id, - }) => { + let err = err.as_ref().ok_or(NullPointerError)?; + match err.downcast_ref::() { + Some(SignalProtocolError::InvalidSenderKeySession { distribution_id }) => { write_result_to(out, *distribution_id.as_bytes())?; } _ => { - return Err(SignalFfiError::Signal( - SignalProtocolError::InvalidArgument(format!( - "cannot get address from error ({})", - err - )), - )); + return Err(SignalProtocolError::InvalidArgument(format!( + "cannot get UUID from error ({})", + err + )) + .into()); } } Ok(()) @@ -137,10 +127,7 @@ pub unsafe extern "C" fn signal_error_get_uuid( #[no_mangle] pub unsafe extern "C" fn signal_error_get_type(err: *const SignalFfiError) -> u32 { match err.as_ref() { - Some(err) => { - let code: SignalErrorCode = err.into(); - code as u32 - } + Some(err) => err.code() as u32, None => 0, } } @@ -152,16 +139,16 @@ pub unsafe extern "C" fn signal_error_get_retry_after_seconds( ) -> *mut SignalFfiError { let err = AssertUnwindSafe(err); run_ffi_safe(|| { - let err = err.as_ref().ok_or(SignalFfiError::NullPointer)?; - match err { - SignalFfiError::RateLimited { + let err = err.as_ref().ok_or(NullPointerError)?; + match err.downcast_ref::() { + Some(libsignal_net::cdsi::LookupError::RateLimited { retry_after_seconds, - } => write_result_to(out, *retry_after_seconds), - err => Err(SignalFfiError::Signal( - SignalProtocolError::InvalidArgument(format!( - "cannot get retry_after_seconds from error ({err})" - )), - )), + }) => write_result_to(out, *retry_after_seconds), + _ => Err(SignalProtocolError::InvalidArgument(format!( + "cannot get retry_after_seconds from error ({})", + err + )) + .into()), } }) } @@ -173,16 +160,16 @@ pub unsafe extern "C" fn signal_error_get_tries_remaining( ) -> *mut SignalFfiError { let err = AssertUnwindSafe(err); run_ffi_safe(|| { - let err = err.as_ref().ok_or(SignalFfiError::NullPointer)?; - match err { - SignalFfiError::Svr(libsignal_net::svr3::Error::RestoreFailed(tries_remaining)) => { + let err = err.as_ref().ok_or(NullPointerError)?; + match err.downcast_ref::() { + Some(libsignal_net::svr3::Error::RestoreFailed(tries_remaining)) => { write_result_to(out, *tries_remaining) } - err => Err(SignalFfiError::Signal( - SignalProtocolError::InvalidArgument(format!( - "cannot get tries_remaining from error ({err})" - )), - )), + _ => Err(SignalProtocolError::InvalidArgument(format!( + "cannot get tries_remaining from error ({})", + err + )) + .into()), } }) } @@ -230,15 +217,13 @@ pub unsafe extern "C" fn signal_sealed_session_cipher_decrypt( let mut kyber_pre_key_store = InMemKyberPreKeyStore::new(); let ctext = ctext.as_slice()?; let trust_root = native_handle_cast::(trust_root)?; - let mut identity_store = identity_store.as_ref().ok_or(SignalFfiError::NullPointer)?; - let mut session_store = session_store.as_ref().ok_or(SignalFfiError::NullPointer)?; - let mut prekey_store = prekey_store.as_ref().ok_or(SignalFfiError::NullPointer)?; - let signed_prekey_store = signed_prekey_store - .as_ref() - .ok_or(SignalFfiError::NullPointer)?; + let mut identity_store = identity_store.as_ref().ok_or(NullPointerError)?; + let mut session_store = session_store.as_ref().ok_or(NullPointerError)?; + let mut prekey_store = prekey_store.as_ref().ok_or(NullPointerError)?; + let signed_prekey_store = signed_prekey_store.as_ref().ok_or(NullPointerError)?; let local_e164 = Option::convert_from(local_e164)?; - let local_uuid = Option::convert_from(local_uuid)?.ok_or(SignalFfiError::NullPointer)?; + let local_uuid = Option::convert_from(local_uuid)?.ok_or(NullPointerError)?; let decrypted = sealed_sender_decrypt( ctext, diff --git a/rust/bridge/ffi/src/util.rs b/rust/bridge/ffi/src/util.rs deleted file mode 100644 index 48e2829a3..000000000 --- a/rust/bridge/ffi/src/util.rs +++ /dev/null @@ -1,346 +0,0 @@ -// -// Copyright 2020-2021 Signal Messenger, LLC. -// SPDX-License-Identifier: AGPL-3.0-only -// - -use attest::enclave::Error as EnclaveError; -use attest::hsm_enclave::Error as HsmEnclaveError; -use device_transfer::Error as DeviceTransferError; -use libsignal_bridge::ffi::*; -use libsignal_net::svr3::Error as Svr3Error; -use libsignal_protocol::*; -use signal_crypto::Error as SignalCryptoError; -use signal_pin::Error as PinError; -use usernames::{UsernameError, UsernameLinkError}; -use zkgroup::ZkGroupVerificationFailure; - -#[derive(Debug)] -#[repr(C)] -pub enum SignalErrorCode { - #[allow(dead_code)] - UnknownError = 1, - InvalidState = 2, - InternalError = 3, - NullParameter = 4, - InvalidArgument = 5, - InvalidType = 6, - InvalidUtf8String = 7, - Cancelled = 8, - - ProtobufError = 10, - - LegacyCiphertextVersion = 21, - UnknownCiphertextVersion = 22, - UnrecognizedMessageVersion = 23, - - InvalidMessage = 30, - SealedSenderSelfSend = 31, - - InvalidKey = 40, - InvalidSignature = 41, - InvalidAttestationData = 42, - - FingerprintVersionMismatch = 51, - FingerprintParsingError = 52, - - UntrustedIdentity = 60, - - InvalidKeyIdentifier = 70, - - SessionNotFound = 80, - InvalidRegistrationId = 81, - InvalidSession = 82, - InvalidSenderKeySession = 83, - - DuplicatedMessage = 90, - - CallbackError = 100, - - VerificationFailure = 110, - - UsernameCannotBeEmpty = 120, - UsernameCannotStartWithDigit = 121, - UsernameMissingSeparator = 122, - UsernameBadDiscriminatorCharacter = 123, - UsernameBadNicknameCharacter = 124, - UsernameTooShort = 125, - UsernameTooLong = 126, - UsernameLinkInvalidEntropyDataLength = 127, - UsernameLinkInvalid = 128, - - UsernameDiscriminatorCannotBeEmpty = 140, - UsernameDiscriminatorCannotBeZero = 141, - UsernameDiscriminatorCannotBeSingleDigit = 142, - UsernameDiscriminatorCannotHaveLeadingZeros = 143, - UsernameDiscriminatorTooLarge = 144, - - IoError = 130, - #[allow(dead_code)] - InvalidMediaInput = 131, - #[allow(dead_code)] - UnsupportedMediaInput = 132, - - ConnectionTimedOut = 133, - NetworkProtocol = 134, - RateLimited = 135, - WebSocket = 136, - CdsiInvalidToken = 137, - ConnectionFailed = 138, - ChatServiceInactive = 139, - - SvrDataMissing = 150, - SvrRestoreFailed = 151, - - AppExpired = 160, - DeviceDeregistered = 161, -} - -impl From<&SignalFfiError> for SignalErrorCode { - fn from(err: &SignalFfiError) -> Self { - match err { - SignalFfiError::NullPointer => SignalErrorCode::NullParameter, - - SignalFfiError::UnexpectedPanic(_) - | SignalFfiError::InternalError(_) - | SignalFfiError::DeviceTransfer(DeviceTransferError::InternalError(_)) - | SignalFfiError::Signal(SignalProtocolError::FfiBindingError(_)) => { - SignalErrorCode::InternalError - } - - SignalFfiError::InvalidUtf8String => SignalErrorCode::InvalidUtf8String, - - SignalFfiError::Cancelled => SignalErrorCode::Cancelled, - - SignalFfiError::Signal(SignalProtocolError::InvalidProtobufEncoding) => { - SignalErrorCode::ProtobufError - } - - SignalFfiError::Signal(SignalProtocolError::DuplicatedMessage(_, _)) => { - SignalErrorCode::DuplicatedMessage - } - - SignalFfiError::Signal(SignalProtocolError::InvalidPreKeyId) - | SignalFfiError::Signal(SignalProtocolError::InvalidSignedPreKeyId) - | SignalFfiError::Signal(SignalProtocolError::InvalidKyberPreKeyId) => { - SignalErrorCode::InvalidKeyIdentifier - } - - SignalFfiError::Signal(SignalProtocolError::SealedSenderSelfSend) => { - SignalErrorCode::SealedSenderSelfSend - } - - SignalFfiError::Signal(SignalProtocolError::SignatureValidationFailed) => { - SignalErrorCode::InvalidSignature - } - - SignalFfiError::Signal(SignalProtocolError::NoKeyTypeIdentifier) - | SignalFfiError::Signal(SignalProtocolError::BadKeyType(_)) - | SignalFfiError::Signal(SignalProtocolError::BadKeyLength(_, _)) - | SignalFfiError::Signal(SignalProtocolError::BadKEMKeyType(_)) - | SignalFfiError::Signal(SignalProtocolError::WrongKEMKeyType(_, _)) - | SignalFfiError::Signal(SignalProtocolError::BadKEMKeyLength(_, _)) - | SignalFfiError::Signal(SignalProtocolError::InvalidMacKeyLength(_)) - | SignalFfiError::DeviceTransfer(DeviceTransferError::KeyDecodingFailed) - | SignalFfiError::HsmEnclave(HsmEnclaveError::InvalidPublicKeyError) - | SignalFfiError::SignalCrypto(SignalCryptoError::InvalidKeySize) => { - SignalErrorCode::InvalidKey - } - - SignalFfiError::Sgx(EnclaveError::AttestationDataError { .. }) => { - SignalErrorCode::InvalidAttestationData - } - - SignalFfiError::Pin(PinError::Argon2Error(_)) - | SignalFfiError::Pin(PinError::DecodingError(_)) - | SignalFfiError::Pin(PinError::MrenclaveLookupError) => { - SignalErrorCode::InvalidArgument - } - - SignalFfiError::Signal(SignalProtocolError::SessionNotFound(_)) - | SignalFfiError::Signal(SignalProtocolError::NoSenderKeyState { .. }) => { - SignalErrorCode::SessionNotFound - } - - SignalFfiError::Signal(SignalProtocolError::InvalidRegistrationId(..)) => { - SignalErrorCode::InvalidRegistrationId - } - - SignalFfiError::Signal(SignalProtocolError::FingerprintParsingError) => { - SignalErrorCode::FingerprintParsingError - } - - SignalFfiError::Signal(SignalProtocolError::FingerprintVersionMismatch(_, _)) => { - SignalErrorCode::FingerprintVersionMismatch - } - - SignalFfiError::Signal(SignalProtocolError::UnrecognizedMessageVersion(_)) - | SignalFfiError::Signal(SignalProtocolError::UnknownSealedSenderVersion(_)) => { - SignalErrorCode::UnrecognizedMessageVersion - } - - SignalFfiError::Signal(SignalProtocolError::UnrecognizedCiphertextVersion(_)) => { - SignalErrorCode::UnknownCiphertextVersion - } - - SignalFfiError::Signal(SignalProtocolError::InvalidMessage(..)) - | SignalFfiError::Signal(SignalProtocolError::CiphertextMessageTooShort(_)) - | SignalFfiError::Signal(SignalProtocolError::InvalidSealedSenderMessage(_)) - | SignalFfiError::Signal(SignalProtocolError::BadKEMCiphertextLength(_, _)) - | SignalFfiError::SignalCrypto(SignalCryptoError::InvalidTag) - | SignalFfiError::Sgx(EnclaveError::AttestationError(_)) - | SignalFfiError::Sgx(EnclaveError::NoiseError(_)) - | SignalFfiError::Sgx(EnclaveError::NoiseHandshakeError(_)) - | SignalFfiError::HsmEnclave(HsmEnclaveError::HSMHandshakeError(_)) - | SignalFfiError::HsmEnclave(HsmEnclaveError::HSMCommunicationError(_)) => { - SignalErrorCode::InvalidMessage - } - - SignalFfiError::Signal(SignalProtocolError::LegacyCiphertextVersion(_)) => { - SignalErrorCode::LegacyCiphertextVersion - } - - SignalFfiError::Signal(SignalProtocolError::UntrustedIdentity(_)) - | SignalFfiError::HsmEnclave(HsmEnclaveError::TrustedCodeError) => { - SignalErrorCode::UntrustedIdentity - } - - SignalFfiError::Signal(SignalProtocolError::InvalidState(_, _)) - | SignalFfiError::Sgx(EnclaveError::InvalidBridgeStateError) - | SignalFfiError::HsmEnclave(HsmEnclaveError::InvalidBridgeStateError) => { - SignalErrorCode::InvalidState - } - - SignalFfiError::Signal(SignalProtocolError::InvalidSessionStructure(_)) => { - SignalErrorCode::InvalidSession - } - - SignalFfiError::Signal(SignalProtocolError::InvalidSenderKeySession { .. }) => { - SignalErrorCode::InvalidSenderKeySession - } - - SignalFfiError::InvalidArgument(_) - | SignalFfiError::Signal(SignalProtocolError::InvalidArgument(_)) - | SignalFfiError::HsmEnclave(HsmEnclaveError::InvalidCodeHashError) - | SignalFfiError::SignalCrypto(_) => SignalErrorCode::InvalidArgument, - - SignalFfiError::Signal(SignalProtocolError::ApplicationCallbackError(_, _)) => { - SignalErrorCode::CallbackError - } - - SignalFfiError::ZkGroupVerificationFailure(ZkGroupVerificationFailure) => { - SignalErrorCode::VerificationFailure - } - - SignalFfiError::ZkGroupDeserializationFailure(_) => SignalErrorCode::InvalidType, - - SignalFfiError::UsernameError(UsernameError::NicknameCannotBeEmpty) => { - SignalErrorCode::UsernameCannotBeEmpty - } - - SignalFfiError::UsernameError(UsernameError::NicknameCannotStartWithDigit) => { - SignalErrorCode::UsernameCannotStartWithDigit - } - - SignalFfiError::UsernameError(UsernameError::MissingSeparator) => { - SignalErrorCode::UsernameMissingSeparator - } - - SignalFfiError::UsernameError(UsernameError::BadNicknameCharacter) => { - SignalErrorCode::UsernameBadNicknameCharacter - } - - SignalFfiError::UsernameError(UsernameError::NicknameTooShort) => { - SignalErrorCode::UsernameTooShort - } - - SignalFfiError::UsernameError(UsernameError::NicknameTooLong) - | SignalFfiError::UsernameLinkError(UsernameLinkError::InputDataTooLong) => { - SignalErrorCode::UsernameTooLong - } - - SignalFfiError::UsernameError(UsernameError::DiscriminatorCannotBeEmpty) => { - SignalErrorCode::UsernameDiscriminatorCannotBeEmpty - } - - SignalFfiError::UsernameError(UsernameError::DiscriminatorCannotBeZero) => { - SignalErrorCode::UsernameDiscriminatorCannotBeZero - } - - SignalFfiError::UsernameError(UsernameError::DiscriminatorCannotBeSingleDigit) => { - SignalErrorCode::UsernameDiscriminatorCannotBeSingleDigit - } - - SignalFfiError::UsernameError(UsernameError::DiscriminatorCannotHaveLeadingZeros) => { - SignalErrorCode::UsernameDiscriminatorCannotHaveLeadingZeros - } - - SignalFfiError::UsernameError(UsernameError::BadDiscriminatorCharacter) => { - SignalErrorCode::UsernameBadDiscriminatorCharacter - } - - SignalFfiError::UsernameError(UsernameError::DiscriminatorTooLarge) => { - SignalErrorCode::UsernameDiscriminatorTooLarge - } - - SignalFfiError::UsernameProofError(usernames::ProofVerificationFailure) => { - SignalErrorCode::VerificationFailure - } - - SignalFfiError::UsernameLinkError(UsernameLinkError::InvalidEntropyDataLength) => { - SignalErrorCode::UsernameLinkInvalidEntropyDataLength - } - - SignalFfiError::UsernameLinkError(_) => SignalErrorCode::UsernameLinkInvalid, - - SignalFfiError::Io(_) => SignalErrorCode::IoError, - - #[cfg(feature = "signal-media")] - SignalFfiError::Mp4SanitizeParse(err) => { - use signal_media::sanitize::mp4::ParseError; - match err.kind { - ParseError::InvalidBoxLayout { .. } - | ParseError::InvalidInput { .. } - | ParseError::MissingRequiredBox { .. } - | ParseError::TruncatedBox => SignalErrorCode::InvalidMediaInput, - - ParseError::UnsupportedBoxLayout { .. } - | ParseError::UnsupportedBox { .. } - | ParseError::UnsupportedFormat { .. } => { - SignalErrorCode::UnsupportedMediaInput - } - } - } - - #[cfg(feature = "signal-media")] - SignalFfiError::WebpSanitizeParse(err) => { - use signal_media::sanitize::webp::ParseError; - match err.kind { - ParseError::InvalidChunkLayout { .. } - | ParseError::InvalidInput { .. } - | ParseError::InvalidVp8lPrefixCode { .. } - | ParseError::MissingRequiredChunk { .. } - | ParseError::TruncatedChunk => SignalErrorCode::InvalidMediaInput, - - ParseError::UnsupportedChunk { .. } - | ParseError::UnsupportedVp8lVersion { .. } => { - SignalErrorCode::UnsupportedMediaInput - } - } - } - SignalFfiError::WebSocket(_) => SignalErrorCode::WebSocket, - SignalFfiError::ConnectionTimedOut => SignalErrorCode::ConnectionTimedOut, - SignalFfiError::ConnectionFailed => SignalErrorCode::ConnectionFailed, - SignalFfiError::ChatServiceInactive => SignalErrorCode::ChatServiceInactive, - SignalFfiError::AppExpired => SignalErrorCode::AppExpired, - SignalFfiError::DeviceDeregistered => SignalErrorCode::DeviceDeregistered, - SignalFfiError::NetworkProtocol(_) => SignalErrorCode::NetworkProtocol, - SignalFfiError::CdsiInvalidToken => SignalErrorCode::CdsiInvalidToken, - SignalFfiError::RateLimited { - retry_after_seconds: _, - } => SignalErrorCode::RateLimited, - SignalFfiError::Svr(Svr3Error::DataMissing) => SignalErrorCode::SvrDataMissing, - SignalFfiError::Svr(Svr3Error::RestoreFailed(_)) => SignalErrorCode::SvrRestoreFailed, - SignalFfiError::Svr(_) => SignalErrorCode::UnknownError, - } - } -} diff --git a/rust/bridge/shared/macros/src/ffi.rs b/rust/bridge/shared/macros/src/ffi.rs index cf5ae8c15..e61be1685 100644 --- a/rust/bridge/shared/macros/src/ffi.rs +++ b/rust/bridge/shared/macros/src/ffi.rs @@ -132,7 +132,7 @@ fn bridge_io_body( let load_async_runtime = generate_code_to_load_input("async_runtime", quote!(&#runtime)); let load_promise = quote! { - let promise = promise.as_mut().ok_or(ffi::SignalFfiError::NullPointer)?; + let promise = promise.as_mut().ok_or(ffi::NullPointerError)?; }; let input_saving = input_args.iter().map(|(name, ty)| { @@ -174,7 +174,7 @@ fn bridge_io_body( Ok(TransformHelper(__result).ok_if_needed()?.0) } _ = __cancel => { - Err(ffi::SignalFfiError::Cancelled) + Err(ffi::FutureCancelled.into()) } } })); diff --git a/rust/bridge/shared/src/ffi/convert.rs b/rust/bridge/shared/src/ffi/convert.rs index da5589235..5da2cbf98 100644 --- a/rust/bridge/shared/src/ffi/convert.rs +++ b/rust/bridge/shared/src/ffi/convert.rs @@ -197,7 +197,7 @@ impl SimpleArgTypeInfo for &mut [u8; LEN] { type ArgType = *mut [u8; LEN]; #[allow(clippy::not_unsafe_ptr_arg_deref)] fn convert_from(input: Self::ArgType) -> SignalFfiResult { - unsafe { input.as_mut() }.ok_or(SignalFfiError::NullPointer) + unsafe { input.as_mut() }.ok_or_else(|| NullPointerError.into()) } } @@ -219,12 +219,12 @@ impl SimpleArgTypeInfo for String { #[allow(clippy::not_unsafe_ptr_arg_deref)] fn convert_from(foreign: *const c_char) -> SignalFfiResult { if foreign.is_null() { - return Err(SignalFfiError::NullPointer); + return Err(NullPointerError.into()); } match unsafe { CStr::from_ptr(foreign).to_str() } { Ok(s) => Ok(s.to_owned()), - Err(_) => Err(SignalFfiError::InvalidUtf8String), + Err(e) => Err(e.into()), } } } @@ -247,7 +247,7 @@ impl SimpleArgTypeInfo for uuid::Uuid { fn convert_from(foreign: Self::ArgType) -> SignalFfiResult { match unsafe { foreign.as_ref() } { Some(array) => Ok(uuid::Uuid::from_bytes(*array)), - None => Err(SignalFfiError::NullPointer), + None => Err(NullPointerError.into()), } } } @@ -273,7 +273,7 @@ impl SimpleArgTypeInfo for libsignal_protocol::ServiceId { .into() }) } - None => Err(SignalFfiError::NullPointer), + None => Err(NullPointerError.into()), } } } @@ -334,7 +334,7 @@ impl SimpleArgTypeInfo for &'_ [u8; LEN] { type ArgType = *const [u8; LEN]; #[allow(clippy::not_unsafe_ptr_arg_deref)] fn convert_from(arg: *const [u8; LEN]) -> SignalFfiResult { - unsafe { arg.as_ref() }.ok_or(SignalFfiError::NullPointer) + unsafe { arg.as_ref() }.ok_or(NullPointerError.into()) } } @@ -354,7 +354,7 @@ macro_rules! bridge_trait { #[allow(clippy::not_unsafe_ptr_arg_deref)] fn borrow(foreign: Self::ArgType) -> SignalFfiResult { match unsafe { foreign.as_ref() } { - None => Err(SignalFfiError::NullPointer), + None => Err(NullPointerError.into()), Some(store) => Ok(store), } } @@ -390,11 +390,11 @@ bridge_trait!(MakeChatListener); impl ResultTypeInfo for Result where - E: Into, + E: FfiError, { type ResultType = T::ResultType; fn convert_into(self) -> SignalFfiResult { - T::convert_into(self.map_err(Into::into)?) + T::convert_into(self?) } } @@ -540,7 +540,7 @@ impl<'a, T: BridgeHandle> ArgTypeInfo<'a> for &'a [&'a T] { // Check preconditions up front. let slice_of_pointers = unsafe { foreign.as_slice() }?; if slice_of_pointers.contains(&std::ptr::null()) { - return Err(SignalFfiError::NullPointer); + return Err(NullPointerError.into()); } Ok(foreign) @@ -584,7 +584,7 @@ where type ArgType = *const T::Array; fn convert_from(foreign: Self::ArgType) -> SignalFfiResult { - let array = unsafe { foreign.as_ref() }.ok_or(SignalFfiError::NullPointer)?; + let array = unsafe { foreign.as_ref() }.ok_or(NullPointerError)?; let result: T = zkgroup::deserialize(array.as_ref()).unwrap_or_else(|_| { panic!( "{} should have been validated on creation", @@ -606,10 +606,11 @@ where let p = P::convert_from(foreign)?; p.try_into() .map_err(|e| { - SignalFfiError::Signal(SignalProtocolError::InvalidArgument(format!( + SignalProtocolError::InvalidArgument(format!( "invalid {}: {e}", std::any::type_name::() - ))) + )) + .into() }) .map(AsType::from) } diff --git a/rust/bridge/shared/src/ffi/error.rs b/rust/bridge/shared/src/ffi/error.rs index e87302610..7d8a6e578 100644 --- a/rust/bridge/shared/src/ffi/error.rs +++ b/rust/bridge/shared/src/ffi/error.rs @@ -10,7 +10,7 @@ use attest::enclave::Error as EnclaveError; use attest::hsm_enclave::Error as HsmEnclaveError; use device_transfer::Error as DeviceTransferError; use libsignal_net::chat::ChatServiceError; -use libsignal_net::infra::ws::{WebSocketConnectError, WebSocketServiceError}; +use libsignal_net::infra::ws::WebSocketConnectError; use libsignal_net::svr3::Error as Svr3Error; use libsignal_protocol::*; use signal_crypto::Error as SignalCryptoError; @@ -20,310 +20,597 @@ use zkgroup::{ZkGroupDeserializationFailure, ZkGroupVerificationFailure}; use crate::support::describe_panic; -use super::NullPointerError; +use super::{FutureCancelled, NullPointerError, UnexpectedPanic}; + +#[derive(Debug)] +#[repr(C)] +pub enum SignalErrorCode { + #[allow(dead_code)] + UnknownError = 1, + InvalidState = 2, + InternalError = 3, + NullParameter = 4, + InvalidArgument = 5, + InvalidType = 6, + InvalidUtf8String = 7, + Cancelled = 8, + + ProtobufError = 10, + + LegacyCiphertextVersion = 21, + UnknownCiphertextVersion = 22, + UnrecognizedMessageVersion = 23, + + InvalidMessage = 30, + SealedSenderSelfSend = 31, + + InvalidKey = 40, + InvalidSignature = 41, + InvalidAttestationData = 42, + + FingerprintVersionMismatch = 51, + FingerprintParsingError = 52, + + UntrustedIdentity = 60, + + InvalidKeyIdentifier = 70, + + SessionNotFound = 80, + InvalidRegistrationId = 81, + InvalidSession = 82, + InvalidSenderKeySession = 83, + + DuplicatedMessage = 90, + + CallbackError = 100, + + VerificationFailure = 110, + + UsernameCannotBeEmpty = 120, + UsernameCannotStartWithDigit = 121, + UsernameMissingSeparator = 122, + UsernameBadDiscriminatorCharacter = 123, + UsernameBadNicknameCharacter = 124, + UsernameTooShort = 125, + UsernameTooLong = 126, + UsernameLinkInvalidEntropyDataLength = 127, + UsernameLinkInvalid = 128, + + UsernameDiscriminatorCannotBeEmpty = 140, + UsernameDiscriminatorCannotBeZero = 141, + UsernameDiscriminatorCannotBeSingleDigit = 142, + UsernameDiscriminatorCannotHaveLeadingZeros = 143, + UsernameDiscriminatorTooLarge = 144, + + IoError = 130, + #[allow(dead_code)] + InvalidMediaInput = 131, + #[allow(dead_code)] + UnsupportedMediaInput = 132, + + ConnectionTimedOut = 133, + NetworkProtocol = 134, + RateLimited = 135, + WebSocket = 136, + CdsiInvalidToken = 137, + ConnectionFailed = 138, + ChatServiceInactive = 139, + + SvrDataMissing = 150, + SvrRestoreFailed = 151, + + AppExpired = 160, + DeviceDeregistered = 161, +} + +pub trait UpcastAsAny { + fn upcast_as_any(&self) -> &dyn std::any::Any; +} +impl UpcastAsAny for T { + fn upcast_as_any(&self) -> &dyn std::any::Any { + self + } +} + +pub trait FfiError: UpcastAsAny + fmt::Debug + Send + 'static { + fn describe(&self) -> String; + fn code(&self) -> SignalErrorCode; +} /// The top-level error type (opaquely) returned to C clients when something goes wrong. -#[derive(Debug, thiserror::Error)] -pub enum SignalFfiError { - Signal(SignalProtocolError), - DeviceTransfer(DeviceTransferError), - HsmEnclave(HsmEnclaveError), - Sgx(EnclaveError), - Pin(PinError), - SignalCrypto(SignalCryptoError), - ZkGroupVerificationFailure(ZkGroupVerificationFailure), - ZkGroupDeserializationFailure(ZkGroupDeserializationFailure), - UsernameError(UsernameError), - UsernameProofError(usernames::ProofVerificationFailure), - UsernameLinkError(UsernameLinkError), - Io(IoError), - WebSocket(#[from] WebSocketServiceError), - ConnectionTimedOut, - ConnectionFailed, - ChatServiceInactive, - AppExpired, - DeviceDeregistered, - NetworkProtocol(String), - CdsiInvalidToken, - RateLimited { - retry_after_seconds: u32, - }, - Svr(Svr3Error), - #[cfg(feature = "signal-media")] - Mp4SanitizeParse(signal_media::sanitize::mp4::ParseErrorReport), - #[cfg(feature = "signal-media")] - WebpSanitizeParse(signal_media::sanitize::webp::ParseErrorReport), - NullPointer, - InvalidUtf8String, - InvalidArgument(String), - Cancelled, - InternalError(String), - UnexpectedPanic(std::boxed::Box), +/// +/// Ideally this would use [ThinBox][], and then we wouldn't need an extra level of indirection when +/// returning it to C, but unfortunately that isn't stable yet. +/// +/// [ThinBox]: https://doc.rust-lang.org/std/boxed/struct.ThinBox.html +#[derive(Debug)] +pub struct SignalFfiError(Box); + +impl SignalFfiError { + pub fn code(&self) -> SignalErrorCode { + self.0.code() + } + + pub fn downcast_ref(&self) -> Option<&T> { + (*self.0).upcast_as_any().downcast_ref() + } } impl fmt::Display for SignalFfiError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - SignalFfiError::Signal(s) => write!(f, "{}", s), - SignalFfiError::DeviceTransfer(c) => { - write!(f, "Device transfer operation failed: {}", c) - } - SignalFfiError::HsmEnclave(e) => { - write!(f, "HSM enclave operation failed: {}", e) - } - SignalFfiError::Sgx(e) => { - write!(f, "SGX operation failed: {}", e) - } - SignalFfiError::SignalCrypto(c) => { - write!(f, "Cryptographic operation failed: {}", c) + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0.describe()) + } +} + +impl From for SignalFfiError { + fn from(mut value: T) -> Self { + // Special case: if the error being boxed is an IoError containing a SignalProtocolError, + // extract the SignalProtocolError up front. + match (&mut value as &mut dyn std::any::Any).downcast_mut::() { + Some(e) => { + let original_error = (e.kind() == IoErrorKind::Other) + .then(|| { + e.get_mut() + .and_then(|e| e.downcast_mut::()) + }) + .flatten() + .map(|e| { + // We can't get the inner error out without putting something in + // its place, so leave some random (cheap-to-construct) error. + // TODO: use IoError::downcast() once it is stabilized + // (https://github.com/rust-lang/rust/issues/99262). + std::mem::replace(e, SignalProtocolError::InvalidPreKeyId) + }); + if let Some(original_error) = original_error { + Self(Box::new(original_error)) + } else { + Self(Box::new(value)) + } } - SignalFfiError::Pin(e) => write!(f, "{}", e), - SignalFfiError::ZkGroupVerificationFailure(e) => write!(f, "{}", e), - SignalFfiError::ZkGroupDeserializationFailure(e) => write!(f, "{}", e), - SignalFfiError::UsernameError(e) => write!(f, "{}", e), - SignalFfiError::UsernameProofError(e) => write!(f, "{}", e), - SignalFfiError::UsernameLinkError(e) => write!(f, "{}", e), - SignalFfiError::Io(e) => write!(f, "IO error: {}", e), - SignalFfiError::ConnectionTimedOut => write!(f, "Connect timed out"), - SignalFfiError::ConnectionFailed => write!(f, "Connection failed"), - SignalFfiError::ChatServiceInactive => write!(f, "Chat service inactive"), - SignalFfiError::AppExpired => write!(f, "App expired"), - SignalFfiError::DeviceDeregistered => write!(f, "Device deregistered or delinked"), - SignalFfiError::WebSocket(e) => write!(f, "WebSocket error: {e}"), - SignalFfiError::CdsiInvalidToken => write!(f, "CDSI request token was invalid"), - SignalFfiError::NetworkProtocol(message) => write!(f, "Protocol error: {}", message), - SignalFfiError::RateLimited { - retry_after_seconds, - } => write!(f, "Rate limited; try again after {}s", retry_after_seconds), - SignalFfiError::Svr(e) => write!(f, "SVR error: {e}"), - #[cfg(feature = "signal-media")] - SignalFfiError::Mp4SanitizeParse(e) => { - write!(f, "Mp4 sanitizer failed to parse mp4 file: {}", e) + None => Self(Box::new(value)), + } + } +} + +impl FfiError for SignalProtocolError { + fn describe(&self) -> String { + self.to_string() + } + + fn code(&self) -> SignalErrorCode { + match self { + Self::InvalidArgument(_) => SignalErrorCode::InvalidArgument, + Self::InvalidState(_, _) => SignalErrorCode::InvalidState, + Self::InvalidProtobufEncoding => SignalErrorCode::ProtobufError, + Self::CiphertextMessageTooShort(_) + | Self::InvalidMessage(_, _) + | Self::InvalidSealedSenderMessage(_) + | Self::BadKEMCiphertextLength(_, _) => SignalErrorCode::InvalidMessage, + Self::LegacyCiphertextVersion(_) => SignalErrorCode::LegacyCiphertextVersion, + Self::UnrecognizedCiphertextVersion(_) => SignalErrorCode::UnknownCiphertextVersion, + Self::UnrecognizedMessageVersion(_) | Self::UnknownSealedSenderVersion(_) => { + SignalErrorCode::UnrecognizedMessageVersion } - #[cfg(feature = "signal-media")] - SignalFfiError::WebpSanitizeParse(e) => { - write!(f, "WebP sanitizer failed to parse webp file: {}", e) + Self::FingerprintVersionMismatch(_, _) => SignalErrorCode::FingerprintVersionMismatch, + Self::FingerprintParsingError => SignalErrorCode::FingerprintParsingError, + Self::NoKeyTypeIdentifier + | Self::BadKeyType(_) + | Self::BadKeyLength(_, _) + | Self::InvalidMacKeyLength(_) + | Self::BadKEMKeyType(_) + | Self::WrongKEMKeyType(_, _) + | Self::BadKEMKeyLength(_, _) => SignalErrorCode::InvalidKey, + Self::SignatureValidationFailed => SignalErrorCode::InvalidSignature, + Self::UntrustedIdentity(_) => SignalErrorCode::UntrustedIdentity, + Self::InvalidPreKeyId | Self::InvalidSignedPreKeyId | Self::InvalidKyberPreKeyId => { + SignalErrorCode::InvalidKeyIdentifier } - SignalFfiError::NullPointer => write!(f, "null pointer"), - SignalFfiError::InvalidUtf8String => write!(f, "invalid UTF8 string"), - SignalFfiError::InvalidArgument(msg) => write!(f, "invalid argument: {msg}"), - SignalFfiError::Cancelled => write!(f, "cancelled"), - SignalFfiError::InternalError(msg) => write!(f, "internal error: {msg}"), - SignalFfiError::UnexpectedPanic(e) => { - write!(f, "unexpected panic: {}", describe_panic(e)) + Self::NoSenderKeyState { .. } | Self::SessionNotFound(_) => { + SignalErrorCode::SessionNotFound } + Self::InvalidSessionStructure(_) => SignalErrorCode::InvalidSession, + Self::InvalidSenderKeySession { .. } => SignalErrorCode::InvalidSenderKeySession, + Self::InvalidRegistrationId(_, _) => SignalErrorCode::InvalidRegistrationId, + Self::DuplicatedMessage(_, _) => SignalErrorCode::DuplicatedMessage, + Self::FfiBindingError(_) => SignalErrorCode::InternalError, + Self::ApplicationCallbackError(_, _) => SignalErrorCode::CallbackError, + Self::SealedSenderSelfSend => SignalErrorCode::SealedSenderSelfSend, } } } -impl From for SignalFfiError { - fn from(e: SignalProtocolError) -> SignalFfiError { - SignalFfiError::Signal(e) +impl FfiError for DeviceTransferError { + fn describe(&self) -> String { + format!("Device transfer operation failed: {self}") } -} -impl From for SignalFfiError { - fn from(e: DeviceTransferError) -> SignalFfiError { - SignalFfiError::DeviceTransfer(e) + fn code(&self) -> SignalErrorCode { + match self { + Self::KeyDecodingFailed => SignalErrorCode::InvalidKey, + Self::InternalError(_) => SignalErrorCode::InternalError, + } } } -impl From for SignalFfiError { - fn from(e: HsmEnclaveError) -> SignalFfiError { - SignalFfiError::HsmEnclave(e) +impl FfiError for HsmEnclaveError { + fn describe(&self) -> String { + format!("HSM enclave operation failed: {self}") + } + + fn code(&self) -> SignalErrorCode { + match self { + Self::HSMCommunicationError(_) | Self::HSMHandshakeError(_) => { + SignalErrorCode::InvalidMessage + } + Self::TrustedCodeError => SignalErrorCode::UntrustedIdentity, + Self::InvalidPublicKeyError => SignalErrorCode::InvalidKey, + Self::InvalidCodeHashError => SignalErrorCode::InvalidArgument, + Self::InvalidBridgeStateError => SignalErrorCode::InvalidState, + } } } -impl From for SignalFfiError { - fn from(e: EnclaveError) -> SignalFfiError { - SignalFfiError::Sgx(e) +impl FfiError for EnclaveError { + fn describe(&self) -> String { + format!("SGX operation failed: {self}") + } + + fn code(&self) -> SignalErrorCode { + match self { + Self::AttestationError(_) | Self::NoiseError(_) | Self::NoiseHandshakeError(_) => { + SignalErrorCode::InvalidMessage + } + Self::AttestationDataError { .. } => SignalErrorCode::InvalidAttestationData, + Self::InvalidBridgeStateError => SignalErrorCode::InvalidState, + } } } -impl From for SignalFfiError { - fn from(e: PinError) -> SignalFfiError { - SignalFfiError::Pin(e) +impl FfiError for PinError { + fn describe(&self) -> String { + self.to_string() + } + + fn code(&self) -> SignalErrorCode { + match self { + Self::Argon2Error(_) | Self::DecodingError(_) | Self::MrenclaveLookupError => { + SignalErrorCode::InvalidArgument + } + } } } -impl From for SignalFfiError { - fn from(e: SignalCryptoError) -> SignalFfiError { - SignalFfiError::SignalCrypto(e) +impl FfiError for SignalCryptoError { + fn describe(&self) -> String { + format!("Cryptographic operation failed: {self}") + } + + fn code(&self) -> SignalErrorCode { + match self { + Self::UnknownAlgorithm(_, _) + | Self::InvalidKeySize + | Self::InvalidNonceSize + | Self::InvalidInputSize => SignalErrorCode::InvalidArgument, + Self::InvalidTag => SignalErrorCode::InvalidMessage, + } } } -impl From for SignalFfiError { - fn from(e: ZkGroupVerificationFailure) -> SignalFfiError { - SignalFfiError::ZkGroupVerificationFailure(e) +impl FfiError for ZkGroupVerificationFailure { + fn describe(&self) -> String { + self.to_string() + } + + fn code(&self) -> SignalErrorCode { + SignalErrorCode::VerificationFailure } } -impl From for SignalFfiError { - fn from(e: ZkGroupDeserializationFailure) -> SignalFfiError { - SignalFfiError::ZkGroupDeserializationFailure(e) +impl FfiError for ZkGroupDeserializationFailure { + fn describe(&self) -> String { + self.to_string() + } + + fn code(&self) -> SignalErrorCode { + SignalErrorCode::InvalidType } } -impl From for SignalFfiError { - fn from(e: UsernameError) -> SignalFfiError { - SignalFfiError::UsernameError(e) +impl FfiError for UsernameError { + fn describe(&self) -> String { + self.to_string() + } + + fn code(&self) -> SignalErrorCode { + match self { + Self::MissingSeparator => SignalErrorCode::UsernameMissingSeparator, + Self::NicknameCannotBeEmpty => SignalErrorCode::UsernameCannotBeEmpty, + Self::NicknameCannotStartWithDigit => SignalErrorCode::UsernameCannotStartWithDigit, + Self::BadNicknameCharacter => SignalErrorCode::UsernameBadNicknameCharacter, + Self::NicknameTooShort => SignalErrorCode::UsernameTooShort, + Self::NicknameTooLong => SignalErrorCode::UsernameTooLong, + Self::DiscriminatorCannotBeEmpty => SignalErrorCode::UsernameDiscriminatorCannotBeEmpty, + Self::DiscriminatorCannotBeZero => SignalErrorCode::UsernameDiscriminatorCannotBeZero, + Self::DiscriminatorCannotBeSingleDigit => { + SignalErrorCode::UsernameDiscriminatorCannotBeSingleDigit + } + Self::DiscriminatorCannotHaveLeadingZeros => { + SignalErrorCode::UsernameDiscriminatorCannotHaveLeadingZeros + } + Self::BadDiscriminatorCharacter => SignalErrorCode::UsernameBadDiscriminatorCharacter, + Self::DiscriminatorTooLarge => SignalErrorCode::UsernameDiscriminatorTooLarge, + } } } -impl From for SignalFfiError { - fn from(e: usernames::ProofVerificationFailure) -> SignalFfiError { - SignalFfiError::UsernameProofError(e) +impl FfiError for usernames::ProofVerificationFailure { + fn describe(&self) -> String { + self.to_string() + } + + fn code(&self) -> SignalErrorCode { + SignalErrorCode::VerificationFailure } } -impl From for SignalFfiError { - fn from(e: UsernameLinkError) -> SignalFfiError { - SignalFfiError::UsernameLinkError(e) +impl FfiError for UsernameLinkError { + fn describe(&self) -> String { + self.to_string() + } + + fn code(&self) -> SignalErrorCode { + match self { + Self::InputDataTooLong => SignalErrorCode::UsernameTooLong, + Self::InvalidEntropyDataLength => SignalErrorCode::UsernameLinkInvalidEntropyDataLength, + Self::UsernameLinkDataTooShort + | Self::HmacMismatch + | Self::BadCiphertext + | Self::InvalidDecryptedDataStructure => SignalErrorCode::UsernameLinkInvalid, + } } } -impl From for SignalFfiError { - fn from(mut e: IoError) -> SignalFfiError { - let original_error = (e.kind() == IoErrorKind::Other) +impl FfiError for IoError { + fn describe(&self) -> String { + format!("IO error: {self}") + } + + fn code(&self) -> SignalErrorCode { + // Parallels the unwrapping that happens when converting to a boxed SignalFfiError. + (self.kind() == IoErrorKind::Other) .then(|| { - e.get_mut() - .and_then(|e| e.downcast_mut::()) + Some( + self.get_ref()? + .downcast_ref::()? + .code(), + ) }) .flatten() - .map(|e| { - // We can't get the inner error out without putting something in - // its place, so leave some random (cheap-to-construct) error. - // TODO: use IoError::downcast() once it is stabilized - // (https://github.com/rust-lang/rust/issues/99262). - std::mem::replace(e, SignalProtocolError::InvalidPreKeyId) - }); - - if let Some(callback_err) = original_error { - Self::Signal(callback_err) - } else { - Self::Io(e) - } + .unwrap_or(SignalErrorCode::IoError) } } -impl From for SignalFfiError { - fn from(value: libsignal_net::cdsi::LookupError) -> Self { - use libsignal_net::cdsi::LookupError; - - match value { - LookupError::AttestationError(e) => SignalFfiError::Sgx(e), - LookupError::ConnectTransport(e) => SignalFfiError::Io(e.into()), - LookupError::WebSocket(e) => SignalFfiError::WebSocket(e), - LookupError::ConnectionTimedOut => SignalFfiError::ConnectionTimedOut, - LookupError::ParseError - | LookupError::Protocol - | LookupError::InvalidResponse - | LookupError::Server { reason: _ } => { - SignalFfiError::NetworkProtocol(value.to_string()) +impl FfiError for libsignal_net::cdsi::LookupError { + fn describe(&self) -> String { + match self { + Self::Protocol | Self::InvalidResponse | Self::ParseError | Self::Server { .. } => { + format!("Protocol error: {self}") } - LookupError::RateLimited { - retry_after_seconds: retry_after, - } => SignalFfiError::RateLimited { - retry_after_seconds: retry_after, - }, - LookupError::InvalidToken => SignalFfiError::CdsiInvalidToken, - LookupError::InvalidArgument { server_reason: _ } => { - SignalFfiError::Signal(SignalProtocolError::InvalidArgument(value.to_string())) + Self::AttestationError(e) => e.describe(), + Self::RateLimited { + retry_after_seconds, + } => format!("Rate limited; try again after {retry_after_seconds}s"), + Self::InvalidToken => "CDSI request token was invalid".to_owned(), + Self::ConnectTransport(e) => format!("IO error: {e}"), + Self::WebSocket(e) => format!("WebSocket error: {e}"), + Self::ConnectionTimedOut => "Connect timed out".to_owned(), + Self::InvalidArgument { .. } => format!("invalid argument: {self}"), + } + } + + fn code(&self) -> SignalErrorCode { + match self { + Self::Protocol | Self::InvalidResponse | Self::ParseError | Self::Server { .. } => { + SignalErrorCode::NetworkProtocol } + Self::AttestationError(e) => e.code(), + Self::RateLimited { .. } => SignalErrorCode::RateLimited, + Self::InvalidToken => SignalErrorCode::CdsiInvalidToken, + Self::ConnectTransport(_) => SignalErrorCode::IoError, + Self::WebSocket(_) => SignalErrorCode::WebSocket, + Self::ConnectionTimedOut => SignalErrorCode::ConnectionTimedOut, + Self::InvalidArgument { .. } => SignalErrorCode::InvalidArgument, } } } -impl From for SignalFfiError { - fn from(err: Svr3Error) -> Self { - match err { - Svr3Error::Connect(e) => match e { - WebSocketConnectError::Transport(e) => SignalFfiError::Io(e.into()), - WebSocketConnectError::Timeout => SignalFfiError::ConnectionTimedOut, - WebSocketConnectError::WebSocketError(e) => WebSocketServiceError::from(e).into(), - WebSocketConnectError::RejectedByServer(response) => { - WebSocketServiceError::Http(response).into() - } - }, - Svr3Error::Service(e) => SignalFfiError::WebSocket(e), - Svr3Error::ConnectionTimedOut => SignalFfiError::ConnectionTimedOut, - Svr3Error::AttestationError(inner) => SignalFfiError::Sgx(inner), - Svr3Error::Protocol(inner) => SignalFfiError::NetworkProtocol(inner.to_string()), - Svr3Error::RequestFailed(_) | Svr3Error::RestoreFailed(_) | Svr3Error::DataMissing => { - SignalFfiError::Svr(err) +impl FfiError for Svr3Error { + fn describe(&self) -> String { + match self { + Self::Connect(WebSocketConnectError::Timeout) | Self::ConnectionTimedOut => { + "Connect timed out".to_owned() + } + Self::Connect(WebSocketConnectError::Transport(e)) => format!("IO error: {e}"), + Self::Connect( + e @ (WebSocketConnectError::WebSocketError(_) + | WebSocketConnectError::RejectedByServer(_)), + ) => { + format!("WebSocket error: {e}") } + Self::Service(e) => format!("WebSocket error: {e}"), + Self::Protocol(e) => format!("Protocol error: {e}"), + Self::AttestationError(inner) => inner.describe(), + Self::RequestFailed(_) | Self::RestoreFailed(_) | Self::DataMissing => { + format!("SVR error: {self}") + } + } + } + + fn code(&self) -> SignalErrorCode { + match self { + Self::Connect(e) => match e { + WebSocketConnectError::Transport(_) => SignalErrorCode::IoError, + WebSocketConnectError::Timeout => SignalErrorCode::ConnectionTimedOut, + WebSocketConnectError::WebSocketError(_) + | WebSocketConnectError::RejectedByServer(_) => SignalErrorCode::WebSocket, + }, + Self::Service(_) => SignalErrorCode::WebSocket, + Self::ConnectionTimedOut => SignalErrorCode::ConnectionTimedOut, + Self::AttestationError(inner) => inner.code(), + Self::Protocol(_) => SignalErrorCode::NetworkProtocol, + Self::RequestFailed(_) => SignalErrorCode::UnknownError, + Self::RestoreFailed(_) => SignalErrorCode::SvrRestoreFailed, + Self::DataMissing => SignalErrorCode::SvrDataMissing, } } } -impl From for SignalFfiError { - fn from(err: ChatServiceError) -> Self { - match err { - ChatServiceError::WebSocket(e) => SignalFfiError::WebSocket(e), - ChatServiceError::AllConnectionRoutesFailed { attempts: _ } - | ChatServiceError::ServiceUnavailable => SignalFfiError::ConnectionFailed, - ChatServiceError::UnexpectedFrameReceived - | ChatServiceError::ServerRequestMissingId - | ChatServiceError::IncomingDataInvalid => { - SignalFfiError::NetworkProtocol(err.to_string()) +impl FfiError for ChatServiceError { + fn describe(&self) -> String { + match self { + Self::WebSocket(e) => format!("WebSocket error: {e}"), + Self::AllConnectionRoutesFailed { .. } | Self::ServiceUnavailable => { + "Connection failed".to_owned() + } + Self::UnexpectedFrameReceived + | Self::ServerRequestMissingId + | Self::IncomingDataInvalid => format!("Protocol error: {self}"), + Self::FailedToPassMessageToIncomingChannel | Self::RequestHasInvalidHeader => { + format!("internal error: {self}") + } + Self::Timeout | Self::TimeoutEstablishingConnection { .. } => { + "Connect timed out".to_owned() + } + Self::ServiceInactive => "Chat service inactive".to_owned(), + Self::AppExpired => "App expired".to_owned(), + Self::DeviceDeregistered => "Device deregistered or delinked".to_owned(), + } + } + + fn code(&self) -> SignalErrorCode { + match self { + Self::WebSocket(_) => SignalErrorCode::WebSocket, + Self::AllConnectionRoutesFailed { .. } | Self::ServiceUnavailable => { + SignalErrorCode::ConnectionFailed } - ChatServiceError::FailedToPassMessageToIncomingChannel => { - SignalFfiError::InternalError(err.to_string()) + Self::UnexpectedFrameReceived + | Self::ServerRequestMissingId + | Self::IncomingDataInvalid => SignalErrorCode::NetworkProtocol, + Self::FailedToPassMessageToIncomingChannel | Self::RequestHasInvalidHeader => { + SignalErrorCode::InternalError } - ChatServiceError::RequestHasInvalidHeader => SignalFfiError::InternalError(format!( - "{err} (but libsignal_ffi only supports string values anyway, so how?)" - )), - ChatServiceError::Timeout - | ChatServiceError::TimeoutEstablishingConnection { attempts: _ } => { - SignalFfiError::ConnectionTimedOut + Self::Timeout | Self::TimeoutEstablishingConnection { .. } => { + SignalErrorCode::ConnectionTimedOut } - ChatServiceError::ServiceInactive => SignalFfiError::ChatServiceInactive, - ChatServiceError::AppExpired => SignalFfiError::AppExpired, - ChatServiceError::DeviceDeregistered => SignalFfiError::DeviceDeregistered, + Self::ServiceInactive => SignalErrorCode::ChatServiceInactive, + Self::AppExpired => SignalErrorCode::AppExpired, + Self::DeviceDeregistered => SignalErrorCode::DeviceDeregistered, } } } -impl From for SignalFfiError { - fn from(err: http::uri::InvalidUri) -> Self { - SignalFfiError::InvalidArgument(err.to_string()) +impl FfiError for http::uri::InvalidUri { + fn describe(&self) -> String { + format!("invalid argument: {self}") + } + + fn code(&self) -> SignalErrorCode { + SignalErrorCode::InvalidArgument } } #[cfg(feature = "signal-media")] -impl From for SignalFfiError { - fn from(e: signal_media::sanitize::mp4::Error) -> SignalFfiError { - use signal_media::sanitize::mp4::Error; - match e { - Error::Io(e) => Self::Io(e), - Error::Parse(e) => Self::Mp4SanitizeParse(e), +impl FfiError for signal_media::sanitize::mp4::Error { + fn describe(&self) -> String { + match self { + Self::Io(e) => e.describe(), + Self::Parse(e) => format!("Mp4 sanitizer failed to parse mp4 file: {e}"), + } + } + + fn code(&self) -> SignalErrorCode { + use signal_media::sanitize::mp4::ParseError; + match self { + Self::Io(e) => e.code(), + Self::Parse(e) => match e.kind { + ParseError::InvalidBoxLayout { .. } + | ParseError::InvalidInput { .. } + | ParseError::MissingRequiredBox { .. } + | ParseError::TruncatedBox => SignalErrorCode::InvalidMediaInput, + + ParseError::UnsupportedBoxLayout { .. } + | ParseError::UnsupportedBox { .. } + | ParseError::UnsupportedFormat { .. } => SignalErrorCode::UnsupportedMediaInput, + }, } } } #[cfg(feature = "signal-media")] -impl From for SignalFfiError { - fn from(e: signal_media::sanitize::webp::Error) -> SignalFfiError { - use signal_media::sanitize::webp::Error; - match e { - Error::Io(e) => Self::Io(e), - Error::Parse(e) => Self::WebpSanitizeParse(e), +impl FfiError for signal_media::sanitize::webp::Error { + fn describe(&self) -> String { + match self { + Self::Io(e) => e.describe(), + Self::Parse(e) => format!("WebP sanitizer failed to parse webp file: {e}"), + } + } + + fn code(&self) -> SignalErrorCode { + use signal_media::sanitize::webp::ParseError; + match self { + Self::Io(e) => e.code(), + Self::Parse(e) => match e.kind { + ParseError::InvalidChunkLayout { .. } + | ParseError::InvalidInput { .. } + | ParseError::InvalidVp8lPrefixCode { .. } + | ParseError::MissingRequiredChunk { .. } + | ParseError::TruncatedChunk => SignalErrorCode::InvalidMediaInput, + + ParseError::UnsupportedChunk { .. } | ParseError::UnsupportedVp8lVersion { .. } => { + SignalErrorCode::UnsupportedMediaInput + } + }, } } } -impl From for SignalFfiError { - fn from(_: NullPointerError) -> SignalFfiError { - SignalFfiError::NullPointer +impl FfiError for NullPointerError { + fn describe(&self) -> String { + "null pointer".to_owned() + } + + fn code(&self) -> SignalErrorCode { + SignalErrorCode::NullParameter } } -impl From for IoError { - fn from(e: SignalFfiError) -> Self { - match e { - SignalFfiError::Io(e) => e, - e => IoError::new(IoErrorKind::Other, e.to_string()), - } +impl FfiError for UnexpectedPanic { + fn describe(&self) -> String { + format!("unexpected panic: {}", describe_panic(&self.0)) + } + + fn code(&self) -> SignalErrorCode { + SignalErrorCode::InternalError + } +} + +impl FfiError for std::str::Utf8Error { + fn describe(&self) -> String { + "invalid UTF8 string".to_owned() + } + + fn code(&self) -> SignalErrorCode { + SignalErrorCode::InvalidUtf8String + } +} + +impl FfiError for FutureCancelled { + fn describe(&self) -> String { + "cancelled".to_owned() + } + + fn code(&self) -> SignalErrorCode { + SignalErrorCode::Cancelled } } diff --git a/rust/bridge/shared/src/ffi/futures.rs b/rust/bridge/shared/src/ffi/futures.rs index d9e079ded..e32c9bfdb 100644 --- a/rust/bridge/shared/src/ffi/futures.rs +++ b/rust/bridge/shared/src/ffi/futures.rs @@ -10,6 +10,9 @@ use futures_util::{FutureExt, TryFutureExt}; use std::future::Future; +#[derive(Debug)] +pub(crate) struct FutureCancelled; + pub type RawCancellationId = u64; /// A C callback used to report the results of Rust futures. @@ -77,7 +80,7 @@ impl ResultReporter for FutureResult let result = result.and_then(|result| { std::panic::catch_unwind(|| result.convert_into()) - .unwrap_or_else(|panic| Err(SignalFfiError::UnexpectedPanic(panic))) + .unwrap_or_else(|panic| Err(UnexpectedPanic(panic).into())) }); match result { @@ -134,5 +137,5 @@ pub fn catch_unwind( ) -> impl Future> + Send + std::panic::UnwindSafe + 'static { future .catch_unwind() - .unwrap_or_else(|panic| Err(SignalFfiError::UnexpectedPanic(panic))) + .unwrap_or_else(|panic| Err(UnexpectedPanic(panic).into())) } diff --git a/rust/bridge/shared/src/ffi/mod.rs b/rust/bridge/shared/src/ffi/mod.rs index 811f3c51c..b44b7fbe0 100644 --- a/rust/bridge/shared/src/ffi/mod.rs +++ b/rust/bridge/shared/src/ffi/mod.rs @@ -27,6 +27,8 @@ pub use io::*; mod storage; pub use storage::*; +use crate::support::describe_panic; + #[derive(Debug)] pub struct NullPointerError; @@ -190,6 +192,16 @@ pub struct FfiResponseAndDebugInfo { debug_info: FfiChatServiceDebugInfo, } +struct UnexpectedPanic(Box); + +impl std::fmt::Debug for UnexpectedPanic { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("UnexpectedPanic") + .field(&describe_panic(&self.0)) + .finish() + } +} + #[inline(always)] pub fn run_ffi_safe Result<(), SignalFfiError> + std::panic::UnwindSafe>( f: F, @@ -197,9 +209,11 @@ pub fn run_ffi_safe Result<(), SignalFfiError> + std::panic::Unwi let result = match std::panic::catch_unwind(f) { Ok(Ok(())) => Ok(()), Ok(Err(e)) => Err(e), - Err(r) => Err(SignalFfiError::UnexpectedPanic(r)), + Err(r) => Err(UnexpectedPanic(r).into()), }; + // When ThinBox is stabilized, we can return that instead of double-boxing. + // (Unfortunately, Box is two pointers wide and not FFI-safe.) match result { Ok(()) => std::ptr::null_mut(), Err(e) => Box::into_raw(Box::new(e)), @@ -208,7 +222,7 @@ pub fn run_ffi_safe Result<(), SignalFfiError> + std::panic::Unwi pub unsafe fn native_handle_cast(handle: *const T) -> Result<&'static T, SignalFfiError> { if handle.is_null() { - return Err(SignalFfiError::NullPointer); + return Err(NullPointerError.into()); } Ok(&*(handle)) @@ -216,7 +230,7 @@ pub unsafe fn native_handle_cast(handle: *const T) -> Result<&'static T, Sign pub unsafe fn native_handle_cast_mut(handle: *mut T) -> Result<&'static mut T, SignalFfiError> { if handle.is_null() { - return Err(SignalFfiError::NullPointer); + return Err(NullPointerError.into()); } Ok(&mut *handle) @@ -227,7 +241,7 @@ pub unsafe fn write_result_to( value: T, ) -> SignalFfiResult<()> { if ptr.is_null() { - return Err(SignalFfiError::NullPointer); + return Err(NullPointerError.into()); } *ptr = value.convert_into()?; Ok(()) diff --git a/swift/Sources/SignalFfi/signal_ffi.h b/swift/Sources/SignalFfi/signal_ffi.h index b61ba613f..bd99f607e 100644 --- a/swift/Sources/SignalFfi/signal_ffi.h +++ b/swift/Sources/SignalFfi/signal_ffi.h @@ -292,6 +292,11 @@ typedef struct SignalSgxClientState SignalSgxClientState; /** * The top-level error type (opaquely) returned to C clients when something goes wrong. + * + * Ideally this would use [ThinBox][], and then we wouldn't need an extra level of indirection when + * returning it to C, but unfortunately that isn't stable yet. + * + * [ThinBox]: https://doc.rust-lang.org/std/boxed/struct.ThinBox.html */ typedef struct SignalFfiError SignalFfiError;