1 | /* |
2 | * IXWebSocketPerMessageDeflateCodec.cpp |
3 | * Author: Benjamin Sergeant |
4 | * Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved. |
5 | */ |
6 | |
7 | #include "IXWebSocketPerMessageDeflateCodec.h" |
8 | |
9 | #include "IXWebSocketPerMessageDeflateOptions.h" |
10 | #include <cassert> |
11 | #include <string.h> |
12 | |
13 | namespace |
14 | { |
15 | // The passed in size (4) is important, without it the string litteral |
16 | // is treated as a char* and the null termination (\x00) makes it |
17 | // look like an empty string. |
18 | const std::string kEmptyUncompressedBlock = std::string("\x00\x00\xff\xff" , 4); |
19 | } // namespace |
20 | |
21 | namespace ix |
22 | { |
23 | // |
24 | // Compressor |
25 | // |
26 | WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor() |
27 | { |
28 | #ifdef IXWEBSOCKET_USE_ZLIB |
29 | memset(&_deflateState, 0, sizeof(_deflateState)); |
30 | |
31 | _deflateState.zalloc = Z_NULL; |
32 | _deflateState.zfree = Z_NULL; |
33 | _deflateState.opaque = Z_NULL; |
34 | #endif |
35 | } |
36 | |
37 | WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor() |
38 | { |
39 | #ifdef IXWEBSOCKET_USE_ZLIB |
40 | deflateEnd(&_deflateState); |
41 | #endif |
42 | } |
43 | |
44 | bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits, |
45 | bool clientNoContextTakeOver) |
46 | { |
47 | #ifdef IXWEBSOCKET_USE_ZLIB |
48 | int ret = deflateInit2(&_deflateState, |
49 | Z_DEFAULT_COMPRESSION, |
50 | Z_DEFLATED, |
51 | -1 * deflateBits, |
52 | 4, // memory level 1-9 |
53 | Z_DEFAULT_STRATEGY); |
54 | |
55 | if (ret != Z_OK) return false; |
56 | |
57 | _flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH; |
58 | |
59 | return true; |
60 | #else |
61 | return false; |
62 | #endif |
63 | } |
64 | |
65 | template<typename T> |
66 | bool WebSocketPerMessageDeflateCompressor::endsWithEmptyUnCompressedBlock(const T& value) |
67 | { |
68 | if (kEmptyUncompressedBlock.size() > value.size()) return false; |
69 | auto N = value.size(); |
70 | return value[N - 1] == kEmptyUncompressedBlock[3] && |
71 | value[N - 2] == kEmptyUncompressedBlock[2] && |
72 | value[N - 3] == kEmptyUncompressedBlock[1] && |
73 | value[N - 4] == kEmptyUncompressedBlock[0]; |
74 | } |
75 | |
76 | bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in, std::string& out) |
77 | { |
78 | return compressData(in, out); |
79 | } |
80 | |
81 | bool WebSocketPerMessageDeflateCompressor::compress(const IXWebSocketSendData& in, |
82 | std::string& out) |
83 | { |
84 | return compressData(in, out); |
85 | } |
86 | |
87 | bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in, |
88 | std::vector<uint8_t>& out) |
89 | { |
90 | return compressData(in, out); |
91 | } |
92 | |
93 | bool WebSocketPerMessageDeflateCompressor::compress(const std::vector<uint8_t>& in, |
94 | std::string& out) |
95 | { |
96 | return compressData(in, out); |
97 | } |
98 | |
99 | bool WebSocketPerMessageDeflateCompressor::compress(const std::vector<uint8_t>& in, |
100 | std::vector<uint8_t>& out) |
101 | { |
102 | return compressData(in, out); |
103 | } |
104 | |
105 | template<typename T, typename S> |
106 | bool WebSocketPerMessageDeflateCompressor::compressData(const T& in, S& out) |
107 | { |
108 | #ifdef IXWEBSOCKET_USE_ZLIB |
109 | // |
110 | // 7.2.1. Compression |
111 | // |
112 | // An endpoint uses the following algorithm to compress a message. |
113 | // |
114 | // 1. Compress all the octets of the payload of the message using |
115 | // DEFLATE. |
116 | // |
117 | // 2. If the resulting data does not end with an empty DEFLATE block |
118 | // with no compression (the "BTYPE" bits are set to 00), append an |
119 | // empty DEFLATE block with no compression to the tail end. |
120 | // |
121 | // 3. Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end. |
122 | // After this step, the last octet of the compressed data contains |
123 | // (possibly part of) the DEFLATE header bits with the "BTYPE" bits |
124 | // set to 00. |
125 | // |
126 | size_t output; |
127 | |
128 | // Clear output |
129 | out.clear(); |
130 | |
131 | if (in.empty()) |
132 | { |
133 | // See issue #167 |
134 | // The normal buffer size should be 6 but |
135 | // we remove the 4 octets from the tail (#4) |
136 | uint8_t buf[2] = {0x02, 0x00}; |
137 | out.push_back(buf[0]); |
138 | out.push_back(buf[1]); |
139 | |
140 | return true; |
141 | } |
142 | |
143 | _deflateState.avail_in = (uInt) in.size(); |
144 | _deflateState.next_in = (Bytef*) in.data(); |
145 | |
146 | do |
147 | { |
148 | // Output to local buffer |
149 | _deflateState.avail_out = (uInt) _compressBuffer.size(); |
150 | _deflateState.next_out = &_compressBuffer.front(); |
151 | |
152 | deflate(&_deflateState, _flush); |
153 | |
154 | output = _compressBuffer.size() - _deflateState.avail_out; |
155 | |
156 | out.insert(out.end(), _compressBuffer.begin(), _compressBuffer.begin() + output); |
157 | } while (_deflateState.avail_out == 0); |
158 | |
159 | if (endsWithEmptyUnCompressedBlock(out)) |
160 | { |
161 | out.resize(out.size() - 4); |
162 | } |
163 | |
164 | return true; |
165 | #else |
166 | return false; |
167 | #endif |
168 | } |
169 | |
170 | // |
171 | // Decompressor |
172 | // |
173 | WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor() |
174 | { |
175 | #ifdef IXWEBSOCKET_USE_ZLIB |
176 | memset(&_inflateState, 0, sizeof(_inflateState)); |
177 | |
178 | _inflateState.zalloc = Z_NULL; |
179 | _inflateState.zfree = Z_NULL; |
180 | _inflateState.opaque = Z_NULL; |
181 | _inflateState.avail_in = 0; |
182 | _inflateState.next_in = Z_NULL; |
183 | #endif |
184 | } |
185 | |
186 | WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor() |
187 | { |
188 | #ifdef IXWEBSOCKET_USE_ZLIB |
189 | inflateEnd(&_inflateState); |
190 | #endif |
191 | } |
192 | |
193 | bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits, |
194 | bool clientNoContextTakeOver) |
195 | { |
196 | #ifdef IXWEBSOCKET_USE_ZLIB |
197 | int ret = inflateInit2(&_inflateState, -1 * inflateBits); |
198 | |
199 | if (ret != Z_OK) return false; |
200 | |
201 | _flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH; |
202 | |
203 | return true; |
204 | #else |
205 | return false; |
206 | #endif |
207 | } |
208 | |
209 | bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in, std::string& out) |
210 | { |
211 | #ifdef IXWEBSOCKET_USE_ZLIB |
212 | // |
213 | // 7.2.2. Decompression |
214 | // |
215 | // An endpoint uses the following algorithm to decompress a message. |
216 | // |
217 | // 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the |
218 | // payload of the message. |
219 | // |
220 | // 2. Decompress the resulting data using DEFLATE. |
221 | // |
222 | std::string inFixed(in); |
223 | inFixed += kEmptyUncompressedBlock; |
224 | |
225 | _inflateState.avail_in = (uInt) inFixed.size(); |
226 | _inflateState.next_in = (unsigned char*) (const_cast<char*>(inFixed.data())); |
227 | |
228 | // Clear output |
229 | out.clear(); |
230 | |
231 | do |
232 | { |
233 | _inflateState.avail_out = (uInt) _compressBuffer.size(); |
234 | _inflateState.next_out = &_compressBuffer.front(); |
235 | |
236 | int ret = inflate(&_inflateState, Z_SYNC_FLUSH); |
237 | |
238 | if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) |
239 | { |
240 | return false; // zlib error |
241 | } |
242 | |
243 | out.append(reinterpret_cast<char*>(&_compressBuffer.front()), |
244 | _compressBuffer.size() - _inflateState.avail_out); |
245 | } while (_inflateState.avail_out == 0); |
246 | |
247 | return true; |
248 | #else |
249 | return false; |
250 | #endif |
251 | } |
252 | } // namespace ix |
253 | |