summaryrefslogtreecommitdiffstats
path: root/src/qpack-enc.c
blob: 006f1f1a9749e7d2a5e7de611691e956e68605cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#include <haproxy/qpack-enc.h>

#include <haproxy/buf.h>
#include <haproxy/intops.h>

/* Returns the byte size required to encode <i> as a <prefix_size>-prefix
 * integer.
 */
static size_t qpack_get_prefix_int_size(int i, int prefix_size)
{
	int n = (1 << prefix_size) - 1;
	if (i < n) {
		return 1;
	}
	else {
		size_t result = 0;
		while (i) {
			++result;
			i >>= 7;
		}
		return 1 + result;
	}
}

/* Encode the integer <i> in the buffer <out> in a <prefix_size>-bit prefix
 * integer. The caller must ensure there is enough size in the buffer. The
 * prefix is OR-ed with <before_prefix> byte.
 *
 * Returns 0 if success else non-zero.
 */
static int qpack_encode_prefix_integer(struct buffer *out, int i,
                                       int prefix_size,
                                       unsigned char before_prefix)
{
	const int mod = (1 << prefix_size) - 1;
	BUG_ON_HOT(!prefix_size);

	if (i < mod) {
		if (b_room(out) < 1)
			return 1;

		b_putchr(out, before_prefix | i);
	}
	else {
		int to_encode = i - mod;
		const size_t sz = to_encode / mod;

		if (b_room(out) < sz)
			return 1;

		b_putchr(out, before_prefix | mod);
		while (1) {
			if (to_encode > 0x7f) {
				b_putchr(out, 0x80 | (to_encode & 0x7f));
				to_encode >>= 7;
			}
			else {
				b_putchr(out, to_encode & 0x7f);
				break;
			}
		}
	}

	return 0;
}

/* Returns 0 on success else non-zero. */
int qpack_encode_int_status(struct buffer *out, unsigned int status)
{
	int status_size, idx = 0;

	if (status < 100 || status > 999)
		return 1;

	switch (status) {
	case 103: idx = 24; break;
	case 200: idx = 25; break;
	case 304: idx = 26; break;
	case 404: idx = 27; break;
	case 503: idx = 28; break;
	case 100: idx = 63; break;
	case 204: idx = 64; break;
	case 206: idx = 65; break;
	case 302: idx = 66; break;
	case 400: idx = 67; break;
	case 403: idx = 68; break;
	case 421: idx = 69; break;
	case 425: idx = 70; break;
	case 500: idx = 71; break;

	/* status code not in QPACK static table, idx is null. */
	default: break;
	}

	if (idx) {
		/* status code present in QPACK static table
		 * -> indexed field line
		 */
		status_size = qpack_get_prefix_int_size(idx, 6);
		if (b_room(out) < status_size)
			return 1;

		qpack_encode_prefix_integer(out, idx, 6, 0xc0);
	}
	else {
		/* status code not present in QPACK static table
		 * -> literal field line with name reference
		 */
		char a, b, c;
		a = '0' + status / 100;
		status -= (status / 100 * 100);
		b = '0' + status / 10;
		status -= (status / 10 * 10);
		c = '0' + status;

		/* field name */
		if (qpack_encode_prefix_integer(out, 24, 4, 0x50))
			return 1;

		/* field value length */
		if (qpack_encode_prefix_integer(out, 3, 7, 0x00))
			return 1;

		if (b_room(out) < 3)
			return 1;

		b_putchr(out, a);
		b_putchr(out, b);
		b_putchr(out, c);
	}

	return 0;
}

/* Returns 0 on success else non-zero. */
int qpack_encode_field_section_line(struct buffer *out)
{
	char qpack_field_section[] = {
	  '\x00',   /* required insert count */
	  '\x00',   /* S + delta base */
	};

	if (b_room(out) < 2)
		return 1;

	b_putblk(out, qpack_field_section, 2);

	return 0;
}

#define QPACK_LFL_WLN_BIT  0x20 // Literal field line with literal name

/* Encode a header in literal field line with literal name.
 * Returns 0 on success else non-zero.
 */
int qpack_encode_header(struct buffer *out, const struct ist n, const struct ist v)
{
	int i;
	size_t sz = qpack_get_prefix_int_size(n.len, 3) + n.len +
	            qpack_get_prefix_int_size(v.len, 7) + v.len;

	if (sz > b_room(out))
		return 1;

	/* literal field line with literal name
	 * | 0 | 0 | 1 | N | H | . | . | . |
	 * N :(allow an intermediary to add the header in a dynamic table)
	 * H: huffman encoded
	 * name len
	 */
	qpack_encode_prefix_integer(out, n.len, 3, QPACK_LFL_WLN_BIT);
	/* name */
	for (i = 0; i < n.len; ++i)
		b_putchr(out, n.ptr[i]);

	/* | 0 | . | . | . | . | . | . | . |
	 * value len
	 */
	qpack_encode_prefix_integer(out, v.len, 7, 0x00);
	/* value */
	for (i = 0; i < v.len; ++i)
		b_putchr(out, v.ptr[i]);

	return 0;
}