/*
 * 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 <androidfw/BackupHelpers.h>
#include <utils/Log.h>
#include <utils/String8.h>

#include <gtest/gtest.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

namespace android {

#define TEST_FILENAME "/test.bd"

// keys of different lengths to test padding
#define KEY1 "key1"
#define KEY2 "key2a"
#define KEY3 "key3bc"
#define KEY4 "key4def"

// payloads of different lengths to test padding
#define DATA1 "abcdefg"
#define DATA2 "hijklmnopq"
#define DATA3 "rstuvwxyz"
// KEY4 is only ever deleted

class BackupDataTest : public testing::Test {
protected:
    char* m_external_storage;
    String8 mFilename;
    String8 mKey1;
    String8 mKey2;
    String8 mKey3;
    String8 mKey4;

    virtual void SetUp() {
        m_external_storage = getenv("EXTERNAL_STORAGE");
        mFilename.append(m_external_storage);
        mFilename.append(TEST_FILENAME);

        ::unlink(mFilename.string());
        int fd = ::open(mFilename.string(), O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
        if (fd < 0) {
            FAIL() << "Couldn't create " << mFilename.string() << " for writing";
        }
        mKey1 = String8(KEY1);
        mKey2 = String8(KEY2);
        mKey3 = String8(KEY3);
        mKey4 = String8(KEY4);
   }

    virtual void TearDown() {
    }
};

TEST_F(BackupDataTest, WriteAndReadSingle) {
  int fd = ::open(mFilename.string(), O_WRONLY);
  BackupDataWriter* writer = new BackupDataWriter(fd);

  EXPECT_EQ(NO_ERROR, writer->WriteEntityHeader(mKey1, sizeof(DATA1)))
          << "WriteEntityHeader returned an error";
  EXPECT_EQ(NO_ERROR, writer->WriteEntityData(DATA1, sizeof(DATA1)))
          << "WriteEntityData returned an error";

  ::close(fd);
  fd = ::open(mFilename.string(), O_RDONLY);
  BackupDataReader* reader = new BackupDataReader(fd);
  EXPECT_EQ(NO_ERROR, reader->Status())
          << "Reader ctor failed";

  bool done;
  int type;
  reader->ReadNextHeader(&done, &type);
  EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
          << "wrong type from ReadNextHeader";

  String8 key;
  size_t dataSize;
  EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
          << "ReadEntityHeader returned an error";
  EXPECT_EQ(mKey1, key)
          << "wrong key from ReadEntityHeader";
  EXPECT_EQ(sizeof(DATA1), dataSize)
          << "wrong size from ReadEntityHeader";

  char* dataBytes = new char[dataSize];
  EXPECT_EQ((int) dataSize, reader->ReadEntityData(dataBytes, dataSize))
          << "ReadEntityData returned an error";
  for (unsigned int i = 0; i < sizeof(DATA1); i++) {
    EXPECT_EQ(DATA1[i], dataBytes[i])
             << "data character " << i << " should be equal";
  }
  delete[] dataBytes;
  delete writer;
  delete reader;
}

TEST_F(BackupDataTest, WriteAndReadMultiple) {
  int fd = ::open(mFilename.string(), O_WRONLY);
  BackupDataWriter* writer = new BackupDataWriter(fd);
  writer->WriteEntityHeader(mKey1, sizeof(DATA1));
  writer->WriteEntityData(DATA1, sizeof(DATA1));
  writer->WriteEntityHeader(mKey2, sizeof(DATA2));
  writer->WriteEntityData(DATA2, sizeof(DATA2));

  ::close(fd);
  fd = ::open(mFilename.string(), O_RDONLY);
  BackupDataReader* reader = new BackupDataReader(fd);

  bool done;
  int type;
  String8 key;
  size_t dataSize;
  char* dataBytes;
  // read first entity
  reader->ReadNextHeader(&done, &type);
  reader->ReadEntityHeader(&key, &dataSize);
  dataBytes = new char[dataSize];
  reader->ReadEntityData(dataBytes, dataSize);
  delete dataBytes;

  // read and verify second entity
  reader->ReadNextHeader(&done, &type);
  EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
          << "wrong type from ReadNextHeader";

  EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
          << "ReadEntityHeader returned an error on second entity";
  EXPECT_EQ(mKey2, key)
          << "wrong key from ReadEntityHeader on second entity";
  EXPECT_EQ(sizeof(DATA2), dataSize)
          << "wrong size from ReadEntityHeader on second entity";

  dataBytes = new char[dataSize];
  EXPECT_EQ((int)dataSize, reader->ReadEntityData(dataBytes, dataSize))
          << "ReadEntityData returned an error on second entity";
  for (unsigned int i = 0; i < sizeof(DATA2); i++) {
    EXPECT_EQ(DATA2[i], dataBytes[i])
             << "data character " << i << " should be equal";
  }
  delete dataBytes;
  delete writer;
  delete reader;
}

TEST_F(BackupDataTest, SkipEntity) {
  int fd = ::open(mFilename.string(), O_WRONLY);
  BackupDataWriter* writer = new BackupDataWriter(fd);
  writer->WriteEntityHeader(mKey1, sizeof(DATA1));
  writer->WriteEntityData(DATA1, sizeof(DATA1));
  writer->WriteEntityHeader(mKey2, sizeof(DATA2));
  writer->WriteEntityData(DATA2, sizeof(DATA2));
  writer->WriteEntityHeader(mKey3, sizeof(DATA3));
  writer->WriteEntityData(DATA3, sizeof(DATA3));

  ::close(fd);
  fd = ::open(mFilename.string(), O_RDONLY);
  BackupDataReader* reader = new BackupDataReader(fd);

  bool done;
  int type;
  String8 key;
  size_t dataSize;
  char* dataBytes;
  // read first entity
  reader->ReadNextHeader(&done, &type);
  reader->ReadEntityHeader(&key, &dataSize);
  dataBytes = new char[dataSize];
  reader->ReadEntityData(dataBytes, dataSize);
  delete dataBytes;

  // skip second entity
  reader->ReadNextHeader(&done, &type);
  reader->ReadEntityHeader(&key, &dataSize);
  reader->SkipEntityData();

  // read and verify third entity
  reader->ReadNextHeader(&done, &type);
  EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
          << "wrong type from ReadNextHeader after skip";

  EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
          << "ReadEntityHeader returned an error on third entity";
  EXPECT_EQ(mKey3, key)
          << "wrong key from ReadEntityHeader on third entity";
  EXPECT_EQ(sizeof(DATA3), dataSize)
          << "wrong size from ReadEntityHeader on third entity";

  dataBytes = new char[dataSize];
  EXPECT_EQ((int) dataSize, reader->ReadEntityData(dataBytes, dataSize))
          << "ReadEntityData returned an error on third entity";
  for (unsigned int i = 0; i < sizeof(DATA3); i++) {
    EXPECT_EQ(DATA3[i], dataBytes[i])
             << "data character " << i << " should be equal";
  }
  delete dataBytes;
  delete writer;
  delete reader;
}

TEST_F(BackupDataTest, DeleteEntity) {
  int fd = ::open(mFilename.string(), O_WRONLY);
  BackupDataWriter* writer = new BackupDataWriter(fd);
  writer->WriteEntityHeader(mKey1, sizeof(DATA1));
  writer->WriteEntityData(DATA1, sizeof(DATA1));
  writer->WriteEntityHeader(mKey2, -1);

  ::close(fd);
  fd = ::open(mFilename.string(), O_RDONLY);
  BackupDataReader* reader = new BackupDataReader(fd);

  bool done;
  int type;
  String8 key;
  size_t dataSize;
  char* dataBytes;
  // read first entity
  reader->ReadNextHeader(&done, &type);
  reader->ReadEntityHeader(&key, &dataSize);
  dataBytes = new char[dataSize];
  reader->ReadEntityData(dataBytes, dataSize);
  delete dataBytes;

  // read and verify deletion
  reader->ReadNextHeader(&done, &type);
  EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
          << "wrong type from ReadNextHeader on deletion";

  EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
          << "ReadEntityHeader returned an error on second entity";
  EXPECT_EQ(mKey2, key)
          << "wrong key from ReadEntityHeader on second entity";
  EXPECT_EQ(-1, (int) dataSize)
          << "not recognizing deletion on second entity";

  delete writer;
  delete reader;
}

TEST_F(BackupDataTest, EneityAfterDelete) {
  int fd = ::open(mFilename.string(), O_WRONLY);
  BackupDataWriter* writer = new BackupDataWriter(fd);
  writer->WriteEntityHeader(mKey1, sizeof(DATA1));
  writer->WriteEntityData(DATA1, sizeof(DATA1));
  writer->WriteEntityHeader(mKey2, -1);
  writer->WriteEntityHeader(mKey3, sizeof(DATA3));
  writer->WriteEntityData(DATA3, sizeof(DATA3));

  ::close(fd);
  fd = ::open(mFilename.string(), O_RDONLY);
  BackupDataReader* reader = new BackupDataReader(fd);

  bool done;
  int type;
  String8 key;
  size_t dataSize;
  char* dataBytes;
  // read first entity
  reader->ReadNextHeader(&done, &type);
  reader->ReadEntityHeader(&key, &dataSize);
  dataBytes = new char[dataSize];
  reader->ReadEntityData(dataBytes, dataSize);
  delete dataBytes;

  // read and verify deletion
  reader->ReadNextHeader(&done, &type);
  EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
          << "wrong type from ReadNextHeader on deletion";

  EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
          << "ReadEntityHeader returned an error on second entity";
  EXPECT_EQ(mKey2, key)
          << "wrong key from ReadEntityHeader on second entity";
  EXPECT_EQ(-1, (int)dataSize)
          << "not recognizing deletion on second entity";

  // read and verify third entity
  reader->ReadNextHeader(&done, &type);
  EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
          << "wrong type from ReadNextHeader after deletion";

  EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
          << "ReadEntityHeader returned an error on third entity";
  EXPECT_EQ(mKey3, key)
          << "wrong key from ReadEntityHeader on third entity";
  EXPECT_EQ(sizeof(DATA3), dataSize)
          << "wrong size from ReadEntityHeader on third entity";

  dataBytes = new char[dataSize];
  EXPECT_EQ((int) dataSize, reader->ReadEntityData(dataBytes, dataSize))
          << "ReadEntityData returned an error on third entity";
  for (unsigned int i = 0; i < sizeof(DATA3); i++) {
    EXPECT_EQ(DATA3[i], dataBytes[i])
             << "data character " << i << " should be equal";
  }
  delete dataBytes;
  delete writer;
  delete reader;
}

TEST_F(BackupDataTest, OnlyDeleteEntities) {
  int fd = ::open(mFilename.string(), O_WRONLY);
  BackupDataWriter* writer = new BackupDataWriter(fd);
  writer->WriteEntityHeader(mKey1, -1);
  writer->WriteEntityHeader(mKey2, -1);
  writer->WriteEntityHeader(mKey3, -1);
  writer->WriteEntityHeader(mKey4, -1);

  ::close(fd);
  fd = ::open(mFilename.string(), O_RDONLY);
  BackupDataReader* reader = new BackupDataReader(fd);

  bool done;
  int type;
  String8 key;
  size_t dataSize;
  // read and verify first deletion
  reader->ReadNextHeader(&done, &type);
  EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
          << "wrong type from ReadNextHeader first deletion";

  EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
          << "ReadEntityHeader returned an error on first entity";
  EXPECT_EQ(mKey1, key)
          << "wrong key from ReadEntityHeader on first entity";
  EXPECT_EQ(-1, (int) dataSize)
          << "not recognizing deletion on first entity";

  // read and verify second deletion
  reader->ReadNextHeader(&done, &type);
  EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
          << "wrong type from ReadNextHeader second deletion";

  EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
          << "ReadEntityHeader returned an error on second entity";
  EXPECT_EQ(mKey2, key)
          << "wrong key from ReadEntityHeader on second entity";
  EXPECT_EQ(-1, (int) dataSize)
          << "not recognizing deletion on second entity";

  // read and verify third deletion
  reader->ReadNextHeader(&done, &type);
  EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
          << "wrong type from ReadNextHeader third deletion";

  EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
          << "ReadEntityHeader returned an error on third entity";
  EXPECT_EQ(mKey3, key)
          << "wrong key from ReadEntityHeader on third entity";
  EXPECT_EQ(-1, (int) dataSize)
          << "not recognizing deletion on third entity";

  // read and verify fourth deletion
  reader->ReadNextHeader(&done, &type);
  EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
          << "wrong type from ReadNextHeader fourth deletion";

  EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
          << "ReadEntityHeader returned an error on fourth entity";
  EXPECT_EQ(mKey4, key)
          << "wrong key from ReadEntityHeader on fourth entity";
  EXPECT_EQ(-1, (int) dataSize)
          << "not recognizing deletion on fourth entity";

  delete writer;
  delete reader;
}

TEST_F(BackupDataTest, ReadDeletedEntityData) {
  int fd = ::open(mFilename.string(), O_WRONLY);
  BackupDataWriter* writer = new BackupDataWriter(fd);
  writer->WriteEntityHeader(mKey1, -1);
  writer->WriteEntityHeader(mKey2, -1);

  ::close(fd);
  fd = ::open(mFilename.string(), O_RDONLY);
  BackupDataReader* reader = new BackupDataReader(fd);

  bool done;
  int type;
  String8 key;
  size_t dataSize;
  // read and verify first deletion
  reader->ReadNextHeader(&done, &type);
  EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
          << "wrong type from ReadNextHeader first deletion";

  EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
          << "ReadEntityHeader returned an error on first entity";
  EXPECT_EQ(mKey1, key)
          << "wrong key from ReadEntityHeader on first entity";
  EXPECT_EQ(-1, (int) dataSize)
          << "not recognizing deletion on first entity";

  // erroneously try to read first entity data
  char* dataBytes = new char[10];
  dataBytes[0] = 'A';
  EXPECT_EQ(NO_ERROR, reader->ReadEntityData(dataBytes, dataSize));
  // expect dataBytes to be unmodofied
  EXPECT_EQ('A', dataBytes[0]);

  // read and verify second deletion
  reader->ReadNextHeader(&done, &type);
  EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
          << "wrong type from ReadNextHeader second deletion";

  EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
          << "ReadEntityHeader returned an error on second entity";
  EXPECT_EQ(mKey2, key)
          << "wrong key from ReadEntityHeader on second entity";
  EXPECT_EQ(-1, (int) dataSize)
          << "not recognizing deletion on second entity";

  delete[] dataBytes;
  delete writer;
  delete reader;
}

}
