363 changes: 363 additions & 0 deletions include/boost/iostreams/filter/zstd.hpp
@@ -0,0 +1,363 @@
// (C) Copyright Reimar Döffinger 2018.
// Based on zstd.hpp by:
// (C) Copyright Milan Svoboda 2008.
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.)

// See http://www.boost.org/libs/iostreams for documentation.

#ifndef BOOST_IOSTREAMS_ZSTD_HPP_INCLUDED
#define BOOST_IOSTREAMS_ZSTD_HPP_INCLUDED

#if defined(_MSC_VER)
# pragma once
#endif

#include <cassert>
#include <iosfwd> // streamsize.
#include <memory> // allocator, bad_alloc.
#include <new>
#include <boost/config.hpp> // MSVC, STATIC_CONSTANT, DEDUCED_TYPENAME, DINKUM.
#include <boost/detail/workaround.hpp>
#include <boost/iostreams/constants.hpp> // buffer size.
#include <boost/iostreams/detail/config/auto_link.hpp>
#include <boost/iostreams/detail/config/dyn_link.hpp>
#include <boost/iostreams/detail/config/wide_streams.hpp>
#include <boost/iostreams/detail/ios.hpp> // failure, streamsize.
#include <boost/iostreams/filter/symmetric.hpp>
#include <boost/iostreams/pipeline.hpp>
#include <boost/type_traits/is_same.hpp>

// Must come last.
#ifdef BOOST_MSVC
# pragma warning(push)
# pragma warning(disable:4251 4231 4660) // Dependencies not exported.
#endif
#include <boost/config/abi_prefix.hpp>

namespace boost { namespace iostreams {

namespace zstd {

typedef void* (*alloc_func)(void*, size_t, size_t);
typedef void (*free_func)(void*, void*);

// Compression levels

BOOST_IOSTREAMS_DECL extern const uint32_t best_speed;
BOOST_IOSTREAMS_DECL extern const uint32_t best_compression;
BOOST_IOSTREAMS_DECL extern const uint32_t default_compression;

// Status codes

BOOST_IOSTREAMS_DECL extern const int okay;
BOOST_IOSTREAMS_DECL extern const int stream_end;

// Flush codes

BOOST_IOSTREAMS_DECL extern const int finish;
BOOST_IOSTREAMS_DECL extern const int flush;
BOOST_IOSTREAMS_DECL extern const int run;

// Code for current OS

// Null pointer constant.

const int null = 0;

// Default values

} // End namespace zstd.

//
// Class name: zstd_params.
// Description: Encapsulates the parameters passed to zstddec_init
// to customize compression and decompression.
//
struct zstd_params {

// Non-explicit constructor.
zstd_params( uint32_t level = zstd::default_compression )
: level(level)
{ }
uint32_t level;
};

//
// Class name: zstd_error.
// Description: Subclass of std::ios::failure thrown to indicate
// zstd errors other than out-of-memory conditions.
//
class BOOST_IOSTREAMS_DECL zstd_error : public BOOST_IOSTREAMS_FAILURE {
public:
explicit zstd_error(size_t error);
int error() const { return error_; }
static void check BOOST_PREVENT_MACRO_SUBSTITUTION(size_t error);
private:
size_t error_;
};

namespace detail {

template<typename Alloc>
struct zstd_allocator_traits {
#ifndef BOOST_NO_STD_ALLOCATOR
#if defined(BOOST_NO_CXX11_ALLOCATOR)
typedef typename Alloc::template rebind<char>::other type;
#else
typedef typename std::allocator_traits<Alloc>::template rebind_alloc<char> type;
#endif
#else
typedef std::allocator<char> type;
#endif
};

template< typename Alloc,
typename Base = // VC6 workaround (C2516)
BOOST_DEDUCED_TYPENAME zstd_allocator_traits<Alloc>::type >
struct zstd_allocator : private Base {
private:
#if defined(BOOST_NO_CXX11_ALLOCATOR) || defined(BOOST_NO_STD_ALLOCATOR)
typedef typename Base::size_type size_type;
#else
typedef typename std::allocator_traits<Base>::size_type size_type;
#endif
public:
BOOST_STATIC_CONSTANT(bool, custom =
(!is_same<std::allocator<char>, Base>::value));
typedef typename zstd_allocator_traits<Alloc>::type allocator_type;
static void* allocate(void* self, size_t items, size_t size);
static void deallocate(void* self, void* address);
};

class BOOST_IOSTREAMS_DECL zstd_base {
public:
typedef char char_type;
protected:
zstd_base();
~zstd_base();
template<typename Alloc>
void init( const zstd_params& p,
bool compress,
zstd_allocator<Alloc>& zalloc )
{
bool custom = zstd_allocator<Alloc>::custom;
do_init( p, compress,
custom ? zstd_allocator<Alloc>::allocate : 0,
custom ? zstd_allocator<Alloc>::deallocate : 0,
&zalloc );
}
void before( const char*& src_begin, const char* src_end,
char*& dest_begin, char* dest_end );
void after( const char*& src_begin, char*& dest_begin,
bool compress );
int deflate(int action);
int inflate(int action);
void reset(bool compress, bool realloc);
private:
void do_init( const zstd_params& p, bool compress,
zstd::alloc_func,
zstd::free_func,
void* derived );
void* cstream_; // Actual type: ZSTD_CStream *
void* dstream_; // Actual type: ZSTD_DStream *
void* in_; // Actual type: ZSTD_inBuffer *
void* out_; // Actual type: ZSTD_outBuffer *
int eof_;
uint32_t level;
};

//
// Template name: zstd_compressor_impl
// Description: Model of C-Style Filter implementing compression by
// delegating to the zstd function deflate.
//
template<typename Alloc = std::allocator<char> >
class zstd_compressor_impl : public zstd_base, public zstd_allocator<Alloc> {
public:
zstd_compressor_impl(const zstd_params& = zstd::default_compression);
~zstd_compressor_impl();
bool filter( const char*& src_begin, const char* src_end,
char*& dest_begin, char* dest_end, bool flush );
void close();
};

//
// Template name: zstd_compressor_impl
// Description: Model of C-Style Filte implementing decompression by
// delegating to the zstd function inflate.
//
template<typename Alloc = std::allocator<char> >
class zstd_decompressor_impl : public zstd_base, public zstd_allocator<Alloc> {
public:
zstd_decompressor_impl(const zstd_params&);
zstd_decompressor_impl();
~zstd_decompressor_impl();
bool filter( const char*& begin_in, const char* end_in,
char*& begin_out, char* end_out, bool flush );
void close();
};

} // End namespace detail.

//
// Template name: zstd_compressor
// Description: Model of InputFilter and OutputFilter implementing
// compression using zstd.
//
template<typename Alloc = std::allocator<char> >
struct basic_zstd_compressor
: symmetric_filter<detail::zstd_compressor_impl<Alloc>, Alloc>
{
private:
typedef detail::zstd_compressor_impl<Alloc> impl_type;
typedef symmetric_filter<impl_type, Alloc> base_type;
public:
typedef typename base_type::char_type char_type;
typedef typename base_type::category category;
basic_zstd_compressor( const zstd_params& = zstd::default_compression,
std::streamsize buffer_size = default_device_buffer_size );
};
BOOST_IOSTREAMS_PIPABLE(basic_zstd_compressor, 1)

typedef basic_zstd_compressor<> zstd_compressor;

//
// Template name: zstd_decompressor
// Description: Model of InputFilter and OutputFilter implementing
// decompression using zstd.
//
template<typename Alloc = std::allocator<char> >
struct basic_zstd_decompressor
: symmetric_filter<detail::zstd_decompressor_impl<Alloc>, Alloc>
{
private:
typedef detail::zstd_decompressor_impl<Alloc> impl_type;
typedef symmetric_filter<impl_type, Alloc> base_type;
public:
typedef typename base_type::char_type char_type;
typedef typename base_type::category category;
basic_zstd_decompressor( std::streamsize buffer_size = default_device_buffer_size );
basic_zstd_decompressor( const zstd_params& p,
std::streamsize buffer_size = default_device_buffer_size );
};
BOOST_IOSTREAMS_PIPABLE(basic_zstd_decompressor, 1)

typedef basic_zstd_decompressor<> zstd_decompressor;

//----------------------------------------------------------------------------//

//------------------Implementation of zstd_allocator--------------------------//

namespace detail {

template<typename Alloc, typename Base>
void* zstd_allocator<Alloc, Base>::allocate
(void* self, size_t items, size_t size)
{
size_type len = items * size;
char* ptr =
static_cast<allocator_type*>(self)->allocate
(len + sizeof(size_type)
#if BOOST_WORKAROUND(BOOST_DINKUMWARE_STDLIB, == 1)
, (char*)0
#endif
);
*reinterpret_cast<size_type*>(ptr) = len;
return ptr + sizeof(size_type);
}

template<typename Alloc, typename Base>
void zstd_allocator<Alloc, Base>::deallocate(void* self, void* address)
{
char* ptr = reinterpret_cast<char*>(address) - sizeof(size_type);
size_type len = *reinterpret_cast<size_type*>(ptr) + sizeof(size_type);
static_cast<allocator_type*>(self)->deallocate(ptr, len);
}

//------------------Implementation of zstd_compressor_impl--------------------//

template<typename Alloc>
zstd_compressor_impl<Alloc>::zstd_compressor_impl(const zstd_params& p)
{ init(p, true, static_cast<zstd_allocator<Alloc>&>(*this)); }

template<typename Alloc>
zstd_compressor_impl<Alloc>::~zstd_compressor_impl()
{ reset(true, false); }

template<typename Alloc>
bool zstd_compressor_impl<Alloc>::filter
( const char*& src_begin, const char* src_end,
char*& dest_begin, char* dest_end, bool flush )
{
before(src_begin, src_end, dest_begin, dest_end);
int result = deflate(flush ? zstd::finish : zstd::run);
after(src_begin, dest_begin, true);
return result != zstd::stream_end;
}

template<typename Alloc>
void zstd_compressor_impl<Alloc>::close() { reset(true, true); }

//------------------Implementation of zstd_decompressor_impl------------------//

template<typename Alloc>
zstd_decompressor_impl<Alloc>::zstd_decompressor_impl(const zstd_params& p)
{ init(p, false, static_cast<zstd_allocator<Alloc>&>(*this)); }

template<typename Alloc>
zstd_decompressor_impl<Alloc>::~zstd_decompressor_impl()
{ reset(false, false); }

template<typename Alloc>
zstd_decompressor_impl<Alloc>::zstd_decompressor_impl()
{
zstd_params p;
init(p, false, static_cast<zstd_allocator<Alloc>&>(*this));
}

template<typename Alloc>
bool zstd_decompressor_impl<Alloc>::filter
( const char*& src_begin, const char* src_end,
char*& dest_begin, char* dest_end, bool flush )
{
before(src_begin, src_end, dest_begin, dest_end);
int result = inflate(flush ? zstd::finish : zstd::run);
after(src_begin, dest_begin, false);
return result != zstd::stream_end;
}

template<typename Alloc>
void zstd_decompressor_impl<Alloc>::close() { reset(false, true); }

} // End namespace detail.

//------------------Implementation of zstd_compressor-----------------------//

template<typename Alloc>
basic_zstd_compressor<Alloc>::basic_zstd_compressor
(const zstd_params& p, std::streamsize buffer_size)
: base_type(buffer_size, p) { }

//------------------Implementation of zstd_decompressor-----------------------//

template<typename Alloc>
basic_zstd_decompressor<Alloc>::basic_zstd_decompressor
(std::streamsize buffer_size)
: base_type(buffer_size) { }

template<typename Alloc>
basic_zstd_decompressor<Alloc>::basic_zstd_decompressor
(const zstd_params& p, std::streamsize buffer_size)
: base_type(buffer_size, p) { }

//----------------------------------------------------------------------------//

} } // End namespaces iostreams, boost.

#include <boost/config/abi_suffix.hpp> // Pops abi_suffix.hpp pragmas.
#ifdef BOOST_MSVC
# pragma warning(pop)
#endif

#endif // #ifndef BOOST_IOSTREAMS_ZSTD_HPP_INCLUDED
166 changes: 166 additions & 0 deletions src/zstd.cpp
@@ -0,0 +1,166 @@
// (C) Copyright Reimar Döffinger 2018.
// Based on zstd.cpp by:
// (C) Copyright Milan Svoboda 2008.
// (C) Copyright Jonathan Turkanis 2003.
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.)

// See http://www.boost.org/libs/iostreams for documentation.

// Define BOOST_IOSTREAMS_SOURCE so that <boost/iostreams/detail/config.hpp>
// knows that we are building the library (possibly exporting code), rather
// than using it (possibly importing code).
#define BOOST_IOSTREAMS_SOURCE

#include <zstd.h>

#include <boost/throw_exception.hpp>
#include <boost/iostreams/detail/config/dyn_link.hpp>
#include <boost/iostreams/filter/zstd.hpp>

namespace boost { namespace iostreams {

namespace zstd {
// Compression levels

const uint32_t best_speed = 1;
const uint32_t best_compression = 19;
const uint32_t default_compression = 3;

// Status codes

const int okay = 0;
const int stream_end = 1;

// Flush codes

const int finish = 0;
const int flush = 1;
const int run = 2;
} // End namespace zstd.

//------------------Implementation of zstd_error------------------------------//

zstd_error::zstd_error(size_t error)
: BOOST_IOSTREAMS_FAILURE(ZSTD_getErrorName(error)), error_(error)
{ }

void zstd_error::check BOOST_PREVENT_MACRO_SUBSTITUTION(size_t error)
{
if (ZSTD_isError(error))
boost::throw_exception(zstd_error(error));
}

//------------------Implementation of zstd_base-------------------------------//

namespace detail {

zstd_base::zstd_base()
: cstream_(ZSTD_createCStream()), dstream_(ZSTD_createDStream()), in_(new ZSTD_inBuffer), out_(new ZSTD_outBuffer), eof_(0)
{ }

zstd_base::~zstd_base()
{
ZSTD_freeCStream(static_cast<ZSTD_CStream *>(cstream_));
ZSTD_freeDStream(static_cast<ZSTD_DStream *>(dstream_));
delete static_cast<ZSTD_inBuffer*>(in_);
delete static_cast<ZSTD_outBuffer*>(out_);
}

void zstd_base::before( const char*& src_begin, const char* src_end,
char*& dest_begin, char* dest_end )
{
ZSTD_inBuffer *in = static_cast<ZSTD_inBuffer *>(in_);
ZSTD_outBuffer *out = static_cast<ZSTD_outBuffer *>(out_);
in->src = src_begin;
in->size = static_cast<size_t>(src_end - src_begin);
in->pos = 0;
out->dst = dest_begin;
out->size = static_cast<size_t>(dest_end - dest_begin);
out->pos = 0;
}

void zstd_base::after(const char*& src_begin, char*& dest_begin, bool)
{
ZSTD_inBuffer *in = static_cast<ZSTD_inBuffer *>(in_);
ZSTD_outBuffer *out = static_cast<ZSTD_outBuffer *>(out_);
src_begin = reinterpret_cast<const char*>(in->src) + in->pos;
dest_begin = reinterpret_cast<char*>(out->dst) + out->pos;
}

int zstd_base::deflate(int action)
{
ZSTD_CStream *s = static_cast<ZSTD_CStream *>(cstream_);
ZSTD_inBuffer *in = static_cast<ZSTD_inBuffer *>(in_);
ZSTD_outBuffer *out = static_cast<ZSTD_outBuffer *>(out_);
// Ignore spurious extra calls.
// Note size > 0 will trigger an error in this case.
if (eof_ && in->size == 0) return zstd::stream_end;
size_t result = ZSTD_compressStream(s, out, in);
zstd_error::check BOOST_PREVENT_MACRO_SUBSTITUTION(result);
if (action != zstd::run)
{
result = action == zstd::finish ? ZSTD_endStream(s, out) : ZSTD_flushStream(s, out);
zstd_error::check BOOST_PREVENT_MACRO_SUBSTITUTION(result);
eof_ = action == zstd::finish && result == 0;
return result == 0 ? zstd::stream_end : zstd::okay;
}
return zstd::okay;
}

int zstd_base::inflate(int action)
{
ZSTD_DStream *s = static_cast<ZSTD_DStream *>(dstream_);
ZSTD_inBuffer *in = static_cast<ZSTD_inBuffer *>(in_);
ZSTD_outBuffer *out = static_cast<ZSTD_outBuffer *>(out_);
// need loop since iostream code cannot handle short reads
do {
size_t result = ZSTD_decompressStream(s, out, in);
zstd_error::check BOOST_PREVENT_MACRO_SUBSTITUTION(result);
} while (in->pos < in->size && out->pos < out->size);
return action == zstd::finish && in->size == 0 && out->pos == 0 ? zstd::stream_end : zstd::okay;
}

void zstd_base::reset(bool compress, bool realloc)
{
ZSTD_inBuffer *in = static_cast<ZSTD_inBuffer *>(in_);
ZSTD_outBuffer *out = static_cast<ZSTD_outBuffer *>(out_);
if (realloc)
{
memset(in, 0, sizeof(*in));
memset(out, 0, sizeof(*out));
eof_ = 0;

zstd_error::check BOOST_PREVENT_MACRO_SUBSTITUTION(
compress ?
ZSTD_initCStream(static_cast<ZSTD_CStream *>(cstream_), level) :
ZSTD_initDStream(static_cast<ZSTD_DStream *>(dstream_))
);
}
}

void zstd_base::do_init
( const zstd_params& p, bool compress,
zstd::alloc_func, zstd::free_func,
void* )
{
ZSTD_inBuffer *in = static_cast<ZSTD_inBuffer *>(in_);
ZSTD_outBuffer *out = static_cast<ZSTD_outBuffer *>(out_);

memset(in, 0, sizeof(*in));
memset(out, 0, sizeof(*out));
eof_ = 0;

level = p.level;
zstd_error::check BOOST_PREVENT_MACRO_SUBSTITUTION(
compress ?
ZSTD_initCStream(static_cast<ZSTD_CStream *>(cstream_), level) :
ZSTD_initDStream(static_cast<ZSTD_DStream *>(dstream_))
);
}

} // End namespace detail.

//----------------------------------------------------------------------------//

} } // End namespaces iostreams, boost.
8 changes: 8 additions & 0 deletions test/Jamfile.v2
Expand Up @@ -14,6 +14,7 @@ import ac ;
local NO_BZIP2 = [ modules.peek : NO_BZIP2 ] ;
local NO_ZLIB = [ modules.peek : NO_ZLIB ] ;
local NO_LZMA = [ modules.peek : NO_LZMA ] ;
local NO_ZSTD = [ modules.peek : NO_ZSTD ] ;
local LARGE_FILE_TEMP = [ modules.peek : LARGE_FILE_TEMP ] ;
local LARGE_FILE_KEEP = [ modules.peek : LARGE_FILE_KEEP ] ;

Expand Down Expand Up @@ -161,6 +162,13 @@ rule compile-fail-iostreams ( sources * : requirements * : target-name ? ) {
lzma_test.cpp ../build//boost_iostreams :
[ ac.check-library /lzma//lzma : : <build>no ] ] ;
}
if ! $(NO_ZSTD)
{
using zstd ;
all-tests += [ test-iostreams
zstd_test.cpp ../build//boost_iostreams :
[ ac.check-library /zstd//zstd : : <build>no ] ] ;
}

test-suite "iostreams" : $(all-tests) ;

178 changes: 178 additions & 0 deletions test/zstd_test.cpp
@@ -0,0 +1,178 @@
// (C) COPYRIGHT 2018 Reimar Döffinger
// Based on zstd_test.cpp by:
// (C) COPYRIGHT 2017 ARM Limited
// (C) Copyright 2008 CodeRage, LLC (turkanis at coderage dot com)
// (C) Copyright 2004-2007 Jonathan Turkanis
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.)

// See http://www.boost.org/libs/iostreams for documentation.

// Note: basically a copy-paste of the gzip test

#include <cstddef>
#include <string>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/filter/zstd.hpp>
#include <boost/iostreams/filter/test.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/ref.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/test/test_tools.hpp>
#include <boost/test/unit_test.hpp>
#include "detail/sequence.hpp"
#include "detail/verification.hpp"

using namespace boost;
using namespace boost::iostreams;
using namespace boost::iostreams::test;
namespace io = boost::iostreams;
using boost::unit_test::test_suite;

struct zstd_alloc : std::allocator<char> {
zstd_alloc() { }
zstd_alloc(const zstd_alloc& other) { }
template<typename T>
zstd_alloc(const std::allocator<T>& other) { }
};

void compression_test()
{
text_sequence data;

// Test compression and decompression with custom allocator
BOOST_CHECK(
test_filter_pair( basic_zstd_compressor<zstd_alloc>(),
basic_zstd_decompressor<zstd_alloc>(),
std::string(data.begin(), data.end()) )
);
}

void multiple_member_test()
{
text_sequence data;
std::vector<char> temp, dest;

// Write compressed data to temp, twice in succession
filtering_ostream out;
out.push(zstd_compressor());
out.push(io::back_inserter(temp));
io::copy(make_iterator_range(data), out);
out.push(io::back_inserter(temp));
io::copy(make_iterator_range(data), out);
BOOST_CHECK(std::equal(temp.begin(), temp.begin() + temp.size()/2, temp.begin() + temp.size()/2));

// Read compressed data from temp into dest
filtering_istream in;
in.push(zstd_decompressor());
in.push(array_source(&temp[0], temp.size()));
io::copy(in, io::back_inserter(dest));

// Check that dest consists of two copies of data
BOOST_REQUIRE_EQUAL(data.size() * 2, dest.size());
BOOST_CHECK(std::equal(data.begin(), data.end(), dest.begin()));
BOOST_CHECK(std::equal(data.begin(), data.end(), dest.begin() + dest.size() / 2));

dest.clear();
io::copy(
array_source(&temp[0], temp.size()),
io::compose(zstd_decompressor(), io::back_inserter(dest)));

// Check that dest consists of two copies of data
BOOST_REQUIRE_EQUAL(data.size() * 2, dest.size());
BOOST_CHECK(std::equal(data.begin(), data.end(), dest.begin()));
BOOST_CHECK(std::equal(data.begin(), data.end(), dest.begin() + dest.size() / 2));
}

void array_source_test()
{
std::string data = "simple test string.";
std::string encoded;

filtering_ostream out;
out.push(zstd_compressor());
out.push(io::back_inserter(encoded));
io::copy(make_iterator_range(data), out);

std::string res;
io::array_source src(encoded.data(),encoded.length());
io::copy(io::compose(io::zstd_decompressor(), src), io::back_inserter(res));

BOOST_CHECK_EQUAL(data, res);
}

void empty_file_test()
{
// This test is in response to https://svn.boost.org/trac/boost/ticket/5237
// The previous implementation of gzip_compressor only wrote the gzip file
// header when the first bytes of uncompressed input were processed, causing
// incorrect behavior for empty files
BOOST_CHECK(
test_filter_pair( zstd_compressor(),
zstd_decompressor(),
std::string() )
);
}

void multipart_test()
{
// This test verifies that the zstd_decompressor properly handles a file
// that consists of multiple concatenated files (matches unzstd behaviour)
static const char multipart_file[] = {
'\x28', '\xb5', '\x2f', '\xfd', '\x24', '\x15', '\x95', '\x00', '\x00', '\x50', '\x4c', '\x69',
'\x6e', '\x65', '\x20', '\x31', '\x0a', '\x32', '\x33', '\x0a', '\x02', '\x00', '\x60', '\x84',
'\xae', '\x62', '\x04', '\x19', '\xf8', '\xe1', '\x2d', '\x28', '\xb5', '\x2f', '\xfd', '\x24',
'\x15', '\x95', '\x00', '\x00', '\x50', '\x4c', '\x69', '\x6e', '\x65', '\x20', '\x34', '\x0a',
'\x35', '\x36', '\x0a', '\x02', '\x00', '\x60', '\x84', '\xae', '\x62', '\x04', '\x5f', '\xcf',
'\xd5', '\xb8', '\x28', '\xb5', '\x2f', '\xfd', '\x24', '\x00', '\x01', '\x00', '\x00', '\x99',
'\xe9', '\xd8', '\x51', '\x28', '\xb5', '\x2f', '\xfd', '\x24', '\x15', '\x95', '\x00', '\x00',
'\x50', '\x4c', '\x69', '\x6e', '\x65', '\x20', '\x37', '\x0a', '\x38', '\x39', '\x0a', '\x02',
'\x00', '\x60', '\x84', '\xae', '\x62', '\x04', '\x94', '\x13', '\xdb', '\xae'
};

filtering_istream in;
std::string line;

in.push(zstd_decompressor());
in.push(io::array_source(multipart_file, sizeof(multipart_file)));

// First part
std::getline(in, line);
BOOST_CHECK_EQUAL("Line 1", line);
std::getline(in, line);
BOOST_CHECK_EQUAL("Line 2", line);
std::getline(in, line);
BOOST_CHECK_EQUAL("Line 3", line);

// Second part immediately follows
std::getline(in, line);
BOOST_CHECK_EQUAL("Line 4", line);
std::getline(in, line);
BOOST_CHECK_EQUAL("Line 5", line);
std::getline(in, line);
BOOST_CHECK_EQUAL("Line 6", line);

// Then an empty part, followed by one last 3-line part.
std::getline(in, line);
BOOST_CHECK_EQUAL("Line 7", line);
std::getline(in, line);
BOOST_CHECK_EQUAL("Line 8", line);
std::getline(in, line);
BOOST_CHECK_EQUAL("Line 9", line);

// Check for zstd errors too.
BOOST_CHECK(!in.bad());
}

test_suite* init_unit_test_suite(int, char* [])
{
test_suite* test = BOOST_TEST_SUITE("zstd test");
test->add(BOOST_TEST_CASE(&compression_test));
test->add(BOOST_TEST_CASE(&multiple_member_test));
test->add(BOOST_TEST_CASE(&array_source_test));
test->add(BOOST_TEST_CASE(&empty_file_test));
test->add(BOOST_TEST_CASE(&multipart_test));
return test;
}