1 /*
2 * Copyright (c) 2021-2022 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 "zip_file.h"
17
18 #include <ostream>
19
20 #include "app_log_wrapper.h"
21 #include "bundle_service_constants.h"
22 #include "securec.h"
23
24 namespace OHOS {
25 namespace AppExecFwk {
26 namespace {
27 constexpr uint32_t MAX_FILE_PATH = 4096;
28 constexpr uint32_t UNZIP_BUFFER_SIZE = 1024;
29 constexpr uint32_t UNZIP_BUF_IN_LEN = 160 * UNZIP_BUFFER_SIZE; // in buffer length: 160KB
30 constexpr uint32_t UNZIP_BUF_OUT_LEN = 320 * UNZIP_BUFFER_SIZE; // out buffer length: 320KB
31 constexpr uint32_t LOCAL_HEADER_SIGNATURE = 0x04034b50;
32 constexpr uint32_t CENTRAL_SIGNATURE = 0x02014b50;
33 constexpr uint32_t EOCD_SIGNATURE = 0x06054b50;
34 constexpr uint32_t DATA_DESC_SIGNATURE = 0x08074b50;
35 constexpr uint32_t FLAG_DATA_DESC = 0x8;
36 constexpr size_t FILE_READ_COUNT = 1;
37 constexpr uint8_t INFLATE_ERROR_TIMES = 5;
38 } // namespace
39
ZipEntry(const CentralDirEntry & centralEntry)40 ZipEntry::ZipEntry(const CentralDirEntry ¢ralEntry)
41 {
42 compressionMethod = centralEntry.compressionMethod;
43 uncompressedSize = centralEntry.uncompressedSize;
44 compressedSize = centralEntry.compressedSize;
45 localHeaderOffset = centralEntry.localHeaderOffset;
46 crc = centralEntry.crc;
47 flags = centralEntry.flags;
48 }
49
ZipFile(const std::string & pathName)50 ZipFile::ZipFile(const std::string &pathName) : pathName_(pathName)
51 {
52 APP_LOGD("create instance from %{private}s", pathName_.c_str());
53 }
54
~ZipFile()55 ZipFile::~ZipFile()
56 {
57 Close();
58 }
59
SetContentLocation(const ZipPos start,const size_t length)60 void ZipFile::SetContentLocation(const ZipPos start, const size_t length)
61 {
62 APP_LOGD("set content location start position(%{public}llu), length(%{public}zu)", start, length);
63 fileStartPos_ = start;
64 fileLength_ = length;
65 }
66
CheckEndDir(const EndDir & endDir) const67 bool ZipFile::CheckEndDir(const EndDir &endDir) const
68 {
69 size_t lenEndDir = sizeof(EndDir);
70 if ((endDir.numDisk != 0) || (endDir.signature != EOCD_SIGNATURE) || (endDir.startDiskOfCentralDir != 0) ||
71 (endDir.offset >= fileLength_) || (endDir.totalEntriesInThisDisk != endDir.totalEntries) ||
72 (endDir.commentLen != 0) ||
73 // central dir can't overlap end of central dir
74 ((endDir.offset + endDir.sizeOfCentralDir + lenEndDir) > fileLength_)) {
75 APP_LOGE("end dir format error");
76 return false;
77 }
78 return true;
79 }
80
ParseEndDirectory()81 bool ZipFile::ParseEndDirectory()
82 {
83 size_t endDirLen = sizeof(EndDir);
84 size_t endFilePos = fileStartPos_ + fileLength_;
85
86 if (fileLength_ <= endDirLen) {
87 APP_LOGE("parse EOCD file length(%{public}llu) <= end dir length(%{public}llu)", fileStartPos_, fileLength_);
88 return false;
89 }
90
91 size_t eocdPos = endFilePos - endDirLen;
92 if (fseek(file_, eocdPos, SEEK_SET) != 0) {
93 APP_LOGE("locate EOCD seek failed, error: %{public}d", errno);
94 return false;
95 }
96
97 if (fread(&endDir_, sizeof(EndDir), FILE_READ_COUNT, file_) != FILE_READ_COUNT) {
98 APP_LOGE("read EOCD struct failed, error: %{public}d", errno);
99 return false;
100 }
101
102 centralDirPos_ = endDir_.offset + fileStartPos_;
103 APP_LOGD("parse EOCD offset(0x%{public}08x) file start position(0x%{public}08llx)", endDir_.offset, fileStartPos_);
104
105 return CheckEndDir(endDir_);
106 }
107
ParseAllEntries()108 bool ZipFile::ParseAllEntries()
109 {
110 bool ret = true;
111 ZipPos currentPos = centralDirPos_;
112 CentralDirEntry directoryEntry = {0};
113
114 for (uint16_t i = 0; i < endDir_.totalEntries; i++) {
115 std::string fileName;
116 fileName.reserve(MAX_FILE_PATH);
117 fileName.resize(MAX_FILE_PATH - 1);
118
119 if (fseek(file_, currentPos, SEEK_SET) != 0) {
120 APP_LOGE("parse entry(%{public}d) seek zipEntry failed, error: %{public}d", i, errno);
121 ret = false;
122 break;
123 }
124
125 if (fread(&directoryEntry, sizeof(CentralDirEntry), FILE_READ_COUNT, file_) != FILE_READ_COUNT) {
126 APP_LOGE("parse entry(%{public}d) read ZipEntry failed, error: %{public}d", i, errno);
127 ret = false;
128 break;
129 }
130
131 if (directoryEntry.signature != CENTRAL_SIGNATURE) {
132 APP_LOGE("parse entry(%{public}d) check signature(0x%08x) at pos(0x%08llx) failed",
133 i,
134 directoryEntry.signature,
135 currentPos);
136 ret = false;
137 break;
138 }
139
140 size_t fileLength = (directoryEntry.nameSize >= MAX_FILE_PATH) ? (MAX_FILE_PATH - 1) : directoryEntry.nameSize;
141 if (fread(&(fileName[0]), fileLength, FILE_READ_COUNT, file_) != FILE_READ_COUNT) {
142 APP_LOGE("parse entry(%{public}d) read file name failed, error: %{public}d", i, errno);
143 ret = false;
144 break;
145 }
146 fileName.resize(fileLength);
147
148 ZipEntry currentEntry(directoryEntry);
149 currentEntry.fileName = fileName;
150 entriesMap_[fileName] = currentEntry;
151
152 currentPos += sizeof(directoryEntry);
153 currentPos += directoryEntry.nameSize + directoryEntry.extraSize + directoryEntry.commentSize;
154 }
155
156 APP_LOGD("parse %{public}d central entries from %{private}s", endDir_.totalEntries, pathName_.c_str());
157 return ret;
158 }
159
Open()160 bool ZipFile::Open()
161 {
162 APP_LOGD("open: %{private}s", pathName_.c_str());
163
164 if (isOpen_) {
165 APP_LOGE("has already opened");
166 return true;
167 }
168
169 if (pathName_.length() > PATH_MAX) {
170 APP_LOGE("path length(%{public}u) longer than max length(%{public}d)",
171 static_cast<unsigned int>(pathName_.length()),
172 PATH_MAX);
173 return false;
174 }
175 std::string realPath;
176 realPath.reserve(PATH_MAX);
177 realPath.resize(PATH_MAX - 1);
178 if (realpath(pathName_.c_str(), &(realPath[0])) == nullptr) {
179 APP_LOGE("transform real path error: %{public}d", errno);
180 return false;
181 }
182
183 FILE *tmpFile = fopen(realPath.c_str(), "rb");
184 if (tmpFile == nullptr) {
185 APP_LOGE("open file(%{private}s) failed, error: %{public}d", pathName_.c_str(), errno);
186 return false;
187 }
188
189 if (fileLength_ == 0) {
190 if (fseek(tmpFile, 0, SEEK_END) != 0) {
191 APP_LOGE("file seek failed, error: %{public}d", errno);
192 fclose(tmpFile);
193 return false;
194 }
195 int64_t fileLength = ftell(tmpFile);
196 if (fileLength == -1) {
197 APP_LOGE("open file %{private}s failed", pathName_.c_str());
198 fclose(tmpFile);
199 return false;
200 }
201 fileLength_ = static_cast<ZipPos>(fileLength);
202 if (fileStartPos_ >= fileLength_) {
203 APP_LOGE("open start pos > length failed");
204 fclose(tmpFile);
205 return false;
206 }
207
208 fileLength_ -= fileStartPos_;
209 }
210
211 file_ = tmpFile;
212 bool result = ParseEndDirectory();
213 if (result) {
214 result = ParseAllEntries();
215 }
216 // it means open file success.
217 isOpen_ = true;
218 return result;
219 }
220
Close()221 void ZipFile::Close()
222 {
223 APP_LOGD("close: %{private}s", pathName_.c_str());
224
225 if (!isOpen_ || file_ == nullptr) {
226 APP_LOGW("file is not opened");
227 return;
228 }
229
230 entriesMap_.clear();
231 pathName_ = "";
232 isOpen_ = false;
233
234 if (fclose(file_) != 0) {
235 APP_LOGW("close failed err: %{public}d", errno);
236 }
237 file_ = nullptr;
238 }
239
240 // Get all file zipEntry in this file
GetAllEntries() const241 const ZipEntryMap &ZipFile::GetAllEntries() const
242 {
243 return entriesMap_;
244 }
245
HasEntry(const std::string & entryName) const246 bool ZipFile::HasEntry(const std::string &entryName) const
247 {
248 return entriesMap_.find(entryName) != entriesMap_.end();
249 }
250
IsDirExist(const std::string & dir) const251 bool ZipFile::IsDirExist(const std::string &dir) const
252 {
253 APP_LOGD("target dir: %{public}s", dir.c_str());
254 if (dir.empty()) {
255 APP_LOGE("target dir is empty");
256 return false;
257 }
258
259 auto tempDir = dir;
260 if (tempDir.back() != ServiceConstants::FILE_SEPARATOR_CHAR) {
261 tempDir.push_back(ServiceConstants::FILE_SEPARATOR_CHAR);
262 }
263
264 for (const auto &item : entriesMap_) {
265 if (item.first.find(tempDir) == 0) {
266 APP_LOGD("find target dir, fileName : %{public}s", item.first.c_str());
267 return true;
268 }
269 }
270 APP_LOGD("target dir not found, dir : %{public}s", dir.c_str());
271 return false;
272 }
273
GetEntry(const std::string & entryName,ZipEntry & resultEntry) const274 bool ZipFile::GetEntry(const std::string &entryName, ZipEntry &resultEntry) const
275 {
276 APP_LOGD("get entry by name: %{public}s", entryName.c_str());
277 auto iter = entriesMap_.find(entryName);
278 if (iter != entriesMap_.end()) {
279 resultEntry = iter->second;
280 APP_LOGD("get entry succeed");
281 return true;
282 }
283 APP_LOGE("get entry failed");
284 return false;
285 }
286
GetLocalHeaderSize(const uint16_t nameSize,const uint16_t extraSize) const287 size_t ZipFile::GetLocalHeaderSize(const uint16_t nameSize, const uint16_t extraSize) const
288 {
289 return sizeof(LocalHeader) + nameSize + extraSize;
290 }
291
CheckDataDesc(const ZipEntry & zipEntry,const LocalHeader & localHeader) const292 bool ZipFile::CheckDataDesc(const ZipEntry &zipEntry, const LocalHeader &localHeader) const
293 {
294 uint32_t crcLocal = 0;
295 uint32_t compressedLocal = 0;
296 uint32_t uncompressedLocal = 0;
297
298 if (localHeader.flags & FLAG_DATA_DESC) { // use data desc
299 DataDesc dataDesc;
300 auto descPos = zipEntry.localHeaderOffset + GetLocalHeaderSize(localHeader.nameSize, localHeader.extraSize);
301 descPos += fileStartPos_ + zipEntry.compressedSize;
302
303 if (fseek(file_, descPos, SEEK_SET) != 0) {
304 APP_LOGE("check local header seek datadesc failed, error: %{public}d", errno);
305 return false;
306 }
307
308 if (fread(&dataDesc, sizeof(DataDesc), FILE_READ_COUNT, file_) != FILE_READ_COUNT) {
309 APP_LOGE("check local header read datadesc failed, error: %{public}d", errno);
310 return false;
311 }
312
313 if (dataDesc.signature != DATA_DESC_SIGNATURE) {
314 APP_LOGE("check local header check datadesc signature failed");
315 return false;
316 }
317
318 crcLocal = dataDesc.crc;
319 compressedLocal = dataDesc.compressedSize;
320 uncompressedLocal = dataDesc.uncompressedSize;
321 } else {
322 crcLocal = localHeader.crc;
323 compressedLocal = localHeader.compressedSize;
324 uncompressedLocal = localHeader.uncompressedSize;
325 }
326
327 if ((zipEntry.crc != crcLocal) || (zipEntry.compressedSize != compressedLocal) ||
328 (zipEntry.uncompressedSize != uncompressedLocal)) {
329 APP_LOGE("check local header compressed size corrupted");
330 return false;
331 }
332
333 return true;
334 }
335
CheckCoherencyLocalHeader(const ZipEntry & zipEntry,uint16_t & extraSize) const336 bool ZipFile::CheckCoherencyLocalHeader(const ZipEntry &zipEntry, uint16_t &extraSize) const
337 {
338 LocalHeader localHeader = {0};
339
340 if (zipEntry.localHeaderOffset >= fileLength_) {
341 APP_LOGE("check local file header offset overflow %{public}d", zipEntry.localHeaderOffset);
342 return false;
343 }
344
345 if (fseek(file_, fileStartPos_ + zipEntry.localHeaderOffset, SEEK_SET) != 0) {
346 APP_LOGE("check local header seek failed, error: %{public}d", errno);
347 return false;
348 }
349
350 if (fread(&localHeader, sizeof(LocalHeader), FILE_READ_COUNT, file_) != FILE_READ_COUNT) {
351 APP_LOGE("check local header read localheader failed, error: %{public}d", errno);
352 return false;
353 }
354
355 if ((localHeader.signature != LOCAL_HEADER_SIGNATURE) ||
356 (zipEntry.compressionMethod != localHeader.compressionMethod)) {
357 APP_LOGE("check local header signature or compressionMethod failed");
358 return false;
359 }
360
361 // current only support store and Z_DEFLATED method
362 if ((zipEntry.compressionMethod != Z_DEFLATED) && (zipEntry.compressionMethod != 0)) {
363 APP_LOGE("check local header compressionMethod(%{public}d) not support", zipEntry.compressionMethod);
364 return false;
365 }
366
367 std::string fileName;
368 fileName.reserve(MAX_FILE_PATH);
369 fileName.resize(MAX_FILE_PATH - 1);
370 size_t fileLength = (localHeader.nameSize >= MAX_FILE_PATH) ? (MAX_FILE_PATH - 1) : localHeader.nameSize;
371 if (fileLength != zipEntry.fileName.length()) {
372 APP_LOGE("check local header file name size failed");
373 return false;
374 }
375 if (fread(&(fileName[0]), fileLength, FILE_READ_COUNT, file_) != FILE_READ_COUNT) {
376 APP_LOGE("check local header read file name failed, error: %{public}d", errno);
377 return false;
378 }
379 fileName.resize(fileLength);
380 if (zipEntry.fileName != fileName) {
381 APP_LOGE("check local header file name corrupted");
382 return false;
383 }
384
385 if (!CheckDataDesc(zipEntry, localHeader)) {
386 APP_LOGE("check data desc failed");
387 return false;
388 }
389
390 extraSize = localHeader.extraSize;
391 return true;
392 }
393
SeekToEntryStart(const ZipEntry & zipEntry,const uint16_t extraSize) const394 bool ZipFile::SeekToEntryStart(const ZipEntry &zipEntry, const uint16_t extraSize) const
395 {
396 ZipPos startOffset = zipEntry.localHeaderOffset;
397 // get data offset, add signature+localheader+namesize+extrasize
398 startOffset += GetLocalHeaderSize(zipEntry.fileName.length(), extraSize);
399 if (startOffset + zipEntry.compressedSize > fileLength_) {
400 APP_LOGE("startOffset(%{public}lld)+entryCompressedSize(%{public}ud) > fileLength(%{public}llu)",
401 startOffset,
402 zipEntry.compressedSize,
403 fileLength_);
404 return false;
405 }
406 startOffset += fileStartPos_; // add file start relative to file stream
407
408 APP_LOGD("seek to entry start 0x%{public}08llx", startOffset);
409 if (fseek(file_, startOffset, SEEK_SET) != 0) {
410 APP_LOGE("seek failed, error: %{public}d", errno);
411 return false;
412 }
413 return true;
414 }
415
UnzipWithStore(const ZipEntry & zipEntry,const uint16_t extraSize,std::ostream & dest) const416 bool ZipFile::UnzipWithStore(const ZipEntry &zipEntry, const uint16_t extraSize, std::ostream &dest) const
417 {
418 APP_LOGD("unzip with store");
419
420 if (!SeekToEntryStart(zipEntry, extraSize)) {
421 APP_LOGE("seek to entry start failed");
422 return false;
423 }
424
425 uint32_t remainSize = zipEntry.compressedSize;
426 std::string readBuffer;
427 readBuffer.reserve(UNZIP_BUF_OUT_LEN);
428 readBuffer.resize(UNZIP_BUF_OUT_LEN - 1);
429 while (remainSize > 0) {
430 size_t readBytes;
431 size_t readLen = (remainSize > UNZIP_BUF_OUT_LEN) ? UNZIP_BUF_OUT_LEN : remainSize;
432 readBytes = fread(&(readBuffer[0]), sizeof(Byte), readLen, file_);
433 if (readBytes == 0) {
434 APP_LOGE("unzip store read failed, error: %{public}d", ferror(file_));
435 return false;
436 }
437 remainSize -= readBytes;
438 dest.write(&(readBuffer[0]), readBytes);
439 }
440
441 return true;
442 }
443
InitZStream(z_stream & zstream) const444 bool ZipFile::InitZStream(z_stream &zstream) const
445 {
446 // init zlib stream
447 if (memset_s(&zstream, sizeof(z_stream), 0, sizeof(z_stream))) {
448 APP_LOGE("unzip stream buffer init failed");
449 return false;
450 }
451 int32_t zlibErr = inflateInit2(&zstream, -MAX_WBITS);
452 if (zlibErr != Z_OK) {
453 APP_LOGE("unzip inflated init failed");
454 return false;
455 }
456
457 BytePtr bufOut = new (std::nothrow) Byte[UNZIP_BUF_OUT_LEN];
458 if (bufOut == nullptr) {
459 APP_LOGE("unzip inflated new out buffer failed");
460 return false;
461 }
462
463 BytePtr bufIn = new (std::nothrow) Byte[UNZIP_BUF_IN_LEN];
464 if (bufIn == nullptr) {
465 APP_LOGE("unzip inflated new in buffer failed");
466 delete[] bufOut;
467 return false;
468 }
469 zstream.next_out = bufOut;
470 zstream.next_in = bufIn;
471 zstream.avail_out = UNZIP_BUF_OUT_LEN;
472 return true;
473 }
474
ReadZStream(const BytePtr & buffer,z_stream & zstream,uint32_t & remainCompressedSize) const475 bool ZipFile::ReadZStream(const BytePtr &buffer, z_stream &zstream, uint32_t &remainCompressedSize) const
476 {
477 if (zstream.avail_in == 0) {
478 size_t readBytes;
479 size_t remainBytes = (remainCompressedSize > UNZIP_BUF_IN_LEN) ? UNZIP_BUF_IN_LEN : remainCompressedSize;
480 readBytes = fread(buffer, sizeof(Byte), remainBytes, file_);
481 if (readBytes == 0) {
482 APP_LOGE("unzip inflated read failed, error: %{public}d", ferror(file_));
483 return false;
484 }
485
486 remainCompressedSize -= readBytes;
487 zstream.avail_in = readBytes;
488 zstream.next_in = buffer;
489 }
490 return true;
491 }
492
UnzipWithInflated(const ZipEntry & zipEntry,const uint16_t extraSize,std::ostream & dest) const493 bool ZipFile::UnzipWithInflated(const ZipEntry &zipEntry, const uint16_t extraSize, std::ostream &dest) const
494 {
495 APP_LOGD("unzip with inflated");
496
497 z_stream zstream;
498 if (!SeekToEntryStart(zipEntry, extraSize) || !InitZStream(zstream)) {
499 return false;
500 }
501
502 BytePtr bufIn = zstream.next_in;
503 BytePtr bufOut = zstream.next_out;
504
505 bool ret = true;
506 int32_t zlibErr = Z_OK;
507 uint32_t remainCompressedSize = zipEntry.compressedSize;
508 uint8_t errorTimes = 0;
509 while ((remainCompressedSize > 0) || (zstream.avail_in > 0)) {
510 if (!ReadZStream(bufIn, zstream, remainCompressedSize)) {
511 ret = false;
512 break;
513 }
514
515 zlibErr = inflate(&zstream, Z_SYNC_FLUSH);
516 if ((zlibErr >= Z_OK) && (zstream.msg != nullptr)) {
517 APP_LOGE("unzip inflated inflate, error: %{public}d, err msg: %{public}s", zlibErr, zstream.msg);
518 ret = false;
519 break;
520 }
521
522 size_t inflateLen = UNZIP_BUF_OUT_LEN - zstream.avail_out;
523 if (inflateLen > 0) {
524 dest.write(reinterpret_cast<const char *>(bufOut), inflateLen);
525 zstream.next_out = bufOut;
526 zstream.avail_out = UNZIP_BUF_OUT_LEN;
527 errorTimes = 0;
528 } else {
529 errorTimes++;
530 }
531 if (errorTimes >= INFLATE_ERROR_TIMES) {
532 APP_LOGE("unzip inflated data is abnormal");
533 ret = false;
534 break;
535 }
536 }
537
538 // free all dynamically allocated data structures except the next_in and next_out for this stream.
539 zlibErr = inflateEnd(&zstream);
540 if (zlibErr != Z_OK) {
541 APP_LOGE("unzip inflateEnd error %{public}d", zlibErr);
542 ret = false;
543 }
544
545 delete[] bufOut;
546 delete[] bufIn;
547 return ret;
548 }
549
GetEntryDataOffset(const ZipEntry & zipEntry,const uint16_t extraSize) const550 ZipPos ZipFile::GetEntryDataOffset(const ZipEntry &zipEntry, const uint16_t extraSize) const
551 {
552 // get entry data offset relative file
553 ZipPos offset = zipEntry.localHeaderOffset;
554
555 offset += GetLocalHeaderSize(zipEntry.fileName.length(), extraSize);
556 offset += fileStartPos_;
557
558 return offset;
559 }
560
GetDataOffsetRelative(const std::string & file,ZipPos & offset,uint32_t & length) const561 bool ZipFile::GetDataOffsetRelative(const std::string &file, ZipPos &offset, uint32_t &length) const
562 {
563 APP_LOGD("get data relative offset for file %{private}s", file.c_str());
564
565 ZipEntry zipEntry;
566 if (!GetEntry(file, zipEntry)) {
567 APP_LOGE("extract file: not find file");
568 return false;
569 }
570
571 uint16_t extraSize = 0;
572 if (!CheckCoherencyLocalHeader(zipEntry, extraSize)) {
573 APP_LOGE("check coherency local header failed");
574 return false;
575 }
576
577 offset = GetEntryDataOffset(zipEntry, extraSize);
578 length = zipEntry.compressedSize;
579 return true;
580 }
581
ExtractFile(const std::string & file,std::ostream & dest) const582 bool ZipFile::ExtractFile(const std::string &file, std::ostream &dest) const
583 {
584 APP_LOGD("extract file %{private}s", file.c_str());
585
586 ZipEntry zipEntry;
587 if (!GetEntry(file, zipEntry)) {
588 APP_LOGE("extract file: not find file");
589 return false;
590 }
591
592 uint16_t extraSize = 0;
593 if (!CheckCoherencyLocalHeader(zipEntry, extraSize)) {
594 APP_LOGE("check coherency local header failed");
595 return false;
596 }
597
598 bool ret = true;
599 if (zipEntry.compressionMethod == 0) {
600 ret = UnzipWithStore(zipEntry, extraSize, dest);
601 } else {
602 ret = UnzipWithInflated(zipEntry, extraSize, dest);
603 }
604
605 return ret;
606 }
607 } // namespace AppExecFwk
608 } // namespace OHOS
609