@onekeyfe/hd-transport 1.2.0-alpha.13 → 1.2.0-alpha.131
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/__tests__/messages.test.js +144 -4
- package/__tests__/protocol-v2-ble-frame-writer.test.js +119 -0
- package/__tests__/protocol-v2-link-manager.test.js +176 -9
- package/__tests__/protocol-v2-usb-transport-base.test.js +36 -2
- package/__tests__/protocol-v2.test.js +540 -108
- package/__tests__/transport-log.test.js +79 -0
- package/dist/constants.d.ts +3 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +645 -217
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +679 -153
- package/dist/protocols/index.d.ts +17 -1
- package/dist/protocols/index.d.ts.map +1 -1
- package/dist/protocols/v2/ble-frame-writer.d.ts +15 -0
- package/dist/protocols/v2/ble-frame-writer.d.ts.map +1 -0
- package/dist/protocols/v2/decode.d.ts +11 -0
- package/dist/protocols/v2/decode.d.ts.map +1 -1
- package/dist/protocols/v2/errors.d.ts +16 -0
- package/dist/protocols/v2/errors.d.ts.map +1 -0
- package/dist/protocols/v2/frame-assembler.d.ts.map +1 -1
- package/dist/protocols/v2/index.d.ts +2 -0
- package/dist/protocols/v2/index.d.ts.map +1 -1
- package/dist/protocols/v2/link-manager.d.ts +5 -0
- package/dist/protocols/v2/link-manager.d.ts.map +1 -1
- package/dist/protocols/v2/session.d.ts +14 -4
- package/dist/protocols/v2/session.d.ts.map +1 -1
- package/dist/protocols/v2/usb-transport-base.d.ts +2 -0
- package/dist/protocols/v2/usb-transport-base.d.ts.map +1 -1
- package/dist/serialization/protobuf/decode.d.ts.map +1 -1
- package/dist/serialization/protobuf/encode.d.ts.map +1 -1
- package/dist/types/messages.d.ts +301 -156
- package/dist/types/messages.d.ts.map +1 -1
- package/dist/types/transport.d.ts +21 -3
- package/dist/types/transport.d.ts.map +1 -1
- package/dist/utils/transportLog.d.ts +9 -0
- package/dist/utils/transportLog.d.ts.map +1 -0
- package/messages-protocol-v2.json +523 -449
- package/package.json +2 -2
- package/scripts/protobuf-build.sh +112 -32
- package/scripts/protobuf-patches/index.js +1 -0
- package/scripts/protobuf-types.js +10 -1
- package/src/constants.ts +30 -16
- package/src/index.ts +5 -1
- package/src/protocols/index.ts +114 -3
- package/src/protocols/v2/ble-frame-writer.ts +77 -0
- package/src/protocols/v2/crc8.ts +1 -1
- package/src/protocols/v2/decode.ts +49 -12
- package/src/protocols/v2/encode.ts +1 -1
- package/src/protocols/v2/errors.ts +54 -0
- package/src/protocols/v2/frame-assembler.ts +6 -4
- package/src/protocols/v2/index.ts +2 -0
- package/src/protocols/v2/link-manager.ts +65 -5
- package/src/protocols/v2/session.ts +224 -80
- package/src/protocols/v2/usb-transport-base.ts +42 -10
- package/src/serialization/protobuf/decode.ts +4 -1
- package/src/serialization/protobuf/encode.ts +5 -1
- package/src/types/messages.ts +375 -198
- package/src/types/transport.ts +30 -3
- package/src/utils/transportLog.ts +102 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.131",
|
|
4
4
|
"description": "Transport layer abstractions and utilities for OneKey hardware SDK.",
|
|
5
5
|
"author": "OneKey",
|
|
6
6
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
@@ -28,5 +28,5 @@
|
|
|
28
28
|
"long": "^4.0.0",
|
|
29
29
|
"protobufjs": "^6.11.2"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "81b472e983e80e6aa771b991dc9c0c9b1bdcf07b"
|
|
32
32
|
}
|
|
@@ -2,57 +2,137 @@
|
|
|
2
2
|
|
|
3
3
|
set -euxo pipefail
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
PARENT_PATH=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
|
|
6
|
+
PACKAGE_ROOT="$PARENT_PATH/.."
|
|
7
|
+
REPO_ROOT="$PARENT_PATH/../../.."
|
|
8
|
+
CORE_MESSAGES_DIR="$REPO_ROOT/packages/core/src/data/messages"
|
|
8
9
|
|
|
9
10
|
SRC="../../submodules/firmware/common/protob"
|
|
10
11
|
DIST="."
|
|
11
12
|
LANG="typescript"
|
|
12
13
|
|
|
13
|
-
if [[ $# -ne 0 && $# -ne 3 ]]
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
if [[ $# -ne 0 && $# -ne 3 ]]; then
|
|
15
|
+
echo "must provide either 3 or 0 arguments. $# provided"
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
if [[ $# -eq 3 ]]; then
|
|
20
|
+
SRC=$1
|
|
21
|
+
DIST=$2
|
|
22
|
+
LANG=$3
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
if [[ "$LANG" != "typescript" && "$LANG" != "flow" ]]; then
|
|
26
|
+
echo "either typescript or flow must be specified as the third argument"
|
|
27
|
+
exit 1
|
|
17
28
|
fi
|
|
18
29
|
|
|
19
|
-
if [[
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
LANG=$3
|
|
30
|
+
if [[ "$SRC" = /* ]]; then
|
|
31
|
+
SRC_PATH="$SRC"
|
|
32
|
+
else
|
|
33
|
+
SRC_PATH="$PACKAGE_ROOT/$SRC"
|
|
24
34
|
fi
|
|
25
35
|
|
|
26
|
-
if [[ "$
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
36
|
+
if [[ "$DIST" = /* ]]; then
|
|
37
|
+
DIST_PATH="$DIST"
|
|
38
|
+
else
|
|
39
|
+
DIST_PATH="$PACKAGE_ROOT/$DIST"
|
|
30
40
|
fi
|
|
31
41
|
|
|
42
|
+
V1_TMP_PROTO="$DIST_PATH/messages-tmp.proto"
|
|
43
|
+
V2_TMP_PROTO="$PARENT_PATH/messages-protocol-v2-tmp.proto"
|
|
44
|
+
trap 'rm -f "$V1_TMP_PROTO" "$V2_TMP_PROTO"' EXIT
|
|
32
45
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
echo 'import "google/protobuf/descriptor.proto";' >> $DIST/messages.proto
|
|
38
|
-
echo "Build proto file from $SRC"
|
|
39
|
-
grep -hv -e '^import ' -e '^syntax' -e '^package' -e 'option java_' $SRC/messages*.proto \
|
|
46
|
+
echo "=== Building Protocol V1 messages ==="
|
|
47
|
+
echo 'syntax = "proto2";' > "$V1_TMP_PROTO"
|
|
48
|
+
echo 'import "google/protobuf/descriptor.proto";' >> "$V1_TMP_PROTO"
|
|
49
|
+
grep -hv -e '^import ' -e '^syntax' -e '^package' -e 'option java_' "$SRC_PATH"/messages*.proto \
|
|
40
50
|
| sed 's/ hw\.trezor\.messages\.common\./ /' \
|
|
41
51
|
| sed 's/ common\./ /' \
|
|
42
52
|
| sed 's/ ethereum_definitions\./ /' \
|
|
43
53
|
| sed 's/ management\./ /' \
|
|
44
54
|
| sed 's/^option /\/\/ option /' \
|
|
45
|
-
| grep -v ' reserved '>> $
|
|
55
|
+
| grep -v ' reserved ' >> "$V1_TMP_PROTO"
|
|
46
56
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
rm $DIST/messages.proto
|
|
57
|
+
npx pbjs -t json -p "$DIST_PATH" -o "$DIST_PATH/messages.json" --keep-case "$V1_TMP_PROTO"
|
|
58
|
+
cp "$DIST_PATH/messages.json" "$CORE_MESSAGES_DIR/messages.json"
|
|
50
59
|
|
|
51
|
-
echo "
|
|
60
|
+
echo "=== Building Protocol V2 messages ==="
|
|
61
|
+
SRC_PRO2_LEGACY="$REPO_ROOT/submodules/firmware-pro2/sys/protobuf/onekey_protocol/legacy"
|
|
62
|
+
SRC_PRO2_LATEST="$REPO_ROOT/submodules/firmware-pro2/sys/protobuf/onekey_protocol/latest"
|
|
52
63
|
|
|
53
|
-
|
|
64
|
+
if [[ ! -d "$SRC_PRO2_LEGACY" || ! -d "$SRC_PRO2_LATEST" ]]; then
|
|
65
|
+
echo "firmware-pro2 protobuf schema is missing"
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
echo 'syntax = "proto2";'
|
|
71
|
+
echo 'import "google/protobuf/descriptor.proto";'
|
|
72
|
+
echo ''
|
|
73
|
+
grep -hv \
|
|
74
|
+
-e '^import ' -e '^syntax' -e '^package' -e 'option java_' \
|
|
75
|
+
"$SRC_PRO2_LEGACY"/messages*.proto \
|
|
76
|
+
| sed 's/ hw\.onekey\.messages\.[a-zA-Z_]*\./ /g' \
|
|
77
|
+
| sed 's/ crypto\./ /g' \
|
|
78
|
+
| sed 's/ ethereum_definitions\./ /g' \
|
|
79
|
+
| sed 's/ management\./ /g' \
|
|
80
|
+
| sed 's/^option /\/\/ option /' \
|
|
81
|
+
| grep -v ' reserved '
|
|
82
|
+
echo ''
|
|
83
|
+
echo '// --- Protocol V2 system messages ---'
|
|
84
|
+
grep -hv \
|
|
85
|
+
-e '^import ' -e '^syntax' -e '^package' -e 'option java_' -e '^option ' \
|
|
86
|
+
"$SRC_PRO2_LATEST"/messages*.proto \
|
|
87
|
+
| grep -v ' reserved '
|
|
88
|
+
} > "$V2_TMP_PROTO"
|
|
54
89
|
|
|
55
|
-
|
|
90
|
+
# Protocol V2 latest schema defines common request names that also exist in
|
|
91
|
+
# legacy chain messages. Keep the legacy definitions and remove the duplicate
|
|
92
|
+
# top-level latest declarations before protobufjs parses the flattened schema.
|
|
93
|
+
node - "$V2_TMP_PROTO" <<'NODE'
|
|
94
|
+
const fs = require('fs');
|
|
95
|
+
|
|
96
|
+
const protoPath = process.argv[2];
|
|
97
|
+
let proto = fs.readFileSync(protoPath, 'utf8');
|
|
98
|
+
|
|
99
|
+
function removeLastTopLevelMessage(source, name) {
|
|
100
|
+
const pattern = new RegExp(`(^|\\n)message\\s+${name}\\s*\\{`, 'gm');
|
|
101
|
+
const matches = Array.from(source.matchAll(pattern));
|
|
102
|
+
if (matches.length < 2) return source;
|
|
103
|
+
const match = matches[matches.length - 1];
|
|
104
|
+
const start = match.index + (match[1] ? match[1].length : 0);
|
|
105
|
+
let depth = 0;
|
|
106
|
+
for (let index = start; index < source.length; index += 1) {
|
|
107
|
+
if (source[index] === '{') depth += 1;
|
|
108
|
+
if (source[index] === '}') {
|
|
109
|
+
depth -= 1;
|
|
110
|
+
if (depth === 0) {
|
|
111
|
+
let end = index + 1;
|
|
112
|
+
while (end < source.length && /\\s/.test(source[end])) end += 1;
|
|
113
|
+
return `${source.slice(0, start)}${source.slice(end)}`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
throw new Error(`Unterminated message block: ${name}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
['Initialize', 'GetFeatures', 'OnekeyGetFeatures', 'Features'].forEach(name => {
|
|
121
|
+
proto = removeLastTopLevelMessage(proto, name);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
fs.writeFileSync(protoPath, proto);
|
|
125
|
+
NODE
|
|
126
|
+
|
|
127
|
+
npx pbjs -t json -p "$PARENT_PATH" -o "$PACKAGE_ROOT/messages-protocol-v2.json" --keep-case "$V2_TMP_PROTO"
|
|
128
|
+
cp "$PACKAGE_ROOT/messages-protocol-v2.json" "$CORE_MESSAGES_DIR/messages-protocol-v2.json"
|
|
129
|
+
|
|
130
|
+
echo "generating type definitions for: $LANG"
|
|
131
|
+
cd "$PARENT_PATH"
|
|
132
|
+
node ./protobuf-types.js "$LANG"
|
|
56
133
|
|
|
57
|
-
yarn prettier --write messages.json
|
|
58
|
-
yarn prettier --write
|
|
134
|
+
yarn --cwd "$PACKAGE_ROOT" prettier --write "$DIST_PATH/messages.json"
|
|
135
|
+
yarn --cwd "$PACKAGE_ROOT" prettier --write "$CORE_MESSAGES_DIR/messages.json"
|
|
136
|
+
yarn --cwd "$PACKAGE_ROOT" prettier --write "$PACKAGE_ROOT/messages-protocol-v2.json"
|
|
137
|
+
yarn --cwd "$PACKAGE_ROOT" prettier --write "$CORE_MESSAGES_DIR/messages-protocol-v2.json"
|
|
138
|
+
yarn --cwd "$PACKAGE_ROOT" prettier --write "$PACKAGE_ROOT/src/types/messages.ts"
|
|
@@ -57,6 +57,9 @@ const ENUM_KEYS = [
|
|
|
57
57
|
'InputScriptType',
|
|
58
58
|
'OutputScriptType',
|
|
59
59
|
'RequestType',
|
|
60
|
+
'KaspaInputScriptType',
|
|
61
|
+
'KaspaOutputScriptType',
|
|
62
|
+
'KaspaRequestType',
|
|
60
63
|
'BackupType',
|
|
61
64
|
'Capability',
|
|
62
65
|
'SafetyCheckLevel',
|
|
@@ -66,6 +69,9 @@ const ENUM_KEYS = [
|
|
|
66
69
|
'ProtocolV2Capability',
|
|
67
70
|
];
|
|
68
71
|
|
|
72
|
+
// Safety checks are accepted as both protobuf enum names and numeric legacy values.
|
|
73
|
+
const ENUM_KEYS_WITH_NUMERIC_VALUES = ['SafetyCheckLevel'];
|
|
74
|
+
|
|
69
75
|
const parseEnumTypescript = (itemName, item) => {
|
|
70
76
|
const value = [];
|
|
71
77
|
const IS_KEY = ENUM_KEYS.includes(itemName);
|
|
@@ -84,7 +90,10 @@ const parseEnumTypescript = (itemName, item) => {
|
|
|
84
90
|
value.push('}');
|
|
85
91
|
|
|
86
92
|
if (IS_KEY) {
|
|
87
|
-
|
|
93
|
+
const numericValue = ENUM_KEYS_WITH_NUMERIC_VALUES.includes(itemName)
|
|
94
|
+
? ` | Enum_${itemName}`
|
|
95
|
+
: '';
|
|
96
|
+
value.push(`export type ${itemName} = keyof typeof Enum_${itemName}${numericValue};`);
|
|
88
97
|
}
|
|
89
98
|
// empty line
|
|
90
99
|
value.push('');
|
package/src/constants.ts
CHANGED
|
@@ -1,50 +1,64 @@
|
|
|
1
1
|
// ---- Protocol V1 (Pro1 / Touch / Mini / Classic) ----
|
|
2
2
|
|
|
3
|
-
/** Protocol V1 USB report
|
|
3
|
+
/** Protocol V1 USB report marker, ASCII `?`. */
|
|
4
4
|
export const PROTOCOL_V1_REPORT_ID = 0x3f;
|
|
5
5
|
|
|
6
|
-
/** Protocol V1 envelope
|
|
6
|
+
/** Protocol V1 envelope marker, ASCII `#`. */
|
|
7
7
|
export const PROTOCOL_V1_HEADER_BYTE = 0x23;
|
|
8
8
|
|
|
9
|
-
/** Protocol V1
|
|
9
|
+
/** Protocol V1 payload bytes per chunk after the report marker. */
|
|
10
10
|
export const PROTOCOL_V1_CHUNK_PAYLOAD_SIZE = 63;
|
|
11
11
|
|
|
12
|
-
/** Protocol V1 USB packet
|
|
12
|
+
/** Protocol V1 USB packet length: report marker plus chunk payload. */
|
|
13
13
|
export const PROTOCOL_V1_USB_PACKET_SIZE = PROTOCOL_V1_CHUNK_PAYLOAD_SIZE + 1;
|
|
14
14
|
|
|
15
|
-
/** Protocol V1 message metadata
|
|
15
|
+
/** Protocol V1 message metadata: two-byte type plus four-byte payload length. */
|
|
16
16
|
export const PROTOCOL_V1_MESSAGE_HEADER_SIZE = 2 + 4;
|
|
17
17
|
|
|
18
|
-
/** Protocol V1 envelope metadata
|
|
18
|
+
/** Protocol V1 envelope metadata: `##`, message type, and payload length. */
|
|
19
19
|
export const PROTOCOL_V1_ENVELOPE_HEADER_SIZE = 1 + 1 + PROTOCOL_V1_MESSAGE_HEADER_SIZE;
|
|
20
20
|
|
|
21
21
|
// ---- Protocol V2 (Pro2 USB / BLE transports) ----
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
export const PROTOCOL_V2_FRAME_MAX_BYTES =
|
|
23
|
+
/** Firmware Proto Link runtime limit for a complete V2 frame, including header and CRC. */
|
|
24
|
+
export const PROTOCOL_V2_FRAME_MAX_BYTES = 4200;
|
|
25
25
|
|
|
26
|
-
/**
|
|
26
|
+
/** FilesystemFileWrite chunk size over WebUSB. */
|
|
27
27
|
export const PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE = 4000;
|
|
28
28
|
|
|
29
|
-
/**
|
|
29
|
+
/** FilesystemFileWrite chunk size over BLE. */
|
|
30
30
|
export const PROTOCOL_V2_BLE_FILE_CHUNK_SIZE = 1800;
|
|
31
31
|
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* FirmwareUpdateV4 chunk size for its fixed BLE staging paths.
|
|
34
|
+
* The longest current path still leaves 28 bytes below the 2048-byte BLE frame limit.
|
|
35
|
+
*/
|
|
36
|
+
export const PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE = 1960;
|
|
37
|
+
|
|
38
|
+
/** BLE FilesystemFileRead chunk size, limited by the Pro2 1024-byte UART TX buffer. */
|
|
33
39
|
export const PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE = 900;
|
|
34
40
|
|
|
35
|
-
/** Pro2 BLE/UART
|
|
41
|
+
/** Pro2 BLE/UART RX FIFO must hold a complete Proto Link frame. */
|
|
36
42
|
export const PROTOCOL_V2_BLE_FRAME_MAX_BYTES = 2048;
|
|
37
43
|
|
|
38
|
-
/** @deprecated
|
|
44
|
+
/** @deprecated Use the transport-specific WebUSB or BLE file chunk constant. */
|
|
39
45
|
export const PROTOCOL_V2_FILE_CHUNK_SIZE = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
|
|
40
46
|
|
|
41
47
|
/**
|
|
42
|
-
* Protocol V2
|
|
43
|
-
*
|
|
48
|
+
* Protocol V2 routing channel. USB reaches the main MCU directly, while BLE routes
|
|
49
|
+
* through the BLE coprocessor UART bridge.
|
|
44
50
|
*/
|
|
45
51
|
export const PROTOCOL_V2_CHANNEL_USB = 0;
|
|
46
52
|
export const PROTOCOL_V2_CHANNEL_BLE_UART = 1;
|
|
47
53
|
export const PROTOCOL_V2_CHANNEL_SOCKET = 2;
|
|
48
54
|
|
|
49
|
-
/** protobuf
|
|
55
|
+
/** packet_src for protobuf commands; firmware routes zero to the protobuf dispatcher. */
|
|
50
56
|
export const PROTOCOL_V2_PACKET_SRC_COMMAND = 0;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Shared upper bound for a Protocol V2 request without a method-specific timeout.
|
|
60
|
+
* Interactive and signing calls may wait on the device UI, so keep this longer than
|
|
61
|
+
* ordinary transport timeouts while still preventing a stalled link from blocking
|
|
62
|
+
* the per-device queue forever.
|
|
63
|
+
*/
|
|
64
|
+
export const PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS = 5 * 60 * 1000;
|
package/src/index.ts
CHANGED
|
@@ -36,16 +36,19 @@ export type {
|
|
|
36
36
|
OneKeyDeviceInfoWithSession,
|
|
37
37
|
MessageFromOneKey,
|
|
38
38
|
TransportCallOptions,
|
|
39
|
+
TransportWriteMetrics,
|
|
39
40
|
LowlevelTransportSharedPlugin,
|
|
40
41
|
LowLevelDevice,
|
|
41
42
|
OneKeyDeviceInfoBase,
|
|
42
43
|
OneKeyDeviceCommType,
|
|
43
44
|
ProtocolType,
|
|
45
|
+
TransportDeviceDisconnectEvent,
|
|
44
46
|
} from './types';
|
|
45
47
|
|
|
46
|
-
export { Messages } from './types';
|
|
48
|
+
export { Messages, TRANSPORT_EVENT } from './types';
|
|
47
49
|
export * from './types/messages';
|
|
48
50
|
export * from './utils/logBlockCommand';
|
|
51
|
+
export * from './utils/transportLog';
|
|
49
52
|
|
|
50
53
|
export * from './constants';
|
|
51
54
|
export * from './protocols';
|
|
@@ -53,6 +56,7 @@ export * as protocolV1 from './protocols/v1';
|
|
|
53
56
|
export * as protocolV2 from './protocols/v2';
|
|
54
57
|
export * from './protocols/v2/session';
|
|
55
58
|
export * from './protocols/v2/link-manager';
|
|
59
|
+
export * from './protocols/v2/ble-frame-writer';
|
|
56
60
|
export * from './protocols/v2/usb-transport-base';
|
|
57
61
|
|
|
58
62
|
export default {
|
package/src/protocols/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ByteBuffer from 'bytebuffer';
|
|
2
|
+
import { Reader, Type } from 'protobufjs/light';
|
|
2
3
|
|
|
3
4
|
import { encodeEnvelopeMessage, encodeMessageChunks, encodeTransportPackets } from './v1/packets';
|
|
4
5
|
import { decodeFirstChunk } from './v1/decode';
|
|
@@ -6,7 +7,12 @@ import { decodeMessage as decodeV1Message } from './v1/receive';
|
|
|
6
7
|
import { createMessageFromName, createMessageFromType } from '../serialization/protobuf/messages';
|
|
7
8
|
import { encode as encodeProtobuf } from '../serialization/protobuf/encode';
|
|
8
9
|
import { decode as decodeProtobuf } from '../serialization/protobuf/decode';
|
|
9
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
decodeFrame as decodeV2Frame,
|
|
12
|
+
encodeProtobufFrame,
|
|
13
|
+
inspectFrameHeader,
|
|
14
|
+
isAckFrame,
|
|
15
|
+
} from './v2';
|
|
10
16
|
|
|
11
17
|
import type { Root } from 'protobufjs/light';
|
|
12
18
|
|
|
@@ -24,6 +30,85 @@ type ProtocolV2FrameOptions = {
|
|
|
24
30
|
seq?: number;
|
|
25
31
|
};
|
|
26
32
|
|
|
33
|
+
const LEGACY_PROTOCOL_V2_PROTOCOL_INFO = Type.fromJSON('LegacyProtocolInfo', {
|
|
34
|
+
fields: {
|
|
35
|
+
version: {
|
|
36
|
+
rule: 'required',
|
|
37
|
+
type: 'uint32',
|
|
38
|
+
id: 1,
|
|
39
|
+
},
|
|
40
|
+
supported_messages: {
|
|
41
|
+
rule: 'repeated',
|
|
42
|
+
type: 'uint32',
|
|
43
|
+
id: 2,
|
|
44
|
+
options: {
|
|
45
|
+
packed: false,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
protobuf_definition: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
id: 3,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const decodeLegacyProtocolV2ProtocolInfo = (pbPayload: Uint8Array) => {
|
|
56
|
+
const byteBuffer = ByteBuffer.wrap(Buffer.from(pbPayload) as unknown as ArrayBuffer);
|
|
57
|
+
const legacy = decodeProtobuf(LEGACY_PROTOCOL_V2_PROTOCOL_INFO, byteBuffer);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
version: legacy.version,
|
|
61
|
+
build_fingerprint: '',
|
|
62
|
+
supported_messages: legacy.supported_messages ?? [],
|
|
63
|
+
// Presence of this legacy field lets Core keep the fallback recovery-only.
|
|
64
|
+
protobuf_definition: null,
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const isLegacyProtocolV2ProtocolInfoWireLayout = (pbPayload: Uint8Array) => {
|
|
69
|
+
const reader = Reader.create(pbPayload);
|
|
70
|
+
let hasVersion = false;
|
|
71
|
+
try {
|
|
72
|
+
while (reader.pos < reader.len) {
|
|
73
|
+
const tag = reader.uint32();
|
|
74
|
+
// eslint-disable-next-line no-bitwise
|
|
75
|
+
const fieldNumber = tag >>> 3;
|
|
76
|
+
// eslint-disable-next-line no-bitwise
|
|
77
|
+
const wireType = tag & 0x07;
|
|
78
|
+
if (fieldNumber === 1) {
|
|
79
|
+
if (wireType !== 0) return false;
|
|
80
|
+
hasVersion = true;
|
|
81
|
+
} else if (fieldNumber === 2) {
|
|
82
|
+
// Legacy field 2 is repeated uint32; current field 2 is a string.
|
|
83
|
+
if (wireType !== 0) return false;
|
|
84
|
+
} else if (fieldNumber === 3) {
|
|
85
|
+
// Legacy field 3 is protobuf_definition; current field 3 is uint32.
|
|
86
|
+
if (wireType !== 2) return false;
|
|
87
|
+
} else {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
reader.skipType(wireType);
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return hasVersion;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const tryDecodeLegacyProtocolV2ProtocolInfo = (messageName: string, pbPayload: Uint8Array) => {
|
|
99
|
+
// TEMPORARY COMPATIBILITY: Remove this fallback after all supported Pro2 and Neo
|
|
100
|
+
// firmware versions return ProtocolInfo with the current build_fingerprint layout.
|
|
101
|
+
if (messageName !== 'ProtocolInfo' || !isLegacyProtocolV2ProtocolInfoWireLayout(pbPayload)) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
return decodeLegacyProtocolV2ProtocolInfo(pbPayload);
|
|
107
|
+
} catch {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
27
112
|
const resolveProtocolV2EncodeSchema = (name: string, schemas: ProtocolV2Schemas) => {
|
|
28
113
|
try {
|
|
29
114
|
schemas.protocolV2.lookupType(name);
|
|
@@ -71,15 +156,19 @@ export const ProtocolV1 = {
|
|
|
71
156
|
|
|
72
157
|
export const ProtocolV2 = {
|
|
73
158
|
isAckFrame,
|
|
159
|
+
inspectFrameHeader,
|
|
74
160
|
|
|
75
161
|
inspectFrame(schemas: ProtocolV2Schemas, frame: Uint8Array) {
|
|
76
|
-
const { messageTypeId, pbPayload, seq } = decodeV2Frame(frame);
|
|
162
|
+
const { messageTypeId, pbPayload, seq, router, packetSrc, dataType } = decodeV2Frame(frame);
|
|
77
163
|
const { messageName } = createProtocolV2MessageFromType(messageTypeId, schemas);
|
|
78
164
|
return {
|
|
79
165
|
messageName,
|
|
80
166
|
messageTypeId,
|
|
81
167
|
pbPayload,
|
|
82
168
|
seq,
|
|
169
|
+
router,
|
|
170
|
+
packetSrc,
|
|
171
|
+
dataType,
|
|
83
172
|
type: messageName,
|
|
84
173
|
};
|
|
85
174
|
},
|
|
@@ -110,7 +199,29 @@ export const ProtocolV2 = {
|
|
|
110
199
|
const { messageTypeId, pbPayload, seq, messageName } = this.inspectFrame(schemas, frame);
|
|
111
200
|
const { Message } = createProtocolV2MessageFromType(messageTypeId, schemas);
|
|
112
201
|
const rxByteBuffer = ByteBuffer.wrap(Buffer.from(pbPayload) as unknown as ArrayBuffer);
|
|
113
|
-
|
|
202
|
+
let message: ReturnType<typeof decodeProtobuf>;
|
|
203
|
+
try {
|
|
204
|
+
message = decodeProtobuf(Message, rxByteBuffer);
|
|
205
|
+
} catch (cause) {
|
|
206
|
+
const legacyProtocolInfo = tryDecodeLegacyProtocolV2ProtocolInfo(messageName, pbPayload);
|
|
207
|
+
if (legacyProtocolInfo) {
|
|
208
|
+
return {
|
|
209
|
+
message: legacyProtocolInfo,
|
|
210
|
+
messageName,
|
|
211
|
+
messageTypeId,
|
|
212
|
+
pbPayload,
|
|
213
|
+
seq,
|
|
214
|
+
type: messageName,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const error = new Error(
|
|
218
|
+
`Protocol V2 protobuf decode failed for "${messageName}" ` +
|
|
219
|
+
`(${messageTypeId}, ${pbPayload.length}-byte payload); ` +
|
|
220
|
+
'the payload is malformed or incompatible with the active SDK schema.'
|
|
221
|
+
);
|
|
222
|
+
(error as Error & { cause?: unknown }).cause = cause;
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
114
225
|
|
|
115
226
|
return {
|
|
116
227
|
message,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export type ProtocolV2BleFrameWriterOptions = {
|
|
2
|
+
frame: Uint8Array;
|
|
3
|
+
packetCapacity: number;
|
|
4
|
+
writePacket: (packet: Uint8Array, packetIndex: number) => Promise<void>;
|
|
5
|
+
assertActive?: () => void;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
abortMessage?: string;
|
|
8
|
+
initialDelayMs?: number;
|
|
9
|
+
burstSize?: number;
|
|
10
|
+
burstPauseMs?: number;
|
|
11
|
+
flushDelayMs?: number;
|
|
12
|
+
wait?: (timeoutMs: number) => Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const defaultWait = (timeoutMs: number) =>
|
|
16
|
+
new Promise<void>(resolve => {
|
|
17
|
+
setTimeout(resolve, timeoutMs);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export async function writeProtocolV2BleFrame({
|
|
21
|
+
frame,
|
|
22
|
+
packetCapacity,
|
|
23
|
+
writePacket,
|
|
24
|
+
assertActive,
|
|
25
|
+
signal,
|
|
26
|
+
abortMessage = 'Protocol V2 BLE write aborted',
|
|
27
|
+
initialDelayMs = 0,
|
|
28
|
+
burstSize,
|
|
29
|
+
burstPauseMs = 0,
|
|
30
|
+
flushDelayMs = 0,
|
|
31
|
+
wait = defaultWait,
|
|
32
|
+
}: ProtocolV2BleFrameWriterOptions): Promise<void> {
|
|
33
|
+
if (!Number.isInteger(packetCapacity) || packetCapacity <= 0) {
|
|
34
|
+
throw new Error('Protocol V2 BLE packet capacity must be a positive integer');
|
|
35
|
+
}
|
|
36
|
+
if (burstSize !== undefined && (!Number.isInteger(burstSize) || burstSize <= 0)) {
|
|
37
|
+
throw new Error('Protocol V2 BLE burst size must be a positive integer');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const assertWritable = () => {
|
|
41
|
+
assertActive?.();
|
|
42
|
+
if (signal?.aborted) {
|
|
43
|
+
throw new Error(abortMessage);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
if (frame.length > 0 && initialDelayMs > 0) {
|
|
48
|
+
assertWritable();
|
|
49
|
+
await wait(initialDelayMs);
|
|
50
|
+
assertWritable();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let packetsWritten = 0;
|
|
54
|
+
for (let offset = 0; offset < frame.length; offset += packetCapacity) {
|
|
55
|
+
assertWritable();
|
|
56
|
+
const packet = frame.slice(offset, offset + packetCapacity);
|
|
57
|
+
await writePacket(packet, packetsWritten);
|
|
58
|
+
packetsWritten += 1;
|
|
59
|
+
assertWritable();
|
|
60
|
+
|
|
61
|
+
const hasMorePackets = offset + packetCapacity < frame.length;
|
|
62
|
+
if (
|
|
63
|
+
hasMorePackets &&
|
|
64
|
+
burstSize !== undefined &&
|
|
65
|
+
packetsWritten % burstSize === 0 &&
|
|
66
|
+
burstPauseMs > 0
|
|
67
|
+
) {
|
|
68
|
+
await wait(burstPauseMs);
|
|
69
|
+
assertWritable();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (burstSize !== undefined && packetsWritten > burstSize && flushDelayMs > 0) {
|
|
74
|
+
await wait(flushDelayMs);
|
|
75
|
+
assertWritable();
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/protocols/v2/crc8.ts
CHANGED
|
@@ -22,7 +22,7 @@ export const CRC8_TABLE = new Uint8Array([
|
|
|
22
22
|
]);
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* Compute CRC-8 over the first len bytes using the firmware-compatible initial value.
|
|
26
26
|
*/
|
|
27
27
|
export function crc8(data: Uint8Array, len: number): number {
|
|
28
28
|
let crc = CRC8_INIT;
|