@ciphore/radiocli 0.2.3 → 0.2.5

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 (172) hide show
  1. package/CHANGELOG.md +147 -2
  2. package/CONTRIBUTING.md +46 -6
  3. package/README.md +108 -209
  4. package/SECURITY.md +23 -1
  5. package/dist/agent/alarm-service.js +10 -7
  6. package/dist/agent/cli.js +16 -1
  7. package/dist/agent/command-schema.js +47 -0
  8. package/dist/agent/headless-host.js +234 -67
  9. package/dist/agent/launcher.js +7 -67
  10. package/dist/agent/mcp-install.js +15 -15
  11. package/dist/agent/mcp-server.js +10 -5
  12. package/dist/agent/service.js +74 -39
  13. package/dist/agent/session.js +227 -53
  14. package/dist/agent/tui-session.js +213 -0
  15. package/dist/alarms/active-session.js +9 -12
  16. package/dist/alarms/cli.js +19 -3
  17. package/dist/alarms/guard.js +79 -36
  18. package/dist/alarms/inhibitor.js +27 -22
  19. package/dist/alarms/occurrence-cache.js +25 -0
  20. package/dist/alarms/power-guard-store.js +2 -10
  21. package/dist/alarms/runner.js +140 -45
  22. package/dist/alarms/schedule.js +37 -2
  23. package/dist/alarms/scheduler.js +202 -52
  24. package/dist/alarms/setup-verification.js +1 -2
  25. package/dist/alarms/system-volume-linux.js +160 -0
  26. package/dist/alarms/system-volume-ownership.js +335 -0
  27. package/dist/alarms/system-volume.js +188 -26
  28. package/dist/alarms/terminal-launcher.js +76 -136
  29. package/dist/alarms/tui-presence.js +2 -4
  30. package/dist/cli/arguments.js +212 -0
  31. package/dist/cli/completion.js +44 -0
  32. package/dist/cli/doctor.js +156 -0
  33. package/dist/cli/interactive.js +79 -0
  34. package/dist/cli/listener.js +227 -0
  35. package/dist/cli/output.js +121 -0
  36. package/dist/cli/render-tui.js +34 -0
  37. package/dist/cli.js +198 -154
  38. package/dist/platform/capabilities.js +60 -0
  39. package/dist/platform/desktop-media.js +61 -0
  40. package/dist/platform/desktop.js +36 -0
  41. package/dist/platform/diagnostics.js +273 -0
  42. package/dist/{player/command.js → platform/executables.js} +43 -9
  43. package/dist/platform/ipc.js +14 -0
  44. package/dist/platform/json-worker.js +111 -0
  45. package/dist/platform/launch-command.js +138 -0
  46. package/dist/platform/loopback.js +44 -0
  47. package/dist/platform/mpris.js +356 -0
  48. package/dist/platform/network.js +380 -0
  49. package/dist/platform/omarchy.js +147 -0
  50. package/dist/platform/packages.js +222 -0
  51. package/dist/platform/paths.js +40 -0
  52. package/dist/platform/runtime.js +67 -0
  53. package/dist/platform/shell.js +24 -0
  54. package/dist/platform/storage.js +48 -0
  55. package/dist/platform/support.js +214 -0
  56. package/dist/platform/terminal.js +63 -0
  57. package/dist/platform/terminals.js +223 -0
  58. package/dist/player/airplay-discovery.js +145 -60
  59. package/dist/player/airplay-policy.js +15 -0
  60. package/dist/player/airplay-process.js +63 -0
  61. package/dist/player/airplay-readiness.js +42 -0
  62. package/dist/player/airplay-sender-health.js +49 -49
  63. package/dist/player/airplay-sender-package.js +5 -0
  64. package/dist/player/airplay-sender-patch.js +102 -4
  65. package/dist/player/airplay-setup.js +51 -0
  66. package/dist/player/airplay-worker.js +97 -41
  67. package/dist/player/backend-install.js +17 -95
  68. package/dist/player/command-diagnostics.js +2 -2
  69. package/dist/player/ffmpeg-input.js +37 -0
  70. package/dist/player/mpv-desktop-media.js +194 -0
  71. package/dist/player/mpv-ipc-client.js +52 -2
  72. package/dist/player/player-controller.js +569 -82
  73. package/dist/player/radio-engine.js +282 -0
  74. package/dist/playlists/playlist.js +18 -16
  75. package/dist/providers/cache.js +293 -99
  76. package/dist/providers/discovery-session.js +111 -0
  77. package/dist/providers/provider-manager.js +106 -28
  78. package/dist/providers/radio-browser.js +418 -89
  79. package/dist/providers/radio-garden.js +18 -23
  80. package/dist/setup-playback.js +152 -0
  81. package/dist/setup.js +163 -168
  82. package/dist/storage/file-lock.js +113 -0
  83. package/dist/storage/read-settings.js +33 -0
  84. package/dist/storage/replace-file.js +20 -0
  85. package/dist/storage/repository.js +247 -0
  86. package/dist/storage/station-identity.js +21 -0
  87. package/dist/storage/station-schema.js +27 -0
  88. package/dist/storage/store.js +333 -107
  89. package/dist/streams/import-stream.js +163 -0
  90. package/dist/ui/AdaptiveContent.js +49 -32
  91. package/dist/ui/App.js +822 -481
  92. package/dist/ui/AppContent.js +5 -5
  93. package/dist/ui/airplay-settings.js +12 -5
  94. package/dist/ui/app-state.js +8 -1
  95. package/dist/ui/ascii.js +11 -2
  96. package/dist/ui/bidi-ranges.js +45 -0
  97. package/dist/ui/components/AdaptiveMarquee.js +20 -10
  98. package/dist/ui/components/Logo.js +5 -2
  99. package/dist/ui/components/Menu.js +2 -2
  100. package/dist/ui/components/ScreenHeader.js +1 -1
  101. package/dist/ui/components/StationList.js +6 -4
  102. package/dist/ui/components/TopTabs.js +1 -1
  103. package/dist/ui/display-context.js +8 -11
  104. package/dist/ui/format.js +37 -4
  105. package/dist/ui/help-content.js +9 -6
  106. package/dist/ui/layout.js +5 -2
  107. package/dist/ui/page-footer.js +3 -3
  108. package/dist/ui/rtl-output.js +137 -0
  109. package/dist/ui/screen-items.js +12 -6
  110. package/dist/ui/screen-meta.js +1 -1
  111. package/dist/ui/screens/AirPlayCodeScreen.js +6 -2
  112. package/dist/ui/screens/AirPlaySettingsScreen.js +10 -6
  113. package/dist/ui/screens/AlarmsScreen.js +5 -2
  114. package/dist/ui/screens/ExploreScreen.js +2 -1
  115. package/dist/ui/screens/HelpScreen.js +8 -4
  116. package/dist/ui/screens/HomeScreen.js +5 -1
  117. package/dist/ui/screens/MapScreen.js +1 -1
  118. package/dist/ui/screens/NowPlayingScreen.js +4 -4
  119. package/dist/ui/screens/SearchScreen.js +3 -1
  120. package/dist/ui/screens/SettingsScreen.js +28 -21
  121. package/dist/ui/screens/StatsScreen.js +3 -1
  122. package/dist/ui/system-actions.js +80 -52
  123. package/dist/ui/terminal-renderer.js +16 -0
  124. package/dist/ui/text-direction.js +9 -0
  125. package/dist/ui/use-alarm-tui.js +57 -42
  126. package/dist/ui/use-app-input.js +84 -30
  127. package/dist/ui/use-command-executor.js +50 -19
  128. package/dist/ui/visualizers/gallop.js +1 -1
  129. package/dist/ui/visualizers/receiver-style-registry.js +2 -1
  130. package/dist/update-check.js +10 -18
  131. package/docs/THIRD_PARTY_NOTICES.md +6 -4
  132. package/package.json +34 -11
  133. package/scripts/build-airplay-native.mjs +22 -0
  134. package/vendor/node-airtunes2/LICENSE +208 -0
  135. package/vendor/node-airtunes2/RADIOCLI_PATCH.md +29 -0
  136. package/vendor/node-airtunes2/README.md +225 -0
  137. package/vendor/node-airtunes2/UPSTREAM.json +50 -0
  138. package/vendor/node-airtunes2/binding.gyp +41 -0
  139. package/vendor/node-airtunes2/lib/ap2_test.js +4 -0
  140. package/vendor/node-airtunes2/lib/atv.js +307 -0
  141. package/vendor/node-airtunes2/lib/atvAuthenticator.js +136 -0
  142. package/vendor/node-airtunes2/lib/audio_out.js +69 -0
  143. package/vendor/node-airtunes2/lib/circular_buffer.js +133 -0
  144. package/vendor/node-airtunes2/lib/config.js +26 -0
  145. package/vendor/node-airtunes2/lib/device_airtunes.js +532 -0
  146. package/vendor/node-airtunes2/lib/devices.js +266 -0
  147. package/vendor/node-airtunes2/lib/ed25519.js +26 -0
  148. package/vendor/node-airtunes2/lib/homekit/credentials.js +86 -0
  149. package/vendor/node-airtunes2/lib/homekit/encryption.js +69 -0
  150. package/vendor/node-airtunes2/lib/homekit/number.js +44 -0
  151. package/vendor/node-airtunes2/lib/homekit/tlv.js +95 -0
  152. package/vendor/node-airtunes2/lib/http.js +189 -0
  153. package/vendor/node-airtunes2/lib/index.d.ts +141 -0
  154. package/vendor/node-airtunes2/lib/index.js +127 -0
  155. package/vendor/node-airtunes2/lib/ntp.js +37 -0
  156. package/vendor/node-airtunes2/lib/num_util.js +21 -0
  157. package/vendor/node-airtunes2/lib/packet_pool.js +43 -0
  158. package/vendor/node-airtunes2/lib/rtsp.js +1807 -0
  159. package/vendor/node-airtunes2/lib/srp.js +158 -0
  160. package/vendor/node-airtunes2/lib/udp_servers.js +214 -0
  161. package/vendor/node-airtunes2/lib/util.js +12 -0
  162. package/vendor/node-airtunes2/package.json +31 -0
  163. package/vendor/node-airtunes2/src/CADebugMacros.h +257 -0
  164. package/vendor/node-airtunes2/src/CAHostTimeBase.cpp +105 -0
  165. package/vendor/node-airtunes2/src/CAHostTimeBase.h +224 -0
  166. package/vendor/node-airtunes2/src/aes_utils.c +864 -0
  167. package/vendor/node-airtunes2/src/aes_utils.h +24 -0
  168. package/vendor/node-airtunes2/src/base64.c +136 -0
  169. package/vendor/node-airtunes2/src/base64.h +42 -0
  170. package/vendor/node-airtunes2/src/bindings.cc +29 -0
  171. package/vendor/node-airtunes2/src/codec.cc +170 -0
  172. package/vendor/node-airtunes2/src/coreaudio.cc +332 -0
@@ -0,0 +1,266 @@
1
+ const events = require('events')
2
+ const util = require('util')
3
+ const async = require('async')
4
+ const AirTunesDevice = require('./device_airtunes')
5
+
6
+ function Devices(audioOut) {
7
+ events.EventEmitter.call(this)
8
+
9
+ this.source = null
10
+ this.devices = {}
11
+ this.hasAirTunes = false
12
+ this.audioOut = audioOut
13
+ }
14
+
15
+ util.inherits(Devices, events.EventEmitter)
16
+
17
+ Devices.prototype.init = function () {
18
+ var self = this
19
+ self.audioOut.on('need_sync', function (seq) {
20
+ // relay to all devices
21
+
22
+ self.forEach(function (dev) {
23
+ try {
24
+ if (dev.onSyncNeeded && dev.controlPort)
25
+ dev.onSyncNeeded(seq)
26
+ }
27
+ catch (e) {}
28
+ })
29
+ })
30
+ }
31
+
32
+ Devices.prototype.forEach = function (it) {
33
+ for (const i in this.devices) {
34
+ if (!this.devices.hasOwnProperty(i))
35
+ continue
36
+
37
+ it(this.devices[i], i)
38
+ }
39
+ }
40
+
41
+ Devices.prototype.add = function (type, host, options) {
42
+ var self = this
43
+
44
+ this.status = 'connecting'
45
+ var dev
46
+ // type === 'coreaudio' ?
47
+ // new CoreAudioDevice(this.hasAirTunes, this.audioOut, options) :
48
+ = new AirTunesDevice(host, this.audioOut, options, options?.mode ?? 0, options?.txt ?? [])
49
+
50
+ var previousDev = this.devices[dev.key]
51
+
52
+ if (previousDev) {
53
+ // if device is already in the pool, just report its existing status.
54
+ previousDev.reportStatus()
55
+
56
+ return previousDev
57
+ }
58
+
59
+ this.devices[dev.key] = dev
60
+
61
+ dev.on('status', function (status, arg) {
62
+ if (status === 'error' || status === 'stopped') {
63
+ delete self.devices[dev.key]
64
+ self.checkAirTunesDevices()
65
+ }
66
+
67
+ if (this.hasAirTunes && status === 'playing') {
68
+ self.emit('need_sync')
69
+ }
70
+
71
+ self.emit('status', dev.key, status, '')
72
+ })
73
+
74
+ dev.start()
75
+ self.checkAirTunesDevices()
76
+
77
+ return dev
78
+ }
79
+
80
+ Devices.prototype.setVolume = function (key, volume, callback) {
81
+ var dev = this.devices[key]
82
+
83
+ if (!dev) {
84
+ this.emit('status', key, 'error', 'not_found')
85
+
86
+ return
87
+ }
88
+
89
+ dev.setVolume(volume, callback)
90
+ }
91
+
92
+ Devices.prototype.setProgress = function (key, progress, duration, callback) {
93
+ try {
94
+ if (key === 'all') {
95
+ for (const device of Object.keys(this.devices)) {
96
+ try {
97
+ this.devices[device].setProgress(progress, duration, callback)
98
+ }
99
+ catch (e) {
100
+ try {
101
+ if (e.name === 'TypeError')
102
+ delete this.devices[device]
103
+ }
104
+ catch (e) {}
105
+
106
+ console.log(e)
107
+ }
108
+ };
109
+ }
110
+ else {
111
+ var dev = this.devices[key]
112
+
113
+ if (!dev) {
114
+ this.emit('status', key, 'error', 'not_found')
115
+
116
+ return
117
+ }
118
+
119
+ dev.setProgress(progress, duration, callback)
120
+ }
121
+ }
122
+ catch (e) { console.log(e) }
123
+ }
124
+
125
+ Devices.prototype.setTrackInfo = function (key, name, artist, album, callback) {
126
+ try {
127
+ if (key === 'all') {
128
+ for (const device of Object.keys(this.devices)) {
129
+ try {
130
+ this.devices[device].setTrackInfo(name, artist, album, callback)
131
+ }
132
+ catch (e) {
133
+ try {
134
+ if (e.name === 'TypeError')
135
+ delete this.devices[device]
136
+ }
137
+ catch (e) {}
138
+ console.log(e)
139
+ }
140
+ }
141
+ }
142
+ else {
143
+ var dev = this.devices[key]
144
+
145
+ if (!dev) {
146
+ this.emit('status', key, 'error', 'not_found')
147
+
148
+ return
149
+ }
150
+ else {
151
+ dev.setTrackInfo(name, artist, album, callback)
152
+ }
153
+ }
154
+ }
155
+ catch (e) { console.log(e) }
156
+ }
157
+
158
+ Devices.prototype.setArtwork = function (key, art, contentType, callback) {
159
+ try {
160
+ if (key === 'all') {
161
+ for (const device of Object.keys(this.devices)) {
162
+ try {
163
+ this.devices[device].setArtwork(art, contentType, callback)
164
+ }
165
+ catch (e) {
166
+ try {
167
+ if (e.name === 'TypeError')
168
+ delete this.devices[device]
169
+ }
170
+ catch (e) {}
171
+ console.log(e)
172
+ }
173
+ }
174
+ }
175
+ else {
176
+ var dev = this.devices[key]
177
+
178
+ if (!dev) {
179
+ this.emit('status', key, 'error', 'not_found')
180
+
181
+ return
182
+ }
183
+
184
+ dev.setArtwork(art, contentType, callback)
185
+ }
186
+ }
187
+ catch (e) { console.log(e) }
188
+ }
189
+
190
+ Devices.prototype.setPasscode = function (key, passcode) {
191
+ var dev = this.devices[key]
192
+
193
+ if (!dev) {
194
+ this.emit('status', key, 'error', 'not_found')
195
+
196
+ return
197
+ }
198
+
199
+ dev.setPasscode(passcode)
200
+ }
201
+
202
+ Devices.prototype.stop = function (key) {
203
+ var dev = this.devices[key]
204
+ if (!dev) {
205
+ this.emit('status', key, 'error', 'not_found')
206
+
207
+ return
208
+ }
209
+ dev.stop()
210
+ this.devices[key] = undefined
211
+ }
212
+
213
+ Devices.prototype.stopAll = function (allCb) {
214
+ // conver to array to make async happy
215
+ var devices = []
216
+ for (const i in this.devices) {
217
+ if (!this.devices.hasOwnProperty(i))
218
+ continue
219
+
220
+ devices.push(this.devices[i])
221
+ }
222
+ if (allCb != null) {
223
+ async.forEach(
224
+ devices,
225
+ function (dev, cb) {
226
+ dev.stop(cb)
227
+ },
228
+ function () {
229
+ this.devices = {}
230
+ allCb()
231
+ },
232
+ )
233
+ }
234
+ }
235
+
236
+ Devices.prototype.checkAirTunesDevices = function () {
237
+ try {
238
+ var newHasAirTunes = false
239
+
240
+ for (const key in this.devices) {
241
+ if (!this.devices.hasOwnProperty(key))
242
+ continue
243
+
244
+ var device = this.devices[key]
245
+
246
+ // if(device.type === 'airtunes') {
247
+ newHasAirTunes = true
248
+ break
249
+ // }
250
+ }
251
+
252
+ if (newHasAirTunes !== this.hasAirTunes) {
253
+ this.emit('airtunes_devices', newHasAirTunes)
254
+
255
+ this.forEach(function (dev) {
256
+ if (dev.setHasAirTunes)
257
+ dev.setHasAirTunes(newHasAirTunes)
258
+ })
259
+ }
260
+
261
+ this.hasAirTunes = newHasAirTunes
262
+ }
263
+ catch (e) { console.log(e) }
264
+ }
265
+
266
+ module.exports = Devices
@@ -0,0 +1,26 @@
1
+ // RadioCLI modification, 2026-10-16. Distributed under the accompanying AGPLv3.
2
+ // Preserve the sender's synchronous hex API using Node's Ed25519 implementation.
3
+ const crypto = require('node:crypto')
4
+ const pkcs8Prefix = Buffer.from('302e020100300506032b657004220420', 'hex')
5
+
6
+ function bytes(hex, length) {
7
+ if (typeof hex !== 'string' || !/^(?:[a-f\d]{2})*$/i.test(hex) || (length !== undefined && hex.length !== length * 2)) {
8
+ throw new TypeError('Invalid Ed25519 hexadecimal input')
9
+ }
10
+ return Buffer.from(hex, 'hex')
11
+ }
12
+
13
+ function privateKey(seed) {
14
+ return crypto.createPrivateKey({key: Buffer.concat([pkcs8Prefix, bytes(seed, 32)]), format: 'der', type: 'pkcs8'})
15
+ }
16
+
17
+ function publicKey(seed) {
18
+ const key = crypto.createPublicKey(privateKey(seed)).export({format: 'jwk'})
19
+ return Buffer.from(key.x, 'base64url').toString('hex')
20
+ }
21
+
22
+ function sign(seed, message) {
23
+ return crypto.sign(null, bytes(message), privateKey(seed)).toString('hex').toUpperCase()
24
+ }
25
+
26
+ module.exports = {publicKey, sign}
@@ -0,0 +1,86 @@
1
+ 'use strict'
2
+ Object.defineProperty(exports, '__esModule', { value: true })
3
+ const struct = require('python-struct')
4
+ const encryption_1 = require('./encryption')
5
+
6
+ class Credentials {
7
+ constructor(uniqueIdentifier, identifier, pairingId, publicKey, encryptionKey) {
8
+ this.uniqueIdentifier = uniqueIdentifier
9
+ this.identifier = identifier
10
+ this.pairingId = pairingId
11
+ this.publicKey = publicKey
12
+ this.encryptionKey = encryptionKey
13
+ this.encryptCount = 0
14
+ this.decryptCount = 0
15
+ }
16
+
17
+ /**
18
+ * Parse a credentials string into a Credentials object.
19
+ * @param text The credentials string.
20
+ * @returns A credentials object.
21
+ */
22
+ static parse(text) {
23
+ let parts = text.split(':')
24
+ return new Credentials(parts[0], Buffer.from(parts[1], 'hex'), Buffer.from(parts[2], 'hex').toString(), Buffer.from(parts[3], 'hex'), Buffer.from(parts[4], 'hex'))
25
+ }
26
+
27
+ /**
28
+ * Returns a string representation of a Credentials object.
29
+ * @returns A string representation of a Credentials object.
30
+ */
31
+ toString() {
32
+ return this.uniqueIdentifier
33
+ + ':'
34
+ + this.identifier.toString('hex')
35
+ + ':'
36
+ + Buffer.from(this.pairingId).toString('hex')
37
+ + ':'
38
+ + this.publicKey.toString('hex')
39
+ + ':'
40
+ + this.encryptionKey.toString('hex')
41
+ }
42
+
43
+ encrypt(message) {
44
+ let offset = 0
45
+ let total = message.byteLength
46
+ let result = Buffer.concat([])
47
+ while (offset < total) {
48
+ let length = Math.min(total - offset, 1024)
49
+ let s1length_bytes = struct.pack('H', length)
50
+ // let cipher = crypto.createCipheriv('chacha20-poly1305', this.writeKey, Buffer.concat([Buffer.from([0x00,0x00,0x00,0x00]),struct.pack("Q", this.decryptCount)]), { authTagLength: 16 });
51
+ // cipher.setAAD(s1length_bytes);
52
+ // let s1ct = cipher.update(message);
53
+ // cipher.final();
54
+ // let s1tag = encryption_1.default.computePoly1305(s1ct,s1length_bytes,Buffer.concat([Buffer.from([0x00,0x00,0x00,0x00]),struct.pack("Q", this.decryptCount)]),this.writeKey)
55
+ let [s1ct, s1tag] = encryption_1.default.encryptAndSeal(message.slice(offset, offset + length), s1length_bytes, Buffer.concat([Buffer.from([0x00, 0x00, 0x00, 0x00]), struct.pack('Q', this.encryptCount)]), this.writeKey)
56
+
57
+ let ciphertext = Buffer.concat([s1length_bytes, s1ct, s1tag])
58
+ offset += length
59
+ this.encryptCount += 1
60
+ result = Buffer.concat([result, ciphertext])
61
+ }
62
+ return result
63
+ }
64
+
65
+ decrypt(message) {
66
+ let offset = 0
67
+ let result = Buffer.concat([])
68
+ while (offset < message.byteLength) {
69
+ let lengthbytes = message.slice(offset, offset + 2)
70
+ let length = struct.unpack('H', lengthbytes)
71
+ let messagea = message.slice(offset + 2, offset + 2 + length[0] + 16)
72
+ let cipherText = messagea.slice(0, -16)
73
+ let hmac = messagea.slice(-16)
74
+ let decrypted = encryption_1.default.verifyAndDecrypt(cipherText, hmac, lengthbytes, Buffer.concat([Buffer.from([0x00, 0x00, 0x00, 0x00]), struct.pack('Q', this.decryptCount)]), this.readKey)
75
+ this.decryptCount += 1
76
+ offset = offset + length[0] + 16 + 2
77
+ result = Buffer.concat([result, decrypted])
78
+ }
79
+ return result
80
+ }
81
+
82
+ encryptAudio(message, aad, nonce) {
83
+ return Buffer.concat([Buffer.concat(encryption_1.default.encryptAndSeal(message, aad, struct.pack('Q', nonce), this.writeKey)), Buffer.from(struct.pack('Q', nonce))])
84
+ }
85
+ }
86
+ exports.Credentials = Credentials
@@ -0,0 +1,69 @@
1
+ 'use strict'
2
+ Object.defineProperty(exports, '__esModule', { value: true })
3
+ const chacha = require('chacha-js')
4
+ const crypto = require('crypto')
5
+ // i'd really prefer for this to be a direct call to
6
+ // Sodium.crypto_aead_chacha20poly1305_decrypt()
7
+ // but unfortunately the way it constructs the message to
8
+ // calculate the HMAC is not compatible with homekit
9
+ // (long story short, it uses [ AAD, AAD.length, CipherText, CipherText.length ]
10
+ // whereas homekit expects [ AAD, CipherText, AAD.length, CipherText.length ]
11
+ function verifyAndDecrypt(cipherText, mac, AAD, nonce, key) {
12
+ try {
13
+ if (nonce.byteLength == 8) nonce = Buffer.concat([Buffer.from([0x00, 0x00, 0x00, 0x00]), nonce])
14
+ const decipher = chacha.createDecipher(key, nonce)
15
+ if (AAD != null) decipher.setAAD(AAD)// must be called before data
16
+ decipher.setAuthTag(mac)
17
+ return decipher.update(cipherText)
18
+ }
19
+ catch (e) {
20
+ return null
21
+ }
22
+ }
23
+
24
+ function encryptAndSeal(plainText, AAD, nonce, key) {
25
+ if (nonce.byteLength == 8) nonce = Buffer.concat([Buffer.from([0x00, 0x00, 0x00, 0x00]), nonce])
26
+ var cipher = chacha.createCipher(key, nonce)
27
+ if (AAD != null) cipher.setAAD(AAD)// must be called before data
28
+ let cipherText = cipher.update(plainText)
29
+ cipher.final()
30
+ let hmac = cipher.getAuthTag()
31
+ return [cipherText, hmac]
32
+ }
33
+ // function getPadding(buffer, blockSize) {
34
+ // return buffer.length % blockSize === 0
35
+ // ? Buffer.alloc(0)
36
+ // : Buffer.alloc(blockSize - (buffer.length % blockSize));
37
+ // }
38
+ function HKDF(hashAlg, salt, ikm, info, size) {
39
+ // create the hash alg to see if it exists and get its length
40
+ var hash = crypto.createHash(hashAlg)
41
+ var hashLength = hash.digest().length
42
+ // now we compute the PRK
43
+ var hmac = crypto.createHmac(hashAlg, salt)
44
+ hmac.update(ikm)
45
+ var prk = hmac.digest()
46
+ var prev = Buffer.alloc(0)
47
+ var output
48
+ var buffers = []
49
+ var num_blocks = Math.ceil(size / hashLength)
50
+ info = Buffer.from(info)
51
+ for (var i = 0; i < num_blocks; i++) {
52
+ var hmac = crypto.createHmac(hashAlg, prk)
53
+ var input = Buffer.concat([
54
+ prev,
55
+ info,
56
+ Buffer.from(String.fromCharCode(i + 1)),
57
+ ])
58
+ hmac.update(input)
59
+ prev = hmac.digest()
60
+ buffers.push(prev)
61
+ }
62
+ output = Buffer.concat(buffers, size)
63
+ return output.slice(0, size)
64
+ }
65
+ exports.default = {
66
+ encryptAndSeal,
67
+ verifyAndDecrypt,
68
+ HKDF,
69
+ }
@@ -0,0 +1,44 @@
1
+ 'use strict'
2
+ Object.defineProperty(exports, '__esModule', { value: true })
3
+ const assert = require('assert')
4
+ /*
5
+ * Originally based on code from github:KhaosT/HAP-NodeJS@0c8fd88 used
6
+ * used per the terms of the Apache Software License v2.
7
+ *
8
+ * Original code copyright Khaos Tian <khaos.tian@gmail.com>
9
+ *
10
+ * Modifications copyright Zach Bean <zb@forty2.com>
11
+ * * Reformatted for ES6-style module
12
+ * * renamed *UInt64* to *UInt53* to be more clear about range
13
+ * * renamed uintHighLow to be more clear about what it does
14
+ * * Refactored to return a buffer rather write into a passed-in buffer
15
+ */
16
+ function splitUInt53(number) {
17
+ const MAX_UINT32 = 0x00000000FFFFFFFF
18
+ const MAX_INT53 = 0x001FFFFFFFFFFFFF
19
+ assert(number > -1 && number <= MAX_INT53, 'number out of range')
20
+ assert(Math.floor(number) === number, 'number must be an integer')
21
+ let high = 0
22
+ const signbit = number & 0xFFFFFFFF
23
+ const low = signbit < 0 ? (number & 0x7FFFFFFF) + 0x80000000 : signbit
24
+ if (number > MAX_UINT32) {
25
+ high = (number - low) / (MAX_UINT32 + 1)
26
+ }
27
+ return [high, low]
28
+ }
29
+ function UInt53toBufferLE(number) {
30
+ const [high, low] = splitUInt53(number)
31
+ const buf = Buffer.alloc(8)
32
+ buf.writeUInt32LE(low, 0)
33
+ buf.writeUInt32LE(high, 4)
34
+ return buf
35
+ }
36
+ function UInt16toBufferBE(number) {
37
+ const buf = Buffer.alloc(2)
38
+ buf.writeUInt16BE(number, 0)
39
+ return buf
40
+ }
41
+ exports.default = {
42
+ UInt53toBufferLE,
43
+ UInt16toBufferBE,
44
+ }
@@ -0,0 +1,95 @@
1
+ 'use strict'
2
+ /**
3
+ * Type Length Value encoding/decoding, used by HAP as a wire format.
4
+ * https://en.wikipedia.org/wiki/Type-length-value
5
+ *
6
+ * Originally based on code from github:KhaosT/HAP-NodeJS@0c8fd88 used
7
+ * used per the terms of the Apache Software License v2.
8
+ *
9
+ * Original code copyright Khaos Tian <khaos.tian@gmail.com>
10
+ *
11
+ * Modifications copyright Zach Bean <zb@forty2.com>
12
+ * * Reformatted for ES6-style module
13
+ * * Rewrote encode() to be non-recursive; also simplified the logic
14
+ * * Rewrote decode()
15
+ */
16
+ Object.defineProperty(exports, '__esModule', { value: true })
17
+ const Tag = {
18
+ PairingMethod: 0x00,
19
+ Username: 0x01,
20
+ Salt: 0x02,
21
+ // could be either the SRP client public key (384 bytes) or the ED25519 public key (32 bytes), depending on context
22
+ PublicKey: 0x03,
23
+ Proof: 0x04,
24
+ EncryptedData: 0x05,
25
+ Sequence: 0x06,
26
+ ErrorCode: 0x07,
27
+ BackOff: 0x08,
28
+ Signature: 0x0A,
29
+ MFiCertificate: 0x09,
30
+ MFiSignature: 0x0A,
31
+ Flags: 0x13,
32
+ }
33
+ function encode(type, data) {
34
+ var encodedTLVBuffer = Buffer.alloc(0)
35
+ // coerce data to Buffer if needed
36
+ if (typeof data === 'number')
37
+ data = Buffer.from([data])
38
+ else if (typeof data === 'string')
39
+ data = Buffer.from(data)
40
+ if (data.length <= 255) {
41
+ encodedTLVBuffer = Buffer.concat([Buffer.from([type, data.length]), data])
42
+ }
43
+ else {
44
+ var leftLength = data.length
45
+ var tempBuffer = Buffer.alloc(0)
46
+ var currentStart = 0
47
+ for (; leftLength > 0;) {
48
+ if (leftLength >= 255) {
49
+ tempBuffer = Buffer.concat([tempBuffer, Buffer.from([type, 0xFF]), data.slice(currentStart, currentStart + 255)])
50
+ leftLength -= 255
51
+ currentStart = currentStart + 255
52
+ }
53
+ else {
54
+ tempBuffer = Buffer.concat([tempBuffer, Buffer.from([type, leftLength]), data.slice(currentStart, currentStart + leftLength)])
55
+ leftLength -= leftLength
56
+ }
57
+ }
58
+ encodedTLVBuffer = tempBuffer
59
+ }
60
+ // do we have more to encode?
61
+ if (arguments.length > 2) {
62
+ // chop off the first two arguments which we already processed, and process the rest recursively
63
+ var remainingArguments = Array.prototype.slice.call(arguments, 2)
64
+ var remainingTLVBuffer = encode.apply(this, remainingArguments)
65
+ // append the remaining encoded arguments directly to the buffer
66
+ encodedTLVBuffer = Buffer.concat([encodedTLVBuffer, remainingTLVBuffer])
67
+ }
68
+ return encodedTLVBuffer
69
+ }
70
+ function decode(data) {
71
+ const objects = {}
72
+ let leftLength = data.length
73
+ let currentIndex = 0
74
+ for (; leftLength > 0;) {
75
+ const type = data[currentIndex]
76
+ const length = data[currentIndex + 1]
77
+ currentIndex += 2
78
+ leftLength -= 2
79
+ const newData = data.slice(currentIndex, currentIndex + length)
80
+ if (objects[type]) {
81
+ objects[type] = Buffer.concat([objects[type], newData])
82
+ }
83
+ else {
84
+ objects[type] = newData
85
+ }
86
+ currentIndex += length
87
+ leftLength -= length
88
+ }
89
+ return objects
90
+ }
91
+ exports.default = {
92
+ Tag,
93
+ encode,
94
+ decode,
95
+ }