idevice 0.1.60

A Rust library to interact with services on iOS devices.
Documentation
// Jackson Coxson

use plist::Value;

pub fn opack_to_plist(bytes: &[u8]) -> Result<Value, String> {
    let mut offset = 0;
    let value = opack_to_plist_inner(bytes, &mut offset)?;
    if offset != bytes.len() {
        return Err(format!(
            "unexpected trailing bytes after OPACK payload: {}",
            bytes.len() - offset
        ));
    }
    Ok(value)
}

pub fn plist_to_opack(value: &Value) -> Vec<u8> {
    let mut buf = Vec::new();
    plist_to_opack_inner(value, &mut buf);

    buf
}

fn plist_to_opack_inner(node: &Value, buf: &mut Vec<u8>) {
    match node {
        Value::Dictionary(dict) => {
            let count = dict.len() as u32;
            let blen = if count < 15 {
                (count as u8).wrapping_sub(32)
            } else {
                0xEF
            };
            buf.push(blen);

            for (key, val) in dict {
                plist_to_opack_inner(&Value::String(key.clone()), buf);
                plist_to_opack_inner(val, buf);
            }

            if count > 14 {
                buf.push(0x03);
            }
        }
        Value::Array(array) => {
            let count = array.len() as u32;
            let blen = if count < 15 {
                (count as u8).wrapping_sub(48)
            } else {
                0xDF
            };
            buf.push(blen);

            for val in array {
                plist_to_opack_inner(val, buf);
            }

            if count > 14 {
                buf.push(0x03); // Terminator
            }
        }
        Value::Boolean(b) => {
            let bval = if *b { 1u8 } else { 2u8 };
            buf.push(bval);
        }
        Value::Integer(integer) => {
            let u64val = integer.as_unsigned().unwrap_or(0);

            if u64val <= u8::MAX as u64 {
                let u8val = u64val as u8;
                if u8val > 0x27 {
                    buf.push(0x30);
                    buf.push(u8val);
                } else {
                    buf.push(u8val + 8);
                }
            } else if u64val <= u32::MAX as u64 {
                buf.push(0x32);
                buf.extend_from_slice(&(u64val as u32).to_le_bytes());
            } else {
                buf.push(0x33);
                buf.extend_from_slice(&u64val.to_le_bytes());
            }
        }
        Value::Real(real) => {
            let dval = *real;
            let fval = dval as f32;

            if fval as f64 == dval {
                buf.push(0x35);
                buf.extend_from_slice(&fval.to_bits().swap_bytes().to_ne_bytes());
            } else {
                buf.push(0x36);
                buf.extend_from_slice(&dval.to_bits().swap_bytes().to_ne_bytes());
            }
        }
        Value::String(s) => {
            let bytes = s.as_bytes();
            let len = bytes.len();

            if len > 0x20 {
                if len <= 0xFF {
                    buf.push(0x61);
                    buf.push(len as u8);
                } else if len <= 0xFFFF {
                    buf.push(0x62);
                    buf.extend_from_slice(&(len as u16).to_le_bytes());
                } else if len <= 0xFFFFFFFF {
                    buf.push(0x63);
                    buf.extend_from_slice(&(len as u32).to_le_bytes());
                } else {
                    buf.push(0x64);
                    buf.extend_from_slice(&(len as u64).to_le_bytes());
                }
            } else {
                buf.push(0x40 + len as u8);
            }
            buf.extend_from_slice(bytes);
        }
        Value::Data(data) => {
            let len = data.len();
            if len > 0x20 {
                if len <= 0xFF {
                    buf.push(0x91);
                    buf.push(len as u8);
                } else if len <= 0xFFFF {
                    buf.push(0x92);
                    buf.extend_from_slice(&(len as u16).to_le_bytes());
                } else if len <= 0xFFFFFFFF {
                    buf.push(0x93);
                    buf.extend_from_slice(&(len as u32).to_le_bytes());
                } else {
                    buf.push(0x94);
                    buf.extend_from_slice(&(len as u64).to_le_bytes());
                }
            } else {
                buf.push(0x70 + len as u8);
            }
            buf.extend_from_slice(data);
        }
        _ => {}
    }
}

fn opack_to_plist_inner(bytes: &[u8], offset: &mut usize) -> Result<Value, String> {
    let tag = read_u8(bytes, offset)?;
    match tag {
        0x01 => Ok(Value::Boolean(true)),
        0x02 => Ok(Value::Boolean(false)),
        0x08..=0x2F => Ok(Value::Integer((tag as u64 - 8).into())),
        0x30 => Ok(Value::Integer((read_u8(bytes, offset)? as u64).into())),
        0x32 => Ok(Value::Integer(
            (u32::from_le_bytes(read_exact::<4>(bytes, offset)?) as u64).into(),
        )),
        0x33 => Ok(Value::Integer(
            u64::from_le_bytes(read_exact::<8>(bytes, offset)?).into(),
        )),
        0x35 => {
            let n = u32::from_ne_bytes(read_exact::<4>(bytes, offset)?).swap_bytes();
            Ok(Value::Real(f32::from_bits(n) as f64))
        }
        0x36 => {
            let n = u64::from_ne_bytes(read_exact::<8>(bytes, offset)?).swap_bytes();
            Ok(Value::Real(f64::from_bits(n)))
        }
        0x40..=0x64 => parse_string_value(tag, bytes, offset),
        0x70..=0x94 => parse_data_value(tag, bytes, offset),
        0xD0..=0xDE => parse_array(bytes, offset, Some((tag - 0xD0) as usize)),
        0xDF => parse_array(bytes, offset, None),
        0xE0..=0xEE => parse_dictionary(bytes, offset, Some((tag - 0xE0) as usize)),
        0xEF => parse_dictionary(bytes, offset, None),
        0x03 => Err("unexpected OPACK terminator".into()),
        _ => Err(format!("unsupported OPACK tag: 0x{tag:02x}")),
    }
}

fn parse_string_value(bytes_tag: u8, bytes: &[u8], offset: &mut usize) -> Result<Value, String> {
    let len = read_sized_len(
        bytes_tag,
        bytes,
        offset,
        SizedLenTags {
            inline_base: 0x40,
            u8_tag: 0x61,
            u16_tag: 0x62,
            u32_tag: 0x63,
            u64_tag: 0x64,
            kind: "string",
        },
    )?;
    Ok(Value::String(read_string(bytes, offset, len)?))
}

fn parse_data_value(bytes_tag: u8, bytes: &[u8], offset: &mut usize) -> Result<Value, String> {
    let len = read_sized_len(
        bytes_tag,
        bytes,
        offset,
        SizedLenTags {
            inline_base: 0x70,
            u8_tag: 0x91,
            u16_tag: 0x92,
            u32_tag: 0x93,
            u64_tag: 0x94,
            kind: "data",
        },
    )?;
    Ok(Value::Data(read_vec(bytes, offset, len)?))
}

#[derive(Copy, Clone)]
struct SizedLenTags {
    inline_base: u8,
    u8_tag: u8,
    u16_tag: u8,
    u32_tag: u8,
    u64_tag: u8,
    kind: &'static str,
}

fn read_sized_len(
    tag: u8,
    bytes: &[u8],
    offset: &mut usize,
    tags: SizedLenTags,
) -> Result<usize, String> {
    match tag {
        t if (tags.inline_base..tags.u8_tag).contains(&t) => Ok((tag - tags.inline_base) as usize),
        t if t == tags.u8_tag => Ok(read_u8(bytes, offset)? as usize),
        t if t == tags.u16_tag => Ok(u16::from_le_bytes(read_exact::<2>(bytes, offset)?) as usize),
        t if t == tags.u32_tag => Ok(u32::from_le_bytes(read_exact::<4>(bytes, offset)?) as usize),
        t if t == tags.u64_tag => {
            let len_u64 = u64::from_le_bytes(read_exact::<8>(bytes, offset)?);
            usize::try_from(len_u64)
                .map_err(|_| format!("{} too large for this platform: {len_u64}", tags.kind))
        }
        _ => Err(format!("unsupported OPACK {} tag: 0x{tag:02x}", tags.kind)),
    }
}

fn parse_array(bytes: &[u8], offset: &mut usize, count: Option<usize>) -> Result<Value, String> {
    let mut items = Vec::with_capacity(count.unwrap_or(0));

    match count {
        Some(count) => {
            for _ in 0..count {
                items.push(opack_to_plist_inner(bytes, offset)?);
            }
        }
        None => {
            while !peek_is_terminator(bytes, *offset) {
                items.push(opack_to_plist_inner(bytes, offset)?);
            }
            *offset += 1;
        }
    }

    Ok(Value::Array(items))
}

fn parse_dictionary(
    bytes: &[u8],
    offset: &mut usize,
    count: Option<usize>,
) -> Result<Value, String> {
    let mut dict = plist::Dictionary::new();

    match count {
        Some(count) => {
            for _ in 0..count {
                let key = read_dictionary_key(bytes, offset)?;
                let value = opack_to_plist_inner(bytes, offset)?;
                dict.insert(key, value);
            }
        }
        None => {
            while !peek_is_terminator(bytes, *offset) {
                let key = read_dictionary_key(bytes, offset)?;
                let value = opack_to_plist_inner(bytes, offset)?;
                dict.insert(key, value);
            }
            *offset += 1;
        }
    }

    Ok(Value::Dictionary(dict))
}

fn read_dictionary_key(bytes: &[u8], offset: &mut usize) -> Result<String, String> {
    opack_to_plist_inner(bytes, offset)?
        .into_string()
        .ok_or_else(|| "dictionary key is not a string".to_string())
}

fn peek_is_terminator(bytes: &[u8], offset: usize) -> bool {
    bytes.get(offset).copied() == Some(0x03)
}

fn read_u8(bytes: &[u8], offset: &mut usize) -> Result<u8, String> {
    let b = bytes
        .get(*offset)
        .copied()
        .ok_or_else(|| "unexpected EOF while reading OPACK tag".to_string())?;
    *offset += 1;
    Ok(b)
}

fn read_exact<const N: usize>(bytes: &[u8], offset: &mut usize) -> Result<[u8; N], String> {
    let end = offset.saturating_add(N);
    let slice = bytes
        .get(*offset..end)
        .ok_or_else(|| format!("unexpected EOF while reading {N} bytes"))?;
    let mut out = [0u8; N];
    out.copy_from_slice(slice);
    *offset = end;
    Ok(out)
}

fn read_vec(bytes: &[u8], offset: &mut usize, len: usize) -> Result<Vec<u8>, String> {
    let end = offset.saturating_add(len);
    let slice = bytes
        .get(*offset..end)
        .ok_or_else(|| format!("unexpected EOF while reading {len} bytes"))?;
    *offset = end;
    Ok(slice.to_vec())
}

fn read_string(bytes: &[u8], offset: &mut usize, len: usize) -> Result<String, String> {
    let data = read_vec(bytes, offset, len)?;
    String::from_utf8(data).map_err(|e| format!("invalid UTF-8 string in OPACK payload: {e}"))
}

#[cfg(test)]
mod tests {
    #[test]
    fn t1() {
        let v = crate::plist!({
            "altIRK": b"\xe9\xe8-\xc0jIykVoT\x00\x19\xb1\xc7{".to_vec(),
            "btAddr": "11:22:33:44:55:66",
            "mac": b"\x11\x22\x33\x44\x55\x66".to_vec(),
            "remotepairing_serial_number": "AAAAAAAAAAAA",
            "accountID": "lolsssss",
            "model": "computer-model",
            "name": "reeeee",
        });

        let res = super::plist_to_opack(&v);

        let expected = [
            0xe7, 0x46, 0x61, 0x6c, 0x74, 0x49, 0x52, 0x4b, 0x80, 0xe9, 0xe8, 0x2d, 0xc0, 0x6a,
            0x49, 0x79, 0x6b, 0x56, 0x6f, 0x54, 0x00, 0x19, 0xb1, 0xc7, 0x7b, 0x46, 0x62, 0x74,
            0x41, 0x64, 0x64, 0x72, 0x51, 0x31, 0x31, 0x3a, 0x32, 0x32, 0x3a, 0x33, 0x33, 0x3a,
            0x34, 0x34, 0x3a, 0x35, 0x35, 0x3a, 0x36, 0x36, 0x43, 0x6d, 0x61, 0x63, 0x76, 0x11,
            0x22, 0x33, 0x44, 0x55, 0x66, 0x5b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x70, 0x61,
            0x69, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x6e,
            0x75, 0x6d, 0x62, 0x65, 0x72, 0x4c, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
            0x41, 0x41, 0x41, 0x41, 0x49, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x44,
            0x48, 0x6c, 0x6f, 0x6c, 0x73, 0x73, 0x73, 0x73, 0x73, 0x45, 0x6d, 0x6f, 0x64, 0x65,
            0x6c, 0x4e, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, 0x2d, 0x6d, 0x6f, 0x64,
            0x65, 0x6c, 0x44, 0x6e, 0x61, 0x6d, 0x65, 0x46, 0x72, 0x65, 0x65, 0x65, 0x65, 0x65,
        ];

        println!("{res:02X?}");
        assert_eq!(res, expected);
    }

    #[test]
    fn t2() {
        let v = [
            0xe7, 0x46, 0x61, 0x6c, 0x74, 0x49, 0x52, 0x4b, 0x80, 0xe9, 0xe8, 0x2d, 0xc0, 0x6a,
            0x49, 0x79, 0x6b, 0x56, 0x6f, 0x54, 0x00, 0x19, 0xb1, 0xc7, 0x7b, 0x46, 0x62, 0x74,
            0x41, 0x64, 0x64, 0x72, 0x51, 0x31, 0x31, 0x3a, 0x32, 0x32, 0x3a, 0x33, 0x33, 0x3a,
            0x34, 0x34, 0x3a, 0x35, 0x35, 0x3a, 0x36, 0x36, 0x43, 0x6d, 0x61, 0x63, 0x76, 0x11,
            0x22, 0x33, 0x44, 0x55, 0x66, 0x5b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x70, 0x61,
            0x69, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x6e,
            0x75, 0x6d, 0x62, 0x65, 0x72, 0x4c, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
            0x41, 0x41, 0x41, 0x41, 0x49, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x44,
            0x48, 0x6c, 0x6f, 0x6c, 0x73, 0x73, 0x73, 0x73, 0x73, 0x45, 0x6d, 0x6f, 0x64, 0x65,
            0x6c, 0x4e, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, 0x2d, 0x6d, 0x6f, 0x64,
            0x65, 0x6c, 0x44, 0x6e, 0x61, 0x6d, 0x65, 0x46, 0x72, 0x65, 0x65, 0x65, 0x65, 0x65,
        ];

        let expected = crate::plist!({
            "altIRK": b"\xe9\xe8-\xc0jIykVoT\x00\x19\xb1\xc7{".to_vec(),
            "btAddr": "11:22:33:44:55:66",
            "mac": b"\x11\x22\x33\x44\x55\x66".to_vec(),
            "remotepairing_serial_number": "AAAAAAAAAAAA",
            "accountID": "lolsssss",
            "model": "computer-model",
            "name": "reeeee",
        });

        let res = super::opack_to_plist(&v).unwrap();

        println!("{res:02X?}");
        assert_eq!(res, expected);
    }
}