1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | import * as ProtoBuf from "protobufjs";
|
8 | import {ByteBuffer} from "protobufjs";
|
9 | import type {Messages} from "./protobuf/messages.js";
|
10 |
|
11 | const HEADER_SIZE = 1 + 1 + 4 + 2;
|
12 | const MESSAGE_HEADER_BYTE: number = 0x23;
|
13 | const BUFFER_SIZE: number = 63;
|
14 |
|
15 |
|
16 | async function sendBuffers(
|
17 | sender: (data: ArrayBuffer) => Promise<void>,
|
18 | buffers: Array<ArrayBuffer>
|
19 | ): Promise<void> {
|
20 |
|
21 | for (let buffer of buffers) {
|
22 | await sender(buffer);
|
23 | }
|
24 | }
|
25 |
|
26 |
|
27 | class BuiltMessage {
|
28 | message: ProtoBuf.Builder.Message;
|
29 | type: number;
|
30 |
|
31 | constructor(messages: Messages, // Builders, generated by reading config
|
32 | name: string, // Name of the message
|
33 | data: Object // data as "pure" object, from trezor.js
|
34 | ) {
|
35 | const Builder = messages.messagesByName[name];
|
36 | if (Builder == null) {
|
37 | throw new Error(`The message name ${name} is not found.`);
|
38 | }
|
39 |
|
40 |
|
41 | cleanupInput(data);
|
42 |
|
43 | if (data) {
|
44 | this.message = new Builder(data);
|
45 | } else {
|
46 | this.message = new Builder();
|
47 | }
|
48 |
|
49 | this.type = messages.messageTypes[`MessageType_${name}`];
|
50 | }
|
51 |
|
52 |
|
53 |
|
54 | _encodeLong(): Uint8Array {
|
55 | const headerSize: number = HEADER_SIZE;
|
56 | const bytes: Uint8Array = new Uint8Array(this.message.encodeAB());
|
57 | const fullSize: number = headerSize + bytes.length;
|
58 |
|
59 | const encodedByteBuffer = new ByteBuffer(fullSize);
|
60 |
|
61 |
|
62 |
|
63 |
|
64 | encodedByteBuffer.writeByte(MESSAGE_HEADER_BYTE);
|
65 | encodedByteBuffer.writeByte(MESSAGE_HEADER_BYTE);
|
66 |
|
67 |
|
68 | encodedByteBuffer.writeUint16(this.type);
|
69 |
|
70 |
|
71 | encodedByteBuffer.writeUint32(bytes.length);
|
72 |
|
73 |
|
74 | encodedByteBuffer.append(bytes);
|
75 |
|
76 |
|
77 |
|
78 | const encoded: Uint8Array = new Uint8Array(encodedByteBuffer.buffer);
|
79 |
|
80 | return encoded;
|
81 | }
|
82 |
|
83 |
|
84 | encode(): Array<ArrayBuffer> {
|
85 | const bytes: Uint8Array = this._encodeLong();
|
86 |
|
87 | const result: Array<ArrayBuffer> = [];
|
88 | const size: number = BUFFER_SIZE;
|
89 |
|
90 |
|
91 | const count: number = Math.floor((bytes.length - 1) / size) + 1;
|
92 |
|
93 |
|
94 | for (let i = 0; i < count; i++) {
|
95 | const slice: Uint8Array = bytes.subarray(i * size, (i + 1) * size);
|
96 | const newArray: Uint8Array = new Uint8Array(size);
|
97 | newArray.set(slice);
|
98 | result.push(newArray.buffer);
|
99 | }
|
100 |
|
101 | return result;
|
102 | }
|
103 | }
|
104 |
|
105 |
|
106 | function cleanupInput(message: Object): void {
|
107 | delete message.$$hashKey;
|
108 |
|
109 | for (const key in message) {
|
110 | const value = message[key];
|
111 | if (value == null) {
|
112 | delete message[key];
|
113 | } else {
|
114 | if (Array.isArray(value)) {
|
115 | value.forEach((i) => {
|
116 | if (typeof i === `object`) {
|
117 | cleanupInput(i);
|
118 | }
|
119 | });
|
120 | }
|
121 | if (typeof value === `object`) {
|
122 | cleanupInput(value);
|
123 | }
|
124 | }
|
125 | }
|
126 | }
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 | function buildBuffers(messages: Messages, name: string, data: Object): Array<ArrayBuffer> {
|
134 | const message: BuiltMessage = new BuiltMessage(messages, name, data);
|
135 | const encoded: Array<ArrayBuffer> = message.encode();
|
136 | return encoded;
|
137 | }
|
138 |
|
139 |
|
140 |
|
141 | export async function buildAndSend(
|
142 | messages: Messages,
|
143 | sender: (data: ArrayBuffer) => Promise<void>,
|
144 | name: string,
|
145 | data: Object
|
146 | ): Promise<void> {
|
147 | const buffers: Array<ArrayBuffer> = buildBuffers(messages, name, data);
|
148 | return sendBuffers(sender, buffers);
|
149 | }
|