summaryrefslogtreecommitdiff
path: root/src/tspi/tspi_nv.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tspi/tspi_nv.c')
-rw-r--r--src/tspi/tspi_nv.c527
1 files changed, 527 insertions, 0 deletions
diff --git a/src/tspi/tspi_nv.c b/src/tspi/tspi_nv.c
new file mode 100644
index 0000000..bdf10ad
--- /dev/null
+++ b/src/tspi/tspi_nv.c
@@ -0,0 +1,527 @@
+/*
+ * The Initial Developer of the Original Code is Intel Corporation.
+ * Portions created by Intel Corporation are Copyright (C) 2007 Intel Corporation.
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Common Public License as published by
+ * IBM Corporation; either version 1 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Common Public License for more details.
+ *
+ * You should have received a copy of the Common Public License
+ * along with this program; if not, a copy can be viewed at
+ * http://www.opensource.org/licenses/cpl1.0.php.
+ *
+ * trousers - An open source TCG Software Stack
+ *
+ * Author: james.xu@intel.com Rossey.liu@intel.com
+ *
+ * Kent Yoder - updates for new authsession mechanism
+ * (C) International Business Machines Corp. 2007
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "trousers/tss.h"
+#include "trousers/trousers.h"
+#include "trousers_types.h"
+#include "trousers_types.h"
+#include "spi_utils.h"
+#include "capabilities.h"
+#include "tsplog.h"
+#include "obj.h"
+#include "authsess.h"
+
+TSS_RESULT
+Tspi_NV_DefineSpace(TSS_HNVSTORE hNvstore, /* in */
+ TSS_HPCRS hReadPcrComposite, /* in, may be NULL */
+ TSS_HPCRS hWritePcrComposite) /* in, may be NULL*/
+{
+ TSS_HCONTEXT tspContext;
+ TSS_HTPM hTpm;
+ TSS_RESULT result;
+ UINT32 uiResultLen;
+ BYTE *pResult;
+ UINT32 i;
+
+ TPM_BOOL defined_index = FALSE;
+ NV_DATA_PUBLIC nv_data_public;
+ TSS_BOOL need_authdata = FALSE;
+ TCPA_DIGEST digest;
+ BYTE *pReadPCR;
+ UINT32 pReadPCR_len;
+ BYTE *pWritePCR;
+ UINT32 pWritePCR_len;
+ UINT64 NVPublic_DataSize;
+ BYTE NVPublicData[MAX_PUBLIC_DATA_SIZE];
+ Trspi_HashCtx hashCtx;
+ struct authsess *xsap = NULL;
+
+ if ((result = obj_nvstore_get_tsp_context(hNvstore, &tspContext)))
+ return result;
+
+ memset(&nv_data_public, 0, sizeof(NV_DATA_PUBLIC));
+
+ if ((result = obj_nvstore_get_index(hNvstore, &nv_data_public.nvIndex)))
+ return result;
+
+ if ((result = obj_nvstore_get_datasize(hNvstore, &nv_data_public.dataSize)))
+ return result;
+
+ if ((result = obj_nvstore_get_permission(hNvstore, &nv_data_public.permission.attributes)))
+ return result;
+
+ if ((result = obj_tpm_get(tspContext, &hTpm)))
+ return result;
+
+ if((result = Tspi_TPM_GetCapability(hTpm, TSS_TPMCAP_NV_LIST, 0,
+ NULL, &uiResultLen, &pResult)))
+ return result;
+
+ for (i = 0; i < uiResultLen/sizeof(UINT32); i++) {
+ if (nv_data_public.nvIndex == Decode_UINT32(pResult + i * sizeof(UINT32))) {
+ defined_index = TRUE;
+ break;
+ }
+ }
+ free_tspi(tspContext, pResult);
+
+ if (defined_index) {
+ result = TSPERR(TSS_E_NV_AREA_EXIST);
+ return result;
+ }
+
+ need_authdata = (nv_data_public.permission.attributes
+ & (TPM_NV_PER_AUTHREAD |TPM_NV_PER_AUTHWRITE)) ? TRUE : FALSE;
+
+ nv_data_public.tag = TPM_TAG_NV_DATA_PUBLIC;
+
+ if ((result = obj_nvstore_create_pcrshortinfo(hNvstore, hReadPcrComposite,
+ &pReadPCR_len, &pReadPCR)))
+ return result;
+
+ if ((result = obj_nvstore_create_pcrshortinfo(hNvstore, hWritePcrComposite,
+ &pWritePCR_len, &pWritePCR))) {
+ free_tspi(tspContext, pReadPCR);
+ return result;
+ }
+
+ NVPublic_DataSize = 0;
+ Trspi_LoadBlob_UINT16(&NVPublic_DataSize, TPM_TAG_NV_DATA_PUBLIC, NVPublicData);
+ Trspi_LoadBlob_UINT32(&NVPublic_DataSize, nv_data_public.nvIndex, NVPublicData);
+ Trspi_LoadBlob(&NVPublic_DataSize, pReadPCR_len, NVPublicData, pReadPCR);
+ Trspi_LoadBlob(&NVPublic_DataSize, pWritePCR_len, NVPublicData, pWritePCR);
+ Trspi_LoadBlob_UINT16(&NVPublic_DataSize, TPM_TAG_NV_ATTRIBUTES, NVPublicData);
+ Trspi_LoadBlob_UINT32(&NVPublic_DataSize, nv_data_public.permission.attributes, NVPublicData);
+ Trspi_LoadBlob_BOOL(&NVPublic_DataSize, nv_data_public.bReadSTClear, NVPublicData);
+ Trspi_LoadBlob_BOOL(&NVPublic_DataSize, nv_data_public.bWriteSTClear, NVPublicData);
+ Trspi_LoadBlob_BOOL(&NVPublic_DataSize, nv_data_public.bWriteDefine, NVPublicData);
+ Trspi_LoadBlob_UINT32(&NVPublic_DataSize, nv_data_public.dataSize, NVPublicData);
+ free_tspi(tspContext, pReadPCR);
+ free_tspi(tspContext, pWritePCR);
+
+ if ((result = authsess_xsap_init(tspContext, hTpm, hNvstore, need_authdata,
+ TPM_ORD_NV_DefineSpace, TPM_ET_OWNER, &xsap))) {
+ if (result == TSPERR(TSS_E_TSP_AUTHREQUIRED))
+ result = TSS_ERROR_CODE(TSS_E_BAD_PARAMETER);
+ return result;
+ }
+
+ result = Trspi_HashInit(&hashCtx, TSS_HASH_SHA1);
+ result |= Trspi_Hash_UINT32(&hashCtx, TPM_ORD_NV_DefineSpace);
+ result |= Trspi_HashUpdate(&hashCtx, NVPublic_DataSize, NVPublicData);
+ result |= Trspi_Hash_ENCAUTH(&hashCtx, xsap->encAuthUse.authdata);
+ if ((result |= Trspi_HashFinal(&hashCtx, digest.digest)))
+ goto error;
+
+ if ((result = authsess_xsap_hmac(xsap, &digest)))
+ goto error;
+
+ if ((result = TCS_API(tspContext)->NV_DefineOrReleaseSpace(tspContext, NVPublic_DataSize,
+ NVPublicData, xsap->encAuthUse,
+ xsap->pAuth)))
+ goto error;
+
+ result = Trspi_HashInit(&hashCtx, TSS_HASH_SHA1);
+ result |= Trspi_Hash_UINT32(&hashCtx, TPM_SUCCESS);
+ result |= Trspi_Hash_UINT32(&hashCtx, TPM_ORD_NV_DefineSpace);
+ if ((result |= Trspi_HashFinal(&hashCtx, digest.digest)))
+ goto error;
+
+ result = authsess_xsap_verify(xsap, &digest);
+error:
+ authsess_free(xsap);
+
+ return result;
+}
+
+TSS_RESULT
+Tspi_NV_ReleaseSpace(TSS_HNVSTORE hNvstore) /* in */
+{
+ TSS_HCONTEXT tspContext;
+ TSS_HTPM hTpm;
+ TSS_RESULT result;
+ UINT32 uiResultLen;
+ BYTE *pResult;
+ UINT32 i;
+ TPM_BOOL defined_index = FALSE;
+ NV_DATA_PUBLIC nv_data_public;
+ TCPA_DIGEST digest;
+ BYTE *pPCR;
+ UINT32 pPCR_len;
+
+ UINT64 NVPublic_DataSize;
+ BYTE NVPublicData[MAX_PUBLIC_DATA_SIZE];
+ Trspi_HashCtx hashCtx;
+ struct authsess *xsap = NULL;
+
+ memset(&nv_data_public, 0, sizeof(NV_DATA_PUBLIC));
+
+ if ((result = obj_nvstore_get_tsp_context(hNvstore, &tspContext)))
+ return result;
+
+ if ((result = obj_nvstore_get_index(hNvstore, &nv_data_public.nvIndex)))
+ return result;
+
+ if ((result = obj_nvstore_get_datasize(hNvstore, &nv_data_public.dataSize)))
+ return result;
+
+ if ((result = obj_nvstore_get_permission(hNvstore, &nv_data_public.permission.attributes)))
+ return result;
+
+ if ((result = obj_tpm_get(tspContext, &hTpm)))
+ return result;
+
+ if((result = Tspi_TPM_GetCapability(hTpm, TSS_TPMCAP_NV_LIST, 0,
+ NULL, &uiResultLen, &pResult)))
+ return result;
+
+ for (i = 0; i < uiResultLen/sizeof(UINT32); i++) {
+ if (nv_data_public.nvIndex == Decode_UINT32(pResult + i * sizeof(UINT32))) {
+ defined_index = TRUE;
+ break;
+ }
+ }
+ free_tspi(tspContext, pResult);
+
+ if (!defined_index) {
+ result = TSPERR(TSS_E_NV_AREA_NOT_EXIST);
+ return result;
+ }
+
+ nv_data_public.tag = TPM_TAG_NV_DATA_PUBLIC;
+
+ if ((result = obj_nvstore_create_pcrshortinfo(hNvstore, NULL_HPCRS, &pPCR_len, &pPCR)))
+ return result;
+
+ NVPublic_DataSize = 0;
+ Trspi_LoadBlob_UINT16(&NVPublic_DataSize, TPM_TAG_NV_DATA_PUBLIC, NVPublicData);
+ Trspi_LoadBlob_UINT32(&NVPublic_DataSize, nv_data_public.nvIndex, NVPublicData);
+ /* load the read pcr short info */
+ Trspi_LoadBlob(&NVPublic_DataSize, pPCR_len, NVPublicData, pPCR);
+ /* load the write pcr short info */
+ Trspi_LoadBlob(&NVPublic_DataSize, pPCR_len, NVPublicData, pPCR);
+ Trspi_LoadBlob_UINT16(&NVPublic_DataSize, TPM_TAG_NV_ATTRIBUTES, NVPublicData);
+ Trspi_LoadBlob_UINT32(&NVPublic_DataSize,
+ nv_data_public.permission.attributes, NVPublicData);
+ Trspi_LoadBlob_BOOL(&NVPublic_DataSize, nv_data_public.bReadSTClear, NVPublicData);
+ Trspi_LoadBlob_BOOL(&NVPublic_DataSize, nv_data_public.bWriteSTClear, NVPublicData);
+ Trspi_LoadBlob_BOOL(&NVPublic_DataSize, nv_data_public.bWriteDefine, NVPublicData);
+ /*Trspi_LoadBlob_UINT32(&NVPublic_DataSize, nv_data_public.dataSize, NVPublicData);*/
+ Trspi_LoadBlob_UINT32(&NVPublic_DataSize, 0, NVPublicData);
+ free_tspi(tspContext, pPCR);
+
+ if ((result = authsess_xsap_init(tspContext, hTpm, hNvstore, TSS_AUTH_POLICY_NOT_REQUIRED,
+ TPM_ORD_NV_DefineSpace, TPM_ET_OWNER, &xsap)))
+ return result;
+
+ result = Trspi_HashInit(&hashCtx, TSS_HASH_SHA1);
+ result |= Trspi_Hash_UINT32(&hashCtx, TPM_ORD_NV_DefineSpace);
+ result |= Trspi_HashUpdate(&hashCtx, NVPublic_DataSize, NVPublicData);
+ result |= Trspi_Hash_ENCAUTH(&hashCtx, xsap->encAuthUse.authdata);
+ if ((result |= Trspi_HashFinal(&hashCtx, digest.digest)))
+ goto error;
+
+ if ((result = authsess_xsap_hmac(xsap, &digest)))
+ goto error;
+
+ if ((result = TCS_API(tspContext)->NV_DefineOrReleaseSpace(tspContext, NVPublic_DataSize,
+ NVPublicData, xsap->encAuthUse,
+ xsap->pAuth)))
+ goto error;
+
+ result = Trspi_HashInit(&hashCtx, TSS_HASH_SHA1);
+ result |= Trspi_Hash_UINT32(&hashCtx, TPM_SUCCESS);
+ result |= Trspi_Hash_UINT32(&hashCtx, TPM_ORD_NV_DefineSpace);
+ if ((result |= Trspi_HashFinal(&hashCtx, digest.digest)))
+ goto error;
+
+ result = authsess_xsap_verify(xsap, &digest);
+error:
+ authsess_free(xsap);
+
+ return result;
+}
+
+TSS_RESULT
+Tspi_NV_WriteValue(TSS_HNVSTORE hNvstore, /* in */
+ UINT32 offset, /* in */
+ UINT32 ulDataLength, /* in */
+ BYTE* rgbDataToWrite) /* in */
+{
+ TSS_HCONTEXT tspContext;
+ TSS_HTPM hTpm;
+ TSS_RESULT result;
+ NV_DATA_PUBLIC nv_data_public;
+ UINT32 need_authdata = 0;
+ UINT32 authwrite =0;
+ TSS_HPOLICY hPolicy;
+ TPM_AUTH auth;
+ TCPA_DIGEST digest;
+ Trspi_HashCtx hashCtx;
+
+ if ((ulDataLength != 0) && (rgbDataToWrite == NULL))
+ return TSPERR(TSS_E_BAD_PARAMETER);
+
+ if((result = obj_nvstore_get_tsp_context(hNvstore, &tspContext)))
+ return result;
+
+ if ((result = obj_tpm_get(tspContext, &hTpm)))
+ return result;
+
+ if ((result = obj_nvstore_get_index(hNvstore, &nv_data_public.nvIndex)))
+ return result;
+
+ if ((result = obj_nvstore_get_policy(hNvstore, TSS_POLICY_USAGE, &hPolicy)))
+ return result;
+
+ if (hPolicy) {
+ if ((result = obj_nvstore_get_permission_from_tpm(hNvstore,
+ &nv_data_public.permission.attributes)))
+ return result;
+
+ need_authdata = nv_data_public.permission.attributes
+ & (TPM_NV_PER_AUTHWRITE | TPM_NV_PER_OWNERWRITE);
+
+ authwrite = nv_data_public.permission.attributes & TPM_NV_PER_AUTHWRITE;
+
+ if (need_authdata) {
+ if (!authwrite) {
+ result = Trspi_HashInit(&hashCtx, TSS_HASH_SHA1);
+ result |= Trspi_Hash_UINT32(&hashCtx, TPM_ORD_NV_WriteValue);
+ result |= Trspi_Hash_UINT32(&hashCtx, nv_data_public.nvIndex);
+ result |= Trspi_Hash_UINT32(&hashCtx, offset);
+ result |= Trspi_Hash_UINT32(&hashCtx, ulDataLength);
+ result |= Trspi_HashUpdate(&hashCtx, ulDataLength, rgbDataToWrite);
+
+ if ((result |= Trspi_HashFinal(&hashCtx, digest.digest)))
+ return result;
+
+ if ((result = secret_PerformAuth_OIAP(hNvstore,
+ TPM_ORD_NV_WriteValue,
+ hPolicy, FALSE, &digest,
+ &auth)))
+ return result;
+
+ if ((result = TCS_API(tspContext)->NV_WriteValue(tspContext,
+ nv_data_public.nvIndex,
+ offset, ulDataLength,
+ rgbDataToWrite, &auth)))
+ return result;
+
+ result = Trspi_HashInit(&hashCtx, TSS_HASH_SHA1);
+ result |= Trspi_Hash_UINT32(&hashCtx, result);
+ result |= Trspi_Hash_UINT32(&hashCtx, TPM_ORD_NV_WriteValue);
+ if ((result |= Trspi_HashFinal(&hashCtx, digest.digest)))
+ return result;
+
+ if ((result = obj_policy_validate_auth_oiap(hPolicy,
+ &digest, &auth)))
+ return result;
+ } else {
+ result = Trspi_HashInit(&hashCtx, TSS_HASH_SHA1);
+ result |= Trspi_Hash_UINT32(&hashCtx, TPM_ORD_NV_WriteValueAuth);
+ result |= Trspi_Hash_UINT32(&hashCtx, nv_data_public.nvIndex);
+ result |= Trspi_Hash_UINT32(&hashCtx, offset);
+ result |= Trspi_Hash_UINT32(&hashCtx, ulDataLength);
+ result |= Trspi_HashUpdate(&hashCtx, ulDataLength, rgbDataToWrite);
+
+ if ((result |= Trspi_HashFinal(&hashCtx, digest.digest)))
+ return result;
+
+ if ((result = secret_PerformAuth_OIAP(hNvstore,
+ TPM_ORD_NV_WriteValueAuth,
+ hPolicy, FALSE, &digest,
+ &auth)))
+ return result;
+
+ if ((result = TCS_API(tspContext)->NV_WriteValueAuth(tspContext,
+ nv_data_public.nvIndex,
+ offset, ulDataLength,
+ rgbDataToWrite, &auth)))
+ return result;
+
+ result = Trspi_HashInit(&hashCtx, TSS_HASH_SHA1);
+ result |= Trspi_Hash_UINT32(&hashCtx, result);
+ result |= Trspi_Hash_UINT32(&hashCtx, TPM_ORD_NV_WriteValueAuth);
+ if ((result |= Trspi_HashFinal(&hashCtx, digest.digest)))
+ return result;
+
+ if ((result = obj_policy_validate_auth_oiap(hPolicy,
+ &digest, &auth)))
+ return result;
+ }
+ } else {
+ if ((result = TCS_API(tspContext)->NV_WriteValue(tspContext,
+ nv_data_public.nvIndex,
+ offset, ulDataLength,
+ rgbDataToWrite, NULL)))
+ return result;
+ }
+ } else {
+ LogDebug("no policy, so noauthentication");
+ if ((result = TCS_API(tspContext)->NV_WriteValue(tspContext, nv_data_public.nvIndex,
+ offset, ulDataLength,
+ rgbDataToWrite, NULL)))
+ return result;
+ }
+
+ return result;
+}
+
+TSS_RESULT
+Tspi_NV_ReadValue(TSS_HNVSTORE hNvstore, /* in */
+ UINT32 offset, /* in */
+ UINT32* ulDataLength, /* in, out */
+ BYTE** rgbDataRead) /* out */
+{
+ TSS_HCONTEXT tspContext;
+ TSS_HTPM hTpm;
+ TSS_RESULT result;
+ NV_DATA_PUBLIC nv_data_public;
+ UINT32 need_authdata = 0;
+ UINT32 authread =0;
+ TSS_HPOLICY hPolicy;
+
+ TPM_AUTH auth;
+ TCPA_DIGEST digest;
+ Trspi_HashCtx hashCtx;
+
+ if (ulDataLength == NULL || rgbDataRead == NULL)
+ return TSPERR(TSS_E_BAD_PARAMETER);
+
+ if((result = obj_nvstore_get_tsp_context(hNvstore, &tspContext)))
+ return result;
+
+ if ((result = obj_tpm_get(tspContext, &hTpm)))
+ return result;
+
+ if ((result = obj_nvstore_get_index(hNvstore, &nv_data_public.nvIndex)))
+ return result;
+
+ if ((result = obj_nvstore_get_policy(hNvstore, TSS_POLICY_USAGE, &hPolicy)))
+ return result;
+
+ if (hPolicy) {/*if the policy secret is set*/
+ if ((result = obj_nvstore_get_permission_from_tpm(hNvstore,
+ &nv_data_public.permission.attributes)))
+ return result;
+
+ need_authdata = nv_data_public.permission.attributes
+ & (TPM_NV_PER_AUTHREAD | TPM_NV_PER_OWNERREAD);
+
+ authread = nv_data_public.permission.attributes & TPM_NV_PER_AUTHREAD;
+
+ if (need_authdata) {
+ if (!authread) {
+ result = Trspi_HashInit(&hashCtx, TSS_HASH_SHA1);
+ result |= Trspi_Hash_UINT32(&hashCtx, TPM_ORD_NV_ReadValue);
+ result |= Trspi_Hash_UINT32(&hashCtx, nv_data_public.nvIndex);
+ result |= Trspi_Hash_UINT32(&hashCtx, offset);
+ result |= Trspi_Hash_UINT32(&hashCtx, *ulDataLength);
+
+ if ((result |= Trspi_HashFinal(&hashCtx, digest.digest)))
+ return result;
+
+ if ((result = secret_PerformAuth_OIAP(hNvstore,
+ TPM_ORD_NV_ReadValue,
+ hPolicy, FALSE, &digest,
+ &auth)))
+ return result;
+
+ if ((result = TCS_API(tspContext)->NV_ReadValue(tspContext,
+ nv_data_public.nvIndex,
+ offset, ulDataLength,
+ &auth, rgbDataRead)))
+ return result;
+
+ result = Trspi_HashInit(&hashCtx, TSS_HASH_SHA1);
+ result |= Trspi_Hash_UINT32(&hashCtx, TSS_SUCCESS);
+ result |= Trspi_Hash_UINT32(&hashCtx, TPM_ORD_NV_ReadValue);
+ result |= Trspi_Hash_UINT32(&hashCtx, *ulDataLength);
+ result |= Trspi_HashUpdate(&hashCtx, *ulDataLength, *rgbDataRead);
+ if ((result |= Trspi_HashFinal(&hashCtx, digest.digest)))
+ return result;
+
+ if ((result = obj_policy_validate_auth_oiap(hPolicy,
+ &digest, &auth)))
+ return result;
+ } else {
+ result = Trspi_HashInit(&hashCtx, TSS_HASH_SHA1);
+ result |= Trspi_Hash_UINT32(&hashCtx, TPM_ORD_NV_ReadValueAuth);
+ result |= Trspi_Hash_UINT32(&hashCtx, nv_data_public.nvIndex);
+ result |= Trspi_Hash_UINT32(&hashCtx, offset);
+ result |= Trspi_Hash_UINT32(&hashCtx, *ulDataLength);
+
+ if ((result |= Trspi_HashFinal(&hashCtx, digest.digest)))
+ return result;
+
+ if ((result = secret_PerformAuth_OIAP(hNvstore,
+ TPM_ORD_NV_ReadValueAuth,
+ hPolicy, FALSE, &digest,
+ &auth)))
+ return result;
+
+ if ((result = TCS_API(tspContext)->NV_ReadValueAuth(tspContext,
+ nv_data_public.nvIndex,
+ offset, ulDataLength,
+ &auth, rgbDataRead)))
+ return result;
+
+ result = Trspi_HashInit(&hashCtx, TSS_HASH_SHA1);
+ result |= Trspi_Hash_UINT32(&hashCtx, TSS_SUCCESS);
+ result |= Trspi_Hash_UINT32(&hashCtx, TPM_ORD_NV_ReadValueAuth);
+ result |= Trspi_Hash_UINT32(&hashCtx, *ulDataLength);
+ result |= Trspi_HashUpdate(&hashCtx, *ulDataLength, *rgbDataRead);
+ if ((result |= Trspi_HashFinal(&hashCtx, digest.digest)))
+ return result;
+
+ if ((result = obj_policy_validate_auth_oiap(hPolicy,
+ &digest, &auth)))
+ return result;
+ }
+ } else {
+ if ((result = TCS_API(tspContext)->NV_ReadValue(tspContext,
+ nv_data_public.nvIndex,
+ offset, ulDataLength, NULL,
+ rgbDataRead)))
+ return result;
+ }
+ } else {
+ if ((result = TCS_API(tspContext)->NV_ReadValue(tspContext, nv_data_public.nvIndex,
+ offset, ulDataLength, NULL,
+ rgbDataRead)))
+ return result;
+ }
+
+ return result;
+}