Add OBB file helper class

ObbFile is a binary blob that will be used in packaging large files
with smaller APKs.

Change-Id: Ib1594346cfa2f49113de6565af77c24efbd89d63
This commit is contained in:
Kenny Root 2010-06-01 10:34:29 -07:00
parent 2f1276dd22
commit b94a9a668f
5 changed files with 453 additions and 0 deletions

87
include/utils/ObbFile.h Normal file
View File

@ -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 <stdint.h>
#include <utils/RefBase.h>
#include <utils/String8.h>
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_ */

View File

@ -26,6 +26,7 @@ commonSources:= \
Debug.cpp \ Debug.cpp \
FileMap.cpp \ FileMap.cpp \
Flattenable.cpp \ Flattenable.cpp \
ObbFile.cpp \
Pool.cpp \ Pool.cpp \
RefBase.cpp \ RefBase.cpp \
ResourceTypes.cpp \ ResourceTypes.cpp \
@ -65,6 +66,11 @@ LOCAL_CFLAGS += -DMB_CUR_MAX=1
endif endif
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) include $(BUILD_HOST_STATIC_LIBRARY)

284
libs/utils/ObbFile.cpp Normal file
View File

@ -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 <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define LOG_TAG "ObbFile"
#include <utils/Log.h>
#include <utils/ObbFile.h>
//#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
* <unistd.h>. (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<char*>(scanBuf + kPackageNameOffset);
mPackageName = String8(const_cast<char*>(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;
}
}

View File

@ -3,6 +3,7 @@ LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS) include $(CLEAR_VARS)
test_src_files := \ test_src_files := \
ObbFile_test.cpp \
PollLoop_test.cpp PollLoop_test.cpp
shared_libraries := \ shared_libraries := \

View File

@ -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 <utils/Log.h>
#include <utils/ObbFile.h>
#include <utils/RefBase.h>
#include <utils/String8.h>
#include <gtest/gtest.h>
namespace android {
#define TEST_FILENAME "/test.obb"
class ObbFileTest : public testing::Test {
protected:
sp<ObbFile> 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";
}
}