1c4ef02aeSEvan Bacon/** 2c4ef02aeSEvan Bacon * Copyright (c) 2021 Expo, Inc. 3c4ef02aeSEvan Bacon * Copyright (c) 2018 Drifty Co. 4c4ef02aeSEvan Bacon * 5c4ef02aeSEvan Bacon * This source code is licensed under the MIT license found in the 6c4ef02aeSEvan Bacon * LICENSE file in the root directory of this source tree. 7c4ef02aeSEvan Bacon */ 8c4ef02aeSEvan Baconimport plist from '@expo/plist'; 9c4ef02aeSEvan Baconimport Debug from 'debug'; 10c4ef02aeSEvan Baconimport { Socket, connect } from 'net'; 11c4ef02aeSEvan Bacon 12*8a424bebSJames Ideimport { ResponseError, ServiceClient } from './ServiceClient'; 13c4ef02aeSEvan Baconimport { CommandError } from '../../../../utils/errors'; 14c4ef02aeSEvan Baconimport { parsePlistBuffer } from '../../../../utils/plist'; 15c4ef02aeSEvan Baconimport { UsbmuxProtocolClient } from '../protocol/UsbmuxProtocol'; 16c4ef02aeSEvan Bacon 17c4ef02aeSEvan Baconconst debug = Debug('expo:apple-device:client:usbmuxd'); 18c4ef02aeSEvan Bacon 19c4ef02aeSEvan Baconexport interface UsbmuxdDeviceProperties { 20bd710fa0SBartosz Kaszubowski /** @example 'USB' */ 21bd710fa0SBartosz Kaszubowski ConnectionType: 'USB' | 'Network'; 22bd710fa0SBartosz Kaszubowski /** @example 7 */ 23c4ef02aeSEvan Bacon DeviceID: number; 24bd710fa0SBartosz Kaszubowski /** @example 339738624 */ 25bd710fa0SBartosz Kaszubowski LocationID?: number; 26bd710fa0SBartosz Kaszubowski /** @example '00008101-001964A22629003A' */ 27c4ef02aeSEvan Bacon SerialNumber: string; 28bd710fa0SBartosz Kaszubowski /** 29bd710fa0SBartosz Kaszubowski * Only available for USB connection. 30bd710fa0SBartosz Kaszubowski * @example 480000000 31bd710fa0SBartosz Kaszubowski */ 32bd710fa0SBartosz Kaszubowski ConnectionSpeed?: number; 33bd710fa0SBartosz Kaszubowski /** 34bd710fa0SBartosz Kaszubowski * Only available for USB connection. 35bd710fa0SBartosz Kaszubowski * @example 4776 36bd710fa0SBartosz Kaszubowski */ 37bd710fa0SBartosz Kaszubowski ProductID?: number; 38bd710fa0SBartosz Kaszubowski /** 39bd710fa0SBartosz Kaszubowski * Only available for USB connection. 40bd710fa0SBartosz Kaszubowski * @example '00008101-001964A22629003A' 41bd710fa0SBartosz Kaszubowski */ 42bd710fa0SBartosz Kaszubowski UDID?: string; 43bd710fa0SBartosz Kaszubowski /** 44bd710fa0SBartosz Kaszubowski * Only available for USB connection. 45bd710fa0SBartosz Kaszubowski * @example '00008101001964A22629003A' 46bd710fa0SBartosz Kaszubowski */ 47bd710fa0SBartosz Kaszubowski USBSerialNumber?: string; 48bd710fa0SBartosz Kaszubowski /** 49bd710fa0SBartosz Kaszubowski * Only available for Network connection. 50bd710fa0SBartosz Kaszubowski * @example '08:c7:29:05:f2:30@fe80::ac7:29ff:fe05:f230-supportsRP._apple-mobdev2._tcp.local.' 51bd710fa0SBartosz Kaszubowski */ 52bd710fa0SBartosz Kaszubowski EscapedFullServiceName?: string; 53bd710fa0SBartosz Kaszubowski /** 54bd710fa0SBartosz Kaszubowski * Only available for Network connection. 55bd710fa0SBartosz Kaszubowski * @example 5 56bd710fa0SBartosz Kaszubowski */ 57bd710fa0SBartosz Kaszubowski InterfaceIndex?: number; 58bd710fa0SBartosz Kaszubowski /** 59bd710fa0SBartosz Kaszubowski * Only available for Network connection. 60bd710fa0SBartosz Kaszubowski */ 61bd710fa0SBartosz Kaszubowski NetworkAddress?: Buffer; 62c4ef02aeSEvan Bacon} 63c4ef02aeSEvan Bacon 64c4ef02aeSEvan Baconexport interface UsbmuxdDevice { 65bd710fa0SBartosz Kaszubowski /** @example 7 */ 66c4ef02aeSEvan Bacon DeviceID: number; 67c4ef02aeSEvan Bacon MessageType: 'Attached'; // TODO: what else? 68c4ef02aeSEvan Bacon Properties: UsbmuxdDeviceProperties; 69c4ef02aeSEvan Bacon} 70c4ef02aeSEvan Bacon 71c4ef02aeSEvan Baconexport interface UsbmuxdConnectResponse { 72c4ef02aeSEvan Bacon MessageType: 'Result'; 73c4ef02aeSEvan Bacon Number: number; 74c4ef02aeSEvan Bacon} 75c4ef02aeSEvan Bacon 76c4ef02aeSEvan Baconexport interface UsbmuxdDeviceResponse { 77c4ef02aeSEvan Bacon DeviceList: UsbmuxdDevice[]; 78c4ef02aeSEvan Bacon} 79c4ef02aeSEvan Bacon 80c4ef02aeSEvan Baconexport interface UsbmuxdPairRecordResponse { 81c4ef02aeSEvan Bacon PairRecordData: Buffer; 82c4ef02aeSEvan Bacon} 83c4ef02aeSEvan Bacon 84c4ef02aeSEvan Baconexport interface UsbmuxdPairRecord { 85c4ef02aeSEvan Bacon DeviceCertificate: Buffer; 86c4ef02aeSEvan Bacon EscrowBag: Buffer; 87c4ef02aeSEvan Bacon HostCertificate: Buffer; 88c4ef02aeSEvan Bacon HostID: string; 89c4ef02aeSEvan Bacon HostPrivateKey: Buffer; 90c4ef02aeSEvan Bacon RootCertificate: Buffer; 91c4ef02aeSEvan Bacon RootPrivateKey: Buffer; 92c4ef02aeSEvan Bacon SystemBUID: string; 93c4ef02aeSEvan Bacon WiFiMACAddress: string; 94c4ef02aeSEvan Bacon} 95c4ef02aeSEvan Bacon 96c4ef02aeSEvan Baconfunction isUsbmuxdConnectResponse(resp: any): resp is UsbmuxdConnectResponse { 97c4ef02aeSEvan Bacon return resp.MessageType === 'Result' && resp.Number !== undefined; 98c4ef02aeSEvan Bacon} 99c4ef02aeSEvan Bacon 100c4ef02aeSEvan Baconfunction isUsbmuxdDeviceResponse(resp: any): resp is UsbmuxdDeviceResponse { 101c4ef02aeSEvan Bacon return resp.DeviceList !== undefined; 102c4ef02aeSEvan Bacon} 103c4ef02aeSEvan Bacon 104c4ef02aeSEvan Baconfunction isUsbmuxdPairRecordResponse(resp: any): resp is UsbmuxdPairRecordResponse { 105c4ef02aeSEvan Bacon return resp.PairRecordData !== undefined; 106c4ef02aeSEvan Bacon} 107c4ef02aeSEvan Bacon 108c4ef02aeSEvan Baconexport class UsbmuxdClient extends ServiceClient<UsbmuxProtocolClient> { 109c4ef02aeSEvan Bacon constructor(public socket: Socket) { 110c4ef02aeSEvan Bacon super(socket, new UsbmuxProtocolClient(socket)); 111c4ef02aeSEvan Bacon } 112c4ef02aeSEvan Bacon 113c4ef02aeSEvan Bacon static connectUsbmuxdSocket(): Socket { 114c4ef02aeSEvan Bacon debug('connectUsbmuxdSocket'); 115c4ef02aeSEvan Bacon if (process.platform === 'win32') { 116c4ef02aeSEvan Bacon return connect({ port: 27015, host: 'localhost' }); 117c4ef02aeSEvan Bacon } else { 118c4ef02aeSEvan Bacon return connect({ path: '/var/run/usbmuxd' }); 119c4ef02aeSEvan Bacon } 120c4ef02aeSEvan Bacon } 121c4ef02aeSEvan Bacon 122c4ef02aeSEvan Bacon async connect(device: Pick<UsbmuxdDevice, 'DeviceID'>, port: number): Promise<Socket> { 123c4ef02aeSEvan Bacon debug(`connect: ${device.DeviceID} on port ${port}`); 124c4ef02aeSEvan Bacon debug(`connect:device: %O`, device); 125c4ef02aeSEvan Bacon 126c4ef02aeSEvan Bacon const response = await this.protocolClient.sendMessage({ 127c4ef02aeSEvan Bacon messageType: 'Connect', 128c4ef02aeSEvan Bacon extraFields: { 129c4ef02aeSEvan Bacon DeviceID: device.DeviceID, 130c4ef02aeSEvan Bacon PortNumber: htons(port), 131c4ef02aeSEvan Bacon }, 132c4ef02aeSEvan Bacon }); 133c4ef02aeSEvan Bacon debug(`connect:device:response: %O`, response); 134c4ef02aeSEvan Bacon 135c4ef02aeSEvan Bacon if (isUsbmuxdConnectResponse(response) && response.Number === 0) { 136c4ef02aeSEvan Bacon return this.protocolClient.socket; 137c4ef02aeSEvan Bacon } else { 138c4ef02aeSEvan Bacon throw new ResponseError( 139c4ef02aeSEvan Bacon `There was an error connecting to the USB connected device (id: ${device.DeviceID}, port: ${port})`, 140c4ef02aeSEvan Bacon response 141c4ef02aeSEvan Bacon ); 142c4ef02aeSEvan Bacon } 143c4ef02aeSEvan Bacon } 144c4ef02aeSEvan Bacon 145c4ef02aeSEvan Bacon async getDevices(): Promise<UsbmuxdDevice[]> { 146c4ef02aeSEvan Bacon debug('getDevices'); 147c4ef02aeSEvan Bacon 148c4ef02aeSEvan Bacon const resp = await this.protocolClient.sendMessage({ 149c4ef02aeSEvan Bacon messageType: 'ListDevices', 150c4ef02aeSEvan Bacon }); 151c4ef02aeSEvan Bacon 152c4ef02aeSEvan Bacon if (isUsbmuxdDeviceResponse(resp)) { 153c4ef02aeSEvan Bacon return resp.DeviceList; 154c4ef02aeSEvan Bacon } else { 155c4ef02aeSEvan Bacon throw new ResponseError('Invalid response from getDevices', resp); 156c4ef02aeSEvan Bacon } 157c4ef02aeSEvan Bacon } 158c4ef02aeSEvan Bacon 159c4ef02aeSEvan Bacon async getDevice(udid?: string): Promise<UsbmuxdDevice> { 160c4ef02aeSEvan Bacon debug(`getDevice ${udid ? 'udid: ' + udid : ''}`); 161c4ef02aeSEvan Bacon const devices = await this.getDevices(); 162c4ef02aeSEvan Bacon 163c4ef02aeSEvan Bacon if (!devices.length) { 164c4ef02aeSEvan Bacon throw new CommandError('APPLE_DEVICE_USBMUXD', 'No devices found'); 165c4ef02aeSEvan Bacon } 166c4ef02aeSEvan Bacon 167c4ef02aeSEvan Bacon if (!udid) { 168c4ef02aeSEvan Bacon return devices[0]; 169c4ef02aeSEvan Bacon } 170c4ef02aeSEvan Bacon 171c4ef02aeSEvan Bacon for (const device of devices) { 172c4ef02aeSEvan Bacon if (device.Properties && device.Properties.SerialNumber === udid) { 173c4ef02aeSEvan Bacon return device; 174c4ef02aeSEvan Bacon } 175c4ef02aeSEvan Bacon } 176c4ef02aeSEvan Bacon 177c4ef02aeSEvan Bacon throw new CommandError('APPLE_DEVICE_USBMUXD', `No device found (udid: ${udid})`); 178c4ef02aeSEvan Bacon } 179c4ef02aeSEvan Bacon 180c4ef02aeSEvan Bacon async readPairRecord(udid: string): Promise<UsbmuxdPairRecord> { 181c4ef02aeSEvan Bacon debug(`readPairRecord: ${udid}`); 182c4ef02aeSEvan Bacon 183c4ef02aeSEvan Bacon const resp = await this.protocolClient.sendMessage({ 184c4ef02aeSEvan Bacon messageType: 'ReadPairRecord', 185c4ef02aeSEvan Bacon extraFields: { PairRecordID: udid }, 186c4ef02aeSEvan Bacon }); 187c4ef02aeSEvan Bacon 188c4ef02aeSEvan Bacon if (isUsbmuxdPairRecordResponse(resp)) { 189c4ef02aeSEvan Bacon // the pair record can be created as a binary plist 190c4ef02aeSEvan Bacon const BPLIST_MAGIC = Buffer.from('bplist00'); 191c4ef02aeSEvan Bacon if (BPLIST_MAGIC.compare(resp.PairRecordData, 0, 8) === 0) { 192c4ef02aeSEvan Bacon debug('Binary plist pair record detected.'); 193c4ef02aeSEvan Bacon return parsePlistBuffer(resp.PairRecordData)[0]; 194c4ef02aeSEvan Bacon } else { 195c4ef02aeSEvan Bacon // TODO: use parsePlistBuffer 196c4ef02aeSEvan Bacon return plist.parse(resp.PairRecordData.toString()) as any; // TODO: type guard 197c4ef02aeSEvan Bacon } 198c4ef02aeSEvan Bacon } else { 199c4ef02aeSEvan Bacon throw new ResponseError( 200c4ef02aeSEvan Bacon `There was an error reading pair record for device (udid: ${udid})`, 201c4ef02aeSEvan Bacon resp 202c4ef02aeSEvan Bacon ); 203c4ef02aeSEvan Bacon } 204c4ef02aeSEvan Bacon } 205c4ef02aeSEvan Bacon} 206c4ef02aeSEvan Bacon 207c4ef02aeSEvan Baconfunction htons(n: number): number { 208c4ef02aeSEvan Bacon return ((n & 0xff) << 8) | ((n >> 8) & 0xff); 209c4ef02aeSEvan Bacon} 210