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