| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! ril-ctl is used for interacting with devices that expose the standard |
| //! Fuchsia RIL (FRIL) |
| //! |
| //! Ex: ril-ctl |
| //! |
| //! or |
| //! |
| //! Ex: ril-ctl -d /dev/class/qmi-usb-transport/000 |
| //! |
| |
| use { |
| crate::commands::{Cmd, ReplControl}, |
| anyhow::{format_err, Context as _, Error}, |
| fidl::endpoints, |
| fidl_fuchsia_net::{IpAddress, Ipv4Address, Subnet}, |
| fidl_fuchsia_net_stack::{ForwardingDestination, ForwardingEntry, StackMarker}, |
| fidl_fuchsia_net_stack_ext::FidlReturn, |
| fidl_fuchsia_netstack::NetstackMarker, |
| fidl_fuchsia_telephony_manager::ManagerMarker, |
| fidl_fuchsia_telephony_ril::{ |
| RadioInterfaceLayerMarker, RadioInterfaceLayerProxy, RadioPowerState, SetupMarker, *, |
| }, |
| fuchsia_async::{self as fasync, futures::select}, |
| fuchsia_component::{ |
| client::{connect_to_service, launch, launcher}, |
| fuchsia_single_component_package_url, |
| }, |
| futures::{FutureExt, TryFutureExt}, |
| parking_lot::Mutex, |
| pin_utils::pin_mut, |
| qmi, |
| std::{fs::File, path::PathBuf, sync::Arc}, |
| structopt::StructOpt, |
| }; |
| |
| mod commands; |
| mod repl; |
| |
| static PROMPT: &str = "\x1b[35mril>\x1b[0m "; |
| const RIL_URI: &str = fuchsia_single_component_package_url!("ril-qmi"); |
| |
| // TODO count actual bit number |
| fn u32_to_cidr(ip: u32) -> Result<u8, Error> { |
| match ip & 0xFF { |
| 255 => Ok(32), |
| 254 => Ok(31), |
| 252 => Ok(30), |
| 248 => Ok(29), |
| 240 => Ok(28), |
| 224 => Ok(27), |
| 192 => Ok(26), |
| 128 => Ok(25), |
| 0 => Ok(24), |
| e => Err(format_err!("bad u32 to cidr conversion: {}", e)), |
| } |
| } |
| |
| fn u32_to_ip_str(ip: u32) -> String { |
| format!( |
| "{}.{}.{}.{}", |
| ((ip >> 24) & 0xFF), |
| ((ip >> 16) & 0xFF), |
| ((ip >> 8) & 0xFF), |
| (ip & 0xFF) |
| ) |
| } |
| |
| // only supports ipv4 now |
| fn u32_to_netaddr(ip: u32, mask: u32) -> Result<Subnet, Error> { |
| let cidr = u32_to_cidr(mask)?; |
| Ok(Subnet { |
| addr: IpAddress::Ipv4(Ipv4Address { |
| addr: [ |
| ((ip >> 24) & 0xFF) as u8, |
| ((ip >> 16) & 0xFF) as u8, |
| ((ip >> 8) & 0xFF) as u8, |
| (ip & 0xFF) as u8, |
| ], |
| }), |
| prefix_len: cidr, |
| }) |
| } |
| |
| async fn get_imei<'a>( |
| _args: &'a [&'a str], |
| ril_modem: &'a RadioInterfaceLayerProxy, |
| ) -> Result<String, Error> { |
| match ril_modem.get_device_identity().await? { |
| Ok(imei) => Ok(imei), |
| Err(_state) => Err(format_err!("error")), |
| } |
| } |
| |
| async fn connect<'a>( |
| args: &'a [&'a str], |
| ril_modem: &'a RadioInterfaceLayerProxy, |
| ) -> Result<(NetworkSettings, NetworkConnectionProxy), Error> { |
| match ril_modem.start_network(args[0]).await? { |
| Ok(iface) => { |
| let settings = ril_modem.get_network_settings().await?; |
| if let Ok(settings) = settings { |
| return Ok((settings, iface.into_proxy()?)); |
| } |
| Err(format_err!("error")) |
| } |
| Err(_e) => Err(format_err!("error")), |
| } |
| } |
| |
| async fn get_power<'a>( |
| _args: &'a [&'a str], |
| ril_modem: &'a RadioInterfaceLayerProxy, |
| ) -> Result<String, Error> { |
| match ril_modem.radio_power_status().await? { |
| Ok(state) => match state { |
| RadioPowerState::On => Ok(String::from("radio on")), |
| RadioPowerState::Off => Ok(String::from("radio off")), |
| }, |
| Err(_e) => Err(format_err!("error")), |
| } |
| } |
| |
| async fn get_signal<'a>( |
| _args: &'a [&'a str], |
| ril_modem: &'a RadioInterfaceLayerProxy, |
| ) -> Result<String, Error> { |
| match ril_modem.get_signal_strength().await? { |
| Ok(strength) => Ok(format!("{} dBm", strength)), |
| Err(_e) => Err(format_err!("error")), |
| } |
| } |
| |
| pub struct Connections { |
| pub net_conn: Option<NetworkConnectionProxy>, |
| pub file_ref: Option<File>, |
| } |
| |
| async fn handle_cmd<'a>( |
| ril_modem: &'a RadioInterfaceLayerProxy, |
| line: String, |
| state: Arc<Mutex<Connections>>, |
| ) -> Result<ReplControl, Error> { |
| let components: Vec<_> = line.trim().split_whitespace().collect(); |
| if let Some((raw_cmd, args)) = components.split_first() { |
| let cmd = raw_cmd.parse(); |
| let res = match cmd { |
| Ok(Cmd::Connect) => { |
| let (settings, iface) = connect(args, &ril_modem).await?; |
| { |
| state.lock().net_conn = Some(iface); |
| } |
| eprintln!("IP Addr: {}", u32_to_ip_str(settings.ip_v4_addr)); |
| eprintln!("IP Subnet: {}", u32_to_ip_str(settings.ip_v4_subnet)); |
| eprintln!("IP Gateway: {}", u32_to_ip_str(settings.ip_v4_gateway)); |
| eprintln!("IP DNS: {}", u32_to_ip_str(settings.ip_v4_dns)); |
| match state.lock().file_ref { |
| Some(ref file_ref) => { |
| // Set up the netstack. |
| // TODO not hardcode to iface 3 |
| qmi::set_network_status(file_ref, true).await?; |
| let netstack = connect_to_service::<StackMarker>()?; |
| let old_netstack = connect_to_service::<NetstackMarker>()?; |
| let (client, server_end) = fidl::endpoints::create_proxy::<fidl_fuchsia_net_dhcp::ClientMarker>()?; |
| old_netstack.get_dhcp_client(3, server_end).await?.map_err(fuchsia_zircon::Status::from_raw)?; |
| client.stop().await?.map_err(fuchsia_zircon::Status::from_raw)?; |
| |
| let () = netstack |
| .add_interface_address( |
| 3, |
| &mut u32_to_netaddr(settings.ip_v4_addr, settings.ip_v4_subnet)? |
| ) |
| .await |
| .squash_result()?; |
| let ip = settings.ip_v4_addr; |
| let () = netstack.add_forwarding_entry(&mut ForwardingEntry { |
| destination: ForwardingDestination::NextHop(IpAddress::Ipv4(Ipv4Address{ |
| addr: [ |
| ((settings.ip_v4_gateway >> 24) & 0xFF) as u8, |
| ((settings.ip_v4_gateway >> 16) & 0xFF) as u8, |
| ((settings.ip_v4_gateway >> 8) & 0xFF) as u8, |
| (settings.ip_v4_gateway & 0xFF) as u8]})), |
| subnet: Subnet { |
| addr: IpAddress::Ipv4(Ipv4Address{ |
| addr: [ |
| ((ip >> 24) & 0xFF) as u8, |
| ((ip >> 16) & 0xFF) as u8, |
| ((ip >> 8) & 0xFF) as u8, |
| (ip & 0xFF) as u8]}), |
| prefix_len: u32_to_cidr(settings.ip_v4_subnet)? |
| }, |
| }).await.squash_result()?; |
| Ok("connected".to_string()) |
| } |
| None => Ok("set up connection on radio. Did not configure ethernet device, exclusive access required".to_string()) |
| } |
| } |
| Ok(Cmd::PowerStatus) => get_power(args, &ril_modem).await, |
| Ok(Cmd::SignalStrength) => get_signal(args, &ril_modem).await, |
| Ok(Cmd::Imei) => get_imei(args, &ril_modem).await, |
| Ok(Cmd::Help) => Ok(Cmd::help_msg().to_string()), |
| Ok(Cmd::Exit) | Ok(Cmd::Quit) => return Ok(ReplControl::Break), |
| Err(_) => Ok(format!("\"{}\" is not a valid command", raw_cmd)), |
| }?; |
| if res != "" { |
| println!("{}", res); |
| } |
| } |
| |
| Ok(ReplControl::Continue) |
| } |
| |
| /// A basic example |
| #[derive(StructOpt, Debug)] |
| #[structopt(name = "basic")] |
| struct Opt { |
| /// Device path (e.g. /dev/class/qmi-transport/000) |
| #[structopt(short = "d", long = "device", parse(from_os_str))] |
| device: Option<PathBuf>, |
| } |
| |
| pub fn main() -> Result<(), Error> { |
| let mut exec = fasync::Executor::new().context("error creating event loop")?; |
| let args = Opt::from_args(); |
| |
| let launcher = launcher().context("Failed to open launcher service")?; |
| let file = match args.device { |
| Some(ref device) => Some(File::open(device)?), |
| None => None, |
| }; |
| let conns = Arc::new(Mutex::new(Connections { file_ref: file, net_conn: None })); |
| |
| let fut = async move { |
| let app; // need outside the match so it won't drop |
| let telephony_svc; |
| let (ril, server) = endpoints::create_proxy()?; |
| let ril_modem = match args.device { |
| Some(device) => { |
| eprintln!("Connecting with exclusive access to {}..", device.display()); |
| let file = File::open(device)?; |
| let chan = qmi::connect_transport_device(&file).await?; |
| app = launch(&launcher, RIL_URI.to_string(), None) |
| .context("Failed to launch ril-qmi service")?; |
| let ril_modem_setup = app.connect_to_service::<SetupMarker>()?; |
| let resp = ril_modem_setup.connect_transport(chan).await?; |
| let ril_modem = app.connect_to_service::<RadioInterfaceLayerMarker>()?; |
| if resp.is_err() { |
| return Err(format_err!( |
| "Failed to connect the driver to the RIL (check telephony svc is not running?)" |
| )); |
| } |
| Ok::<_, Error>(ril_modem) |
| } |
| None => { |
| eprintln!("Connecting through telephony service..."); |
| telephony_svc = connect_to_service::<ManagerMarker>()?; |
| let resp = telephony_svc.get_ril_handle(server).await?; |
| if !resp { |
| return Err(format_err!("Failed to get an active RIL")); |
| } |
| Ok::<_, Error>(ril) |
| } |
| }?; |
| |
| let repl = repl::run(&ril_modem, conns) |
| .unwrap_or_else(|e| eprintln!("REPL failed unexpectedly {:?}", e)); |
| pin_mut!(repl); |
| select! { |
| () = repl.fuse() => Ok(()), |
| // TODO(bwb): events loop future |
| } |
| }; |
| |
| exec.run_singlethreaded(fut) |
| } |