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

mendersoftware / mender-mcu / 2099574738

14 Oct 2025 08:53AM UTC coverage: 60.837% (+3.4%) from 57.388%
2099574738

push

gitlab-ci

web-flow
Merge pull request #214 from danielskinstad/device-tiers

feat: send tier in authentication request

19 of 20 new or added lines in 3 files covered. (95.0%)

21 existing lines in 4 files now uncovered.

2456 of 4037 relevant lines covered (60.84%)

68.91 hits per line

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

73.22
/src/platform/net/zephyr/http.c
1
/**
2
 * @file      http.c
3
 * @brief     Mender HTTP interface for Zephyr platform
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 <version.h>
22
#include <zephyr/net/http/client.h>
23
#include <zephyr/kernel.h>
24
#include "api.h"
25
#include "http.h"
26
#include "log.h"
27

28
#include "net.h"
29

30
/* Zephyr versions 4.2.0 and above return int, versions below 4.2.0 return void */
31
#if (ZEPHYR_VERSION_CODE < ZEPHYR_VERSION(4, 2, 0))
32
#define HTTP_CALLBACK_RETURN_VALUE
33
#define HTTP_CALLBACK_RETURN_TYPE void
34
#else
35
#define HTTP_CALLBACK_RETURN_VALUE 0
36
#define HTTP_CALLBACK_RETURN_TYPE  int
37
#endif
38

39
/**
40
 * @brief HTTP User-Agent
41
 */
42
#define MENDER_HEADER_HTTP_USER_AGENT "User-Agent: Mender/" MENDER_CLIENT_VERSION " MCU Zephyr/" KERNEL_VERSION_STRING "\r\n"
43

44
/**
45
 * @brief Request timeout (milliseconds)
46
 */
47
#define MENDER_HTTP_REQUEST_TIMEOUT (60 * 1000)
48

49
const size_t mender_http_recv_buf_length = 512;
50

51
/**
52
 * @brief Request context
53
 */
54
typedef struct {
55
    mender_err_t (*callback)(mender_http_client_event_t, void *, size_t, void *); /**< Callback to be invoked when data are received */
56
    void        *params;                                                          /**< Callback parameters */
57
    mender_err_t ret;                                                             /**< Last callback return value */
58
} mender_http_request_context;
59

60
/**
61
 * @brief Mender HTTP configuration
62
 */
63
static mender_http_config_t http_config;
64

65
/**
66
 * @brief HTTP response callback, invoked to handle data received
67
 * @param response HTTP response structure
68
 * @param final_call Indicate final call
69
 * @param user_data User data, used to retrieve request context data
70
 * @return HTTP_CALLBACK_RETURN_VALUE, 0 if Zephyr >= 4.2.0, void otherwise
71
 */
72
static HTTP_CALLBACK_RETURN_TYPE http_response_cb(struct http_response *response, enum http_final_call final_call, void *user_data);
73

74
/**
75
 * @brief HTTP artifact response callback, invoked to handle data received
76
 * @param response HTTP response structure
77
 * @param final_call Indicate final call
78
 * @param user_data User data, used to retrieve request context data
79
 * @return HTTP_CALLBACK_RETURN_VALUE, 0 if Zephyr >= 4.2.0, void otherwise
80
 */
81
static HTTP_CALLBACK_RETURN_TYPE artifact_response_cb(struct http_response *response, enum http_final_call final_call, void *user_data);
82

83
/**
84
 * @brief Convert mender HTTP method to Zephyr HTTP client method
85
 * @param method Mender HTTP method
86
 * @return Zephyr HTTP client method if the function succeeds, -1 otherwise
87
 */
88
static enum http_method http_method_to_zephyr_http_client_method(mender_http_method_t method);
89

90
mender_err_t
91
mender_http_init(mender_http_config_t *config) {
29✔
92

93
    assert(NULL != config);
29✔
94
    assert(NULL != config->host);
29✔
95

96
    /* Save configuration */
97
    memcpy(&http_config, config, sizeof(mender_http_config_t));
29✔
98

99
    return MENDER_OK;
29✔
100
}
101

102
/* Request built will look like this:
103
    GET https://hosted.mender.io/api/devices/v1/deployments/artifacts/{id} HTTP/1.1
104
    Host: hosted.mender.io
105
    User-Agent: Mender/2.0.0 MCU Zephyr/2.7.0
106
    Authorization: Bearer <jwt token>
107
    X-MEN-Signature: <string>
108
    Content-Type: application/json
109
*/
110
mender_err_t
111
mender_http_perform(char                *jwt,
150✔
112
                    char                *path,
113
                    mender_http_method_t method,
114
                    char                *payload,
115
                    char                *signature,
116
                    mender_err_t (*callback)(mender_http_client_event_t, void *, size_t, void *),
117
                    void *params,
118
                    int  *status) {
119

120
    assert(NULL != path);
150✔
121
    assert(NULL != callback);
150✔
122
    assert(NULL != status);
150✔
123
    mender_err_t                ret                = MENDER_FAIL;
150✔
124
    struct http_request         request            = { 0 };
150✔
125
    mender_http_request_context request_context    = { .callback = callback, .params = params, .ret = MENDER_OK };
150✔
126
    const char                 *header_fields[6]   = { NULL }; /* The list is NULL terminated; make sure the size reflects it */
150✔
127
    size_t                      header_fields_size = sizeof(header_fields) / sizeof(header_fields[0]);
150✔
128
    char                       *host               = NULL;
150✔
129
    char                       *port               = NULL;
150✔
130
    char                       *url                = NULL;
150✔
131
    int                         sock               = -1;
150✔
132
    int                         http_req_ret;
133

134
    /* Headers to be added to the request */
135
    char *host_header      = NULL;
150✔
136
    char *auth_header      = NULL;
150✔
137
    char *signature_header = NULL;
150✔
138

139
    /* Retrieve host, port and url */
140
    if (MENDER_OK != mender_net_get_host_port_url(path, http_config.host, &host, &port, &url)) {
150✔
141
        mender_log_error("Unable to retrieve host/port/url");
×
142
        goto END;
×
143
    }
144

145
    /* Configuration of the client */
146
    request.method      = http_method_to_zephyr_http_client_method(method);
150✔
147
    request.url         = url;
150✔
148
    request.host        = host;
150✔
149
    request.protocol    = "HTTP/1.1";
150✔
150
    request.payload     = payload;
150✔
151
    request.payload_len = (NULL != payload) ? strlen(payload) : 0;
150✔
152
    request.response    = http_response_cb;
150✔
153
    if (NULL == (request.recv_buf = (uint8_t *)mender_malloc(mender_http_recv_buf_length))) {
150✔
154
        mender_log_error("Unable to allocate memory");
×
155
        goto END;
×
156
    }
157
    request.recv_buf_len = mender_http_recv_buf_length;
150✔
158

159
    /* Add headers */
160

161
    if (MENDER_FAIL == header_add(header_fields, header_fields_size, MENDER_HEADER_HTTP_USER_AGENT)) {
150✔
162
        mender_log_error("Unable to add 'User-Agent' header");
×
163
        goto END;
×
164
    }
165

166
    if (NULL != jwt) {
150✔
167
        auth_header = header_alloc_and_add(header_fields, header_fields_size, "Authorization: Bearer %s\r\n", jwt);
120✔
168
        if (NULL == auth_header) {
120✔
169
            mender_log_error("Unable to add 'Authorization' header");
×
170
            goto END;
×
171
        }
172
    }
173

174
    if (NULL != signature) {
150✔
175
        signature_header = header_alloc_and_add(header_fields, header_fields_size, "X-MEN-Signature: %s\r\n", signature);
30✔
176
        if (NULL == signature_header) {
30✔
177
            mender_log_error("Unable to add 'X-MEN-Signature' header");
×
178
            goto END;
×
179
        }
180
    }
181

182
    if (NULL != payload) {
150✔
183
        if (MENDER_FAIL == header_add(header_fields, header_fields_size, "Content-Type: application/json\r\n")) {
150✔
184
            mender_log_error("Unable to add 'Content-Type' header");
×
185
            goto END;
×
186
        }
187
    }
188

189
    request.header_fields = header_fields;
150✔
190

191
    /* Connect to the server */
192
    sock = mender_net_connect(host, port);
150✔
193
    if (sock < 0) {
135✔
UNCOV
194
        mender_log_error("Unable to open HTTP client connection");
×
UNCOV
195
        goto END;
×
196
    }
197
    if (MENDER_OK != (ret = callback(MENDER_HTTP_EVENT_CONNECTED, NULL, 0, params))) {
135✔
198
        mender_log_error("An error occurred while calling 'MENDER_HTTP_EVENT_CONNECTED' callback");
×
199
        goto END;
×
200
    }
201

202
    /* Perform HTTP request */
203
    if ((http_req_ret = http_client_req(sock, &request, MENDER_HTTP_REQUEST_TIMEOUT, (void *)&request_context)) < 0) {
135✔
204
        mender_log_error("HTTP request failed: %s", strerror(-http_req_ret));
×
205
        goto END;
×
206
    }
207

208
    /* Check if an error occured during the treatment of data */
209
    if (MENDER_OK != (ret = request_context.ret)) {
135✔
210
        goto END;
×
211
    }
212

213
    /* Read HTTP status code */
214
    if (0 == request.internal.response.http_status_code) {
135✔
215
        mender_log_error("An error occurred, connection has been closed");
×
216
        callback(MENDER_HTTP_EVENT_ERROR, NULL, 0, params);
×
217
        goto END;
×
218
    } else {
219
        *status = request.internal.response.http_status_code;
135✔
220
    }
221
    if (MENDER_OK != (ret = callback(MENDER_HTTP_EVENT_DISCONNECTED, NULL, 0, params))) {
135✔
222
        mender_log_error("An error occurred while calling 'MENDER_HTTP_EVENT_DISCONNECTED' callback");
×
223
        goto END;
×
224
    }
225

226
    ret = MENDER_OK;
135✔
227

228
END:
135✔
229

230
    /* Close connection */
231
    if (sock >= 0) {
135✔
232
        mender_net_disconnect(sock);
135✔
233
    }
234

235
    /* Release memory */
236
    mender_free(host);
135✔
237
    mender_free(port);
135✔
238
    mender_free(url);
135✔
239
    mender_free(host_header);
135✔
240
    mender_free(auth_header);
135✔
241
    mender_free(signature_header);
135✔
242

243
    mender_free(request.recv_buf);
135✔
244

245
    /* Return MENDER_RETRY_ERROR if ret is MENDER_FAIL, otherwise return ret */
246
    return (MENDER_FAIL != ret) ? ret : MENDER_RETRY_ERROR;
135✔
247
}
248

249
mender_err_t
250
mender_http_artifact_download(const char *uri, mender_artifact_download_data_t *dl_data, int *status) {
12✔
251
    assert(NULL != dl_data);
12✔
252
    assert(NULL != status);
12✔
253

254
    mender_err_t        ret                = MENDER_FAIL;
12✔
255
    struct http_request request            = { 0 };
12✔
256
    mender_err_t        request_ret        = MENDER_OK;
12✔
257
    const char         *header_fields[3]   = { NULL }; /* The list is NULL terminated; make sure the size reflects it */
12✔
258
    size_t              header_fields_size = sizeof(header_fields) / sizeof(header_fields[0]);
12✔
259
    char               *host               = NULL;
12✔
260
    char               *port               = NULL;
12✔
261
    char               *url                = NULL;
12✔
262
    int                 sock               = -1;
12✔
263
    int                 http_req_ret;
264

265
    /* Headers to be added to the request */
266
    char *host_header = NULL;
12✔
267

268
    /* Retrieve host, port and url */
269
    if (MENDER_OK != mender_net_get_host_port_url(uri, http_config.host, &host, &port, &url)) {
12✔
270
        mender_log_error("Unable to retrieve host/port/url");
×
271
        goto END;
×
272
    }
273

274
    /* Configuration of the client */
275
    request.method   = http_method_to_zephyr_http_client_method(MENDER_HTTP_GET);
12✔
276
    request.url      = url;
12✔
277
    request.host     = host;
12✔
278
    request.protocol = "HTTP/1.1";
12✔
279
    request.response = artifact_response_cb;
12✔
280
    if (NULL == (request.recv_buf = (uint8_t *)mender_malloc(mender_http_recv_buf_length))) {
12✔
281
        mender_log_error("Unable to allocate memory");
×
282
        goto END;
×
283
    }
284
    request.recv_buf_len = mender_http_recv_buf_length;
12✔
285

286
    /* Add headers */
287
    host_header = header_alloc_and_add(header_fields, header_fields_size, "Host: %s\r\n", host);
12✔
288
    if (NULL == host_header) {
12✔
289
        mender_log_error("Unable to add 'Host' header");
×
290
        goto END;
×
291
    }
292
    if (MENDER_FAIL == header_add(header_fields, header_fields_size, MENDER_HEADER_HTTP_USER_AGENT)) {
12✔
293
        mender_log_error("Unable to add 'User-Agent' header");
×
294
        goto END;
×
295
    }
296
    request.header_fields = header_fields;
12✔
297

298
    /* Connect to the server */
299
    sock = mender_net_connect(host, port);
12✔
300
    if (sock < 0) {
12✔
301
        mender_log_error("Unable to open HTTP client connection");
×
302
        goto END;
×
303
    }
304
    if (MENDER_OK != (ret = dl_data->artifact_download_callback(MENDER_HTTP_EVENT_CONNECTED, NULL, 0, dl_data))) {
12✔
305
        mender_log_error("An error occurred while calling 'MENDER_HTTP_EVENT_CONNECTED' artifact callback");
×
306
        goto END;
×
307
    }
308

309
    /* Perform HTTP request */
310
    if ((http_req_ret = http_client_req(sock, &request, MENDER_HTTP_REQUEST_TIMEOUT, dl_data)) < 0) {
12✔
311
        mender_log_error("HTTP request failed: %s", strerror(-http_req_ret));
×
312
        goto END;
×
313
    }
314

315
    /* Artifact download failed*/
316
    if (MENDER_OK != dl_data->ret) {
12✔
317
        goto END;
1✔
318
    }
319

320
    /* Check if an error occured during the treatment of data */
321
    if (MENDER_OK != (ret = request_ret)) {
11✔
322
        goto END;
×
323
    }
324

325
    /* Read HTTP status code */
326
    if (0 == request.internal.response.http_status_code) {
11✔
327
        mender_log_error("An error occurred, connection has been closed");
×
328
        dl_data->artifact_download_callback(MENDER_HTTP_EVENT_ERROR, NULL, 0, dl_data);
×
329
        goto END;
×
330
    } else {
331
        *status = request.internal.response.http_status_code;
11✔
332
    }
333
    if (MENDER_OK != (ret = dl_data->artifact_download_callback(MENDER_HTTP_EVENT_DISCONNECTED, NULL, 0, dl_data))) {
11✔
334
        mender_log_error("An error occurred while calling 'MENDER_HTTP_EVENT_DISCONNECTED' artifact callback");
×
335
        goto END;
×
336
    }
337

338
    ret = MENDER_OK;
11✔
339

340
END:
12✔
341

342
    /* Close connection */
343
    if (sock >= 0) {
12✔
344
        mender_net_disconnect(sock);
12✔
345
    }
346

347
    /* Release memory */
348
    mender_free(host);
12✔
349
    mender_free(port);
12✔
350
    mender_free(url);
12✔
351
    mender_free(host_header);
12✔
352

353
    mender_free(request.recv_buf);
12✔
354

355
    /* Return MENDER_RETRY_ERROR if ret is MENDER_FAIL, otherwise return ret */
356
    return (MENDER_FAIL != ret) ? ret : MENDER_RETRY_ERROR;
12✔
357
}
358

359
mender_err_t
360
mender_http_exit(void) {
×
361

362
    /* Nothing to do */
363
    return MENDER_OK;
×
364
}
365

366
static HTTP_CALLBACK_RETURN_TYPE
367
http_response_cb(struct http_response *response, MENDER_ARG_UNUSED enum http_final_call final_call, void *user_data) {
218✔
368
    assert(NULL != response);
218✔
369
    assert(NULL != user_data);
218✔
370

371
    /* Retrieve request context */
372
    mender_http_request_context *request_context = (mender_http_request_context *)user_data;
218✔
373

374
    /* Check if data is available */
375
    if ((true == response->body_found) && (NULL != response->body_frag_start) && (0 != response->body_frag_len) && (MENDER_OK == request_context->ret)) {
218✔
376

377
        /* Transmit data received to the upper layer */
378
        if (MENDER_OK
125✔
379
            != (request_context->ret = request_context->callback(
125✔
380
                    MENDER_HTTP_EVENT_DATA_RECEIVED, (void *)response->body_frag_start, response->body_frag_len, request_context->params))) {
125✔
381
            mender_log_error("An error occurred, stop reading data");
×
382
        }
383
    }
384
    return HTTP_CALLBACK_RETURN_VALUE;
218✔
385
}
386

387
static HTTP_CALLBACK_RETURN_TYPE
388
artifact_response_cb(struct http_response *response, MENDER_ARG_UNUSED enum http_final_call final_call, void *user_data) {
260✔
389

390
    assert(NULL != response);
260✔
391
    assert(NULL != user_data);
260✔
392

393
    /* Retrieve request context */
394
    mender_artifact_download_data_t *dl_data = user_data;
260✔
395

396
    /* Check if data is available */
397
    if (response->body_found && (NULL != response->body_frag_start) && (0 != response->body_frag_len) && (MENDER_OK == (dl_data->ret))) {
260✔
398
        /* Transmit data received to the upper layer */
399
        dl_data->ret
400
            = dl_data->artifact_download_callback(MENDER_HTTP_EVENT_DATA_RECEIVED, (void *)response->body_frag_start, response->body_frag_len, dl_data);
252✔
401
        if (MENDER_OK != (dl_data->ret)) {
252✔
402
            mender_log_error("An error occurred, stop reading data");
1✔
403
        }
404
    }
405
    return HTTP_CALLBACK_RETURN_VALUE;
260✔
406
}
407

408
static enum http_method
409
http_method_to_zephyr_http_client_method(mender_http_method_t method) {
162✔
410

411
    /* Convert method */
412
    switch (method) {
162✔
413
        case MENDER_HTTP_GET:
12✔
414
            return HTTP_GET;
12✔
415
        case MENDER_HTTP_POST:
69✔
416
            return HTTP_POST;
69✔
417
        case MENDER_HTTP_PUT:
80✔
418
            return HTTP_PUT;
80✔
419
        case MENDER_HTTP_PATCH:
1✔
420
            return HTTP_PATCH;
1✔
421
        default:
×
422
            return -1;
×
423
    }
424
}
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