Packages

iOS device USB interface discovery for Elixir

Current section

Files

Jump to
ifone lib ifone.ex
Raw

lib/ifone.ex

defmodule Ifone do
@moduledoc """
iOS device USB interface discovery.
This module provides functions to discover NCM (Network Control Model) data
interfaces on connected iOS devices, and to look up USB device information
from network interface names.
## Usage
List NCM data interfaces for a device by serial number:
iex> Ifone.list_ncm_data_interfaces("00008101000000000000001E")
{:ok, %{configuration: 5, interfaces: %{3 => "en16", 5 => "en17"}}}
Or by USB location ID:
iex> Ifone.list_ncm_data_interfaces(0x02100000)
{:ok, %{configuration: 5, interfaces: %{3 => "en16", 5 => "en17"}}}
Enumerate every connected device at once:
iex> Ifone.list_devices()
{:ok, %{"00008101000000000000001E" => %{configuration: 5, interfaces: %{3 => "en16", 5 => "en17"}}}}
Look up which device a network interface belongs to:
iex> Ifone.get_serial_for_interface("en16")
{:ok, "00008101000000000000001E"}
## Platform Support
- **macOS**: Uses IOKit via a NIF
- **Linux**: Uses sysfs traversal (pure Elixir)
Note: On Linux, USB location ID lookup is not supported — use serial numbers instead.
"""
@typedoc """
Result of listing NCM data interfaces.
- `configuration` - The current USB configuration number
- `interfaces` - Map of USB interface number to network interface name (or nil if not yet bound)
"""
@type interfaces_result :: %{
configuration: non_neg_integer(),
interfaces: %{non_neg_integer() => String.t() | nil}
}
@doc """
Lists NCM data interfaces for a USB device.
Returns the current USB configuration and a map of NCM interfaces that belong
to the specified device. Each interface is mapped to its corresponding network
interface name (e.g., "en16" on macOS or "enx8e7aaa54ae75" on Linux), or `nil`
if the interface exists but has no network interface bound yet.
## Arguments
- `identifier` - Either a USB serial number (string) or USB location ID (integer)
## Returns
- `{:ok, %{configuration: N, interfaces: %{...}}}` - Map with configuration and interfaces
- `{:error, :not_found}` - Device not found
- `{:error, reason}` - Other error
## Platform Differences
The interface numbers in the returned map differ by platform. On macOS, network
interfaces are reported on the CDC-Data interface (e.g., interface 3). On Linux,
they are reported on the CDC-Control interface (e.g., interface 2). To write
cross-platform code, check for both:
case Ifone.list_ncm_data_interfaces("00008101000000000000001E") do
{:ok, %{configuration: 5, interfaces: interfaces}} ->
ifname = interfaces[2] || interfaces[3]
# ...
{:error, reason} ->
# ...
end
## Examples
# macOS — keyed by CDC-Data interface number
iex> Ifone.list_ncm_data_interfaces("00008101000000000000001E")
{:ok, %{configuration: 5, interfaces: %{3 => "en16", 5 => "en17"}}}
# Linux — keyed by CDC-Control interface number
iex> Ifone.list_ncm_data_interfaces("00008101000000000000001E")
{:ok, %{configuration: 5, interfaces: %{2 => "enx8e7aaa54ae75", 4 => "enxb60457655ed3"}}}
iex> Ifone.list_ncm_data_interfaces(0x02100000)
{:ok, %{configuration: 5, interfaces: %{3 => "en16", 5 => "en17"}}}
iex> Ifone.list_ncm_data_interfaces("nonexistent")
{:error, :not_found}
"""
@spec list_ncm_data_interfaces(String.t() | non_neg_integer()) ::
{:ok, interfaces_result()} | {:error, atom()}
defdelegate list_ncm_data_interfaces(identifier), to: Ifone.USB
@doc """
Lists all connected iOS devices and their NCM interfaces in one call.
Returns a map keyed by USB serial number, where each value has the same shape
as `list_ncm_data_interfaces/1` — the current USB configuration and a map of
NCM interface numbers to network interface names. This is the discovery
counterpart of `list_ncm_data_interfaces/1`: it tells you which devices are
present without needing to know a serial number up front.
Only iOS devices are listed. On platforms where no device can be enumerated,
returns `{:ok, %{}}`.
## Returns
- `{:ok, %{serial => %{configuration: N, interfaces: %{...}}}}` - Connected devices
- `{:error, reason}` - Error
## Examples
# macOS — interfaces keyed by CDC-Data interface number
iex> Ifone.list_devices()
{:ok, %{"00008101000000000000001E" => %{configuration: 5, interfaces: %{3 => "en16", 5 => "en17"}}}}
# Linux — interfaces keyed by CDC-Control interface number
iex> Ifone.list_devices()
{:ok, %{"00008101000000000000001E" => %{configuration: 5, interfaces: %{2 => "enx8e7aaa54ae75", 4 => "enxb60457655ed3"}}}}
iex> Ifone.list_devices()
{:ok, %{}}
"""
@spec list_devices() :: {:ok, %{String.t() => interfaces_result()}} | {:error, atom()}
defdelegate list_devices(), to: Ifone.USB
@doc """
Gets the USB serial number for a network interface.
Given a network interface name (e.g., "en16"), returns the USB serial number
of the iOS device that owns that interface. This is useful for correlating
network interfaces back to specific devices.
## Arguments
- `interface_name` - Network interface name (e.g., "en16")
## Returns
- `{:ok, serial}` - The USB serial number of the device
- `{:error, :not_found}` - Interface not found or not a USB NCM interface
## Examples
iex> Ifone.get_serial_for_interface("en16")
{:ok, "00008101000000000000001E"}
iex> Ifone.get_serial_for_interface("lo0")
{:error, :not_found}
"""
@spec get_serial_for_interface(String.t()) :: {:ok, String.t()} | {:error, atom()}
defdelegate get_serial_for_interface(interface_name), to: Ifone.USB
end