diff --git a/include/utils/ObbFile.h b/include/utils/ObbFile.h new file mode 100644 index 000000000..075927cdf --- /dev/null +++ b/include/utils/ObbFile.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OBBFILE_H_ +#define OBBFILE_H_ + +#include + +#include +#include + +namespace android { + +class ObbFile : public RefBase { +protected: + virtual ~ObbFile(); + +public: + ObbFile(); + + bool readFrom(const char* filename); + bool readFrom(int fd); + bool writeTo(const char* filename); + bool writeTo(int fd); + + const char* getFileName() const { + return mFileName; + } + + const String8 getPackageName() const { + return mPackageName; + } + + int32_t getVersion() const { + return mVersion; + } + + void setPackageName(String8 packageName) { + mPackageName = packageName; + } + + void setVersion(int32_t version) { + mVersion = version; + } + + static inline uint32_t get4LE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + } + + static inline void put4LE(unsigned char* buf, uint32_t val) { + buf[0] = val & 0xFF; + buf[1] = (val >> 8) & 0xFF; + buf[2] = (val >> 16) & 0xFF; + buf[3] = (val >> 24) & 0xFF; + } + +private: + /* Package name this ObbFile is associated with */ + String8 mPackageName; + + /* Package version this ObbFile is associated with */ + int32_t mVersion; + + const char* mFileName; + + size_t mFileSize; + + unsigned char* mReadBuf; + + bool parseObbFile(int fd); +}; + +} +#endif /* OBBFILE_H_ */ diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk index 7d4524aa5..2bb42ab68 100644 --- a/libs/utils/Android.mk +++ b/libs/utils/Android.mk @@ -26,6 +26,7 @@ commonSources:= \ Debug.cpp \ FileMap.cpp \ Flattenable.cpp \ + ObbFile.cpp \ Pool.cpp \ RefBase.cpp \ ResourceTypes.cpp \ @@ -65,6 +66,11 @@ LOCAL_CFLAGS += -DMB_CUR_MAX=1 endif endif +ifeq ($(HOST_OS),darwin) +# MacOS doesn't have lseek64. However, off_t is 64-bit anyway. +LOCAL_CFLAGS += -DOFF_T_IS_64_BIT +endif + include $(BUILD_HOST_STATIC_LIBRARY) diff --git a/libs/utils/ObbFile.cpp b/libs/utils/ObbFile.cpp new file mode 100644 index 000000000..3a4a03a77 --- /dev/null +++ b/libs/utils/ObbFile.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#define LOG_TAG "ObbFile" +#include +#include + +//#define DEBUG 1 + +#define kFooterTagSize 8 /* last two 32-bit integers */ + +#define kFooterMinSize 21 /* 32-bit signature version + * 32-bit package version + * 32-bit package name size + * 1-character package name + * 32-bit footer size + * 32-bit footer marker + */ + +#define kMaxBufSize 32768 /* Maximum file read buffer */ + +#define kSignature 0x01059983U /* ObbFile signature */ + +#define kSigVersion 1 /* We only know about signature version 1 */ + +/* offsets in version 1 of the header */ +#define kPackageVersionOffset 4 +#define kPackageNameLenOffset 8 +#define kPackageNameOffset 12 + +/* + * TEMP_FAILURE_RETRY is defined by some, but not all, versions of + * . (Alas, it is not as standard as we'd hoped!) So, if it's + * not already defined, then define it here. + */ +#ifndef TEMP_FAILURE_RETRY +/* Used to retry syscalls that can return EINTR. */ +#define TEMP_FAILURE_RETRY(exp) ({ \ + typeof (exp) _rc; \ + do { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; }) +#endif + +/* + * Work around situations where off_t is 64-bit and use off64_t in + * situations where it's 32-bit. + */ +#ifdef OFF_T_IS_64_BIT +#define my_lseek64 lseek +typedef off_t my_off64_t; +#else +#define my_lseek64 lseek64 +typedef off64_t my_off64_t; +#endif + +namespace android { + +ObbFile::ObbFile() : + mVersion(-1) { +} + +ObbFile::~ObbFile() { +} + +bool ObbFile::readFrom(const char* filename) +{ + int fd; + bool success = false; + + fd = ::open(filename, O_RDONLY); + if (fd < 0) { + goto out; + } + success = readFrom(fd); + close(fd); + +out: + if (!success) { + LOGW("failed to read from %s\n", filename); + } + return success; +} + +bool ObbFile::readFrom(int fd) +{ + if (fd < 0) { + LOGW("failed to read file\n"); + return false; + } + + return parseObbFile(fd); +} + +bool ObbFile::parseObbFile(int fd) +{ + my_off64_t fileLength = my_lseek64(fd, 0, SEEK_END); + + if (fileLength < kFooterMinSize) { + if (fileLength < 0) { + LOGW("error seeking in ObbFile: %s\n", strerror(errno)); + } else { + LOGW("file is only %lld (less than %d minimum)\n", fileLength, kFooterMinSize); + } + return false; + } + + ssize_t actual; + size_t footerSize; + + { + my_lseek64(fd, fileLength - kFooterTagSize, SEEK_SET); + + char *footer = new char[kFooterTagSize]; + actual = TEMP_FAILURE_RETRY(read(fd, footer, kFooterTagSize)); + if (actual != kFooterTagSize) { + LOGW("couldn't read footer signature: %s\n", strerror(errno)); + return false; + } + + unsigned int fileSig = get4LE((unsigned char*)footer + sizeof(int32_t)); + if (fileSig != kSignature) { + LOGW("footer didn't match magic string (expected 0x%08x; got 0x%08x)\n", + kSignature, fileSig); + return false; + } + + footerSize = get4LE((unsigned char*)footer); + if (footerSize > (size_t)fileLength - kFooterTagSize + || footerSize > kMaxBufSize) { + LOGW("claimed footer size is too large (0x%08lx; file size is 0x%08llx)\n", + footerSize, fileLength); + return false; + } + } + + my_off64_t fileOffset = fileLength - footerSize - kFooterTagSize; + if (my_lseek64(fd, fileOffset, SEEK_SET) != fileOffset) { + LOGW("seek %lld failed: %s\n", fileOffset, strerror(errno)); + return false; + } + + size_t readAmount = kMaxBufSize; + if (readAmount > footerSize) + readAmount = footerSize; + + char* scanBuf = (char*)malloc(readAmount); + if (scanBuf == NULL) { + LOGW("couldn't allocate scanBuf: %s\n", strerror(errno)); + return false; + } + + actual = TEMP_FAILURE_RETRY(read(fd, scanBuf, readAmount)); + // readAmount is guaranteed to be less than kMaxBufSize + if (actual != (ssize_t)readAmount) { + LOGI("couldn't read ObbFile footer: %s\n", strerror(errno)); + free(scanBuf); + return false; + } + +#ifdef DEBUG + for (int i = 0; i < readAmount; ++i) { + LOGI("char: 0x%02x", scanBuf[i]); + } +#endif + + uint32_t sigVersion = get4LE((unsigned char*)scanBuf); + if (sigVersion != kSigVersion) { + LOGW("Unsupported ObbFile version %d\n", sigVersion); + free(scanBuf); + return false; + } + + mVersion = (int32_t) get4LE((unsigned char*)scanBuf + kPackageVersionOffset); + + uint32_t packageNameLen = get4LE((unsigned char*)scanBuf + kPackageNameLenOffset); + if (packageNameLen <= 0 + || packageNameLen > (footerSize - kPackageNameOffset)) { + LOGW("bad ObbFile package name length (0x%08x)\n", packageNameLen); + free(scanBuf); + return false; + } + + char* packageName = reinterpret_cast(scanBuf + kPackageNameOffset); + mPackageName = String8(const_cast(packageName), packageNameLen); + + free(scanBuf); + return true; +} + +bool ObbFile::writeTo(const char* filename) +{ + int fd; + bool success = false; + + fd = ::open(filename, O_WRONLY); + if (fd < 0) { + goto out; + } + success = writeTo(fd); + close(fd); + +out: + if (!success) { + LOGW("failed to write to %s: %s\n", filename, strerror(errno)); + } + return success; +} + +bool ObbFile::writeTo(int fd) +{ + if (fd < 0) { + return false; + } + + if (mPackageName.size() == 0 || mVersion == -1) { + LOGW("tried to write uninitialized ObbFile data"); + return false; + } + + unsigned char intBuf[sizeof(uint32_t)+1]; + memset(&intBuf, 0, sizeof(intBuf)); + + put4LE(intBuf, kSigVersion); + if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) { + LOGW("couldn't write signature version: %s", strerror(errno)); + return false; + } + + put4LE(intBuf, mVersion); + if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) { + LOGW("couldn't write package version"); + return false; + } + + size_t packageNameLen = mPackageName.size(); + put4LE(intBuf, packageNameLen); + if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) { + LOGW("couldn't write package name length: %s", strerror(errno)); + return false; + } + + if (write(fd, mPackageName.string(), packageNameLen) != (ssize_t)packageNameLen) { + LOGW("couldn't write package name: %s", strerror(errno)); + return false; + } + + put4LE(intBuf, 3*sizeof(uint32_t) + packageNameLen); + if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) { + LOGW("couldn't write footer size: %s", strerror(errno)); + return false; + } + + put4LE(intBuf, kSignature); + if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) { + LOGW("couldn't write footer magic signature: %s", strerror(errno)); + return false; + } + + return true; +} + +} diff --git a/libs/utils/tests/Android.mk b/libs/utils/tests/Android.mk index 92ebfd7c3..f1b8cd56e 100644 --- a/libs/utils/tests/Android.mk +++ b/libs/utils/tests/Android.mk @@ -3,6 +3,7 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) test_src_files := \ + ObbFile_test.cpp \ PollLoop_test.cpp shared_libraries := \ diff --git a/libs/utils/tests/ObbFile_test.cpp b/libs/utils/tests/ObbFile_test.cpp new file mode 100644 index 000000000..05aaf0870 --- /dev/null +++ b/libs/utils/tests/ObbFile_test.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "ObbFile_test" +#include +#include +#include +#include + +#include + +namespace android { + +#define TEST_FILENAME "/test.obb" + +class ObbFileTest : public testing::Test { +protected: + sp mObbFile; + char* mExternalStorage; + char* mFileName; + + virtual void SetUp() { + mObbFile = new ObbFile(); + mExternalStorage = getenv("EXTERNAL_STORAGE"); + + const int totalLen = strlen(mExternalStorage) + strlen(TEST_FILENAME) + 1; + mFileName = new char[totalLen]; + snprintf(mFileName, totalLen, "%s%s", mExternalStorage, TEST_FILENAME); + } + + virtual void TearDown() { + } +}; + +TEST_F(ObbFileTest, ReadFailure) { + EXPECT_FALSE(mObbFile->readFrom(-1)) + << "No failure on invalid file descriptor"; +} + +TEST_F(ObbFileTest, WriteThenRead) { + const char* packageName = "com.example.obbfile"; + const int32_t versionNum = 1; + + mObbFile->setPackageName(String8(packageName)); + mObbFile->setVersion(versionNum); + + EXPECT_TRUE(mObbFile->writeTo(mFileName)) + << "couldn't write to fake .obb file"; + + mObbFile = new ObbFile(); + + EXPECT_TRUE(mObbFile->readFrom(mFileName)) + << "couldn't read from fake .obb file"; + + EXPECT_EQ(versionNum, mObbFile->getVersion()) + << "version didn't come out the same as it went in"; + const char* currentPackageName = mObbFile->getPackageName().string(); + EXPECT_STREQ(packageName, currentPackageName) + << "package name didn't come out the same as it went in"; +} + +}