1use std::io::{self, ErrorKind, Read};
5
6pub const MAX_REQUEST_LEN: usize = 0xffff;
9
10pub const SYNC_MAX_CHUNK: usize = 64 * 1024;
12
13pub const SYNC_MAX_PATH: usize = 1024;
15
16pub mod shell_id {
18 pub const STDIN: u8 = 0;
19 pub const STDOUT: u8 = 1;
20 pub const STDERR: u8 = 2;
21 pub const EXIT: u8 = 3;
22 pub const CLOSE_STDIN: u8 = 4;
23}
24
25const S_IFREG: u32 = 0o100000;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30pub enum Status {
31 Okay,
32 Fail(String),
33}
34
35fn invalid(message: String) -> io::Error {
36 io::Error::new(ErrorKind::InvalidData, message)
37}
38
39pub fn encode_request(service: &str) -> Option<Vec<u8>> {
42 (service.len() <= MAX_REQUEST_LEN)
43 .then(|| format!("{:04x}{service}", service.len()).into_bytes())
44}
45
46pub fn read_status<R: Read>(r: &mut R) -> io::Result<Status> {
49 let mut id = [0u8; 4];
50 r.read_exact(&mut id)?;
51 match &id {
52 b"OKAY" => Ok(Status::Okay),
53 b"FAIL" => {
54 let message = read_hex_block(r)?;
55 Ok(Status::Fail(String::from_utf8_lossy(&message).into_owned()))
56 }
57 other => Err(invalid(format!(
58 "expected OKAY or FAIL, got {:?}",
59 String::from_utf8_lossy(other)
60 ))),
61 }
62}
63
64pub fn read_hex_block<R: Read>(r: &mut R) -> io::Result<Vec<u8>> {
66 let mut hex = [0u8; 4];
67 r.read_exact(&mut hex)?;
68 let len = std::str::from_utf8(&hex)
69 .ok()
70 .filter(|s| s.bytes().all(|b| b.is_ascii_hexdigit()))
71 .and_then(|s| usize::from_str_radix(s, 16).ok())
72 .ok_or_else(|| {
73 invalid(format!(
74 "bad hex length {:?}",
75 String::from_utf8_lossy(&hex)
76 ))
77 })?;
78 let mut body = vec![0; len];
79 r.read_exact(&mut body)?;
80 Ok(body)
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
85pub enum ShellPacket {
86 Stdout(Vec<u8>),
87 Stderr(Vec<u8>),
88 Exit(u8),
90}
91
92#[derive(Debug, Clone, Default, PartialEq, Eq)]
94pub struct ShellOutput {
95 pub stdout: Vec<u8>,
96 pub stderr: Vec<u8>,
97 pub exit: Option<u8>,
99}
100
101impl ShellOutput {
102 pub fn success(&self) -> bool {
104 self.exit == Some(0)
105 }
106
107 pub fn stdout_text(&self) -> String {
109 String::from_utf8_lossy(&self.stdout).into_owned()
110 }
111
112 pub fn stderr_text(&self) -> String {
114 String::from_utf8_lossy(&self.stderr).into_owned()
115 }
116}
117
118pub fn shell_packet(id: u8, data: &[u8]) -> Vec<u8> {
121 let len = u32::try_from(data.len()).expect("a shell packet fits in u32");
122 let mut out = Vec::with_capacity(5 + data.len());
123 out.push(id);
124 out.extend_from_slice(&len.to_le_bytes());
125 out.extend_from_slice(data);
126 out
127}
128
129pub fn read_shell_packet<R: Read>(r: &mut R) -> io::Result<Option<ShellPacket>> {
133 loop {
134 let mut header = [0u8; 5];
135 let mut got = 0;
136 while got < header.len() {
137 match r.read(&mut header[got..]) {
138 Ok(0) if got == 0 => return Ok(None),
139 Ok(0) => {
140 return Err(io::Error::new(
141 ErrorKind::UnexpectedEof,
142 "stream ended inside a shell v2 header",
143 ));
144 }
145 Ok(n) => got += n,
146 Err(e) if e.kind() == ErrorKind::Interrupted => {}
147 Err(e) => return Err(e),
148 }
149 }
150 let len = u32::from_le_bytes([header[1], header[2], header[3], header[4]]) as usize;
151 let mut data = vec![0; len];
152 r.read_exact(&mut data)?;
153 match header[0] {
154 shell_id::STDOUT => return Ok(Some(ShellPacket::Stdout(data))),
155 shell_id::STDERR => return Ok(Some(ShellPacket::Stderr(data))),
156 shell_id::EXIT => {
157 let status = data
158 .first()
159 .copied()
160 .ok_or_else(|| invalid("empty shell v2 exit packet".into()))?;
161 return Ok(Some(ShellPacket::Exit(status)));
162 }
163 _ => {}
164 }
165 }
166}
167
168pub fn read_shell_output<R: Read>(r: &mut R) -> io::Result<ShellOutput> {
170 let mut out = ShellOutput::default();
171 while let Some(packet) = read_shell_packet(r)? {
172 match packet {
173 ShellPacket::Stdout(data) => out.stdout.extend_from_slice(&data),
174 ShellPacket::Stderr(data) => out.stderr.extend_from_slice(&data),
175 ShellPacket::Exit(status) => {
176 out.exit = Some(status);
177 break;
178 }
179 }
180 }
181 Ok(out)
182}
183
184pub fn sync_request(id: &[u8; 4], arg: &[u8]) -> Vec<u8> {
186 let len = u32::try_from(arg.len()).expect("a sync argument fits in u32");
187 let mut out = Vec::with_capacity(8 + arg.len());
188 out.extend_from_slice(id);
189 out.extend_from_slice(&len.to_le_bytes());
190 out.extend_from_slice(arg);
191 out
192}
193
194pub fn send_arg(remote: &str, mode: u32) -> String {
197 format!("{remote},{}", S_IFREG | (mode & 0o7777))
198}
199
200pub fn push_data_chunk(buf: &mut Vec<u8>, data: &[u8]) {
206 assert!(
207 data.len() <= SYNC_MAX_CHUNK,
208 "sync DATA chunks are at most 64 KiB, got {} bytes",
209 data.len()
210 );
211 buf.extend_from_slice(b"DATA");
212 buf.extend_from_slice(&(data.len() as u32).to_le_bytes());
213 buf.extend_from_slice(data);
214}
215
216pub fn push_done(buf: &mut Vec<u8>, mtime: u32) {
218 buf.extend_from_slice(b"DONE");
219 buf.extend_from_slice(&mtime.to_le_bytes());
220}
221
222pub fn read_sync_status<R: Read>(r: &mut R) -> io::Result<Result<(), String>> {
225 let mut msg = [0u8; 8];
226 r.read_exact(&mut msg)?;
227 let len = u32::from_le_bytes([msg[4], msg[5], msg[6], msg[7]]) as usize;
228 match &msg[..4] {
229 b"OKAY" => Ok(Ok(())),
230 b"FAIL" => {
231 let mut text = vec![0; len];
232 r.read_exact(&mut text)?;
233 Ok(Err(String::from_utf8_lossy(&text).into_owned()))
234 }
235 other => Err(invalid(format!(
236 "expected a sync OKAY or FAIL, got {:?}",
237 String::from_utf8_lossy(other)
238 ))),
239 }
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
244pub struct Rule {
245 pub host: String,
247 pub device: String,
249}
250
251fn three_fields(line: &str) -> Option<[&str; 3]> {
253 let mut fields = line.split_whitespace();
254 let parsed = [fields.next()?, fields.next()?, fields.next()?];
255 fields.next().is_none().then_some(parsed)
256}
257
258pub fn parse_forward_listing(listing: &str, serial: &str) -> Vec<Rule> {
261 listing
262 .lines()
263 .filter_map(three_fields)
264 .filter(|[s, _, _]| *s == serial)
265 .map(|[_, host, device]| Rule {
266 host: host.to_owned(),
267 device: device.to_owned(),
268 })
269 .collect()
270}
271
272pub fn parse_reverse_listing(listing: &str) -> Vec<Rule> {
275 listing
276 .lines()
277 .filter_map(three_fields)
278 .map(|[_, device, host]| Rule {
279 host: host.to_owned(),
280 device: device.to_owned(),
281 })
282 .collect()
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288 use std::io::{Cursor, ErrorKind};
289
290 #[test]
291 fn requests_carry_a_four_digit_lowercase_hex_length() {
292 assert_eq!(
293 encode_request("host:devices-l").unwrap(),
294 b"000ehost:devices-l"
295 );
296 assert_eq!(
297 encode_request("host:transport:emulator-5554").unwrap(),
298 b"001chost:transport:emulator-5554"
299 );
300 assert_eq!(encode_request("").unwrap(), b"0000");
301 }
302
303 #[test]
304 fn a_request_longer_than_four_hex_digits_allow_is_refused() {
305 let longest = "x".repeat(MAX_REQUEST_LEN);
306 assert_eq!(encode_request(&longest).unwrap()[..4], *b"ffff");
307 assert_eq!(encode_request(&"x".repeat(MAX_REQUEST_LEN + 1)), None);
308 }
309
310 #[test]
311 fn okay_fail_and_garbage_statuses() {
312 assert_eq!(
313 read_status(&mut Cursor::new(b"OKAY".to_vec())).unwrap(),
314 Status::Okay
315 );
316 assert_eq!(
317 read_status(&mut Cursor::new(b"FAIL000edevice offline".to_vec())).unwrap(),
318 Status::Fail("device offline".into())
319 );
320 assert_eq!(
321 read_status(&mut Cursor::new(b"FAIL0006closed".to_vec())).unwrap(),
322 Status::Fail("closed".into())
323 );
324 let garbage = read_status(&mut Cursor::new(b"WHAT".to_vec())).unwrap_err();
325 assert_eq!(garbage.kind(), ErrorKind::InvalidData);
326 let short = read_status(&mut Cursor::new(b"OK".to_vec())).unwrap_err();
327 assert_eq!(short.kind(), ErrorKind::UnexpectedEof);
328 }
329
330 #[test]
331 fn hex_length_blocks() {
332 assert_eq!(
333 read_hex_block(&mut Cursor::new(b"0005hello".to_vec())).unwrap(),
334 b"hello"
335 );
336 assert_eq!(
337 read_hex_block(&mut Cursor::new(b"0000".to_vec())).unwrap(),
338 b""
339 );
340 assert_eq!(
341 read_hex_block(&mut Cursor::new(b"000Ahelloworld".to_vec())).unwrap(),
342 b"helloworld"
343 );
344 let bad = read_hex_block(&mut Cursor::new(b"zzzz".to_vec())).unwrap_err();
345 assert_eq!(bad.kind(), ErrorKind::InvalidData);
346 let short = read_hex_block(&mut Cursor::new(b"0005hel".to_vec())).unwrap_err();
347 assert_eq!(short.kind(), ErrorKind::UnexpectedEof);
348 }
349
350 #[test]
351 fn shell_packets_carry_the_id_and_a_little_endian_length() {
352 assert_eq!(
353 shell_packet(shell_id::STDIN, b"abc"),
354 [0, 3, 0, 0, 0, b'a', b'b', b'c']
355 );
356 assert_eq!(shell_packet(shell_id::CLOSE_STDIN, b""), [4, 0, 0, 0, 0]);
357 }
358
359 #[test]
360 fn shell_v2_separates_streams_and_keeps_the_exit_status() {
361 let bytes = [
362 shell_packet(shell_id::STDOUT, b"ok\n"),
363 shell_packet(shell_id::STDERR, b"warn"),
364 shell_packet(shell_id::STDOUT, b"more"),
365 shell_packet(shell_id::EXIT, &[7]),
366 ]
367 .concat();
368 let out = read_shell_output(&mut Cursor::new(bytes)).unwrap();
369 assert_eq!(out.stdout, b"ok\nmore");
370 assert_eq!(out.stderr, b"warn");
371 assert_eq!(out.exit, Some(7));
372 assert!(!out.success());
373 }
374
375 #[test]
376 fn packets_read_one_at_a_time_end_cleanly() {
377 let bytes = [
378 shell_packet(shell_id::STDOUT, b"a"),
379 shell_packet(shell_id::EXIT, &[0]),
380 ]
381 .concat();
382 let mut r = Cursor::new(bytes);
383 assert_eq!(
384 read_shell_packet(&mut r).unwrap(),
385 Some(ShellPacket::Stdout(b"a".to_vec()))
386 );
387 assert_eq!(
388 read_shell_packet(&mut r).unwrap(),
389 Some(ShellPacket::Exit(0))
390 );
391 assert_eq!(read_shell_packet(&mut r).unwrap(), None);
392 }
393
394 #[test]
395 fn shell_v2_without_an_exit_packet_has_no_status() {
396 let out =
397 read_shell_output(&mut Cursor::new(shell_packet(shell_id::STDOUT, b"ok"))).unwrap();
398 assert_eq!(out.stdout, b"ok");
399 assert_eq!(out.exit, None);
400 assert!(!out.success());
401 }
402
403 #[test]
404 fn a_truncated_shell_v2_packet_is_unexpected_eof() {
405 let header_only = read_shell_output(&mut Cursor::new(vec![1, 10, 0])).unwrap_err();
406 assert_eq!(header_only.kind(), ErrorKind::UnexpectedEof);
407 let short_body =
408 read_shell_output(&mut Cursor::new(vec![1, 10, 0, 0, 0, b'o'])).unwrap_err();
409 assert_eq!(short_body.kind(), ErrorKind::UnexpectedEof);
410 }
411
412 #[test]
413 fn unknown_shell_packet_ids_are_skipped() {
414 let bytes = [
415 shell_packet(5, b"24x80"),
416 shell_packet(shell_id::STDOUT, b"ok"),
417 shell_packet(shell_id::EXIT, &[0]),
418 ]
419 .concat();
420 let out = read_shell_output(&mut Cursor::new(bytes)).unwrap();
421 assert_eq!(out.stdout, b"ok");
422 assert!(out.success());
423 }
424
425 #[test]
426 fn sync_messages_use_little_endian_lengths() {
427 assert_eq!(
428 sync_request(b"SEND", b"/data/local/tmp/x,33188"),
429 [
430 b"SEND".as_slice(),
431 &23u32.to_le_bytes(),
432 b"/data/local/tmp/x,33188"
433 ]
434 .concat()
435 );
436 let mut buf = b"prefix".to_vec();
437 push_data_chunk(&mut buf, b"abc");
438 push_done(&mut buf, 0x0102_0304);
439 assert_eq!(
440 buf,
441 [
442 b"prefix".as_slice(),
443 b"DATA",
444 &3u32.to_le_bytes(),
445 b"abc",
446 b"DONE",
447 &[4, 3, 2, 1]
448 ]
449 .concat()
450 );
451 }
452
453 #[test]
454 fn a_full_chunk_is_allowed() {
455 let mut buf = Vec::new();
456 push_data_chunk(&mut buf, &vec![0; SYNC_MAX_CHUNK]);
457 assert_eq!(buf.len(), 8 + SYNC_MAX_CHUNK);
458 assert_eq!(buf[4..8], (SYNC_MAX_CHUNK as u32).to_le_bytes());
459 }
460
461 #[test]
462 #[should_panic(expected = "64 KiB")]
463 fn a_chunk_over_64_kib_is_refused() {
464 push_data_chunk(&mut Vec::new(), &vec![0; SYNC_MAX_CHUNK + 1]);
465 }
466
467 #[test]
468 fn the_send_mode_marks_a_regular_file() {
469 assert_eq!(
470 send_arg("/data/local/tmp/a.jar", 0o644),
471 "/data/local/tmp/a.jar,33188"
472 );
473 assert_eq!(
474 send_arg("/data/local/tmp/run", 0o755),
475 "/data/local/tmp/run,33261"
476 );
477 assert_eq!(send_arg("/x", 0o170644), "/x,33188");
479 }
480
481 #[test]
482 fn sync_status_okay_and_fail() {
483 let okay = [b"OKAY".as_slice(), &[0; 4]].concat();
484 assert_eq!(read_sync_status(&mut Cursor::new(okay)).unwrap(), Ok(()));
485 let fail = [b"FAIL".as_slice(), &6u32.to_le_bytes(), b"denied"].concat();
486 assert_eq!(
487 read_sync_status(&mut Cursor::new(fail)).unwrap(),
488 Err("denied".to_owned())
489 );
490 let other = [b"DATA".as_slice(), &[0; 4]].concat();
491 let err = read_sync_status(&mut Cursor::new(other)).unwrap_err();
492 assert_eq!(err.kind(), ErrorKind::InvalidData);
493 }
494
495 #[test]
496 fn forward_listing_keeps_this_serials_rules() {
497 let listing = "emulator-5554 tcp:40123 localabstract:mobile-agent\n\
498 emulator-5556 tcp:40124 localabstract:other\n\
499 emulator-5554 tcp:8081 tcp:8081\n";
500 assert_eq!(
501 parse_forward_listing(listing, "emulator-5554"),
502 [
503 Rule {
504 host: "tcp:40123".into(),
505 device: "localabstract:mobile-agent".into()
506 },
507 Rule {
508 host: "tcp:8081".into(),
509 device: "tcp:8081".into()
510 },
511 ]
512 );
513 assert!(parse_forward_listing(listing, "R58M123ABC").is_empty());
514 assert!(parse_forward_listing("", "emulator-5554").is_empty());
515 }
516
517 #[test]
518 fn reverse_listing_reads_the_device_side_first() {
519 let listing =
520 "host-16 localabstract:s6r_1 tcp:5000\nhost-16 tcp:8081 tcp:8081\nmalformed\n";
521 assert_eq!(
522 parse_reverse_listing(listing),
523 [
524 Rule {
525 host: "tcp:5000".into(),
526 device: "localabstract:s6r_1".into()
527 },
528 Rule {
529 host: "tcp:8081".into(),
530 device: "tcp:8081".into()
531 },
532 ]
533 );
534 assert!(parse_reverse_listing("").is_empty());
535 }
536}