Skip to content

Commit 7fd283a

Browse files
authored
Merge pull request #54 from yoziru/dev
dev
2 parents c987807 + 6c37746 commit 7fd283a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+3969
-2634
lines changed

AGENTS.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ cd build && ctest -j 2>&1 | grep -E "(FAILED.*\]|^[0-9]+% tests|expected equalit
2222
- **Protocol**: Protobuf messages (nanopb), AES-GCM encryption (mbedtls)
2323
- **Dependencies**: nanopb, mbedtls, googletest
2424
- **Generated Code**: `generated/src/*.pb.c`, `generated/include/*.pb.h`
25+
- **Generated Code**: Avoid manual edits; regenerate from `proto/` when needed
26+
27+
## Protocol Notes
28+
- **Session info verification**: HMAC tag is required; request UUID must match the last request per domain.
29+
- **Response AAD**: Use response flags when building AAD (not a hardcoded encrypt-response flag).
30+
- **VCSEC responses**: Treat plaintext unless `AES_GCM_Response_data` is present.
2531

2632
## Code Style
2733
- **Namespace**: `TeslaBLE`
@@ -32,7 +38,6 @@ cd build && ctest -j 2>&1 | grep -E "(FAILED.*\]|^[0-9]+% tests|expected equalit
3238
- `_` suffix for private/protected methods (`cleanup_`, `initialize_peers_`)
3339
- `UPPER_CASE` for static constants
3440
- **Types**: Use `pb_byte_t`, `pb_size_t` for protobuf; `std::array`, `std::unique_ptr`
35-
- **Types**: Use `pb_byte_t`, `pb_size_t` for protobuf; `std::array`, `std::unique_ptr`
3641
- **Error Handling**: Return `TeslaBLE_Status_E` enum (0=OK); use `LOG_ERROR`/`LOG_DEBUG` macros
3742
- **Formatting**: No comments unless requested; 4-space indent; braces on same line
3843
- **Protobuf**: Use `*_init_default`/`*_init_zero`; encode with `pb_encode_fields`, decode with `pb_decode`

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ set(TESLABLE_SRCS
88
"src/crypto_context.cpp"
99
"src/errors.cpp"
1010
"src/message_builders.cpp"
11+
"src/message_processor.cpp"
1112
"src/peer.cpp"
1213
"src/tb_logging.cpp"
1314
"src/tb_utils.cpp"
@@ -35,6 +36,7 @@ set(TESLABLE_PUBLIC_HEADERS
3536
include/client.h
3637
include/adapters.h
3738
include/errors.h
39+
include/message_processor.h
3840
include/peer.h
3941
include/tb_logging.h
4042
include/tb_utils.h

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ ctest --output-on-failure --verbose
9696
./tests/test_message_building
9797
./tests/test_message_parsing
9898
./tests/test_session_management
99+
./tests/test_protocol_compliance
99100
./tests/test_utils
100101

101102
# Run the complete test suite
@@ -191,9 +192,25 @@ The test suite is organized into several categories:
191192
- **`test_message_parsing.cpp`**: Parsing of received messages
192193
- **`test_session_management.cpp`**: Session handling and peer management
193194
- **`test_utils.cpp`**: Utility functions and helper methods
195+
- **`test_vehicle.cpp`**: Vehicle state management and command processing
196+
- **`test_exponential_backoff.cpp`**: Exponential backoff retry logic
194197

195198
Each test file contains comprehensive unit tests covering both success and failure scenarios, edge cases, and parameter validation.
196199

200+
### State Architecture
201+
202+
The library uses a unified state pattern for command processing with the following states:
203+
204+
- **`IDLE`**: Initial state for new commands
205+
- **`AUTHENTICATING`**: Unified authentication initiation (replaces legacy domain-specific states)
206+
- **`AUTH_RESPONSE_WAITING`**: Unified authentication response waiting
207+
- **`READY`**: Command ready to be sent
208+
- **`WAITING_FOR_RESPONSE`**: Waiting for command response
209+
- **`COMPLETED`**: Command completed successfully
210+
- **`FAILED`**: Command failed
211+
212+
This architecture provides a clean, maintainable approach to command lifecycle management with exponential backoff for retries.
213+
197214
### Dependencies
198215

199216
- [nanopb](https://github.com/nanopb/nanopb)

examples/simple/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ project(simple)
33

44
set(CMAKE_CXX_STANDARD 23)
55
add_subdirectory(../../ TeslaBLE)
6-
add_executable(simple main.cpp log.cpp)
6+
add_executable(simple main.cpp)
77
target_link_libraries(simple PRIVATE TeslaBLE)

examples/simple/main.cpp

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
#include <client.h>
22
#include <cstdio>
3-
#include <cstring>
43
#include <cinttypes>
54
#include <pb_decode.h>
65
#include <pb_encode.h>
76
#include <signatures.pb.h>
8-
#include <string.h>
7+
#include <cstring>
98
#include <universal_message.pb.h>
109
#include <vcsec.pb.h>
1110
#include <sstream>
1211
#include <iomanip>
1312

1413
#include "defs.h"
1514
#include "errors.h"
16-
#include "tb_utils.h"
17-
#include "log.cpp"
15+
#include "log.h"
1816

1917
// mock data from PROTOCOL.md examples
20-
static const char *MOCK_VIN = "5YJ30123456789ABC";
18+
static constexpr char MOCK_VIN[] = "5YJ30123456789ABC";
2119
static const unsigned char MOCK_PRIVATE_KEY[227] =
2220
"-----BEGIN EC PRIVATE "
2321
"KEY-----\nMHcCAQEEILRjIS9VEyG+0K71a2T/"
@@ -40,16 +38,16 @@ int main() {
4038
* this loads an existing private key and generates the public key
4139
*/
4240
LOG_INFO("Loading private key");
43-
TeslaBLE::TeslaBLEStatus status = client.load_private_key(MOCK_PRIVATE_KEY, sizeof MOCK_PRIVATE_KEY);
44-
// int status = client.createPrivateKey();
45-
if (status != TeslaBLE::TeslaBLEStatus::OK) {
41+
int status = client.load_private_key(MOCK_PRIVATE_KEY, sizeof MOCK_PRIVATE_KEY);
42+
// int status = client.create_private_key();
43+
if (status != 0) {
4644
LOG_ERROR("Failed create private key");
4745
}
4846

4947
unsigned char private_key_buffer[sizeof MOCK_PRIVATE_KEY + 1];
5048
size_t private_key_length;
5149
status = client.get_private_key(private_key_buffer, sizeof(private_key_buffer), &private_key_length);
52-
if (status != TeslaBLE::TeslaBLEStatus::OK) {
50+
if (status != 0) {
5351
LOG_ERROR("Failed to get private key");
5452
}
5553
LOG_DEBUG("Private key length: %d", private_key_length);
@@ -60,13 +58,13 @@ int main() {
6058
// support for wake command added to CHARGING_MANAGER_ROLE in 2024.20.x (not sure?)
6159
// https://github.com/teslamotors/vehicle-command/issues/232#issuecomment-2181503570
6260
LOG_INFO("Building whitelist message for CHARGING MANAGER");
63-
TeslaBLE::TeslaBLEStatus return_code =
61+
int return_code =
6462
client.build_white_list_message(Keys_Role_ROLE_CHARGING_MANAGER, VCSEC_KeyFormFactor_KEY_FORM_FACTOR_CLOUD_KEY,
6563
whitelist_message_buffer, &whitelist_message_length);
6664

67-
if (return_code != TeslaBLE::TeslaBLEStatus::OK) {
68-
LOG_ERROR("Failed to build whitelist message: %s",
69-
TeslaBLE::teslable_status_to_string(static_cast<TeslaBLE::TeslaBLEStatus>(return_code)));
65+
if (return_code != 0) {
66+
auto status = static_cast<TeslaBLE::TeslaBLE_Status_E>(return_code);
67+
LOG_ERROR("Failed to build whitelist message: %s", TeslaBLE::teslable_status_to_string(status));
7068
return -1;
7169
}
7270
LOG_DEBUG("Whitelist message length: %d", whitelist_message_length);
@@ -91,26 +89,26 @@ int main() {
9189
UniversalMessage_RoutableMessage received_message_vcsec = UniversalMessage_RoutableMessage_init_default;
9290
return_code =
9391
client.parse_universal_message(received_bytes_vcsec, sizeof(received_bytes_vcsec), &received_message_vcsec);
94-
if (return_code != TeslaBLE::TeslaBLEStatus::OK) {
95-
LOG_ERROR("Failed to parse received message VSSE: %s",
96-
TeslaBLE::teslable_status_to_string(static_cast<TeslaBLE::TeslaBLEStatus>(return_code)));
92+
if (return_code != 0) {
93+
auto status = static_cast<TeslaBLE::TeslaBLE_Status_E>(return_code);
94+
LOG_ERROR("Failed to parse received message VSSE: %s", TeslaBLE::teslable_status_to_string(status));
9795
return -1;
9896
}
9997
log_routable_message(&received_message_vcsec);
10098

10199
Signatures_SessionInfo session_info_vcsec = Signatures_SessionInfo_init_default;
102100
return_code = client.parse_payload_session_info(&received_message_vcsec.payload.session_info, &session_info_vcsec);
103-
if (return_code != TeslaBLE::TeslaBLEStatus::OK) {
101+
if (return_code != 0) {
104102
LOG_ERROR("Failed to parse session info VSSEC");
105103
return -1;
106104
}
107105
log_session_info(&session_info_vcsec);
108106

109107
UniversalMessage_Domain domain = UniversalMessage_Domain_DOMAIN_VEHICLE_SECURITY;
110-
auto session = client.get_peer(domain);
108+
auto *session = client.get_peer(domain);
111109

112110
return_code = session->update_session(&session_info_vcsec);
113-
if (return_code != TeslaBLE::TeslaBLEStatus::OK) {
111+
if (return_code != 0) {
114112
LOG_ERROR("Failed to update session VSSEC");
115113
return -1;
116114
}
@@ -134,7 +132,7 @@ int main() {
134132
size_t action_message_buffer_length = 0;
135133
return_code = client.build_vcsec_action_message(VCSEC_RKEAction_E_RKE_ACTION_WAKE_VEHICLE, action_message_buffer,
136134
&action_message_buffer_length);
137-
if (return_code != TeslaBLE::TeslaBLEStatus::OK) {
135+
if (return_code != 0) {
138136
LOG_ERROR("Failed to build action message ");
139137
return -1;
140138
}
@@ -148,7 +146,7 @@ int main() {
148146
return_code =
149147
client.build_vcsec_information_request_message(VCSEC_InformationRequestType_INFORMATION_REQUEST_TYPE_GET_STATUS,
150148
info_request_status_buffer, &info_request_status_length);
151-
if (return_code != TeslaBLE::TeslaBLEStatus::OK) {
149+
if (return_code != 0) {
152150
LOG_ERROR("Failed to build action message ");
153151
return -1;
154152
}
@@ -175,7 +173,7 @@ int main() {
175173
UniversalMessage_RoutableMessage received_message = UniversalMessage_RoutableMessage_init_default;
176174
return_code = client.parse_universal_message(received_bytes_infotainment, sizeof(received_bytes_infotainment),
177175
&received_message);
178-
if (return_code != TeslaBLE::TeslaBLEStatus::OK) {
176+
if (return_code != 0) {
179177
LOG_ERROR("Failed to parse received message INFOTAINMENT");
180178
return -1;
181179
}
@@ -184,15 +182,15 @@ int main() {
184182
LOG_INFO("Parsing session info INFOTAINMENT");
185183
Signatures_SessionInfo session_info = Signatures_SessionInfo_init_default;
186184
return_code = client.parse_payload_session_info(&received_message.payload.session_info, &session_info);
187-
if (return_code != TeslaBLE::TeslaBLEStatus::OK) {
188-
printf("Failed to parse session info INFOTAINMENT");
185+
if (return_code != 0) {
186+
LOG_ERROR("Failed to parse session info INFOTAINMENT");
189187
return -1;
190188
}
191189
log_session_info(&session_info);
192190

193191
session = client.get_peer(UniversalMessage_Domain_DOMAIN_INFOTAINMENT);
194192
return_code = session->update_session(&session_info);
195-
if (return_code != TeslaBLE::TeslaBLEStatus::OK) {
193+
if (return_code != 0) {
196194
LOG_ERROR("Failed to update session INFOTAINMENT");
197195
return -1;
198196
}
@@ -223,7 +221,7 @@ int main() {
223221
return_code = client.build_car_server_vehicle_action_message(
224222
charging_amps_message_buffer, &charging_amps_message_length, CarServer_VehicleAction_setChargingAmpsAction_tag,
225223
&charging_amps_action);
226-
if (return_code != TeslaBLE::TeslaBLEStatus::OK) {
224+
if (return_code != 0) {
227225
LOG_ERROR("Failed to build charging amps message");
228226
return -1;
229227
}
@@ -239,7 +237,7 @@ int main() {
239237
return_code = client.build_car_server_vehicle_action_message(
240238
charging_limit_message_buffer, &charging_limit_message_length, CarServer_VehicleAction_chargingSetLimitAction_tag,
241239
&charging_limit_action);
242-
if (return_code != TeslaBLE::TeslaBLEStatus::OK) {
240+
if (return_code != 0) {
243241
LOG_ERROR("Failed to build charging limit message");
244242
return -1;
245243
}
@@ -255,7 +253,7 @@ int main() {
255253
hvac_action.manual_override = false;
256254
return_code = client.build_car_server_vehicle_action_message(
257255
hvac_on_message_buffer, &hvac_on_message_length, CarServer_VehicleAction_hvacAutoAction_tag, &hvac_action);
258-
if (return_code != TeslaBLE::TeslaBLEStatus::OK) {
256+
if (return_code != 0) {
259257
LOG_ERROR("Failed to build HVAC message");
260258
return -1;
261259
}
@@ -267,8 +265,8 @@ int main() {
267265
size_t get_data_message_length;
268266
return_code = client.build_car_server_get_vehicle_data_message(get_data_message_buffer, &get_data_message_length,
269267
CarServer_GetVehicleData_getChargeState_tag);
270-
if (return_code != TeslaBLE::TeslaBLEStatus::OK) {
271-
LOG_ERROR("Failed to buildCarServerGetVehicleDataMessage");
268+
if (return_code != 0) {
269+
LOG_ERROR("Failed to build_car_server_get_vehicle_data_message");
272270
return -1;
273271
}
274272
LOG_DEBUG("HVAC length: %d", get_data_message_length);

0 commit comments

Comments
 (0)