181 lines
4.7 KiB
JavaScript
181 lines
4.7 KiB
JavaScript
import { buildA7Packets } from '../core/packet'
|
||
import { parseDeviceInfo } from '../parsers'
|
||
import { BLE_STAGE } from '../core/state'
|
||
|
||
function waitForStateCode(ble, timeout = 6000) {
|
||
return new Promise((resolve, reject) => {
|
||
let timer = null
|
||
let off = null
|
||
|
||
const finish = (callback) => {
|
||
if (timer) {
|
||
clearTimeout(timer)
|
||
}
|
||
|
||
if (off) {
|
||
off()
|
||
}
|
||
|
||
callback()
|
||
}
|
||
|
||
timer = setTimeout(() => {
|
||
finish(() => reject(new Error('等待 UUID_STATE 超时')))
|
||
}, timeout)
|
||
|
||
off = ble.on('protocol:state', (payload) => {
|
||
finish(() => resolve(payload))
|
||
})
|
||
})
|
||
}
|
||
|
||
function generateRandomKey(length = 16) {
|
||
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789'
|
||
let result = ''
|
||
|
||
for (let index = 0; index < length; index += 1) {
|
||
const random = Math.floor(Math.random() * chars.length)
|
||
result += chars[random]
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
async function writePacketsToKeyCharacteristic(ble, keyText) {
|
||
const { key } = ble.getState().characteristics
|
||
|
||
if (!key || !key.uuid) {
|
||
throw new Error('未发现 UUID_KEY 特征值')
|
||
}
|
||
|
||
const profile = ble.getProfile()
|
||
const keyValue = String(keyText || '').trim()
|
||
|
||
if (keyValue.length !== profile.auth.keyLength) {
|
||
throw new Error(`密钥长度必须为 ${profile.auth.keyLength}`)
|
||
}
|
||
|
||
const packets = buildA7Packets(keyValue, profile.packet)
|
||
for (const packet of packets) {
|
||
await ble.writeCharacteristic(key.uuid, packet)
|
||
}
|
||
|
||
return keyValue
|
||
}
|
||
|
||
async function writeKeyAndExpectStates(ble, keyText, expectedStateCodes, options = {}) {
|
||
const keyValue = await writePacketsToKeyCharacteristic(ble, keyText)
|
||
const stateEvent = await waitForStateCode(ble, options.timeout || 6000)
|
||
const stateCode = stateEvent.stateCode
|
||
|
||
if (!expectedStateCodes.includes(stateCode)) {
|
||
throw new Error(`密钥流程状态异常,期望=${expectedStateCodes.join('/')} 实际=${stateCode}`)
|
||
}
|
||
|
||
ble.setStage(BLE_STAGE.AUTHORIZED)
|
||
ble.setAuthState({
|
||
keyMatched: true,
|
||
lastStateCode: stateCode
|
||
})
|
||
|
||
return {
|
||
success: true,
|
||
stateCode,
|
||
key: keyValue
|
||
}
|
||
}
|
||
|
||
export function createAuthModule(ble) {
|
||
async function readDeviceInfo() {
|
||
const { msg } = ble.getState().characteristics
|
||
|
||
if (!msg || !msg.uuid) {
|
||
throw new Error('未发现 UUID_MSG 特征值')
|
||
}
|
||
|
||
const valuePromise = new Promise((resolve) => {
|
||
const off = ble.on('protocol:msg', (payload) => {
|
||
off()
|
||
resolve(payload)
|
||
})
|
||
})
|
||
|
||
await ble.readCharacteristic(msg.uuid)
|
||
const payload = await valuePromise
|
||
|
||
return parseDeviceInfo(payload.value)
|
||
}
|
||
|
||
/**
|
||
* 严格遵循协议1.2绑定流程:
|
||
* 1) 先读取 UUID_MSG 获取 bindStatus;
|
||
* 2) bindStatus=0 -> 写随机密钥并等待 UUID_STATE=5(绑定成功);
|
||
* 3) bindStatus=1 -> 写已知密钥并等待 UUID_STATE=7(密钥匹配成功)。
|
||
*
|
||
* 兼容说明:当某些固件 MSG 未明确返回 0/1 时,回退为“已知密钥匹配优先”,
|
||
* 允许状态 7(匹配成功) 或 5(设备首次绑定成功)。
|
||
*/
|
||
async function ensureAuthorized(keyText, options = {}) {
|
||
ble.setStage(BLE_STAGE.AUTHORIZING)
|
||
|
||
const info = await readDeviceInfo()
|
||
const bindStatus = Number(info.bindStatus)
|
||
|
||
ble.setAuthState({
|
||
bound: Number.isNaN(bindStatus) ? null : bindStatus,
|
||
keyMatched: false
|
||
})
|
||
|
||
// 未绑定:按协议执行绑定。
|
||
if (bindStatus === 0) {
|
||
const keyToBind = String(keyText || '').trim() || generateRandomKey(ble.getProfile().auth.keyLength)
|
||
const authResult = await writeKeyAndExpectStates(ble, keyToBind, [5], options)
|
||
|
||
return {
|
||
mode: 'BIND',
|
||
deviceInfo: info,
|
||
authResult,
|
||
key: keyToBind
|
||
}
|
||
}
|
||
|
||
// 已绑定:按协议执行密钥匹配。
|
||
if (bindStatus === 1) {
|
||
const keyToMatch = String(keyText || '').trim()
|
||
if (!keyToMatch) {
|
||
throw new Error('设备已绑定,必须提供已绑定密钥用于匹配')
|
||
}
|
||
|
||
const authResult = await writeKeyAndExpectStates(ble, keyToMatch, [7], options)
|
||
|
||
return {
|
||
mode: 'MATCH',
|
||
deviceInfo: info,
|
||
authResult,
|
||
key: keyToMatch
|
||
}
|
||
}
|
||
|
||
// 绑定状态不明确时,执行兼容分支(优先匹配,允许绑定成功)。
|
||
const fallbackKey = String(keyText || '').trim()
|
||
if (!fallbackKey) {
|
||
throw new Error(`无法识别的绑定状态: ${info.bindStatus},且未提供密钥`)
|
||
}
|
||
|
||
const fallbackResult = await writeKeyAndExpectStates(ble, fallbackKey, [7, 5], options)
|
||
|
||
return {
|
||
mode: 'UNKNOWN_BIND_STATE_FALLBACK',
|
||
deviceInfo: info,
|
||
authResult: fallbackResult,
|
||
key: fallbackKey
|
||
}
|
||
}
|
||
|
||
return {
|
||
readDeviceInfo,
|
||
ensureAuthorized,
|
||
generateRandomKey
|
||
}
|
||
}
|