1/*
2 * edns.c
3 *
4 * edns implementation
5 *
6 * a Net::DNS like library for C
7 *
8 * (c) NLnet Labs, 2004-2022
9 *
10 * See the file LICENSE for the license
11 */
12
13#include <ldns/ldns.h>
14
15#define LDNS_OPTIONLIST_INIT 8
16
17/*
18 * Access functions
19 * functions to get and set type checking
20 */
21
22/* read */
23size_t
24ldns_edns_get_size(const ldns_edns_option *edns)
25{
26	assert(edns != NULL);
27	return edns->_size;
28}
29
30ldns_edns_option_code
31ldns_edns_get_code(const ldns_edns_option *edns)
32{
33	assert(edns != NULL);
34	return edns->_code;
35}
36
37uint8_t *
38ldns_edns_get_data(const ldns_edns_option *edns)
39{
40	assert(edns != NULL);
41	return edns->_data;
42}
43
44ldns_buffer *
45ldns_edns_get_wireformat_buffer(const ldns_edns_option *edns)
46{
47	uint16_t option;
48	size_t size;
49	uint8_t* data;
50	ldns_buffer* buffer;
51
52	if (edns == NULL) {
53		return NULL;
54	}
55
56	option = ldns_edns_get_code(edns);
57	size = ldns_edns_get_size(edns);
58	data = ldns_edns_get_data(edns);
59
60	buffer = ldns_buffer_new(size + 4);
61
62	if (buffer == NULL) {
63		return NULL;
64	}
65
66	ldns_buffer_write_u16(buffer, option);
67	ldns_buffer_write_u16(buffer, size);
68	ldns_buffer_write(buffer, data, size);
69
70	ldns_buffer_flip(buffer);
71
72	return buffer;
73}
74
75/* write */
76static void
77ldns_edns_set_size(ldns_edns_option *edns, size_t size)
78{
79	assert(edns != NULL);
80	edns->_size = size;
81}
82
83static void
84ldns_edns_set_code(ldns_edns_option *edns, ldns_edns_option_code code)
85{
86	assert(edns != NULL);
87	edns->_code = code;
88}
89
90static void
91ldns_edns_set_data(ldns_edns_option *edns, void *data)
92{
93	/* only copy the pointer */
94	assert(edns != NULL);
95	edns->_data = data;
96}
97
98/* note: data must be allocated memory */
99ldns_edns_option *
100ldns_edns_new(ldns_edns_option_code code, size_t size, void *data)
101{
102	ldns_edns_option *edns;
103	edns = LDNS_MALLOC(ldns_edns_option);
104	if (!edns) {
105		return NULL;
106	}
107	ldns_edns_set_code(edns, code);
108	ldns_edns_set_size(edns, size);
109	ldns_edns_set_data(edns, data);
110
111	return edns;
112}
113
114ldns_edns_option *
115ldns_edns_new_from_data(ldns_edns_option_code code, size_t size, const void *data)
116{
117	ldns_edns_option *edns;
118	edns = LDNS_MALLOC(ldns_edns_option);
119	if (!edns) {
120		return NULL;
121	}
122	edns->_data = LDNS_XMALLOC(uint8_t, size);
123	if (!edns->_data) {
124		LDNS_FREE(edns);
125		return NULL;
126	}
127
128	/* set the values */
129	ldns_edns_set_code(edns, code);
130	ldns_edns_set_size(edns, size);
131	memcpy(edns->_data, data, size);
132
133	return edns;
134}
135
136ldns_edns_option *
137ldns_edns_clone(ldns_edns_option *edns)
138{
139	ldns_edns_option *new_option;
140
141	assert(edns != NULL);
142
143	new_option = ldns_edns_new_from_data(ldns_edns_get_code(edns),
144		ldns_edns_get_size(edns),
145		ldns_edns_get_data(edns));
146
147	return new_option;
148}
149
150void
151ldns_edns_deep_free(ldns_edns_option *edns)
152{
153	if (edns) {
154		if (edns->_data) {
155			LDNS_FREE(edns->_data);
156		}
157		LDNS_FREE(edns);
158	}
159}
160
161void
162ldns_edns_free(ldns_edns_option *edns)
163{
164	if (edns) {
165		LDNS_FREE(edns);
166	}
167}
168
169ldns_edns_option_list*
170ldns_edns_option_list_new()
171{
172	ldns_edns_option_list *option_list = LDNS_MALLOC(ldns_edns_option_list);
173	if(!option_list) {
174		return NULL;
175	}
176
177	option_list->_option_count = 0;
178	option_list->_option_capacity = 0;
179	option_list->_options_size = 0;
180	option_list->_options = NULL;
181	return option_list;
182}
183
184ldns_edns_option_list *
185ldns_edns_option_list_clone(ldns_edns_option_list *old_list)
186{
187	size_t i;
188	ldns_edns_option_list *new_list;
189
190	if (!old_list) {
191		return NULL;
192	}
193
194	new_list = ldns_edns_option_list_new();
195	if (!new_list) {
196		return NULL;
197	}
198
199	if (old_list->_option_count == 0) {
200		return new_list;
201	}
202
203	/* adding options also updates the total options size */
204	for (i = 0; i < old_list->_option_count; i++) {
205		ldns_edns_option *option = ldns_edns_clone(ldns_edns_option_list_get_option(old_list, i));
206		if (!ldns_edns_option_list_push(new_list, option)) {
207			ldns_edns_deep_free(option);
208			ldns_edns_option_list_deep_free(new_list);
209			return NULL;
210		}
211	}
212	return new_list;
213}
214
215void
216ldns_edns_option_list_free(ldns_edns_option_list *option_list)
217{
218	if (option_list) {
219		LDNS_FREE(option_list->_options);
220		LDNS_FREE(option_list);
221	}
222}
223
224void
225ldns_edns_option_list_deep_free(ldns_edns_option_list *option_list)
226{
227	size_t i;
228
229	if (option_list) {
230		for (i=0; i < ldns_edns_option_list_get_count(option_list); i++) {
231			ldns_edns_deep_free(ldns_edns_option_list_get_option(option_list, i));
232		}
233		ldns_edns_option_list_free(option_list);
234	}
235}
236
237size_t
238ldns_edns_option_list_get_count(const ldns_edns_option_list *option_list)
239{
240	if (option_list) {
241		return option_list->_option_count;
242	} else {
243		return 0;
244	}
245}
246
247ldns_edns_option *
248ldns_edns_option_list_get_option(const ldns_edns_option_list *option_list, size_t index)
249{
250	if (option_list && index < ldns_edns_option_list_get_count(option_list)) {
251		assert(option_list->_options[index]);
252		return option_list->_options[index];
253	} else {
254		return NULL;
255	}
256}
257
258size_t
259ldns_edns_option_list_get_options_size(const ldns_edns_option_list *option_list)
260{
261	if (option_list) {
262		return option_list->_options_size;
263	} else {
264		return 0;
265	}
266}
267
268
269ldns_edns_option *
270ldns_edns_option_list_set_option(ldns_edns_option_list *option_list,
271	ldns_edns_option *option, size_t index)
272{
273	ldns_edns_option* old;
274
275	assert(option_list != NULL);
276
277	if (index > ldns_edns_option_list_get_count(option_list)) {
278		return NULL;
279	}
280
281	if (option == NULL) {
282		return NULL;
283	}
284
285	old = ldns_edns_option_list_get_option(option_list, index);
286
287	/* shrink the total EDNS size if the old EDNS option exists */
288	if (old != NULL) {
289		option_list->_options_size -= (ldns_edns_get_size(old) + 4);
290	}
291
292	option_list->_options_size += (ldns_edns_get_size(option) + 4);
293
294	option_list->_options[index] = option;
295	return old;
296}
297
298bool
299ldns_edns_option_list_push(ldns_edns_option_list *option_list,
300	ldns_edns_option *option)
301{
302	size_t cap;
303	size_t option_count;
304
305	assert(option_list != NULL);
306
307	if (option == NULL) {
308		return false;
309	}
310
311	cap = option_list->_option_capacity;
312	option_count = ldns_edns_option_list_get_count(option_list);
313
314	/* verify we need to grow the array to fit the new option */
315	if (option_count+1 > cap) {
316		ldns_edns_option **new_list;
317
318		/* initialize the capacity if needed, otherwise grow by doubling */
319		if (cap == 0) {
320			cap = LDNS_OPTIONLIST_INIT; /* initial list size */
321		} else {
322			cap *= 2;
323		}
324
325		new_list = LDNS_XREALLOC(option_list->_options,
326			ldns_edns_option *, cap);
327
328		if (!new_list) {
329			return false;
330		}
331
332		option_list->_options = new_list;
333		option_list->_option_capacity = cap;
334	}
335
336	/* add the new option */
337	ldns_edns_option_list_set_option(option_list, option,
338		option_list->_option_count);
339	option_list->_option_count += 1;
340
341	return true;
342}
343
344ldns_edns_option *
345ldns_edns_option_list_pop(ldns_edns_option_list *option_list)
346{
347	ldns_edns_option* pop;
348	size_t count;
349	size_t cap;
350
351	assert(option_list != NULL);
352
353	cap = option_list->_option_capacity;
354	count = ldns_edns_option_list_get_count(option_list);
355
356	if (count == 0) {
357		return NULL;
358	}
359	/* get the last option from the list */
360	pop = ldns_edns_option_list_get_option(option_list, count-1);
361
362	/* shrink the array */
363	if (cap > LDNS_OPTIONLIST_INIT && count-1 <= cap/2) {
364		ldns_edns_option **new_list;
365
366		cap /= 2;
367
368		new_list = LDNS_XREALLOC(option_list->_options,
369			ldns_edns_option *, cap);
370		if (new_list) {
371			option_list->_options = new_list;
372		}
373		/* if the realloc fails, the capacity for the list remains unchanged */
374	}
375
376	/* shrink the total EDNS size of the options if the popped EDNS option exists */
377	if (pop != NULL) {
378		option_list->_options_size -= (ldns_edns_get_size(pop) + 4);
379	}
380
381	option_list->_option_count = count - 1;
382
383	return pop;
384}
385
386ldns_buffer *
387ldns_edns_option_list2wireformat_buffer(const ldns_edns_option_list *option_list)
388{
389	size_t i, list_size, options_size, option, size;
390	ldns_buffer* buffer;
391	ldns_edns_option *edns;
392	uint8_t* data = NULL;
393
394	if (!option_list) {
395		return NULL;
396	}
397
398	/* get the number of EDNS options in the list*/
399	list_size = ldns_edns_option_list_get_count(option_list);
400
401	/* create buffer the size of the total EDNS wireformat options */
402	options_size = ldns_edns_option_list_get_options_size(option_list);
403	buffer = ldns_buffer_new(options_size);
404
405	if (!buffer) {
406		return NULL;
407	}
408
409	/* write individual serialized EDNS options to final buffer*/
410	for (i = 0; i < list_size; i++) {
411		edns = ldns_edns_option_list_get_option(option_list, i);
412
413		if (edns == NULL) {
414			/* this shouldn't be possible */
415			return NULL;
416		}
417
418		option = ldns_edns_get_code(edns);
419		size = ldns_edns_get_size(edns);
420		data = ldns_edns_get_data(edns);
421
422		/* make sure the option fits */
423		if (!(ldns_buffer_available(buffer, size + 4))) {
424			ldns_buffer_free(buffer);
425			return NULL;
426		}
427
428		ldns_buffer_write_u16(buffer, option);
429		ldns_buffer_write_u16(buffer, size);
430		ldns_buffer_write(buffer, data, size);
431	}
432
433	ldns_buffer_flip(buffer);
434
435	return buffer;
436}
437