Skip to content

Commit d9e42c9

Browse files
committed
Remove call functionality from humility rpc
1 parent a512d38 commit d9e42c9

4 files changed

Lines changed: 18 additions & 273 deletions

File tree

README.md

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ a specified target. (In the above example, one could execute `humility
276276
- [humility repl](#humility-repl): read, eval, print, loop
277277
- [humility reset](#humility-reset): Reset the chip using external pins
278278
- [humility ringbuf](#humility-ringbuf): read and display a specified ring buffer
279-
- [humility rpc](#humility-rpc): execute Idol calls over a network
279+
- [humility rpc](#humility-rpc): listen for compatible SPs on a network
280280
- [humility sbrmi](#humility-sbrmi): Sideband Remote Management Interface (SB-RMI) commands
281281
- [humility sensors](#humility-sensors): query sensors and sensor data
282282
- [humility spctrl](#humility-spctrl): RoT -> SP control
@@ -2763,26 +2763,9 @@ documentation](https://github.com/oxidecomputer/hubris/blob/master/lib/ringbuf/s
27632763

27642764
### `humility rpc`
27652765

2766-
`humility rpc` allows for execution of Idol commands over a network, rather
2767-
than through a debugger.
2768-
2769-
It requires the Hubris `udprpc` task to be listening on port 8. This task
2770-
decodes bytes from a UDP packet, and shoves them directly into `sys_send` to
2771-
a target task.
2772-
2773-
An archive is required so that `humility` knows what functions are available
2774-
and how to call them. The archive ID is checked against the image ID on the
2775-
target; `udprpc` will refuse to execute commands when the ID does not match.
2776-
2777-
Function calls are handled identically to the `humility hiffy` subcommand,
2778-
except that an `--ip` address is required:
2779-
2780-
```console
2781-
$ humility rpc --ip fe80::0c1d:9aff:fe64:b8c2%en0 -c UserLeds.led_on -aindex=0
2782-
UserLeds.led_on() = ()
2783-
```
2784-
2785-
Alternatively, you can set the `HUMILITY_RPC_IP` environmental variable.
2766+
`humility rpc` lets you discover SPs on a network, instead of using a
2767+
physically attached debugger. Once SPs are discovered, they may be used as
2768+
a target by setting `HUMILITY_IP` or providing the `--ip` argument.
27862769

27872770
You may need to configure an IPv6 network for `humility rpc` to work. On
27882771
illumos, it looks like this:
@@ -2812,9 +2795,6 @@ not include identity information. If they are marked as `(vpdfail)`, they
28122795
are running a new-enough `udpbroadcast`, but the SP was unable to read its
28132796
identity from its VPD.
28142797

2815-
To call all targets that match an archive, `--listen` can be combined with
2816-
`--call`
2817-
28182798

28192799
### `humility sbrmi`
28202800

cmd/rpc/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "humility-cmd-rpc"
33
version = "0.1.0"
44
edition.workspace = true
5-
description = "execute Idol calls over a network"
5+
description = "listen for compatible SPs on a network"
66

77
[dependencies]
88
humility.workspace = true

cmd/rpc/src/lib.rs

Lines changed: 10 additions & 246 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,9 @@
44

55
//! ## `humility rpc`
66
//!
7-
//! `humility rpc` allows for execution of Idol commands over a network, rather
8-
//! than through a debugger.
9-
//!
10-
//! It requires the Hubris `udprpc` task to be listening on port 8. This task
11-
//! decodes bytes from a UDP packet, and shoves them directly into `sys_send` to
12-
//! a target task.
13-
//!
14-
//! An archive is required so that `humility` knows what functions are available
15-
//! and how to call them. The archive ID is checked against the image ID on the
16-
//! target; `udprpc` will refuse to execute commands when the ID does not match.
17-
//!
18-
//! Function calls are handled identically to the `humility hiffy` subcommand,
19-
//! except that an `--ip` address is required:
20-
//!
21-
//! ```console
22-
//! $ humility rpc --ip fe80::0c1d:9aff:fe64:b8c2%en0 -c UserLeds.led_on -aindex=0
23-
//! UserLeds.led_on() = ()
24-
//! ```
25-
//!
26-
//! Alternatively, you can set the `HUMILITY_RPC_IP` environmental variable.
7+
//! `humility rpc` lets you discover SPs on a network, instead of using a
8+
//! physically attached debugger. Once SPs are discovered, they may be used as
9+
//! a target by setting `HUMILITY_IP` or providing the `--ip` argument.
2710
//!
2811
//! You may need to configure an IPv6 network for `humility rpc` to work. On
2912
//! illumos, it looks like this:
@@ -52,26 +35,19 @@
5235
//! not include identity information. If they are marked as `(vpdfail)`, they
5336
//! are running a new-enough `udpbroadcast`, but the SP was unable to read its
5437
//! identity from its VPD.
55-
//!
56-
//! To call all targets that match an archive, `--listen` can be combined with
57-
//! `--call`
5838
5939
use std::collections::BTreeSet;
60-
use std::net::{IpAddr, Ipv6Addr, ToSocketAddrs, UdpSocket};
40+
use std::net::{IpAddr, Ipv6Addr, UdpSocket};
6141
use std::time::{Duration, Instant};
6242

63-
use anyhow::{Context, Result, anyhow, bail};
43+
use anyhow::{Result, bail};
6444
use clap::{ArgGroup, IntoApp, Parser};
6545
use colored::Colorize;
6646
use hubpack::SerializedSize;
67-
use humility::net::{ScopedV6Addr, decode_iface};
68-
use humility::{hubris::*, reflect};
47+
use humility::net::decode_iface;
6948
use humility_cli::{ExecutionContext, Subcommand};
7049
use humility_cmd::{Archive, Command, CommandKind};
71-
use humility_doppel::RpcHeader;
72-
use humility_idol as idol;
7350
use serde::{Deserialize, Serialize};
74-
use zerocopy::{AsBytes, U16, U64};
7551

7652
#[derive(Parser, Debug)]
7753
#[clap(
@@ -90,10 +66,6 @@ struct RpcArgs {
9066
#[clap(long, short)]
9167
list: bool,
9268

93-
/// call a particular function
94-
#[clap(long, short, conflicts_with = "list", requires = "target")]
95-
call: Option<String>,
96-
9769
/// listen for compatible SPs on the network
9870
#[clap(
9971
long,
@@ -103,26 +75,9 @@ struct RpcArgs {
10375
)]
10476
listen: bool,
10577

106-
/// arguments
107-
#[clap(long, short, requires = "call")]
108-
task: Option<String>,
109-
11078
/// interface on which to listen, e.g. 'en0'
11179
#[clap(short, requires = "listen", value_parser = decode_iface)]
11280
interface: Option<u32>,
113-
114-
/// arguments
115-
#[clap(long, short, requires = "call", use_delimiter = true)]
116-
arguments: Vec<String>,
117-
118-
/// IPv6 addresses, e.g. `fe80::0c1d:9aff:fe64:b8c2%en0`
119-
#[clap(
120-
long,
121-
env = "HUMILITY_RPC_IP",
122-
group = "target",
123-
use_value_delimiter = true
124-
)]
125-
ip: Option<Vec<ScopedV6Addr>>,
12681
}
12782

12883
#[derive(Debug, Clone, Copy, Deserialize, Serialize, SerializedSize)]
@@ -360,209 +315,18 @@ fn rpc_dump(seen: BTreeSet<Target>, image_id: &[u8]) {
360315
}
361316
}
362317

363-
pub struct RpcClient<'a> {
364-
hubris: &'a HubrisArchive,
365-
socket: UdpSocket,
366-
rpc_reply_type: &'a HubrisEnum,
367-
buf: Vec<u8>, // sized to match socket buffer size
368-
}
369-
370-
impl<'a> RpcClient<'a> {
371-
pub fn new(
372-
hubris: &'a HubrisArchive,
373-
ip: ScopedV6Addr,
374-
timeout: Duration,
375-
) -> Result<Self> {
376-
let udprpc = hubris.manifest.get_socket_by_task("udprpc")?;
377-
let target = format!("[{ip}]:{}", udprpc.port);
378-
379-
let dest = target.to_socket_addrs()?.collect::<Vec<_>>();
380-
let socket = UdpSocket::bind("[::]:0")?;
381-
socket.set_read_timeout(Some(timeout))?;
382-
socket.connect(&dest[..])?;
383-
384-
let rpc_task = hubris.lookup_task("udprpc").ok_or_else(|| {
385-
anyhow!(
386-
"Could not find `udprpc` task in this image. \
387-
Only -dev and -lab images include `udprpc`; \
388-
are you running a production image?"
389-
)
390-
})?;
391-
let rpc_reply_type = hubris
392-
.lookup_module(rpc_task)?
393-
.lookup_enum_byname(hubris, "RpcReply")?
394-
.ok_or_else(|| anyhow!("can't find RpcReply"))?;
395-
396-
let buf = vec![0u8; udprpc.tx.bytes];
397-
Ok(Self { hubris, socket, rpc_reply_type, buf })
398-
}
399-
400-
pub fn call(
401-
&mut self,
402-
op: &idol::IdolOperation,
403-
args: &[(&str, idol::IdolArgument)],
404-
) -> Result<Result<reflect::Value, String>> {
405-
let payload = op.payload(args)?;
406-
407-
let our_image_id = self.hubris.image_id().unwrap();
408-
409-
let nreply = op.reply_size()?;
410-
411-
let header = RpcHeader {
412-
image_id: U64::from_bytes(our_image_id.try_into().unwrap()),
413-
task: U16::new(op.task.task().try_into().unwrap()),
414-
op: U16::new(op.code),
415-
nreply: U16::new(nreply as u16),
416-
nbytes: U16::new(payload.len().try_into().unwrap()),
417-
};
418-
let mut packet = header.as_bytes().to_vec();
419-
packet.extend(payload.iter());
420-
421-
self.socket.send(&packet).context("unable to send packet")?;
422-
let n = self
423-
.socket
424-
.recv(&mut self.buf)
425-
.context("unable to receive packet")?;
426-
let buf = &self.buf[..n];
427-
428-
if buf[0] != 0 {
429-
// TODO: assumes the discriminator is a u8. It's not clear from
430-
// context whether this assumption carries through into the udprpc
431-
// task.
432-
match self.rpc_reply_type.lookup_variant_by_tag(Tag::from(buf[0])) {
433-
Some(e) => {
434-
let msg = format!("Got error from `udprpc`: {}", e.name);
435-
if e.name == "BadImageId" {
436-
bail!(
437-
"{msg}: {:02x?} (Humility) {:02x?} (Hubris)",
438-
our_image_id,
439-
&buf[1..9]
440-
);
441-
} else {
442-
bail!("{msg}");
443-
}
444-
}
445-
None => bail!("Got unknown error from `udprpc`: {}", buf[0]),
446-
}
447-
} else {
448-
// Check the return code from the Idol call
449-
let rc = u32::from_be_bytes(buf[1..5].try_into().unwrap());
450-
let val = if rc == 0 { Ok(buf[5..].to_vec()) } else { Err(rc) };
451-
let result = humility_hiffy::hiffy_decode(self.hubris, op, val)?;
452-
Ok(result)
453-
}
454-
}
455-
}
456-
457-
fn rpc_call(
458-
hubris: &HubrisArchive,
459-
op: &idol::IdolOperation,
460-
args: &[(&str, idol::IdolArgument)],
461-
ips: Vec<ScopedV6Addr>,
462-
timeout: u32,
463-
) -> Result<()> {
464-
let timeout = Duration::from_millis(u64::from(timeout));
465-
466-
for &ip in &ips {
467-
let mut client = RpcClient::new(hubris, ip, timeout)
468-
.context("unable to create RpcClient")?;
469-
let result = client.call(op, args).context("unable to make call")?;
470-
print!("{:25} ", ip);
471-
humility_hiffy::hiffy_print_result(hubris, op, result)
472-
.context("unable to print result")?;
473-
}
474-
475-
Ok(())
476-
}
477-
478318
fn rpc_run(context: &mut ExecutionContext) -> Result<()> {
479319
let Subcommand::Other(subargs) = context.cli.cmd.as_ref().unwrap();
480320
let subargs = RpcArgs::try_parse_from(subargs)?;
481321
let hubris = context.archive.as_ref().unwrap();
482322

483-
if subargs.list {
484-
cmd_hiffy::hiffy_list(hubris, vec![])?;
485-
return Ok(());
486-
}
487-
488-
if subargs.listen && subargs.call.is_none() {
323+
// We previously had more subcommands here, but are down to just `--listen`
324+
if subargs.listen {
489325
rpc_dump(rpc_listen(&subargs)?, hubris.image_id().unwrap());
490-
return Ok(());
491-
}
492-
493-
// For some reason, macOS requires the interface to be non-zero:
494-
// https://users.rust-lang.org/t/ipv6-upnp-multicast-for-rust-dlna-server-macos/24425
495-
// https://bluejekyll.github.io/blog/posts/multicasting-in-rust/
496-
let interface = match subargs.interface {
497-
None => {
498-
if cfg!(target_os = "macos") {
499-
bail!("Must specify interface with `-i` on macOS");
500-
} else {
501-
0
502-
}
503-
}
504-
Some(iface) => iface,
505-
};
506-
507-
let ips = if subargs.listen {
508-
let image_id = hubris.image_id().unwrap();
509-
510-
rpc_listen(&subargs)?
511-
.iter()
512-
.filter(|t| t.image_id == image_id)
513-
.map(|target| match target.ip {
514-
IpAddr::V4(ip) => bail!(
515-
"target {target:?} had an unanticipated \
516-
IPv4 address ({ip})"
517-
),
518-
IpAddr::V6(ip) => Ok(ScopedV6Addr { ip, scopeid: interface }),
519-
})
520-
.collect::<Result<Vec<_>>>()?
326+
Ok(())
521327
} else {
522-
subargs.ip.ok_or_else(|| {
523-
anyhow!(
524-
"the `--ip <IPS>` argument is required by `humility rpc` \
525-
unless `--listen` is also set"
526-
)
527-
})?
528-
};
529-
530-
if let Some(call) = &subargs.call {
531-
if hubris.lookup_task("udprpc").is_none() {
532-
bail!("no `udprpc` task in the target image");
533-
}
534-
535-
let func: Vec<&str> = call.split('.').collect();
536-
537-
if func.len() != 2 {
538-
bail!("calls must be interface.operation (-l to list)");
539-
}
540-
541-
let mut args = vec![];
542-
543-
for arg in &subargs.arguments {
544-
let arg: Vec<&str> = arg.split('=').collect();
545-
546-
if arg.len() != 2 {
547-
bail!("arguments must be argument=value (-l to list)");
548-
}
549-
550-
args.push((arg[0], idol::IdolArgument::String(arg[1])));
551-
}
552-
553-
let task = match &subargs.task {
554-
Some(task) => Some(hubris.try_lookup_task(task)?),
555-
None => None,
556-
};
557-
558-
let op = idol::IdolOperation::new(hubris, func[0], func[1], task)?;
559-
rpc_call(hubris, &op, &args, ips, subargs.timeout)
560-
.context("failed to make RPC call")?;
561-
562-
return Ok(());
328+
bail!("expected --listen")
563329
}
564-
565-
bail!("expected --listen, --list, or --call")
566330
}
567331

568332
pub fn init() -> Command {

humility-cli/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
pub mod env;
66

7-
use anyhow::Result;
7+
use anyhow::{Context, Result};
88
use clap::{AppSettings, ArgGroup, ArgMatches, Parser};
99
use env::Environment;
1010
use humility::{core::Core, hubris::HubrisArchive, msg, net, warn};
@@ -163,7 +163,8 @@ impl ExecutionContext {
163163
if let Ok(e) = env::var("HUMILITY_PROBE") {
164164
cli.probe = Some(e);
165165
} else if let Ok(e) = env::var("HUMILITY_IP") {
166-
cli.ip = Some(e.parse()?);
166+
cli.ip =
167+
Some(e.parse().context("could not parse HUMILITY_IP")?);
167168
} else if let Ok(e) = env::var("HUMILITY_TARGET") {
168169
cli.target = Some(e);
169170
} else if let Ok(e) = env::var("HUMILITY_DUMP") {

0 commit comments

Comments
 (0)