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
13namespace
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
21namespace 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