Skip to content

Commit

Permalink
enhance tidevice info, complete pair function
Browse files Browse the repository at this point in the history
  • Loading branch information
codeskyblue committed Mar 5, 2021
1 parent bef6c49 commit 0c4a599
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 42 deletions.
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,57 @@ $ tidevice developer
[I 210127 11:37:53 _device:589] DeveloperImage mounted successfully
```

# 查看设备信息
```bash
$ tidevice info

# 查看设备电源信息
$ tidevice info --domain com.apple.mobile.battery --json
{
"BatteryCurrentCapacity": 53,
"BatteryIsCharging": true,
"ExternalChargeCapable": true,
"ExternalConnected": true,
"FullyCharged": false,
"GasGaugeCapability": true,
"HasBattery": true
}
```

Known domains are:

```text
com.apple.disk_usage
com.apple.disk_usage.factory
com.apple.mobile.battery
com.apple.iqagent
com.apple.purplebuddy
com.apple.PurpleBuddy
com.apple.mobile.chaperone
com.apple.mobile.third_party_termination
com.apple.mobile.lockdownd
com.apple.mobile.lockdown_cache
com.apple.xcode.developerdomain
com.apple.international
com.apple.mobile.data_sync
com.apple.mobile.tethered_sync
com.apple.mobile.mobile_application_usage
com.apple.mobile.backup
com.apple.mobile.nikita
com.apple.mobile.restriction
com.apple.mobile.user_preferences
com.apple.mobile.sync_data_class
com.apple.mobile.software_behavior
com.apple.mobile.iTunes.SQLMusicLibraryPostProcessCommands
com.apple.mobile.iTunes.accessories
com.apple.mobile.internal
com.apple.mobile.wireless_lockdown
com.apple.fairplay
com.apple.iTunes
com.apple.mobile.iTunes.store
com.apple.mobile.iTunes
```

### 其他常用
```bash
# 重启
Expand Down
51 changes: 51 additions & 0 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,57 @@ $ tidevice developer
[I 210127 11:37:53 _device:589] DeveloperImage mounted successfully
```

### Check device info
```bash
$ tidevice info

# check device power info
$ tidevice info --domain com.apple.mobile.battery --json
{
"BatteryCurrentCapacity": 53,
"BatteryIsCharging": true,
"ExternalChargeCapable": true,
"ExternalConnected": true,
"FullyCharged": false,
"GasGaugeCapability": true,
"HasBattery": true
}
```

Known domains are:

```text
com.apple.disk_usage
com.apple.disk_usage.factory
com.apple.mobile.battery
com.apple.iqagent
com.apple.purplebuddy
com.apple.PurpleBuddy
com.apple.mobile.chaperone
com.apple.mobile.third_party_termination
com.apple.mobile.lockdownd
com.apple.mobile.lockdown_cache
com.apple.xcode.developerdomain
com.apple.international
com.apple.mobile.data_sync
com.apple.mobile.tethered_sync
com.apple.mobile.mobile_application_usage
com.apple.mobile.backup
com.apple.mobile.nikita
com.apple.mobile.restriction
com.apple.mobile.user_preferences
com.apple.mobile.sync_data_class
com.apple.mobile.software_behavior
com.apple.mobile.iTunes.SQLMusicLibraryPostProcessCommands
com.apple.mobile.iTunes.accessories
com.apple.mobile.internal
com.apple.mobile.wireless_lockdown
com.apple.fairplay
com.apple.iTunes
com.apple.mobile.iTunes.store
com.apple.mobile.iTunes
```

### Other
```bash
# reboot device
Expand Down
36 changes: 36 additions & 0 deletions examples/read_properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# coding: utf-8
#

from tidevice._usbmux import Usbmux
from tidevice import Device
from pprint import pprint

def main():
u = Usbmux()

# List devices
devices = u.device_list()
pprint(devices)

buid = u.read_system_BUID()
print("BUID:", buid)

d = Device()
dev_pkey = d.get_value("DevicePublicKey", no_session=True)
print("DevicePublicKey:", dev_pkey)

wifi_address = d.get_value("WiFiAddress", no_session=True)
print("WiFi Address:", wifi_address)

with d.create_inner_connection() as s:
ret = s.send_recv_packet({
"Request": "GetValue",
"Label": "example",
})
pprint(ret['Value'])

# print("Values", values)


if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Pillow
requests
colored
packaging
pyOpenSSL #>=20.0.1
pyasn1 #>=0.4.8

tornado
simple_tornado
19 changes: 11 additions & 8 deletions tidevice/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,24 +92,24 @@ def cmd_list(args: argparse.Namespace):

def cmd_device_info(args: argparse.Namespace):
d = _udid2device(args.udid)
dinfo = d.device_info()
value = d.get_value(no_session=args.simple, key=args.key, domain=args.domain)
if args.json:

def _bytes_hook(obj):
if isinstance(obj, bytes):
return base64.b64encode(obj).decode()

print(json.dumps(dinfo, indent=4, default=_bytes_hook))
print(json.dumps(value, indent=4, default=_bytes_hook))
elif args.key or args.domain:
pprint(value)
else:
print("{:17s} {}".format("ProductName:",
MODELS.get(dinfo['ProductType'])))
print("{:17s} {}".format("MarketName:",
MODELS.get(value['ProductType'])))
for attr in ('DeviceName', 'ProductVersion', 'ProductType',
'ModelNumber', 'SerialNumber', 'PhoneNumber',
'CPUArchitecture', 'ProductName', 'ProtocolVersion',
'RegionInfo', 'TimeIntervalSince1970', 'TimeZone',
'UniqueDeviceID', 'WiFiAddress', 'BluetoothAddress',
'BasebandVersion'):
print("{:17s} {}".format(attr + ":", dinfo.get(attr)))
print("{:17s} {}".format(attr + ":", value.get(attr)))


def cmd_version(args: argparse.Namespace):
Expand Down Expand Up @@ -354,7 +354,10 @@ def cmd_test(args: argparse.Namespace):
flags=[
dict(args=['--json'],
action='store_true',
help="output as json format")
help="output as json format"),
dict(args=['-s', '--simple'], action='store_true', help='use a simple connection to avoid auto-pairing with the device'),
dict(args=['-k', '--key'], type=str, help='only query specified KEY'),
dict(args=['--domain'], help='set domain of query to NAME.'),
],
help="show device info"),
dict(action=cmd_system_info,
Expand Down
113 changes: 80 additions & 33 deletions tidevice/_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@
import os
import pprint
import re
import ssl
import sys
import shutil
import ssl
import subprocess
import sys
import tempfile
import threading
import time
import typing
import uuid
import zipfile
from collections import namedtuple
from typing import Iterator, Optional, Tuple, Union
import zipfile

import requests
from cached_property import cached_property
Expand All @@ -35,11 +35,12 @@
from ._instruments import (AUXMessageBuffer, DTXMessage, DTXService, Event,
ServiceInstruments)
from ._ipautil import IPAReader
from ._ssl import make_certs_and_key
from ._proto import *
from ._safe_socket import *
from ._sync import Sync
from ._usbmux import Usbmux
from ._utils import ProgressReader, get_app_dir, get_binary_by_name
from ._utils import ProgressReader, get_app_dir
from .exceptions import *

logger = setup_logger(PROGRAM_NAME,
Expand Down Expand Up @@ -81,6 +82,7 @@ def __init__(self,
self._usbmux = Usbmux(usbmux)
elif isinstance(usbmux, Usbmux):
self._usbmux = usbmux

self._udid = udid
self._info = self.info
self._lock = threading.Lock()
Expand Down Expand Up @@ -119,6 +121,10 @@ def is_connected(self) -> bool:
@property
def udid(self) -> str:
return self._udid

@property
def devid(self) -> int:
return self._info['DeviceID']

@property
def pair_record(self) -> dict:
Expand Down Expand Up @@ -176,12 +182,49 @@ def pair(self):
Same as idevicepair pair
iconsole is a github project, hosted in https://github.com/anonymous5l/iConsole
"""
iconsole_path = get_binary_by_name("iconsole")
if not os.path.isfile(iconsole_path):
raise MuxError("Unable to pair without iconsole")
output = subprocess.check_output([iconsole_path, 'afc', '-u', self.udid, 'space']).decode('utf-8')
if 'TotalSpace:' not in output:
raise MuxError("Pair: " + output)
device_public_key = self.get_value("DevicePublicKey", no_session=True)
if not device_public_key:
raise MuxError("Unable to retrieve DevicePublicKey")
buid = self._usbmux.read_system_BUID()
wifi_address = self.get_value("WiFiAddress", no_session=True)

cert_pem, priv_key_pem, dev_cert_pem = make_certs_and_key(device_public_key)
pair_record = {
'DevicePublicKey': device_public_key,
'DeviceCertificate': dev_cert_pem,
'HostCertificate': cert_pem,
'HostID': str(uuid.uuid4()).upper(),
'RootCertificate': cert_pem,
'SystemBUID': buid,
}

with self.create_inner_connection() as s:
ret = s.send_recv_packet({
"Request": "Pair",
"PairRecord": pair_record,
"Label": PROGRAM_NAME,
"ProtocolVersion": "2",
"PairingOptions": {
"ExtendedPairingErrors": True,
}
})
assert ret, "Pair request got empty response"
if "Error" in ret:
# error could be "PasswordProtected" or "PairingDialogResponsePending"
raise MuxError("pair:", ret['Error'])

assert 'EscrowBag' in ret, ret
pair_record['HostPrivateKey'] = priv_key_pem
pair_record['EscrowBag'] = ret['EscrowBag']
pair_record['WiFiMACAddress'] = wifi_address

self.usbmux.send_recv({
"MessageType": "SavePairRecord",
"PairRecordID": self.udid,
"PairRecordData": bplist.dumps(pair_record),
"DeviceID": self.devid,
})
return pair_record

def handshake(self):
"""
Expand All @@ -191,8 +234,7 @@ def handshake(self):
self._pair_record = self._read_pair_record()
except MuxReplyError as err:
if err.reply_code == UsbmuxReplyCode.BadDevice:
self.pair()
self._pair_record = self._read_pair_record()
self._pair_record = self.pair()

@property
def ssl_pemfile_path(self):
Expand Down Expand Up @@ -298,6 +340,8 @@ def create_session(self) -> PlistSocket:

s.send_packet({
"Request": "StopSession",
"ProtocolVersion": '2',
"Label": PROGRAM_NAME,
"SessionID": session_id,
})
s.recv_packet()
Expand All @@ -307,36 +351,39 @@ def device_info(self, domain: Optional[str] = None) -> dict:
Args:
domain: can be found in "ideviceinfo -h", eg: com.apple.disk_usage
"""
with self.create_session() as conn:
packet = {
"Request": "GetValue",
"Label": PROGRAM_NAME,
}
if domain:
packet["Domain"] = domain
ret = conn.send_recv_packet(packet)
return ret['Value']

def get_value(self, key: str, no_session: bool = False):
return self.get_value(domain=domain)
# with self.create_session() as conn:
# packet = {
# "Request": "GetValue",
# "Label": PROGRAM_NAME,
# }
# if domain:
# packet["Domain"] = domain
# ret = conn.send_recv_packet(packet)
# return ret['Value']

def get_value(self, key: str = '', domain: str = "", no_session: bool = False):
""" key can be: ProductVersion
Args:
domain (str): com.apple.disk_usage
no_session: set to True when not paired
"""
request = {
"Request": "GetValue",
"Label": PROGRAM_NAME,
}
if key:
request['Key'] = key
if domain:
request['Domain'] = domain

if no_session:
with self.create_inner_connection() as s:
ret = s.send_recv_packet({
"Request": "GetValue",
"Key": key,
"Label": PROGRAM_NAME,
})
ret = s.send_recv_packet(request)
return ret['Value']
else:
with self.create_session() as conn:
ret = conn.send_recv_packet({
"Request": "GetValue",
"Key": key,
"Label": PROGRAM_NAME,
})
ret = conn.send_recv_packet(request)
return ret['Value']

def screen_info(self) -> tuple:
Expand Down
Loading

0 comments on commit 0c4a599

Please sign in to comment.