This document contains a specification of the protocol used by the mobile app to send commands to Tesla vehicles. It covers session management and cryptography. Application-layer payloads are out of scope, but should be easily reproduced by following source code links from the package documentation.
Protocol messages are encoded using Google's Protocol
Buffers. The top-level type is a RoutableMessage
,
defined in universal_message.proto.
A typical RoutableMessage looks like this:
to_destination {
domain: DOMAIN_VEHICLE_SECURITY
}
from_destination {
routing_address: 0a7962c10d38b61dd2a7722780a4f096
}
protobuf_message_as_bytes: 0a020805
uuid: 05514f57616bcc81a8ce0f9d7b483229
The fields of a RoutableMessage are always raw binary values; the hex encodings above are for printability.
- For commands sent to the vehicle, the
to_destination
should be the Domain responsible for handling the command. - For commands sent to the vehicle,
from_destination
should be a randomly generated 16-byterouting_address
that uniquely identifies the connection. Vehicles use routing addresses to associate clients with transport-layer addresses when forwarding the response. - The vehicle reverses the to and from fields in its response.
- The
protobuf_message_as_bytes
field is aoneof
namedpayload
that can have the following types:payload.protobuf_message_as_bytes
contains an application-layer payload. Messages addressed toDOMAIN_VEHICLE_SECURITY
(VCSEC) parse this as avcsec.UnsignedMessage
, while messages addressed toDOMAIN_INFOTAINMENT
parse this as acarsever.Action
.payload.session_info_request
is a handshake request from a client. The client needs to complete a handshake before it can send commands.payload.session_info
is a handshake response from the vehicle.
- The
signature_data
field contains information used to authenticate the command. See Authentication. - The
uuid
field must uniquely identify the command, and must be at most 16 bytes. It will be copied into therequest_uuid
field of the response. Due to memory constraints, therequest_uuid
field is typically not populated in replies from VCSEC. Requests can use unique routing addresses to disambiguate responses, but note that due to VCSEC memory constraints, clients should avoid making simultaneous requests to that domain. The UUID should be unpredictable in order to prevent replayed handshake responses. - The vehicle sets
signedMessageStatus
to indicate protocol-layer errors. Application-layer errors appear inprotobuf_message_as_bytes
. - The
flags
field is a bit mask ofuniversal_message.Flags
values. There is currently no need for clients to use this field; future versions of the protocol may use this field to add authenticated data that is discarded by older vehicles.
For command-line debugging, a RoutableMessage can be decoded using the
protoc tool from Google. It can
also generate language-specific bindings from the
*.proto
files included in this repository.
For example, running tesla-control -vin YOUR_VIN -ble -debug list-keys
shows
a debug line:
2023-12-13T14:41:13-08:00 [debug] TX: 320208023a1212100a7962c10d38b61dd2a7722780a4f0969a031005514f57616bcc81a8ce0f9d7b48322952040a020805
The hex string above can be decoded using protoc
using the command below,
executed from this document's directory:
echo 320208023a1212100a7962c10d38b61dd2a7722780a4f0969a031005514f57616bcc81a8ce0f9d7b48322952040a020805 \
| xxd -r -p \
| protoc --decode=UniversalMessage.RoutableMessage -I protobuf protobuf/*.proto
to_destination {
domain: DOMAIN_VEHICLE_SECURITY
}
from_destination {
routing_address: "\nyb\301\r8\266\035\322\247r\'\200\244\360\226"
}
protobuf_message_as_bytes: "\n\002\010\005"
uuid: "\005QOWak\314\201\250\316\017\235{H2)"
This shows the client transmitted (TX) a RoutableMessage sent to
DOMAIN_VEHICLE_SECURITY
. If a message is sent to this domain, then the
protobuf_message_as_bytes
field encodes a VCSEC.UnsignedMessage, which can be
similarly decoded with protoc
:
printf "\n\002\010\005" | protoc --decode=VCSEC.UnsignedMessage -I protobuf protobuf/*.proto
InformationRequest {
informationRequestType: INFORMATION_REQUEST_TYPE_GET_WHITELIST_INFO
}
Messages can be sent to vehicles either over a REST API or over BLE.
See Fleet API documentation for information on using OAuth authentication.
To send a message to a vehicle, make a POST request to
api/1/vehicles/<vin>/signed_command
. The body should be {"routable_message": <base64-encoded RoutableMessage>}
.
If the server receives a response from the vehicle, it returns status code 200
and the response body will be a JSON document of the form {"response": <base64-encoded RoutableMessage>}
.
Note that a 200 HTTP code does not indicate the vehicle successfully executed
a command, just that the server received a response. The RoutableMessage
may
contain an error message.
See online documentation for information on other HTTP status codes.
Although communication between clients and Tesla's servers use TLS/TCP, the communication channel between Tesla's servers and vehicles does not provide TCP transport guarantees; messages may be dropped or arrive out of order.
Clients connect to vehicles using the following identifiers:
- Service UUID:
00000211-b2d1-43f0-9b88-960cebf8b91e
- Vehicle write characteristic:
00000212-b2d1-43f0-9b88-960cebf8b91e
. Use your BLE library's "write with response" API. - Vehicle read characteristic:
00000213-b2d1-43f0-9b88-960cebf8b91e
- BLE advertisement local name:
S + <ID> + C
, where<ID>
is the lower-case hex-encoding of the first eight bytes of the SHA1 digest of the Vehicle Identification Number (VIN). For example, If the VIN is5YJS0000000000000
, then the BLE advertisement Local Name isS1a87a5a75f3df858C
.
Messages sent or received over BLE are preceded by the two-byte big-endian encoding of the message length.
Note: Due to hardware constraints, VCSEC can only reliably maintain up to three simultaneous BLE connections. These are shared by keyfobs and phone keys.
This section provides an overview of concepts handled by the protocol.
Vehicles have subsystems called domains that have different public keys and therefore require separate handshakes and session state tracking. The VCSEC domain controls locks, remote start, and trunk, among others; The Infotainment domain processes the remaining commands. VCSEC can be reached over BLE even when infotainment is asleep.
Each domain has its own clock and represents time using (epoch_id, timestamp)
pairs. The epoch_id
is a random 16-byte value generated at boot,
and the timestamp
is an integer giving the number of seconds since the start
of the epoch.
The vehicle associates each client public key with a role that determines what commands that client can authorize. The most common roles are Owner and Driver. Roles are enumerated in keys.proto.
The capabilities of a given role may change as new features are added or new use-cases arise.
An Owner can authorize all commands, including adding and removing public keys for other users.
A Driver has access to most commands but cannot manage other users' keys or otherwise configure access controls, such as changing vehicle PINs.
A Fleet Manager represents a cloud-based Owner key. In vehicles running 2023.38 or later, a Fleet Manager cannot add or remove other users' keys from the vehicle and cannot send commands over BLE. If a cloud-based service needs to manage Owner and Driver access, this should be done at the account level using Fleet API.
A Vehicle Monitor can read vehicle data, such as location information, but cannot authorize commands that change the vehicle's state.
A Charging Manager can read vehicle data and authorize commands that affect vehicle charging.
A Service key bootstraps pairing other keys and authorizes commands on behalf of service technicians. Service keys can remotely (un)lock vehicles in order to provide roadside assistance, but vehicles in their default state prevent Service keys from authorizing other commands over the Internet.
The protocol requires peers to authenticate messages in a way that binds them to associated metadata, such as the VIN. This in turn requires a canonical method of serializing the metadata so it can be used as an input to a hash function.
A metadata key/value pair is serialized using a tag-length-value encoding.
Each metadata value has an associated numeric tag defined in
Signatures.Tag. For example, the tag for a VIN is
TAG_PERSONALIZATION = 2
. So the key-value pair VIN: "abc"
would be serialized
as:
TLV(VIN: "abc") = TAG_PERSONALIZATION || LEN("abc") || "abc"
= 0x02 || 0x03 || 0x61 0x62 0x63
= 0x0203616263
Integer values are encoded as big-endian four-byte values:
TLV(COUNTER: 100) = TAG_COUNTER || LEN(uint32) || 0x00000064
= 0x05 || 0x04 || 0x00000064
= 0x050400000064
To serialize a set of metadata items, sort by tag in ascending order, serialize
each item, concatenate the results, and append a final 0xFF
byte to mark the
end of the metadata string:
SERIALIZE({COUNTER: 100, VIN: "abc"})
= TLV(VIN: "abc") || TLV(COUNTER: 100) || 0xFF
= 0x0203616263 || 0x050400000064 || 0xFF
= 0x0203616263050400000064FF
c
- Client Private KeyC = (Cx, Cy)
- Client Public key, a point on NIST-P256.v
- Vehicle Private keyV = (Vx, Vy)
- Vehicle Public key, a point on NIST-P256.s[:n]
- The first n bytes of the string sBIG_ENDIAN(m, n)
- The n-byte big-endian encoding of m, padded with 0x00 bytes.K
- 128-bit AES-GCM key shared between the client and the vehicleENCODE_PUBLIC(P)
- The encoding of a public keyP = (x, y)
as0x04 || BIG_ENDIAN(x, 32) || BIG_ENDIAN(y, 32)
; libraries refer to this as an uncompressed curve point or encoding.
This section contains example keys that will be used to generate test vectors in the remainder of the document.
*Do not use this key except to debug implementations using the test vectors included in these documents. Since the private key is published, enrolling the public key on a vehicle may result in unauthorized access.
In vehicle.key
:
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIDRO5bRmp88e6xK29QMx2y5exYNO9fS+/P2MvlXCUo1woAoGCCqGSM49
AwEHoUQDQgAEx6H0cThIaqRymXFJSHjTOxok45Vx90im4WxZVbPYd9OmqqDpVRZk
dK9dMsQQ9DmiI0E3rRuwhf1OiBPJWPEdlw==
-----END EC PRIVATE KEY-----
In vehicle.pem
:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEx6H0cThIaqRymXFJSHjTOxok45Vx
90im4WxZVbPYd9OmqqDpVRZkdK9dMsQQ9DmiI0E3rRuwhf1OiBPJWPEdlw==
-----END PUBLIC KEY-----
v = 0x344EE5B466A7CF1EEB12B6F50331DB2E5EC5834EF5F4BEFCFD8CBE55C2528D70
Vx = 0xC7A1F47138486AA4729971494878D33B1A24E39571F748A6E16C5955B3D877D3
Vy = 0xA6AAA0E955166474AF5D32C410F439A2234137AD1BB085FD4E8813C958F11D97
HEX(ENCODE_PUBLIC(V)) = 04c7a1f47138486aa4729971494878d33b1a24e39571f748a6e16c5955b3d877d3a6aaa0e955166474af5d32c410f439a2234137ad1bb085fd4e8813c958f11d97
Do not use this key except to debug implementations using the test vectors included in these documents. Since the private key is published, enrolling the public key on a vehicle may result in unauthorized access.
In client.key
:
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICU4zcKal8GcHpmmN9bPT4yXDBGLVu3h5jI+bRYsSzDboAoGCCqGSM49
AwEHoUQDQgAEsra8aMLaBmXOZWgVWUmWxiOU7di+qQX+eBp1T+aoRacUMwkC8iXp
Jp1GbgWzSZgf2p2FzCPG+0RKpztikQXcbg==
-----END EC PRIVATE KEY-----
In client.pem
:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsra8aMLaBmXOZWgVWUmWxiOU7di+
qQX+eBp1T+aoRacUMwkC8iXpJp1GbgWzSZgf2p2FzCPG+0RKpztikQXcbg==
-----END PUBLIC KEY-----
c = 0x2538CDC29A97C19C1E99A637D6CF4F8C970C118B56EDE1E6323E6D162C4B30DB
Cx = 0xB2B6BC68C2DA0665CE656815594996C62394EDD8BEA905FE781A754FE6A845A7
Cy = 0x14330902F225E9269D466E05B349981FDA9D85CC23C6FB444AA73B629105DC6E
HEX(ENCODE_PUBLIC(C)) = 04b2b6bc68c2da0665ce656815594996c62394edd8bea905fe781a754fe6a845a714330902f225e9269d466e05b349981fda9d85cc23c6fb444aa73b629105dc6e
This section assumes the client public key C is already enrolled in the vehicle. Keys are bootstrapped by using the protocol or an NFC card to authorize new keys using existing keys, with a Tesla Service key as the root of trust. See the README file in the repository root for instructions on pairing a new key.
The client sends a RoutableMessage to the vehicle Domain it wishes to send
commands to, setting the RoutableMessage.session_info_request.public_key
to
ENCODE_PUBLIC(C)
.
Example handshake request for the test client public key:
to_destination {
domain: DOMAIN_INFOTAINMENT
}
from_destination {
routing_address: 2c907bd76c640d360b3027dc7404efde
}
session_info_request {
public_key: 04b2b6bc68c2da0665ce656815594996c62394edd8bea905fe781a754fe6a845a714330902f225e9269d466e05b349981fda9d85cc23c6fb444aa73b629105dc6e
}
uuid: 1588d5a30eabc6f8fc9a951b11f6fd11
The vehicle response has the RoutableMessage.session_info
populated. The
client parses this field as a SessionInfo
message (defined in
signatures.proto).
The RoutableMessage.signature_data
contains information that can be used to
verify the integrity (but not authenticity) of the response, as discussed
later.
Example response:
to_destination {
routing_address: 2c907bd76c640d360b3027dc7404efde
}
from_destination {
domain: DOMAIN_INFOTAINMENT
}
signature_data {
session_info_tag {
tag: 996c1fe38331be138f8039c194b14db2198846ed7d8251e6749284d7b32ea002
}
}
session_info: 0806124104c7a1f47138486aa4729971494878d33b1a24e39571f748a6e16c5955b3d877d3a6aaa0e955166474af5d32c410f439a2234137ad1bb085fd4e8813c958f11d971a104c463f9cc0d3d26906e982ed224adde6255a0a0000
request_uuid: 1588d5a30eabc6f8fc9a951b11f6fd11
The session_info
field decodes as:
counter: 6
publicKey: 04c7a1f47138486aa4729971494878d33b1a24e39571f748a6e16c5955b3d877d3a6aaa0e955166474af5d32c410f439a2234137ad1bb085fd4e8813c958f11d97
epoch: 4c463f9cc0d3d26906e982ed224adde6
clock_time: 2650
The session info fields are used to authorize commands.
The client and the vehicle derive a shared 128-bit AES-GCM key K using ECDH:
S = (Sx, Sy) = ECDH(c, V) = ECDH(v, C)
K = SHA1(BIG_ENDIAN(Sx, 32))[:16]
Example: Computing K using client.key
and vehicle.pem
from above with
OpenSSL:
export K=$(openssl pkeyutl -derive -inkey client.key -peerkey vehicle.pem \
| openssl dgst -sha1 -binary \
| head -c 16 \
| xxd -p)
echo $K
1b2fce19967b79db696f909cff89ea9a
Use a mature cryptographic library such as OpenSSL to compute K. A custom implementation will likely be vulnerable to subtle attacks.
The protocol always uses the binary encoding of K; the hex representation above is only for printability.
In order to prevent a man-in-the-middle (MITM) attacker from obtaining a command that expires later than intended, the client must authenticate the session info response. The vehicle will reject relayed commands if the attacker modifies the included public key, and the client will reject a modified response if attacker leaves the public key intact.
The client first derives a session info authentication key from the shared secret K:
SESSION_INFO_KEY = HMAC-SHA256(K, "session info")
The "session info" is the literal ASCII-encoded string.
Example: Using the test vector value for K
above as hexkey
below,
export SESSION_INFO_KEY=$(\
echo -n "session info" \
| openssl dgst -sha256 -mac hmac -macopt hexkey:"$K")
echo $SESSION_INFO_KEY
fceb679ee7bca756fcd441bf238bf2f338629b41d9eb9c67be1b32c9672ce300
Next, the client [serializes the following metadata](#Metadata serialization):
TAG_SIGNATURE_TYPE
:Signatures.SIGNATURE_TYPE_HMAC
TAG_PERSONALIZATION
:VIN
TAG_CHALLENGE
: UUID from session info request message
Example: Continuing with the above example values and VIN = 5YJ30123456789ABC
, the serialized metadata is
METADATA = TLV(TAG_SIGNATURE_TYPE, Signatures.SIGNATURE_TYPE_HMAC) ||
TLV(TAG_PERSONALIZATION, "5YJ30123456789ABC") ||
TLV(TAG_CHALLENGE, 1588d5a30eabc6f8fc9a951b11f6fd11) ||
ff
= 00 01 06 || // Tag, length, value
02 11 35594a3330313233343536373839414243 || // etc.
06 10 1588d5a30eabc6f8fc9a951b11f6fd11 |\
ff
= 000106021135594a333031323334353637383941424306101588d5a30eabc6f8fc9a951b11f6fd11ff
The client computes the expected HMAC-SHA256 tag using the serialized metadata and session info.
export SESSION_INFO=0806124104c... (truncated from above)
echo "$METADATA$SESSION_INFO" | xxd -r -p | openssl dgst -sha256 -mac hmac -macopt hexkey:"$SESSION_INFO_KEY"
996c1fe38331be138f8039c194b14db2198846ed7d8251e6749284d7b32ea002
The client compares this value to the
RoutableMessage.signature_data.session_info_tag.tag
from the vehicle
response.
Warning: Always use a constant-time comparison function when validating HMAC tags. All mature cryptographic libraries will have a special function documented for this purpose.
The client must discard responses with an invalid tag.
After completing the above handshake process, the client has:
- The vehicle time, expressed as an
(epoch, timestamp)
pair. - The anti-replay counter
- The shared key symmetric key
K
.
The client first generates a command protobuf P
, which generally encodes
either a VCSEC.UnsignedMessage
or a CarServer.Action
message. To determine
the required message type and contents for a given command, follow source code
links from the package
documentation.
The client serializes the following metadata values into a string M
:
Value | Tag | Description |
---|---|---|
Signature type | Signatures.TAG_SIGNATURE_TYPE |
Either Signatures.SIGNATURE_TYPE_HMAC_PERSONALIZED or Signatures.SIGNATURE_TYPE_AES_GCM_PERSONALIZED . See below. |
Domain | Signatures.TAG_DOMAIN |
Typically UniversalMesasge.DOMAIN_VEHICLE_SECURITY or UniversalMessage.DOMAIN_INFOTAINMENT |
VIN | Signatures.TAG_PERSONALIZATION |
17-character vehicle identification number |
Epoch | Signatures.TAG_EPOCH |
Copied from session_info.epoch |
Expiration time | Signatures.TAG_EXPIRES_AT |
Time in seconds according to domain's clock |
Counter | Signatures.TAG_COUNTER |
Monotonic counter, initially session_info.counter |
Setting the expiration time requires the client to track the difference between the domain's clock and the local clock. Note that each domain has its own clock.
The counter must increase with each command within the same epoch. Infotainment tracks a sliding window of valid counters to allow for out-of-order message arrival, while VCSEC requires that messages arrive in counter order.
Vehicles support two authentication methods: HMAC-SHA256 and AES-GCM. Messages sent with HMAC-SHA256 authentication are sent in plaintext. This allows the Fleet API backend to (1) reject commands based on OAuth scopes and (2) drop deprecated messages sent by VCSEC that would require a more complex client state machine to disambiguate. Commands sent with AES-GCM are encrypted; Fleet API blocks these commands because it cannot enforce OAuth scopes.
The official Golang package uses AES-GCM over BLE and HMAC-SHA256 over Fleet API.
To add HMAC-SHA256 authentication:
- Derive an HMAC-SHA256 key
K' = HMAC-SHA256(K, "authenticated command")
. The "authenticated command" is a string literal. - Construct a RoutableMessage as described above.
- Set the
RoutableMessge.payload.protobuf_message_as_bytes
toP
. - Set the
RoutableMessage.signature_data.signer_identity.public_key
toENCODE_PUBLIC(C)
(see Notation). - Compute the HMAC tag
tag = HMAC-SHA256(K', M || P)
. - Populate
RoutableMessage.signature_data.HMAC_PersonalizedData
with the metadata values and authenticationtag
.
To encrypt the plaintext protobuf P
using AES-GCM:
- Set the 128-bit encryption key to
K
- Use a random 12-byte nonce (sometimes called an initialization vector, or IV). Some libraries may choose one for you.
- Set the Associated Authenticated Data (AAD) field to
SHA256(M)
- Encrypt the
P
with the above parameters to obtain a ciphertextx
and a message authenticationtag
. - Construct a RoutableMessage as described above.
- Set the
RoutableMessge.payload.protobuf_message_as_bytes
tox
. - Set the
RoutableMessage.signature_data.signer_identity.public_key
toENCODE_PUBLIC(C)
(see Notation). - Populate
RoutableMessage.signature_data.AES_GCM_Personalized_Signature_Data
with the metadata values, authenticationtag
, and nonce.
Example: We'll send an "Turn HVAC on" command using the above example
with VIN = 5YJ30123456789ABC
and the hex representation of the shared key K = 1b2fce19967b79db696f909cff89ea9a
.
First we find the protobuf encoding for the command. Normally this is done
using the language-specific bindings created by protoc
from the *.proto
files, but for purposes of illustration we'll do it from the command line:
echo 'vehicleAction {
hvacAutoAction {
power_on: true
}
}' | protoc --encode=CarServer.Action -I protobuf protobuf/*.proto | xxd -p
Output: 120452020801
.
Next, we construct the serialized metadata string from values in the table below. The metadata string is used as the associated authenticated data (AAD) field of AES-GCM.
Tag | Value | Encoding |
---|---|---|
Signatures.TAG_SIGNATURE_TYPE |
Signatures.SIGNATURE_TYPE_AES_GCM_PERSONALIZED = 0x05 |
00 01 05 |
Signatures.TAG_DOMAIN |
UniversalMessage.DOMAIN_INFOTAINMENT = 0x03 |
01 01 03 |
Signatures.TAG_PERSONALIZATION |
5YJ30123456789ABC |
02 11 35594a3330313233343536373839414243 |
Signatures.TAG_EPOCH |
session_info.epoch |
03 10 4c463f9cc0d3d26906e982ed224adde6 |
Signatures.TAG_EXPIRES_AT |
t=2655 seconds |
04 04 00000a5f |
Signatures.TAG_COUNTER |
session_info.counter |
05 04 00000007 |
Concatenating the above encoded values together with the terminal 0xff
byte gives:
000105010103021135594a333031323334353637383941424303104c463f9cc0d3d26906e982ed224adde6040400000a5f050400000007ff
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives import hashes
plaintext = bytes.fromhex("120452020801")
metadata = bytes.fromhex("000105010103021135594a333031323334353637383941424303104c463f9cc0d3d26906e982ed224adde6040400000a5f050400000007ff")
aad = hashes.Hash(hashes.SHA256())
aad.update(metadata)
key = bytes.fromhex("1b2fce19967b79db696f909cff89ea9a")
aesgcm = AESGCM(key)
nonce = os.urandom(12)
ct = aesgcm.encrypt(nonce, plaintext, aad.finalize())
print(f"Nonce: {nonce.hex()}, Ciphertext: {ct[:-16].hex()}, Tag: {ct[-16:].hex()}")
Output (will be randomized each time by the nonce):
Nonce: dbf79447fa156674dae1caed, Ciphertext: 38038e8c0f2e, Tag: 8e128da165f162f4d7d2c8da866cf82a
Copying the above fields into a RoutableMessage protobuf yields:
to_destination {
domain: DOMAIN_INFOTAINMENT
}
from_destination {
routing_address: 2c907bd76c640d360b3027dc7404efde
}
protobuf_message_as_bytes: 38038e8c0f2e
signature_data {
signer_identity {
public_key: 04b2b6bc68c2da0665ce656815594996c62394edd8bea905fe781a754fe6a845a714330902f225e9269d466e05b349981fda9d85cc23c6fb444aa73b629105dc6e
}
AES_GCM_Personalized_data {
epoch: 4c463f9cc0d3d26906e982ed224adde6
nonce: dbf79447fa156674dae1caed
counter: 7
expires_at: 2655
tag: 8e128da165f162f4d7d2c8da866cf82a
}
}
uuid: 58406580528b6a5301391800b4fe9b99
The sender may omit the epoch
field from the protobuf, but including the
epoch allows the vehicle to return a more specific error message if there is a
mismatch.
If a client is not running continuously, it should cache session state to disk, along with the time difference between the local clock and the vehicle clock. Loading the session from cache removes the need to send session info requests, which reduces the latency of the first command and, when using Fleet API, reduces the number of Fleet API requests made by the client. If the session state is no longer valid, then the client can automatically recover as described below. The recovery mechanism is no more expensive than performing the handshake in the first place, so clients never incur a penalty by optimistically assuming a cache is valid.
The vehicle may include up-to-date session state in an error message in cases where an authentication error could be attributed to a synchronization fault. For example, if the infotainment system reboots, then the vehicle and the client may not be using the same epoch.
The client MUST discard the session information if any of the following are true:
- The client did not use the request UUID in the last several seconds.
- The session info HMAC tag is incorrect.
- The clock time is earlier than the clock time in a previously authenticated session info message with the same epoch.
The client MUST update its session state if none of the above are true. When updating its session state, the client MUST NOT rollback its anti-replay counter unless the epoch changes. These are not security requirements per se, since the vehicle bears responsibility for rejecting replayed messages; however, this behavior allows clients to more reliably handle poor connectivity and out-of-order message delivery.
Note that if client transmits a command and does not receive a response back from the vehicle, the vehicle may still have received and executed the command. This is especially likely if the vehicle or client is on an unreliable network.
Vehicle responses also RoutableMessages. If a protocol-layer error occurred,
then the vehicle sets the
RoutableMessage.signedMessageStatus.signed_message_fault
field.
Error codes and their remediation are summarized in
universal_message.proto.
See comments in the MessageFault_E
definition.
If a reply comes from the Infotainment domain, the client should parse the
protobuf_message_as_bytes
field as a CarServer.Response
, defined in
car_server.proto. An application-layer status code
is set in Response.actionStatus
.
If a reply comes from the Vehicle Security (VCSEC) domain, the client should
parse the protobuf_message_as_bytes
field as a VCSEC.FromVCSECMessage
,
defined in vcsec.proto. VCSEC emits up to three
responses to a given request. Clients that use Fleet API only receive the
final response; clients that use BLE will need to implement special logic to
determine what responses are final, as described below.
If the client sent a request to pair a new key using the NFC card, then VCSEC
sends FromVSCEC.commandStatus.operationStatus = OPERATIONSTATUS_WAIT
to
indicate it is waiting for the NFC card tap.
For other requests, OPERATIONSTATUS_WAIT
indicates VSCEC is busy with some
other request and the client should retry after a short delay.
Other values of FromVCSEC.commandStatus.operationStatus
can be ignored. In
particular, a value of OPERATIONSTATUS_ERROR
is sent only for the benefit of
legacy clients. New clients should discard the message and wait for a more
specific error code in a subsequent message.
If the client sent a whitelist operation request (e.g., add or remove another
key), a message is terminal if
FromVCSECMessage.commandStatus.whitelistOperationStatus
is populated. The
client should drop empty messages.
For non-whitelist operations, an empty message indicates success and
FromVCSECMessage.commandStatus.nominalError
indicates an error.