0% found this document useful (0 votes)
2 views10 pages

API Client.c

Uploaded by

Raghu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views10 pages

API Client.c

Uploaded by

Raghu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 10

Code Review Report: api_client.

c
1. General: Inconsistent TAG Definition
Problem:
The TAG macro is defined globally as FILE_TAG, then undefined and redefined
locally within each function. This practice is unusual and can make debugging
less clear if FILE_TAG is intended for file-wide logging and specific functions
need more granular tags.
Suggestion:
If each function requires a unique tag, declare a static const char* TAG =
"function_name"; at the top of each function. If FILE_TAG is sufficient for the
entire file, remove the local #undef TAG and #define TAG directives.
Code Snippet (Example for function-specific TAG):
C
// Remove: #define FILE_TAG "api_client" at the top if you go this route

// Inside api_client_init
static const char* TAG = "api_client.init"; // Define TAG locally for the function
// ... rest of the function ...

// Inside api_client_get_remote_config
static const char* TAG = "api_client.get_config"; // Define TAG locally for the function
// ... rest of the function ...
2. _http_event_handler: Advanced TLS Error Handling
Problem:
The HTTP_EVENT_DISCONNECTED block attempts to get and clear the last TLS
error using evt->data cast to esp_tls_error_handle_t. While evt->data might
contain a TLS error handle in some disconnection scenarios, this isn't always
guaranteed or the most robust way to get TLS errors, as the data field's
meaning can vary by event. Relying on this without clearer documentation or
understanding of evt->data for all disconnection types could be brittle.
Suggestion:
For most applications, the error returned by esp_http_client_perform or
esp_http_client_read is sufficient. If detailed TLS error debugging is required,
ensure that the evt->data field at HTTP_EVENT_DISCONNECTED reliably points
to a valid TLS error handle. Otherwise, simplify the logging.
Code Snippet (Simplified HTTP_EVENT_DISCONNECTED handler if deep
TLS error inspection is not critical there):
C
// Inside _http_event_handler
case HTTP_EVENT_DISCONNECTED:
ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
// Removed specific esp_tls_get_and_clear_last_error call here
// as primary error checking occurs at esp_http_client_perform/read calls.
break;
3. api_client_init: Total Request Timeout Consideration
Problem:
The timeout_ms in esp_http_client_config_t is set to
CONFIG_ESP_HTTP_CLIENT_EVENT_POST_TIMEOUT. This parameter defines the
timeout for individual read/write operations or connection attempts. It does not
set a total timeout for the entire HTTP request operation (e.g., from connection
to final response).
Suggestion:
If a total timeout for the entire request is desired, use
esp_http_client_set_timeout_ms() after esp_http_client_init() to specify it.
Ensure CONFIG_ESP_HTTP_CLIENT_EVENT_POST_TIMEOUT is appropriate for
per-operation timeouts.
Code Snippet (Example of setting total timeout):
C
// Inside api_client_init
client = esp_http_client_init(&config);
if (client == NULL) {
ESP_LOGE(TAG, "Failed to initialize HTTP client");
return ESP_FAIL;
}

// Set a total request timeout if different from individual operation timeout


// For example, 30 seconds for the entire request, regardless of chunking.
esp_http_client_set_timeout_ms(client, 30000); // 30 seconds total timeout
4. url_encode_device_id: Limited Encoding Scope
Problem:
The url_encode_device_id function only handles the ':' character by replacing it
with "%3A". If the device_id (or any other string passed to this function in the
future) could contain other characters that require URL encoding (e.g.,
spaces, /, ?, &, etc.), this function is insufficient and could lead to malformed
URLs.
Suggestion:
For MAC addresses, this is acceptable. However, for a more general url_encode
utility, it should handle all characters that are not unreserved (alphanumeric, -,
_, ., ~). If a general URL encoding utility is needed, a more comprehensive
implementation or a dedicated library function would be required. For this
specific use case, it's fine as long as device_id is strictly a MAC address.
Code Snippet (No direct change needed for MAC address specific encoding, but
be aware of limitation):
No code change, but a design consideration.
5. api_client_post_sensor_data: Robust File and HTTP Connection State
Management
Problem:
The api_client_post_sensor_data function has several subtle issues related to
resource management and state tracking, particularly with file handles and
HTTP connection state.
 File Truncation Logic: The logic for handling file_size <= 0 could be
cleaner. Using fopen("w") always truncates, even if the file is already
empty.
 Missing State Tracking: After fclose(file) and before the second
fopen("w") for truncation, the file_opened flag is set to false. If the second
fopen("w") fails, file becomes NULL, and file_opened remains false,
preventing the cleanup block from attempting fclose again, which is
technically safe but could lead to confusion about resource state.
 Redundant response_buffer: The response_buffer is allocated but not
used for receiving the POST response body in this specific function (unlike
get_remote_config which uses the event handler for response collection).
Error responses are read separately into error_buf.
Suggestion:
Introduce clear boolean flags to track the successful opening of both the file for
reading and the HTTP connection. Initialize all heap-allocated pointers to NULL
to ensure free(NULL) safety. Re-evaluate response_buffer necessity.
Code Snippet (Improvements for api_client_post_sensor_data):
Capi_client_post_sensor_data
esp_err_t api_client_post_sensor_data() {
#undef TAG
#define TAG FILE_TAG ".api_client_post_sensor_data"
// ... (initial checks) ...

esp_err_t err = ESP_FAIL;


FILE* file = NULL;
long file_size = 0;
char* url = NULL; // Initialize to NULL
char* encoded_device_id = NULL; // Initialize to NULL
char* read_buffer = NULL; // Initialize to NULL
// char* response_buffer = NULL; // Not used in this POST function's response reading
char* error_buf = NULL; // Initialize to NULL

int http_status = 0;
bool file_opened_for_read = false; // Track if data file was opened for reading
bool http_connection_opened = false; // Track if HTTP connection was successfully
opened
// --- Get File Size ---
struct stat st;
if (stat(SENSOR_DATA_FILE, &st) == 0) {
file_size = st.st_size;
} else {
if (errno == ENOENT) {
ESP_LOGI(TAG, "Sensor data file '%s' not found. Nothing to send.",
SENSOR_DATA_FILE);
return ESP_OK; // No file, no data to send, not an error.
} else {
ESP_LOGE(TAG, "Failed to get status for file '%s'. errno: %d (%s)",
SENSOR_DATA_FILE, errno, strerror(errno));
return ESP_FAIL;
}
}

if (file_size <= 0) {
ESP_LOGI(TAG, "File '%s' is empty or zero size. Ensuring it exists and is empty.",
SENSOR_DATA_FILE);
FILE* temp_file_for_truncate = fopen(SENSOR_DATA_FILE, "w"); // Use temporary
handle for truncation
if (temp_file_for_truncate) {
fclose(temp_file_for_truncate);
ESP_LOGI(TAG, "Successfully ensured file '%s' is empty.", SENSOR_DATA_FILE);
} else {
ESP_LOGW(TAG, "Could not open file '%s' for truncation/creation.",
SENSOR_DATA_FILE);
}
return ESP_OK;
}
ESP_LOGI(TAG, "File '%s' size: %ld bytes.", SENSOR_DATA_FILE, file_size);

// --- Allocate heap buffers ---


url = malloc(url_buf_size);
encoded_device_id = malloc(encoded_id_buf_size);
read_buffer = malloc(FILE_READ_CHUNK_SIZE);
// response_buffer allocation removed as it's not used in this function for response
collection.

if (!url || !encoded_device_id || !read_buffer) {


ESP_LOGE(TAG, "Failed to allocate heap memory for buffers.");
err = ESP_ERR_NO_MEM;
goto cleanup;
}

// --- Open the file for reading ---


file = fopen(SENSOR_DATA_FILE, "rb");
if (!file) {
ESP_LOGE(TAG, "Failed to open file '%s' for reading. errno: %d (%s)",
SENSOR_DATA_FILE, errno, strerror(errno));
err = ESP_ERR_NOT_FOUND;
goto cleanup;
}
file_opened_for_read = true; // Mark as successfully opened for reading
// ... (Prepare URL and Configure HTTP Client for POST - no change needed here) ...

// --- Open HTTP connection and set Content-Length ---


err = esp_http_client_open(client, file_size);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
goto cleanup;
}
http_connection_opened = true; // Mark HTTP connection as successfully opened

// ... (Stream file content - no change needed here) ...

// --- Check status and truncate file on success ---


if (http_status >= 200 && http_status < 300) {
ESP_LOGI(TAG, "Sensor data posted successfully (HTTP %d).", http_status);
err = ESP_OK; // Overall success for the POST operation

// Close read handle first, then attempt to truncate


if (file_opened_for_read && file != NULL) {
fclose(file);
file = NULL; // Set to NULL immediately after closing
file_opened_for_read = false; // Reset flag
}

// Reopen in write mode ("w") to truncate the file


file = fopen(SENSOR_DATA_FILE, "w");
if (file == NULL) {
ESP_LOGE(TAG, "Failed to open file '%s' for truncation: %s",
SENSOR_DATA_FILE, strerror(errno));
// Log the truncation error, but the POST itself was successful (err remains
ESP_OK)
} else {
fclose(file);
file = NULL; // Set to NULL immediately after closing truncated file
ESP_LOGI(TAG, "Successfully truncated file '%s'.", SENSOR_DATA_FILE);
}
} else {
// ... (Error handling for HTTP status and error_buf - mostly fine) ...
free(error_buf); // Ensure error_buf is freed here
error_buf = NULL;
err = ESP_FAIL; // Set overall function failure for non-2xx status
}

cleanup:
// Free all allocated heap buffers. Always call free, as free(NULL) is safe.
free(url); url = NULL;
free(encoded_device_id); encoded_device_id = NULL;
free(read_buffer); read_buffer = NULL;
// free(response_buffer); response_buffer = NULL; // Removed as not used here.

// Close file if it was opened for reading and still holds a valid pointer
if (file_opened_for_read && file != NULL) {
fclose(file);
}
// Close HTTP connection if it was successfully opened
if (http_connection_opened) {
esp_http_client_close(client);
}
// Clean up headers and user data
esp_http_client_delete_header(client, "Content-Type");
// esp_http_client_delete_header(client, "Content-Length"); // Usually redundant
esp_http_client_set_user_data(client, NULL); // Crucial to clear if it was set for this request

if (err == ESP_OK) {
ESP_LOGI(TAG, "Post sensor data finished successfully.");
} else {
ESP_LOGE(TAG, "Post sensor data failed (err=%d). File not truncated if failure
occurred before truncation.", err);
}
return err;
}

Other improvements

1. Improved Memory Management (RAII-style cleanup)

// Helper macro for cleanup


#define CLEANUP_AND_RETURN(cleanup_code, ret_val) \
do { cleanup_code; return ret_val; } while(0)

// Usage in api_client_get_remote_config()
esp_err_t api_client_get_remote_config(remote_config_t* remote_config) {
char* response_buffer = NULL;
cJSON* outer_json = NULL;
char* data_json_string = NULL;

// Allocations
response_buffer = malloc(HTTP_READ_BUFFER_SIZE);
if (!response_buffer) {
CLEANUP_AND_RETURN((ESP_LOGE(TAG, "Alloc failed")), ESP_ERR_NO_MEM);
}

// ... request logic ...

outer_json = cJSON_Parse(response_buffer);
if (!outer_json) {
CLEANUP_AND_RETURN((
free(response_buffer),
ESP_FAIL
);
}

// ... parsing logic ...


cleanup:
free(response_buffer);
cJSON_Delete(outer_json);
free(data_json_string);
return err;
}

---

2. Enhanced HTTP Client with Retry Logic

esp_err_t perform_http_request_with_retry(esp_http_client_handle_t client,


int max_retries,
int retry_delay_ms) {
esp_err_t err;
int retry_count = 0;

while (retry_count < max_retries) {


err = esp_http_client_perform(client);

if (err == ESP_OK) {
int status = esp_http_client_get_status_code(client);
if (status >= 200 && status < 300) {
return ESP_OK;
}
}

ESP_LOGW(TAG, "Request failed (attempt %d/%d): %s",


retry_count+1, max_retries, esp_err_to_name(err));

if (++retry_count < max_retries) {


vTaskDelay(pdMS_TO_TICKS(retry_delay_ms * (1 << retry_count))); // Exponential
backoff
esp_http_client_close(client); // Ensure clean connection for retry
}
}

return err;
}

---

3. Atomic File Operations with Locking

esp_err_t truncate_file_atomic(const char* filename) {


const char* temp_filename = "/storage/temp_truncate";
FILE* temp_file = fopen(temp_filename, "w");
if (!temp_file) {
ESP_LOGE(TAG, "Failed to create temp file");
return ESP_FAIL;
}
fclose(temp_file);

if (rename(temp_filename, filename) != 0) {
ESP_LOGE(TAG, "Failed to rename temp file");
unlink(temp_filename);
return ESP_FAIL;
}

return ESP_OK;
}

---

4. Proper URL Encoding (Full Implementation)

static esp_err_t url_encode_full(const char* input, char* output, size_t output_size) {


static const char hex[] = "0123456789ABCDEF";
size_t i, j;

if (!input || !output || output_size == 0) {


return ESP_ERR_INVALID_ARG;
}

for (i = 0, j = 0; input[i] && j < output_size - 1; i++) {


if (isalnum(input[i]) || input[i] == '-' || input[i] == '_' ||
input[i] == '.' || input[i] == '~') {
output[j++] = input[i];
} else {
if (j + 3 >= output_size) {
output[j] = '\0';
return ESP_ERR_INVALID_SIZE;
}
output[j++] = '%';
output[j++] = hex[(input[i] >> 4) & 0xF];
output[j++] = hex[input[i] & 0xF];
}
}

output[j] = '\0';
return ESP_OK;
}

---

5. Secure Connection Setup


esp_err_t configure_secure_connection(esp_http_client_handle_t client) {
esp_http_client_config_t config = {
.transport_type = HTTP_TRANSPORT_OVER_SSL,
.crt_bundle_attach = esp_crt_bundle_attach,
.cert_pem = CONFIG_SERVER_CERTIFICATE, // From Kconfig
.skip_cert_common_name_check = false,
.keep_alive_enable = true,
.keep_alive_idle = 30,
.keep_alive_interval = 5,
.keep_alive_count = 3
};

return esp_http_client_set_config(client, &config);


}

---

6. Buffer Safety with Bounds Checking

esp_err_t safe_snprintf(char* buffer, size_t buffer_size, const char* format, ...) {


va_list args;
va_start(args, format);

int needed = vsnprintf(NULL, 0, format, args);


va_end(args);

if (needed < 0 || (size_t)needed >= buffer_size) {


return ESP_ERR_INVALID_SIZE;
}

va_start(args, format);
vsnprintf(buffer, buffer_size, format, args);
va_end(args);

return ESP_OK;
}

// Usage:
char url[300];
if (safe_snprintf(url, sizeof(url), "%s/device/%s", base_url, endpoint) != ESP_OK) {
// Handle error
}

---

7. Improved Error Response Handling


esp_err_t handle_http_error(esp_http_client_handle_t client) {
int status = esp_http_client_get_status_code(client);
int64_t content_len = esp_http_client_get_content_length(client);

if (content_len > 0 && content_len < 1024) {


char* error_buf = malloc(content_len + 1);
if (error_buf) {
int read = esp_http_client_read(client, error_buf, content_len);
error_buf[read] = '\0';

ESP_LOGE(TAG, "HTTP Error %d: %.*s",


status, (int)content_len, error_buf);

// Parse error JSON if needed


cJSON* error_json = cJSON_Parse(error_buf);
if (error_json) {
// Extract error details
cJSON_Delete(error_json);
}

free(error_buf);
}
}

return (status >= 400 && status < 500) ? ESP_ERR_INVALID_RESPONSE


: ESP_ERR_HTTP_FAILED;
}

These snippets address the major issues while maintaining the original code style and ESP-IDF
conventions. Each one can be integrated into the existing codebase with minimal changes to the
surrounding code.

You might also like