# decides whether"
)]
#![cfg_attr(
not(feature = "rpc"),
doc = "`PeerIdentity` (`rpc` feature) decides whether"
)]
use crate::error::{Result, StatusCode};
#[cfg_attr(
feature = "rpc",
doc = "has), [`RpcSession::caps`](crate::rpc::RpcSession::caps) (what this"
)]
#[cfg_attr(
not(feature = "rpc"),
doc = "has), `RpcSession::caps` (`rpc` feature) (what this"
)]
#[derive(Copy, Clone, PartialEq, Eq, Hash, Default)]
pub struct TransportCaps(u32);
impl TransportCaps {
#[cfg_attr(
feature = "rpc",
doc = "[`FileDescriptorTransportMode::Unix`](crate::rpc::FileDescriptorTransportMode),"
)]
#[cfg_attr(
not(feature = "rpc"),
doc = "`FileDescriptorTransportMode::Unix` (`rpc` feature),"
)]
pub const FD_PASSING: Self = Self(1 << 0);
pub const TRUSTED_UID: Self = Self(1 << 1);
pub const CALLBACKS: Self = Self(1 << 2);
pub const SAME_HOST: Self = Self(1 << 3);
pub const KERNEL_KNOBS: Self = Self(1 << 4);
pub const NONE: Self = Self(0);
pub const KERNEL: Self = Self(0b1_1111);
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub const fn difference(self, other: Self) -> Self {
Self(self.0 & !other.0)
}
pub const fn is_empty(self) -> bool {
self.0 == 0
}
pub const fn bits(self) -> u32 {
self.0
}
pub fn require(self, needed: Self, what: &str) -> Result<()> {
if self.contains(needed) {
return Ok(());
}
let missing = needed.difference(self);
log::error!(
"rsbinder: {what} needs {missing} (this transport has {self}); \
{}",
missing.remedy()
);
Err(StatusCode::InvalidOperation)
}
fn remedy(self) -> String {
let mut out = String::new();
for (bit, advice) in [
(
Self::CALLBACKS,
"CALLBACKS holds on kernel binder, and on an RPC session whose \
client end opened incoming connections — see \
`TransportCaps::CALLBACKS`",
),
(
Self::FD_PASSING,
"FD_PASSING holds on kernel binder, and on a Unix-socket RPC \
session that negotiated `FileDescriptorTransportMode::Unix`",
),
(
Self::TRUSTED_UID,
"TRUSTED_UID holds on kernel binder and Unix-socket RPC, where \
the kernel vouches for the peer's uid",
),
(
Self::SAME_HOST,
"SAME_HOST holds on kernel binder and Unix-socket RPC, where \
both ends share a kernel",
),
(
Self::KERNEL_KNOBS,
"KERNEL_KNOBS holds on kernel binder only",
),
] {
if self.contains(bit) {
if !out.is_empty() {
out.push_str("; ");
}
out.push_str(advice);
}
}
out
}
}
impl core::ops::BitOr for TransportCaps {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
self.union(rhs)
}
}
impl core::ops::BitOrAssign for TransportCaps {
fn bitor_assign(&mut self, rhs: Self) {
*self = self.union(rhs);
}
}
impl core::fmt::Display for TransportCaps {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.is_empty() {
return f.write_str("NONE");
}
let mut first = true;
for (bit, name) in [
(Self::FD_PASSING, "FD_PASSING"),
(Self::TRUSTED_UID, "TRUSTED_UID"),
(Self::CALLBACKS, "CALLBACKS"),
(Self::SAME_HOST, "SAME_HOST"),
(Self::KERNEL_KNOBS, "KERNEL_KNOBS"),
] {
if self.contains(bit) {
if !first {
f.write_str("|")?;
}
f.write_str(name)?;
first = false;
}
}
Ok(())
}
}
impl core::fmt::Debug for TransportCaps {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "TransportCaps({self})")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_algebra() {
let unix =
TransportCaps::FD_PASSING | TransportCaps::TRUSTED_UID | TransportCaps::SAME_HOST;
assert!(unix.contains(TransportCaps::FD_PASSING));
assert!(unix.contains(TransportCaps::FD_PASSING | TransportCaps::SAME_HOST));
assert!(!unix.contains(TransportCaps::CALLBACKS));
assert!(!unix.contains(TransportCaps::KERNEL));
assert!(unix.contains(TransportCaps::NONE));
assert!(TransportCaps::NONE.contains(TransportCaps::NONE));
assert!(TransportCaps::NONE.is_empty());
assert!(!unix.is_empty());
assert_eq!(
unix.difference(TransportCaps::FD_PASSING),
TransportCaps::TRUSTED_UID | TransportCaps::SAME_HOST
);
assert_eq!(
TransportCaps::KERNEL,
TransportCaps::FD_PASSING
| TransportCaps::TRUSTED_UID
| TransportCaps::CALLBACKS
| TransportCaps::SAME_HOST
| TransportCaps::KERNEL_KNOBS
);
assert_eq!(TransportCaps::default(), TransportCaps::NONE);
let mut acc = TransportCaps::NONE;
acc |= TransportCaps::CALLBACKS;
assert_eq!(acc, TransportCaps::CALLBACKS);
}
#[test]
fn require_reports_only_what_is_missing() {
let tls = TransportCaps::NONE;
assert_eq!(
tls.require(TransportCaps::CALLBACKS, "streaming sink"),
Err(StatusCode::InvalidOperation)
);
assert_eq!(
TransportCaps::KERNEL.require(TransportCaps::KERNEL, "x"),
Ok(())
);
let unix = TransportCaps::FD_PASSING | TransportCaps::TRUSTED_UID;
assert_eq!(unix.require(TransportCaps::FD_PASSING, "a pipe"), Ok(()));
assert_eq!(
unix.require(
TransportCaps::FD_PASSING | TransportCaps::CALLBACKS,
"a pipe handed to a callback"
),
Err(StatusCode::InvalidOperation)
);
assert_eq!(tls.require(TransportCaps::NONE, "nothing"), Ok(()));
}
#[test]
fn display_names_the_bits() {
assert_eq!(TransportCaps::NONE.to_string(), "NONE");
assert_eq!(
TransportCaps::KERNEL.to_string(),
"FD_PASSING|TRUSTED_UID|CALLBACKS|SAME_HOST|KERNEL_KNOBS"
);
assert_eq!(
(TransportCaps::TRUSTED_UID | TransportCaps::SAME_HOST).to_string(),
"TRUSTED_UID|SAME_HOST"
);
assert_eq!(
format!("{:?}", TransportCaps::CALLBACKS),
"TransportCaps(CALLBACKS)"
);
}
}