@willieee802/zigbee-herdsman 0.48.3 → 0.49.1

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 (1045) hide show
  1. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  2. package/.github/copilot-instructions.md +689 -0
  3. package/.github/dependabot.yml +22 -0
  4. package/.github/prompts/copilot-instructions-blueprint-generator.prompt.md +294 -0
  5. package/.github/prompts/create-agentsmd.prompt.md +249 -0
  6. package/.github/prompts/create-specification.prompt.md +127 -0
  7. package/.github/prompts/review-and-refactor.prompt.md +15 -0
  8. package/.github/prompts/update-specification.prompt.md +127 -0
  9. package/.github/workflows/ci.yml +70 -0
  10. package/.github/workflows/release-please.yml +18 -0
  11. package/.github/workflows/stale.yml +20 -0
  12. package/.github/workflows/typedoc.yaml +47 -0
  13. package/.release-please-manifest.json +2 -2
  14. package/.vscode/extensions.json +3 -0
  15. package/.vscode/settings.json +11 -0
  16. package/AGENTS.md +441 -0
  17. package/CHANGELOG.md +1283 -0
  18. package/README.md +1 -1
  19. package/biome.json +103 -0
  20. package/dist/adapter/adapter.d.ts.map +1 -1
  21. package/dist/adapter/adapterDiscovery.d.ts.map +1 -0
  22. package/dist/adapter/const.d.ts.map +1 -0
  23. package/dist/adapter/deconz/adapter/deconzAdapter.d.ts.map +1 -1
  24. package/dist/adapter/ember/adapter/emberAdapter.d.ts.map +1 -1
  25. package/dist/adapter/ember/adapter/endpoints.d.ts.map +1 -1
  26. package/dist/adapter/ember/adapter/oneWaitress.d.ts.map +1 -1
  27. package/dist/adapter/ember/adapter/tokensManager.d.ts.map +1 -1
  28. package/dist/adapter/ember/ezsp/ezsp.d.ts.map +1 -1
  29. package/dist/adapter/ember/utils/initters.d.ts.map +1 -1
  30. package/dist/adapter/events.d.ts.map +1 -1
  31. package/dist/adapter/ezsp/adapter/backup.d.ts.map +1 -1
  32. package/dist/adapter/ezsp/adapter/ezspAdapter.d.ts.map +1 -1
  33. package/dist/adapter/ezsp/driver/driver.d.ts.map +1 -1
  34. package/dist/adapter/ezsp/driver/index.d.ts.map +1 -1
  35. package/dist/adapter/ezsp/driver/multicast.d.ts.map +1 -1
  36. package/dist/adapter/index.d.ts.map +1 -1
  37. package/dist/adapter/z-stack/adapter/endpoints.d.ts.map +1 -1
  38. package/dist/adapter/z-stack/adapter/manager.d.ts.map +1 -1
  39. package/dist/adapter/z-stack/adapter/zStackAdapter.d.ts.map +1 -1
  40. package/dist/adapter/z-stack/models/startup-options.d.ts.map +1 -1
  41. package/dist/adapter/zboss/adapter/zbossAdapter.d.ts.map +1 -0
  42. package/dist/adapter/zboss/driver.d.ts.map +1 -0
  43. package/dist/adapter/zboss/frame.d.ts.map +1 -0
  44. package/dist/adapter/zboss/frame.js +200 -0
  45. package/dist/adapter/zboss/frame.js.map +1 -0
  46. package/dist/adapter/zboss/uart.d.ts.map +1 -0
  47. package/dist/adapter/zigate/adapter/zigateAdapter.d.ts.map +1 -1
  48. package/dist/adapter/zigate/driver/buffaloZiGate.d.ts.map +1 -1
  49. package/dist/adapter/zigate/driver/buffaloZiGate.js +70 -62
  50. package/dist/adapter/zigate/driver/buffaloZiGate.js.map +1 -1
  51. package/dist/adapter/zigate/driver/ziGateObject.d.ts.map +1 -1
  52. package/dist/adapter/zigate/driver/zigate.d.ts.map +1 -1
  53. package/dist/adapter/zoh/adapter/zohAdapter.d.ts.map +1 -0
  54. package/dist/controller/controller.d.ts.map +1 -1
  55. package/dist/controller/controller.js +524 -350
  56. package/dist/controller/controller.js.map +1 -1
  57. package/dist/controller/database.d.ts.map +1 -1
  58. package/dist/controller/events.d.ts.map +1 -1
  59. package/dist/controller/events.js +0 -110
  60. package/dist/controller/events.js.map +1 -1
  61. package/dist/controller/greenPower.d.ts.map +1 -1
  62. package/dist/controller/greenPower.js +330 -121
  63. package/dist/controller/greenPower.js.map +1 -1
  64. package/dist/controller/helpers/index.d.ts.map +1 -1
  65. package/dist/controller/helpers/ota.d.ts.map +1 -0
  66. package/dist/controller/helpers/ota.js +467 -0
  67. package/dist/controller/helpers/ota.js.map +1 -0
  68. package/dist/controller/helpers/request.d.ts.map +1 -1
  69. package/dist/controller/helpers/requestQueue.d.ts.map +1 -1
  70. package/dist/controller/helpers/zclFrameConverter.d.ts.map +1 -1
  71. package/dist/controller/helpers/zclFrameConverter.js +36 -22
  72. package/dist/controller/helpers/zclFrameConverter.js.map +1 -1
  73. package/dist/controller/index.d.ts.map +1 -1
  74. package/dist/controller/model/device.d.ts.map +1 -1
  75. package/dist/controller/model/device.js +1052 -402
  76. package/dist/controller/model/device.js.map +1 -1
  77. package/dist/controller/model/endpoint.d.ts.map +1 -1
  78. package/dist/controller/model/endpoint.js +447 -292
  79. package/dist/controller/model/endpoint.js.map +1 -1
  80. package/dist/controller/model/entity.d.ts.map +1 -1
  81. package/dist/controller/model/group.d.ts.map +1 -1
  82. package/dist/controller/model/group.js +202 -85
  83. package/dist/controller/model/group.js.map +1 -1
  84. package/dist/controller/model/index.d.ts.map +1 -1
  85. package/dist/controller/model/zigbeeEntity.d.ts.map +1 -0
  86. package/dist/controller/touchlink.d.ts.map +1 -1
  87. package/dist/controller/tstype.d.ts.map +1 -1
  88. package/dist/controller/tstype.js +0 -6
  89. package/dist/controller/tstype.js.map +1 -1
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/utils/timeService.d.ts.map +1 -0
  92. package/dist/utils/timeService.js +127 -0
  93. package/dist/utils/timeService.js.map +1 -0
  94. package/dist/zspec/zcl/buffaloZcl.d.ts.map +1 -1
  95. package/dist/zspec/zcl/buffaloZcl.js +327 -287
  96. package/dist/zspec/zcl/buffaloZcl.js.map +1 -1
  97. package/dist/zspec/zcl/definition/cluster.d.ts.map +1 -1
  98. package/dist/zspec/zcl/definition/cluster.js +6050 -4043
  99. package/dist/zspec/zcl/definition/cluster.js.map +1 -1
  100. package/dist/zspec/zcl/definition/clusters-types.d.ts +8135 -0
  101. package/dist/zspec/zcl/definition/clusters-types.d.ts.map +1 -0
  102. package/dist/zspec/zcl/definition/clusters-types.js +3 -0
  103. package/dist/zspec/zcl/definition/clusters-types.js.map +1 -0
  104. package/dist/zspec/zcl/definition/foundation.d.ts.map +1 -1
  105. package/dist/zspec/zcl/definition/foundation.js +170 -99
  106. package/dist/zspec/zcl/definition/foundation.js.map +1 -1
  107. package/dist/zspec/zcl/definition/tstype.d.ts +190 -31
  108. package/dist/zspec/zcl/definition/tstype.d.ts.map +1 -1
  109. package/dist/zspec/zcl/definition/tstype.js +0 -1
  110. package/dist/zspec/zcl/definition/tstype.js.map +1 -1
  111. package/dist/zspec/zcl/index.d.ts.map +1 -1
  112. package/dist/zspec/zcl/index.js +23 -13
  113. package/dist/zspec/zcl/index.js.map +1 -1
  114. package/dist/zspec/zcl/utils.d.ts.map +1 -1
  115. package/dist/zspec/zcl/utils.js +288 -103
  116. package/dist/zspec/zcl/utils.js.map +1 -1
  117. package/dist/zspec/zcl/zclFrame.d.ts.map +1 -1
  118. package/dist/zspec/zcl/zclFrame.js +121 -97
  119. package/dist/zspec/zcl/zclFrame.js.map +1 -1
  120. package/dist/zspec/zcl/zclHeader.d.ts.map +1 -1
  121. package/dist/zspec/zcl/zclHeader.js +13 -13
  122. package/dist/zspec/zcl/zclHeader.js.map +1 -1
  123. package/examples/join-and-log.js +18 -18
  124. package/package.json +23 -3
  125. package/release-please-config.json +8 -8
  126. package/scripts/check-clusters-changes.ts +328 -0
  127. package/scripts/clusters-changes.log +584 -0
  128. package/scripts/clusters-typegen.ts +608 -0
  129. package/scripts/utils.ts +88 -0
  130. package/scripts/zap-update-clusters-report.json +303 -0
  131. package/scripts/zap-update-clusters.ts +1520 -0
  132. package/scripts/zap-update-types.ts +707 -0
  133. package/scripts/zap-xml-clusters-overrides-data.ts +52 -0
  134. package/scripts/zap-xml-clusters-overrides.ts +400 -0
  135. package/scripts/zap-xml-types.ts +146 -0
  136. package/src/adapter/adapter.ts +210 -0
  137. package/src/adapter/adapterDiscovery.ts +736 -0
  138. package/src/adapter/const.ts +12 -0
  139. package/src/adapter/deconz/adapter/deconzAdapter.ts +888 -0
  140. package/src/adapter/deconz/driver/constants.ts +246 -0
  141. package/src/adapter/deconz/driver/driver.ts +1528 -0
  142. package/src/adapter/deconz/driver/frame.ts +11 -0
  143. package/src/adapter/deconz/driver/frameParser.ts +766 -0
  144. package/src/adapter/deconz/driver/parser.ts +45 -0
  145. package/src/adapter/deconz/driver/writer.ts +22 -0
  146. package/src/adapter/deconz/types.d.ts +13 -0
  147. package/src/adapter/ember/adapter/emberAdapter.ts +2262 -0
  148. package/src/adapter/ember/adapter/endpoints.ts +86 -0
  149. package/src/adapter/ember/adapter/oneWaitress.ts +324 -0
  150. package/src/adapter/ember/adapter/tokensManager.ts +780 -0
  151. package/src/adapter/ember/consts.ts +178 -0
  152. package/src/adapter/ember/enums.ts +1746 -0
  153. package/src/adapter/ember/ezsp/buffalo.ts +1392 -0
  154. package/src/adapter/ember/ezsp/consts.ts +148 -0
  155. package/src/adapter/ember/ezsp/enums.ts +1114 -0
  156. package/src/adapter/ember/ezsp/ezsp.ts +9073 -0
  157. package/src/adapter/ember/ezspError.ts +10 -0
  158. package/src/adapter/ember/types.ts +866 -0
  159. package/src/adapter/ember/uart/ash.ts +1933 -0
  160. package/src/adapter/ember/uart/consts.ts +109 -0
  161. package/src/adapter/ember/uart/enums.ts +192 -0
  162. package/src/adapter/ember/uart/parser.ts +42 -0
  163. package/src/adapter/ember/uart/queues.ts +247 -0
  164. package/src/adapter/ember/uart/writer.ts +50 -0
  165. package/src/adapter/ember/utils/initters.ts +58 -0
  166. package/src/adapter/ember/utils/math.ts +71 -0
  167. package/src/adapter/events.ts +21 -0
  168. package/src/adapter/ezsp/adapter/backup.ts +100 -0
  169. package/src/adapter/ezsp/adapter/ezspAdapter.ts +632 -0
  170. package/src/adapter/ezsp/driver/commands.ts +2497 -0
  171. package/src/adapter/ezsp/driver/consts.ts +11 -0
  172. package/src/adapter/ezsp/driver/driver.ts +1002 -0
  173. package/src/adapter/ezsp/driver/ezsp.ts +802 -0
  174. package/src/adapter/ezsp/driver/frame.ts +101 -0
  175. package/src/adapter/ezsp/driver/index.ts +4 -0
  176. package/src/adapter/ezsp/driver/multicast.ts +78 -0
  177. package/src/adapter/ezsp/driver/parser.ts +81 -0
  178. package/src/adapter/ezsp/driver/types/basic.ts +201 -0
  179. package/src/adapter/ezsp/driver/types/index.ts +239 -0
  180. package/src/adapter/ezsp/driver/types/named.ts +2330 -0
  181. package/src/adapter/ezsp/driver/types/struct.ts +844 -0
  182. package/src/adapter/ezsp/driver/uart.ts +460 -0
  183. package/src/adapter/ezsp/driver/utils/crc16ccitt.ts +44 -0
  184. package/src/adapter/ezsp/driver/utils/index.ts +32 -0
  185. package/src/adapter/ezsp/driver/writer.ts +64 -0
  186. package/src/adapter/index.ts +3 -0
  187. package/src/adapter/serialPort.ts +58 -0
  188. package/src/adapter/tstype.ts +57 -0
  189. package/src/adapter/utils.ts +41 -0
  190. package/src/adapter/z-stack/adapter/adapter-backup.ts +509 -0
  191. package/src/adapter/z-stack/adapter/adapter-nv-memory.ts +457 -0
  192. package/src/adapter/z-stack/adapter/endpoints.ts +60 -0
  193. package/src/adapter/z-stack/adapter/manager.ts +543 -0
  194. package/src/adapter/z-stack/adapter/tstype.ts +6 -0
  195. package/src/adapter/z-stack/adapter/zStackAdapter.ts +1350 -0
  196. package/src/adapter/z-stack/constants/af.ts +27 -0
  197. package/src/adapter/z-stack/constants/common.ts +285 -0
  198. package/src/adapter/z-stack/constants/dbg.ts +23 -0
  199. package/src/adapter/z-stack/constants/index.ts +11 -0
  200. package/src/adapter/z-stack/constants/mac.ts +128 -0
  201. package/src/adapter/z-stack/constants/sapi.ts +25 -0
  202. package/src/adapter/z-stack/constants/sys.ts +72 -0
  203. package/src/adapter/z-stack/constants/util.ts +82 -0
  204. package/src/adapter/z-stack/constants/utils.ts +14 -0
  205. package/src/adapter/z-stack/constants/zdo.ts +103 -0
  206. package/src/adapter/z-stack/models/startup-options.ts +13 -0
  207. package/src/adapter/z-stack/structs/entries/address-manager-entry.ts +44 -0
  208. package/src/adapter/z-stack/structs/entries/address-manager-table.ts +19 -0
  209. package/src/adapter/z-stack/structs/entries/aps-link-key-data-entry.ts +12 -0
  210. package/src/adapter/z-stack/structs/entries/aps-link-key-data-table.ts +21 -0
  211. package/src/adapter/z-stack/structs/entries/aps-tc-link-key-entry.ts +19 -0
  212. package/src/adapter/z-stack/structs/entries/aps-tc-link-key-table.ts +21 -0
  213. package/src/adapter/z-stack/structs/entries/channel-list.ts +8 -0
  214. package/src/adapter/z-stack/structs/entries/has-configured.ts +16 -0
  215. package/src/adapter/z-stack/structs/entries/index.ts +16 -0
  216. package/src/adapter/z-stack/structs/entries/nib.ts +66 -0
  217. package/src/adapter/z-stack/structs/entries/nwk-key-descriptor.ts +15 -0
  218. package/src/adapter/z-stack/structs/entries/nwk-key.ts +13 -0
  219. package/src/adapter/z-stack/structs/entries/nwk-pan-id.ts +8 -0
  220. package/src/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-entry.ts +20 -0
  221. package/src/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-table.ts +19 -0
  222. package/src/adapter/z-stack/structs/entries/security-manager-entry.ts +33 -0
  223. package/src/adapter/z-stack/structs/entries/security-manager-table.ts +22 -0
  224. package/src/adapter/z-stack/structs/index.ts +4 -0
  225. package/src/adapter/z-stack/structs/serializable-memory-object.ts +14 -0
  226. package/src/adapter/z-stack/structs/struct.ts +367 -0
  227. package/src/adapter/z-stack/structs/table.ts +198 -0
  228. package/src/adapter/z-stack/unpi/constants.ts +33 -0
  229. package/src/adapter/z-stack/unpi/frame.ts +62 -0
  230. package/src/adapter/z-stack/unpi/index.ts +4 -0
  231. package/src/adapter/z-stack/unpi/parser.ts +67 -0
  232. package/src/adapter/z-stack/unpi/writer.ts +37 -0
  233. package/src/adapter/z-stack/utils/channel-list.ts +40 -0
  234. package/src/adapter/z-stack/utils/index.ts +2 -0
  235. package/src/adapter/z-stack/utils/network-options.ts +26 -0
  236. package/src/adapter/z-stack/znp/buffaloZnp.ts +175 -0
  237. package/src/adapter/z-stack/znp/definition.ts +2713 -0
  238. package/src/adapter/z-stack/znp/index.ts +2 -0
  239. package/src/adapter/z-stack/znp/parameterType.ts +22 -0
  240. package/src/adapter/z-stack/znp/tstype.ts +44 -0
  241. package/src/adapter/z-stack/znp/utils.ts +10 -0
  242. package/src/adapter/z-stack/znp/znp.ts +345 -0
  243. package/src/adapter/z-stack/znp/zpiObject.ts +148 -0
  244. package/src/adapter/zboss/adapter/zbossAdapter.ts +535 -0
  245. package/src/adapter/zboss/commands.ts +1184 -0
  246. package/src/adapter/zboss/consts.ts +9 -0
  247. package/src/adapter/zboss/driver.ts +422 -0
  248. package/src/adapter/zboss/enums.ts +360 -0
  249. package/src/adapter/zboss/frame.ts +227 -0
  250. package/src/adapter/zboss/reader.ts +65 -0
  251. package/src/adapter/zboss/types.ts +0 -0
  252. package/src/adapter/zboss/uart.ts +428 -0
  253. package/src/adapter/zboss/utils.ts +58 -0
  254. package/src/adapter/zboss/writer.ts +49 -0
  255. package/src/adapter/zigate/adapter/patchZdoBuffaloBE.ts +25 -0
  256. package/src/adapter/zigate/adapter/zigateAdapter.ts +633 -0
  257. package/src/adapter/zigate/driver/LICENSE +17 -0
  258. package/src/adapter/zigate/driver/buffaloZiGate.ts +210 -0
  259. package/src/adapter/zigate/driver/commandType.ts +418 -0
  260. package/src/adapter/zigate/driver/constants.ts +150 -0
  261. package/src/adapter/zigate/driver/frame.ts +197 -0
  262. package/src/adapter/zigate/driver/messageType.ts +287 -0
  263. package/src/adapter/zigate/driver/parameterType.ts +32 -0
  264. package/src/adapter/zigate/driver/ziGateObject.ts +146 -0
  265. package/src/adapter/zigate/driver/zigate.ts +422 -0
  266. package/src/adapter/zoh/adapter/utils.ts +27 -0
  267. package/src/adapter/zoh/adapter/zohAdapter.ts +931 -0
  268. package/src/buffalo/buffalo.ts +336 -0
  269. package/src/buffalo/index.ts +1 -0
  270. package/src/controller/controller.ts +1159 -0
  271. package/src/controller/database.ts +148 -0
  272. package/src/controller/events.ts +52 -0
  273. package/src/controller/greenPower.ts +613 -0
  274. package/src/controller/helpers/index.ts +1 -0
  275. package/src/controller/helpers/installCodes.ts +107 -0
  276. package/src/controller/helpers/ota.ts +578 -0
  277. package/src/controller/helpers/request.ts +96 -0
  278. package/src/controller/helpers/requestQueue.ts +126 -0
  279. package/src/controller/helpers/zclFrameConverter.ts +64 -0
  280. package/src/controller/helpers/zclTransactionSequenceNumber.ts +15 -0
  281. package/src/controller/index.ts +6 -0
  282. package/src/controller/model/device.ts +1791 -0
  283. package/src/controller/model/endpoint.ts +1235 -0
  284. package/src/controller/model/entity.ts +43 -0
  285. package/src/controller/model/group.ts +446 -0
  286. package/src/controller/model/index.ts +5 -0
  287. package/src/controller/model/konnextConfig.ts +6 -0
  288. package/src/controller/model/zigbeeEntity.ts +30 -0
  289. package/src/controller/touchlink.ts +195 -0
  290. package/src/controller/tstype.ts +374 -0
  291. package/src/index.ts +14 -0
  292. package/src/models/backup-storage-legacy.ts +48 -0
  293. package/src/models/backup-storage-unified.ts +47 -0
  294. package/src/models/backup.ts +37 -0
  295. package/src/models/index.ts +5 -0
  296. package/src/models/network-options.ts +11 -0
  297. package/src/utils/aes.ts +218 -0
  298. package/src/utils/async-mutex.ts +31 -0
  299. package/src/utils/backup.ts +152 -0
  300. package/src/utils/index.ts +5 -0
  301. package/src/utils/logger.ts +20 -0
  302. package/src/utils/patchBigIntSerialization.ts +8 -0
  303. package/src/utils/queue.ts +79 -0
  304. package/src/utils/timeService.ts +139 -0
  305. package/src/utils/utils.ts +19 -0
  306. package/src/utils/wait.ts +5 -0
  307. package/src/utils/waitress.ts +96 -0
  308. package/src/zspec/consts.ts +89 -0
  309. package/src/zspec/enums.ts +22 -0
  310. package/src/zspec/index.ts +3 -0
  311. package/src/zspec/tstypes.ts +18 -0
  312. package/src/zspec/utils.ts +247 -0
  313. package/src/zspec/zcl/buffaloZcl.ts +1073 -0
  314. package/src/zspec/zcl/definition/cluster.ts +7554 -0
  315. package/src/zspec/zcl/definition/clusters-types.ts +8228 -0
  316. package/src/zspec/zcl/definition/consts.ts +24 -0
  317. package/src/zspec/zcl/definition/datatypes.ts +2454 -0
  318. package/src/zspec/zcl/definition/enums.ts +224 -0
  319. package/src/zspec/zcl/definition/foundation.ts +342 -0
  320. package/src/zspec/zcl/definition/manufacturerCode.ts +730 -0
  321. package/src/zspec/zcl/definition/status.ts +69 -0
  322. package/src/zspec/zcl/definition/tstype.ts +432 -0
  323. package/src/zspec/zcl/index.ts +11 -0
  324. package/src/zspec/zcl/utils.ts +504 -0
  325. package/src/zspec/zcl/zclFrame.ts +383 -0
  326. package/src/zspec/zcl/zclHeader.ts +102 -0
  327. package/src/zspec/zcl/zclStatusError.ts +10 -0
  328. package/src/zspec/zdo/buffaloZdo.ts +2336 -0
  329. package/src/zspec/zdo/definition/clusters.ts +722 -0
  330. package/src/zspec/zdo/definition/consts.ts +16 -0
  331. package/src/zspec/zdo/definition/enums.ts +99 -0
  332. package/src/zspec/zdo/definition/status.ts +105 -0
  333. package/src/zspec/zdo/definition/tstypes.ts +1062 -0
  334. package/src/zspec/zdo/index.ts +7 -0
  335. package/src/zspec/zdo/utils.ts +76 -0
  336. package/src/zspec/zdo/zdoStatusError.ts +10 -0
  337. package/test/adapter/adapter.test.ts +1276 -0
  338. package/test/adapter/ember/ash.test.ts +337 -0
  339. package/test/adapter/ember/consts.ts +131 -0
  340. package/test/adapter/ember/emberAdapter.test.ts +3447 -0
  341. package/test/adapter/ember/ezsp.test.ts +389 -0
  342. package/test/adapter/ember/ezspBuffalo.test.ts +93 -0
  343. package/test/adapter/ember/ezspError.test.ts +12 -0
  344. package/test/adapter/ember/math.test.ts +190 -0
  345. package/test/adapter/ezsp/frame.test.ts +30 -0
  346. package/test/adapter/ezsp/uart.test.ts +181 -0
  347. package/test/adapter/z-stack/adapter.test.ts +4260 -0
  348. package/test/adapter/z-stack/constants.test.ts +33 -0
  349. package/test/adapter/z-stack/structs.test.ts +115 -0
  350. package/test/adapter/z-stack/unpi.test.ts +213 -0
  351. package/test/adapter/z-stack/znp.test.ts +1288 -0
  352. package/test/adapter/zboss/fixZdoResponse.test.ts +179 -0
  353. package/test/adapter/zigate/patchZdoBuffaloBE.test.ts +81 -0
  354. package/test/adapter/zigate/zdo.test.ts +187 -0
  355. package/test/adapter/zoh/utils.test.ts +36 -0
  356. package/test/adapter/zoh/zohAdapter.test.ts +1451 -0
  357. package/test/benchOptions.ts +14 -0
  358. package/test/buffalo.test.ts +431 -0
  359. package/test/controller.bench.ts +215 -0
  360. package/test/controller.test.ts +10038 -0
  361. package/test/data/integrity-code-1166-012B-24031511-upgradeMe-RB 249 T.zigbee +0 -0
  362. package/test/data/manuf-tags-tradfri-cv-cct-unified_release_prod_v587757105_33e34452-9267-4665-bc5a-844c8f61f063.ota +0 -0
  363. package/test/data/padded-tretakt_smart_plug_soc-0x1100-2.4.25-prod.ota.ota.signed +0 -0
  364. package/test/data/telink-aes-A60_RGBW_T-0x00B6-0x03483712-MF_DIS.OTA +0 -0
  365. package/test/data/zbminir2_v1.0.8.ota +0 -0
  366. package/test/device-ota.test.ts +3332 -0
  367. package/test/greenpower.test.ts +1409 -0
  368. package/test/mockAdapters.ts +95 -0
  369. package/test/mockDevices.ts +623 -0
  370. package/test/requests.bench.ts +321 -0
  371. package/test/testUtils.ts +20 -0
  372. package/test/timeService.test.ts +214 -0
  373. package/test/tsconfig.json +9 -0
  374. package/test/utils/math.ts +19 -0
  375. package/test/utils.test.ts +352 -0
  376. package/test/vitest.config.mts +28 -0
  377. package/test/vitest.ts +9 -0
  378. package/test/zcl.test.ts +2887 -0
  379. package/test/zspec/utils.test.ts +68 -0
  380. package/test/zspec/zcl/buffalo.test.ts +1316 -0
  381. package/test/zspec/zcl/frame.test.ts +1056 -0
  382. package/test/zspec/zcl/utils.test.ts +650 -0
  383. package/test/zspec/zdo/buffalo.test.ts +1850 -0
  384. package/test/zspec/zdo/utils.test.ts +241 -0
  385. package/tsconfig.json +8 -10
  386. package/.babelrc.js +0 -6
  387. package/.eslintignore +0 -3
  388. package/dist/adapter/adapter.d.ts +0 -64
  389. package/dist/adapter/adapter.js +0 -157
  390. package/dist/adapter/adapter.js.map +0 -1
  391. package/dist/adapter/deconz/adapter/deconzAdapter.d.ts +0 -71
  392. package/dist/adapter/deconz/adapter/deconzAdapter.js +0 -1072
  393. package/dist/adapter/deconz/adapter/deconzAdapter.js.map +0 -1
  394. package/dist/adapter/deconz/adapter/index.d.ts +0 -3
  395. package/dist/adapter/deconz/adapter/index.d.ts.map +0 -1
  396. package/dist/adapter/deconz/adapter/index.js +0 -11
  397. package/dist/adapter/deconz/adapter/index.js.map +0 -1
  398. package/dist/adapter/deconz/driver/constants.d.ts +0 -105
  399. package/dist/adapter/deconz/driver/constants.d.ts.map +0 -1
  400. package/dist/adapter/deconz/driver/constants.js +0 -56
  401. package/dist/adapter/deconz/driver/constants.js.map +0 -1
  402. package/dist/adapter/deconz/driver/driver.d.ts +0 -82
  403. package/dist/adapter/deconz/driver/driver.d.ts.map +0 -1
  404. package/dist/adapter/deconz/driver/driver.js +0 -751
  405. package/dist/adapter/deconz/driver/driver.js.map +0 -1
  406. package/dist/adapter/deconz/driver/frame.d.ts +0 -7
  407. package/dist/adapter/deconz/driver/frame.d.ts.map +0 -1
  408. package/dist/adapter/deconz/driver/frame.js +0 -14
  409. package/dist/adapter/deconz/driver/frame.js.map +0 -1
  410. package/dist/adapter/deconz/driver/frameParser.d.ts +0 -3
  411. package/dist/adapter/deconz/driver/frameParser.d.ts.map +0 -1
  412. package/dist/adapter/deconz/driver/frameParser.js +0 -444
  413. package/dist/adapter/deconz/driver/frameParser.js.map +0 -1
  414. package/dist/adapter/deconz/driver/parser.d.ts +0 -13
  415. package/dist/adapter/deconz/driver/parser.d.ts.map +0 -1
  416. package/dist/adapter/deconz/driver/parser.js +0 -64
  417. package/dist/adapter/deconz/driver/parser.js.map +0 -1
  418. package/dist/adapter/deconz/driver/writer.d.ts +0 -9
  419. package/dist/adapter/deconz/driver/writer.d.ts.map +0 -1
  420. package/dist/adapter/deconz/driver/writer.js +0 -45
  421. package/dist/adapter/deconz/driver/writer.js.map +0 -1
  422. package/dist/adapter/ember/adapter/emberAdapter.d.ts +0 -806
  423. package/dist/adapter/ember/adapter/emberAdapter.js +0 -2942
  424. package/dist/adapter/ember/adapter/emberAdapter.js.map +0 -1
  425. package/dist/adapter/ember/adapter/endpoints.d.ts +0 -27
  426. package/dist/adapter/ember/adapter/endpoints.js +0 -68
  427. package/dist/adapter/ember/adapter/endpoints.js.map +0 -1
  428. package/dist/adapter/ember/adapter/index.d.ts +0 -3
  429. package/dist/adapter/ember/adapter/index.d.ts.map +0 -1
  430. package/dist/adapter/ember/adapter/index.js +0 -6
  431. package/dist/adapter/ember/adapter/index.js.map +0 -1
  432. package/dist/adapter/ember/adapter/oneWaitress.d.ts +0 -108
  433. package/dist/adapter/ember/adapter/oneWaitress.js +0 -241
  434. package/dist/adapter/ember/adapter/oneWaitress.js.map +0 -1
  435. package/dist/adapter/ember/adapter/requestQueue.d.ts +0 -57
  436. package/dist/adapter/ember/adapter/requestQueue.d.ts.map +0 -1
  437. package/dist/adapter/ember/adapter/requestQueue.js +0 -139
  438. package/dist/adapter/ember/adapter/requestQueue.js.map +0 -1
  439. package/dist/adapter/ember/adapter/tokensManager.d.ts +0 -69
  440. package/dist/adapter/ember/adapter/tokensManager.js +0 -688
  441. package/dist/adapter/ember/adapter/tokensManager.js.map +0 -1
  442. package/dist/adapter/ember/consts.d.ts +0 -191
  443. package/dist/adapter/ember/consts.d.ts.map +0 -1
  444. package/dist/adapter/ember/consts.js +0 -246
  445. package/dist/adapter/ember/consts.js.map +0 -1
  446. package/dist/adapter/ember/enums.d.ts +0 -2172
  447. package/dist/adapter/ember/enums.d.ts.map +0 -1
  448. package/dist/adapter/ember/enums.js +0 -2375
  449. package/dist/adapter/ember/enums.js.map +0 -1
  450. package/dist/adapter/ember/ezsp/buffalo.d.ts +0 -156
  451. package/dist/adapter/ember/ezsp/buffalo.d.ts.map +0 -1
  452. package/dist/adapter/ember/ezsp/buffalo.js +0 -1033
  453. package/dist/adapter/ember/ezsp/buffalo.js.map +0 -1
  454. package/dist/adapter/ember/ezsp/consts.d.ts +0 -116
  455. package/dist/adapter/ember/ezsp/consts.d.ts.map +0 -1
  456. package/dist/adapter/ember/ezsp/consts.js +0 -128
  457. package/dist/adapter/ember/ezsp/consts.js.map +0 -1
  458. package/dist/adapter/ember/ezsp/enums.d.ts +0 -879
  459. package/dist/adapter/ember/ezsp/enums.d.ts.map +0 -1
  460. package/dist/adapter/ember/ezsp/enums.js +0 -948
  461. package/dist/adapter/ember/ezsp/enums.js.map +0 -1
  462. package/dist/adapter/ember/ezsp/ezsp.d.ts +0 -2662
  463. package/dist/adapter/ember/ezsp/ezsp.js +0 -6454
  464. package/dist/adapter/ember/ezsp/ezsp.js.map +0 -1
  465. package/dist/adapter/ember/types.d.ts +0 -733
  466. package/dist/adapter/ember/types.d.ts.map +0 -1
  467. package/dist/adapter/ember/types.js +0 -3
  468. package/dist/adapter/ember/types.js.map +0 -1
  469. package/dist/adapter/ember/uart/ash.d.ts +0 -464
  470. package/dist/adapter/ember/uart/ash.d.ts.map +0 -1
  471. package/dist/adapter/ember/uart/ash.js +0 -1633
  472. package/dist/adapter/ember/uart/ash.js.map +0 -1
  473. package/dist/adapter/ember/uart/consts.d.ts +0 -91
  474. package/dist/adapter/ember/uart/consts.d.ts.map +0 -1
  475. package/dist/adapter/ember/uart/consts.js +0 -100
  476. package/dist/adapter/ember/uart/consts.js.map +0 -1
  477. package/dist/adapter/ember/uart/enums.d.ts +0 -191
  478. package/dist/adapter/ember/uart/enums.d.ts.map +0 -1
  479. package/dist/adapter/ember/uart/enums.js +0 -197
  480. package/dist/adapter/ember/uart/enums.js.map +0 -1
  481. package/dist/adapter/ember/uart/parser.d.ts +0 -10
  482. package/dist/adapter/ember/uart/parser.d.ts.map +0 -1
  483. package/dist/adapter/ember/uart/parser.js +0 -37
  484. package/dist/adapter/ember/uart/parser.js.map +0 -1
  485. package/dist/adapter/ember/uart/queues.d.ts +0 -85
  486. package/dist/adapter/ember/uart/queues.d.ts.map +0 -1
  487. package/dist/adapter/ember/uart/queues.js +0 -214
  488. package/dist/adapter/ember/uart/queues.js.map +0 -1
  489. package/dist/adapter/ember/uart/writer.d.ts +0 -15
  490. package/dist/adapter/ember/uart/writer.d.ts.map +0 -1
  491. package/dist/adapter/ember/uart/writer.js +0 -46
  492. package/dist/adapter/ember/uart/writer.js.map +0 -1
  493. package/dist/adapter/ember/utils/initters.d.ts +0 -20
  494. package/dist/adapter/ember/utils/initters.js +0 -58
  495. package/dist/adapter/ember/utils/initters.js.map +0 -1
  496. package/dist/adapter/ember/utils/math.d.ts +0 -51
  497. package/dist/adapter/ember/utils/math.d.ts.map +0 -1
  498. package/dist/adapter/ember/utils/math.js +0 -102
  499. package/dist/adapter/ember/utils/math.js.map +0 -1
  500. package/dist/adapter/ember/zdo.d.ts +0 -925
  501. package/dist/adapter/ember/zdo.d.ts.map +0 -1
  502. package/dist/adapter/ember/zdo.js +0 -723
  503. package/dist/adapter/ember/zdo.js.map +0 -1
  504. package/dist/adapter/events.d.ts +0 -42
  505. package/dist/adapter/events.js +0 -13
  506. package/dist/adapter/events.js.map +0 -1
  507. package/dist/adapter/ezsp/adapter/backup.d.ts +0 -13
  508. package/dist/adapter/ezsp/adapter/backup.js +0 -101
  509. package/dist/adapter/ezsp/adapter/backup.js.map +0 -1
  510. package/dist/adapter/ezsp/adapter/ezspAdapter.d.ts +0 -65
  511. package/dist/adapter/ezsp/adapter/ezspAdapter.js +0 -634
  512. package/dist/adapter/ezsp/adapter/ezspAdapter.js.map +0 -1
  513. package/dist/adapter/ezsp/adapter/index.d.ts +0 -3
  514. package/dist/adapter/ezsp/adapter/index.d.ts.map +0 -1
  515. package/dist/adapter/ezsp/adapter/index.js +0 -11
  516. package/dist/adapter/ezsp/adapter/index.js.map +0 -1
  517. package/dist/adapter/ezsp/driver/commands.d.ts +0 -37
  518. package/dist/adapter/ezsp/driver/commands.d.ts.map +0 -1
  519. package/dist/adapter/ezsp/driver/commands.js +0 -2387
  520. package/dist/adapter/ezsp/driver/commands.js.map +0 -1
  521. package/dist/adapter/ezsp/driver/consts.d.ts +0 -11
  522. package/dist/adapter/ezsp/driver/consts.d.ts.map +0 -1
  523. package/dist/adapter/ezsp/driver/consts.js +0 -14
  524. package/dist/adapter/ezsp/driver/consts.js.map +0 -1
  525. package/dist/adapter/ezsp/driver/driver.d.ts +0 -109
  526. package/dist/adapter/ezsp/driver/driver.js +0 -796
  527. package/dist/adapter/ezsp/driver/driver.js.map +0 -1
  528. package/dist/adapter/ezsp/driver/ezsp.d.ts +0 -106
  529. package/dist/adapter/ezsp/driver/ezsp.d.ts.map +0 -1
  530. package/dist/adapter/ezsp/driver/ezsp.js +0 -664
  531. package/dist/adapter/ezsp/driver/ezsp.js.map +0 -1
  532. package/dist/adapter/ezsp/driver/frame.d.ts +0 -40
  533. package/dist/adapter/ezsp/driver/frame.d.ts.map +0 -1
  534. package/dist/adapter/ezsp/driver/frame.js +0 -101
  535. package/dist/adapter/ezsp/driver/frame.js.map +0 -1
  536. package/dist/adapter/ezsp/driver/index.d.ts +0 -4
  537. package/dist/adapter/ezsp/driver/index.js +0 -9
  538. package/dist/adapter/ezsp/driver/index.js.map +0 -1
  539. package/dist/adapter/ezsp/driver/multicast.d.ts +0 -13
  540. package/dist/adapter/ezsp/driver/multicast.js +0 -74
  541. package/dist/adapter/ezsp/driver/multicast.js.map +0 -1
  542. package/dist/adapter/ezsp/driver/parser.d.ts +0 -12
  543. package/dist/adapter/ezsp/driver/parser.d.ts.map +0 -1
  544. package/dist/adapter/ezsp/driver/parser.js +0 -105
  545. package/dist/adapter/ezsp/driver/parser.js.map +0 -1
  546. package/dist/adapter/ezsp/driver/types/basic.d.ts +0 -63
  547. package/dist/adapter/ezsp/driver/types/basic.d.ts.map +0 -1
  548. package/dist/adapter/ezsp/driver/types/basic.js +0 -209
  549. package/dist/adapter/ezsp/driver/types/basic.js.map +0 -1
  550. package/dist/adapter/ezsp/driver/types/index.d.ts +0 -10
  551. package/dist/adapter/ezsp/driver/types/index.d.ts.map +0 -1
  552. package/dist/adapter/ezsp/driver/types/index.js +0 -139
  553. package/dist/adapter/ezsp/driver/types/index.js.map +0 -1
  554. package/dist/adapter/ezsp/driver/types/named.d.ts +0 -1288
  555. package/dist/adapter/ezsp/driver/types/named.d.ts.map +0 -1
  556. package/dist/adapter/ezsp/driver/types/named.js +0 -2330
  557. package/dist/adapter/ezsp/driver/types/named.js.map +0 -1
  558. package/dist/adapter/ezsp/driver/types/struct.d.ts +0 -271
  559. package/dist/adapter/ezsp/driver/types/struct.d.ts.map +0 -1
  560. package/dist/adapter/ezsp/driver/types/struct.js +0 -804
  561. package/dist/adapter/ezsp/driver/types/struct.js.map +0 -1
  562. package/dist/adapter/ezsp/driver/uart.d.ts +0 -49
  563. package/dist/adapter/ezsp/driver/uart.d.ts.map +0 -1
  564. package/dist/adapter/ezsp/driver/uart.js +0 -383
  565. package/dist/adapter/ezsp/driver/uart.js.map +0 -1
  566. package/dist/adapter/ezsp/driver/utils/crc16ccitt.d.ts +0 -3
  567. package/dist/adapter/ezsp/driver/utils/crc16ccitt.d.ts.map +0 -1
  568. package/dist/adapter/ezsp/driver/utils/crc16ccitt.js +0 -56
  569. package/dist/adapter/ezsp/driver/utils/crc16ccitt.js.map +0 -1
  570. package/dist/adapter/ezsp/driver/utils/index.d.ts +0 -20
  571. package/dist/adapter/ezsp/driver/utils/index.d.ts.map +0 -1
  572. package/dist/adapter/ezsp/driver/utils/index.js +0 -73
  573. package/dist/adapter/ezsp/driver/utils/index.js.map +0 -1
  574. package/dist/adapter/ezsp/driver/writer.d.ts +0 -14
  575. package/dist/adapter/ezsp/driver/writer.d.ts.map +0 -1
  576. package/dist/adapter/ezsp/driver/writer.js +0 -83
  577. package/dist/adapter/ezsp/driver/writer.js.map +0 -1
  578. package/dist/adapter/index.d.ts +0 -5
  579. package/dist/adapter/index.js +0 -36
  580. package/dist/adapter/index.js.map +0 -1
  581. package/dist/adapter/serialPort.d.ts +0 -14
  582. package/dist/adapter/serialPort.d.ts.map +0 -1
  583. package/dist/adapter/serialPort.js +0 -47
  584. package/dist/adapter/serialPort.js.map +0 -1
  585. package/dist/adapter/serialPortUtils.d.ts +0 -13
  586. package/dist/adapter/serialPortUtils.d.ts.map +0 -1
  587. package/dist/adapter/serialPortUtils.js +0 -19
  588. package/dist/adapter/serialPortUtils.js.map +0 -1
  589. package/dist/adapter/socketPortUtils.d.ts +0 -11
  590. package/dist/adapter/socketPortUtils.d.ts.map +0 -1
  591. package/dist/adapter/socketPortUtils.js +0 -17
  592. package/dist/adapter/socketPortUtils.js.map +0 -1
  593. package/dist/adapter/tstype.d.ts +0 -86
  594. package/dist/adapter/tstype.d.ts.map +0 -1
  595. package/dist/adapter/tstype.js +0 -3
  596. package/dist/adapter/tstype.js.map +0 -1
  597. package/dist/adapter/z-stack/adapter/adapter-backup.d.ts +0 -62
  598. package/dist/adapter/z-stack/adapter/adapter-backup.d.ts.map +0 -1
  599. package/dist/adapter/z-stack/adapter/adapter-backup.js +0 -459
  600. package/dist/adapter/z-stack/adapter/adapter-backup.js.map +0 -1
  601. package/dist/adapter/z-stack/adapter/adapter-nv-memory.d.ts +0 -151
  602. package/dist/adapter/z-stack/adapter/adapter-nv-memory.d.ts.map +0 -1
  603. package/dist/adapter/z-stack/adapter/adapter-nv-memory.js +0 -259
  604. package/dist/adapter/z-stack/adapter/adapter-nv-memory.js.map +0 -1
  605. package/dist/adapter/z-stack/adapter/endpoints.d.ts +0 -12
  606. package/dist/adapter/z-stack/adapter/endpoints.js +0 -74
  607. package/dist/adapter/z-stack/adapter/endpoints.js.map +0 -1
  608. package/dist/adapter/z-stack/adapter/index.d.ts +0 -3
  609. package/dist/adapter/z-stack/adapter/index.d.ts.map +0 -1
  610. package/dist/adapter/z-stack/adapter/index.js +0 -9
  611. package/dist/adapter/z-stack/adapter/index.js.map +0 -1
  612. package/dist/adapter/z-stack/adapter/manager.d.ts +0 -84
  613. package/dist/adapter/z-stack/adapter/manager.js +0 -474
  614. package/dist/adapter/z-stack/adapter/manager.js.map +0 -1
  615. package/dist/adapter/z-stack/adapter/tstype.d.ts +0 -7
  616. package/dist/adapter/z-stack/adapter/tstype.d.ts.map +0 -1
  617. package/dist/adapter/z-stack/adapter/tstype.js +0 -10
  618. package/dist/adapter/z-stack/adapter/tstype.js.map +0 -1
  619. package/dist/adapter/z-stack/adapter/zStackAdapter.d.ts +0 -86
  620. package/dist/adapter/z-stack/adapter/zStackAdapter.js +0 -912
  621. package/dist/adapter/z-stack/adapter/zStackAdapter.js.map +0 -1
  622. package/dist/adapter/z-stack/constants/af.d.ts +0 -24
  623. package/dist/adapter/z-stack/constants/af.d.ts.map +0 -1
  624. package/dist/adapter/z-stack/constants/af.js +0 -28
  625. package/dist/adapter/z-stack/constants/af.js.map +0 -1
  626. package/dist/adapter/z-stack/constants/common.d.ts +0 -279
  627. package/dist/adapter/z-stack/constants/common.d.ts.map +0 -1
  628. package/dist/adapter/z-stack/constants/common.js +0 -293
  629. package/dist/adapter/z-stack/constants/common.js.map +0 -1
  630. package/dist/adapter/z-stack/constants/dbg.d.ts +0 -23
  631. package/dist/adapter/z-stack/constants/dbg.d.ts.map +0 -1
  632. package/dist/adapter/z-stack/constants/dbg.js +0 -25
  633. package/dist/adapter/z-stack/constants/dbg.js.map +0 -1
  634. package/dist/adapter/z-stack/constants/index.d.ts +0 -11
  635. package/dist/adapter/z-stack/constants/index.d.ts.map +0 -1
  636. package/dist/adapter/z-stack/constants/index.js +0 -48
  637. package/dist/adapter/z-stack/constants/index.js.map +0 -1
  638. package/dist/adapter/z-stack/constants/mac.d.ts +0 -128
  639. package/dist/adapter/z-stack/constants/mac.d.ts.map +0 -1
  640. package/dist/adapter/z-stack/constants/mac.js +0 -130
  641. package/dist/adapter/z-stack/constants/mac.js.map +0 -1
  642. package/dist/adapter/z-stack/constants/sapi.d.ts +0 -25
  643. package/dist/adapter/z-stack/constants/sapi.d.ts.map +0 -1
  644. package/dist/adapter/z-stack/constants/sapi.js +0 -27
  645. package/dist/adapter/z-stack/constants/sapi.js.map +0 -1
  646. package/dist/adapter/z-stack/constants/sys.d.ts +0 -72
  647. package/dist/adapter/z-stack/constants/sys.d.ts.map +0 -1
  648. package/dist/adapter/z-stack/constants/sys.js +0 -74
  649. package/dist/adapter/z-stack/constants/sys.js.map +0 -1
  650. package/dist/adapter/z-stack/constants/util.d.ts +0 -82
  651. package/dist/adapter/z-stack/constants/util.d.ts.map +0 -1
  652. package/dist/adapter/z-stack/constants/util.js +0 -84
  653. package/dist/adapter/z-stack/constants/util.js.map +0 -1
  654. package/dist/adapter/z-stack/constants/utils.d.ts +0 -5
  655. package/dist/adapter/z-stack/constants/utils.d.ts.map +0 -1
  656. package/dist/adapter/z-stack/constants/utils.js +0 -15
  657. package/dist/adapter/z-stack/constants/utils.js.map +0 -1
  658. package/dist/adapter/z-stack/constants/zdo.d.ts +0 -103
  659. package/dist/adapter/z-stack/constants/zdo.d.ts.map +0 -1
  660. package/dist/adapter/z-stack/constants/zdo.js +0 -105
  661. package/dist/adapter/z-stack/constants/zdo.js.map +0 -1
  662. package/dist/adapter/z-stack/models/index.d.ts +0 -2
  663. package/dist/adapter/z-stack/models/index.d.ts.map +0 -1
  664. package/dist/adapter/z-stack/models/index.js +0 -18
  665. package/dist/adapter/z-stack/models/index.js.map +0 -1
  666. package/dist/adapter/z-stack/models/startup-options.d.ts +0 -13
  667. package/dist/adapter/z-stack/models/startup-options.js +0 -3
  668. package/dist/adapter/z-stack/models/startup-options.js.map +0 -1
  669. package/dist/adapter/z-stack/structs/entries/address-manager-entry.d.ts +0 -24
  670. package/dist/adapter/z-stack/structs/entries/address-manager-entry.d.ts.map +0 -1
  671. package/dist/adapter/z-stack/structs/entries/address-manager-entry.js +0 -46
  672. package/dist/adapter/z-stack/structs/entries/address-manager-entry.js.map +0 -1
  673. package/dist/adapter/z-stack/structs/entries/address-manager-table.d.ts +0 -11
  674. package/dist/adapter/z-stack/structs/entries/address-manager-table.d.ts.map +0 -1
  675. package/dist/adapter/z-stack/structs/entries/address-manager-table.js +0 -23
  676. package/dist/adapter/z-stack/structs/entries/address-manager-table.js.map +0 -1
  677. package/dist/adapter/z-stack/structs/entries/aps-link-key-data-entry.d.ts +0 -11
  678. package/dist/adapter/z-stack/structs/entries/aps-link-key-data-entry.d.ts.map +0 -1
  679. package/dist/adapter/z-stack/structs/entries/aps-link-key-data-entry.js +0 -22
  680. package/dist/adapter/z-stack/structs/entries/aps-link-key-data-entry.js.map +0 -1
  681. package/dist/adapter/z-stack/structs/entries/aps-link-key-data-table.d.ts +0 -11
  682. package/dist/adapter/z-stack/structs/entries/aps-link-key-data-table.d.ts.map +0 -1
  683. package/dist/adapter/z-stack/structs/entries/aps-link-key-data-table.js +0 -24
  684. package/dist/adapter/z-stack/structs/entries/aps-link-key-data-table.js.map +0 -1
  685. package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-entry.d.ts +0 -11
  686. package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-entry.d.ts.map +0 -1
  687. package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-entry.js +0 -25
  688. package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-entry.js.map +0 -1
  689. package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-table.d.ts +0 -11
  690. package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-table.d.ts.map +0 -1
  691. package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-table.js +0 -24
  692. package/dist/adapter/z-stack/structs/entries/aps-tc-link-key-table.js.map +0 -1
  693. package/dist/adapter/z-stack/structs/entries/channel-list.d.ts +0 -9
  694. package/dist/adapter/z-stack/structs/entries/channel-list.d.ts.map +0 -1
  695. package/dist/adapter/z-stack/structs/entries/channel-list.js +0 -16
  696. package/dist/adapter/z-stack/structs/entries/channel-list.js.map +0 -1
  697. package/dist/adapter/z-stack/structs/entries/has-configured.d.ts +0 -9
  698. package/dist/adapter/z-stack/structs/entries/has-configured.d.ts.map +0 -1
  699. package/dist/adapter/z-stack/structs/entries/has-configured.js +0 -17
  700. package/dist/adapter/z-stack/structs/entries/has-configured.js.map +0 -1
  701. package/dist/adapter/z-stack/structs/entries/index.d.ts +0 -17
  702. package/dist/adapter/z-stack/structs/entries/index.d.ts.map +0 -1
  703. package/dist/adapter/z-stack/structs/entries/index.js +0 -33
  704. package/dist/adapter/z-stack/structs/entries/index.js.map +0 -1
  705. package/dist/adapter/z-stack/structs/entries/nib.d.ts +0 -11
  706. package/dist/adapter/z-stack/structs/entries/nib.d.ts.map +0 -1
  707. package/dist/adapter/z-stack/structs/entries/nib.js +0 -69
  708. package/dist/adapter/z-stack/structs/entries/nib.js.map +0 -1
  709. package/dist/adapter/z-stack/structs/entries/nwk-key-descriptor.d.ts +0 -11
  710. package/dist/adapter/z-stack/structs/entries/nwk-key-descriptor.d.ts.map +0 -1
  711. package/dist/adapter/z-stack/structs/entries/nwk-key-descriptor.js +0 -19
  712. package/dist/adapter/z-stack/structs/entries/nwk-key-descriptor.js.map +0 -1
  713. package/dist/adapter/z-stack/structs/entries/nwk-key.d.ts +0 -9
  714. package/dist/adapter/z-stack/structs/entries/nwk-key.d.ts.map +0 -1
  715. package/dist/adapter/z-stack/structs/entries/nwk-key.js +0 -16
  716. package/dist/adapter/z-stack/structs/entries/nwk-key.js.map +0 -1
  717. package/dist/adapter/z-stack/structs/entries/nwk-pan-id.d.ts +0 -9
  718. package/dist/adapter/z-stack/structs/entries/nwk-pan-id.d.ts.map +0 -1
  719. package/dist/adapter/z-stack/structs/entries/nwk-pan-id.js +0 -16
  720. package/dist/adapter/z-stack/structs/entries/nwk-pan-id.js.map +0 -1
  721. package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-entry.d.ts +0 -14
  722. package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-entry.d.ts.map +0 -1
  723. package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-entry.js +0 -24
  724. package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-entry.js.map +0 -1
  725. package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-table.d.ts +0 -11
  726. package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-table.d.ts.map +0 -1
  727. package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-table.js +0 -23
  728. package/dist/adapter/z-stack/structs/entries/nwk-sec-material-descriptor-table.js.map +0 -1
  729. package/dist/adapter/z-stack/structs/entries/security-manager-entry.d.ts +0 -21
  730. package/dist/adapter/z-stack/structs/entries/security-manager-entry.d.ts.map +0 -1
  731. package/dist/adapter/z-stack/structs/entries/security-manager-entry.js +0 -37
  732. package/dist/adapter/z-stack/structs/entries/security-manager-entry.js.map +0 -1
  733. package/dist/adapter/z-stack/structs/entries/security-manager-table.d.ts +0 -11
  734. package/dist/adapter/z-stack/structs/entries/security-manager-table.d.ts.map +0 -1
  735. package/dist/adapter/z-stack/structs/entries/security-manager-table.js +0 -25
  736. package/dist/adapter/z-stack/structs/entries/security-manager-table.js.map +0 -1
  737. package/dist/adapter/z-stack/structs/index.d.ts +0 -5
  738. package/dist/adapter/z-stack/structs/index.d.ts.map +0 -1
  739. package/dist/adapter/z-stack/structs/index.js +0 -21
  740. package/dist/adapter/z-stack/structs/index.js.map +0 -1
  741. package/dist/adapter/z-stack/structs/serializable-memory-object.d.ts +0 -14
  742. package/dist/adapter/z-stack/structs/serializable-memory-object.d.ts.map +0 -1
  743. package/dist/adapter/z-stack/structs/serializable-memory-object.js +0 -3
  744. package/dist/adapter/z-stack/structs/serializable-memory-object.js.map +0 -1
  745. package/dist/adapter/z-stack/structs/struct.d.ts +0 -100
  746. package/dist/adapter/z-stack/structs/struct.d.ts.map +0 -1
  747. package/dist/adapter/z-stack/structs/struct.js +0 -297
  748. package/dist/adapter/z-stack/structs/struct.js.map +0 -1
  749. package/dist/adapter/z-stack/structs/table.d.ts +0 -95
  750. package/dist/adapter/z-stack/structs/table.d.ts.map +0 -1
  751. package/dist/adapter/z-stack/structs/table.js +0 -164
  752. package/dist/adapter/z-stack/structs/table.js.map +0 -1
  753. package/dist/adapter/z-stack/unpi/constants.d.ts +0 -29
  754. package/dist/adapter/z-stack/unpi/constants.d.ts.map +0 -1
  755. package/dist/adapter/z-stack/unpi/constants.js +0 -40
  756. package/dist/adapter/z-stack/unpi/constants.js.map +0 -1
  757. package/dist/adapter/z-stack/unpi/frame.d.ts +0 -17
  758. package/dist/adapter/z-stack/unpi/frame.d.ts.map +0 -1
  759. package/dist/adapter/z-stack/unpi/frame.js +0 -55
  760. package/dist/adapter/z-stack/unpi/frame.js.map +0 -1
  761. package/dist/adapter/z-stack/unpi/index.d.ts +0 -6
  762. package/dist/adapter/z-stack/unpi/index.d.ts.map +0 -1
  763. package/dist/adapter/z-stack/unpi/index.js +0 -38
  764. package/dist/adapter/z-stack/unpi/index.js.map +0 -1
  765. package/dist/adapter/z-stack/unpi/parser.d.ts +0 -13
  766. package/dist/adapter/z-stack/unpi/parser.d.ts.map +0 -1
  767. package/dist/adapter/z-stack/unpi/parser.js +0 -86
  768. package/dist/adapter/z-stack/unpi/parser.js.map +0 -1
  769. package/dist/adapter/z-stack/unpi/writer.d.ts +0 -12
  770. package/dist/adapter/z-stack/unpi/writer.d.ts.map +0 -1
  771. package/dist/adapter/z-stack/unpi/writer.js +0 -55
  772. package/dist/adapter/z-stack/unpi/writer.js.map +0 -1
  773. package/dist/adapter/z-stack/utils/channel-list.d.ts +0 -21
  774. package/dist/adapter/z-stack/utils/channel-list.d.ts.map +0 -1
  775. package/dist/adapter/z-stack/utils/channel-list.js +0 -41
  776. package/dist/adapter/z-stack/utils/channel-list.js.map +0 -1
  777. package/dist/adapter/z-stack/utils/index.d.ts +0 -3
  778. package/dist/adapter/z-stack/utils/index.d.ts.map +0 -1
  779. package/dist/adapter/z-stack/utils/index.js +0 -19
  780. package/dist/adapter/z-stack/utils/index.js.map +0 -1
  781. package/dist/adapter/z-stack/utils/network-options.d.ts +0 -9
  782. package/dist/adapter/z-stack/utils/network-options.d.ts.map +0 -1
  783. package/dist/adapter/z-stack/utils/network-options.js +0 -23
  784. package/dist/adapter/z-stack/utils/network-options.js.map +0 -1
  785. package/dist/adapter/z-stack/znp/buffaloZnp.d.ts +0 -14
  786. package/dist/adapter/z-stack/znp/buffaloZnp.d.ts.map +0 -1
  787. package/dist/adapter/z-stack/znp/buffaloZnp.js +0 -243
  788. package/dist/adapter/z-stack/znp/buffaloZnp.js.map +0 -1
  789. package/dist/adapter/z-stack/znp/definition.d.ts +0 -6
  790. package/dist/adapter/z-stack/znp/definition.d.ts.map +0 -1
  791. package/dist/adapter/z-stack/znp/definition.js +0 -3052
  792. package/dist/adapter/z-stack/znp/definition.js.map +0 -1
  793. package/dist/adapter/z-stack/znp/index.d.ts +0 -4
  794. package/dist/adapter/z-stack/znp/index.d.ts.map +0 -1
  795. package/dist/adapter/z-stack/znp/index.js +0 -11
  796. package/dist/adapter/z-stack/znp/index.js.map +0 -1
  797. package/dist/adapter/z-stack/znp/parameterType.d.ts +0 -23
  798. package/dist/adapter/z-stack/znp/parameterType.d.ts.map +0 -1
  799. package/dist/adapter/z-stack/znp/parameterType.js +0 -26
  800. package/dist/adapter/z-stack/znp/parameterType.js.map +0 -1
  801. package/dist/adapter/z-stack/znp/tstype.d.ts +0 -23
  802. package/dist/adapter/z-stack/znp/tstype.d.ts.map +0 -1
  803. package/dist/adapter/z-stack/znp/tstype.js +0 -3
  804. package/dist/adapter/z-stack/znp/tstype.js.map +0 -1
  805. package/dist/adapter/z-stack/znp/znp.d.ts +0 -47
  806. package/dist/adapter/z-stack/znp/znp.d.ts.map +0 -1
  807. package/dist/adapter/z-stack/znp/znp.js +0 -322
  808. package/dist/adapter/z-stack/znp/znp.js.map +0 -1
  809. package/dist/adapter/z-stack/znp/zpiObject.d.ts +0 -20
  810. package/dist/adapter/z-stack/znp/zpiObject.d.ts.map +0 -1
  811. package/dist/adapter/z-stack/znp/zpiObject.js +0 -103
  812. package/dist/adapter/z-stack/znp/zpiObject.js.map +0 -1
  813. package/dist/adapter/zigate/adapter/index.d.ts +0 -3
  814. package/dist/adapter/zigate/adapter/index.d.ts.map +0 -1
  815. package/dist/adapter/zigate/adapter/index.js +0 -11
  816. package/dist/adapter/zigate/adapter/index.js.map +0 -1
  817. package/dist/adapter/zigate/adapter/zigateAdapter.d.ts +0 -72
  818. package/dist/adapter/zigate/adapter/zigateAdapter.js +0 -681
  819. package/dist/adapter/zigate/adapter/zigateAdapter.js.map +0 -1
  820. package/dist/adapter/zigate/driver/buffaloZiGate.d.ts +0 -18
  821. package/dist/adapter/zigate/driver/commandType.d.ts +0 -43
  822. package/dist/adapter/zigate/driver/commandType.d.ts.map +0 -1
  823. package/dist/adapter/zigate/driver/commandType.js +0 -390
  824. package/dist/adapter/zigate/driver/commandType.js.map +0 -1
  825. package/dist/adapter/zigate/driver/constants.d.ts +0 -277
  826. package/dist/adapter/zigate/driver/constants.d.ts.map +0 -1
  827. package/dist/adapter/zigate/driver/constants.js +0 -372
  828. package/dist/adapter/zigate/driver/constants.js.map +0 -1
  829. package/dist/adapter/zigate/driver/frame.d.ts +0 -27
  830. package/dist/adapter/zigate/driver/frame.d.ts.map +0 -1
  831. package/dist/adapter/zigate/driver/frame.js +0 -173
  832. package/dist/adapter/zigate/driver/frame.js.map +0 -1
  833. package/dist/adapter/zigate/driver/messageType.d.ts +0 -13
  834. package/dist/adapter/zigate/driver/messageType.d.ts.map +0 -1
  835. package/dist/adapter/zigate/driver/messageType.js +0 -284
  836. package/dist/adapter/zigate/driver/messageType.js.map +0 -1
  837. package/dist/adapter/zigate/driver/parameterType.d.ts +0 -28
  838. package/dist/adapter/zigate/driver/parameterType.d.ts.map +0 -1
  839. package/dist/adapter/zigate/driver/parameterType.js +0 -33
  840. package/dist/adapter/zigate/driver/parameterType.js.map +0 -1
  841. package/dist/adapter/zigate/driver/ziGateObject.d.ts +0 -24
  842. package/dist/adapter/zigate/driver/ziGateObject.js +0 -111
  843. package/dist/adapter/zigate/driver/ziGateObject.js.map +0 -1
  844. package/dist/adapter/zigate/driver/zigate.d.ts +0 -50
  845. package/dist/adapter/zigate/driver/zigate.js +0 -289
  846. package/dist/adapter/zigate/driver/zigate.js.map +0 -1
  847. package/dist/buffalo/buffalo.d.ts +0 -55
  848. package/dist/buffalo/buffalo.d.ts.map +0 -1
  849. package/dist/buffalo/buffalo.js +0 -230
  850. package/dist/buffalo/buffalo.js.map +0 -1
  851. package/dist/buffalo/index.d.ts +0 -3
  852. package/dist/buffalo/index.d.ts.map +0 -1
  853. package/dist/buffalo/index.js +0 -9
  854. package/dist/buffalo/index.js.map +0 -1
  855. package/dist/controller/controller.d.ts +0 -119
  856. package/dist/controller/database.d.ts +0 -20
  857. package/dist/controller/database.js +0 -94
  858. package/dist/controller/database.js.map +0 -1
  859. package/dist/controller/events.d.ts +0 -59
  860. package/dist/controller/greenPower.d.ts +0 -14
  861. package/dist/controller/helpers/index.d.ts +0 -3
  862. package/dist/controller/helpers/index.js +0 -29
  863. package/dist/controller/helpers/index.js.map +0 -1
  864. package/dist/controller/helpers/request.d.ts +0 -22
  865. package/dist/controller/helpers/request.js +0 -78
  866. package/dist/controller/helpers/request.js.map +0 -1
  867. package/dist/controller/helpers/requestQueue.d.ts +0 -13
  868. package/dist/controller/helpers/requestQueue.js +0 -106
  869. package/dist/controller/helpers/requestQueue.js.map +0 -1
  870. package/dist/controller/helpers/zclFrameConverter.d.ts +0 -9
  871. package/dist/controller/helpers/zclTransactionSequenceNumber.d.ts +0 -6
  872. package/dist/controller/helpers/zclTransactionSequenceNumber.d.ts.map +0 -1
  873. package/dist/controller/helpers/zclTransactionSequenceNumber.js +0 -14
  874. package/dist/controller/helpers/zclTransactionSequenceNumber.js.map +0 -1
  875. package/dist/controller/index.d.ts +0 -6
  876. package/dist/controller/index.js +0 -9
  877. package/dist/controller/index.js.map +0 -1
  878. package/dist/controller/model/device.d.ts +0 -140
  879. package/dist/controller/model/endpoint.d.ts +0 -134
  880. package/dist/controller/model/entity.d.ts +0 -15
  881. package/dist/controller/model/entity.js +0 -27
  882. package/dist/controller/model/entity.js.map +0 -1
  883. package/dist/controller/model/group.d.ts +0 -39
  884. package/dist/controller/model/index.d.ts +0 -6
  885. package/dist/controller/model/index.js +0 -15
  886. package/dist/controller/model/index.js.map +0 -1
  887. package/dist/controller/model/konnextConfig.d.ts +0 -7
  888. package/dist/controller/model/konnextConfig.d.ts.map +0 -1
  889. package/dist/controller/model/konnextConfig.js +0 -3
  890. package/dist/controller/model/konnextConfig.js.map +0 -1
  891. package/dist/controller/touchlink.d.ts +0 -20
  892. package/dist/controller/touchlink.js +0 -157
  893. package/dist/controller/touchlink.js.map +0 -1
  894. package/dist/controller/tstype.d.ts +0 -21
  895. package/dist/index.d.ts +0 -6
  896. package/dist/index.js +0 -37
  897. package/dist/index.js.map +0 -1
  898. package/dist/models/backup-storage-legacy.d.ts +0 -27
  899. package/dist/models/backup-storage-legacy.d.ts.map +0 -1
  900. package/dist/models/backup-storage-legacy.js +0 -3
  901. package/dist/models/backup-storage-legacy.js.map +0 -1
  902. package/dist/models/backup-storage-unified.d.ts +0 -50
  903. package/dist/models/backup-storage-unified.d.ts.map +0 -1
  904. package/dist/models/backup-storage-unified.js +0 -3
  905. package/dist/models/backup-storage-unified.js.map +0 -1
  906. package/dist/models/backup.d.ts +0 -38
  907. package/dist/models/backup.d.ts.map +0 -1
  908. package/dist/models/backup.js +0 -3
  909. package/dist/models/backup.js.map +0 -1
  910. package/dist/models/index.d.ts +0 -5
  911. package/dist/models/index.d.ts.map +0 -1
  912. package/dist/models/index.js +0 -21
  913. package/dist/models/index.js.map +0 -1
  914. package/dist/models/network-options.d.ts +0 -13
  915. package/dist/models/network-options.d.ts.map +0 -1
  916. package/dist/models/network-options.js +0 -3
  917. package/dist/models/network-options.js.map +0 -1
  918. package/dist/utils/aes.d.ts +0 -40
  919. package/dist/utils/aes.d.ts.map +0 -1
  920. package/dist/utils/aes.js +0 -198
  921. package/dist/utils/aes.js.map +0 -1
  922. package/dist/utils/assertString.d.ts +0 -3
  923. package/dist/utils/assertString.d.ts.map +0 -1
  924. package/dist/utils/assertString.js +0 -9
  925. package/dist/utils/assertString.js.map +0 -1
  926. package/dist/utils/backup.d.ts +0 -21
  927. package/dist/utils/backup.d.ts.map +0 -1
  928. package/dist/utils/backup.js +0 -190
  929. package/dist/utils/backup.js.map +0 -1
  930. package/dist/utils/equalsPartial.d.ts +0 -3
  931. package/dist/utils/equalsPartial.d.ts.map +0 -1
  932. package/dist/utils/equalsPartial.js +0 -12
  933. package/dist/utils/equalsPartial.js.map +0 -1
  934. package/dist/utils/index.d.ts +0 -10
  935. package/dist/utils/index.d.ts.map +0 -1
  936. package/dist/utils/index.js +0 -46
  937. package/dist/utils/index.js.map +0 -1
  938. package/dist/utils/isNumberArray.d.ts +0 -3
  939. package/dist/utils/isNumberArray.d.ts.map +0 -1
  940. package/dist/utils/isNumberArray.js +0 -7
  941. package/dist/utils/isNumberArray.js.map +0 -1
  942. package/dist/utils/logger.d.ts +0 -9
  943. package/dist/utils/logger.d.ts.map +0 -1
  944. package/dist/utils/logger.js +0 -14
  945. package/dist/utils/logger.js.map +0 -1
  946. package/dist/utils/queue.d.ts +0 -12
  947. package/dist/utils/queue.d.ts.map +0 -1
  948. package/dist/utils/queue.js +0 -62
  949. package/dist/utils/queue.js.map +0 -1
  950. package/dist/utils/realpathSync.d.ts +0 -3
  951. package/dist/utils/realpathSync.d.ts.map +0 -1
  952. package/dist/utils/realpathSync.js +0 -13
  953. package/dist/utils/realpathSync.js.map +0 -1
  954. package/dist/utils/wait.d.ts +0 -3
  955. package/dist/utils/wait.d.ts.map +0 -1
  956. package/dist/utils/wait.js +0 -9
  957. package/dist/utils/wait.js.map +0 -1
  958. package/dist/utils/waitress.d.ts +0 -22
  959. package/dist/utils/waitress.d.ts.map +0 -1
  960. package/dist/utils/waitress.js +0 -69
  961. package/dist/utils/waitress.js.map +0 -1
  962. package/dist/zspec/consts.d.ts +0 -60
  963. package/dist/zspec/consts.d.ts.map +0 -1
  964. package/dist/zspec/consts.js +0 -64
  965. package/dist/zspec/consts.js.map +0 -1
  966. package/dist/zspec/enums.d.ts +0 -19
  967. package/dist/zspec/enums.d.ts.map +0 -1
  968. package/dist/zspec/enums.js +0 -28
  969. package/dist/zspec/enums.js.map +0 -1
  970. package/dist/zspec/index.d.ts +0 -4
  971. package/dist/zspec/index.d.ts.map +0 -1
  972. package/dist/zspec/index.js +0 -43
  973. package/dist/zspec/index.js.map +0 -1
  974. package/dist/zspec/tstypes.d.ts +0 -19
  975. package/dist/zspec/tstypes.d.ts.map +0 -1
  976. package/dist/zspec/tstypes.js +0 -3
  977. package/dist/zspec/tstypes.js.map +0 -1
  978. package/dist/zspec/utils.d.ts +0 -14
  979. package/dist/zspec/utils.d.ts.map +0 -1
  980. package/dist/zspec/utils.js +0 -29
  981. package/dist/zspec/utils.js.map +0 -1
  982. package/dist/zspec/zcl/buffaloZcl.d.ts +0 -55
  983. package/dist/zspec/zcl/definition/cluster.d.ts +0 -3
  984. package/dist/zspec/zcl/definition/consts.d.ts +0 -9
  985. package/dist/zspec/zcl/definition/consts.d.ts.map +0 -1
  986. package/dist/zspec/zcl/definition/consts.js +0 -27
  987. package/dist/zspec/zcl/definition/consts.js.map +0 -1
  988. package/dist/zspec/zcl/definition/enums.d.ts +0 -177
  989. package/dist/zspec/zcl/definition/enums.d.ts.map +0 -1
  990. package/dist/zspec/zcl/definition/enums.js +0 -187
  991. package/dist/zspec/zcl/definition/enums.js.map +0 -1
  992. package/dist/zspec/zcl/definition/foundation.d.ts +0 -11
  993. package/dist/zspec/zcl/definition/manufacturerCode.d.ts +0 -727
  994. package/dist/zspec/zcl/definition/manufacturerCode.d.ts.map +0 -1
  995. package/dist/zspec/zcl/definition/manufacturerCode.js +0 -733
  996. package/dist/zspec/zcl/definition/manufacturerCode.js.map +0 -1
  997. package/dist/zspec/zcl/definition/status.d.ts +0 -69
  998. package/dist/zspec/zcl/definition/status.d.ts.map +0 -1
  999. package/dist/zspec/zcl/definition/status.js +0 -74
  1000. package/dist/zspec/zcl/definition/status.js.map +0 -1
  1001. package/dist/zspec/zcl/index.d.ts +0 -11
  1002. package/dist/zspec/zcl/utils.d.ts +0 -7
  1003. package/dist/zspec/zcl/zclFrame.d.ts +0 -36
  1004. package/dist/zspec/zcl/zclHeader.d.ts +0 -17
  1005. package/dist/zspec/zcl/zclStatusError.d.ts +0 -6
  1006. package/dist/zspec/zcl/zclStatusError.d.ts.map +0 -1
  1007. package/dist/zspec/zcl/zclStatusError.js +0 -13
  1008. package/dist/zspec/zcl/zclStatusError.js.map +0 -1
  1009. package/dist/zspec/zdo/buffaloZdo.d.ts +0 -438
  1010. package/dist/zspec/zdo/buffaloZdo.d.ts.map +0 -1
  1011. package/dist/zspec/zdo/buffaloZdo.js +0 -1892
  1012. package/dist/zspec/zdo/buffaloZdo.js.map +0 -1
  1013. package/dist/zspec/zdo/definition/clusters.d.ts +0 -624
  1014. package/dist/zspec/zdo/definition/clusters.d.ts.map +0 -1
  1015. package/dist/zspec/zdo/definition/clusters.js +0 -687
  1016. package/dist/zspec/zdo/definition/clusters.js.map +0 -1
  1017. package/dist/zspec/zdo/definition/consts.d.ts +0 -13
  1018. package/dist/zspec/zdo/definition/consts.d.ts.map +0 -1
  1019. package/dist/zspec/zdo/definition/consts.js +0 -16
  1020. package/dist/zspec/zdo/definition/consts.js.map +0 -1
  1021. package/dist/zspec/zdo/definition/enums.d.ts +0 -75
  1022. package/dist/zspec/zdo/definition/enums.d.ts.map +0 -1
  1023. package/dist/zspec/zdo/definition/enums.js +0 -97
  1024. package/dist/zspec/zdo/definition/enums.js.map +0 -1
  1025. package/dist/zspec/zdo/definition/status.d.ts +0 -99
  1026. package/dist/zspec/zdo/definition/status.d.ts.map +0 -1
  1027. package/dist/zspec/zdo/definition/status.js +0 -109
  1028. package/dist/zspec/zdo/definition/status.js.map +0 -1
  1029. package/dist/zspec/zdo/definition/tstypes.d.ts +0 -787
  1030. package/dist/zspec/zdo/definition/tstypes.d.ts.map +0 -1
  1031. package/dist/zspec/zdo/definition/tstypes.js +0 -3
  1032. package/dist/zspec/zdo/definition/tstypes.js.map +0 -1
  1033. package/dist/zspec/zdo/index.d.ts +0 -7
  1034. package/dist/zspec/zdo/index.d.ts.map +0 -1
  1035. package/dist/zspec/zdo/index.js +0 -39
  1036. package/dist/zspec/zdo/index.js.map +0 -1
  1037. package/dist/zspec/zdo/utils.d.ts +0 -25
  1038. package/dist/zspec/zdo/utils.d.ts.map +0 -1
  1039. package/dist/zspec/zdo/utils.js +0 -75
  1040. package/dist/zspec/zdo/utils.js.map +0 -1
  1041. package/dist/zspec/zdo/zdoStatusError.d.ts +0 -6
  1042. package/dist/zspec/zdo/zdoStatusError.d.ts.map +0 -1
  1043. package/dist/zspec/zdo/zdoStatusError.js +0 -13
  1044. package/dist/zspec/zdo/zdoStatusError.js.map +0 -1
  1045. package/typedoc-tsconfig.json +0 -44
@@ -0,0 +1,3332 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import {performance} from "node:perf_hooks";
5
+ import {afterEach, beforeEach, describe, expect, it, vi} from "vitest";
6
+ import type {Adapter} from "../src/adapter";
7
+ import Database from "../src/controller/database";
8
+ import * as otaHelpers from "../src/controller/helpers/ota";
9
+ import {Device, Entity} from "../src/controller/model";
10
+ import {InterviewState} from "../src/controller/model/device";
11
+ import type Endpoint from "../src/controller/model/endpoint";
12
+ import type {OtaDataSettings, OtaImage, OtaSource, ZigbeeOtaImageMeta} from "../src/controller/tstype";
13
+ import {logger} from "../src/utils/logger";
14
+ import * as Zcl from "../src/zspec/zcl";
15
+ import type {TClusterCommandPayload} from "../src/zspec/zcl/definition/clusters-types";
16
+
17
+ const tempDirs: string[] = [];
18
+
19
+ const OTA_CLUSTER_ID = Zcl.Clusters.genOta.ID;
20
+ const QUERY_NEXT_IMAGE_REQUEST_ID = Zcl.Clusters.genOta.commands.queryNextImageRequest.ID;
21
+ const UPGRADE_END_REQUEST_ID = Zcl.Clusters.genOta.commands.upgradeEndRequest.ID;
22
+ const IMAGE_BLOCK_REQUEST_ID = Zcl.Clusters.genOta.commands.imageBlockRequest.ID;
23
+ const IMAGE_PAGE_REQUEST_ID = Zcl.Clusters.genOta.commands.imagePageRequest.ID;
24
+
25
+ type OtaDeviceBehavior = {
26
+ baseSize: number;
27
+ /** Mimick the device sending image page requests instead of image block requests */
28
+ usePageRequests?: boolean;
29
+ pageSize?: number;
30
+ /** Mimick the device sending non-value (0xff) as `maximumDataSize` */
31
+ useNonValueDataSize?: boolean;
32
+ /** Mimick the device stopping image block/page requests at specified block (i.e. stalling) */
33
+ stopAfterBlocks?: number;
34
+ /** Mimick the device sending out-of-order offset for block/page request, block 2 swapped with block 3 */
35
+ shuffleOffsets?: boolean;
36
+ /**
37
+ * TODO: implement this
38
+ * Mimick the device sending block/page request with an offset that is lower or higher than expected flow of "previous offset+data size" at block 2:
39
+ * - normal flow would be something like: block1=[offset=0, dataSize=50], block2=[offset=50, dataSize=50], block3=[offset=100, dataSize=50]
40
+ * - with this block2 has this applied to offset: block1=[offset=0, dataSize=50], block2=[offset=(dataSize-misalignedOffset), dataSize=50], block3=[offset=(dataSize*2-misalignedOffset), dataSize=50]
41
+ */
42
+ misalignedOffset?: number;
43
+ /** Mimick failing block 2 response (mimick device sending new image block/page request for same offset) */
44
+ failBlockResponse?: boolean;
45
+ /** Mimick the device sending or not of `upgradeEndRequest` (at end of block/page requests, or as specified by other behaviors) */
46
+ sendUpgradeEnd?: boolean;
47
+ /** Mimick the device sending that specific status in `upgradeEndRequest` */
48
+ upgradeEndStatus?: Zcl.Status;
49
+ /** Mimick the device sending `upgradeEndRequest` after that specific block/page request */
50
+ upgradeEndAfterBlocks?: number;
51
+ };
52
+
53
+ const DEFAULT_BEHAVIOR: Readonly<OtaDeviceBehavior> = {
54
+ baseSize: 64,
55
+ sendUpgradeEnd: true,
56
+ upgradeEndStatus: Zcl.Status.SUCCESS,
57
+ };
58
+
59
+ const createEndpointStub = () => {
60
+ const commandResponse = vi.fn().mockResolvedValue(undefined);
61
+ const defaultResponse = vi.fn().mockResolvedValue(undefined);
62
+ const supportsOutputCluster = vi.fn((clusterKey: number | string) => clusterKey === OTA_CLUSTER_ID || clusterKey === "genOta");
63
+
64
+ return {ID: 1, supportsOutputCluster, commandResponse, defaultResponse} as unknown as Endpoint;
65
+ };
66
+
67
+ const createOtaDeviceWaitFor = (
68
+ endpoint: Endpoint,
69
+ image: OtaImage,
70
+ current: TClusterCommandPayload<"genOta", "queryNextImageRequest">,
71
+ behavior: OtaDeviceBehavior,
72
+ ): Adapter["waitFor"] => {
73
+ const settings = {...DEFAULT_BEHAVIOR, ...behavior};
74
+ let nextOffset = 0;
75
+ let blocksServed = 0;
76
+ let blockTsn = 2;
77
+ let upgradeEndResolver: ((frame: Zcl.Frame) => void) | undefined;
78
+ let upgradeEndTimer: NodeJS.Timeout | undefined;
79
+ let upgradeEndSent = false;
80
+ let repeatLastBlock = false;
81
+ let previousBlock: {offset: number; size: number} | undefined;
82
+
83
+ const pendingPromise = (): ReturnType<Adapter["waitFor"]>["promise"] => new Promise<never>(() => {}) as never;
84
+ const makeFrame = (
85
+ commandId: number,
86
+ payload: TClusterCommandPayload<"genOta", "imageBlockRequest" | "imagePageRequest" | "upgradeEndRequest" | "queryNextImageRequest">,
87
+ tsn: number,
88
+ ) => Zcl.Frame.create(Zcl.FrameType.SPECIFIC, Zcl.Direction.CLIENT_TO_SERVER, true, undefined, tsn, commandId, OTA_CLUSTER_ID, payload, {});
89
+
90
+ const maybeScheduleUpgradeEnd = () => {
91
+ if (!settings.sendUpgradeEnd || upgradeEndSent || !upgradeEndResolver) {
92
+ return;
93
+ }
94
+
95
+ upgradeEndSent = true;
96
+
97
+ queueMicrotask(() => {
98
+ const frame = makeFrame(
99
+ UPGRADE_END_REQUEST_ID,
100
+ {
101
+ status: settings.upgradeEndStatus ?? Zcl.Status.SUCCESS,
102
+ manufacturerCode: image.header.manufacturerCode,
103
+ imageType: image.header.imageType,
104
+ fileVersion: image.header.fileVersion,
105
+ },
106
+ 250,
107
+ );
108
+
109
+ upgradeEndResolver?.(frame);
110
+ });
111
+ };
112
+
113
+ if (settings.failBlockResponse) {
114
+ endpoint.commandResponse = vi.fn((_clusterKey, commandKey, _payload, _options, _transactionSequenceNumber) => {
115
+ if (commandKey === "imageBlockResponse") {
116
+ if (blocksServed === 1) {
117
+ repeatLastBlock = true;
118
+ return Promise.reject(new Error("block-fail"));
119
+ }
120
+
121
+ return Promise.resolve();
122
+ }
123
+
124
+ return Promise.resolve();
125
+ });
126
+ }
127
+
128
+ const handleBlockRequest = (endpointId: number, transactionSequenceNumber: number | undefined, networkAddress: number) => {
129
+ if (repeatLastBlock && previousBlock) {
130
+ repeatLastBlock = false;
131
+ // revert offset to previous block
132
+ ({offset: nextOffset} = previousBlock);
133
+ }
134
+
135
+ if (settings.stopAfterBlocks !== undefined && blocksServed >= settings.stopAfterBlocks) {
136
+ return {
137
+ promise: Promise.reject(new Error("device stopped requesting blocks")),
138
+ cancel: () => {},
139
+ };
140
+ }
141
+
142
+ let fileOffset = nextOffset;
143
+
144
+ if (settings.shuffleOffsets) {
145
+ if (blocksServed === 1) {
146
+ fileOffset = settings.baseSize * 2;
147
+ } else if (blocksServed === 2) {
148
+ fileOffset = settings.baseSize;
149
+ }
150
+ } else if (settings.misalignedOffset) {
151
+ if (blocksServed === 1) {
152
+ fileOffset = settings.baseSize + settings.misalignedOffset;
153
+ }
154
+ }
155
+
156
+ const remaining = image.header.totalImageSize - fileOffset;
157
+
158
+ if (remaining <= 0) {
159
+ maybeScheduleUpgradeEnd();
160
+
161
+ return {
162
+ promise: Promise.reject(new Error("all blocks sent")),
163
+ cancel: () => {},
164
+ };
165
+ }
166
+
167
+ const payload: TClusterCommandPayload<"genOta", "imageBlockRequest"> = {
168
+ fieldControl: 0,
169
+ manufacturerCode: image.header.manufacturerCode,
170
+ imageType: image.header.imageType,
171
+ fileVersion: image.header.fileVersion,
172
+ fileOffset,
173
+ maximumDataSize: settings.useNonValueDataSize ? Number.NaN : settings.baseSize,
174
+ };
175
+
176
+ blocksServed += 1;
177
+ const nextTsn = transactionSequenceNumber ?? blockTsn;
178
+ blockTsn = (blockTsn + 1) & 0xff;
179
+ const frame = makeFrame(IMAGE_BLOCK_REQUEST_ID, payload, nextTsn);
180
+ nextOffset = Math.max(nextOffset, fileOffset + settings.baseSize);
181
+ previousBlock = {offset: fileOffset, size: settings.baseSize};
182
+
183
+ if (settings.upgradeEndAfterBlocks !== undefined && blocksServed >= settings.upgradeEndAfterBlocks) {
184
+ maybeScheduleUpgradeEnd();
185
+ } else if (nextOffset >= image.header.totalImageSize && settings.sendUpgradeEnd) {
186
+ maybeScheduleUpgradeEnd();
187
+ }
188
+
189
+ return {
190
+ promise: Promise.resolve({
191
+ clusterID: OTA_CLUSTER_ID,
192
+ header: frame.header,
193
+ data: frame.toBuffer(),
194
+ endpoint: endpointId,
195
+ linkquality: 0,
196
+ address: networkAddress,
197
+ groupID: 0,
198
+ wasBroadcast: false,
199
+ destinationEndpoint: endpointId,
200
+ }),
201
+ cancel: () => {},
202
+ };
203
+ };
204
+
205
+ const handlePageRequest = (endpointId: number, transactionSequenceNumber: number | undefined, networkAddress: number) => {
206
+ const remaining = image.header.totalImageSize - nextOffset;
207
+
208
+ if (remaining <= 0) {
209
+ maybeScheduleUpgradeEnd();
210
+
211
+ return {
212
+ promise: pendingPromise(),
213
+ cancel: () => {},
214
+ };
215
+ }
216
+
217
+ const pageSize = Math.min(settings.pageSize ?? settings.baseSize * 16, remaining);
218
+ const payload: TClusterCommandPayload<"genOta", "imagePageRequest"> = {
219
+ fieldControl: 0,
220
+ manufacturerCode: image.header.manufacturerCode,
221
+ imageType: image.header.imageType,
222
+ fileVersion: image.header.fileVersion,
223
+ fileOffset: nextOffset,
224
+ maximumDataSize: settings.baseSize,
225
+ pageSize,
226
+ responseSpacing: 0,
227
+ };
228
+
229
+ const nextTsn = transactionSequenceNumber ?? blockTsn;
230
+ blockTsn = (blockTsn + 1) & 0xff;
231
+ const frame = makeFrame(IMAGE_PAGE_REQUEST_ID, payload, nextTsn);
232
+ nextOffset = Math.max(nextOffset, payload.fileOffset + pageSize);
233
+ previousBlock = {offset: payload.fileOffset, size: pageSize};
234
+
235
+ if (nextOffset >= image.header.totalImageSize) {
236
+ maybeScheduleUpgradeEnd();
237
+ }
238
+
239
+ return {
240
+ promise: Promise.resolve({
241
+ clusterID: OTA_CLUSTER_ID,
242
+ header: frame.header,
243
+ data: frame.toBuffer(),
244
+ endpoint: endpointId,
245
+ linkquality: 0,
246
+ address: networkAddress,
247
+ groupID: 0,
248
+ wasBroadcast: false,
249
+ destinationEndpoint: endpointId,
250
+ }),
251
+ cancel: () => {},
252
+ };
253
+ };
254
+
255
+ const waitFor: Adapter["waitFor"] = (
256
+ networkAddress,
257
+ endpointId,
258
+ _frameType,
259
+ _direction,
260
+ transactionSequenceNumber,
261
+ clusterID,
262
+ commandId,
263
+ timeout,
264
+ ) => {
265
+ const fail = (message: string) => ({
266
+ promise: Promise.reject(new Error(message)),
267
+ cancel: () => {},
268
+ });
269
+
270
+ if (clusterID !== OTA_CLUSTER_ID) {
271
+ return fail("unexpected cluster");
272
+ }
273
+
274
+ if (commandId === QUERY_NEXT_IMAGE_REQUEST_ID) {
275
+ const frame = makeFrame(commandId, current, transactionSequenceNumber ?? 1);
276
+
277
+ return {
278
+ promise: Promise.resolve({
279
+ clusterID,
280
+ header: frame.header,
281
+ data: frame.toBuffer(),
282
+ endpoint: endpointId,
283
+ linkquality: 0,
284
+ address: networkAddress!,
285
+ groupID: 0,
286
+ wasBroadcast: false,
287
+ destinationEndpoint: endpointId,
288
+ }),
289
+ cancel: () => {},
290
+ };
291
+ }
292
+
293
+ if (commandId === UPGRADE_END_REQUEST_ID) {
294
+ const promise = new Promise<Zcl.Frame>((resolve, reject) => {
295
+ upgradeEndResolver = resolve;
296
+
297
+ if (timeout && timeout > 0) {
298
+ upgradeEndTimer = setTimeout(() => reject(new Error("timeout")), timeout);
299
+ }
300
+ });
301
+
302
+ return {
303
+ promise: promise.then((frame) => ({
304
+ clusterID,
305
+ header: frame.header,
306
+ data: frame.toBuffer(),
307
+ endpoint: endpointId,
308
+ linkquality: 0,
309
+ address: networkAddress!,
310
+ groupID: 0,
311
+ wasBroadcast: false,
312
+ destinationEndpoint: endpointId,
313
+ })),
314
+ cancel: () => {
315
+ if (upgradeEndTimer) {
316
+ clearTimeout(upgradeEndTimer);
317
+ upgradeEndTimer = undefined;
318
+ }
319
+ upgradeEndResolver = undefined;
320
+ },
321
+ };
322
+ }
323
+
324
+ if (commandId === IMAGE_PAGE_REQUEST_ID) {
325
+ if (!settings.usePageRequests) {
326
+ return {
327
+ promise: pendingPromise(),
328
+ cancel: () => {},
329
+ };
330
+ }
331
+
332
+ return handlePageRequest(endpointId, transactionSequenceNumber, networkAddress!);
333
+ }
334
+
335
+ if (commandId === IMAGE_BLOCK_REQUEST_ID) {
336
+ if (settings.usePageRequests) {
337
+ return {
338
+ promise: pendingPromise(),
339
+ cancel: () => {},
340
+ };
341
+ }
342
+
343
+ return handleBlockRequest(endpointId, transactionSequenceNumber, networkAddress!);
344
+ }
345
+
346
+ return fail("unsupported commandId");
347
+ };
348
+
349
+ return waitFor;
350
+ };
351
+
352
+ const OTA_FILES = [
353
+ "zbminir2_v1.0.8.ota",
354
+ "telink-aes-A60_RGBW_T-0x00B6-0x03483712-MF_DIS.OTA",
355
+ "integrity-code-1166-012B-24031511-upgradeMe-RB 249 T.zigbee",
356
+ "manuf-tags-tradfri-cv-cct-unified_release_prod_v587757105_33e34452-9267-4665-bc5a-844c8f61f063.ota",
357
+ "padded-tretakt_smart_plug_soc-0x1100-2.4.25-prod.ota.ota.signed",
358
+ ];
359
+ const OTA_FILES_SHA512 = [
360
+ "051912851dffed4b49d3caece8d67b0fec9987dcbdf4d6db45c4e23f4ea0c9be867491758af12f153bc9672d4205129a9270be79e781d150734be295c44971bf",
361
+ "6696fe6707686d9586cdfa045bb3ce24ef960b6cb664a09096e6e11025143373528760f525f5bd2c715d590a2891bd1a69f24227dfe62290aa80e3fcc7e1949d",
362
+ "d3c86737e3a9de6102c013b00b69ea37c551debb898c25f5ce67bfdab4c52b8acca5932e34953d06087dd078d77d83d5d8654c695af6a18440fc2acea0c07f5f",
363
+ "98d8be5b4dcd9692cb6ff87531513023c0116cb9137c5690105ce2077765f35ae2651bdd2aa80ce0363f6db4ef3b9795449252c6c0ff8a8b0e6e05789fdb03be",
364
+ "cd2f88f3f47f459218dd23d1317e76f413cea8a4eedee047a7a6627a595e742f47ec2783eeb2b33e634435cda3f219fa7b540dde6d67876f05d3fdf6feb636d2",
365
+ ];
366
+
367
+ const getResponses = (endpoint: Endpoint, command: string) => {
368
+ const mock = endpoint.commandResponse as unknown as ReturnType<typeof vi.fn>;
369
+
370
+ return mock.mock.calls.filter((call) => call[1] === command);
371
+ };
372
+
373
+ const loadImage = async (fileName: string): Promise<[image: OtaImage, path: string]> => {
374
+ const filePath = path.join(__dirname, "data", fileName);
375
+ const buffer = await otaHelpers.getOtaFirmware(filePath, undefined);
376
+
377
+ return [otaHelpers.parseOtaImage(buffer), filePath];
378
+ };
379
+
380
+ const mockImage = (
381
+ totalImageSize: number,
382
+ manufacturerCode = 1,
383
+ imageType = 2,
384
+ fileVersion = 3,
385
+ securityCredentialVersion?: number,
386
+ upgradeFileDestination?: Buffer,
387
+ minimumHardwareVersion?: number,
388
+ maximumHardwareVersion?: number,
389
+ ): OtaImage => {
390
+ if (totalImageSize < 56) {
391
+ throw new Error("Mock image totalImageSize too small");
392
+ }
393
+
394
+ const header: OtaImage["header"] = {
395
+ otaUpgradeFileIdentifier: otaHelpers.UPGRADE_FILE_IDENTIFIER,
396
+ otaHeaderVersion: 0x0100, // OTA_HEADER_VERSION_ZIGBEE
397
+ otaHeaderLength: 56,
398
+ otaHeaderFieldControl: 0,
399
+ manufacturerCode,
400
+ imageType,
401
+ fileVersion,
402
+ zigbeeStackVersion: 2, // ZIGBEE_PRO_STACK_VERSION
403
+ otaHeaderString: "test",
404
+ totalImageSize,
405
+ };
406
+ const raw = Buffer.alloc(totalImageSize, 0xf1);
407
+ let offset = 0;
408
+ offset = raw.writeUInt32LE(header.otaUpgradeFileIdentifier, offset);
409
+ offset = raw.writeUInt16LE(header.otaHeaderVersion, offset);
410
+ offset = raw.writeUInt16LE(header.otaHeaderLength, offset);
411
+ const otaHeaderFieldControlOffset = offset; // will write at end
412
+ offset += 2;
413
+ offset = raw.writeUInt16LE(header.manufacturerCode, offset);
414
+ offset = raw.writeUInt16LE(header.imageType, offset);
415
+ offset = raw.writeUInt32LE(header.fileVersion, offset);
416
+ offset = raw.writeUInt16LE(header.zigbeeStackVersion, offset);
417
+ offset += raw.write(header.otaHeaderString.padEnd(32, "\u0000"), offset, "utf8");
418
+ offset = raw.writeUInt32LE(header.totalImageSize, offset);
419
+
420
+ if (securityCredentialVersion !== undefined) {
421
+ header.otaHeaderFieldControl |= 1;
422
+ header.otaHeaderLength += 1;
423
+ header.securityCredentialVersion = securityCredentialVersion;
424
+ offset = raw.writeUInt8(securityCredentialVersion, offset);
425
+ }
426
+
427
+ if (upgradeFileDestination !== undefined) {
428
+ if (upgradeFileDestination.byteLength !== 8) {
429
+ throw new Error("Mock image invalid upgradeFileDestination");
430
+ }
431
+
432
+ header.otaHeaderFieldControl |= 2;
433
+ header.otaHeaderLength += 8;
434
+ header.upgradeFileDestination = upgradeFileDestination;
435
+ offset += upgradeFileDestination.copy(raw, offset);
436
+ }
437
+
438
+ if (minimumHardwareVersion !== undefined && maximumHardwareVersion !== undefined) {
439
+ header.otaHeaderFieldControl |= 4;
440
+ header.otaHeaderLength += 4;
441
+ header.minimumHardwareVersion = minimumHardwareVersion;
442
+ offset = raw.writeUInt16LE(minimumHardwareVersion, offset);
443
+ header.maximumHardwareVersion = maximumHardwareVersion;
444
+ offset = raw.writeUInt16LE(maximumHardwareVersion, offset);
445
+ }
446
+
447
+ raw.writeUInt16LE(header.otaHeaderFieldControl, otaHeaderFieldControlOffset);
448
+
449
+ // enough to write UpgradeImage tag
450
+ if (totalImageSize < header.otaHeaderLength + 7) {
451
+ throw new Error("Mock image totalImageSize too small");
452
+ }
453
+
454
+ offset = raw.writeUInt16LE(otaHelpers.OtaTagId.UpgradeImage, offset);
455
+ offset = raw.writeUInt32LE(totalImageSize - header.otaHeaderLength - 6, offset);
456
+
457
+ return {
458
+ header,
459
+ elements: [],
460
+ raw,
461
+ };
462
+ };
463
+
464
+ const writeMockImage = (image: OtaImage, fileName: string, tempDir?: string) => {
465
+ if (!tempDir) {
466
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "zh-ota-"));
467
+ }
468
+
469
+ tempDirs.push(tempDir);
470
+
471
+ const filePath = path.join(tempDir, fileName);
472
+
473
+ fs.writeFileSync(filePath, image.raw);
474
+
475
+ return filePath;
476
+ };
477
+
478
+ type CreateDeviceOptions = {
479
+ image: OtaImage;
480
+ behavior?: Partial<OtaDeviceBehavior>;
481
+ source: OtaSource | undefined;
482
+ requestPayload?: TClusterCommandPayload<"genOta", "queryNextImageRequest">;
483
+ dataSettings?: OtaDataSettings;
484
+ onProgress?: (progress: number, remaining?: number) => void;
485
+ modelID?: string;
486
+ manufacturerName?: string;
487
+ meta?: Record<string, unknown>;
488
+ autoAnnounce?: boolean;
489
+ };
490
+
491
+ const createSimpleDevice = ({modelID, manufacturerName, meta}: Pick<CreateDeviceOptions, "modelID" | "manufacturerName" | "meta">) => {
492
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "zh-ota-"));
493
+
494
+ tempDirs.push(tempDir);
495
+
496
+ const database = Database.open(path.join(tempDir, "database.db"));
497
+ database.write = () => {};
498
+ Entity.injectDatabase(database);
499
+
500
+ const device = Device.create("Router", "0x1", 0x1001, 1, manufacturerName, "Mains", modelID, InterviewState.Successful, undefined);
501
+
502
+ if (meta) {
503
+ device.meta = meta;
504
+ }
505
+
506
+ const endpoint = createEndpointStub();
507
+ device.endpoints.push(endpoint);
508
+
509
+ return {
510
+ device,
511
+ endpoint,
512
+ tempDir,
513
+ };
514
+ };
515
+
516
+ const createDevice = ({
517
+ image,
518
+ behavior,
519
+ source,
520
+ requestPayload,
521
+ dataSettings = {requestTimeout: 100, responseDelay: 0, baseSize: 64},
522
+ onProgress = vi.fn(),
523
+ modelID = "ModelX",
524
+ manufacturerName = "TestCo",
525
+ meta,
526
+ autoAnnounce = true,
527
+ }: CreateDeviceOptions) => {
528
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "zh-ota-"));
529
+
530
+ tempDirs.push(tempDir);
531
+
532
+ const database = Database.open(path.join(tempDir, "database.db"));
533
+ database.write = () => {};
534
+ Entity.injectDatabase(database);
535
+
536
+ const endpoint = createEndpointStub();
537
+ const currentPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = requestPayload ?? {
538
+ fieldControl: 0,
539
+ manufacturerCode: image.header.manufacturerCode,
540
+ imageType: image.header.imageType,
541
+ fileVersion: image.header.fileVersion + (source?.downgrade ? 1 : -1),
542
+ };
543
+ const waitFor = createOtaDeviceWaitFor(endpoint, image, currentPayload, {baseSize: dataSettings.baseSize, ...behavior});
544
+ const adapter = {waitFor, hasZdoMessageOverhead: false} as unknown as Adapter;
545
+ Entity.injectAdapter(adapter);
546
+
547
+ const device = Device.create("Router", "0x1", 0x1001, 1, manufacturerName, "Mains", modelID, InterviewState.Successful, undefined);
548
+
549
+ if (autoAnnounce) {
550
+ const originalOnce = device.once.bind(device);
551
+
552
+ device.once = ((event: Parameters<typeof device.once>[0], listener: Parameters<typeof device.once>[1]) => {
553
+ if (event === "deviceAnnounce") {
554
+ queueMicrotask(() => device.emit("deviceAnnounce", {} as never));
555
+ }
556
+
557
+ return originalOnce(event, listener);
558
+ }) as typeof device.once;
559
+ }
560
+
561
+ if (meta) {
562
+ device.meta = meta;
563
+ }
564
+
565
+ device.endpoints.push(endpoint);
566
+
567
+ return {
568
+ device,
569
+ endpoint,
570
+ onProgress,
571
+ run: async () => {
572
+ if (requestPayload === undefined) {
573
+ return await device.updateOta(source, undefined, undefined, {}, onProgress, dataSettings, endpoint);
574
+ }
575
+
576
+ return await device.updateOta(source, requestPayload, 1, {}, onProgress, dataSettings, endpoint);
577
+ },
578
+ tempDir,
579
+ };
580
+ };
581
+
582
+ const buildIndexEntry = (fileName: string, image: OtaImage, index?: number, url?: string) => ({
583
+ fileName,
584
+ url: url ?? path.join(__dirname, "data", fileName),
585
+ manufacturerCode: image.header.manufacturerCode,
586
+ imageType: image.header.imageType,
587
+ fileVersion: image.header.fileVersion,
588
+ sha512: index === undefined ? undefined : OTA_FILES_SHA512[index],
589
+ });
590
+
591
+ describe("Device OTA", () => {
592
+ let fetchIndexEntries: ZigbeeOtaImageMeta[] = [];
593
+ let firmwareBuffer: Buffer | undefined;
594
+ const fetchMockFail = {ok: false, body: null, status: 403};
595
+ const fetchMockIndex = {
596
+ ok: true,
597
+ body: {},
598
+ status: 200,
599
+ json: async () => fetchIndexEntries,
600
+ };
601
+ const fetchMockFirmware = {
602
+ ok: true,
603
+ body: {},
604
+ status: 200,
605
+ arrayBuffer: async () => firmwareBuffer,
606
+ };
607
+ const fetchMock = vi.fn((input: string | URL): typeof fetchMockFail | typeof fetchMockIndex | typeof fetchMockFirmware => {
608
+ const url = typeof input === "string" ? input : input.toString();
609
+
610
+ if (url.endsWith(".json")) {
611
+ return fetchMockIndex;
612
+ }
613
+
614
+ if (!firmwareBuffer) {
615
+ throw new Error("firmware buffer not provided");
616
+ }
617
+
618
+ return fetchMockFirmware;
619
+ });
620
+
621
+ beforeEach(async () => {
622
+ Device.resetCache();
623
+ vi.stubGlobal("fetch", fetchMock);
624
+
625
+ fetchIndexEntries = [];
626
+
627
+ for (let i = 0; i < OTA_FILES.length; i++) {
628
+ const fileName = OTA_FILES[i];
629
+ const [image] = await loadImage(fileName);
630
+
631
+ fetchIndexEntries.push(buildIndexEntry(fileName, image, i));
632
+ }
633
+
634
+ firmwareBuffer = undefined;
635
+
636
+ // @ts-expect-error not proper API, but here we just want to reset to default
637
+ otaHelpers.setOtaConfiguration(undefined, undefined);
638
+ });
639
+
640
+ afterEach(() => {
641
+ vi.restoreAllMocks();
642
+ vi.unstubAllGlobals();
643
+
644
+ for (const dir of tempDirs.splice(0)) {
645
+ fs.rmSync(dir, {recursive: true, force: true});
646
+ }
647
+ });
648
+
649
+ describe("Checks", () => {
650
+ it("returns no image when index is empty", async () => {
651
+ fetchIndexEntries = [];
652
+ const {device} = createSimpleDevice({});
653
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
654
+ fieldControl: 0,
655
+ manufacturerCode: 1234,
656
+ imageType: 55,
657
+ fileVersion: 10,
658
+ };
659
+ const result = await device.checkOta({}, current, {});
660
+
661
+ expect(result).toStrictEqual({available: false, current});
662
+ expect(fetchMock).toHaveBeenCalledTimes(1);
663
+ });
664
+
665
+ it("performs notify flow when current payload is missing", async () => {
666
+ const fileName = OTA_FILES[1];
667
+ const [image, filePath] = await loadImage(fileName);
668
+ const {device, endpoint} = createDevice({image, source: {}, requestPayload: undefined});
669
+ const result = await device.checkOta({}, undefined, {});
670
+
671
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
672
+ "genOta",
673
+ "imageNotify",
674
+ {payloadType: 0, queryJitter: 100},
675
+ {sendPolicy: "immediate"},
676
+ );
677
+ expect(getResponses(endpoint, "queryNextImageResponse").length).toStrictEqual(1);
678
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
679
+ "genOta",
680
+ "queryNextImageResponse",
681
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
682
+ undefined,
683
+ 1,
684
+ );
685
+ expect(result).toStrictEqual({
686
+ available: true,
687
+ availableMeta: expect.objectContaining({url: filePath}),
688
+ current: {
689
+ fieldControl: 0,
690
+ manufacturerCode: image.header.manufacturerCode,
691
+ imageType: image.header.imageType,
692
+ fileVersion: image.header.fileVersion - 1,
693
+ },
694
+ });
695
+ });
696
+
697
+ it("matches OTA images using extra metadata overrides", async () => {
698
+ const image = mockImage(512);
699
+ const meta: ZigbeeOtaImageMeta = {
700
+ fileName: "custom.ota",
701
+ url: path.join(os.tmpdir(), "custom.ota"),
702
+ manufacturerCode: image.header.manufacturerCode,
703
+ imageType: image.header.imageType,
704
+ fileVersion: image.header.fileVersion,
705
+ modelId: "MatchedModel",
706
+ };
707
+ fetchIndexEntries = [meta];
708
+ const {device} = createSimpleDevice({modelID: "OtherModel", manufacturerName: "OtherCo"});
709
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
710
+ fieldControl: 0,
711
+ manufacturerCode: meta.manufacturerCode,
712
+ imageType: meta.imageType,
713
+ fileVersion: meta.fileVersion - 1,
714
+ };
715
+ const result = await device.checkOta({}, current, {modelId: "MatchedModel"});
716
+
717
+ expect(result.available).toStrictEqual(true);
718
+ expect(result.availableMeta?.fileName).toStrictEqual(meta.fileName);
719
+ });
720
+
721
+ it("matches OTA images using manufacturer name and hardware ranges", async () => {
722
+ const image = mockImage(128);
723
+ const matchingMeta: ZigbeeOtaImageMeta = {
724
+ fileName: "match.ota",
725
+ url: path.join(os.tmpdir(), "match.ota"),
726
+ manufacturerCode: image.header.manufacturerCode,
727
+ imageType: image.header.imageType,
728
+ fileVersion: image.header.fileVersion,
729
+ manufacturerName: ["BrandA device"],
730
+ hardwareVersionMin: 2,
731
+ hardwareVersionMax: 5,
732
+ };
733
+ fetchIndexEntries = [matchingMeta];
734
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
735
+ fieldControl: 1,
736
+ manufacturerCode: matchingMeta.manufacturerCode,
737
+ imageType: matchingMeta.imageType,
738
+ fileVersion: matchingMeta.fileVersion - 1,
739
+ hardwareVersion: 3,
740
+ };
741
+ const {device} = createSimpleDevice({manufacturerName: "BrandA device", modelID: "ModelY"});
742
+ const result = await device.checkOta({}, current, {});
743
+
744
+ expect(result.available).toStrictEqual(true);
745
+ expect(result.availableMeta?.fileName).toStrictEqual("match.ota");
746
+ });
747
+
748
+ it("matches OTA images using extra manufacturer metadata when device manufacturer name mismatches", async () => {
749
+ const image = mockImage(256);
750
+ const matchingMeta: ZigbeeOtaImageMeta = {
751
+ fileName: "extra-match.ota",
752
+ url: path.join(os.tmpdir(), "extra-match.ota"),
753
+ manufacturerCode: image.header.manufacturerCode,
754
+ imageType: image.header.imageType,
755
+ fileVersion: image.header.fileVersion,
756
+ manufacturerName: ["AltBrand"],
757
+ hardwareVersionMin: 10,
758
+ hardwareVersionMax: 20,
759
+ };
760
+ fetchIndexEntries = [matchingMeta];
761
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
762
+ fieldControl: 0,
763
+ manufacturerCode: matchingMeta.manufacturerCode,
764
+ imageType: matchingMeta.imageType,
765
+ fileVersion: matchingMeta.fileVersion - 2,
766
+ };
767
+ const {device} = createSimpleDevice({manufacturerName: "Mismatch", modelID: "ModelZ"});
768
+ const result = await device.checkOta({}, current, {manufacturerName: "AltBrand", hardwareVersionMin: 12, hardwareVersionMax: 18});
769
+
770
+ expect(result.available).toStrictEqual(true);
771
+ expect(result.availableMeta?.fileName).toStrictEqual("extra-match.ota");
772
+ });
773
+
774
+ it("handles custom index as absolute path", async () => {
775
+ const fileName = OTA_FILES[0];
776
+ const [image] = await loadImage(fileName);
777
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
778
+ fieldControl: 0,
779
+ manufacturerCode: image.header.manufacturerCode,
780
+ imageType: image.header.imageType,
781
+ fileVersion: image.header.fileVersion - 1,
782
+ };
783
+ const {device, tempDir} = createDevice({image, source: {}, requestPayload: undefined});
784
+ const indexMeta = buildIndexEntry(fileName, image, undefined, path.join(tempDir, fileName));
785
+ const getOtaIndexSpy = vi.spyOn(otaHelpers, "getOtaIndex");
786
+
787
+ fs.writeFileSync(path.join(tempDir, "my_index.json"), JSON.stringify([indexMeta]), "utf8");
788
+
789
+ const result = await device.checkOta({url: path.resolve(tempDir, "my_index.json")}, undefined, {});
790
+
791
+ expect(result.available).toStrictEqual(true);
792
+ expect(result.availableMeta?.fileVersion).toStrictEqual(requestPayload.fileVersion + 1);
793
+ expect(getOtaIndexSpy).toHaveResolvedWith([indexMeta]);
794
+ });
795
+
796
+ it("handles custom index as relative path", async () => {
797
+ const fileName = OTA_FILES[0];
798
+ const [image] = await loadImage(fileName);
799
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
800
+ fieldControl: 0,
801
+ manufacturerCode: image.header.manufacturerCode,
802
+ imageType: image.header.imageType,
803
+ fileVersion: image.header.fileVersion - 1,
804
+ };
805
+ const {device, tempDir} = createDevice({image, source: {}, requestPayload: undefined});
806
+ const indexMeta = buildIndexEntry(fileName, image, undefined, path.join(tempDir, fileName));
807
+ const getOtaIndexSpy = vi.spyOn(otaHelpers, "getOtaIndex");
808
+
809
+ fs.writeFileSync(path.join(tempDir, "my_index.json"), JSON.stringify([indexMeta]), "utf8");
810
+ otaHelpers.setOtaConfiguration(tempDir, undefined);
811
+
812
+ const result = await device.checkOta({url: "my_index.json"}, undefined, {});
813
+
814
+ expect(result.available).toStrictEqual(true);
815
+ expect(result.availableMeta?.fileVersion).toStrictEqual(requestPayload.fileVersion + 1);
816
+ expect(getOtaIndexSpy).toHaveResolvedWith([indexMeta]);
817
+ });
818
+
819
+ it("throws with custom index as relative path when OTA not configured properly", async () => {
820
+ const [image] = await loadImage(OTA_FILES[0]);
821
+ const {device} = createDevice({image, source: {}, requestPayload: undefined});
822
+
823
+ await expect(device.checkOta({url: "my_index.json"}, undefined, {})).rejects.toThrow(/Invalid OTA configuration/);
824
+ });
825
+
826
+ it("handles failures when waiting for OTA notify response", async () => {
827
+ const [image] = await loadImage(OTA_FILES[0]);
828
+ const {device} = createDevice({image, source: {}, requestPayload: undefined});
829
+ // just need to trigger something called inside the right code path
830
+ const frameSpy = vi.spyOn(Zcl.Frame, "fromBuffer").mockImplementationOnce(() => {
831
+ throw new Error("corrupt-frame");
832
+ });
833
+
834
+ await expect(device.checkOta({}, undefined, {})).rejects.toThrow(/didn't respond to OTA request/);
835
+ expect(frameSpy).toHaveBeenCalled();
836
+ });
837
+
838
+ it("fails when remote index fetching fails", async () => {
839
+ fetchMock.mockResolvedValueOnce(fetchMockFail);
840
+
841
+ const [image] = await loadImage(OTA_FILES[0]);
842
+ firmwareBuffer = image.raw;
843
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
844
+ fieldControl: 0,
845
+ manufacturerCode: image.header.manufacturerCode,
846
+ imageType: image.header.imageType,
847
+ fileVersion: image.header.fileVersion - 1,
848
+ };
849
+ const {device, endpoint} = createSimpleDevice({});
850
+
851
+ await expect(device.checkOta({}, requestPayload, {}, endpoint)).rejects.toThrow(/Invalid response from/);
852
+ });
853
+
854
+ it("merges override index (prio) with URL index", async () => {
855
+ const fileName = "custom.ota";
856
+ const image = mockImage(400, undefined, undefined, 333);
857
+ firmwareBuffer = image.raw;
858
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
859
+ fieldControl: 0,
860
+ manufacturerCode: image.header.manufacturerCode,
861
+ imageType: image.header.imageType,
862
+ fileVersion: image.header.fileVersion - 1,
863
+ };
864
+ const {device, endpoint, tempDir} = createSimpleDevice({});
865
+ const getOtaIndexSpy = vi.spyOn(otaHelpers, "getOtaIndex");
866
+ const filePath = writeMockImage(image, fileName, tempDir);
867
+ const indexMeta = buildIndexEntry(fileName, image, undefined, filePath);
868
+
869
+ fs.writeFileSync(path.join(tempDir, "my_override_index.json"), JSON.stringify([indexMeta]), "utf8");
870
+ otaHelpers.setOtaConfiguration(tempDir, "my_override_index.json");
871
+
872
+ const result = await device.checkOta({}, requestPayload, {}, endpoint);
873
+
874
+ expect(result.available).toStrictEqual(true);
875
+ expect(result.availableMeta?.fileVersion).toStrictEqual(image.header.fileVersion);
876
+ expect(getOtaIndexSpy).toHaveResolvedWith([indexMeta, ...fetchIndexEntries]);
877
+ });
878
+
879
+ it("auto-adds meta for override index local firmware when unspecified", async () => {
880
+ const fileName = "custom.ota";
881
+ const image = mockImage(512, undefined, undefined, 124);
882
+ firmwareBuffer = image.raw;
883
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
884
+ fieldControl: 0,
885
+ manufacturerCode: image.header.manufacturerCode,
886
+ imageType: image.header.imageType,
887
+ fileVersion: image.header.fileVersion - 1,
888
+ };
889
+ const {device, endpoint, tempDir} = createSimpleDevice({});
890
+ const getOtaIndexSpy = vi.spyOn(otaHelpers, "getOtaIndex");
891
+ const indexMeta = {url: fileName};
892
+
893
+ writeMockImage(image, fileName, tempDir);
894
+ fs.writeFileSync(path.join(tempDir, "my_override_index.json"), JSON.stringify([indexMeta]), "utf8");
895
+ otaHelpers.setOtaConfiguration(tempDir, "my_override_index.json");
896
+
897
+ const result = await device.checkOta({}, requestPayload, {}, endpoint);
898
+
899
+ expect(result.available).toStrictEqual(true);
900
+ expect(result.availableMeta?.fileVersion).toStrictEqual(image.header.fileVersion);
901
+ expect(getOtaIndexSpy).toHaveResolvedWith([
902
+ {
903
+ url: fileName,
904
+ fileVersion: image.header.fileVersion,
905
+ imageType: image.header.imageType,
906
+ manufacturerCode: image.header.manufacturerCode,
907
+ },
908
+ ...fetchIndexEntries,
909
+ ]);
910
+ });
911
+
912
+ it("allows use of remote override index even if URL index fetching fails", async () => {
913
+ fetchMock.mockResolvedValueOnce(fetchMockIndex).mockResolvedValueOnce(fetchMockFail);
914
+ const [image] = await loadImage(OTA_FILES[0]);
915
+ firmwareBuffer = image.raw;
916
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
917
+ fieldControl: 0,
918
+ manufacturerCode: image.header.manufacturerCode,
919
+ imageType: image.header.imageType,
920
+ fileVersion: image.header.fileVersion - 1,
921
+ };
922
+ const {device, endpoint, tempDir} = createSimpleDevice({});
923
+ const infoSpy = vi.spyOn(logger, "info");
924
+
925
+ otaHelpers.setOtaConfiguration(tempDir, "https://example.com/index.json");
926
+
927
+ const result = await device.checkOta({}, requestPayload, {}, endpoint);
928
+
929
+ expect(result.available).toStrictEqual(true);
930
+ expect(result.availableMeta?.fileVersion).toStrictEqual(requestPayload.fileVersion + 1);
931
+ expect(infoSpy).toHaveBeenCalledWith("Failed to download main index, only override index is loaded", "zh:controller:ota");
932
+ });
933
+
934
+ it("allows use of local override index even if default URL index fetching fails", async () => {
935
+ fetchMock.mockResolvedValueOnce(fetchMockFail);
936
+
937
+ const fileName = OTA_FILES[0];
938
+ const [image] = await loadImage(fileName);
939
+ firmwareBuffer = image.raw;
940
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
941
+ fieldControl: 0,
942
+ manufacturerCode: image.header.manufacturerCode,
943
+ imageType: image.header.imageType,
944
+ fileVersion: image.header.fileVersion - 1,
945
+ };
946
+ const {device, endpoint, tempDir} = createSimpleDevice({});
947
+ const infoSpy = vi.spyOn(logger, "info");
948
+ const getOtaIndexSpy = vi.spyOn(otaHelpers, "getOtaIndex");
949
+ const indexMeta = buildIndexEntry(fileName, image, undefined, path.join(tempDir, fileName));
950
+
951
+ fs.writeFileSync(path.join(tempDir, "my_index.json"), JSON.stringify([indexMeta]), "utf8");
952
+ otaHelpers.setOtaConfiguration(tempDir, "my_index.json");
953
+
954
+ const result = await device.checkOta({}, requestPayload, {}, endpoint);
955
+
956
+ expect(result.available).toStrictEqual(true);
957
+ expect(result.availableMeta?.fileVersion).toStrictEqual(requestPayload.fileVersion + 1);
958
+ expect(fetchMock).toHaveBeenCalledWith("https://raw.githubusercontent.com/Koenkk/zigbee-OTA/master/index.json");
959
+ expect(infoSpy).toHaveBeenCalledWith("Failed to download main index, only override index is loaded", "zh:controller:ota");
960
+ expect(getOtaIndexSpy).toHaveResolvedWith([indexMeta]);
961
+ });
962
+
963
+ it("allows use of local override index even if custom URL index fetching fails", async () => {
964
+ fetchMock.mockResolvedValueOnce(fetchMockFail);
965
+
966
+ const fileName = OTA_FILES[1];
967
+ const [image] = await loadImage(fileName);
968
+ firmwareBuffer = image.raw;
969
+ const {device, endpoint, tempDir} = createDevice({image, source: {}, requestPayload: undefined});
970
+ const infoSpy = vi.spyOn(logger, "info");
971
+ const getOtaIndexSpy = vi.spyOn(otaHelpers, "getOtaIndex");
972
+ const indexMeta = buildIndexEntry(fileName, image, undefined, path.join(tempDir, fileName));
973
+
974
+ fs.writeFileSync(path.join(tempDir, "my_index.json"), JSON.stringify([indexMeta]), "utf8");
975
+ otaHelpers.setOtaConfiguration(tempDir, "my_index.json");
976
+
977
+ const result = await device.checkOta({url: "https://example.com/index.json"}, undefined, {}, endpoint);
978
+
979
+ expect(result.available).toStrictEqual(true);
980
+ expect(result.availableMeta?.fileVersion).toStrictEqual(image.header.fileVersion);
981
+ expect(fetchMock).toHaveBeenCalledWith("https://example.com/index.json");
982
+ expect(infoSpy).toHaveBeenCalledWith("Failed to download main index, only override index is loaded", "zh:controller:ota");
983
+ expect(getOtaIndexSpy).toHaveResolvedWith([indexMeta]);
984
+ });
985
+
986
+ it.each([
987
+ "lumi.airrtc.agl001",
988
+ "lumi.curtain.acn003",
989
+ "lumi.curtain.agl001",
990
+ ])("overrides %s file version with meta and reports upgrade availability", async (modelID) => {
991
+ const fileName = OTA_FILES[0];
992
+ const [image, filePath] = await loadImage(fileName);
993
+ const {device} = createSimpleDevice({
994
+ modelID,
995
+ manufacturerName: "Aqara",
996
+ meta: {lumiFileVersion: image.header.fileVersion - 1},
997
+ });
998
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
999
+ fieldControl: 0,
1000
+ manufacturerCode: image.header.manufacturerCode,
1001
+ imageType: image.header.imageType,
1002
+ fileVersion: 1,
1003
+ };
1004
+ const result = await device.checkOta({}, current, {});
1005
+
1006
+ expect(result).toStrictEqual({
1007
+ current: {...current, fileVersion: image.header.fileVersion - 1},
1008
+ available: true,
1009
+ availableMeta: {
1010
+ fileName,
1011
+ fileVersion: image.header.fileVersion,
1012
+ imageType: current.imageType,
1013
+ manufacturerCode: current.manufacturerCode,
1014
+ url: filePath,
1015
+ sha512: OTA_FILES_SHA512[0],
1016
+ },
1017
+ });
1018
+ expect(fetchMock).toHaveBeenCalledTimes(1);
1019
+ });
1020
+
1021
+ it("writes scenes group before checking OTA for PP-WHT-US", async () => {
1022
+ const fileName = OTA_FILES[2];
1023
+ const [image, filePath] = await loadImage(fileName);
1024
+ const {device} = createSimpleDevice({modelID: "PP-WHT-US", manufacturerName: "Lutron"});
1025
+ const scenesEndpoint = {
1026
+ ID: 2,
1027
+ supportsOutputCluster: vi.fn((clusterKey: number | string) => clusterKey === "genScenes"),
1028
+ write: vi.fn().mockResolvedValue(undefined),
1029
+ } as unknown as Endpoint;
1030
+
1031
+ device.endpoints.push(scenesEndpoint);
1032
+
1033
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1034
+ fieldControl: 0,
1035
+ manufacturerCode: image.header.manufacturerCode,
1036
+ imageType: image.header.imageType,
1037
+ fileVersion: image.header.fileVersion - 1,
1038
+ };
1039
+ const result = await device.checkOta({}, current, {});
1040
+
1041
+ expect(scenesEndpoint.write).toHaveBeenCalledWith("genScenes", {currentGroup: 49502});
1042
+ expect(result.availableMeta?.url).toStrictEqual(filePath);
1043
+ });
1044
+ });
1045
+
1046
+ describe("Updates", () => {
1047
+ let debugSpy: ReturnType<typeof vi.spyOn> | undefined;
1048
+
1049
+ beforeEach(() => {
1050
+ debugSpy = vi.spyOn(logger, "debug").mockImplementation(() => {});
1051
+ });
1052
+
1053
+ afterEach(() => {
1054
+ debugSpy?.mockRestore();
1055
+ debugSpy = undefined;
1056
+ });
1057
+
1058
+ it.each(OTA_FILES)("applies an upgrade image end-to-end (%s)", async (fileName) => {
1059
+ const [image] = await loadImage(fileName);
1060
+ firmwareBuffer = image.raw;
1061
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1062
+ fieldControl: 0,
1063
+ manufacturerCode: image.header.manufacturerCode,
1064
+ imageType: image.header.imageType,
1065
+ fileVersion: image.header.fileVersion - 1,
1066
+ };
1067
+ const baseSize = 64;
1068
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
1069
+
1070
+ const {device, endpoint, run, onProgress} = createDevice({
1071
+ image,
1072
+ source: {},
1073
+ requestPayload,
1074
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
1075
+ behavior: {baseSize, sendUpgradeEnd: true},
1076
+ });
1077
+
1078
+ const [from, to] = await run();
1079
+
1080
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1081
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1082
+ expect(getResponses(endpoint, "queryNextImageResponse").length).toStrictEqual(1);
1083
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1084
+ "genOta",
1085
+ "queryNextImageResponse",
1086
+ expect.objectContaining({status: Zcl.Status.SUCCESS}),
1087
+ undefined,
1088
+ 1,
1089
+ );
1090
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1091
+ const calls = getResponses(endpoint, "imageBlockResponse");
1092
+ const lastOffset = (expectedBlocks - 1) * baseSize;
1093
+ const lastSize = image.header.totalImageSize - lastOffset;
1094
+ expect(calls[0][2]).toEqual(expect.objectContaining({fileOffset: 0, dataSize: baseSize, data: image.raw.subarray(0, baseSize)}));
1095
+ expect(calls[1][2]).toEqual(
1096
+ expect.objectContaining({fileOffset: baseSize, dataSize: baseSize, data: image.raw.subarray(baseSize, baseSize * 2)}),
1097
+ );
1098
+ expect(calls[2][2]).toEqual(
1099
+ expect.objectContaining({fileOffset: baseSize * 2, dataSize: baseSize, data: image.raw.subarray(baseSize * 2, baseSize * 3)}),
1100
+ );
1101
+ expect(calls[expectedBlocks - 1][2]).toEqual(
1102
+ expect.objectContaining({fileOffset: lastOffset, dataSize: lastSize, data: image.raw.subarray(image.raw.length - lastSize)}),
1103
+ );
1104
+ expect(getResponses(endpoint, "upgradeEndResponse").length).toStrictEqual(1);
1105
+ expect(onProgress).toHaveBeenCalled();
1106
+ expect(device.otaInProgress).toStrictEqual(false);
1107
+ });
1108
+
1109
+ it("applies a downgrade image end-to-end", async () => {
1110
+ const fileName = OTA_FILES[0];
1111
+ const [image, filePath] = await loadImage(fileName);
1112
+ firmwareBuffer = image.raw;
1113
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1114
+ fieldControl: 0,
1115
+ manufacturerCode: image.header.manufacturerCode,
1116
+ imageType: image.header.imageType,
1117
+ fileVersion: image.header.fileVersion + 1,
1118
+ };
1119
+ const baseSize = 64;
1120
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
1121
+ const logDebugSpy = vi.spyOn(logger, "debug");
1122
+
1123
+ const {device, endpoint, run, onProgress} = createDevice({
1124
+ image,
1125
+ source: {downgrade: true},
1126
+ requestPayload,
1127
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
1128
+ behavior: {baseSize, sendUpgradeEnd: true},
1129
+ });
1130
+
1131
+ const [from, to] = await run();
1132
+
1133
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1134
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1135
+ expect(getResponses(endpoint, "queryNextImageResponse").length).toStrictEqual(1);
1136
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1137
+ "genOta",
1138
+ "queryNextImageResponse",
1139
+ expect.objectContaining({status: Zcl.Status.SUCCESS}),
1140
+ undefined,
1141
+ 1,
1142
+ );
1143
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1144
+ const calls = getResponses(endpoint, "imageBlockResponse");
1145
+ const lastOffset = (expectedBlocks - 1) * baseSize;
1146
+ const lastSize = image.header.totalImageSize - lastOffset;
1147
+ expect(calls[0][2]).toEqual(expect.objectContaining({fileOffset: 0, dataSize: baseSize, data: image.raw.subarray(0, baseSize)}));
1148
+ expect(calls[1][2]).toEqual(
1149
+ expect.objectContaining({fileOffset: baseSize, dataSize: baseSize, data: image.raw.subarray(baseSize, baseSize * 2)}),
1150
+ );
1151
+ expect(calls[2][2]).toEqual(
1152
+ expect.objectContaining({fileOffset: baseSize * 2, dataSize: baseSize, data: image.raw.subarray(baseSize * 2, baseSize * 3)}),
1153
+ );
1154
+ expect(calls[expectedBlocks - 1][2]).toEqual(
1155
+ expect.objectContaining({fileOffset: lastOffset, dataSize: lastSize, data: image.raw.subarray(image.raw.length - lastSize)}),
1156
+ );
1157
+ expect(getResponses(endpoint, "upgradeEndResponse").length).toStrictEqual(1);
1158
+ expect(onProgress).toHaveBeenCalled();
1159
+
1160
+ const debugCalls = logDebugSpy.mock.calls.map((c) => (typeof c[0] === "string" ? c[0] : c[0]()));
1161
+ expect(debugCalls[0]).toMatch("Checking OTA 0x1 downgrade image availability");
1162
+ expect(debugCalls[1]).toMatch("Getting image metadata for 0x1...");
1163
+ expect(debugCalls[2]).toMatch("OTA downgrade image availability for 0x1");
1164
+ expect(debugCalls[3]).toMatch(`Reading firmware image from '${filePath}'`);
1165
+ expect(debugCalls[4]).toMatch(`Parsed image from '${filePath}' for 0x1`);
1166
+ expect(device.otaInProgress).toStrictEqual(false);
1167
+ });
1168
+
1169
+ it("considers an upgrade successful even if no device announce", async () => {
1170
+ const fileName = OTA_FILES[0];
1171
+ const [image] = await loadImage(fileName);
1172
+ firmwareBuffer = image.raw;
1173
+ const baseSize = 64;
1174
+
1175
+ vi.useFakeTimers();
1176
+
1177
+ try {
1178
+ const {run, device, endpoint} = createDevice({
1179
+ image,
1180
+ source: {},
1181
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
1182
+ behavior: {baseSize, sendUpgradeEnd: true},
1183
+ autoAnnounce: false,
1184
+ });
1185
+ const promise = run();
1186
+
1187
+ await vi.advanceTimersByTimeAsync(120000);
1188
+
1189
+ const [from, to] = await promise;
1190
+
1191
+ expect(from.fileVersion).toStrictEqual(image.header.fileVersion - 1);
1192
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1193
+ expect(getResponses(endpoint, "queryNextImageResponse").length).toStrictEqual(1);
1194
+ expect(device.otaInProgress).toStrictEqual(false);
1195
+ } finally {
1196
+ vi.useRealTimers();
1197
+ }
1198
+ });
1199
+
1200
+ it("handles receiving non-value for data size", async () => {
1201
+ const fileName = OTA_FILES[0];
1202
+ const [image] = await loadImage(fileName);
1203
+ firmwareBuffer = image.raw;
1204
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1205
+ fieldControl: 0,
1206
+ manufacturerCode: image.header.manufacturerCode,
1207
+ imageType: image.header.imageType,
1208
+ fileVersion: image.header.fileVersion - 1,
1209
+ };
1210
+ const baseSize = 55;
1211
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
1212
+
1213
+ const {device, endpoint, run} = createDevice({
1214
+ image,
1215
+ source: {},
1216
+ requestPayload,
1217
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
1218
+ behavior: {baseSize, sendUpgradeEnd: true, useNonValueDataSize: true},
1219
+ });
1220
+
1221
+ const [from, to] = await run();
1222
+
1223
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1224
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1225
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1226
+ expect(getResponses(endpoint, "upgradeEndResponse").length).toStrictEqual(1);
1227
+ expect(device.otaInProgress).toStrictEqual(false);
1228
+ });
1229
+
1230
+ it("handles out-of-order block offsets", async () => {
1231
+ const fileName = OTA_FILES[0];
1232
+ const [image] = await loadImage(fileName);
1233
+ firmwareBuffer = image.raw;
1234
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1235
+ fieldControl: 0,
1236
+ manufacturerCode: image.header.manufacturerCode,
1237
+ imageType: image.header.imageType,
1238
+ fileVersion: image.header.fileVersion - 1,
1239
+ };
1240
+ const baseSize = 50;
1241
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
1242
+
1243
+ const {device, endpoint, run, onProgress} = createDevice({
1244
+ image,
1245
+ source: {},
1246
+ requestPayload,
1247
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
1248
+ behavior: {baseSize, sendUpgradeEnd: true, shuffleOffsets: true},
1249
+ });
1250
+
1251
+ const [from, to] = await run();
1252
+
1253
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1254
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1255
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1256
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1257
+ "genOta",
1258
+ "queryNextImageResponse",
1259
+ expect.objectContaining({status: Zcl.Status.SUCCESS}),
1260
+ undefined,
1261
+ 1,
1262
+ );
1263
+ const calls = getResponses(endpoint, "imageBlockResponse");
1264
+ const lastOffset = (expectedBlocks - 1) * baseSize;
1265
+ const lastSize = image.header.totalImageSize - lastOffset;
1266
+ expect(calls[0][2]).toEqual(expect.objectContaining({fileOffset: 0, dataSize: baseSize, data: image.raw.subarray(0, baseSize)}));
1267
+ expect(calls[1][2]).toEqual(
1268
+ expect.objectContaining({
1269
+ fileOffset: baseSize * 2,
1270
+ dataSize: baseSize,
1271
+ data: image.raw.subarray(baseSize * 2, baseSize * 3),
1272
+ }),
1273
+ );
1274
+ expect(calls[2][2]).toEqual(
1275
+ expect.objectContaining({fileOffset: baseSize, dataSize: baseSize, data: image.raw.subarray(baseSize, baseSize * 2)}),
1276
+ );
1277
+ expect(calls[expectedBlocks - 1][2]).toEqual(
1278
+ expect.objectContaining({fileOffset: lastOffset, dataSize: lastSize, data: image.raw.subarray(image.raw.length - lastSize)}),
1279
+ );
1280
+ expect(getResponses(endpoint, "upgradeEndResponse").length).toStrictEqual(1);
1281
+ expect(onProgress).toHaveBeenCalled();
1282
+ expect(device.otaInProgress).toStrictEqual(false);
1283
+ });
1284
+
1285
+ it("handles negative misaligned block offset - resends portion already sent", async () => {
1286
+ const fileName = OTA_FILES[0];
1287
+ const [image] = await loadImage(fileName);
1288
+ firmwareBuffer = image.raw;
1289
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1290
+ fieldControl: 0,
1291
+ manufacturerCode: image.header.manufacturerCode,
1292
+ imageType: image.header.imageType,
1293
+ fileVersion: image.header.fileVersion - 1,
1294
+ };
1295
+ const baseSize = 64;
1296
+ const misalignedOffset = -15;
1297
+ const expectedBlocks = Math.ceil((image.header.totalImageSize - misalignedOffset) / baseSize);
1298
+
1299
+ const {device, endpoint, run, onProgress} = createDevice({
1300
+ image,
1301
+ source: {},
1302
+ requestPayload,
1303
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
1304
+ behavior: {baseSize, sendUpgradeEnd: true, misalignedOffset},
1305
+ });
1306
+
1307
+ const [from, to] = await run();
1308
+
1309
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1310
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1311
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1312
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1313
+ "genOta",
1314
+ "queryNextImageResponse",
1315
+ expect.objectContaining({status: Zcl.Status.SUCCESS}),
1316
+ undefined,
1317
+ 1,
1318
+ );
1319
+ const calls = getResponses(endpoint, "imageBlockResponse");
1320
+ const lastOffset = (expectedBlocks - 1) * baseSize + misalignedOffset;
1321
+ const lastSize = image.header.totalImageSize - lastOffset;
1322
+ expect(calls[0][2]).toEqual(expect.objectContaining({fileOffset: 0, dataSize: baseSize, data: image.raw.subarray(0, baseSize)}));
1323
+ expect(calls[1][2]).toEqual(
1324
+ expect.objectContaining({
1325
+ fileOffset: baseSize + misalignedOffset,
1326
+ dataSize: baseSize,
1327
+ data: image.raw.subarray(baseSize + misalignedOffset, baseSize * 2 + misalignedOffset),
1328
+ }),
1329
+ );
1330
+ expect(calls[2][2]).toEqual(
1331
+ expect.objectContaining({
1332
+ fileOffset: baseSize * 2 + misalignedOffset,
1333
+ dataSize: baseSize,
1334
+ data: image.raw.subarray(baseSize * 2 + misalignedOffset, baseSize * 3 + misalignedOffset),
1335
+ }),
1336
+ );
1337
+ expect(calls[expectedBlocks - 1][2]).toEqual(
1338
+ expect.objectContaining({fileOffset: lastOffset, dataSize: lastSize, data: image.raw.subarray(image.raw.length - lastSize)}),
1339
+ );
1340
+ expect(getResponses(endpoint, "upgradeEndResponse").length).toStrictEqual(1);
1341
+ expect(onProgress).toHaveBeenCalled();
1342
+ expect(device.otaInProgress).toStrictEqual(false);
1343
+ });
1344
+
1345
+ it("handles positive misaligned block offset - skips portion", async () => {
1346
+ const fileName = OTA_FILES[0];
1347
+ const [image] = await loadImage(fileName);
1348
+ firmwareBuffer = image.raw;
1349
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1350
+ fieldControl: 0,
1351
+ manufacturerCode: image.header.manufacturerCode,
1352
+ imageType: image.header.imageType,
1353
+ fileVersion: image.header.fileVersion - 1,
1354
+ };
1355
+ const baseSize = 50;
1356
+ const misalignedOffset = 17;
1357
+ const expectedBlocks = Math.ceil((image.header.totalImageSize - misalignedOffset) / baseSize);
1358
+
1359
+ const {device, endpoint, run, onProgress} = createDevice({
1360
+ image,
1361
+ source: {},
1362
+ requestPayload,
1363
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
1364
+ behavior: {baseSize, sendUpgradeEnd: true, misalignedOffset},
1365
+ });
1366
+
1367
+ const [from, to] = await run();
1368
+
1369
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1370
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1371
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1372
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1373
+ "genOta",
1374
+ "queryNextImageResponse",
1375
+ expect.objectContaining({status: Zcl.Status.SUCCESS}),
1376
+ undefined,
1377
+ 1,
1378
+ );
1379
+ const calls = getResponses(endpoint, "imageBlockResponse");
1380
+ const lastOffset = (expectedBlocks - 1) * baseSize + misalignedOffset;
1381
+ const lastSize = image.header.totalImageSize - lastOffset;
1382
+ expect(calls[0][2]).toEqual(expect.objectContaining({fileOffset: 0, dataSize: baseSize, data: image.raw.subarray(0, baseSize)}));
1383
+ expect(calls[1][2]).toEqual(
1384
+ expect.objectContaining({
1385
+ fileOffset: baseSize + misalignedOffset,
1386
+ dataSize: baseSize,
1387
+ data: image.raw.subarray(baseSize + misalignedOffset, baseSize * 2 + misalignedOffset),
1388
+ }),
1389
+ );
1390
+ expect(calls[2][2]).toEqual(
1391
+ expect.objectContaining({
1392
+ fileOffset: baseSize * 2 + misalignedOffset,
1393
+ dataSize: baseSize,
1394
+ data: image.raw.subarray(baseSize * 2 + misalignedOffset, baseSize * 3 + misalignedOffset),
1395
+ }),
1396
+ );
1397
+ expect(calls[expectedBlocks - 1][2]).toEqual(
1398
+ expect.objectContaining({fileOffset: lastOffset, dataSize: lastSize, data: image.raw.subarray(image.raw.length - lastSize)}),
1399
+ );
1400
+ expect(getResponses(endpoint, "upgradeEndResponse").length).toStrictEqual(1);
1401
+ expect(onProgress).toHaveBeenCalled();
1402
+ expect(device.otaInProgress).toStrictEqual(false);
1403
+ });
1404
+
1405
+ it("handles page requests", async () => {
1406
+ const fileName = OTA_FILES[0];
1407
+ const [image] = await loadImage(fileName);
1408
+ firmwareBuffer = image.raw;
1409
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1410
+ fieldControl: 0,
1411
+ manufacturerCode: image.header.manufacturerCode,
1412
+ imageType: image.header.imageType,
1413
+ fileVersion: image.header.fileVersion - 1,
1414
+ };
1415
+ const baseSize = 52;
1416
+ const pageSize = 128;
1417
+ const expectedBlocks = (() => {
1418
+ let remaining = image.header.totalImageSize;
1419
+ let blocks = 0;
1420
+
1421
+ while (remaining > 0) {
1422
+ const page = Math.min(pageSize, remaining);
1423
+ blocks += Math.ceil(page / baseSize);
1424
+ remaining -= page;
1425
+ }
1426
+
1427
+ return blocks;
1428
+ })();
1429
+
1430
+ const {device, endpoint, run, onProgress} = createDevice({
1431
+ image,
1432
+ source: {},
1433
+ requestPayload,
1434
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
1435
+ behavior: {baseSize, sendUpgradeEnd: true, usePageRequests: true, pageSize},
1436
+ });
1437
+
1438
+ const [from, to] = await run();
1439
+
1440
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1441
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1442
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1443
+ "genOta",
1444
+ "queryNextImageResponse",
1445
+ expect.objectContaining({status: Zcl.Status.SUCCESS}),
1446
+ undefined,
1447
+ 1,
1448
+ );
1449
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1450
+ const calls = getResponses(endpoint, "imageBlockResponse");
1451
+ const firstPageRemainder = Math.min(pageSize, image.header.totalImageSize) - baseSize * 2;
1452
+ expect(calls[0][2]).toEqual(expect.objectContaining({fileOffset: 0, dataSize: baseSize, data: image.raw.subarray(0, baseSize)}));
1453
+ expect(calls[1][2]).toEqual(
1454
+ expect.objectContaining({fileOffset: baseSize, dataSize: baseSize, data: image.raw.subarray(baseSize, baseSize * 2)}),
1455
+ );
1456
+ expect(calls[2][2]).toEqual(
1457
+ expect.objectContaining({
1458
+ fileOffset: baseSize * 2,
1459
+ dataSize: firstPageRemainder,
1460
+ data: image.raw.subarray(baseSize * 2, baseSize * 2 + firstPageRemainder),
1461
+ }),
1462
+ );
1463
+ const lastCallPayload = calls[calls.length - 1][2] as unknown as {
1464
+ fileOffset: number;
1465
+ dataSize: number;
1466
+ data: Buffer;
1467
+ };
1468
+ expect(lastCallPayload.data).toStrictEqual(
1469
+ image.raw.subarray(lastCallPayload.fileOffset, lastCallPayload.fileOffset + lastCallPayload.dataSize),
1470
+ );
1471
+ expect(getResponses(endpoint, "upgradeEndResponse").length).toStrictEqual(1);
1472
+ expect(onProgress).toHaveBeenCalled();
1473
+ expect(device.otaInProgress).toStrictEqual(false);
1474
+ });
1475
+
1476
+ it("applies manufacturer-specific timeout", async () => {
1477
+ const fileName = "custom.ota";
1478
+ const image = mockImage(400, Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH, 33);
1479
+ const filePath = writeMockImage(image, fileName);
1480
+ const meta: ZigbeeOtaImageMeta = {
1481
+ fileName,
1482
+ url: filePath,
1483
+ manufacturerCode: image.header.manufacturerCode,
1484
+ imageType: image.header.imageType,
1485
+ fileVersion: image.header.fileVersion,
1486
+ };
1487
+ fetchIndexEntries = [meta];
1488
+ firmwareBuffer = image.raw;
1489
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1490
+ fieldControl: 0,
1491
+ manufacturerCode: image.header.manufacturerCode,
1492
+ imageType: image.header.imageType,
1493
+ fileVersion: image.header.fileVersion - 1,
1494
+ };
1495
+ const baseSize = 50;
1496
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
1497
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
1498
+ const {run, device, endpoint} = createDevice({
1499
+ image,
1500
+ source: {},
1501
+ requestPayload,
1502
+ dataSettings,
1503
+ behavior: {baseSize, sendUpgradeEnd: true},
1504
+ });
1505
+ const [from, to] = await run();
1506
+
1507
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1508
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1509
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1510
+ "genOta",
1511
+ "queryNextImageResponse",
1512
+ expect.objectContaining({status: Zcl.Status.SUCCESS, imageSize: image.header.totalImageSize}),
1513
+ undefined,
1514
+ 1,
1515
+ );
1516
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1517
+ expect(dataSettings.requestTimeout).toStrictEqual(60 * 60 * 1000);
1518
+ expect(dataSettings.responseDelay).toStrictEqual(0);
1519
+ expect(device.otaInProgress).toStrictEqual(false);
1520
+ });
1521
+
1522
+ it("applies manufacturer-specific and imageType-specific timeout", async () => {
1523
+ const fileName = "custom.ota";
1524
+ const image = mockImage(311, Zcl.ManufacturerCode.SHENZHEN_COOLKIT_TECHNOLOGY_CO_LTD, 8199);
1525
+ const filePath = writeMockImage(image, fileName);
1526
+ const meta: ZigbeeOtaImageMeta = {
1527
+ fileName,
1528
+ url: filePath,
1529
+ manufacturerCode: image.header.manufacturerCode,
1530
+ imageType: image.header.imageType,
1531
+ fileVersion: image.header.fileVersion,
1532
+ };
1533
+ fetchIndexEntries = [meta];
1534
+ firmwareBuffer = image.raw;
1535
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1536
+ fieldControl: 0,
1537
+ manufacturerCode: image.header.manufacturerCode,
1538
+ imageType: image.header.imageType,
1539
+ fileVersion: image.header.fileVersion - 1,
1540
+ };
1541
+ const baseSize = 50;
1542
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
1543
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
1544
+ const {run, device, endpoint} = createDevice({
1545
+ image,
1546
+ source: {},
1547
+ requestPayload,
1548
+ dataSettings,
1549
+ behavior: {baseSize, sendUpgradeEnd: true},
1550
+ });
1551
+ const [from, to] = await run();
1552
+
1553
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1554
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1555
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1556
+ "genOta",
1557
+ "queryNextImageResponse",
1558
+ expect.objectContaining({status: Zcl.Status.SUCCESS, imageSize: image.header.totalImageSize}),
1559
+ undefined,
1560
+ 1,
1561
+ );
1562
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1563
+ expect(dataSettings.requestTimeout).toStrictEqual(3600000);
1564
+ expect(dataSettings.responseDelay).toStrictEqual(0);
1565
+ expect(device.otaInProgress).toStrictEqual(false);
1566
+ });
1567
+
1568
+ it("applies manufacturer-specific data size", async () => {
1569
+ const fileName = "custom.ota";
1570
+ const image = mockImage(400, Zcl.ManufacturerCode.INSTA_GMBH, 33);
1571
+ const filePath = writeMockImage(image, fileName);
1572
+ const meta: ZigbeeOtaImageMeta = {
1573
+ fileName,
1574
+ url: filePath,
1575
+ manufacturerCode: image.header.manufacturerCode,
1576
+ imageType: image.header.imageType,
1577
+ fileVersion: image.header.fileVersion,
1578
+ };
1579
+ fetchIndexEntries = [meta];
1580
+ firmwareBuffer = image.raw;
1581
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1582
+ fieldControl: 0,
1583
+ manufacturerCode: image.header.manufacturerCode,
1584
+ imageType: image.header.imageType,
1585
+ fileVersion: image.header.fileVersion - 1,
1586
+ };
1587
+ const baseSize = 64;
1588
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
1589
+ const {run, device, endpoint} = createDevice({
1590
+ image,
1591
+ source: {},
1592
+ requestPayload,
1593
+ dataSettings,
1594
+ behavior: {baseSize, sendUpgradeEnd: true},
1595
+ });
1596
+ const [from, to] = await run();
1597
+
1598
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1599
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1600
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1601
+ "genOta",
1602
+ "queryNextImageResponse",
1603
+ expect.objectContaining({status: Zcl.Status.SUCCESS, imageSize: image.header.totalImageSize}),
1604
+ undefined,
1605
+ 1,
1606
+ );
1607
+ const responses = getResponses(endpoint, "imageBlockResponse");
1608
+ expect(responses[0][2]).toStrictEqual(expect.objectContaining({dataSize: 40}));
1609
+ expect(responses[1][2]).toStrictEqual(expect.objectContaining({dataSize: 40}));
1610
+ expect(device.otaInProgress).toStrictEqual(false);
1611
+ });
1612
+
1613
+ it("handles URL as source URL", async () => {
1614
+ const fileName = "custom.ota";
1615
+ // coverage for more manufacturer-specific timeout
1616
+ const image = mockImage(400, Zcl.ManufacturerCode.LEGRAND_GROUP, 1000);
1617
+ const filePath = writeMockImage(image, fileName);
1618
+ const meta: ZigbeeOtaImageMeta = {
1619
+ fileName,
1620
+ url: filePath,
1621
+ manufacturerCode: image.header.manufacturerCode,
1622
+ imageType: image.header.imageType,
1623
+ fileVersion: image.header.fileVersion,
1624
+ };
1625
+ fetchIndexEntries = [meta];
1626
+ firmwareBuffer = image.raw;
1627
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1628
+ fieldControl: 0,
1629
+ manufacturerCode: image.header.manufacturerCode,
1630
+ imageType: image.header.imageType,
1631
+ fileVersion: image.header.fileVersion - 1,
1632
+ };
1633
+ const baseSize = 50;
1634
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
1635
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
1636
+ const {run, device, endpoint} = createDevice({
1637
+ image,
1638
+ source: {url: "https://example.com/fw.ota"},
1639
+ requestPayload,
1640
+ dataSettings,
1641
+ behavior: {baseSize, sendUpgradeEnd: true},
1642
+ });
1643
+ const [from, to] = await run();
1644
+
1645
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1646
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1647
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1648
+ "genOta",
1649
+ "queryNextImageResponse",
1650
+ expect.objectContaining({status: Zcl.Status.SUCCESS, imageSize: image.header.totalImageSize}),
1651
+ undefined,
1652
+ 1,
1653
+ );
1654
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1655
+ expect(dataSettings.requestTimeout).toStrictEqual(30 * 60 * 1000);
1656
+ expect(dataSettings.responseDelay).toStrictEqual(0);
1657
+ expect(device.otaInProgress).toStrictEqual(false);
1658
+ });
1659
+
1660
+ it("handles filesystem as source URL", async () => {
1661
+ const fileName = "custom.ota";
1662
+ const image = mockImage(400);
1663
+ const filePath = writeMockImage(image, fileName);
1664
+ const meta: ZigbeeOtaImageMeta = {
1665
+ fileName,
1666
+ url: filePath,
1667
+ manufacturerCode: image.header.manufacturerCode,
1668
+ imageType: image.header.imageType,
1669
+ fileVersion: image.header.fileVersion,
1670
+ };
1671
+ fetchIndexEntries = [meta];
1672
+ firmwareBuffer = image.raw;
1673
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1674
+ fieldControl: 0,
1675
+ manufacturerCode: image.header.manufacturerCode,
1676
+ imageType: image.header.imageType,
1677
+ fileVersion: image.header.fileVersion + 1,
1678
+ };
1679
+ const baseSize = 60;
1680
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
1681
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
1682
+ const {run, device, endpoint} = createDevice({
1683
+ image,
1684
+ source: {url: filePath, downgrade: true},
1685
+ requestPayload,
1686
+ dataSettings,
1687
+ behavior: {baseSize, sendUpgradeEnd: true},
1688
+ });
1689
+ const [from, to] = await run();
1690
+
1691
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1692
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1693
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1694
+ "genOta",
1695
+ "queryNextImageResponse",
1696
+ expect.objectContaining({status: Zcl.Status.SUCCESS, imageSize: image.header.totalImageSize}),
1697
+ undefined,
1698
+ 1,
1699
+ );
1700
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
1701
+ expect(dataSettings.requestTimeout).toStrictEqual(150000);
1702
+ expect(dataSettings.responseDelay).toStrictEqual(0);
1703
+ expect(device.otaInProgress).toStrictEqual(false);
1704
+ });
1705
+
1706
+ it("applies fallback timeouts when manufacturer is unknown", async () => {
1707
+ const image = mockImage(240, 0x1234);
1708
+ firmwareBuffer = image.raw;
1709
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1710
+ fieldControl: 0,
1711
+ manufacturerCode: image.header.manufacturerCode,
1712
+ imageType: image.header.imageType,
1713
+ fileVersion: image.header.fileVersion - 1,
1714
+ };
1715
+ const dataSettings: OtaDataSettings = {requestTimeout: 0, responseDelay: 0, baseSize: 0};
1716
+ const {run, device, endpoint} = createDevice({
1717
+ image,
1718
+ source: {url: "https://example.com/fw.ota"},
1719
+ requestPayload,
1720
+ dataSettings,
1721
+ behavior: {baseSize: 50, sendUpgradeEnd: true},
1722
+ });
1723
+ const [from, to] = await run();
1724
+
1725
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1726
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1727
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
1728
+ "genOta",
1729
+ "queryNextImageResponse",
1730
+ expect.objectContaining({status: Zcl.Status.SUCCESS}),
1731
+ undefined,
1732
+ 1,
1733
+ );
1734
+ expect(dataSettings.requestTimeout).toStrictEqual(150000);
1735
+ expect(dataSettings.baseSize).toStrictEqual(50);
1736
+ expect(dataSettings.responseDelay).toStrictEqual(0);
1737
+ expect(device.otaInProgress).toStrictEqual(false);
1738
+ });
1739
+
1740
+ it("calls onProgress with round blocks", async () => {
1741
+ const fileName = "custom.ota";
1742
+ const image = mockImage(320);
1743
+ const filePath = writeMockImage(image, fileName);
1744
+ const meta: ZigbeeOtaImageMeta = {
1745
+ fileName,
1746
+ url: filePath,
1747
+ manufacturerCode: image.header.manufacturerCode,
1748
+ imageType: image.header.imageType,
1749
+ fileVersion: image.header.fileVersion,
1750
+ };
1751
+ fetchIndexEntries = [meta];
1752
+ firmwareBuffer = image.raw;
1753
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1754
+ fieldControl: 0,
1755
+ manufacturerCode: image.header.manufacturerCode,
1756
+ imageType: image.header.imageType,
1757
+ fileVersion: image.header.fileVersion - 1,
1758
+ };
1759
+ const baseSize = 40;
1760
+ const dataSettings: OtaDataSettings = {requestTimeout: 10, responseDelay: 0, baseSize};
1761
+ // Mock time to ensure progress is reported at regular intervals
1762
+ let currentTime = 0;
1763
+ const timeDelays = [
1764
+ 0, // constructor
1765
+ 100, // first block request (offset 0, no progress yet)
1766
+ 100,
1767
+ 30100, // second block request (offset 40, triggers 12.5% progress)
1768
+ 100,
1769
+ 30100, // third block request (offset 80, triggers 25% progress)
1770
+ 100,
1771
+ 30100, // fourth block request (offset 120, triggers 37.5% progress)
1772
+ 100,
1773
+ 30100, // fifth block request (offset 160, triggers 50% progress)
1774
+ 100,
1775
+ 30100, // sixth block request (offset 200, triggers 62.5% progress)
1776
+ 100,
1777
+ 30100, // seventh block request (offset 240, triggers 75% progress)
1778
+ 100,
1779
+ 30100, // eighth block request (offset 280, triggers 87.5% progress)
1780
+ 100,
1781
+ ];
1782
+ const nowSpy = vi.spyOn(performance, "now").mockImplementation(() => {
1783
+ const next = timeDelays.shift();
1784
+ // For any additional calls, add small time
1785
+ currentTime += next ?? 100;
1786
+
1787
+ return currentTime;
1788
+ });
1789
+
1790
+ const {run, device, onProgress} = createDevice({
1791
+ image,
1792
+ source: {url: "https://example.com/fw.ota"},
1793
+ requestPayload,
1794
+ dataSettings,
1795
+ behavior: {baseSize, sendUpgradeEnd: true},
1796
+ });
1797
+
1798
+ const [from, to] = await run();
1799
+ nowSpy.mockRestore();
1800
+
1801
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1802
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1803
+
1804
+ // With 320 bytes and 40-byte chunks, we have 8 blocks
1805
+ expect(onProgress).toHaveBeenNthCalledWith(1, 0, 0.4); // Initial call with estimate (non-mocked time, so tiny due to tiny # of blocks)
1806
+ expect(onProgress).toHaveBeenNthCalledWith(2, 12.5, 213); // After 1st block sent (40/320)
1807
+ expect(onProgress).toHaveBeenNthCalledWith(3, 25, 182); // After 2nd block sent (80/320)
1808
+ expect(onProgress).toHaveBeenNthCalledWith(4, 37.5, 151); // After 3rd block sent (120/320)
1809
+ expect(onProgress).toHaveBeenNthCalledWith(5, 50, 121); // After 4th block sent (160/320)
1810
+ expect(onProgress).toHaveBeenNthCalledWith(6, 62.5, 91); // After 5th block sent (200/320)
1811
+ expect(onProgress).toHaveBeenNthCalledWith(7, 75, 60); // After 6th block sent (240/320)
1812
+ expect(onProgress).toHaveBeenNthCalledWith(8, 87.5, 30); // After 7th block sent (280/320)
1813
+ expect(onProgress).toHaveBeenNthCalledWith(9, 100, 0); // Final completion
1814
+ expect(device.otaInProgress).toStrictEqual(false);
1815
+ });
1816
+
1817
+ it("calls onProgress with response delay (changes estimate)", async () => {
1818
+ const fileName = "custom.ota";
1819
+ const image = mockImage(320);
1820
+ const filePath = writeMockImage(image, fileName);
1821
+ const meta: ZigbeeOtaImageMeta = {
1822
+ fileName,
1823
+ url: filePath,
1824
+ manufacturerCode: image.header.manufacturerCode,
1825
+ imageType: image.header.imageType,
1826
+ fileVersion: image.header.fileVersion,
1827
+ };
1828
+ fetchIndexEntries = [meta];
1829
+ firmwareBuffer = image.raw;
1830
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1831
+ fieldControl: 0,
1832
+ manufacturerCode: image.header.manufacturerCode,
1833
+ imageType: image.header.imageType,
1834
+ fileVersion: image.header.fileVersion - 1,
1835
+ };
1836
+ const baseSize = 40;
1837
+ const dataSettings: OtaDataSettings = {requestTimeout: 10, responseDelay: 250, baseSize};
1838
+ // Mock time to ensure progress is reported at regular intervals
1839
+ let currentTime = 0;
1840
+ const timeDelays = [
1841
+ 0, // constructor
1842
+ 100, // first block request (offset 0, no progress yet)
1843
+ 100,
1844
+ 30100, // second block request (offset 40, triggers 12.5% progress)
1845
+ 100,
1846
+ 30100, // third block request (offset 80, triggers 25% progress)
1847
+ 100,
1848
+ 30100, // fourth block request (offset 120, triggers 37.5% progress)
1849
+ 100,
1850
+ 30100, // fifth block request (offset 160, triggers 50% progress)
1851
+ 100,
1852
+ 30100, // sixth block request (offset 200, triggers 62.5% progress)
1853
+ 100,
1854
+ 30100, // seventh block request (offset 240, triggers 75% progress)
1855
+ 100,
1856
+ 30100, // eighth block request (offset 280, triggers 87.5% progress)
1857
+ 100,
1858
+ ];
1859
+ const nowSpy = vi.spyOn(performance, "now").mockImplementation(() => {
1860
+ const next = timeDelays.shift();
1861
+ // For any additional calls, add small time
1862
+ currentTime += next ?? 100;
1863
+
1864
+ return currentTime;
1865
+ });
1866
+
1867
+ const {run, device, onProgress} = createDevice({
1868
+ image,
1869
+ source: {url: "https://example.com/fw.ota"},
1870
+ requestPayload,
1871
+ dataSettings,
1872
+ behavior: {baseSize, sendUpgradeEnd: true},
1873
+ });
1874
+
1875
+ const [from, to] = await run();
1876
+ nowSpy.mockRestore();
1877
+
1878
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1879
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1880
+
1881
+ // With 320 bytes and 40-byte chunks, we have 8 blocks
1882
+ expect(onProgress).toHaveBeenNthCalledWith(1, 0, 2); // Initial call with estimate (non-mocked time, so tiny due to tiny # of blocks)
1883
+ expect(onProgress).toHaveBeenNthCalledWith(2, 12.5, 213); // After 1st block sent (40/320)
1884
+ expect(onProgress).toHaveBeenNthCalledWith(3, 25, 182); // After 2nd block sent (80/320)
1885
+ expect(onProgress).toHaveBeenNthCalledWith(4, 37.5, 151); // After 3rd block sent (120/320)
1886
+ expect(onProgress).toHaveBeenNthCalledWith(5, 50, 121); // After 4th block sent (160/320)
1887
+ expect(onProgress).toHaveBeenNthCalledWith(6, 62.5, 91); // After 5th block sent (200/320)
1888
+ expect(onProgress).toHaveBeenNthCalledWith(7, 75, 60); // After 6th block sent (240/320)
1889
+ expect(onProgress).toHaveBeenNthCalledWith(8, 87.5, 30); // After 7th block sent (280/320)
1890
+ expect(onProgress).toHaveBeenNthCalledWith(9, 100, 0); // Final completion
1891
+ expect(device.otaInProgress).toStrictEqual(false);
1892
+ });
1893
+
1894
+ it("calls onProgress with non-round blocks", async () => {
1895
+ const fileName = "custom.ota";
1896
+ const image = mockImage(521);
1897
+ const filePath = writeMockImage(image, fileName);
1898
+ const meta: ZigbeeOtaImageMeta = {
1899
+ fileName,
1900
+ url: filePath,
1901
+ manufacturerCode: image.header.manufacturerCode,
1902
+ imageType: image.header.imageType,
1903
+ fileVersion: image.header.fileVersion,
1904
+ };
1905
+ fetchIndexEntries = [meta];
1906
+ firmwareBuffer = image.raw;
1907
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1908
+ fieldControl: 0,
1909
+ manufacturerCode: image.header.manufacturerCode,
1910
+ imageType: image.header.imageType,
1911
+ fileVersion: image.header.fileVersion - 1,
1912
+ };
1913
+ const baseSize = 64;
1914
+ const dataSettings: OtaDataSettings = {requestTimeout: 10, responseDelay: 0, baseSize};
1915
+ // Mock time to ensure progress is reported at regular intervals
1916
+ let currentTime = 0;
1917
+ const timeDelays = [
1918
+ 0, // constructor
1919
+ 100, // first block request (offset 0, no progress yet)
1920
+ 100,
1921
+ 30100, // second block request (offset 64, triggers 12.5% progress)
1922
+ 100,
1923
+ 30100, // third block request (offset 128, triggers 25% progress)
1924
+ 100,
1925
+ 30100, // fourth block request (offset 192, triggers 37.5% progress)
1926
+ 100,
1927
+ 30100, // fifth block request (offset 256, triggers 50% progress)
1928
+ 100,
1929
+ 30100, // sixth block request (offset 320, triggers 62.5% progress)
1930
+ 100,
1931
+ 30100, // seventh block request (offset 384, triggers 75% progress)
1932
+ 100,
1933
+ 30100, // eighth block request (offset 448, triggers 87.5% progress)
1934
+ 100,
1935
+ 30100, // ninth block request (offset 512, triggers 87.5% progress)
1936
+ 100,
1937
+ ];
1938
+ const nowSpy = vi.spyOn(performance, "now").mockImplementation(() => {
1939
+ const next = timeDelays.shift();
1940
+ // For any additional calls, add small time
1941
+ currentTime += next ?? 100;
1942
+
1943
+ return currentTime;
1944
+ });
1945
+
1946
+ const {run, device, onProgress} = createDevice({
1947
+ image,
1948
+ source: {url: "https://example.com/fw.ota"},
1949
+ requestPayload,
1950
+ dataSettings,
1951
+ behavior: {baseSize, sendUpgradeEnd: true},
1952
+ });
1953
+
1954
+ const [from, to] = await run();
1955
+ nowSpy.mockRestore();
1956
+
1957
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
1958
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
1959
+
1960
+ // With 521 bytes and 64-byte chunks, we have 8 blocks
1961
+ expect(onProgress).toHaveBeenNthCalledWith(1, 0, 0.45); // Initial call with estimate (non-mocked time, so tiny due to tiny # of blocks)
1962
+ expect(onProgress).toHaveBeenNthCalledWith(2, 12.28, 217); // After 1st block sent (64/521)
1963
+ expect(onProgress).toHaveBeenNthCalledWith(3, 24.57, 186); // After 2nd block sent (128/521)
1964
+ expect(onProgress).toHaveBeenNthCalledWith(4, 36.85, 156); // After 3rd block sent (192/521)
1965
+ expect(onProgress).toHaveBeenNthCalledWith(5, 49.14, 125); // After 4th block sent (256/521)
1966
+ expect(onProgress).toHaveBeenNthCalledWith(6, 61.42, 95); // After 5th block sent (320/521)
1967
+ expect(onProgress).toHaveBeenNthCalledWith(7, 73.7, 65); // After 6th block sent (384/521)
1968
+ expect(onProgress).toHaveBeenNthCalledWith(8, 85.99, 34); // After 7th block sent (448/521)
1969
+ expect(onProgress).toHaveBeenNthCalledWith(9, 98.27, 4); // After 7th block sent (512/521)
1970
+ expect(onProgress).toHaveBeenNthCalledWith(10, 100, 0); // Final completion
1971
+ expect(device.otaInProgress).toStrictEqual(false);
1972
+ });
1973
+
1974
+ it("throttles when response delay is configured", async () => {
1975
+ const fileName = "custom.ota";
1976
+ const image = mockImage(320);
1977
+ const filePath = writeMockImage(image, fileName);
1978
+ const meta: ZigbeeOtaImageMeta = {
1979
+ fileName,
1980
+ url: filePath,
1981
+ manufacturerCode: image.header.manufacturerCode,
1982
+ imageType: image.header.imageType,
1983
+ fileVersion: image.header.fileVersion,
1984
+ };
1985
+ fetchIndexEntries = [meta];
1986
+ firmwareBuffer = image.raw;
1987
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
1988
+ fieldControl: 0,
1989
+ manufacturerCode: image.header.manufacturerCode,
1990
+ imageType: image.header.imageType,
1991
+ fileVersion: image.header.fileVersion - 1,
1992
+ };
1993
+ const baseSize = 40;
1994
+ const responseDelay = 25; // keep this low so we don't have to mock it
1995
+ const dataSettings: OtaDataSettings = {requestTimeout: 1000, responseDelay, baseSize};
1996
+ const startTime = performance.now();
1997
+ const {run, device} = createDevice({
1998
+ image,
1999
+ source: {url: "https://example.com/fw.ota"},
2000
+ requestPayload,
2001
+ dataSettings,
2002
+ behavior: {baseSize, sendUpgradeEnd: true},
2003
+ });
2004
+
2005
+ const [from, to] = await run();
2006
+ const elapsedTime = performance.now() - startTime;
2007
+
2008
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2009
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
2010
+
2011
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
2012
+ const minimumExpectedTime = (expectedBlocks - 1) * responseDelay;
2013
+
2014
+ expect(elapsedTime).toBeGreaterThanOrEqual(minimumExpectedTime);
2015
+ expect(device.otaInProgress).toStrictEqual(false);
2016
+ });
2017
+
2018
+ it("cancels scheduled OTA when completed", async () => {
2019
+ const fileName = "custom.ota";
2020
+ const image = mockImage(320);
2021
+ const filePath = writeMockImage(image, fileName);
2022
+ const meta: ZigbeeOtaImageMeta = {
2023
+ fileName,
2024
+ url: filePath,
2025
+ manufacturerCode: image.header.manufacturerCode,
2026
+ imageType: image.header.imageType,
2027
+ fileVersion: image.header.fileVersion,
2028
+ };
2029
+ fetchIndexEntries = [meta];
2030
+ firmwareBuffer = image.raw;
2031
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2032
+ fieldControl: 0,
2033
+ manufacturerCode: image.header.manufacturerCode,
2034
+ imageType: image.header.imageType,
2035
+ fileVersion: image.header.fileVersion - 1,
2036
+ };
2037
+ const baseSize = 40;
2038
+ const dataSettings: OtaDataSettings = {requestTimeout: 10, responseDelay: 0, baseSize};
2039
+
2040
+ const {run, device} = createDevice({
2041
+ image,
2042
+ source: undefined,
2043
+ requestPayload,
2044
+ dataSettings,
2045
+ behavior: {baseSize, sendUpgradeEnd: true},
2046
+ });
2047
+
2048
+ device.scheduleOta({url: filePath});
2049
+
2050
+ expect(device.scheduledOta).toStrictEqual({url: filePath});
2051
+
2052
+ const [from, to] = await run();
2053
+
2054
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2055
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
2056
+ expect(device.scheduledOta).toStrictEqual(undefined);
2057
+ expect(device.otaInProgress).toStrictEqual(false);
2058
+ });
2059
+
2060
+ it("keeps scheduled OTA on failure", async () => {
2061
+ const fileName = "custom.ota";
2062
+ const image = mockImage(320);
2063
+ const filePath = writeMockImage(image, fileName);
2064
+ const meta: ZigbeeOtaImageMeta = {
2065
+ fileName,
2066
+ url: filePath,
2067
+ manufacturerCode: image.header.manufacturerCode,
2068
+ imageType: image.header.imageType,
2069
+ fileVersion: image.header.fileVersion,
2070
+ };
2071
+ fetchIndexEntries = [meta];
2072
+ firmwareBuffer = image.raw;
2073
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2074
+ fieldControl: 0,
2075
+ manufacturerCode: image.header.manufacturerCode,
2076
+ imageType: image.header.imageType,
2077
+ fileVersion: image.header.fileVersion + 1,
2078
+ };
2079
+ const baseSize = 40;
2080
+ const dataSettings: OtaDataSettings = {requestTimeout: 10, responseDelay: 0, baseSize};
2081
+
2082
+ const {run, device} = createDevice({
2083
+ image,
2084
+ source: undefined,
2085
+ requestPayload,
2086
+ dataSettings,
2087
+ behavior: {baseSize, stopAfterBlocks: 1, sendUpgradeEnd: false},
2088
+ });
2089
+
2090
+ device.scheduleOta({url: filePath, downgrade: true});
2091
+
2092
+ expect(device.scheduledOta).toStrictEqual({url: filePath, downgrade: true});
2093
+
2094
+ await expect(run()).rejects.toThrow(/did not start\/finish firmware download/);
2095
+
2096
+ expect(device.scheduledOta).toStrictEqual({url: filePath, downgrade: true});
2097
+ expect(device.otaInProgress).toStrictEqual(false);
2098
+ });
2099
+
2100
+ it("uses custom firmware in dataDir", async () => {
2101
+ const fileName = "mycustom.ota";
2102
+ const image = mockImage(300, Zcl.ManufacturerCode.ABB);
2103
+ firmwareBuffer = image.raw;
2104
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2105
+ fieldControl: 0,
2106
+ manufacturerCode: image.header.manufacturerCode,
2107
+ imageType: image.header.imageType,
2108
+ fileVersion: image.header.fileVersion + 1,
2109
+ };
2110
+ const baseSize = 41;
2111
+ const {run, device} = createDevice({
2112
+ image,
2113
+ source: {url: "mycustom.ota", downgrade: true},
2114
+ requestPayload,
2115
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2116
+ behavior: {baseSize, sendUpgradeEnd: true},
2117
+ });
2118
+ const tempDataDir = fs.mkdtempSync("zh-ota");
2119
+
2120
+ tempDirs.push(tempDataDir);
2121
+ fs.writeFileSync(path.join(tempDataDir, fileName), image.raw);
2122
+ otaHelpers.setOtaConfiguration(tempDataDir, undefined);
2123
+
2124
+ const [from, to] = await run();
2125
+
2126
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2127
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
2128
+ expect(device.otaInProgress).toStrictEqual(false);
2129
+ });
2130
+
2131
+ it("recovers from image block response failures", async () => {
2132
+ const [image] = await loadImage(OTA_FILES[2]);
2133
+ firmwareBuffer = image.raw;
2134
+ const baseSize = 64;
2135
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
2136
+ const dataSettings: OtaDataSettings = {requestTimeout: 100, responseDelay: 0, baseSize};
2137
+ const {run, device, endpoint, onProgress} = createDevice({
2138
+ image,
2139
+ source: {},
2140
+ dataSettings,
2141
+ behavior: {baseSize, sendUpgradeEnd: true, failBlockResponse: true},
2142
+ });
2143
+
2144
+ const [from, to] = await run();
2145
+
2146
+ expect(from.fileVersion).toStrictEqual(image.header.fileVersion - 1);
2147
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
2148
+ expect(getResponses(endpoint, "queryNextImageResponse").length).toStrictEqual(1);
2149
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2150
+ "genOta",
2151
+ "queryNextImageResponse",
2152
+ expect.objectContaining({status: Zcl.Status.SUCCESS}),
2153
+ undefined,
2154
+ 1,
2155
+ );
2156
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks + 1 /* 1 retried */);
2157
+ const calls = getResponses(endpoint, "imageBlockResponse");
2158
+ const lastOffset = (expectedBlocks - 1) * baseSize;
2159
+ const lastSize = image.header.totalImageSize - lastOffset;
2160
+ const retryBlock = {fileOffset: 0, dataSize: baseSize, data: image.raw.subarray(0, baseSize)};
2161
+ expect(calls[0][2]).toEqual(expect.objectContaining(retryBlock));
2162
+ expect(calls[1][2]).toEqual(expect.objectContaining(retryBlock));
2163
+ expect(calls[2][2]).toEqual(
2164
+ expect.objectContaining({fileOffset: baseSize, dataSize: baseSize, data: image.raw.subarray(baseSize, baseSize * 2)}),
2165
+ );
2166
+ expect(calls[3][2]).toEqual(
2167
+ expect.objectContaining({fileOffset: baseSize * 2, dataSize: baseSize, data: image.raw.subarray(baseSize * 2, baseSize * 3)}),
2168
+ );
2169
+ expect(calls[expectedBlocks][2]).toEqual(
2170
+ expect.objectContaining({fileOffset: lastOffset, dataSize: lastSize, data: image.raw.subarray(image.raw.length - lastSize)}),
2171
+ );
2172
+ expect(getResponses(endpoint, "upgradeEndResponse").length).toStrictEqual(1);
2173
+ expect(onProgress).toHaveBeenCalled();
2174
+ expect(device.otaInProgress).toStrictEqual(false);
2175
+ });
2176
+
2177
+ it("prevents running two OTAs on same device", async () => {
2178
+ const fileName = "custom.ota";
2179
+ const image = mockImage(320);
2180
+ const filePath = writeMockImage(image, fileName);
2181
+ const meta: ZigbeeOtaImageMeta = {
2182
+ fileName,
2183
+ url: filePath,
2184
+ manufacturerCode: image.header.manufacturerCode,
2185
+ imageType: image.header.imageType,
2186
+ fileVersion: image.header.fileVersion,
2187
+ };
2188
+ fetchIndexEntries = [meta];
2189
+ firmwareBuffer = image.raw;
2190
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2191
+ fieldControl: 0,
2192
+ manufacturerCode: image.header.manufacturerCode,
2193
+ imageType: image.header.imageType,
2194
+ fileVersion: image.header.fileVersion - 1,
2195
+ };
2196
+ const baseSize = 40;
2197
+ const dataSettings: OtaDataSettings = {requestTimeout: 10, responseDelay: 0, baseSize};
2198
+
2199
+ const {run, device} = createDevice({
2200
+ image,
2201
+ source: {url: filePath},
2202
+ requestPayload,
2203
+ dataSettings,
2204
+ behavior: {baseSize, sendUpgradeEnd: true},
2205
+ });
2206
+
2207
+ const p1 = run();
2208
+
2209
+ expect(device.otaInProgress).toStrictEqual(true);
2210
+
2211
+ const p2 = run();
2212
+ const [from, to] = await p1;
2213
+
2214
+ await expect(p2).rejects.toThrow("OTA already in progress for 0x1");
2215
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2216
+ expect(to?.fileVersion).toStrictEqual(image.header.fileVersion);
2217
+ expect(device.otaInProgress).toStrictEqual(false);
2218
+ });
2219
+
2220
+ it("fails when check fails", async () => {
2221
+ const fileName = OTA_FILES[0];
2222
+ const [image] = await loadImage(fileName);
2223
+ firmwareBuffer = image.raw;
2224
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2225
+ fieldControl: 0,
2226
+ manufacturerCode: image.header.manufacturerCode,
2227
+ imageType: image.header.imageType,
2228
+ fileVersion: image.header.fileVersion + 1,
2229
+ };
2230
+ const baseSize = 64;
2231
+ const {device, run} = createDevice({
2232
+ image,
2233
+ source: {downgrade: true},
2234
+ requestPayload,
2235
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2236
+ behavior: {baseSize, sendUpgradeEnd: true},
2237
+ });
2238
+
2239
+ vi.spyOn(device, "checkOta").mockRejectedValueOnce(new Error("check-fail"));
2240
+
2241
+ await expect(run()).rejects.toThrow("check-fail");
2242
+ expect(device.otaInProgress).toStrictEqual(false);
2243
+ });
2244
+
2245
+ it("fails when device stops sending data requests", async () => {
2246
+ const fileName = OTA_FILES[0];
2247
+ const [image] = await loadImage(fileName);
2248
+ firmwareBuffer = image.raw;
2249
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2250
+ fieldControl: 0,
2251
+ manufacturerCode: image.header.manufacturerCode,
2252
+ imageType: image.header.imageType,
2253
+ fileVersion: image.header.fileVersion - 1,
2254
+ };
2255
+ const baseSize = 50;
2256
+ const expectedBlocks = 1;
2257
+ const {run, device, endpoint} = createDevice({
2258
+ image,
2259
+ source: {},
2260
+ requestPayload,
2261
+ dataSettings: {requestTimeout: 30, responseDelay: 0, baseSize},
2262
+ behavior: {baseSize, stopAfterBlocks: 1, sendUpgradeEnd: false},
2263
+ });
2264
+
2265
+ await expect(run()).rejects.toThrow(/did not start\/finish firmware download/);
2266
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
2267
+ expect(device.otaInProgress).toStrictEqual(false);
2268
+ });
2269
+
2270
+ it("fails when upgrade end never arrives", async () => {
2271
+ const fileName = OTA_FILES[0];
2272
+ const [image] = await loadImage(fileName);
2273
+ firmwareBuffer = image.raw;
2274
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2275
+ fieldControl: 0,
2276
+ manufacturerCode: image.header.manufacturerCode,
2277
+ imageType: image.header.imageType,
2278
+ fileVersion: image.header.fileVersion - 1,
2279
+ };
2280
+ const baseSize = 30;
2281
+ const expectedBlocks = Math.ceil(image.header.totalImageSize / baseSize);
2282
+
2283
+ const {run, device, endpoint} = createDevice({
2284
+ image,
2285
+ source: {},
2286
+ requestPayload,
2287
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2288
+ behavior: {baseSize, sendUpgradeEnd: false},
2289
+ });
2290
+
2291
+ await expect(run()).rejects.toThrow(/did not start\/finish firmware download/);
2292
+ expect(getResponses(endpoint, "imageBlockResponse").length).toStrictEqual(expectedBlocks);
2293
+ expect(device.otaInProgress).toStrictEqual(false);
2294
+ });
2295
+
2296
+ it("fails when device sends upgrade end request with INVALID_IMAGE after image fully sent", async () => {
2297
+ const fileName = OTA_FILES[1];
2298
+ const [image] = await loadImage(fileName);
2299
+ firmwareBuffer = image.raw;
2300
+ const baseSize = 64;
2301
+ const {run, device, endpoint} = createDevice({
2302
+ image,
2303
+ source: {},
2304
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2305
+ behavior: {baseSize, sendUpgradeEnd: true, upgradeEndStatus: Zcl.Status.INVALID_IMAGE},
2306
+ });
2307
+
2308
+ await expect(run()).rejects.toThrow(/INVALID_IMAGE/);
2309
+ expect(endpoint.defaultResponse).toHaveBeenCalledWith(
2310
+ UPGRADE_END_REQUEST_ID,
2311
+ Zcl.Status.SUCCESS,
2312
+ Zcl.Clusters.genOta.ID,
2313
+ expect.any(Number),
2314
+ );
2315
+ expect(device.otaInProgress).toStrictEqual(false);
2316
+ });
2317
+
2318
+ it("fails when device sends upgrade end request with ABORT after a certain number of blocks", async () => {
2319
+ const fileName = OTA_FILES[0];
2320
+ const [image] = await loadImage(fileName);
2321
+ firmwareBuffer = image.raw;
2322
+ const baseSize = 64;
2323
+ const {run, device, endpoint} = createDevice({
2324
+ image,
2325
+ source: {},
2326
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2327
+ behavior: {
2328
+ baseSize,
2329
+ sendUpgradeEnd: true,
2330
+ upgradeEndStatus: Zcl.Status.ABORT,
2331
+ upgradeEndAfterBlocks: 2,
2332
+ },
2333
+ });
2334
+
2335
+ await expect(run()).rejects.toThrow(/ABORT/);
2336
+ expect(endpoint.defaultResponse).toHaveBeenCalledWith(
2337
+ UPGRADE_END_REQUEST_ID,
2338
+ Zcl.Status.SUCCESS,
2339
+ Zcl.Clusters.genOta.ID,
2340
+ expect.any(Number),
2341
+ );
2342
+ expect(device.otaInProgress).toStrictEqual(false);
2343
+ });
2344
+
2345
+ it("fails when image notify fails", async () => {
2346
+ const fileName = OTA_FILES[0];
2347
+ const [image] = await loadImage(fileName);
2348
+ firmwareBuffer = image.raw;
2349
+ const baseSize = 64;
2350
+ const {run, device, endpoint} = createDevice({
2351
+ image,
2352
+ source: {},
2353
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2354
+ behavior: {baseSize, sendUpgradeEnd: true},
2355
+ });
2356
+ endpoint.commandResponse = vi.fn((_cluster, command) => {
2357
+ if (command === "imageNotify") {
2358
+ return Promise.reject(new Error("image-notify-fail"));
2359
+ }
2360
+
2361
+ return Promise.resolve();
2362
+ });
2363
+
2364
+ await expect(run()).rejects.toThrow("Device didn't respond to OTA request");
2365
+ expect(device.otaInProgress).toStrictEqual(false);
2366
+ });
2367
+
2368
+ it("fails when upgrade end response fails", async () => {
2369
+ const fileName = OTA_FILES[0];
2370
+ const [image] = await loadImage(fileName);
2371
+ firmwareBuffer = image.raw;
2372
+ const baseSize = 64;
2373
+ const {run, device, endpoint} = createDevice({
2374
+ image,
2375
+ source: {},
2376
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2377
+ behavior: {baseSize, sendUpgradeEnd: true},
2378
+ });
2379
+ endpoint.commandResponse = vi.fn((_cluster, command) => {
2380
+ if (command === "upgradeEndResponse") {
2381
+ return Promise.reject(new Error("upgrade-end-fail"));
2382
+ }
2383
+
2384
+ return Promise.resolve();
2385
+ });
2386
+
2387
+ await expect(run()).rejects.toThrow(/upgrade end response failed/i);
2388
+ expect(device.otaInProgress).toStrictEqual(false);
2389
+ });
2390
+
2391
+ it("fails when query next image response fails", async () => {
2392
+ const fileName = OTA_FILES[0];
2393
+ const [image] = await loadImage(fileName);
2394
+ firmwareBuffer = image.raw;
2395
+ const baseSize = 50;
2396
+ const {run, device, endpoint} = createDevice({
2397
+ image,
2398
+ source: {},
2399
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2400
+ behavior: {baseSize, sendUpgradeEnd: true},
2401
+ });
2402
+ endpoint.commandResponse = vi.fn((_cluster, command) => {
2403
+ if (command === "queryNextImageResponse") {
2404
+ return Promise.reject(new Error("query-next-image-fail"));
2405
+ }
2406
+
2407
+ return Promise.resolve();
2408
+ });
2409
+
2410
+ await expect(run()).rejects.toThrow("query-next-image-fail");
2411
+ expect(device.otaInProgress).toStrictEqual(false);
2412
+ });
2413
+
2414
+ it("does not throw on failed default response after non-success upgrade end request", async () => {
2415
+ const fileName = OTA_FILES[0];
2416
+ const [image] = await loadImage(fileName);
2417
+ firmwareBuffer = image.raw;
2418
+ const baseSize = 64;
2419
+ const {run, device, endpoint} = createDevice({
2420
+ image,
2421
+ source: {},
2422
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2423
+ behavior: {baseSize, sendUpgradeEnd: true, upgradeEndStatus: Zcl.Status.ABORT},
2424
+ });
2425
+ const epDefaultResponse = endpoint.defaultResponse as unknown as ReturnType<typeof vi.fn>;
2426
+
2427
+ epDefaultResponse.mockRejectedValueOnce(new Error("default-response-failed"));
2428
+
2429
+ await expect(run()).rejects.toThrow(/ABORT/);
2430
+ expect(epDefaultResponse).toHaveBeenCalledTimes(1);
2431
+ expect(epDefaultResponse.mock.settledResults[0]).toStrictEqual({type: "rejected", value: new Error("default-response-failed")});
2432
+ expect(device.otaInProgress).toStrictEqual(false);
2433
+ });
2434
+
2435
+ it("allows bypassing version check with force meta", async () => {
2436
+ const fileName = "custom.ota";
2437
+ const image = mockImage(300);
2438
+ firmwareBuffer = image.raw;
2439
+ const filePath = writeMockImage(image, fileName);
2440
+ const meta: ZigbeeOtaImageMeta = {
2441
+ fileName,
2442
+ url: filePath,
2443
+ manufacturerCode: image.header.manufacturerCode,
2444
+ imageType: image.header.imageType,
2445
+ fileVersion: image.header.fileVersion,
2446
+ force: true,
2447
+ };
2448
+ fetchIndexEntries = [meta];
2449
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2450
+ fieldControl: 0,
2451
+ manufacturerCode: image.header.manufacturerCode,
2452
+ imageType: image.header.imageType,
2453
+ fileVersion: image.header.fileVersion,
2454
+ };
2455
+ const baseSize = 25;
2456
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2457
+ const {run, device} = createDevice({
2458
+ image,
2459
+ source: {},
2460
+ requestPayload,
2461
+ dataSettings,
2462
+ behavior: {baseSize, sendUpgradeEnd: true},
2463
+ });
2464
+ const [from, to] = await run();
2465
+
2466
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2467
+ expect(to?.fileVersion).toStrictEqual(requestPayload.fileVersion);
2468
+ expect(device.otaInProgress).toStrictEqual(false);
2469
+ });
2470
+
2471
+ it("returns NO_IMAGE_AVAILABLE when device version matches available in repo", async () => {
2472
+ const [image] = await loadImage(OTA_FILES[0]);
2473
+ firmwareBuffer = image.raw;
2474
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2475
+ fieldControl: 0,
2476
+ manufacturerCode: image.header.manufacturerCode,
2477
+ imageType: image.header.imageType,
2478
+ fileVersion: image.header.fileVersion,
2479
+ };
2480
+ const baseSize = 16;
2481
+ const logErrorSpy = vi.spyOn(logger, "error");
2482
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2483
+ const {run, device, endpoint} = createDevice({
2484
+ image,
2485
+ source: {},
2486
+ requestPayload,
2487
+ dataSettings,
2488
+ behavior: {baseSize, sendUpgradeEnd: true},
2489
+ });
2490
+ const [from, to] = await run();
2491
+
2492
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2493
+ expect(to).toBeUndefined();
2494
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2495
+ "genOta",
2496
+ "queryNextImageResponse",
2497
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2498
+ undefined,
2499
+ 1,
2500
+ );
2501
+ expect(logErrorSpy).not.toHaveBeenCalled();
2502
+ expect(device.otaInProgress).toStrictEqual(false);
2503
+ });
2504
+
2505
+ it("returns NO_IMAGE_AVAILABLE when device version is above available in repo (upgrade)", async () => {
2506
+ const [image] = await loadImage(OTA_FILES[0]);
2507
+ firmwareBuffer = image.raw;
2508
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2509
+ fieldControl: 0,
2510
+ manufacturerCode: image.header.manufacturerCode,
2511
+ imageType: image.header.imageType,
2512
+ fileVersion: image.header.fileVersion + 1,
2513
+ };
2514
+ const baseSize = 16;
2515
+ const logErrorSpy = vi.spyOn(logger, "error");
2516
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2517
+ const {run, device, endpoint} = createDevice({
2518
+ image,
2519
+ source: {},
2520
+ requestPayload,
2521
+ dataSettings,
2522
+ behavior: {baseSize, sendUpgradeEnd: true},
2523
+ });
2524
+ const [from, to] = await run();
2525
+
2526
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2527
+ expect(to).toBeUndefined();
2528
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2529
+ "genOta",
2530
+ "queryNextImageResponse",
2531
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2532
+ undefined,
2533
+ 1,
2534
+ );
2535
+ expect(logErrorSpy).not.toHaveBeenCalled();
2536
+ expect(device.otaInProgress).toStrictEqual(false);
2537
+ });
2538
+
2539
+ it("returns NO_IMAGE_AVAILABLE when device version is below available in repo (downgrade)", async () => {
2540
+ const [image] = await loadImage(OTA_FILES[0]);
2541
+ firmwareBuffer = image.raw;
2542
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2543
+ fieldControl: 0,
2544
+ manufacturerCode: image.header.manufacturerCode,
2545
+ imageType: image.header.imageType,
2546
+ fileVersion: image.header.fileVersion - 1,
2547
+ };
2548
+ const baseSize = 16;
2549
+ const logErrorSpy = vi.spyOn(logger, "error");
2550
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2551
+ const {run, device, endpoint} = createDevice({
2552
+ image,
2553
+ source: {downgrade: true},
2554
+ requestPayload,
2555
+ dataSettings,
2556
+ behavior: {baseSize, sendUpgradeEnd: true},
2557
+ });
2558
+ const [from, to] = await run();
2559
+
2560
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2561
+ expect(to).toBeUndefined();
2562
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2563
+ "genOta",
2564
+ "queryNextImageResponse",
2565
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2566
+ undefined,
2567
+ 1,
2568
+ );
2569
+ expect(logErrorSpy).not.toHaveBeenCalled();
2570
+ expect(device.otaInProgress).toStrictEqual(false);
2571
+ });
2572
+
2573
+ it("returns NO_IMAGE_AVAILABLE when device version matches available at given URL", async () => {
2574
+ const [image] = await loadImage(OTA_FILES[0]);
2575
+ firmwareBuffer = image.raw;
2576
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2577
+ fieldControl: 0,
2578
+ manufacturerCode: image.header.manufacturerCode,
2579
+ imageType: image.header.imageType,
2580
+ fileVersion: image.header.fileVersion,
2581
+ };
2582
+ const baseSize = 16;
2583
+ const logErrorSpy = vi.spyOn(logger, "error");
2584
+ const logDebugSpy = vi.spyOn(logger, "debug");
2585
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2586
+ const {run, device, endpoint} = createDevice({
2587
+ image,
2588
+ source: {url: "https://example.com/fw.ota"},
2589
+ requestPayload,
2590
+ dataSettings,
2591
+ behavior: {baseSize, sendUpgradeEnd: true},
2592
+ });
2593
+ const [from, to] = await run();
2594
+
2595
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2596
+ expect(to).toBeUndefined();
2597
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2598
+ "genOta",
2599
+ "queryNextImageResponse",
2600
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2601
+ undefined,
2602
+ 1,
2603
+ );
2604
+ expect(logErrorSpy).not.toHaveBeenCalled();
2605
+ const calls = logDebugSpy.mock.calls.map((c) => (typeof c[0] === "string" ? c[0] : c[0]()));
2606
+ expect(calls[0]).toMatch("Downloading firmware image from 'https://example.com/fw.ota'");
2607
+ expect(calls[1]).toMatch("Parsed image from 'https://example.com/fw.ota' for 0x1");
2608
+ expect(device.otaInProgress).toStrictEqual(false);
2609
+ });
2610
+
2611
+ it("returns NO_IMAGE_AVAILABLE when parsing repo firmware fails", async () => {
2612
+ const [image] = await loadImage(OTA_FILES[0]);
2613
+ firmwareBuffer = image.raw;
2614
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2615
+ fieldControl: 0,
2616
+ manufacturerCode: image.header.manufacturerCode,
2617
+ imageType: image.header.imageType,
2618
+ fileVersion: image.header.fileVersion - 1,
2619
+ };
2620
+ const baseSize = 16;
2621
+ const logErrorSpy = vi.spyOn(logger, "error");
2622
+ const parseOtaImageSpy = vi.spyOn(otaHelpers, "parseOtaImage").mockImplementation(() => {
2623
+ throw new Error("fail");
2624
+ });
2625
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2626
+ const {run, device, endpoint} = createDevice({
2627
+ image,
2628
+ source: {},
2629
+ requestPayload,
2630
+ dataSettings,
2631
+ behavior: {baseSize, sendUpgradeEnd: true},
2632
+ });
2633
+ const [from, to] = await run();
2634
+
2635
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2636
+ expect(to).toBeUndefined();
2637
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2638
+ "genOta",
2639
+ "queryNextImageResponse",
2640
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2641
+ undefined,
2642
+ 1,
2643
+ );
2644
+ expect(logErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Failed to parse OTA image"), "zh:controller:device");
2645
+ expect(device.otaInProgress).toStrictEqual(false);
2646
+
2647
+ parseOtaImageSpy.mockRestore();
2648
+ });
2649
+
2650
+ it("returns NO_IMAGE_AVAILABLE when parsing custom firmware fails", async () => {
2651
+ const [image] = await loadImage(OTA_FILES[0]);
2652
+ firmwareBuffer = Buffer.from(image.raw);
2653
+ firmwareBuffer[0] = 0xff;
2654
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2655
+ fieldControl: 0,
2656
+ manufacturerCode: image.header.manufacturerCode,
2657
+ imageType: image.header.imageType,
2658
+ fileVersion: image.header.fileVersion - 1,
2659
+ };
2660
+ const baseSize = 16;
2661
+ const logErrorSpy = vi.spyOn(logger, "error");
2662
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2663
+ const {run, device, endpoint} = createDevice({
2664
+ image,
2665
+ source: {url: "https://example.com/fw.ota"},
2666
+ requestPayload,
2667
+ dataSettings,
2668
+ behavior: {baseSize, sendUpgradeEnd: true},
2669
+ });
2670
+ const [from, to] = await run();
2671
+
2672
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2673
+ expect(to).toBeUndefined();
2674
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2675
+ "genOta",
2676
+ "queryNextImageResponse",
2677
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2678
+ undefined,
2679
+ 1,
2680
+ );
2681
+ expect(logErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Failed to parse OTA image"), "zh:controller:device");
2682
+ expect(device.otaInProgress).toStrictEqual(false);
2683
+ });
2684
+
2685
+ it("returns NO_IMAGE_AVAILABLE when parsing custom firmware in dataDir fails", async () => {
2686
+ const fileName = "mycustom.ota";
2687
+ const image = mockImage(300, Zcl.ManufacturerCode.ABB);
2688
+ firmwareBuffer = image.raw;
2689
+ firmwareBuffer[0] = 0xff;
2690
+ const baseSize = 41;
2691
+ const logErrorSpy = vi.spyOn(logger, "error");
2692
+ const {run, device, endpoint} = createDevice({
2693
+ image,
2694
+ source: {url: "mycustom.ota", downgrade: true},
2695
+ requestPayload: undefined,
2696
+ dataSettings: {requestTimeout: 1000, responseDelay: 0, baseSize},
2697
+ behavior: {baseSize, sendUpgradeEnd: true},
2698
+ });
2699
+ const tempDataDir = fs.mkdtempSync("zh-ota");
2700
+
2701
+ tempDirs.push(tempDataDir);
2702
+ fs.writeFileSync(path.join(tempDataDir, fileName), image.raw);
2703
+ otaHelpers.setOtaConfiguration(tempDataDir, undefined);
2704
+
2705
+ const [from, to] = await run();
2706
+
2707
+ expect(from.fileVersion).toStrictEqual(image.header.fileVersion + 1);
2708
+ expect(to).toBeUndefined();
2709
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2710
+ "genOta",
2711
+ "queryNextImageResponse",
2712
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2713
+ undefined,
2714
+ 1,
2715
+ );
2716
+ expect(logErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Failed to parse OTA image"), "zh:controller:device");
2717
+ expect(device.otaInProgress).toStrictEqual(false);
2718
+ });
2719
+
2720
+ it("returns NO_IMAGE_AVAILABLE when firmware fetching fails", async () => {
2721
+ fetchMock.mockResolvedValueOnce(fetchMockIndex).mockResolvedValueOnce(fetchMockFail);
2722
+
2723
+ const fileName = OTA_FILES[0];
2724
+ const [image] = await loadImage(fileName);
2725
+ const meta: ZigbeeOtaImageMeta = {
2726
+ fileName,
2727
+ url: "https://example.com/custom.ota",
2728
+ manufacturerCode: image.header.manufacturerCode,
2729
+ imageType: image.header.imageType,
2730
+ fileVersion: image.header.fileVersion,
2731
+ };
2732
+ fetchIndexEntries = [meta];
2733
+ firmwareBuffer = image.raw;
2734
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2735
+ fieldControl: 0,
2736
+ manufacturerCode: image.header.manufacturerCode,
2737
+ imageType: image.header.imageType,
2738
+ fileVersion: image.header.fileVersion - 1,
2739
+ };
2740
+ const baseSize = 16;
2741
+ const logErrorSpy = vi.spyOn(logger, "error");
2742
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2743
+ const {run, device, endpoint} = createDevice({
2744
+ image,
2745
+ source: {},
2746
+ requestPayload,
2747
+ dataSettings,
2748
+ behavior: {baseSize, sendUpgradeEnd: true},
2749
+ });
2750
+ const [from, to] = await run();
2751
+
2752
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2753
+ expect(to).toBeUndefined();
2754
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2755
+ "genOta",
2756
+ "queryNextImageResponse",
2757
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2758
+ undefined,
2759
+ 1,
2760
+ );
2761
+ expect(logErrorSpy).toHaveBeenCalledWith(
2762
+ expect.stringContaining("Invalid response from https://example.com/custom.ota status=403"),
2763
+ "zh:controller:device",
2764
+ );
2765
+ expect(device.otaInProgress).toStrictEqual(false);
2766
+ });
2767
+
2768
+ it("returns NO_IMAGE_AVAILABLE when custom file fails checksum", async () => {
2769
+ const fileName = OTA_FILES[0];
2770
+ const [image, filePath] = await loadImage(fileName);
2771
+ const meta: ZigbeeOtaImageMeta = {
2772
+ fileName,
2773
+ url: filePath,
2774
+ manufacturerCode: image.header.manufacturerCode,
2775
+ imageType: image.header.imageType,
2776
+ fileVersion: image.header.fileVersion,
2777
+ sha512: "invalid",
2778
+ };
2779
+ fetchIndexEntries = [meta];
2780
+ firmwareBuffer = image.raw;
2781
+ const requestPayload: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2782
+ fieldControl: 0,
2783
+ manufacturerCode: image.header.manufacturerCode,
2784
+ imageType: image.header.imageType,
2785
+ fileVersion: image.header.fileVersion - 1,
2786
+ };
2787
+ const baseSize = 43;
2788
+ const logErrorSpy = vi.spyOn(logger, "error");
2789
+ const dataSettings: OtaDataSettings = {requestTimeout: 150000, responseDelay: 0, baseSize};
2790
+ const {run, device, endpoint} = createDevice({
2791
+ image,
2792
+ source: {},
2793
+ requestPayload,
2794
+ dataSettings,
2795
+ behavior: {baseSize, sendUpgradeEnd: true},
2796
+ });
2797
+ const [from, to] = await run();
2798
+
2799
+ expect(from.fileVersion).toStrictEqual(requestPayload.fileVersion);
2800
+ expect(to).toBeUndefined();
2801
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2802
+ "genOta",
2803
+ "queryNextImageResponse",
2804
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2805
+ undefined,
2806
+ 1,
2807
+ );
2808
+ expect(logErrorSpy).toHaveBeenCalledWith(expect.stringContaining("File checksum validation failed"), "zh:controller:device");
2809
+ expect(device.otaInProgress).toStrictEqual(false);
2810
+ });
2811
+
2812
+ it("returns NO_IMAGE_AVAILABLE when index has no entries", async () => {
2813
+ fetchIndexEntries = [];
2814
+ const [image] = await loadImage(OTA_FILES[1]);
2815
+ firmwareBuffer = image.raw;
2816
+ const dataSettings: OtaDataSettings = {requestTimeout: 0, responseDelay: 0, baseSize: 32};
2817
+ const {run, device, endpoint} = createDevice({
2818
+ image,
2819
+ source: {},
2820
+ dataSettings,
2821
+ behavior: {baseSize: 32, sendUpgradeEnd: true},
2822
+ });
2823
+
2824
+ const [from, to] = await run();
2825
+
2826
+ expect(from.fileVersion).toStrictEqual(image.header.fileVersion - 1);
2827
+ expect(to).toBeUndefined();
2828
+ expect(endpoint.commandResponse).toHaveBeenCalledWith(
2829
+ "genOta",
2830
+ "queryNextImageResponse",
2831
+ {status: Zcl.Status.NO_IMAGE_AVAILABLE},
2832
+ undefined,
2833
+ 1,
2834
+ );
2835
+ expect(device.otaInProgress).toStrictEqual(false);
2836
+ });
2837
+ });
2838
+
2839
+ describe("Schedules / Unschedules", () => {
2840
+ it("schedules OTA request", () => {
2841
+ const {device} = createSimpleDevice({});
2842
+
2843
+ device.scheduleOta({url: "first"});
2844
+
2845
+ expect(device.scheduledOta).toStrictEqual({url: "first"});
2846
+ });
2847
+
2848
+ it("replaces previously-scheduled OTA request", () => {
2849
+ const {device} = createSimpleDevice({});
2850
+
2851
+ device.scheduleOta({url: "first"});
2852
+
2853
+ expect(device.scheduledOta).toStrictEqual({url: "first"});
2854
+
2855
+ device.scheduleOta({url: "second", downgrade: true});
2856
+
2857
+ expect(device.scheduledOta).toStrictEqual({url: "second", downgrade: true});
2858
+ });
2859
+
2860
+ it("unschedules OTA when present", () => {
2861
+ const {device} = createSimpleDevice({});
2862
+
2863
+ device.scheduleOta({url: "upgrade"});
2864
+
2865
+ expect(device.scheduledOta).toStrictEqual({url: "upgrade"});
2866
+
2867
+ device.unscheduleOta();
2868
+
2869
+ expect(device.scheduledOta).toStrictEqual(undefined);
2870
+ });
2871
+
2872
+ it("handles unscheduling when scheduled not present", () => {
2873
+ const {device} = createSimpleDevice({});
2874
+
2875
+ device.unscheduleOta();
2876
+
2877
+ expect(device.scheduledOta).toStrictEqual(undefined);
2878
+ });
2879
+ });
2880
+
2881
+ describe("Finds image", () => {
2882
+ // tests for each possibility in `findMatchingOtaImage`
2883
+
2884
+ it.each(OTA_FILES)("finds match by spec (%s)", async (fileName) => {
2885
+ const [image] = await loadImage(fileName);
2886
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2887
+ fieldControl: 0,
2888
+ manufacturerCode: image.header.manufacturerCode,
2889
+ imageType: image.header.imageType,
2890
+ fileVersion: image.header.fileVersion,
2891
+ };
2892
+ const {device} = createSimpleDevice({});
2893
+
2894
+ await expect(device.findMatchingOtaImage({}, current, {})).resolves.toMatchObject({
2895
+ fileName,
2896
+ manufacturerCode: image.header.manufacturerCode,
2897
+ imageType: image.header.imageType,
2898
+ fileVersion: image.header.fileVersion,
2899
+ });
2900
+ });
2901
+
2902
+ it("within file version range", async () => {
2903
+ const fileName = "custom.ota";
2904
+ const image = mockImage(300, undefined, undefined, 4);
2905
+ const meta: ZigbeeOtaImageMeta = {
2906
+ fileName,
2907
+ url: fileName,
2908
+ manufacturerCode: image.header.manufacturerCode,
2909
+ imageType: image.header.imageType,
2910
+ fileVersion: image.header.fileVersion,
2911
+ minFileVersion: 3,
2912
+ maxFileVersion: 5,
2913
+ };
2914
+ fetchIndexEntries.push(
2915
+ {
2916
+ ...meta,
2917
+ minFileVersion: 2,
2918
+ maxFileVersion: 3,
2919
+ fileName: "not this",
2920
+ },
2921
+ meta,
2922
+ {
2923
+ ...meta,
2924
+ minFileVersion: 4,
2925
+ maxFileVersion: 6,
2926
+ fileName: "not that",
2927
+ },
2928
+ );
2929
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2930
+ fieldControl: 0,
2931
+ manufacturerCode: image.header.manufacturerCode,
2932
+ imageType: image.header.imageType,
2933
+ fileVersion: image.header.fileVersion,
2934
+ };
2935
+ const {device} = createSimpleDevice({modelID: "", manufacturerName: "", meta: {}});
2936
+
2937
+ await expect(device.findMatchingOtaImage({}, current, {})).resolves.toMatchObject({
2938
+ fileName,
2939
+ manufacturerCode: image.header.manufacturerCode,
2940
+ imageType: image.header.imageType,
2941
+ fileVersion: image.header.fileVersion,
2942
+ minFileVersion: 3,
2943
+ maxFileVersion: 5,
2944
+ });
2945
+ });
2946
+
2947
+ it("by minFileVersion", async () => {
2948
+ const fileName = "custom.ota";
2949
+ const image = mockImage(300, undefined, undefined, 4);
2950
+ const meta: ZigbeeOtaImageMeta = {
2951
+ fileName,
2952
+ url: fileName,
2953
+ manufacturerCode: image.header.manufacturerCode,
2954
+ imageType: image.header.imageType,
2955
+ fileVersion: image.header.fileVersion,
2956
+ minFileVersion: 3,
2957
+ };
2958
+ fetchIndexEntries.push({...meta, minFileVersion: 5, fileName: "not this"}, meta, {...meta, minFileVersion: 6, fileName: "not that"});
2959
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2960
+ fieldControl: 0,
2961
+ manufacturerCode: image.header.manufacturerCode,
2962
+ imageType: image.header.imageType,
2963
+ fileVersion: image.header.fileVersion,
2964
+ };
2965
+ const {device} = createSimpleDevice({modelID: "", manufacturerName: "", meta: {}});
2966
+
2967
+ await expect(device.findMatchingOtaImage({}, current, {})).resolves.toMatchObject({
2968
+ fileName,
2969
+ manufacturerCode: image.header.manufacturerCode,
2970
+ imageType: image.header.imageType,
2971
+ fileVersion: image.header.fileVersion,
2972
+ minFileVersion: 3,
2973
+ });
2974
+ });
2975
+
2976
+ it("by maxFileVersion", async () => {
2977
+ const fileName = "custom.ota";
2978
+ const image = mockImage(300, undefined, undefined, 4);
2979
+ const meta: ZigbeeOtaImageMeta = {
2980
+ fileName,
2981
+ url: fileName,
2982
+ manufacturerCode: image.header.manufacturerCode,
2983
+ imageType: image.header.imageType,
2984
+ fileVersion: image.header.fileVersion,
2985
+ maxFileVersion: 5,
2986
+ };
2987
+ fetchIndexEntries.push({...meta, maxFileVersion: 3, fileName: "not this"}, meta, {...meta, maxFileVersion: 9, fileName: "not that"});
2988
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
2989
+ fieldControl: 0,
2990
+ manufacturerCode: image.header.manufacturerCode,
2991
+ imageType: image.header.imageType,
2992
+ fileVersion: image.header.fileVersion,
2993
+ };
2994
+ const {device} = createSimpleDevice({modelID: "", manufacturerName: "", meta: {}});
2995
+
2996
+ await expect(device.findMatchingOtaImage({}, current, {})).resolves.toMatchObject({
2997
+ fileName,
2998
+ manufacturerCode: image.header.manufacturerCode,
2999
+ imageType: image.header.imageType,
3000
+ fileVersion: image.header.fileVersion,
3001
+ maxFileVersion: 5,
3002
+ });
3003
+ });
3004
+
3005
+ it("by modelId", async () => {
3006
+ const fileName = "custom.ota";
3007
+ const image = mockImage(300, undefined, undefined, 4);
3008
+ const meta: ZigbeeOtaImageMeta = {
3009
+ fileName,
3010
+ url: fileName,
3011
+ manufacturerCode: image.header.manufacturerCode,
3012
+ imageType: image.header.imageType,
3013
+ fileVersion: image.header.fileVersion,
3014
+ modelId: "ZH",
3015
+ };
3016
+ fetchIndexEntries.push({...meta, modelId: "not this", fileName: "not this"}, meta); // non-match first to ensure not order-driven
3017
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3018
+ fieldControl: 0,
3019
+ manufacturerCode: image.header.manufacturerCode,
3020
+ imageType: image.header.imageType,
3021
+ fileVersion: image.header.fileVersion,
3022
+ };
3023
+ const {device} = createSimpleDevice({modelID: "ZH"});
3024
+
3025
+ await expect(device.findMatchingOtaImage({}, current, {})).resolves.toMatchObject({
3026
+ fileName,
3027
+ manufacturerCode: image.header.manufacturerCode,
3028
+ imageType: image.header.imageType,
3029
+ fileVersion: image.header.fileVersion,
3030
+ modelId: "ZH",
3031
+ });
3032
+ });
3033
+
3034
+ it("by extra meta modelId", async () => {
3035
+ const fileName = "custom.ota";
3036
+ const image = mockImage(300, undefined, undefined, 4);
3037
+ const meta: ZigbeeOtaImageMeta = {
3038
+ fileName,
3039
+ url: fileName,
3040
+ manufacturerCode: image.header.manufacturerCode,
3041
+ imageType: image.header.imageType,
3042
+ fileVersion: image.header.fileVersion,
3043
+ modelId: "ZH",
3044
+ };
3045
+ fetchIndexEntries.push({...meta, modelId: "not this", fileName: "not this"}, meta); // non-match first to ensure not order-driven
3046
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3047
+ fieldControl: 0,
3048
+ manufacturerCode: image.header.manufacturerCode,
3049
+ imageType: image.header.imageType,
3050
+ fileVersion: image.header.fileVersion,
3051
+ };
3052
+ const {device} = createSimpleDevice({modelID: "other"});
3053
+
3054
+ await expect(device.findMatchingOtaImage({}, current, {modelId: "ZH"})).resolves.toMatchObject({
3055
+ fileName,
3056
+ manufacturerCode: image.header.manufacturerCode,
3057
+ imageType: image.header.imageType,
3058
+ fileVersion: image.header.fileVersion,
3059
+ modelId: "ZH",
3060
+ });
3061
+ });
3062
+
3063
+ it("by manufacturerName", async () => {
3064
+ const fileName = "custom.ota";
3065
+ const image = mockImage(300, undefined, undefined, 4);
3066
+ const meta: ZigbeeOtaImageMeta = {
3067
+ fileName,
3068
+ url: fileName,
3069
+ manufacturerCode: image.header.manufacturerCode,
3070
+ imageType: image.header.imageType,
3071
+ fileVersion: image.header.fileVersion,
3072
+ manufacturerName: ["ZH"],
3073
+ };
3074
+ fetchIndexEntries.push({...meta, manufacturerName: ["not this"], fileName: "not this"}, meta); // non-match first to ensure not order-driven
3075
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3076
+ fieldControl: 0,
3077
+ manufacturerCode: image.header.manufacturerCode,
3078
+ imageType: image.header.imageType,
3079
+ fileVersion: image.header.fileVersion,
3080
+ };
3081
+ const {device} = createSimpleDevice({manufacturerName: "ZH"});
3082
+
3083
+ await expect(device.findMatchingOtaImage({}, current, {})).resolves.toMatchObject({
3084
+ fileName,
3085
+ manufacturerCode: image.header.manufacturerCode,
3086
+ imageType: image.header.imageType,
3087
+ fileVersion: image.header.fileVersion,
3088
+ manufacturerName: ["ZH"],
3089
+ });
3090
+ });
3091
+
3092
+ it("by extra meta manufacturerName", async () => {
3093
+ const fileName = "custom.ota";
3094
+ const image = mockImage(300, undefined, undefined, 4);
3095
+ const meta: ZigbeeOtaImageMeta = {
3096
+ fileName,
3097
+ url: fileName,
3098
+ manufacturerCode: image.header.manufacturerCode,
3099
+ imageType: image.header.imageType,
3100
+ fileVersion: image.header.fileVersion,
3101
+ manufacturerName: ["ZH"],
3102
+ };
3103
+ fetchIndexEntries.push({...meta, manufacturerName: ["not this"], fileName: "not this"}, meta); // non-match first to ensure not order-driven
3104
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3105
+ fieldControl: 0,
3106
+ manufacturerCode: image.header.manufacturerCode,
3107
+ imageType: image.header.imageType,
3108
+ fileVersion: image.header.fileVersion,
3109
+ };
3110
+ const {device} = createSimpleDevice({manufacturerName: "other"});
3111
+
3112
+ await expect(device.findMatchingOtaImage({}, current, {manufacturerName: "ZH"})).resolves.toMatchObject({
3113
+ fileName,
3114
+ manufacturerCode: image.header.manufacturerCode,
3115
+ imageType: image.header.imageType,
3116
+ fileVersion: image.header.fileVersion,
3117
+ manufacturerName: ["ZH"],
3118
+ });
3119
+ });
3120
+
3121
+ it("by extra meta otaHeaderString", async () => {
3122
+ const fileName = "custom.ota";
3123
+ const image = mockImage(300, undefined, undefined, 4);
3124
+ const meta: ZigbeeOtaImageMeta = {
3125
+ fileName,
3126
+ url: fileName,
3127
+ manufacturerCode: image.header.manufacturerCode,
3128
+ imageType: image.header.imageType,
3129
+ fileVersion: image.header.fileVersion,
3130
+ otaHeaderString: "ZH",
3131
+ };
3132
+ fetchIndexEntries.push({...meta, otaHeaderString: "not this", fileName: "not this"}, meta); // non-match first to ensure not order-driven
3133
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3134
+ fieldControl: 0,
3135
+ manufacturerCode: image.header.manufacturerCode,
3136
+ imageType: image.header.imageType,
3137
+ fileVersion: image.header.fileVersion,
3138
+ };
3139
+ const {device} = createSimpleDevice({});
3140
+
3141
+ await expect(device.findMatchingOtaImage({}, current, {otaHeaderString: "ZH"})).resolves.toMatchObject({
3142
+ fileName,
3143
+ manufacturerCode: image.header.manufacturerCode,
3144
+ imageType: image.header.imageType,
3145
+ fileVersion: image.header.fileVersion,
3146
+ otaHeaderString: "ZH",
3147
+ });
3148
+ });
3149
+
3150
+ it("by hardwareVersionMin", async () => {
3151
+ const fileName = "custom.ota";
3152
+ const image = mockImage(300, undefined, undefined, 56, undefined, undefined, 3, 0);
3153
+ const meta: ZigbeeOtaImageMeta = {
3154
+ fileName,
3155
+ url: fileName,
3156
+ manufacturerCode: image.header.manufacturerCode,
3157
+ imageType: image.header.imageType,
3158
+ fileVersion: image.header.fileVersion,
3159
+ hardwareVersionMin: image.header.minimumHardwareVersion,
3160
+ };
3161
+ const fileName2 = "custom2.ota";
3162
+ const image2 = mockImage(300, undefined, undefined, 58, undefined, undefined, 6, 0);
3163
+ const meta2: ZigbeeOtaImageMeta = {
3164
+ fileName: fileName2,
3165
+ url: fileName2,
3166
+ manufacturerCode: image2.header.manufacturerCode,
3167
+ imageType: image2.header.imageType,
3168
+ fileVersion: image2.header.fileVersion,
3169
+ hardwareVersionMin: image2.header.minimumHardwareVersion,
3170
+ };
3171
+ fetchIndexEntries.push(meta2, meta); // non-match first to ensure not order-driven
3172
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3173
+ fieldControl: 0,
3174
+ manufacturerCode: image.header.manufacturerCode,
3175
+ imageType: image.header.imageType,
3176
+ fileVersion: image.header.fileVersion,
3177
+ hardwareVersion: 4,
3178
+ };
3179
+ const {device} = createSimpleDevice({});
3180
+
3181
+ await expect(device.findMatchingOtaImage({}, current, {})).resolves.toMatchObject({
3182
+ fileName,
3183
+ manufacturerCode: image.header.manufacturerCode,
3184
+ imageType: image.header.imageType,
3185
+ fileVersion: image.header.fileVersion,
3186
+ hardwareVersionMin: 3,
3187
+ });
3188
+ });
3189
+
3190
+ it("by extra meta hardwareVersionMin", async () => {
3191
+ const fileName = "custom.ota";
3192
+ const image = mockImage(300, undefined, undefined, 56, undefined, undefined, 3, 0);
3193
+ const meta: ZigbeeOtaImageMeta = {
3194
+ fileName,
3195
+ url: fileName,
3196
+ manufacturerCode: image.header.manufacturerCode,
3197
+ imageType: image.header.imageType,
3198
+ fileVersion: image.header.fileVersion,
3199
+ hardwareVersionMin: image.header.minimumHardwareVersion,
3200
+ };
3201
+ const fileName2 = "custom2.ota";
3202
+ const image2 = mockImage(300, undefined, undefined, 58, undefined, undefined, 6, 0);
3203
+ const meta2: ZigbeeOtaImageMeta = {
3204
+ fileName: fileName2,
3205
+ url: fileName2,
3206
+ manufacturerCode: image2.header.manufacturerCode,
3207
+ imageType: image2.header.imageType,
3208
+ fileVersion: image2.header.fileVersion,
3209
+ hardwareVersionMin: image2.header.minimumHardwareVersion,
3210
+ };
3211
+ fetchIndexEntries.push(meta2, meta); // non-match first to ensure not order-driven
3212
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3213
+ fieldControl: 0,
3214
+ manufacturerCode: image.header.manufacturerCode,
3215
+ imageType: image.header.imageType,
3216
+ fileVersion: image.header.fileVersion,
3217
+ };
3218
+ const {device} = createSimpleDevice({});
3219
+
3220
+ await expect(device.findMatchingOtaImage({}, current, {hardwareVersionMin: 4})).resolves.toMatchObject({
3221
+ fileName,
3222
+ manufacturerCode: image.header.manufacturerCode,
3223
+ imageType: image.header.imageType,
3224
+ fileVersion: image.header.fileVersion,
3225
+ hardwareVersionMin: 3,
3226
+ });
3227
+ });
3228
+
3229
+ it("by hardwareVersionMax", async () => {
3230
+ const fileName = "custom.ota";
3231
+ const image = mockImage(300, undefined, undefined, 56, undefined, undefined, 0, 6);
3232
+ const meta: ZigbeeOtaImageMeta = {
3233
+ fileName,
3234
+ url: fileName,
3235
+ manufacturerCode: image.header.manufacturerCode,
3236
+ imageType: image.header.imageType,
3237
+ fileVersion: image.header.fileVersion,
3238
+ hardwareVersionMax: image.header.maximumHardwareVersion,
3239
+ };
3240
+ const fileName2 = "custom2.ota";
3241
+ const image2 = mockImage(300, undefined, undefined, 58, undefined, undefined, 0, 3);
3242
+ const meta2: ZigbeeOtaImageMeta = {
3243
+ fileName: fileName2,
3244
+ url: fileName2,
3245
+ manufacturerCode: image2.header.manufacturerCode,
3246
+ imageType: image2.header.imageType,
3247
+ fileVersion: image2.header.fileVersion,
3248
+ hardwareVersionMax: image2.header.maximumHardwareVersion,
3249
+ };
3250
+ fetchIndexEntries.push(meta2, meta); // non-match first to ensure not order-driven
3251
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3252
+ fieldControl: 0,
3253
+ manufacturerCode: image.header.manufacturerCode,
3254
+ imageType: image.header.imageType,
3255
+ fileVersion: image.header.fileVersion,
3256
+ hardwareVersion: 4,
3257
+ };
3258
+ const {device} = createSimpleDevice({});
3259
+
3260
+ await expect(device.findMatchingOtaImage({}, current, {})).resolves.toMatchObject({
3261
+ fileName,
3262
+ manufacturerCode: image.header.manufacturerCode,
3263
+ imageType: image.header.imageType,
3264
+ fileVersion: image.header.fileVersion,
3265
+ hardwareVersionMax: 6,
3266
+ });
3267
+ });
3268
+
3269
+ it("by extra meta hardwareVersionMax", async () => {
3270
+ const fileName = "custom.ota";
3271
+ const image = mockImage(300, undefined, undefined, 56, undefined, undefined, 0, 6);
3272
+ const meta: ZigbeeOtaImageMeta = {
3273
+ fileName,
3274
+ url: fileName,
3275
+ manufacturerCode: image.header.manufacturerCode,
3276
+ imageType: image.header.imageType,
3277
+ fileVersion: image.header.fileVersion,
3278
+ hardwareVersionMax: image.header.maximumHardwareVersion,
3279
+ };
3280
+ const fileName2 = "custom2.ota";
3281
+ const image2 = mockImage(300, undefined, undefined, 58, undefined, undefined, 0, 3);
3282
+ const meta2: ZigbeeOtaImageMeta = {
3283
+ fileName: fileName2,
3284
+ url: fileName2,
3285
+ manufacturerCode: image2.header.manufacturerCode,
3286
+ imageType: image2.header.imageType,
3287
+ fileVersion: image2.header.fileVersion,
3288
+ hardwareVersionMax: image2.header.maximumHardwareVersion,
3289
+ };
3290
+ fetchIndexEntries.push(meta2, meta); // non-match first to ensure not order-driven
3291
+ const current: TClusterCommandPayload<"genOta", "queryNextImageRequest"> = {
3292
+ fieldControl: 0,
3293
+ manufacturerCode: image.header.manufacturerCode,
3294
+ imageType: image.header.imageType,
3295
+ fileVersion: image.header.fileVersion,
3296
+ };
3297
+ const {device} = createSimpleDevice({});
3298
+
3299
+ await expect(device.findMatchingOtaImage({}, current, {hardwareVersionMax: 4})).resolves.toMatchObject({
3300
+ fileName,
3301
+ manufacturerCode: image.header.manufacturerCode,
3302
+ imageType: image.header.imageType,
3303
+ fileVersion: image.header.fileVersion,
3304
+ hardwareVersionMax: 6,
3305
+ });
3306
+ });
3307
+ });
3308
+
3309
+ describe("Utils", () => {
3310
+ it("parses firmware with unusual header fields", () => {
3311
+ const upgradeFileDestination = Buffer.from([0x21, 0x23, 0x34, 0x32, 0x89, 0x98, 0x76, 0x67]);
3312
+ const image = mockImage(500, undefined, undefined, undefined, 123, upgradeFileDestination, 1, 3);
3313
+
3314
+ expect(otaHelpers.parseOtaHeader(image.raw)).toStrictEqual({
3315
+ otaUpgradeFileIdentifier: 0x0beef11e,
3316
+ otaHeaderVersion: 256,
3317
+ otaHeaderLength: 56,
3318
+ otaHeaderFieldControl: 7,
3319
+ manufacturerCode: 1,
3320
+ imageType: 2,
3321
+ fileVersion: 3,
3322
+ zigbeeStackVersion: 2,
3323
+ otaHeaderString: expect.stringContaining("test"),
3324
+ totalImageSize: 500,
3325
+ securityCredentialVersion: 123,
3326
+ upgradeFileDestination,
3327
+ minimumHardwareVersion: 1,
3328
+ maximumHardwareVersion: 3,
3329
+ });
3330
+ });
3331
+ });
3332
+ });