blob: a88a092b8e3b0d89dc33bfcba9537e2e644841b9 [file] [log] [blame]
// 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::{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_protocol, 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)
)
}
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(
ril_modem: &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.
qmi::set_network_status(file_ref, true).await?;
let netstack = connect_to_protocol::<StackMarker>()?;
let old_netstack = connect_to_protocol::<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 to_addr = |ip: u32| {
Ipv4Address {
addr: ip.to_be_bytes()
}
};
let addr = to_addr(settings.ip_v4_addr);
let prefix_len = u32_to_cidr(settings.ip_v4_subnet).context("compute subnet")?;
{
let debug_interfaces = connect_to_protocol::<fidl_fuchsia_net_debug::InterfacesMarker>()?;
let (control, server_end) = fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
.context("create admin control endpoints")?;
// TODO not hardcode to iface 3
let () = debug_interfaces.get_admin(3, server_end).context("send get admin request")?;
let (address_state_provider, server_end) = fidl::endpoints::create_proxy()
.context("create proxy")?;
let mut addr = {
fidl_fuchsia_net::InterfaceAddress::Ipv4(fidl_fuchsia_net::Ipv4AddressWithPrefix {
addr,
prefix_len,
})
};
let () = control
.add_address(
&mut addr,
fidl_fuchsia_net_interfaces_admin::AddressParameters::EMPTY,
server_end,
)
.context("call add address")?;
let () = address_state_provider.detach().context("detach address lifetime")?;
}
let () = netstack.add_forwarding_entry(&mut ForwardingEntry {
subnet: Subnet {
addr: IpAddress::Ipv4(addr),
prefix_len,
},
device_id: 0,
next_hop: Some(Box::new(IpAddress::Ipv4(to_addr(settings.ip_v4_gateway)))),
metric: 0,
}).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::LocalExecutor::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_protocol::<SetupMarker>()?;
let resp = ril_modem_setup.connect_transport(chan).await?;
let ril_modem = app.connect_to_protocol::<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_protocol::<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)
}