/*
 * Copyright (C) 2016 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 <assert.h>
#include <stdint.h>
#include <stdbool.h>
#include <gtest/gtest.h>

#include <trusty/lib/storage.h>

#define TRUSTY_DEVICE_NAME "/dev/trusty-ipc-dev0"

#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))

static inline bool is_32bit_aligned(size_t sz)
{
    return ((sz & 0x3) == 0);
}

static inline bool is_valid_size(size_t sz) {
    return (sz > 0) && is_32bit_aligned(sz);
}

static bool is_valid_offset(storage_off_t off)
{
    return (off & 0x3) == 0ULL;
}

static void fill_pattern32(uint32_t *buf, size_t len, storage_off_t off)
{
    size_t cnt = len / sizeof(uint32_t);
    uint32_t pattern = (uint32_t)(off / sizeof(uint32_t));
    while (cnt--) {
        *buf++ = pattern++;
    }
}

static bool check_pattern32(const uint32_t *buf, size_t len, storage_off_t off)
{
    size_t cnt = len / sizeof(uint32_t);
    uint32_t pattern = (uint32_t)(off / sizeof(uint32_t));
    while (cnt--) {
        if (*buf != pattern)
            return false;
        buf++;
        pattern++;
    }
    return true;
}

static bool check_value32(const uint32_t *buf, size_t len, uint32_t val)
{
    size_t cnt = len / sizeof(uint32_t);
    while (cnt--) {
        if (*buf != val)
            return false;
        buf++;
    }
    return true;
}

using testing::TestWithParam;

class StorageServiceTest : public virtual TestWithParam<const char *> {
public:
    StorageServiceTest() {}
    virtual ~StorageServiceTest() {}

    virtual void SetUp() {
        port_ = GetParam();
        test_buf_ = NULL;
        aux_session_ = STORAGE_INVALID_SESSION;
        int rc = storage_open_session(TRUSTY_DEVICE_NAME, &session_, port_);
        ASSERT_EQ(0, rc);
    }

    virtual void TearDown() {
        if (test_buf_) {
            delete[] test_buf_;
            test_buf_ = NULL;
        }
        storage_close_session(session_);

        if (aux_session_ != STORAGE_INVALID_SESSION) {
            storage_close_session(aux_session_);
            aux_session_ = STORAGE_INVALID_SESSION;
        }
    }

    void WriteReadAtOffsetHelper(file_handle_t handle, size_t blk, size_t cnt, bool complete);

    void WriteZeroChunk(file_handle_t handle, storage_off_t off, size_t chunk_len, bool complete );
    void WritePatternChunk(file_handle_t handle, storage_off_t off, size_t chunk_len, bool complete);
    void WritePattern(file_handle_t handle, storage_off_t off, size_t data_len, size_t chunk_len, bool complete);

    void ReadChunk(file_handle_t handle, storage_off_t off, size_t chunk_len,
                   size_t head_len, size_t pattern_len, size_t tail_len);
    void ReadPattern(file_handle_t handle, storage_off_t off, size_t data_len, size_t chunk_len);
    void ReadPatternEOF(file_handle_t handle, storage_off_t off, size_t chunk_len, size_t exp_len);

protected:
    const char *port_;
    uint32_t *test_buf_;
    storage_session_t session_;
    storage_session_t aux_session_;
};

INSTANTIATE_TEST_CASE_P(SS_TD_Tests, StorageServiceTest,   ::testing::Values(STORAGE_CLIENT_TD_PORT));
INSTANTIATE_TEST_CASE_P(SS_TDEA_Tests, StorageServiceTest, ::testing::Values(STORAGE_CLIENT_TDEA_PORT));
INSTANTIATE_TEST_CASE_P(SS_TP_Tests, StorageServiceTest,   ::testing::Values(STORAGE_CLIENT_TP_PORT));


void StorageServiceTest::WriteZeroChunk(file_handle_t handle, storage_off_t off,
                                       size_t chunk_len, bool complete)
{
    int rc;
    uint32_t data_buf[chunk_len/sizeof(uint32_t)];

    ASSERT_PRED1(is_valid_size, chunk_len);
    ASSERT_PRED1(is_valid_offset, off);

    memset(data_buf, 0, chunk_len);

    rc = storage_write(handle, off, data_buf, sizeof(data_buf),
                       complete ? STORAGE_OP_COMPLETE : 0);
    ASSERT_EQ((int)chunk_len, rc);
}

void StorageServiceTest::WritePatternChunk(file_handle_t handle, storage_off_t off,
                                           size_t chunk_len, bool complete)
{
    int rc;
    uint32_t data_buf[chunk_len/sizeof(uint32_t)];

    ASSERT_PRED1(is_valid_size, chunk_len);
    ASSERT_PRED1(is_valid_offset, off);

    fill_pattern32(data_buf, chunk_len, off);

    rc = storage_write(handle, off, data_buf, sizeof(data_buf),
                       complete ? STORAGE_OP_COMPLETE : 0);
    ASSERT_EQ((int)chunk_len, rc);
}

void StorageServiceTest::WritePattern(file_handle_t handle, storage_off_t off,
                                      size_t data_len, size_t chunk_len, bool complete)
{
    ASSERT_PRED1(is_valid_size, data_len);
    ASSERT_PRED1(is_valid_size, chunk_len);

    while (data_len) {
        if (data_len < chunk_len)
            chunk_len = data_len;
        WritePatternChunk(handle, off, chunk_len, (chunk_len == data_len) && complete);
        ASSERT_FALSE(HasFatalFailure());
        off += chunk_len;
        data_len -= chunk_len;
    }
}

void StorageServiceTest::ReadChunk(file_handle_t handle,
                                   storage_off_t off, size_t chunk_len,
                                   size_t head_len, size_t pattern_len,
                                   size_t tail_len)
{
    int rc;
    uint32_t data_buf[chunk_len/sizeof(uint32_t)];
    uint8_t *data_ptr = (uint8_t *)data_buf;

    ASSERT_PRED1(is_valid_size, chunk_len);
    ASSERT_PRED1(is_valid_offset, off);
    ASSERT_EQ(head_len + pattern_len + tail_len, chunk_len);

    rc = storage_read(handle, off, data_buf, chunk_len);
    ASSERT_EQ((int)chunk_len, rc);

    if (head_len) {
        ASSERT_TRUE(check_value32((const uint32_t *)data_ptr, head_len, 0));
        data_ptr += head_len;
        off += head_len;
    }

    if (pattern_len) {
        ASSERT_TRUE(check_pattern32((const uint32_t *)data_ptr, pattern_len, off));
        data_ptr += pattern_len;
    }

    if (tail_len) {
        ASSERT_TRUE(check_value32((const uint32_t *)data_ptr, tail_len, 0));
    }
}

void StorageServiceTest::ReadPattern(file_handle_t handle, storage_off_t off,
                                     size_t data_len, size_t chunk_len)
{
    int rc;
    uint32_t data_buf[chunk_len/sizeof(uint32_t)];

    ASSERT_PRED1(is_valid_size, chunk_len);
    ASSERT_PRED1(is_valid_size, data_len);
    ASSERT_PRED1(is_valid_offset, off);

    while (data_len) {
        if (chunk_len > data_len)
            chunk_len = data_len;
        rc = storage_read(handle, off, data_buf, sizeof(data_buf));
        ASSERT_EQ((int)chunk_len, rc);
        ASSERT_TRUE(check_pattern32(data_buf, chunk_len, off));
        off += chunk_len;
        data_len -= chunk_len;
    }
}

void StorageServiceTest::ReadPatternEOF(file_handle_t handle, storage_off_t off,
                                        size_t chunk_len, size_t exp_len)
{
    int rc;
    size_t bytes_read = 0;
    uint32_t data_buf[chunk_len/sizeof(uint32_t)];

    ASSERT_PRED1(is_valid_size, chunk_len);
    ASSERT_PRED1(is_32bit_aligned, exp_len);

    while (true) {
         rc = storage_read(handle, off, data_buf, sizeof(data_buf));
         ASSERT_GE(rc, 0);
         if (rc == 0)
             break; // end of file reached
         ASSERT_PRED1(is_valid_size, (size_t)rc);
         ASSERT_TRUE(check_pattern32(data_buf, rc, off));
         off += rc;
         bytes_read += rc;
    }
    ASSERT_EQ(bytes_read, exp_len);
}

TEST_P(StorageServiceTest, CreateDelete) {
    int rc;
    file_handle_t handle;
    const char *fname = "test_create_delete_file";

    // make sure test file does not exist (expect success or -ENOENT)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    rc = (rc == -ENOENT) ? 0 : rc;
    ASSERT_EQ(0, rc);

    // one more time (expect -ENOENT only)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    ASSERT_EQ(-ENOENT, rc);

    // create file (expect 0)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // try to create it again while it is still opened (expect -EEXIST)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(-EEXIST, rc);

    // close it
    storage_close_file(handle);

    // try to create it again while it is closed (expect -EEXIST)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(-EEXIST, rc);

    // delete file (expect 0)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // one more time (expect -ENOENT)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    ASSERT_EQ(-ENOENT, rc);
}


TEST_P(StorageServiceTest, DeleteOpened) {
    int rc;
    file_handle_t handle;
    const char *fname = "delete_opened_test_file";

    // make sure test file does not exist (expect success or -ENOENT)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    rc = (rc == -ENOENT) ? 0 : rc;
    ASSERT_EQ(0, rc);

    // one more time (expect -ENOENT)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    ASSERT_EQ(-ENOENT, rc);

    // open/create file (expect 0)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // delete opened file (expect 0)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // one more time (expect -ENOENT)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    ASSERT_EQ(-ENOENT, rc);

    // close file
    storage_close_file(handle);

    // one more time (expect -ENOENT)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    ASSERT_EQ(-ENOENT, rc);
}


TEST_P(StorageServiceTest, OpenNoCreate) {
    int rc;
    file_handle_t handle;
    const char *fname = "test_open_no_create_file";

    // make sure test file does not exist (expect success or -ENOENT)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    rc = (rc == -ENOENT) ? 0 : rc;
    ASSERT_EQ(0, rc);

    // open non-existing file (expect -ENOENT)
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(-ENOENT, rc);

    // create file (expect 0)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);
    storage_close_file(handle);

    // open existing file (expect 0)
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(0, rc);

    // close it
    storage_close_file(handle);

    // delete file (expect 0)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);
}


TEST_P(StorageServiceTest, OpenOrCreate) {
    int rc;
    file_handle_t handle;
    const char *fname = "test_open_create_file";

    // make sure test file does not exist (expect success or -ENOENT)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    rc = (rc == -ENOENT) ? 0 : rc;
    ASSERT_EQ(0, rc);

    // open/create a non-existing file (expect 0)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE, STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);
    storage_close_file(handle);

    // open/create an existing file (expect 0)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE, STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);
    storage_close_file(handle);

    // delete file (expect 0)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);
}


TEST_P(StorageServiceTest, OpenCreateDeleteCharset) {
    int rc;
    file_handle_t handle;
    const char *fname = "ABCDEFGHIJKLMNOPQRSTUVWXYZ-abcdefghijklmnopqrstuvwxyz_01234.56789";

    // open/create file (expect 0)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE, STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);
    storage_close_file(handle);

    // open/create an existing file (expect 0)
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(0, rc);
    storage_close_file(handle);

    // delete file (expect 0)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // open again
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(-ENOENT, rc);
}


TEST_P(StorageServiceTest, WriteReadSequential) {
    int rc;
    size_t blk = 2048;
    file_handle_t handle;
    const char *fname = "test_write_read_sequential";

    // make sure test file does not exist (expect success or -ENOENT)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    rc = (rc == -ENOENT) ? 0 : rc;
    ASSERT_EQ(0, rc);

    // create file.
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write a bunch of blocks (sequentially)
    WritePattern(handle, 0, 32 * blk, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    ReadPattern(handle, 0, 32 * blk, blk);
    ASSERT_FALSE(HasFatalFailure());

    // close file
    storage_close_file(handle);

    // open the same file again
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(0, rc);

    // read data back (sequentially) and check pattern again
    ReadPattern(handle, 0, 32 * blk, blk);
    ASSERT_FALSE(HasFatalFailure());

    // cleanup
    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, OpenTruncate) {
    int rc;
    uint32_t val;
    size_t blk = 2048;
    file_handle_t handle;
    const char *fname = "test_open_truncate";

    // make sure test file does not exist (expect success or -ENOENT)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    rc = (rc == -ENOENT) ? 0 : rc;
    ASSERT_EQ(0, rc);

    // create file.
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write some data and read it back
    WritePatternChunk(handle, 0, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    ReadPattern(handle, 0, blk, blk);
    ASSERT_FALSE(HasFatalFailure());

     // close file
    storage_close_file(handle);

    // reopen with truncate
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_TRUNCATE, STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    /* try to read data back (expect no data) */
    rc = storage_read(handle, 0LL, &val, sizeof(val));
    ASSERT_EQ(0, rc);

    // cleanup
    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, OpenSame) {
    int rc;
    file_handle_t handle1;
    file_handle_t handle2;
    file_handle_t handle3;
    const char *fname = "test_open_same_file";

    // open/create file (expect 0)
    rc = storage_open_file(session_, &handle1, fname, STORAGE_FILE_OPEN_CREATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);
    storage_close_file(handle1);

    // open an existing file first time (expect 0)
    rc = storage_open_file(session_, &handle1, fname, 0, 0);
    ASSERT_EQ(0, rc);

    // open the same file second time (expect error)
    rc = storage_open_file(session_, &handle2, fname, 0, 0);
    ASSERT_NE(0, rc);

    storage_close_file(handle1);

    // delete file (expect 0)
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // open deleted file (expect -ENOENT)
    rc = storage_open_file(session_, &handle3, fname, 0, 0);
    ASSERT_EQ(-ENOENT, rc);
}


TEST_P(StorageServiceTest, OpenMany) {
    int rc;
    file_handle_t handles[10];
    char filename[10];
    const char *fname_fmt = "mf%d";

    // open or create a bunch of files (expect 0)
    for (uint i = 0; i < ARRAY_SIZE(handles); ++i) {
        snprintf(filename, sizeof(filename), fname_fmt, i);
        rc = storage_open_file(session_, &handles[i], filename,
                               STORAGE_FILE_OPEN_CREATE, STORAGE_OP_COMPLETE);
        ASSERT_EQ(0, rc);
    }

    // check that all handles are different
    for (uint i = 0; i < ARRAY_SIZE(handles)-1; i++) {
        for (uint j = i+1; j < ARRAY_SIZE(handles); j++) {
            ASSERT_NE(handles[i], handles[j]);
        }
    }

    // close them all
    for (uint i = 0; i < ARRAY_SIZE(handles); ++i) {
        storage_close_file(handles[i]);
    }

    // open all files without CREATE flags (expect 0)
    for (uint i = 0; i < ARRAY_SIZE(handles); ++i) {
        snprintf(filename, sizeof(filename), fname_fmt, i);
        rc = storage_open_file(session_, &handles[i], filename, 0, 0);
        ASSERT_EQ(0, rc);
    }

    // check that all handles are different
    for (uint i = 0; i < ARRAY_SIZE(handles)-1; i++) {
        for (uint j = i+1; j < ARRAY_SIZE(handles); j++) {
            ASSERT_NE(handles[i], handles[j]);
        }
    }

    // close and remove all test files
    for (uint i = 0; i < ARRAY_SIZE(handles); ++i) {
        storage_close_file(handles[i]);
        snprintf(filename, sizeof(filename), fname_fmt, i);
        rc = storage_delete_file(session_, filename, STORAGE_OP_COMPLETE);
        ASSERT_EQ(0, rc);
    }
}


TEST_P(StorageServiceTest, ReadAtEOF) {
    int rc;
    uint32_t val;
    size_t blk = 2048;
    file_handle_t handle;
    const char *fname = "test_read_eof";

    // open/create/truncate file
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write block at offset 0
    WritePatternChunk(handle, 0, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // close file
    storage_close_file(handle);

    // open same file again
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(0, rc);

    // read the whole block back and check pattern again
    ReadPattern(handle, 0, blk, blk);
    ASSERT_FALSE(HasFatalFailure());

    // read at end of file (expected 0 bytes)
    rc = storage_read(handle, blk, &val, sizeof(val));
    ASSERT_EQ(0, rc);

    // partial read at end of the file (expected partial data)
    ReadPatternEOF(handle, blk/2, blk, blk/2);
    ASSERT_FALSE(HasFatalFailure());

    // read past end of file
    rc = storage_read(handle, blk + 2, &val, sizeof(val));
    ASSERT_EQ(-EINVAL, rc);

    // cleanup
    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, GetFileSize) {
    int rc;
    size_t blk = 2048;
    storage_off_t size;
    file_handle_t handle;
    const char *fname = "test_get_file_size";

    // open/create/truncate file.
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // check file size (expect success and size == 0)
    size = 1;
    rc = storage_get_file_size(handle, &size);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, size);

    // write block
    WritePatternChunk(handle, 0, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // check size
    rc = storage_get_file_size(handle, &size);
    ASSERT_EQ(0, rc);
    ASSERT_EQ(blk, size);

    // write another block
    WritePatternChunk(handle, blk, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // check size again
    rc = storage_get_file_size(handle, &size);
    ASSERT_EQ(0, rc);
    ASSERT_EQ(blk*2, size);

    // cleanup
    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, SetFileSize) {
    int rc;
    size_t blk = 2048;
    storage_off_t size;
    file_handle_t handle;
    const char *fname = "test_set_file_size";

    // open/create/truncate file.
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // check file size (expect success and size == 0)
    size = 1;
    rc = storage_get_file_size(handle, &size);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, size);

    // write block
    WritePatternChunk(handle, 0, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // check size
    rc = storage_get_file_size(handle, &size);
    ASSERT_EQ(0, rc);
    ASSERT_EQ(blk, size);

    storage_close_file(handle);

    // reopen normally
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(0, rc);

    // check size again
    rc = storage_get_file_size(handle, &size);
    ASSERT_EQ(0, rc);
    ASSERT_EQ(blk, size);

    // set file size to half
    rc = storage_set_file_size(handle, blk/2, STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // check size again (should be half of original size)
    rc = storage_get_file_size(handle, &size);
    ASSERT_EQ(0, rc);
    ASSERT_EQ(blk/2, size);

    // read data back
    ReadPatternEOF(handle, 0, blk, blk/2);
    ASSERT_FALSE(HasFatalFailure());

    // set file size to 0
    rc = storage_set_file_size(handle, 0, STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // check size again (should be 0)
    rc = storage_get_file_size(handle, &size);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0LL, size);

    // try to read again
    ReadPatternEOF(handle, 0, blk, 0);
    ASSERT_FALSE(HasFatalFailure());

    // cleanup
    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


void StorageServiceTest::WriteReadAtOffsetHelper(file_handle_t handle, size_t blk, size_t cnt, bool complete)
{
    storage_off_t off1 = blk;
    storage_off_t off2 = blk * (cnt-1);

    // write known pattern data at non-zero offset1
    WritePatternChunk(handle, off1, blk, complete);
    ASSERT_FALSE(HasFatalFailure());

    // write known pattern data at non-zero offset2
    WritePatternChunk(handle, off2, blk, complete);
    ASSERT_FALSE(HasFatalFailure());

    // read data back at offset1
    ReadPattern(handle, off1, blk, blk);
    ASSERT_FALSE(HasFatalFailure());

    // read data back at offset2
    ReadPattern(handle, off2, blk, blk);
    ASSERT_FALSE(HasFatalFailure());

    // read partially written data at end of file(expect to get data only, no padding)
    ReadPatternEOF(handle, off2 + blk/2, blk, blk/2);
    ASSERT_FALSE(HasFatalFailure());

    // read data at offset 0 (expect success and zero data)
    ReadChunk(handle, 0, blk, blk, 0, 0);
    ASSERT_FALSE(HasFatalFailure());

    // read data from gap (expect success and zero data)
    ReadChunk(handle, off1 + blk, blk, blk, 0, 0);
    ASSERT_FALSE(HasFatalFailure());

    // read partially written data (start pointing within written data)
    // (expect to get written data back and zeroes at the end)
    ReadChunk(handle, off1 + blk/2, blk, 0, blk/2, blk/2);
    ASSERT_FALSE(HasFatalFailure());

    // read partially written data (start pointing withing unwritten data)
    // expect to get zeroes at the beginning and proper data at the end
    ReadChunk(handle, off1 - blk/2, blk, blk/2, blk/2, 0);
    ASSERT_FALSE(HasFatalFailure());
}


TEST_P(StorageServiceTest, WriteReadAtOffset) {
    int rc;
    file_handle_t handle;
    size_t blk = 2048;
    size_t blk_cnt = 32;
    const char *fname = "test_write_at_offset";

    // create/truncate file.
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write a bunch of blocks filled with zeroes
    for (uint i = 0; i < blk_cnt; i++) {
        WriteZeroChunk(handle, i * blk, blk, true);
        ASSERT_FALSE(HasFatalFailure());
    }

    WriteReadAtOffsetHelper(handle, blk, blk_cnt, true);
    ASSERT_FALSE(HasFatalFailure());

    // cleanup
    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, WriteSparse) {
    int rc;
    file_handle_t handle;
    const char *fname = "test_write_sparse";

    // open/create/truncate file.
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write value past en of file
    uint32_t val = 0xDEADBEEF;
    rc = storage_write(handle, 1, &val, sizeof(val), STORAGE_OP_COMPLETE);
    ASSERT_EQ(-EINVAL, rc);

    // cleanup
    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}

// Persistent 32k

TEST_P(StorageServiceTest, CreatePersistent32K) {
    int rc;
    file_handle_t handle;
    size_t blk = 2048;
    size_t file_size = 32768;
    const char *fname = "test_persistent_32K_file";

    // create/truncate file.
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write a bunch of blocks filled with pattern
    WritePattern(handle, 0, file_size, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // close but do not delete file
    storage_close_file(handle);
}

TEST_P(StorageServiceTest, ReadPersistent32k) {
    int rc;
    file_handle_t handle;
    size_t exp_len = 32 * 1024;
    const char *fname = "test_persistent_32K_file";

    // create/truncate file.
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(0, rc);

    ReadPatternEOF(handle, 0, 2048, exp_len);
    ASSERT_FALSE(HasFatalFailure());

    ReadPatternEOF(handle, 0, 1024, exp_len);
    ASSERT_FALSE(HasFatalFailure());

    ReadPatternEOF(handle, 0,  332, exp_len);
    ASSERT_FALSE(HasFatalFailure());

    // close but do not delete file
    storage_close_file(handle);
}

TEST_P(StorageServiceTest, CleanUpPersistent32K) {
    int rc;
    const char *fname = "test_persistent_32K_file";
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    rc = (rc == -ENOENT) ? 0 : rc;
    ASSERT_EQ(0, rc);
}

// Persistent 1M
TEST_P(StorageServiceTest, CreatePersistent1M_4040) {
    int rc;
    file_handle_t handle;
    size_t file_size = 1024 * 1024;
    const char *fname = "test_persistent_1M_file";

    // create/truncate file.
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write a bunch of blocks filled with pattern
    WritePattern(handle, 0, file_size, 4040, true);
    ASSERT_FALSE(HasFatalFailure());

    // close but do not delete file
    storage_close_file(handle);
}

TEST_P(StorageServiceTest, CreatePersistent1M_2032) {
    int rc;
    file_handle_t handle;
    size_t file_size = 1024 * 1024;
    const char *fname = "test_persistent_1M_file";

    // create/truncate file.
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write a bunch of blocks filled with pattern
    WritePattern(handle, 0, file_size, 2032, true);
    ASSERT_FALSE(HasFatalFailure());

    // close but do not delete file
    storage_close_file(handle);
}


TEST_P(StorageServiceTest, CreatePersistent1M_496) {
    int rc;
    file_handle_t handle;
    size_t file_size = 1024 * 1024;
    const char *fname = "test_persistent_1M_file";

    // create/truncate file.
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write a bunch of blocks filled with pattern
    WritePattern(handle, 0, file_size, 496, true);
    ASSERT_FALSE(HasFatalFailure());

    // close but do not delete file
    storage_close_file(handle);
}

TEST_P(StorageServiceTest, CreatePersistent1M_240) {
    int rc;
    file_handle_t handle;
    size_t file_size = 1024 * 1024;
    const char *fname = "test_persistent_1M_file";

    // create/truncate file.
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write a bunch of blocks filled with pattern
    WritePattern(handle, 0, file_size, 240, true);
    ASSERT_FALSE(HasFatalFailure());

    // close but do not delete file
    storage_close_file(handle);
}

TEST_P(StorageServiceTest, ReadPersistent1M_4040) {
    int rc;
    file_handle_t handle;
    size_t exp_len = 1024 * 1024;
    const char *fname = "test_persistent_1M_file";

    // create/truncate file.
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(0, rc);

    ReadPatternEOF(handle, 0, 4040, exp_len);
    ASSERT_FALSE(HasFatalFailure());

    // close but do not delete file
    storage_close_file(handle);
}

TEST_P(StorageServiceTest, ReadPersistent1M_2032) {
    int rc;
    file_handle_t handle;
    size_t exp_len = 1024 * 1024;
    const char *fname = "test_persistent_1M_file";

    // create/truncate file.
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(0, rc);

    ReadPatternEOF(handle, 0, 2032, exp_len);
    ASSERT_FALSE(HasFatalFailure());

    // close but do not delete file
    storage_close_file(handle);
}

TEST_P(StorageServiceTest, ReadPersistent1M_496) {
    int rc;
    file_handle_t handle;
    size_t exp_len = 1024 * 1024;
    const char *fname = "test_persistent_1M_file";

    // create/truncate file.
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(0, rc);

    ReadPatternEOF(handle, 0, 496, exp_len);
    ASSERT_FALSE(HasFatalFailure());

    // close but do not delete file
    storage_close_file(handle);
}

TEST_P(StorageServiceTest, ReadPersistent1M_240) {
    int rc;
    file_handle_t handle;
    size_t exp_len = 1024 * 1024;
    const char *fname = "test_persistent_1M_file";

    // create/truncate file.
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(0, rc);

    ReadPatternEOF(handle, 0, 240, exp_len);
    ASSERT_FALSE(HasFatalFailure());

    // close but do not delete file
    storage_close_file(handle);
}

TEST_P(StorageServiceTest, CleanUpPersistent1M) {
    int rc;
    const char *fname = "test_persistent_1M_file";
    rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
    rc = (rc == -ENOENT) ? 0 : rc;
    ASSERT_EQ(0, rc);
}

TEST_P(StorageServiceTest, WriteReadLong) {
    int rc;
    file_handle_t handle;
    size_t wc = 10000;
    const char *fname = "test_write_read_long";

    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    test_buf_ = new uint32_t[wc];
    fill_pattern32(test_buf_, wc * sizeof(uint32_t), 0);
    rc = storage_write(handle, 0, test_buf_, wc * sizeof(uint32_t), STORAGE_OP_COMPLETE);
    ASSERT_EQ((int)(wc * sizeof(uint32_t)), rc);

    rc = storage_read(handle, 0, test_buf_, wc * sizeof(uint32_t));
    ASSERT_EQ((int)(wc * sizeof(uint32_t)), rc);
    ASSERT_TRUE(check_pattern32(test_buf_, wc * sizeof(uint32_t), 0));

    // cleanup
    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}

// Negative tests

TEST_P(StorageServiceTest, OpenInvalidFileName) {
    int rc;
    file_handle_t handle;
    const char *fname1 = "";
    const char *fname2 = "ffff$ffff";
    const char *fname3 = "ffff\\ffff";
    char max_name[STORAGE_MAX_NAME_LENGTH_BYTES+1];

    rc = storage_open_file(session_, &handle, fname1,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(-EINVAL, rc);

    rc = storage_open_file(session_, &handle, fname2,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(-EINVAL, rc);

    rc = storage_open_file(session_, &handle, fname3,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(-EINVAL, rc);

    /* max name */
    memset(max_name, 'a', sizeof(max_name));
    max_name[sizeof(max_name)-1] = 0;

    rc = storage_open_file(session_, &handle, max_name,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(-EINVAL, rc);

    max_name[sizeof(max_name)-2] = 0;
    rc = storage_open_file(session_, &handle, max_name,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    storage_close_file(handle);
    storage_delete_file(session_, max_name, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, BadFileHnadle) {
    int rc;
    file_handle_t handle;
    file_handle_t handle1;
    const char *fname = "test_invalid_file_handle";

    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    handle1 = handle + 1;

    // write to invalid file handle
    uint32_t val = 0xDEDBEEF;
    rc = storage_write(handle1,  0, &val, sizeof(val), STORAGE_OP_COMPLETE);
    ASSERT_EQ(-EINVAL, rc);

    // read from invalid handle
    rc = storage_read(handle1,  0, &val, sizeof(val));
    ASSERT_EQ(-EINVAL, rc);

    // set size
    rc = storage_set_file_size(handle1,  0, STORAGE_OP_COMPLETE);
    ASSERT_EQ(-EINVAL, rc);

    // get size
    storage_off_t fsize = (storage_off_t)(-1);
    rc = storage_get_file_size(handle1,  &fsize);
    ASSERT_EQ(-EINVAL, rc);

    // close (there is no way to check errors here)
    storage_close_file(handle1);

    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, ClosedFileHnadle) {
    int rc;
    file_handle_t handle1;
    file_handle_t handle2;
    const char *fname1 = "test_invalid_file_handle1";
    const char *fname2 = "test_invalid_file_handle2";

    rc = storage_open_file(session_, &handle1, fname1,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    rc = storage_open_file(session_, &handle2, fname2,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // close first file handle
    storage_close_file(handle1);

    // write to invalid file handle
    uint32_t val = 0xDEDBEEF;
    rc = storage_write(handle1,  0, &val, sizeof(val), STORAGE_OP_COMPLETE);
    ASSERT_EQ(-EINVAL, rc);

    // read from invalid handle
    rc = storage_read(handle1,  0, &val, sizeof(val));
    ASSERT_EQ(-EINVAL, rc);

    // set size
    rc = storage_set_file_size(handle1,  0, STORAGE_OP_COMPLETE);
    ASSERT_EQ(-EINVAL, rc);

    // get size
    storage_off_t fsize = (storage_off_t)(-1);
    rc = storage_get_file_size(handle1,  &fsize);
    ASSERT_EQ(-EINVAL, rc);

    // close (there is no way to check errors here)
    storage_close_file(handle1);

    // clean up
    storage_close_file(handle2);
    storage_delete_file(session_, fname1, STORAGE_OP_COMPLETE);
    storage_delete_file(session_, fname2, STORAGE_OP_COMPLETE);
}

// Transactions

TEST_P(StorageServiceTest, TransactDiscardInactive) {
    int rc;

    // discard current transaction (there should not be any)
    rc = storage_end_transaction(session_, false);
    ASSERT_EQ(0, rc);

    // try it again
    rc = storage_end_transaction(session_, false);
    ASSERT_EQ(0, rc);
}

TEST_P(StorageServiceTest, TransactCommitInactive) {
    int rc;

    // try to commit current transaction
    rc = storage_end_transaction(session_, true);
    ASSERT_EQ(0, rc);

    // try it again
    rc = storage_end_transaction(session_, true);
    ASSERT_EQ(0, rc);
}

TEST_P(StorageServiceTest, TransactDiscardWrite) {

    int rc;
    file_handle_t handle;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_discard_write";

    // open create truncate file (with commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // check file size
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    // write (without commit)
    WritePattern(handle, 0, exp_len, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // check file size
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // abort current transaction
    rc = storage_end_transaction(session_, false);
    ASSERT_EQ(0, rc);

    // check file size
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    // cleanup
    storage_close_file( handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, TransactDiscardWriteAppend) {

    int rc;
    file_handle_t handle;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_write_append";

    // open create truncate file (with commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write data with commit
    WritePattern(handle, 0, exp_len/2, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // write data without commit
    WritePattern(handle, exp_len/2, exp_len/2, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // check file size (should be exp_len)
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // discard transaction
    rc = storage_end_transaction(session_, false);
    ASSERT_EQ(0, rc);

    // check file size, it should be exp_len/2
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len/2, fsize);

    // check file data
    ReadPatternEOF(handle, 0, blk, exp_len/2);
    ASSERT_FALSE(HasFatalFailure());

    // cleanup
    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}

TEST_P(StorageServiceTest, TransactDiscardWriteRead) {

    int rc;
    file_handle_t handle;
    size_t blk = 2048;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_discard_write_read";

    // open create truncate file (with commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // check file size
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    // Fill with zeroes (with commit)
    for (uint i = 0; i < 32; i++) {
        WriteZeroChunk(handle, i * blk, blk, true);
        ASSERT_FALSE(HasFatalFailure());
    }

    // check that test chunk is filled with zeroes
    ReadChunk(handle, blk, blk, blk, 0, 0);
    ASSERT_FALSE(HasFatalFailure());

    // write test pattern (without commit)
    WritePattern(handle, blk, blk, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // read it back an check pattern
    ReadChunk(handle, blk, blk, 0, blk, 0);
    ASSERT_FALSE(HasFatalFailure());

    // abort current transaction
    rc = storage_end_transaction(session_, false);
    ASSERT_EQ(0, rc);

    // read same chunk back (should be filled with zeros)
    ReadChunk(handle, blk, blk, blk, 0, 0);
    ASSERT_FALSE(HasFatalFailure());

    // cleanup
    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}

TEST_P(StorageServiceTest, TransactDiscardWriteMany) {
    int rc;
    file_handle_t handle1;
    file_handle_t handle2;
    size_t blk = 2048;
    size_t exp_len1 = 32 * 1024;
    size_t exp_len2 = 31 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname1 = "test_transact_discard_write_file1";
    const char *fname2 = "test_transact_discard_write_file2";

    // open create truncate (with commit)
    rc = storage_open_file(session_, &handle1, fname1,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // open create truncate (with commit)
    rc = storage_open_file(session_, &handle2, fname2,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // file1: fill file with pattern (without commit)
    WritePattern(handle1, 0, exp_len1, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // file2: fill file with pattern (without commit)
    WritePattern(handle2, 0, exp_len2, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // check file size, it should be exp_len1
    rc = storage_get_file_size(handle1, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len1, fsize);

    // check file size, it should be exp_len2
    rc = storage_get_file_size(handle2, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len2, fsize);

    // commit transaction
    rc = storage_end_transaction(session_, false);
    ASSERT_EQ(0, rc);

    // check file size, it should be exp_len1
    rc = storage_get_file_size(handle1, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    // check file size, it should be exp_len2
    rc = storage_get_file_size(handle2, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    // check data
    ReadPatternEOF(handle1, 0, blk, 0);
    ASSERT_FALSE(HasFatalFailure());

    ReadPatternEOF(handle2, 0, blk, 0);
    ASSERT_FALSE(HasFatalFailure());

    // cleanup
    storage_close_file(handle1);
    storage_delete_file(session_, fname1, STORAGE_OP_COMPLETE);
    storage_close_file(handle2);
    storage_delete_file(session_, fname2, STORAGE_OP_COMPLETE);
}

TEST_P(StorageServiceTest, TransactDiscardTruncate) {
    int rc;
    file_handle_t handle;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_discard_truncate";

    // open create truncate file (with commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write data (with commit)
    WritePattern(handle, 0, exp_len, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // check file size
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // close file
    storage_close_file(handle);

    // open truncate file (without commit)
    rc = storage_open_file(session_, &handle, fname, STORAGE_FILE_OPEN_TRUNCATE, 0);
    ASSERT_EQ(0, rc);

    // check file size
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    // abort current transaction
    rc = storage_end_transaction(session_, false);
    ASSERT_EQ(0, rc);

    // check file size (should be an oruginal size)
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // cleanup
    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}

TEST_P(StorageServiceTest, TransactDiscardSetSize) {
    int rc;
    file_handle_t handle;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_discard_set_size";

    // open create truncate file (with commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write data (with commit)
    WritePattern(handle, 0, exp_len, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // check file size
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // set file size to half of original (no commit)
    rc = storage_set_file_size(handle,  (storage_off_t)exp_len/2, 0);
    ASSERT_EQ(0, rc);

    // check file size
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len/2, fsize);

    // set file size to 1/3 of original (no commit)
    rc = storage_set_file_size(handle,  (storage_off_t)exp_len/3, 0);
    ASSERT_EQ(0, rc);

    // check file size
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len/3, fsize);

    // abort current transaction
    rc = storage_end_transaction(session_, false);
    ASSERT_EQ(0, rc);

    // check file size (should be an original size)
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // cleanup
    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}

TEST_P(StorageServiceTest, TransactDiscardDelete) {
    int rc;
    file_handle_t handle;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_discard_delete";

    // open create truncate file (with commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write data (with commit)
    WritePattern(handle, 0, exp_len, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // close it
    storage_close_file(handle);

    // delete file (without commit)
    rc = storage_delete_file(session_, fname, 0);
    ASSERT_EQ(0, rc);

    // try to open it (should fail)
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(-ENOENT, rc);

    // abort current transaction
    rc = storage_end_transaction(session_, false);
    ASSERT_EQ(0, rc);

    // try to open it
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(0, rc);

    // check file size (should be an original size)
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // cleanup
    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}

TEST_P(StorageServiceTest, TransactDiscardDelete2) {
    int rc;
    file_handle_t handle;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_discard_delete";

    // open create truncate file (with commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write data (with commit)
    WritePattern(handle, 0, exp_len, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // delete file (without commit)
    rc = storage_delete_file(session_, fname, 0);
    ASSERT_EQ(0, rc);
    storage_close_file(handle);

    // try to open it (should fail)
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(-ENOENT, rc);

    // abort current transaction
    rc = storage_end_transaction(session_, false);
    ASSERT_EQ(0, rc);

    // try to open it
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(0, rc);

    // check file size (should be an original size)
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // cleanup
    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, TransactDiscardCreate) {
    int rc;
    file_handle_t handle;
    const char *fname = "test_transact_discard_create_excl";

    // delete test file just in case
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);

    // create file (without commit)
    rc = storage_open_file(session_, &handle, fname,
                               STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE,
                               0);
    ASSERT_EQ(0, rc);

    // abort current transaction
    rc = storage_end_transaction(session_, false);
    ASSERT_EQ(0, rc);

    // cleanup
    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}

TEST_P(StorageServiceTest, TransactCommitWrites) {

    int rc;
    file_handle_t handle;
    file_handle_t handle_aux;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_commit_writes";

    // open second session
    rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_);
    ASSERT_EQ(0, rc);

    // open create truncate file (with commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // open the same file in aux session
    rc = storage_open_file(aux_session_, &handle_aux, fname,  0, 0);
    ASSERT_EQ(0, rc);

    // check file size, it should be 0
    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    // write data in primary session (without commit)
    WritePattern(handle, 0, exp_len/2, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // write more data in primary session (without commit)
    WritePattern(handle, exp_len/2, exp_len/2, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // check file size in aux session, it should still be 0
    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    // commit current transaction
    rc = storage_end_transaction(session_, true);
    ASSERT_EQ(0, rc);

    // check file size of aux session, should fail
    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(-EBUSY, rc);

    // abort transaction in aux session to recover
    rc = storage_end_transaction(aux_session_, false);
    ASSERT_EQ(0, rc);

    // check file size in aux session, it should be exp_len
    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // check file size in primary session, it should be exp_len
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // check data in primary session
    ReadPatternEOF(handle, 0, blk, exp_len);
    ASSERT_FALSE(HasFatalFailure());

    // check data in aux session
    ReadPatternEOF(handle_aux, 0, blk, exp_len);
    ASSERT_FALSE(HasFatalFailure());

    // cleanup
    storage_close_file(handle);
    storage_close_file(handle_aux);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, TransactCommitWrites2) {

    int rc;
    file_handle_t handle;
    file_handle_t handle_aux;
    size_t blk = 2048;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_commit_writes2";

    // open second session
    rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_);
    ASSERT_EQ(0, rc);

    // open create truncate file (with commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // open the same file in separate session
    rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0);
    ASSERT_EQ(0, rc);

    // check file size
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    // discard transaction in aux_session
    rc = storage_end_transaction(aux_session_,  false);
    ASSERT_EQ(0, rc);

    // Fill with zeroes (with commit)
    for (uint i = 0; i < 8; i++) {
        WriteZeroChunk(handle, i * blk, blk, true);
        ASSERT_FALSE(HasFatalFailure());
    }

    // check that test chunks are filled with zeroes
    ReadChunk(handle, blk, blk, blk, 0, 0);
    ASSERT_FALSE(HasFatalFailure());

    ReadChunk(handle, 2 * blk, blk, blk, 0, 0);
    ASSERT_FALSE(HasFatalFailure());

    // write test pattern (without commit)
    WritePattern(handle, blk, blk, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // write test pattern (without commit)
    WritePattern(handle, 2 * blk, blk, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // read it back and check pattern
    ReadChunk(handle, blk, blk, 0, blk, 0);
    ASSERT_FALSE(HasFatalFailure());

    ReadChunk(handle, 2 * blk, blk, 0, blk, 0);
    ASSERT_FALSE(HasFatalFailure());

    // In aux session it still should be empty
    ReadChunk(handle_aux, blk, blk, blk, 0, 0);
    ASSERT_FALSE(HasFatalFailure());

    ReadChunk(handle_aux, 2 * blk, blk, blk, 0, 0);
    ASSERT_FALSE(HasFatalFailure());

    // commit current transaction
    rc = storage_end_transaction(session_, true);
    ASSERT_EQ(0, rc);

    // read same chunks back in primary session
    ReadChunk(handle, blk, blk, 0, blk, 0);
    ASSERT_FALSE(HasFatalFailure());

    ReadChunk(handle, 2 * blk, blk, 0, blk, 0);
    ASSERT_FALSE(HasFatalFailure());

    // read same chunks back in aux session (should fail)
    uint32_t val;
    rc = storage_read(handle_aux, blk, &val, sizeof(val));
    ASSERT_EQ(-EBUSY, rc);

    rc = storage_read(handle_aux, 2 * blk, &val, sizeof(val));
    ASSERT_EQ(-EBUSY, rc);

    // abort transaction in aux session
    rc = storage_end_transaction(aux_session_,  false);
    ASSERT_EQ(0, rc);

    // read same chunk again in aux session
    ReadChunk(handle_aux, blk, blk, 0, blk, 0);
    ASSERT_FALSE(HasFatalFailure());

    ReadChunk(handle_aux, 2 * blk, blk, 0, blk, 0);
    ASSERT_FALSE(HasFatalFailure());


    // cleanup
    storage_close_file(handle);
    storage_close_file(handle_aux);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}

TEST_P(StorageServiceTest, TransactCommitSetSize) {
    int rc;
    file_handle_t handle;
    file_handle_t handle_aux;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_commit_set_size";

    // open second session
    rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_);
    ASSERT_EQ(0, rc);

    // open create truncate file (with commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // open the same file in separate session
    rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0);
    ASSERT_EQ(0, rc);

    // write data (with commit)
    WritePattern(handle, 0, exp_len, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // check file size
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // same in aux session
    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // set file size to half of original (no commit)
    rc = storage_set_file_size(handle,  (storage_off_t)exp_len/2, 0);
    ASSERT_EQ(0, rc);

    // check file size
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len/2, fsize);

    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // set file size to 1/3 of original (no commit)
    rc = storage_set_file_size(handle,  (storage_off_t)exp_len/3, 0);
    ASSERT_EQ(0, rc);

    // check file size
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len/3, fsize);

    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // commit current transaction
    rc = storage_end_transaction(session_, true);
    ASSERT_EQ(0, rc);

    // check file size (should be 1/3 of an original size)
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len/3, fsize);

    // check file size from aux session
    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(-EBUSY, rc);

    // abort transaction in aux_session
    rc = storage_end_transaction(aux_session_, false);
    ASSERT_EQ(0, rc);

    // check again
    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len/3, fsize);

    // cleanup
    storage_close_file(handle);
    storage_close_file(handle_aux);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, TransactCommitDelete) {
    int rc;
    file_handle_t handle;
    file_handle_t handle_aux;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    const char *fname = "test_transact_commit_delete";

    // open second session
    rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_);
    ASSERT_EQ(0, rc);

    // open create truncate file (with commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write data (with commit)
    WritePattern(handle, 0, exp_len, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // close it
    storage_close_file(handle);

    // open the same file in separate session
    rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0);
    ASSERT_EQ(0, rc);
    storage_close_file(handle_aux);

    // delete file (without commit)
    rc = storage_delete_file(session_, fname, 0);
    ASSERT_EQ(0, rc);

    // try to open it (should fail)
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(-ENOENT, rc);

    // open the same file in separate session (should be fine)
    rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0);
    ASSERT_EQ(0, rc);
    storage_close_file(handle_aux);

    // commit current transaction
    rc = storage_end_transaction(session_, true);
    ASSERT_EQ(0, rc);

    // try to open it in primary session (still fails)
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(-ENOENT, rc);

    // open the same file in aux session (should also fail)
    rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0);
    ASSERT_EQ(-ENOENT, rc);
}


TEST_P(StorageServiceTest, TransactCommitTruncate) {
    int rc;
    file_handle_t handle;
    file_handle_t handle_aux;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_commit_truncate";

    // open second session
    rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_);
    ASSERT_EQ(0, rc);

    // open create truncate file (with commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write data (with commit)
    WritePattern(handle, 0, exp_len, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // check file size
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // close file
    storage_close_file(handle);

    // check from different session
    rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0);
    ASSERT_EQ(0, rc);

    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // open truncate file (without commit)
    rc = storage_open_file(session_, &handle, fname, STORAGE_FILE_OPEN_TRUNCATE, 0);
    ASSERT_EQ(0, rc);

    // check file size
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // commit current transaction
    rc = storage_end_transaction(session_, true);
    ASSERT_EQ(0, rc);

    // check file size (should be 0)
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    // check file size in aux session (should be -EBUSY)
    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(-EBUSY, rc);

    // abort transaction in aux session
    rc = storage_end_transaction(aux_session_, false);
    ASSERT_EQ(0, rc);

    // check again
    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    // cleanup
    storage_close_file(handle);
    storage_close_file(handle_aux);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}

TEST_P(StorageServiceTest, TransactCommitCreate) {
    int rc;
    file_handle_t handle;
    file_handle_t handle_aux;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_commit_create";

    // open second session
    rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_);
    ASSERT_EQ(0, rc);

    // delete test file just in case
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);

    // check from aux session
    rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0);
    ASSERT_EQ(-ENOENT, rc);

    // create file (without commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE,
                           0);
    ASSERT_EQ(0, rc);

    // check file size
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    // close file
    storage_close_file(handle);

    // check from aux session (should fail)
    rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0);
    ASSERT_EQ(-ENOENT, rc);

    // commit current transaction
    rc = storage_end_transaction(session_, true);
    ASSERT_EQ(0, rc);

    // check open from normal session
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(0, rc);

    // check open from aux session (should succeed)
    rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0);
    ASSERT_EQ(0, rc);

    // cleanup
    storage_close_file(handle);
    storage_close_file(handle_aux);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}

TEST_P(StorageServiceTest, TransactCommitCreateMany) {
    int rc;
    file_handle_t handle1;
    file_handle_t handle2;
    file_handle_t handle1_aux;
    file_handle_t handle2_aux;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname1 = "test_transact_commit_create1";
    const char *fname2 = "test_transact_commit_create2";

    // open second session
    rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_);
    ASSERT_EQ(0, rc);

    // delete test file just in case
    storage_delete_file(session_, fname1, STORAGE_OP_COMPLETE);
    storage_delete_file(session_, fname2, STORAGE_OP_COMPLETE);

    // create file (without commit)
    rc = storage_open_file(session_, &handle1, fname1,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE,
                           0);
    ASSERT_EQ(0, rc);

    // create file (without commit)
    rc = storage_open_file(session_, &handle2, fname2,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE,
                           0);
    ASSERT_EQ(0, rc);

    // check file sizes
    rc = storage_get_file_size(handle1, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    rc = storage_get_file_size(handle1, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    // close files
    storage_close_file(handle1);
    storage_close_file(handle2);

    // open files from aux session
    rc = storage_open_file(aux_session_, &handle1_aux, fname1, 0, 0);
    ASSERT_EQ(-ENOENT, rc);

    rc = storage_open_file(aux_session_, &handle2_aux, fname2, 0, 0);
    ASSERT_EQ(-ENOENT, rc);

    // commit current transaction
    rc = storage_end_transaction(session_, true);
    ASSERT_EQ(0, rc);

    // open from primary session
    rc = storage_open_file(session_, &handle1, fname1, 0, 0);
    ASSERT_EQ(0, rc);

    rc = storage_open_file(session_, &handle2, fname2, 0, 0);
    ASSERT_EQ(0, rc);

    // open from aux session
    rc = storage_open_file(aux_session_, &handle1_aux, fname1, 0, 0);
    ASSERT_EQ(0, rc);

    rc = storage_open_file(aux_session_, &handle2_aux, fname2, 0, 0);
    ASSERT_EQ(0, rc);

    // cleanup
    storage_close_file(handle1);
    storage_close_file(handle1_aux);
    storage_delete_file(session_, fname1, STORAGE_OP_COMPLETE);
    storage_close_file(handle2);
    storage_close_file(handle2_aux);
    storage_delete_file(session_, fname2, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, TransactCommitWriteMany) {
    int rc;
    file_handle_t handle1;
    file_handle_t handle2;
    file_handle_t handle1_aux;
    file_handle_t handle2_aux;
    size_t blk = 2048;
    size_t exp_len1 = 32 * 1024;
    size_t exp_len2 = 31 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname1 = "test_transact_commit_write_file1";
    const char *fname2 = "test_transact_commit_write_file2";

    // open second session
    rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_);
    ASSERT_EQ(0, rc);

    // open create truncate (with commit)
    rc = storage_open_file(session_, &handle1, fname1,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // open create truncate (with commit)
    rc = storage_open_file(session_, &handle2, fname2,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // open same files from aux session
    rc = storage_open_file(aux_session_, &handle1_aux, fname1, 0, 0);
    ASSERT_EQ(0, rc);

    rc = storage_open_file(aux_session_, &handle2_aux, fname2, 0, 0);
    ASSERT_EQ(0, rc);

    // file1: fill file with pattern (without commit)
    WritePattern(handle1, 0, exp_len1, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // file2: fill file with pattern (without commit)
    WritePattern(handle2, 0, exp_len2, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // check file size, it should be exp_len1
    rc = storage_get_file_size(handle1, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len1, fsize);

    // check file size, it should be exp_len2
    rc = storage_get_file_size(handle2, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len2, fsize);

    // check file sizes from aux session (should be 0)
    rc = storage_get_file_size(handle1_aux, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    rc = storage_get_file_size(handle2_aux, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    // commit transaction
    rc = storage_end_transaction(session_, true);
    ASSERT_EQ(0, rc);

    // check file size, it should be exp_len1
    rc = storage_get_file_size(handle1, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len1, fsize);

    // check file size, it should be exp_len2
    rc = storage_get_file_size(handle2, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len2, fsize);

    // check from aux session (should be -EBUSY)
    rc = storage_get_file_size(handle1_aux, &fsize);
    ASSERT_EQ(-EBUSY, rc);

    // abort transaction in aux session
    rc = storage_end_transaction(aux_session_, false);
    ASSERT_EQ(0, rc);

    // and check again
    rc = storage_get_file_size(handle1_aux, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len1, fsize);

    rc = storage_get_file_size(handle2_aux, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len2, fsize);

    // check data
    ReadPatternEOF(handle1, 0, blk, exp_len1);
    ASSERT_FALSE(HasFatalFailure());

    ReadPatternEOF(handle2, 0, blk, exp_len2);
    ASSERT_FALSE(HasFatalFailure());

    ReadPatternEOF(handle1_aux, 0, blk, exp_len1);
    ASSERT_FALSE(HasFatalFailure());

    ReadPatternEOF(handle2_aux, 0, blk, exp_len2);
    ASSERT_FALSE(HasFatalFailure());

    // cleanup
    storage_close_file(handle1);
    storage_close_file(handle1_aux);
    storage_delete_file(session_, fname1, STORAGE_OP_COMPLETE);
    storage_close_file(handle2);
    storage_close_file(handle2_aux);
    storage_delete_file(session_, fname2, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, TransactCommitDeleteCreate) {
    int rc;
    file_handle_t handle;
    file_handle_t handle_aux;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_delete_create";

    // open second session
    rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_);
    ASSERT_EQ(0, rc);

    // open create truncate file (with commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write data (with commit)
    WritePattern(handle, 0, exp_len, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // close it
    storage_close_file(handle);

    // delete file (without commit)
    rc = storage_delete_file(session_, fname, 0);
    ASSERT_EQ(0, rc);

    // try to open it (should fail)
    rc = storage_open_file(session_, &handle, fname, 0, 0);
    ASSERT_EQ(-ENOENT, rc);

    // try to open it in aux session (should succeed)
    rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0);
    ASSERT_EQ(0, rc);

    // create file with the same name (no commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE,
                           0);
    ASSERT_EQ(0, rc);

    // write half of data (with commit)
    WritePattern(handle, 0, exp_len/2, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // check file size (should be half)
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len/2, fsize);

    // commit transaction
    rc = storage_end_transaction(session_, true);
    ASSERT_EQ(0, rc);

    // check data from primary session
    ReadPatternEOF(handle, 0, blk, exp_len/2);
    ASSERT_FALSE(HasFatalFailure());

    // check from aux session (should fail)
    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(-EINVAL, rc);

    // abort trunsaction in aux session
    rc = storage_end_transaction(aux_session_, false);
    ASSERT_EQ(0, rc);

    // and try again (should still fail)
    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(-EINVAL, rc);

    // close file and reopen it again
    storage_close_file(handle_aux);
    rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0);
    ASSERT_EQ(0, rc);

    // try it again (should succeed)
    rc = storage_get_file_size(handle_aux, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len/2, fsize);

    // check data
    ReadPatternEOF(handle_aux, 0, blk, exp_len/2);
    ASSERT_FALSE(HasFatalFailure());

    // cleanup
    storage_close_file(handle);
    storage_close_file(handle_aux);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}

TEST_P(StorageServiceTest, TransactRewriteExistingTruncate) {
    int rc;
    file_handle_t handle;
    size_t blk = 2048;
    const char *fname = "test_transact_rewrite_existing_truncate";

    // open create truncate file (with commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // close it
    storage_close_file(handle);

    // up
    for (uint i = 1; i < 32; i++) {
        // open truncate (no commit)
        rc = storage_open_file(session_, &handle, fname, STORAGE_FILE_OPEN_TRUNCATE, 0);
        ASSERT_EQ(0, rc);

        // write data (with commit)
        WritePattern(handle, 0, i * blk, blk, true);
        ASSERT_FALSE(HasFatalFailure());

        // close
        storage_close_file(handle);
    }

    // down
    for (uint i = 1; i < 32; i++) {
        // open truncate (no commit)
        rc = storage_open_file(session_, &handle, fname, STORAGE_FILE_OPEN_TRUNCATE, 0);
        ASSERT_EQ(0, rc);

        // write data (with commit)
        WritePattern(handle, 0, (32 - i) * blk, blk, true);
        ASSERT_FALSE(HasFatalFailure());

        // close
        storage_close_file(handle);
    }

    // cleanup
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, TransactRewriteExistingSetSize) {
    int rc;
    file_handle_t handle;
    size_t blk = 2048;
    const char *fname = "test_transact_rewrite_existing_set_size";

    // open create truncate file (with commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // close it
    storage_close_file(handle);

    // up
    for (uint i = 1; i < 32; i++) {
        // open truncate (no commit)
        rc = storage_open_file(session_, &handle, fname, 0, 0);
        ASSERT_EQ(0, rc);

        // write data (with commit)
        WritePattern(handle, 0, i * blk, blk, false);
        ASSERT_FALSE(HasFatalFailure());

        // update size (with commit)
        rc = storage_set_file_size(handle, i * blk, STORAGE_OP_COMPLETE);
        ASSERT_EQ(0, rc);

        // close
        storage_close_file(handle);
    }

    // down
    for (uint i = 1; i < 32; i++) {
        // open trancate (no commit)
        rc = storage_open_file(session_, &handle, fname, 0, 0);
        ASSERT_EQ(0, rc);

        // write data (with commit)
        WritePattern(handle, 0, (32 - i) * blk, blk, false);
        ASSERT_FALSE(HasFatalFailure());

        // update size (with commit)
        rc = storage_set_file_size(handle, (32 - i) * blk, STORAGE_OP_COMPLETE);
        ASSERT_EQ(0, rc);

        // close
        storage_close_file(handle);
    }

    // cleanup
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, TransactResumeAfterNonFatalError) {

    int rc;
    file_handle_t handle;
    file_handle_t handle1;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_resume_writes";

    // open create truncate file (with commit)
    rc = storage_open_file(session_, &handle, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // write (without commit)
    WritePattern(handle, 0, exp_len/2, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // issue some commands that should fail with non-fatal errors

    // write past end of file
    uint32_t val = 0xDEDBEEF;
    rc = storage_write(handle,  exp_len/2 + 1, &val, sizeof(val), 0);
    ASSERT_EQ(-EINVAL, rc);

    // read past end of file
    rc = storage_read(handle, exp_len/2 + 1, &val, sizeof(val));
    ASSERT_EQ(-EINVAL, rc);

    // try to extend file past end of file
    rc = storage_set_file_size(handle, exp_len/2 + 1, 0);
    ASSERT_EQ(-EINVAL, rc);

    // open non existing file
    rc = storage_open_file(session_, &handle1, "foo",
                           STORAGE_FILE_OPEN_TRUNCATE, STORAGE_OP_COMPLETE);
    ASSERT_EQ(-ENOENT, rc);

    // delete non-existing file
    rc = storage_delete_file(session_, "foo", STORAGE_OP_COMPLETE);
    ASSERT_EQ(-ENOENT, rc);

    // then resume writinga (without commit)
    WritePattern(handle, exp_len/2, exp_len/2, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // commit current transaction
    rc = storage_end_transaction(session_, true);
    ASSERT_EQ(0, rc);

    // check file size, it should be exp_len
    rc = storage_get_file_size(handle, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // check data
    ReadPatternEOF(handle, 0, blk, exp_len);
    ASSERT_FALSE(HasFatalFailure());

    // cleanup
    storage_close_file(handle);
    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


// Transaction Collisions

TEST_P(StorageServiceTest, Transact2_WriteNC) {
    int rc;
    file_handle_t handle1;
    file_handle_t handle2;
    size_t blk = 2048;
    const char *fname1 = "test_transact_f1";
    const char *fname2 = "test_transact_f2";

    // open second session
    rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_);
    ASSERT_EQ(0, rc);

    rc = storage_open_file(session_, &handle1, fname1,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    rc = storage_open_file(aux_session_, &handle2, fname2,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // session 1
    WritePattern(handle1, 0, blk, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // read it back
    ReadPatternEOF(handle1, 0, blk, blk);
    ASSERT_FALSE(HasFatalFailure());

    // session 2
    WritePattern(handle2, 0, blk, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // read it back
    ReadPatternEOF(handle2, 0, blk, blk);
    ASSERT_FALSE(HasFatalFailure());

    // cleanup
    storage_close_file(handle1);
    storage_close_file(handle2);

    storage_delete_file(session_, fname1, STORAGE_OP_COMPLETE);
    storage_delete_file(aux_session_, fname2, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, Transact2_DeleteNC) {
    int rc;
    file_handle_t handle1;
    file_handle_t handle2;
    size_t blk = 2048;
    const char *fname1 = "test_transact_delete_f1";
    const char *fname2 = "test_transact_delete_f2";

    // open second session
    rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_);
    ASSERT_EQ(0, rc);

    rc = storage_open_file(session_, &handle1, fname1,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    rc = storage_open_file(aux_session_, &handle2, fname2,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // session 1
    WritePattern(handle1, 0, blk, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // read it back
    ReadPatternEOF(handle1, 0, blk, blk);
    ASSERT_FALSE(HasFatalFailure());

    // session 2
    WritePattern(handle2, 0, blk, blk, true);
    ASSERT_FALSE(HasFatalFailure());

    // read it back
    ReadPatternEOF(handle2, 0, blk, blk);
    ASSERT_FALSE(HasFatalFailure());

    // close files and delete them
    storage_close_file(handle1);
    storage_delete_file(session_, fname1, 0);

    storage_close_file(handle2);
    storage_delete_file(aux_session_, fname2, 0);

    // commit
    rc = storage_end_transaction(session_, true);
    ASSERT_EQ(0, rc);

    rc = storage_end_transaction(aux_session_, true);
    ASSERT_EQ(0, rc);
}


TEST_P(StorageServiceTest, Transact2_Write_Read) {
    int rc;
    file_handle_t handle1;
    file_handle_t handle2;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_writeRead";

    // open second session
    rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_);
    ASSERT_EQ(0, rc);

    // S1: open create truncate file
    rc = storage_open_file(session_, &handle1, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // S2: open the same file
    rc = storage_open_file(aux_session_, &handle2, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // S1: write (no commit)
    WritePattern(handle1, 0, exp_len, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // S1: read it back
    ReadPatternEOF(handle1, 0, blk, exp_len);
    ASSERT_FALSE(HasFatalFailure());

    // S2: check file size, it should be 0
    rc = storage_get_file_size(handle2, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    // S2: read it back (should no data)
    ReadPatternEOF(handle2, 0, blk, 0);
    ASSERT_FALSE(HasFatalFailure());

    // S1: commit
    rc = storage_end_transaction(session_, true);
    ASSERT_EQ(0, rc);

    // S2: check file size, it should fail
    rc = storage_get_file_size(handle2, &fsize);
    ASSERT_EQ(-EBUSY, rc);

    // S2: abort transaction
    rc = storage_end_transaction(aux_session_, false);
    ASSERT_EQ(0, rc);

    // S2: check file size again, it should be exp_len
    rc = storage_get_file_size(handle2, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // S2: read it again (should be exp_len)
    ReadPatternEOF(handle2, 0, blk, exp_len);
    ASSERT_FALSE(HasFatalFailure());

    // cleanup
    storage_close_file(handle1);
    storage_close_file(handle2);

    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, Transact2_Write_Write_Commit_Commit) {
    int rc;
    file_handle_t handle1;
    file_handle_t handle2;
    file_handle_t handle3;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_write_write_commit_commit";

    // open second session
    rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_);
    ASSERT_EQ(0, rc);

    // S1: open create truncate file
    rc = storage_open_file(session_, &handle1, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // S2: open the same file
    rc = storage_open_file(aux_session_, &handle2, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // S1: write (no commit)
    WritePattern(handle1, 0, exp_len, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // S2: write (no commit)
    WritePattern(handle2, 0, exp_len/2, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // S1: commit
    rc = storage_end_transaction(session_, true);
    ASSERT_EQ(0, rc);

    // S2: read/write/get/set size/delete (all should fail)
    uint32_t val = 0;
    rc = storage_read(handle2, 0, &val, sizeof(val));
    ASSERT_EQ(-EBUSY, rc);

    rc = storage_write(handle2, 0, &val, sizeof(val), 0);
    ASSERT_EQ(-EBUSY, rc);

    rc = storage_get_file_size(handle2, &fsize);
    ASSERT_EQ(-EBUSY, rc);

    rc = storage_set_file_size(handle2,  fsize, 0);
    ASSERT_EQ(-EBUSY, rc);

    rc = storage_delete_file(aux_session_, fname, 0);
    ASSERT_EQ(-EBUSY, rc);

    rc = storage_open_file(aux_session_, &handle3, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, 0);
    ASSERT_EQ(-EBUSY, rc);

    // S2: commit (should fail, and failed state should be cleared)
    rc = storage_end_transaction(aux_session_, true);
    ASSERT_EQ(-EBUSY, rc);

    // S2: check file size, it should be exp_len
    rc = storage_get_file_size(handle2, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // S2: read it again (should be exp_len)
    ReadPatternEOF(handle2, 0, blk, exp_len);
    ASSERT_FALSE(HasFatalFailure());

    // cleanup
    storage_close_file(handle1);
    storage_close_file(handle2);

    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, Transact2_Write_Write_Commit_Discard) {
    int rc;
    file_handle_t handle1;
    file_handle_t handle2;
    file_handle_t handle3;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_write_write_commit_discard";

    // open second session
    rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_);
    ASSERT_EQ(0, rc);

    // S1: open create truncate file
    rc = storage_open_file(session_, &handle1, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // S2: open the same file
    rc = storage_open_file(aux_session_, &handle2, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // S1: write (no commit)
    WritePattern(handle1, 0, exp_len, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // S2: write (no commit)
    WritePattern(handle2, 0, exp_len/2, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // S1: commit
    rc = storage_end_transaction(session_, true);
    ASSERT_EQ(0, rc);

    // S2: read/write/get/set size/delete (all should fail)
    uint32_t val = 0;
    rc = storage_read(handle2, 0, &val, sizeof(val));
    ASSERT_EQ(-EBUSY, rc);

    rc = storage_write(handle2, 0, &val, sizeof(val), 0);
    ASSERT_EQ(-EBUSY, rc);

    rc = storage_get_file_size(handle2, &fsize);
    ASSERT_EQ(-EBUSY, rc);

    rc = storage_set_file_size(handle2,  fsize, 0);
    ASSERT_EQ(-EBUSY, rc);

    rc = storage_delete_file(aux_session_, fname, 0);
    ASSERT_EQ(-EBUSY, rc);

    rc = storage_open_file(aux_session_, &handle3, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, 0);
    ASSERT_EQ(-EBUSY, rc);

    // S2: discard (should fail, and failed state should be cleared)
    rc = storage_end_transaction(aux_session_, false);
    ASSERT_EQ(0, rc);

    // S2: check file size, it should be exp_len
    rc = storage_get_file_size(handle2, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len, fsize);

    // S2: read it again (should be exp_len)
    ReadPatternEOF(handle2, 0, blk, exp_len);
    ASSERT_FALSE(HasFatalFailure());

    // cleanup
    storage_close_file(handle1);
    storage_close_file(handle2);

    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}

TEST_P(StorageServiceTest, Transact2_Write_Write_Discard_Commit) {
    int rc;
    file_handle_t handle1;
    file_handle_t handle2;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_write_write_discard_commit";

    // open second session
    rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_);
    ASSERT_EQ(0, rc);

    // S1: open create truncate file
    rc = storage_open_file(session_, &handle1, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // S2: open the same file
    rc = storage_open_file(aux_session_, &handle2, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // S1: write (no commit)
    WritePattern(handle1, 0, exp_len, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // S2: write (no commit)
    WritePattern(handle2, 0, exp_len/2, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // S1: discard
    rc = storage_end_transaction(session_, false);
    ASSERT_EQ(0, rc);

    // S2: commit (should succeed)
    rc = storage_end_transaction(aux_session_, true);
    ASSERT_EQ(0, rc);

    // S2: check file size, it should be exp_len
    rc = storage_get_file_size(handle2, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)exp_len/2, fsize);

    // S2: read it again (should be exp_len)
    ReadPatternEOF(handle2, 0, blk, exp_len/2);
    ASSERT_FALSE(HasFatalFailure());

    // cleanup
    storage_close_file(handle1);
    storage_close_file(handle2);

    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}


TEST_P(StorageServiceTest, Transact2_Write_Write_Discard_Discard) {
    int rc;
    file_handle_t handle1;
    file_handle_t handle2;
    size_t blk = 2048;
    size_t exp_len = 32 * 1024;
    storage_off_t fsize = (storage_off_t)(-1);
    const char *fname = "test_transact_write_write_discard_Discard";

    // open second session
    rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_);
    ASSERT_EQ(0, rc);

    // S1: open create truncate file
    rc = storage_open_file(session_, &handle1, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // S2: open the same file
    rc = storage_open_file(aux_session_, &handle2, fname,
                           STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE,
                           STORAGE_OP_COMPLETE);
    ASSERT_EQ(0, rc);

    // S1: write (no commit)
    WritePattern(handle1, 0, exp_len, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // S2: write (no commit)
    WritePattern(handle2, 0, exp_len/2, blk, false);
    ASSERT_FALSE(HasFatalFailure());

    // S1: discard
    rc = storage_end_transaction(session_, false);
    ASSERT_EQ(0, rc);

    // S2: discard
    rc = storage_end_transaction(aux_session_, false);
    ASSERT_EQ(0, rc);

    // S2: check file size, it should be 0
    rc = storage_get_file_size(handle2, &fsize);
    ASSERT_EQ(0, rc);
    ASSERT_EQ((storage_off_t)0, fsize);

    // S2: read it again (should be 0)
    ReadPatternEOF(handle2, 0, blk, 0);
    ASSERT_FALSE(HasFatalFailure());

    // cleanup
    storage_close_file(handle1);
    storage_close_file(handle2);

    storage_delete_file(session_, fname, STORAGE_OP_COMPLETE);
}

