tesla-fleet-api 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/README.md +284 -1
  2. package/dist/_virtual/_rolldown/runtime.cjs +32 -0
  3. package/dist/_virtual/_rolldown/runtime.mjs +12 -0
  4. package/dist/bluetooth/adapters/shared.cjs +38 -0
  5. package/dist/bluetooth/adapters/shared.mjs +36 -0
  6. package/dist/bluetooth/async.cjs +121 -0
  7. package/dist/bluetooth/async.d.cts +10 -0
  8. package/dist/bluetooth/async.d.mts +10 -0
  9. package/dist/bluetooth/async.mjs +116 -0
  10. package/dist/bluetooth/broadcast.cjs +153 -0
  11. package/dist/bluetooth/broadcast.d.cts +59 -0
  12. package/dist/bluetooth/broadcast.d.mts +59 -0
  13. package/dist/bluetooth/broadcast.mjs +151 -0
  14. package/dist/bluetooth/constants.cjs +99 -0
  15. package/dist/bluetooth/constants.d.cts +70 -0
  16. package/dist/bluetooth/constants.d.mts +70 -0
  17. package/dist/bluetooth/constants.mjs +72 -0
  18. package/dist/bluetooth/esphome.cjs +292 -0
  19. package/dist/bluetooth/esphome.d.cts +83 -0
  20. package/dist/bluetooth/esphome.d.mts +83 -0
  21. package/dist/bluetooth/esphome.mjs +291 -0
  22. package/dist/bluetooth/framing.cjs +125 -0
  23. package/dist/bluetooth/framing.d.cts +45 -0
  24. package/dist/bluetooth/framing.d.mts +45 -0
  25. package/dist/bluetooth/framing.mjs +122 -0
  26. package/dist/bluetooth/homey.cjs +155 -0
  27. package/dist/bluetooth/homey.d.cts +75 -0
  28. package/dist/bluetooth/homey.d.mts +75 -0
  29. package/dist/bluetooth/homey.mjs +150 -0
  30. package/dist/bluetooth/names.cjs +24 -0
  31. package/dist/bluetooth/names.d.cts +10 -0
  32. package/dist/bluetooth/names.d.mts +10 -0
  33. package/dist/bluetooth/names.mjs +20 -0
  34. package/dist/bluetooth/noble.cjs +178 -0
  35. package/dist/bluetooth/noble.d.cts +58 -0
  36. package/dist/bluetooth/noble.d.mts +58 -0
  37. package/dist/bluetooth/noble.mjs +177 -0
  38. package/dist/bluetooth/streamglue.cjs +157 -0
  39. package/dist/bluetooth/streamglue.d.cts +37 -0
  40. package/dist/bluetooth/streamglue.d.mts +37 -0
  41. package/dist/bluetooth/streamglue.mjs +154 -0
  42. package/dist/bluetooth/teslabluetooth.cjs +67 -0
  43. package/dist/bluetooth/teslabluetooth.d.cts +41 -0
  44. package/dist/bluetooth/teslabluetooth.d.mts +41 -0
  45. package/dist/bluetooth/teslabluetooth.mjs +66 -0
  46. package/dist/bluetooth/transport.cjs +10 -0
  47. package/dist/bluetooth/transport.d.cts +95 -0
  48. package/dist/bluetooth/transport.d.mts +95 -0
  49. package/dist/bluetooth/transport.mjs +10 -0
  50. package/dist/bluetooth/vehiclebluetooth.cjs +1272 -0
  51. package/dist/bluetooth/vehiclebluetooth.d.cts +495 -0
  52. package/dist/bluetooth/vehiclebluetooth.d.mts +495 -0
  53. package/dist/bluetooth/vehiclebluetooth.mjs +1270 -0
  54. package/dist/bluetooth/verify.cjs +93 -0
  55. package/dist/bluetooth/verify.d.cts +35 -0
  56. package/dist/bluetooth/verify.d.mts +35 -0
  57. package/dist/bluetooth/verify.mjs +91 -0
  58. package/dist/bluetooth.cjs +53 -0
  59. package/dist/bluetooth.d.cts +10 -0
  60. package/dist/bluetooth.d.mts +10 -0
  61. package/dist/bluetooth.mjs +9 -0
  62. package/dist/charging.cjs +53 -0
  63. package/dist/charging.d.cts +31 -0
  64. package/dist/charging.d.mts +30 -0
  65. package/dist/charging.mjs +53 -0
  66. package/dist/commands.cjs +1797 -0
  67. package/dist/commands.d.cts +881 -0
  68. package/dist/commands.d.mts +881 -0
  69. package/dist/commands.mjs +1791 -0
  70. package/dist/const.cjs +322 -0
  71. package/dist/const.d.cts +308 -0
  72. package/dist/const.d.mts +308 -0
  73. package/dist/const.mjs +282 -0
  74. package/dist/energy.cjs +142 -0
  75. package/dist/energy.d.cts +101 -0
  76. package/dist/energy.d.mts +100 -0
  77. package/dist/energy.mjs +142 -0
  78. package/dist/energyisland.cjs +38 -0
  79. package/dist/energyisland.mjs +37 -0
  80. package/dist/energyparsers.cjs +118 -0
  81. package/dist/energyparsers.d.cts +39 -0
  82. package/dist/energyparsers.d.mts +39 -0
  83. package/dist/energyparsers.mjs +116 -0
  84. package/dist/energyspecific.cjs +457 -0
  85. package/dist/energyspecific.d.cts +258 -0
  86. package/dist/energyspecific.d.mts +258 -0
  87. package/dist/energyspecific.mjs +451 -0
  88. package/dist/exceptions.cjs +1488 -0
  89. package/dist/exceptions.d.cts +1143 -0
  90. package/dist/exceptions.d.mts +1143 -0
  91. package/dist/exceptions.mjs +1325 -0
  92. package/dist/funnel.cjs +297 -0
  93. package/dist/funnel.d.cts +118 -0
  94. package/dist/funnel.d.mts +118 -0
  95. package/dist/funnel.mjs +292 -0
  96. package/dist/index.cjs +287 -0
  97. package/dist/index.d.cts +28 -0
  98. package/dist/index.d.mts +28 -0
  99. package/dist/index.mjs +27 -0
  100. package/dist/log.cjs +75 -0
  101. package/dist/log.d.cts +4 -0
  102. package/dist/log.d.mts +4 -0
  103. package/dist/log.mjs +71 -0
  104. package/dist/oauth.cjs +143 -0
  105. package/dist/oauth.d.cts +69 -0
  106. package/dist/oauth.d.mts +69 -0
  107. package/dist/oauth.mjs +143 -0
  108. package/dist/partner.cjs +32 -0
  109. package/dist/partner.d.cts +24 -0
  110. package/dist/partner.d.mts +23 -0
  111. package/dist/partner.mjs +32 -0
  112. package/dist/powerwall/client.cjs +818 -0
  113. package/dist/powerwall/client.d.cts +297 -0
  114. package/dist/powerwall/client.d.mts +297 -0
  115. package/dist/powerwall/client.mjs +807 -0
  116. package/dist/powerwall/components.cjs +188 -0
  117. package/dist/powerwall/components.d.cts +45 -0
  118. package/dist/powerwall/components.d.mts +45 -0
  119. package/dist/powerwall/components.mjs +182 -0
  120. package/dist/powerwall/energysite.cjs +216 -0
  121. package/dist/powerwall/energysite.d.cts +104 -0
  122. package/dist/powerwall/energysite.d.mts +104 -0
  123. package/dist/powerwall/energysite.mjs +214 -0
  124. package/dist/powerwall/exceptions.cjs +59 -0
  125. package/dist/powerwall/exceptions.d.cts +38 -0
  126. package/dist/powerwall/exceptions.d.mts +38 -0
  127. package/dist/powerwall/exceptions.mjs +51 -0
  128. package/dist/powerwall/gateway.cjs +102 -0
  129. package/dist/powerwall/gateway.d.cts +57 -0
  130. package/dist/powerwall/gateway.d.mts +57 -0
  131. package/dist/powerwall/gateway.mjs +101 -0
  132. package/dist/powerwall/merge.cjs +163 -0
  133. package/dist/powerwall/merge.d.cts +63 -0
  134. package/dist/powerwall/merge.d.mts +63 -0
  135. package/dist/powerwall/merge.mjs +159 -0
  136. package/dist/powerwall/models.d.cts +95 -0
  137. package/dist/powerwall/models.d.mts +95 -0
  138. package/dist/powerwall/queries.cjs +17 -0
  139. package/dist/powerwall/queries.mjs +10 -0
  140. package/dist/powerwall/transport.cjs +232 -0
  141. package/dist/powerwall/transport.mjs +227 -0
  142. package/dist/powerwall.cjs +47 -0
  143. package/dist/powerwall.d.cts +9 -0
  144. package/dist/powerwall.d.mts +9 -0
  145. package/dist/powerwall.mjs +8 -0
  146. package/dist/router/base.cjs +203 -0
  147. package/dist/router/base.d.cts +112 -0
  148. package/dist/router/base.d.mts +112 -0
  149. package/dist/router/base.mjs +200 -0
  150. package/dist/router/energysite.cjs +65 -0
  151. package/dist/router/energysite.d.cts +47 -0
  152. package/dist/router/energysite.d.mts +47 -0
  153. package/dist/router/energysite.mjs +60 -0
  154. package/dist/router.cjs +13 -0
  155. package/dist/router.d.cts +3 -0
  156. package/dist/router.d.mts +3 -0
  157. package/dist/router.mjs +3 -0
  158. package/dist/signing/crypto.cjs +59 -0
  159. package/dist/signing/crypto.mjs +52 -0
  160. package/dist/signing/energy.cjs +123 -0
  161. package/dist/signing/energy.d.cts +45 -0
  162. package/dist/signing/energy.d.mts +45 -0
  163. package/dist/signing/energy.mjs +120 -0
  164. package/dist/signing/jws.cjs +229 -0
  165. package/dist/signing/jws.d.cts +45 -0
  166. package/dist/signing/jws.d.mts +45 -0
  167. package/dist/signing/jws.mjs +211 -0
  168. package/dist/signing/keys.cjs +191 -0
  169. package/dist/signing/keys.d.cts +21 -0
  170. package/dist/signing/keys.d.mts +21 -0
  171. package/dist/signing/keys.mjs +182 -0
  172. package/dist/signing/session.cjs +186 -0
  173. package/dist/signing/session.d.cts +106 -0
  174. package/dist/signing/session.d.mts +106 -0
  175. package/dist/signing/session.mjs +184 -0
  176. package/dist/tariff.cjs +319 -0
  177. package/dist/tariff.d.cts +40 -0
  178. package/dist/tariff.d.mts +40 -0
  179. package/dist/tariff.mjs +317 -0
  180. package/dist/teslafleetapi.cjs +326 -0
  181. package/dist/teslafleetapi.d.cts +160 -0
  182. package/dist/teslafleetapi.d.mts +160 -0
  183. package/dist/teslafleetapi.mjs +326 -0
  184. package/dist/teslemetry.cjs +183 -0
  185. package/dist/teslemetry.d.cts +113 -0
  186. package/dist/teslemetry.d.mts +113 -0
  187. package/dist/teslemetry.mjs +181 -0
  188. package/dist/teslemetryenergy.cjs +287 -0
  189. package/dist/teslemetryenergy.d.cts +154 -0
  190. package/dist/teslemetryenergy.d.mts +154 -0
  191. package/dist/teslemetryenergy.mjs +282 -0
  192. package/dist/teslemetryvehicle.cjs +407 -0
  193. package/dist/teslemetryvehicle.d.cts +238 -0
  194. package/dist/teslemetryvehicle.d.mts +238 -0
  195. package/dist/teslemetryvehicle.mjs +405 -0
  196. package/dist/tessie.cjs +71 -0
  197. package/dist/tessie.d.cts +69 -0
  198. package/dist/tessie.d.mts +69 -0
  199. package/dist/tessie.mjs +71 -0
  200. package/dist/tessievehicle.cjs +514 -0
  201. package/dist/tessievehicle.d.cts +333 -0
  202. package/dist/tessievehicle.d.mts +333 -0
  203. package/dist/tessievehicle.mjs +513 -0
  204. package/dist/types/commands.d.cts +28 -0
  205. package/dist/types/commands.d.mts +28 -0
  206. package/dist/types/live_status.d.cts +30 -0
  207. package/dist/types/live_status.d.mts +30 -0
  208. package/dist/types/products.d.cts +58 -0
  209. package/dist/types/products.d.mts +58 -0
  210. package/dist/types/responses.d.cts +13 -0
  211. package/dist/types/responses.d.mts +13 -0
  212. package/dist/types/site_info.d.cts +154 -0
  213. package/dist/types/site_info.d.mts +154 -0
  214. package/dist/types/vehicle.d.cts +33 -0
  215. package/dist/types/vehicle.d.mts +33 -0
  216. package/dist/types/vehicle_data.d.cts +278 -0
  217. package/dist/types/vehicle_data.d.mts +278 -0
  218. package/dist/types.cjs +0 -0
  219. package/dist/types.d.cts +9 -0
  220. package/dist/types.d.mts +9 -0
  221. package/dist/types.mjs +1 -0
  222. package/dist/user.cjs +28 -0
  223. package/dist/user.d.cts +17 -0
  224. package/dist/user.d.mts +16 -0
  225. package/dist/user.mjs +28 -0
  226. package/dist/util.cjs +41 -0
  227. package/dist/util.d.cts +18 -0
  228. package/dist/util.d.mts +18 -0
  229. package/dist/util.mjs +40 -0
  230. package/dist/vehicle.cjs +847 -0
  231. package/dist/vehicle.d.cts +566 -0
  232. package/dist/vehicle.d.mts +565 -0
  233. package/dist/vehicle.mjs +847 -0
  234. package/dist/vehiclesigned.cjs +35 -0
  235. package/dist/vehiclesigned.d.cts +23 -0
  236. package/dist/vehiclesigned.d.mts +22 -0
  237. package/dist/vehiclesigned.mjs +35 -0
  238. package/dist/vehiclespecific.cjs +715 -0
  239. package/dist/vehiclespecific.d.cts +497 -0
  240. package/dist/vehiclespecific.d.mts +496 -0
  241. package/dist/vehiclespecific.mjs +715 -0
  242. package/package.json +178 -38
  243. package/dist/charging.d.ts +0 -30
  244. package/dist/charging.js +0 -51
  245. package/dist/charging.js.map +0 -1
  246. package/dist/commands.d.ts +0 -352
  247. package/dist/commands.js +0 -875
  248. package/dist/commands.js.map +0 -1
  249. package/dist/energy.d.ts +0 -94
  250. package/dist/energy.js +0 -125
  251. package/dist/energy.js.map +0 -1
  252. package/dist/energyspecific.d.ts +0 -77
  253. package/dist/energyspecific.js +0 -97
  254. package/dist/energyspecific.js.map +0 -1
  255. package/dist/helpers.d.ts +0 -2
  256. package/dist/helpers.js +0 -10
  257. package/dist/helpers.js.map +0 -1
  258. package/dist/index.d.ts +0 -15
  259. package/dist/index.js +0 -15
  260. package/dist/index.js.map +0 -1
  261. package/dist/partner.d.ts +0 -23
  262. package/dist/partner.js +0 -31
  263. package/dist/partner.js.map +0 -1
  264. package/dist/signing/crypto.d.ts +0 -10
  265. package/dist/signing/crypto.js +0 -26
  266. package/dist/signing/crypto.js.map +0 -1
  267. package/dist/signing/errors.d.ts +0 -29
  268. package/dist/signing/errors.js +0 -40
  269. package/dist/signing/errors.js.map +0 -1
  270. package/dist/signing/session.d.ts +0 -69
  271. package/dist/signing/session.js +0 -102
  272. package/dist/signing/session.js.map +0 -1
  273. package/dist/tariff.d.ts +0 -29
  274. package/dist/tariff.js +0 -302
  275. package/dist/tariff.js.map +0 -1
  276. package/dist/teslafleetapi.d.ts +0 -58
  277. package/dist/teslafleetapi.js +0 -120
  278. package/dist/teslafleetapi.js.map +0 -1
  279. package/dist/teslemetry.d.ts +0 -29
  280. package/dist/teslemetry.js +0 -39
  281. package/dist/teslemetry.js.map +0 -1
  282. package/dist/tessie.d.ts +0 -23
  283. package/dist/tessie.js +0 -28
  284. package/dist/tessie.js.map +0 -1
  285. package/dist/types/commands.d.ts +0 -44
  286. package/dist/types/commands.js +0 -11
  287. package/dist/types/commands.js.map +0 -1
  288. package/dist/types/live_status.d.ts +0 -26
  289. package/dist/types/live_status.js +0 -2
  290. package/dist/types/live_status.js.map +0 -1
  291. package/dist/types/products.d.ts +0 -54
  292. package/dist/types/products.js +0 -2
  293. package/dist/types/products.js.map +0 -1
  294. package/dist/types/responses.d.ts +0 -5
  295. package/dist/types/responses.js +0 -2
  296. package/dist/types/responses.js.map +0 -1
  297. package/dist/types/site_info.d.ts +0 -150
  298. package/dist/types/site_info.js +0 -2
  299. package/dist/types/site_info.js.map +0 -1
  300. package/dist/types/vehicle.d.ts +0 -23
  301. package/dist/types/vehicle.js +0 -10
  302. package/dist/types/vehicle.js.map +0 -1
  303. package/dist/types/vehicle_data.d.ts +0 -274
  304. package/dist/types/vehicle_data.js +0 -2
  305. package/dist/types/vehicle_data.js.map +0 -1
  306. package/dist/user.d.ts +0 -16
  307. package/dist/user.js +0 -26
  308. package/dist/user.js.map +0 -1
  309. package/dist/vehicle.d.ts +0 -548
  310. package/dist/vehicle.js +0 -774
  311. package/dist/vehicle.js.map +0 -1
  312. package/dist/vehiclesigned.d.ts +0 -13
  313. package/dist/vehiclesigned.js +0 -19
  314. package/dist/vehiclesigned.js.map +0 -1
  315. package/dist/vehiclespecific.d.ts +0 -432
  316. package/dist/vehiclespecific.js +0 -601
  317. package/dist/vehiclespecific.js.map +0 -1
  318. package/src/charging.ts +0 -66
  319. package/src/commands.ts +0 -1134
  320. package/src/energy.ts +0 -217
  321. package/src/energyspecific.ts +0 -116
  322. package/src/helpers.ts +0 -11
  323. package/src/index.ts +0 -21
  324. package/src/partner.ts +0 -37
  325. package/src/signing/crypto.ts +0 -29
  326. package/src/signing/errors.ts +0 -50
  327. package/src/signing/session.ts +0 -132
  328. package/src/tariff.ts +0 -375
  329. package/src/teslafleetapi.ts +0 -182
  330. package/src/teslemetry.ts +0 -54
  331. package/src/tessie.ts +0 -42
  332. package/src/types/commands.ts +0 -62
  333. package/src/types/live_status.ts +0 -27
  334. package/src/types/products.ts +0 -61
  335. package/src/types/responses.ts +0 -1
  336. package/src/types/site_info.ts +0 -159
  337. package/src/types/vehicle.ts +0 -33
  338. package/src/types/vehicle_data.ts +0 -286
  339. package/src/user.ts +0 -35
  340. package/src/vehicle.ts +0 -886
  341. package/src/vehiclesigned.ts +0 -20
  342. package/src/vehiclespecific.ts +0 -818
@@ -0,0 +1,106 @@
1
+ import { Domain } from "@teslemetry/tesla-protocol/command/universal_message";
2
+ import { AESGCMPersonalizedSignatureData, HMACPersonalizedSignatureData, SessionInfo } from "@teslemetry/tesla-protocol/command/signatures";
3
+ /** Key material derived (but not yet trusted/committed) for a candidate vehicle public key. */
4
+ export interface SessionKeys {
5
+ sharedKey: Buffer;
6
+ hmacKey: Buffer;
7
+ sessionInfoKey: Buffer;
8
+ }
9
+ /**
10
+ * Per-domain (VCSEC / Infotainment) signed-command session: tracks the
11
+ * anti-replay counter/epoch handed out by the vehicle's handshake reply and
12
+ * the HMAC keys derived from it.
13
+ *
14
+ * The vehicle's key is trusted once, at first contact, then pinned for the life
15
+ * of the session, as in Tesla's Go SDK. ECDH is symmetric, so anyone who saw the
16
+ * client's public key can tag a `SessionInfo` naming a key of their own: a tag
17
+ * proves nothing about a key derived from the same reply. `trustedKeys` is the
18
+ * only way a reply reaches key material, so once pinned it is verified with the
19
+ * stored keys, and nothing clears the pin. A re-keyed vehicle needs a new instance.
20
+ *
21
+ * First contact itself is trust on first use. Deriving keys (`keysFor`) is still
22
+ * split from committing them (`commit`), so a reply that fails its tag or carries
23
+ * a whitelist rejection pins nothing. See `Commands.validate_msg`.
24
+ *
25
+ * A `Session` is not itself thread-safe; `withLock` serializes the
26
+ * message-build step (which mutates `counter`) so concurrent commands on the
27
+ * same domain cannot race on the same counter value.
28
+ *
29
+ * The derived keys are native private fields, read through accessors, so a
30
+ * `JSON.stringify` or structured log of a session cannot reach them.
31
+ */
32
+ export declare class Session {
33
+ #private;
34
+ private readonly deriveSharedKey;
35
+ readonly domain: Domain;
36
+ counter: number;
37
+ epoch?: Buffer;
38
+ delta?: number;
39
+ /** Clock time of the last *authenticated* SessionInfo committed for the current epoch, guarding against replay/rollback. */
40
+ private lastAuthenticatedClockTime?;
41
+ /** `(requestHash, counter)` pairs seen in this epoch, oldest first. */
42
+ private responseCounters;
43
+ private queue;
44
+ constructor(domain: Domain, deriveSharedKey: (vehiclePublicKey: Buffer) => Buffer);
45
+ /** The vehicle key pinned at first contact. There is deliberately no setter. */
46
+ get publicKey(): Buffer | undefined;
47
+ get sharedKey(): Buffer | undefined;
48
+ get hmacKey(): Buffer | undefined;
49
+ get sessionInfoKey(): Buffer | undefined;
50
+ /** Whether the handshake has completed and commands can be signed. */
51
+ get ready(): boolean;
52
+ /**
53
+ * Derive the shared key and its two HMAC sub-keys for a candidate vehicle
54
+ * public key, without touching any session state. Pure and side-effect
55
+ * free so a caller can verify authenticity first and discard the result
56
+ * if verification fails. Verifying a reply goes through `trustedKeys`.
57
+ */
58
+ keysFor(vehiclePublicKey: Buffer): SessionKeys;
59
+ /**
60
+ * The keys to verify a reply naming `vehiclePublicKey` with: derived from it
61
+ * at first contact, and the stored keys from then on, never derived again.
62
+ * @throws SessionInfoAuthenticationFault when the session is pinned to another key.
63
+ */
64
+ trustedKeys(vehiclePublicKey: Buffer): SessionKeys;
65
+ /**
66
+ * Commit an already-authenticated `SessionInfo` plus its `trustedKeys`
67
+ * result. Keys are adopted at first contact only: after that, `keys` is
68
+ * ignored and a different public key is refused. Per the vehicle-command
69
+ * protocol, the anti-replay counter must never roll back within the same
70
+ * epoch, and a clock time that regresses within the same epoch marks the
71
+ * reply as stale/replayed - returns `false` (without mutating any state) in
72
+ * that case so the caller can reject it.
73
+ * @throws SessionInfoAuthenticationFault, changing nothing, for a different public key, or a clock or counter that could not sign a single command.
74
+ */
75
+ commit(sessionInfo: SessionInfo, keys: SessionKeys): boolean;
76
+ /**
77
+ * Record an authenticated response's counter for the request it answers.
78
+ * @returns `false` if that pair was already seen, marking the response a replay.
79
+ */
80
+ record_response_counter(requestHash: Uint8Array, counter: number): boolean;
81
+ /**
82
+ * Advance the anti-replay counter for a command valid for `lifetimeSeconds`,
83
+ * corrected for clock drift against the vehicle (`delta`). The clock keeps
84
+ * running after `commit`, so its limit is checked here too; a session past it
85
+ * recovers when the vehicle starts a new epoch.
86
+ * @throws SessionInfoAuthenticationFault, leaving the counter alone, when either value would overflow its wire field.
87
+ */
88
+ private nextSignature;
89
+ /**
90
+ * Fresh signature metadata for an HMAC-personalized command, which expires
91
+ * 10 seconds from now.
92
+ */
93
+ hmacPersonalized(): HMACPersonalizedSignatureData;
94
+ /**
95
+ * Fresh signature metadata for an AES-GCM-personalized command, with a
96
+ * random nonce, which expires 30 seconds from now.
97
+ */
98
+ aesGcmPersonalized(): AESGCMPersonalizedSignatureData;
99
+ /**
100
+ * Run `fn` with exclusive access to this session, queuing behind any
101
+ * in-flight caller. Not reentrant: `fn` must not call `withLock` on this
102
+ * session. Infotainment holds it for the message build only, and VCSEC for
103
+ * the whole dispatch (see `Commands.dispatch`).
104
+ */
105
+ withLock<T>(fn: () => T | Promise<T>): Promise<T>;
106
+ }
@@ -0,0 +1,106 @@
1
+ import { Domain } from "@teslemetry/tesla-protocol/command/universal_message";
2
+ import { AESGCMPersonalizedSignatureData, HMACPersonalizedSignatureData, SessionInfo } from "@teslemetry/tesla-protocol/command/signatures";
3
+ /** Key material derived (but not yet trusted/committed) for a candidate vehicle public key. */
4
+ export interface SessionKeys {
5
+ sharedKey: Buffer;
6
+ hmacKey: Buffer;
7
+ sessionInfoKey: Buffer;
8
+ }
9
+ /**
10
+ * Per-domain (VCSEC / Infotainment) signed-command session: tracks the
11
+ * anti-replay counter/epoch handed out by the vehicle's handshake reply and
12
+ * the HMAC keys derived from it.
13
+ *
14
+ * The vehicle's key is trusted once, at first contact, then pinned for the life
15
+ * of the session, as in Tesla's Go SDK. ECDH is symmetric, so anyone who saw the
16
+ * client's public key can tag a `SessionInfo` naming a key of their own: a tag
17
+ * proves nothing about a key derived from the same reply. `trustedKeys` is the
18
+ * only way a reply reaches key material, so once pinned it is verified with the
19
+ * stored keys, and nothing clears the pin. A re-keyed vehicle needs a new instance.
20
+ *
21
+ * First contact itself is trust on first use. Deriving keys (`keysFor`) is still
22
+ * split from committing them (`commit`), so a reply that fails its tag or carries
23
+ * a whitelist rejection pins nothing. See `Commands.validate_msg`.
24
+ *
25
+ * A `Session` is not itself thread-safe; `withLock` serializes the
26
+ * message-build step (which mutates `counter`) so concurrent commands on the
27
+ * same domain cannot race on the same counter value.
28
+ *
29
+ * The derived keys are native private fields, read through accessors, so a
30
+ * `JSON.stringify` or structured log of a session cannot reach them.
31
+ */
32
+ export declare class Session {
33
+ #private;
34
+ private readonly deriveSharedKey;
35
+ readonly domain: Domain;
36
+ counter: number;
37
+ epoch?: Buffer;
38
+ delta?: number;
39
+ /** Clock time of the last *authenticated* SessionInfo committed for the current epoch, guarding against replay/rollback. */
40
+ private lastAuthenticatedClockTime?;
41
+ /** `(requestHash, counter)` pairs seen in this epoch, oldest first. */
42
+ private responseCounters;
43
+ private queue;
44
+ constructor(domain: Domain, deriveSharedKey: (vehiclePublicKey: Buffer) => Buffer);
45
+ /** The vehicle key pinned at first contact. There is deliberately no setter. */
46
+ get publicKey(): Buffer | undefined;
47
+ get sharedKey(): Buffer | undefined;
48
+ get hmacKey(): Buffer | undefined;
49
+ get sessionInfoKey(): Buffer | undefined;
50
+ /** Whether the handshake has completed and commands can be signed. */
51
+ get ready(): boolean;
52
+ /**
53
+ * Derive the shared key and its two HMAC sub-keys for a candidate vehicle
54
+ * public key, without touching any session state. Pure and side-effect
55
+ * free so a caller can verify authenticity first and discard the result
56
+ * if verification fails. Verifying a reply goes through `trustedKeys`.
57
+ */
58
+ keysFor(vehiclePublicKey: Buffer): SessionKeys;
59
+ /**
60
+ * The keys to verify a reply naming `vehiclePublicKey` with: derived from it
61
+ * at first contact, and the stored keys from then on, never derived again.
62
+ * @throws SessionInfoAuthenticationFault when the session is pinned to another key.
63
+ */
64
+ trustedKeys(vehiclePublicKey: Buffer): SessionKeys;
65
+ /**
66
+ * Commit an already-authenticated `SessionInfo` plus its `trustedKeys`
67
+ * result. Keys are adopted at first contact only: after that, `keys` is
68
+ * ignored and a different public key is refused. Per the vehicle-command
69
+ * protocol, the anti-replay counter must never roll back within the same
70
+ * epoch, and a clock time that regresses within the same epoch marks the
71
+ * reply as stale/replayed - returns `false` (without mutating any state) in
72
+ * that case so the caller can reject it.
73
+ * @throws SessionInfoAuthenticationFault, changing nothing, for a different public key, or a clock or counter that could not sign a single command.
74
+ */
75
+ commit(sessionInfo: SessionInfo, keys: SessionKeys): boolean;
76
+ /**
77
+ * Record an authenticated response's counter for the request it answers.
78
+ * @returns `false` if that pair was already seen, marking the response a replay.
79
+ */
80
+ record_response_counter(requestHash: Uint8Array, counter: number): boolean;
81
+ /**
82
+ * Advance the anti-replay counter for a command valid for `lifetimeSeconds`,
83
+ * corrected for clock drift against the vehicle (`delta`). The clock keeps
84
+ * running after `commit`, so its limit is checked here too; a session past it
85
+ * recovers when the vehicle starts a new epoch.
86
+ * @throws SessionInfoAuthenticationFault, leaving the counter alone, when either value would overflow its wire field.
87
+ */
88
+ private nextSignature;
89
+ /**
90
+ * Fresh signature metadata for an HMAC-personalized command, which expires
91
+ * 10 seconds from now.
92
+ */
93
+ hmacPersonalized(): HMACPersonalizedSignatureData;
94
+ /**
95
+ * Fresh signature metadata for an AES-GCM-personalized command, with a
96
+ * random nonce, which expires 30 seconds from now.
97
+ */
98
+ aesGcmPersonalized(): AESGCMPersonalizedSignatureData;
99
+ /**
100
+ * Run `fn` with exclusive access to this session, queuing behind any
101
+ * in-flight caller. Not reentrant: `fn` must not call `withLock` on this
102
+ * session. Infotainment holds it for the message build only, and VCSEC for
103
+ * the whole dispatch (see `Commands.dispatch`).
104
+ */
105
+ withLock<T>(fn: () => T | Promise<T>): Promise<T>;
106
+ }
@@ -0,0 +1,184 @@
1
+ import { SessionInfoAuthenticationFault } from "../exceptions.mjs";
2
+ import crypto from "crypto";
3
+ import { AESGCMPersonalizedSignatureData, HMACPersonalizedSignatureData } from "@teslemetry/tesla-protocol/command/signatures";
4
+ // src/signing/session.ts
5
+ /** Wire limit of the `counter` and `expires_at` fields. */
6
+ const UINT32_MAX = 4294967295;
7
+ /** Seconds a signed command stays valid. BLE can be slow, so AES-GCM allows longer. */
8
+ const HMAC_LIFETIME_S = 10;
9
+ const AES_GCM_LIFETIME_S = 30;
10
+ /** Response counters remembered per session, which bounds how far back a replay is caught. */
11
+ const RESPONSE_COUNTER_CACHE_SIZE = 256;
12
+ /**
13
+ * Per-domain (VCSEC / Infotainment) signed-command session: tracks the
14
+ * anti-replay counter/epoch handed out by the vehicle's handshake reply and
15
+ * the HMAC keys derived from it.
16
+ *
17
+ * The vehicle's key is trusted once, at first contact, then pinned for the life
18
+ * of the session, as in Tesla's Go SDK. ECDH is symmetric, so anyone who saw the
19
+ * client's public key can tag a `SessionInfo` naming a key of their own: a tag
20
+ * proves nothing about a key derived from the same reply. `trustedKeys` is the
21
+ * only way a reply reaches key material, so once pinned it is verified with the
22
+ * stored keys, and nothing clears the pin. A re-keyed vehicle needs a new instance.
23
+ *
24
+ * First contact itself is trust on first use. Deriving keys (`keysFor`) is still
25
+ * split from committing them (`commit`), so a reply that fails its tag or carries
26
+ * a whitelist rejection pins nothing. See `Commands.validate_msg`.
27
+ *
28
+ * A `Session` is not itself thread-safe; `withLock` serializes the
29
+ * message-build step (which mutates `counter`) so concurrent commands on the
30
+ * same domain cannot race on the same counter value.
31
+ *
32
+ * The derived keys are native private fields, read through accessors, so a
33
+ * `JSON.stringify` or structured log of a session cannot reach them.
34
+ */
35
+ var Session = class {
36
+ deriveSharedKey;
37
+ domain;
38
+ counter = 0;
39
+ epoch;
40
+ delta;
41
+ #publicKey;
42
+ #keys;
43
+ /** Clock time of the last *authenticated* SessionInfo committed for the current epoch, guarding against replay/rollback. */
44
+ lastAuthenticatedClockTime;
45
+ /** `(requestHash, counter)` pairs seen in this epoch, oldest first. */
46
+ responseCounters = /* @__PURE__ */ new Set();
47
+ queue = Promise.resolve();
48
+ constructor(domain, deriveSharedKey) {
49
+ this.deriveSharedKey = deriveSharedKey;
50
+ this.domain = domain;
51
+ }
52
+ /** The vehicle key pinned at first contact. There is deliberately no setter. */
53
+ get publicKey() {
54
+ return this.#publicKey;
55
+ }
56
+ get sharedKey() {
57
+ return this.#keys?.sharedKey;
58
+ }
59
+ get hmacKey() {
60
+ return this.#keys?.hmacKey;
61
+ }
62
+ get sessionInfoKey() {
63
+ return this.#keys?.sessionInfoKey;
64
+ }
65
+ /** Whether the handshake has completed and commands can be signed. */
66
+ get ready() {
67
+ return this.epoch !== void 0 && this.#keys !== void 0 && this.delta !== void 0;
68
+ }
69
+ /**
70
+ * Derive the shared key and its two HMAC sub-keys for a candidate vehicle
71
+ * public key, without touching any session state. Pure and side-effect
72
+ * free so a caller can verify authenticity first and discard the result
73
+ * if verification fails. Verifying a reply goes through `trustedKeys`.
74
+ */
75
+ keysFor(vehiclePublicKey) {
76
+ const sharedKey = this.deriveSharedKey(vehiclePublicKey);
77
+ return {
78
+ sharedKey,
79
+ hmacKey: crypto.createHmac("sha256", sharedKey).update("authenticated command").digest(),
80
+ sessionInfoKey: crypto.createHmac("sha256", sharedKey).update("session info").digest()
81
+ };
82
+ }
83
+ /**
84
+ * The keys to verify a reply naming `vehiclePublicKey` with: derived from it
85
+ * at first contact, and the stored keys from then on, never derived again.
86
+ * @throws SessionInfoAuthenticationFault when the session is pinned to another key.
87
+ */
88
+ trustedKeys(vehiclePublicKey) {
89
+ if (this.#publicKey === void 0 || this.#keys === void 0) return this.keysFor(vehiclePublicKey);
90
+ if (!this.#publicKey.equals(vehiclePublicKey)) throw new SessionInfoAuthenticationFault("Session info reply names a vehicle public key that has changed since first contact; discarded. If the vehicle was really re-keyed, build a new instance to trust it.");
91
+ return this.#keys;
92
+ }
93
+ /**
94
+ * Commit an already-authenticated `SessionInfo` plus its `trustedKeys`
95
+ * result. Keys are adopted at first contact only: after that, `keys` is
96
+ * ignored and a different public key is refused. Per the vehicle-command
97
+ * protocol, the anti-replay counter must never roll back within the same
98
+ * epoch, and a clock time that regresses within the same epoch marks the
99
+ * reply as stale/replayed - returns `false` (without mutating any state) in
100
+ * that case so the caller can reject it.
101
+ * @throws SessionInfoAuthenticationFault, changing nothing, for a different public key, or a clock or counter that could not sign a single command.
102
+ */
103
+ commit(sessionInfo, keys) {
104
+ const trusted = this.#keys === void 0 ? keys : this.trustedKeys(Buffer.from(sessionInfo.publicKey));
105
+ const incomingEpoch = Buffer.from(sessionInfo.epoch);
106
+ const sameEpoch = this.epoch !== void 0 && this.epoch.equals(incomingEpoch);
107
+ if (sameEpoch && this.lastAuthenticatedClockTime !== void 0 && sessionInfo.clockTime < this.lastAuthenticatedClockTime) return false;
108
+ if (sessionInfo.clockTime + AES_GCM_LIFETIME_S > UINT32_MAX || sessionInfo.counter >= UINT32_MAX) throw new SessionInfoAuthenticationFault("Session info reply has a clock or counter no command could be signed with; discarded.");
109
+ this.counter = sameEpoch ? Math.max(this.counter, sessionInfo.counter) : sessionInfo.counter;
110
+ this.epoch = incomingEpoch;
111
+ this.delta = Math.floor(Date.now() / 1e3) - sessionInfo.clockTime;
112
+ this.lastAuthenticatedClockTime = sessionInfo.clockTime;
113
+ this.#publicKey = Buffer.from(sessionInfo.publicKey);
114
+ this.#keys = trusted;
115
+ if (!sameEpoch) this.responseCounters.clear();
116
+ return true;
117
+ }
118
+ /**
119
+ * Record an authenticated response's counter for the request it answers.
120
+ * @returns `false` if that pair was already seen, marking the response a replay.
121
+ */
122
+ record_response_counter(requestHash, counter) {
123
+ const identity = `${Buffer.from(requestHash).toString("hex")}:${counter}`;
124
+ if (this.responseCounters.has(identity)) return false;
125
+ this.responseCounters.add(identity);
126
+ if (this.responseCounters.size > RESPONSE_COUNTER_CACHE_SIZE) this.responseCounters.delete(this.responseCounters.values().next().value);
127
+ return true;
128
+ }
129
+ /**
130
+ * Advance the anti-replay counter for a command valid for `lifetimeSeconds`,
131
+ * corrected for clock drift against the vehicle (`delta`). The clock keeps
132
+ * running after `commit`, so its limit is checked here too; a session past it
133
+ * recovers when the vehicle starts a new epoch.
134
+ * @throws SessionInfoAuthenticationFault, leaving the counter alone, when either value would overflow its wire field.
135
+ */
136
+ nextSignature(lifetimeSeconds) {
137
+ if (!this.ready || !this.epoch || this.delta === void 0) throw new Error("Session is not ready: perform the handshake first.");
138
+ const expiresAt = Math.floor(Date.now() / 1e3) - this.delta + lifetimeSeconds;
139
+ if (expiresAt > UINT32_MAX || this.counter >= UINT32_MAX) throw new SessionInfoAuthenticationFault("Session has reached the limit of the vehicle clock or counter, so nothing was signed.");
140
+ this.counter++;
141
+ return {
142
+ epoch: this.epoch,
143
+ counter: this.counter,
144
+ expiresAt
145
+ };
146
+ }
147
+ /**
148
+ * Fresh signature metadata for an HMAC-personalized command, which expires
149
+ * 10 seconds from now.
150
+ */
151
+ hmacPersonalized() {
152
+ return HMACPersonalizedSignatureData.create(this.nextSignature(HMAC_LIFETIME_S));
153
+ }
154
+ /**
155
+ * Fresh signature metadata for an AES-GCM-personalized command, with a
156
+ * random nonce, which expires 30 seconds from now.
157
+ */
158
+ aesGcmPersonalized() {
159
+ return AESGCMPersonalizedSignatureData.create({
160
+ ...this.nextSignature(AES_GCM_LIFETIME_S),
161
+ nonce: crypto.randomBytes(12)
162
+ });
163
+ }
164
+ /**
165
+ * Run `fn` with exclusive access to this session, queuing behind any
166
+ * in-flight caller. Not reentrant: `fn` must not call `withLock` on this
167
+ * session. Infotainment holds it for the message build only, and VCSEC for
168
+ * the whole dispatch (see `Commands.dispatch`).
169
+ */
170
+ async withLock(fn) {
171
+ const previous = this.queue;
172
+ let release;
173
+ this.queue = new Promise((resolve) => {
174
+ release = resolve;
175
+ });
176
+ await previous.catch(() => void 0);
177
+ try {
178
+ return await fn();
179
+ } finally {
180
+ release();
181
+ }
182
+ }
183
+ };
184
+ export { Session };