bubble-shareshowcase

Bubble Share, taken apart: how a FOSS Wi-Fi Aware sharing app for Android works, in demos you can poke at.

codeberg.org/s1m/bubble-share

What Bubble Share is

Bubble Share is a small Android app by Simon Gougeon (s1m, of UnifiedPush) that shares files, text and links to the phone next to you. It is an alternative to AirDrop, Quick Share and Nearby Share built on Wi-Fi Aware: no internet, no shared network, no QR code. You pick a device from the share sheet and both phones show the same six-digit pairing code.

This page is an unofficial showcase. Every byte it displays comes from a JavaScript re-implementation of the app's protocol pieces (protocol.mjs), checked against RFC and library test vectors. Nothing leaves your browser.

Demo · Two phones, one share

Pick a scenario and step through a real transfer. The bytes in the log are generated live with the same encodings the app uses.

Sender
Receiver

    How a share works

    1Advertise. The receiver's app publish()es the Wi-Fi Aware service fr.s1m.share with a CBOR blob: device name, X25519 public key, and whether it accepts files. The sender's share sheet subscribe()s, and also publishes an informative copy with r = false so the receiver's "Nearby devices" list shows the sender too. 2Connect. Tapping a peer sends one Wi-Fi Aware L2 message (≤255 bytes): Connect{name, nonce, pubKey, kind, nFiles}. Both sides compute HMAC-SHA256(X25519 shared secret, "NONCE:" ‖ nonce) and derive the Wi-Fi passphrase and the pairing code from it. 3Consent. Each phone shows the same six digits in an Accept / Refuse dialog with a 30 s timeout. Nothing else is compared, so the code is the whole anti-MITM story: if the digits differ, somebody is in the middle. 4Data path. The receiver opens a ServerSocket, requests a TRANSPORT_WIFI_AWARE network with WifiAwareNetworkSpecifier(peer).setPskPassphrase(psk).setPort(port), and echoes the Connect. The sender requests the matching network, reads the peer's IPv6 and port from WifiAwareNetworkInfo, and opens one TCP socket per file. The receiver only accepts that IPv6. 5Stream. Each socket carries one frame: FILE_V1 (opcode, name, 64-bit size, bytes) or TEXT_V1. Files land in Downloads/BubbleShare through MediaStore; a length mismatch deletes the partial file. URLs open an app picker, other text opens the system share sheet.

    Lab · The six-digit code

    Two throwaway identities, a fresh nonce, and the exact key schedule from Crypto.kt, computed here with X25519 and HMAC-SHA256. Both columns must agree.

    Device id = "%08x".format(pubKey.contentHashCode()) over the 44-byte X.509 encoding of the public key. Pairing code = |Arrays.hashCode(HMAC(msk, "PAIRING_CODE"))| mod 10⁶. The Wi-Fi passphrase is Base64url(HMAC(msk, "WIFI_PSK")); TLS_KEY_ID and TLS_PSK feed the PSK-TLS session used over Wi-Fi Direct.

    Lab · Bytes on the wire

    What the sender actually writes. Type some text, or pick a local file to see its FILE_V1 header (only the name and size are read, nothing is uploaded).

    The discovery blobs

    kotlinx.serialization CBOR with its defaults: indefinite-length maps, byte arrays as arrays of signed integers, default-valued fields omitted. The Connect message must fit a Wi-Fi Aware message (255 bytes), which caps how long a device name can be.

    Lab · The NFC tap

    While the share sheet is open, the sender emulates an NFC Type-4 tag (HostApduService). A reader sees two applications on it.

    Three transports, one handshake

    TransportPeers found byPairingEncryptionNotes
    Wi-Fi AwareNAN publish / subscribe, no AP needed6-digit code, Accept on bothNAN data path with a per-share PSKDefault. Hardware-dependent (FEATURE_WIFI_AWARE); unavailable while a hotspot is on. The author flags it as the path to iOS support.
    Wi-Fi DirectDNS-SD over P2P (_share._tcp, instance <id>.share.s1m.fr)Code after an NFC handshakePSK-TLS (Bouncy Castle) with keys from the same scheduleFallback only: not preselected, chosen when an NFC tap says the other side lacks Aware. Discovery socket exchanges Connect for a port.
    NFC tapPhysical contactImplicit (the tap), code still shownNegotiates nonce and keys, then hands offProprietary AID F0 "s1m.share.1": transport selection, then Connect. A plain Type-4 NDEF URI is also served so any phone can take a link.

    Requirements and caveats

    Compared with the usual suspects

    Source and credits

    Bubble Share is © 2026 Simon Gougeon, AGPL-3.0, source on Codeberg with translations on Weblate. Screens in the simulator are sketches, not screenshots. The protocol module behind these demos lives in this site's repo at projects/bubble-share; run node projects/bubble-share/tests/run.mjs for the test suite.

    ← apps.andymolenda.com