SEC_Warehouse/utils/ble/modules/auth.js

204 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
}