1 /*
2 * nghttp3
3 *
4 * Copyright (c) 2019 nghttp3 contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 */
25 #include "qpack_encode.h"
26
27 #include <arpa/inet.h>
28
29 #include <cerrno>
30 #include <cstring>
31 #include <cassert>
32 #include <iostream>
33 #include <fstream>
34 #include <algorithm>
35 #include <array>
36 #include <iomanip>
37 #include <vector>
38
39 #include "qpack.h"
40 #include "template.h"
41 #include "util.h"
42
43 namespace nghttp3 {
44
45 extern Config config;
46
Encoder(size_t max_dtable_size,size_t max_blocked,bool immediate_ack)47 Encoder::Encoder(size_t max_dtable_size, size_t max_blocked, bool immediate_ack)
48 : mem_(nghttp3_mem_default()),
49 enc_(nullptr),
50 max_dtable_size_(max_dtable_size),
51 max_blocked_(max_blocked),
52 immediate_ack_(immediate_ack) {}
53
~Encoder()54 Encoder::~Encoder() { nghttp3_qpack_encoder_del(enc_); }
55
init()56 int Encoder::init() {
57 int rv;
58
59 rv = nghttp3_qpack_encoder_new(&enc_, mem_);
60 if (rv != 0) {
61 std::cerr << "nghttp3_qpack_encoder_new: " << nghttp3_strerror(rv)
62 << std::endl;
63 return -1;
64 }
65
66 nghttp3_qpack_encoder_set_hard_max_dtable_capacity(enc_, max_dtable_size_);
67
68 rv = nghttp3_qpack_encoder_set_max_dtable_capacity(enc_, max_dtable_size_);
69 if (rv != 0) {
70 std::cerr << "nghttp3_qpack_encoder_set_max_dtable_capacity: "
71 << nghttp3_strerror(rv) << std::endl;
72 return -1;
73 }
74
75 nghttp3_qpack_encoder_set_max_blocked_streams(enc_, max_blocked_);
76
77 return 0;
78 }
79
encode(nghttp3_buf * pbuf,nghttp3_buf * rbuf,nghttp3_buf * ebuf,int64_t stream_id,const nghttp3_nv * nva,size_t len)80 int Encoder::encode(nghttp3_buf *pbuf, nghttp3_buf *rbuf, nghttp3_buf *ebuf,
81 int64_t stream_id, const nghttp3_nv *nva, size_t len) {
82 auto rv =
83 nghttp3_qpack_encoder_encode(enc_, pbuf, rbuf, ebuf, stream_id, nva, len);
84 if (rv != 0) {
85 std::cerr << "nghttp3_qpack_encoder_encode: " << nghttp3_strerror(rv)
86 << std::endl;
87 return -1;
88 }
89 if (immediate_ack_) {
90 nghttp3_qpack_encoder_ack_everything(enc_);
91 }
92 return 0;
93 }
94
95 namespace {
write_encoder_stream(std::ostream & out,nghttp3_buf * ebuf)96 void write_encoder_stream(std::ostream &out, nghttp3_buf *ebuf) {
97 uint64_t stream_id = 0;
98 out.write(reinterpret_cast<char *>(&stream_id), sizeof(stream_id));
99 uint32_t size = htonl(nghttp3_buf_len(ebuf));
100 out.write(reinterpret_cast<char *>(&size), sizeof(size));
101 out.write(reinterpret_cast<char *>(ebuf->pos), nghttp3_buf_len(ebuf));
102 }
103 } // namespace
104
105 namespace {
write_request_stream(std::ostream & out,int64_t stream_id,nghttp3_buf * pbuf,nghttp3_buf * rbuf)106 void write_request_stream(std::ostream &out, int64_t stream_id,
107 nghttp3_buf *pbuf, nghttp3_buf *rbuf) {
108 stream_id = nghttp3_htonl64(stream_id);
109 out.write(reinterpret_cast<char *>(&stream_id), sizeof(stream_id));
110 uint32_t size = htonl(nghttp3_buf_len(pbuf) + nghttp3_buf_len(rbuf));
111 out.write(reinterpret_cast<char *>(&size), sizeof(size));
112 out.write(reinterpret_cast<char *>(pbuf->pos), nghttp3_buf_len(pbuf));
113 out.write(reinterpret_cast<char *>(rbuf->pos), nghttp3_buf_len(rbuf));
114 }
115 } // namespace
116
encode(const std::string_view & outfile,const std::string_view & infile)117 int encode(const std::string_view &outfile, const std::string_view &infile) {
118 auto in = std::ifstream(infile.data(), std::ios::binary);
119
120 if (!in) {
121 std::cerr << "Could not open file " << infile << ": " << strerror(errno)
122 << std::endl;
123 return -1;
124 }
125
126 auto out = std::ofstream(outfile.data(), std::ios::trunc | std::ios::binary);
127 if (!out) {
128 std::cerr << "Could not open file " << outfile << ": " << strerror(errno)
129 << std::endl;
130 return -1;
131 }
132
133 auto enc =
134 Encoder(config.max_dtable_size, config.max_blocked, config.immediate_ack);
135 if (enc.init() != 0) {
136 return -1;
137 }
138
139 nghttp3_buf pbuf, rbuf, ebuf;
140 nghttp3_buf_init(&pbuf);
141 nghttp3_buf_init(&rbuf);
142 nghttp3_buf_init(&ebuf);
143
144 auto mem = nghttp3_mem_default();
145 auto pbufd = defer(nghttp3_buf_free, &pbuf, mem);
146 auto rbufd = defer(nghttp3_buf_free, &rbuf, mem);
147 auto ebufd = defer(nghttp3_buf_free, &ebuf, mem);
148
149 int64_t stream_id = 1;
150 std::array<std::string, 1024> sarray;
151
152 size_t srclen = 0;
153 size_t enclen = 0;
154 size_t rslen = 0;
155 size_t eslen = 0;
156
157 for (; in;) {
158 auto nva = std::vector<nghttp3_nv>();
159 for (std::string line; std::getline(in, line);) {
160 if (line == "") {
161 break;
162 }
163
164 if (sarray.size() == nva.size()) {
165 std::cerr << "Too many headers: " << nva.size() << std::endl;
166 return -1;
167 }
168
169 sarray[nva.size()] = line;
170 const auto &s = sarray[nva.size()];
171
172 auto d = s.find('\t');
173 if (d == std::string_view::npos) {
174 std::cerr << "Could not find TAB in " << s << std::endl;
175 return -1;
176 }
177 auto name = std::string_view(s.c_str(), d);
178 auto value = std::string_view(s.c_str() + d + 1, s.size() - d - 1);
179 value.remove_prefix(std::min(value.find_first_not_of(" "), value.size()));
180
181 srclen += name.size() + value.size();
182
183 nva.emplace_back(nghttp3_nv{
184 const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(name.data())),
185 const_cast<uint8_t *>(
186 reinterpret_cast<const uint8_t *>(value.data())),
187 name.size(), value.size()});
188 }
189
190 if (nva.empty()) {
191 break;
192 }
193
194 if (auto rv =
195 enc.encode(&pbuf, &rbuf, &ebuf, stream_id, nva.data(), nva.size());
196 rv != 0) {
197 return -1;
198 }
199
200 enclen += nghttp3_buf_len(&pbuf) + nghttp3_buf_len(&rbuf) +
201 nghttp3_buf_len(&ebuf);
202
203 if (nghttp3_buf_len(&ebuf)) {
204 write_encoder_stream(out, &ebuf);
205 }
206 write_request_stream(out, stream_id, &pbuf, &rbuf);
207
208 rslen += nghttp3_buf_len(&pbuf) + nghttp3_buf_len(&rbuf);
209 eslen += nghttp3_buf_len(&ebuf);
210
211 nghttp3_buf_reset(&pbuf);
212 nghttp3_buf_reset(&rbuf);
213 nghttp3_buf_reset(&ebuf);
214
215 ++stream_id;
216 }
217
218 if (srclen == 0) {
219 std::cerr << "No header field processed" << std::endl;
220 } else {
221 std::cerr << srclen << " -> " << enclen << " (r:" << rslen
222 << " + e:" << eslen << ") " << std::fixed << std::setprecision(2)
223 << (1. - (static_cast<double>(enclen) / srclen)) * 100
224 << "% compressed" << std::endl;
225 }
226 return 0;
227 }
228
229 } // namespace nghttp3
230