Skip to content

Port Mob.VendorUsb Android C bridges to Zig (rebase mob#5 + mob_new#2) #6

Description

@GenericJam

What

Bring @HeroesLament's Mob.VendorUsb PRs forward to current master by porting the Android JNI bridges from C to Zig.

  • Runtime PR: mob#5 — Elixir API + iOS stubs + Android NIFs (these need porting)
  • Templates PR: mob_new#2 — Kotlin/JNI codegen (mostly clean — see below)

Why this exists

Both PRs were opened against master at b4471290… on 2026-05-09. Shortly after, commit b091e67 (Phase 6b iter 3d (mob side): finale — delete mob_nif.c, all-Zig NIF surface) deleted android/jni/mob_nif.c entirely and migrated the Android NIF surface to Zig. The PR's 241 lines of C in mob_nif.c therefore cannot be applied as written — that file no longer exists.

The Elixir/iOS/Erlang sides of the PR are unaffected by the migration and only have textual conflicts.

Scope of the port

1. Android C → Zig (the actual port)

The seven NIFs added in android/jni/mob_nif.c need to be added to android/jni/mob_nif.zig instead. They are:

vendor_usb_list_devices/1
vendor_usb_request_permission/1
vendor_usb_open/1
vendor_usb_bulk_write/3
vendor_usb_start_reading/2
vendor_usb_stop_reading/1
vendor_usb_close/1

Each NIF dispatches to a @JvmStatic method on MobBridge (Kotlin side, no port needed — that's in mob_new#2). The seven mob_send_vendor_usb_* delivery functions and the jmethodID cache slots also need their Zig equivalents.

2. The patterns to follow

android/jni/mob_nif.zig already has 30+ NIFs in this shape. Pick any one (e.g. webview_load_url, camera_start) and mirror it:

pub fn vendor_usb_open(
    env: ?*erts.ErlNifEnv,
    _argc: c_int,
    argv: [*c]const erts.ERL_NIF_TERM,
) callconv(.c) erts.ERL_NIF_TERM {
    _ = _argc;
    // 1. Extract args from argv via erts.enif_get_*
    // 2. Acquire JNIEnv via mob_zig.jni.AttachCurrentThread (see existing helpers)
    // 3. Call the MobBridge.vendor_usb_open method via cached jmethodID
    // 4. Return enif_make_atom(env, \"ok\") or an error term
}

Useful neighbors:

  • mob_zig.zig — JNI bindings, libc, jmethodID cache (Cache struct + getOrInit)
  • mob_erts.zig — NIF helpers (makeAtom, makeBinary, getBinary, sendToPid, etc.)
  • mob_beam.zigmob_send_* helpers (the C-side mob_send_vendor_usb_* callbacks the Kotlin reader thread invokes need new Zig siblings here, or wire directly into existing dispatch)

The PR's C code uses jmethodID caching at android/jni/mob_beam.h — that file is still C and may need to be touched, OR the cache slots can move into mob_zig.zig's Cache struct. Pick whichever matches the existing pattern most cleanly.

3. The driver_tab registration

Each NIF needs to land in the static driver table. The Zig NIFs in mob_nif.zig are registered via the generated driver_tab_android.zig (see MobDev.StaticNifs in mob_dev — the registration happens via mob.exs :static_nifs). For built-in mob NIFs (not user project NIFs), there's a direct registration in mob_nif.zig's NIF table. Follow the pattern for the 30+ existing NIFs.

4. Clean-merge parts of mob#5 (no porting needed)

These files have only textual conflicts and can be brought over after the Zig port lands:

File What it adds Conflict shape
lib/mob/vendor_usb.ex Whole new module, 333 lines None (new file)
test/mob/vendor_usb_test.exs 10 tests None (new file)
lib/mob/screen.ex Adds one handle_info({:peripheral, :vendor_usb, ...}) clause that routes through Mob.VendorUsb.normalize_message/1 Trivial; new clause slots into the message routing pipeline before {:mob, :back}
src/mob_nif.erl Adds 7 entries to -export([...]) and -nifs([...]) Trivial textual — current master's lists differ by surrounding NIFs but the additions don't collide
ios/mob_nif.m Appends 60 lines of unsupported-stubs at end of file Trivial — likely appends after current end-of-file content
android/jni/mob_beam.h 13 lines of extern decls Needs review against current mob_beam.h — may be obsolete if delivery moved to Zig

5. mob_new#2 (templates) — mostly clean

The Kotlin code in MobBridge.kt.eex and the manifest changes are unaffected by the Zig migration (that was a runtime-side change; templates still emit beam_jni.c.eex for downstream projects' JNI bridges). Expect textual conflicts only.

The beam_jni.c.eex change (+58 lines of native trampolines) lands as-is because downstream-project glue is still C in the generator.

Done definition

  • mob#5 rebased onto current master; 7 NIFs implemented in android/jni/mob_nif.zig
  • mix mob.deploy --native --device <android-arm64> succeeds for a project that uses Mob.VendorUsb
  • The Mob.VendorUsb lifecycle (list_devices → request_permission → open → start_reading + bulk_write → stop_reading → close) works end-to-end on a real vendor-class device. The original contributor's test rig is AtomVM ESP32 + Taixin TX-AH HaLow modem over a OnePlus CPH2451; any vendor-class USB device with bulk IN/OUT endpoints should exercise the same code path.
  • mob_new#2 rebased onto current master and merges cleanly
  • lib/mob/screen.ex :peripheral routing exercised by a screen that handles a {:peripheral, :vendor_usb, :data, session, binary} event

Hardware caveat

The original test rig (HaLow modem + ESP32) is non-trivial to reproduce. If the agent doing this port doesn't have a vendor-class USB device on hand, stop after the rebased PR builds + tests + deploys without crashing, and ping @HeroesLament to verify the end-to-end lifecycle on their device. Their PR description is explicit about the bug classes they hit (nil → JSON "nil" string, getInt JSONException → BEAM crash); those are not catchable without the real hardware in the loop.

References

  • Migration commit: `b091e67` ("Phase 6b iter 3d (mob side): finale — delete mob_nif.c, all-Zig NIF surface")
  • Zig NIF entry point pattern: `android/jni/mob_nif.zig` lines 56, 71, 90, 306, 400, 425, 482, 492, 503, 520 (each is one NIF)
  • JNI / libc / Android FFI: `android/jni/mob_zig.zig`
  • Static NIF table generation: `MobDev.StaticNifs` in `mob_dev/lib/mob_dev/static_nifs.ex`

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions