@bota.dev/react-native-app-sdk 2.0.0-beta.0 → 2.0.0-beta.3
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/README.md +66 -2
- package/android/build.gradle +1 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDiagnostics.kt +53 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogs.kt +16 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploads.kt +144 -14
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordings.kt +150 -29
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKEncryptedUploadV2Materials.kt +27 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKModule.kt +68 -3
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogsTest.kt +33 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploadsTest.kt +135 -6
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingsTest.kt +236 -10
- package/generated/codegen-contract.json +495 -8
- package/ios/BotaDeviceSDK.mm +102 -0
- package/ios/BotaDeviceSDKAppleBridge.swift +126 -4
- package/ios/BotaDeviceSDKAppleDiagnostics.swift +40 -0
- package/ios/BotaDeviceSDKAppleLogs.swift +18 -0
- package/ios/BotaDeviceSDKAppleRecordingUploads.swift +138 -6
- package/ios/BotaDeviceSDKAppleRecordings.swift +113 -5
- package/ios/BotaDeviceSDKEncryptedUploadV2Materials.swift +40 -0
- package/lib/commonjs/BotaClient.js +13 -2
- package/lib/commonjs/BotaClient.js.map +1 -1
- package/lib/commonjs/ble/constants.js +5 -1
- package/lib/commonjs/ble/constants.js.map +1 -1
- package/lib/commonjs/ble/deviceDiagnostics.js +240 -0
- package/lib/commonjs/ble/deviceDiagnostics.js.map +1 -0
- package/lib/commonjs/client.js +163 -28
- package/lib/commonjs/client.js.map +1 -1
- package/lib/commonjs/index.js +15 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/managers/DeviceManager.js +8 -0
- package/lib/commonjs/managers/DeviceManager.js.map +1 -1
- package/lib/commonjs/managers/NativeUploadQueue.js +386 -0
- package/lib/commonjs/managers/NativeUploadQueue.js.map +1 -0
- package/lib/commonjs/managers/RecordingManager.js +83 -102
- package/lib/commonjs/managers/RecordingManager.js.map +1 -1
- package/lib/commonjs/managers/uploadRecoveryMetadata.js +83 -0
- package/lib/commonjs/managers/uploadRecoveryMetadata.js.map +1 -0
- package/lib/commonjs/specs/NativeBotaDeviceSDK.js.map +1 -1
- package/lib/module/BotaClient.js +13 -2
- package/lib/module/BotaClient.js.map +1 -1
- package/lib/module/ble/constants.js +4 -0
- package/lib/module/ble/constants.js.map +1 -1
- package/lib/module/ble/deviceDiagnostics.js +234 -0
- package/lib/module/ble/deviceDiagnostics.js.map +1 -0
- package/lib/module/client.js +163 -28
- package/lib/module/client.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/managers/DeviceManager.js +8 -0
- package/lib/module/managers/DeviceManager.js.map +1 -1
- package/lib/module/managers/NativeUploadQueue.js +380 -0
- package/lib/module/managers/NativeUploadQueue.js.map +1 -0
- package/lib/module/managers/RecordingManager.js +83 -102
- package/lib/module/managers/RecordingManager.js.map +1 -1
- package/lib/module/managers/uploadRecoveryMetadata.js +77 -0
- package/lib/module/managers/uploadRecoveryMetadata.js.map +1 -0
- package/lib/module/specs/NativeBotaDeviceSDK.js.map +1 -1
- package/lib/typescript/BotaClient.d.ts.map +1 -1
- package/lib/typescript/ble/constants.d.ts +4 -0
- package/lib/typescript/ble/constants.d.ts.map +1 -1
- package/lib/typescript/ble/deviceDiagnostics.d.ts +10 -0
- package/lib/typescript/ble/deviceDiagnostics.d.ts.map +1 -0
- package/lib/typescript/client.d.ts +18 -2
- package/lib/typescript/client.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +6 -4
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/managers/DeviceManager.d.ts +3 -1
- package/lib/typescript/managers/DeviceManager.d.ts.map +1 -1
- package/lib/typescript/managers/NativeUploadQueue.d.ts +41 -0
- package/lib/typescript/managers/NativeUploadQueue.d.ts.map +1 -0
- package/lib/typescript/managers/RecordingManager.d.ts +3 -8
- package/lib/typescript/managers/RecordingManager.d.ts.map +1 -1
- package/lib/typescript/managers/types.d.ts +6 -1
- package/lib/typescript/managers/types.d.ts.map +1 -1
- package/lib/typescript/managers/uploadRecoveryMetadata.d.ts +6 -0
- package/lib/typescript/managers/uploadRecoveryMetadata.d.ts.map +1 -0
- package/lib/typescript/models/Device.d.ts +42 -0
- package/lib/typescript/models/Device.d.ts.map +1 -1
- package/lib/typescript/models/Recording.d.ts +47 -0
- package/lib/typescript/models/Recording.d.ts.map +1 -1
- package/lib/typescript/models/Status.d.ts +5 -1
- package/lib/typescript/models/Status.d.ts.map +1 -1
- package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts +61 -0
- package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts.map +1 -1
- package/package.json +1 -1
- package/scripts/bota_device_sdk_spm_workaround.rb +26 -15
- package/src/BotaClient.ts +13 -3
- package/src/ble/constants.ts +5 -0
- package/src/ble/deviceDiagnostics.ts +238 -0
- package/src/client.ts +166 -18
- package/src/index.ts +12 -0
- package/src/managers/DeviceManager.ts +11 -0
- package/src/managers/NativeUploadQueue.ts +330 -0
- package/src/managers/RecordingManager.ts +83 -116
- package/src/managers/types.ts +7 -1
- package/src/managers/uploadRecoveryMetadata.ts +59 -0
- package/src/models/Device.ts +43 -0
- package/src/models/Recording.ts +52 -0
- package/src/models/Status.ts +5 -1
- package/src/specs/NativeBotaDeviceSDK.ts +56 -0
package/package.json
CHANGED
|
@@ -25,29 +25,40 @@ module BotaDeviceSDKSPMWorkaround
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
rewrite_aggregate_modulemap_references(installer)
|
|
28
|
+
installer.pod_targets.each do |pod_target|
|
|
29
|
+
pod_target.build_settings.each do |configuration_name, build_settings|
|
|
30
|
+
path = pod_target.xcconfig_path(configuration_name)
|
|
31
|
+
# Expo can re-save this cached config after the SPM post-install hook.
|
|
32
|
+
rewrite_modulemap_config(build_settings.xcconfig, path)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
28
35
|
end
|
|
29
36
|
|
|
30
37
|
def self.rewrite_aggregate_modulemap_references(installer)
|
|
31
|
-
nested = "${PODS_CONFIGURATION_BUILD_DIR}/#{POD_NAME}/#{POD_NAME}.modulemap"
|
|
32
|
-
flattened = "${PODS_CONFIGURATION_BUILD_DIR}/#{POD_NAME}.modulemap"
|
|
33
|
-
|
|
34
38
|
installer.aggregate_targets.each do |aggregate_target|
|
|
35
39
|
aggregate_target.xcconfigs.each do |configuration_name, config_file|
|
|
36
|
-
|
|
37
|
-
%w[OTHER_CFLAGS OTHER_SWIFT_FLAGS].each do |key|
|
|
38
|
-
value = config_file.attributes[key]
|
|
39
|
-
next unless value
|
|
40
|
-
|
|
41
|
-
updated = value.gsub(nested, flattened)
|
|
42
|
-
next if updated == value
|
|
43
|
-
|
|
44
|
-
config_file.attributes[key] = updated
|
|
45
|
-
changed = true
|
|
46
|
-
end
|
|
47
|
-
config_file.save_as(aggregate_target.xcconfig_path(configuration_name)) if changed
|
|
40
|
+
rewrite_modulemap_config(config_file, aggregate_target.xcconfig_path(configuration_name))
|
|
48
41
|
end
|
|
49
42
|
end
|
|
50
43
|
end
|
|
51
44
|
|
|
45
|
+
def self.rewrite_modulemap_config(config_file, path)
|
|
46
|
+
nested = "${PODS_CONFIGURATION_BUILD_DIR}/#{POD_NAME}/#{POD_NAME}.modulemap"
|
|
47
|
+
flattened = "${PODS_CONFIGURATION_BUILD_DIR}/#{POD_NAME}.modulemap"
|
|
48
|
+
changed = false
|
|
49
|
+
%w[OTHER_CFLAGS OTHER_SWIFT_FLAGS].each do |key|
|
|
50
|
+
value = config_file.attributes[key]
|
|
51
|
+
next unless value
|
|
52
|
+
|
|
53
|
+
updated = value.gsub(nested, flattened)
|
|
54
|
+
next if updated == value
|
|
55
|
+
|
|
56
|
+
config_file.attributes[key] = updated
|
|
57
|
+
changed = true
|
|
58
|
+
end
|
|
59
|
+
config_file.save_as(path) if changed
|
|
60
|
+
end
|
|
61
|
+
|
|
52
62
|
private_class_method :rewrite_aggregate_modulemap_references
|
|
63
|
+
private_class_method :rewrite_modulemap_config
|
|
53
64
|
end
|
package/src/BotaClient.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { getCompatibilityClient } from './compatibility/runtime';
|
|
|
5
5
|
import { DeviceManager } from './managers/DeviceManager';
|
|
6
6
|
import { OTAManager } from './managers/OTAManager';
|
|
7
7
|
import { RecordingManager } from './managers/RecordingManager';
|
|
8
|
+
import { rejectRecordingDataStore } from './managers/uploadRecoveryMetadata';
|
|
8
9
|
import type {
|
|
9
10
|
BluetoothState,
|
|
10
11
|
BotaClientEvents,
|
|
@@ -17,12 +18,16 @@ import { logger, type LogHandler } from './utils/logger';
|
|
|
17
18
|
|
|
18
19
|
const log = logger.tag('BotaClient');
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
type NormalizedConfig = Required<Omit<BotaConfig, 'uploadRecoveryProvider' | 'recordingDataStore'>> &
|
|
22
|
+
Pick<BotaConfig, 'uploadRecoveryProvider'>;
|
|
23
|
+
|
|
24
|
+
const normalizeConfig = (config: BotaConfig): NormalizedConfig => ({
|
|
21
25
|
environment: config.environment ?? 'production',
|
|
22
26
|
backgroundSyncEnabled: config.backgroundSyncEnabled ?? true,
|
|
23
27
|
wifiOnlyUpload: config.wifiOnlyUpload ?? false,
|
|
24
28
|
logLevel: config.logLevel ?? 'warn',
|
|
25
29
|
debug: config.debug ?? false,
|
|
30
|
+
...(config.uploadRecoveryProvider ? { uploadRecoveryProvider: config.uploadRecoveryProvider } : {}),
|
|
26
31
|
});
|
|
27
32
|
|
|
28
33
|
class BotaClientImpl extends EventEmitter<BotaClientEvents> {
|
|
@@ -74,6 +79,11 @@ class BotaClientImpl extends EventEmitter<BotaClientEvents> {
|
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
configure(config: BotaConfig = {}): Promise<void> {
|
|
82
|
+
try {
|
|
83
|
+
rejectRecordingDataStore(config.recordingDataStore);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return Promise.reject(error);
|
|
86
|
+
}
|
|
77
87
|
if (this._configurePromise && !this._destroyPromise) {
|
|
78
88
|
return this._configurePromise;
|
|
79
89
|
}
|
|
@@ -141,7 +151,7 @@ class BotaClientImpl extends EventEmitter<BotaClientEvents> {
|
|
|
141
151
|
return queued;
|
|
142
152
|
}
|
|
143
153
|
|
|
144
|
-
private async performConfigure(config:
|
|
154
|
+
private async performConfigure(config: NormalizedConfig): Promise<void> {
|
|
145
155
|
if (this._state !== 'uninitialized' || this._nativeOwned) {
|
|
146
156
|
log.warn('SDK already configured, reconfiguring');
|
|
147
157
|
await this.releaseOwnerGraph();
|
|
@@ -170,7 +180,7 @@ class BotaClientImpl extends EventEmitter<BotaClientEvents> {
|
|
|
170
180
|
|
|
171
181
|
deviceManager = new DeviceManager();
|
|
172
182
|
await deviceManager.initialize();
|
|
173
|
-
recordingManager = new RecordingManager();
|
|
183
|
+
recordingManager = new RecordingManager({ uploadRecoveryProvider: config.uploadRecoveryProvider });
|
|
174
184
|
await recordingManager.initialize();
|
|
175
185
|
otaManager = new OTAManager(deviceManager);
|
|
176
186
|
|
package/src/ble/constants.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
export const DEVICE_LOG_FLAG_BACKLOG = 0x01;
|
|
2
2
|
export const DEVICE_LOG_FLAG_DROPPED = 0x02;
|
|
3
3
|
export const BLE_ERROR_STORAGE_KEY_UNAVAILABLE = 0x14;
|
|
4
|
+
|
|
5
|
+
export const DEVICE_DIAGNOSTICS_EVT_META = 0x90;
|
|
6
|
+
export const DEVICE_DIAGNOSTICS_EVT_SIGNATURE = 0x91;
|
|
7
|
+
export const DEVICE_DIAGNOSTICS_EVT_END = 0x92;
|
|
8
|
+
export const DEVICE_DIAGNOSTICS_EVT_DETAIL = 0x95;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DEVICE_DIAGNOSTICS_EVT_END,
|
|
5
|
+
DEVICE_DIAGNOSTICS_EVT_DETAIL,
|
|
6
|
+
DEVICE_DIAGNOSTICS_EVT_META,
|
|
7
|
+
DEVICE_DIAGNOSTICS_EVT_SIGNATURE,
|
|
8
|
+
} from './constants';
|
|
9
|
+
import type {
|
|
10
|
+
DeviceDiagnosticEvent,
|
|
11
|
+
DeviceDiagnosticEventType,
|
|
12
|
+
DeviceDiagnosticReasonCode,
|
|
13
|
+
DeviceDiagnosticsBatch,
|
|
14
|
+
} from '../models/Device';
|
|
15
|
+
|
|
16
|
+
const EVENT_TYPES: Record<number, DeviceDiagnosticEventType> = {
|
|
17
|
+
1: 'reset', 2: 'watchdog', 3: 'hard_fault', 4: 'storage',
|
|
18
|
+
5: 'power', 6: 'radio', 7: 'security',
|
|
19
|
+
};
|
|
20
|
+
const REASONS: Record<number, DeviceDiagnosticReasonCode> = {
|
|
21
|
+
1: 'watchdog_timeout', 2: 'cpu_illegal_instruction',
|
|
22
|
+
3: 'cpu_misaligned_access', 4: 'cpu_stack_overflow',
|
|
23
|
+
5: 'cpu_usage_fault', 6: 'memory_protection_fault',
|
|
24
|
+
7: 'invalid_register_read', 8: 'invalid_register_write',
|
|
25
|
+
9: 'audio_subsystem_fault', 10: 'wireless_subsystem_fault',
|
|
26
|
+
11: 'flash_mmu_fault', 12: 'low_voltage_reset',
|
|
27
|
+
255: 'unknown_cpu_exception',
|
|
28
|
+
};
|
|
29
|
+
const SUBSYSTEMS: Record<number, string> = {
|
|
30
|
+
0: 'system', 1: 'audio', 2: 'wireless', 3: 'storage',
|
|
31
|
+
4: 'power', 5: 'security',
|
|
32
|
+
};
|
|
33
|
+
const STATES: Record<number, string> = {
|
|
34
|
+
0: 'boot', 1: 'deep_standby', 2: 'recording', 3: 'upload_preparing',
|
|
35
|
+
4: 'uploading', 5: 'error_recovery', 6: 'full_sleep',
|
|
36
|
+
};
|
|
37
|
+
const BREADCRUMBS: Record<number, string> = {
|
|
38
|
+
1: 'boot', 2: 'standby_entered', 3: 'recording_started',
|
|
39
|
+
4: 'upload_preparing', 5: 'upload_started', 6: 'error_recovery',
|
|
40
|
+
7: 'full_sleep',
|
|
41
|
+
};
|
|
42
|
+
const DETAIL_WIRE_SIZE = 176;
|
|
43
|
+
|
|
44
|
+
function u64LeHex(bytes: Uint8Array): string {
|
|
45
|
+
return Buffer.from(bytes).reverse().toString('hex').padStart(16, '0');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function diagnosticEventIdCommand(eventId: string): Buffer {
|
|
49
|
+
if (!/^[0-9a-f]{16}$/.test(eventId)) {
|
|
50
|
+
throw new Error('Diagnostic event_id must be 16 lowercase hex characters');
|
|
51
|
+
}
|
|
52
|
+
return Buffer.concat([
|
|
53
|
+
Buffer.from([0x11]),
|
|
54
|
+
Buffer.from(eventId, 'hex').reverse(),
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type PartialEvent = Omit<DeviceDiagnosticEvent, 'signature' | 'firmware_build_id' |
|
|
59
|
+
'subsystem' | 'state_before_event'> & {
|
|
60
|
+
signature?: string;
|
|
61
|
+
firmware_build_id?: string;
|
|
62
|
+
subsystem?: string;
|
|
63
|
+
state_before_event?: string;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
interface DetailAssembly {
|
|
67
|
+
buffer: Buffer;
|
|
68
|
+
received: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizedAddress(value: number): string | undefined {
|
|
72
|
+
if (value === 0) return undefined;
|
|
73
|
+
const region = (value & 0x80000000) !== 0
|
|
74
|
+
? 'sdram'
|
|
75
|
+
: (value & 0x40000000) !== 0 ? 'ram' : 'rom';
|
|
76
|
+
const offset = (value & 0x3fffffff).toString(16).padStart(8, '0');
|
|
77
|
+
return `${region}:${offset}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function fixedHex32(value: number): string {
|
|
81
|
+
return value.toString(16).padStart(8, '0');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function decodeDetail(bytes: Buffer): Pick<DeviceDiagnosticEvent,
|
|
85
|
+
'firmware_build_id' | 'subsystem' | 'state_before_event' | 'report'> {
|
|
86
|
+
if (bytes.length !== DETAIL_WIRE_SIZE || bytes[0] !== 1) {
|
|
87
|
+
throw new Error('Unsupported diagnostic detail payload');
|
|
88
|
+
}
|
|
89
|
+
const hasReport = (bytes[1] & 1) !== 0;
|
|
90
|
+
const firmwareBuildId = Buffer.from(bytes.subarray(2, 14)).toString('ascii');
|
|
91
|
+
if (!/^[0-9a-f]{12}$/.test(firmwareBuildId)) {
|
|
92
|
+
throw new Error('Invalid diagnostic firmware build id');
|
|
93
|
+
}
|
|
94
|
+
const common = {
|
|
95
|
+
firmware_build_id: firmwareBuildId,
|
|
96
|
+
subsystem: SUBSYSTEMS[bytes[14]] ?? 'system',
|
|
97
|
+
state_before_event: STATES[bytes[15]] ?? 'unknown',
|
|
98
|
+
};
|
|
99
|
+
if (!hasReport) return common;
|
|
100
|
+
|
|
101
|
+
const pcCount = Math.min(bytes[17], 6);
|
|
102
|
+
const breadcrumbCount = Math.min(bytes[18], 8);
|
|
103
|
+
const runtimeMask = bytes[19];
|
|
104
|
+
const pcTrace: string[] = [];
|
|
105
|
+
for (let index = 0; index < pcCount; index++) {
|
|
106
|
+
const address = normalizedAddress(bytes.readUInt32LE(48 + index * 4));
|
|
107
|
+
if (address) pcTrace.push(address);
|
|
108
|
+
}
|
|
109
|
+
const task = Buffer.from(bytes.subarray(80, 96)).toString('ascii').replace(/\0.*$/, '');
|
|
110
|
+
const breadcrumbs = [];
|
|
111
|
+
for (let index = 0; index < breadcrumbCount; index++) {
|
|
112
|
+
const offset = 96 + index * 10;
|
|
113
|
+
breadcrumbs.push({
|
|
114
|
+
delta_ms: bytes.readInt32LE(offset),
|
|
115
|
+
code: BREADCRUMBS[bytes.readUInt16LE(offset + 4)] ?? 'state_changed',
|
|
116
|
+
arg0: bytes.readInt32LE(offset + 6),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const reti = normalizedAddress(bytes.readUInt32LE(40));
|
|
120
|
+
const rets = normalizedAddress(bytes.readUInt32LE(44));
|
|
121
|
+
return {
|
|
122
|
+
...common,
|
|
123
|
+
report: {
|
|
124
|
+
fault: {
|
|
125
|
+
cpu_id: bytes[16],
|
|
126
|
+
cpu_emu: fixedHex32(bytes.readUInt32LE(20)),
|
|
127
|
+
core_emu: fixedHex32(bytes.readUInt32LE(24)),
|
|
128
|
+
hsb_emu: fixedHex32(bytes.readUInt32LE(28)),
|
|
129
|
+
audio_emu: fixedHex32(bytes.readUInt32LE(32)),
|
|
130
|
+
wireless_emu: fixedHex32(bytes.readUInt32LE(36)),
|
|
131
|
+
},
|
|
132
|
+
execution: {
|
|
133
|
+
...(task ? { task } : {}),
|
|
134
|
+
...(reti ? { reti } : {}),
|
|
135
|
+
...(rets ? { rets } : {}),
|
|
136
|
+
pc_trace: pcTrace,
|
|
137
|
+
},
|
|
138
|
+
...((runtimeMask & 3) !== 0 ? {
|
|
139
|
+
runtime: {
|
|
140
|
+
...((runtimeMask & 1) !== 0
|
|
141
|
+
? { heap_free_bytes: bytes.readUInt32LE(72) } : {}),
|
|
142
|
+
...((runtimeMask & 2) !== 0
|
|
143
|
+
? { task_stack_remaining_bytes: bytes.readUInt32LE(76) } : {}),
|
|
144
|
+
},
|
|
145
|
+
} : {}),
|
|
146
|
+
...(breadcrumbs.length ? { breadcrumbs } : {}),
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export class DeviceDiagnosticsDecoder {
|
|
152
|
+
private events = new Map<number, PartialEvent>();
|
|
153
|
+
private details = new Map<number, DetailAssembly>();
|
|
154
|
+
|
|
155
|
+
push(packet: Buffer): DeviceDiagnosticsBatch | null {
|
|
156
|
+
if (packet.length === 17 && packet[0] === DEVICE_DIAGNOSTICS_EVT_META) {
|
|
157
|
+
const type = EVENT_TYPES[packet[2]];
|
|
158
|
+
if (!type) return null;
|
|
159
|
+
this.events.set(packet[1], {
|
|
160
|
+
event_id: u64LeHex(packet.subarray(9, 17)),
|
|
161
|
+
event_type: type,
|
|
162
|
+
reason_code: REASONS[packet.readUInt16LE(3)] ?? 'unknown_cpu_exception',
|
|
163
|
+
uptime_ms: packet.readUInt32LE(5),
|
|
164
|
+
});
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
if (packet.length === 10 && packet[0] === DEVICE_DIAGNOSTICS_EVT_SIGNATURE) {
|
|
168
|
+
const event = this.events.get(packet[1]);
|
|
169
|
+
if (event) event.signature = u64LeHex(packet.subarray(2, 10));
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
if (packet.length >= 6 && packet.length <= 20 &&
|
|
173
|
+
packet[0] === DEVICE_DIAGNOSTICS_EVT_DETAIL) {
|
|
174
|
+
const index = packet[1];
|
|
175
|
+
const offset = packet.readUInt16LE(2);
|
|
176
|
+
const total = packet.readUInt16LE(4);
|
|
177
|
+
if (total !== DETAIL_WIRE_SIZE || offset + packet.length - 6 > total) {
|
|
178
|
+
this.reset();
|
|
179
|
+
throw new Error('Invalid diagnostic detail chunk');
|
|
180
|
+
}
|
|
181
|
+
let detail = this.details.get(index);
|
|
182
|
+
if (offset === 0) {
|
|
183
|
+
if (detail) {
|
|
184
|
+
this.reset();
|
|
185
|
+
throw new Error(`Duplicate diagnostic detail at index ${index}`);
|
|
186
|
+
}
|
|
187
|
+
detail = { buffer: Buffer.alloc(total), received: 0 };
|
|
188
|
+
this.details.set(index, detail);
|
|
189
|
+
}
|
|
190
|
+
if (!detail || detail.received !== offset) {
|
|
191
|
+
this.reset();
|
|
192
|
+
throw new Error(`Out-of-order diagnostic detail at index ${index}`);
|
|
193
|
+
}
|
|
194
|
+
packet.copy(detail.buffer, offset, 6);
|
|
195
|
+
detail.received += packet.length - 6;
|
|
196
|
+
if (detail.received === total) {
|
|
197
|
+
const event = this.events.get(index);
|
|
198
|
+
if (!event) {
|
|
199
|
+
this.reset();
|
|
200
|
+
throw new Error(`Diagnostic detail without metadata at index ${index}`);
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
Object.assign(event, decodeDetail(detail.buffer));
|
|
204
|
+
} catch (error) {
|
|
205
|
+
this.reset();
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
if (packet.length === 2 && packet[0] === DEVICE_DIAGNOSTICS_EVT_END) {
|
|
212
|
+
const expected = packet[1];
|
|
213
|
+
const events: DeviceDiagnosticEvent[] = [];
|
|
214
|
+
for (let index = 0; index < expected; index++) {
|
|
215
|
+
const event = this.events.get(index);
|
|
216
|
+
if (!event?.signature || !event.firmware_build_id ||
|
|
217
|
+
!event.subsystem || !event.state_before_event ||
|
|
218
|
+
this.details.get(index)?.received !== DETAIL_WIRE_SIZE) {
|
|
219
|
+
this.reset();
|
|
220
|
+
throw new Error(`Incomplete diagnostic event at index ${index}`);
|
|
221
|
+
}
|
|
222
|
+
events.push(event as DeviceDiagnosticEvent);
|
|
223
|
+
}
|
|
224
|
+
const batch: DeviceDiagnosticsBatch = {
|
|
225
|
+
schema_version: 1,
|
|
226
|
+
events,
|
|
227
|
+
};
|
|
228
|
+
this.reset();
|
|
229
|
+
return batch;
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
reset(): void {
|
|
235
|
+
this.events.clear();
|
|
236
|
+
this.details.clear();
|
|
237
|
+
}
|
|
238
|
+
}
|