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 Bacon 9c4ef02aeSEvan Baconimport plist from '@expo/plist'; 10c4ef02aeSEvan Baconimport Debug from 'debug'; 11c4ef02aeSEvan Baconimport { Socket } from 'net'; 12c4ef02aeSEvan Bacon 13c4ef02aeSEvan Baconimport type { ProtocolWriter } from './AbstractProtocol'; 14c4ef02aeSEvan Baconimport { PlistProtocolReader, ProtocolClient, ProtocolReaderFactory } from './AbstractProtocol'; 15*8a424bebSJames Ideimport { CommandError } from '../../../../utils/errors'; 16c4ef02aeSEvan Bacon 17c4ef02aeSEvan Baconconst debug = Debug('expo:apple-device:protocol:lockdown'); 18c4ef02aeSEvan Baconexport const LOCKDOWN_HEADER_SIZE = 4; 19c4ef02aeSEvan Bacon 20c4ef02aeSEvan Baconexport interface LockdownCommand { 21c4ef02aeSEvan Bacon Command: string; 22c4ef02aeSEvan Bacon [key: string]: any; 23c4ef02aeSEvan Bacon} 24c4ef02aeSEvan Bacon 25c4ef02aeSEvan Baconexport interface LockdownResponse { 26c4ef02aeSEvan Bacon Status: string; 27c4ef02aeSEvan Bacon [key: string]: any; 28c4ef02aeSEvan Bacon} 29c4ef02aeSEvan Bacon 30c4ef02aeSEvan Baconexport interface LockdownErrorResponse { 31c4ef02aeSEvan Bacon Error: string; 32c4ef02aeSEvan Bacon Request?: string; 33c4ef02aeSEvan Bacon Service?: string; 34c4ef02aeSEvan Bacon} 35c4ef02aeSEvan Bacon 36c4ef02aeSEvan Baconexport interface LockdownRequest { 37c4ef02aeSEvan Bacon Request: string; 38c4ef02aeSEvan Bacon [key: string]: any; 39c4ef02aeSEvan Bacon} 40c4ef02aeSEvan Bacon 41c4ef02aeSEvan Baconfunction isDefined(val: any) { 42c4ef02aeSEvan Bacon return typeof val !== 'undefined'; 43c4ef02aeSEvan Bacon} 44c4ef02aeSEvan Bacon 45c4ef02aeSEvan Baconexport function isLockdownResponse(resp: any): resp is LockdownResponse { 46c4ef02aeSEvan Bacon return isDefined(resp.Status); 47c4ef02aeSEvan Bacon} 48c4ef02aeSEvan Bacon 49c4ef02aeSEvan Baconexport function isLockdownErrorResponse(resp: any): resp is LockdownErrorResponse { 50c4ef02aeSEvan Bacon return isDefined(resp.Error); 51c4ef02aeSEvan Bacon} 52c4ef02aeSEvan Bacon 53c4ef02aeSEvan Baconexport class LockdownProtocolClient< 54*8a424bebSJames Ide MessageType extends LockdownRequest | LockdownCommand = LockdownRequest, 55c4ef02aeSEvan Bacon> extends ProtocolClient<MessageType> { 56c4ef02aeSEvan Bacon constructor(socket: Socket) { 57c4ef02aeSEvan Bacon super(socket, new ProtocolReaderFactory(LockdownProtocolReader), new LockdownProtocolWriter()); 58c4ef02aeSEvan Bacon } 59c4ef02aeSEvan Bacon} 60c4ef02aeSEvan Bacon 61c4ef02aeSEvan Baconexport class LockdownProtocolReader extends PlistProtocolReader { 62c4ef02aeSEvan Bacon constructor(callback: (data: any) => any) { 63c4ef02aeSEvan Bacon super(LOCKDOWN_HEADER_SIZE, callback); 64c4ef02aeSEvan Bacon } 65c4ef02aeSEvan Bacon 66c4ef02aeSEvan Bacon parseHeader(data: Buffer) { 67c4ef02aeSEvan Bacon return data.readUInt32BE(0); 68c4ef02aeSEvan Bacon } 69c4ef02aeSEvan Bacon 70c4ef02aeSEvan Bacon parseBody(data: Buffer) { 71c4ef02aeSEvan Bacon const resp = super.parseBody(data); 72c4ef02aeSEvan Bacon debug(`Response: ${JSON.stringify(resp)}`); 73c4ef02aeSEvan Bacon if (isLockdownErrorResponse(resp)) { 74c4ef02aeSEvan Bacon if (resp.Error === 'DeviceLocked') { 75c4ef02aeSEvan Bacon throw new CommandError('APPLE_DEVICE_LOCKED', 'Device is currently locked.'); 76c4ef02aeSEvan Bacon } 77c4ef02aeSEvan Bacon 78c4ef02aeSEvan Bacon if (resp.Error === 'InvalidService') { 79c4ef02aeSEvan Bacon let errorMessage = `${resp.Error}: ${resp.Service} (request: ${resp.Request})`; 80c4ef02aeSEvan Bacon if (resp.Service === 'com.apple.debugserver') { 81c4ef02aeSEvan Bacon errorMessage += 82c4ef02aeSEvan Bacon '\nTry reconnecting your device. You can also debug service logs with `export DEBUG=expo:xdl:ios:*`'; 83c4ef02aeSEvan Bacon } 84c4ef02aeSEvan Bacon throw new CommandError('APPLE_DEVICE_LOCKDOWN', errorMessage); 85c4ef02aeSEvan Bacon } 86c4ef02aeSEvan Bacon 87c4ef02aeSEvan Bacon throw new CommandError('APPLE_DEVICE_LOCKDOWN', resp.Error); 88c4ef02aeSEvan Bacon } 89c4ef02aeSEvan Bacon return resp; 90c4ef02aeSEvan Bacon } 91c4ef02aeSEvan Bacon} 92c4ef02aeSEvan Bacon 93c4ef02aeSEvan Baconexport class LockdownProtocolWriter implements ProtocolWriter { 94c4ef02aeSEvan Bacon write(socket: Socket, plistData: any) { 95c4ef02aeSEvan Bacon debug(`socket write: ${JSON.stringify(plistData)}`); 96c4ef02aeSEvan Bacon const plistMessage = plist.build(plistData); 97c4ef02aeSEvan Bacon const header = Buffer.alloc(LOCKDOWN_HEADER_SIZE); 98c4ef02aeSEvan Bacon header.writeUInt32BE(plistMessage.length, 0); 99c4ef02aeSEvan Bacon socket.write(header); 100c4ef02aeSEvan Bacon socket.write(plistMessage); 101c4ef02aeSEvan Bacon } 102c4ef02aeSEvan Bacon} 103