1use std::collections::HashMap;
14
15use crate::proto::afc::{AfcHeader, AfcOpcode, AFC_MAGIC};
16use bytes::{Bytes, BytesMut};
17use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
18use zerocopy::{FromBytes, IntoBytes};
19
20#[path = "../house_arrest/mod.rs"]
21pub mod house_arrest;
22
23pub mod protocol; #[derive(Debug, thiserror::Error)]
28pub enum AfcError {
29 #[error("IO error: {0}")]
30 Io(#[from] std::io::Error),
31 #[error("AFC error: {0}")]
32 Status(AfcStatusCode),
33 #[error("protocol error: {0}")]
34 Protocol(String),
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum AfcStatusCode {
40 Success,
41 Unknown,
42 OperationHeaderInvalid,
43 NoResources,
44 ReadError,
45 WriteError,
46 UnknownPacketType,
47 InvalidArgument,
48 ObjectNotFound,
49 ObjectIsDir,
50 PermDenied,
51 ServiceNotConnected,
52 Timeout,
53 TooMuchData,
54 EndOfData,
55 OpNotSupported,
56 ObjectExists,
57 ObjectBusy,
58 NoSpaceLeft,
59 OpWouldBlock,
60 IoError,
61 OpInterrupted,
62 OpInProgress,
63 InternalError,
64 MuxError,
65 NoMem,
66 NotEnoughData,
67 DirNotEmpty,
68 Other(u64),
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct AfcFileInfo {
73 pub name: Option<String>,
74 pub file_type: Option<String>,
75 pub size: Option<u64>,
76 pub mode: Option<u32>,
77 pub link_target: Option<String>,
78 pub raw: HashMap<String, String>,
79}
80
81impl AfcStatusCode {
82 pub fn from_u64(code: u64) -> Self {
83 match code {
84 0 => Self::Success,
85 1 => Self::Unknown,
86 2 => Self::OperationHeaderInvalid,
87 3 => Self::NoResources,
88 4 => Self::ReadError,
89 5 => Self::WriteError,
90 6 => Self::UnknownPacketType,
91 7 => Self::InvalidArgument,
92 8 => Self::ObjectNotFound,
93 9 => Self::ObjectIsDir,
94 10 => Self::PermDenied,
95 11 => Self::ServiceNotConnected,
96 12 => Self::Timeout,
97 13 => Self::TooMuchData,
98 14 => Self::EndOfData,
99 15 => Self::OpNotSupported,
100 16 => Self::ObjectExists,
101 17 => Self::ObjectBusy,
102 18 => Self::NoSpaceLeft,
103 19 => Self::OpWouldBlock,
104 20 => Self::IoError,
105 21 => Self::OpInterrupted,
106 22 => Self::OpInProgress,
107 23 => Self::InternalError,
108 30 => Self::MuxError,
109 31 => Self::NoMem,
110 32 => Self::NotEnoughData,
111 33 => Self::DirNotEmpty,
112 _ => Self::Other(code),
113 }
114 }
115}
116
117impl std::fmt::Display for AfcStatusCode {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 match self {
120 Self::Success => write!(f, "success"),
121 Self::Unknown => write!(f, "unknown error (1)"),
122 Self::OperationHeaderInvalid => write!(f, "operation header invalid (2)"),
123 Self::NoResources => write!(f, "no resources (3)"),
124 Self::ReadError => write!(f, "read error (4)"),
125 Self::WriteError => write!(f, "write error (5)"),
126 Self::UnknownPacketType => write!(f, "unknown packet type (6)"),
127 Self::InvalidArgument => write!(f, "invalid argument (7)"),
128 Self::ObjectNotFound => write!(f, "object not found (8)"),
129 Self::ObjectIsDir => write!(f, "object is directory (9)"),
130 Self::PermDenied => write!(f, "permission denied (10)"),
131 Self::ServiceNotConnected => write!(f, "service not connected (11)"),
132 Self::Timeout => write!(f, "timeout (12)"),
133 Self::TooMuchData => write!(f, "too much data (13)"),
134 Self::EndOfData => write!(f, "end of data (14)"),
135 Self::OpNotSupported => write!(f, "operation not supported (15)"),
136 Self::ObjectExists => write!(f, "object exists (16)"),
137 Self::ObjectBusy => write!(f, "object busy (17)"),
138 Self::NoSpaceLeft => write!(f, "no space left (18)"),
139 Self::OpWouldBlock => write!(f, "operation would block (19)"),
140 Self::IoError => write!(f, "I/O error (20)"),
141 Self::OpInterrupted => write!(f, "operation interrupted (21)"),
142 Self::OpInProgress => write!(f, "operation in progress (22)"),
143 Self::InternalError => write!(f, "internal error (23)"),
144 Self::MuxError => write!(f, "mux error (30)"),
145 Self::NoMem => write!(f, "no memory (31)"),
146 Self::NotEnoughData => write!(f, "not enough data (32)"),
147 Self::DirNotEmpty => write!(f, "directory not empty (33)"),
148 Self::Other(code) => write!(f, "unknown status ({code})"),
149 }
150 }
151}
152
153struct Packet {
156 #[allow(dead_code)]
157 opcode: u64,
158 header_payload: Bytes,
161 payload: Bytes,
164}
165
166pub struct AfcClient<S> {
172 stream: S,
173 packet_num: u64,
174}
175
176impl<S: AsyncRead + AsyncWrite + Unpin> AfcClient<S> {
177 pub const FILE_MODE_READ_ONLY: u64 = 0x00000001;
178 pub const FILE_MODE_READ_WRITE: u64 = 0x00000002;
179 pub const FILE_MODE_WRITE_ONLY_CREATE_TRUNC: u64 = 0x00000003;
180 pub const LOCK_EXCLUSIVE: u64 = 2 | 4;
181 pub const LOCK_UNLOCK: u64 = 8 | 4;
182
183 pub fn new(stream: S) -> Self {
184 Self {
187 stream,
188 packet_num: 1,
189 }
190 }
191
192 fn next_pnum(&mut self) -> u64 {
193 let n = self.packet_num;
194 self.packet_num += 1;
195 n
196 }
197
198 async fn send(
201 &mut self,
202 opcode: AfcOpcode,
203 header_payload: &[u8],
204 payload: &[u8],
205 ) -> Result<(), AfcError> {
206 let pnum = self.next_pnum();
207 let hdr = AfcHeader::new(pnum, opcode, header_payload.len(), payload.len());
208 self.stream.write_all(hdr.as_bytes()).await?;
209 if !header_payload.is_empty() {
210 self.stream.write_all(header_payload).await?;
211 }
212 if !payload.is_empty() {
213 self.stream.write_all(payload).await?;
214 }
215 self.stream.flush().await?;
216 Ok(())
217 }
218
219 async fn recv(&mut self) -> Result<Packet, AfcError> {
222 let mut hdr_buf = [0u8; AfcHeader::SIZE];
223 self.stream.read_exact(&mut hdr_buf).await?;
224
225 let hdr = AfcHeader::ref_from_bytes(&hdr_buf)
226 .map_err(|_| AfcError::Protocol("bad AFC header".into()))?;
227
228 if hdr.magic.get() != AFC_MAGIC {
229 return Err(AfcError::Protocol(format!(
230 "bad AFC magic: 0x{:016X}",
231 hdr.magic.get()
232 )));
233 }
234
235 let entire_len = hdr.entire_len.get() as usize;
236 let this_len = hdr.this_len.get() as usize;
237 let opcode = hdr.operation.get();
238
239 let header_payload_len = this_len.saturating_sub(AfcHeader::SIZE);
240 let payload_len = entire_len.saturating_sub(this_len);
241
242 const MAX_AFC_MSG: usize = 256 * 1024 * 1024; if header_payload_len > MAX_AFC_MSG || payload_len > MAX_AFC_MSG {
245 return Err(AfcError::Protocol(format!(
246 "AFC frame too large: header_payload={header_payload_len} payload={payload_len}"
247 )));
248 }
249
250 let mut header_payload = vec![0u8; header_payload_len];
251 let mut payload = vec![0u8; payload_len];
252
253 if header_payload_len > 0 {
254 self.stream.read_exact(&mut header_payload).await?;
255 }
256 if payload_len > 0 {
257 self.stream.read_exact(&mut payload).await?;
258 }
259
260 if opcode == AfcOpcode::Status as u64 {
262 let code = AfcStatusCode::from_u64(if header_payload.len() >= 8 {
263 u64::from_le_bytes(
264 header_payload[..8]
265 .try_into()
266 .map_err(|_| AfcError::Protocol("bad status code".into()))?,
267 )
268 } else {
269 0
270 });
271 if code != AfcStatusCode::Success {
272 return Err(AfcError::Status(code));
273 }
274 }
275
276 Ok(Packet {
277 opcode,
278 header_payload: Bytes::from(header_payload),
279 payload: Bytes::from(payload),
280 })
281 }
282
283 pub async fn list_dir(&mut self, path: &str) -> Result<Vec<String>, AfcError> {
290 let mut hp = path.as_bytes().to_vec();
291 hp.push(0);
292 self.send(AfcOpcode::ReadDir, &hp, &[]).await?;
293 let pkt = self.recv().await?;
294 let entries = split_null_strings(&pkt.payload)
296 .into_iter()
297 .filter(|s| s != "." && s != "..")
298 .collect();
299 Ok(entries)
300 }
301
302 pub async fn stat(&mut self, path: &str) -> Result<HashMap<String, String>, AfcError> {
304 let mut hp = path.as_bytes().to_vec();
305 hp.push(0);
306 self.send(AfcOpcode::GetFileInfo, &hp, &[]).await?;
307 let pkt = self.recv().await?;
308 Ok(parse_kv_pairs(&pkt.payload))
309 }
310
311 pub async fn stat_info(&mut self, path: &str) -> Result<AfcFileInfo, AfcError> {
316 let raw = self.stat(path).await?;
317 Ok(parse_file_info(path, raw))
318 }
319
320 pub async fn make_dir(&mut self, path: &str) -> Result<(), AfcError> {
322 let mut hp = path.as_bytes().to_vec();
323 hp.push(0);
324 self.send(AfcOpcode::MakePath, &hp, &[]).await?;
325 self.recv().await?;
326 Ok(())
327 }
328
329 pub async fn remove(&mut self, path: &str) -> Result<(), AfcError> {
331 let mut hp = path.as_bytes().to_vec();
332 hp.push(0);
333 self.send(AfcOpcode::RemovePath, &hp, &[]).await?;
334 self.recv().await?;
335 Ok(())
336 }
337
338 pub async fn remove_all(&mut self, path: &str) -> Result<(), AfcError> {
340 let mut hp = path.as_bytes().to_vec();
341 hp.push(0);
342 self.send(AfcOpcode::RemovePathAndContents, &hp, &[])
343 .await?;
344 self.recv().await?;
345 Ok(())
346 }
347
348 pub async fn rename(&mut self, from: &str, to: &str) -> Result<(), AfcError> {
350 let mut hp = from.as_bytes().to_vec();
351 hp.push(0);
352 hp.extend_from_slice(to.as_bytes());
353 hp.push(0);
354 self.send(AfcOpcode::RenamePath, &hp, &[]).await?;
355 self.recv().await?;
356 Ok(())
357 }
358
359 pub async fn read_file(&mut self, path: &str) -> Result<Bytes, AfcError> {
361 let fd = self.file_open(path, Self::FILE_MODE_READ_ONLY).await?; let mut data = BytesMut::new();
363 let chunk = 65536u64;
364 loop {
365 let buf = self.file_read(fd, chunk).await?;
366 if buf.is_empty() {
367 break;
368 }
369 data.extend_from_slice(&buf);
370 }
371 self.file_close(fd).await?;
372 Ok(data.freeze())
373 }
374
375 pub async fn read_file_follow_links(&mut self, path: &str) -> Result<Bytes, AfcError> {
381 let target = self.resolve_read_path(path).await?;
382 self.read_file(&target).await
383 }
384
385 pub async fn write_file(&mut self, path: &str, data: &[u8]) -> Result<(), AfcError> {
387 let fd = self
388 .file_open(path, Self::FILE_MODE_WRITE_ONLY_CREATE_TRUNC)
389 .await?; self.file_write(fd, data).await?;
391 self.file_close(fd).await?;
392 Ok(())
393 }
394
395 pub async fn open_file(&mut self, path: &str, mode: u64) -> Result<u64, AfcError> {
396 self.file_open(path, mode).await
397 }
398
399 pub async fn lock_file(&mut self, fd: u64, operation: u64) -> Result<(), AfcError> {
400 let mut hp = [0u8; 16];
401 hp[..8].copy_from_slice(&fd.to_le_bytes());
402 hp[8..].copy_from_slice(&operation.to_le_bytes());
403 self.send(AfcOpcode::FileRefLock, &hp, &[]).await?;
404 self.recv().await?;
405 Ok(())
406 }
407
408 pub async fn close_file(&mut self, fd: u64) -> Result<(), AfcError> {
409 self.file_close(fd).await
410 }
411
412 pub async fn device_info(&mut self) -> Result<HashMap<String, String>, AfcError> {
414 self.send(AfcOpcode::GetDeviceInfo, &[], &[]).await?;
415 let pkt = self.recv().await?;
416 Ok(parse_kv_pairs(&pkt.payload))
417 }
418
419 async fn file_open(&mut self, path: &str, mode: u64) -> Result<u64, AfcError> {
422 let mut hp = vec![0u8; 8];
423 hp[..8].copy_from_slice(&mode.to_le_bytes());
424 hp.extend_from_slice(path.as_bytes());
425 hp.push(0);
426 self.send(AfcOpcode::FileRefOpen, &hp, &[]).await?;
427 let pkt = self.recv().await?;
428 if pkt.header_payload.len() < 8 {
429 return Err(AfcError::Protocol(
430 "FileRefOpenResult: short response".into(),
431 ));
432 }
433 let fd = u64::from_le_bytes(
434 pkt.header_payload[..8]
435 .try_into()
436 .map_err(|_| AfcError::Protocol("bad file handle".into()))?,
437 );
438 Ok(fd)
439 }
440
441 async fn file_read(&mut self, fd: u64, size: u64) -> Result<Bytes, AfcError> {
442 let mut hp = [0u8; 16];
443 hp[..8].copy_from_slice(&fd.to_le_bytes());
444 hp[8..].copy_from_slice(&size.to_le_bytes());
445 self.send(AfcOpcode::FileRefRead, &hp, &[]).await?;
446 let pkt = self.recv().await?;
447 Ok(pkt.payload)
448 }
449
450 async fn file_write(&mut self, fd: u64, data: &[u8]) -> Result<(), AfcError> {
451 let mut hp = [0u8; 8];
452 hp.copy_from_slice(&fd.to_le_bytes());
453 self.send(AfcOpcode::FileRefWrite, &hp, data).await?;
454 self.recv().await?;
455 Ok(())
456 }
457
458 async fn file_close(&mut self, fd: u64) -> Result<(), AfcError> {
459 let hp = fd.to_le_bytes();
460 self.send(AfcOpcode::FileRefClose, &hp, &[]).await?;
461 self.recv().await?;
462 Ok(())
463 }
464
465 async fn resolve_read_path(&mut self, path: &str) -> Result<String, AfcError> {
466 let info = self.stat_info(path).await?;
467 Ok(resolve_link_target(path, &info))
468 }
469}
470
471fn split_null_strings(data: &[u8]) -> Vec<String> {
475 data.split(|&b| b == 0)
476 .filter(|s| !s.is_empty())
477 .map(|s| String::from_utf8_lossy(s).into_owned())
478 .collect()
479}
480
481fn parse_kv_pairs(data: &[u8]) -> HashMap<String, String> {
483 let parts = split_null_strings(data);
484 let mut map = HashMap::new();
485 let mut it = parts.into_iter();
486 while let (Some(k), Some(v)) = (it.next(), it.next()) {
487 map.insert(k, v);
488 }
489 map
490}
491
492fn parse_file_info(path: &str, raw: HashMap<String, String>) -> AfcFileInfo {
493 let name = path
494 .rsplit('/')
495 .next()
496 .filter(|s| !s.is_empty())
497 .map(str::to_string);
498 let file_type = raw.get("st_ifmt").cloned();
499 let size = raw.get("st_size").and_then(|s| s.parse::<u64>().ok());
500 let mode = raw
501 .get("st_mode")
502 .and_then(|s| u32::from_str_radix(s, 8).ok());
503 let link_target = raw.get("st_linktarget").cloned().filter(|s| !s.is_empty());
504
505 AfcFileInfo {
506 name,
507 file_type,
508 size,
509 mode,
510 link_target,
511 raw,
512 }
513}
514
515fn resolve_link_target(path: &str, info: &AfcFileInfo) -> String {
516 let is_link = matches!(info.file_type.as_deref(), Some("S_IFLNK"));
517 if is_link {
518 if let Some(target) = &info.link_target {
519 return target.clone();
520 }
521 }
522 path.to_string()
523}
524
525pub mod mode {
527 pub const READ_ONLY: u64 = 0x00000001;
528 pub const READ_WRITE_CREATE: u64 = 0x00000002;
529 pub const WRITE_ONLY_CREATE_TRUNC: u64 = 0x00000003;
530 pub const READ_WRITE_CREATE_TRUNC: u64 = 0x00000004;
531 pub const WRITE_ONLY_CREATE_APPEND: u64 = 0x00000005;
532 pub const READ_WRITE_CREATE_APPEND: u64 = 0x00000006;
533}
534
535#[cfg(test)]
536mod tests {
537 use super::*;
538
539 #[test]
540 fn test_split_null_strings() {
541 let data = b"foo\0bar\0baz\0";
542 let result = split_null_strings(data);
543 assert_eq!(result, vec!["foo", "bar", "baz"]);
544 }
545
546 #[test]
547 fn test_parse_kv_pairs() {
548 let data = b"st_size\x0012345\0st_ifmt\0S_IFREG\0";
549 let map = parse_kv_pairs(data);
550 assert_eq!(map["st_size"], "12345");
551 assert_eq!(map["st_ifmt"], "S_IFREG");
552 }
553
554 #[test]
555 fn test_afc_header_size() {
556 assert_eq!(std::mem::size_of::<AfcHeader>(), 40);
557 }
558
559 #[test]
560 fn test_afc_header_new() {
561 let hdr = AfcHeader::new(7, AfcOpcode::ReadDir, 5, 10);
562 assert_eq!(hdr.magic.get(), AFC_MAGIC);
563 assert_eq!(hdr.packet_num.get(), 7);
564 assert_eq!(hdr.this_len.get(), 45); assert_eq!(hdr.entire_len.get(), 55); assert_eq!(hdr.operation.get(), AfcOpcode::ReadDir as u64);
567 }
568
569 #[tokio::test]
571 async fn test_list_dir_roundtrip() {
572 use zerocopy::IntoBytes;
573
574 let names = b".\0..\0Photos\0Downloads\0";
577 let hdr = AfcHeader::new(
578 1, AfcOpcode::ReadDir, 0, names.len(), );
583 let mut server_resp = hdr.as_bytes().to_vec();
584 server_resp.extend_from_slice(names); let (client_side, mut server_side) = tokio::io::duplex(4096);
587 tokio::spawn(async move {
588 use tokio::io::{AsyncReadExt, AsyncWriteExt};
589 let mut buf = vec![0u8; 256];
590 let _ = server_side.read(&mut buf).await;
591 server_side.write_all(&server_resp).await.unwrap();
592 });
593
594 let mut afc = AfcClient::new(client_side);
595 let entries = afc.list_dir("/").await.unwrap();
596 assert_eq!(entries, vec!["Photos", "Downloads"]);
598 }
599
600 #[test]
601 fn test_resolve_link_target_uses_st_linktarget_for_symlink() {
602 let mut raw = HashMap::new();
603 raw.insert("st_ifmt".to_string(), "S_IFLNK".to_string());
604 raw.insert(
605 "st_linktarget".to_string(),
606 "/var/mobile/real-file".to_string(),
607 );
608 let info = parse_file_info("/var/mobile/link", raw);
609
610 let resolved = resolve_link_target("/var/mobile/link", &info);
611 assert_eq!(resolved, "/var/mobile/real-file");
612 }
613
614 #[test]
615 fn test_resolve_link_target_keeps_original_path_for_regular_file() {
616 let mut raw = HashMap::new();
617 raw.insert("st_ifmt".to_string(), "S_IFREG".to_string());
618 let info = parse_file_info("/var/mobile/file", raw);
619
620 let resolved = resolve_link_target("/var/mobile/file", &info);
621 assert_eq!(resolved, "/var/mobile/file");
622 }
623
624 #[test]
625 fn test_parse_file_info_parses_st_mode_from_octal() {
626 let mut raw = HashMap::new();
627 raw.insert("st_ifmt".to_string(), "S_IFREG".to_string());
628 raw.insert("st_mode".to_string(), "100644".to_string());
629 raw.insert("st_size".to_string(), "12".to_string());
630
631 let info = parse_file_info("/var/mobile/file.txt", raw);
632 assert_eq!(info.name.as_deref(), Some("file.txt"));
633 assert_eq!(info.file_type.as_deref(), Some("S_IFREG"));
634 assert_eq!(info.size, Some(12));
635 assert_eq!(info.mode, Some(0o100644));
636 }
637
638 #[test]
639 fn test_afc_status_code_mapping_matches_go_ios_upper_status_codes() {
640 assert_eq!(AfcStatusCode::from_u64(24), AfcStatusCode::Other(24));
641 assert_eq!(AfcStatusCode::from_u64(25), AfcStatusCode::Other(25));
642 assert_eq!(AfcStatusCode::from_u64(26), AfcStatusCode::Other(26));
643 assert_eq!(AfcStatusCode::from_u64(27), AfcStatusCode::Other(27));
644 assert_eq!(AfcStatusCode::from_u64(30), AfcStatusCode::MuxError);
645 assert_eq!(AfcStatusCode::from_u64(31), AfcStatusCode::NoMem);
646 assert_eq!(AfcStatusCode::from_u64(32), AfcStatusCode::NotEnoughData);
647 assert_eq!(AfcStatusCode::from_u64(33), AfcStatusCode::DirNotEmpty);
648 }
649}