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

mendersoftware / mender-mcu / 2092230987

10 Oct 2025 08:32AM UTC coverage: 61.268% (+3.9%) from 57.388%
2092230987

push

gitlab-ci

danielskinstad
feat: send tier in authentication request

Ticket: MEN-8559
Changelog: Add device tier support to authentication requests. The client now
sends a 'tier' parameter in authentication requests, supporting "standard"
(default) and "micro" tiers. The tier is configurable via Kconfig or
through the client config struct before initialization, which will take
precedence over the Kconfig option.

Signed-off-by: Daniel Skinstad Drabitzius <daniel.drabitzius@northern.tech>

17 of 18 new or added lines in 2 files covered. (94.44%)

4 existing lines in 1 file now uncovered.

2474 of 4038 relevant lines covered (61.27%)

70.16 hits per line

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

62.85
/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) {
34✔
103
    assert(NULL != config);
34✔
104
    assert(NULL != config->device_type);
34✔
105
    assert(NULL != config->host);
34✔
106
    assert(NULL != config->identity_cb);
34✔
107

108
    mender_err_t ret;
109

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

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

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

125
    return ret;
34✔
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 != api_config.device_tier) {
30✔
234
        cJSON_AddStringToObject(json_payload, "tier", api_config.device_tier);
30✔
235
    }
236
    if (NULL == (payload = cJSON_PrintUnformatted(json_payload))) {
30✔
237
        mender_log_error("Unable to allocate memory");
×
238
        ret = MENDER_FAIL;
×
239
        goto END;
×
240
    }
241

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

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

265
            /* Just in case something was already gathered as response. */
266
            FREE_AND_NULL(response);
2✔
267
        }
268
    } while ((MENDER_RETRY_ERROR == ret) && (remaining_attempts > 0));
31✔
269

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

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

299
END:
29✔
300

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

310
    return ret;
29✔
311
}
312

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

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

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

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

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

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

380
    return ret;
107✔
381
}
382

383
static mender_err_t
384
api_check_for_deployment_v2(int *status, char **response) {
39✔
385
    assert(NULL != status);
39✔
386
    assert(NULL != response);
39✔
387

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

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

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

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

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

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

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

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

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

453
    ret = MENDER_OK;
27✔
454

455
END:
27✔
456

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

467
static mender_err_t
468
api_check_for_deployment_v1(int *status, char **response) {
×
469

470
    assert(NULL != status);
×
471
    assert(NULL != response);
×
472

473
    mender_err_t ret           = MENDER_FAIL;
×
474
    char        *path          = NULL;
×
475
    const char  *artifact_name = NULL;
×
476

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

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

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

494
    ret = MENDER_OK;
×
495

496
END:
×
497

498
    /* Release memory */
499
    mender_free(path);
×
500

501
    return ret;
×
502
}
503

504
mender_err_t
505
mender_api_check_for_deployment(mender_api_deployment_data_t *deployment) {
39✔
506

507
    assert(NULL != deployment);
39✔
508
    mender_err_t ret      = MENDER_FAIL;
39✔
509
    char        *response = NULL;
39✔
510
    int          status   = 0;
39✔
511

512
    if (MENDER_FAIL == (ret = api_check_for_deployment_v2(&status, &response))) {
39✔
513
        goto END;
×
514
    }
515

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

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

604
END:
27✔
605

606
    /* Release memory */
607
    mender_free(response);
27✔
608

609
    return ret;
27✔
610
}
611

612
#ifdef CONFIG_MENDER_DEPLOYMENT_LOGS
613
static mender_err_t mender_api_publish_deployment_logs(const char *id);
614
#endif /* CONFIG_MENDER_DEPLOYMENT_LOGS */
615

616
mender_err_t
617
mender_api_publish_deployment_status(const char *id, mender_deployment_status_t deployment_status) {
47✔
618
    assert(NULL != id);
47✔
619

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

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

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

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

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

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

674
END:
45✔
675

676
    /* Release memory */
677
    mender_free(response);
45✔
678
    mender_free(path);
45✔
679
    mender_free(payload);
45✔
680
    cJSON_Delete(json_payload);
45✔
681

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

694
    return ret;
45✔
695
}
696

697
#ifdef CONFIG_MENDER_DEPLOYMENT_LOGS
698
static void
699
append_depl_log_msg(char *msg, void *ctx) {
19✔
700
    assert(NULL != ctx);
19✔
701

702
    char  *tstamp   = NULL;
19✔
703
    char  *level    = NULL;
19✔
704
    char  *log_msg  = NULL;
19✔
705
    cJSON *json_msg = NULL;
19✔
706
    cJSON *messages = ctx;
19✔
707

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

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

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

734
    if (' ' == *c) {
19✔
735
        /* skip the space */
736
        c++;
19✔
737
    }
738

739
    if ('<' == *c) {
19✔
740
        /* if the log level follow, like above, store the pointer and find its end */
741
        c++;
19✔
742
        level = c;
19✔
743
        while (('\0' != *c) && ('>' != *c)) {
76✔
744
            c++;
57✔
745
        }
746
        if ('\0' == *c) {
19✔
747
            goto DONE_PARSING;
748
        }
749
        *c = '\0';
19✔
750
        c++;
19✔
751
    }
752

753
    if (' ' == *c) {
19✔
754
        /* skip the space */
755
        c++;
19✔
756
    }
757

758
    if ('\0' != *c) {
19✔
759
        log_msg = c;
19✔
760
        if (mender_utils_strbeginswith(log_msg, "mender: ")) {
19✔
761
            log_msg += strlen("mender: ");
19✔
762
        }
763
    }
764

765
DONE_PARSING:
766
    if (NULL == (json_msg = cJSON_CreateObject())) {
19✔
767
        mender_log_error("Unable to allocate memory");
768
        return;
769
    }
770

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

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

795
    if (NULL == cJSON_AddStringToObject(json_msg, "message", log_msg)) {
19✔
796
        mender_log_error("Unable to allocate memory");
797
        goto END;
798
    }
799

800
    if (!cJSON_AddItemToArray(messages, json_msg)) {
19✔
801
        mender_log_error("Unable to allocate memory");
802
    }
803
    json_msg = NULL;
19✔
804

805
END:
19✔
806
    cJSON_Delete(json_msg);
19✔
807
}
808

809
static mender_err_t
810
mender_api_publish_deployment_logs(const char *id) {
9✔
811
    assert(NULL != id);
9✔
812

813
    mender_err_t ret;
814
    cJSON       *json_payload  = NULL;
9✔
815
    cJSON       *json_messages = NULL;
9✔
816
    char        *payload       = NULL;
9✔
817
    char        *path          = NULL;
9✔
818
    char        *response      = NULL;
9✔
819
    int          status        = 0;
9✔
820

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

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

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

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

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

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

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

879
END:
9✔
880

881
    /* Release memory */
882
    mender_free(response);
9✔
883
    mender_free(path);
9✔
884
    mender_free(payload);
9✔
885
    cJSON_Delete(json_payload);
9✔
886

887
    return ret;
9✔
888
}
889
#endif /* CONFIG_MENDER_DEPLOYMENT_LOGS */
890

891
#ifndef CONFIG_MENDER_CLIENT_INVENTORY_DISABLE
892

893
mender_err_t
894
mender_api_publish_inventory_data(cJSON *inventory, bool patch) {
29✔
895

896
    mender_err_t ret;
897
    char        *payload  = NULL;
29✔
898
    char        *response = NULL;
29✔
899
    int          status   = 0;
29✔
900

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

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

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

925
END:
29✔
926

927
    /* Release memory */
928
    mender_free(response);
29✔
929
    mender_free(payload);
29✔
930
    cJSON_Delete(inventory);
29✔
931

932
    return ret;
29✔
933
}
934

935
#endif /* CONFIG_MENDER_CLIENT_INVENTORY_DISABLE */
936

937
mender_err_t
938
mender_api_exit(void) {
5✔
939

940
    /* Release all modules */
941
    mender_http_exit();
5✔
942

943
    /* Destroy the authentication lock */
944
    mender_os_mutex_delete(auth_lock);
5✔
945

946
    /* Release memory */
947
    FREE_AND_NULL(api_jwt);
5✔
948

949
    return MENDER_OK;
5✔
950
}
951

952
static mender_err_t
953
mender_api_http_text_callback(mender_http_client_event_t event, void *data, size_t data_length, void *params) {
397✔
954

955
    assert(NULL != params);
397✔
956
    char       **response = (char **)params;
397✔
957
    mender_err_t ret      = MENDER_OK;
397✔
958
    char        *tmp;
959

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

997
    return ret;
397✔
998
}
999

1000
void
1001
mender_api_print_response_error(char *response, int status) {
2✔
1002
    const char *desc;
1003

1004
    /* Treatment depending of the status */
1005
    if (NULL != (desc = mender_utils_http_status_to_string(status))) {
2✔
1006
        if (NULL != response) {
1✔
1007
            cJSON *json_response = cJSON_Parse(response);
1✔
1008
            if (NULL != json_response) {
1✔
1009
                cJSON *json_error = cJSON_GetObjectItemCaseSensitive(json_response, "error");
1✔
1010
                if (NULL != json_error) {
1✔
1011
                    mender_log_error("[%d] %s: %s", status, desc, cJSON_GetStringValue(json_error));
1✔
1012
                } else {
1013
                    mender_log_error("[%d] %s: unknown error", status, desc);
×
1014
                }
1015
                cJSON_Delete(json_response);
1✔
1016
            } else {
1017
                mender_log_error("[%d] %s: unknown error", status, desc);
×
1018
            }
1019
        } else {
1020
            mender_log_error("[%d] %s: unknown error", status, desc);
×
1021
        }
1022
    } else {
1023
        mender_log_error("Unknown error occurred, status=%d", status);
1✔
1024
    }
1025
}
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