blob: 25b502b80084cc8ca39f31a76dcc68d927c6b10c [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::{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)
}