homebridge-midea-platform 1.1.0-beta.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/.husky/pre-commit +0 -0
  2. package/CHANGELOG.md +3 -0
  3. package/config.schema.json +8 -1
  4. package/dist/accessory/AccessoryFactory.d.ts +11 -11
  5. package/dist/accessory/AccessoryFactory.js +28 -28
  6. package/dist/accessory/AirConditionerAccessory.d.ts +86 -86
  7. package/dist/accessory/AirConditionerAccessory.d.ts.map +1 -1
  8. package/dist/accessory/AirConditionerAccessory.js +560 -561
  9. package/dist/accessory/AirConditionerAccessory.js.map +1 -1
  10. package/dist/accessory/BaseAccessory.d.ts +11 -11
  11. package/dist/accessory/BaseAccessory.js +21 -21
  12. package/dist/accessory/DehumidifierAccessory.d.ts +45 -45
  13. package/dist/accessory/DehumidifierAccessory.js +343 -343
  14. package/dist/accessory/ElectricWaterHeaterAccessory.d.ts +44 -44
  15. package/dist/accessory/ElectricWaterHeaterAccessory.js +176 -176
  16. package/dist/accessory/FanAccessory.d.ts +39 -39
  17. package/dist/accessory/FanAccessory.js +123 -123
  18. package/dist/accessory/GasWaterHeaterAccessory.d.ts +51 -51
  19. package/dist/accessory/GasWaterHeaterAccessory.js +216 -216
  20. package/dist/core/MideaCloud.d.ts +35 -35
  21. package/dist/core/MideaCloud.js +350 -350
  22. package/dist/core/MideaConstants.d.ts +48 -48
  23. package/dist/core/MideaConstants.js +56 -56
  24. package/dist/core/MideaDevice.d.ts +76 -76
  25. package/dist/core/MideaDevice.js +409 -409
  26. package/dist/core/MideaDiscover.d.ts +35 -35
  27. package/dist/core/MideaDiscover.js +212 -212
  28. package/dist/core/MideaMessage.d.ts +75 -75
  29. package/dist/core/MideaMessage.js +184 -184
  30. package/dist/core/MideaPacketBuilder.d.ts +10 -10
  31. package/dist/core/MideaPacketBuilder.js +60 -60
  32. package/dist/core/MideaSecurity.d.ts +63 -63
  33. package/dist/core/MideaSecurity.js +242 -242
  34. package/dist/core/MideaUtils.d.ts +32 -32
  35. package/dist/core/MideaUtils.js +181 -181
  36. package/dist/devices/DeviceFactory.d.ts +11 -11
  37. package/dist/devices/DeviceFactory.js +30 -30
  38. package/dist/devices/a1/MideaA1Device.d.ts +76 -76
  39. package/dist/devices/a1/MideaA1Device.js +136 -136
  40. package/dist/devices/a1/MideaA1Message.d.ts +40 -40
  41. package/dist/devices/a1/MideaA1Message.js +198 -198
  42. package/dist/devices/ac/MideaACDevice.d.ts +100 -98
  43. package/dist/devices/ac/MideaACDevice.d.ts.map +1 -1
  44. package/dist/devices/ac/MideaACDevice.js +370 -357
  45. package/dist/devices/ac/MideaACDevice.js.map +1 -1
  46. package/dist/devices/ac/MideaACMessage.d.ts +92 -92
  47. package/dist/devices/ac/MideaACMessage.js +589 -589
  48. package/dist/devices/e2/MideaE2Device.d.ts +44 -44
  49. package/dist/devices/e2/MideaE2Device.js +119 -119
  50. package/dist/devices/e2/MideaE2Message.d.ts +33 -33
  51. package/dist/devices/e2/MideaE2Message.js +132 -132
  52. package/dist/devices/e3/MideaE3Device.d.ts +43 -43
  53. package/dist/devices/e3/MideaE3Device.js +125 -125
  54. package/dist/devices/e3/MideaE3Message.d.ts +51 -51
  55. package/dist/devices/e3/MideaE3Message.js +136 -136
  56. package/dist/devices/fa/MideaFADevice.d.ts +36 -36
  57. package/dist/devices/fa/MideaFADevice.js +92 -92
  58. package/dist/devices/fa/MideaFAMessage.d.ts +38 -38
  59. package/dist/devices/fa/MideaFAMessage.js +98 -98
  60. package/dist/index.d.ts +6 -6
  61. package/dist/index.js +6 -6
  62. package/dist/platform.d.ts +60 -60
  63. package/dist/platform.js +213 -213
  64. package/dist/platformUtils.d.ts +97 -96
  65. package/dist/platformUtils.d.ts.map +1 -1
  66. package/dist/platformUtils.js +95 -94
  67. package/dist/platformUtils.js.map +1 -1
  68. package/dist/settings.d.ts +8 -8
  69. package/dist/settings.js +11 -11
  70. package/docs/ac.md +2 -0
  71. package/docs/fa.md +3 -0
  72. package/package.json +1 -1
@@ -1,243 +1,243 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LocalSecurity = exports.ArtisonClimaSecurity = exports.NetHomePlusSecurity = exports.SimpleSecurity = exports.MeijuCloudSecurity = exports.MSmartHomeCloudSecurity = exports.ProxiedSecurity = exports.CloudSecurity = void 0;
4
- const crypto_1 = require("crypto");
5
- const MideaConstants_1 = require("./MideaConstants");
6
- const MideaUtils_1 = require("./MideaUtils");
7
- const querystring_1 = require("querystring");
8
- function unescape_plus(str) {
9
- return (0, querystring_1.unescape)(str.replace(/\+/g, ' '));
10
- }
11
- class CloudSecurity {
12
- constructor(login_key, iot_key, hmac_key) {
13
- this.LOGIN_KEY = login_key;
14
- if (hmac_key) {
15
- this.HMAC_KEY = Buffer.from(hmac_key.toString(16), 'hex').toString();
16
- }
17
- if (iot_key) {
18
- this.IOT_KEY = Buffer.from(iot_key.toString(16), 'hex').toString();
19
- }
20
- }
21
- // Generate a HMAC signature for the provided data and random data.
22
- sign(data, random) {
23
- const message = `${this.IOT_KEY}${data}${random}`;
24
- return (0, crypto_1.createHmac)('sha256', this.HMAC_KEY).update(message).digest('hex');
25
- }
26
- // Encrypt the password for cloud API password.
27
- encrpytPassword(loginId, password) {
28
- const m1 = (0, crypto_1.createHash)('sha256').update(password);
29
- const login_hash = `${loginId}${m1.digest('hex')}${this.LOGIN_KEY}`;
30
- const m2 = (0, crypto_1.createHash)('sha256').update(login_hash);
31
- return m2.digest('hex');
32
- }
33
- static getUDPID(device_id_buf) {
34
- const data = (0, crypto_1.createHash)('sha256').update(device_id_buf).digest();
35
- const output = Buffer.alloc(16);
36
- for (let i = 0; i < 16; i++) {
37
- output[i] = data[i] ^ data[i + 16];
38
- }
39
- return output.toString('hex');
40
- }
41
- }
42
- exports.CloudSecurity = CloudSecurity;
43
- class ProxiedSecurity extends CloudSecurity {
44
- getAppKeyAndIv() {
45
- const hash = (0, crypto_1.createHash)('sha256').update(Buffer.from(this.APP_KEY, 'utf8')).digest('hex');
46
- return {
47
- appKey: Buffer.from(hash.substring(0, 16)),
48
- iv: Buffer.from(hash.substring(16, 32)),
49
- };
50
- }
51
- encryptAESAppKey(data) {
52
- const { appKey, iv } = this.getAppKeyAndIv();
53
- const cipher = (0, crypto_1.createCipheriv)('aes-128-cbc', appKey, iv);
54
- return Buffer.concat([cipher.update(data), cipher.final()]);
55
- }
56
- decryptAESAppKey(data) {
57
- const { appKey, iv } = this.getAppKeyAndIv();
58
- const decipher = (0, crypto_1.createDecipheriv)('aes-128-cbc', appKey, iv);
59
- return Buffer.concat([decipher.update(data), decipher.final()]);
60
- }
61
- }
62
- exports.ProxiedSecurity = ProxiedSecurity;
63
- class MSmartHomeCloudSecurity extends ProxiedSecurity {
64
- constructor() {
65
- super(MSmartHomeCloudSecurity._LOGIN_KEY, BigInt('7882822598523843940'), BigInt('117390035944627627450677220413733956185864939010425'));
66
- this.APP_KEY = 'ac21b9f9cbfe4ca5a88562ef25e2b768';
67
- }
68
- encrpytIAMPassword(loginId, password) {
69
- const m1 = (0, crypto_1.createHash)('md5').update(password);
70
- const m2 = (0, crypto_1.createHash)('md5').update(m1.digest('hex'));
71
- const login_hash = `${loginId}${m2.digest('hex')}${this.LOGIN_KEY}`;
72
- const sha = (0, crypto_1.createHash)('sha256').update(login_hash);
73
- return sha.digest('hex');
74
- }
75
- }
76
- exports.MSmartHomeCloudSecurity = MSmartHomeCloudSecurity;
77
- MSmartHomeCloudSecurity._LOGIN_KEY = 'ac21b9f9cbfe4ca5a88562ef25e2b768';
78
- class MeijuCloudSecurity extends ProxiedSecurity {
79
- constructor() {
80
- super(MeijuCloudSecurity._LOGIN_KEY, BigInt('9795516279659324117647275084689641883661667'), BigInt('117390035944627627450677220413733956185864939010425'));
81
- this.APP_KEY = 'ac21b9f9cbfe4ca5a88562ef25e2b768';
82
- }
83
- encrpytIAMPassword(_loginId, password) {
84
- const m1 = (0, crypto_1.createHash)('md5').update(password);
85
- const m2 = (0, crypto_1.createHash)('md5').update(m1.digest('hex'));
86
- return m2.digest('hex');
87
- }
88
- }
89
- exports.MeijuCloudSecurity = MeijuCloudSecurity;
90
- MeijuCloudSecurity._LOGIN_KEY = 'ad0ee21d48a64bf49f4fb583ab76e799';
91
- class SimpleSecurity extends CloudSecurity {
92
- constructor(login_key) {
93
- super(login_key);
94
- }
95
- encrpytIAMPassword() {
96
- return '';
97
- }
98
- sign(url, query) {
99
- const parsedUrl = new URL(url);
100
- const path = parsedUrl.pathname;
101
- return (0, crypto_1.createHash)('sha256')
102
- .update(Buffer.from(`${path}${unescape_plus(query)}${this.LOGIN_KEY}`, 'ascii'))
103
- .digest('hex');
104
- }
105
- }
106
- exports.SimpleSecurity = SimpleSecurity;
107
- class NetHomePlusSecurity extends SimpleSecurity {
108
- constructor() {
109
- super(NetHomePlusSecurity._LOGIN_KEY);
110
- }
111
- }
112
- exports.NetHomePlusSecurity = NetHomePlusSecurity;
113
- NetHomePlusSecurity._LOGIN_KEY = '3742e9e5842d4ad59c2db887e12449f9';
114
- class ArtisonClimaSecurity extends SimpleSecurity {
115
- constructor() {
116
- super(ArtisonClimaSecurity._LOGIN_KEY);
117
- }
118
- }
119
- exports.ArtisonClimaSecurity = ArtisonClimaSecurity;
120
- ArtisonClimaSecurity._LOGIN_KEY = '434a209a5ce141c3b726de067835d7f0';
121
- class LocalSecurity {
122
- constructor() {
123
- this.aes_key = Buffer.from(BigInt('141661095494369103254425781617665632877').toString(16), 'hex');
124
- this.salt = Buffer.from(BigInt('233912452794221312800602098970898185176935770387238278451789080441632479840061417076563').toString(16), 'hex');
125
- this.iv = Buffer.alloc(16);
126
- this.request_count = 0;
127
- this.response_count = 0;
128
- }
129
- aes_cbc_encrpyt(raw, key) {
130
- const cipher = (0, crypto_1.createCipheriv)('aes-256-cbc', key, this.iv);
131
- cipher.setAutoPadding(false);
132
- return Buffer.concat([cipher.update(raw), cipher.final()]);
133
- }
134
- aes_cbc_decrypt(raw, key) {
135
- const decipher = (0, crypto_1.createDecipheriv)('aes-256-cbc', key, this.iv);
136
- decipher.setAutoPadding(false);
137
- return Buffer.concat([decipher.update(raw), decipher.final()]);
138
- }
139
- aes_encrypt(data) {
140
- const cipher = (0, crypto_1.createCipheriv)('aes-128-ecb', this.aes_key, null);
141
- return Buffer.concat([cipher.update(data), cipher.final()]);
142
- }
143
- aes_decrypt(data) {
144
- const decipher = (0, crypto_1.createDecipheriv)('aes-128-ecb', this.aes_key, null);
145
- return Buffer.concat([decipher.update(data), decipher.final()]);
146
- }
147
- encode32_data(raw) {
148
- return (0, crypto_1.createHash)('md5')
149
- .update(Buffer.concat([raw, this.salt]))
150
- .digest();
151
- }
152
- tcp_key_from_resp(response, key) {
153
- if (response.toString() === 'ERROR') {
154
- throw Error('Authentication response is ERROR, cannot get TCP key.');
155
- }
156
- if (response.length !== 64) {
157
- throw Error('Authentication response has unexpected data length, cannot get TCP key..');
158
- }
159
- const payload = response.subarray(0, 32);
160
- const sign = response.subarray(32, response.length);
161
- const plain = this.aes_cbc_decrypt(payload, key);
162
- if ((0, crypto_1.createHash)('sha256').update(plain).digest().compare(sign) !== 0) {
163
- throw Error('Authentication sign does not match, cannot get TCP key.');
164
- }
165
- this.tcp_key = (0, MideaUtils_1.strxor)(plain, key);
166
- this.request_count = 0;
167
- this.response_count = 0;
168
- return this.tcp_key;
169
- }
170
- encode_8370(data, message_type) {
171
- let header = Buffer.from([0x83, 0x70]);
172
- let size = data.length;
173
- let padding = 0;
174
- if (message_type === MideaConstants_1.TCPMessageType.ENCRYPTED_REQUEST || message_type === MideaConstants_1.TCPMessageType.ENCRYPTED_RESPONSE) {
175
- if ((size + 2) % 16 !== 0) {
176
- padding = 16 - ((size + 2) & 0xf);
177
- size += padding + 32;
178
- data = Buffer.concat([data, (0, crypto_1.randomBytes)(padding)]);
179
- }
180
- }
181
- header = Buffer.concat([header, (0, MideaUtils_1.numberToUint8Array)(size, 2, MideaConstants_1.Endianness.Big)]);
182
- header = Buffer.concat([header, Buffer.from([0x20, (padding << 4) | message_type])]);
183
- data = Buffer.concat([(0, MideaUtils_1.numberToUint8Array)(this.request_count, 2, MideaConstants_1.Endianness.Big), data]);
184
- this.request_count += 1;
185
- if (this.request_count >= 0xffff) {
186
- this.request_count = 0;
187
- }
188
- if (message_type === MideaConstants_1.TCPMessageType.ENCRYPTED_REQUEST || message_type === MideaConstants_1.TCPMessageType.ENCRYPTED_RESPONSE) {
189
- const sign = (0, crypto_1.createHash)('sha256')
190
- .update(Buffer.concat([header, data]))
191
- .digest();
192
- data = Buffer.concat([this.aes_cbc_encrpyt(data, this.tcp_key), sign]);
193
- }
194
- return Buffer.concat([header, data]);
195
- }
196
- decode_8370(data) {
197
- if (data.length < 6) {
198
- return [[], data];
199
- }
200
- const header = data.subarray(0, 6);
201
- if (header[0] !== 0x83 || header[1] !== 0x70) {
202
- throw new Error('Not an 8370 message.');
203
- }
204
- const size = header.subarray(2, 4).readUInt16BE() + 8;
205
- let leftover = Buffer.alloc(0);
206
- if (data.length < size) {
207
- return [[], data];
208
- }
209
- else if (data.length > size) {
210
- leftover = data.subarray(size, data.length);
211
- data = data.subarray(0, size);
212
- }
213
- if (header[4] !== 0x20) {
214
- throw new Error('Missing byte 4');
215
- }
216
- const padding = header[5] >> 4;
217
- const message_type_received = header[5] & 0xf;
218
- data = data.subarray(6, data.length);
219
- if ([MideaConstants_1.TCPMessageType.ENCRYPTED_RESPONSE, MideaConstants_1.TCPMessageType.ENCRYPTED_REQUEST].includes(message_type_received)) {
220
- const sign = data.subarray(data.length - 32, data.length);
221
- data = data.subarray(0, data.length - 32);
222
- data = this.aes_cbc_decrypt(data, this.tcp_key);
223
- if ((0, crypto_1.createHash)('sha256')
224
- .update(Buffer.concat([header, data]))
225
- .digest()
226
- .compare(sign) !== 0) {
227
- throw new Error('Sign does not match');
228
- }
229
- if (padding) {
230
- data = data.subarray(0, data.length - padding);
231
- }
232
- }
233
- this.response_count = data.subarray(0, 2).readUInt16BE();
234
- data = data.subarray(2, data.length);
235
- if (leftover.length > 0) {
236
- const [packets, incomplete] = this.decode_8370(leftover);
237
- return [[data, ...packets], incomplete];
238
- }
239
- return [[data], leftover];
240
- }
241
- }
242
- exports.LocalSecurity = LocalSecurity;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalSecurity = exports.ArtisonClimaSecurity = exports.NetHomePlusSecurity = exports.SimpleSecurity = exports.MeijuCloudSecurity = exports.MSmartHomeCloudSecurity = exports.ProxiedSecurity = exports.CloudSecurity = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const MideaConstants_1 = require("./MideaConstants");
6
+ const MideaUtils_1 = require("./MideaUtils");
7
+ const querystring_1 = require("querystring");
8
+ function unescape_plus(str) {
9
+ return (0, querystring_1.unescape)(str.replace(/\+/g, ' '));
10
+ }
11
+ class CloudSecurity {
12
+ constructor(login_key, iot_key, hmac_key) {
13
+ this.LOGIN_KEY = login_key;
14
+ if (hmac_key) {
15
+ this.HMAC_KEY = Buffer.from(hmac_key.toString(16), 'hex').toString();
16
+ }
17
+ if (iot_key) {
18
+ this.IOT_KEY = Buffer.from(iot_key.toString(16), 'hex').toString();
19
+ }
20
+ }
21
+ // Generate a HMAC signature for the provided data and random data.
22
+ sign(data, random) {
23
+ const message = `${this.IOT_KEY}${data}${random}`;
24
+ return (0, crypto_1.createHmac)('sha256', this.HMAC_KEY).update(message).digest('hex');
25
+ }
26
+ // Encrypt the password for cloud API password.
27
+ encrpytPassword(loginId, password) {
28
+ const m1 = (0, crypto_1.createHash)('sha256').update(password);
29
+ const login_hash = `${loginId}${m1.digest('hex')}${this.LOGIN_KEY}`;
30
+ const m2 = (0, crypto_1.createHash)('sha256').update(login_hash);
31
+ return m2.digest('hex');
32
+ }
33
+ static getUDPID(device_id_buf) {
34
+ const data = (0, crypto_1.createHash)('sha256').update(device_id_buf).digest();
35
+ const output = Buffer.alloc(16);
36
+ for (let i = 0; i < 16; i++) {
37
+ output[i] = data[i] ^ data[i + 16];
38
+ }
39
+ return output.toString('hex');
40
+ }
41
+ }
42
+ exports.CloudSecurity = CloudSecurity;
43
+ class ProxiedSecurity extends CloudSecurity {
44
+ getAppKeyAndIv() {
45
+ const hash = (0, crypto_1.createHash)('sha256').update(Buffer.from(this.APP_KEY, 'utf8')).digest('hex');
46
+ return {
47
+ appKey: Buffer.from(hash.substring(0, 16)),
48
+ iv: Buffer.from(hash.substring(16, 32)),
49
+ };
50
+ }
51
+ encryptAESAppKey(data) {
52
+ const { appKey, iv } = this.getAppKeyAndIv();
53
+ const cipher = (0, crypto_1.createCipheriv)('aes-128-cbc', appKey, iv);
54
+ return Buffer.concat([cipher.update(data), cipher.final()]);
55
+ }
56
+ decryptAESAppKey(data) {
57
+ const { appKey, iv } = this.getAppKeyAndIv();
58
+ const decipher = (0, crypto_1.createDecipheriv)('aes-128-cbc', appKey, iv);
59
+ return Buffer.concat([decipher.update(data), decipher.final()]);
60
+ }
61
+ }
62
+ exports.ProxiedSecurity = ProxiedSecurity;
63
+ class MSmartHomeCloudSecurity extends ProxiedSecurity {
64
+ constructor() {
65
+ super(MSmartHomeCloudSecurity._LOGIN_KEY, BigInt('7882822598523843940'), BigInt('117390035944627627450677220413733956185864939010425'));
66
+ this.APP_KEY = 'ac21b9f9cbfe4ca5a88562ef25e2b768';
67
+ }
68
+ encrpytIAMPassword(loginId, password) {
69
+ const m1 = (0, crypto_1.createHash)('md5').update(password);
70
+ const m2 = (0, crypto_1.createHash)('md5').update(m1.digest('hex'));
71
+ const login_hash = `${loginId}${m2.digest('hex')}${this.LOGIN_KEY}`;
72
+ const sha = (0, crypto_1.createHash)('sha256').update(login_hash);
73
+ return sha.digest('hex');
74
+ }
75
+ }
76
+ exports.MSmartHomeCloudSecurity = MSmartHomeCloudSecurity;
77
+ MSmartHomeCloudSecurity._LOGIN_KEY = 'ac21b9f9cbfe4ca5a88562ef25e2b768';
78
+ class MeijuCloudSecurity extends ProxiedSecurity {
79
+ constructor() {
80
+ super(MeijuCloudSecurity._LOGIN_KEY, BigInt('9795516279659324117647275084689641883661667'), BigInt('117390035944627627450677220413733956185864939010425'));
81
+ this.APP_KEY = 'ac21b9f9cbfe4ca5a88562ef25e2b768';
82
+ }
83
+ encrpytIAMPassword(_loginId, password) {
84
+ const m1 = (0, crypto_1.createHash)('md5').update(password);
85
+ const m2 = (0, crypto_1.createHash)('md5').update(m1.digest('hex'));
86
+ return m2.digest('hex');
87
+ }
88
+ }
89
+ exports.MeijuCloudSecurity = MeijuCloudSecurity;
90
+ MeijuCloudSecurity._LOGIN_KEY = 'ad0ee21d48a64bf49f4fb583ab76e799';
91
+ class SimpleSecurity extends CloudSecurity {
92
+ constructor(login_key) {
93
+ super(login_key);
94
+ }
95
+ encrpytIAMPassword() {
96
+ return '';
97
+ }
98
+ sign(url, query) {
99
+ const parsedUrl = new URL(url);
100
+ const path = parsedUrl.pathname;
101
+ return (0, crypto_1.createHash)('sha256')
102
+ .update(Buffer.from(`${path}${unescape_plus(query)}${this.LOGIN_KEY}`, 'ascii'))
103
+ .digest('hex');
104
+ }
105
+ }
106
+ exports.SimpleSecurity = SimpleSecurity;
107
+ class NetHomePlusSecurity extends SimpleSecurity {
108
+ constructor() {
109
+ super(NetHomePlusSecurity._LOGIN_KEY);
110
+ }
111
+ }
112
+ exports.NetHomePlusSecurity = NetHomePlusSecurity;
113
+ NetHomePlusSecurity._LOGIN_KEY = '3742e9e5842d4ad59c2db887e12449f9';
114
+ class ArtisonClimaSecurity extends SimpleSecurity {
115
+ constructor() {
116
+ super(ArtisonClimaSecurity._LOGIN_KEY);
117
+ }
118
+ }
119
+ exports.ArtisonClimaSecurity = ArtisonClimaSecurity;
120
+ ArtisonClimaSecurity._LOGIN_KEY = '434a209a5ce141c3b726de067835d7f0';
121
+ class LocalSecurity {
122
+ constructor() {
123
+ this.aes_key = Buffer.from(BigInt('141661095494369103254425781617665632877').toString(16), 'hex');
124
+ this.salt = Buffer.from(BigInt('233912452794221312800602098970898185176935770387238278451789080441632479840061417076563').toString(16), 'hex');
125
+ this.iv = Buffer.alloc(16);
126
+ this.request_count = 0;
127
+ this.response_count = 0;
128
+ }
129
+ aes_cbc_encrpyt(raw, key) {
130
+ const cipher = (0, crypto_1.createCipheriv)('aes-256-cbc', key, this.iv);
131
+ cipher.setAutoPadding(false);
132
+ return Buffer.concat([cipher.update(raw), cipher.final()]);
133
+ }
134
+ aes_cbc_decrypt(raw, key) {
135
+ const decipher = (0, crypto_1.createDecipheriv)('aes-256-cbc', key, this.iv);
136
+ decipher.setAutoPadding(false);
137
+ return Buffer.concat([decipher.update(raw), decipher.final()]);
138
+ }
139
+ aes_encrypt(data) {
140
+ const cipher = (0, crypto_1.createCipheriv)('aes-128-ecb', this.aes_key, null);
141
+ return Buffer.concat([cipher.update(data), cipher.final()]);
142
+ }
143
+ aes_decrypt(data) {
144
+ const decipher = (0, crypto_1.createDecipheriv)('aes-128-ecb', this.aes_key, null);
145
+ return Buffer.concat([decipher.update(data), decipher.final()]);
146
+ }
147
+ encode32_data(raw) {
148
+ return (0, crypto_1.createHash)('md5')
149
+ .update(Buffer.concat([raw, this.salt]))
150
+ .digest();
151
+ }
152
+ tcp_key_from_resp(response, key) {
153
+ if (response.toString() === 'ERROR') {
154
+ throw Error('Authentication response is ERROR, cannot get TCP key.');
155
+ }
156
+ if (response.length !== 64) {
157
+ throw Error('Authentication response has unexpected data length, cannot get TCP key..');
158
+ }
159
+ const payload = response.subarray(0, 32);
160
+ const sign = response.subarray(32, response.length);
161
+ const plain = this.aes_cbc_decrypt(payload, key);
162
+ if ((0, crypto_1.createHash)('sha256').update(plain).digest().compare(sign) !== 0) {
163
+ throw Error('Authentication sign does not match, cannot get TCP key.');
164
+ }
165
+ this.tcp_key = (0, MideaUtils_1.strxor)(plain, key);
166
+ this.request_count = 0;
167
+ this.response_count = 0;
168
+ return this.tcp_key;
169
+ }
170
+ encode_8370(data, message_type) {
171
+ let header = Buffer.from([0x83, 0x70]);
172
+ let size = data.length;
173
+ let padding = 0;
174
+ if (message_type === MideaConstants_1.TCPMessageType.ENCRYPTED_REQUEST || message_type === MideaConstants_1.TCPMessageType.ENCRYPTED_RESPONSE) {
175
+ if ((size + 2) % 16 !== 0) {
176
+ padding = 16 - ((size + 2) & 0xf);
177
+ size += padding + 32;
178
+ data = Buffer.concat([data, (0, crypto_1.randomBytes)(padding)]);
179
+ }
180
+ }
181
+ header = Buffer.concat([header, (0, MideaUtils_1.numberToUint8Array)(size, 2, MideaConstants_1.Endianness.Big)]);
182
+ header = Buffer.concat([header, Buffer.from([0x20, (padding << 4) | message_type])]);
183
+ data = Buffer.concat([(0, MideaUtils_1.numberToUint8Array)(this.request_count, 2, MideaConstants_1.Endianness.Big), data]);
184
+ this.request_count += 1;
185
+ if (this.request_count >= 0xffff) {
186
+ this.request_count = 0;
187
+ }
188
+ if (message_type === MideaConstants_1.TCPMessageType.ENCRYPTED_REQUEST || message_type === MideaConstants_1.TCPMessageType.ENCRYPTED_RESPONSE) {
189
+ const sign = (0, crypto_1.createHash)('sha256')
190
+ .update(Buffer.concat([header, data]))
191
+ .digest();
192
+ data = Buffer.concat([this.aes_cbc_encrpyt(data, this.tcp_key), sign]);
193
+ }
194
+ return Buffer.concat([header, data]);
195
+ }
196
+ decode_8370(data) {
197
+ if (data.length < 6) {
198
+ return [[], data];
199
+ }
200
+ const header = data.subarray(0, 6);
201
+ if (header[0] !== 0x83 || header[1] !== 0x70) {
202
+ throw new Error('Not an 8370 message.');
203
+ }
204
+ const size = header.subarray(2, 4).readUInt16BE() + 8;
205
+ let leftover = Buffer.alloc(0);
206
+ if (data.length < size) {
207
+ return [[], data];
208
+ }
209
+ else if (data.length > size) {
210
+ leftover = data.subarray(size, data.length);
211
+ data = data.subarray(0, size);
212
+ }
213
+ if (header[4] !== 0x20) {
214
+ throw new Error('Missing byte 4');
215
+ }
216
+ const padding = header[5] >> 4;
217
+ const message_type_received = header[5] & 0xf;
218
+ data = data.subarray(6, data.length);
219
+ if ([MideaConstants_1.TCPMessageType.ENCRYPTED_RESPONSE, MideaConstants_1.TCPMessageType.ENCRYPTED_REQUEST].includes(message_type_received)) {
220
+ const sign = data.subarray(data.length - 32, data.length);
221
+ data = data.subarray(0, data.length - 32);
222
+ data = this.aes_cbc_decrypt(data, this.tcp_key);
223
+ if ((0, crypto_1.createHash)('sha256')
224
+ .update(Buffer.concat([header, data]))
225
+ .digest()
226
+ .compare(sign) !== 0) {
227
+ throw new Error('Sign does not match');
228
+ }
229
+ if (padding) {
230
+ data = data.subarray(0, data.length - padding);
231
+ }
232
+ }
233
+ this.response_count = data.subarray(0, 2).readUInt16BE();
234
+ data = data.subarray(2, data.length);
235
+ if (leftover.length > 0) {
236
+ const [packets, incomplete] = this.decode_8370(leftover);
237
+ return [[data, ...packets], incomplete];
238
+ }
239
+ return [[data], leftover];
240
+ }
241
+ }
242
+ exports.LocalSecurity = LocalSecurity;
243
243
  //# sourceMappingURL=MideaSecurity.js.map
@@ -1,33 +1,33 @@
1
- /// <reference types="node" />
2
- /***********************************************************************
3
- * Homebridge-midea-platform miscellaneous support functions.
4
- *
5
- * Copyright (c) 2023 Kovalovszky Patrik, https://github.com/kovapatrik
6
- * Portions Copyright (c) 2023 David Kerr, https://github.com/dkerr64
7
- *
8
- * Includes very basic implementation of a promise-wrapped Socket class.
9
- *
10
- */
11
- import { Logger } from 'homebridge';
12
- import { Endianness } from './MideaConstants';
13
- export declare function numberToUint8Array(num: number, byte_length: number, endianness: Endianness): Uint8Array;
14
- export declare function strxor(a: Buffer, b: Buffer): Buffer;
15
- export declare function calculate(data: Buffer): number;
16
- /*********************************************************************
17
- * PromiseSocket
18
- * A very basic implementation of promise-wrapped Socket
19
- *
20
- */
21
- export declare class PromiseSocket {
22
- private readonly logger;
23
- private readonly logerror;
24
- private innerSok;
25
- destroyed: boolean;
26
- constructor(logger: Logger, logerror: boolean);
27
- connect(port: number, host: string): Promise<void>;
28
- setTimeout(t: number): void;
29
- destroy(): void;
30
- write(data: string | Buffer, encoding?: BufferEncoding): Promise<void>;
31
- read(): Promise<Buffer>;
32
- }
1
+ /// <reference types="node" />
2
+ /***********************************************************************
3
+ * Homebridge-midea-platform miscellaneous support functions.
4
+ *
5
+ * Copyright (c) 2023 Kovalovszky Patrik, https://github.com/kovapatrik
6
+ * Portions Copyright (c) 2023 David Kerr, https://github.com/dkerr64
7
+ *
8
+ * Includes very basic implementation of a promise-wrapped Socket class.
9
+ *
10
+ */
11
+ import { Logger } from 'homebridge';
12
+ import { Endianness } from './MideaConstants';
13
+ export declare function numberToUint8Array(num: number, byte_length: number, endianness: Endianness): Uint8Array;
14
+ export declare function strxor(a: Buffer, b: Buffer): Buffer;
15
+ export declare function calculate(data: Buffer): number;
16
+ /*********************************************************************
17
+ * PromiseSocket
18
+ * A very basic implementation of promise-wrapped Socket
19
+ *
20
+ */
21
+ export declare class PromiseSocket {
22
+ private readonly logger;
23
+ private readonly logerror;
24
+ private innerSok;
25
+ destroyed: boolean;
26
+ constructor(logger: Logger, logerror: boolean);
27
+ connect(port: number, host: string): Promise<void>;
28
+ setTimeout(t: number): void;
29
+ destroy(): void;
30
+ write(data: string | Buffer, encoding?: BufferEncoding): Promise<void>;
31
+ read(): Promise<Buffer>;
32
+ }
33
33
  //# sourceMappingURL=MideaUtils.d.ts.map