1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <stdlib.h>
6 
7 #include <algorithm>
8 #include <memory>
9 
10 #include <blobfs/lz4.h>
11 #include <unittest/unittest.h>
12 
13 namespace blobfs {
14 namespace {
15 
16 // Tests the API of using an unset Compressor.
NullCompressor()17 bool NullCompressor() {
18     BEGIN_TEST;
19 
20     Compressor compressor;
21     EXPECT_FALSE(compressor.Compressing());
22     EXPECT_EQ(ZX_ERR_BUFFER_TOO_SMALL, compressor.Initialize(nullptr, 0));
23 
24     END_TEST;
25 }
26 
GenerateInput(unsigned seed,size_t size)27 std::unique_ptr<char[]> GenerateInput(unsigned seed, size_t size) {
28     std::unique_ptr<char[]> input(new char[size]);
29     for (size_t i = 0; i < size; i++) {
30         input[i] = static_cast<char>(rand_r(&seed));
31     }
32     return input;
33 }
34 
CompressionHelper(Compressor * compressor,const char * input,size_t size,size_t step,std::unique_ptr<char[]> * out_compressed)35 bool CompressionHelper(Compressor* compressor, const char* input, size_t size, size_t step,
36                        std::unique_ptr<char[]>* out_compressed) {
37     BEGIN_HELPER;
38 
39     size_t max_output = Compressor::BufferMax(size);
40     std::unique_ptr<char[]> compressed(new char[max_output]);
41     ASSERT_EQ(ZX_OK, compressor->Initialize(compressed.get(), max_output));
42     EXPECT_TRUE(compressor->Compressing());
43 
44     size_t offset = 0;
45     while (offset != size) {
46         const void* data = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(input) + offset);
47         const size_t incremental_size = std::min(step, size - offset);
48         ASSERT_EQ(ZX_OK, compressor->Update(data, incremental_size));
49         offset += incremental_size;
50     }
51     ASSERT_EQ(ZX_OK, compressor->End());
52     EXPECT_GT(compressor->Size(), 0);
53 
54     *out_compressed = std::move(compressed);
55 
56     END_HELPER;
57 }
58 
DecompressionHelper(const char * compressed,size_t compressed_size,const char * expected,size_t expected_size)59 bool DecompressionHelper(const char* compressed, size_t compressed_size,
60                          const char* expected, size_t expected_size) {
61     BEGIN_HELPER;
62     std::unique_ptr<char[]> output(new char[expected_size]);
63     size_t target_size = expected_size;
64     size_t src_size = compressed_size;
65     ASSERT_EQ(ZX_OK, Decompressor::Decompress(output.get(), &target_size, compressed, &src_size));
66     EXPECT_EQ(expected_size, target_size);
67     EXPECT_EQ(compressed_size, src_size);
68     EXPECT_EQ(0, memcmp(expected, output.get(), expected_size));
69 
70     END_HELPER;
71 }
72 
73 // Tests a contained case of compression and decompression.
74 //
75 // kSize: The Size of the input buffer.
76 // kStep: The step size of updating the compression buffer.
77 template <size_t kSize, size_t kStep>
CompressDecompressRandom()78 bool CompressDecompressRandom() {
79     BEGIN_TEST;
80 
81     static_assert(kStep <= kSize, "Step size too large");
82 
83     // Generate input.
84     std::unique_ptr<char[]> input(GenerateInput(0, kSize));
85 
86     // Compress a buffer.
87     Compressor compressor;
88     std::unique_ptr<char[]> compressed;
89     ASSERT_TRUE(CompressionHelper(&compressor, input.get(), kSize, kStep, &compressed));
90 
91     // Decompress the buffer.
92     ASSERT_TRUE(DecompressionHelper(compressed.get(), compressor.Size(), input.get(), kSize));
93 
94     END_TEST;
95 }
96 
CompressDecompressReset()97 bool CompressDecompressReset() {
98     BEGIN_TEST;
99 
100     Compressor compressor;
101 
102     size_t step = 128;
103     size_t input_size = 1024;
104     std::unique_ptr<char[]> input(GenerateInput(0, input_size));
105     std::unique_ptr<char[]> compressed;
106     ASSERT_TRUE(CompressionHelper(&compressor, input.get(), input_size, step, &compressed));
107     ASSERT_TRUE(DecompressionHelper(compressed.get(), compressor.Size(), input.get(), input_size));
108 
109     // We should be able to re-use a buffer of the same size.
110     compressor.Reset();
111     ASSERT_TRUE(CompressionHelper(&compressor, input.get(), input_size, step, &compressed));
112     ASSERT_TRUE(DecompressionHelper(compressed.get(), compressor.Size(), input.get(), input_size));
113 
114     // We should be able to re-use a buffer of a different size (larger).
115     compressor.Reset();
116     input_size = 2048;
117     input = GenerateInput(0, input_size);
118     ASSERT_TRUE(CompressionHelper(&compressor, input.get(), input_size, step, &compressed));
119     ASSERT_TRUE(DecompressionHelper(compressed.get(), compressor.Size(), input.get(), input_size));
120 
121     // We should be able to re-use a buffer of a different size (smaller).
122     compressor.Reset();
123     input_size = 512;
124     input = GenerateInput(0, input_size);
125     ASSERT_TRUE(CompressionHelper(&compressor, input.get(), input_size, step, &compressed));
126     ASSERT_TRUE(DecompressionHelper(compressed.get(), compressor.Size(), input.get(), input_size));
127 
128     END_TEST;
129 }
130 
UpdateNoData()131 bool UpdateNoData() {
132     BEGIN_TEST;
133 
134     Compressor compressor;
135     const size_t input_size = 1024;
136     std::unique_ptr<char[]> input(GenerateInput(0, input_size));
137     const size_t max_output = Compressor::BufferMax(input_size);
138     std::unique_ptr<char[]> compressed(new char[max_output]);
139     ASSERT_EQ(ZX_OK, compressor.Initialize(compressed.get(), max_output));
140 
141     // Test that using "Update(data, 0)" acts a no-op, rather than corrupting the buffer.
142     ASSERT_EQ(ZX_OK, compressor.Update(input.get(), 0));
143     ASSERT_EQ(ZX_OK, compressor.Update(input.get(), input_size));
144     ASSERT_EQ(ZX_OK, compressor.End());
145 
146     // Ensure that even with the addition of a zero-length buffer, we still decompress
147     // to the expected output.
148     ASSERT_TRUE(DecompressionHelper(compressed.get(), compressor.Size(), input.get(), input_size));
149 
150     END_TEST;
151 }
152 
153 // Tests Compressor returns an error if we try to compress more data than the buffer can hold.
BufferTooSmall()154 bool BufferTooSmall() {
155     BEGIN_TEST;
156 
157     // Pretend we're going to compress only one byte of data.
158     const size_t buf_size = Compressor::BufferMax(1);
159     std::unique_ptr<char[]> buf(new char[buf_size]);
160     Compressor compressor;
161     ASSERT_EQ(ZX_OK, compressor.Initialize(buf.get(), buf_size));
162 
163     // Create data that is just too big to fit within this buffer size.
164     size_t data_size = 0;
165     while (Compressor::BufferMax(++data_size) <= buf_size) {}
166     ASSERT_GT(data_size, 0);
167 
168     unsigned int seed = 0;
169     std::unique_ptr<char[]> data(new char[data_size]);
170 
171     for (size_t i = 0; i < data_size; i++) {
172         data[i] = static_cast<char>(rand_r(&seed));
173     }
174 
175     ASSERT_EQ(ZX_ERR_IO_DATA_INTEGRITY, compressor.Update(&data, data_size));
176     END_TEST;
177 }
178 
179 } // namespace
180 } // namespace blobfs
181 
182 BEGIN_TEST_CASE(blobfsCompressorTests)
183 RUN_TEST(blobfs::NullCompressor)
184 RUN_TEST((blobfs::CompressDecompressRandom<1 << 0, 1 << 0>))
185 RUN_TEST((blobfs::CompressDecompressRandom<1 << 1, 1 << 0>))
186 RUN_TEST((blobfs::CompressDecompressRandom<1 << 10, 1 << 5>))
187 RUN_TEST((blobfs::CompressDecompressRandom<1 << 15, 1 << 10>))
188 RUN_TEST(blobfs::CompressDecompressReset)
189 RUN_TEST(blobfs::UpdateNoData)
190 RUN_TEST(blobfs::BufferTooSmall)
191 END_TEST_CASE(blobfsCompressorTests);
192