diff --git a/Cargo.lock b/Cargo.lock index d596c95..b85e659 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,9 +115,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "arbitrary" @@ -1932,6 +1932,7 @@ dependencies = [ name = "serial" version = "0.2.6" dependencies = [ + "anyhow", "nusb", "tokio", ] diff --git a/serial/Cargo.toml b/serial/Cargo.toml index 2c0e800..f69b4be 100644 --- a/serial/Cargo.toml +++ b/serial/Cargo.toml @@ -6,5 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.97" nusb = "0.1.13" tokio = { version = "1.44.1", features = ["macros", "rt", "time"] } diff --git a/serial/src/main.rs b/serial/src/main.rs index 85de9cd..115b614 100644 --- a/serial/src/main.rs +++ b/serial/src/main.rs @@ -3,38 +3,46 @@ //! This binary has two main functions, putting the orbic device in update mode which enables ADB //! and running AT commands on the serial modem interface which can be used to upload a shell and chown it to root //! -//! # Panics +//! # Errors //! //! No device found - make sure your device is plugged in and turned on. If it is, it's possible you have a device with a different //! usb id, file a bug with the output of `lsusb` attached. use std::str; use std::time::Duration; +use anyhow::{bail, Context, Result}; use nusb::transfer::{Control, ControlType, Recipient, RequestBuffer}; use nusb::{Device, Interface}; +const ORBIC_NOT_FOUND: &str = r#"No Orbic device found. +Make sure your device is plugged in and turned on. + +If it's possible you have a device with a different usb id: +please file a bug with the output of `lsusb` attached."#; + #[tokio::main(flavor = "current_thread")] -async fn main() { +async fn main() -> Result<()> { let args: Vec = std::env::args().collect(); - if args.len() != 2 { + if args.len() != 2 || args[1] == "-h" || args[1] == "--help" { println!("usage: {0} [ | --root]", args[0]); - return; + std::process::exit(1); } if args[1] == "--root" { - enable_command_mode(); + enable_command_mode() } else { - match open_orbic() { + match open_orbic()? { Some(interface) => send_command(interface, &args[1]).await, - None => panic!("No Orbic device found"), + None => bail!(ORBIC_NOT_FOUND), } } } + /// Sends an AT command to the usb device over the serial port /// /// First establish a USB handle and context by calling `open_orbic() -async fn send_command(interface: Interface, command: &str) { +async fn send_command(interface: Interface, command: &str) -> Result<()> { let mut data = String::new(); data.push_str("\r\n"); data.push_str(command); @@ -53,51 +61,53 @@ async fn send_command(interface: Interface, command: &str) { // Set up the serial port appropriately interface .control_out_blocking(enable_serial_port, &[], timeout) - .expect("Failed to send control request"); + .context("Failed to send control request")?; // Send the command tokio::time::timeout(timeout, interface.bulk_out(0x2, data.as_bytes().to_vec())) .await - .expect("Timed out writing command") + .context("Timed out writing command")? .into_result() - .expect("Failed to write command"); + .context("Failed to write command")?; // Consume the echoed command tokio::time::timeout(timeout, interface.bulk_in(0x82, RequestBuffer::new(256))) .await - .expect("Timed out reading submitted command") + .context("Timed out reading submitted command")? .into_result() - .expect("Failed to read submitted command"); + .context("Failed to read submitted command")?; // Read the actual response let response = tokio::time::timeout(timeout, interface.bulk_in(0x82, RequestBuffer::new(256))) .await - .expect("Timed out reading response") + .context("Timed out reading response")? .into_result() - .expect("Failed to read response"); + .context("Failed to read response")?; // For some reason, on macOS the response buffer gets filled with garbage data that's // rarely valid UTF-8. Luckily we only care about the first couple bytes, so just drop // the garbage with `from_utf8_lossy` and look for our expected success string. let responsestr = String::from_utf8_lossy(&response); if !responsestr.contains("\r\nOK\r\n") { - println!("Received unexpected response{0}", responsestr); + println!("Received unexpected response: {0}", responsestr); std::process::exit(1); } + + Ok(()) } /// Send a command to switch the device into generic mode, exposing serial /// /// If the device reboots while the command is still executing you may get a pipe error here, not sure what to do about this race condition. -fn enable_command_mode() { - if open_orbic().is_some() { +fn enable_command_mode() -> Result<()> { + if open_orbic()?.is_some() { println!("Device already in command mode. Doing nothing..."); - return; + return Ok(()); } let timeout = Duration::from_secs(1); - if let Some(interface) = open_device(0x05c6, 0xf626) { + if let Some(interface) = open_device(0x05c6, 0xf626)? { let enable_command_mode = Control { control_type: ControlType::Vendor, recipient: Recipient::Device, @@ -109,52 +119,52 @@ fn enable_command_mode() { // If the device reboots while the command is still executing we // may get a pipe error here if e == nusb::transfer::TransferError::Stall { - return; + return Ok(()); } - panic!("Failed to send device switch control request: {0}", e) + bail!("Failed to send device switch control request: {0}", e) } - return; + return Ok(()); } - panic!("No Orbic device found"); + bail!(ORBIC_NOT_FOUND); } /// Get an Interface for the orbic device -fn open_orbic() -> Option { +fn open_orbic() -> Result> { // Device after initial mode switch - if let Some(device) = open_device(0x05c6, 0xf601) { + if let Some(device) = open_device(0x05c6, 0xf601)? { let interface = device .detach_and_claim_interface(1) // will reattach drivers on release - .expect("detach_and_claim_interface(1) failed"); - return Some(interface); + .context("detach_and_claim_interface(1) failed")?; + return Ok(Some(interface)); } // Device with rndis enabled as well - if let Some(device) = open_device(0x05c6, 0xf622) { + if let Some(device) = open_device(0x05c6, 0xf622)? { let interface = device .detach_and_claim_interface(1) // will reattach drivers on release - .expect("detach_and_claim_interface(1) failed"); - return Some(interface); + .context("detach_and_claim_interface(1) failed")?; + return Ok(Some(interface)); } - None + Ok(None) } /// General function to open a USB device -fn open_device(vid: u16, pid: u16) -> Option { +fn open_device(vid: u16, pid: u16) -> Result> { let devices = match nusb::list_devices() { Ok(d) => d, - Err(_) => return None, + Err(_) => return Ok(None), }; for device in devices { if device.vendor_id() == vid && device.product_id() == pid { match device.open() { - Ok(d) => return Some(d), - Err(e) => panic!("device found but failed to open: {}", e), + Ok(d) => return Ok(Some(d)), + Err(e) => bail!("device found but failed to open: {}", e), } } } - None + Ok(None) }