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 Debug from 'debug'; 9c4ef02aeSEvan Baconimport * as fs from 'fs'; 10c4ef02aeSEvan Baconimport { Socket } from 'net'; 11c4ef02aeSEvan Bacon 12*8a424bebSJames Ideimport { ResponseError, ServiceClient } from './ServiceClient'; 13c4ef02aeSEvan Baconimport type { LockdownCommand, LockdownResponse } from '../protocol/LockdownProtocol'; 14c4ef02aeSEvan Baconimport { isLockdownResponse, LockdownProtocolClient } from '../protocol/LockdownProtocol'; 15c4ef02aeSEvan Bacon 16c4ef02aeSEvan Baconconst debug = Debug('expo:apple-device:client:mobile_image_mounter'); 17c4ef02aeSEvan Bacon 18c4ef02aeSEvan Baconexport type MIMMountResponse = LockdownResponse; 19c4ef02aeSEvan Bacon 20c4ef02aeSEvan Baconexport interface MIMMessage extends LockdownCommand { 21c4ef02aeSEvan Bacon ImageType: string; 22c4ef02aeSEvan Bacon} 23c4ef02aeSEvan Bacon 24c4ef02aeSEvan Baconexport interface MIMLookupResponse extends LockdownResponse { 25c4ef02aeSEvan Bacon ImageSignature?: string; 26c4ef02aeSEvan Bacon} 27c4ef02aeSEvan Bacon 28c4ef02aeSEvan Baconexport interface MIMUploadCompleteResponse extends LockdownResponse { 29c4ef02aeSEvan Bacon Status: 'Complete'; 30c4ef02aeSEvan Bacon} 31c4ef02aeSEvan Bacon 32c4ef02aeSEvan Baconexport interface MIMUploadReceiveBytesResponse extends LockdownResponse { 33c4ef02aeSEvan Bacon Status: 'ReceiveBytesAck'; 34c4ef02aeSEvan Bacon} 35c4ef02aeSEvan Bacon 36c4ef02aeSEvan Baconfunction isMIMUploadCompleteResponse(resp: any): resp is MIMUploadCompleteResponse { 37c4ef02aeSEvan Bacon return resp.Status === 'Complete'; 38c4ef02aeSEvan Bacon} 39c4ef02aeSEvan Bacon 40c4ef02aeSEvan Baconfunction isMIMUploadReceiveBytesResponse(resp: any): resp is MIMUploadReceiveBytesResponse { 41c4ef02aeSEvan Bacon return resp.Status === 'ReceiveBytesAck'; 42c4ef02aeSEvan Bacon} 43c4ef02aeSEvan Bacon 44c4ef02aeSEvan Baconexport class MobileImageMounterClient extends ServiceClient<LockdownProtocolClient<MIMMessage>> { 45c4ef02aeSEvan Bacon constructor(socket: Socket) { 46c4ef02aeSEvan Bacon super(socket, new LockdownProtocolClient(socket)); 47c4ef02aeSEvan Bacon } 48c4ef02aeSEvan Bacon 49c4ef02aeSEvan Bacon async mountImage(imagePath: string, imageSig: Buffer) { 50c4ef02aeSEvan Bacon debug(`mountImage: ${imagePath}`); 51c4ef02aeSEvan Bacon 52c4ef02aeSEvan Bacon const resp = await this.protocolClient.sendMessage({ 53c4ef02aeSEvan Bacon Command: 'MountImage', 54c4ef02aeSEvan Bacon ImagePath: imagePath, 55c4ef02aeSEvan Bacon ImageSignature: imageSig, 56c4ef02aeSEvan Bacon ImageType: 'Developer', 57c4ef02aeSEvan Bacon }); 58c4ef02aeSEvan Bacon 59c4ef02aeSEvan Bacon if (!isLockdownResponse(resp) || resp.Status !== 'Complete') { 60c4ef02aeSEvan Bacon throw new ResponseError(`There was an error mounting ${imagePath} on device`, resp); 61c4ef02aeSEvan Bacon } 62c4ef02aeSEvan Bacon } 63c4ef02aeSEvan Bacon 64c4ef02aeSEvan Bacon async uploadImage(imagePath: string, imageSig: Buffer) { 65c4ef02aeSEvan Bacon debug(`uploadImage: ${imagePath}`); 66c4ef02aeSEvan Bacon 67c4ef02aeSEvan Bacon const imageSize = fs.statSync(imagePath).size; 68c4ef02aeSEvan Bacon return this.protocolClient.sendMessage( 69c4ef02aeSEvan Bacon { 70c4ef02aeSEvan Bacon Command: 'ReceiveBytes', 71c4ef02aeSEvan Bacon ImageSize: imageSize, 72c4ef02aeSEvan Bacon ImageSignature: imageSig, 73c4ef02aeSEvan Bacon ImageType: 'Developer', 74c4ef02aeSEvan Bacon }, 75c4ef02aeSEvan Bacon (resp: any, resolve, reject) => { 76c4ef02aeSEvan Bacon if (isMIMUploadReceiveBytesResponse(resp)) { 77c4ef02aeSEvan Bacon const imageStream = fs.createReadStream(imagePath); 78c4ef02aeSEvan Bacon imageStream.pipe(this.protocolClient.socket, { end: false }); 79c4ef02aeSEvan Bacon imageStream.on('error', (err) => reject(err)); 80c4ef02aeSEvan Bacon } else if (isMIMUploadCompleteResponse(resp)) { 81c4ef02aeSEvan Bacon resolve(); 82c4ef02aeSEvan Bacon } else { 83c4ef02aeSEvan Bacon reject( 84c4ef02aeSEvan Bacon new ResponseError(`There was an error uploading image ${imagePath} to the device`, resp) 85c4ef02aeSEvan Bacon ); 86c4ef02aeSEvan Bacon } 87c4ef02aeSEvan Bacon } 88c4ef02aeSEvan Bacon ); 89c4ef02aeSEvan Bacon } 90c4ef02aeSEvan Bacon 91c4ef02aeSEvan Bacon async lookupImage() { 92c4ef02aeSEvan Bacon debug('lookupImage'); 93c4ef02aeSEvan Bacon 94c4ef02aeSEvan Bacon return this.protocolClient.sendMessage<MIMLookupResponse>({ 95c4ef02aeSEvan Bacon Command: 'LookupImage', 96c4ef02aeSEvan Bacon ImageType: 'Developer', 97c4ef02aeSEvan Bacon }); 98c4ef02aeSEvan Bacon } 99c4ef02aeSEvan Bacon} 100