Skip to content

Commit

Permalink
Merge pull request #80 from MerchGuardian/android-support
Browse files Browse the repository at this point in the history
Android support for non-rooted phones
  • Loading branch information
kevinmehall authored Sep 21, 2024
2 parents 3ec3508 + fd62469 commit d6ec80a
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 24 deletions.
15 changes: 14 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,24 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Install toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
override: true
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

build_android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: 'aarch64-linux-android, armv7-linux-androideabi'
- name: build
run: |
cargo build --target aarch64-linux-android --all-features
cargo build --target armv7-linux-androideabi --all-features
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ slab = "0.4.9"
env_logger = "0.10.0"
futures-lite = "1.13.0"

[target.'cfg(target_os="linux")'.dependencies]
[target.'cfg(any(target_os="linux", target_os="android"))'.dependencies]
rustix = { version = "0.38.17", features = ["fs", "event", "net"] }
libc = "0.2.155"

Expand Down
22 changes: 14 additions & 8 deletions src/device.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
use std::{io::ErrorKind, sync::Arc, time::Duration};

use log::error;

use crate::{
descriptors::{
decode_string_descriptor, validate_string_descriptor, ActiveConfigurationError,
Expand All @@ -14,6 +10,8 @@ use crate::{
},
DeviceInfo, Error,
};
use log::error;
use std::{io::ErrorKind, sync::Arc, time::Duration};

/// An opened USB device.
///
Expand Down Expand Up @@ -46,6 +44,14 @@ impl Device {
Ok(Device { backend })
}

/// Wraps a device that is already open.
#[cfg(any(target_os = "android", target_os = "linux"))]
pub fn from_fd(fd: std::os::fd::OwnedFd) -> Result<Device, Error> {
Ok(Device {
backend: platform::Device::from_fd(fd)?,
})
}

/// Open an interface of the device and claim it for exclusive use.
pub fn claim_interface(&self, interface: u8) -> Result<Interface, Error> {
let backend = self.backend.claim_interface(interface)?;
Expand Down Expand Up @@ -242,7 +248,7 @@ impl Device {
/// and use the interface handle to submit transfers.
/// * On Linux, this takes a device-wide lock, so if you have multiple threads, you
/// are better off using the async methods.
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))]
pub fn control_in_blocking(
&self,
control: Control,
Expand All @@ -260,7 +266,7 @@ impl Device {
/// and use the interface handle to submit transfers.
/// * On Linux, this takes a device-wide lock, so if you have multiple threads, you
/// are better off using the async methods.
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))]
pub fn control_out_blocking(
&self,
control: Control,
Expand Down Expand Up @@ -296,7 +302,7 @@ impl Device {
///
/// * Not supported on Windows. You must [claim an interface][`Device::claim_interface`]
/// and use the interface handle to submit transfers.
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))]
pub fn control_in(&self, data: ControlIn) -> TransferFuture<ControlIn> {
let mut t = self.backend.make_control_transfer();
t.submit::<ControlIn>(data);
Expand Down Expand Up @@ -329,7 +335,7 @@ impl Device {
///
/// * Not supported on Windows. You must [claim an interface][`Device::claim_interface`]
/// and use the interface handle to submit transfers.
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))]
pub fn control_out(&self, data: ControlOut) -> TransferFuture<ControlOut> {
let mut t = self.backend.make_control_transfer();
t.submit::<ControlOut>(data);
Expand Down
13 changes: 8 additions & 5 deletions src/enumeration.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[cfg(target_os = "windows")]
use std::ffi::{OsStr, OsString};

#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "android"))]
use crate::platform::SysfsPath;

use crate::{Device, Error};
Expand All @@ -22,10 +22,10 @@ pub struct DeviceId(pub(crate) crate::platform::DeviceId);
/// * macOS: `registry_id`, `location_id`
#[derive(Clone)]
pub struct DeviceInfo {
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) path: SysfsPath,

#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) busnum: u8,

#[cfg(target_os = "windows")]
Expand Down Expand Up @@ -83,7 +83,7 @@ impl DeviceInfo {
DeviceId(self.devinst)
}

#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "android"))]
{
DeviceId(crate::platform::DeviceId {
bus: self.busnum,
Expand Down Expand Up @@ -114,7 +114,7 @@ impl DeviceInfo {
/// *(Linux-only)* Bus number.
///
/// On Linux, the `bus_id` is an integer and this provides the value as `u8`.
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn busnum(&self) -> u8 {
self.busnum
}
Expand Down Expand Up @@ -314,6 +314,9 @@ impl std::fmt::Debug for DeviceInfo {
#[cfg(target_os = "linux")]
{
s.field("sysfs_path", &self.path);
}
#[cfg(any(target_os = "linux", target_os = "android"))]
{
s.field("busnum", &self.busnum);
}

Expand Down
112 changes: 107 additions & 5 deletions src/platform/linux_usbfs/device.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::io::{ErrorKind, Seek};
use std::{ffi::c_void, time::Duration};
use std::{
fs::File,
Expand All @@ -24,7 +25,9 @@ use super::{
usbfs::{self, Urb},
SysfsPath,
};
use crate::descriptors::Configuration;
use crate::platform::linux_usbfs::events::Watch;
use crate::transfer::{ControlType, Recipient};
use crate::{
descriptors::{parse_concatenated_config_descriptors, DESCRIPTOR_LEN_DEVICE},
transfer::{
Expand Down Expand Up @@ -54,13 +57,39 @@ impl LinuxDevice {
let fd = rustix::fs::open(&path, OFlags::RDWR | OFlags::CLOEXEC, Mode::empty())
.inspect_err(|e| warn!("Failed to open device {path:?}: {e}"))?;

let inner = Self::create_inner(fd, Some(d.path.clone()), Some(active_config));
if inner.is_ok() {
debug!("Opened device bus={busnum} addr={devnum}",);
}
inner
}

pub(crate) fn from_fd(fd: OwnedFd) -> Result<Arc<LinuxDevice>, Error> {
debug!("Wrapping fd {} as usbfs device", fd.as_raw_fd());

Self::create_inner(fd, None, None)
}

pub(crate) fn create_inner(
fd: OwnedFd,
sysfs: Option<SysfsPath>,
active_config: Option<u8>,
) -> Result<Arc<LinuxDevice>, Error> {
let descriptors = {
let mut file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd.as_raw_fd())) };
// NOTE: Seek required on android
file.seek(std::io::SeekFrom::Start(0))?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
buf
};

let active_config = if let Some(active_config) = active_config {
active_config
} else {
Self::get_config(&descriptors, &fd)?
};

// because there's no Arc::try_new_cyclic
let mut events_err = None;
let arc = Arc::new_cyclic(|weak| {
Expand All @@ -71,11 +100,14 @@ impl LinuxDevice {
);
let events_id = *res.as_ref().unwrap_or(&usize::MAX);
events_err = res.err();
if events_err.is_none() {
debug!("Opened device fd={} with id {}", fd.as_raw_fd(), events_id,);
}
LinuxDevice {
fd,
events_id,
descriptors,
sysfs: Some(d.path.clone()),
sysfs,
active_config: AtomicU8::new(active_config),
}
});
Expand All @@ -84,10 +116,6 @@ impl LinuxDevice {
error!("Failed to initialize event loop: {err}");
Err(err)
} else {
debug!(
"Opened device bus={busnum} addr={devnum} with id {}",
arc.events_id
);
Ok(arc)
}
}
Expand Down Expand Up @@ -262,13 +290,15 @@ impl LinuxDevice {
}))
}

#[cfg(target_os = "linux")]
pub(crate) fn detach_kernel_driver(
self: &Arc<Self>,
interface_number: u8,
) -> Result<(), Error> {
usbfs::detach_kernel_driver(&self.fd, interface_number).map_err(|e| e.into())
}

#[cfg(target_os = "linux")]
pub(crate) fn attach_kernel_driver(
self: &Arc<Self>,
interface_number: u8,
Expand Down Expand Up @@ -303,6 +333,78 @@ impl LinuxDevice {
}
}
}

fn get_config(descriptors: &[u8], fd: &OwnedFd) -> Result<u8, Error> {
const REQUEST_GET_CONFIGURATION: u8 = 0x08;

let mut dst = [0u8; 1];

let control = Control {
control_type: ControlType::Standard,
recipient: Recipient::Device,
request: REQUEST_GET_CONFIGURATION,
value: 0,
index: 0,
};

let r = usbfs::control(
&fd,
usbfs::CtrlTransfer {
bRequestType: control.request_type(Direction::In),
bRequest: control.request,
wValue: control.value,
wIndex: control.index,
wLength: dst.len() as u16,
timeout: Duration::from_millis(50)
.as_millis()
.try_into()
.expect("timeout must fit in u32 ms"),
data: &mut dst[0] as *mut u8 as *mut c_void,
},
);

match r {
Ok(n) => {
if n == dst.len() {
let active_config = dst[0];
debug!("Obtained active configuration for fd {} from GET_CONFIGURATION request: {active_config}", fd.as_raw_fd());
return Ok(active_config);
} else {
warn!("GET_CONFIGURATION request returned incorrect length: {n}, expected 1",);
}
}
Err(e) => {
warn!(
"GET_CONFIGURATION request failed: {:?}",
errno_to_transfer_error(e)
);
}
}

if descriptors.len() < DESCRIPTOR_LEN_DEVICE as usize {
warn!(
"Descriptors for device fd {} too short to use fallback configuration",
fd.as_raw_fd()
);
return Err(ErrorKind::Other.into());
}

// Assume the current configuration is the first one
// See: https://github.com/libusb/libusb/blob/467b6a8896daea3d104958bf0887312c5d14d150/libusb/os/linux_usbfs.c#L865
let mut descriptors =
parse_concatenated_config_descriptors(&descriptors[DESCRIPTOR_LEN_DEVICE as usize..])
.map(Configuration::new);

if let Some(config) = descriptors.next() {
return Ok(config.configuration_value());
}

error!(
"No available configurations for device fd {}",
fd.as_raw_fd()
);
return Err(ErrorKind::Other.into());
}
}

impl Drop for LinuxDevice {
Expand Down
5 changes: 3 additions & 2 deletions src/platform/linux_usbfs/usbfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ mod opcodes {
pub fn detach_kernel_driver<Fd: AsFd>(fd: Fd, interface: u8) -> io::Result<()> {
let command = UsbFsIoctl {
interface: interface.into(),
ioctl_code: opcodes::nested::USBDEVFS_DISCONNECT::OPCODE.raw(),
// NOTE: Cast needed since on android this type is i32 vs u32 on linux
ioctl_code: opcodes::nested::USBDEVFS_DISCONNECT::OPCODE.raw() as _,
data: std::ptr::null_mut(),
};
unsafe {
Expand All @@ -107,7 +108,7 @@ pub fn detach_kernel_driver<Fd: AsFd>(fd: Fd, interface: u8) -> io::Result<()> {
pub fn attach_kernel_driver<Fd: AsFd>(fd: Fd, interface: u8) -> io::Result<()> {
let command = UsbFsIoctl {
interface: interface.into(),
ioctl_code: opcodes::nested::USBDEVFS_CONNECT::OPCODE.raw(),
ioctl_code: opcodes::nested::USBDEVFS_CONNECT::OPCODE.raw() as _,
data: std::ptr::null_mut(),
};
unsafe {
Expand Down
4 changes: 2 additions & 2 deletions src/platform/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "android"))]
mod linux_usbfs;

#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "android"))]
pub use linux_usbfs::*;

#[cfg(target_os = "windows")]
Expand Down

0 comments on commit d6ec80a

Please sign in to comment.