Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
262 changes: 187 additions & 75 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,119 +2,231 @@

## 📁 核心架构

OneKey Hardware SDK 采用三层架构设计
这个仓库目前同时维护两条硬件接入路径

```
应用层 (DApps)
```text
OneKey 原生设备
应用层
@onekeyfe/hd-core
@onekeyfe/hd-transport
WebUSB / Node USB / HTTP / Lowlevel(BLE)
OneKey 设备

第三方硬件(当前重点是 Ledger)
应用层
@onekeyfe/hwk-adapter-core
SDK接口层 (@onekeyfe/core)
@onekeyfe/hwk-ledger-adapter
传输抽象层 (@onekeyfe/hd-transport)
@onekeyfe/hwk-ledger-connector-webhid / @onekeyfe/hwk-ledger-connector-ble
平台适配层 (WebUSB/BLE/HTTP)
Ledger Device Management Kit / Transport Kit
硬件设备层 (OneKey设备)
Ledger 设备
```

前者是 OneKey 自有协议栈;后者是多厂商硬件适配层,重点解决连接、会话、UI 事件和链方法的一致接口问题。

## 🏗️ 核心包结构

### API层
- **`@onekeyfe/hd-core`** - 核心API和业务逻辑
- **`@onekeyfe/hd-transport`** - 传输层抽象
### OneKey 原生协议栈

- **`@onekeyfe/hd-core`** - 核心 API、`BaseMethod` 生命周期、设备状态管理
- **`@onekeyfe/hd-transport`** - 统一消息分包、序列化、传输抽象
- **`@onekeyfe/hd-transport-web-device`** - 浏览器 WebUSB 传输
- **`@onekeyfe/hd-transport-usb`** - Node.js USB 传输(libusb)
- **`@onekeyfe/hd-transport-http`** - HTTP Bridge
- **`@onekeyfe/hd-transport-lowlevel`** - 低层插件模式传输
- **`@onekeyfe/hd-transport-react-native`** - React Native BLE 传输实现

### 传输层
- **`@onekeyfe/hd-transport-webusb`** - WebUSB传输(浏览器)
- **`@onekeyfe/hd-transport-usb`** - Node.js USB传输(CLI/服务端,基于 libusb)
- **`@onekeyfe/hd-transport-http`** - HTTP Bridge传输
- **`@onekeyfe/hd-transport-lowlevel`** - 低层传输(BLE 插件模式)
### 第三方硬件适配层

### 平台SDK
- **`@onekeyfe/hd-web-sdk`** - Web平台SDK
- **`@onekeyfe/hd-ble-sdk`** - 移动端BLE SDK
- **`@onekeyfe/hwk-adapter-core`** - `IConnector`、`IHardwareWallet`、UI 事件和通用错误约定
- **`@onekeyfe/hwk-ledger-adapter`** - Ledger 链方法、设备指纹校验、作业队列、自动装 App 工作流
- **`@onekeyfe/hwk-ledger-connector-webhid`** - WebHID connector
- **`@onekeyfe/hwk-ledger-connector-ble`** - React Native BLE connector

### 示例应用
- **`@onekeyfe/connect-examples`** - 集成示例
- `expo-example` - Web集成示例
- `expo-playground` - 开发测试平台

## 🔄 API调用流程
- **`@onekeyfe/connect-examples`**
- `expo-example` - 常规集成示例和参数面板
- `expo-playground` - 开发 / 调试 playground

## 🔄 API 调用流程

### OneKey 原生调用链

```typescript
// 典型调用链
HardwareSDK.btcGetAddress()
HardwareSDK.evmSignTransaction(connectId, deviceId, params)
BaseMethod.init()
BaseMethod.run()
Core.callAPI()
Device.call()
Device.run()
Transport.send()
Device.initialize() // 满足条件时可被 preInitialize 命中并跳过
硬件设备响应
Transport.call()
设备响应
```

## 🎯 设计原则
### Ledger 调用链

```typescript
ledgerAdapter.evmSignTransaction(connectId, deviceId, params, commonParams)
LedgerAdapter.callChain()
LedgerAdapter.connectorCall()
IConnector.call(sessionId, method, params)
LedgerConnectorBase
Ledger DMK / transport kit
设备响应或 connector ui-event / ui-request
```

## 🔥 BLE 预热流程:`preInitialize()` / `usePreInitialize`

`preInitialize()` 是一个 **BLE 签名前预热信号**,目标是把代价较高的 `Initialize` 提前做掉,而不是给后续请求强行关闭初始化。

### 行为约束

### 分层解耦
- 业务逻辑与传输协议分离
- 核心API与平台实现解耦
- 支持独立测试和开发
| 条件 | 源码行为 | 影响 |
| --- | --- | --- |
| `HardwareSDK.preInitialize(connectId, params)` | 走 `PreInitialize` 方法,标记为 `isPreWarmSignal` | 进入专用的预热分支 |
| 同一个预热 key 并发触发 | Core 侧会合并 in-flight 调用 | 避免重复抢占 BLE 链路 |
| 60 秒内重复预热 | 命中 TTL,直接返回 `true` | 不会重复做 `Initialize` |
| 后续业务调用设置 `usePreInitialize: true` | 只有方法自身 `allowUsePreInitialize = true` 时才允许跳过初始化 | 不是所有方法都支持 |
| `passphraseState` 不匹配、`connectId` 缺失、设备还没有 `features`、TTL 过期 | 记为 `[PRE-INIT][MISS]`,仍然执行正常 `Initialize` | 失败闭合,不会错误复用旧状态 |
| Cardano 签名 | `cardanoSignTransaction` / `cardanoSignMessage` 显式 `allowUsePreInitialize = false` | 近期修复后不再复用预热跳过初始化 |

### 平台无关
- 一套API支持Web/Mobile/Desktop
- 平台差异在适配层处理
- 核心逻辑完全复用
### `deriveCardano` 的边界

### 协议可扩展
- 支持多种传输协议
- 向后兼容旧版本
- 便于添加新协议
`Initialize` 是否带上 `deriveCardano` 不是只看显式参数。当前会在以下情况自动打开:

## 🧩 关键设计模式
- 方法名以 `cardano` 开头
- 调用参数里显式设置了 `deriveCardano: true`
- `allNetworkGetAddress()` 的 bundle 中包含 `network: 'ada'`

这意味着:

- `deriveCardano` 决定初始化阶段是否派生 Cardano 相关状态
- `usePreInitialize` 决定后续业务调用能否跳过这次初始化
- 两者相关,但不是同一个开关

### 推荐调用方式

### 模板方法 (BaseMethod)
```typescript
abstract class BaseMethod<Request, Response> {
async execute(): Promise<Response> {
this.validateParams();
await this.checkDevice();
return await this.run();
}

abstract run(): Promise<Response>;
}
await HardwareSDK.preInitialize(connectId, {
passphraseState,
});

const result = await HardwareSDK.evmSignTransaction(connectId, deviceId, {
path: "m/44'/60'/0'/0/0",
transaction,
passphraseState,
usePreInitialize: true,
});
```

### 策略模式 (Transport)
### 常见误区

- `preInitialize()` 成功返回 `true`,**不代表** 下一次业务调用一定会跳过 `Initialize`
- `usePreInitialize` 只对显式 opt-in 的签名类方法生效;地址、公钥类方法不会因为预热而跳过初始化
- `passphraseState` 发生变化时必须重新预热,否则会被安全地降级回正常初始化流程

## 🔌 Ledger 适配层:公共接口、工作流与坑位

### 1) 入口对象和 transport 绑定

`LedgerAdapter` 在构造时绑定一个 connector;运行中不会动态切换 connector。Web 场景通常用 WebHID,React Native 场景通常用 BLE:

```typescript
// 根据环境选择传输方式
switch(env) {
case 'webusb': return new WebUsbTransport(); // 浏览器 WebUSB
case 'node-usb': return new NodeUsbTransport(); // Node.js libusb (CLI)
case 'lowlevel': return new LowlevelTransport(); // BLE 插件模式
case 'http': return new HttpTransport(); // HTTP Bridge
}
import { LedgerAdapter } from '@onekeyfe/hwk-ledger-adapter';
import { createLedgerWebHidConnector } from '@onekeyfe/hwk-ledger-connector-webhid';

const hw = new LedgerAdapter(createLedgerWebHidConnector());
```

## 📦 依赖关系
### 2) 最小接入流程

```typescript
const devices = await hw.searchDevices({ resetSession: true });
const connectId = devices[0]?.connectId;
if (!connectId) throw new Error('No Ledger found');

await hw.connectDevice(connectId);

const response = await hw.allNetworkGetAddress(connectId, '', {
autoInstallApp: true,
bundle: [
{
network: 'evm',
methodName: 'evmGetAddress',
path: "m/44'/60'/0'/0/0",
},
{
network: 'doge',
methodName: 'btcGetPublicKey',
path: "m/44'/3'/0'",
},
],
});
```
应用层
├── @onekeyfe/hd-web-sdk
├── @onekeyfe/hd-ble-sdk
├── @onekeyfe/hd-core ←── 核心层
│ └── @onekeyfe/hd-transport
└── 传输层实现
├── @onekeyfe/hd-transport-webusb (浏览器)
├── @onekeyfe/hd-transport-usb (Node.js CLI)
├── @onekeyfe/hd-transport-lowlevel (BLE 插件)
└── @onekeyfe/hd-transport-http (Bridge)
```

### 3) `allNetworkGetAddress()` 的真实语义

- 只接受以下方法名:`evmGetAddress`、`btcGetAddress`、`btcGetPublicKey`、`solGetAddress`、`tronGetAddress`
- 每个条目会按链维度缓存 `chainFingerprint`
- 调用方没有传 `deviceId` 时,adapter 会为该链补做一次指纹引导;后续同链条目复用
- BTC fork 网络会在 adapter 内部补齐 Ledger 需要的 `coin` 参数,目前包括 `tbtc`、`bch`、`doge`、`ltc`、`neurai`
- 顶层失败是 fail-fast 的:`DeviceMismatch`、`UserAborted`、`UserRejected` 会直接终止整个 bundle

如果你的上层已经拿到并验证过同链指纹,应优先把它作为公共方法里的 `deviceId` 参数传入,避免把“首次建立信任”和“批量取地址”混在同一次调用里。

### 4) `autoInstallApp` 默认关闭,而且只重试一次

`ICommonCallParams.autoInstallApp` 默认是 `false`。打开后,缺少目标 App 时的流程是:

1. 触发 `UI_REQUEST.REQUEST_INSTALL_APP`
2. 用户确认后调用 `installApp`
3. 安装期间持续发出 `ui-event: app-install-progress`
4. 原始链方法只会重试一次

这样设计有两个边界:

- **未开启 `autoInstallApp`**:保持原始 “App not installed” 失败
- **安装后 App 仍然缺失**:adapter 会触发 loop guard,直接报错,不会二次弹窗进入死循环

### 5) `AppInstallProgress` 事件如何转发

底层 connector 发出的 payload 键是 `sessionId`;`LedgerAdapter` 对外会把它重写成 `connectId`,并按 `connectId + appName` 维度节流:

- 进度差值小于 `5%` 的中间帧会被丢弃
- `progress >= 1` 的最终帧总会保留
- 如果找不到活跃 session 映射,事件会被直接丢弃,而不是把过期 session 暴露给上层

适合把它直接当作 UI 展示事件使用,不适合当作强一致的审计日志。

### 6) 运行时排障建议

- **WebHID 设备刷新后 connectId 变了**:先用 `searchDevices({ resetSession: true })` 清理旧 session,再重新选设备
- **用户拒绝装 App 或设备上拒绝确认**:`allNetworkGetAddress()` 会终止整个 bundle,这是刻意的 fail-fast 行为
- **需要中断当前请求**:调用 `cancel()` 只会中断当前作业队列;如果设备上还有待确认提示,仍然要由用户在设备侧拒绝或等待超时

## 🔧 开发工具

- **Lerna** - Monorepo管理
- **Lerna** - Monorepo 管理
- **TypeScript** - 类型安全
- **Jest** - 单元测试

Expand Down
65 changes: 54 additions & 11 deletions docs/transport.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,55 @@ export default class WebUsbTransport {
}
```

### React Native BLE Firmware / OTA Writes

`packages/hd-transport-react-native` 对普通消息和大 payload 的写入路径做了区分。最近这部分的改动主要集中在 `FirmwareUpload` 的 BLE 背压恢复。

#### 哪些消息会走特殊写入路径

| 消息名 | 写入方式 | 目的 |
| --- | --- | --- |
| `EmmcFileWrite` | `transport.writeWithRetry()` | 沿用现有分块重试逻辑 |
| `FirmwareUpload` | `writeWithoutResponse()` + 节流 + 有条件重连 | 减少 OTA 期间的 BLE 队列拥塞 |
| 其他消息 | 普通 63-byte HID 分包写入 | 保持统一协议路径 |

#### `FirmwareUpload` 的节流参数

| 参数 | iOS | Android | 说明 |
| --- | --- | --- | --- |
| `requestMTU` | 256 | 256 | 连接时的首选 MTU;若 MTU 变更失败会回退 |
| 写入聚合容量 | `IOS_PACKET_LENGTH` | `192` bytes | Android 固件上传会使用更大的 packet capacity |
| `FIRMWARE_UPLOAD_WRITE_BURST_SIZE` | 4 | 5 | 每写完一组 burst 主动暂停 |
| `FIRMWARE_UPLOAD_WRITE_PAUSE_MS` | 8ms | 10ms | burst 间隔,给 BLE 队列排空时间 |
| `FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS` | 24ms | 30ms | 本轮分块写完后的 flush 延迟 |
| `FIRMWARE_UPLOAD_WRITE_MAX_RETRIES` | 8 | 8 | 单个 chunk 的最大恢复次数 |

#### 重试与重连策略

- 只有以下错误会进入恢复逻辑:
- `GATT_CONGESTED` / `status 143`
- `DeviceDisconnected`
- `CharacteristicNotFound`
- **拥塞类错误**:按指数退避等待,延迟从 `200ms` 开始,上限 `1200ms`
- **可重连错误**:固定等待 `2000ms`,然后重建 transport、重新发现 characteristic,并重新挂上 notify / disconnect 订阅
- 超过 `8` 次恢复后,错误会直接向上抛出,不再静默重试

#### 为什么只对 `FirmwareUpload` 特判

`FirmwareUpload` 的流量特征和普通 APDU 完全不同:

- chunk 连续、持续时间长
- Android 上更容易触发 BLE 写队列背压
- 断连后不能只依赖普通的 `writeWithoutResponse()` 失败重试,需要把 characteristic 和订阅一起恢复

因此这里没有把策略做成全局默认值,而是只挂在 `name === 'FirmwareUpload'` 的路径上。

#### 排障建议

- **上传中反复出现 `GATT_CONGESTED`**:先观察是否能在 8 次内恢复;这类错误按设计会自动退避,不需要在上层再包一层立即重试
- **上传中途断连**:重点检查 reconnect 之后是否重新拿到了 write / notify characteristic,以及 notify 订阅有没有恢复
- **Android 连接后立刻报 MTU 变更失败**:transport 会把 `connectOptions` 回退为空对象再重连;如果你在上层保存了 transport 状态,不要假设每次连接都成功协商到 MTU 256

## Session Management

### Session Lifecycle
Expand Down Expand Up @@ -259,16 +308,10 @@ export function receiveOne(messages: Root, data: string) {

## Summary

OneKey传输层通过分层架构、协议设计和错误恢复机制,成功解决了跨平台硬件钱包通信的复杂性
当前传输层文档重点覆盖了三类已在源码中稳定存在的约定

**核心特性:**
- **协议设计**: 统一的消息格式和分包机制
- **会话管理**: 安全隔离的设备会话和自动清理
- **错误恢复**: 智能重试和指数退避机制
- **安全保护**: 全面防护常见攻击向量
- **统一协议格式**:`[##][Type][Length][Payload]` 与 63-byte HID 分包
- **会话模型**:`enumerate → acquire → configure → call → release`
- **平台特化恢复逻辑**:例如 React Native BLE 在 `FirmwareUpload` 场景下的节流、退避和重连

**性能指标:**
- **延迟**: 典型操作亚秒级响应
- **可靠性**: 高成功率和自动错误恢复
- **兼容性**: 支持所有OneKey设备型号和固件版本
- **稳定性**: 成熟的协议栈和传输机制
如果你在排查跨平台差异,建议优先对照具体 transport 实现,而不要假设所有消息路径都共享同一套写入和恢复策略。
Loading