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

mendersoftware / mender-mcu / 2096440417

13 Oct 2025 12:38PM UTC coverage: 60.837% (-0.4%) from 61.268%
2096440417

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>

18 of 19 new or added lines in 2 files covered. (94.74%)

292 existing lines in 5 files now uncovered.

2456 of 4037 relevant lines covered (60.84%)

68.87 hits per line

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

62.08
/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
    assert(NULL != config->device_tier);
34✔
108

109
    mender_err_t ret;
110

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

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

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

126
    return ret;
34✔
127
}
128

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

141
    return ret;
6✔
142
}
143

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

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

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

160
static mender_err_t
161
ensure_authenticated_and_locked(void) {
126✔
162
    mender_err_t ret;
163

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

169
    if (NULL != api_jwt) {
126✔
170
        return MENDER_DONE;
96✔
171
    }
172

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

181
    return ret;
29✔
182
}
183

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

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

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

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

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

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

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

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

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

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

300
END:
29✔
301

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

311
    return ret;
29✔
312
}
313

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

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

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

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

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

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

381
    return ret;
106✔
382
}
383

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

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

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

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

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

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

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

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

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

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

454
    ret = MENDER_OK;
27✔
455

456
END:
27✔
457

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

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

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

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

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

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

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

495
    ret = MENDER_OK;
×
496

UNCOV
497
END:
×
498

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

UNCOV
502
    return ret;
×
503
}
504

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

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

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

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

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

605
END:
27✔
606

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

610
    return ret;
27✔
611
}
612

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

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

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

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

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

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

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

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

675
END:
45✔
676

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

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

695
    return ret;
45✔
696
}
697

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

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

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

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

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

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

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

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

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

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

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

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

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

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

806
END:
15✔
807
    cJSON_Delete(json_msg);
15✔
808
}
809

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

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

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

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

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

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

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

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

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

880
END:
9✔
881

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

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

892
#ifndef CONFIG_MENDER_CLIENT_INVENTORY_DISABLE
893

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

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

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

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

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

926
END:
29✔
927

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

933
    return ret;
29✔
934
}
935

936
#endif /* CONFIG_MENDER_CLIENT_INVENTORY_DISABLE */
937

938
mender_err_t
939
mender_api_exit(void) {
5✔
940

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

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

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

950
    return MENDER_OK;
5✔
951
}
952

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

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

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

998
    return ret;
395✔
999
}
1000

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

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