diff --git a/flatdata-cpp/include/flatdata/MemoryMappedTarFileStorage.h b/flatdata-cpp/include/flatdata/MemoryMappedTarFileStorage.h new file mode 100644 index 00000000..99147254 --- /dev/null +++ b/flatdata-cpp/include/flatdata/MemoryMappedTarFileStorage.h @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2021 HERE Europe B.V. + * See the LICENSE file in the root of this project for license details. + */ + +#pragma once + +#include "MemoryDescriptor.h" +#include "internal/TarReader.h" + +#include +#include + +#include +#include +#include + +namespace flatdata +{ +class MemoryMappedTarFileStorage +{ +public: + explicit MemoryMappedTarFileStorage( const char* tar_path ); + + MemoryDescriptor read( const char* path ) const; + +private: + boost::interprocess::mapped_region m_region; + std::map< std::string, MemoryDescriptor > m_files; +}; + +inline MemoryMappedTarFileStorage::MemoryMappedTarFileStorage( const char* tar_path ) +{ + try + { + boost::interprocess::file_mapping file( tar_path, boost::interprocess::read_only ); + boost::interprocess::mapped_region region( file, boost::interprocess::read_only ); + if ( region.get_size( ) == 0 ) + { + return; + } + + m_region = std::move( region ); + } + catch ( boost::interprocess::interprocess_exception& ) + { + return; + } + + auto files = internal::read_tar_files( tar_path ); + + for ( const auto& file : files ) + { + std::string path = file.name.substr( 0, 2 ) == "./" ? file.name.substr( 2 ) : file.name; + m_files.emplace( + std::move( path ), + MemoryDescriptor( + static_cast< const unsigned char* >( m_region.get_address( ) ) + file.offset, + std::min( file.size, m_region.get_size( ) - file.offset ) ) ); + } +} + +inline MemoryDescriptor +MemoryMappedTarFileStorage::read( const char* path ) const +{ + auto found = m_files.find( path ); + if ( found != m_files.end( ) ) + { + return found->second; + } + + return MemoryDescriptor( ); +} + +} // namespace flatdata diff --git a/flatdata-cpp/include/flatdata/TarFileResourceStorage.h b/flatdata-cpp/include/flatdata/TarFileResourceStorage.h new file mode 100644 index 00000000..1389a184 --- /dev/null +++ b/flatdata-cpp/include/flatdata/TarFileResourceStorage.h @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2021 HERE Europe B.V. + * See the LICENSE file in the root of this project for license details. + */ + +#pragma once + +#include "MemoryMappedTarFileStorage.h" +#include "ResourceStorage.h" + +#include + +#include + +namespace flatdata +{ +/** + * @brief Read-only resource storage for reading flatdata archives inside a TAR file. + */ +class TarFileResourceStorage : public ResourceStorage +{ +public: + /** + * @brief Create resource storage for a TAR file + * @param tar_path The path to the TAR file + * @param tar_path The path inside the TAR file + * @throws std::runtime_error if the reading of the TAR file fails + * @return TarFileResourceStorage or nullptr on error + */ + static std::unique_ptr< TarFileResourceStorage > create( const char* tar_path, + const char* sub_path = "" ); + + std::unique_ptr< ResourceStorage > create_directory( const char* key ) override; + std::unique_ptr< ResourceStorage > directory( const char* key ) override; + bool exists( const char* key ) override; + +protected: + std::shared_ptr< std::ostream > create_output_stream( const char* key ) override; + MemoryDescriptor read_resource( const char* key ) override; + +private: + TarFileResourceStorage( std::shared_ptr< const MemoryMappedTarFileStorage > storage, + const std::string& tar_path, + const std::string& sub_path ); + std::string get_path( const char* key ) const; + +private: + std::shared_ptr< const MemoryMappedTarFileStorage > m_storage; + std::string m_tar_path; + std::string m_sub_path; +}; + +// ------------------------------------------------------------------------------------------------- + +inline std::unique_ptr< TarFileResourceStorage > +TarFileResourceStorage::create( const char* tar_path, const char* sub_path ) +{ + std::shared_ptr< const MemoryMappedTarFileStorage > storage( + new MemoryMappedTarFileStorage( tar_path ) ); + + return std::unique_ptr< TarFileResourceStorage >( + new TarFileResourceStorage( storage, tar_path, sub_path ) ); +} + +inline std::shared_ptr< std::ostream > +TarFileResourceStorage::create_output_stream( const char* ) +{ + // Writing to TAR files is not supported + return nullptr; +} + +inline TarFileResourceStorage::TarFileResourceStorage( + std::shared_ptr< const MemoryMappedTarFileStorage > storage, + const std::string& tar_path, + const std::string& sub_path ) + : m_storage( std::move( storage ) ) + , m_tar_path( tar_path ) + , m_sub_path( sub_path ) +{ +} + +inline std::string +TarFileResourceStorage::get_path( const char* key ) const +{ + const char TAR_PATH_SEPARATOR = '/'; + + return m_sub_path.empty( ) ? std::string( key ) : m_sub_path + TAR_PATH_SEPARATOR + key; +} + +inline MemoryDescriptor +TarFileResourceStorage::read_resource( const char* key ) +{ + if ( !exists( key ) ) + { + return MemoryDescriptor( ); + } + return m_storage->read( get_path( key ).c_str( ) ); +} + +inline std::unique_ptr< ResourceStorage > +TarFileResourceStorage::create_directory( const char* key ) +{ + return directory( key ); +} + +inline std::unique_ptr< ResourceStorage > +TarFileResourceStorage::directory( const char* key ) +{ + return std::unique_ptr< TarFileResourceStorage >( + new TarFileResourceStorage( m_storage, m_tar_path, get_path( key ) ) ); +} + +inline bool +TarFileResourceStorage::exists( const char* key ) +{ + return m_storage->read( get_path( key ).c_str( ) ).data( ) != nullptr; +} + +} // namespace flatdata diff --git a/flatdata-cpp/include/flatdata/flatdata.h b/flatdata-cpp/include/flatdata/flatdata.h index 817a2809..b72032bc 100644 --- a/flatdata-cpp/include/flatdata/flatdata.h +++ b/flatdata-cpp/include/flatdata/flatdata.h @@ -23,4 +23,5 @@ #include "MultiVector.h" #include "ResourceStorage.h" #include "Struct.h" -#include "Vector.h" \ No newline at end of file +#include "TarFileResourceStorage.h" +#include "Vector.h" diff --git a/flatdata-cpp/include/flatdata/internal/Reader.h b/flatdata-cpp/include/flatdata/internal/Reader.h index 93b9a2ca..9e294331 100644 --- a/flatdata-cpp/include/flatdata/internal/Reader.h +++ b/flatdata-cpp/include/flatdata/internal/Reader.h @@ -13,6 +13,7 @@ #include #include +#include namespace flatdata { diff --git a/flatdata-cpp/include/flatdata/internal/TarReader.h b/flatdata-cpp/include/flatdata/internal/TarReader.h new file mode 100644 index 00000000..994f589b --- /dev/null +++ b/flatdata-cpp/include/flatdata/internal/TarReader.h @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2021 HERE Europe B.V. + * See the LICENSE file in the root of this project for license details. + */ + +#pragma once + +#include +#include +#include + +namespace flatdata +{ +namespace internal +{ +struct TarFileEntry +{ + std::string name; + size_t offset = 0; + size_t size = 0; +}; + +std::vector< TarFileEntry > read_tar_files( const char* tar_path ); +} // namespace internal +} // namespace flatdata diff --git a/flatdata-cpp/src/TarReader.cpp b/flatdata-cpp/src/TarReader.cpp new file mode 100644 index 00000000..ca69fbf1 --- /dev/null +++ b/flatdata-cpp/src/TarReader.cpp @@ -0,0 +1,192 @@ +/** + * Copyright (c) 2021 HERE Europe B.V. + * See the LICENSE file in the root of this project for license details. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace flatdata +{ +namespace internal +{ +namespace +{ +/* tar Header Block, from POSIX 1003.1-1990. */ +struct TarFileHeader +{ /* byte offset */ + char name[ 100 ]; /* 0 */ + char mode[ 8 ]; /* 100 */ + char uid[ 8 ]; /* 108 */ + char gid[ 8 ]; /* 116 */ + char size[ 12 ]; /* 124 */ + char mtime[ 12 ]; /* 136 */ + char chksum[ 8 ]; /* 148 */ + char typeflag; /* 156 */ + char linkname[ 100 ]; /* 157 */ + char magic[ 6 ]; /* 257 */ + char version[ 2 ]; /* 263 */ + char uname[ 32 ]; /* 265 */ + char gname[ 32 ]; /* 297 */ + char devmajor[ 8 ]; /* 329 */ + char devminor[ 8 ]; /* 337 */ + char prefix[ 155 ]; /* 345 */ + char padding[ 12 ]; /* 500 */ + /* 512 */ +}; + +/* Values used in typeflag field. */ +const char REGTYPE = '0'; /* regular file */ +const char AREGTYPE = '\0'; /* regular file */ +const char DIRTYPE = '5'; /* directory */ +const char GNUTYPE_LONGNAME = 'L'; // Identifies the *next* file on the tape as having a long name. + +uint64_t +decode_numeric_field( const char* data, size_t size ) +{ + const uint64_t MAX_VALUE = std::numeric_limits< size_t >::max( ); + + if ( *data & 0x80 ) // Highest byte set indicates base-256 encoding + { + uint64_t value = ( *data & 0x7F ); + for ( size_t idx = 1; idx < size; ++idx ) + { + if ( value > MAX_VALUE / 256 ) + { + throw std::runtime_error( "Numeric value too large" ); + } + value = value * 256 + static_cast< unsigned char >( data[ idx ] ); + } + return value; + } + + // Decode octal number + uint64_t value = 0; + for ( size_t idx = 0; idx < size && data[ idx ] != '\0'; ++idx ) + { + if ( value > MAX_VALUE / 8 ) + { + throw std::runtime_error( "Numeric value too large" ); + } + if ( data[ idx ] < '0' || data[ idx ] > '7' ) + { + throw std::runtime_error( "Unexpected character" ); + } + value = value * 8 + static_cast< unsigned char >( data[ idx ] - '0' ); + } + return value; +} + +bool +verify_checksum( const TarFileHeader& header ) +{ + TarFileHeader header_copy = header; + + memset( header_copy.chksum, ' ', sizeof( header_copy.chksum ) ); + uint32_t sum = 0; + for ( size_t idx = 0; idx < sizeof( TarFileHeader ); ++idx ) + { + sum += ( reinterpret_cast< unsigned char* >( &header_copy ) )[ idx ]; + } + + return sum == decode_numeric_field( header.chksum, sizeof( header.chksum ) ); +} +} + +std::vector< TarFileEntry > +read_tar_files( const char* tar_path ) +{ + static const size_t BLOCK_SIZE = 512; + static_assert( sizeof( TarFileHeader ) == BLOCK_SIZE, "" ); + + std::vector< TarFileEntry > files; + + std::ifstream in( tar_path, std::ios_base::binary ); + std::array< char, BLOCK_SIZE > zero_block{}; + std::vector< char > buffer; + size_t offset = 0; + std::string long_name; + + while ( !in.eof( ) ) + { + TarFileHeader header; + in.read( reinterpret_cast< char* >( &header ), BLOCK_SIZE ); + if ( in.gcount( ) == 0 ) + { + return files; + } + else if ( static_cast< size_t >( in.gcount( ) ) < BLOCK_SIZE ) + { + throw std::runtime_error( "TAR size is not multiple of 512" ); + } + offset += BLOCK_SIZE; + + if ( std::memcmp( &header, &zero_block, BLOCK_SIZE ) == 0 ) + { + // Zero block indicates end of TAR + return files; + } + + if ( !verify_checksum( header ) ) + { + throw std::runtime_error( "Incorrect TAR header checksum" ); + } + + if ( header.typeflag == REGTYPE || header.typeflag == AREGTYPE ) + { + std::string file_name( header.name, strnlen( header.name, sizeof( header.name ) ) ); + if ( !long_name.empty( ) ) + { + file_name = std::move( long_name ); + long_name.clear( ); + } + + const size_t file_size = decode_numeric_field( header.size, sizeof( header.size ) ); + + TarFileEntry file; + file.name = file_name; + file.offset = offset; + file.size = file_size; + files.emplace_back( file ); + + const size_t padding = ( BLOCK_SIZE - ( file_size % BLOCK_SIZE ) ) % BLOCK_SIZE; + in.seekg( file_size + padding, std::ios_base::cur ); + offset += file_size + padding; + } + else if ( header.typeflag == DIRTYPE ) + { + // Skip directories + } + else if ( header.typeflag == GNUTYPE_LONGNAME ) + { + const size_t name_size = decode_numeric_field( header.size, sizeof( header.size ) ); + const size_t padding = ( BLOCK_SIZE - ( name_size % BLOCK_SIZE ) ) % BLOCK_SIZE; + + buffer.resize( name_size + padding, 0 ); + in.read( buffer.data( ), buffer.size( ) ); + if ( static_cast< size_t >( in.gcount( ) ) < buffer.size( ) ) + { + throw std::runtime_error( "Unexpected end of TAR file" ); + } + offset += buffer.size( ); + + long_name = std::string( buffer.data( ), strnlen( buffer.data( ), buffer.size( ) ) ); + buffer.clear( ); + } + else + { + throw std::runtime_error( "Unsupported TAR file type encountered" ); + } + } + + return files; +} +} // namespace internal +} // namespace flatdata diff --git a/flatdata-cpp/test/TarReaderTest.cpp b/flatdata-cpp/test/TarReaderTest.cpp new file mode 100644 index 00000000..56eac8fc --- /dev/null +++ b/flatdata-cpp/test/TarReaderTest.cpp @@ -0,0 +1,176 @@ +/** + * Copyright (c) 2021 HERE Europe B.V. + * See the LICENSE file in the root of this project for license details. + */ + +#include +#include +#include "catch.hpp" + +#include + +using namespace flatdata; + +boost::filesystem::path +create_test_tar_archive( ) +{ + const char TEST_ARCHIVE[ 2560 ] + = "\x65\x6D\x70\x74\x79\x2E\x73\x63\x68\x65\x6D\x61\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x30\x30\x30\x36\x36\x34\x00\x30\x30" + "\x30\x31\x37\x35\x30\x00\x30\x30\x30\x31\x37\x35\x30\x00\x30\x30\x30\x30\x30\x30\x30\x30" + "\x30\x33\x34\x00\x31\x33\x37\x37\x33\x34\x35\x33\x37\x36\x30\x00\x30\x31\x32\x31\x34\x34" + "\x00\x20\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x75\x73\x74\x61\x72\x20\x20" + "\x00\x72\x6F\x6F\x74\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x72\x6F\x6F\x74\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x6E\x61\x6D\x65\x73\x70\x61\x63\x65\x20\x6E\x7B\x0A\x61\x72\x63" + "\x68\x69\x76\x65\x20\x41\x20\x7B\x0A\x7D\x0A\x7D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2E\x2F\x2E\x2F\x40\x4C\x6F\x6E\x67\x4C" + "\x69\x6E\x6B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x30\x30\x30\x30\x36\x34\x34\x00\x30\x30\x30\x30\x30\x30\x30\x00\x30\x30\x30\x30" + "\x30\x30\x30\x00\x30\x30\x30\x30\x30\x30\x30\x30\x31\x37\x31\x00\x30\x30\x30\x30\x30\x30" + "\x30\x30\x30\x30\x30\x00\x30\x31\x31\x36\x30\x32\x00\x20\x4C\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x75\x73\x74\x61\x72\x20\x20\x00\x72\x6F\x6F\x74\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x72\x6F\x6F\x74\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4C\x6F\x72\x65" + "\x6D\x2D\x69\x70\x73\x75\x6D\x2D\x64\x6F\x6C\x6F\x72\x2D\x73\x69\x74\x2D\x61\x6D\x65\x74" + "\x2D\x63\x6F\x6E\x73\x65\x63\x74\x65\x74\x75\x72\x2D\x61\x64\x69\x70\x69\x73\x63\x69\x6E" + "\x67\x2D\x65\x6C\x69\x74\x2D\x73\x65\x64\x2D\x64\x6F\x2D\x65\x69\x75\x73\x6D\x6F\x64\x2D" + "\x74\x65\x6D\x70\x6F\x72\x2D\x69\x6E\x63\x69\x64\x69\x64\x75\x6E\x74\x2D\x75\x74\x2D\x6C" + "\x61\x62\x6F\x72\x65\x2D\x65\x74\x2D\x64\x6F\x6C\x6F\x72\x65\x2D\x6D\x61\x67\x6E\x61\x2D" + "\x61\x6C\x69\x71\x75\x61\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x4C\x6F\x72\x65\x6D\x2D\x69\x70\x73\x75\x6D\x2D\x64\x6F\x6C\x6F\x72\x2D\x73\x69" + "\x74\x2D\x61\x6D\x65\x74\x2D\x63\x6F\x6E\x73\x65\x63\x74\x65\x74\x75\x72\x2D\x61\x64\x69" + "\x70\x69\x73\x63\x69\x6E\x67\x2D\x65\x6C\x69\x74\x2D\x73\x65\x64\x2D\x64\x6F\x2D\x65\x69" + "\x75\x73\x6D\x6F\x64\x2D\x74\x65\x6D\x70\x6F\x72\x2D\x69\x6E\x63\x69\x64\x69\x64\x75\x6E" + "\x74\x2D\x75\x74\x2D\x6C\x61\x62\x6F\x72\x65\x2D\x65\x74\x30\x30\x30\x30\x36\x36\x34\x00" + "\x30\x30\x30\x31\x37\x35\x30\x00\x30\x30\x30\x31\x37\x35\x30\x00\x80\x00\x00\x00\x00\x00" + "\x00\x05\x00\x00\x00\x00\x31\x33\x37\x37\x33\x34\x35\x33\x37\x36\x30\x00\x30\x33\x32\x31" + "\x34\x31\x00\x20\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x75\x73\x74\x61\x72" + "\x20\x20\x00\x72\x6F\x6F\x74\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x72\x6F\x6F\x74\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00"; + + auto tmpfile = boost::filesystem::temp_directory_path( ) / boost::filesystem::unique_path( ); + + std::ofstream out( tmpfile.c_str( ), std::ios_base::binary ); + out.write( TEST_ARCHIVE, sizeof( TEST_ARCHIVE ) ); + out.close( ); + + return tmpfile; +} + +TEST_CASE( "Read TAR archive", "[TarReader]" ) +{ + auto tmpfile = create_test_tar_archive( ); + + auto files = internal::read_tar_files( tmpfile.c_str( ) ); + + REQUIRE( files.size( ) == 2 ); + // Test regular filename and octal filesize encoding + CHECK( files[ 0 ].name == "empty.schema" ); + CHECK( files[ 0 ].offset == 512 ); + CHECK( files[ 0 ].size == 28 ); + // Test long (>100 characters) filenames and large file sizes + CHECK( files[ 1 ].name == "Lorem-ipsum-dolor-sit-amet-consectetur-adipiscing-elit-sed-do-" + "eiusmod-tempor-incididunt-ut-labore-et-dolore-magna-aliqua" ); + CHECK( files[ 1 ].offset == 2560 ); + CHECK( files[ 1 ].size == 20ull * 1024 * 1024 * 1024 ); + + boost::filesystem::remove_all( tmpfile ); +} + +TEST_CASE( "Read file from TAR file resource storage", "[TarReader]" ) +{ + auto tmpfile = create_test_tar_archive( ); + + std::unique_ptr< ResourceStorage > storage; + REQUIRE_NOTHROW( storage = TarFileResourceStorage::create( tmpfile.c_str( ) ) ); + + const std::string schema = std::string( storage->read_schema( "empty" ).char_ptr( ) ); + CHECK( schema == "namespace n{\narchive A {\n}\n}" ); + + boost::filesystem::remove_all( tmpfile ); +}