@efffrida/rpc 0.0.27 → 0.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/frida/FridaRpcClient.d.ts +5 -29
- package/dist/frida/FridaRpcClient.d.ts.map +1 -1
- package/dist/frida/FridaRpcClient.js +48 -83
- package/dist/frida/FridaRpcClient.js.map +1 -1
- package/dist/frida/FridaRpcServer.d.ts +7 -7
- package/dist/frida/FridaRpcServer.d.ts.map +1 -1
- package/dist/frida/FridaRpcServer.js +37 -49
- package/dist/frida/FridaRpcServer.js.map +1 -1
- package/dist/node/FridaRpcClient.d.ts +6 -10
- package/dist/node/FridaRpcClient.d.ts.map +1 -1
- package/dist/node/FridaRpcClient.js +67 -46
- package/dist/node/FridaRpcClient.js.map +1 -1
- package/dist/node/FridaRpcServer.d.ts +10 -3
- package/dist/node/FridaRpcServer.d.ts.map +1 -1
- package/dist/node/FridaRpcServer.js +63 -64
- package/dist/node/FridaRpcServer.js.map +1 -1
- package/dist/shared/{constants.d.ts → Constants.d.ts} +7 -3
- package/dist/shared/Constants.d.ts.map +1 -0
- package/dist/shared/Constants.js +10 -0
- package/dist/shared/Constants.js.map +1 -0
- package/dist/shared/index.d.ts +10 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/index.js +10 -0
- package/dist/shared/index.js.map +1 -0
- package/package.json +12 -20
- package/src/frida/FridaRpcClient.ts +65 -141
- package/src/frida/FridaRpcServer.ts +47 -54
- package/src/node/FridaRpcClient.ts +84 -71
- package/src/node/FridaRpcServer.ts +92 -80
- package/src/shared/Constants.ts +13 -0
- package/src/shared/index.ts +10 -0
- package/dist/shared/constants.d.ts.map +0 -1
- package/dist/shared/constants.js +0 -7
- package/dist/shared/constants.js.map +0 -1
- package/dist/shared/predicates.d.ts +0 -10
- package/dist/shared/predicates.d.ts.map +0 -1
- package/dist/shared/predicates.js +0 -12
- package/dist/shared/predicates.js.map +0 -1
- package/src/shared/constants.ts +0 -11
- package/src/shared/predicates.ts +0 -19
|
@@ -6,150 +6,90 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import "@efffrida/polyfills";
|
|
9
|
-
|
|
10
|
-
import type * as Duration from "effect/Duration";
|
|
9
|
+
|
|
11
10
|
import type * as Scope from "effect/Scope";
|
|
11
|
+
import type * as RpcMessage from "effect/unstable/rpc/RpcMessage";
|
|
12
12
|
|
|
13
|
-
import * as
|
|
14
|
-
import * as RpcSerialization from "@effect/rpc/RpcSerialization";
|
|
15
|
-
import * as FridaStream from "@efffrida/platform/Stream";
|
|
16
|
-
import * as Cause from "effect/Cause";
|
|
17
|
-
import * as Deferred from "effect/Deferred";
|
|
13
|
+
import * as Crypto from "effect/Crypto";
|
|
18
14
|
import * as Effect from "effect/Effect";
|
|
19
15
|
import * as Function from "effect/Function";
|
|
20
16
|
import * as Layer from "effect/Layer";
|
|
21
|
-
import * as
|
|
22
|
-
import * as
|
|
23
|
-
import * as
|
|
24
|
-
import * as Schema from "effect/Schema";
|
|
25
|
-
import * as Stream from "effect/Stream";
|
|
17
|
+
import * as RpcClient from "effect/unstable/rpc/RpcClient";
|
|
18
|
+
import * as RpcClientError from "effect/unstable/rpc/RpcClientError";
|
|
19
|
+
import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
|
|
26
20
|
|
|
27
|
-
import * as constants from "../shared/
|
|
28
|
-
import * as sharedPredicates from "../shared/predicates.ts";
|
|
21
|
+
import * as constants from "../shared/Constants.ts";
|
|
29
22
|
|
|
30
23
|
/**
|
|
31
24
|
* @since 1.0.0
|
|
32
25
|
* @category Protocol
|
|
33
26
|
*/
|
|
34
|
-
export const makeProtocolFrida = (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
| {
|
|
40
|
-
readonly capacity: "unbounded";
|
|
41
|
-
readonly replay?: number | undefined;
|
|
42
|
-
readonly idleTimeToLive?: Duration.DurationInput | undefined;
|
|
43
|
-
}
|
|
44
|
-
| {
|
|
45
|
-
readonly capacity: number;
|
|
46
|
-
readonly strategy?: "sliding" | "dropping" | "suspend" | undefined;
|
|
47
|
-
readonly replay?: number | undefined;
|
|
48
|
-
readonly idleTimeToLive?: Duration.DurationInput | undefined;
|
|
49
|
-
}
|
|
50
|
-
| undefined;
|
|
51
|
-
}
|
|
52
|
-
| undefined
|
|
53
|
-
): Effect.Effect<RpcClient.Protocol["Type"], never, RpcSerialization.RpcSerialization | Scope.Scope> =>
|
|
27
|
+
export const makeProtocolFrida = (): Effect.Effect<
|
|
28
|
+
RpcClient.Protocol["Service"],
|
|
29
|
+
never,
|
|
30
|
+
Crypto.Crypto | RpcSerialization.RpcSerialization | Scope.Scope
|
|
31
|
+
> =>
|
|
54
32
|
RpcClient.Protocol.make(
|
|
55
|
-
Effect.fnUntraced(function* (writeResponse) {
|
|
33
|
+
Effect.fnUntraced(function* (writeResponse, clientIds) {
|
|
56
34
|
const serialization = yield* RpcSerialization.RpcSerialization;
|
|
35
|
+
const crypto = yield* Crypto.Crypto;
|
|
57
36
|
|
|
58
37
|
const encoder = new TextEncoder();
|
|
59
|
-
const parser = serialization.
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Once the server has acknowledged our initiation request and sent
|
|
63
|
-
* use a client id to identify ourselves, this deferred will
|
|
64
|
-
* complete.
|
|
65
|
-
*/
|
|
66
|
-
const clientIdDeferred = yield* Deferred.make<number, ParseResult.ParseError>();
|
|
38
|
+
const parser = serialization.makeUnsafe();
|
|
39
|
+
const requestClientMap = new Map<string, number>();
|
|
67
40
|
|
|
68
|
-
|
|
69
|
-
* Setup the receiving handler for the client id. We receive the
|
|
70
|
-
* client id over a script export instead of over the receiving
|
|
71
|
-
* stream since there could be multiple clients in this frida script
|
|
72
|
-
* and multiple could request client ids at the same time. This
|
|
73
|
-
* could be mitigated with a nonce of some kind in the request
|
|
74
|
-
* message, but receiving over a script export is full-proof so long
|
|
75
|
-
* as the exports are unique.
|
|
76
|
-
*/
|
|
77
|
-
const exportName = options?.generateExportName?.() ?? constants.generateClientCallbackExportNameForServer();
|
|
78
|
-
yield* Effect.addFinalizer(() => Effect.sync(() => delete rpc.exports[exportName]));
|
|
79
|
-
rpc.exports[exportName] = (maybeIncomingClientId: unknown): void => {
|
|
80
|
-
const parsedClientId = Schema.decodeUnknown(Schema.Number)(maybeIncomingClientId);
|
|
81
|
-
Deferred.unsafeDone(clientIdDeferred, parsedClientId);
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Request a client id from the node side and wait for the response,
|
|
86
|
-
* handling any transient errors that might occur by retrying with
|
|
87
|
-
* the retry policy.
|
|
88
|
-
*/
|
|
41
|
+
const exportName = yield* crypto.randomUUIDv4.pipe(Effect.orDie);
|
|
89
42
|
const connectionRequest = constants.nodeRpcClientMakeConnectionRequestForServer(exportName);
|
|
90
|
-
const connectionRequestTimeout: Duration.DurationInput = "1 second";
|
|
91
|
-
const clientId = yield* Effect.sync(() => send(connectionRequest)).pipe(
|
|
92
|
-
Effect.flatMap(() => Deferred.await(clientIdDeferred)),
|
|
93
|
-
Effect.timeout(connectionRequestTimeout),
|
|
94
|
-
Effect.catchIf(ParseResult.isParseError, () => Effect.dieMessage("Failed to parse client ID")),
|
|
95
|
-
Effect.catchIf(Cause.isTimeoutException, () => Effect.dieMessage("Timed out too many times"))
|
|
96
|
-
);
|
|
97
43
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
* come in at, and filter for only our messages.
|
|
101
|
-
*/
|
|
102
|
-
const receivingPredicate = sharedPredicates.isTaggedForClient(clientId);
|
|
103
|
-
const receiveStream = yield* FridaStream.receiveStream(
|
|
104
|
-
options?.receivingStreamShareOptions ?? {
|
|
105
|
-
replay: 100,
|
|
106
|
-
capacity: "unbounded",
|
|
107
|
-
}
|
|
108
|
-
);
|
|
44
|
+
const broadcast = (response: RpcMessage.FromServerEncoded) =>
|
|
45
|
+
Effect.forEach(clientIds, (id) => writeResponse(id, response), { discard: true });
|
|
109
46
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
47
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => delete rpc.exports[exportName]));
|
|
48
|
+
rpc.exports[exportName] = (data: string | Uint8Array): Promise<void> => {
|
|
49
|
+
try {
|
|
50
|
+
const responses = parser.decode(data) as Array<RpcMessage.FromServerEncoded>;
|
|
51
|
+
if (responses.length === 0) return Promise.resolve();
|
|
52
|
+
let i = 0;
|
|
53
|
+
return Effect.whileLoop({
|
|
54
|
+
step: Function.constVoid,
|
|
55
|
+
while: () => i < responses.length,
|
|
56
|
+
body: () => {
|
|
57
|
+
const response = responses[i++]!;
|
|
58
|
+
if ("requestId" in response) {
|
|
59
|
+
const clientId = requestClientMap.get(response.requestId)!;
|
|
60
|
+
if (response._tag === "Exit") requestClientMap.delete(response.requestId);
|
|
61
|
+
return writeResponse(clientId, response);
|
|
62
|
+
} else return broadcast(response);
|
|
63
|
+
},
|
|
64
|
+
}).pipe(Effect.runPromise);
|
|
65
|
+
} catch (defect) {
|
|
66
|
+
return broadcast({
|
|
67
|
+
_tag: "ClientProtocolError",
|
|
68
|
+
error: new RpcClientError.RpcClientError({
|
|
69
|
+
reason: new RpcClientError.RpcClientDefect({
|
|
70
|
+
message: "Error decoding message",
|
|
71
|
+
cause: defect,
|
|
72
|
+
}),
|
|
73
|
+
}),
|
|
74
|
+
}).pipe(Effect.runPromise);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
136
77
|
|
|
137
|
-
|
|
138
|
-
* Sending messages is as simple as encoding them, then sending them
|
|
139
|
-
* to the node side tagged with our client id so it knows where to
|
|
140
|
-
* send them back to.
|
|
141
|
-
*/
|
|
142
|
-
const sendHelper = Effect.fnUntraced(function* (message: RpcMessage.FromClientEncoded) {
|
|
143
|
-
const encoded = parser.encode(message);
|
|
144
|
-
if (Predicate.isUndefined(encoded)) return;
|
|
145
|
-
const transformed = typeof encoded === "string" ? encoder.encode(encoded) : encoded;
|
|
146
|
-
send({ clientId }, (transformed as Uint8Array<ArrayBuffer>).buffer);
|
|
147
|
-
});
|
|
78
|
+
send(connectionRequest);
|
|
148
79
|
|
|
149
80
|
return {
|
|
150
|
-
send: sendHelper,
|
|
151
81
|
supportsAck: true,
|
|
152
82
|
supportsTransferables: false,
|
|
83
|
+
send(clientId, request) {
|
|
84
|
+
if (request._tag === "Request") {
|
|
85
|
+
requestClientMap.set(request.id, clientId);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const encoded = parser.encode(request);
|
|
89
|
+
if (encoded === undefined) return Effect.void;
|
|
90
|
+
const transformed = typeof encoded === "string" ? encoder.encode(encoded) : encoded;
|
|
91
|
+
return Effect.sync(() => send(exportName, (transformed as Uint8Array<ArrayBuffer>).buffer));
|
|
92
|
+
},
|
|
153
93
|
};
|
|
154
94
|
})
|
|
155
95
|
);
|
|
@@ -158,24 +98,8 @@ export const makeProtocolFrida = (
|
|
|
158
98
|
* @since 1.0.0
|
|
159
99
|
* @category Layers
|
|
160
100
|
*/
|
|
161
|
-
export const layerProtocolFrida
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
| {
|
|
167
|
-
readonly capacity: "unbounded";
|
|
168
|
-
readonly replay?: number | undefined;
|
|
169
|
-
readonly idleTimeToLive?: Duration.DurationInput | undefined;
|
|
170
|
-
}
|
|
171
|
-
| {
|
|
172
|
-
readonly capacity: number;
|
|
173
|
-
readonly strategy?: "sliding" | "dropping" | "suspend" | undefined;
|
|
174
|
-
readonly replay?: number | undefined;
|
|
175
|
-
readonly idleTimeToLive?: Duration.DurationInput | undefined;
|
|
176
|
-
}
|
|
177
|
-
| undefined;
|
|
178
|
-
}
|
|
179
|
-
| undefined
|
|
180
|
-
): Layer.Layer<RpcClient.Protocol, never, RpcSerialization.RpcSerialization> =>
|
|
181
|
-
Layer.scoped(RpcClient.Protocol, makeProtocolFrida(options));
|
|
101
|
+
export const layerProtocolFrida: Layer.Layer<
|
|
102
|
+
RpcClient.Protocol,
|
|
103
|
+
never,
|
|
104
|
+
Crypto.Crypto | RpcSerialization.RpcSerialization
|
|
105
|
+
> = Layer.effect(RpcClient.Protocol, makeProtocolFrida());
|
|
@@ -6,22 +6,22 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import "@efffrida/polyfills";
|
|
9
|
-
|
|
10
|
-
import * as RpcSerialization from "@effect/rpc/RpcSerialization";
|
|
11
|
-
import * as RpcServer from "@effect/rpc/RpcServer";
|
|
9
|
+
|
|
12
10
|
import * as Effect from "effect/Effect";
|
|
13
11
|
import * as Function from "effect/Function";
|
|
14
12
|
import * as Layer from "effect/Layer";
|
|
15
|
-
import * as
|
|
16
|
-
import * as
|
|
13
|
+
import * as Queue from "effect/Queue";
|
|
14
|
+
import * as RpcMessage from "effect/unstable/rpc/RpcMessage";
|
|
15
|
+
import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
|
|
16
|
+
import * as RpcServer from "effect/unstable/rpc/RpcServer";
|
|
17
17
|
|
|
18
|
-
import * as constants from "../shared/
|
|
18
|
+
import * as constants from "../shared/Constants.ts";
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* @since 1.0.0
|
|
22
22
|
* @category Protocol
|
|
23
23
|
*/
|
|
24
|
-
export const
|
|
24
|
+
export const makeProtocolFridaNoSendRecv = (
|
|
25
25
|
options?:
|
|
26
26
|
| {
|
|
27
27
|
/** Generates server listener rpc exports for individual clients. */
|
|
@@ -30,71 +30,66 @@ export const makeProtocolFrida = (
|
|
|
30
30
|
| undefined
|
|
31
31
|
): Effect.Effect<
|
|
32
32
|
{
|
|
33
|
-
readonly protocol: RpcServer.Protocol["
|
|
34
|
-
readonly rpcExport: () => Promise<
|
|
33
|
+
readonly protocol: RpcServer.Protocol["Service"];
|
|
34
|
+
readonly rpcExport: () => Promise<string>;
|
|
35
35
|
},
|
|
36
36
|
never,
|
|
37
37
|
RpcSerialization.RpcSerialization
|
|
38
38
|
> =>
|
|
39
39
|
Effect.gen(function* () {
|
|
40
|
-
const encoder = new TextEncoder();
|
|
41
|
-
const disconnects = yield* Mailbox.make<number>();
|
|
42
40
|
const serialization = yield* RpcSerialization.RpcSerialization;
|
|
43
41
|
|
|
42
|
+
const disconnects = yield* Queue.unbounded<number>();
|
|
43
|
+
const parser = serialization.makeUnsafe();
|
|
44
|
+
const encoder = new TextEncoder();
|
|
45
|
+
|
|
44
46
|
let clientId = 0;
|
|
45
47
|
const clientIds = new Set<number>();
|
|
46
|
-
const clients = new Map<
|
|
48
|
+
const clients = new Map<
|
|
49
|
+
number,
|
|
50
|
+
{
|
|
51
|
+
readonly write: (bytes: RpcMessage.FromServerEncoded) => void;
|
|
52
|
+
}
|
|
53
|
+
>();
|
|
47
54
|
|
|
48
55
|
let writeRequest!: (clientId: number, message: RpcMessage.FromClientEncoded) => Effect.Effect<void>;
|
|
49
56
|
const makeExportName = options?.generateExportName ?? constants.generateServerExportNameForClient;
|
|
50
57
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
const writeRaw = (data: string | Uint8Array): void => {
|
|
59
|
+
const transformed = typeof data === "string" ? encoder.encode(data) : data;
|
|
60
|
+
return send(void 0, (transformed as Uint8Array<ArrayBuffer>).buffer);
|
|
61
|
+
};
|
|
55
62
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
stack: response.defect.stack,
|
|
67
|
-
cause: response.defect.cause,
|
|
68
|
-
...response.defect,
|
|
69
|
-
})
|
|
70
|
-
: response
|
|
71
|
-
);
|
|
72
|
-
if (Predicate.isNotUndefined(encoded)) {
|
|
73
|
-
return writeRaw(encoded);
|
|
74
|
-
}
|
|
75
|
-
} catch (cause) {
|
|
76
|
-
const encoded = parser.encode(RpcMessage.ResponseDefectEncoded(cause))!;
|
|
77
|
-
return writeRaw(encoded);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
63
|
+
const write = (response: RpcMessage.FromServerEncoded): void => {
|
|
64
|
+
try {
|
|
65
|
+
const encoded = parser.encode(response);
|
|
66
|
+
if (encoded === undefined) return;
|
|
67
|
+
return writeRaw(encoded);
|
|
68
|
+
} catch (cause) {
|
|
69
|
+
const encoded = parser.encode(RpcMessage.ResponseDefectEncoded(cause))!;
|
|
70
|
+
return writeRaw(encoded);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
80
73
|
|
|
81
|
-
|
|
74
|
+
// @effect-diagnostics-next-line runEffectInsideEffect:off
|
|
75
|
+
const rpcExport = Effect.gen(function* () {
|
|
76
|
+
const id = ++clientId;
|
|
82
77
|
clients.set(id, { write });
|
|
78
|
+
clientIds.add(id);
|
|
83
79
|
|
|
84
80
|
const onMessage = (input: string | Record<number, string>): Effect.Effect<void, never, never> => {
|
|
85
|
-
const data = typeof input === "string" ? input : Uint8Array.from(Object.values(input));
|
|
86
|
-
|
|
87
81
|
try {
|
|
82
|
+
const data = typeof input === "string" ? input : Uint8Array.from(Object.values(input));
|
|
88
83
|
const decoded = parser.decode(data) as ReadonlyArray<RpcMessage.FromClientEncoded>;
|
|
89
84
|
if (decoded.length === 0) return Effect.void;
|
|
90
85
|
let i = 0;
|
|
91
86
|
return Effect.whileLoop({
|
|
92
87
|
while: () => i < decoded.length,
|
|
88
|
+
step: Function.constVoid,
|
|
93
89
|
body() {
|
|
94
90
|
const message = decoded[i++];
|
|
95
91
|
return writeRequest(id, message);
|
|
96
92
|
},
|
|
97
|
-
step: Function.constVoid,
|
|
98
93
|
});
|
|
99
94
|
} catch (cause) {
|
|
100
95
|
return Effect.sync(() => writeRaw(parser.encode(RpcMessage.ResponseDefectEncoded(cause))!));
|
|
@@ -104,8 +99,8 @@ export const makeProtocolFrida = (
|
|
|
104
99
|
rpc.exports[makeExportName(id)] = (data: string | Record<number, string>): Promise<void> =>
|
|
105
100
|
Effect.runPromise(onMessage(data));
|
|
106
101
|
|
|
107
|
-
return id;
|
|
108
|
-
});
|
|
102
|
+
return makeExportName(id);
|
|
103
|
+
}).pipe((export_) => () => Effect.runPromise(export_));
|
|
109
104
|
|
|
110
105
|
const protocol = yield* RpcServer.Protocol.make((writeRequest_) => {
|
|
111
106
|
writeRequest = writeRequest_;
|
|
@@ -117,9 +112,6 @@ export const makeProtocolFrida = (
|
|
|
117
112
|
return Effect.sync(() => client.write(response));
|
|
118
113
|
},
|
|
119
114
|
end(clientId) {
|
|
120
|
-
clientIds.delete(clientId); // TODO: Is this required?
|
|
121
|
-
clients.delete(clientId); // TODO: Is this required?
|
|
122
|
-
const makeExportName = options?.generateExportName ?? constants.generateServerExportNameForClient;
|
|
123
115
|
delete rpc.exports[makeExportName(clientId)];
|
|
124
116
|
return Effect.void;
|
|
125
117
|
},
|
|
@@ -133,7 +125,7 @@ export const makeProtocolFrida = (
|
|
|
133
125
|
|
|
134
126
|
return {
|
|
135
127
|
protocol,
|
|
136
|
-
rpcExport
|
|
128
|
+
rpcExport,
|
|
137
129
|
};
|
|
138
130
|
});
|
|
139
131
|
|
|
@@ -141,7 +133,7 @@ export const makeProtocolFrida = (
|
|
|
141
133
|
* @since 1.0.0
|
|
142
134
|
* @category Protocol
|
|
143
135
|
*/
|
|
144
|
-
export const
|
|
136
|
+
export const makeProtocolFrida = (
|
|
145
137
|
options?:
|
|
146
138
|
| {
|
|
147
139
|
/**
|
|
@@ -154,9 +146,10 @@ export const makeProtocolFridaWithExport = (
|
|
|
154
146
|
readonly generateExportName?: ((clientId: number) => string) | undefined;
|
|
155
147
|
}
|
|
156
148
|
| undefined
|
|
157
|
-
): Effect.Effect<RpcServer.Protocol["
|
|
149
|
+
): Effect.Effect<RpcServer.Protocol["Service"], never, RpcSerialization.RpcSerialization> =>
|
|
158
150
|
Effect.gen(function* () {
|
|
159
|
-
const
|
|
151
|
+
const protocolOptions = { generateExportName: options?.generateExportName };
|
|
152
|
+
const { protocol, rpcExport } = yield* makeProtocolFridaNoSendRecv(protocolOptions);
|
|
160
153
|
rpc.exports[options?.exportName ?? constants.defaultServerMainExportName] = rpcExport;
|
|
161
154
|
return protocol;
|
|
162
155
|
});
|
|
@@ -179,4 +172,4 @@ export const layerProtocolFrida = (
|
|
|
179
172
|
}
|
|
180
173
|
| undefined
|
|
181
174
|
): Layer.Layer<RpcServer.Protocol, never, RpcSerialization.RpcSerialization> =>
|
|
182
|
-
Layer.effect(RpcServer.Protocol,
|
|
175
|
+
Layer.effect(RpcServer.Protocol, makeProtocolFrida(options));
|
|
@@ -5,23 +5,26 @@
|
|
|
5
5
|
* @since 1.0.0
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type * as RpcMessage from "@effect/rpc/RpcMessage";
|
|
9
|
-
import type * as FridaSessionError from "@efffrida/frida-tools/FridaSessionError";
|
|
10
8
|
import type * as Scope from "effect/Scope";
|
|
9
|
+
import type * as RpcMessage from "effect/unstable/rpc/RpcMessage";
|
|
11
10
|
|
|
12
|
-
import * as
|
|
13
|
-
import * as RpcSerialization from "@effect/rpc/RpcSerialization";
|
|
14
|
-
import * as FridaScript from "@efffrida/frida-tools/FridaScript";
|
|
11
|
+
import * as Cause from "effect/Cause";
|
|
15
12
|
import * as Effect from "effect/Effect";
|
|
16
13
|
import * as Function from "effect/Function";
|
|
17
14
|
import * as Layer from "effect/Layer";
|
|
18
15
|
import * as Option from "effect/Option";
|
|
19
|
-
import * as
|
|
20
|
-
import * as Predicate from "effect/Predicate";
|
|
16
|
+
import * as Result from "effect/Result";
|
|
21
17
|
import * as Schema from "effect/Schema";
|
|
22
18
|
import * as Stream from "effect/Stream";
|
|
19
|
+
import * as RpcClient from "effect/unstable/rpc/RpcClient";
|
|
20
|
+
import * as RpcClientError from "effect/unstable/rpc/RpcClientError";
|
|
21
|
+
import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
|
|
22
|
+
|
|
23
|
+
import type * as FridaSessionError from "@efffrida/frida-tools/FridaSessionError";
|
|
24
|
+
|
|
25
|
+
import * as FridaScript from "@efffrida/frida-tools/FridaScript";
|
|
23
26
|
|
|
24
|
-
import * as constants from "../shared/
|
|
27
|
+
import * as constants from "../shared/Constants.ts";
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
30
|
* @since 1.0.0
|
|
@@ -35,84 +38,97 @@ export const makeProtocolFrida = (
|
|
|
35
38
|
* connecting to.
|
|
36
39
|
*/
|
|
37
40
|
readonly exportName?: string | undefined;
|
|
38
|
-
|
|
39
|
-
/** Generates server listener rpc exports for individual clients. */
|
|
40
|
-
readonly generateExportName?: ((clientId: number) => string) | undefined;
|
|
41
41
|
}
|
|
42
42
|
| undefined
|
|
43
43
|
): Effect.Effect<
|
|
44
|
-
RpcClient.Protocol["
|
|
44
|
+
RpcClient.Protocol["Service"],
|
|
45
45
|
FridaSessionError.FridaSessionError,
|
|
46
46
|
RpcSerialization.RpcSerialization | FridaScript.FridaScript | Scope.Scope
|
|
47
47
|
> =>
|
|
48
48
|
RpcClient.Protocol.make(
|
|
49
|
-
Effect.fnUntraced(function* (writeResponse) {
|
|
49
|
+
Effect.fnUntraced(function* (writeResponse, clientIds) {
|
|
50
50
|
const script = yield* FridaScript.FridaScript;
|
|
51
51
|
const serialization = yield* RpcSerialization.RpcSerialization;
|
|
52
52
|
|
|
53
|
-
const parser = serialization.unsafeMake();
|
|
54
53
|
const mainExportName = options?.exportName ?? constants.defaultServerMainExportName;
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Obtain a client id from the frida script export, this will allow
|
|
59
|
-
* us to filter future messages for ours since the script channels
|
|
60
|
-
* are shared.
|
|
61
|
-
*/
|
|
62
|
-
const clientId = yield* Effect.catchIf(
|
|
63
|
-
script.callExport(mainExportName, Schema.Number)(),
|
|
64
|
-
ParseResult.isParseError,
|
|
65
|
-
() => Effect.dieMessage("Failed to obtain client ID from Frida script export")
|
|
66
|
-
);
|
|
54
|
+
const informServerToSetupExport = script.callExport(mainExportName, Schema.String);
|
|
55
|
+
const clientExportName = yield* Effect.orDie(informServerToSetupExport());
|
|
67
56
|
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
Predicate.struct({ clientId: isClientId })
|
|
72
|
-
);
|
|
57
|
+
const parser = serialization.makeUnsafe();
|
|
58
|
+
const requestClientMap = new Map<string, number>();
|
|
59
|
+
let currentError: RpcClientError.RpcClientError | undefined = undefined;
|
|
73
60
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
61
|
+
const broadcast = (response: RpcMessage.FromServerEncoded) =>
|
|
62
|
+
Effect.forEach(clientIds, (clientId) => writeResponse(clientId, response));
|
|
63
|
+
|
|
64
|
+
yield* Stream.runForEach(script.stream, (message) => {
|
|
65
|
+
if (Option.isNone(message.data)) {
|
|
66
|
+
return Effect.void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const responses = parser.decode(message.data.value) as Array<RpcMessage.FromServerEncoded>;
|
|
71
|
+
if (responses.length === 0) return Effect.void;
|
|
72
|
+
let i = 0;
|
|
73
|
+
return Effect.whileLoop({
|
|
74
|
+
step: Function.constVoid,
|
|
75
|
+
while: () => i < responses.length,
|
|
76
|
+
body: () => {
|
|
77
|
+
const response = responses[i++]!;
|
|
78
|
+
if ("requestId" in response) {
|
|
79
|
+
const clientId = requestClientMap.get(response.requestId)!;
|
|
80
|
+
if (response._tag === "Exit") requestClientMap.delete(response.requestId);
|
|
81
|
+
return writeResponse(clientId, response);
|
|
82
|
+
} else return broadcast(response);
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
} catch (defect) {
|
|
86
|
+
return broadcast({
|
|
87
|
+
_tag: "ClientProtocolError",
|
|
88
|
+
error: new RpcClientError.RpcClientError({
|
|
89
|
+
reason: new RpcClientError.RpcClientDefect({
|
|
90
|
+
message: "Error decoding message",
|
|
91
|
+
cause: defect,
|
|
92
|
+
}),
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}).pipe(
|
|
97
|
+
Effect.tapCause((cause) => {
|
|
98
|
+
const error = Cause.findError(cause);
|
|
99
|
+
const hasError = Result.isSuccess(error);
|
|
100
|
+
const defect = hasError
|
|
101
|
+
? new RpcClientError.RpcClientDefect({
|
|
102
|
+
message: error.success.message,
|
|
103
|
+
cause: error.success.cause,
|
|
104
|
+
})
|
|
105
|
+
: new RpcClientError.RpcClientDefect({
|
|
106
|
+
message: "Unknown error in Frida RPC client stream",
|
|
107
|
+
cause: Cause.squash(cause),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
currentError = new RpcClientError.RpcClientError({ reason: defect });
|
|
111
|
+
return broadcast({ _tag: "ClientProtocolError", error: currentError });
|
|
96
112
|
}),
|
|
97
|
-
Effect.interruptible,
|
|
98
113
|
Effect.forkScoped
|
|
99
114
|
);
|
|
100
115
|
|
|
101
|
-
/**
|
|
102
|
-
* Sending messages is as simple as encoding them, then posting them
|
|
103
|
-
* to the frida side tagged with our client id so it knows where to
|
|
104
|
-
* send them back to.
|
|
105
|
-
*/
|
|
106
|
-
const send = Effect.fnUntraced(function* (message: RpcMessage.FromClientEncoded) {
|
|
107
|
-
const encoded = parser.encode(message);
|
|
108
|
-
if (Predicate.isUndefined(encoded)) return;
|
|
109
|
-
yield* script.callExport(makeExportName(clientId), Schema.Void)(encoded).pipe(Effect.orDie);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
116
|
return {
|
|
113
|
-
send,
|
|
114
117
|
supportsAck: true,
|
|
115
118
|
supportsTransferables: false,
|
|
119
|
+
send(clientId, request) {
|
|
120
|
+
if (currentError) {
|
|
121
|
+
return Effect.fail(currentError);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (request._tag === "Request") {
|
|
125
|
+
requestClientMap.set(request.id, clientId);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const encoded = parser.encode(request);
|
|
129
|
+
if (encoded === undefined) return Effect.void;
|
|
130
|
+
return Effect.orDie(script.callExport(clientExportName)(encoded));
|
|
131
|
+
},
|
|
116
132
|
};
|
|
117
133
|
})
|
|
118
134
|
);
|
|
@@ -126,16 +142,13 @@ export const layerProtocolFrida = (
|
|
|
126
142
|
| {
|
|
127
143
|
/**
|
|
128
144
|
* Name for the main rpc export that all clients start by
|
|
129
|
-
* connecting to.
|
|
145
|
+
* connecting to to inform the script of their client id.
|
|
130
146
|
*/
|
|
131
147
|
readonly exportName?: string | undefined;
|
|
132
|
-
|
|
133
|
-
/** Generates server listener rpc exports for individual clients. */
|
|
134
|
-
readonly generateExportName?: ((clientId: number) => string) | undefined;
|
|
135
148
|
}
|
|
136
149
|
| undefined
|
|
137
150
|
): Layer.Layer<
|
|
138
151
|
RpcClient.Protocol,
|
|
139
152
|
FridaSessionError.FridaSessionError,
|
|
140
153
|
RpcSerialization.RpcSerialization | FridaScript.FridaScript
|
|
141
|
-
> => Layer.
|
|
154
|
+
> => Layer.effect(RpcClient.Protocol, makeProtocolFrida(options));
|