-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Android support for non-rooted phones #80
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was meaning to try this at some point, glad you got it working.
src/platform/linux_usbfs/device.rs
Outdated
// NOTE: must be called from the same thread that gave us the FD on android, otherwise this fails | ||
let proc_path = PathBuf::from(format!("/proc/self/fd/{}", fd.as_raw_fd())); | ||
|
||
debug!("Reading link: {proc_path:?}"); | ||
let fd_path = std::fs::read_link(&proc_path)?; | ||
|
||
// TODO: should we switch to regex or regex-lite for this? | ||
let prefix = "/dev/bus/usb/"; | ||
let Ok(dev_num_bus_num) = fd_path.strip_prefix(prefix) else { | ||
error!( | ||
"Usb device path does not start with required prefix `{prefix}`: {}", | ||
fd_path.to_string_lossy() | ||
); | ||
return Err(ErrorKind::Other.into()); | ||
}; | ||
let Some(dev_num_bus_num) = dev_num_bus_num.to_str() else { | ||
error!("End of usb device path is not UTF-8!: {dev_num_bus_num:?}",); | ||
return Err(ErrorKind::Other.into()); | ||
}; | ||
|
||
let mut split = dev_num_bus_num.split("/"); | ||
let Some(bus_str) = split.next() else { | ||
error!("Failed to split usbfs device path: {}", dev_num_bus_num); | ||
return Err(ErrorKind::Other.into()); | ||
}; | ||
let Some(dev_str) = split.next() else { | ||
error!("Failed to split usbfs device path: {}", dev_num_bus_num); | ||
return Err(ErrorKind::Other.into()); | ||
}; | ||
|
||
let Ok(busnum) = bus_str.parse::<u8>() else { | ||
error!("Usb bus number failed to parse: {bus_str}",); | ||
return Err(ErrorKind::Other.into()); | ||
}; | ||
let Ok(devnum) = dev_str.parse::<u8>() else { | ||
error!("Usb device number failed to parse: {dev_str}",); | ||
return Err(ErrorKind::Other.into()); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
create_inner
doesn't do anything with busnum
and devnum
beyond logging it. I wouldn't mind changing that log message to avoid going so far out of your way to get these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good catch. Got caught up trying to replicate the libusb code
src/platform/linux_usbfs/device.rs
Outdated
return Err(ErrorKind::Other.into()); | ||
}; | ||
|
||
const LIBUSB_REQUEST_GET_CONFIGURATION: u8 = 0x08; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const LIBUSB_REQUEST_GET_CONFIGURATION: u8 = 0x08; | |
const REQUEST_GET_CONFIGURATION: u8 = 0x08; |
(it's a request defined by the USB standard, not something from libusb)
src/platform/linux_usbfs/device.rs
Outdated
index: 0, | ||
}; | ||
|
||
// TODO: what to do about blocking here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opening the device file also blocks for USB resume and a GET_STATUS
request, so I guess it's no worse than the other way to open a device?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. I suppose this asks a larger question about blocking since its likely that the user will call one of these methods on an async runtime if they later use the async transfer methods
src/platform/linux_usbfs/device.rs
Outdated
// See: https://github.com/libusb/libusb/blob/467b6a8896daea3d104958bf0887312c5d14d150/libusb/os/linux_usbfs.c#L865 | ||
let n = r.map_err(errno_to_transfer_error)?; | ||
if n != dst.len() { | ||
error!("Failed to read descriptor"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
error!("Failed to read descriptor"); | |
error!("GET_CONFIGURATION request failed"); |
Since it's not a descriptor
src/platform/linux_usbfs/device.rs
Outdated
// TODO: Could add some more logic here to support buggy devices, similar to what libusb does | ||
// See: https://github.com/libusb/libusb/blob/467b6a8896daea3d104958bf0887312c5d14d150/libusb/os/linux_usbfs.c#L865 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't be surprised if it's common to not implement GET_CONFIGURATION
because it doesn't seem like it would be commonly used.
If there's only one configuration, which will be the case for the vast majority of devices, it seems pretty safe to assume that's the active configuration.
Took another pass! |
Btw I added CI for android which builds the whole thing on aarch64 and armv7. Also switched |
Thanks! Tested on Linux with a device that supports GET_CONFIGURATION and one that doesn't. It would be useful to have example code to point to on how to integrate this with the Java APIs on Android, so if you publish code for an app that uses this, please let me know! |
Certainly! Getting the permissions setup and opening the device to get the FD is a bit annoying, so an example would be great to have. I'll put up a PR up for this in a bit when I have time |
@@ -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"))] | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed this, but @tuna-f1sh noticed when trying to rebase on top of it -- what's your reasoning for splitting this to exclude sysfs_path
here, and similarly not adding target_os = "android"
to the DeviceInfo::sysfs_path
getter?
Un-rooted Android doesn't allow sysfs, but everything in DeviceInfo
comes from sysfs, so list_devices
isn't expected to work there at all; that's why you added from_fd
. I was assuming you added target_os = "android"
in this file for use on rooted devices, or just consistency with Linux, in which case it seems like sysfs_path
should be included. Alternatively, should we instead cfg
out list_devices
and DeviceInfo
entirely?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Yeah this is something I missed.
I feel like the correct thing to do would be to add target_os = "android"
to the getter, fmt::Debug
impl etc. for consistency between linux and rooted devices. Then add some docs in list_devices
noting that this will fail on unrooted android, with a link to wrap_device
like what libusb does: https://libusb.sourceforge.io/api-1.0/group__libusb__lib.html#gga07d4ec54cf575d672ba94c72b3c0de7ca5534030a8299c85555aebfae39161ef7
Android support for non-rooted phones
Android support for non-rooted phones
Adds a
Device::from_fd
function for wrapping devices obtained from android's UsbManagerTesting
Tested with HackrfOne (firmware version
2024.02.1
) on an A7 lite running Android 14 (kernel version4.19.191
).The basic configuration methods work, as well as receiving from bulk endpoints via the async Queue interface:
See: https://github.com/MerchGuardian/seify/blob/hackrf/crates/hackrf-one-rs/src/lib.rs#L535-L566
Prints:
TODO:
cargo ndk
to ensure we hit the new `#[cfg(target_os = "android")] blocks in CI