Skip to content

Commit 52506d3

Browse files
committed
fix(ble): decode whitelistOperationStatus in Commands._command VCSEC reply
A WhitelistOperation sent through the signed-command path reported {"result": True} even when the vehicle rejected it (e.g. NO_PERMISSION_TO_ADD), because the OPERATIONSTATUS_OK branch never inspected commandStatus.whitelistOperationStatus. Reuse the same WHITELIST_OPERATION_STATUS mapping bluetooth.py's _raise_for_whitelist_reply already uses. Bumps version to 1.14.1 (bug fix).
1 parent cf3bfa9 commit 52506d3

5 files changed

Lines changed: 82 additions & 3 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ requires = ["setuptools>=77.0"]
44

55
[project]
66
name = "tesla_fleet_api"
7-
version = "1.14.0"
7+
version = "1.14.1"
88
license = "Apache-2.0"
99
description = "Tesla Fleet API library for Python"
1010
readme = "README.md"

tesla_fleet_api/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Tesla Fleet API"""
22

33
__author__ = "hello@teslemetry.com"
4-
__version__ = "1.14.0"
4+
__version__ = "1.14.1"
55

66
from tesla_fleet_api.const import Region, is_valid_region
77
from tesla_fleet_api.funnel import (

tesla_fleet_api/tesla/vehicle/commands.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
from tesla_fleet_api.exceptions import (
2727
MESSAGE_FAULTS,
2828
SIGNED_MESSAGE_INFORMATION_FAULTS,
29+
WHITELIST_OPERATION_STATUS,
2930
NotOnWhitelistFault,
3031
SessionInfoAuthenticationFault,
3132
SignedCommandResponseReplayed,
3233
SigningDisabled,
3334
TeslaFleetError,
35+
WhitelistOperationStatus,
3436
# TeslaFleetMessageFaultInvalidSignature,
3537
TeslaFleetMessageFaultIncorrectEpoch,
3638
TeslaFleetMessageFaultInvalidTokenOrCounter,
@@ -788,6 +790,16 @@ async def _command(
788790
vcsec.commandStatus.operationStatus
789791
== OperationStatus_E.OPERATIONSTATUS_OK
790792
):
793+
info = vcsec.commandStatus.whitelistOperationStatus.whitelistOperationInformation
794+
if info:
795+
if info < len(WHITELIST_OPERATION_STATUS):
796+
exception = WHITELIST_OPERATION_STATUS[info]
797+
if exception:
798+
raise exception
799+
else:
800+
raise WhitelistOperationStatus(
801+
f"Unknown whitelist operation failure: {info}"
802+
)
791803
return {"response": {"result": True, "reason": ""}}
792804
elif (
793805
vcsec.commandStatus.operationStatus
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""``Commands._command()`` must decode ``whitelistOperationStatus`` on a VCSEC
2+
reply, not just ``operationStatus`` - a ``WhitelistOperation`` the vehicle
3+
rejects still replies with ``OPERATIONSTATUS_OK`` at the outer level.
4+
"""
5+
6+
from __future__ import annotations
7+
8+
from tesla_fleet_api.exceptions import WhitelistOperationNoPermissionToAdd
9+
from tesla_protocol.command.universal_message_pb2 import (
10+
Destination,
11+
Domain,
12+
RoutableMessage,
13+
)
14+
from tesla_protocol.command.vcsec_pb2 import (
15+
CommandStatus,
16+
FromVCSECMessage,
17+
OperationStatus_E,
18+
WHITELISTOPERATION_INFORMATION_NO_PERMISSION_TO_ADD,
19+
WhitelistOperation_status,
20+
)
21+
22+
from ble_mocked_transport import MockedBleTransportTestCase
23+
24+
25+
def whitelist_op_reply(info: int) -> RoutableMessage:
26+
"""A VCSEC reply to a ``WhitelistOperation``: outer status OK, op status in ``info``."""
27+
body = FromVCSECMessage(
28+
commandStatus=CommandStatus(
29+
operationStatus=OperationStatus_E.OPERATIONSTATUS_OK,
30+
whitelistOperationStatus=WhitelistOperation_status(
31+
whitelistOperationInformation=info
32+
),
33+
)
34+
)
35+
return RoutableMessage(
36+
from_destination=Destination(domain=Domain.DOMAIN_VEHICLE_SECURITY),
37+
protobuf_message_as_bytes=body.SerializeToString(),
38+
)
39+
40+
41+
class VcsecWhitelistStatusDecodeTest(MockedBleTransportTestCase):
42+
async def test_rejected_whitelist_operation_raises(self) -> None:
43+
"""A vehicle-rejected key add must raise, not report ``result: True``."""
44+
vehicle, send = self.make_vehicle()
45+
send.return_value = whitelist_op_reply(
46+
WHITELISTOPERATION_INFORMATION_NO_PERMISSION_TO_ADD
47+
)
48+
49+
with self.assertRaises(WhitelistOperationNoPermissionToAdd):
50+
await vehicle._command(
51+
Domain.DOMAIN_VEHICLE_SECURITY,
52+
b"",
53+
expects_data=False,
54+
)
55+
56+
async def test_accepted_whitelist_operation_succeeds(self) -> None:
57+
"""A zero/absent whitelist status leaves the existing success path intact."""
58+
vehicle, send = self.make_vehicle()
59+
send.return_value = whitelist_op_reply(0)
60+
61+
result = await vehicle._command(
62+
Domain.DOMAIN_VEHICLE_SECURITY,
63+
b"",
64+
expects_data=False,
65+
)
66+
67+
self.assertEqual(result, {"response": {"result": True, "reason": ""}})

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)