Willy Tarreau | 1be4f3d | 2017-09-21 14:35:57 +0200 | [diff] [blame] | 1 | /* |
| 2 | * HPACK compressor (RFC7541) |
| 3 | * |
| 4 | * Copyright (C) 2014-2017 Willy Tarreau <willy@haproxy.org> |
| 5 | * Copyright (C) 2017 HAProxy Technologies |
| 6 | * |
| 7 | * Permission is hereby granted, free of charge, to any person obtaining |
| 8 | * a copy of this software and associated documentation files (the |
| 9 | * "Software"), to deal in the Software without restriction, including |
| 10 | * without limitation the rights to use, copy, modify, merge, publish, |
| 11 | * distribute, sublicense, and/or sell copies of the Software, and to |
| 12 | * permit persons to whom the Software is furnished to do so, subject to |
| 13 | * the following conditions: |
| 14 | * |
| 15 | * The above copyright notice and this permission notice shall be |
| 16 | * included in all copies or substantial portions of the Software. |
| 17 | * |
| 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| 19 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
| 20 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| 21 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
| 22 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 23 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| 24 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
| 25 | * OTHER DEALINGS IN THE SOFTWARE. |
| 26 | */ |
| 27 | |
| 28 | #ifndef _COMMON_HPACK_ENC_H |
| 29 | #define _COMMON_HPACK_ENC_H |
| 30 | |
| 31 | #include <stdint.h> |
Willy Tarreau | bd5659b | 2018-12-11 07:44:19 +0100 | [diff] [blame] | 32 | #include <string.h> |
Willy Tarreau | 2df026f | 2018-12-11 09:03:07 +0100 | [diff] [blame] | 33 | #include <common/buf.h> |
Willy Tarreau | 1be4f3d | 2017-09-21 14:35:57 +0200 | [diff] [blame] | 34 | #include <common/config.h> |
Willy Tarreau | 39c80eb | 2018-12-10 18:24:19 +0100 | [diff] [blame] | 35 | #include <common/http.h> |
Willy Tarreau | 1be4f3d | 2017-09-21 14:35:57 +0200 | [diff] [blame] | 36 | #include <common/ist.h> |
| 37 | |
Willy Tarreau | 83061a8 | 2018-07-13 11:56:34 +0200 | [diff] [blame] | 38 | int hpack_encode_header(struct buffer *out, const struct ist n, |
| 39 | const struct ist v); |
Willy Tarreau | 1be4f3d | 2017-09-21 14:35:57 +0200 | [diff] [blame] | 40 | |
Willy Tarreau | bad0a38 | 2018-12-11 07:44:00 +0100 | [diff] [blame] | 41 | /* Returns the number of bytes required to encode the string length <len>. The |
| 42 | * number of usable bits is an integral multiple of 7 plus 6 for the last byte. |
| 43 | * The maximum number of bytes returned is 4 (2097279 max length). Larger values |
| 44 | * return 0. |
| 45 | */ |
| 46 | static inline int hpack_len_to_bytes(size_t len) |
| 47 | { |
| 48 | ssize_t slen = len; |
| 49 | |
| 50 | slen -= 127; |
| 51 | if (__builtin_expect(slen < 0, 1)) |
| 52 | return 1; |
| 53 | if (slen < (1 << 14)) { |
| 54 | if (__builtin_expect(slen < (1 << 7), 1)) |
| 55 | return 2; |
| 56 | else |
| 57 | return 3; |
| 58 | } |
| 59 | if (slen < (1 << 21)) |
| 60 | return 4; |
| 61 | return 0; |
| 62 | } |
| 63 | |
| 64 | /* Encodes <len> into <out>+<pos> and return the new position. The caller is |
| 65 | * responsible for checking for available room using hpack_len_to_bytes() |
| 66 | * first. |
| 67 | */ |
| 68 | static inline int hpack_encode_len(char *out, int pos, int len) |
| 69 | { |
| 70 | int code = len - 127; |
| 71 | |
| 72 | if (code < 0) { |
| 73 | out[pos++] = len; |
| 74 | } else { |
| 75 | out[pos++] = 127; |
| 76 | for (; code >= 128; code >>= 7) |
| 77 | out[pos++] = code | 128; |
| 78 | out[pos++] = code; |
| 79 | } |
| 80 | return pos; |
| 81 | } |
| 82 | |
Willy Tarreau | 30eb809 | 2018-12-11 06:16:45 +0100 | [diff] [blame] | 83 | /* Tries to encode header field index <idx> with short value <val> into the |
| 84 | * aligned buffer <out>. Returns non-zero on success, 0 on failure (buffer |
| 85 | * full). The caller is responsible for ensuring that the length of <val> is |
| 86 | * strictly lower than 127, and that <idx> is lower than 64 (static list only), |
| 87 | * and that the buffer is aligned (head==0). |
| 88 | */ |
| 89 | static inline int hpack_encode_short_idx(struct buffer *out, int idx, struct ist val) |
| 90 | { |
| 91 | if (out->data + 2 + val.len > out->size) |
| 92 | return 0; |
| 93 | |
| 94 | /* literal header field with incremental indexing */ |
| 95 | out->area[out->data++] = idx | 0x40; |
| 96 | out->area[out->data++] = val.len; |
| 97 | ist2bin(&out->area[out->data], val); |
| 98 | out->data += val.len; |
| 99 | return 1; |
| 100 | } |
| 101 | |
Willy Tarreau | bd5659b | 2018-12-11 07:44:19 +0100 | [diff] [blame] | 102 | /* Tries to encode header field index <idx> with long value <val> into the |
| 103 | * aligned buffer <out>. Returns non-zero on success, 0 on failure (buffer |
| 104 | * full). The caller is responsible for ensuring <idx> is lower than 64 (static |
| 105 | * list only), and that the buffer is aligned (head==0). |
| 106 | */ |
| 107 | static inline int hpack_encode_long_idx(struct buffer *out, int idx, struct ist val) |
| 108 | { |
| 109 | int len = out->data; |
| 110 | |
| 111 | if (!hpack_len_to_bytes(val.len) || |
| 112 | 1 + len + hpack_len_to_bytes(val.len) + val.len > out->size) |
| 113 | return 0; |
| 114 | |
| 115 | /* emit literal with indexing (7541#6.2.1) : |
| 116 | * [ 0 | 1 | Index (6+) ] |
| 117 | */ |
| 118 | out->area[len++] = idx | 0x40; |
| 119 | len = hpack_encode_len(out->area, len, val.len); |
| 120 | memcpy(out->area + len, val.ptr, val.len); |
| 121 | len += val.len; |
| 122 | |
| 123 | out->data = len; |
| 124 | return 1; |
| 125 | } |
| 126 | |
Willy Tarreau | 8895367 | 2018-12-10 18:04:42 +0100 | [diff] [blame] | 127 | /* Tries to encode a :status pseudo-header with the integer status <status> |
| 128 | * into the aligned buffer <out>. Returns non-zero on success, 0 on failure |
| 129 | * (buffer full). The caller is responsible for ensuring that the status is |
| 130 | * comprised between 100 and 999 inclusive and that the buffer is aligned. It's |
| 131 | * inlined because it's easily optimizable by the compiler. |
| 132 | */ |
| 133 | static inline int hpack_encode_int_status(struct buffer *out, unsigned int status) |
| 134 | { |
| 135 | int len = out->data; |
| 136 | int size = out->size; |
| 137 | unsigned char c = 0; |
| 138 | |
| 139 | /* try to emit a single byte code */ |
| 140 | len++; |
| 141 | if (__builtin_expect(len > size, 0)) |
| 142 | goto fail; |
| 143 | |
| 144 | c = (status <= 304) ? |
| 145 | (status <= 204) ? |
| 146 | (status == 204) ? 0x89 : |
| 147 | (status == 200) ? 0x88 : |
| 148 | 0: /* > 204 */ |
| 149 | (status == 304) ? 0x8b : |
| 150 | (status == 206) ? 0x8a : |
| 151 | 0: |
| 152 | (status <= 404) ? |
| 153 | (status == 404) ? 0x8d : |
| 154 | (status == 400) ? 0x8c : |
| 155 | 0: /* > 404 */ |
| 156 | (status == 500) ? 0x8e : |
| 157 | 0; |
| 158 | |
| 159 | if (c) |
| 160 | goto last; |
| 161 | |
| 162 | /* fall back to literal */ |
| 163 | len += 4; |
| 164 | if (__builtin_expect(len > size, 0)) |
| 165 | goto fail; |
| 166 | |
| 167 | /* basic encoding of the status code */ |
| 168 | out->area[len - 5] = 0x48; // indexed name -- name=":status" (idx 8) |
| 169 | out->area[len - 4] = 0x03; // 3 bytes status |
| 170 | out->area[len - 3] = '0' + status / 100; |
| 171 | out->area[len - 2] = '0' + status / 10 % 10; |
| 172 | c = '0' + status % 10; |
| 173 | last: |
| 174 | out->area[len - 1] = c; |
| 175 | out->data = len; |
| 176 | return 1; |
| 177 | fail: |
| 178 | return 0; |
| 179 | } |
| 180 | |
| 181 | /* Tries to encode a :status pseudo-header with the integer status <status> |
| 182 | * also represented by <str> into the aligned buffer <out>. Returns non-zero |
| 183 | * on success or 0 on failure (buffer full). The caller is responsible for |
| 184 | * ensuring that the status is comprised between 100 and 999 inclusive, that |
| 185 | * <str> contains a valid representation of the numerical value, and that the |
| 186 | * buffer is aligned. This version is preferred when the caller already knows |
| 187 | * a string representation of the status because it avoids the computation in |
| 188 | * the uncompressed case. It's inlined because it's easily optimizable. |
| 189 | */ |
| 190 | static inline int hpack_encode_str_status(struct buffer *out, unsigned int status, struct ist str) |
| 191 | { |
| 192 | /* don't try too hard, we already have the ASCII value for less common cases */ |
| 193 | if (status == 200 || status == 304) { |
| 194 | if (out->data >= out->size) |
| 195 | return 0; |
| 196 | out->area[out->data] = (status == 304) ? 0x8b : 0x88; |
| 197 | out->data++; |
| 198 | return 1; |
| 199 | } |
| 200 | return hpack_encode_short_idx(out, 8, str); // name=":status" (idx 8) |
| 201 | } |
| 202 | |
Willy Tarreau | 39c80eb | 2018-12-10 18:24:19 +0100 | [diff] [blame] | 203 | /* Tries to encode a :method pseudo-header with the method in <meth>, which |
| 204 | * also exists as a string in <str>, into the aligned buffer <out>. Returns |
| 205 | * non-zero on success or 0 on failure (buffer full). The caller is responsible |
| 206 | * for ensuring that the string matches <meth>, that it's smaller than 127 |
| 207 | * bytes, and that the buffer is aligned. If <meth> is unknown then using |
| 208 | * HTTP_METH_OTHER will lead to the string being encoded as a literal. It's |
| 209 | * inlined because it's easily optimizable. |
| 210 | */ |
| 211 | static inline int hpack_encode_method(struct buffer *out, enum http_meth_t meth, struct ist str) |
| 212 | { |
| 213 | if (out->data < out->size && meth == HTTP_METH_GET) |
| 214 | out->area[out->data++] = 0x82; // indexed field : idx[02]=(":method", "GET") |
| 215 | else if (out->data < out->size && meth == HTTP_METH_POST) |
| 216 | out->area[out->data++] = 0x83; // indexed field : idx[03]=(":method", "POST") |
| 217 | else |
| 218 | return hpack_encode_short_idx(out, 2, str); // name=":method" (idx 2) |
| 219 | return 1; |
| 220 | } |
| 221 | |
Willy Tarreau | 820b391 | 2018-12-10 19:16:20 +0100 | [diff] [blame] | 222 | /* Tries to encode a :scheme pseudo-header with the scheme in <scheme>, into |
| 223 | * the aligned buffer <out>. Returns non-zero on success or 0 on failure |
| 224 | * (buffer full). Only "http" and "https" are recognized and handled as indexed |
| 225 | * values, others are turned into short literals. The caller is responsible for |
| 226 | * ensuring that the scheme is smaller than 127 bytes, and that the buffer is |
| 227 | * aligned. Normally the compiler will detect constant strings in the comparison |
| 228 | * if the code remains inlined. |
| 229 | */ |
| 230 | static inline int hpack_encode_scheme(struct buffer *out, struct ist scheme) |
| 231 | { |
| 232 | if (out->data < out->size && isteq(scheme, ist("https"))) |
| 233 | out->area[out->data++] = 0x87; // indexed field : idx[07]=(":scheme", "https") |
| 234 | else if (out->data < out->size && isteq(scheme, ist("http"))) |
| 235 | out->area[out->data++] = 0x86; // indexed field : idx[06]=(":scheme", "http") |
| 236 | else |
| 237 | return hpack_encode_short_idx(out, 6, scheme); // name=":scheme" (idx 6) |
| 238 | return 1; |
| 239 | } |
Willy Tarreau | 39c80eb | 2018-12-10 18:24:19 +0100 | [diff] [blame] | 240 | |
Willy Tarreau | eaeeb68 | 2018-12-10 19:26:51 +0100 | [diff] [blame] | 241 | /* Tries to encode a :path pseudo-header with the path in <path>, into the |
| 242 | * aligned buffer <out>. Returns non-zero on success or 0 on failure (buffer |
| 243 | * full). The well-known values "/" and "/index.html" are recognized, and other |
| 244 | * ones are handled as literals. The caller is responsible for ensuring that |
| 245 | * the buffer is aligned. Normally the compiler will detect constant strings |
| 246 | * in the comparison if the code remains inlined. |
| 247 | */ |
| 248 | static inline int hpack_encode_path(struct buffer *out, struct ist path) |
| 249 | { |
| 250 | if (out->data < out->size && isteq(path, ist("/"))) |
| 251 | out->area[out->data++] = 0x84; // indexed field : idx[04]=(":path", "/") |
| 252 | else if (out->data < out->size && isteq(path, ist("/index.html"))) |
| 253 | out->area[out->data++] = 0x85; // indexed field : idx[05]=(":path", "/index.html") |
| 254 | else if (path.len < 127) |
| 255 | return hpack_encode_short_idx(out, 4, path); // name=":path" (idx 4) |
| 256 | else |
| 257 | return hpack_encode_long_idx(out, 4, path); // name=":path" (idx 4) |
| 258 | return 1; |
| 259 | } |
| 260 | |
| 261 | |
Willy Tarreau | 1be4f3d | 2017-09-21 14:35:57 +0200 | [diff] [blame] | 262 | #endif /* _COMMON_HPACK_ENC_H */ |