1 //
2 // Copyright 2017 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 //    names, trademarks, service marks, or product names of the Licensor
11 //    and its affiliates, except as required to comply with Section 4(c) of
12 //    the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 //     http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 
25 #include "pxr/pxr.h"
26 
27 #include "pxr/base/tf/diagnostic.h"
28 #include "pxr/base/tf/fastCompression.h"
29 
30 // XXX: Need to isolate symbols here?
31 #include "pxrLZ4/lz4.h"
32 
33 PXR_NAMESPACE_OPEN_SCOPE
34 
35 using namespace pxr_lz4;
36 
37 size_t
GetMaxInputSize()38 TfFastCompression::GetMaxInputSize()
39 {
40     return 127 * static_cast<size_t>(LZ4_MAX_INPUT_SIZE);
41 }
42 
43 size_t
GetCompressedBufferSize(size_t inputSize)44 TfFastCompression::GetCompressedBufferSize(size_t inputSize)
45 {
46     if (inputSize > GetMaxInputSize())
47         return 0;
48 
49     // If it fits in one chunk then it's just the compress bound plus 1.
50     if (inputSize <= LZ4_MAX_INPUT_SIZE) {
51         return LZ4_compressBound(inputSize) + 1;
52     }
53     size_t nWholeChunks = inputSize / LZ4_MAX_INPUT_SIZE;
54     size_t partChunkSz = inputSize % LZ4_MAX_INPUT_SIZE;
55     size_t sz = 1 + nWholeChunks *
56         (LZ4_compressBound(LZ4_MAX_INPUT_SIZE) + sizeof(int32_t));
57     if (partChunkSz)
58         sz += LZ4_compressBound(partChunkSz) + sizeof(int32_t);
59     return sz;
60 }
61 
62 size_t
CompressToBuffer(char const * input,char * compressed,size_t inputSize)63 TfFastCompression::CompressToBuffer(
64     char const *input, char *compressed, size_t inputSize)
65 {
66     if (inputSize > GetMaxInputSize()) {
67         TF_CODING_ERROR("Attempted to compress a buffer of %zu bytes, "
68                         "more than the maximum supported %zu",
69                         inputSize, GetMaxInputSize());
70         return 0;
71     }
72 
73     // If it fits in one chunk, just do it.
74     char const * const origCompressed = compressed;
75     if (inputSize <= LZ4_MAX_INPUT_SIZE) {
76         compressed[0] = 0; // < zero byte means one chunk.
77         compressed += 1 + LZ4_compress_default(
78             input, compressed + 1, inputSize,
79             GetCompressedBufferSize(inputSize));
80     } else {
81         size_t nWholeChunks = inputSize / LZ4_MAX_INPUT_SIZE;
82         size_t partChunkSz = inputSize % LZ4_MAX_INPUT_SIZE;
83         *compressed++ = nWholeChunks + (partChunkSz ? 1 : 0);
84         auto writeChunk = [](char const *&input, char *&output, size_t size) {
85             char *o = output;
86             output += sizeof(int32_t);
87             int32_t n = LZ4_compress_default(
88                 input, output, size, LZ4_compressBound(size));
89             memcpy(o, &n, sizeof(n));
90             output += n;
91             input += size;
92         };
93         for (size_t chunk = 0; chunk != nWholeChunks; ++chunk) {
94             writeChunk(input, compressed, LZ4_MAX_INPUT_SIZE);
95         }
96         if (partChunkSz) {
97             writeChunk(input, compressed, partChunkSz);
98         }
99     }
100 
101     return compressed - origCompressed;
102 }
103 
104 size_t
DecompressFromBuffer(char const * compressed,char * output,size_t compressedSize,size_t maxOutputSize)105 TfFastCompression::DecompressFromBuffer(
106     char const *compressed, char *output,
107     size_t compressedSize, size_t maxOutputSize)
108 {
109     // Check first byte for # chunks.
110     int nChunks = *compressed++;
111     if (nChunks == 0) {
112         // Just one.
113         int nDecompressed = LZ4_decompress_safe(
114             compressed, output, compressedSize-1, maxOutputSize);
115         if (nDecompressed < 0) {
116             TF_RUNTIME_ERROR("Failed to decompress data, possibly corrupt? "
117                              "LZ4 error code: %d", nDecompressed);
118             return 0;
119         }
120         return nDecompressed;
121     } else {
122         // Do each chunk.
123         size_t totalDecompressed = 0;
124         for (int i = 0; i != nChunks; ++i) {
125             int32_t chunkSize = 0;
126             memcpy(&chunkSize, compressed, sizeof(chunkSize));
127             compressed += sizeof(chunkSize);
128             int nDecompressed = LZ4_decompress_safe(
129                 compressed, output, chunkSize,
130                 std::min<size_t>(LZ4_MAX_INPUT_SIZE, maxOutputSize));
131             if (nDecompressed < 0) {
132                 TF_RUNTIME_ERROR("Failed to decompress data, possibly corrupt? "
133                                  "LZ4 error code: %d", nDecompressed);
134                 return 0;
135             }
136             compressed += chunkSize;
137             output += nDecompressed;
138             maxOutputSize -= nDecompressed;
139             totalDecompressed += nDecompressed;
140         }
141         return totalDecompressed;
142     }
143     // unreachable.
144 }
145 
146 PXR_NAMESPACE_CLOSE_SCOPE
147