tailscale / tailscale/tailscale

Taildrop (Android) truncates files when the ContentResolver-reported size is smaller than the real stream (Samsung Voice Recorder .m4a)

Open
#20,585 0 comments 0 reactions 0 assignees View on GitHub
OS-android taildrop
Dominant language
Go
Stars
36.5k
Forks
3.2k
Avg merge
1d 23h
Merged PRs (30d)
132

Description

### Summary

Sending a Samsung Voice Recorder `.m4a` via Taildrop from Android delivers a file that is short by a fixed number of bytes, cutting the trailing `moov` atom so the file won't open in any standard player. The audio data (`mdat`) always arrives complete — only the tail (the index) is lost, which is why the sender also reports an error near the end of the transfer.

### Environment

- **Sender:** Tailscale Android **1.98.2**, Samsung Galaxy S24, One UI 8.5, Samsung Voice Recorder 2.1.5.86.30
- **Receiver:** `tailscaled` **1.98.9** on macOS (Homebrew), drained with `tailscale file get`

### Root cause (evidence)

The number of bytes dropped equals the size of Samsung's proprietary `ampl` atom (the Voice Recorder waveform data) **+ 400**, measured independently on two different recordings:

| recording | `ampl` atom size | bytes missing | difference |
|-----------|------------------|---------------|------------|
| A | 137,923 | 138,323 | 400 |
| B | 137,487 | 137,887 | 400 |

So the phone advertises `Content-Length = actualSize - ampl - 400` for these files, and Taildrop sends exactly that many bytes and stops. Because `moov` sits at the end of the file, the index is lost and the recording is unplayable outside the Voice Recorder app (which keeps its own index).

**Confirmation:** renaming the file `.m4a` → `.bin` (so Android types it `application/octet-stream` instead of `audio/mp4`) makes the reported size correct — the transfer then completes byte-for-byte (full file, ~20 min, decodes with zero errors). Same file, same network, same moment; only the advertised size differs.

### Steps to reproduce

1. Record ~20 min in Samsung Voice Recorder on a Galaxy S24.
2. Share the `.m4a` to a peer via the Tailscale Android app.
3. The received file is ~138 KB short and won't play (`moov atom not found`).
4. Rename the source `.m4a` → `.bin` and resend → the file arrives complete.

### Suggested fix

When sending from a `content://` URI, stream to EOF rather than trusting the queried `OpenableColumns.SIZE` / `Content-Length`. At minimum, verify that bytes read == advertised size and surface a mismatch instead of silently sending a truncated file.

This lives in the Android SAF file-send path touched by #15545 and #16316.

Contributor guide

Open the contributing guide

Research direction

Start at the Android SAF file-send path touched by issues #15545 and #16316, then reproduce with a Samsung Voice Recorder .m4a supplied through a content:// URI. Done means the received file matches the source through EOF, including the trailing moov atom, or a size mismatch is surfaced instead of silently truncating the transfer.

Written by the indexing model from the issue text.

Assessment

Tech stack
android
Domain
mobile-dev
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
65/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.