Here's the cleaned-up bug report, ready to file:
[BUG] Encrypted attachment sending fails on physical iOS devices with RoomError.InvalidAttachmentData, but works reliably in the Simulator
Environment
@unomed/react-native-matrix-sdk: 0.9.1 (latest published)
- React Native 0.81.5, Expo SDK ~54.0.34
- Physical devices (both affected): iPhone 15 Pro Max, iPhone XR — iOS 26.5.2
- Simulator: works correctly — same SDK version, same app code
- Homeserver: Synapse 1.156.0, MSC4186 confirmed working,
max_upload_size confirmed 50 MB
Summary
Every call to timeline.sendFile() / timeline.sendVoiceMessage() inside a genuinely encrypted room throws RoomError.InvalidAttachmentData on real devices. Same SDK, same room type — works reliably in the Simulator (3 consecutive clean passes). Text messages work correctly on physical devices in the same rooms, so this is specific to attachments, not encryption or connectivity.
Reproduction
Create an E2EE room, confirm membership, then attempt to send a file, video, or voice message. Fails every time on device (2 devices tested), succeeds every time in the Simulator. Reproduced across multiple fresh identity pairs, multiple formats (HEIC/JPEG/PNG, MP4/MOV, M4A), and a range of file sizes.
Ruled out, with evidence
- File validity/existence — confirmed present and correctly sized at call time.
- Missing/incorrect metadata — tried both omitting and supplying metadata explicitly; same failure.
- Upload mechanism — both
UploadSource.File and UploadSource.Data (raw bytes) fail identically.
- Server upload limit — the SDK's own
max_upload_size pre-flight check passes every time (returns 50 MB, matching our real server limit).
- Rust-level tracing — run at maximum verbosity, the SDK's own tracer shows the text send path working clearly, but produces zero output anywhere for the attachment code path.
- Physical-device stackshots — captured with a symbolicated build (first time we achieved real function name resolution). Every SDK runtime thread was idle at capture time; the failure resolves too quickly to catch reliably with a manually-timed snapshot. Flagged honestly — this is not evidence either way.
Conclusion
Given that maximum-verbosity tracing shows nothing for this failure while the identical infrastructure clearly works for text, the rejection most likely occurs in the FFI/native binding glue layer itself — below what the SDK's Rust-side tracing can see into from JS/TypeScript.
Questions for maintainers
- Is there a known Simulator-vs-device divergence in the attachment FFI path?
- Is any lower-level Swift-side logging available for the binding layer?
- Can
InvalidAttachmentData ever be thrown directly from the binding layer, before reaching Rust? This seems the most likely explanation given the evidence above.
Here's the cleaned-up bug report, ready to file:
[BUG] Encrypted attachment sending fails on physical iOS devices with
RoomError.InvalidAttachmentData, but works reliably in the SimulatorEnvironment
@unomed/react-native-matrix-sdk: 0.9.1 (latest published)max_upload_sizeconfirmed 50 MBSummary
Every call to
timeline.sendFile()/timeline.sendVoiceMessage()inside a genuinely encrypted room throwsRoomError.InvalidAttachmentDataon real devices. Same SDK, same room type — works reliably in the Simulator (3 consecutive clean passes). Text messages work correctly on physical devices in the same rooms, so this is specific to attachments, not encryption or connectivity.Reproduction
Create an E2EE room, confirm membership, then attempt to send a file, video, or voice message. Fails every time on device (2 devices tested), succeeds every time in the Simulator. Reproduced across multiple fresh identity pairs, multiple formats (HEIC/JPEG/PNG, MP4/MOV, M4A), and a range of file sizes.
Ruled out, with evidence
UploadSource.FileandUploadSource.Data(raw bytes) fail identically.max_upload_sizepre-flight check passes every time (returns 50 MB, matching our real server limit).Conclusion
Given that maximum-verbosity tracing shows nothing for this failure while the identical infrastructure clearly works for text, the rejection most likely occurs in the FFI/native binding glue layer itself — below what the SDK's Rust-side tracing can see into from JS/TypeScript.
Questions for maintainers
InvalidAttachmentDataever be thrown directly from the binding layer, before reaching Rust? This seems the most likely explanation given the evidence above.