@tdxvolt/volt-client-grpc 0.1.2 → 0.11.0

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.2",
6
+ "version": "0.11.0",
7
7
  "description": "tdx Volt library for nodejs clients",
8
8
  "type": "module",
9
9
  "exports": {
@@ -47,4 +47,4 @@
47
47
  "node-forge": "^0.9.0",
48
48
  "uuid": "^8.1.0"
49
49
  }
50
- }
50
+ }
@@ -3,19 +3,19 @@ syntax = "proto3";
3
3
  package tdx.volt_api.data.v1;
4
4
 
5
5
  // Just reflect SQLite types for now.
6
- enum ColumnType {
7
- COLUMN_TYPE_UNKNOWN = 0;
8
- COLUMN_TYPE_TEXT = 1;
9
- COLUMN_TYPE_INTEGER = 2;
10
- COLUMN_TYPE_REAL = 3;
11
- COLUMN_TYPE_BLOB = 4;
12
- COLUMN_TYPE_NULL = 5;
6
+ enum DataType {
7
+ DATA_TYPE_UNKNOWN = 0;
8
+ DATA_TYPE_TEXT = 1;
9
+ DATA_TYPE_INTEGER = 2;
10
+ DATA_TYPE_REAL = 3;
11
+ DATA_TYPE_BLOB = 4;
12
+ DATA_TYPE_NULL = 5;
13
13
  }
14
14
 
15
15
  message Column {
16
16
  string name = 1;
17
17
  string description = 2;
18
- ColumnType type = 3;
18
+ DataType type = 3;
19
19
  }
20
20
 
21
21
  message Schema {
@@ -25,12 +25,12 @@ message Schema {
25
25
  }
26
26
 
27
27
  message Variant {
28
- ColumnType type = 1;
29
28
  oneof data {
30
- string text = 2;
31
- int64 integer = 3;
32
- double real = 4;
33
- bytes blob = 5;
29
+ string text = 1;
30
+ int64 integer = 2;
31
+ double real = 3;
32
+ bytes blob = 4;
33
+ bool null = 5;
34
34
  }
35
35
  }
36
36
 
@@ -82,6 +82,9 @@ message ProxyConnection {
82
82
 
83
83
  // Set to indicate the Volt API itself is not automatically exposed to the connection.
84
84
  bool disable_volt_api = 9;
85
+
86
+ // Optional challenge that can be presented in the bind phase when initialising the connection.
87
+ string challenge = 10;
85
88
  }
86
89
 
87
90
  enum SecureMode {
@@ -14,7 +14,7 @@ service WireAPI {
14
14
  rpc SubscribeWire(stream SubscribeWireRequest) returns (stream SubscribeWireResponse);
15
15
  }
16
16
 
17
- // Because the publish RPC is client-streaming, the policy will not be checked until the first message arrives. This can mean that the `PublishWire` call appears to succeed but then fails after the first attempt to publish a message.
17
+ // Because the publish RPC is streaming, the policy will not be checked until the first message arrives. This can mean that the `PublishWire` call appears to succeed but then fails after the first attempt to publish a message.
18
18
  // To avoid this, clients can immediately send a message with the `wire_id` set and no `chunk` set. This will establish that the correct permissions are in place and fail fast if not.
19
19
  message PublishWireRequest {
20
20
  // Only necessary in the first payload.
package/src/grpc-call.js CHANGED
@@ -1,262 +1,285 @@
1
1
  /* eslint-disable no-underscore-dangle */
2
2
  import debug from "debug";
3
3
  import EventEmitter from "events";
4
- import {VoltCredential} from "./volt-credential.js";
4
+ import { VoltCredential } from "./volt-credential.js";
5
5
 
6
6
  const log = debug("volt-client-grpc:grpc-call");
7
7
 
8
- function _prepareInvokeRequest(request, method, methodType = "METHOD_TYPE_UNARY") {
9
- const meta = this._voltClient.credential.getIdentityMetadata(
10
- this._voltClient.grpc,
11
- this._voltClient.voltConfig.id,
12
- this._voltClient.isRemote
13
- );
14
- let invokeRequest;
15
- let callMethod = method;
16
-
17
- if (this._voltClient.isVoltRelay) {
18
- let requestPayload;
19
- if (request) {
20
- requestPayload = this._grpcClient[method].requestSerialize(request);
21
- } else if (request !== null) {
22
- log("****************LOOKOUT****************** - empty request");
23
- } else {
24
- log("explicit empty request payload");
25
- }
26
-
27
- const tunnelMethod = this._grpcClient.Tunnel;
28
-
29
- const tunnelResp = {
30
- method_invoke: {
31
- method_name: this._grpcClient[method].path,
32
- method_type: methodType,
33
- token: meta.token,
34
- request: requestPayload,
35
- },
36
- };
37
-
38
- const invokePayload = VoltCredential.aesEncrypt(
39
- tunnelMethod.responseSerialize(tunnelResp),
40
- meta.sharedKey.key,
41
- meta.sharedKey.iv
42
- );
43
-
44
- log("target volt is %s", this._voltClient.credential.voltFingerprint);
45
-
46
- invokeRequest = {
47
- identity_fingerprint: this._voltClient.credential.voltFingerprint,
48
- payload: invokePayload,
49
- };
50
-
51
- callMethod = "Invoke";
52
- } else if (this._voltClient.isRemote && meta.sharedKey.key) {
53
- const requestPayload = this._grpcClient[method].requestSerializeOriginal(request);
54
- invokeRequest = VoltCredential.aesEncrypt(requestPayload, meta.sharedKey.key, meta.sharedKey.iv);
55
- } else {
56
- invokeRequest = request;
57
- }
58
-
59
- return {
60
- meta,
61
- method: callMethod,
62
- methodName: method,
63
- request: invokeRequest,
64
- };
8
+ function _prepareInvokeRequest(
9
+ request,
10
+ method,
11
+ methodType = "METHOD_TYPE_UNARY",
12
+ ) {
13
+ const meta = this._voltClient.credential.getIdentityMetadata(
14
+ this._voltClient.grpc,
15
+ this._voltClient.voltConfig.id,
16
+ this._voltClient.isRemote,
17
+ );
18
+ let invokeRequest;
19
+ let callMethod = method;
20
+
21
+ if (this._voltClient.isVoltRelay) {
22
+ let requestPayload;
23
+ if (request) {
24
+ requestPayload = this._grpcClient[method].requestSerialize(request);
25
+ } else if (request !== null) {
26
+ log("****************LOOKOUT****************** - empty request");
27
+ } else {
28
+ log("explicit empty request payload");
29
+ }
30
+
31
+ const tunnelMethod = this._grpcClient.Tunnel;
32
+
33
+ const tunnelResp = {
34
+ method_invoke: {
35
+ method_name: this._grpcClient[method].path,
36
+ method_type: methodType,
37
+ token: meta.token,
38
+ request: requestPayload,
39
+ },
40
+ };
41
+
42
+ const invokePayload = VoltCredential.aesEncrypt(
43
+ tunnelMethod.responseSerialize(tunnelResp),
44
+ meta.sharedKey.key,
45
+ meta.sharedKey.iv,
46
+ );
47
+
48
+ log("target volt is %s", this._voltClient.credential.voltFingerprint);
49
+
50
+ invokeRequest = {
51
+ identity_fingerprint: this._voltClient.credential.voltFingerprint,
52
+ payload: invokePayload,
53
+ };
54
+
55
+ callMethod = "Invoke";
56
+ } else if (this._voltClient.isRemote && meta.sharedKey.key) {
57
+ const requestPayload =
58
+ this._grpcClient[method].requestSerializeOriginal(request);
59
+ invokeRequest = VoltCredential.aesEncrypt(
60
+ requestPayload,
61
+ meta.sharedKey.key,
62
+ meta.sharedKey.iv,
63
+ );
64
+ } else {
65
+ invokeRequest = request;
66
+ }
67
+
68
+ return {
69
+ meta,
70
+ method: callMethod,
71
+ methodName: method,
72
+ request: invokeRequest,
73
+ };
65
74
  }
66
75
 
67
76
  function _preparePayloadRequest(request, invokeInfo) {
68
- let payloadRequest;
69
-
70
- if (this._voltClient.isVoltRelay) {
71
- const requestPayload = this._grpcClient[invokeInfo.methodName].requestSerialize(request);
72
- const tunnelMethod = this._grpcClient.Tunnel;
73
-
74
- const tunnelResp = {
75
- method_payload: {
76
- payload: requestPayload,
77
- },
78
- };
79
-
80
- const invokePayload = VoltCredential.aesEncrypt(
81
- tunnelMethod.responseSerialize(tunnelResp),
82
- invokeInfo.meta.sharedKey.key,
83
- invokeInfo.meta.sharedKey.iv
84
- );
85
-
86
- payloadRequest = {
87
- payload: invokePayload,
88
- };
89
- } else if (this._voltClient.isRemote && invokeInfo.meta.sharedKey.key) {
90
- const requestPayload = this._grpcClient[invokeInfo.methodName].requestSerializeOriginal(request);
91
- payloadRequest = VoltCredential.aesEncrypt(
92
- requestPayload,
93
- invokeInfo.meta.sharedKey.key,
94
- invokeInfo.meta.sharedKey.iv
95
- );
96
- } else {
97
- payloadRequest = request;
98
- }
99
-
100
- return payloadRequest;
77
+ let payloadRequest;
78
+
79
+ if (this._voltClient.isVoltRelay) {
80
+ const requestPayload =
81
+ this._grpcClient[invokeInfo.methodName].requestSerialize(request);
82
+ const tunnelMethod = this._grpcClient.Tunnel;
83
+
84
+ const tunnelResp = {
85
+ method_payload: {
86
+ payload: requestPayload,
87
+ },
88
+ };
89
+
90
+ const invokePayload = VoltCredential.aesEncrypt(
91
+ tunnelMethod.responseSerialize(tunnelResp),
92
+ invokeInfo.meta.sharedKey.key,
93
+ invokeInfo.meta.sharedKey.iv,
94
+ );
95
+
96
+ payloadRequest = {
97
+ payload: invokePayload,
98
+ };
99
+ } else if (this._voltClient.isRemote && invokeInfo.meta.sharedKey.key) {
100
+ const requestPayload =
101
+ this._grpcClient[invokeInfo.methodName].requestSerializeOriginal(request);
102
+ payloadRequest = VoltCredential.aesEncrypt(
103
+ requestPayload,
104
+ invokeInfo.meta.sharedKey.key,
105
+ invokeInfo.meta.sharedKey.iv,
106
+ );
107
+ } else {
108
+ payloadRequest = request;
109
+ }
110
+
111
+ return payloadRequest;
101
112
  }
102
113
 
103
114
  function _parseResponse(method, meta, response) {
104
- let invokeResponse = {};
105
-
106
- if (this._voltClient.isVoltRelay) {
107
- if (response.payload) {
108
- const decryptedPayload = VoltCredential.aesDecrypt(response.payload, meta.sharedKey.key, meta.sharedKey.iv);
109
- const tunnelMethod = this._grpcClient.Tunnel;
110
- const responsePayload = tunnelMethod.requestDeserialize(decryptedPayload);
111
- if (responsePayload.payload === "method_payload") {
112
- invokeResponse.payload = this._grpcClient[method].responseDeserialize(responsePayload.method_payload.payload);
113
- } else if (responsePayload.payload === "method_end") {
114
- invokeResponse = responsePayload.method_end;
115
- } else {
116
- log("unexpected tunnel payload type: %s", responsePayload.payload);
117
- }
118
- } else if (response.status?.message) {
119
- // There's been an error in the tunnel.
120
- log(
121
- "Tunnel status received: %s, code %d, description: %s",
122
- response.status.message,
123
- response.status.code,
124
- response.status.description || "n/a"
125
- );
126
- invokeResponse = {ended: true, error: `Error in tunnel: ${response.status.message}`};
127
- } else {
128
- invokeResponse.methodId = response.invoke_id;
129
- log("started method %d for %s", invokeResponse.methodId, method);
130
- }
131
- } else if (this._voltClient.isRemote && meta.sharedKey.key) {
132
- const decryptedPayload = VoltCredential.aesDecrypt(response, meta.sharedKey.key, meta.sharedKey.iv);
133
- invokeResponse = {payload: this._grpcClient[method].responseDeserializeOriginal(decryptedPayload)};
134
- } else {
135
- invokeResponse = {payload: response};
136
- }
137
-
138
- return invokeResponse;
115
+ let invokeResponse = {};
116
+
117
+ if (this._voltClient.isVoltRelay) {
118
+ if (response.payload) {
119
+ const decryptedPayload = VoltCredential.aesDecrypt(
120
+ response.payload,
121
+ meta.sharedKey.key,
122
+ meta.sharedKey.iv,
123
+ );
124
+ const tunnelMethod = this._grpcClient.Tunnel;
125
+ const responsePayload = tunnelMethod.requestDeserialize(decryptedPayload);
126
+ if (responsePayload.payload === "method_payload") {
127
+ invokeResponse.payload = this._grpcClient[method].responseDeserialize(
128
+ responsePayload.method_payload.payload,
129
+ );
130
+ } else if (responsePayload.payload === "method_end") {
131
+ invokeResponse = responsePayload.method_end;
132
+ } else {
133
+ log("unexpected tunnel payload type: %s", responsePayload.payload);
134
+ }
135
+ } else if (response.status?.message) {
136
+ // There's been an error in the tunnel.
137
+ log(
138
+ "Tunnel status received: %s, code %d, description: %s",
139
+ response.status.message,
140
+ response.status.code,
141
+ response.status.description || "n/a",
142
+ );
143
+ invokeResponse = {
144
+ ended: true,
145
+ error: `Error in tunnel: ${response.status.message}`,
146
+ };
147
+ } else {
148
+ invokeResponse.methodId = response.invoke_id;
149
+ log("started method %d for %s", invokeResponse.methodId, method);
150
+ }
151
+ } else if (this._voltClient.isRemote && meta.sharedKey.key) {
152
+ const decryptedPayload = VoltCredential.aesDecrypt(
153
+ response,
154
+ meta.sharedKey.key,
155
+ meta.sharedKey.iv,
156
+ );
157
+ invokeResponse = {
158
+ payload:
159
+ this._grpcClient[method].responseDeserializeOriginal(decryptedPayload),
160
+ };
161
+ } else {
162
+ invokeResponse = { payload: response };
163
+ }
164
+
165
+ return invokeResponse;
139
166
  }
140
167
 
141
168
  export default class GRPCCall extends EventEmitter {
142
- constructor(voltClient, methodName, methodType) {
143
- super();
144
- this._methodName = methodName;
145
- this._methodType = methodType;
146
- this._call = null;
147
- this._voltClient = voltClient;
148
- this._lastResponse = null;
149
- this._promise = null;
150
- this._reject = null;
151
- }
152
-
153
- reject(err) {
154
- if (this._reject) {
155
- this._reject(err);
156
- }
157
- }
158
-
159
- wait() {
160
- return this._promise;
161
- }
162
-
163
- start(grpcClient, request, responseCB) {
164
- this._promise = new Promise((resolve, reject) => {
165
- this._grpcClient = grpcClient;
166
- this._reject = reject;
167
-
168
- const callbackIntercept = (err, response) => {
169
- log("received callback for method %s", this._methodName);
170
- if (err) {
171
- reject(err);
172
- } else if (response) {
173
- this._lastResponse = response;
174
- if (responseCB) {
175
- responseCB(err, response);
176
- }
177
- } else {
178
- this.end();
179
- resolve(this._lastResponse);
180
- }
181
- };
182
-
183
- this._initialRequest = _prepareInvokeRequest.call(this, request, this._methodName, this._methodType);
184
-
185
- if (!this._grpcClient[this._initialRequest.method]) {
186
- return reject(new Error(`method not found: '${this._initialRequest.method}'`));
187
- }
188
-
189
- this._call = this._grpcClient[this._initialRequest.method](this._initialRequest.meta.metadata, callbackIntercept);
190
-
191
- this._call.on("data", (streamResponse) => {
192
- try {
193
- const parsedResponse = _parseResponse.call(this, this._methodName, this._initialRequest.meta, streamResponse);
194
- if (parsedResponse.payload) {
195
- callbackIntercept(null, parsedResponse.payload);
196
- } else if (parsedResponse.ended) {
197
- if (parsedResponse.error) {
198
- callbackIntercept(new Error(parsedResponse.error), null);
199
- } else {
200
- callbackIntercept(null, null);
201
- }
202
- } else {
203
- log("method id is %s", parsedResponse.methodId);
204
- }
205
- } catch (err) {
206
- log("failure processing connect response: %s", err.message);
207
- this.emit("error", err);
208
- }
209
- });
210
-
211
- this._call.on("error", (err) => {
212
- log("ERROR - intercepted stream error %s", err.message);
213
- callbackIntercept(err, null);
214
- });
215
-
216
- this._call.on("finish", () => {
217
- // The read side has ended (i.e. we called end()).
218
- log("finished call %s", this._methodName);
219
- this.emit("finish");
220
- });
221
-
222
- this._call.on("end", () => {
223
- // The remote peer ended the stream.
224
- log("ended call %s, method id: %s", this._methodName, this._initialRequest.methodId || "n/a - not tunnelling");
225
- this.emit("end");
226
- callbackIntercept(null, null);
227
- });
228
-
229
- if (this._initialRequest.request) {
230
- this._call.write(this._initialRequest.request);
231
- }
232
- });
233
-
234
- return this;
235
- }
236
-
237
- end() {
238
- if (this._call) {
239
- return this._call.end();
240
- } else {
241
- throw new Error("call not initialised");
242
- }
243
- }
244
-
245
- get writable() {
246
- return this._call?.writable;
247
- }
248
-
249
- write(request) {
250
- if (!this._call) {
251
- log("ERROR - call not initialised");
252
- throw new Error("call not initialised");
253
- } else if (this._initialRequest) {
254
- const payloadRequest = _preparePayloadRequest.call(this, request, this._initialRequest);
255
- return this._call.write(payloadRequest);
256
- } else {
257
- this._initialRequest = _prepareInvokeRequest.call(this, request, this._methodName, this._methodType);
258
-
259
- return this._call.write(this._initialRequest.request);
260
- }
261
- }
169
+ constructor(voltClient, methodName, methodType) {
170
+ super();
171
+ this._methodName = methodName;
172
+ this._methodType = methodType;
173
+ this._call = null;
174
+ this._voltClient = voltClient;
175
+ }
176
+
177
+ start(grpcClient, request) {
178
+ this._grpcClient = grpcClient;
179
+
180
+ this._initialRequest = _prepareInvokeRequest.call(
181
+ this,
182
+ request,
183
+ this._methodName,
184
+ this._methodType,
185
+ );
186
+
187
+ if (!this._grpcClient[this._initialRequest.method]) {
188
+ return reject(
189
+ new Error(`method not found: '${this._initialRequest.method}'`),
190
+ );
191
+ }
192
+
193
+ this._call = this._grpcClient[this._initialRequest.method](
194
+ this._initialRequest.meta.metadata,
195
+ );
196
+
197
+ this._call.on("data", (streamResponse) => {
198
+ try {
199
+ const parsedResponse = _parseResponse.call(
200
+ this,
201
+ this._methodName,
202
+ this._initialRequest.meta,
203
+ streamResponse,
204
+ );
205
+ if (parsedResponse.payload) {
206
+ this.emit("data", parsedResponse.payload);
207
+ } else if (parsedResponse.ended) {
208
+ if (parsedResponse.error) {
209
+ this.emit("error", new Error(parsedResponse.error));
210
+ } else {
211
+ // Not sure this is necessary - we should receive "end" from grpc soon...
212
+ this.emit("end");
213
+ }
214
+ } else {
215
+ log("method id is %s", parsedResponse.methodId);
216
+ }
217
+ } catch (err) {
218
+ log("failure processing connect response: %s", err.message);
219
+ this.emit("error", err);
220
+ }
221
+ });
222
+
223
+ this._call.on("error", (err) => {
224
+ log("ERROR - intercepted stream error %s", err.message);
225
+ this.emit("error", err);
226
+ });
227
+
228
+ this._call.on("finish", () => {
229
+ // The read side has ended (i.e. we called end()).
230
+ log("finished call %s", this._methodName);
231
+ this.emit("finish");
232
+ });
233
+
234
+ this._call.on("end", () => {
235
+ // The remote peer ended the stream.
236
+ log(
237
+ "ended call %s, method id: %s",
238
+ this._methodName,
239
+ this._initialRequest.methodId || "n/a - not tunnelling",
240
+ );
241
+ this.emit("end");
242
+ });
243
+
244
+ if (this._initialRequest.request) {
245
+ this._call.write(this._initialRequest.request);
246
+ }
247
+
248
+ return this;
249
+ }
250
+
251
+ end() {
252
+ if (this._call) {
253
+ return this._call.end();
254
+ } else {
255
+ throw new Error("call not initialised");
256
+ }
257
+ }
258
+
259
+ get writable() {
260
+ return this._call?.writable;
261
+ }
262
+
263
+ write(request) {
264
+ if (!this._call) {
265
+ log("ERROR - call not initialised");
266
+ throw new Error("call not initialised");
267
+ } else if (this._initialRequest) {
268
+ const payloadRequest = _preparePayloadRequest.call(
269
+ this,
270
+ request,
271
+ this._initialRequest,
272
+ );
273
+ return this._call.write(payloadRequest);
274
+ } else {
275
+ this._initialRequest = _prepareInvokeRequest.call(
276
+ this,
277
+ request,
278
+ this._methodName,
279
+ this._methodType,
280
+ );
281
+
282
+ return this._call.write(this._initialRequest.request);
283
+ }
284
+ }
262
285
  }