Merge "Update deprecated libprivilege-control API functions." into tizen
[platform/framework/native/appfw.git] / src / app / FApp_DataControlProviderManagerImpl.cpp
1 //
2 // Copyright (c) 2012 Samsung Electronics Co., Ltd.
3 //
4 // Licensed under the Apache License, Version 2.0 (the License);
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //     http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16
17 /**
18  * @file        FApp_DataControlProviderManagerImpl.cpp
19  * @brief       This is the implementation for the %_DataControlProviderManagerImpl class.
20  */
21
22 #include <new>
23 #include <unique_ptr.h>
24 #include <security-server.h>
25
26 #include <FBaseDataType.h>
27 #include <FBaseInteger.h>
28 #include <FBaseString.h>
29 #include <FBaseLongLong.h>
30 #include <FBaseColArrayList.h>
31 #include <FBaseColIList.h>
32 #include <FBaseSysLog.h>
33 #include <FBaseErrors.h>
34 #include <FAppTypes.h>
35 #include <FAppAppManager.h>
36 #include <FAppSqlDataControl.h>
37 #include <FAppMapDataControl.h>
38 #include <FAppDataControlProviderManager.h>
39 #include <FIoFile.h>
40
41 #include <FBase_StringConverter.h>
42 #include <FIo_DataControlResultSetImpl.h>
43 #include <FAppPkg_PackageManagerImpl.h>
44
45 #include "FApp_Aul.h"
46 #include "FApp_AppArg.h"
47 #include "FApp_AppInfo.h"
48 #include "FApp_AppImpl.h"
49 #include "FApp_AppControlManager.h"
50 #include "FApp_SqlDataControlImpl.h"
51 #include "FApp_MapDataControlImpl.h"
52 #include "FApp_DataControlProviderManagerImpl.h"
53 #include "FApp_DataControlManager.h"
54
55 using namespace std;
56 using namespace Tizen::Base;
57 using namespace Tizen::Base::Collection;
58 using namespace Tizen::Io;
59 using namespace Tizen::App::Package;
60
61 extern const char* _DATACONTROL_RESULT_DIR;
62 extern const char* _DATACONTROL_RESULT_COMPAT_DIR;
63
64 namespace Tizen { namespace App
65 {
66
67 static const int _MAX_ARGUMENT_SIZE = 16384; // 16KB
68
69 result
70 _DataControlProviderManagerImpl::SetSqlDataControlProviderEventListener(ISqlDataControlProviderEventListener* pListener)
71 {
72         _AppImpl* pAppImpl = _AppImpl::GetInstance();
73         SysTryReturn(NID_APP, pAppImpl, false, E_INVALID_STATE, "[E_INVALID_STATE] Getting _AppImpl instance failed.");
74
75         return pAppImpl->SetSqlDataControlProviderEventListener(pListener);
76 }
77
78 result
79 _DataControlProviderManagerImpl::SetMapDataControlProviderEventListener(IMapDataControlProviderEventListener* pListener)
80 {
81         _AppImpl* pAppImpl = _AppImpl::GetInstance();
82         SysTryReturn(NID_APP, pAppImpl, false, E_INVALID_STATE, "[E_INVALID_STATE] Getting _AppImpl instance failed.");
83
84         return pAppImpl->SetMapDataControlProviderEventListener(pListener);
85 }
86
87 result
88 _DataControlProviderManagerImpl::SendDataControlResult(RequestId reqId, _DataControlRequestType apiType,
89                 IDbEnumerator* pDbEnum, IList* pResultValueList, long long insertRowId, bool providerResult, const String* pErrorMsg)
90 {
91         ArrayList* pList = null;
92         String callerAppId;
93         int callerPid = -1;
94         String version;
95         String reqType;
96         int type = 0;
97         _DataControlRequestType requestType = _DATACONTROL_REQUEST_TYPE_UNDEFINED;
98         String providerId;
99         String callerReqId;
100         ArrayList* pResultArgList = null;
101         String* pResult = null;
102         String* pErrorMessage = null;
103         String* pTempFilePath = null;
104         String* pNo = null;
105         int pageNo = 0;
106         String* pCount = null;
107         int countPerPage = 0;
108         String* pResultCount = null;
109         String* pResultValue = null;
110         _AppArg resultArg;
111         String* pDataId = null;
112         result r = E_SUCCESS;
113         //int ret = -1;
114
115         _AppControlManager* pAppMgr = _AppControlManager::GetInstance();
116         SysTryReturnResult(NID_APP, pAppMgr, E_SYSTEM, "Failed to get instance.");
117
118         _ResultInfo* pResultInfo = pAppMgr->__resultManager.FindItem(static_cast< int >(reqId)); // provider reqId
119         SysTryReturnResult(NID_APP, pResultInfo, E_OBJ_NOT_FOUND,
120                         "The data control request specified with the req (%ld) did not exist.", reqId);
121
122         const _AppArg& arg = pResultInfo->arg; // request info
123
124         // key-based request
125         reqType = arg.GetValue(OSP_K_DATACONTROL_REQUEST_TYPE);
126         Integer::Parse(reqType, type);
127         requestType = static_cast< _DataControlRequestType >(type);
128         if (providerResult == true && apiType != requestType)
129         {
130                 if ((apiType == _DATACONTROL_REQUEST_TYPE_SQL_UPDATE /*UpdateDelete*/ && requestType == _DATACONTROL_REQUEST_TYPE_SQL_DELETE) ||
131                                 apiType == _DATACONTROL_REQUEST_TYPE_UNDEFINED /*MAP*/)
132                 {
133                         r = E_SUCCESS;
134                 }
135                 else
136                 {
137                         r = E_INVALID_ARG;
138                         SysLog(NID_APP, "[E_INVALID_ARG] This method cannot send the result set for the specified reqId.");
139                         goto CATCH;
140                 }
141         }
142
143         callerAppId = arg.GetCallerAppId();
144         callerPid = arg.GetCallerPid();
145         version = arg.GetValue(OSP_K_DATACONTROL_PROTOCOL_VERSION);
146         callerReqId = arg.GetValue(OSP_K_REQUEST_ID);
147         providerId = arg.GetValue(OSP_K_DATACONTROL_PROVIDER);
148
149         // list-based request
150         pList = _AppArg::GetListN(arg.GetBundle(), OSP_K_ARG);
151         SysTryCatch(NID_APP, pList, r = E_SYSTEM, E_SYSTEM, "[E_SYSTEM] invalid result object");
152
153         pDataId = dynamic_cast< String* >(pList->GetAt(0)); // request list[0]
154         SysTryCatch(NID_APP, pDataId, r = E_SYSTEM, E_SYSTEM, "[E_SYSTEM] invalid result object");
155
156         SysSecureLog(NID_APP, "[DC_PROV_SEND] > caller app: %ls, caller proc: %d, version: %ls requestType: %d, callerReq: %ls, provider: %ls, data: %ls",
157                         callerAppId.GetPointer(), callerPid, version.GetPointer(), requestType, callerReqId.GetPointer(), providerId.GetPointer(), pDataId->GetPointer());
158
159         // Serializes result
160         pResultArgList = new (std::nothrow) ArrayList();
161         SysTryCatch(NID_APP, pResultArgList, r = E_OUT_OF_MEMORY, E_OUT_OF_MEMORY,
162                         "[E_OUT_OF_MEMORY] The memory was insufficient.");
163         pResultArgList->Construct();
164
165         pResult = new (std::nothrow) String();
166         SysTryCatch(NID_APP, pResult, r = E_OUT_OF_MEMORY, E_OUT_OF_MEMORY,
167                         "[E_OUT_OF_MEMORY] The memory was insufficient.");
168         pResult->Append(static_cast< int >(providerResult));
169         pResultArgList->Add(*pResult); // result list[0]
170
171         if (pErrorMsg == null)
172         {
173                 pErrorMessage = new (std::nothrow) String();
174         }
175         else
176         {
177                 pErrorMessage = new (std::nothrow) String(*pErrorMsg);
178         }
179         SysTryCatch(NID_APP, pErrorMessage, r = E_OUT_OF_MEMORY, E_OUT_OF_MEMORY,
180                         "[E_OUT_OF_MEMORY] The memory was insufficient.");
181         SysTryCatch(NID_APP, pErrorMessage->GetLength() <= _MAX_ARGUMENT_SIZE, r = E_MAX_EXCEEDED, E_MAX_EXCEEDED,
182                         "[E_MAX_EXCEEDED] The size of sending argument (%d) exceeds the maximum limit.", pErrorMessage->GetLength());
183         pResultArgList->Add(*pErrorMessage); // result list[1]
184
185         switch (requestType)
186         {
187         case _DATACONTROL_REQUEST_TYPE_SQL_QUERY:
188         {
189                 if (pDbEnum)
190                 {
191                         unique_ptr<_DataControlResultSetImpl> pResultSet(new (std::nothrow) _DataControlResultSetImpl(reqId));
192                         SysTryCatch(NID_APP, pResultSet, r = E_OUT_OF_MEMORY, E_OUT_OF_MEMORY,
193                                         "[E_OUT_OF_MEMORY] The memory is insufficient.");
194
195                         r = pResultSet->FillWith(pDbEnum, version);
196                         SysTryCatch(NID_APP, !IsFailed(r), , E_SYSTEM,
197                                         "[E_SYSTEM] The method cannot proceed due to a severe system error.");
198
199                         String tempFilePath;
200                         if (version == L"ver_2.1.0.3")
201                         {
202                                 tempFilePath.Append(_DATACONTROL_RESULT_DIR);
203
204                                 DataControlProviderManager* pDcMgr = DataControlProviderManager::GetInstance();
205                                 SysTryCatch(NID_APP, pDcMgr != null, r = E_SYSTEM, E_SYSTEM, "[E_SYSTEM] Failed to get DataControlProviderManager instance.");
206                                 r = pDcMgr->__pDataControlProviderManagerImpl->AllowAccess(callerAppId);
207                                 SysTryCatch(NID_APP, !IsFailed(r), , r, "[%s] Propagating to caller...", GetErrorMessage(r));
208                         }
209                         else
210                         {
211                                 tempFilePath.Append(_DATACONTROL_RESULT_COMPAT_DIR);
212                         }
213                         tempFilePath.Append(callerAppId);
214                         tempFilePath.Append(callerReqId);
215                         pTempFilePath = new (std::nothrow) String(tempFilePath);
216                 }
217                 else
218                 {
219                         pTempFilePath = new (std::nothrow) String(L"NoResultSet");
220                 }
221                 SysTryCatch(NID_APP, pTempFilePath, r = E_OUT_OF_MEMORY, E_OUT_OF_MEMORY,
222                                 "[E_OUT_OF_MEMORY] The memory is insufficient.");
223
224                 pResultArgList->Add(*pTempFilePath); // result list[2]
225                 break;
226         }
227         case _DATACONTROL_REQUEST_TYPE_SQL_INSERT:
228                 pResultArgList->Add(*(new String(LongLong::ToString(insertRowId)))); // result list[2]
229                 break;
230
231         case _DATACONTROL_REQUEST_TYPE_SQL_UPDATE:
232                 // fall through
233         case _DATACONTROL_REQUEST_TYPE_SQL_DELETE:
234                 break;
235
236         case _DATACONTROL_REQUEST_TYPE_MAP_QUERY:
237         {
238                 SysTryCatch(NID_APP, !(providerResult == true && pResultValueList == null), r = E_INVALID_ARG, E_INVALID_ARG,
239                                 "[E_INVALID_ARG] The specified pResultValueList should not be null if the request is GetValue query.");
240
241                 if (pResultValueList)
242                 {
243                         // list-based request
244                         pNo = dynamic_cast< String* >(pList->GetAt(2)); // request list[2]
245                         SysTryCatch(NID_APP, pNo, r = E_SYSTEM, E_SYSTEM, "[E_SYSTEM] invalid result object");
246                         Integer::Parse(*pNo, pageNo);
247
248                         pCount = dynamic_cast< String* >(pList->GetAt(3)); // request list[3]
249                         SysTryCatch(NID_APP, pCount, r = E_SYSTEM, E_SYSTEM, "[E_SYSTEM] invalid result object");
250                         Integer::Parse(*pCount, countPerPage);
251                         SysLog(NID_APP, "[DC_PROV_SEND] pageNo: %d, countPerPage: %d", pageNo, countPerPage);
252
253                         String resultCount;
254                         int num = pResultValueList->GetCount();
255                         int currentoffset = (pageNo - 1) * countPerPage;
256                         int remainingNum = num - currentoffset;
257                         remainingNum = (remainingNum > 0) ? remainingNum : 0; // round off to zero if negative num is found
258                         int addItemCount = (countPerPage > remainingNum) ? remainingNum : countPerPage;
259                         resultCount.Append(addItemCount);
260
261                         pResultCount = new (std::nothrow) String(resultCount);
262                         SysTryCatch(NID_APP, pResultCount, r = E_OUT_OF_MEMORY, E_OUT_OF_MEMORY,
263                                         "[E_OUT_OF_MEMORY] The memory was insufficient.");
264                         pResultArgList->Add(*pResultCount); // result list[2]
265                         SysLog(NID_APP, "[DC_PROV_SEND] result count: %ls", resultCount.GetPointer());
266
267                         long long argSize = 0;
268                         if (addItemCount > 0)
269                         {
270                                 if (version == L"ver_2.1.0.1" || version == L"ver_2.1.0.2" || version == L"ver_2.1.0.3")
271                                 {
272                                         String tempFilePath;
273                                         if (version == L"ver_2.1.0.3")
274                                         {
275                                                 tempFilePath.Append(_DATACONTROL_RESULT_DIR);
276                                         }
277                                         else
278                                         {
279                                                 tempFilePath.Append(_DATACONTROL_RESULT_COMPAT_DIR);
280                                         }
281                                         tempFilePath.Append(callerAppId);
282                                         tempFilePath.Append(callerReqId);
283                                         pTempFilePath = new (std::nothrow) String(tempFilePath);
284                                         pResultArgList->Add(*pTempFilePath); // result list[3]
285                                         SysLog(NID_APP, "[DC_PROV_SEND] protocol version: %ls, path: %ls", version.GetPointer(), pTempFilePath->GetPointer());
286
287                                         unique_ptr< File > pFile(new (std::nothrow) File());
288                                         SysTryCatch(NID_APP, pFile != null, , E_OUT_OF_MEMORY, "[E_OUT_OF_MEMORY] The memory is insufficient.");
289
290                                         r = pFile->Construct(*pTempFilePath, L"w+", true);
291                                         SysTryCatch(NID_APP, !IsFailed(r), , r, "[%s] Failed to create temp file (%ls) for result set.",
292                                                         GetErrorMessage(r), pTempFilePath->GetPointer());
293
294                                         for (int i = currentoffset; i < num; ++i)
295                                         {
296                                                 String* pValue = dynamic_cast< String* >(pResultValueList->GetAt(i));
297                                                 SysTryCatch(NID_APP, pValue != null, r = E_INVALID_ARG, E_INVALID_ARG,
298                                                                 "[E_INVALID_ARG] The specified pResultValueList parameter should be String class.");
299
300                                                 unique_ptr< char[] > pData(_StringConverter::CopyToCharArrayN(*pValue));
301                                                 SysTryCatch(NID_APP, pData != null, , GetLastResult(), "[%s] Invalid result value",
302                                                                 GetErrorMessage(GetLastResult()));
303
304                                                 int length = strlen(pData.get());
305                                                 r = pFile->Write(&length, sizeof(int)); // data length
306                                                 SysTryCatch(NID_APP, !IsFailed(r), , E_SYSTEM, "[%s] Failed to send result.", GetErrorMessage(r));
307
308                                                 r = pFile->Write(pData.get(), length); // data
309                                                 SysTryCatch(NID_APP, !IsFailed(r), , E_SYSTEM, "[%s] Failed to send result.", GetErrorMessage(r));
310                                         }
311                                         pFile->Flush();
312
313                                         if (version == L"ver_2.1.0.3")
314                                         {
315                                                 DataControlProviderManager* pDcMgr = DataControlProviderManager::GetInstance();
316                                                 SysTryCatch(NID_APP, pDcMgr != null, r = E_SYSTEM, E_SYSTEM, "[E_SYSTEM] Failed to get DataControlProviderManager instance.");
317                                                 r = pDcMgr->__pDataControlProviderManagerImpl->AllowAccess(callerAppId);
318                                                 SysTryCatch(NID_APP, !IsFailed(r), , r, "[%s] Propagating to caller...", GetErrorMessage(r));
319                                         }
320                                 }
321                                 else
322                                 {
323                                         SysLog(NID_APP, "[DC_PROV_SEND] protocol version: none");
324                                         for (int i = currentoffset; i < num; ++i)
325                                         {
326                                                 String* pValue = dynamic_cast< String* >(pResultValueList->GetAt(i));
327                                                 SysTryCatch(NID_APP, pValue != null, r = E_INVALID_ARG, E_INVALID_ARG,
328                                                                 "[E_INVALID_ARG] The specified pResultValueList parameter should be String class.");
329
330                                                 pResultValue = new (std::nothrow) String(*pValue);
331                                                 SysTryCatch(NID_APP, pResultValue, r = E_OUT_OF_MEMORY, E_OUT_OF_MEMORY,
332                                                                 "[E_OUT_OF_MEMORY] The memory was insufficient.");
333                                                 pResultArgList->Add(*pResultValue); // list[3] ~
334                                                 argSize += pValue->GetLength() * sizeof(wchar_t);
335                                         }
336                                         SysTryCatch(NID_APP, argSize <= _MAX_ARGUMENT_SIZE, r = E_MAX_EXCEEDED, E_MAX_EXCEEDED,
337                                                         "[E_MAX_EXCEEDED] The size of sending argument (%d) exceeds the maximum limit.", argSize);
338                                 }
339                         }
340                 }
341                 break;
342         }
343         case _DATACONTROL_REQUEST_TYPE_MAP_INSERT:
344                 // fall through
345         case _DATACONTROL_REQUEST_TYPE_MAP_UPDATE:
346                 // fall through
347         case _DATACONTROL_REQUEST_TYPE_MAP_DELETE:
348                 SysTryCatch(NID_APP, !(providerResult == true && pResultValueList != null), r = E_INVALID_ARG, E_INVALID_ARG,
349                                 "[E_INVALID_ARG] The specified pResultValueList should be null if the request is \
350                                 one of AddValue, SetValue, RemoveValue queries.");
351                 break;
352
353         default:
354                 SysTryCatch(NID_APP, false, r = E_SYSTEM, E_SYSTEM, "[E_SYSTEM] invalid result object");
355                 break;
356         }
357
358         r = resultArg.ConstructResult(arg, pResultArgList);
359         SysTryCatch(NID_APP, !IsFailed(r), r = E_SYSTEM, r, "[%s] Propagating.", GetErrorMessage(r));
360         resultArg.UpdateKeyValue(OSP_K_REQUEST_ID, callerReqId);
361         resultArg.UpdateKeyValue(OSP_K_DATACONTROL_REQUEST_TYPE, reqType);
362         resultArg.UpdateKeyValue(OSP_K_DATACONTROL_PROVIDER, providerId);
363         resultArg.UpdateKeyValue(OSP_K_DATACONTROL_DATA, *pDataId);
364         resultArg.UpdateKeyValue(OSP_K_DATACONTROL_PROTOCOL_VERSION, version);
365
366         //resultArg.Print();
367         r = _Aul::SendResult(resultArg.GetBundle(), static_cast< appsvc_result_val >(0), false);
368         SysTryCatch(NID_APP, !IsFailed(r), , r, "[%s] Failed to send result.", GetErrorMessage(r));
369
370         // Erases _AppArg after sending the result back to the caller
371         pAppMgr->__resultManager.RemoveItem(static_cast< int >(reqId));
372
373 CATCH:
374         if (pList)
375         {
376                 pList->RemoveAll(true);
377         }
378         delete pList;
379
380         if (pResultArgList)
381         {
382                 pResultArgList->RemoveAll(true);
383         }
384         delete pResultArgList;
385
386         return r;
387 }
388
389 result
390 _DataControlProviderManagerImpl::SendSqlDataControlSelectResult(RequestId reqId, IDbEnumerator* pDbEnum)
391 {
392         return _DataControlProviderManagerImpl::SendDataControlResult(reqId, _DATACONTROL_REQUEST_TYPE_SQL_QUERY,
393                         pDbEnum, null, -1, true, null);
394 }
395
396 result
397 _DataControlProviderManagerImpl::SendSqlDataControlInsertResult(RequestId reqId, long long insertRowId)
398 {
399         return _DataControlProviderManagerImpl::SendDataControlResult(reqId, _DATACONTROL_REQUEST_TYPE_SQL_INSERT,
400                         null, null, insertRowId, true, null);
401 }
402
403 result
404 _DataControlProviderManagerImpl::SendSqlDataControlUpdateDeleteResult(RequestId reqId)
405 {
406         return _DataControlProviderManagerImpl::SendDataControlResult(reqId, _DATACONTROL_REQUEST_TYPE_SQL_UPDATE,
407                         null, null, -1, true, null);
408 }
409
410 result
411 _DataControlProviderManagerImpl::SendMapDataControlResult(RequestId reqId, IList* pResultValueList)
412 {
413         return _DataControlProviderManagerImpl::SendDataControlResult(reqId, _DATACONTROL_REQUEST_TYPE_UNDEFINED,
414                         null, pResultValueList, -1, true, null);
415 }
416
417 result
418 _DataControlProviderManagerImpl::SendDataControlError(RequestId reqId, const String& errorMsg)
419 {
420         return _DataControlProviderManagerImpl::SendDataControlResult(reqId, _DATACONTROL_REQUEST_TYPE_UNDEFINED,
421                         null, null, -1, false, &errorMsg);
422 }
423
424 DataControlProviderManager*
425 _DataControlProviderManagerImpl::GetInstance(void)
426 {
427         static DataControlProviderManager* pDcMgr = null;
428
429         if (pDcMgr == null)
430         {
431                 pDcMgr = new (std::nothrow) DataControlProviderManager();
432                 SysTryReturn(NID_APP, pDcMgr, null, E_OUT_OF_MEMORY, "[E_OUT_OF_MEMORY] The memory was insufficient.");
433         }
434
435         return pDcMgr;
436 }
437
438 void
439 _DataControlProviderManagerImpl::Cache(const AppId& appId)
440 {
441         __pProviderList->Add(new (std::nothrow) String(appId));
442 }
443
444 bool
445 _DataControlProviderManagerImpl::IsCached(const AppId& appId)
446 {
447         unique_ptr< IEnumerator > pEnum(__pProviderList->GetEnumeratorN());
448         while (pEnum->MoveNext() == E_SUCCESS)
449         {
450                 String* pCachedAppId = dynamic_cast< String* >(pEnum->GetCurrent());
451                 if (pCachedAppId != null && pCachedAppId->Equals(appId) == true)
452                 {
453                         return true;
454                 }
455         }
456         return false;
457 }
458
459 result
460 _DataControlProviderManagerImpl::AllowAccess(const AppId& appId)
461 {
462         //if (IsCached(appId) == false)
463         //{
464                 const PackageId& pkgId = _PackageManagerImpl::GetPackageIdByAppId(appId);
465                 unique_ptr< char[] > pPkgId(_StringConverter::CopyToCharArrayN(pkgId));
466                 SysTryReturnResult(NID_APP, pPkgId != null, E_SYSTEM, "The method cannot proceed due to a severe system error.");
467
468                 int ret = security_server_app_give_access(pPkgId.get(), -1);
469                 SysTryReturnResult(NID_APP, ret == 0, E_SYSTEM,
470                                 "Failed to call security_server_app_give_access(), provider: %s, ret: %d", pPkgId.get(), ret);
471
472         //      Cache(appId);
473         //}
474
475         SysLog(NID_APP, "[DC_PROV_SEND] Allow %ls to access", appId.GetPointer());
476         return E_SUCCESS;
477 }
478
479 _DataControlProviderManagerImpl::_DataControlProviderManagerImpl(void)
480         : __pProviderList(null)
481 {
482         __pProviderList = new (std::nothrow) LinkedList(SingleObjectDeleter);
483         SysTryReturnVoidResult(NID_APP, __pProviderList != null, E_OUT_OF_MEMORY, "[E_OUT_OF_MEMORY] The memory is insufficient.");
484 }
485
486 _DataControlProviderManagerImpl::~_DataControlProviderManagerImpl(void)
487 {
488         delete __pProviderList;
489 }
490
491 }} // Tizen::App
492