Skip to content

Commit 9ea40d7

Browse files
authored
Slight refactoring, add proper CRC support (#183)
* Terse defaults (#162) * Replaced manual argument default checking with ES6 style default arguments and destructuring assignments Improves code clarity through easy to see defaults and reduced boilerplate Tiny tweaks for terse code; less code, less reading, less maintenance :) * Fixed namespace collision with timeout accidentally introduced in last commit * Travis doesn't like how terse my code is ;P * Removed check for IP address in _send which will never be reached, since we're checking .isConnected() first * Parser cipher updates (#160) * Fixed a few parsing issues: zero-padding is actually the return code from the device where 0 = success Added _returnCode, _crc to MessageParser (reordered to match order in memory) (crc is not actually checked here, but it could be!) More restrictive length checks, taking into account _returnCode and _crc _commandByte is a uint32 Fill _leftOver with, well, leftovers Reordered parse logic to match order in memory (this also allows us to extract more data in case of a suffix mismatch) suffix indexed relative to _payloadSize, relevant in the case of leftovers (which happens when two messages are sent in one) These changes were made with the best attempt to adhere to the existing coding style I would personally make parse and encode both static functions We could check the crc value for data integrity If _leftOver is populated, better try decoding that too A more drastic overhaul can be found: https://github.com/kueblc/mocktuyacloud/blob/master/lib/lan-frame.js But no attempt has been made to maintain the previous coding style This adaptation was made to integrate seamlessly into the existing tuyapi project * Reimplemented encrypt, decrypt, and md5 using built in crypto library Removes dependency on third party node-forge library Clarified md5 documentation * Default encrypt options.base64 to true * Fix lint no-negated-condition * Cleaned up _encode implementation using one buffer to assemble payload and remove string representations of binary data * Fixed test for encrypting with base64 option disabled Had previously made a poor assumption about the meaning of the flag and hadn't realized utf8 is not binary safe * Fixed cipher.encrypt base64 option, now correctly outputs a Buffer when base64 = false * Throw appropriate errors when parsing fails Replaced 16 with a constant HEADER_SIZE for better code readability Cleverly account for return code or lack thereof by checking if the first 3 bytes are zero, which they often (always?) are for return code Moving forward, we should decide whether this parser should serve both client and device emulation * Removed unused _parsed flag * Added tests for message-parser, bringing coverage to 100 Test decoding a plaintext payload, as is the case when a device reports an error Test for several types of corrupt messages; short, way too short, prefix and suffix mismatch Test handling of two packets coming through at the same time (for the time being, we just don't catch the second message; this should change) * Removed unused crc package from dependencies * Move clone from prod dependencies to dev dependencies * Moved parse and encode from static wrapped Class methods into static functions, skipping the middle man This change touched a lot of lines due to indentation and a few renames but code was otherwise unmodified More cleanup to come once we move on to breaking changes (ie its possible to receive multiple packets in one buffer, should therefore change parse to return an array of parsed packets) (this will require touching index.js and so is left for another revision) We can also return more information about the packet than we currently are (ie sequence index, return code) We can also generate and check CRCs (to be ported from MockTuyaCloud) * Pin dependency clone to 2.1.2 (#167) This PR contains the following updates: | Package | Type | Update | Change | References | |---|---|---|---|---| | clone | devDependencies | pin | `^2.1.2` -> `2.1.2` | [source](https://togithub.com/pvorb/node-clone) | :pushpin: **Important**: Renovate will wait until you have merged this Pin PR before creating any *upgrade* PRs for the affected packages. Add the preset `:preserveSemverRanges` your config if you instead don't wish to pin dependencies. --- ### Renovate configuration :date: **Schedule**: At any time (no schedule defined). :vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied. :recycle: **Rebasing**: Whenever PR is stale, or if you modify the PR title to begin with "`rebase!`". :ghost: **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/config-help/issues) if that's undesired. --- - [ ] <!-- renovate-rebase -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://togithub.com/marketplace/renovate). View repository job log [here](https://renovatebot.com/dashboard#codetheweb/tuyapi). * Update dependency ava to v1.3.0 (#170) This PR contains the following updates: | Package | Type | Update | Change | References | |---|---|---|---|---| | ava | devDependencies | minor | `1.2.1` -> `1.3.0` | [homepage](https://ava.li), [source](https://togithub.com/avajs/ava) | --- ### Release Notes <details> <summary>avajs/ava</summary> ### [`v1.3.0`](https://togithub.com/avajs/ava/releases/v1.3.0) [Compare Source](https://togithub.com/avajs/ava/compare/v1.2.1...v1.3.0) #### Bug fixes - We've fixed a rather embarrasing bug with `t.throws()` and `t.throwsAsync()`. If you'd set a `code` expectation to a number we never actually _checked_ that the thrown error had such a code! Thanks to [@&#8203;qlonik](https://togithub.com/qlonik) for both spotting and fixing this. [`82daa5e`](https://togithub.com/avajs/ava/commit/82daa5e373ef0587693927acc63ce5590e8d8eb2) - 1.2.0 contained a regression which meant that if you [faked](https://www.npmjs.com/package/lolex) `clearTimeout()`, you'd break AVA. That's now been fixed. [`40f331c`](https://togithub.com/avajs/ava/commit/40f331c27e70690e724f9ddc893fed0556058987) - Snapshot files are now recognized as source files, so if you're using watch mode and you delete one, AVA won't rerun _all_ your test files. [`d066f6f`](https://togithub.com/avajs/ava/commit/d066f6fbc06ab2fa2d391ff373f5f09b2585b900) #### New features You can now use `require()` in `ava.config.js` files to load non-ES modules. [`334e15b`](https://togithub.com/avajs/ava/commit/334e15b4af06492c9aed2800a0764f245d6a908b) #### All changes [`v1.2.1...v1.3.0`](https://togithub.com/avajs/ava/compare/v1.2.1...v1.3.0) #### Thanks Thank you [@&#8203;itaisteinherz](https://togithub.com/itaisteinherz), [@&#8203;jdalton](https://togithub.com/jdalton), [@&#8203;kagawagao](https://togithub.com/kagawagao), [@&#8203;KompKK](https://togithub.com/KompKK), [@&#8203;SleeplessByte](https://togithub.com/SleeplessByte), [@&#8203;Chrisyee22](https://togithub.com/Chrisyee22) and [@&#8203;qlonik](https://togithub.com/qlonik) for helping us with this release. We couldn't have done this without you! #### Get involved We welcome new contributors. AVA is a friendly place to get started in open source. We have a [great article](https://medium.com/@&#8203;vadimdemedes/making-your-first-contribution-de6576ddb190#.umxr7id07) on getting started contributing and a comprehensive [contributing guide](https://togithub.com/avajs/ava/blob/master/contributing.md). </details> --- ### Renovate configuration :date: **Schedule**: At any time (no schedule defined). :vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied. :recycle: **Rebasing**: Whenever PR becomes conflicted, or if you modify the PR title to begin with "`rebase!`". :no_bell: **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- renovate-rebase -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://togithub.com/marketplace/renovate). View repository job log [here](https://renovatebot.com/dashboard#codetheweb/tuyapi). * Update dependency ava to v1.3.1 (#171) This PR contains the following updates: | Package | Type | Update | Change | References | |---|---|---|---|---| | ava | devDependencies | patch | `1.3.0` -> `1.3.1` | [homepage](https://ava.li), [source](https://togithub.com/avajs/ava) | --- ### Release Notes <details> <summary>avajs/ava</summary> ### [`v1.3.1`](https://togithub.com/avajs/ava/releases/v1.3.1) [Compare Source](https://togithub.com/avajs/ava/compare/v1.3.0...v1.3.1) #### Bug fixes - We've fixed a rather embarrasing bug with `t.throws()` and `t.throwsAsync()`. If you'd set a `code` expectation to a number we never actually _checked_ that the thrown error had such a code! Thanks to [@&#8203;qlonik](https://togithub.com/qlonik) for both spotting and fixing this. [`82daa5e`](https://togithub.com/avajs/ava/commit/82daa5e373ef0587693927acc63ce5590e8d8eb2) - 1.2.0 contained a regression which meant that if you [faked](https://www.npmjs.com/package/lolex) `clearTimeout()`, you'd break AVA. That's now been fixed. [`40f331c`](https://togithub.com/avajs/ava/commit/40f331c27e70690e724f9ddc893fed0556058987) - Snapshot files are now recognized as source files, so if you're using watch mode and you delete one, AVA won't rerun _all_ your test files. [`d066f6f`](https://togithub.com/avajs/ava/commit/d066f6fbc06ab2fa2d391ff373f5f09b2585b900) #### New features You can now use `require()` in `ava.config.js` files to load non-ES modules. [`334e15b`](https://togithub.com/avajs/ava/commit/334e15b4af06492c9aed2800a0764f245d6a908b) #### All changes [`v1.2.1...v1.3.1`](https://togithub.com/avajs/ava/compare/v1.2.1...v1.3.1) #### Thanks Thank you [@&#8203;itaisteinherz](https://togithub.com/itaisteinherz), [@&#8203;jdalton](https://togithub.com/jdalton), [@&#8203;kagawagao](https://togithub.com/kagawagao), [@&#8203;KompKK](https://togithub.com/KompKK), [@&#8203;SleeplessByte](https://togithub.com/SleeplessByte), [@&#8203;Chrisyee22](https://togithub.com/Chrisyee22) and [@&#8203;qlonik](https://togithub.com/qlonik) for helping us with this release. We couldn't have done this without you! #### Get involved We welcome new contributors. AVA is a friendly place to get started in open source. We have a [great article](https://medium.com/@&#8203;vadimdemedes/making-your-first-contribution-de6576ddb190#.umxr7id07) on getting started contributing and a comprehensive [contributing guide](https://togithub.com/avajs/ava/blob/master/contributing.md). </details> --- ### Renovate configuration :date: **Schedule**: At any time (no schedule defined). :vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied. :recycle: **Rebasing**: Whenever PR becomes conflicted, or if you modify the PR title to begin with "`rebase!`". :no_bell: **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- renovate-rebase -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://togithub.com/marketplace/renovate). View repository job log [here](https://renovatebot.com/dashboard#codetheweb/tuyapi). * Update dependency p-retry to v4 (#172) This PR contains the following updates: | Package | Type | Update | Change | References | |---|---|---|---|---| | p-retry | dependencies | major | [`3.0.1` -> `4.0.0`](https://diff.intrinsic.com/p-retry/3.0.1/4.0.0) | [source](https://togithub.com/sindresorhus/p-retry) | --- ### Release Notes <details> <summary>sindresorhus/p-retry</summary> ### [`v4.0.0`](https://togithub.com/sindresorhus/p-retry/releases/v4.0.0) [Compare Source](https://togithub.com/sindresorhus/p-retry/compare/v3.0.1...v4.0.0) Breaking: - Require Node.js 8 [`0ed8d5d`](https://togithub.com/sindresorhus/p-retry/commit/0ed8d5d) ([#&#8203;21](https://togithub.com/sindresorhus/p-retry/issues/21)) [`4b6ec19`](https://togithub.com/sindresorhus/p-retry/commit/4b6ec19) Enhancements: - Throw a useful error if a non-error is thrown [`a9d75a5`](https://togithub.com/sindresorhus/p-retry/commit/a9d75a5) - Add TypeScript definition ([#&#8203;21](https://togithub.com/sindresorhus/p-retry/issues/21)) [`4b6ec19`](https://togithub.com/sindresorhus/p-retry/commit/4b6ec19) </details> --- ### Renovate configuration :date: **Schedule**: At any time (no schedule defined). :vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied. :recycle: **Rebasing**: Whenever PR becomes conflicted, or if you modify the PR title to begin with "`rebase!`". :no_bell: **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- renovate-rebase -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://togithub.com/marketplace/renovate). View repository job log [here](https://renovatebot.com/dashboard#codetheweb/tuyapi). * Update dependency p-timeout to v3 (#173) This PR contains the following updates: | Package | Type | Update | Change | References | |---|---|---|---|---| | p-timeout | dependencies | major | [`2.0.1` -> `3.0.0`](https://diff.intrinsic.com/p-timeout/2.0.1/3.0.0) | [source](https://togithub.com/sindresorhus/p-timeout) | --- ### Release Notes <details> <summary>sindresorhus/p-timeout</summary> ### [`v3.0.0`](https://togithub.com/sindresorhus/p-timeout/releases/v3.0.0) [Compare Source](https://togithub.com/sindresorhus/p-timeout/compare/v2.0.1...v3.0.0) Breaking: - Require Node.js 8 ([#&#8203;10](https://togithub.com/sindresorhus/p-timeout/issues/10)) [`9a429bc`](https://togithub.com/sindresorhus/p-timeout/commit/9a429bc) Enhancements: - Add TypeScript definition ([#&#8203;10](https://togithub.com/sindresorhus/p-timeout/issues/10)) [`9a429bc`](https://togithub.com/sindresorhus/p-timeout/commit/9a429bc) </details> --- ### Renovate configuration :date: **Schedule**: At any time (no schedule defined). :vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied. :recycle: **Rebasing**: Whenever PR becomes conflicted, or if you modify the PR title to begin with "`rebase!`". :no_bell: **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- renovate-rebase -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://togithub.com/marketplace/renovate). View repository job log [here](https://renovatebot.com/dashboard#codetheweb/tuyapi). * Update dependency documentation to v9.3.1 (#174) This PR contains the following updates: | Package | Type | Update | Change | References | |---|---|---|---|---| | documentation | devDependencies | minor | [`9.2.0` -> `9.3.1`](https://diff.intrinsic.com/documentation/9.2.0/9.3.1) | [source](https://togithub.com/documentationjs/documentation) | --- ### Release Notes <details> <summary>documentationjs/documentation</summary> ### [`v9.3.1`](https://togithub.com/documentationjs/documentation/blob/master/CHANGELOG.md#&#8203;1000-alpha0httpsgithubcomdocumentationjsdocumentationcomparev931v1000-alpha0-2019-03-12) [Compare Source](https://togithub.com/documentationjs/documentation/compare/v9.3.0...v9.3.1) ##### Features - Support custom babel config ([#&#8203;1205](https://togithub.com/documentationjs/documentation/issues/1205)) ([746d0a9](https://togithub.com/documentationjs/documentation/commit/746d0a9)) ##### BREAKING CHANGES - this may change babel configuration loading, and is a major change to the documentation.js approach to Babel. #### [9.3.1](https://togithub.com/documentationjs/documentation/compare/v9.3.0...v9.3.1) (2019-03-12) ### [`v9.3.0`](https://togithub.com/documentationjs/documentation/blob/master/CHANGELOG.md#&#8203;930httpsgithubcomdocumentationjsdocumentationcomparev921v930-2019-02-27) [Compare Source](https://togithub.com/documentationjs/documentation/compare/v9.2.1...v9.3.0) ##### Features - Add inner section ([#&#8203;1212](https://togithub.com/documentationjs/documentation/issues/1212)) ([64c9ca6](https://togithub.com/documentationjs/documentation/commit/64c9ca6)) #### [9.2.1](https://togithub.com/documentationjs/documentation/compare/v9.2.0...v9.2.1) (2019-02-26) ### [`v9.2.1`](https://togithub.com/documentationjs/documentation/blob/master/CHANGELOG.md#&#8203;930httpsgithubcomdocumentationjsdocumentationcomparev921v930-2019-02-27) [Compare Source](https://togithub.com/documentationjs/documentation/compare/v9.2.0...v9.2.1) ##### Features - Add inner section ([#&#8203;1212](https://togithub.com/documentationjs/documentation/issues/1212)) ([64c9ca6](https://togithub.com/documentationjs/documentation/commit/64c9ca6)) #### [9.2.1](https://togithub.com/documentationjs/documentation/compare/v9.2.0...v9.2.1) (2019-02-26) </details> --- ### Renovate configuration :date: **Schedule**: At any time (no schedule defined). :vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied. :recycle: **Rebasing**: Whenever PR becomes conflicted, or if you modify the PR title to begin with "`rebase!`". :no_bell: **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- renovate-rebase -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://togithub.com/marketplace/renovate). View repository job log [here](https://renovatebot.com/dashboard#codetheweb/tuyapi). * Update dependency ava to v1.4.0 (#177) This PR contains the following updates: | Package | Type | Update | Change | References | |---|---|---|---|---| | ava | devDependencies | minor | [`1.3.1` -> `1.4.0`](https://diff.intrinsic.com/ava/1.3.1/1.4.0) | [homepage](https://ava.li), [source](https://togithub.com/avajs/ava) | --- ### Release Notes <details> <summary>avajs/ava</summary> ### [`v1.4.0`](https://togithub.com/avajs/ava/releases/v1.4.0) [Compare Source](https://togithub.com/avajs/ava/compare/v1.3.1...v1.4.0) #### Focusing [`power-assert`](https://togithub.com/power-assert-js/power-assert) AVA comes with [`power-assert`](https://togithub.com/power-assert-js/power-assert) built-in, giving you more descriptive assertion messages. However it's been confusing to understand _which_ assertions come with `power-assert`. To address this we've added the new `t.assert()` assertion. It's now the only assertion that is `power-assert` enabled. The assertion passes if called with a truthy value. Consider this example: ```js test('enhanced assertions', t => { const a = /foo/; const b = 'bar'; const c = 'baz'; t.assert(a.test(b) || b === c); }); ``` <details> <summary>AVA will output:</summary> <pre><code>6: const c = 'baz'; 7: t.assert(a.test(b) || b === c); 8: }); Value is not truthy: false a.test(b) || b === c => false b === c => false c => 'baz' b => 'bar' a.test(b) => false b => 'bar' a => /foo/</code></pre> </details> Our [ESLint plugin](https://togithub.com/avajs/eslint-plugin-ava) has been updated to support this new assertion. Many thanks to [@&#8203;eemed](https://togithub.com/eemed) for implementing this! [`9406470`](https://togithub.com/avajs/ava/commit/94064702837583f1cd3920142c5d0ce50e71e255) #### Watch mode Watch mode now prints the available commands. Thanks [@&#8203;KompKK](https://togithub.com/KompKK)! [`cd256ac`](https://togithub.com/avajs/ava/commit/cd256ac53c975d51ddabd3d80a9f909424f5d7e3) #### Bug fixes - Filtered tests (when using `--match`, `.skip()` or `.only()`) are no longer included in the list of pending tests when timeouts occur or when you interrupt a test run. Thanks [@&#8203;vancouverwill](https://togithub.com/vancouverwill)! [`23e302a`](https://togithub.com/avajs/ava/commit/23e302a8dc35d03ba82916bd6591822a28d499d1) - We're now shimming all TTY methods in the worker processes, thanks to [@&#8203;okyantoro](https://togithub.com/okyantoro). [`c1f6fdf`](https://togithub.com/avajs/ava/commit/c1f6fdfed61c99c1144cff56b58d113810c630c8) #### Documentation updates - We've added a note to say that, by default, AVA does not have a default test timeout. Thanks [@&#8203;amokmen](https://togithub.com/amokmen)! [`99a10a1`](https://togithub.com/avajs/ava/commit/99a10a16a3e3037326c91fb23c2a052acd3abef9) #### All changes [`v1.3.1...v1.4.0`](https://togithub.com/avajs/ava/compare/v1.3.1...v1.4.0) #### Thanks Thank you [@&#8203;eemed](https://togithub.com/eemed), [@&#8203;KompKK](https://togithub.com/KompKK), [@&#8203;vancouverwill](https://togithub.com/vancouverwill), [@&#8203;okyantoro](https://togithub.com/okyantoro) and [@&#8203;amokmen](https://togithub.com/amokmen). We couldn't have done this without you! #### Get involved We welcome new contributors. AVA is a friendly place to get started in open source. We have a [great article](https://medium.com/@&#8203;vadimdemedes/making-your-first-contribution-de6576ddb190#.umxr7id07) on getting started contributing and a comprehensive [contributing guide](https://togithub.com/avajs/ava/blob/master/contributing.md). </details> --- ### Renovate configuration :date: **Schedule**: At any time (no schedule defined). :vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied. :recycle: **Rebasing**: Whenever PR becomes conflicted, or if you modify the PR title to begin with "`rebase!`". :no_bell: **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- renovate-rebase -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://togithub.com/marketplace/renovate). View repository job log [here](https://renovatebot.com/dashboard#codetheweb/tuyapi). * Implement Tuya flavored CRC (#178) * Fixed a few parsing issues: zero-padding is actually the return code from the device where 0 = success Added _returnCode, _crc to MessageParser (reordered to match order in memory) (crc is not actually checked here, but it could be!) More restrictive length checks, taking into account _returnCode and _crc _commandByte is a uint32 Fill _leftOver with, well, leftovers Reordered parse logic to match order in memory (this also allows us to extract more data in case of a suffix mismatch) suffix indexed relative to _payloadSize, relevant in the case of leftovers (which happens when two messages are sent in one) These changes were made with the best attempt to adhere to the existing coding style I would personally make parse and encode both static functions We could check the crc value for data integrity If _leftOver is populated, better try decoding that too A more drastic overhaul can be found: https://github.com/kueblc/mocktuyacloud/blob/master/lib/lan-frame.js But no attempt has been made to maintain the previous coding style This adaptation was made to integrate seamlessly into the existing tuyapi project * Reimplemented encrypt, decrypt, and md5 using built in crypto library Removes dependency on third party node-forge library Clarified md5 documentation * Default encrypt options.base64 to true * Fix lint no-negated-condition * Cleaned up _encode implementation using one buffer to assemble payload and remove string representations of binary data * Replaced manual argument default checking with ES6 style default arguments and destructuring assignments Improves code clarity through easy to see defaults and reduced boilerplate Tiny tweaks for terse code; less code, less reading, less maintenance :) * Fixed namespace collision with timeout accidentally introduced in last commit * Travis doesn't like how terse my code is ;P * Fixed test for encrypting with base64 option disabled Had previously made a poor assumption about the meaning of the flag and hadn't realized utf8 is not binary safe * Fixed cipher.encrypt base64 option, now correctly outputs a Buffer when base64 = false * Throw appropriate errors when parsing fails Replaced 16 with a constant HEADER_SIZE for better code readability Cleverly account for return code or lack thereof by checking if the first 3 bytes are zero, which they often (always?) are for return code Moving forward, we should decide whether this parser should serve both client and device emulation * Removed unused _parsed flag * Added tests for message-parser, bringing coverage to 100 Test decoding a plaintext payload, as is the case when a device reports an error Test for several types of corrupt messages; short, way too short, prefix and suffix mismatch Test handling of two packets coming through at the same time (for the time being, we just don't catch the second message; this should change) * Removed unused crc package from dependencies * Move clone from prod dependencies to dev dependencies * Moved parse and encode from static wrapped Class methods into static functions, skipping the middle man This change touched a lot of lines due to indentation and a few renames but code was otherwise unmodified More cleanup to come once we move on to breaking changes (ie its possible to receive multiple packets in one buffer, should therefore change parse to return an array of parsed packets) (this will require touching index.js and so is left for another revision) We can also return more information about the packet than we currently are (ie sequence index, return code) We can also generate and check CRCs (to be ported from MockTuyaCloud) * Removed check for IP address in _send which will never be reached, since we're checking .isConnected() first * Added implementation of Tuya's modified CRC32 algorithm * Restore package-lock from upstream/development * Fix lint warnings * Integrate CRC check in message-parser * Update dependency ava to v1.4.1 (#179) This PR contains the following updates: | Package | Type | Update | Change | References | |---|---|---|---|---| | ava | devDependencies | patch | [`1.4.0` -> `1.4.1`](https://diff.intrinsic.com/ava/1.4.0/1.4.1) | [homepage](https://ava.li), [source](https://togithub.com/avajs/ava) | --- ### Release Notes <details> <summary>avajs/ava</summary> ### [`v1.4.1`](https://togithub.com/avajs/ava/releases/v1.4.1) [Compare Source](https://togithub.com/avajs/ava/compare/v1.4.0...v1.4.1) #### Focusing [`power-assert`](https://togithub.com/power-assert-js/power-assert) AVA comes with [`power-assert`](https://togithub.com/power-assert-js/power-assert) built-in, giving you more descriptive assertion messages. However it's been confusing to understand _which_ assertions come with `power-assert`. To address this we've added the new `t.assert()` assertion. It's now the only assertion that is `power-assert` enabled. The assertion passes if called with a truthy value. Consider this example: ```js test('enhanced assertions', t => { const a = /foo/; const b = 'bar'; const c = 'baz'; t.assert(a.test(b) || b === c); }); ``` <details> <summary>AVA will output:</summary> <pre><code>6: const c = 'baz'; 7: t.assert(a.test(b) || b === c); 8: }); Value is not truthy: false a.test(b) || b === c => false b === c => false c => 'baz' b => 'bar' a.test(b) => false b => 'bar' a => /foo/</code></pre> </details> Our [ESLint plugin](https://togithub.com/avajs/eslint-plugin-ava) has been updated to support this new assertion. Many thanks to [@&#8203;eemed](https://togithub.com/eemed) for implementing this! [`9406470`](https://togithub.com/avajs/ava/commit/94064702837583f1cd3920142c5d0ce50e71e255) #### Watch mode Watch mode now prints the available commands. Thanks [@&#8203;KompKK](https://togithub.com/KompKK)! [`cd256ac`](https://togithub.com/avajs/ava/commit/cd256ac53c975d51ddabd3d80a9f909424f5d7e3) #### Bug fixes - Filtered tests (when using `--match`, `.skip()` or `.only()`) are no longer included in the list of pending tests when timeouts occur or when you interrupt a test run. Thanks [@&#8203;vancouverwill](https://togithub.com/vancouverwill)! [`23e302a`](https://togithub.com/avajs/ava/commit/23e302a8dc35d03ba82916bd6591822a28d499d1) - We're now shimming all TTY methods in the worker processes, thanks to [@&#8203;okyantoro](https://togithub.com/okyantoro). [`c1f6fdf`](https://togithub.com/avajs/ava/commit/c1f6fdfed61c99c1144cff56b58d113810c630c8) #### Documentation updates - We've added a note to say that, by default, AVA does not have a default test timeout. Thanks [@&#8203;amokmen](https://togithub.com/amokmen)! [`99a10a1`](https://togithub.com/avajs/ava/commit/99a10a16a3e3037326c91fb23c2a052acd3abef9) #### All changes [`v1.3.1...v1.4.1`](https://togithub.com/avajs/ava/compare/v1.3.1...v1.4.1) #### Thanks Thank you [@&#8203;eemed](https://togithub.com/eemed), [@&#8203;KompKK](https://togithub.com/KompKK), [@&#8203;vancouverwill](https://togithub.com/vancouverwill), [@&#8203;okyantoro](https://togithub.com/okyantoro) and [@&#8203;amokmen](https://togithub.com/amokmen). We couldn't have done this without you! #### Get involved We welcome new contributors. AVA is a friendly place to get started in open source. We have a [great article](https://medium.com/@&#8203;vadimdemedes/making-your-first-contribution-de6576ddb190#.umxr7id07) on getting started contributing and a comprehensive [contributing guide](https://togithub.com/avajs/ava/blob/master/contributing.md). </details> --- ### Renovate configuration :date: **Schedule**: At any time (no schedule defined). :vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied. :recycle: **Rebasing**: Whenever PR becomes conflicted, or if you modify the PR title to begin with "`rebase!`". :no_bell: **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- renovate-rebase -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://togithub.com/marketplace/renovate). View repository job log [here](https://renovatebot.com/dashboard#codetheweb/tuyapi). * Fix CRC computation; wrong payload range (my bad! no coding before coffee) (#182) * Update dependency p-retry to v4.1.0 (#181)
1 parent fba0db6 commit 9ea40d7

File tree

8 files changed

+1049
-662
lines changed

8 files changed

+1049
-662
lines changed

index.js

Lines changed: 20 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
const dgram = require('dgram');
33
const net = require('net');
44
const {EventEmitter} = require('events');
5-
const timeout = require('p-timeout');
5+
const pTimeout = require('p-timeout');
66
const pRetry = require('p-retry');
77
const debug = require('debug')('TuyAPI');
88

@@ -31,41 +31,24 @@ const Parser = require('./lib/message-parser');
3131
* key: 'xxxxxxxxxxxxxxxx'})
3232
*/
3333
class TuyaDevice extends EventEmitter {
34-
constructor(options) {
34+
constructor({ip, port = 6668, id, gwID = id, key, productKey, version = 3.1} = {}) {
3535
super();
3636

3737
// Set device to user-passed options
38-
this.device = options;
39-
40-
// Default version (necessary for later checks)
41-
if (this.device.version === undefined) {
42-
this.device.version = 3.1;
43-
}
38+
this.device = {ip, port, id, gwID, key, productKey, version};
4439

4540
// Check arguments
46-
if (!(this.checkIfValidString(this.device.id) ||
47-
this.checkIfValidString(this.device.ip))) {
41+
if (!(this.checkIfValidString(id) ||
42+
this.checkIfValidString(ip))) {
4843
throw new TypeError('ID and IP are missing from device.');
4944
}
5045

51-
if (this.checkIfValidString(this.device.key) && this.device.key.length === 16) {
52-
// Create cipher from key
53-
this.device.cipher = new Cipher({
54-
key: this.device.key,
55-
version: this.device.version
56-
});
57-
} else {
46+
if (!this.checkIfValidString(key) || key.length !== 16) {
5847
throw new TypeError('Key is missing or incorrect.');
5948
}
6049

61-
// Defaults
62-
if (this.device.port === undefined) {
63-
this.device.port = 6668;
64-
}
65-
66-
if (this.device.gwID === undefined) {
67-
this.device.gwID = this.device.id;
68-
}
50+
// Create cipher from key
51+
this.device.cipher = new Cipher({key, version});
6952

7053
// Contains array of found devices when calling .find()
7154
this.foundDevices = [];
@@ -100,10 +83,7 @@ class TuyaDevice extends EventEmitter {
10083
* @returns {Promise<Boolean|Object>}
10184
* returns boolean if single property is requested, otherwise returns object of results
10285
*/
103-
get(options) {
104-
// Set empty object as default
105-
options = options ? options : {};
106-
86+
get(options = {}) {
10787
const payload = {
10888
gwId: this.device.gwID,
10989
devId: this.device.id
@@ -195,8 +175,7 @@ class TuyaDevice extends EventEmitter {
195175
}
196176

197177
// Get time
198-
const now = new Date();
199-
const timeStamp = (parseInt(now.getTime() / 1000, 10)).toString();
178+
const timeStamp = parseInt(new Date() / 1000, 10);
200179

201180
// Construct payload
202181
const payload = {
@@ -266,11 +245,6 @@ class TuyaDevice extends EventEmitter {
266245
throw new Error('No connection has been made to the device.');
267246
}
268247

269-
// Check for IP
270-
if (typeof this.device.ip === 'undefined') {
271-
throw new TypeError('Device missing IP address.');
272-
}
273-
274248
// Retry up to 5 times
275249
return pRetry(async () => {
276250
// Send data
@@ -492,11 +466,7 @@ class TuyaDevice extends EventEmitter {
492466
* `true` if is string and length != 0, `false` otherwise.
493467
*/
494468
checkIfValidString(input) {
495-
if (input === undefined || typeof input !== typeof 'string' || input.length === 0) {
496-
return false;
497-
}
498-
499-
return true;
469+
return typeof input === 'string' && input.length > 0;
500470
}
501471

502472
/**
@@ -523,15 +493,7 @@ class TuyaDevice extends EventEmitter {
523493
* @returns {Promise<Boolean|Array>}
524494
* true if ID/IP was found and device is ready to be used
525495
*/
526-
find(options) {
527-
// Set default options
528-
options = options ? options : {};
529-
530-
// Default timeout of 10 seconds
531-
if (options.timeout === undefined) {
532-
options.timeout = 10;
533-
}
534-
496+
find({timeout = 10, all = false} = {}) {
535497
if (this.checkIfValidString(this.device.id) &&
536498
this.checkIfValidString(this.device.ip)) {
537499
// Don't need to do anything
@@ -546,7 +508,7 @@ class TuyaDevice extends EventEmitter {
546508
debug(`Finding missing IP ${this.device.ip} or ID ${this.device.id}`);
547509

548510
// Find IP for device
549-
return timeout(new Promise((resolve, reject) => { // Timeout
511+
return pTimeout(new Promise((resolve, reject) => { // Timeout
550512
listener.on('message', message => {
551513
debug('Received UDP message.');
552514

@@ -569,19 +531,15 @@ class TuyaDevice extends EventEmitter {
569531
this.foundDevices.push({id: thisID, ip: thisIP});
570532
}
571533

572-
if (!options.all &&
534+
if (!all &&
573535
(this.device.id === thisID || this.device.ip === thisIP) &&
574536
dataRes.data) {
575537
// Add IP
576538
this.device.ip = dataRes.data.ip;
577539

578-
// Add ID
540+
// Add ID and gwID
579541
this.device.id = dataRes.data.gwId;
580-
581-
// Update gwID if required
582-
if (this.device.gwID === undefined) {
583-
this.device.gwID = dataRes.data.gwId;
584-
}
542+
this.device.gwID = dataRes.data.gwId;
585543

586544
// Change product key if neccessary
587545
this.device.productKey = dataRes.data.productKey;
@@ -599,13 +557,13 @@ class TuyaDevice extends EventEmitter {
599557
listener.on('error', err => {
600558
reject(err);
601559
});
602-
}), options.timeout * 1000, () => {
560+
}), timeout * 1000, () => {
603561
// Have to do this so we exit cleanly
604562
listener.close();
605563
listener.removeAllListeners();
606564

607565
// Return all devices
608-
if (options.all) {
566+
if (all) {
609567
return this.foundDevices;
610568
}
611569

@@ -620,8 +578,8 @@ class TuyaDevice extends EventEmitter {
620578
* @param {Number} [property=1] property to toggle
621579
* @returns {Promise<Boolean>} the resulting state
622580
*/
623-
async toggle(property) {
624-
property = property === undefined ? '1' : property.toString();
581+
async toggle(property = '1') {
582+
property = property.toString();
625583

626584
try {
627585
// Get status

lib/cipher.js

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const forge = require('node-forge');
1+
const crypto = require('crypto');
22

33
/**
44
* Class for encrypting and decrypting payloads.
@@ -12,8 +12,7 @@ const forge = require('node-forge');
1212
*/
1313
class TuyaCipher {
1414
constructor(options) {
15-
this.cipher = forge.cipher.createCipher('AES-ECB', options.key);
16-
this.decipher = forge.cipher.createDecipher('AES-ECB', options.key);
15+
this.key = options.key;
1716
this.version = options.version;
1817
}
1918

@@ -27,15 +26,17 @@ class TuyaCipher {
2726
* @returns {Buffer|String} returns Buffer unless options.base64 is true
2827
*/
2928
encrypt(options) {
30-
this.cipher.start({iv: ''});
31-
this.cipher.update(forge.util.createBuffer(options.data, 'utf8'));
32-
this.cipher.finish();
29+
const cipher = crypto.createCipheriv('aes-128-ecb', this.key, '');
3330

34-
if (options.base64 !== false) {
35-
return forge.util.encode64(this.cipher.output.data);
31+
let encrypted = cipher.update(options.data, 'utf8', 'base64');
32+
encrypted += cipher.final('base64');
33+
34+
// Default base64 enable
35+
if (options.base64 === false) {
36+
return Buffer.from(encrypted, 'base64');
3637
}
3738

38-
return this.cipher.output.data;
39+
return encrypted;
3940
}
4041

4142
/**
@@ -45,24 +46,23 @@ class TuyaCipher {
4546
* returns object if data is JSON, else returns string
4647
*/
4748
decrypt(data) {
49+
// Incoming data format
50+
let format = 'buffer';
51+
4852
if (data.indexOf(this.version.toString()) !== -1) {
4953
// Data has version number and is encoded in base64
5054

5155
// Remove prefix of version number and MD5 hash
5256
data = data.slice(19);
5357

54-
// Decode data
55-
data = forge.util.decode64(data);
58+
// Decode incoming data as base64
59+
format = 'base64';
5660
}
5761

58-
// Turn data into Buffer
59-
data = forge.util.createBuffer(data);
60-
61-
this.decipher.start({iv: ''});
62-
this.decipher.update(data);
63-
this.decipher.finish();
64-
65-
const result = this.decipher.output.data;
62+
// Decrypt data
63+
const decipher = crypto.createDecipheriv('aes-128-ecb', this.key, '');
64+
let result = decipher.update(data, format, 'utf8');
65+
result += decipher.final('utf8');
6666

6767
// Try to parse data as JSON,
6868
// otherwise return as string.
@@ -76,11 +76,11 @@ class TuyaCipher {
7676
/**
7777
* Calculates a MD5 hash.
7878
* @param {String} data to hash
79-
* @returns {String} last 8 characters of hash of data
79+
* @returns {String} characters 8 through 16 of hash of data
8080
*/
8181
md5(data) {
82-
const md5hash = forge.md.md5.create().update(data).digest().toHex();
83-
return md5hash.toString().toLowerCase().substr(8, 16);
82+
const md5hash = crypto.createHash('md5').update(data, 'utf8').digest('hex');
83+
return md5hash.substr(8, 16);
8484
}
8585
}
8686
module.exports = TuyaCipher;

lib/crc.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/* Reverse engineered by kueblc */
2+
3+
/* eslint-disable array-element-newline */
4+
const crc32Table = [
5+
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
6+
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
7+
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
8+
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
9+
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
10+
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
11+
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
12+
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
13+
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
14+
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
15+
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
16+
0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
17+
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
18+
0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
19+
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
20+
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
21+
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
22+
0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
23+
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
24+
0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
25+
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
26+
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
27+
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
28+
0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
29+
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
30+
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
31+
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
32+
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
33+
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
34+
0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
35+
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
36+
0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
37+
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
38+
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
39+
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
40+
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
41+
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
42+
0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
43+
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
44+
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
45+
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
46+
0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
47+
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
48+
0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
49+
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
50+
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
51+
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
52+
0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
53+
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
54+
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
55+
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
56+
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
57+
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
58+
0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
59+
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
60+
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
61+
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
62+
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
63+
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
64+
0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
65+
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
66+
0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
67+
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
68+
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
69+
];
70+
71+
/**
72+
* Computes a Tuya flavored CRC32
73+
* @param {Iterable} data
74+
* @returns {Number} Tuya CRC32
75+
*/
76+
function crc32(bytes) {
77+
let crc = 0xFFFFFFFF;
78+
79+
for (const b of bytes) {
80+
crc = (crc >>> 8) ^ crc32Table[(crc ^ b) & 255];
81+
}
82+
83+
return crc ^ 0xFFFFFFFF;
84+
}
85+
86+
module.exports = crc32;

0 commit comments

Comments
 (0)