1 /*
2 * Copyright (c) 2022-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "cert_manager_storage.h"
17
18 #include <openssl/x509.h>
19 #include <unistd.h>
20
21 #include "cert_manager_file_operator.h"
22 #include "cert_manager_mem.h"
23 #include "cert_manager_uri.h"
24 #include "cm_log.h"
25 #include "cm_type.h"
26 #include "securec.h"
27
GetRootPath(uint32_t store,char * rootPath,uint32_t pathLen)28 int32_t GetRootPath(uint32_t store, char *rootPath, uint32_t pathLen)
29 {
30 errno_t ret;
31
32 /* keep \0 at end */
33 switch (store) {
34 case CM_CREDENTIAL_STORE:
35 ret = memcpy_s(rootPath, pathLen - 1, CREDNTIAL_STORE, strlen(CREDNTIAL_STORE));
36 break;
37 case CM_SYSTEM_TRUSTED_STORE:
38 ret = memcpy_s(rootPath, pathLen - 1, SYSTEM_CA_STORE, strlen(SYSTEM_CA_STORE));
39 break;
40 case CM_USER_TRUSTED_STORE:
41 ret = memcpy_s(rootPath, pathLen - 1, USER_CA_STORE, strlen(USER_CA_STORE));
42 break;
43 case CM_PRI_CREDENTIAL_STORE:
44 ret = memcpy_s(rootPath, pathLen - 1, PRI_CREDNTIAL_STORE, strlen(PRI_CREDNTIAL_STORE));
45 break;
46 case CM_SYS_CREDENTIAL_STORE:
47 ret = memcpy_s(rootPath, pathLen - 1, SYS_CREDNTIAL_STORE, strlen(SYS_CREDNTIAL_STORE));
48 break;
49 default:
50 return CMR_ERROR_INVALID_ARGUMENT_STORE_TYPE;
51 }
52
53 if (ret != EOK) {
54 CM_LOG_E("copy path failed, store = %u", store);
55 return CMR_ERROR_MEM_OPERATION_COPY;
56 }
57
58 return CM_SUCCESS;
59 }
60
ConstructUserIdPath(const struct CmContext * context,uint32_t store,char * userIdPath,uint32_t pathLen)61 int32_t ConstructUserIdPath(const struct CmContext *context, uint32_t store,
62 char *userIdPath, uint32_t pathLen)
63 {
64 char rootPath[CERT_MAX_PATH_LEN] = { 0 };
65 int32_t ret = GetRootPath(store, rootPath, CERT_MAX_PATH_LEN);
66 if (ret != CM_SUCCESS) {
67 return ret;
68 }
69
70 if (snprintf_s(userIdPath, pathLen, pathLen - 1, "%s%u", rootPath, context->userId) < 0) {
71 CM_LOG_E("construct user id path failed");
72 return CMR_ERROR_MEM_OPERATION_PRINT;
73 }
74
75 ret = CmMakeDir(userIdPath);
76 if (ret == CMR_ERROR_MAKE_DIR_FAIL) {
77 CM_LOG_E("mkdir userId path failed");
78 return ret;
79 } /* ret may be CMR_ERROR_ALREADY_EXISTS */
80
81 return CM_SUCCESS;
82 }
83
ConstructUidPath(const struct CmContext * context,uint32_t store,char * uidPath,uint32_t pathLen)84 int32_t ConstructUidPath(const struct CmContext *context, uint32_t store,
85 char *uidPath, uint32_t pathLen)
86 {
87 char userIdPath[CERT_MAX_PATH_LEN] = { 0 };
88 int32_t ret = ConstructUserIdPath(context, store, userIdPath, CERT_MAX_PATH_LEN);
89 if (ret != CM_SUCCESS) {
90 return ret;
91 }
92
93 if (snprintf_s(uidPath, pathLen, pathLen - 1, "%s/%u", userIdPath, context->uid) < 0) {
94 CM_LOG_E("construct uid path failed");
95 return CMR_ERROR_MEM_OPERATION_PRINT;
96 }
97
98 ret = CmMakeDir(uidPath);
99 if (ret == CMR_ERROR_MAKE_DIR_FAIL) {
100 CM_LOG_E("mkdir uid path failed");
101 return ret;
102 } /* ret may be CMR_ERROR_ALREADY_EXISTS */
103
104 return CM_SUCCESS;
105 }
106
ConstructAuthListPath(const struct CmContext * context,uint32_t store,char * authListPath,uint32_t pathLen)107 int32_t ConstructAuthListPath(const struct CmContext *context, uint32_t store,
108 char *authListPath, uint32_t pathLen)
109 {
110 char uidPath[CERT_MAX_PATH_LEN] = { 0 };
111 int32_t ret = ConstructUidPath(context, store, uidPath, CERT_MAX_PATH_LEN);
112 if (ret != CM_SUCCESS) {
113 return ret;
114 }
115
116 if (snprintf_s(authListPath, pathLen, pathLen - 1, "%s/%s", uidPath, "authlist") < 0) {
117 CM_LOG_E("construct authlist failed");
118 return CMR_ERROR_MEM_OPERATION_PRINT;
119 }
120
121 ret = CmMakeDir(authListPath);
122 if (ret == CMR_ERROR_MAKE_DIR_FAIL) {
123 CM_LOG_E("mkdir auth list path failed");
124 return ret;
125 } /* ret may be CMR_ERROR_ALREADY_EXISTS */
126
127 return CM_SUCCESS;
128 }
129
CmStorageGetBuf(const char * path,const char * fileName,struct CmBlob * storageBuf)130 int32_t CmStorageGetBuf(const char *path, const char *fileName, struct CmBlob *storageBuf)
131 {
132 uint32_t fileSize = CmFileSize(path, fileName);
133 if (fileSize > MAX_OUT_BLOB_SIZE) {
134 CM_LOG_E("file size[%u] invalid", fileSize);
135 return CMR_ERROR_STORAGE;
136 }
137
138 if (fileSize == 0) {
139 CM_LOG_E("file is not exist");
140 return CMR_ERROR_NOT_EXIST;
141 }
142
143 uint8_t *data = (uint8_t *)CMMalloc(fileSize);
144 if (data == NULL) {
145 CM_LOG_E("malloc file buffer failed");
146 return CMR_ERROR_MALLOC_FAIL;
147 }
148
149 uint32_t readSize = CmFileRead(path, fileName, 0, data, fileSize);
150 if (readSize == 0) {
151 CM_LOG_E("read file size 0 invalid");
152 CMFree(data);
153 return CMR_ERROR_NOT_EXIST;
154 }
155
156 storageBuf->data = data;
157 storageBuf->size = fileSize;
158 return CM_SUCCESS;
159 }
160
CmStorageGetAppCert(const struct CmContext * context,uint32_t store,const struct CmBlob * keyUri,struct CmBlob * certBlob)161 int32_t CmStorageGetAppCert(const struct CmContext *context, uint32_t store,
162 const struct CmBlob *keyUri, struct CmBlob *certBlob)
163 {
164 uint32_t uid = 0;
165 int32_t ret = CertManagerGetUidFromUri(keyUri, &uid);
166 if (ret != CM_SUCCESS) {
167 return ret;
168 }
169
170 struct CmContext uriContext = { context->userId, uid, { 0 } };
171 char uidPath[CERT_MAX_PATH_LEN] = { 0 };
172 ret = ConstructUidPath(&uriContext, store, uidPath, CERT_MAX_PATH_LEN);
173 if (ret != CM_SUCCESS) {
174 return ret;
175 }
176
177 return CmStorageGetBuf(uidPath, (const char *)keyUri->data, certBlob);
178 }
179
180 // LCOV_EXCL_START
CmGetCertFilePath(const struct CmContext * context,uint32_t store,struct CmMutableBlob * pathBlob)181 int32_t CmGetCertFilePath(const struct CmContext *context, uint32_t store, struct CmMutableBlob *pathBlob)
182 {
183 char pathPtr[CERT_MAX_PATH_LEN] = {0};
184
185 if ((pathBlob == NULL) || (pathBlob->data == NULL)) {
186 CM_LOG_E("Null pointer failure");
187 return CMR_ERROR_NULL_POINTER;
188 }
189
190 int32_t ret = ConstructUidPath(context, store, pathPtr, CERT_MAX_PATH_LEN);
191 if (ret != CM_SUCCESS) {
192 CM_LOG_E("Get file path faild");
193 return ret;
194 }
195
196 char *path = (char *)pathBlob->data;
197 if (sprintf_s(path, CERT_MAX_PATH_LEN, "%s", pathPtr) < 0) {
198 return CMR_ERROR_MEM_OPERATION_PRINT;
199 }
200 pathBlob->size = strlen(path) + 1;
201
202 return CM_SUCCESS;
203 }
204
205 /**
206 * @brief Construct the absolute path to the {confRootDir} directory
207 *
208 * @param[out] confRootDir The buffer that holds the absolute path of the {confRootDir} directory
209 * @param[in] dirLen Maximum length of the confRootDir buffer
210 * @return int32_t result
211 * @retval 0 success
212 * @retval <0 failure
213 */
GetCertConfRootDir(char * confRootDir,uint32_t dirLen)214 static int32_t GetCertConfRootDir(char *confRootDir, uint32_t dirLen)
215 {
216 int32_t ret = CM_SUCCESS;
217 mode_t mode = 0;
218
219 if (confRootDir == NULL) {
220 CM_LOG_E("Input params invalid");
221 return CMR_ERROR_INVALID_ARGUMENT;
222 }
223
224 if (dirLen < sizeof(CERT_BACKUP_CONFIG_ROOT_DIR)) {
225 CM_LOG_E("dirLen(%u) is too small for save user cert backup config file root path", dirLen);
226 return CMR_ERROR_BUFFER_TOO_SMALL;
227 }
228
229 /* Create the root directory for storing user backup config files */
230 mode = S_IRWXU; /* The permission on the user_config directory should be 0700 */
231 ret = CmUserBakupMakeDir(CERT_BACKUP_CONFIG_ROOT_DIR, (const mode_t *)&mode);
232 if (ret != CMR_OK) {
233 CM_LOG_E("Create CERT_BACKUP_CONFIG_ROOT_DIR failed, err code: %d", ret);
234 return CMR_ERROR_MAKE_DIR_FAIL;
235 }
236
237 if (snprintf_s(confRootDir, dirLen, dirLen - 1, "%s", CERT_BACKUP_CONFIG_ROOT_DIR) < 0) {
238 CM_LOG_E("Construct confRootDir failed");
239 return CMR_ERROR_MEM_OPERATION_PRINT;
240 }
241
242 return CM_SUCCESS;
243 }
244
CmGetCertConfUserIdDir(uint32_t userId,char * confUserIdDir,uint32_t dirLen)245 int32_t CmGetCertConfUserIdDir(uint32_t userId, char *confUserIdDir, uint32_t dirLen)
246 {
247 if (confUserIdDir == NULL) {
248 CM_LOG_E("Input params invalid");
249 return CMR_ERROR_INVALID_ARGUMENT;
250 }
251
252 int32_t ret = CM_SUCCESS;
253 char rootPath[CERT_MAX_PATH_LEN] = { 0 };
254 ret = GetCertConfRootDir(rootPath, CERT_MAX_PATH_LEN);
255 if (ret != CM_SUCCESS) {
256 CM_LOG_E("Get user cert root path failed");
257 return ret;
258 }
259
260 char pathTmp[CERT_MAX_PATH_LEN] = { 0 };
261 /* Concatenate the {confRootDir}/{userid} directory */
262 if (snprintf_s(pathTmp, CERT_MAX_PATH_LEN, CERT_MAX_PATH_LEN - 1, "%s/%u", rootPath, userId) < 0) {
263 CM_LOG_E("Construct userIdPath failed, rootPath: %s, userId: %u", rootPath, userId);
264 return CMR_ERROR_MEM_OPERATION_PRINT;
265 }
266 /* Create the {confRootDir}/{userid} directory */
267 ret = CmUserBakupMakeDir(pathTmp, NULL);
268 if (ret != CMR_OK) {
269 CM_LOG_E("Create userIdPath failed, err code: %d", ret);
270 return CMR_ERROR_MAKE_DIR_FAIL;
271 }
272
273 if (snprintf_s(confUserIdDir, dirLen, dirLen - 1, "%s", pathTmp) < 0) {
274 CM_LOG_E("Failed to construct confUserIdDir");
275 return CMR_ERROR_MEM_OPERATION_PRINT;
276 }
277
278 return CM_SUCCESS;
279 }
280
CmGetCertConfUidDir(uint32_t userId,uint32_t uid,char * certConfUidDir,uint32_t dirLen)281 int32_t CmGetCertConfUidDir(uint32_t userId, uint32_t uid, char *certConfUidDir, uint32_t dirLen)
282 {
283 if (certConfUidDir == NULL) {
284 CM_LOG_E("Input params invalid");
285 return CMR_ERROR_INVALID_ARGUMENT;
286 }
287
288 int32_t ret = CM_SUCCESS;
289 char confUserIdDir[CERT_MAX_PATH_LEN] = { 0 };
290 ret = CmGetCertConfUserIdDir(userId, confUserIdDir, CERT_MAX_PATH_LEN);
291 if (ret != CM_SUCCESS) {
292 CM_LOG_E("Construct confUserIdDir(userId: %u) failed, ret = %d", userId, ret);
293 return ret;
294 }
295
296 char pathTmp[CERT_MAX_PATH_LEN] = { 0 };
297 /* Concatenate the {confRootDir}/{userid}/{uid} directory */
298 if (snprintf_s(pathTmp, CERT_MAX_PATH_LEN, CERT_MAX_PATH_LEN - 1, "%s/%u", confUserIdDir, uid) < 0) {
299 CM_LOG_E("Construct uidPath failed, uid: %u", uid);
300 return CMR_ERROR_MEM_OPERATION_PRINT;
301 }
302 /* Create the {confRootDir}/{userid}/{uid} directory */
303 ret = CmUserBakupMakeDir(pathTmp, NULL);
304 if (ret != CMR_OK) {
305 CM_LOG_E("Create uidPath failed, err code: %d", ret);
306 return CMR_ERROR_MAKE_DIR_FAIL;
307 }
308
309 if (snprintf_s(certConfUidDir, dirLen, dirLen - 1, "%s", pathTmp) < 0) {
310 CM_LOG_E("Failed to construct certConfUidDir");
311 return CMR_ERROR_MEM_OPERATION_PRINT;
312 }
313
314 return CM_SUCCESS;
315 }
316
CmGetCertConfPath(uint32_t userId,uint32_t uid,const struct CmBlob * certUri,char * confFilePath,uint32_t confFilePathLen)317 int32_t CmGetCertConfPath(uint32_t userId, uint32_t uid, const struct CmBlob *certUri, char *confFilePath,
318 uint32_t confFilePathLen)
319 {
320 if ((CmCheckBlob(certUri) != CM_SUCCESS) || (confFilePath == NULL) || (confFilePathLen == 0)) {
321 CM_LOG_E("input params is invaild");
322 return CMR_ERROR_INVALID_ARGUMENT;
323 }
324
325 int32_t ret = CM_SUCCESS;
326 char certConfUidDir[CERT_MAX_PATH_LEN] = { 0 };
327 ret = CmGetCertConfUidDir(userId, uid, certConfUidDir, CERT_MAX_PATH_LEN);
328 if (ret != CM_SUCCESS) {
329 CM_LOG_E("Get user cert root path failed, ret = %d", ret);
330 return ret;
331 }
332
333 if (snprintf_s(confFilePath, confFilePathLen, confFilePathLen - 1, "%s/%.*s%s", certConfUidDir, certUri->size,
334 certUri->data, CERT_CONFIG_FILE_SUFFIX) < 0) {
335 CM_LOG_E("Failed to construct user cert config file path");
336 return CMR_ERROR_MEM_OPERATION_PRINT;
337 }
338 return CM_SUCCESS;
339 }
340
341 /**
342 * @brief Get the user certificate backup file root directory
343 *
344 * @param[out] certBackupRootDir Save the buffer of the user certificate backup file root directory
345 * @param[in] dirLen Maximum length of the certBackupRootDir buffer
346 * @return int32_t result
347 * @retval 0 success
348 * @retval <0 failure
349 */
GetCertBackupRootDir(char * certBackupRootDir,uint32_t dirLen)350 static int32_t GetCertBackupRootDir(char *certBackupRootDir, uint32_t dirLen)
351 {
352 if (certBackupRootDir == NULL) {
353 CM_LOG_E("Input params invalid");
354 return CMR_ERROR_INVALID_ARGUMENT;
355 }
356
357 if (dirLen < sizeof(CERT_BACKUP_ROOT_DIR)) {
358 CM_LOG_E("dirLen(%u) is too small for save user cert backup root path", dirLen);
359 return CMR_ERROR_BUFFER_TOO_SMALL;
360 }
361
362 int32_t ret = CM_SUCCESS;
363 mode_t mode = 0;
364 /* Create the root directory for storing user backup files */
365 mode = S_IRWXU | S_IXOTH; /* The permission on the user_open directory should be 0701 */
366 ret = CmUserBakupMakeDir(CERT_BACKUP_ROOT_DIR, (const mode_t *)&mode);
367 if (ret != CMR_OK) {
368 CM_LOG_E("Create CERT_BACKUP_ROOT_DIR failed, err code: %d", ret);
369 return CMR_ERROR_MAKE_DIR_FAIL;
370 }
371
372 if (snprintf_s(certBackupRootDir, dirLen, dirLen - 1, "%s", CERT_BACKUP_ROOT_DIR) < 0) {
373 CM_LOG_E("Construct certBackupRootDir failed");
374 return CMR_ERROR_MEM_OPERATION_PRINT;
375 }
376
377 return CM_SUCCESS;
378 }
379
CmGetCertBackupDir(uint32_t userId,char * certBackupDir,uint32_t certBackupDirLen)380 int32_t CmGetCertBackupDir(uint32_t userId, char *certBackupDir, uint32_t certBackupDirLen)
381 {
382 int32_t ret = CM_SUCCESS;
383 char rootPath[CERT_MAX_PATH_LEN] = { 0 };
384
385 ret = GetCertBackupRootDir(rootPath, CERT_MAX_PATH_LEN);
386 if (ret != CM_SUCCESS) {
387 CM_LOG_E("Get user cert root path failed");
388 return ret;
389 }
390
391 char userIdPath[CERT_MAX_PATH_LEN] = { 0 };
392 /* Concatenate the {userId} directory for the certificate backup */
393 if (snprintf_s(userIdPath, CERT_MAX_PATH_LEN, CERT_MAX_PATH_LEN - 1, "%s/%u", rootPath, userId) < 0) {
394 CM_LOG_E("Construct userIdPath failed");
395 return CMR_ERROR_MEM_OPERATION_PRINT;
396 }
397 /* Create the {userId} directory for the certificate backup */
398 ret = CmUserBakupMakeDir(userIdPath, NULL);
399 if (ret != CMR_OK) {
400 CM_LOG_E("Create userIdPath failed, err code: %d", ret);
401 return CMR_ERROR_MAKE_DIR_FAIL;
402 }
403
404 if (snprintf_s(certBackupDir, certBackupDirLen, certBackupDirLen - 1, "%s", userIdPath) < 0) {
405 CM_LOG_E("Construct certBackupDir failed");
406 return CMR_ERROR_MEM_OPERATION_PRINT;
407 }
408 return CM_SUCCESS;
409 }
410
411 /**
412 * @brief Get the minimum serial number of the backup file available for the user CA certificate
413 *
414 * @param[in] certSubjectNameHash hash value of a CA certificate SubjectName
415 * @return int Get the minimum serial number or error code
416 * @retval >=0 Get the minimum serial number
417 * @retval <0 Error code
418 */
CmGetCertMinSeqNum(uint32_t userId,unsigned long certSubjectNameHash)419 static int32_t CmGetCertMinSeqNum(uint32_t userId, unsigned long certSubjectNameHash)
420 {
421 int32_t ret = CM_SUCCESS;
422 char certBackupDir[CERT_MAX_PATH_LEN] = { 0 };
423
424 ret = CmGetCertBackupDir(userId, certBackupDir, CERT_MAX_PATH_LEN);
425 if (ret != CM_SUCCESS) {
426 CM_LOG_E("Construct userCertBackupDirPath failed");
427 return ret;
428 }
429
430 int32_t sequenceNumber = CM_FAILURE;
431 char backupFileSearchPath[CERT_MAX_PATH_LEN] = { 0 };
432 for (int32_t seq = 0; seq < MAX_COUNT_CERTIFICATE; seq++) {
433 if (snprintf_s(backupFileSearchPath, CERT_MAX_PATH_LEN, CERT_MAX_PATH_LEN - 1,
434 "%s/" CERT_BACKUP_FILENAME_FORMAT, certBackupDir, certSubjectNameHash, seq) < 0) {
435 CM_LOG_E("Call snprintf_s return failed");
436 return CMR_ERROR_MEM_OPERATION_PRINT;
437 }
438
439 if (access(backupFileSearchPath, F_OK) == 0) {
440 CM_LOG_D("backupFileSearchPath is exist");
441 continue;
442 } else {
443 CM_LOG_D("backupFileSearchPath is not exist");
444 sequenceNumber = seq;
445 break;
446 }
447 }
448
449 CM_LOG_D("Get sequenceNumber(%d)", sequenceNumber);
450 return sequenceNumber;
451 }
452
CmGetCertBackupFileName(const X509 * userCertX509,uint32_t userId,char * certBackupFileName,uint32_t certBackupFileNameLen)453 int32_t CmGetCertBackupFileName(const X509 *userCertX509, uint32_t userId, char *certBackupFileName,
454 uint32_t certBackupFileNameLen)
455 {
456 if (userCertX509 == NULL || certBackupFileName == NULL) {
457 CM_LOG_E("Input params invalid");
458 return CMR_ERROR_INVALID_ARGUMENT;
459 }
460
461 int sequenceNumber = 0;
462 unsigned long certSubjectNameHash = 0;
463
464 /* Calculate the hash value of CA certificate subject_name */
465 certSubjectNameHash = X509_NAME_hash(X509_get_subject_name(userCertX509));
466
467 sequenceNumber = CmGetCertMinSeqNum(userId, certSubjectNameHash);
468 if (sequenceNumber < 0) {
469 CM_LOG_E("Get User Cert Min Useable SequenceNumber failed");
470 return CM_FAILURE;
471 }
472
473 if (snprintf_s(certBackupFileName, certBackupFileNameLen, certBackupFileNameLen - 1, CERT_BACKUP_FILENAME_FORMAT,
474 certSubjectNameHash, sequenceNumber) < 0) {
475 CM_LOG_E("Call snprintf_s return failed");
476 return CMR_ERROR_MEM_OPERATION_PRINT;
477 }
478 return CM_SUCCESS;
479 }
480
CmGetCertBackupFilePath(const X509 * userCertX509,uint32_t userId,char * backupFilePath,uint32_t backupFilePathLen)481 int32_t CmGetCertBackupFilePath(const X509 *userCertX509, uint32_t userId, char *backupFilePath,
482 uint32_t backupFilePathLen)
483 {
484 if (userCertX509 == NULL || backupFilePath == NULL) {
485 CM_LOG_E("Input params invalid");
486 return CMR_ERROR_INVALID_ARGUMENT;
487 }
488
489 int32_t ret = CM_SUCCESS;
490 char certBackupDir[CERT_MAX_PATH_LEN] = { 0 };
491 ret = CmGetCertBackupDir(userId, certBackupDir, CERT_MAX_PATH_LEN);
492 if (ret != CM_SUCCESS) {
493 CM_LOG_E("Construct userCertBackupDirPath failed");
494 return ret;
495 }
496
497 char certBackupFileName[CERT_MAX_PATH_LEN] = { 0 };
498 ret = CmGetCertBackupFileName(userCertX509, userId, certBackupFileName, CERT_MAX_PATH_LEN);
499 if (ret != CM_SUCCESS) {
500 CM_LOG_E("Get certBackupFileName failed");
501 return ret;
502 }
503
504 if (snprintf_s(backupFilePath, backupFilePathLen, backupFilePathLen - 1, "%s/%s", certBackupDir,
505 certBackupFileName) < 0) {
506 CM_LOG_E("Call snprintf_s return failed");
507 ret = CMR_ERROR_MEM_OPERATION_PRINT;
508 }
509 return ret;
510 }
511 // LCOV_EXCL_STOP