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