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 Bacon
9c4ef02aeSEvan Baconimport plist from '@expo/plist';
10c4ef02aeSEvan Baconimport Debug from 'debug';
11c4ef02aeSEvan Baconimport { Socket } from 'net';
12c4ef02aeSEvan Bacon
13c4ef02aeSEvan Baconimport type { ProtocolWriter } from './AbstractProtocol';
14c4ef02aeSEvan Baconimport { PlistProtocolReader, ProtocolClient, ProtocolReaderFactory } from './AbstractProtocol';
15*8a424bebSJames Ideimport { CommandError } from '../../../../utils/errors';
16c4ef02aeSEvan Bacon
17c4ef02aeSEvan Baconconst debug = Debug('expo:apple-device:protocol:lockdown');
18c4ef02aeSEvan Baconexport const LOCKDOWN_HEADER_SIZE = 4;
19c4ef02aeSEvan Bacon
20c4ef02aeSEvan Baconexport interface LockdownCommand {
21c4ef02aeSEvan Bacon  Command: string;
22c4ef02aeSEvan Bacon  [key: string]: any;
23c4ef02aeSEvan Bacon}
24c4ef02aeSEvan Bacon
25c4ef02aeSEvan Baconexport interface LockdownResponse {
26c4ef02aeSEvan Bacon  Status: string;
27c4ef02aeSEvan Bacon  [key: string]: any;
28c4ef02aeSEvan Bacon}
29c4ef02aeSEvan Bacon
30c4ef02aeSEvan Baconexport interface LockdownErrorResponse {
31c4ef02aeSEvan Bacon  Error: string;
32c4ef02aeSEvan Bacon  Request?: string;
33c4ef02aeSEvan Bacon  Service?: string;
34c4ef02aeSEvan Bacon}
35c4ef02aeSEvan Bacon
36c4ef02aeSEvan Baconexport interface LockdownRequest {
37c4ef02aeSEvan Bacon  Request: string;
38c4ef02aeSEvan Bacon  [key: string]: any;
39c4ef02aeSEvan Bacon}
40c4ef02aeSEvan Bacon
41c4ef02aeSEvan Baconfunction isDefined(val: any) {
42c4ef02aeSEvan Bacon  return typeof val !== 'undefined';
43c4ef02aeSEvan Bacon}
44c4ef02aeSEvan Bacon
45c4ef02aeSEvan Baconexport function isLockdownResponse(resp: any): resp is LockdownResponse {
46c4ef02aeSEvan Bacon  return isDefined(resp.Status);
47c4ef02aeSEvan Bacon}
48c4ef02aeSEvan Bacon
49c4ef02aeSEvan Baconexport function isLockdownErrorResponse(resp: any): resp is LockdownErrorResponse {
50c4ef02aeSEvan Bacon  return isDefined(resp.Error);
51c4ef02aeSEvan Bacon}
52c4ef02aeSEvan Bacon
53c4ef02aeSEvan Baconexport class LockdownProtocolClient<
54*8a424bebSJames Ide  MessageType extends LockdownRequest | LockdownCommand = LockdownRequest,
55c4ef02aeSEvan Bacon> extends ProtocolClient<MessageType> {
56c4ef02aeSEvan Bacon  constructor(socket: Socket) {
57c4ef02aeSEvan Bacon    super(socket, new ProtocolReaderFactory(LockdownProtocolReader), new LockdownProtocolWriter());
58c4ef02aeSEvan Bacon  }
59c4ef02aeSEvan Bacon}
60c4ef02aeSEvan Bacon
61c4ef02aeSEvan Baconexport class LockdownProtocolReader extends PlistProtocolReader {
62c4ef02aeSEvan Bacon  constructor(callback: (data: any) => any) {
63c4ef02aeSEvan Bacon    super(LOCKDOWN_HEADER_SIZE, callback);
64c4ef02aeSEvan Bacon  }
65c4ef02aeSEvan Bacon
66c4ef02aeSEvan Bacon  parseHeader(data: Buffer) {
67c4ef02aeSEvan Bacon    return data.readUInt32BE(0);
68c4ef02aeSEvan Bacon  }
69c4ef02aeSEvan Bacon
70c4ef02aeSEvan Bacon  parseBody(data: Buffer) {
71c4ef02aeSEvan Bacon    const resp = super.parseBody(data);
72c4ef02aeSEvan Bacon    debug(`Response: ${JSON.stringify(resp)}`);
73c4ef02aeSEvan Bacon    if (isLockdownErrorResponse(resp)) {
74c4ef02aeSEvan Bacon      if (resp.Error === 'DeviceLocked') {
75c4ef02aeSEvan Bacon        throw new CommandError('APPLE_DEVICE_LOCKED', 'Device is currently locked.');
76c4ef02aeSEvan Bacon      }
77c4ef02aeSEvan Bacon
78c4ef02aeSEvan Bacon      if (resp.Error === 'InvalidService') {
79c4ef02aeSEvan Bacon        let errorMessage = `${resp.Error}: ${resp.Service} (request: ${resp.Request})`;
80c4ef02aeSEvan Bacon        if (resp.Service === 'com.apple.debugserver') {
81c4ef02aeSEvan Bacon          errorMessage +=
82c4ef02aeSEvan Bacon            '\nTry reconnecting your device. You can also debug service logs with `export DEBUG=expo:xdl:ios:*`';
83c4ef02aeSEvan Bacon        }
84c4ef02aeSEvan Bacon        throw new CommandError('APPLE_DEVICE_LOCKDOWN', errorMessage);
85c4ef02aeSEvan Bacon      }
86c4ef02aeSEvan Bacon
87c4ef02aeSEvan Bacon      throw new CommandError('APPLE_DEVICE_LOCKDOWN', resp.Error);
88c4ef02aeSEvan Bacon    }
89c4ef02aeSEvan Bacon    return resp;
90c4ef02aeSEvan Bacon  }
91c4ef02aeSEvan Bacon}
92c4ef02aeSEvan Bacon
93c4ef02aeSEvan Baconexport class LockdownProtocolWriter implements ProtocolWriter {
94c4ef02aeSEvan Bacon  write(socket: Socket, plistData: any) {
95c4ef02aeSEvan Bacon    debug(`socket write: ${JSON.stringify(plistData)}`);
96c4ef02aeSEvan Bacon    const plistMessage = plist.build(plistData);
97c4ef02aeSEvan Bacon    const header = Buffer.alloc(LOCKDOWN_HEADER_SIZE);
98c4ef02aeSEvan Bacon    header.writeUInt32BE(plistMessage.length, 0);
99c4ef02aeSEvan Bacon    socket.write(header);
100c4ef02aeSEvan Bacon    socket.write(plistMessage);
101c4ef02aeSEvan Bacon  }
102c4ef02aeSEvan Bacon}
103