Skip to main content

android/agent/
connection.rs

1//! One connection to an agent: the protocol crate's `HostSession` driven over
2//! the agent's socket (an adb service stream to `localabstract:<socket>`).
3//!
4//! A reader thread feeds the session what arrives, answers pings, acts on
5//! deadlines and liveness, hands responses to the callers waiting for them
6//! and every other session event to an [`EventSink`]. Events that arrive
7//! before a sink is set are kept and replayed to it, so a warning the agent
8//! sends right after its hello is not lost. Requests may come from any
9//! thread; each waits for its own response.
10
11use std::collections::{HashMap, HashSet, VecDeque};
12use std::io::{ErrorKind, Read, Write};
13use std::net::{Shutdown, TcpStream};
14use std::sync::{Arc, Condvar, Mutex, MutexGuard};
15use std::thread::{self, JoinHandle};
16use std::time::{Duration, Instant};
17
18use protocol::Error;
19use protocol::message::{Call, Reply};
20use protocol::session::{
21    DEADLINE_GRACE, DisconnectReason, HostConfig, HostEvent, HostSession, Negotiated,
22};
23
24/// Receives the session's events other than responses, with the host time
25/// each arrived: protocol events, sequence gaps and resyncs, and finally
26/// `Closed`.
27pub type EventSink = Box<dyn FnMut(Instant, HostEvent) + Send>;
28
29/// The longest the reader blocks in one read, so that deadlines of requests
30/// sent meanwhile are acted on within this bound.
31const MAX_READ_WAIT: Duration = Duration::from_millis(100);
32
33/// How long a caller waits past a request's deadline and the session's
34/// grace period before giving up on its own.
35const WAIT_MARGIN: Duration = Duration::from_secs(1);
36
37/// A live connection to one agent. Dropping it closes the connection; the
38/// agent keeps running.
39pub struct Connection {
40    shared: Arc<Shared>,
41    reader: Option<JoinHandle<()>>,
42}
43
44struct Shared {
45    state: Mutex<State>,
46    changed: Condvar,
47    /// Taken after `state` when both are held, so events reach the sink in
48    /// the order the session produced them.
49    sink: Mutex<Sink>,
50    /// The socket, for closing it from any thread.
51    socket: TcpStream,
52}
53
54struct State {
55    session: HostSession,
56    writer: TcpStream,
57    responses: HashMap<u32, Result<Reply, Error>>,
58    /// Requests whose callers stopped waiting; their answers are dropped.
59    abandoned: HashSet<u32>,
60    negotiated: Option<Negotiated>,
61    closed: Option<DisconnectReason>,
62}
63
64#[derive(Default)]
65struct Sink {
66    sink: Option<EventSink>,
67    buffered: VecDeque<(Instant, HostEvent)>,
68}
69
70impl State {
71    /// Writes what the session has queued; a failed write ends the session.
72    fn flush(&mut self, now: Instant) {
73        while let Some(bytes) = self.session.poll_transmit() {
74            if self.writer.write_all(&bytes).is_err() {
75                self.session.receive_eof(now);
76            }
77        }
78    }
79
80    /// Takes the session's events: responses to their callers, the handshake
81    /// and the close into the state, and the rest for the sink. Returns
82    /// whether a waiting caller may have something new.
83    fn collect(&mut self, now: Instant, events: &mut Vec<(Instant, HostEvent)>) -> bool {
84        let mut changed = false;
85        while let Some(event) = self.session.poll_event() {
86            changed = true;
87            match event {
88                HostEvent::Ready(negotiated) => self.negotiated = Some(negotiated),
89                HostEvent::Response { id, result, .. } => {
90                    if !self.abandoned.remove(&id) {
91                        self.responses.insert(id, result);
92                    }
93                }
94                HostEvent::Closed(reason) => {
95                    self.closed = Some(reason.clone());
96                    events.push((now, HostEvent::Closed(reason)));
97                }
98                other => events.push((now, other)),
99            }
100        }
101        changed
102    }
103}
104
105impl Shared {
106    fn state(&self) -> MutexGuard<'_, State> {
107        // The session is only ever changed through complete calls, so a
108        // panic elsewhere leaves it consistent.
109        self.state.lock().unwrap_or_else(|e| e.into_inner())
110    }
111
112    fn sink(&self) -> MutexGuard<'_, Sink> {
113        self.sink.lock().unwrap_or_else(|e| e.into_inner())
114    }
115
116    /// Hands what the session produced to waiting callers and the sink,
117    /// keeping the session's order: the sink is locked before `state` is
118    /// released.
119    fn settle(&self, mut state: MutexGuard<'_, State>, now: Instant) {
120        state.flush(now);
121        let mut events = Vec::new();
122        if state.collect(now, &mut events) {
123            self.changed.notify_all();
124        }
125        if events.is_empty() {
126            return;
127        }
128        let mut sink = self.sink();
129        drop(state);
130        match &mut sink.sink {
131            Some(deliver) => {
132                for (at, event) in events {
133                    deliver(at, event);
134                }
135            }
136            None => sink.buffered.extend(events),
137        }
138    }
139}
140
141impl Connection {
142    /// Starts a host session over `stream`, which the agent speaks first on.
143    pub fn open(stream: TcpStream, config: HostConfig) -> std::io::Result<Connection> {
144        let _ = stream.set_nodelay(true);
145        let writer = stream.try_clone()?;
146        let reading = stream.try_clone()?;
147        let shared = Arc::new(Shared {
148            state: Mutex::new(State {
149                session: HostSession::new(config, Instant::now()),
150                writer,
151                responses: HashMap::new(),
152                abandoned: HashSet::new(),
153                negotiated: None,
154                closed: None,
155            }),
156            changed: Condvar::new(),
157            sink: Mutex::new(Sink::default()),
158            socket: stream,
159        });
160        let for_reader = Arc::clone(&shared);
161        let reader = thread::Builder::new()
162            .name("android-agent-connection".into())
163            .spawn(move || read_loop(&for_reader, reading))?;
164        Ok(Connection {
165            shared,
166            reader: Some(reader),
167        })
168    }
169
170    /// Waits until the handshake completes, or fails with the reason the
171    /// session closed: a refused hello (another token, no common protocol
172    /// version), silence past the peer timeout, or the end of the stream.
173    /// `timeout` caps the wait; past it the connection is closed and the
174    /// failure is silence.
175    pub fn handshake(&self, timeout: Duration) -> Result<Negotiated, DisconnectReason> {
176        let limit = Instant::now() + timeout;
177        let mut state = self.shared.state();
178        loop {
179            if let Some(negotiated) = &state.negotiated {
180                return Ok(negotiated.clone());
181            }
182            if let Some(reason) = &state.closed {
183                return Err(reason.clone());
184            }
185            let now = Instant::now();
186            if now >= limit {
187                drop(state);
188                self.close();
189                return Err(DisconnectReason::Silent(timeout));
190            }
191            state = self
192                .shared
193                .changed
194                .wait_timeout(state, limit - now)
195                .unwrap_or_else(|e| e.into_inner())
196                .0;
197        }
198    }
199
200    /// What the handshake settled, once it has.
201    pub fn negotiated(&self) -> Option<Negotiated> {
202        self.shared.state().negotiated.clone()
203    }
204
205    /// Sends `call` with `deadline` and waits for its answer: the agent's, or
206    /// the session's own `deadline_exceeded` or `disconnected` error.
207    pub fn call(&self, call: Call, deadline: Duration) -> Result<Reply, Error> {
208        let method = call.method();
209        let now = Instant::now();
210        let mut state = self.shared.state();
211        if let Some(reason) = &state.closed {
212            return Err(Error::disconnected(reason));
213        }
214        let id = state.session.send_request(call, Some(deadline), now)?;
215        self.shared.settle(state, now);
216        let limit = now + deadline + DEADLINE_GRACE + WAIT_MARGIN;
217        let mut state = self.shared.state();
218        loop {
219            if let Some(result) = state.responses.remove(&id) {
220                return result;
221            }
222            if let Some(reason) = &state.closed {
223                return Err(Error::disconnected(reason));
224            }
225            let now = Instant::now();
226            if now >= limit {
227                state.abandoned.insert(id);
228                let ms = u32::try_from(deadline.as_millis()).unwrap_or(u32::MAX);
229                return Err(Error::deadline_exceeded(method, id, ms));
230            }
231            state = self
232                .shared
233                .changed
234                .wait_timeout(state, limit - now)
235                .unwrap_or_else(|e| e.into_inner())
236                .0;
237        }
238    }
239
240    /// Asks the agent to resynchronize `stream`; the outcome reaches the
241    /// sink as `Resynced` or `ResyncFailed`.
242    pub fn resync(&self, stream: protocol::event::Stream) -> Result<u32, Error> {
243        let now = Instant::now();
244        let mut state = self.shared.state();
245        if let Some(reason) = &state.closed {
246            return Err(Error::disconnected(reason));
247        }
248        let id = state.session.resync(stream, now)?;
249        self.shared.settle(state, now);
250        Ok(id)
251    }
252
253    /// Sets where events go from now on, after replaying those that arrived
254    /// while no sink was set.
255    pub fn set_sink(&self, mut sink: EventSink) {
256        let mut slot = self.shared.sink();
257        for (at, event) in slot.buffered.drain(..) {
258            sink(at, event);
259        }
260        slot.sink = Some(sink);
261    }
262
263    /// Why the connection closed, once it has.
264    pub fn close_reason(&self) -> Option<DisconnectReason> {
265        self.shared.state().closed.clone()
266    }
267
268    pub fn is_closed(&self) -> bool {
269        self.close_reason().is_some()
270    }
271
272    /// Closes the connection from this side, failing pending calls.
273    pub fn close(&self) {
274        let mut state = self.shared.state();
275        if state.closed.is_none() {
276            state.session.close();
277        }
278        self.shared.settle(state, Instant::now());
279        let _ = self.shared.socket.shutdown(Shutdown::Both);
280    }
281}
282
283impl Drop for Connection {
284    fn drop(&mut self) {
285        self.close();
286        if let Some(reader) = self.reader.take() {
287            let _ = reader.join();
288        }
289    }
290}
291
292/// Feeds the session what arrives until it closes.
293fn read_loop(shared: &Shared, mut stream: TcpStream) {
294    let mut buf = vec![0u8; 256 * 1024];
295    loop {
296        let wait = {
297            let state = shared.state();
298            if state.closed.is_some() {
299                return;
300            }
301            let now = Instant::now();
302            state
303                .session
304                .poll_timeout()
305                .map_or(MAX_READ_WAIT, |due| due.saturating_duration_since(now))
306                .clamp(Duration::from_millis(1), MAX_READ_WAIT)
307        };
308        let read = match stream.set_read_timeout(Some(wait)) {
309            Ok(()) => stream.read(&mut buf),
310            // macOS refuses socket options once the peer has reset it.
311            Err(e) => Err(e),
312        };
313        let now = Instant::now();
314        let mut state = shared.state();
315        match read {
316            Ok(0) => state.session.receive_eof(now),
317            Ok(n) => state.session.receive(&buf[..n], now),
318            Err(e)
319                if matches!(
320                    e.kind(),
321                    ErrorKind::WouldBlock | ErrorKind::TimedOut | ErrorKind::Interrupted
322                ) => {}
323            Err(_) => state.session.receive_eof(now),
324        }
325        if state.session.poll_timeout().is_some_and(|due| due <= now) {
326            state.session.handle_timeout(now);
327        }
328        shared.settle(state, now);
329    }
330}
331
332#[cfg(test)]
333mod tests {
334    use super::*;
335    use crate::fake_agent::{FakeAgent, config};
336    use protocol::code;
337    use protocol::event::{EventPayload, Lifecycle, LifecycleKind, TreeChange, TreeChangeKind};
338    use protocol::message::{InputAck, TapParams};
339    use protocol::session::HandshakeError;
340    use std::sync::mpsc;
341
342    const WAIT: Duration = Duration::from_secs(5);
343
344    fn host(token: Option<&str>) -> HostConfig {
345        HostConfig {
346            token: token.map(str::to_owned),
347            ..HostConfig::default()
348        }
349    }
350
351    fn tap() -> Call {
352        Call::Tap(TapParams {
353            x: 10,
354            y: 20,
355            duration_ms: None,
356        })
357    }
358
359    #[test]
360    fn the_handshake_completes_with_the_token_the_agent_was_started_with() {
361        let agent = FakeAgent::start(config(Some("t0k3n")), Box::new(|_| None));
362        let connection = Connection::open(agent.connect(), host(Some("t0k3n"))).unwrap();
363        let negotiated = connection.handshake(WAIT).unwrap();
364        assert_eq!(negotiated.version, 1);
365        assert_eq!(negotiated.agent, "mobile-agent-android 0.1.0");
366        assert_eq!(connection.negotiated(), Some(negotiated));
367        agent.wait_for(WAIT, |log| log.ready);
368    }
369
370    #[test]
371    fn a_hello_with_another_token_is_refused() {
372        let agent = FakeAgent::start(config(Some("other")), Box::new(|_| None));
373        let connection = Connection::open(agent.connect(), host(Some("t0k3n"))).unwrap();
374        assert_eq!(
375            connection.handshake(WAIT),
376            Err(DisconnectReason::Handshake(HandshakeError::TokenMismatch))
377        );
378        assert!(connection.is_closed());
379    }
380
381    /// The agent exits when a host refuses its version, so that the host can
382    /// start the version it needs.
383    #[test]
384    fn an_agent_speaking_only_an_older_protocol_is_refused_with_version_mismatch() {
385        let mut old = config(Some("t"));
386        old.versions = vec![0];
387        let agent = FakeAgent::start(old, Box::new(|_| None));
388        let connection = Connection::open(agent.connect(), host(Some("t"))).unwrap();
389        assert_eq!(
390            connection.handshake(WAIT),
391            Err(DisconnectReason::Handshake(
392                HandshakeError::NoCommonVersion {
393                    host: vec![1],
394                    agent: vec![0],
395                }
396            ))
397        );
398        agent.wait_for(WAIT, |log| log.closed.is_some());
399        match &agent.log().closed {
400            Some(DisconnectReason::Handshake(HandshakeError::Refused(error))) => {
401                assert_eq!(error.code, code::VERSION_MISMATCH)
402            }
403            other => panic!("the agent saw {other:?}"),
404        }
405    }
406
407    #[test]
408    fn a_call_returns_the_agents_reply_and_carries_its_deadline() {
409        let agent = FakeAgent::start(
410            config(None),
411            Box::new(|_| Some(Ok(Reply::Input(InputAck { accepted: true })))),
412        );
413        let connection = Connection::open(agent.connect(), host(None)).unwrap();
414        connection.handshake(WAIT).unwrap();
415        let reply = connection.call(tap(), Duration::from_millis(1500)).unwrap();
416        assert_eq!(reply, Reply::Input(InputAck { accepted: true }));
417        let log = agent.log();
418        assert_eq!(log.requests.len(), 1);
419        assert_eq!(log.requests[0].call, tap());
420        assert_eq!(log.requests[0].deadline_ms, Some(1500));
421    }
422
423    #[test]
424    fn calls_from_several_threads_each_get_their_own_answer() {
425        let agent = FakeAgent::start(
426            config(None),
427            Box::new(|request| {
428                let accepted = matches!(&request.call, Call::Tap(t) if t.x % 2 == 0);
429                Some(Ok(Reply::Input(InputAck { accepted })))
430            }),
431        );
432        let connection = Arc::new(Connection::open(agent.connect(), host(None)).unwrap());
433        connection.handshake(WAIT).unwrap();
434        let workers: Vec<_> = (0..8)
435            .map(|i| {
436                let connection = Arc::clone(&connection);
437                std::thread::spawn(move || {
438                    let call = Call::Tap(TapParams {
439                        x: i,
440                        y: 0,
441                        duration_ms: None,
442                    });
443                    (i, connection.call(call, Duration::from_secs(2)).unwrap())
444                })
445            })
446            .collect();
447        for worker in workers {
448            let (i, reply) = worker.join().unwrap();
449            assert_eq!(
450                reply,
451                Reply::Input(InputAck {
452                    accepted: i % 2 == 0
453                })
454            );
455        }
456    }
457
458    #[test]
459    fn events_reach_the_sink_in_order_including_those_before_it_was_set() {
460        let agent = FakeAgent::start(config(None), Box::new(|_| None));
461        let connection = Connection::open(agent.connect(), host(None)).unwrap();
462        connection.handshake(WAIT).unwrap();
463        agent.emit(EventPayload::Lifecycle(Lifecycle {
464            change: LifecycleKind::WARNING,
465            message: "input injection is unavailable".into(),
466        }));
467        // Give the warning time to arrive before the sink exists.
468        std::thread::sleep(Duration::from_millis(100));
469        let (tx, rx) = mpsc::channel();
470        connection.set_sink(Box::new(move |_, event| {
471            let _ = tx.send(event);
472        }));
473        agent.emit(EventPayload::Tree(TreeChange {
474            change: TreeChangeKind::TEXT,
475            window: 7,
476            path: vec![0, 2],
477        }));
478        let first = rx.recv_timeout(WAIT).unwrap();
479        let second = rx.recv_timeout(WAIT).unwrap();
480        match (first, second) {
481            (HostEvent::Event(a), HostEvent::Event(b)) => {
482                assert!(matches!(a.payload, EventPayload::Lifecycle(_)), "{a:?}");
483                assert_eq!(a.seq, 1);
484                assert!(matches!(b.payload, EventPayload::Tree(_)), "{b:?}");
485                assert_eq!(b.seq, 1);
486            }
487            other => panic!("unexpected events {other:?}"),
488        }
489    }
490
491    #[test]
492    fn the_end_of_the_stream_fails_pending_calls_and_reports_the_close() {
493        let agent = FakeAgent::start(config(None), Box::new(|_| None));
494        let connection = Arc::new(Connection::open(agent.connect(), host(None)).unwrap());
495        connection.handshake(WAIT).unwrap();
496        let (tx, rx) = mpsc::channel();
497        connection.set_sink(Box::new(move |_, event| {
498            let _ = tx.send(event);
499        }));
500        let caller = {
501            let connection = Arc::clone(&connection);
502            std::thread::spawn(move || connection.call(tap(), Duration::from_secs(30)))
503        };
504        agent.wait_for(WAIT, |log| log.requests.len() == 1);
505        let closed_at = Instant::now();
506        agent.close();
507        let error = caller.join().unwrap().unwrap_err();
508        assert_eq!(error.code, code::DISCONNECTED);
509        assert!(error.message.contains("reconnect"), "{}", error.message);
510        assert!(closed_at.elapsed() < Duration::from_secs(1));
511        assert_eq!(connection.close_reason(), Some(DisconnectReason::Eof));
512        let closed = rx.recv_timeout(WAIT).unwrap();
513        assert_eq!(closed, HostEvent::Closed(DisconnectReason::Eof));
514        let later = connection.call(tap(), Duration::from_secs(1)).unwrap_err();
515        assert_eq!(later.code, code::DISCONNECTED);
516    }
517
518    #[test]
519    fn the_reader_answers_pings_while_the_host_is_idle() {
520        let mut quick = config(None);
521        quick.ping_interval = Duration::from_millis(50);
522        quick.peer_timeout = Duration::from_millis(300);
523        let agent = FakeAgent::start(quick, Box::new(|_| None));
524        let connection = Connection::open(agent.connect(), host(None)).unwrap();
525        connection.handshake(WAIT).unwrap();
526        std::thread::sleep(Duration::from_millis(900));
527        assert!(
528            agent.log().closed.is_none(),
529            "the agent gave up on a silent host: {:?}",
530            agent.log().closed
531        );
532        assert!(!connection.is_closed());
533    }
534
535    #[test]
536    fn closing_from_this_side_fails_later_calls() {
537        let agent = FakeAgent::start(config(None), Box::new(|_| None));
538        let connection = Connection::open(agent.connect(), host(None)).unwrap();
539        connection.handshake(WAIT).unwrap();
540        connection.close();
541        assert_eq!(connection.close_reason(), Some(DisconnectReason::Closed));
542        let error = connection.call(tap(), Duration::from_secs(1)).unwrap_err();
543        assert_eq!(error.code, code::DISCONNECTED);
544        agent.wait_for(WAIT, |log| log.closed.is_some());
545    }
546}