#![cfg_attr(
feature = "tokio",
doc = "[`Sink::send_async`] and [`Sink::end_async`]."
)]
#![cfg_attr(
not(feature = "tokio"),
doc = "`Sink::send_async` and `Sink::end_async` (`tokio` feature)."
)]
use std::marker::PhantomData;
use std::sync::Arc;
use std::time::{Duration, Instant};
use crate::binder::Interface;
use crate::error::{Result, StatusCode};
use crate::parcel::Parcel;
use crate::parcelable::{Deserialize, Serialize};
use crate::status::{BinderResult, ExceptionCode, Status};
use crate::SIBinder;
mod generated {
include!(concat!(env!("OUT_DIR"), "/stream.rs"));
}
mod calls;
#[cfg(feature = "tokio")]
mod pool;
mod ring;
pub use generated::rsbinder::stream::StreamEndpoint::StreamEndpoint;
impl<T> StreamEndpoint<T> {
pub fn cast<U>(self) -> StreamEndpoint<U> {
StreamEndpoint {
ring: self.ring,
sink: self.sink,
_phantom_T: PhantomData,
}
}
pub fn try_clone(&self) -> Result<Self> {
let ring = match &self.ring {
Some(ring) => Some(crate::fmq::Descriptor::try_from(ring)?.try_into()?),
None => None,
};
Ok(StreamEndpoint {
ring,
sink: self.sink.clone(),
_phantom_T: PhantomData,
})
}
fn with(
ring: Option<crate::fmq::MQDescriptor<i8, crate::fmq::SynchronizedReadWrite>>,
sink: SIBinder,
) -> Self {
StreamEndpoint {
ring,
sink: Some(sink),
_phantom_T: PhantomData,
}
}
}
pub const END_RESERVE: usize = 256;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum RingUse {
#[default]
KernelOnly,
AlsoUnixRpc,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ReceiverPolicy {
pub ring_bytes: usize,
pub credit_window: u32,
pub max_opening: u32,
pub ping: PingPolicy,
pub ring_use: RingUse,
}
impl Default for ReceiverPolicy {
fn default() -> Self {
ReceiverPolicy {
ring_bytes: 64 * 1024,
credit_window: 4,
max_opening: 4,
ping: PingPolicy::default(),
ring_use: RingUse::default(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SinkPolicy {
pub max_ring_bytes: usize,
pub max_batch_bytes: usize,
pub initial_credits: u32,
pub send_timeout: Option<Duration>,
pub ping: PingPolicy,
}
impl Default for SinkPolicy {
fn default() -> Self {
SinkPolicy {
max_ring_bytes: 4 * 1024 * 1024,
max_batch_bytes: 16 * 1024,
initial_credits: 4,
send_timeout: None,
ping: PingPolicy::default(),
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PingPolicy {
#[default]
Inherit,
Off,
}
pub struct Token {
binder: SIBinder,
}
struct TokenObject;
impl crate::Remotable for TokenObject {
fn descriptor() -> &'static str {
"rsbinder.stream.Token"
}
fn on_transact(
&self,
_code: crate::TransactionCode,
_reader: &mut Parcel,
_reply: &mut Parcel,
) -> Result<()> {
Err(StatusCode::UnknownTransaction)
}
fn on_dump(&self, _writer: &mut dyn std::io::Write, _args: &[String]) -> Result<()> {
Ok(())
}
}
impl Token {
pub fn new() -> Self {
Token {
binder: crate::native::Binder::new(TokenObject).as_binder(),
}
}
pub fn binder(&self) -> SIBinder {
self.binder.clone()
}
}
impl Default for Token {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Token").finish()
}
}
fn watch_death<R>(binder: &SIBinder, recipient: R) -> Result<Option<Arc<dyn crate::DeathRecipient>>>
where
R: crate::DeathRecipient + 'static,
{
if binder.as_remote().is_none() {
return Ok(None);
}
let recipient: Arc<dyn crate::DeathRecipient> = Arc::new(recipient);
if let Err(e) = binder.link_to_death(Arc::downgrade(&recipient)) {
if e == StatusCode::InvalidOperation && binder.as_remote().is_some() {
log::error!(
"stream: the RPC session to the other end has no incoming connections, so the \
stream could neither be pushed to nor see that end die; open them \
(ClientOptions::incoming_connections / RpcClientConfig::incoming_connections)"
);
} else {
log::error!("stream: cannot watch the peer for death: {e:?}");
}
return Err(e);
}
Ok(Some(recipient))
}
fn unlink_death(binder: &SIBinder, recipient: &Option<Arc<dyn crate::DeathRecipient>>) {
let Some(recipient) = recipient else { return };
let unlinked = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = binder.unlink_to_death(Arc::downgrade(recipient));
}));
if unlinked.is_err() {
log::error!("stream: unlink_to_death panicked; a death link stays registered");
}
}
fn peer_caps(binder: &SIBinder) -> crate::TransportCaps {
#[cfg(feature = "rpc")]
if let Some(proxy) = (**binder).as_any().downcast_ref::<crate::rpc::RpcProxy>() {
return proxy.session_caps();
}
#[cfg(not(feature = "rpc"))]
let _ = binder;
crate::TransportCaps::KERNEL
}
#[cfg(feature = "rpc")]
const MIN_PING_INTERVAL: Duration = Duration::from_millis(1);
struct Ping {
peer: SIBinder,
every: Duration,
}
impl Ping {
fn to(peer: &SIBinder, policy: PingPolicy) -> Option<Ping> {
if policy == PingPolicy::Off {
return None;
}
#[cfg(feature = "rpc")]
if let Some(proxy) = (**peer).as_any().downcast_ref::<crate::rpc::RpcProxy>() {
let every = (proxy.session_timeout()? / 3).max(MIN_PING_INTERVAL);
return Some(Ping {
peer: peer.clone(),
every,
});
}
#[cfg(not(feature = "rpc"))]
let _ = peer;
None
}
fn send(&self) {
match self.peer.ping_binder() {
Ok(()) | Err(StatusCode::DeadObject) => {}
Err(e) => log::warn!("stream: pinging the peer failed: {e:?}"),
}
}
}
const RING_OVER_RPC: crate::TransportCaps = crate::TransportCaps::SAME_HOST
.union(crate::TransportCaps::FD_PASSING)
.union(crate::TransportCaps::CALLBACKS);
fn over_ring(peer: &SIBinder, policy: &ReceiverPolicy) -> bool {
let opted_in = policy.ring_use == RingUse::AlsoUnixRpc;
if !cfg!(any(target_os = "linux", target_os = "android")) {
if opted_in {
log::debug!("stream: RingUse::AlsoUnixRpc, but this platform has no ring");
}
return false;
}
let caps = peer_caps(peer);
if caps.contains(crate::TransportCaps::KERNEL_KNOBS) {
return true;
}
if !opted_in {
return false;
}
if caps.contains(RING_OVER_RPC) {
return true;
}
log::debug!(
"stream: RingUse::AlsoUnixRpc, but the peer's session lacks {}; no ring",
RING_OVER_RPC.difference(caps)
);
false
}
fn call_deadline(timeout: Option<Duration>) -> Option<Instant> {
timeout.and_then(|timeout| Instant::now().checked_add(timeout))
}
fn encode_item<T: Serialize + ?Sized>(scratch: &mut Parcel, item: &T) -> Result<()> {
scratch.set_data_size(0)?;
scratch.write(item)
}
fn decode_item<T: Deserialize>(parcel: &mut Parcel, bytes: &[u8]) -> Result<T> {
parcel.refill_data_only(bytes);
let item = parcel.read::<T>()?;
let left = parcel.data_avail();
if left != 0 {
log::error!(
"stream: {left} bytes left after one item; the two ends disagree on the item type"
);
return Err(StatusCode::BadValue);
}
Ok(item)
}
const UNCARRIABLE_TERMINATOR: &str =
"the stream's producer ended with a status that cannot be carried";
fn truncated_terminator(exception: i32, lost: i32) -> Option<String> {
if lost <= 0 || exception != ExceptionCode::None as i32 {
return None;
}
Some(format!(
"the stream's producer could not deliver its last batch; \
{lost} queued item(s) were not sent"
))
}
fn dropped_terminator(lost: i32) -> String {
format!(
"the stream's producer dropped its sink without ending the stream; \
{lost} queued item(s) were not sent"
)
}
fn status_fields(status: &Status) -> Result<(i32, i32, Option<String>)> {
let exception = status.exception_code();
match exception {
ExceptionCode::TransactionFailed
| ExceptionCode::HasNotedAppOpsReplyHeader
| ExceptionCode::HasReplyHeader
| ExceptionCode::JustError => {
log::error!("Sink::end: {exception} cannot be carried as a stream terminator");
Err(StatusCode::BadValue)
}
ExceptionCode::ServiceSpecific => Ok((
exception as i32,
status.service_specific_error(),
status.message().map(str::to_owned),
)),
_ => Ok((exception as i32, 0, status.message().map(str::to_owned))),
}
}
fn status_from_fields(
exception: i32,
service_specific: i32,
message: Option<&str>,
) -> Result<Status> {
let code = exception_from_i32(exception)?;
Ok(match (code, message) {
(ExceptionCode::ServiceSpecific, _) => {
Status::new_service_specific_error(service_specific, message.map(str::to_owned))
}
(code, None) => Status::from(code),
(code, Some(message)) => Status::from((code, message)),
})
}
fn exception_from_i32(exception: i32) -> Result<ExceptionCode> {
Ok(match exception {
0 => ExceptionCode::None,
-1 => ExceptionCode::Security,
-2 => ExceptionCode::BadParcelable,
-3 => ExceptionCode::IllegalArgument,
-4 => ExceptionCode::NullPointer,
-5 => ExceptionCode::IllegalState,
-6 => ExceptionCode::NetworkMainThread,
-7 => ExceptionCode::UnsupportedOperation,
-8 => ExceptionCode::ServiceSpecific,
-9 => ExceptionCode::Parcelable,
other => {
log::error!("stream: {other} is not a valid stream terminator exception code");
return Err(StatusCode::BadValue);
}
})
}
enum SinkInner<T: ?Sized> {
Calls(Box<calls::Producer<T>>),
Ring(ring::Producer<T>),
}
pub struct Sink<T: ?Sized> {
inner: SinkInner<T>,
send_timeout: Option<Duration>,
}
impl<T: Serialize> Sink<T> {
pub fn open(endpoint: &StreamEndpoint<T>) -> Result<Self> {
Self::open_with(endpoint, &SinkPolicy::default())
}
pub fn open_with(endpoint: &StreamEndpoint<T>, policy: &SinkPolicy) -> Result<Self> {
Self::open_borrowed(endpoint, policy)
}
}
impl<T: Serialize + ?Sized> Sink<T> {
pub fn open_borrowed<E: std::borrow::Borrow<T>>(
endpoint: &StreamEndpoint<E>,
policy: &SinkPolicy,
) -> Result<Self> {
let Some(sink) = endpoint.sink.as_ref() else {
log::error!("Sink::open: the endpoint has no sink");
return Err(StatusCode::BadValue);
};
let inner = match &endpoint.ring {
Some(ring) => SinkInner::Ring(ring::Producer::open(ring, sink, policy)?),
None => SinkInner::Calls(Box::new(calls::Producer::open(sink, policy)?)),
};
Ok(Sink {
inner,
send_timeout: policy.send_timeout,
})
}
pub fn send(&mut self, item: &T) -> Result<()> {
let deadline = call_deadline(self.send_timeout);
self.send_until(item, deadline)
}
fn send_until(&mut self, item: &T, deadline: Option<Instant>) -> Result<()> {
match &mut self.inner {
SinkInner::Calls(p) => p.send(item, deadline),
SinkInner::Ring(p) => p.send(item, deadline),
}
}
pub fn send_all<'a, I>(&mut self, items: I) -> Result<()>
where
I: IntoIterator<Item = &'a T>,
T: 'a,
{
let deadline = call_deadline(self.send_timeout);
for item in items {
self.send_until(item, deadline)?;
}
Ok(())
}
pub fn flush(&mut self) -> Result<()> {
let deadline = call_deadline(self.send_timeout);
match &mut self.inner {
SinkInner::Calls(p) => p.flush(deadline),
SinkInner::Ring(p) => p.flush(deadline),
}
}
pub fn end(self) -> Result<()> {
self.terminate(ExceptionCode::None as i32, 0, None)
}
pub fn end_with(self, status: &Status) -> Result<()> {
match status_fields(status) {
Ok((exception, service_specific, message)) => {
self.terminate(exception, service_specific, message.as_deref())
}
Err(e) => {
let _ = self.terminate(
ExceptionCode::IllegalArgument as i32,
0,
Some(UNCARRIABLE_TERMINATOR),
);
Err(e)
}
}
}
fn terminate(self, exception: i32, service_specific: i32, message: Option<&str>) -> Result<()> {
let deadline = call_deadline(self.send_timeout);
match self.inner {
SinkInner::Calls(p) => p.terminate(deadline, exception, service_specific, message),
SinkInner::Ring(p) => p.terminate(deadline, exception, service_specific, message),
}
}
#[cfg(feature = "tokio")]
#[must_use = "the item is only written or sent when this is awaited"]
pub fn send_async(
&mut self,
item: &T,
) -> impl std::future::Future<Output = Result<()>> + Send + '_ {
let timeout = self.send_timeout;
let send = match &mut self.inner {
SinkInner::Calls(p) => pool::Either::A(p.send_async(item, timeout)),
SinkInner::Ring(p) => pool::Either::B(p.send_async(item, timeout)),
};
send.run()
}
#[cfg(feature = "tokio")]
pub async fn flush_async(&mut self) -> Result<()> {
let deadline = call_deadline(self.send_timeout);
match &mut self.inner {
SinkInner::Calls(p) => p.flush_async(deadline).await,
SinkInner::Ring(p) => p.flush_async().await,
}
}
#[cfg(feature = "tokio")]
pub async fn end_async(self) -> Result<()> {
self.terminate_async(ExceptionCode::None as i32, 0, None)
.await
}
#[cfg(feature = "tokio")]
pub async fn end_with_async(self, status: &Status) -> Result<()> {
match status_fields(status) {
Ok((exception, service_specific, message)) => {
self.terminate_async(exception, service_specific, message)
.await
}
Err(e) => {
let _ = self
.terminate_async(
ExceptionCode::IllegalArgument as i32,
0,
Some(UNCARRIABLE_TERMINATOR.to_owned()),
)
.await;
Err(e)
}
}
}
#[cfg(feature = "tokio")]
async fn terminate_async(
self,
exception: i32,
service_specific: i32,
message: Option<String>,
) -> Result<()> {
let deadline = call_deadline(self.send_timeout);
match self.inner {
SinkInner::Calls(p) => {
p.terminate_async(deadline, exception, service_specific, message)
.await
}
SinkInner::Ring(p) => {
p.terminate_async(exception, service_specific, message)
.await
}
}
}
}
impl<T: ?Sized> Sink<T> {
pub fn is_canceled(&self) -> bool {
match &self.inner {
SinkInner::Calls(p) => p.is_canceled(),
SinkInner::Ring(p) => p.is_canceled(),
}
}
pub fn pending(&self) -> usize {
match &self.inner {
SinkInner::Calls(p) => p.pending(),
SinkInner::Ring(p) => p.pending(),
}
}
}
impl<T: ?Sized> std::fmt::Debug for Sink<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Sink")
.field(
"transport",
&match &self.inner {
SinkInner::Calls(_) => "rpc",
SinkInner::Ring(_) => "ring",
},
)
.field("canceled", &self.is_canceled())
.field("pending", &self.pending())
.finish()
}
}
enum ReceiverInner<T> {
Calls(calls::Consumer<T>),
Ring(ring::Consumer<T>),
}
pub struct Receiver<T> {
inner: ReceiverInner<T>,
peer: SIBinder,
death: Option<Arc<dyn crate::DeathRecipient>>,
_item: PhantomData<fn() -> T>,
}
impl<T: Deserialize> Receiver<T> {
pub fn new(peer: &SIBinder) -> Result<(Self, StreamEndpoint<T>)> {
Self::with_policy(peer, &ReceiverPolicy::default())
}
pub fn with_policy(
peer: &SIBinder,
policy: &ReceiverPolicy,
) -> Result<(Self, StreamEndpoint<T>)> {
let (inner, ring, sink, death) = if over_ring(peer, policy) {
let (consumer, ring, sink) = ring::Consumer::new(policy)?;
consumer.set_peer(peer, policy.ping);
let death = watch_death(peer, consumer.death_recipient())?;
(ReceiverInner::Ring(consumer), Some(ring), sink, death)
} else {
let (consumer, sink) = calls::Consumer::new(policy);
let death = watch_death(peer, consumer.death_recipient())?;
(ReceiverInner::Calls(consumer), None, sink, death)
};
let endpoint = StreamEndpoint::with(ring, sink);
let receiver = Receiver {
inner,
peer: peer.clone(),
death,
_item: PhantomData,
};
Ok((receiver, endpoint))
}
pub fn endpoint(&self) -> Result<StreamEndpoint<T>> {
let (ring, sink) = match &self.inner {
ReceiverInner::Calls(c) => (None, c.sink_binder()),
ReceiverInner::Ring(c) => (Some(c.ring()?), c.sink_binder()),
};
Ok(StreamEndpoint::with(ring, sink))
}
pub fn recv(&mut self) -> Option<BinderResult<T>> {
match &mut self.inner {
ReceiverInner::Calls(c) => c.recv(),
ReceiverInner::Ring(c) => c.recv(),
}
}
pub fn try_recv(&mut self) -> BinderResult<Option<T>> {
match &mut self.inner {
ReceiverInner::Calls(c) => c.try_recv(),
ReceiverInner::Ring(c) => c.try_recv(),
}
}
pub fn recv_timeout(&mut self, timeout: Duration) -> BinderResult<Option<T>> {
match &mut self.inner {
ReceiverInner::Calls(c) => c.recv_timeout(timeout),
ReceiverInner::Ring(c) => c.recv_timeout(timeout),
}
}
#[cfg(feature = "tokio")]
pub async fn recv_async(&mut self) -> Option<BinderResult<T>> {
match &mut self.inner {
ReceiverInner::Calls(c) => c.recv_async().await,
ReceiverInner::Ring(c) => c.recv_async().await,
}
}
pub fn uses_ring(&self) -> bool {
matches!(self.inner, ReceiverInner::Ring(_))
}
pub fn is_finished(&self) -> bool {
match &self.inner {
ReceiverInner::Calls(c) => c.is_finished(),
ReceiverInner::Ring(c) => c.is_finished(),
}
}
pub fn end_status(&self) -> Option<Status> {
match &self.inner {
ReceiverInner::Calls(c) => c.end_status(),
ReceiverInner::Ring(c) => c.end_status(),
}
}
pub fn cancel(&self) -> Result<()> {
match &self.inner {
ReceiverInner::Calls(c) => c.cancel(),
ReceiverInner::Ring(c) => c.cancel(),
}
}
}
impl<T: Deserialize> Iterator for Receiver<T> {
type Item = BinderResult<T>;
fn next(&mut self) -> Option<Self::Item> {
self.recv()
}
}
impl<T> Drop for Receiver<T> {
fn drop(&mut self) {
let death = self.death.take();
unlink_death(&self.peer, &death);
}
}
impl<T> std::fmt::Debug for Receiver<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut s = f.debug_struct("Receiver");
match &self.inner {
ReceiverInner::Calls(c) => s
.field("transport", &"rpc")
.field("finished", &c.is_finished())
.field("started", &c.started()),
ReceiverInner::Ring(c) => s
.field("transport", &"ring")
.field("finished", &c.is_finished()),
};
s.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::mpsc;
use std::thread;
fn pair<T: Serialize + Deserialize>() -> (Sink<T>, Receiver<T>) {
let peer = Token::new().binder();
let (rx, endpoint) = Receiver::<T>::new(&peer).expect("a receiver");
let sink = Sink::<T>::open(&endpoint).expect("open");
(sink, rx)
}
#[test]
fn a_reused_parcel_is_neither_lost_nor_left_with_the_last_item() {
let mut scratch = Parcel::new_data_only();
encode_item(&mut scratch, &vec![9u8; 300]).expect("encode");
let long = scratch.as_bytes().expect("bytes").to_vec();
encode_item(&mut scratch, &7i32).expect("encode over it");
let short = scratch.as_bytes().expect("bytes").to_vec();
assert_eq!(short.len(), 4, "nothing of the earlier item is left");
let mut parcel = Parcel::new_data_only();
assert_eq!(
decode_item::<Vec<u8>>(&mut parcel, &long),
Ok(vec![9u8; 300])
);
let grown = parcel.capacity();
assert_eq!(decode_item::<i32>(&mut parcel, &short), Ok(7));
assert_eq!(parcel.capacity(), grown, "the allocation is kept");
assert_eq!(
decode_item::<i64>(&mut parcel, &short).err(),
Some(StatusCode::NotEnoughData)
);
let mut leftover = short.clone();
leftover.extend_from_slice(&[0; 4]);
assert_eq!(
decode_item::<i32>(&mut parcel, &leftover).err(),
Some(StatusCode::BadValue)
);
assert_eq!(
decode_item::<i32>(&mut parcel, &short),
Ok(7),
"a refused record leaves the parcel usable"
);
}
#[test]
fn a_local_peer_gets_a_ring_endpoint_on_linux() {
let peer = Token::new().binder();
let (rx, endpoint) = Receiver::<i32>::new(&peer).expect("a receiver");
assert!(endpoint.sink.is_some());
assert_eq!(
endpoint.ring.is_some(),
cfg!(any(target_os = "linux", target_os = "android"))
);
assert!(!rx.is_finished());
let again = rx.endpoint().expect("endpoint");
assert_eq!(again.ring.is_some(), endpoint.ring.is_some());
}
#[test]
fn items_arrive_in_order_and_the_stream_ends_clean() {
let (mut sink, mut rx) = pair::<i32>();
let sent: Vec<i32> = (0..10).collect();
sink.send_all(sent.iter()).expect("send_all");
sink.end().expect("end");
let got: Vec<i32> = (&mut rx).map(|item| item.expect("item")).collect();
assert_eq!(got, sent);
assert!(rx.is_finished());
assert!(rx.end_status().expect("a terminator arrived").is_ok());
}
#[test]
fn a_cast_endpoint_carries_a_type_that_encodes_alike() {
#[derive(Debug, PartialEq)]
struct Celsius(i32);
impl Deserialize for Celsius {
fn deserialize(parcel: &mut Parcel) -> Result<Self> {
parcel.read::<i32>().map(Celsius)
}
}
let peer = Token::new().binder();
let (mut rx, endpoint) = Receiver::<Celsius>::new(&peer).expect("a receiver");
let endpoint: StreamEndpoint<i32> = endpoint.cast();
let mut sink = Sink::open(&endpoint).expect("open");
sink.send_all([20, 21].iter()).expect("send_all");
sink.end().expect("end");
let got: Vec<Celsius> = (&mut rx).map(|item| item.expect("item")).collect();
assert_eq!(got, [Celsius(20), Celsius(21)]);
}
#[test]
fn a_borrowed_endpoint_casts_through_try_clone() {
struct Celsius(i32);
impl Serialize for Celsius {
fn serialize(&self, parcel: &mut Parcel) -> Result<()> {
parcel.write(&self.0)
}
}
fn handler(endpoint: &StreamEndpoint<i32>) -> Result<Sink<Celsius>> {
Sink::open(&endpoint.try_clone()?.cast())
}
let peer = Token::new().binder();
let (mut rx, endpoint) = Receiver::<i32>::new(&peer).expect("a receiver");
let mut sink = handler(&endpoint).expect("open");
drop(endpoint);
sink.send_all([Celsius(20), Celsius(21)].iter())
.expect("send_all");
sink.end().expect("end");
let got: Vec<i32> = (&mut rx).map(|item| item.expect("item")).collect();
assert_eq!(got, [20, 21]);
}
#[test]
fn a_borrowed_item_type_opens_through_open_borrowed() {
let peer = Token::new().binder();
let (mut rx, endpoint) = Receiver::<String>::new(&peer).expect("a receiver");
let mut sink = Sink::<str>::open_borrowed(&endpoint, &SinkPolicy::default()).expect("open");
drop(endpoint);
for line in "one\ntwo".lines() {
sink.send(line).expect("send");
}
sink.end().expect("end");
let got: Vec<String> = (&mut rx).map(|item| item.expect("item")).collect();
assert_eq!(got, ["one", "two"]);
}
#[test]
fn an_endpoint_formats_whatever_its_item_type() {
struct Opaque;
let printed = format!("{:?}", StreamEndpoint::<Opaque>::default());
assert!(printed.starts_with("StreamEndpoint"), "{printed}");
assert!(!printed.contains("_phantom"), "{printed}");
}
#[test]
fn a_service_specific_failure_survives_the_terminator() {
let (mut sink, mut rx) = pair::<i32>();
sink.send(&7).expect("send");
sink.end_with(&Status::new_service_specific_error(
42,
Some("quota exhausted".to_string()),
))
.expect("end_with");
assert_eq!(rx.next().expect("an item").expect("item"), 7);
let failure = rx.next().expect("the failure").expect_err("an error");
assert_eq!(failure.exception_code(), ExceptionCode::ServiceSpecific);
assert_eq!(failure.service_specific_error(), 42);
assert_eq!(failure.message(), Some("quota exhausted"));
assert!(
rx.next().is_none(),
"the failure is reported once and the stream is then over"
);
}
#[test]
fn a_terminator_that_cannot_be_carried_is_refused() {
let (sink, mut rx) = pair::<i32>();
assert_eq!(
sink.end_with(&Status::from(ExceptionCode::TransactionFailed))
.err(),
Some(StatusCode::BadValue)
);
let ended = rx.next().expect("a terminator").expect_err("an error");
assert_eq!(ended.exception_code(), ExceptionCode::IllegalArgument);
assert_eq!(ended.message(), Some(UNCARRIABLE_TERMINATOR));
}
#[test]
fn a_dropped_sink_ends_the_stream_as_failed() {
let (mut sink, mut rx) = pair::<i32>();
sink.send(&1).expect("send");
sink.send(&2).expect("send");
drop(sink);
assert_eq!(rx.next().expect("item 1").expect("ok"), 1);
assert_eq!(rx.next().expect("item 2").expect("ok"), 2);
let failure = rx.next().expect("a terminator").expect_err("an error");
assert_eq!(failure.exception_code(), ExceptionCode::IllegalState);
assert!(rx.next().is_none(), "the stream is over");
}
#[test]
fn a_cancel_releases_a_parked_producer_and_the_next_send_is_refused() {
let peer = Token::new().binder();
let (rx, endpoint) = Receiver::<i32>::with_policy(
&peer,
&ReceiverPolicy {
ring_bytes: 512,
credit_window: 1,
max_opening: 1,
..ReceiverPolicy::default()
},
)
.expect("a receiver");
let mut sink = Sink::<i32>::open_with(
&endpoint,
&SinkPolicy {
max_batch_bytes: 4,
initial_credits: 1,
..SinkPolicy::default()
},
)
.expect("open");
let (outcome, watch) = mpsc::channel();
let producer = thread::spawn(move || {
let parked = loop {
if let Err(e) = sink.send(&0) {
break e;
}
};
let _ = outcome.send((parked, sink.is_canceled()));
});
assert!(
watch.recv_timeout(Duration::from_millis(300)).is_err(),
"the producer must be parked"
);
rx.cancel().expect("cancel");
let (parked, canceled) = watch
.recv_timeout(Duration::from_secs(5))
.expect("cancel must release the producer");
assert_eq!(parked, StatusCode::InvalidOperation);
assert!(canceled);
producer.join().expect("producer");
}
#[test]
fn an_endpoint_without_a_sink_is_refused() {
let endpoint = StreamEndpoint::default();
assert_eq!(
Sink::<i32>::open(&endpoint).err(),
Some(StatusCode::BadValue)
);
}
#[test]
fn a_token_answers_no_transaction() {
let token = Token::new();
let binder = token.binder();
assert!(binder.as_remote().is_none(), "a local object");
assert_eq!(binder.descriptor(), "rsbinder.stream.Token");
assert_eq!(
SIBinder::downgrade(&token.binder()),
SIBinder::downgrade(&binder)
);
}
#[test]
fn a_status_the_producer_accepts_is_one_the_consumer_reads() {
for code in [
ExceptionCode::None,
ExceptionCode::Security,
ExceptionCode::BadParcelable,
ExceptionCode::IllegalArgument,
ExceptionCode::NullPointer,
ExceptionCode::IllegalState,
ExceptionCode::NetworkMainThread,
ExceptionCode::UnsupportedOperation,
ExceptionCode::ServiceSpecific,
ExceptionCode::Parcelable,
ExceptionCode::HasNotedAppOpsReplyHeader,
ExceptionCode::HasReplyHeader,
ExceptionCode::TransactionFailed,
ExceptionCode::JustError,
] {
let Ok((exception, service_specific, message)) = status_fields(&Status::from(code))
else {
continue;
};
assert!(
status_from_fields(exception, service_specific, message.as_deref()).is_ok(),
"{code:?} is sent and cannot be read"
);
}
}
#[test]
fn an_exception_code_the_wire_may_not_carry_is_refused() {
for exception in [-127, -128, -129, 7] {
assert_eq!(
status_from_fields(exception, 0, None).err(),
Some(StatusCode::BadValue),
"{exception} must not decode into a status"
);
}
}
#[test]
fn a_status_round_trips_through_the_terminator_arguments() {
for status in [
Status::from(ExceptionCode::None),
Status::from((ExceptionCode::Security, "denied")),
Status::new_service_specific_error(9, Some("busy".to_string())),
] {
let (exception, service_specific, message) =
status_fields(&status).expect("a carryable status");
let back = status_from_fields(exception, service_specific, message.as_deref())
.expect("decode");
assert_eq!(back, status);
assert_eq!(back.message(), status.message());
assert_eq!(
back.service_specific_error(),
status.service_specific_error()
);
}
}
fn timed_policy(timeout: Duration) -> SinkPolicy {
SinkPolicy {
send_timeout: Some(timeout),
..SinkPolicy::default()
}
}
fn bounded<S: Send + 'static, R: Send + 'static>(
subject: S,
call: impl FnOnce(&mut S) -> R + Send + 'static,
) -> (R, Duration, S) {
let (done, watch) = mpsc::channel();
thread::spawn(move || {
let mut subject = subject;
let before = Instant::now();
let outcome = call(&mut subject);
let _ = done.send((outcome, before.elapsed(), subject));
});
watch
.recv_timeout(Duration::from_secs(5))
.expect("the timeout must end the wait")
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn full_ring(policy: &SinkPolicy) -> (Sink<i32>, Receiver<i32>, i32) {
let peer = Token::new().binder();
let (rx, endpoint) = Receiver::<i32>::with_policy(
&peer,
&ReceiverPolicy {
ring_bytes: 512,
..ReceiverPolicy::default()
},
)
.expect("a receiver");
assert!(endpoint.ring.is_some());
let mut sink = Sink::<i32>::open_with(&endpoint, policy).expect("open");
let fill = ((512 - END_RESERVE) / 8) as i32;
for item in 0..fill {
sink.send(&item).expect("room for it");
}
(sink, rx, fill)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn a_send_timeout_ends_a_send_on_a_full_ring_and_the_stream_goes_on() {
let timeout = Duration::from_millis(50);
let (sink, mut rx, fill) = full_ring(&timed_policy(timeout));
let (sent, elapsed, mut sink) = bounded(sink, |sink| sink.send(&-1));
assert_eq!(sent.err(), Some(StatusCode::TimedOut));
assert!(elapsed >= timeout, "returned after {elapsed:?}");
assert!(!sink.is_canceled());
for expected in 0..fill {
assert_eq!(rx.try_recv().expect("no error"), Some(expected));
}
assert_eq!(
rx.try_recv().expect("no error"),
None,
"the item is not in the ring"
);
sink.send(&fill).expect("room again");
sink.end().expect("end");
assert_eq!(rx.recv().expect("the item").expect("ok"), fill);
assert!(rx.recv().is_none());
assert!(
rx.end_status().expect("ended").is_ok(),
"a clean end: nothing was lost"
);
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn a_zero_send_timeout_never_waits_and_send_all_keeps_what_went_in() {
let (sink, mut rx, fill) = full_ring(&timed_policy(Duration::ZERO));
let (sent, _, sink) = bounded(sink, |sink| sink.send(&-1));
assert_eq!(sent.err(), Some(StatusCode::TimedOut));
assert_eq!(rx.try_recv().expect("no error"), Some(0));
let refill: Vec<i32> = (fill..2 * fill - 2).collect();
let (sent, _, sink) = bounded(sink, move |sink| sink.send_all(refill.iter()));
sent.expect("room for these");
let extra = [2 * fill - 2, 2 * fill - 1, 2 * fill];
let (sent, _, sink) = bounded(sink, move |sink| sink.send_all(extra.iter()));
assert_eq!(sent.err(), Some(StatusCode::TimedOut));
sink.end().expect("end");
let rest: Vec<i32> = (&mut rx).map(|item| item.expect("ok")).collect();
assert_eq!(rest, (1..2 * fill).collect::<Vec<_>>());
assert!(rx.end_status().expect("ended").is_ok());
}
fn out_of_credit_pair(policy: SinkPolicy) -> (Sink<i32>, calls::Consumer<i32>) {
let (rx, sink_binder) = calls::Consumer::<i32>::new(&ReceiverPolicy {
credit_window: 1,
max_opening: 4,
..ReceiverPolicy::default()
});
let endpoint = StreamEndpoint::with(None, sink_binder);
let policy = SinkPolicy {
initial_credits: 1,
..policy
};
let mut sink = Sink::<i32>::open_with(&endpoint, &policy).expect("open");
sink.send(&0).expect("the opening credit");
if sink.pending() > 0 {
sink.flush().expect("the opening credit");
}
(sink, rx)
}
#[test]
fn a_send_timeout_ends_a_wait_for_credit_and_the_item_is_not_queued() {
let timeout = Duration::from_millis(50);
let (sink, mut rx) = out_of_credit_pair(SinkPolicy {
max_batch_bytes: 4,
..timed_policy(timeout)
});
let (sent, elapsed, mut sink) = bounded(sink, |sink| sink.send(&1));
assert_eq!(sent.err(), Some(StatusCode::TimedOut));
assert!(elapsed >= timeout, "returned after {elapsed:?}");
assert_eq!(sink.pending(), 0, "the item was taken back out");
assert_eq!(rx.recv().expect("item 0").expect("ok"), 0);
sink.send(&1).expect("a grant came");
sink.end().expect("end");
assert_eq!(rx.recv().expect("item 1").expect("ok"), 1);
assert!(rx.recv().is_none());
assert!(
rx.end_status().expect("ended").is_ok(),
"a clean end: nothing was lost"
);
}
#[test]
fn a_flush_that_times_out_keeps_its_batch_and_an_end_that_does_still_ends() {
let timeout = Duration::from_millis(50);
let (mut sink, mut rx) = out_of_credit_pair(timed_policy(timeout));
sink.send(&1).expect("queued");
let (flushed, elapsed, mut sink) = bounded(sink, |sink| sink.flush());
assert_eq!(flushed.err(), Some(StatusCode::TimedOut));
assert!(elapsed >= timeout, "returned after {elapsed:?}");
assert_eq!(sink.pending(), 1, "the batch stays pending, whole");
assert_eq!(rx.recv().expect("item 0").expect("ok"), 0);
sink.flush().expect("a grant came");
sink.send(&2).expect("queued");
let (ended, elapsed, _) = bounded(Some(sink), |sink| sink.take().expect("sink").end());
assert_eq!(ended.err(), Some(StatusCode::TimedOut));
assert!(elapsed >= timeout, "returned after {elapsed:?}");
assert_eq!(rx.recv().expect("item 1").expect("ok"), 1);
let failure = rx
.recv()
.expect("a terminator")
.expect_err("not a clean end");
assert_eq!(failure.exception_code(), ExceptionCode::IllegalState);
assert!(
failure
.message()
.unwrap_or_default()
.contains("1 queued item"),
"the given-up batch is reported: {failure:?}"
);
}
#[test]
fn an_item_with_a_binder_is_refused() {
let (mut sink, mut rx) = pair::<SIBinder>();
let refused = sink
.send(&Token::new().binder())
.expect_err("no binder in an item");
assert!(
matches!(
refused,
StatusCode::BadType | StatusCode::FdsNotAllowed | StatusCode::InvalidOperation
),
"{refused:?}"
);
sink.end().expect("end");
assert!(rx.next().is_none(), "nothing was written");
}
}