1use 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
24pub type EventSink = Box<dyn FnMut(Instant, HostEvent) + Send>;
28
29const MAX_READ_WAIT: Duration = Duration::from_millis(100);
32
33const WAIT_MARGIN: Duration = Duration::from_secs(1);
36
37pub struct Connection {
40 shared: Arc<Shared>,
41 reader: Option<JoinHandle<()>>,
42}
43
44struct Shared {
45 state: Mutex<State>,
46 changed: Condvar,
47 sink: Mutex<Sink>,
50 socket: TcpStream,
52}
53
54struct State {
55 session: HostSession,
56 writer: TcpStream,
57 responses: HashMap<u32, Result<Reply, Error>>,
58 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 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 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 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 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 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 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 pub fn negotiated(&self) -> Option<Negotiated> {
202 self.shared.state().negotiated.clone()
203 }
204
205 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 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 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 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 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
292fn 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 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 #[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 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}