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