diff --git a/lib/model/tlv.js b/lib/model/tlv.js index 78ad0f4..5328294 100644 --- a/lib/model/tlv.js +++ b/lib/model/tlv.js @@ -11,12 +11,12 @@ const kTLVType_Separator = 255; * See Chapter 12.1 * * @param {Buffer} buffer - Buffer to decode - * @returns {Object} TLV object + * @returns {Map} TLV object */ function decodeBuffer(buffer) { let position = 0; let lastTag = -1; - const result = {}; + const result = new Map(); if (!Buffer.isBuffer(buffer)) { return result; @@ -27,25 +27,26 @@ function decodeBuffer(buffer) { const length = buffer.readUInt8(position++); const value = buffer.slice(position, position + length); - if (result.hasOwnProperty(tag)) { - if (Array.isArray(result[tag]) && tag === lastTag) { - const idx = result[tag].length - 1; - const newValue = Buffer.allocUnsafe(result[tag][idx].length + length); - result[tag][idx].copy(newValue, 0); - value.copy(newValue, result[tag][idx].length); - result[tag][idx] = newValue; - } else if (Array.isArray(result[tag])) { - result[tag].push(value); + if (result.has(tag)) { + const existingValue = result.get(tag); + if (Array.isArray(existingValue) && tag === lastTag) { + const idx = existingValue.length - 1; + const newValue = Buffer.allocUnsafe(existingValue[idx].length + length); + existingValue[idx].copy(newValue, 0); + value.copy(newValue, existingValue[idx].length); + existingValue[idx] = newValue; + } else if (Array.isArray(existingValue)) { + existingValue.push(value); } else if (tag === lastTag) { - const newValue = Buffer.allocUnsafe(result[tag].length + length); - result[tag].copy(newValue, 0); - value.copy(newValue, result[tag].length); - result[tag] = newValue; + const newValue = Buffer.allocUnsafe(existingValue.length + length); + existingValue.copy(newValue, 0); + value.copy(newValue, existingValue.length); + result.set(tag, newValue); } else { - result[tag] = [result[tag], value]; + result.set(tag, [existingValue, value]); } } else { - result[tag] = value; + result.set(tag, value); } position += length; @@ -60,13 +61,14 @@ function decodeBuffer(buffer) { * * See Chapter 12.1 * - * @param {Object} object - TLV object to encode + * @param {Map} object - TLV object to encode * @returns {Buffer} Encoded buffer */ function encodeObject(object) { const tlvs = []; - for (let tag in object) { + // eslint-disable-next-line prefer-const + for (let [tag, value] of object) { tag = parseInt(tag, 10); if (tag < 0 || tag > 255) { @@ -78,7 +80,6 @@ function encodeObject(object) { continue; } - const value = object[tag]; let values; if (Array.isArray(value)) { values = value; diff --git a/lib/protocol/pairing-protocol.js b/lib/protocol/pairing-protocol.js index 2d61c6b..5e2b1a4 100644 --- a/lib/protocol/pairing-protocol.js +++ b/lib/protocol/pairing-protocol.js @@ -161,10 +161,10 @@ class PairingProtocol { * @returns {Promise} Promise which resolves to a Buffer. */ buildPairSetupM1() { - const packet = TLV.encodeObject({ - [Types.kTLVType_State]: Buffer.from([Steps.M1]), - [Types.kTLVType_Method]: Buffer.from([Methods.PairSetup]), - }); + const data = new Map(); + data.set(Types.kTLVType_State, Buffer.from([Steps.M1])); + data.set(Types.kTLVType_Method, Buffer.from([Methods.PairSetup])); + const packet = TLV.encodeObject(data); return Promise.resolve(packet); } @@ -178,23 +178,24 @@ class PairingProtocol { return new Promise((resolve, reject) => { const tlv = TLV.decodeBuffer(m2Buffer); - if (!tlv || Object.keys(tlv).length === 0) { + if (!tlv || tlv.size === 0) { reject('M2: Empty TLV'); return; } - if (tlv[Types.kTLVType_Error]) { - reject(`M2: Error: ${tlv[Types.kTLVType_Error].readUInt8(0)}`); + if (tlv.has(Types.kTLVType_Error)) { + reject(`M2: Error: ${tlv.get(Types.kTLVType_Error).readUInt8(0)}`); return; } - if (!tlv[Types.kTLVType_State]) { + if (!tlv.has(Types.kTLVType_State)) { reject('M2: Missing state'); return; } - if (tlv[Types.kTLVType_State][0] !== Steps.M2) { - reject(`M2: Invalid state: ${tlv[Types.kTLVType_State][0]}`); + const state = tlv.get(Types.kTLVType_State)[0]; + if (state !== Steps.M2) { + reject(`M2: Invalid state: ${state}`); return; } @@ -214,18 +215,18 @@ class PairingProtocol { srp.genKey(32, (err, key) => { this.srpClient = new srp.Client( srp.params['3072'], - m2Tlv[Types.kTLVType_Salt], + m2Tlv.get(Types.kTLVType_Salt), Buffer.from('Pair-Setup'), Buffer.from(pin), key ); - this.srpClient.setB(m2Tlv[Types.kTLVType_PublicKey]); + this.srpClient.setB(m2Tlv.get(Types.kTLVType_PublicKey)); - const packet = TLV.encodeObject({ - [Types.kTLVType_State]: Buffer.from([Steps.M3]), - [Types.kTLVType_PublicKey]: this.srpClient.computeA(), - [Types.kTLVType_Proof]: this.srpClient.computeM1(), - }); + const data = new Map(); + data.set(Types.kTLVType_State, Buffer.from([Steps.M3])); + data.set(Types.kTLVType_PublicKey, this.srpClient.computeA()); + data.set(Types.kTLVType_Proof, this.srpClient.computeM1()); + const packet = TLV.encodeObject(data); resolve(packet); }); @@ -242,33 +243,34 @@ class PairingProtocol { return new Promise((resolve, reject) => { const tlv = TLV.decodeBuffer(m4Buffer); - if (!tlv || Object.keys(tlv).length === 0) { + if (!tlv || tlv.size === 0) { reject('M4: Empty TLV'); return; } - if (tlv[Types.kTLVType_Error]) { - reject(`M4: Error: ${tlv[Types.kTLVType_Error].readUInt8(0)}`); + if (tlv.has(Types.kTLVType_Error)) { + reject(`M4: Error: ${tlv.get(Types.kTLVType_Error).readUInt8(0)}`); return; } - if (!tlv[Types.kTLVType_State]) { + if (!tlv.has(Types.kTLVType_State)) { reject('M4: Missing state'); return; } - if (tlv[Types.kTLVType_State][0] !== Steps.M4) { - reject(`M4: Invalid state: ${tlv[Types.kTLVType_State][0]}`); + const state = tlv.get(Types.kTLVType_State)[0]; + if (state !== Steps.M4) { + reject(`M4: Invalid state: ${state}`); return; } - if (!tlv[Types.kTLVType_Proof]) { + if (!tlv.has(Types.kTLVType_Proof)) { reject('M4: Proof missing from TLV'); return; } try { - this.srpClient.checkM2(tlv[Types.kTLVType_Proof]); + this.srpClient.checkM2(tlv.get(Types.kTLVType_Proof)); } catch (e) { reject('M4: Proof verification failed'); return; @@ -311,11 +313,11 @@ class PairingProtocol { ) ); - const subTlv = TLV.encodeObject({ - [Types.kTLVType_Identifier]: this.iOSDevicePairingID, - [Types.kTLVType_PublicKey]: this.iOSDeviceLTPK, - [Types.kTLVType_Signature]: iOSDeviceSignature, - }); + const data = new Map(); + data.set(Types.kTLVType_Identifier, this.iOSDevicePairingID); + data.set(Types.kTLVType_PublicKey, this.iOSDeviceLTPK); + data.set(Types.kTLVType_Signature, iOSDeviceSignature); + const subTlv = TLV.encodeObject(data); new HKDF( 'sha512', @@ -335,10 +337,10 @@ class PairingProtocol { ) ); - const tlv = TLV.encodeObject({ - [Types.kTLVType_State]: Buffer.from([Steps.M5]), - [Types.kTLVType_EncryptedData]: encryptedData, - }); + const data = new Map(); + data.set(Types.kTLVType_State, Buffer.from([Steps.M5])); + data.set(Types.kTLVType_EncryptedData, encryptedData); + const tlv = TLV.encodeObject(data); resolve(tlv); }); @@ -358,27 +360,28 @@ class PairingProtocol { return new Promise((resolve, reject) => { const tlv = TLV.decodeBuffer(m6Buffer); - if (!tlv || Object.keys(tlv).length === 0) { + if (!tlv || tlv.size === 0) { reject('M6: Empty TLV'); return; } - if (tlv[Types.kTLVType_Error]) { - reject(`M6: Error: ${tlv[Types.kTLVType_Error].readUInt8(0)}`); + if (tlv.has(Types.kTLVType_Error)) { + reject(`M6: Error: ${tlv.get(Types.kTLVType_Error).readUInt8(0)}`); return; } - if (!tlv[Types.kTLVType_State]) { + if (!tlv.has(Types.kTLVType_State)) { reject('M6: Missing state'); return; } - if (tlv[Types.kTLVType_State][0] !== Steps.M6) { - reject(`M6: Invalid state: ${tlv[Types.kTLVType_State][0]}`); + const state = tlv.get(Types.kTLVType_State)[0]; + if (state !== Steps.M6) { + reject(`M6: Invalid state: ${state}`); return; } - if (!tlv[Types.kTLVType_EncryptedData]) { + if (!tlv.has(Types.kTLVType_EncryptedData)) { reject('M6: Encrypted data missing from TLV'); return; } @@ -388,7 +391,7 @@ class PairingProtocol { decryptedData = Buffer.from( sodium.crypto_aead_chacha20poly1305_ietf_decrypt( null, - tlv[Types.kTLVType_EncryptedData], + tlv.get(Types.kTLVType_EncryptedData), null, Buffer.concat( [Buffer.from([0, 0, 0, 0]), Buffer.from('PS-Msg06')] @@ -403,17 +406,17 @@ class PairingProtocol { const subTlv = TLV.decodeBuffer(decryptedData); - if (!subTlv[Types.kTLVType_Signature]) { + if (!subTlv.has(Types.kTLVType_Signature)) { reject('M6: Signature missing from sub-TLV'); return; } - if (!subTlv[Types.kTLVType_Identifier]) { + if (!subTlv.has(Types.kTLVType_Identifier)) { reject('M6: Identifier missing from sub-TLV'); return; } - if (!subTlv[Types.kTLVType_PublicKey]) { + if (!subTlv.has(Types.kTLVType_PublicKey)) { reject('M6: Public key missing from sub-TLV'); return; } @@ -424,9 +427,9 @@ class PairingProtocol { this.srpClient.computeK() ).derive('Pair-Setup-Accessory-Sign-Info', 32, (key) => { const AccessoryX = key; - this.AccessoryPairingID = subTlv[Types.kTLVType_Identifier]; - this.AccessoryLTPK = subTlv[Types.kTLVType_PublicKey]; - const AccessorySignature = subTlv[Types.kTLVType_Signature]; + this.AccessoryPairingID = subTlv.get(Types.kTLVType_Identifier); + this.AccessoryLTPK = subTlv.get(Types.kTLVType_PublicKey); + const AccessorySignature = subTlv.get(Types.kTLVType_Signature); const AccessoryInfo = Buffer.concat([ AccessoryX, this.AccessoryPairingID, @@ -458,10 +461,10 @@ class PairingProtocol { sodium.crypto_scalarmult_base(this.pairVerify.privateKey) ); - const packet = TLV.encodeObject({ - [Types.kTLVType_State]: Buffer.from([Steps.M1]), - [Types.kTLVType_PublicKey]: this.pairVerify.publicKey, - }); + const data = new Map(); + data.set(Types.kTLVType_State, Buffer.from([Steps.M1])); + data.set(Types.kTLVType_PublicKey, this.pairVerify.publicKey); + const packet = TLV.encodeObject(data); return packet; }); @@ -478,37 +481,38 @@ class PairingProtocol { return new Promise((resolve, reject) => { const tlv = TLV.decodeBuffer(m2Buffer); - if (!tlv || Object.keys(tlv).length === 0) { + if (!tlv || tlv.size === 0) { reject('M2: Empty TLV'); return; } - if (tlv[Types.kTLVType_Error]) { - reject(`M2: Error: ${tlv[Types.kTLVType_Error].readUInt8(0)}`); + if (tlv.has(Types.kTLVType_Error)) { + reject(`M2: Error: ${tlv.get(Types.kTLVType_Error).readUInt8(0)}`); return; } - if (!tlv[Types.kTLVType_State]) { + if (!tlv.has(Types.kTLVType_State)) { reject('M2: Missing state'); return; } - if (tlv[Types.kTLVType_State][0] !== Steps.M2) { - reject(`M2: Invalid state: ${tlv[Types.kTLVType_State][0]}`); + const state = tlv.get(Types.kTLVType_State)[0]; + if (state !== Steps.M2) { + reject(`M2: Invalid state: ${state}`); return; } - if (!tlv[Types.kTLVType_PublicKey]) { + if (!tlv.has(Types.kTLVType_PublicKey)) { reject('M2: Public key missing from TLV'); return; } - if (!tlv[Types.kTLVType_EncryptedData]) { + if (!tlv.has(Types.kTLVType_EncryptedData)) { reject('M2: Encrypted data missing from TLV'); return; } - this.pairVerify.accessoryPublicKey = tlv[Types.kTLVType_PublicKey]; + this.pairVerify.accessoryPublicKey = tlv.get(Types.kTLVType_PublicKey); this.pairVerify.sharedSecret = Buffer.from( sodium.crypto_scalarmult( @@ -536,7 +540,7 @@ class PairingProtocol { decryptedData = Buffer.from( sodium.crypto_aead_chacha20poly1305_ietf_decrypt( null, - tlv[Types.kTLVType_EncryptedData], + tlv.get(Types.kTLVType_EncryptedData), null, Buffer.concat([Buffer.from([0, 0, 0, 0]), Buffer.from('PV-Msg02')]), @@ -550,18 +554,18 @@ class PairingProtocol { const subTlv = TLV.decodeBuffer(decryptedData); - if (!subTlv[Types.kTLVType_Signature]) { + if (!subTlv.has(Types.kTLVType_Signature)) { reject('M2: Signature missing from sub-TLV'); return; } - if (!subTlv[Types.kTLVType_Identifier]) { + if (!subTlv.has(Types.kTLVType_Identifier)) { reject('M2: Identifier missing from sub-TLV'); return; } const AccessoryPairingID = - subTlv[Types.kTLVType_Identifier].toString(); + subTlv.get(Types.kTLVType_Identifier).toString(); if (AccessoryPairingID !== this.AccessoryPairingID.toString()) { reject('M2: Wrong accessory pairing ID'); return; @@ -574,7 +578,7 @@ class PairingProtocol { sodium.crypto_scalarmult_base(this.pairVerify.privateKey)), ]); - const signature = subTlv[Types.kTLVType_Signature]; + const signature = subTlv.get(Types.kTLVType_Signature); if (sodium.crypto_sign_verify_detached(signature, AccessoryInfo, this.AccessoryLTPK)) { @@ -605,10 +609,13 @@ class PairingProtocol { sodium.crypto_sign_detached(iOSDeviceInfo, this.iOSDeviceLTSK) ); - const subTlv = TLV.encodeObject({ - [Types.kTLVType_Identifier]: Buffer.from(this.iOSDevicePairingID), - [Types.kTLVType_Signature]: iOSDeviceSignature, - }); + const subData = new Map(); + subData.set( + Types.kTLVType_Identifier, + Buffer.from(this.iOSDevicePairingID) + ); + subData.set(Types.kTLVType_Signature, iOSDeviceSignature); + const subTlv = TLV.encodeObject(subData); const encryptedData = Buffer.from( sodium.crypto_aead_chacha20poly1305_ietf_encrypt( @@ -621,10 +628,10 @@ class PairingProtocol { ) ); - const tlv = TLV.encodeObject({ - [Types.kTLVType_State]: Buffer.from([Steps.M3]), - [Types.kTLVType_EncryptedData]: encryptedData, - }); + const data = new Map(); + data.set(Types.kTLVType_State, Buffer.from([Steps.M3])); + data.set(Types.kTLVType_EncryptedData, encryptedData); + const tlv = TLV.encodeObject(data); return tlv; }); @@ -640,23 +647,24 @@ class PairingProtocol { return new Promise((resolve, reject) => { const tlv = TLV.decodeBuffer(m4Buffer); - if (!tlv || Object.keys(tlv).length === 0) { + if (!tlv || tlv.size === 0) { reject('M4: Empty TLV'); return; } - if (tlv[Types.kTLVType_Error]) { - reject(`M4: Error: ${tlv[Types.kTLVType_Error].readUInt8(0)}`); + if (tlv.has(Types.kTLVType_Error)) { + reject(`M4: Error: ${tlv.get(Types.kTLVType_Error).readUInt8(0)}`); return; } - if (!tlv[Types.kTLVType_State]) { + if (!tlv.has(Types.kTLVType_State)) { reject('M4: Missing state'); return; } - if (tlv[Types.kTLVType_State][0] !== Steps.M4) { - reject(`M4: Invalid state: ${tlv[Types.kTLVType_State][0]}`); + const state = tlv.get(Types.kTLVType_State)[0]; + if (state !== Steps.M4) { + reject(`M4: Invalid state: ${state}`); return; } @@ -705,13 +713,13 @@ class PairingProtocol { * @returns {Promise} Promise which resolves to a Buffer. */ buildAddPairingM1(identifier, ltpk, isAdmin) { - const packet = TLV.encodeObject({ - [Types.kTLVType_State]: Buffer.from([Steps.M1]), - [Types.kTLVType_Method]: Buffer.from([Methods.AddPairing]), - [Types.kTLVType_Identifier]: Buffer.from(identifier), - [Types.kTLVType_PublicKey]: ltpk, - [Types.kTLVType_Permissions]: Buffer.from([isAdmin ? 1 : 0]), - }); + const data = new Map(); + data.set(Types.kTLVType_State, Buffer.from([Steps.M1])); + data.set(Types.kTLVType_Method, Buffer.from([Methods.AddPairing])); + data.set(Types.kTLVType_Identifier, Buffer.from(identifier)); + data.set(Types.kTLVType_PublicKey, ltpk); + data.set(Types.kTLVType_Permissions, Buffer.from([isAdmin ? 1 : 0])); + const packet = TLV.encodeObject(data); return Promise.resolve(packet); } @@ -725,23 +733,24 @@ class PairingProtocol { return new Promise((resolve, reject) => { const tlv = TLV.decodeBuffer(m2Buffer); - if (!tlv || Object.keys(tlv).length === 0) { + if (!tlv || tlv.size === 0) { reject('M2: Empty TLV'); return; } - if (tlv[Types.kTLVType_Error]) { - reject(`M2: Error: ${tlv[Types.kTLVType_Error].readUInt8(0)}`); + if (tlv.has(Types.kTLVType_Error)) { + reject(`M2: Error: ${tlv.get(Types.kTLVType_Error).readUInt8(0)}`); return; } - if (!tlv[Types.kTLVType_State]) { + if (!tlv.has(Types.kTLVType_State)) { reject('M2: Missing state'); return; } - if (tlv[Types.kTLVType_State][0] !== Steps.M2) { - reject(`M2: Invalid state: ${tlv[Types.kTLVType_State][0]}`); + const state = tlv.get(Types.kTLVType_State)[0]; + if (state !== Steps.M2) { + reject(`M2: Invalid state: ${state}`); return; } @@ -756,11 +765,11 @@ class PairingProtocol { * @returns {Promise} Promise which resolves to a Buffer. */ buildRemovePairingM1(identifier) { - const packet = TLV.encodeObject({ - [Types.kTLVType_State]: Buffer.from([Steps.M1]), - [Types.kTLVType_Method]: Buffer.from([Methods.RemovePairing]), - [Types.kTLVType_Identifier]: Buffer.from(identifier), - }); + const data = new Map(); + data.set(Types.kTLVType_State, Buffer.from([Steps.M1])); + data.set(Types.kTLVType_Method, Buffer.from([Methods.RemovePairing])); + data.set(Types.kTLVType_Identifier, Buffer.from(identifier)); + const packet = TLV.encodeObject(data); return Promise.resolve(packet); } @@ -774,23 +783,24 @@ class PairingProtocol { return new Promise((resolve, reject) => { const tlv = TLV.decodeBuffer(m2Buffer); - if (!tlv || Object.keys(tlv).length === 0) { + if (!tlv || tlv.size === 0) { reject('M2: Empty TLV'); return; } - if (tlv[Types.kTLVType_Error]) { - reject(`M2: Error: ${tlv[Types.kTLVType_Error].readUInt8(0)}`); + if (tlv.has(Types.kTLVType_Error)) { + reject(`M2: Error: ${tlv.get(Types.kTLVType_Error).readUInt8(0)}`); return; } - if (!tlv[Types.kTLVType_State]) { + if (!tlv.has(Types.kTLVType_State)) { reject('M2: Missing state'); return; } - if (tlv[Types.kTLVType_State][0] !== Steps.M2) { - reject(`M2: Invalid state: ${tlv[Types.kTLVType_State][0]}`); + const state = tlv.get(Types.kTLVType_State)[0]; + if (state !== Steps.M2) { + reject(`M2: Invalid state: ${state}`); return; } @@ -804,10 +814,10 @@ class PairingProtocol { * @returns {Promise} Promise which resolves to a Buffer. */ buildListPairingsM1() { - const packet = TLV.encodeObject({ - [Types.kTLVType_State]: Buffer.from([Steps.M1]), - [Types.kTLVType_Method]: Buffer.from([Methods.ListPairings]), - }); + const data = new Map(); + data.set(Types.kTLVType_State, Buffer.from([Steps.M1])); + data.set(Types.kTLVType_Method, Buffer.from([Methods.ListPairings])); + const packet = TLV.encodeObject(data); return Promise.resolve(packet); } @@ -821,23 +831,24 @@ class PairingProtocol { return new Promise((resolve, reject) => { const tlv = TLV.decodeBuffer(m2Buffer); - if (!tlv || Object.keys(tlv).length === 0) { + if (!tlv || tlv.size === 0) { reject('M2: Empty TLV'); return; } - if (tlv[Types.kTLVType_Error]) { - reject(`M2: Error: ${tlv[Types.kTLVType_Error].readUInt8(0)}`); + if (tlv.has(Types.kTLVType_Error)) { + reject(`M2: Error: ${tlv.get(Types.kTLVType_Error).readUInt8(0)}`); return; } - if (!tlv[Types.kTLVType_State]) { + if (!tlv.has(Types.kTLVType_State)) { reject('M2: Missing state'); return; } - if (tlv[Types.kTLVType_State][0] !== Steps.M2) { - reject(`M2: Invalid state: ${tlv[Types.kTLVType_State][0]}`); + const state = tlv.get(Types.kTLVType_State)[0]; + if (state !== Steps.M2) { + reject(`M2: Invalid state: ${state}`); return; } @@ -875,13 +886,13 @@ class PairingProtocol { ) ); - const packet = TLV.encodeObject({ - [Types.kTLVType_State]: Buffer.from([Steps.M1]), - [Types.kTLVType_Method]: Buffer.from([Methods.PairResume]), - [Types.kTLVType_PublicKey]: this.pairVerify.publicKey, - [Types.kTLVType_SessionID]: this.pairVerify.sessionID, - [Types.kTLVType_EncryptedData]: encryptedData, - }); + const data = new Map(); + data.set(Types.kTLVType_State, Buffer.from([Steps.M1])); + data.set(Types.kTLVType_Method, Buffer.from([Methods.PairResume])); + data.set(Types.kTLVType_PublicKey, this.pairVerify.publicKey); + data.set(Types.kTLVType_SessionID, this.pairVerify.sessionID); + data.set(Types.kTLVType_EncryptedData, encryptedData); + const packet = TLV.encodeObject(data); resolve(packet); }); @@ -900,37 +911,38 @@ class PairingProtocol { return new Promise((resolve, reject) => { const tlv = TLV.decodeBuffer(m2Buffer); - if (!tlv || Object.keys(tlv).length === 0) { + if (!tlv || tlv.size === 0) { reject('M2: Empty TLV'); return; } - if (tlv[Types.kTLVType_Error]) { - reject(`M2: Error: ${tlv[Types.kTLVType_Error].readUInt8(0)}`); + if (tlv.has(Types.kTLVType_Error)) { + reject(`M2: Error: ${tlv.get(Types.kTLVType_Error).readUInt8(0)}`); return; } - if (!tlv[Types.kTLVType_State]) { + if (!tlv.has(Types.kTLVType_State)) { reject('M2: Missing state'); return; } - if (tlv[Types.kTLVType_State][0] !== Steps.M2) { - reject(`M2: Invalid state: ${tlv[Types.kTLVType_State][0]}`); + const state = tlv.get(Types.kTLVType_State)[0]; + if (state !== Steps.M2) { + reject(`M2: Invalid state: ${state}`); return; } - if (!tlv[Types.kTLVType_SessionID]) { + if (!tlv.has(Types.kTLVType_SessionID)) { reject('M2: Session ID missing from TLV'); return; } - if (!tlv[Types.kTLVType_EncryptedData]) { + if (!tlv.has(Types.kTLVType_EncryptedData)) { reject('M2: Encrypted data missing from TLV'); return; } - this.pairVerify.sessionID = tlv[Types.kTLVType_SessionID]; + this.pairVerify.sessionID = tlv.get(Types.kTLVType_SessionID); new HKDF( 'sha512', @@ -940,7 +952,7 @@ class PairingProtocol { try { sodium.crypto_aead_chacha20poly1305_ietf_decrypt( null, - tlv[Types.kTLVType_EncryptedData], + tlv.get(Types.kTLVType_EncryptedData), null, Buffer.concat([Buffer.from([0, 0, 0, 0]), Buffer.from('PR-Msg02')]), diff --git a/lib/transport/ble/gatt-client.js b/lib/transport/ble/gatt-client.js index 833cfa8..ed0b409 100644 --- a/lib/transport/ble/gatt-client.js +++ b/lib/transport/ble/gatt-client.js @@ -115,14 +115,13 @@ class GattClient extends EventEmitter { return {iid, characteristic}; }); }).then(({iid, characteristic}) => { + const data = new Map(); + data.set(GattConstants.Types['HAP-Param-Value'], Buffer.from([1])); const pdu = this.gattProtocol.buildCharacteristicWriteRequest( this.getNextTransactionId(), iid, - { - [GattConstants.Types['HAP-Param-Value']]: - Buffer.from([1]), - } + data ); return connection.writeCharacteristic(characteristic, [pdu]); @@ -259,14 +258,16 @@ class GattClient extends EventEmitter { iid = instanceId; return this.pairingProtocol.buildPairSetupM1(); }).then((packet) => { + const data = new Map(); + data.set(GattConstants.Types['HAP-Param-Value'], packet); + data.set( + GattConstants.Types['HAP-Param-Return-Response'], + Buffer.from([1]) + ); const pdu = this.gattProtocol.buildCharacteristicWriteRequest( this.getNextTransactionId(), iid, - { - [GattConstants.Types['HAP-Param-Value']]: packet, - [GattConstants.Types['HAP-Param-Return-Response']]: - Buffer.from([1]), - } + data ); return connection.writeCharacteristic(characteristic, [pdu]); }).then((pdus) => { @@ -305,7 +306,7 @@ class GattClient extends EventEmitter { response.slice(5, response.length) ); return this.pairingProtocol.parsePairSetupM2( - body[GattConstants.Types['HAP-Param-Value']] + body.get(GattConstants.Types['HAP-Param-Value']) ); }).then((tlv) => { return {tlv, iid, characteristic}; @@ -330,14 +331,16 @@ class GattClient extends EventEmitter { const connection = this._pairingConnection; const protocol = this.pairingProtocol; return protocol.buildPairSetupM3(tlv, pin).then((packet) => { + const data = new Map(); + data.set(GattConstants.Types['HAP-Param-Value'], packet); + data.set( + GattConstants.Types['HAP-Param-Return-Response'], + Buffer.from([1]) + ); const pdu = this.gattProtocol.buildCharacteristicWriteRequest( this.getNextTransactionId(), iid, - { - [GattConstants.Types['HAP-Param-Value']]: packet, - [GattConstants.Types['HAP-Param-Return-Response']]: - Buffer.from([1]), - } + data ); return connection.writeCharacteristic(characteristic, [pdu]); }).then((pdus) => { @@ -377,19 +380,21 @@ class GattClient extends EventEmitter { const body = TLV.decodeBuffer(Buffer.concat(buffers)); return this.pairingProtocol.parsePairSetupM4( - body[GattConstants.Types['HAP-Param-Value']] + body.get(GattConstants.Types['HAP-Param-Value']) ); }).then(() => { return this.pairingProtocol.buildPairSetupM5(); }).then((packet) => { + const data = new Map(); + data.set(GattConstants.Types['HAP-Param-Value'], packet); + data.set( + GattConstants.Types['HAP-Param-Return-Response'], + Buffer.from([1]) + ); const pdu = this.gattProtocol.buildCharacteristicWriteRequest( this.getNextTransactionId(), iid, - { - [GattConstants.Types['HAP-Param-Value']]: packet, - [GattConstants.Types['HAP-Param-Return-Response']]: - Buffer.from([1]), - } + data ); return connection.writeCharacteristic(characteristic, [pdu]); }).then((pdus) => { @@ -428,7 +433,7 @@ class GattClient extends EventEmitter { response.slice(5, response.length) ); return this.pairingProtocol.parsePairSetupM6( - body[GattConstants.Types['HAP-Param-Value']] + body.get(GattConstants.Types['HAP-Param-Value']) ); }).then(() => { return connection.disconnect() @@ -498,14 +503,16 @@ class GattClient extends EventEmitter { if (this.pairingProtocol.canResume()) { return this.pairingProtocol.buildPairResumeM1().then((packet) => { + const data = new Map(); + data.set(GattConstants.Types['HAP-Param-Value'], packet); + data.set( + GattConstants.Types['HAP-Param-Return-Response'], + Buffer.from([1]) + ); const pdu = this.gattProtocol.buildCharacteristicWriteRequest( this.getNextTransactionId(), iid, - { - [GattConstants.Types['HAP-Param-Value']]: packet, - [GattConstants.Types['HAP-Param-Return-Response']]: - Buffer.from([1]), - } + data ); return connection.writeCharacteristic(characteristic, [pdu]); }).then((pdus) => { @@ -544,21 +551,23 @@ class GattClient extends EventEmitter { response.slice(5, response.length) ); return this.pairingProtocol.parsePairResumeM2( - body[GattConstants.Types['HAP-Param-Value']] + body.get(GattConstants.Types['HAP-Param-Value']) ).catch(() => { return this.pairingProtocol.parsePairVerifyM2( - body[GattConstants.Types['HAP-Param-Value']] + body.get(GattConstants.Types['HAP-Param-Value']) ).then(() => { return this.pairingProtocol.buildPairVerifyM3(); }).then((packet) => { + const data = new Map(); + data.set(GattConstants.Types['HAP-Param-Value'], packet); + data.set( + GattConstants.Types['HAP-Param-Return-Response'], + Buffer.from([1]) + ); const pdu = this.gattProtocol.buildCharacteristicWriteRequest( this.getNextTransactionId(), iid, - { - [GattConstants.Types['HAP-Param-Value']]: packet, - [GattConstants.Types['HAP-Param-Return-Response']]: - Buffer.from([1]), - } + data ); return connection.writeCharacteristic(characteristic, [pdu]); }).then((pdus) => { @@ -597,7 +606,7 @@ class GattClient extends EventEmitter { response.slice(5, response.length) ); return this.pairingProtocol.parsePairVerifyM4( - body[GattConstants.Types['HAP-Param-Value']] + body.get(GattConstants.Types['HAP-Param-Value']) ); }); }); @@ -608,14 +617,16 @@ class GattClient extends EventEmitter { }); } else { return this.pairingProtocol.buildPairVerifyM1().then((packet) => { + const data = new Map(); + data.set(GattConstants.Types['HAP-Param-Value'], packet); + data.set( + GattConstants.Types['HAP-Param-Return-Response'], + Buffer.from([1]) + ); const pdu = this.gattProtocol.buildCharacteristicWriteRequest( this.getNextTransactionId(), iid, - { - [GattConstants.Types['HAP-Param-Value']]: packet, - [GattConstants.Types['HAP-Param-Return-Response']]: - Buffer.from([1]), - } + data ); return connection.writeCharacteristic(characteristic, [pdu]); }).then((pdus) => { @@ -654,19 +665,21 @@ class GattClient extends EventEmitter { response.slice(5, response.length) ); return this.pairingProtocol.parsePairVerifyM2( - body[GattConstants.Types['HAP-Param-Value']] + body.get(GattConstants.Types['HAP-Param-Value']) ); }).then(() => { return this.pairingProtocol.buildPairVerifyM3(); }).then((packet) => { + const data = new Map(); + data.set(GattConstants.Types['HAP-Param-Value'], packet); + data.set( + GattConstants.Types['HAP-Param-Return-Response'], + Buffer.from([1]) + ); const pdu = this.gattProtocol.buildCharacteristicWriteRequest( this.getNextTransactionId(), iid, - { - [GattConstants.Types['HAP-Param-Value']]: packet, - [GattConstants.Types['HAP-Param-Return-Response']]: - Buffer.from([1]), - } + data ); return connection.writeCharacteristic(characteristic, [pdu]); }).then((pdus) => { @@ -705,7 +718,7 @@ class GattClient extends EventEmitter { response.slice(5, response.length) ); return this.pairingProtocol.parsePairVerifyM4( - body[GattConstants.Types['HAP-Param-Value']] + body.get(GattConstants.Types['HAP-Param-Value']) ); }).then(() => { return this.pairingProtocol.getSessionKeys(); @@ -773,14 +786,16 @@ class GattClient extends EventEmitter { }).then(() => { return this.pairingProtocol.buildRemovePairingM1(identifier); }).then((packet) => { + const data = new Map(); + data.set(GattConstants.Types['HAP-Param-Value'], packet); + data.set( + GattConstants.Types['HAP-Param-Return-Response'], + Buffer.from([1]) + ); const pdu = this.gattProtocol.buildCharacteristicWriteRequest( this.getNextTransactionId(), iid, - { - [GattConstants.Types['HAP-Param-Value']]: packet, - [GattConstants.Types['HAP-Param-Return-Response']]: - Buffer.from([1]), - } + data ); return connection.writeCharacteristic(characteristic, [pdu]); }).then((pdus) => { @@ -819,7 +834,7 @@ class GattClient extends EventEmitter { response.slice(5, response.length) ); return this.pairingProtocol.parseRemovePairingM2( - body[GattConstants.Types['HAP-Param-Value']] + body.get(GattConstants.Types['HAP-Param-Value']) ); }).then(() => { return connection.disconnect() @@ -891,14 +906,16 @@ class GattClient extends EventEmitter { isAdmin ); }).then((packet) => { + const data = new Map(); + data.set(GattConstants.Types['HAP-Param-Value'], packet); + data.set( + GattConstants.Types['HAP-Param-Return-Response'], + Buffer.from([1]) + ); const pdu = this.gattProtocol.buildCharacteristicWriteRequest( this.getNextTransactionId(), iid, - { - [GattConstants.Types['HAP-Param-Value']]: packet, - [GattConstants.Types['HAP-Param-Return-Response']]: - Buffer.from([1]), - } + data ); return connection.writeCharacteristic(characteristic, [pdu]); }).then((pdus) => { @@ -937,7 +954,7 @@ class GattClient extends EventEmitter { response.slice(5, response.length) ); return this.pairingProtocol.parseAddPairingM2( - body[GattConstants.Types['HAP-Param-Value']] + body.get(GattConstants.Types['HAP-Param-Value']) ); }).then(() => { return connection.disconnect() @@ -1003,14 +1020,16 @@ class GattClient extends EventEmitter { }).then(() => { return this.pairingProtocol.buildListPairingsM1(); }).then((packet) => { + const data = new Map(); + data.set(GattConstants.Types['HAP-Param-Value'], packet); + data.set( + GattConstants.Types['HAP-Param-Return-Response'], + Buffer.from([1]) + ); const pdu = this.gattProtocol.buildCharacteristicWriteRequest( this.getNextTransactionId(), iid, - { - [GattConstants.Types['HAP-Param-Value']]: packet, - [GattConstants.Types['HAP-Param-Return-Response']]: - Buffer.from([1]), - } + data ); return connection.writeCharacteristic(characteristic, [pdu]); }).then((pdus) => { @@ -1049,7 +1068,7 @@ class GattClient extends EventEmitter { response.slice(5, response.length) ); return this.pairingProtocol.parseListPairingsM2( - body[GattConstants.Types['HAP-Param-Value']] + body.get(GattConstants.Types['HAP-Param-Value']) ); }).then((tlv) => { return connection.disconnect() @@ -1241,7 +1260,7 @@ class GattClient extends EventEmitter { const body = TLV.decodeBuffer( response.slice(5, response.length) ); - const value = body[GattConstants.Types['HAP-Param-Value']]; + const value = body.get(GattConstants.Types['HAP-Param-Value']); if (!value) { return; } @@ -1454,11 +1473,11 @@ class GattClient extends EventEmitter { response.slice(5, response.length) ); - const properties = body[ + const properties = body.get( GattConstants.Types[ 'HAP-Param-HAP-Characteristic-Properties-Descriptor' ] - ]; + ); if (properties && options.perms) { entry.perms = []; @@ -1484,20 +1503,20 @@ class GattClient extends EventEmitter { } } - const description = body[ + const description = body.get( GattConstants.Types[ 'HAP-Param-GATT-User-Description-Descriptor' ] - ]; + ); if (description && options.extra) { entry.description = description.toString(); } - const format = body[ + const format = body.get( GattConstants.Types[ 'HAP-Param-GATT-Presentation-Format-Descriptor' ] - ]; + ); if (format && options.meta) { const sigFormat = format.readUInt8(0); const hapFormat = GattConstants.BTSigToHapFormat[sigFormat]; @@ -1512,11 +1531,11 @@ class GattClient extends EventEmitter { } } - const validRange = body[ + const validRange = body.get( GattConstants.Types[ 'HAP-Param-GATT-Valid-Range' ] - ]; + ); if (validRange && options.meta && entry.format) { switch (entry.format) { @@ -1557,11 +1576,11 @@ class GattClient extends EventEmitter { } } - const stepValue = body[ + const stepValue = body.get( GattConstants.Types[ 'HAP-Param-HAP-Step-Value-Descriptor' ] - ]; + ); if (stepValue && options.meta && entry.format) { switch (entry.format) { case 'uint8': @@ -1586,22 +1605,22 @@ class GattClient extends EventEmitter { } } - const validValues = body[ + const validValues = body.get( GattConstants.Types[ 'HAP-Param-HAP-Valid-Values-Descriptor' ] - ]; + ); if (validValues && options.extra) { entry['valid-values'] = Array.from( validValues.values() ); } - const validValuesRange = body[ + const validValuesRange = body.get( GattConstants.Types[ 'HAP-Param-HAP-Valid-Values-Range-Descriptor' ] - ]; + ); if (validValuesRange && options.extra) { entry['valid-values-range'] = []; Array.from(validValuesRange.values()) @@ -1670,7 +1689,7 @@ class GattClient extends EventEmitter { const body = TLV.decodeBuffer( response.slice(5, response.length) ); - const value = body[GattConstants.Types['HAP-Param-Value']]; + const value = body.get(GattConstants.Types['HAP-Param-Value']); if (!value) { return; } @@ -1762,13 +1781,13 @@ class GattClient extends EventEmitter { break; } + const data = new Map(); + data.set(GattConstants.Types['HAP-Param-Value'], v.value); const pdu = this.gattProtocol.buildCharacteristicWriteRequest( this.getNextTransactionId(), v.iid, - { - [GattConstants.Types['HAP-Param-Value']]: v.value, - } + data ); lastOp = queue.queue(() => { diff --git a/package.json b/package.json index 7ac75fc..8016338 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hap-controller", - "version": "0.2.2", + "version": "0.3.0", "description": "Library to implement a HAP (HomeKit) controller", "main": "lib/index.js", "directories": { @@ -30,7 +30,7 @@ "hkdf": "0.0.2", "http-parser-js": "^0.5.2", "libsodium-wrappers": "^0.7.6", - "@abandonware/noble": "^1.9.2-5", + "@abandonware/noble": "^1.9.2-6", "uuid": "^3.4.0" }, "devDependencies": {