heimdall-tide 0.13.16 → 0.13.21
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/cjs/enclaves/ApprovalEnclaveNEW.d.ts +2 -2
- package/dist/cjs/enclaves/RequestEnclave.d.ts +13 -5
- package/dist/cjs/enclaves/RequestEnclave.js +127 -12
- package/dist/cjs/enclaves/RequestEnclave.js.map +1 -1
- package/dist/cjs/heimdall.d.ts +3 -1
- package/dist/cjs/heimdall.js +10 -8
- package/dist/cjs/heimdall.js.map +1 -1
- package/dist/cjs/index.d.ts +0 -2
- package/dist/cjs/index.js +1 -6
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/models/PolicySignRequest.d.ts +7 -4
- package/dist/cjs/models/PolicySignRequest.js +22 -6
- package/dist/cjs/models/PolicySignRequest.js.map +1 -1
- package/dist/cjs/wrapper.d.ts +2 -2
- package/dist/cjs/wrapper.js +4 -3
- package/dist/cjs/wrapper.js.map +1 -1
- package/dist/esm/enclaves/ApprovalEnclaveNEW.d.ts +2 -2
- package/dist/esm/enclaves/RequestEnclave.d.ts +13 -5
- package/dist/esm/enclaves/RequestEnclave.js +124 -9
- package/dist/esm/enclaves/RequestEnclave.js.map +1 -1
- package/dist/esm/heimdall.d.ts +3 -1
- package/dist/esm/heimdall.js +10 -8
- package/dist/esm/heimdall.js.map +1 -1
- package/dist/esm/index.d.ts +0 -2
- package/dist/esm/index.js +0 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/models/PolicySignRequest.d.ts +7 -4
- package/dist/esm/models/PolicySignRequest.js +20 -4
- package/dist/esm/models/PolicySignRequest.js.map +1 -1
- package/dist/esm/wrapper.d.ts +2 -2
- package/dist/esm/wrapper.js +2 -1
- package/dist/esm/wrapper.js.map +1 -1
- package/heimdall-tide-0.13.21.tgz +0 -0
- package/package.json +3 -3
- package/package.json.bak +3 -3
- package/src/enclaves/ApprovalEnclaveNEW.ts +2 -2
- package/src/enclaves/RequestEnclave.ts +129 -11
- package/src/heimdall.ts +12 -8
- package/src/index.ts +0 -2
- package/src/models/PolicySignRequest.ts +24 -5
- package/src/wrapper.ts +3 -2
- package/heimdall-tide-0.13.16.tgz +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { BaseTideRequest } from "asgard-tide";
|
|
2
1
|
import { Heimdall, HiddenInit, windowType } from "../heimdall";
|
|
3
|
-
import {
|
|
2
|
+
import { Cryptide, Models, Clients, Tools } from "@tideorg/js";
|
|
3
|
+
const TideMemory = Tools.TideMemory;
|
|
4
|
+
const BaseTideRequest = Models.BaseTideRequest;
|
|
4
5
|
|
|
5
6
|
enum State{
|
|
6
7
|
Closed,
|
|
@@ -18,19 +19,28 @@ export class RequestEnclave extends Heimdall<RequestEnclave>{
|
|
|
18
19
|
protected requireReloginCallback: () => Promise<string>;
|
|
19
20
|
private state: State = State.Closed;
|
|
20
21
|
|
|
22
|
+
protected bgUrl: string;
|
|
23
|
+
protected logoUrl: string;
|
|
24
|
+
|
|
21
25
|
_windowType: windowType = windowType.Hidden;
|
|
22
26
|
|
|
23
27
|
protected initDone: Promise<any> = this.recieve("init done");
|
|
28
|
+
private sessionId?: string;
|
|
24
29
|
|
|
25
30
|
init(data: HiddenInit): RequestEnclave {
|
|
26
31
|
if(!data.doken) throw 'Doken not provided';
|
|
27
32
|
|
|
33
|
+
if(data.backgroundUrl) this.bgUrl = data.backgroundUrl
|
|
34
|
+
if(data.logoUrl) this.logoUrl = data.logoUrl
|
|
35
|
+
|
|
28
36
|
this.doken = data.doken;
|
|
29
37
|
let parsedDoken = decodeToken(this.doken);
|
|
30
38
|
if(parsedDoken["t.uho"]) this.enclaveOrigin = parsedDoken["t.uho"]; // use tidecloak set user home ork from doken
|
|
31
39
|
this.dokenRefreshCallback = data.dokenRefreshCallback;
|
|
32
40
|
this.requireReloginCallback = data.requireReloginCallback;
|
|
33
41
|
|
|
42
|
+
this.handleSessionCheck();
|
|
43
|
+
|
|
34
44
|
if(this.state !== State.Closed) return; // someone has already called init
|
|
35
45
|
|
|
36
46
|
if(this._windowType === windowType.Hidden){
|
|
@@ -129,6 +139,11 @@ export class RequestEnclave extends Heimdall<RequestEnclave>{
|
|
|
129
139
|
// Set requestsed enclave
|
|
130
140
|
url.searchParams.set("type", this.name);
|
|
131
141
|
|
|
142
|
+
this.doken
|
|
143
|
+
|
|
144
|
+
if(this.bgUrl) url.searchParams.set("backgroundUrl", this.bgUrl)
|
|
145
|
+
if(this.logoUrl) url.searchParams.set("logoUrl", this.logoUrl)
|
|
146
|
+
|
|
132
147
|
return url;
|
|
133
148
|
}
|
|
134
149
|
checkEnclaveOpen(){
|
|
@@ -165,10 +180,14 @@ export class RequestEnclave extends Heimdall<RequestEnclave>{
|
|
|
165
180
|
console.log(`[HEIMDALL] Attempting to open ${windowType[this._windowType]} window`);
|
|
166
181
|
this.open().then((success: boolean) => {
|
|
167
182
|
if(success){
|
|
183
|
+
const session = new Uint32Array(5);
|
|
184
|
+
self.crypto.getRandomValues(session);
|
|
185
|
+
this.sessionId = Array.from(session, v => v.toString(16).padStart(8, '0')).join('');
|
|
168
186
|
this.send({
|
|
169
187
|
type: "init",
|
|
170
188
|
message:{
|
|
171
|
-
doken: this.doken
|
|
189
|
+
doken: this.doken,
|
|
190
|
+
sessionId: this.sessionId
|
|
172
191
|
}
|
|
173
192
|
});
|
|
174
193
|
console.log(`[HEIMDALL] Successfully opened ${windowType[this._windowType]} window`);
|
|
@@ -187,7 +206,7 @@ export class RequestEnclave extends Heimdall<RequestEnclave>{
|
|
|
187
206
|
}
|
|
188
207
|
}
|
|
189
208
|
|
|
190
|
-
async initializeRequest(request: TideMemory): Promise<Uint8Array>{
|
|
209
|
+
async initializeRequest(request: Tools.TideMemory): Promise<Uint8Array>{
|
|
191
210
|
// construct request to sign this request's creation
|
|
192
211
|
const requestToInitialize = BaseTideRequest.decode(request);
|
|
193
212
|
const requestToInitializeDetails = await requestToInitialize.getRequestInitDetails();
|
|
@@ -210,7 +229,7 @@ export class RequestEnclave extends Heimdall<RequestEnclave>{
|
|
|
210
229
|
return requestToInitialize.addCreationSignature(requestToInitializeDetails.creationTime, creationSig).encode();
|
|
211
230
|
}
|
|
212
231
|
|
|
213
|
-
async execute(data: TideMemory, waitForAll: boolean = false): Promise<Uint8Array[]>{
|
|
232
|
+
async execute(data: Tools.TideMemory, waitForAll: boolean = false): Promise<Uint8Array[]>{
|
|
214
233
|
this.checkEnclaveOpen();
|
|
215
234
|
await this.initDone;
|
|
216
235
|
const pre_resp = this.recieve("sign request completed");
|
|
@@ -223,38 +242,129 @@ export class RequestEnclave extends Heimdall<RequestEnclave>{
|
|
|
223
242
|
}
|
|
224
243
|
})
|
|
225
244
|
const resp = await pre_resp;
|
|
245
|
+
if(typeof resp.error === 'string') {
|
|
246
|
+
throw Error(resp.error);
|
|
247
|
+
}
|
|
226
248
|
if(!Array.isArray(resp)) throw 'Expecting request completed data to be an array, not' + resp;
|
|
227
249
|
if(!resp.every((d: any) => d instanceof Uint8Array)) throw 'Expecting all entries in response to be Uint8Arrays';
|
|
228
250
|
return resp;
|
|
229
251
|
}
|
|
230
|
-
async decrypt(data: decryptRequest): Promise<Uint8Array[]>{
|
|
252
|
+
async decrypt(data: decryptRequest[], policy?: Uint8Array): Promise<Uint8Array[]>{
|
|
231
253
|
this.checkEnclaveOpen();
|
|
232
254
|
await this.initDone;
|
|
233
255
|
const pre_resp = this.recieve("decrypt request completed");
|
|
234
256
|
this.send({
|
|
235
257
|
type: "request",
|
|
236
258
|
message:{
|
|
237
|
-
flow: "decrypt",
|
|
238
|
-
request: data
|
|
259
|
+
flow: policy ? "policy decrypt" : "decrypt",
|
|
260
|
+
request: data,
|
|
261
|
+
policy: policy
|
|
239
262
|
}
|
|
240
263
|
})
|
|
241
264
|
const resp = await pre_resp;
|
|
265
|
+
if(typeof resp.error === 'string') {
|
|
266
|
+
throw Error(resp.error);
|
|
267
|
+
}
|
|
242
268
|
if(!Array.isArray(resp)) throw 'Expecting request completed data to be an array, not' + resp;
|
|
243
269
|
if(!resp.every((d: any) => d instanceof Uint8Array)) throw 'Expecting all entries in response to be Uint8Arrays';
|
|
244
270
|
return resp;
|
|
245
271
|
}
|
|
246
|
-
async encrypt(data: encryptRequest): Promise<Uint8Array[]>{
|
|
272
|
+
async encrypt(data: encryptRequest[], policy?: Uint8Array): Promise<Uint8Array[]>{
|
|
247
273
|
this.checkEnclaveOpen();
|
|
248
274
|
await this.initDone;
|
|
249
275
|
const pre_resp = this.recieve("encrypt request completed");
|
|
250
276
|
this.send({
|
|
251
277
|
type: "request",
|
|
252
278
|
message: {
|
|
253
|
-
flow: "encrypt",
|
|
254
|
-
request: data
|
|
279
|
+
flow: policy ? "policy encrypt" : "encrypt",
|
|
280
|
+
request: data,
|
|
281
|
+
policy: policy
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
const resp = await pre_resp;
|
|
285
|
+
if(typeof resp.error === 'string') {
|
|
286
|
+
throw Error(resp.error);
|
|
287
|
+
}
|
|
288
|
+
if(!Array.isArray(resp)) throw 'Expecting request completed data to be an array, not' + resp;
|
|
289
|
+
if(!resp.every((d: any) => d instanceof Uint8Array)) throw 'Expecting all entries in response to be Uint8Arrays';
|
|
290
|
+
return resp;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async draftEncryption(data: encryptRequest[]) : Promise<Uint8Array> {
|
|
294
|
+
this.checkEnclaveOpen();
|
|
295
|
+
await this.initDone;
|
|
296
|
+
const pre_resp = this.recieve("encrypt request completed");
|
|
297
|
+
this.send({
|
|
298
|
+
type: "request",
|
|
299
|
+
message: {
|
|
300
|
+
flow: "policy encrypt",
|
|
301
|
+
request: data,
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
const resp = await pre_resp;
|
|
305
|
+
if(typeof resp.error === 'string') {
|
|
306
|
+
throw Error(resp.error);
|
|
307
|
+
}
|
|
308
|
+
if(!(resp instanceof Uint8Array)) throw `Expecting response type to be Uint8Array`;
|
|
309
|
+
return resp;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async draftDecryption(data: decryptRequest[]) : Promise<Uint8Array> {
|
|
313
|
+
this.checkEnclaveOpen();
|
|
314
|
+
await this.initDone;
|
|
315
|
+
const pre_resp = this.recieve("decrypt request completed");
|
|
316
|
+
this.send({
|
|
317
|
+
type: "request",
|
|
318
|
+
message: {
|
|
319
|
+
flow: "policy decrypt",
|
|
320
|
+
request: data,
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
const resp = await pre_resp;
|
|
324
|
+
if(typeof resp.error === 'string') {
|
|
325
|
+
throw Error(resp.error);
|
|
326
|
+
}
|
|
327
|
+
if(!(resp instanceof Uint8Array)) throw `Expecting response type to be Uint8Array`;
|
|
328
|
+
return resp;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async commitEncryption(readyEncryptionRequest: Uint8Array, policy: Uint8Array): Promise<Uint8Array[]> {
|
|
332
|
+
this.checkEnclaveOpen();
|
|
333
|
+
await this.initDone;
|
|
334
|
+
const pre_resp = this.recieve("commit encrypt request completed");
|
|
335
|
+
this.send({
|
|
336
|
+
type: "request",
|
|
337
|
+
message: {
|
|
338
|
+
flow: "commit encrypt",
|
|
339
|
+
request: readyEncryptionRequest,
|
|
340
|
+
policy: policy
|
|
255
341
|
}
|
|
256
342
|
})
|
|
257
343
|
const resp = await pre_resp;
|
|
344
|
+
if(typeof resp.error === 'string') {
|
|
345
|
+
throw Error(resp.error);
|
|
346
|
+
}
|
|
347
|
+
if(!Array.isArray(resp)) throw 'Expecting request completed data to be an array, not' + resp;
|
|
348
|
+
if(!resp.every((d: any) => d instanceof Uint8Array)) throw 'Expecting all entries in response to be Uint8Arrays';
|
|
349
|
+
return resp;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async commitDecryption(readyDecryptionRequest: Uint8Array, policy: Uint8Array): Promise<Uint8Array[]> {
|
|
353
|
+
this.checkEnclaveOpen();
|
|
354
|
+
await this.initDone;
|
|
355
|
+
const pre_resp = this.recieve("commit decrypt request completed");
|
|
356
|
+
this.send({
|
|
357
|
+
type: "request",
|
|
358
|
+
message: {
|
|
359
|
+
flow: "commit decrypt",
|
|
360
|
+
request: readyDecryptionRequest,
|
|
361
|
+
policy: policy
|
|
362
|
+
}
|
|
363
|
+
})
|
|
364
|
+
const resp = await pre_resp;
|
|
365
|
+
if(typeof resp.error === 'string') {
|
|
366
|
+
throw Error(resp.error);
|
|
367
|
+
}
|
|
258
368
|
if(!Array.isArray(resp)) throw 'Expecting request completed data to be an array, not' + resp;
|
|
259
369
|
if(!resp.every((d: any) => d instanceof Uint8Array)) throw 'Expecting all entries in response to be Uint8Arrays';
|
|
260
370
|
return resp;
|
|
@@ -270,6 +380,14 @@ export class RequestEnclave extends Heimdall<RequestEnclave>{
|
|
|
270
380
|
});
|
|
271
381
|
}
|
|
272
382
|
|
|
383
|
+
async handleSessionCheck() {
|
|
384
|
+
this.recieve("session check", true).then(() => this.handleSessionCheck());
|
|
385
|
+
this.send({
|
|
386
|
+
type: "current session",
|
|
387
|
+
message: this.sessionId
|
|
388
|
+
})
|
|
389
|
+
}
|
|
390
|
+
|
|
273
391
|
async onerror(data: any) {
|
|
274
392
|
if(typeof data.message === "string"){
|
|
275
393
|
switch(data.message){
|
package/src/heimdall.ts
CHANGED
|
@@ -74,14 +74,14 @@ export abstract class Heimdall<T> implements EnclaveFlow<T> {
|
|
|
74
74
|
break;
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
public async recieve(type: string): Promise<any> {
|
|
77
|
+
public async recieve(type: string, silent: boolean = false): Promise<any> {
|
|
78
78
|
switch(this._windowType){
|
|
79
79
|
case windowType.Popup:
|
|
80
|
-
return this.waitForWindowPostMessage(type);
|
|
80
|
+
return this.waitForWindowPostMessage(type, silent);
|
|
81
81
|
case windowType.Redirect:
|
|
82
82
|
throw new Error("Method not implemented.");
|
|
83
83
|
case windowType.Hidden:
|
|
84
|
-
return this.waitForWindowPostMessage(type);
|
|
84
|
+
return this.waitForWindowPostMessage(type, silent);
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
public close() {
|
|
@@ -170,10 +170,10 @@ export abstract class Heimdall<T> implements EnclaveFlow<T> {
|
|
|
170
170
|
this.enclaveWindow?.close();
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
private async waitForWindowPostMessage(responseTypeToAwait: string) {
|
|
173
|
+
private async waitForWindowPostMessage(responseTypeToAwait: string, silent: boolean = false) {
|
|
174
174
|
return new Promise((resolve) => {
|
|
175
175
|
const handler = (event) => {
|
|
176
|
-
const response = this.processEvent(event.data, event.origin, responseTypeToAwait);
|
|
176
|
+
const response = this.processEvent(event.data, event.origin, responseTypeToAwait, silent);
|
|
177
177
|
if (response.ok) {
|
|
178
178
|
resolve(response.message);
|
|
179
179
|
window.removeEventListener("message", handler);
|
|
@@ -189,7 +189,7 @@ export abstract class Heimdall<T> implements EnclaveFlow<T> {
|
|
|
189
189
|
this.enclaveWindow?.postMessage(message, this.enclaveOrigin);
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
private processEvent(data: any, origin: string, expectedType: string){
|
|
192
|
+
private processEvent(data: any, origin: string, expectedType: string, silent: boolean){
|
|
193
193
|
if (origin !== new URL(this.enclaveOrigin).origin) {
|
|
194
194
|
// Something's not right... The message has come from an unknown domain...
|
|
195
195
|
return {ok: false, print: false, error: "WRONG WINDOW SENT MESSAGE"};
|
|
@@ -205,10 +205,10 @@ export abstract class Heimdall<T> implements EnclaveFlow<T> {
|
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
if(expectedType !== data.type) {
|
|
208
|
-
console.log("[HEIMDALL] Received type{" + data.type + "} but waiting for type{" + expectedType + "}");
|
|
208
|
+
if(!silent) console.log("[HEIMDALL] Received type{" + data.type + "} but waiting for type{" + expectedType + "}");
|
|
209
209
|
return {ok: false, print: false, error: "handled error"}
|
|
210
210
|
}else{
|
|
211
|
-
console.log("[HEIMDALL] Correctly received type{" + data.type + "}");
|
|
211
|
+
if(!silent) console.log("[HEIMDALL] Correctly received type{" + data.type + "}");
|
|
212
212
|
return {ok: true, message: data.message}
|
|
213
213
|
}
|
|
214
214
|
}
|
|
@@ -219,6 +219,10 @@ export enum windowType{
|
|
|
219
219
|
Hidden
|
|
220
220
|
};
|
|
221
221
|
export interface HiddenInit{
|
|
222
|
+
backgroundUrl: string;
|
|
223
|
+
|
|
224
|
+
logoUrl: string;
|
|
225
|
+
|
|
222
226
|
doken: string;
|
|
223
227
|
/**
|
|
224
228
|
* @returns A refresh doken for Heimdall
|
package/src/index.ts
CHANGED
|
@@ -2,10 +2,8 @@ import { ApprovalEnclave } from "./enclaves/ApprovalEnclave"
|
|
|
2
2
|
import { RequestEnclave } from "./enclaves/RequestEnclave"
|
|
3
3
|
import { ApprovalEnclaveNew } from "./enclaves/ApprovalEnclaveNEW"
|
|
4
4
|
import PolicySignRequest from "./models/PolicySignRequest"
|
|
5
|
-
import { TideMemory, Policy, BaseTideRequest, PolicyParameters } from "asgard-tide"
|
|
6
5
|
|
|
7
6
|
export { ApprovalEnclave }
|
|
8
7
|
export { RequestEnclave }
|
|
9
8
|
export { ApprovalEnclaveNew }
|
|
10
9
|
export { PolicySignRequest }
|
|
11
|
-
export { TideMemory, Policy, BaseTideRequest, PolicyParameters }
|
|
@@ -1,19 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { Cryptide, Models, Clients, Tools, Contracts } from "@tideorg/js";
|
|
2
|
+
const TideMemory = Tools.TideMemory;
|
|
3
|
+
const BaseTideRequest = Models.BaseTideRequest;
|
|
4
|
+
const Policy = Models.Policy;
|
|
5
|
+
const GenericResourceAccessThresholdRoleContract = Contracts.GenericResourceAccessThresholdRoleContract
|
|
6
|
+
|
|
7
|
+
const forsetiType = "forseti";
|
|
8
|
+
const forsetiEntryType = "Contract";
|
|
4
9
|
|
|
5
10
|
export default class PolicySignRequest extends BaseTideRequest {
|
|
11
|
+
private new_policy: Models.Policy;
|
|
6
12
|
constructor(name: string, version: string, authFlow: string, draft: Uint8Array, dyanmicData: Uint8Array) {
|
|
7
13
|
super(name, version, authFlow, draft, dyanmicData);
|
|
8
14
|
}
|
|
9
|
-
static New(policy: Policy) : PolicySignRequest{
|
|
10
|
-
|
|
15
|
+
static New(policy: Models.Policy) : PolicySignRequest{
|
|
16
|
+
const p = new PolicySignRequest(
|
|
11
17
|
"Policy",
|
|
12
18
|
"1",
|
|
13
19
|
"Policy:1",
|
|
14
20
|
TideMemory.CreateFromArray([policy.toBytes()]),
|
|
15
21
|
new TideMemory()
|
|
16
22
|
);
|
|
23
|
+
p.new_policy = policy;
|
|
24
|
+
return p;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
addForsetiContractToUpload(source: Uint8Array | string){
|
|
28
|
+
const te = new TextEncoder();
|
|
29
|
+
const s = typeof source === "string" ? te.encode(source) : source;
|
|
30
|
+
const innerPayload = TideMemory.CreateFromArray([s, te.encode(forsetiEntryType)]);
|
|
31
|
+
const forsetiData = TideMemory.CreateFromArray([new Uint8Array(0), innerPayload]);
|
|
32
|
+
const contractTransport = TideMemory.CreateFromArray([te.encode(forsetiType), forsetiData]);
|
|
33
|
+
const draft = TideMemory.CreateFromArray([this.new_policy.toBytes(), contractTransport]);
|
|
34
|
+
this.draft = draft;
|
|
35
|
+
return this;
|
|
17
36
|
}
|
|
18
37
|
|
|
19
38
|
getRequestedPolicy(){
|
package/src/wrapper.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Tools } from "@tideorg/js";
|
|
2
|
+
const TideMemory = Tools.TideMemory;
|
|
2
3
|
export const version = "1";
|
|
3
4
|
|
|
4
|
-
export function wrapper(arr: NestedEntry): TideMemory {
|
|
5
|
+
export function wrapper(arr: NestedEntry): Tools.TideMemory {
|
|
5
6
|
// If array is only Uint8Arrays - create a TideMemory out of it
|
|
6
7
|
// If there is any entry in an array that is another array
|
|
7
8
|
// -> Go inside that array and repeat the process
|
|
Binary file
|