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 { Socket } from 'net'; 10c4ef02aeSEvan Bacon 11c4ef02aeSEvan Baconimport type { ProtocolReaderCallback, ProtocolWriter } from './AbstractProtocol'; 12c4ef02aeSEvan Baconimport { ProtocolClient, ProtocolReader, ProtocolReaderFactory } from './AbstractProtocol'; 13*8a424bebSJames Ideimport { CommandError } from '../../../../utils/errors'; 14c4ef02aeSEvan Bacon 15c4ef02aeSEvan Baconconst debug = Debug('expo:apple-device:protocol:afc'); 16c4ef02aeSEvan Bacon 17c4ef02aeSEvan Baconexport const AFC_MAGIC = 'CFA6LPAA'; 18c4ef02aeSEvan Baconexport const AFC_HEADER_SIZE = 40; 19c4ef02aeSEvan Bacon 20c4ef02aeSEvan Baconexport interface AFCHeader { 21c4ef02aeSEvan Bacon magic: typeof AFC_MAGIC; 22c4ef02aeSEvan Bacon totalLength: number; 23c4ef02aeSEvan Bacon headerLength: number; 24c4ef02aeSEvan Bacon requestId: number; 25c4ef02aeSEvan Bacon operation: AFC_OPS; 26c4ef02aeSEvan Bacon} 27c4ef02aeSEvan Bacon 28c4ef02aeSEvan Baconexport interface AFCMessage { 29c4ef02aeSEvan Bacon operation: AFC_OPS; 30c4ef02aeSEvan Bacon data?: any; 31c4ef02aeSEvan Bacon payload?: any; 32c4ef02aeSEvan Bacon} 33c4ef02aeSEvan Bacon 34c4ef02aeSEvan Baconexport interface AFCResponse { 35c4ef02aeSEvan Bacon operation: AFC_OPS; 36c4ef02aeSEvan Bacon id: number; 37c4ef02aeSEvan Bacon data: Buffer; 38c4ef02aeSEvan Bacon} 39c4ef02aeSEvan Bacon 40c4ef02aeSEvan Baconexport interface AFCStatusResponse { 41c4ef02aeSEvan Bacon operation: AFC_OPS.STATUS; 42c4ef02aeSEvan Bacon id: number; 43c4ef02aeSEvan Bacon data: number; 44c4ef02aeSEvan Bacon} 45c4ef02aeSEvan Bacon 46c4ef02aeSEvan Bacon/** 47c4ef02aeSEvan Bacon * AFC Operations 48c4ef02aeSEvan Bacon */ 49c4ef02aeSEvan Baconexport enum AFC_OPS { 50c4ef02aeSEvan Bacon /** 51c4ef02aeSEvan Bacon * Invalid 52c4ef02aeSEvan Bacon */ 53c4ef02aeSEvan Bacon INVALID = 0x00000000, 54c4ef02aeSEvan Bacon 55c4ef02aeSEvan Bacon /** 56c4ef02aeSEvan Bacon * Status 57c4ef02aeSEvan Bacon */ 58c4ef02aeSEvan Bacon STATUS = 0x00000001, 59c4ef02aeSEvan Bacon 60c4ef02aeSEvan Bacon /** 61c4ef02aeSEvan Bacon * Data 62c4ef02aeSEvan Bacon */ 63c4ef02aeSEvan Bacon DATA = 0x00000002, 64c4ef02aeSEvan Bacon 65c4ef02aeSEvan Bacon /** 66c4ef02aeSEvan Bacon * ReadDir 67c4ef02aeSEvan Bacon */ 68c4ef02aeSEvan Bacon READ_DIR = 0x00000003, 69c4ef02aeSEvan Bacon 70c4ef02aeSEvan Bacon /** 71c4ef02aeSEvan Bacon * ReadFile 72c4ef02aeSEvan Bacon */ 73c4ef02aeSEvan Bacon READ_FILE = 0x00000004, 74c4ef02aeSEvan Bacon 75c4ef02aeSEvan Bacon /** 76c4ef02aeSEvan Bacon * WriteFile 77c4ef02aeSEvan Bacon */ 78c4ef02aeSEvan Bacon WRITE_FILE = 0x00000005, 79c4ef02aeSEvan Bacon 80c4ef02aeSEvan Bacon /** 81c4ef02aeSEvan Bacon * WritePart 82c4ef02aeSEvan Bacon */ 83c4ef02aeSEvan Bacon WRITE_PART = 0x00000006, 84c4ef02aeSEvan Bacon 85c4ef02aeSEvan Bacon /** 86c4ef02aeSEvan Bacon * TruncateFile 87c4ef02aeSEvan Bacon */ 88c4ef02aeSEvan Bacon TRUNCATE = 0x00000007, 89c4ef02aeSEvan Bacon 90c4ef02aeSEvan Bacon /** 91c4ef02aeSEvan Bacon * RemovePath 92c4ef02aeSEvan Bacon */ 93c4ef02aeSEvan Bacon REMOVE_PATH = 0x00000008, 94c4ef02aeSEvan Bacon 95c4ef02aeSEvan Bacon /** 96c4ef02aeSEvan Bacon * MakeDir 97c4ef02aeSEvan Bacon */ 98c4ef02aeSEvan Bacon MAKE_DIR = 0x00000009, 99c4ef02aeSEvan Bacon 100c4ef02aeSEvan Bacon /** 101c4ef02aeSEvan Bacon * GetFileInfo 102c4ef02aeSEvan Bacon */ 103c4ef02aeSEvan Bacon GET_FILE_INFO = 0x0000000a, 104c4ef02aeSEvan Bacon 105c4ef02aeSEvan Bacon /** 106c4ef02aeSEvan Bacon * GetDeviceInfo 107c4ef02aeSEvan Bacon */ 108c4ef02aeSEvan Bacon GET_DEVINFO = 0x0000000b, 109c4ef02aeSEvan Bacon 110c4ef02aeSEvan Bacon /** 111c4ef02aeSEvan Bacon * WriteFileAtomic (tmp file+rename) 112c4ef02aeSEvan Bacon */ 113c4ef02aeSEvan Bacon WRITE_FILE_ATOM = 0x0000000c, 114c4ef02aeSEvan Bacon 115c4ef02aeSEvan Bacon /** 116c4ef02aeSEvan Bacon * FileRefOpen 117c4ef02aeSEvan Bacon */ 118c4ef02aeSEvan Bacon FILE_OPEN = 0x0000000d, 119c4ef02aeSEvan Bacon 120c4ef02aeSEvan Bacon /** 121c4ef02aeSEvan Bacon * FileRefOpenResult 122c4ef02aeSEvan Bacon */ 123c4ef02aeSEvan Bacon FILE_OPEN_RES = 0x0000000e, 124c4ef02aeSEvan Bacon 125c4ef02aeSEvan Bacon /** 126c4ef02aeSEvan Bacon * FileRefRead 127c4ef02aeSEvan Bacon */ 128c4ef02aeSEvan Bacon FILE_READ = 0x0000000f, 129c4ef02aeSEvan Bacon 130c4ef02aeSEvan Bacon /** 131c4ef02aeSEvan Bacon * FileRefWrite 132c4ef02aeSEvan Bacon */ 133c4ef02aeSEvan Bacon FILE_WRITE = 0x00000010, 134c4ef02aeSEvan Bacon 135c4ef02aeSEvan Bacon /** 136c4ef02aeSEvan Bacon * FileRefSeek 137c4ef02aeSEvan Bacon */ 138c4ef02aeSEvan Bacon FILE_SEEK = 0x00000011, 139c4ef02aeSEvan Bacon 140c4ef02aeSEvan Bacon /** 141c4ef02aeSEvan Bacon * FileRefTell 142c4ef02aeSEvan Bacon */ 143c4ef02aeSEvan Bacon FILE_TELL = 0x00000012, 144c4ef02aeSEvan Bacon 145c4ef02aeSEvan Bacon /** 146c4ef02aeSEvan Bacon * FileRefTellResult 147c4ef02aeSEvan Bacon */ 148c4ef02aeSEvan Bacon FILE_TELL_RES = 0x00000013, 149c4ef02aeSEvan Bacon 150c4ef02aeSEvan Bacon /** 151c4ef02aeSEvan Bacon * FileRefClose 152c4ef02aeSEvan Bacon */ 153c4ef02aeSEvan Bacon FILE_CLOSE = 0x00000014, 154c4ef02aeSEvan Bacon 155c4ef02aeSEvan Bacon /** 156c4ef02aeSEvan Bacon * FileRefSetFileSize (ftruncate) 157c4ef02aeSEvan Bacon */ 158c4ef02aeSEvan Bacon FILE_SET_SIZE = 0x00000015, 159c4ef02aeSEvan Bacon 160c4ef02aeSEvan Bacon /** 161c4ef02aeSEvan Bacon * GetConnectionInfo 162c4ef02aeSEvan Bacon */ 163c4ef02aeSEvan Bacon GET_CON_INFO = 0x00000016, 164c4ef02aeSEvan Bacon 165c4ef02aeSEvan Bacon /** 166c4ef02aeSEvan Bacon * SetConnectionOptions 167c4ef02aeSEvan Bacon */ 168c4ef02aeSEvan Bacon SET_CON_OPTIONS = 0x00000017, 169c4ef02aeSEvan Bacon 170c4ef02aeSEvan Bacon /** 171c4ef02aeSEvan Bacon * RenamePath 172c4ef02aeSEvan Bacon */ 173c4ef02aeSEvan Bacon RENAME_PATH = 0x00000018, 174c4ef02aeSEvan Bacon 175c4ef02aeSEvan Bacon /** 176c4ef02aeSEvan Bacon * SetFSBlockSize (0x800000) 177c4ef02aeSEvan Bacon */ 178c4ef02aeSEvan Bacon SET_FS_BS = 0x00000019, 179c4ef02aeSEvan Bacon 180c4ef02aeSEvan Bacon /** 181c4ef02aeSEvan Bacon * SetSocketBlockSize (0x800000) 182c4ef02aeSEvan Bacon */ 183c4ef02aeSEvan Bacon SET_SOCKET_BS = 0x0000001a, 184c4ef02aeSEvan Bacon 185c4ef02aeSEvan Bacon /** 186c4ef02aeSEvan Bacon * FileRefLock 187c4ef02aeSEvan Bacon */ 188c4ef02aeSEvan Bacon FILE_LOCK = 0x0000001b, 189c4ef02aeSEvan Bacon 190c4ef02aeSEvan Bacon /** 191c4ef02aeSEvan Bacon * MakeLink 192c4ef02aeSEvan Bacon */ 193c4ef02aeSEvan Bacon MAKE_LINK = 0x0000001c, 194c4ef02aeSEvan Bacon 195c4ef02aeSEvan Bacon /** 196c4ef02aeSEvan Bacon * GetFileHash 197c4ef02aeSEvan Bacon */ 198c4ef02aeSEvan Bacon GET_FILE_HASH = 0x0000001d, 199c4ef02aeSEvan Bacon 200c4ef02aeSEvan Bacon /** 201c4ef02aeSEvan Bacon * SetModTime 202c4ef02aeSEvan Bacon */ 203c4ef02aeSEvan Bacon SET_FILE_MOD_TIME = 0x0000001e, 204c4ef02aeSEvan Bacon 205c4ef02aeSEvan Bacon /** 206c4ef02aeSEvan Bacon * GetFileHashWithRange 207c4ef02aeSEvan Bacon */ 208c4ef02aeSEvan Bacon GET_FILE_HASH_RANGE = 0x0000001f, 209c4ef02aeSEvan Bacon 210c4ef02aeSEvan Bacon // iOS 6+ 211c4ef02aeSEvan Bacon 212c4ef02aeSEvan Bacon /** 213c4ef02aeSEvan Bacon * FileRefSetImmutableHint 214c4ef02aeSEvan Bacon */ 215c4ef02aeSEvan Bacon FILE_SET_IMMUTABLE_HINT = 0x00000020, 216c4ef02aeSEvan Bacon 217c4ef02aeSEvan Bacon /** 218c4ef02aeSEvan Bacon * GetSizeOfPathContents 219c4ef02aeSEvan Bacon */ 220c4ef02aeSEvan Bacon GET_SIZE_OF_PATH_CONTENTS = 0x00000021, 221c4ef02aeSEvan Bacon 222c4ef02aeSEvan Bacon /** 223c4ef02aeSEvan Bacon * RemovePathAndContents 224c4ef02aeSEvan Bacon */ 225c4ef02aeSEvan Bacon REMOVE_PATH_AND_CONTENTS = 0x00000022, 226c4ef02aeSEvan Bacon 227c4ef02aeSEvan Bacon /** 228c4ef02aeSEvan Bacon * DirectoryEnumeratorRefOpen 229c4ef02aeSEvan Bacon */ 230c4ef02aeSEvan Bacon DIR_OPEN = 0x00000023, 231c4ef02aeSEvan Bacon 232c4ef02aeSEvan Bacon /** 233c4ef02aeSEvan Bacon * DirectoryEnumeratorRefOpenResult 234c4ef02aeSEvan Bacon */ 235c4ef02aeSEvan Bacon DIR_OPEN_RESULT = 0x00000024, 236c4ef02aeSEvan Bacon 237c4ef02aeSEvan Bacon /** 238c4ef02aeSEvan Bacon * DirectoryEnumeratorRefRead 239c4ef02aeSEvan Bacon */ 240c4ef02aeSEvan Bacon DIR_READ = 0x00000025, 241c4ef02aeSEvan Bacon 242c4ef02aeSEvan Bacon /** 243c4ef02aeSEvan Bacon * DirectoryEnumeratorRefClose 244c4ef02aeSEvan Bacon */ 245c4ef02aeSEvan Bacon DIR_CLOSE = 0x00000026, 246c4ef02aeSEvan Bacon 247c4ef02aeSEvan Bacon // iOS 7+ 248c4ef02aeSEvan Bacon 249c4ef02aeSEvan Bacon /** 250c4ef02aeSEvan Bacon * FileRefReadWithOffset 251c4ef02aeSEvan Bacon */ 252c4ef02aeSEvan Bacon FILE_READ_OFFSET = 0x00000027, 253c4ef02aeSEvan Bacon 254c4ef02aeSEvan Bacon /** 255c4ef02aeSEvan Bacon * FileRefWriteWithOffset 256c4ef02aeSEvan Bacon */ 257c4ef02aeSEvan Bacon FILE_WRITE_OFFSET = 0x00000028, 258c4ef02aeSEvan Bacon} 259c4ef02aeSEvan Bacon 260c4ef02aeSEvan Bacon/** 261c4ef02aeSEvan Bacon * Error Codes 262c4ef02aeSEvan Bacon */ 263c4ef02aeSEvan Baconexport enum AFC_STATUS { 264c4ef02aeSEvan Bacon SUCCESS = 0, 265c4ef02aeSEvan Bacon UNKNOWN_ERROR = 1, 266c4ef02aeSEvan Bacon OP_HEADER_INVALID = 2, 267c4ef02aeSEvan Bacon NO_RESOURCES = 3, 268c4ef02aeSEvan Bacon READ_ERROR = 4, 269c4ef02aeSEvan Bacon WRITE_ERROR = 5, 270c4ef02aeSEvan Bacon UNKNOWN_PACKET_TYPE = 6, 271c4ef02aeSEvan Bacon INVALID_ARG = 7, 272c4ef02aeSEvan Bacon OBJECT_NOT_FOUND = 8, 273c4ef02aeSEvan Bacon OBJECT_IS_DIR = 9, 274c4ef02aeSEvan Bacon PERM_DENIED = 10, 275c4ef02aeSEvan Bacon SERVICE_NOT_CONNECTED = 11, 276c4ef02aeSEvan Bacon OP_TIMEOUT = 12, 277c4ef02aeSEvan Bacon TOO_MUCH_DATA = 13, 278c4ef02aeSEvan Bacon END_OF_DATA = 14, 279c4ef02aeSEvan Bacon OP_NOT_SUPPORTED = 15, 280c4ef02aeSEvan Bacon OBJECT_EXISTS = 16, 281c4ef02aeSEvan Bacon OBJECT_BUSY = 17, 282c4ef02aeSEvan Bacon NO_SPACE_LEFT = 18, 283c4ef02aeSEvan Bacon OP_WOULD_BLOCK = 19, 284c4ef02aeSEvan Bacon IO_ERROR = 20, 285c4ef02aeSEvan Bacon OP_INTERRUPTED = 21, 286c4ef02aeSEvan Bacon OP_IN_PROGRESS = 22, 287c4ef02aeSEvan Bacon INTERNAL_ERROR = 23, 288c4ef02aeSEvan Bacon MUX_ERROR = 30, 289c4ef02aeSEvan Bacon NO_MEM = 31, 290c4ef02aeSEvan Bacon NOT_ENOUGH_DATA = 32, 291c4ef02aeSEvan Bacon DIR_NOT_EMPTY = 33, 292c4ef02aeSEvan Bacon FORCE_SIGNED_TYPE = -1, 293c4ef02aeSEvan Bacon} 294c4ef02aeSEvan Bacon 295c4ef02aeSEvan Baconexport enum AFC_FILE_OPEN_FLAGS { 296c4ef02aeSEvan Bacon /** 297c4ef02aeSEvan Bacon * r (O_RDONLY) 298c4ef02aeSEvan Bacon */ 299c4ef02aeSEvan Bacon RDONLY = 0x00000001, 300c4ef02aeSEvan Bacon 301c4ef02aeSEvan Bacon /** 302c4ef02aeSEvan Bacon * r+ (O_RDWR | O_CREAT) 303c4ef02aeSEvan Bacon */ 304c4ef02aeSEvan Bacon RW = 0x00000002, 305c4ef02aeSEvan Bacon 306c4ef02aeSEvan Bacon /** 307c4ef02aeSEvan Bacon * w (O_WRONLY | O_CREAT | O_TRUNC) 308c4ef02aeSEvan Bacon */ 309c4ef02aeSEvan Bacon WRONLY = 0x00000003, 310c4ef02aeSEvan Bacon 311c4ef02aeSEvan Bacon /** 312c4ef02aeSEvan Bacon * w+ (O_RDWR | O_CREAT | O_TRUNC) 313c4ef02aeSEvan Bacon */ 314c4ef02aeSEvan Bacon WR = 0x00000004, 315c4ef02aeSEvan Bacon 316c4ef02aeSEvan Bacon /** 317c4ef02aeSEvan Bacon * a (O_WRONLY | O_APPEND | O_CREAT) 318c4ef02aeSEvan Bacon */ 319c4ef02aeSEvan Bacon APPEND = 0x00000005, 320c4ef02aeSEvan Bacon 321c4ef02aeSEvan Bacon /** 322c4ef02aeSEvan Bacon * a+ (O_RDWR | O_APPEND | O_CREAT) 323c4ef02aeSEvan Bacon */ 324c4ef02aeSEvan Bacon RDAPPEND = 0x00000006, 325c4ef02aeSEvan Bacon} 326c4ef02aeSEvan Bacon 327c4ef02aeSEvan Baconfunction isAFCResponse(resp: any): resp is AFCResponse { 328c4ef02aeSEvan Bacon return AFC_OPS[resp.operation] !== undefined && resp.id !== undefined && resp.data !== undefined; 329c4ef02aeSEvan Bacon} 330c4ef02aeSEvan Bacon 331c4ef02aeSEvan Baconfunction isStatusResponse(resp: any): resp is AFCStatusResponse { 332c4ef02aeSEvan Bacon return isAFCResponse(resp) && resp.operation === AFC_OPS.STATUS; 333c4ef02aeSEvan Bacon} 334c4ef02aeSEvan Bacon 335c4ef02aeSEvan Baconfunction isErrorStatusResponse(resp: AFCResponse): boolean { 336c4ef02aeSEvan Bacon return isStatusResponse(resp) && resp.data !== AFC_STATUS.SUCCESS; 337c4ef02aeSEvan Bacon} 338c4ef02aeSEvan Bacon 339c4ef02aeSEvan Baconclass AFCInternalError extends Error { 340*8a424bebSJames Ide constructor( 341*8a424bebSJames Ide msg: string, 342*8a424bebSJames Ide public requestId: number 343*8a424bebSJames Ide ) { 344c4ef02aeSEvan Bacon super(msg); 345c4ef02aeSEvan Bacon } 346c4ef02aeSEvan Bacon} 347c4ef02aeSEvan Bacon 348c4ef02aeSEvan Baconexport class AFCError extends Error { 349*8a424bebSJames Ide constructor( 350*8a424bebSJames Ide msg: string, 351*8a424bebSJames Ide public status: AFC_STATUS 352*8a424bebSJames Ide ) { 353c4ef02aeSEvan Bacon super(msg); 354c4ef02aeSEvan Bacon } 355c4ef02aeSEvan Bacon} 356c4ef02aeSEvan Bacon 357c4ef02aeSEvan Baconexport class AFCProtocolClient extends ProtocolClient { 358c4ef02aeSEvan Bacon private requestId = 0; 359c4ef02aeSEvan Bacon private requestCallbacks: { [key: number]: ProtocolReaderCallback } = {}; 360c4ef02aeSEvan Bacon 361c4ef02aeSEvan Bacon constructor(socket: Socket) { 362c4ef02aeSEvan Bacon super(socket, new ProtocolReaderFactory(AFCProtocolReader), new AFCProtocolWriter()); 363c4ef02aeSEvan Bacon 364c4ef02aeSEvan Bacon const reader = this.readerFactory.create((resp, err) => { 365c4ef02aeSEvan Bacon if (err && err instanceof AFCInternalError) { 366c4ef02aeSEvan Bacon this.requestCallbacks[err.requestId](resp, err); 367c4ef02aeSEvan Bacon } else if (isErrorStatusResponse(resp)) { 368c4ef02aeSEvan Bacon this.requestCallbacks[resp.id](resp, new AFCError(AFC_STATUS[resp.data], resp.data)); 369c4ef02aeSEvan Bacon } else { 370c4ef02aeSEvan Bacon this.requestCallbacks[resp.id](resp); 371c4ef02aeSEvan Bacon } 372c4ef02aeSEvan Bacon }); 373c4ef02aeSEvan Bacon socket.on('data', reader.onData); 374c4ef02aeSEvan Bacon } 375c4ef02aeSEvan Bacon 376c4ef02aeSEvan Bacon sendMessage(msg: AFCMessage): Promise<AFCResponse> { 377c4ef02aeSEvan Bacon return new Promise<AFCResponse>((resolve, reject) => { 378c4ef02aeSEvan Bacon const requestId = this.requestId++; 379c4ef02aeSEvan Bacon this.requestCallbacks[requestId] = async (resp: any, err?: Error) => { 380c4ef02aeSEvan Bacon if (err) { 381c4ef02aeSEvan Bacon reject(err); 382c4ef02aeSEvan Bacon return; 383c4ef02aeSEvan Bacon } 384c4ef02aeSEvan Bacon if (isAFCResponse(resp)) { 385c4ef02aeSEvan Bacon resolve(resp); 386c4ef02aeSEvan Bacon } else { 387c4ef02aeSEvan Bacon reject(new CommandError('APPLE_DEVICE_AFC', 'Malformed AFC response')); 388c4ef02aeSEvan Bacon } 389c4ef02aeSEvan Bacon }; 390c4ef02aeSEvan Bacon this.writer.write(this.socket, { ...msg, requestId }); 391c4ef02aeSEvan Bacon }); 392c4ef02aeSEvan Bacon } 393c4ef02aeSEvan Bacon} 394c4ef02aeSEvan Bacon 395c4ef02aeSEvan Baconexport class AFCProtocolReader extends ProtocolReader { 396c4ef02aeSEvan Bacon private header!: AFCHeader; // TODO: ! -> ? 397c4ef02aeSEvan Bacon 398c4ef02aeSEvan Bacon constructor(callback: ProtocolReaderCallback) { 399c4ef02aeSEvan Bacon super(AFC_HEADER_SIZE, callback); 400c4ef02aeSEvan Bacon } 401c4ef02aeSEvan Bacon 402c4ef02aeSEvan Bacon parseHeader(data: Buffer) { 403c4ef02aeSEvan Bacon const magic = data.slice(0, 8).toString('ascii'); 404c4ef02aeSEvan Bacon if (magic !== AFC_MAGIC) { 405c4ef02aeSEvan Bacon throw new AFCInternalError( 406c4ef02aeSEvan Bacon `Invalid AFC packet received (magic != ${AFC_MAGIC})`, 407c4ef02aeSEvan Bacon data.readUInt32LE(24) 408c4ef02aeSEvan Bacon ); 409c4ef02aeSEvan Bacon } 410c4ef02aeSEvan Bacon // technically these are uint64 411c4ef02aeSEvan Bacon this.header = { 412c4ef02aeSEvan Bacon magic, 413c4ef02aeSEvan Bacon totalLength: data.readUInt32LE(8), 414c4ef02aeSEvan Bacon headerLength: data.readUInt32LE(16), 415c4ef02aeSEvan Bacon requestId: data.readUInt32LE(24), 416c4ef02aeSEvan Bacon operation: data.readUInt32LE(32), 417c4ef02aeSEvan Bacon }; 418c4ef02aeSEvan Bacon 419c4ef02aeSEvan Bacon debug(`parse header: ${JSON.stringify(this.header)}`); 420c4ef02aeSEvan Bacon if (this.header.headerLength < AFC_HEADER_SIZE) { 421c4ef02aeSEvan Bacon throw new AFCInternalError('Invalid AFC header', this.header.requestId); 422c4ef02aeSEvan Bacon } 423c4ef02aeSEvan Bacon return this.header.totalLength - AFC_HEADER_SIZE; 424c4ef02aeSEvan Bacon } 425c4ef02aeSEvan Bacon 426c4ef02aeSEvan Bacon parseBody(data: Buffer): AFCResponse | AFCStatusResponse { 427c4ef02aeSEvan Bacon const body: any = { 428c4ef02aeSEvan Bacon operation: this.header.operation, 429c4ef02aeSEvan Bacon id: this.header.requestId, 430c4ef02aeSEvan Bacon data, 431c4ef02aeSEvan Bacon }; 432c4ef02aeSEvan Bacon if (isStatusResponse(body)) { 433c4ef02aeSEvan Bacon const status = data.readUInt32LE(0); 434c4ef02aeSEvan Bacon debug(`${AFC_OPS[this.header.operation]} response: ${AFC_STATUS[status]}`); 435c4ef02aeSEvan Bacon body.data = status; 436c4ef02aeSEvan Bacon } else if (data.length <= 8) { 437c4ef02aeSEvan Bacon debug(`${AFC_OPS[this.header.operation]} response: ${Array.prototype.toString.call(body)}`); 438c4ef02aeSEvan Bacon } else { 439c4ef02aeSEvan Bacon debug(`${AFC_OPS[this.header.operation]} response length: ${data.length} bytes`); 440c4ef02aeSEvan Bacon } 441c4ef02aeSEvan Bacon return body; 442c4ef02aeSEvan Bacon } 443c4ef02aeSEvan Bacon} 444c4ef02aeSEvan Bacon 445c4ef02aeSEvan Baconexport class AFCProtocolWriter implements ProtocolWriter { 446c4ef02aeSEvan Bacon write(socket: Socket, msg: AFCMessage & { requestId: number }) { 447c4ef02aeSEvan Bacon const { data, payload, operation, requestId } = msg; 448c4ef02aeSEvan Bacon 449c4ef02aeSEvan Bacon const dataLength = data ? data.length : 0; 450c4ef02aeSEvan Bacon const payloadLength = payload ? payload.length : 0; 451c4ef02aeSEvan Bacon 452c4ef02aeSEvan Bacon const header = Buffer.alloc(AFC_HEADER_SIZE); 453c4ef02aeSEvan Bacon const magic = Buffer.from(AFC_MAGIC); 454c4ef02aeSEvan Bacon magic.copy(header); 455c4ef02aeSEvan Bacon header.writeUInt32LE(AFC_HEADER_SIZE + dataLength + payloadLength, 8); 456c4ef02aeSEvan Bacon header.writeUInt32LE(AFC_HEADER_SIZE + dataLength, 16); 457c4ef02aeSEvan Bacon header.writeUInt32LE(requestId, 24); 458c4ef02aeSEvan Bacon header.writeUInt32LE(operation, 32); 459c4ef02aeSEvan Bacon socket.write(header); 460c4ef02aeSEvan Bacon socket.write(data); 461c4ef02aeSEvan Bacon if (data.length <= 8) { 462c4ef02aeSEvan Bacon debug( 463c4ef02aeSEvan Bacon `socket write, header: { requestId: ${requestId}, operation: ${ 464c4ef02aeSEvan Bacon AFC_OPS[operation] 465c4ef02aeSEvan Bacon }}, body: ${Array.prototype.toString.call(data)}` 466c4ef02aeSEvan Bacon ); 467c4ef02aeSEvan Bacon } else { 468c4ef02aeSEvan Bacon debug( 469c4ef02aeSEvan Bacon `socket write, header: { requestId: ${requestId}, operation: ${AFC_OPS[operation]}}, body: ${data.length} bytes` 470c4ef02aeSEvan Bacon ); 471c4ef02aeSEvan Bacon } 472c4ef02aeSEvan Bacon 473c4ef02aeSEvan Bacon debug(`socket write, bytes written ${header.length} (header), ${data.length} (body)`); 474c4ef02aeSEvan Bacon if (payload) { 475c4ef02aeSEvan Bacon socket.write(payload); 476c4ef02aeSEvan Bacon } 477c4ef02aeSEvan Bacon } 478c4ef02aeSEvan Bacon} 479