• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender-mcu / 1928841380

15 Jul 2025 09:23PM UTC coverage: 57.289% (-0.1%) from 57.388%
1928841380

Pull #209

gitlab-ci

mender-test-bot
chore(main): release 0.10.0

Signed-off-by: mender-test-bot <mender@northern.tech>
Pull Request #209: chore(main): release 0.10.0

2303 of 4020 relevant lines covered (57.29%)

69.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.62
/src/core/api.c
1
/**
2
 * @file      api.c
3
 * @brief     Implementation of the Mender API
4
 *
5
 * Copyright joelguittet and mender-mcu-client contributors
6
 * Copyright Northern.tech AS
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20

21
#include "alloc.h"
22
#include "api.h"
23
#include "artifact.h"
24
#include "error-counters.h"
25
#include "os.h"
26
#include "storage.h"
27
#include "http.h"
28
#include "log.h"
29
#include "tls.h"
30
#include "utils.h"
31

32
/**
33
 * @brief HTTP retry mechanism parameters
34
 *
35
 * On unreliable networks, an HTTP request can easily fail due to some short
36
 * network issue. Thus there needs to be a retry mechanism to prevent every
37
 * single such temporary failure to cause an error.
38
 *
39
 * 5 attempts (so 4 retries, to be precise) with 100ms as the first pause
40
 * interval doubled with every attempt gives a total of 1500ms period of retries
41
 * with nice progressive pauses in between (100, 200, 400, 800ms).
42
 *
43
 * @note: RETRY_ATTEMPTS is uint8_t,
44
 *        INTERVAL_BASE * INTERVAL_FACTOR**(ATTEMPTS - 1) has to fit in uint16_t
45
 *        Or the code below has to be adjusted, but such a long sleep doesn't make sense here!
46
 */
47
#define HTTP_RETRY_ATTEMPTS        5
48
#define HTTP_RETRY_INTERVAL_BASE   100 /* milliseconds */
49
#define HTTP_RETRY_INTERVAL_FACTOR 2
50

51
/**
52
 * @brief Paths of the mender-server APIs
53
 */
54
#define MENDER_API_PATH_POST_AUTHENTICATION_REQUESTS "/api/devices/v1/authentication/auth_requests"
55
#define MENDER_API_PATH_GET_NEXT_DEPLOYMENT          "/api/devices/v1/deployments/device/deployments/next"
56
#define MENDER_API_PATH_POST_NEXT_DEPLOYMENT_V2      "/api/devices/v2/deployments/device/deployments/next"
57
#define MENDER_API_PATH_PUT_DEPLOYMENT_STATUS        "/api/devices/v1/deployments/device/deployments/%s/status"
58
#define MENDER_API_PATH_PUT_DEPLOYMENT_LOGS          "/api/devices/v1/deployments/device/deployments/%s/log"
59
#define MENDER_API_PATH_GET_DEVICE_CONFIGURATION     "/api/devices/v1/deviceconfig/configuration"
60
#define MENDER_API_PATH_PUT_DEVICE_CONFIGURATION     "/api/devices/v1/deviceconfig/configuration"
61
#define MENDER_API_PATH_GET_DEVICE_CONNECT           "/api/devices/v1/deviceconnect/connect"
62
#define MENDER_API_PATH_PUT_DEVICE_ATTRIBUTES        "/api/devices/v1/inventory/device/attributes"
63

64
/**
65
 * @brief Mender API configuration
66
 */
67
static mender_api_config_t api_config;
68

69
/**
70
 * @brief Authentication token
71
 */
72
static char *api_jwt = NULL;
73

74
/**
75
 * @brief A mutex ensuring there are no concurrent operations using or updating the authentication token
76
 */
77
static void *auth_lock = NULL;
78

79
/**
80
 * @brief HTTP callback used to handle text content
81
 * @param event HTTP client event
82
 * @param data Data received
83
 * @param data_length Data length
84
 * @param params Callback parameters
85
 * @return MENDER_OK if the function succeeds, error code otherwise
86
 */
87
static mender_err_t mender_api_http_text_callback(mender_http_client_event_t event, void *data, size_t data_length, void *params);
88

89
/**
90
 * @brief Perform authentication of the device, retrieve token from mender-server used for the next requests
91
 * @return MENDER_OK if the function succeeds, error code otherwise
92
 */
93
static mender_err_t perform_authentication(void);
94

95
/**
96
 * @brief Ensure authenticated and holding the #auth_lock
97
 * @return MENDER_OK if success, MENDER_LOCK_FAILED in case of lock failure, other errors otherwise
98
 */
99
static mender_err_t ensure_authenticated_and_locked(void);
100

101
mender_err_t
102
mender_api_init(mender_api_config_t *config) {
29✔
103
    assert(NULL != config);
29✔
104
    assert(NULL != config->device_type);
29✔
105
    assert(NULL != config->host);
29✔
106
    assert(NULL != config->identity_cb);
29✔
107

108
    mender_err_t ret;
109

110
    /* Save configuration */
111
    memcpy(&api_config, config, sizeof(mender_api_config_t));
29✔
112

113
    /* Initializations */
114
    mender_http_config_t mender_http_config = { .host = api_config.host };
29✔
115
    if (MENDER_OK != (ret = mender_http_init(&mender_http_config))) {
29✔
116
        mender_log_error("Unable to initialize HTTP");
×
117
        return ret;
×
118
    }
119

120
    if (MENDER_OK != (ret = mender_os_mutex_create(&auth_lock))) {
29✔
121
        mender_log_error("Unable to initialize authentication lock");
×
122
        return ret;
×
123
    }
124

125
    return ret;
29✔
126
}
127

128
mender_err_t
129
mender_api_drop_authentication_data(void) {
6✔
130
    mender_err_t ret;
131
    if (MENDER_OK != (ret = mender_os_mutex_take(auth_lock, -1))) {
6✔
132
        mender_log_error("Unable to obtain the authentication lock");
×
133
        return MENDER_LOCK_FAILED;
×
134
    }
135
    FREE_AND_NULL(api_jwt);
6✔
136
    if (MENDER_OK != (ret = mender_os_mutex_give(auth_lock))) {
6✔
137
        mender_log_error("Unable to release the authentication lock");
×
138
    }
139

140
    return ret;
6✔
141
}
142

143
mender_err_t
144
mender_api_ensure_authenticated(void) {
6✔
145
    mender_err_t ret = ensure_authenticated_and_locked();
6✔
146
    if (MENDER_LOCK_FAILED == ret) {
5✔
147
        /* Error already logged. */
148
        return MENDER_FAIL;
×
149
    }
150
    bool authenticated = ((MENDER_OK == ret) || (MENDER_DONE == ret));
5✔
151

152
    if (MENDER_OK != (ret = mender_os_mutex_give(auth_lock))) {
5✔
153
        mender_log_error("Unable to release the authentication lock");
×
154
    }
155

156
    return authenticated ? ret : MENDER_FAIL;
5✔
157
}
158

159
static mender_err_t
160
ensure_authenticated_and_locked(void) {
127✔
161
    mender_err_t ret;
162

163
    if (MENDER_OK != (ret = mender_os_mutex_take(auth_lock, -1))) {
127✔
164
        mender_log_error("Unable to obtain the authentication lock");
×
165
        return MENDER_LOCK_FAILED;
×
166
    }
167

168
    if (NULL != api_jwt) {
127✔
169
        return MENDER_DONE;
97✔
170
    }
171

172
    /* Perform authentication with the mender server */
173
    if (MENDER_OK != (ret = perform_authentication())) {
30✔
174
        mender_log_error("Authentication failed");
×
175
        return ret;
×
176
    } else {
177
        mender_log_debug("Authenticated successfully");
29✔
178
    }
179

180
    return ret;
29✔
181
}
182

183
static mender_err_t
184
perform_authentication(void) {
30✔
185
    mender_err_t             ret;
186
    char                    *public_key_pem   = NULL;
30✔
187
    const mender_identity_t *identity         = NULL;
30✔
188
    cJSON                   *json_identity    = NULL;
30✔
189
    char                    *identity_info    = NULL;
30✔
190
    cJSON                   *json_payload     = NULL;
30✔
191
    char                    *payload          = NULL;
30✔
192
    char                    *response         = NULL;
30✔
193
    char                    *signature        = NULL;
30✔
194
    size_t                   signature_length = 0;
30✔
195
    int                      status           = 0;
30✔
196
    uint8_t                  remaining_attempts;
197
    uint16_t                 retry_interval;
198

199
    /* Get public key in PEM format */
200
    if (MENDER_OK != (ret = mender_tls_get_public_key_pem(&public_key_pem))) {
30✔
201
        mender_log_error("Unable to get public key");
×
202
        goto END;
×
203
    }
204

205
    /* Get identity (we don't own the returned data) */
206
    if (MENDER_OK != (ret = api_config.identity_cb(&identity))) {
30✔
207
        mender_log_error("Unable to get identity");
×
208
        goto END;
×
209
    }
210

211
    /* Format identity */
212
    if (MENDER_OK != (ret = mender_utils_identity_to_json(identity, &json_identity))) {
30✔
213
        mender_log_error("Unable to format identity");
×
214
        goto END;
×
215
    }
216
    if (NULL == (identity_info = cJSON_PrintUnformatted(json_identity))) {
30✔
217
        mender_log_error("Unable to allocate memory");
×
218
        ret = MENDER_FAIL;
×
219
        goto END;
×
220
    }
221

222
    /* Format payload */
223
    if (NULL == (json_payload = cJSON_CreateObject())) {
30✔
224
        mender_log_error("Unable to allocate memory");
×
225
        ret = MENDER_FAIL;
×
226
        goto END;
×
227
    }
228
    cJSON_AddStringToObject(json_payload, "id_data", identity_info);
30✔
229
    cJSON_AddStringToObject(json_payload, "pubkey", public_key_pem);
30✔
230
    if (NULL != api_config.tenant_token) {
30✔
231
        cJSON_AddStringToObject(json_payload, "tenant_token", api_config.tenant_token);
30✔
232
    }
233
    if (NULL == (payload = cJSON_PrintUnformatted(json_payload))) {
30✔
234
        mender_log_error("Unable to allocate memory");
×
235
        ret = MENDER_FAIL;
×
236
        goto END;
×
237
    }
238

239
    /* Sign payload */
240
    if (MENDER_OK != (ret = mender_tls_sign_payload(payload, &signature, &signature_length))) {
30✔
241
        mender_log_error("Unable to sign payload");
×
242
        goto END;
×
243
    }
244

245
    /* Perform HTTP request */
246
    remaining_attempts = HTTP_RETRY_ATTEMPTS;
30✔
247
    retry_interval     = HTTP_RETRY_INTERVAL_BASE;
30✔
248
    do {
249
        ret = mender_http_perform(NULL,
36✔
250
                                  MENDER_API_PATH_POST_AUTHENTICATION_REQUESTS,
251
                                  MENDER_HTTP_POST,
252
                                  payload,
253
                                  signature,
254
                                  &mender_api_http_text_callback,
255
                                  (void *)&response,
256
                                  &status);
257
        if (MENDER_RETRY_ERROR == ret) {
35✔
258
            mender_os_sleep(retry_interval);
6✔
259
            retry_interval = retry_interval * HTTP_RETRY_INTERVAL_FACTOR;
6✔
260
            remaining_attempts--;
6✔
261

262
            /* Just in case something was already gathered as response. */
263
            FREE_AND_NULL(response);
6✔
264
        }
265
    } while ((MENDER_RETRY_ERROR == ret) && (remaining_attempts > 0));
35✔
266

267
    if (MENDER_OK != ret) {
29✔
268
        mender_log_error("Unable to perform HTTP request");
×
269
        mender_err_count_net_inc();
×
270
        goto END;
×
271
    }
272

273
    /* Treatment depending of the status */
274
    if (200 == status) {
29✔
275
        if (NULL == response) {
29✔
276
            mender_log_error("Response is empty");
×
277
            ret = MENDER_FAIL;
×
278
            goto END;
×
279
        }
280
        if (NULL != api_jwt) {
29✔
281
            mender_free(api_jwt);
×
282
        }
283
        if (NULL == (api_jwt = mender_utils_strdup(response))) {
29✔
284
            mender_log_error("Unable to allocate memory");
×
285
            ret = MENDER_FAIL;
×
286
            goto END;
×
287
        }
288
        ret = MENDER_OK;
29✔
289
    } else {
290
        mender_api_print_response_error(response, status);
×
291
        /* Maybe the identity is wrong? Let's make sure we get fresh data for the next attempt. */
292
        FREE_AND_NULL(identity_info);
×
293
        ret = MENDER_RETRY_ERROR;
×
294
    }
295

296
END:
29✔
297

298
    /* Release memory */
299
    mender_free(response);
29✔
300
    mender_free(signature);
29✔
301
    mender_free(payload);
29✔
302
    cJSON_Delete(json_payload);
29✔
303
    cJSON_Delete(json_identity);
29✔
304
    mender_free(identity_info);
29✔
305
    mender_free(public_key_pem);
29✔
306

307
    return ret;
29✔
308
}
309

310
/**
311
 * @see mender_http_perform()
312
 */
313
static mender_err_t
314
authenticated_http_perform(char *path, mender_http_method_t method, char *payload, char *signature, char **response, int *status) {
121✔
315
    mender_err_t ret;
316
    uint8_t      remaining_attempts;
317
    uint16_t     retry_interval;
318

319
    if (MENDER_IS_ERROR(ret = ensure_authenticated_and_locked())) {
121✔
320
        /* Errors already logged. */
321
        if (MENDER_LOCK_FAILED != ret) {
×
322
            if (MENDER_OK != mender_os_mutex_give(auth_lock)) {
×
323
                mender_log_error("Unable to release the authentication lock");
×
324
                return MENDER_FAIL;
×
325
            }
326
        }
327
        return ret;
×
328
    }
329

330
    remaining_attempts = HTTP_RETRY_ATTEMPTS;
121✔
331
    retry_interval     = HTTP_RETRY_INTERVAL_BASE;
121✔
332
    do {
333
        ret = mender_http_perform(api_jwt, path, method, payload, signature, &mender_api_http_text_callback, response, status);
121✔
334
        if (MENDER_RETRY_ERROR == ret) {
107✔
335
            mender_os_sleep(retry_interval);
×
336
            retry_interval = retry_interval * HTTP_RETRY_INTERVAL_FACTOR;
×
337
            remaining_attempts--;
×
338

339
            /* Just in case something was already gathered as response. */
340
            FREE_AND_NULL(*response);
×
341
        }
342
    } while ((MENDER_RETRY_ERROR == ret) && (remaining_attempts > 0));
107✔
343

344
    if (MENDER_OK != mender_os_mutex_give(auth_lock)) {
107✔
345
        mender_log_error("Unable to release the authentication lock");
×
346
        return MENDER_FAIL;
×
347
    }
348
    if (MENDER_OK != ret) {
107✔
349
        /* HTTP errors already logged. */
350
        mender_err_count_net_inc();
×
351
        return ret;
×
352
    }
353

354
    if (401 == *status) {
107✔
355
        /* Unauthorized => try to re-authenticate and perform the request again */
356
        mender_log_info("Trying to re-authenticate");
×
357
        FREE_AND_NULL(api_jwt);
×
358
        if (MENDER_IS_ERROR(ret = ensure_authenticated_and_locked())) {
×
359
            mender_free(*response);
×
360
            ret = mender_http_perform(api_jwt, path, method, payload, signature, &mender_api_http_text_callback, response, status);
×
361
            if (MENDER_OK != mender_os_mutex_give(auth_lock)) {
×
362
                mender_log_error("Unable to release the authentication lock");
×
363
                return MENDER_FAIL;
×
364
            }
365
            if (MENDER_OK != ret) {
×
366
                /* HTTP errors already logged. */
367
                mender_err_count_net_inc();
×
368
            }
369
        } else if (MENDER_LOCK_FAILED != ret) {
×
370
            if (MENDER_OK != mender_os_mutex_give(auth_lock)) {
×
371
                mender_log_error("Unable to release the authentication lock");
×
372
                return MENDER_FAIL;
×
373
            }
374
        }
375
    }
376

377
    return ret;
107✔
378
}
379

380
static mender_err_t
381
api_check_for_deployment_v2(int *status, char **response) {
39✔
382
    assert(NULL != status);
39✔
383
    assert(NULL != response);
39✔
384

385
    mender_err_t ret           = MENDER_FAIL;
39✔
386
    cJSON       *json_payload  = NULL;
39✔
387
    char        *payload       = NULL;
39✔
388
    const char  *artifact_name = NULL;
39✔
389
#ifdef CONFIG_MENDER_PROVIDES_DEPENDS
390
#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT
391
    mender_key_value_list_t *provides = NULL;
39✔
392
#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */
393
#endif /* CONFIG_MENDER_PROVIDES_DEPENDS */
394

395
    /* Create payload */
396
    if (NULL == (json_payload = cJSON_CreateObject())) {
39✔
397
        mender_log_error("Unable to allocate memory");
×
398
        goto END;
×
399
    }
400

401
    /* Add "device_provides" entity to payload */
402
    cJSON *json_provides = NULL;
39✔
403
    if (NULL == (json_provides = cJSON_AddObjectToObject(json_payload, "device_provides"))) {
39✔
404
        mender_log_error("Unable to allocate memory");
×
405
        goto END;
×
406
    }
407

408
    if (NULL == cJSON_AddStringToObject(json_provides, "device_type", api_config.device_type)) {
39✔
409
        mender_log_error("Unable to allocate memory");
×
410
        goto END;
×
411
    }
412

413
#ifdef CONFIG_MENDER_PROVIDES_DEPENDS
414
#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT
415
    /* Add provides from storage */
416
    if (MENDER_FAIL == mender_storage_get_provides(&provides)) {
39✔
417
        mender_log_error("Unable to get provides");
×
418
        goto END;
×
419
    }
420
    for (mender_key_value_list_t *item = provides; NULL != item; item = item->next) {
44✔
421
        if (NULL == cJSON_AddStringToObject(json_provides, item->key, item->value)) {
5✔
422
            mender_log_error("Unable to allocate memory");
×
423
            goto END;
×
424
        }
425
    }
426
#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */
427
#endif /* CONFIG_MENDER_PROVIDES_DEPENDS */
428

429
    if ((MENDER_OK != mender_storage_get_artifact_name(&artifact_name)) && (NULL != artifact_name)) {
39✔
430
        mender_log_error("Unable to get artifact name");
×
431
        return MENDER_FAIL;
×
432
    }
433

434
    if (NULL == cJSON_AddStringToObject(json_provides, "artifact_name", artifact_name)) {
39✔
435
        mender_log_error("Unable to allocate memory");
×
436
        goto END;
×
437
    }
438

439
    if (NULL == (payload = cJSON_PrintUnformatted(json_payload))) {
39✔
440
        mender_log_error("Unable to allocate memory");
×
441
        goto END;
×
442
    }
443

444
    /* Perform HTTP request */
445
    if (MENDER_OK != (ret = authenticated_http_perform(MENDER_API_PATH_POST_NEXT_DEPLOYMENT_V2, MENDER_HTTP_POST, payload, NULL, response, status))) {
39✔
446
        mender_log_error("Unable to perform HTTP request");
×
447
        goto END;
×
448
    }
449

450
    ret = MENDER_OK;
27✔
451

452
END:
27✔
453

454
#ifdef CONFIG_MENDER_PROVIDES_DEPENDS
455
#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT
456
    mender_utils_key_value_list_free(provides);
27✔
457
#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */
458
#endif /* CONFIG_MENDER_PROVIDES_DEPENDS */
459
    cJSON_Delete(json_payload);
27✔
460
    mender_free(payload);
27✔
461
    return ret;
27✔
462
}
463

464
static mender_err_t
465
api_check_for_deployment_v1(int *status, char **response) {
×
466

467
    assert(NULL != status);
×
468
    assert(NULL != response);
×
469

470
    mender_err_t ret           = MENDER_FAIL;
×
471
    char        *path          = NULL;
×
472
    const char  *artifact_name = NULL;
×
473

474
    if ((MENDER_OK != mender_storage_get_artifact_name(&artifact_name)) && (NULL != artifact_name)) {
×
475
        mender_log_error("Unable to get artifact name");
×
476
        return MENDER_FAIL;
×
477
    }
478

479
    /* Compute path */
480
    if (-1 == mender_utils_asprintf(&path, MENDER_API_PATH_GET_NEXT_DEPLOYMENT "?artifact_name=%s&device_type=%s", artifact_name, api_config.device_type)) {
×
481
        mender_log_error("Unable to allocate memory");
×
482
        goto END;
×
483
    }
484

485
    /* Perform HTTP request */
486
    if (MENDER_OK != (ret = authenticated_http_perform(path, MENDER_HTTP_GET, NULL, NULL, response, status))) {
×
487
        mender_log_error("Unable to perform HTTP request");
×
488
        goto END;
×
489
    }
490

491
    ret = MENDER_OK;
×
492

493
END:
×
494

495
    /* Release memory */
496
    mender_free(path);
×
497

498
    return ret;
×
499
}
500

501
mender_err_t
502
mender_api_check_for_deployment(mender_api_deployment_data_t *deployment) {
39✔
503

504
    assert(NULL != deployment);
39✔
505
    mender_err_t ret      = MENDER_FAIL;
39✔
506
    char        *response = NULL;
39✔
507
    int          status   = 0;
39✔
508

509
    if (MENDER_FAIL == (ret = api_check_for_deployment_v2(&status, &response))) {
39✔
510
        goto END;
×
511
    }
512

513
    /* Yes, 404 still means MENDER_OK above */
514
    if (404 == status) {
27✔
515
        mender_log_debug("POST request to v2 version of the deployments API failed, falling back to v1 version and GET");
516
        FREE_AND_NULL(response);
×
517
        if (MENDER_FAIL == (ret = api_check_for_deployment_v1(&status, &response))) {
×
518
            goto END;
×
519
        }
520
    }
521

522
    /* Treatment depending of the status */
523
    if (200 == status) {
27✔
524
        cJSON *json_response = cJSON_Parse(response);
12✔
525
        if (NULL != json_response) {
12✔
526
            cJSON *json_id = cJSON_GetObjectItem(json_response, "id");
12✔
527
            if (NULL != json_id) {
12✔
528
                if (NULL == (deployment->id = mender_utils_strdup(cJSON_GetStringValue(json_id)))) {
12✔
529
                    ret = MENDER_FAIL;
×
530
                    goto END;
×
531
                }
532
            }
533
            cJSON *json_artifact = cJSON_GetObjectItem(json_response, "artifact");
12✔
534
            if (NULL != json_artifact) {
12✔
535
                cJSON *json_artifact_name = cJSON_GetObjectItem(json_artifact, "artifact_name");
12✔
536
                if (NULL != json_artifact_name) {
12✔
537
                    if (NULL == (deployment->artifact_name = mender_utils_strdup(cJSON_GetStringValue(json_artifact_name)))) {
12✔
538
                        ret = MENDER_FAIL;
×
539
                        goto END;
×
540
                    }
541
                }
542
                cJSON *json_source = cJSON_GetObjectItem(json_artifact, "source");
12✔
543
                if (NULL != json_source) {
12✔
544
                    cJSON *json_uri = cJSON_GetObjectItem(json_source, "uri");
12✔
545
                    if (NULL != json_uri) {
12✔
546
                        if (NULL == (deployment->uri = mender_utils_strdup(cJSON_GetStringValue(json_uri)))) {
12✔
547
                            ret = MENDER_FAIL;
×
548
                            goto END;
×
549
                        }
550
                        ret = MENDER_OK;
12✔
551
                    } else {
552
                        mender_log_error("Invalid response");
×
553
                        ret = MENDER_FAIL;
×
554
                    }
555
                } else {
556
                    mender_log_error("Invalid response");
×
557
                    ret = MENDER_FAIL;
×
558
                }
559
                cJSON *json_device_types_compatible = cJSON_GetObjectItem(json_artifact, "device_types_compatible");
12✔
560
                if (NULL != json_device_types_compatible && cJSON_IsArray(json_device_types_compatible)) {
12✔
561
                    deployment->device_types_compatible_size = cJSON_GetArraySize(json_device_types_compatible);
12✔
562
                    deployment->device_types_compatible      = (char **)mender_malloc(deployment->device_types_compatible_size * sizeof(char *));
12✔
563
                    if (NULL == deployment->device_types_compatible) {
12✔
564
                        mender_log_error("Unable to allocate memory");
×
565
                        ret = MENDER_FAIL;
×
566
                        goto END;
×
567
                    }
568
                    for (size_t i = 0; i < deployment->device_types_compatible_size; i++) {
24✔
569
                        cJSON *json_device_type = cJSON_GetArrayItem(json_device_types_compatible, i);
12✔
570
                        if (NULL != json_device_type && cJSON_IsString(json_device_type)) {
12✔
571
                            if (NULL == (deployment->device_types_compatible[i] = mender_utils_strdup(cJSON_GetStringValue(json_device_type)))) {
12✔
572
                                ret = MENDER_FAIL;
×
573
                                goto END;
×
574
                            }
575
                        } else {
576
                            mender_log_error("Could not get device type form device_types_compatible array");
×
577
                            ret = MENDER_FAIL;
×
578
                        }
579
                    }
580
                } else {
581
                    mender_log_error("Could not load device_types_compatible");
×
582
                    ret = MENDER_FAIL;
×
583
                }
584
            } else {
585
                mender_log_error("Invalid response");
×
586
                ret = MENDER_FAIL;
×
587
            }
588
            cJSON_Delete(json_response);
12✔
589
        } else {
590
            mender_log_error("Invalid response");
×
591
            ret = MENDER_FAIL;
×
592
        }
593
    } else if (204 == status) {
15✔
594
        /* No response expected */
595
        ret = MENDER_NOT_FOUND;
15✔
596
    } else {
597
        mender_api_print_response_error(response, status);
×
598
        ret = MENDER_RETRY_ERROR;
×
599
    }
600

601
END:
27✔
602

603
    /* Release memory */
604
    mender_free(response);
27✔
605

606
    return ret;
27✔
607
}
608

609
#ifdef CONFIG_MENDER_DEPLOYMENT_LOGS
610
static mender_err_t mender_api_publish_deployment_logs(const char *id);
611
#endif /* CONFIG_MENDER_DEPLOYMENT_LOGS */
612

613
mender_err_t
614
mender_api_publish_deployment_status(const char *id, mender_deployment_status_t deployment_status) {
47✔
615
    assert(NULL != id);
47✔
616

617
    mender_err_t ret;
618
    const char  *value        = NULL;
47✔
619
    cJSON       *json_payload = NULL;
47✔
620
    char        *payload      = NULL;
47✔
621
    char        *path         = NULL;
47✔
622
    char        *response     = NULL;
47✔
623
    int          status       = 0;
47✔
624

625
    /* Deployment status to string */
626
    if (NULL == (value = mender_utils_deployment_status_to_string(deployment_status))) {
47✔
627
        mender_log_error("Invalid status");
×
628
        ret = MENDER_FAIL;
×
629
        goto END;
×
630
    }
631

632
    /* Format payload */
633
    if (NULL == (json_payload = cJSON_CreateObject())) {
47✔
634
        mender_log_error("Unable to allocate memory");
×
635
        ret = MENDER_FAIL;
×
636
        goto END;
×
637
    }
638
    cJSON_AddStringToObject(json_payload, "status", value);
47✔
639
    if (NULL == (payload = cJSON_PrintUnformatted(json_payload))) {
47✔
640
        mender_log_error("Unable to allocate memory");
×
641
        ret = MENDER_FAIL;
×
642
        goto END;
×
643
    }
644

645
    /* Compute path */
646
    if (mender_utils_asprintf(&path, MENDER_API_PATH_PUT_DEPLOYMENT_STATUS, id) <= 0) {
47✔
647
        mender_log_error("Unable to allocate memory");
×
648
        ret = MENDER_FAIL;
×
649
        goto END;
×
650
    }
651

652
    /* Perform HTTP request */
653
    if (MENDER_OK != (ret = authenticated_http_perform(path, MENDER_HTTP_PUT, payload, NULL, &response, &status))) {
47✔
654
        mender_log_error("Unable to perform HTTP request");
×
655
        goto END;
×
656
    }
657

658
    /* Treatment depending of the status */
659
    if (204 == status) {
45✔
660
        /* No response expected */
661
        ret = MENDER_OK;
44✔
662
    } else if (409 == status) {
1✔
663
        /* Deployment aborted */
664
        mender_api_print_response_error(response, status);
1✔
665
        ret = MENDER_ABORTED;
1✔
666
    } else {
667
        mender_api_print_response_error(response, status);
×
668
        ret = MENDER_RETRY_ERROR;
×
669
    }
670

671
END:
45✔
672

673
    /* Release memory */
674
    mender_free(response);
45✔
675
    mender_free(path);
45✔
676
    mender_free(payload);
45✔
677
    cJSON_Delete(json_payload);
45✔
678

679
#ifdef CONFIG_MENDER_DEPLOYMENT_LOGS
680
    /* Do this after we have released memory above, potentially giving us some
681
       extra room we may need. */
682
    if ((MENDER_OK == ret) && (MENDER_DEPLOYMENT_STATUS_FAILURE == deployment_status)) {
45✔
683
        /* Successfully reported a deployment failure, upload deployment
684
           logs.  */
685
        if (MENDER_OK != mender_api_publish_deployment_logs(id)) {
9✔
686
            mender_log_error("Failed to publish deployment logs");
687
        }
688
    }
689
#endif /* CONFIG_MENDER_DEPLOYMENT_LOGS */
690

691
    return ret;
45✔
692
}
693

694
#ifdef CONFIG_MENDER_DEPLOYMENT_LOGS
695
static void
696
append_depl_log_msg(char *msg, void *ctx) {
21✔
697
    assert(NULL != ctx);
21✔
698

699
    char  *tstamp   = NULL;
21✔
700
    char  *level    = NULL;
21✔
701
    char  *log_msg  = NULL;
21✔
702
    cJSON *json_msg = NULL;
21✔
703
    cJSON *messages = ctx;
21✔
704

705
    /* Example log message we expect:
706
     *   "[00:39:06.746,000] <err> mender: Unable to perform HTTP request"
707
     * The code below goes through the string, searches for the expected parts and breaks the string
708
     * down accordingly.
709
     */
710

711
    /* Start by setting log_msg to the whole message. In case all of the
712
       break-down below fails, we send the whole message as the log message with
713
       no extra metadata. */
714
    log_msg = msg;
21✔
715

716
    char *c = msg;
21✔
717
    if ('[' == *c) {
21✔
718
        /* if it does start with a timestamp, like above, store the pointer and find its end */
719
        c++;
21✔
720
        tstamp = c;
21✔
721
        while (('\0' != *c) && (']' != *c)) {
588✔
722
            c++;
567✔
723
        }
724
        if ('\0' == *c) {
21✔
725
            goto DONE_PARSING;
726
        }
727
        *c = '\0';
21✔
728
        c++;
21✔
729
    }
730

731
    if (' ' == *c) {
21✔
732
        /* skip the space */
733
        c++;
21✔
734
    }
735

736
    if ('<' == *c) {
21✔
737
        /* if the log level follow, like above, store the pointer and find its end */
738
        c++;
21✔
739
        level = c;
21✔
740
        while (('\0' != *c) && ('>' != *c)) {
84✔
741
            c++;
63✔
742
        }
743
        if ('\0' == *c) {
21✔
744
            goto DONE_PARSING;
745
        }
746
        *c = '\0';
21✔
747
        c++;
21✔
748
    }
749

750
    if (' ' == *c) {
21✔
751
        /* skip the space */
752
        c++;
21✔
753
    }
754

755
    if ('\0' != *c) {
21✔
756
        log_msg = c;
21✔
757
        if (mender_utils_strbeginswith(log_msg, "mender: ")) {
21✔
758
            log_msg += strlen("mender: ");
21✔
759
        }
760
    }
761

762
DONE_PARSING:
763
    if (NULL == (json_msg = cJSON_CreateObject())) {
21✔
764
        mender_log_error("Unable to allocate memory");
765
        return;
766
    }
767

768
    if (NULL != tstamp) {
21✔
769
        if (NULL == cJSON_AddStringToObject(json_msg, "timestamp", tstamp)) {
21✔
770
            mender_log_error("Unable to allocate memory");
771
            goto END;
772
        }
773
    } else {
774
        if (NULL == cJSON_AddNullToObject(json_msg, "timestamp")) {
775
            mender_log_error("Unable to allocate memory");
776
            goto END;
777
        }
778
    }
779

780
    if (NULL != level) {
21✔
781
        if (NULL == cJSON_AddStringToObject(json_msg, "level", level)) {
21✔
782
            mender_log_error("Unable to allocate memory");
783
            goto END;
784
        }
785
    } else {
786
        if (NULL == cJSON_AddNullToObject(json_msg, "level")) {
787
            mender_log_error("Unable to allocate memory");
788
            goto END;
789
        }
790
    }
791

792
    if (NULL == cJSON_AddStringToObject(json_msg, "message", log_msg)) {
21✔
793
        mender_log_error("Unable to allocate memory");
794
        goto END;
795
    }
796

797
    if (!cJSON_AddItemToArray(messages, json_msg)) {
21✔
798
        mender_log_error("Unable to allocate memory");
799
    }
800
    json_msg = NULL;
21✔
801

802
END:
21✔
803
    cJSON_Delete(json_msg);
21✔
804
}
805

806
static mender_err_t
807
mender_api_publish_deployment_logs(const char *id) {
9✔
808
    assert(NULL != id);
9✔
809

810
    mender_err_t ret;
811
    cJSON       *json_payload  = NULL;
9✔
812
    cJSON       *json_messages = NULL;
9✔
813
    char        *payload       = NULL;
9✔
814
    char        *path          = NULL;
9✔
815
    char        *response      = NULL;
9✔
816
    int          status        = 0;
9✔
817

818
    /* Format payload */
819
    if (NULL == (json_payload = cJSON_CreateObject())) {
9✔
820
        mender_log_error("Unable to allocate memory");
821
        ret = MENDER_FAIL;
822
        goto END;
823
    }
824
    if (NULL == (json_messages = cJSON_AddArrayToObject(json_payload, "messages"))) {
9✔
825
        mender_log_error("Unable to allocate memory");
826
        ret = MENDER_FAIL;
827
        goto END;
828
    }
829

830
    if (MENDER_OK != (ret = mender_storage_deployment_log_walk(append_depl_log_msg, json_messages))) {
9✔
831
        mender_log_error("Failed to add deployment log messages to payload");
832
        ret = MENDER_FAIL;
833
        goto END;
834
    }
835

836
    if (0 == cJSON_GetArraySize(json_messages)) {
9✔
837
        /* Nothing to do, no logs to submit. */
838
        ret = MENDER_OK;
3✔
839
        goto END;
3✔
840
    }
841

842
    if (NULL == (payload = cJSON_PrintUnformatted(json_payload))) {
6✔
843
        mender_log_error("Unable to allocate memory");
844
        ret = MENDER_FAIL;
845
        goto END;
846
    }
847
    /* We no longer need the JSON now that we have the string representation so
848
       reclaim that (potentially big) chunk of memory). */
849
    DESTROY_AND_NULL(cJSON_Delete, json_payload);
6✔
850

851
    /* Perform HTTP request */
852
    if (mender_utils_asprintf(&path, MENDER_API_PATH_PUT_DEPLOYMENT_LOGS, id) <= 0) {
6✔
853
        mender_log_error("Unable to allocate memory");
854
        goto END;
855
    }
856

857
    mender_log_info("Publishing deployment logs");
6✔
858
    if (MENDER_OK != (ret = authenticated_http_perform(path, MENDER_HTTP_PUT, payload, NULL, &response, &status))) {
6✔
859
        mender_log_error("Unable to perform HTTP request");
860
        goto END;
861
    }
862

863
    /* Treatment depending of the status */
864
    if (204 == status) {
6✔
865
        /* No response expected */
866
        ret = MENDER_OK;
6✔
867
    } else if (409 == status) {
868
        /* Deployment aborted */
869
        mender_api_print_response_error(response, status);
870
        ret = MENDER_ABORTED;
871
    } else {
872
        mender_api_print_response_error(response, status);
873
        ret = MENDER_FAIL;
874
    }
875

876
END:
9✔
877

878
    /* Release memory */
879
    mender_free(response);
9✔
880
    mender_free(path);
9✔
881
    mender_free(payload);
9✔
882
    cJSON_Delete(json_payload);
9✔
883

884
    return ret;
9✔
885
}
886
#endif /* CONFIG_MENDER_DEPLOYMENT_LOGS */
887

888
#ifndef CONFIG_MENDER_CLIENT_INVENTORY_DISABLE
889

890
mender_err_t
891
mender_api_publish_inventory_data(cJSON *inventory, bool patch) {
29✔
892

893
    mender_err_t ret;
894
    char        *payload  = NULL;
29✔
895
    char        *response = NULL;
29✔
896
    int          status   = 0;
29✔
897

898
    /* Format payload */
899
    if (NULL == (payload = cJSON_PrintUnformatted(inventory))) {
29✔
900
        mender_log_error("Unable to allocate memory");
×
901
        ret = MENDER_FAIL;
×
902
        goto END;
×
903
    }
904

905
    /* Perform HTTP request */
906
    if (MENDER_OK
29✔
907
        != (ret = authenticated_http_perform(
29✔
908
                MENDER_API_PATH_PUT_DEVICE_ATTRIBUTES, patch ? MENDER_HTTP_PATCH : MENDER_HTTP_PUT, payload, NULL, &response, &status))) {
909
        mender_log_error("Unable to perform HTTP request");
×
910
        goto END;
×
911
    }
912

913
    /* Treatment depending of the status */
914
    if (200 == status) {
29✔
915
        /* No response expected */
916
        ret = MENDER_OK;
29✔
917
    } else {
918
        mender_api_print_response_error(response, status);
×
919
        ret = MENDER_RETRY_ERROR;
×
920
    }
921

922
END:
29✔
923

924
    /* Release memory */
925
    mender_free(response);
29✔
926
    mender_free(payload);
29✔
927
    cJSON_Delete(inventory);
29✔
928

929
    return ret;
29✔
930
}
931

932
#endif /* CONFIG_MENDER_CLIENT_INVENTORY_DISABLE */
933

934
mender_err_t
935
mender_api_exit(void) {
×
936

937
    /* Release all modules */
938
    mender_http_exit();
×
939

940
    /* Destroy the authentication lock */
941
    mender_os_mutex_delete(auth_lock);
×
942

943
    /* Release memory */
944
    FREE_AND_NULL(api_jwt);
×
945

946
    return MENDER_OK;
×
947
}
948

949
static mender_err_t
950
mender_api_http_text_callback(mender_http_client_event_t event, void *data, size_t data_length, void *params) {
367✔
951

952
    assert(NULL != params);
367✔
953
    char       **response = (char **)params;
367✔
954
    mender_err_t ret      = MENDER_OK;
367✔
955
    char        *tmp;
956

957
    /* Treatment depending of the event */
958
    switch (event) {
367✔
959
        case MENDER_HTTP_EVENT_CONNECTED:
136✔
960
            /* Nothing to do */
961
            break;
136✔
962
        case MENDER_HTTP_EVENT_DATA_RECEIVED:
95✔
963
            /* Check input data */
964
            if ((NULL == data) || (0 == data_length)) {
95✔
965
                mender_log_error("Invalid data received");
×
966
                ret = MENDER_FAIL;
×
967
                break;
×
968
            }
969
            /* Concatenate data to the response */
970
            size_t response_length = (NULL != *response) ? strlen(*response) : 0;
95✔
971
            if (NULL == (tmp = mender_realloc(*response, response_length + data_length + 1))) {
95✔
972
                mender_log_error("Unable to allocate memory");
×
973
                ret = MENDER_FAIL;
×
974
                break;
×
975
            }
976
            *response = tmp;
95✔
977
            memcpy((*response) + response_length, data, data_length);
95✔
978
            *((*response) + response_length + data_length) = '\0';
95✔
979
            break;
95✔
980
        case MENDER_HTTP_EVENT_DISCONNECTED:
136✔
981
            /* Nothing to do */
982
            break;
136✔
983
        case MENDER_HTTP_EVENT_ERROR:
×
984
            /* Downloading the response fails */
985
            mender_log_error("An error occurred");
×
986
            ret = MENDER_FAIL;
×
987
            break;
×
988
        default:
×
989
            /* Should no occur */
990
            ret = MENDER_FAIL;
×
991
            break;
×
992
    }
993

994
    return ret;
367✔
995
}
996

997
void
998
mender_api_print_response_error(char *response, int status) {
2✔
999
    const char *desc;
1000

1001
    /* Treatment depending of the status */
1002
    if (NULL != (desc = mender_utils_http_status_to_string(status))) {
2✔
1003
        if (NULL != response) {
1✔
1004
            cJSON *json_response = cJSON_Parse(response);
1✔
1005
            if (NULL != json_response) {
1✔
1006
                cJSON *json_error = cJSON_GetObjectItemCaseSensitive(json_response, "error");
1✔
1007
                if (NULL != json_error) {
1✔
1008
                    mender_log_error("[%d] %s: %s", status, desc, cJSON_GetStringValue(json_error));
1✔
1009
                } else {
1010
                    mender_log_error("[%d] %s: unknown error", status, desc);
×
1011
                }
1012
                cJSON_Delete(json_response);
1✔
1013
            } else {
1014
                mender_log_error("[%d] %s: unknown error", status, desc);
×
1015
            }
1016
        } else {
1017
            mender_log_error("[%d] %s: unknown error", status, desc);
×
1018
        }
1019
    } else {
1020
        mender_log_error("Unknown error occurred, status=%d", status);
1✔
1021
    }
1022
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc