swift: Make FingerprintMismatch error more useful

This commit is contained in:
moiseev-signal
2025-06-25 15:20:04 -07:00
committed by GitHub
parent f395e1cb7d
commit d0425f2d3f
10 changed files with 110 additions and 36 deletions

1
Cargo.lock generated
View File

@@ -2494,6 +2494,7 @@ dependencies = [
"libsignal-protocol",
"log",
"log-panics",
"paste",
"signal-media",
"zerocopy",
]

View File

@@ -1,3 +1,5 @@
v0.76.1
- Making a chat connection now accepts a locale (Java) or a list of language codes (Swift, TypeScript), which will set the default language to be used for any requests on that connection if provided.
- swift: Fingerpint mismatch error now contains both versions

View File

@@ -34,6 +34,7 @@ signal-media = { workspace = true }
hex = { workspace = true }
log = { workspace = true }
log-panics = { workspace = true, features = ["with-backtrace"] }
paste = { workspace = true }
zerocopy = { workspace = true }
[target.aarch64-apple-ios.dependencies]

View File

@@ -15,6 +15,7 @@ use libsignal_bridge::ffi::*;
use libsignal_bridge_testing::*;
use libsignal_core::try_scoped;
use libsignal_protocol::*;
use paste::paste;
pub mod logging;
@@ -149,40 +150,6 @@ pub unsafe extern "C" fn signal_error_get_invalid_protocol_address(
})
}
#[no_mangle]
pub unsafe extern "C" fn signal_error_get_retry_after_seconds(
err: *const SignalFfiError,
out: *mut u32,
) -> *mut SignalFfiError {
let err = AssertUnwindSafe(err);
run_ffi_safe(|| {
let err = err.as_ref().ok_or(NullPointerError)?;
let value = err.provide_retry_after_seconds().map_err(|_| {
SignalProtocolError::InvalidArgument(format!(
"cannot get retry_after_seconds from error ({err})"
))
})?;
write_result_to(out, value)
})
}
#[no_mangle]
pub unsafe extern "C" fn signal_error_get_tries_remaining(
err: *const SignalFfiError,
out: *mut u32,
) -> *mut SignalFfiError {
let err = AssertUnwindSafe(err);
run_ffi_safe(|| {
let err = err.as_ref().ok_or(NullPointerError)?;
let value = err.provide_tries_remaining().map_err(|_| {
SignalProtocolError::InvalidArgument(format!(
"cannot get tries_remaining from error ({err})"
))
})?;
write_result_to(out, value)
})
}
#[no_mangle]
pub unsafe extern "C" fn signal_error_get_unknown_fields(
err: *const SignalFfiError,
@@ -258,6 +225,57 @@ pub unsafe extern "C" fn signal_error_get_registration_lock(
})
}
macro_rules! get_named_u32_from_err_impl {
($name:ident) => {
paste! {
#[no_mangle]
pub unsafe extern "C" fn [< signal_error_get_ $name >](
err: *const SignalFfiError,
out: *mut u32,
) -> *mut SignalFfiError {
let err = AssertUnwindSafe(err);
run_ffi_safe(|| {
let err = err.as_ref().ok_or(NullPointerError)?;
let value = err.[< provide_ $name >]().map_err(|_| {
SignalProtocolError::InvalidArgument(format!(
"cannot get $name from error ({err})"
))
})?;
write_result_to(out, value)
})
}
}
};
// Similar to the above, only adds an extra .map(...) step to extract the final u32
($name:ident, $field:ident, $c_name:ident) => {
paste! {
#[no_mangle]
pub unsafe extern "C" fn [< signal_error_get_ $c_name >](
err: *const SignalFfiError,
out: *mut u32,
) -> *mut SignalFfiError {
let err = AssertUnwindSafe(err);
run_ffi_safe(|| {
let err = err.as_ref().ok_or(NullPointerError)?;
let value = err.[< provide_ $name >]()
.map(|x| x.$field)
.map_err(|_| {
SignalProtocolError::InvalidArgument(format!(
"cannot get $name from error ({err})"
))}
)?;
write_result_to(out, value)
})
}
}
};
}
get_named_u32_from_err_impl!(fingerprint_versions, ours, our_fingerprint_version);
get_named_u32_from_err_impl!(fingerprint_versions, theirs, their_fingerprint_version);
get_named_u32_from_err_impl!(retry_after_seconds);
get_named_u32_from_err_impl!(tries_remaining);
#[no_mangle]
pub unsafe extern "C" fn signal_error_get_rate_limit_challenge(
err: *const SignalFfiError,

View File

@@ -328,3 +328,13 @@ async fn TESTING_InputStreamReadIntoZeroLengthSlice(
first.into_iter().chain(remainder).collect()
}
#[bridge_fn(jni = false, node = false)]
fn TESTING_FingerprintVersionMismatchError(
theirs: u32,
ours: u32,
) -> Result<(), SignalProtocolError> {
Err(SignalProtocolError::FingerprintVersionMismatch(
theirs, ours,
))
}

View File

@@ -138,6 +138,11 @@ impl<T: std::any::Any> UpcastAsAny for T {
/// Error returned when asking for an attribute of an error that doesn't support that attribute.
pub struct WrongErrorKind;
pub struct FingerprintVersions {
pub theirs: u32,
pub ours: u32,
}
pub trait FfiError: UpcastAsAny + fmt::Debug + Send + 'static {
fn describe(&self) -> String;
fn code(&self) -> SignalErrorCode;
@@ -171,6 +176,9 @@ pub trait FfiError: UpcastAsAny + fmt::Debug + Send + 'static {
fn provide_rate_limit_challenge(&self) -> Result<&RateLimitChallenge, WrongErrorKind> {
Err(WrongErrorKind)
}
fn provide_fingerprint_versions(&self) -> Result<FingerprintVersions, WrongErrorKind> {
Err(WrongErrorKind)
}
}
/// The top-level error type (opaquely) returned to C clients when something goes wrong.
@@ -306,6 +314,16 @@ impl FfiError for SignalProtocolError {
_ => Err(WrongErrorKind),
}
}
fn provide_fingerprint_versions(&self) -> Result<FingerprintVersions, WrongErrorKind> {
match self {
Self::FingerprintVersionMismatch(theirs, ours) => Ok(FingerprintVersions {
theirs: *theirs,
ours: *ours,
}),
_ => Err(WrongErrorKind),
}
}
}
impl FfiError for DeviceTransferError {

View File

@@ -21,7 +21,7 @@ public enum SignalError: Error {
case invalidKey(String)
case invalidSignature(String)
case invalidAttestationData(String)
case fingerprintVersionMismatch(String)
case fingerprintVersionMismatch(theirs: UInt32, ours: UInt32)
case fingerprintParsingError(String)
case sealedSenderSelfSend(String)
case untrustedIdentity(String)
@@ -135,7 +135,13 @@ internal func checkError(_ error: SignalFfiErrorRef?) throws {
case SignalErrorCodeInvalidAttestationData:
throw SignalError.invalidAttestationData(errStr)
case SignalErrorCodeFingerprintVersionMismatch:
throw SignalError.fingerprintVersionMismatch(errStr)
let theirs = try invokeFnReturningInteger {
signal_error_get_their_fingerprint_version(error, $0)
}
let ours = try invokeFnReturningInteger {
signal_error_get_our_fingerprint_version(error, $0)
}
throw SignalError.fingerprintVersionMismatch(theirs: theirs, ours: ours)
case SignalErrorCodeUntrustedIdentity:
throw SignalError.untrustedIdentity(errStr)
case SignalErrorCodeInvalidKeyIdentifier:

View File

@@ -1594,6 +1594,8 @@ SignalFfiError *signal_error_get_invalid_protocol_address(const SignalFfiError *
SignalFfiError *signal_error_get_message(const SignalFfiError *err, const char **out);
SignalFfiError *signal_error_get_our_fingerprint_version(const SignalFfiError *err, uint32_t *out);
SignalFfiError *signal_error_get_rate_limit_challenge(const SignalFfiError *err, const char **out_token, SignalOwnedBuffer *out_options);
SignalFfiError *signal_error_get_registration_error_not_deliverable(const SignalFfiError *err, const char **out_reason, bool *out_permanent);
@@ -1602,6 +1604,8 @@ SignalFfiError *signal_error_get_registration_lock(const SignalFfiError *err, ui
SignalFfiError *signal_error_get_retry_after_seconds(const SignalFfiError *err, uint32_t *out);
SignalFfiError *signal_error_get_their_fingerprint_version(const SignalFfiError *err, uint32_t *out);
SignalFfiError *signal_error_get_tries_remaining(const SignalFfiError *err, uint32_t *out);
uint32_t signal_error_get_type(const SignalFfiError *err);

View File

@@ -319,6 +319,8 @@ SignalFfiError *signal_testing_fake_chat_server_get_next_remote(SignalCPromiseMu
SignalFfiError *signal_testing_fake_registration_session_create_session(SignalCPromiseMutPointerRegistrationService *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalFfiRegistrationCreateSessionRequest create_session, SignalConstPointerFakeChatServer chat);
SignalFfiError *signal_testing_fingerprint_version_mismatch_error(uint32_t theirs, uint32_t ours);
SignalFfiError *signal_testing_future_cancellation_counter_create(SignalMutPointerTestingFutureCancellationCounter *out, uint8_t initial_value);
SignalFfiError *signal_testing_future_cancellation_counter_destroy(SignalMutPointerTestingFutureCancellationCounter p);

View File

@@ -253,6 +253,18 @@ final class BridgingTests: XCTestCase {
}
XCTAssertEqual(UUID(uuidString: "abababab-1212-8989-baba-565656565656"), shouldBePresent)
}
func testFingerprintVersionMismatchError() throws {
let theirs = UInt32(11)
let ours = UInt32(22)
do {
try checkError(signal_testing_fingerprint_version_mismatch_error(theirs, ours))
XCTFail("should have thrown")
} catch SignalError.fingerprintVersionMismatch(let actualTheirs, let actualOurs) {
XCTAssertEqual(theirs, actualTheirs)
XCTAssertEqual(ours, actualOurs)
}
}
}
#endif