import { writeA7Payload } from '../core/packet' import { waitForProtocolState } from '../core/stateWait' import { parseDeviceInfo } from '../parsers' import { BLE_STAGE } from '../core/state' 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 } function normalizeMacForKey(macText = '') { return String(macText || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase() } /** * 按协议约定生成设备密钥:MAC(去分隔符) + 固定后缀。 * 默认后缀为 "0000",对应 12 位 MAC + 4 位后缀 = 16 位密钥。 */ export function buildKeyFromMac(macText, suffix = '0000', expectedLength = 16) { const normalizedMac = normalizeMacForKey(macText) const normalizedSuffix = String(suffix || '').trim() if (!normalizedSuffix) { throw new Error('密钥后缀不能为空') } const requiredMacLength = expectedLength - normalizedSuffix.length if (requiredMacLength <= 0) { throw new Error('密钥长度配置异常') } if (normalizedMac.length !== requiredMacLength) { throw new Error(`设备 MAC 长度异常,期望 ${requiredMacLength} 位,实际 ${normalizedMac.length} 位`) } return `${normalizedMac}${normalizedSuffix}` } 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}`) } await writeA7Payload(ble, key.uuid, keyValue, profile.packet) return keyValue } async function writeKeyAndExpectStates(ble, keyText, expectedStateCodes, options = {}) { const keyValue = await writePacketsToKeyCharacteristic(ble, keyText) const stateEvent = await waitForProtocolState(ble, { allowedCodes: expectedStateCodes, rejectOnUnexpected: true, timeout: options.timeout || 6000, timeoutMessage: '等待 UUID_STATE 超时', unexpectedMessage: '密钥流程状态异常' }) const stateCode = Number(stateEvent.stateCode) ble.setStage(BLE_STAGE.AUTHORIZED) ble.setAuthState({ keyMatched: true, lastStateCode: stateCode }) return { success: true, stateCode, key: keyValue } } async function ensureAuthorizedWithDeviceInfo(ble, info, keyText, options = {}) { const bindStatus = Number(info.bindStatus) const profile = ble.getProfile() ble.setAuthState({ bound: Number.isNaN(bindStatus) ? null : bindStatus, keyMatched: false }) if (bindStatus === 0) { const keyToBind = String(keyText || '').trim() || generateRandomKey(profile.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 } } 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 写随机/指定密钥并等待 STATE=5; * 3) bindStatus=1 写已知密钥并等待 STATE=7。 */ async function ensureAuthorized(keyText, options = {}) { ble.setStage(BLE_STAGE.AUTHORIZING) const info = await readDeviceInfo() return ensureAuthorizedWithDeviceInfo(ble, info, keyText, options) } /** * 固定规则鉴权:密钥 = 设备MAC(去分隔符) + "0000"。 * 用于“先鉴权再进行 WiFi 配置”的页面流程。 */ async function ensureAuthorizedByMac(options = {}) { ble.setStage(BLE_STAGE.AUTHORIZING) const info = await readDeviceInfo() const profile = ble.getProfile() const keyByMac = buildKeyFromMac(info.mac, options.suffix || '0000', profile.auth.keyLength) const authResult = await ensureAuthorizedWithDeviceInfo(ble, info, keyByMac, options) return { ...authResult, key: keyByMac, keyRule: 'MAC+0000' } } return { readDeviceInfo, ensureAuthorized, ensureAuthorizedByMac, buildKeyFromMac, generateRandomKey } }