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'; 9eec3a87eSCedric van Puttenimport Debug from 'debug'; 10c4ef02aeSEvan Baconimport { Socket } from 'net'; 11c4ef02aeSEvan Bacon 12eec3a87eSCedric van Puttenimport { CommandError } from '../../../../utils/errors'; 13c4ef02aeSEvan Baconimport { parsePlistBuffer } from '../../../../utils/plist'; 14c4ef02aeSEvan Bacon 15c4ef02aeSEvan Baconconst BPLIST_MAGIC = Buffer.from('bplist00'); 16eec3a87eSCedric van Puttenconst debug = Debug('expo:apple-device:protocol'); 17eec3a87eSCedric van Putten 18eec3a87eSCedric van Puttenexport class ProtocolClientError extends CommandError { 19*8a424bebSJames Ide constructor( 20*8a424bebSJames Ide msg: string, 21*8a424bebSJames Ide public error: Error, 22*8a424bebSJames Ide public protocolMessage?: any 23*8a424bebSJames Ide ) { 24eec3a87eSCedric van Putten super(msg); 25eec3a87eSCedric van Putten } 26eec3a87eSCedric van Putten} 27c4ef02aeSEvan Bacon 28c4ef02aeSEvan Baconexport type ProtocolReaderCallback = (resp: any, err?: Error) => void; 29c4ef02aeSEvan Bacon 30c4ef02aeSEvan Baconexport class ProtocolReaderFactory<T> { 31c4ef02aeSEvan Bacon constructor(private ProtocolReader: new (callback: ProtocolReaderCallback) => T) {} 32c4ef02aeSEvan Bacon 33c4ef02aeSEvan Bacon create(callback: (resp: any, err?: Error) => void): T { 34c4ef02aeSEvan Bacon return new this.ProtocolReader(callback); 35c4ef02aeSEvan Bacon } 36c4ef02aeSEvan Bacon} 37c4ef02aeSEvan Bacon 38c4ef02aeSEvan Baconexport abstract class ProtocolReader { 39c4ef02aeSEvan Bacon protected body!: Buffer; // TODO: ! -> ? 40c4ef02aeSEvan Bacon protected bodyLength!: number; // TODO: ! -> ? 41c4ef02aeSEvan Bacon protected buffer = Buffer.alloc(0); 42*8a424bebSJames Ide constructor( 43*8a424bebSJames Ide protected headerSize: number, 44*8a424bebSJames Ide protected callback: ProtocolReaderCallback 45*8a424bebSJames Ide ) { 46c4ef02aeSEvan Bacon this.onData = this.onData.bind(this); 47c4ef02aeSEvan Bacon } 48c4ef02aeSEvan Bacon 49c4ef02aeSEvan Bacon /** Returns length of body, or -1 if header doesn't contain length */ 50c4ef02aeSEvan Bacon protected abstract parseHeader(data: Buffer): number; 51c4ef02aeSEvan Bacon protected abstract parseBody(data: Buffer): any; 52c4ef02aeSEvan Bacon 53c4ef02aeSEvan Bacon onData(data?: Buffer) { 54c4ef02aeSEvan Bacon try { 55c4ef02aeSEvan Bacon // if there's data, add it on to existing buffer 56c4ef02aeSEvan Bacon this.buffer = data ? Buffer.concat([this.buffer, data]) : this.buffer; 57c4ef02aeSEvan Bacon // we haven't gotten the body length from the header yet 58c4ef02aeSEvan Bacon if (!this.bodyLength) { 59c4ef02aeSEvan Bacon if (this.buffer.length < this.headerSize) { 60c4ef02aeSEvan Bacon // partial header, wait for rest 61c4ef02aeSEvan Bacon return; 62c4ef02aeSEvan Bacon } 63c4ef02aeSEvan Bacon this.bodyLength = this.parseHeader(this.buffer); 64c4ef02aeSEvan Bacon // move on to body 65c4ef02aeSEvan Bacon this.buffer = this.buffer.slice(this.headerSize); 66c4ef02aeSEvan Bacon if (!this.buffer.length) { 67c4ef02aeSEvan Bacon // only got header, wait for body 68c4ef02aeSEvan Bacon return; 69c4ef02aeSEvan Bacon } 70c4ef02aeSEvan Bacon } 71c4ef02aeSEvan Bacon if (this.buffer.length < this.bodyLength) { 72c4ef02aeSEvan Bacon // wait for rest of body 73c4ef02aeSEvan Bacon return; 74c4ef02aeSEvan Bacon } 75c4ef02aeSEvan Bacon 76c4ef02aeSEvan Bacon if (this.bodyLength === -1) { 77c4ef02aeSEvan Bacon this.callback(this.parseBody(this.buffer)); 78c4ef02aeSEvan Bacon this.buffer = Buffer.alloc(0); 79c4ef02aeSEvan Bacon } else { 80c4ef02aeSEvan Bacon this.body = this.buffer.slice(0, this.bodyLength); 81c4ef02aeSEvan Bacon this.bodyLength -= this.body.length; 82c4ef02aeSEvan Bacon if (!this.bodyLength) { 83c4ef02aeSEvan Bacon this.callback(this.parseBody(this.body)); 84c4ef02aeSEvan Bacon } 85c4ef02aeSEvan Bacon this.buffer = this.buffer.slice(this.body.length); 86c4ef02aeSEvan Bacon // There are multiple messages here, call parse again 87c4ef02aeSEvan Bacon if (this.buffer.length) { 88c4ef02aeSEvan Bacon this.onData(); 89c4ef02aeSEvan Bacon } 90c4ef02aeSEvan Bacon } 91c4ef02aeSEvan Bacon } catch (err: any) { 92c4ef02aeSEvan Bacon this.callback(null, err); 93c4ef02aeSEvan Bacon } 94c4ef02aeSEvan Bacon } 95c4ef02aeSEvan Bacon} 96c4ef02aeSEvan Bacon 97c4ef02aeSEvan Baconexport abstract class PlistProtocolReader extends ProtocolReader { 98c4ef02aeSEvan Bacon protected parseBody(body: Buffer) { 99c4ef02aeSEvan Bacon if (BPLIST_MAGIC.compare(body, 0, 8) === 0) { 100c4ef02aeSEvan Bacon return parsePlistBuffer(body); 101c4ef02aeSEvan Bacon } else { 102c4ef02aeSEvan Bacon return plist.parse(body.toString('utf8')); 103c4ef02aeSEvan Bacon } 104c4ef02aeSEvan Bacon } 105c4ef02aeSEvan Bacon} 106c4ef02aeSEvan Bacon 107c4ef02aeSEvan Baconexport interface ProtocolWriter { 108c4ef02aeSEvan Bacon write(sock: Socket, msg: any): void; 109c4ef02aeSEvan Bacon} 110c4ef02aeSEvan Bacon 111c4ef02aeSEvan Baconexport abstract class ProtocolClient<MessageType = any> { 112c4ef02aeSEvan Bacon constructor( 113c4ef02aeSEvan Bacon public socket: Socket, 114c4ef02aeSEvan Bacon protected readerFactory: ProtocolReaderFactory<ProtocolReader>, 115c4ef02aeSEvan Bacon protected writer: ProtocolWriter 116c4ef02aeSEvan Bacon ) {} 117c4ef02aeSEvan Bacon 118c4ef02aeSEvan Bacon sendMessage<ResponseType = any>(msg: MessageType): Promise<ResponseType>; 119c4ef02aeSEvan Bacon sendMessage<CallbackType = void, ResponseType = any>( 120c4ef02aeSEvan Bacon msg: MessageType, 121c4ef02aeSEvan Bacon callback: (response: ResponseType, resolve: any, reject: any) => void 122c4ef02aeSEvan Bacon ): Promise<CallbackType>; 123c4ef02aeSEvan Bacon sendMessage<CallbackType = void, ResponseType = any>( 124c4ef02aeSEvan Bacon msg: MessageType, 125c4ef02aeSEvan Bacon callback?: (response: ResponseType, resolve: any, reject: any) => void 126c4ef02aeSEvan Bacon ): Promise<CallbackType | ResponseType> { 127eec3a87eSCedric van Putten const onError = (error: Error) => { 128eec3a87eSCedric van Putten debug('Unexpected protocol socket error encountered: %s', error); 129eec3a87eSCedric van Putten throw new ProtocolClientError( 130eec3a87eSCedric van Putten `Unexpected protocol error encountered: ${error.message}`, 131eec3a87eSCedric van Putten error, 132eec3a87eSCedric van Putten msg 133eec3a87eSCedric van Putten ); 134eec3a87eSCedric van Putten }; 135eec3a87eSCedric van Putten 136c4ef02aeSEvan Bacon return new Promise<ResponseType | CallbackType>((resolve, reject) => { 137c4ef02aeSEvan Bacon const reader = this.readerFactory.create(async (response: ResponseType, error?: Error) => { 138c4ef02aeSEvan Bacon if (error) { 139c4ef02aeSEvan Bacon reject(error); 140c4ef02aeSEvan Bacon return; 141c4ef02aeSEvan Bacon } 142c4ef02aeSEvan Bacon if (callback) { 143c4ef02aeSEvan Bacon callback( 144c4ef02aeSEvan Bacon response, 145c4ef02aeSEvan Bacon (value: any) => { 146c4ef02aeSEvan Bacon this.socket.removeListener('data', reader.onData); 147eec3a87eSCedric van Putten this.socket.removeListener('error', onError); 148c4ef02aeSEvan Bacon resolve(value); 149c4ef02aeSEvan Bacon }, 150c4ef02aeSEvan Bacon reject 151c4ef02aeSEvan Bacon ); 152c4ef02aeSEvan Bacon } else { 153c4ef02aeSEvan Bacon this.socket.removeListener('data', reader.onData); 154eec3a87eSCedric van Putten this.socket.removeListener('error', onError); 155c4ef02aeSEvan Bacon resolve(response); 156c4ef02aeSEvan Bacon } 157c4ef02aeSEvan Bacon }); 158c4ef02aeSEvan Bacon this.socket.on('data', reader.onData); 159eec3a87eSCedric van Putten this.socket.on('error', onError); 160c4ef02aeSEvan Bacon this.writer.write(this.socket, msg); 161c4ef02aeSEvan Bacon }); 162c4ef02aeSEvan Bacon } 163c4ef02aeSEvan Bacon} 164