@efffrida/rpc 0.0.14 → 0.0.16
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 +48 -0
- package/dist/frida/FridaRpcClient.d.ts.map +1 -0
- package/dist/frida/FridaRpcClient.js +116 -0
- package/dist/frida/FridaRpcClient.js.map +1 -0
- package/dist/frida/FridaRpcServer.d.ts +49 -0
- package/dist/frida/FridaRpcServer.d.ts.map +1 -0
- package/dist/frida/FridaRpcServer.js +121 -0
- package/dist/frida/FridaRpcServer.js.map +1 -0
- package/dist/frida/index.d.ts +18 -0
- package/dist/frida/index.d.ts.map +1 -0
- package/dist/{esm → frida}/index.js +5 -4
- package/dist/frida/index.js.map +1 -0
- package/dist/{dts → node}/FridaRpcClient.d.ts +16 -6
- package/dist/node/FridaRpcClient.d.ts.map +1 -0
- package/dist/node/FridaRpcClient.js +83 -0
- package/dist/node/FridaRpcClient.js.map +1 -0
- package/dist/node/FridaRpcServer.d.ts +16 -0
- package/dist/node/FridaRpcServer.d.ts.map +1 -0
- package/dist/node/FridaRpcServer.js +115 -0
- package/dist/node/FridaRpcServer.js.map +1 -0
- package/dist/node/index.d.ts +18 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/{dts/index.d.ts → node/index.js} +6 -5
- package/dist/node/index.js.map +1 -0
- package/dist/shared/constants.d.ts +6 -0
- package/dist/shared/constants.d.ts.map +1 -0
- package/dist/shared/constants.js +7 -0
- package/dist/shared/constants.js.map +1 -0
- package/dist/shared/predicates.d.ts +10 -0
- package/dist/shared/predicates.d.ts.map +1 -0
- package/dist/shared/predicates.js +12 -0
- package/dist/shared/predicates.js.map +1 -0
- package/package.json +61 -45
- package/src/frida/FridaRpcClient.ts +182 -0
- package/src/frida/FridaRpcServer.ts +172 -0
- package/src/frida/index.ts +19 -0
- package/src/node/FridaRpcClient.ts +141 -0
- package/src/node/FridaRpcServer.ts +134 -0
- package/src/node/index.ts +19 -0
- package/src/shared/constants.ts +11 -0
- package/src/shared/predicates.ts +19 -0
- package/FridaRpcClient/package.json +0 -6
- package/FridaRpcServer/package.json +0 -6
- package/dist/cjs/FridaRpcClient.js +0 -77
- package/dist/cjs/FridaRpcClient.js.map +0 -1
- package/dist/cjs/FridaRpcServer.js +0 -135
- package/dist/cjs/FridaRpcServer.js.map +0 -1
- package/dist/cjs/index.js +0 -12
- package/dist/cjs/index.js.map +0 -1
- package/dist/dts/FridaRpcClient.d.ts.map +0 -1
- package/dist/dts/FridaRpcServer.d.ts +0 -36
- package/dist/dts/FridaRpcServer.d.ts.map +0 -1
- package/dist/dts/index.d.ts.map +0 -1
- package/dist/esm/FridaRpcClient.js +0 -67
- package/dist/esm/FridaRpcClient.js.map +0 -1
- package/dist/esm/FridaRpcServer.js +0 -125
- package/dist/esm/FridaRpcServer.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/package.json +0 -4
- package/index/package.json +0 -6
- package/src/FridaRpcClient.ts +0 -107
- package/src/FridaRpcServer.ts +0 -172
- package/src/index.ts +0 -17
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implements a Frida RPC server protocol for effect using the frida script
|
|
3
|
+
* exports.
|
|
4
|
+
*
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import "@efffrida/polyfills";
|
|
9
|
+
|
|
10
|
+
import * as RpcMessage from "@effect/rpc/RpcMessage";
|
|
11
|
+
import * as RpcSerialization from "@effect/rpc/RpcSerialization";
|
|
12
|
+
import * as RpcServer from "@effect/rpc/RpcServer";
|
|
13
|
+
import * as Effect from "effect/Effect";
|
|
14
|
+
import * as Function from "effect/Function";
|
|
15
|
+
import * as Layer from "effect/Layer";
|
|
16
|
+
import * as Mailbox from "effect/Mailbox";
|
|
17
|
+
import * as Predicate from "effect/Predicate";
|
|
18
|
+
|
|
19
|
+
import * as constants from "../shared/constants.ts";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @since 1.0.0
|
|
23
|
+
* @category Protocol
|
|
24
|
+
*/
|
|
25
|
+
export const makeProtocolFrida = (
|
|
26
|
+
options?:
|
|
27
|
+
| {
|
|
28
|
+
/** Generates server listener rpc exports for individual clients. */
|
|
29
|
+
readonly generateExportName?: ((clientId: number) => string) | undefined;
|
|
30
|
+
}
|
|
31
|
+
| undefined
|
|
32
|
+
): Effect.Effect<
|
|
33
|
+
{
|
|
34
|
+
readonly protocol: RpcServer.Protocol["Type"];
|
|
35
|
+
readonly rpcExport: () => Promise<number>;
|
|
36
|
+
},
|
|
37
|
+
never,
|
|
38
|
+
RpcSerialization.RpcSerialization
|
|
39
|
+
> =>
|
|
40
|
+
Effect.gen(function* () {
|
|
41
|
+
const encoder = new TextEncoder();
|
|
42
|
+
const disconnects = yield* Mailbox.make<number>();
|
|
43
|
+
const serialization = yield* RpcSerialization.RpcSerialization;
|
|
44
|
+
|
|
45
|
+
let clientId = 0;
|
|
46
|
+
const clientIds = new Set<number>();
|
|
47
|
+
const clients = new Map<number, { readonly write: (bytes: RpcMessage.FromServerEncoded) => void }>();
|
|
48
|
+
|
|
49
|
+
let writeRequest!: (clientId: number, message: RpcMessage.FromClientEncoded) => Effect.Effect<void>;
|
|
50
|
+
const makeExportName = options?.generateExportName ?? constants.generateServerExportNameForClient;
|
|
51
|
+
|
|
52
|
+
// Listen for new clients on the main rpc export
|
|
53
|
+
const rpcExport = Effect.gen(function* () {
|
|
54
|
+
const id = ++clientId;
|
|
55
|
+
const parser = serialization.unsafeMake();
|
|
56
|
+
|
|
57
|
+
const writeRaw = (data: string | Uint8Array): void => {
|
|
58
|
+
const transformed = typeof data === "string" ? encoder.encode(data) : data;
|
|
59
|
+
return send({ clientId: id }, (transformed as Uint8Array<ArrayBuffer>).buffer);
|
|
60
|
+
};
|
|
61
|
+
const write = (response: RpcMessage.FromServerEncoded): void => {
|
|
62
|
+
try {
|
|
63
|
+
const encoded = parser.encode(response);
|
|
64
|
+
if (Predicate.isNotUndefined(encoded)) return writeRaw(encoded);
|
|
65
|
+
} catch (cause) {
|
|
66
|
+
const encoded = parser.encode(RpcMessage.ResponseDefectEncoded(cause))!;
|
|
67
|
+
return writeRaw(encoded);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
clientIds.add(id);
|
|
72
|
+
clients.set(id, { write });
|
|
73
|
+
|
|
74
|
+
const onMessage = (input: string | Record<number, string>): Effect.Effect<void, never, never> => {
|
|
75
|
+
const data = typeof input === "string" ? input : Uint8Array.from(Object.values(input));
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const decoded = parser.decode(data) as ReadonlyArray<RpcMessage.FromClientEncoded>;
|
|
79
|
+
if (decoded.length === 0) return Effect.void;
|
|
80
|
+
let i = 0;
|
|
81
|
+
return Effect.whileLoop({
|
|
82
|
+
while: () => i < decoded.length,
|
|
83
|
+
body() {
|
|
84
|
+
const message = decoded[i++];
|
|
85
|
+
return writeRequest(id, message);
|
|
86
|
+
},
|
|
87
|
+
step: Function.constVoid,
|
|
88
|
+
});
|
|
89
|
+
} catch (cause) {
|
|
90
|
+
return Effect.sync(() => writeRaw(parser.encode(RpcMessage.ResponseDefectEncoded(cause))!));
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
rpc.exports[makeExportName(id)] = (data: string | Record<number, string>): Promise<void> =>
|
|
95
|
+
Effect.runPromise(onMessage(data));
|
|
96
|
+
|
|
97
|
+
return id;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const protocol = yield* RpcServer.Protocol.make((writeRequest_) => {
|
|
101
|
+
writeRequest = writeRequest_;
|
|
102
|
+
return Effect.succeed({
|
|
103
|
+
disconnects,
|
|
104
|
+
send: (clientId, response) => {
|
|
105
|
+
const client = clients.get(clientId);
|
|
106
|
+
if (!client) return Effect.void;
|
|
107
|
+
return Effect.sync(() => client.write(response));
|
|
108
|
+
},
|
|
109
|
+
end(clientId) {
|
|
110
|
+
clientIds.delete(clientId); // TODO: Is this required?
|
|
111
|
+
clients.delete(clientId); // TODO: Is this required?
|
|
112
|
+
const makeExportName = options?.generateExportName ?? constants.generateServerExportNameForClient;
|
|
113
|
+
delete rpc.exports[makeExportName(clientId)];
|
|
114
|
+
return Effect.void;
|
|
115
|
+
},
|
|
116
|
+
clientIds: Effect.sync(() => clientIds),
|
|
117
|
+
initialMessage: Effect.succeedNone,
|
|
118
|
+
supportsAck: true,
|
|
119
|
+
supportsTransferables: false,
|
|
120
|
+
supportsSpanPropagation: true,
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
protocol,
|
|
126
|
+
rpcExport: () => Effect.runPromise(rpcExport),
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @since 1.0.0
|
|
132
|
+
* @category Protocol
|
|
133
|
+
*/
|
|
134
|
+
export const makeProtocolFridaWithExport = (
|
|
135
|
+
options?:
|
|
136
|
+
| {
|
|
137
|
+
/**
|
|
138
|
+
* Name for the main rpc export that all clients start by
|
|
139
|
+
* connecting to.
|
|
140
|
+
*/
|
|
141
|
+
readonly exportName?: string | undefined;
|
|
142
|
+
|
|
143
|
+
/** Generates server listener rpc exports for individual clients. */
|
|
144
|
+
readonly generateExportName?: ((clientId: number) => string) | undefined;
|
|
145
|
+
}
|
|
146
|
+
| undefined
|
|
147
|
+
): Effect.Effect<RpcServer.Protocol["Type"], never, RpcSerialization.RpcSerialization> =>
|
|
148
|
+
Effect.gen(function* () {
|
|
149
|
+
const { protocol, rpcExport } = yield* makeProtocolFrida({ generateExportName: options?.generateExportName });
|
|
150
|
+
rpc.exports[options?.exportName ?? constants.defaultServerMainExportName] = rpcExport;
|
|
151
|
+
return protocol;
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @since 1.0.0
|
|
156
|
+
* @category Layer
|
|
157
|
+
*/
|
|
158
|
+
export const layerProtocolFrida = (
|
|
159
|
+
options?:
|
|
160
|
+
| {
|
|
161
|
+
/**
|
|
162
|
+
* Name for the main rpc export that all clients start by
|
|
163
|
+
* connecting to.
|
|
164
|
+
*/
|
|
165
|
+
readonly exportName?: string | undefined;
|
|
166
|
+
|
|
167
|
+
/** Generates server listener rpc exports for individual clients. */
|
|
168
|
+
readonly generateExportName?: ((clientId: number) => string) | undefined;
|
|
169
|
+
}
|
|
170
|
+
| undefined
|
|
171
|
+
): Layer.Layer<RpcServer.Protocol, never, RpcSerialization.RpcSerialization> =>
|
|
172
|
+
Layer.effect(RpcServer.Protocol, makeProtocolFridaWithExport(options));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Implements a Frida RPC client protocol for effect using the frida script
|
|
7
|
+
* exports.
|
|
8
|
+
*
|
|
9
|
+
* @since 1.0.0
|
|
10
|
+
*/
|
|
11
|
+
export * as FridaRpcClient from "./FridaRpcClient.ts"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Implements a Frida RPC server protocol for effect using the frida script
|
|
15
|
+
* exports.
|
|
16
|
+
*
|
|
17
|
+
* @since 1.0.0
|
|
18
|
+
*/
|
|
19
|
+
export * as FridaRpcServer from "./FridaRpcServer.ts"
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implements a Frida RPC client protocol for effect using the frida script
|
|
3
|
+
* exports.
|
|
4
|
+
*
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type * as RpcMessage from "@effect/rpc/RpcMessage";
|
|
9
|
+
import type * as FridaSessionError from "@efffrida/frida-tools/FridaSessionError";
|
|
10
|
+
import type * as Scope from "effect/Scope";
|
|
11
|
+
|
|
12
|
+
import * as RpcClient from "@effect/rpc/RpcClient";
|
|
13
|
+
import * as RpcSerialization from "@effect/rpc/RpcSerialization";
|
|
14
|
+
import * as FridaScript from "@efffrida/frida-tools/FridaScript";
|
|
15
|
+
import * as Effect from "effect/Effect";
|
|
16
|
+
import * as Function from "effect/Function";
|
|
17
|
+
import * as Layer from "effect/Layer";
|
|
18
|
+
import * as Option from "effect/Option";
|
|
19
|
+
import * as ParseResult from "effect/ParseResult";
|
|
20
|
+
import * as Predicate from "effect/Predicate";
|
|
21
|
+
import * as Schema from "effect/Schema";
|
|
22
|
+
import * as Stream from "effect/Stream";
|
|
23
|
+
|
|
24
|
+
import * as constants from "../shared/constants.ts";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @since 1.0.0
|
|
28
|
+
* @category Protocol
|
|
29
|
+
*/
|
|
30
|
+
export const makeProtocolFrida = (
|
|
31
|
+
options?:
|
|
32
|
+
| {
|
|
33
|
+
/**
|
|
34
|
+
* Name for the main rpc export that all clients start by
|
|
35
|
+
* connecting to.
|
|
36
|
+
*/
|
|
37
|
+
readonly exportName?: string | undefined;
|
|
38
|
+
|
|
39
|
+
/** Generates server listener rpc exports for individual clients. */
|
|
40
|
+
readonly generateExportName?: ((clientId: number) => string) | undefined;
|
|
41
|
+
}
|
|
42
|
+
| undefined
|
|
43
|
+
): Effect.Effect<
|
|
44
|
+
RpcClient.Protocol["Type"],
|
|
45
|
+
FridaSessionError.FridaSessionError,
|
|
46
|
+
RpcSerialization.RpcSerialization | FridaScript.FridaScript | Scope.Scope
|
|
47
|
+
> =>
|
|
48
|
+
RpcClient.Protocol.make(
|
|
49
|
+
Effect.fnUntraced(function* (writeResponse) {
|
|
50
|
+
const script = yield* FridaScript.FridaScript;
|
|
51
|
+
const serialization = yield* RpcSerialization.RpcSerialization;
|
|
52
|
+
|
|
53
|
+
const parser = serialization.unsafeMake();
|
|
54
|
+
const mainExportName = options?.exportName ?? constants.defaultServerMainExportName;
|
|
55
|
+
const makeExportName = options?.generateExportName ?? constants.generateServerExportNameForClient;
|
|
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
|
+
);
|
|
67
|
+
|
|
68
|
+
const isClientId = Predicate.compose(Predicate.isNumber, (id) => id === clientId);
|
|
69
|
+
const receivingPredicate = Predicate.compose(
|
|
70
|
+
Predicate.isUnknown,
|
|
71
|
+
Predicate.struct({ clientId: isClientId })
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Start listening for responses to our requests, decode them, and
|
|
76
|
+
* send the responses back to the implementation.
|
|
77
|
+
*/
|
|
78
|
+
yield* script.stream.pipe(
|
|
79
|
+
Stream.filterMap((unfiltered) => {
|
|
80
|
+
if (receivingPredicate(unfiltered.message)) return unfiltered.data;
|
|
81
|
+
else return Option.none<Buffer<ArrayBufferLike>>();
|
|
82
|
+
}),
|
|
83
|
+
Stream.runForEach((filtered) => {
|
|
84
|
+
try {
|
|
85
|
+
const responses = parser.decode(filtered) as Array<RpcMessage.FromServerEncoded>;
|
|
86
|
+
if (responses.length === 0) return Effect.void;
|
|
87
|
+
let i = 0;
|
|
88
|
+
return Effect.whileLoop({
|
|
89
|
+
while: () => i < responses.length,
|
|
90
|
+
body: () => writeResponse(responses[i++]),
|
|
91
|
+
step: Function.constVoid,
|
|
92
|
+
});
|
|
93
|
+
} catch (defect) {
|
|
94
|
+
return writeResponse({ _tag: "Defect", defect });
|
|
95
|
+
}
|
|
96
|
+
}),
|
|
97
|
+
Effect.interruptible,
|
|
98
|
+
Effect.forkScoped
|
|
99
|
+
);
|
|
100
|
+
|
|
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
|
+
return {
|
|
113
|
+
send,
|
|
114
|
+
supportsAck: true,
|
|
115
|
+
supportsTransferables: false,
|
|
116
|
+
};
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @since 1.0.0
|
|
122
|
+
* @category Layers
|
|
123
|
+
*/
|
|
124
|
+
export const layerProtocolFrida = (
|
|
125
|
+
options?:
|
|
126
|
+
| {
|
|
127
|
+
/**
|
|
128
|
+
* Name for the main rpc export that all clients start by
|
|
129
|
+
* connecting to.
|
|
130
|
+
*/
|
|
131
|
+
readonly exportName?: string | undefined;
|
|
132
|
+
|
|
133
|
+
/** Generates server listener rpc exports for individual clients. */
|
|
134
|
+
readonly generateExportName?: ((clientId: number) => string) | undefined;
|
|
135
|
+
}
|
|
136
|
+
| undefined
|
|
137
|
+
): Layer.Layer<
|
|
138
|
+
RpcClient.Protocol,
|
|
139
|
+
FridaSessionError.FridaSessionError,
|
|
140
|
+
RpcSerialization.RpcSerialization | FridaScript.FridaScript
|
|
141
|
+
> => Layer.scoped(RpcClient.Protocol, makeProtocolFrida(options));
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implements a Frida RPC server protocol for effect using the frida script
|
|
3
|
+
* exports.
|
|
4
|
+
*
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type * as Scope from "effect/Scope";
|
|
9
|
+
|
|
10
|
+
import * as RpcMessage from "@effect/rpc/RpcMessage";
|
|
11
|
+
import * as RpcSerialization from "@effect/rpc/RpcSerialization";
|
|
12
|
+
import * as RpcServer from "@effect/rpc/RpcServer";
|
|
13
|
+
import * as FridaScript from "@efffrida/frida-tools/FridaScript";
|
|
14
|
+
import * as Console from "effect/Console";
|
|
15
|
+
import * as Effect from "effect/Effect";
|
|
16
|
+
import * as Function from "effect/Function";
|
|
17
|
+
import * as Mailbox from "effect/Mailbox";
|
|
18
|
+
import * as Option from "effect/Option";
|
|
19
|
+
import * as Predicate from "effect/Predicate";
|
|
20
|
+
import * as Schema from "effect/Schema";
|
|
21
|
+
import * as Stream from "effect/Stream";
|
|
22
|
+
import * as String from "effect/String";
|
|
23
|
+
import * as Tuple from "effect/Tuple";
|
|
24
|
+
|
|
25
|
+
import * as constants from "../shared/constants.ts";
|
|
26
|
+
import * as predicates from "../shared/predicates.ts";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @since 1.0.0
|
|
30
|
+
* @category Protocol
|
|
31
|
+
*/
|
|
32
|
+
export const makeProtocolFrida = (): Effect.Effect<
|
|
33
|
+
unknown,
|
|
34
|
+
never,
|
|
35
|
+
FridaScript.FridaScript | RpcSerialization.RpcSerialization | Scope.Scope
|
|
36
|
+
> =>
|
|
37
|
+
Effect.gen(function* () {
|
|
38
|
+
const encoder = new TextEncoder();
|
|
39
|
+
const script = yield* FridaScript.FridaScript;
|
|
40
|
+
const serialization = yield* RpcSerialization.RpcSerialization;
|
|
41
|
+
|
|
42
|
+
let clientId = 0;
|
|
43
|
+
const clientIds = new Set<number>();
|
|
44
|
+
const parser = serialization.unsafeMake();
|
|
45
|
+
const disconnects = yield* Mailbox.make<number>();
|
|
46
|
+
|
|
47
|
+
let writeRequest!: (clientId: number, message: RpcMessage.FromClientEncoded) => Effect.Effect<void>;
|
|
48
|
+
|
|
49
|
+
// Listen for new clients
|
|
50
|
+
yield* script.stream.pipe(
|
|
51
|
+
Stream.filterMap(({ message }) => {
|
|
52
|
+
if (predicates.newClientPredicate(message)) {
|
|
53
|
+
const stripPrefix = String.replace(constants.nodeRpcClientConnectionRequestMessagePrefix, "");
|
|
54
|
+
return Option.some(stripPrefix(message));
|
|
55
|
+
} else {
|
|
56
|
+
return Option.none();
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
59
|
+
Stream.runForEach((exportName) => {
|
|
60
|
+
const id = clientId++;
|
|
61
|
+
clientIds.add(id);
|
|
62
|
+
return script.callExport(exportName, Schema.Void)(clientId++);
|
|
63
|
+
}),
|
|
64
|
+
Stream.tapError(Console.error),
|
|
65
|
+
Stream.runDrain,
|
|
66
|
+
Effect.forkScoped
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Listen for messages from connected clients
|
|
70
|
+
yield* script.stream.pipe(
|
|
71
|
+
Stream.filterMap(({ data: maybeData, message }) => {
|
|
72
|
+
if (predicates.isTaggedForAnyClient(message)) {
|
|
73
|
+
return Option.map(maybeData, (data) => Tuple.make(message.clientId, data));
|
|
74
|
+
} else {
|
|
75
|
+
return Option.none();
|
|
76
|
+
}
|
|
77
|
+
}),
|
|
78
|
+
Stream.runForEach(([clientId, data]) => {
|
|
79
|
+
try {
|
|
80
|
+
const decoded = parser.decode(data) as ReadonlyArray<RpcMessage.FromClientEncoded>;
|
|
81
|
+
if (decoded.length === 0) return Effect.void;
|
|
82
|
+
let i = 0;
|
|
83
|
+
return Effect.whileLoop({
|
|
84
|
+
while: () => i < decoded.length,
|
|
85
|
+
body() {
|
|
86
|
+
const message = decoded[i++];
|
|
87
|
+
return writeRequest(clientId, message);
|
|
88
|
+
},
|
|
89
|
+
step: Function.constVoid,
|
|
90
|
+
});
|
|
91
|
+
} catch (cause) {
|
|
92
|
+
return Effect.sync(() => {
|
|
93
|
+
const encoded = parser.encode(RpcMessage.ResponseDefectEncoded(cause))!;
|
|
94
|
+
const transformed = typeof encoded === "string" ? encoder.encode(encoded) : encoded;
|
|
95
|
+
script.script.post({ clientId }, Buffer.from(transformed));
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}),
|
|
99
|
+
Stream.tapError(Console.error),
|
|
100
|
+
Stream.runDrain,
|
|
101
|
+
Effect.forkScoped
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return yield* RpcServer.Protocol.make((_writeRequest) => {
|
|
105
|
+
writeRequest = _writeRequest;
|
|
106
|
+
return Effect.succeed({
|
|
107
|
+
disconnects,
|
|
108
|
+
send: (clientId, response) => {
|
|
109
|
+
const writeRaw = (data: string | Uint8Array) => {
|
|
110
|
+
const transformed = typeof data === "string" ? encoder.encode(data) : data;
|
|
111
|
+
script.script.post({ clientId }, Buffer.from(transformed));
|
|
112
|
+
return Effect.void;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const encoded = parser.encode(response);
|
|
117
|
+
if (Predicate.isNotUndefined(encoded)) return writeRaw(encoded);
|
|
118
|
+
else return Effect.void;
|
|
119
|
+
} catch (cause) {
|
|
120
|
+
const encoded = parser.encode(RpcMessage.ResponseDefectEncoded(cause))!;
|
|
121
|
+
return writeRaw(encoded);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
end(_clientId) {
|
|
125
|
+
return Effect.void;
|
|
126
|
+
},
|
|
127
|
+
clientIds: Effect.sync(() => clientIds),
|
|
128
|
+
initialMessage: Effect.succeedNone,
|
|
129
|
+
supportsAck: true,
|
|
130
|
+
supportsTransferables: false,
|
|
131
|
+
supportsSpanPropagation: true,
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Implements a Frida RPC client protocol for effect using the frida script
|
|
7
|
+
* exports.
|
|
8
|
+
*
|
|
9
|
+
* @since 1.0.0
|
|
10
|
+
*/
|
|
11
|
+
export * as FridaRpcClient from "./FridaRpcClient.ts"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Implements a Frida RPC server protocol for effect using the frida script
|
|
15
|
+
* exports.
|
|
16
|
+
*
|
|
17
|
+
* @since 1.0.0
|
|
18
|
+
*/
|
|
19
|
+
export * as FridaRpcServer from "./FridaRpcServer.ts"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const defaultServerMainExportName = "rpc";
|
|
2
|
+
export const generateServerExportNameForClient = (clientId: number): string =>
|
|
3
|
+
`@efffrida/rpc/FridaServerRpcListenerForClient/${clientId}`;
|
|
4
|
+
|
|
5
|
+
let exportIdForClientCallback: number = 0;
|
|
6
|
+
export const generateClientCallbackExportNameForServer = (): string =>
|
|
7
|
+
`@efffrida/rpc/FridaClientRpcCallback/${exportIdForClientCallback++}`;
|
|
8
|
+
|
|
9
|
+
export const nodeRpcClientConnectionRequestMessagePrefix = "client id request message";
|
|
10
|
+
export const nodeRpcClientMakeConnectionRequestForServer = (exportName: string) =>
|
|
11
|
+
`${nodeRpcClientConnectionRequestMessagePrefix}:${exportName}`;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as Predicate from "effect/Predicate";
|
|
2
|
+
import * as String from "effect/String";
|
|
3
|
+
|
|
4
|
+
import * as constants from "./constants.ts";
|
|
5
|
+
|
|
6
|
+
export const isClientId = (clientId: number) => Predicate.compose(Predicate.isNumber, (id) => id === clientId);
|
|
7
|
+
|
|
8
|
+
export const isTaggedForClient = (clientId: number) =>
|
|
9
|
+
Predicate.compose(Predicate.isUnknown, Predicate.struct({ clientId: isClientId(clientId) }));
|
|
10
|
+
|
|
11
|
+
export const isTaggedForAnyClient = Predicate.compose(
|
|
12
|
+
Predicate.isUnknown,
|
|
13
|
+
Predicate.struct({ clientId: Predicate.isNumber })
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
export const newClientPredicate = Predicate.compose(
|
|
17
|
+
Predicate.isString,
|
|
18
|
+
String.startsWith(constants.nodeRpcClientConnectionRequestMessagePrefix)
|
|
19
|
+
);
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.makeProtocolFrida = exports.layerProtocolFrida = void 0;
|
|
7
|
-
var RpcClient = _interopRequireWildcard(require("@effect/rpc/RpcClient"));
|
|
8
|
-
var RpcSerialization = _interopRequireWildcard(require("@effect/rpc/RpcSerialization"));
|
|
9
|
-
var FridaScript = _interopRequireWildcard(require("@efffrida/frida-tools/FridaScript"));
|
|
10
|
-
var Effect = _interopRequireWildcard(require("effect/Effect"));
|
|
11
|
-
var Function = _interopRequireWildcard(require("effect/Function"));
|
|
12
|
-
var Layer = _interopRequireWildcard(require("effect/Layer"));
|
|
13
|
-
var Predicate = _interopRequireWildcard(require("effect/Predicate"));
|
|
14
|
-
var Schema = _interopRequireWildcard(require("effect/Schema"));
|
|
15
|
-
var Stream = _interopRequireWildcard(require("effect/Stream"));
|
|
16
|
-
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
17
|
-
/**
|
|
18
|
-
* Implements a Frida RPC client protocol for effect using the frida script
|
|
19
|
-
* exports. The reason we don't use the send/recv script channels is because
|
|
20
|
-
* those are shared channels by everybody.
|
|
21
|
-
*
|
|
22
|
-
* @since 1.0.0
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @since 1.0.0
|
|
27
|
-
* @category Protocol
|
|
28
|
-
*/
|
|
29
|
-
const makeProtocolFrida = (script, options) => RpcClient.Protocol.make(Effect.fnUntraced(function* (writeResponse) {
|
|
30
|
-
const serialization = yield* RpcSerialization.RpcSerialization;
|
|
31
|
-
const exportName = options?.exportName ?? "rpc";
|
|
32
|
-
const rpcIsAvailableWhen = options?.rpcIsAvailableWhen;
|
|
33
|
-
const send = request => {
|
|
34
|
-
if (request._tag !== "Request") {
|
|
35
|
-
return Effect.void;
|
|
36
|
-
}
|
|
37
|
-
const parser = serialization.unsafeMake();
|
|
38
|
-
const schema = Schema.Union(Schema.String, Schema.Uint8Array);
|
|
39
|
-
const encode = Function.compose(parser.encode, Schema.encode(schema));
|
|
40
|
-
const decode = Function.compose(Schema.decodeUnknownSync(schema), parser.decode);
|
|
41
|
-
return encode(request).pipe(Effect.flatMap(script.callExport(exportName))).pipe(Effect.orDie).pipe(Effect.flatMap(incoming => {
|
|
42
|
-
try {
|
|
43
|
-
const responses = decode(incoming);
|
|
44
|
-
if (responses.length === 0) return Effect.void;
|
|
45
|
-
let i = 0;
|
|
46
|
-
return Effect.whileLoop({
|
|
47
|
-
while: () => i < responses.length,
|
|
48
|
-
body: () => writeResponse(responses[i++]),
|
|
49
|
-
step: Function.constVoid
|
|
50
|
-
});
|
|
51
|
-
} catch (defect) {
|
|
52
|
-
return writeResponse({
|
|
53
|
-
_tag: "Defect",
|
|
54
|
-
defect
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}));
|
|
58
|
-
};
|
|
59
|
-
if (Predicate.isNotUndefined(rpcIsAvailableWhen)) {
|
|
60
|
-
yield* script.stream.pipe(Stream.map(({
|
|
61
|
-
message
|
|
62
|
-
}) => message)).pipe(Stream.filter(Predicate.isString)).pipe(Stream.takeUntil(rpcIsAvailableWhen)).pipe(Stream.runDrain);
|
|
63
|
-
}
|
|
64
|
-
return {
|
|
65
|
-
send,
|
|
66
|
-
supportsAck: false,
|
|
67
|
-
supportsTransferables: false
|
|
68
|
-
};
|
|
69
|
-
}));
|
|
70
|
-
/**
|
|
71
|
-
* @since 1.0.0
|
|
72
|
-
* @category Layers
|
|
73
|
-
*/
|
|
74
|
-
exports.makeProtocolFrida = makeProtocolFrida;
|
|
75
|
-
const layerProtocolFrida = options => Layer.effect(RpcClient.Protocol, Effect.flatMap(FridaScript.FridaScript, script => makeProtocolFrida(script, options)));
|
|
76
|
-
exports.layerProtocolFrida = layerProtocolFrida;
|
|
77
|
-
//# sourceMappingURL=FridaRpcClient.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FridaRpcClient.js","names":["RpcClient","_interopRequireWildcard","require","RpcSerialization","FridaScript","Effect","Function","Layer","Predicate","Schema","Stream","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","makeProtocolFrida","script","options","Protocol","make","fnUntraced","writeResponse","serialization","exportName","rpcIsAvailableWhen","send","request","_tag","void","parser","unsafeMake","schema","Union","String","Uint8Array","encode","compose","decode","decodeUnknownSync","pipe","flatMap","callExport","orDie","incoming","responses","length","whileLoop","while","body","step","constVoid","defect","isNotUndefined","stream","map","message","filter","isString","takeUntil","runDrain","supportsAck","supportsTransferables","exports","layerProtocolFrida","effect"],"sources":["../../src/FridaRpcClient.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
|