1 /*
2 * Copyright (C) 2021-2023 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 "account_unrelated_group_auth.h"
17 #include "common_defs.h"
18 #include "compatible_auth_sub_session_common.h"
19 #include "device_auth_defines.h"
20 #include "group_auth_data_operation.h"
21 #include "hc_log.h"
22 #include "hc_time.h"
23 #include "hc_types.h"
24 #include "json_utils.h"
25 #include "os_account_adapter.h"
26 #include "performance_dumper.h"
27 #include "string_util.h"
28 #include "hisysevent_adapter.h"
29
30 static void OnDasFinish(int64_t requestId, const CJson *authParam, const CJson *out,
31 const DeviceAuthCallback *callback);
32 static int32_t FillNonAccountAuthInfo(int32_t osAccountId, const TrustedGroupEntry *entry,
33 const TrustedDeviceEntry *localAuthInfo, CJson *paramsData);
34 static int32_t GetAuthParamsVecForServer(const CJson *dataFromClient, ParamsVecForAuth *authParamsVec);
35 static int32_t CombineDasServerConfirmParams(const CJson *confirmationJson, CJson *dataFromClient);
36
37 static NonAccountGroupAuth g_nonAccountGroupAuth = {
38 .base.onFinish = OnDasFinish,
39 .base.fillDeviceAuthInfo = FillNonAccountAuthInfo,
40 .base.getAuthParamsVecForServer = GetAuthParamsVecForServer,
41 .base.combineServerConfirmParams = CombineDasServerConfirmParams,
42 .base.authType = ACCOUNT_UNRELATED_GROUP_AUTH_TYPE,
43 };
44
ReturnSessionKey(int64_t requestId,const CJson * out,const DeviceAuthCallback * callback)45 static int32_t ReturnSessionKey(int64_t requestId, const CJson *out, const DeviceAuthCallback *callback)
46 {
47 const char *returnSessionKeyStr = GetStringFromJson(out, FIELD_SESSION_KEY);
48 if (returnSessionKeyStr == NULL) {
49 LOGE("Failed to get sessionKey!");
50 return HC_ERR_JSON_GET;
51 }
52 uint32_t keyLen = (HcStrlen(returnSessionKeyStr) / BYTE_TO_HEX_OPER_LENGTH);
53 uint8_t *sessionKey = (uint8_t *)HcMalloc(keyLen, 0);
54 if (sessionKey == NULL) {
55 LOGE("Failed to allocate memory for sessionKey!");
56 return HC_ERR_ALLOC_MEMORY;
57 }
58
59 int32_t res = HC_SUCCESS;
60 do {
61 if (GetByteFromJson(out, FIELD_SESSION_KEY, sessionKey, keyLen) != HC_SUCCESS) {
62 LOGE("Failed to get sessionKey!");
63 res = HC_ERR_JSON_GET;
64 break;
65 }
66 if ((callback == NULL) || (callback->onSessionKeyReturned == NULL)) {
67 LOGE("The callback of onSessionKeyReturned is null!");
68 res = HC_ERR_INVALID_PARAMS;
69 break;
70 }
71 LOGI("Begin invoke onSessionKeyReturned.");
72 UPDATE_PERFORM_DATA_BY_INPUT_INDEX(requestId, ON_SESSION_KEY_RETURN_TIME, HcGetCurTimeInMillis());
73 callback->onSessionKeyReturned(requestId, sessionKey, keyLen);
74 LOGI("End invoke onSessionKeyReturned, res = %d.", res);
75 } while (0);
76 (void)memset_s(sessionKey, keyLen, 0, keyLen);
77 HcFree(sessionKey);
78 sessionKey = NULL;
79 return res;
80 }
81
AddGroupIdToSelfData(const CJson * authParam,CJson * returnToSelf)82 static int32_t AddGroupIdToSelfData(const CJson *authParam, CJson *returnToSelf)
83 {
84 const char *groupId = GetStringFromJson(authParam, FIELD_SERVICE_TYPE);
85 if (groupId == NULL) {
86 LOGE("Failed to get groupId from authParam!");
87 return HC_ERR_JSON_GET;
88 }
89 if (AddStringToJson(returnToSelf, FIELD_GROUP_ID, groupId) != HC_SUCCESS) {
90 LOGE("Failed to add group id!");
91 return HC_ERR_JSON_FAIL;
92 }
93 return HC_SUCCESS;
94 }
95
AddPeerUdidToSelfData(const CJson * authParam,CJson * returnToSelf)96 static int32_t AddPeerUdidToSelfData(const CJson *authParam, CJson *returnToSelf)
97 {
98 const char *peerUdid = GetStringFromJson(authParam, FIELD_PEER_CONN_DEVICE_ID);
99 if (peerUdid == NULL) {
100 LOGD("The input has no peerUdid in authParam!");
101 return HC_SUCCESS;
102 }
103 if (AddStringToJson(returnToSelf, FIELD_PEER_CONN_DEVICE_ID, peerUdid) != HC_SUCCESS) {
104 LOGE("Failed to add peer udid!");
105 return HC_ERR_JSON_FAIL;
106 }
107 return HC_SUCCESS;
108 }
109
AddPeerAuthIdToSelfData(const CJson * authParam,CJson * returnToSelf)110 static int32_t AddPeerAuthIdToSelfData(const CJson *authParam, CJson *returnToSelf)
111 {
112 const char *peerAuthId = GetStringFromJson(authParam, FIELD_PEER_AUTH_ID);
113 if (peerAuthId == NULL) {
114 LOGD("No peerAuthId in auth session cached params!");
115 return HC_SUCCESS;
116 }
117
118 if (AddStringToJson(returnToSelf, FIELD_PEER_AUTH_ID, peerAuthId) != HC_SUCCESS) {
119 LOGE("Failed to add peerAuthId!");
120 return HC_ERR_JSON_FAIL;
121 }
122 return HC_SUCCESS;
123 }
124
AddSelfTypeToSelfData(const CJson * authParam,CJson * returnToSelf)125 static int32_t AddSelfTypeToSelfData(const CJson *authParam, CJson *returnToSelf)
126 {
127 int32_t userType = 0;
128 if (GetIntFromJson(authParam, FIELD_SELF_TYPE, &userType) != HC_SUCCESS) {
129 LOGE("Failed to get self userType from authParam!");
130 return HC_ERR_JSON_GET;
131 }
132 if (AddIntToJson(returnToSelf, FIELD_USER_TYPE, userType) != HC_SUCCESS) {
133 LOGE("Failed to add self userType for onFinish!");
134 return HC_ERR_JSON_FAIL;
135 }
136 return HC_SUCCESS;
137 }
138
AddPeerTypeToSelfData(const CJson * authParam,CJson * returnToSelf)139 static int32_t AddPeerTypeToSelfData(const CJson *authParam, CJson *returnToSelf)
140 {
141 int32_t peerUserType = 0;
142 if (GetIntFromJson(authParam, FIELD_PEER_USER_TYPE, &peerUserType) != HC_SUCCESS) {
143 LOGE("Failed to get peerUserType from authParam!");
144 return HC_ERR_JSON_GET;
145 }
146 if (AddIntToJson(returnToSelf, FIELD_PEER_USER_TYPE, peerUserType) != HC_SUCCESS) {
147 LOGE("Failed to add peer peerUserType for onFinish!");
148 return HC_ERR_JSON_FAIL;
149 }
150 return HC_SUCCESS;
151 }
152
AddSessionKeyToSelfData(const CJson * authParam,const CJson * sendToSelf,CJson * returnToSelf)153 static int32_t AddSessionKeyToSelfData(const CJson *authParam, const CJson *sendToSelf, CJson *returnToSelf)
154 {
155 int32_t keyLen = DEFAULT_RETURN_KEY_LENGTH;
156 (void)GetIntFromJson(authParam, FIELD_KEY_LENGTH, &keyLen);
157 uint8_t *sessionKey = (uint8_t *)HcMalloc(keyLen, 0);
158 if (sessionKey == NULL) {
159 LOGE("Failed to allocate memory for sessionKey!");
160 return HC_ERR_ALLOC_MEMORY;
161 }
162 int32_t res = HC_SUCCESS;
163 do {
164 if (GetByteFromJson(sendToSelf, FIELD_SESSION_KEY, sessionKey, keyLen) != HC_SUCCESS) {
165 LOGE("Failed to get sessionKey!");
166 res = HC_ERR_JSON_GET;
167 break;
168 }
169 if (AddByteToJson(returnToSelf, FIELD_SESSION_KEY, (const uint8_t *)sessionKey, keyLen) != HC_SUCCESS) {
170 LOGE("Failed to add sessionKey for onFinish!");
171 res = HC_ERR_JSON_FAIL;
172 break;
173 }
174 } while (0);
175 (void)memset_s(sessionKey, keyLen, 0, keyLen);
176 HcFree(sessionKey);
177 sessionKey = NULL;
178 return res;
179 }
180
PrepareDasReturnToSelfData(const CJson * authParam,const CJson * sendToSelf,CJson * returnToSelf)181 static int32_t PrepareDasReturnToSelfData(const CJson *authParam, const CJson *sendToSelf, CJson *returnToSelf)
182 {
183 int32_t res = AddGroupIdToSelfData(authParam, returnToSelf);
184 if (res != HC_SUCCESS) {
185 return res;
186 }
187 res = AddPeerUdidToSelfData(authParam, returnToSelf);
188 if (res != HC_SUCCESS) {
189 return res;
190 }
191 res = AddPeerAuthIdToSelfData(authParam, returnToSelf);
192 if (res != HC_SUCCESS) {
193 return res;
194 }
195 res = AddSelfTypeToSelfData(authParam, returnToSelf);
196 if (res != HC_SUCCESS) {
197 return res;
198 }
199 res = AddPeerTypeToSelfData(authParam, returnToSelf);
200 if (res != HC_SUCCESS) {
201 return res;
202 }
203 res = AddSessionKeyToSelfData(authParam, sendToSelf, returnToSelf);
204 if (res != HC_SUCCESS) {
205 return res;
206 }
207 return HC_SUCCESS;
208 }
209
DasOnFinishToPeer(int64_t requestId,const CJson * out,const DeviceAuthCallback * callback)210 static int32_t DasOnFinishToPeer(int64_t requestId, const CJson *out, const DeviceAuthCallback *callback)
211 {
212 int32_t res = HC_SUCCESS;
213 CJson *sendToPeer = GetObjFromJson(out, FIELD_SEND_TO_PEER);
214 if (sendToPeer == NULL) {
215 LOGI("No need to transmit data to peer.");
216 return res;
217 }
218 if (AddBoolToJson(sendToPeer, FIELD_IS_DEVICE_LEVEL, false) != HC_SUCCESS) {
219 LOGE("Failed to add device level flag!");
220 return HC_ERR_JSON_ADD;
221 }
222 char *sendToPeerStr = PackJsonToString(sendToPeer);
223 if (sendToPeerStr == NULL) {
224 LOGE("Failed to pack sendToPeerStr for onTransmit!");
225 return HC_ERR_ALLOC_MEMORY;
226 }
227 if ((callback != NULL) && (callback->onTransmit != NULL)) {
228 LOGD("Begin to transmit data to peer for auth in DasOnFinishToPeer.");
229 UPDATE_PERFORM_DATA_BY_SELF_INDEX(requestId, HcGetCurTimeInMillis());
230 if (!callback->onTransmit(requestId, (uint8_t *)sendToPeerStr, (uint32_t)HcStrlen(sendToPeerStr) + 1)) {
231 LOGE("Failed to transmit data to peer!");
232 res = HC_ERR_TRANSMIT_FAIL;
233 }
234 LOGD("End to transmit data to peer for auth in DasOnFinishToPeer.");
235 }
236 FreeJsonString(sendToPeerStr);
237 return res;
238 }
239
ReportV1UnrelatedAuthCallEvent(int64_t requestId,const CJson * authParam)240 static void ReportV1UnrelatedAuthCallEvent(int64_t requestId, const CJson *authParam)
241 {
242 #ifdef DEV_AUTH_HIVIEW_ENABLE
243 DevAuthCallEvent eventData;
244 eventData.appId = SOFTBUS_APP_ID;
245 eventData.funcName = AUTH_DEV_EVENT;
246 eventData.osAccountId = DEFAULT_OS_ACCOUNT;
247 (void)GetIntFromJson(authParam, FIELD_OS_ACCOUNT_ID, &eventData.osAccountId);
248 eventData.callResult = DEFAULT_CALL_RESULT;
249 eventData.processCode = PROCESS_AUTH_V1;
250 eventData.credType = DEFAULT_CRED_TYPE;
251 eventData.groupType = PEER_TO_PEER_GROUP;
252 eventData.executionTime = GET_TOTAL_CONSUME_TIME_BY_REQ_ID(requestId);
253 eventData.extInfo = DEFAULT_EXT_INFO;
254 DEV_AUTH_REPORT_CALL_EVENT(eventData);
255 return;
256 #endif
257 (void)requestId;
258 (void)authParam;
259 return;
260 }
261
DasOnFinishToSelf(int64_t requestId,const CJson * authParam,const CJson * out,const DeviceAuthCallback * callback)262 static int32_t DasOnFinishToSelf(int64_t requestId, const CJson *authParam, const CJson *out,
263 const DeviceAuthCallback *callback)
264 {
265 const CJson *sendToSelf = GetObjFromJson(out, FIELD_SEND_TO_SELF);
266 if (sendToSelf == NULL) {
267 LOGE("No data to send to self for onFinish.");
268 return HC_ERR_LOST_DATA;
269 }
270 CJson *returnToSelf = CreateJson();
271 if (returnToSelf == NULL) {
272 LOGE("Create json failed!");
273 return HC_ERR_ALLOC_MEMORY;
274 }
275 int32_t res = PrepareDasReturnToSelfData(authParam, sendToSelf, returnToSelf);
276 if (res != HC_SUCCESS) {
277 LOGE("Failed to add das returnToSelf data!");
278 ClearSensitiveStringInJson(returnToSelf, FIELD_SESSION_KEY);
279 FreeJson(returnToSelf);
280 return res;
281 }
282 char *returnStr = PackJsonToString(returnToSelf);
283 ClearSensitiveStringInJson(returnToSelf, FIELD_SESSION_KEY);
284 FreeJson(returnToSelf);
285 if (returnStr == NULL) {
286 LOGE("Failed to pack returnToSelf for onFinish!");
287 return HC_ERR_ALLOC_MEMORY;
288 }
289 if ((callback != NULL) && (callback->onFinish != NULL)) {
290 LOGD("Group auth call onFinish for account unrelated auth.");
291 UPDATE_PERFORM_DATA_BY_INPUT_INDEX(requestId, ON_FINISH_TIME, HcGetCurTimeInMillis());
292 callback->onFinish(requestId, AUTH_FORM_ACCOUNT_UNRELATED, returnStr);
293 }
294 ClearAndFreeJsonString(returnStr);
295 ReportV1UnrelatedAuthCallEvent(requestId, authParam);
296 return res;
297 }
298
AddNonAccountPkgName(const TrustedGroupEntry * entry,CJson * paramsData)299 static int32_t AddNonAccountPkgName(const TrustedGroupEntry *entry, CJson *paramsData)
300 {
301 uint32_t groupType = entry->type;
302 if (groupType == COMPATIBLE_GROUP) {
303 if ((entry->managers).size(&(entry->managers)) == 0) {
304 LOGE("The manager size is 0!");
305 return HC_ERR_DB;
306 }
307 HcString ownerName = (entry->managers).get(&(entry->managers), 0);
308 const char *ownerNameStr = StringGet(&ownerName);
309 if (ownerNameStr == NULL) {
310 LOGE("Failed to get ownerName!");
311 return HC_ERR_DB;
312 }
313 if (AddStringToJson(paramsData, FIELD_SERVICE_PKG_NAME, ownerNameStr) != HC_SUCCESS) {
314 LOGE("Failed to add ownerName to json!");
315 return HC_ERR_JSON_FAIL;
316 }
317 } else {
318 if (AddStringToJson(paramsData, FIELD_SERVICE_PKG_NAME, GROUP_MANAGER_PACKAGE_NAME) != HC_SUCCESS) {
319 LOGE("Failed to add group manager name to json!");
320 return HC_ERR_JSON_FAIL;
321 }
322 }
323 return HC_SUCCESS;
324 }
325
AddNonAccountAuthInfo(const TrustedDeviceEntry * localAuthInfo,const TrustedDeviceEntry * peerAuthInfo,CJson * paramsData)326 static int32_t AddNonAccountAuthInfo(const TrustedDeviceEntry *localAuthInfo, const TrustedDeviceEntry *peerAuthInfo,
327 CJson *paramsData)
328 {
329 int32_t keyLen = DEFAULT_RETURN_KEY_LENGTH;
330 (void)GetIntFromJson(paramsData, FIELD_KEY_LENGTH, &keyLen);
331 if (AddIntToJson(paramsData, FIELD_KEY_LENGTH, keyLen) != HC_SUCCESS) {
332 LOGE("Failed to add keyLen for auth!");
333 return HC_ERR_JSON_FAIL;
334 }
335 if (AddStringToJson(paramsData, FIELD_SELF_AUTH_ID, StringGet(&localAuthInfo->authId))
336 != HC_SUCCESS) {
337 LOGE("Failed to add self authId to paramsData from db!");
338 return HC_ERR_JSON_FAIL;
339 }
340 if (AddIntToJson(paramsData, FIELD_SELF_TYPE, localAuthInfo->devType) != HC_SUCCESS) {
341 LOGE("Failed to add self devType to paramsData from db!");
342 return HC_ERR_JSON_FAIL;
343 }
344 const char *peerAuthId = GetStringFromJson(paramsData, FIELD_PEER_ID_FROM_REQUEST);
345 if (peerAuthId == NULL) {
346 peerAuthId = StringGet(&peerAuthInfo->authId);
347 }
348 if (AddStringToJson(paramsData, FIELD_PEER_AUTH_ID, peerAuthId) != HC_SUCCESS) {
349 LOGE("Failed to add peer authId to paramsData!");
350 return HC_ERR_JSON_FAIL;
351 }
352 if (AddIntToJson(paramsData, FIELD_PEER_USER_TYPE, peerAuthInfo->devType) != HC_SUCCESS) {
353 LOGE("Failed to add peer devType to paramsData from db!");
354 return HC_ERR_JSON_FAIL;
355 }
356 return HC_SUCCESS;
357 }
358
FillNonAccountAuthInfo(int32_t osAccountId,const TrustedGroupEntry * entry,const TrustedDeviceEntry * localAuthInfo,CJson * paramsData)359 static int32_t FillNonAccountAuthInfo(int32_t osAccountId, const TrustedGroupEntry *entry,
360 const TrustedDeviceEntry *localAuthInfo, CJson *paramsData)
361 {
362 int32_t res;
363 const char *groupId = StringGet(&entry->id);
364 TrustedDeviceEntry *peerAuthInfo = CreateDeviceEntry();
365 if (peerAuthInfo == NULL) {
366 LOGE("Failed to allocate devEntry memory for peerAuthInfo!");
367 return HC_ERR_ALLOC_MEMORY;
368 }
369 const char *peerUdid = GetStringFromJson(paramsData, FIELD_PEER_CONN_DEVICE_ID);
370 const char *peerAuthId = GetStringFromJson(paramsData, FIELD_PEER_ID_FROM_REQUEST);
371 if (peerAuthId == NULL) {
372 peerAuthId = GetStringFromJson(paramsData, FIELD_PEER_AUTH_ID);
373 }
374 if (peerUdid != NULL) {
375 res = GaGetTrustedDeviceEntryById(osAccountId, peerUdid, true, groupId, peerAuthInfo);
376 } else if (peerAuthId != NULL) {
377 res = GaGetTrustedDeviceEntryById(osAccountId, peerAuthId, false, groupId, peerAuthInfo);
378 } else {
379 LOGE("Invalid input, both peer udid and peer authId are null!");
380 res = HC_ERR_NULL_PTR;
381 }
382 do {
383 if (res != HC_SUCCESS) {
384 LOGE("Failed to get peer device info from database!");
385 break;
386 }
387 res = AddNonAccountPkgName(entry, paramsData);
388 if (res != HC_SUCCESS) {
389 LOGE("Failed to add pkg name to paramsData!");
390 break;
391 }
392 res = AddNonAccountAuthInfo(localAuthInfo, peerAuthInfo, paramsData);
393 if (res != HC_SUCCESS) {
394 LOGE("Failed to add device auth info for non-account group!");
395 break;
396 }
397 } while (0);
398 DestroyDeviceEntry(peerAuthInfo);
399 return res;
400 }
401
CombineDasServerConfirmParams(const CJson * confirmationJson,CJson * dataFromClient)402 static int32_t CombineDasServerConfirmParams(const CJson *confirmationJson, CJson *dataFromClient)
403 {
404 bool isClient = false;
405 if (AddBoolToJson(dataFromClient, FIELD_IS_CLIENT, isClient) != HC_SUCCESS) {
406 LOGE("Failed to combine server param for isClient!");
407 return HC_ERR_JSON_FAIL;
408 }
409 const char *pkgName = GetStringFromJson(confirmationJson, FIELD_SERVICE_PKG_NAME);
410 if (pkgName != NULL) {
411 if (AddStringToJson(dataFromClient, FIELD_SERVICE_PKG_NAME, pkgName) != HC_SUCCESS) {
412 LOGE("Failed to combine server param for pkgName!");
413 return HC_ERR_JSON_FAIL;
414 }
415 }
416
417 const char *peerUdid = GetStringFromJson(confirmationJson, FIELD_PEER_CONN_DEVICE_ID);
418 if (peerUdid != NULL) {
419 if (AddStringToJson(dataFromClient, FIELD_PEER_CONN_DEVICE_ID, peerUdid) != HC_SUCCESS) {
420 LOGE("Failed to combine server param for peerUdid!");
421 return HC_ERR_JSON_FAIL;
422 }
423 }
424 const char *peerAuthId = GetStringFromJson(confirmationJson, FIELD_PEER_AUTH_ID);
425 if (peerAuthId != NULL) {
426 if (AddStringToJson(dataFromClient, FIELD_PEER_ID_FROM_REQUEST, peerAuthId) != HC_SUCCESS) {
427 LOGE("Failed to combine server param for peerAuthId!");
428 return HC_ERR_JSON_FAIL;
429 }
430 }
431 return HC_SUCCESS;
432 }
433
GetAuthParamsVecForServer(const CJson * dataFromClient,ParamsVecForAuth * authParamsVec)434 static int32_t GetAuthParamsVecForServer(const CJson *dataFromClient, ParamsVecForAuth *authParamsVec)
435 {
436 LOGI("Begin get non-account auth params for server.");
437 int32_t osAccountId = ANY_OS_ACCOUNT;
438 if (GetIntFromJson(dataFromClient, FIELD_OS_ACCOUNT_ID, &osAccountId) != HC_SUCCESS) {
439 LOGE("Failed to get os accountId from dataFromClient!");
440 return HC_ERR_JSON_GET;
441 }
442 int32_t res = GetAuthParamsVec(osAccountId, dataFromClient, authParamsVec);
443 if (res != HC_SUCCESS) {
444 LOGE("Failed to get non-account auth params!");
445 }
446 return res;
447 }
448
OnDasFinish(int64_t requestId,const CJson * authParam,const CJson * out,const DeviceAuthCallback * callback)449 static void OnDasFinish(int64_t requestId, const CJson *authParam, const CJson *out,
450 const DeviceAuthCallback *callback)
451 {
452 LOGI("Begin call onFinish for non-account auth.");
453 if (DasOnFinishToPeer(requestId, out, callback) != HC_SUCCESS) {
454 LOGE("Failed to send data to peer when auth finished!");
455 return;
456 }
457 if (ReturnSessionKey(requestId, out, callback) != HC_SUCCESS) {
458 LOGE("Failed to return session key when auth finished!");
459 return;
460 }
461 if (DasOnFinishToSelf(requestId, authParam, out, callback) != HC_SUCCESS) {
462 LOGE("Failed to send data to self when auth finished!");
463 return;
464 }
465 LOGI("Call onFinish for non-account auth successfully.");
466 }
467
GetAccountUnrelatedGroupAuth(void)468 BaseGroupAuth *GetAccountUnrelatedGroupAuth(void)
469 {
470 return (BaseGroupAuth *)&g_nonAccountGroupAuth;
471 }
472