Skip to main content

Android compatibility

Scope and identity​

The Softchat Android application keeps its own event objects, Room storage, outbox, relay protocol owner and UI. Since v0.1 it delegates four things to the SDK: legacy event cryptography, legacy gift-wrap envelopes, one native relay connection, and streaming attachment encryption. This contract covers exactly those operations. The Rust account runtime and its storage are not used by the application and are covered by the other contracts.

Supported behavior​

Legacy event objects​

android.legacy.event-objects — Signing and validation accept the application's Int kinds, Long timestamps and raw tag arrays, including empty arrays and trailing empty fields, and return them unchanged. The portable event limits are not widened. IDs and BIP-340 signatures are always verified; a structurally valid event with a wrong ID or signature is rejected.

Legacy envelopes​

android.legacy.envelope — Wrapping checks that the rumor's ID and author match this identity before encryption, keeps the caller's timestamp and alert policy, and uses fresh wrapper keys and nonces every time. Reading authenticates the outer event and the seal before any plaintext is returned, accepts only the legacy two-byte NIP-44 length prefix, and leaves recipient selection to the caller. A forged rumor cannot hide behind a valid envelope.

Native connection lifecycle​

android.native-connection.lifecycle — The Android compatibility factory completes a send when its bounded queue accepts the frame and then owns that frame. Input keeps flowing while a write is blocked. A send cancelled while queued is never written; cancelling an active write discards the connection. A full receive queue does not block sends or shutdown, and shutdown ends a stalled TLS handshake. A plain-WS peer reset ends input without inventing a failure, so the application's heartbeat and retry policy decide what happens. Secure WebSockets use bounded ten-second output windows instead of a whole-message deadline.

Platform trust​

android.native-connection.platform-trust — Secure connections verify the certificate chain with Android's hostname-aware trust manager, including the application's base and domain network-security policy, while Rustls verifies the hostname and handshake signatures.

Attachment streams​

android.attachment.stream — Streaming encryption and decryption keep the released combined format (nonce, ciphertext, tag), accept an imported key without exporting it again, support empty files and cancellation, and authenticate before the caller may publish decrypted output.

Persistence semantics​

None. These operations open no database and keep no account state. The Android application owns Room, the outbox, retries and staging files; see the application's incremental Rust client documentation.

Observable behavior​

Each operation returns a value or a typed, redacted failure. A successful send is queue acceptance, never relay acceptance; the application waits for its own protocol acknowledgement.

Verification and limits​

Rust host tests and the Kotlin facade tests on the JVM run in CI for every change. Platform trust runs only on an Android device through the packed consumer application. The application's own device suites, including real Softrelay exchanges, are recorded in the Android repository. Physical network changes, Doze and production relay delivery are not established by this contract. Implementation entry points are in compat/android/events.rs and transport/connection.rs.

No execution report is bundled with this documentation. Linked test cases describe verification scope; their existence is not a passing result.

InvariantRequired environmentsExecution evidence
android.legacy.event-objects
rust-core
rust-host, android-jvmmissing
6 mapped test cases
  • compat::android::events::tests::android_field_ranges_round_trip_without_narrowing — semantic; sdk, cargo:softchat
  • compat::android::events::tests::independent_signed_fixture_authenticates_without_rewriting_raw_tags — semantic; sdk, cargo:softchat
  • compat::android::events::tests::legacy_json_retains_android_size_acceptance_without_widening_portable_events — semantic; sdk, cargo:softchat
  • compat::android::events::tests::signatures_ids_and_structural_fields_still_authenticate — semantic; sdk, cargo:softchat
  • com.softcose.softchat.sdk.EnvelopeCompatibilityTest#androidObjectCompatibilityRetainsWideFieldsWithoutWeakeningAuthentication — facade; sdk, gradle:library:test
  • com.softcose.softchat.sdk.EnvelopeCompatibilityTest#largeLegacySignedJsonCrossesTheFacadeWithoutChangingPortableLimits — facade; sdk, gradle:library:test
android.legacy.envelope
rust-core
rust-host, android-jvmmissing
7 mapped test cases
  • compat::android::events::tests::authenticated_envelopes_cannot_hide_forged_rumors_or_invalid_seals — semantic; sdk, cargo:softchat
  • compat::android::events::tests::wrapping_keeps_fresh_keys_and_rejects_foreign_or_oversized_rumors — semantic; sdk, cargo:softchat
  • protocol::nip59::tests::legacy_reader_accepts_maximum_legacy_seal_and_rejects_extended_seal — semantic; sdk, cargo:softchat
  • protocol::nip59::tests::legacy_reader_leaves_route_selection_to_the_android_caller — semantic; sdk, cargo:softchat
  • explicit_envelope_time_preserves_rumor_route_alert_and_fresh_keys — semantic; sdk, cargo:softchat
  • com.softcose.softchat.sdk.EnvelopeCompatibilityTest#legacyReaderRetainsCallerRoutingThroughTheFacade — facade; sdk, gradle:library:test
  • com.softcose.softchat.sdk.EnvelopeCompatibilityTest#explicitTimeAndAlertSurviveTheAuthoredFacade — facade; sdk, gradle:library:test
Shared requirements
android.native-connection.lifecycle
rust-core
rust-host, android-jvmmissing
11 mapped test cases
  • transport::connection::tests::legacy_send_transfers_queue_ownership_before_upgrade — semantic; sdk, cargo:softchat
  • transport::connection::tests::queued_cancelled_send_is_never_written — semantic; sdk, cargo:softchat
  • transport::connection::tests::cancelling_a_backpressured_write_discards_the_connection — semantic; sdk, cargo:softchat
  • transport::connection::tests::backpressured_legacy_write_preserves_input_without_a_native_deadline — semantic; sdk, cargo:softchat
  • transport::connection::tests::full_receive_queue_does_not_block_sends_or_shutdown — semantic; sdk, cargo:softchat
  • transport::connection::tests::shutdown_interrupts_a_stalled_tls_handshake_and_drops_the_tcp_stream — semantic; sdk, cargo:softchat
  • transport::socket::tests::cio_peer_reset_ends_legacy_input_without_changing_other_profiles — semantic; sdk, cargo:softchat
  • transport::write_timeout::tests::complete_windows_allow_a_message_longer_than_fifteen_seconds — semantic; sdk, cargo:softchat
  • com.softcose.softchat.sdk.NativeRelayConnectionTest#androidLegacyPeerResetEndsInputWhileTheOrdinaryFacadeReportsFailure — facade; sdk, gradle:library:test
  • com.softcose.softchat.sdk.NativeRelayConnectionTest#legacySendTransfersOwnershipToTheQueueBeforeUpgrade — facade; sdk, gradle:library:test
  • com.softcose.softchat.sdk.NativeRelayConnectionTest#asyncFacadeClosesAStalledTlsHandshakeWithoutACallerTokioRuntime — facade; sdk, gradle:library:test
android.native-connection.platform-trust
platform-adapter
android-devicemissing
2 mapped test cases
  • com.softcose.softchat.consumer.AndroidTrustTest#chainTrustAndHostnameMatchTheApplicationPolicy — integration; sdk, instrumentation:com.softcose.softchat.consumer
  • com.softcose.softchat.consumer.AndroidDomainPolicyTest#domainInheritanceAndExplicitDenialMatchThePlatformChainPolicy — integration; sdk, instrumentation:com.softcose.softchat.consumer
android.attachment.stream
rust-core
rust-host, android-jvmmissing
4 mapped test cases
  • messaging::media::tests::streaming_attachment_crypto_matches_released_combined_format — semantic; sdk, cargo:softchat
  • messaging::media::tests::streaming_attachment_crypto_supports_empty_files_and_cancellation — semantic; sdk, cargo:softchat
  • messaging::media::tests::random_stream_descriptor_round_trips_without_exporting_raw_key — semantic; sdk, cargo:softchat
  • com.softcose.softchat.sdk.AttachmentKeyCompatibilityTest#importedKeyRetainsTheExistingCombinedFormat — facade; sdk, gradle:library:test