/src/freeradius-server/src/protocols/dhcpv6/decode.c
Line | Count | Source |
1 | | /* |
2 | | * This library is free software; you can redistribute it and/or |
3 | | * modify it under the terms of the GNU Lesser General Public |
4 | | * License as published by the Free Software Foundation; either |
5 | | * version 2.1 of the License, or (at your option) any later version. |
6 | | * |
7 | | * This library is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
10 | | * Lesser General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU Lesser General Public |
13 | | * License along with this library; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: 046738f83460ed03b8d0c27582387190bf17c710 $ |
19 | | * |
20 | | * @file protocols/dhcpv6/decode.c |
21 | | * @brief Functions to decode DHCP options. |
22 | | * |
23 | | * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org) |
24 | | * |
25 | | * @copyright 2018 The FreeRADIUS server project |
26 | | * @copyright 2018 NetworkRADIUS SARL (legal@networkradius.com) |
27 | | */ |
28 | | #include <stdint.h> |
29 | | #include <stddef.h> |
30 | | |
31 | | #include <freeradius-devel/io/test_point.h> |
32 | | #include <freeradius-devel/util/dns.h> |
33 | | #include <freeradius-devel/util/proto.h> |
34 | | #include <freeradius-devel/util/struct.h> |
35 | | |
36 | | #include "dhcpv6.h" |
37 | | #include "attrs.h" |
38 | | |
39 | | static ssize_t decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out, |
40 | | fr_dict_attr_t const *parent, |
41 | | uint8_t const *data, size_t const data_len, void *decode_ctx); |
42 | | |
43 | | static ssize_t decode_tlv_trampoline(TALLOC_CTX *ctx, fr_pair_list_t *out, |
44 | | fr_dict_attr_t const *parent, |
45 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
46 | 691 | { |
47 | 691 | return fr_pair_tlvs_from_network(ctx, out, parent, data, data_len, decode_ctx, decode_option, NULL, true); |
48 | 691 | } |
49 | | |
50 | | static ssize_t decode_value(TALLOC_CTX *ctx, fr_pair_list_t *out, |
51 | | fr_dict_attr_t const *parent, |
52 | | uint8_t const *data, size_t const data_len, void *decode_ctx); |
53 | | |
54 | | /** Handle arrays of DNS labels for fr_struct_from_network() |
55 | | * |
56 | | */ |
57 | | static ssize_t decode_value_trampoline(TALLOC_CTX *ctx, fr_pair_list_t *out, |
58 | | fr_dict_attr_t const *parent, |
59 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
60 | 165k | { |
61 | 165k | if ((parent->type == FR_TYPE_STRING) && fr_dhcpv6_flag_any_dns_label(parent)) { |
62 | 49.1k | return fr_pair_dns_labels_from_network(ctx, out, parent, data, data, data_len, NULL, false); |
63 | 49.1k | } |
64 | | |
65 | 116k | return decode_value(ctx, out, parent, data, data_len, decode_ctx); |
66 | 165k | } |
67 | | |
68 | | |
69 | | static ssize_t decode_value(TALLOC_CTX *ctx, fr_pair_list_t *out, |
70 | | fr_dict_attr_t const *parent, |
71 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
72 | 20.3k | { |
73 | 20.3k | ssize_t slen; |
74 | 20.3k | fr_pair_t *vp = NULL; |
75 | 20.3k | fr_dict_attr_t const *ref; |
76 | | |
77 | 20.3k | FR_PROTO_HEX_DUMP(data, data_len, "decode_value"); |
78 | | |
79 | 20.3k | switch (parent->type) { |
80 | 614 | case FR_TYPE_ATTR: |
81 | | /* |
82 | | * Force the length of the data to be two, |
83 | | * otherwise the "from network" call complains. |
84 | | * Because we pass in the enumv as the _parent_ |
85 | | * and not the da. The da is marked as "array", |
86 | | * but the parent is not. |
87 | | */ |
88 | 614 | if (data_len < 2) goto raw; |
89 | | |
90 | 565 | fr_assert(parent->parent->flags.is_root); |
91 | | |
92 | 565 | vp = fr_pair_afrom_da(ctx, parent); |
93 | 565 | if (!vp) return PAIR_DECODE_OOM; |
94 | 565 | PAIR_ALLOCED(vp); |
95 | | |
96 | 565 | slen = fr_value_box_from_network(vp, &vp->data, vp->vp_type, parent->parent, |
97 | 565 | &FR_DBUFF_TMP(data, 2), 2, true); |
98 | 565 | if (slen <= 0) { |
99 | 0 | TALLOC_FREE(vp); |
100 | 0 | goto raw; |
101 | 0 | } |
102 | | |
103 | 565 | vp->vp_tainted = true; |
104 | 565 | fr_pair_append(out, vp); |
105 | 565 | return 2; |
106 | | |
107 | | /* |
108 | | * Address MAY be shorter than 16 bytes. |
109 | | */ |
110 | 606 | case FR_TYPE_IPV6_PREFIX: |
111 | 606 | if (data_len == 0) { |
112 | 5.74k | raw: |
113 | 5.74k | return fr_pair_raw_from_network(ctx, out, parent, data, data_len); |
114 | | |
115 | 7 | } |
116 | | |
117 | 599 | vp = fr_pair_afrom_da(ctx, parent); |
118 | 599 | if (!vp) return PAIR_DECODE_OOM; |
119 | 599 | PAIR_ALLOCED(vp); |
120 | | |
121 | 599 | slen = fr_value_box_ipaddr_from_network(&vp->data, parent->type, parent, |
122 | 599 | data[0], data + 1, data_len - 1, |
123 | 599 | (parent->parent->type == FR_TYPE_STRUCT), true); |
124 | 599 | if (slen < 0) goto raw_free; |
125 | | |
126 | 286 | slen++; /* account for the prefix */ |
127 | 286 | break; |
128 | | |
129 | | /* |
130 | | * A bool is encoded as an empty option if it's |
131 | | * true. A bool is omitted entirely if it's |
132 | | * false. |
133 | | */ |
134 | 124 | case FR_TYPE_BOOL: |
135 | 124 | if (data_len != 0) goto raw; |
136 | 40 | vp = fr_pair_afrom_da(ctx, parent); |
137 | 40 | if (!vp) return PAIR_DECODE_OOM; |
138 | 40 | PAIR_ALLOCED(vp); |
139 | | |
140 | 40 | vp->vp_bool = true; |
141 | 40 | slen = 0; |
142 | 40 | break; |
143 | | |
144 | | /* |
145 | | * A standard 32bit integer, but unlike normal UNIX timestamps |
146 | | * starts from the 1st of January 2000. |
147 | | * |
148 | | * In the encoder we subtract 30 years to any values, so |
149 | | * here we need to add that to the time here. |
150 | | */ |
151 | 546 | case FR_TYPE_DATE: |
152 | 546 | vp = fr_pair_afrom_da(ctx, parent); |
153 | 546 | if (!vp) return PAIR_DECODE_OOM; |
154 | 546 | PAIR_ALLOCED(vp); |
155 | | |
156 | 546 | slen = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da, |
157 | 546 | &FR_DBUFF_TMP(data, data_len), data_len, true); |
158 | 546 | if (slen < 0) { |
159 | 181 | talloc_free(vp); |
160 | 181 | goto raw; |
161 | 181 | } |
162 | 365 | vp->vp_date = fr_unix_time_add(vp->vp_date, fr_time_delta_from_sec(DHCPV6_DATE_OFFSET)); |
163 | 365 | break; |
164 | | |
165 | 2.82k | case FR_TYPE_STRUCT: |
166 | 2.82k | slen = fr_struct_from_network(ctx, out, parent, data, data_len, |
167 | 2.82k | decode_ctx, decode_value_trampoline, decode_tlv_trampoline); |
168 | 2.82k | if (slen < 0) goto raw; |
169 | | |
170 | 1.55k | if (parent->flags.array) return slen; |
171 | 1.55k | return data_len; |
172 | | |
173 | 3.83k | case FR_TYPE_GROUP: |
174 | 3.83k | vp = fr_pair_afrom_da(ctx, parent); |
175 | 3.83k | if (!vp) return PAIR_DECODE_OOM; |
176 | 3.83k | PAIR_ALLOCED(vp); |
177 | | |
178 | 3.83k | ref = fr_dict_attr_ref(parent); |
179 | 3.83k | if (ref && (ref->dict != dict_dhcpv6)) { |
180 | 3.05k | fr_dict_protocol_t const *proto; |
181 | | |
182 | 3.05k | proto = fr_dict_protocol(ref->dict); |
183 | 3.05k | fr_assert(proto != NULL); |
184 | | |
185 | 3.05k | if (!proto->decode) { |
186 | 4.15k | raw_free: |
187 | 4.15k | talloc_free(vp); |
188 | 4.15k | goto raw; |
189 | 0 | } |
190 | | |
191 | 3.05k | slen = proto->decode(vp, &vp->vp_group, data, data_len); |
192 | 3.05k | if (slen < 0) goto raw_free; |
193 | | |
194 | 596 | vp->vp_tainted = true; |
195 | | |
196 | 789 | } else { |
197 | 789 | if (!ref) ref = fr_dict_root(dict_dhcpv6); |
198 | | |
199 | | /* |
200 | | * Child VPs go into the child group, not in the main parent list. BUT, we start |
201 | | * decoding attributes from the ref, and not from the group parent. |
202 | | */ |
203 | 789 | slen = fr_pair_tlvs_from_network(vp, &vp->vp_group, ref, data, data_len, decode_ctx, decode_option, NULL, false); |
204 | 789 | if (slen < 0) goto raw_free; |
205 | 789 | } |
206 | | |
207 | 724 | fr_pair_append(out, vp); |
208 | 724 | return data_len; |
209 | | |
210 | 630 | case FR_TYPE_IPV6_ADDR: |
211 | 630 | vp = fr_pair_afrom_da(ctx, parent); |
212 | 630 | if (!vp) return PAIR_DECODE_OOM; |
213 | 630 | PAIR_ALLOCED(vp); |
214 | | |
215 | 630 | slen = fr_value_box_ipaddr_from_network(&vp->data, parent->type, parent, |
216 | 630 | 128, data, data_len, |
217 | 630 | true, true); |
218 | 630 | if (slen < 0) goto raw_free; |
219 | 291 | break; |
220 | | |
221 | 11.1k | default: |
222 | 11.1k | vp = fr_pair_afrom_da(ctx, parent); |
223 | 11.1k | if (!vp) return PAIR_DECODE_OOM; |
224 | 11.1k | PAIR_ALLOCED(vp); |
225 | | |
226 | 11.1k | slen = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da, |
227 | 11.1k | &FR_DBUFF_TMP(data, data_len), data_len, true); |
228 | 11.1k | if (slen < 0) goto raw_free; |
229 | 10.7k | break; |
230 | 20.3k | } |
231 | | |
232 | | /* |
233 | | * The input is larger than the decoded value, re-do it as a raw attribute. |
234 | | */ |
235 | 11.7k | if (!parent->flags.array && ((size_t) slen < data_len)) { |
236 | 0 | talloc_free(vp); |
237 | 0 | goto raw; |
238 | 0 | } |
239 | | |
240 | 11.7k | fr_assert(vp != NULL); |
241 | | |
242 | 11.7k | vp->vp_tainted = true; |
243 | 11.7k | fr_pair_append(out, vp); |
244 | | |
245 | 11.7k | if (parent->flags.array) return slen; |
246 | | |
247 | 8.56k | return data_len; |
248 | 11.7k | } |
249 | | |
250 | | |
251 | | static ssize_t decode_vsa(TALLOC_CTX *ctx, fr_pair_list_t *out, |
252 | | fr_dict_attr_t const *parent, |
253 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
254 | 994 | { |
255 | 994 | uint32_t pen; |
256 | 994 | fr_dict_attr_t const *da; |
257 | 994 | fr_pair_t *vp; |
258 | 994 | fr_dhcpv6_decode_ctx_t *packet_ctx = decode_ctx; |
259 | | |
260 | 994 | FR_PROTO_HEX_DUMP(data, data_len, "decode_vsa"); |
261 | | |
262 | 994 | if (!fr_cond_assert_msg(parent->type == FR_TYPE_VSA, |
263 | 994 | "%s: Internal sanity check failed, attribute \"%s\" is not of type 'vsa'", |
264 | 994 | __FUNCTION__, parent->name)) return PAIR_DECODE_FATAL_ERROR; |
265 | | |
266 | | /* |
267 | | * Enterprise code plus at least one option header |
268 | | */ |
269 | 994 | if (data_len < 8) return fr_pair_raw_from_network(ctx, out, parent, data, data_len); |
270 | | |
271 | 811 | memcpy(&pen, data, sizeof(pen)); |
272 | 811 | pen = htonl(pen); |
273 | | |
274 | | /* |
275 | | * Verify that the parent (which should be a VSA) |
276 | | * contains a fake attribute representing the vendor. |
277 | | * |
278 | | * If it doesn't then this vendor is unknown, but we know |
279 | | * vendor attributes have a standard format, so we can |
280 | | * decode the data anyway. |
281 | | */ |
282 | 811 | da = fr_dict_attr_child_by_num(parent, pen); |
283 | 811 | if (!da) { |
284 | 351 | fr_dict_attr_t *n; |
285 | | |
286 | 351 | n = fr_dict_attr_unknown_vendor_afrom_num(packet_ctx->tmp_ctx, parent, pen); |
287 | 351 | if (!n) return PAIR_DECODE_OOM; |
288 | 351 | da = n; |
289 | 351 | } |
290 | | |
291 | 811 | FR_PROTO_TRACE("decode context %s -> %s", parent->name, da->name); |
292 | | |
293 | 811 | vp = fr_pair_find_by_da(out, NULL, da); |
294 | 811 | if (vp) { |
295 | 161 | return fr_pair_tlvs_from_network(vp, &vp->vp_group, da, data + 4, data_len - 4, decode_ctx, decode_option, NULL, false); |
296 | 161 | } |
297 | | |
298 | 650 | return fr_pair_tlvs_from_network(ctx, out, da, data + 4, data_len - 4, decode_ctx, decode_option, NULL, true); |
299 | 811 | } |
300 | | |
301 | | static ssize_t decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out, |
302 | | fr_dict_attr_t const *parent, |
303 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
304 | 19.7k | { |
305 | 19.7k | unsigned int option; |
306 | 19.7k | size_t len; |
307 | 19.7k | ssize_t slen; |
308 | 19.7k | fr_dict_attr_t const *da; |
309 | 19.7k | fr_dhcpv6_decode_ctx_t *packet_ctx = decode_ctx; |
310 | | |
311 | | #ifdef STATIC_ANALYZER |
312 | | if (!packet_ctx || !packet_ctx->tmp_ctx) return PAIR_DECODE_FATAL_ERROR; |
313 | | #endif |
314 | | |
315 | | /* |
316 | | * Must have at least an option header. |
317 | | */ |
318 | 19.7k | if (data_len < 4) { |
319 | 1.10k | fr_strerror_printf("%s: Insufficient data", __FUNCTION__); |
320 | 1.10k | return -(data_len); |
321 | 1.10k | } |
322 | | |
323 | 18.6k | option = DHCPV6_GET_OPTION_NUM(data); |
324 | 18.6k | len = DHCPV6_GET_OPTION_LEN(data); |
325 | 18.6k | if (len > (data_len - 4)) { |
326 | 3.51k | fr_strerror_printf("%s: Option overflows input. " |
327 | 3.51k | "Optional length must be less than %zu bytes, got %zu bytes", |
328 | 3.51k | __FUNCTION__, data_len - 4, len); |
329 | 3.51k | return PAIR_DECODE_FATAL_ERROR; |
330 | 3.51k | } |
331 | | |
332 | 15.0k | da = fr_dict_attr_child_by_num(parent, option); |
333 | 15.0k | if (!da) { |
334 | 4.21k | da = fr_dict_attr_unknown_raw_afrom_num(packet_ctx->tmp_ctx, parent, option); |
335 | 4.21k | if (!da) return PAIR_DECODE_FATAL_ERROR; |
336 | 4.21k | } |
337 | 15.0k | FR_PROTO_TRACE("decode context changed %s -> %s",da->parent->name, da->name); |
338 | | |
339 | | /* |
340 | | * Relay messages are weird, and contain complete DHCPv6 |
341 | | * packets, copied verbatim from the DHCPv6 client. |
342 | | */ |
343 | 15.0k | if (da == attr_relay_message) { |
344 | 1.38k | fr_pair_t *vp; |
345 | | |
346 | 1.38k | vp = fr_pair_afrom_da(ctx, attr_relay_message); |
347 | 1.38k | if (!vp) return PAIR_DECODE_FATAL_ERROR; |
348 | 1.38k | PAIR_ALLOCED(vp); |
349 | | |
350 | 1.38k | slen = fr_dhcpv6_decode(vp, &vp->vp_group, data + 4, len); |
351 | 1.38k | if (slen < 0) { |
352 | 885 | talloc_free(vp); |
353 | 1.81k | raw: |
354 | 1.81k | slen = fr_pair_raw_from_network(ctx, out, da, data + 4, len); |
355 | 1.81k | if (slen < 0) return slen; |
356 | 1.81k | return 4 + slen; |
357 | 1.81k | } |
358 | | |
359 | 495 | fr_pair_append(out, vp); |
360 | | |
361 | 13.7k | } else if ((da->type == FR_TYPE_STRING) && fr_dhcpv6_flag_any_dns_label(da)) { |
362 | 457 | slen = fr_pair_dns_labels_from_network(ctx, out, da, data + 4, data + 4, len, NULL, true); |
363 | 457 | if (slen < 0) return slen; |
364 | | |
365 | 13.2k | } else if (da->flags.array) { |
366 | 419 | slen = fr_pair_array_from_network(ctx, out, da, data + 4, len, decode_ctx, decode_value); |
367 | | |
368 | 12.8k | } else if (da->type == FR_TYPE_VSA) { |
369 | 994 | bool append = false; |
370 | 994 | fr_pair_t *vp; |
371 | | |
372 | 994 | vp = fr_pair_find_by_da(out, NULL, da); |
373 | 994 | if (!vp) { |
374 | 678 | vp = fr_pair_afrom_da(ctx, da); |
375 | 678 | if (!vp) return PAIR_DECODE_FATAL_ERROR; |
376 | 678 | PAIR_ALLOCED(vp); |
377 | | |
378 | 678 | append = true; |
379 | 678 | } |
380 | | |
381 | 994 | slen = decode_vsa(vp, &vp->vp_group, da, data + 4, len, decode_ctx); |
382 | 994 | if (append) { |
383 | 678 | if (slen < 0) { |
384 | 491 | TALLOC_FREE(vp); |
385 | 491 | } else { |
386 | 187 | fr_pair_append(out, vp); |
387 | 187 | } |
388 | 678 | } |
389 | | |
390 | 11.8k | } else if (da->type == FR_TYPE_TLV) { |
391 | 190 | slen = fr_pair_tlvs_from_network(ctx, out, da, data + 4, len, decode_ctx, decode_option, NULL, true); |
392 | | |
393 | 11.6k | } else { |
394 | 11.6k | slen = decode_value(ctx, out, da, data + 4, len, decode_ctx); |
395 | 11.6k | } |
396 | | |
397 | 14.2k | if (slen < 0) goto raw; |
398 | | |
399 | 13.2k | return len + 4; |
400 | 14.2k | } |
401 | | |
402 | | |
403 | | /** Create a "normal" fr_pair_t from the given data |
404 | | * |
405 | | * 0 1 2 3 |
406 | | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
407 | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
408 | | * | option-code | option-len | |
409 | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
410 | | */ |
411 | | ssize_t fr_dhcpv6_decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out, |
412 | | uint8_t const *data, size_t data_len, void *decode_ctx) |
413 | 16.6k | { |
414 | 16.6k | FR_PROTO_HEX_DUMP(data, data_len, "fr_dhcpv6_decode_pair"); |
415 | | |
416 | | /* |
417 | | * The API changes, so we just bounce directly to the |
418 | | * decode_option() function. |
419 | | * |
420 | | * All options including VSAs in DHCPv6 MUST follow the |
421 | | * standard format. |
422 | | */ |
423 | 16.6k | return decode_option(ctx, out, fr_dict_root(dict_dhcpv6), data, data_len, decode_ctx); |
424 | 16.6k | } |
425 | | |
426 | | ssize_t fr_dhcpv6_decode_foreign(TALLOC_CTX *ctx, fr_pair_list_t *out, |
427 | | uint8_t const *data, size_t data_len) |
428 | 3.06k | { |
429 | 3.06k | ssize_t slen; |
430 | 3.06k | uint8_t const *attr, *end; |
431 | | |
432 | 3.06k | fr_dhcpv6_decode_ctx_t decode_ctx = {}; |
433 | | |
434 | 3.06k | fr_assert(dict_dhcpv6 != NULL); |
435 | | |
436 | 3.06k | decode_ctx.tmp_ctx = talloc(ctx, uint8_t); |
437 | | |
438 | 3.06k | attr = data; |
439 | 3.06k | end = data + data_len; |
440 | | |
441 | 15.2k | while (attr < end) { |
442 | 14.7k | slen = fr_dhcpv6_decode_option(ctx, out, attr, (end - attr), &decode_ctx); |
443 | 14.7k | if (slen < 0) { |
444 | 2.53k | talloc_free(decode_ctx.tmp_ctx); |
445 | 2.53k | return slen; |
446 | 2.53k | } |
447 | | |
448 | | /* |
449 | | * If slen is larger than the room in the packet, |
450 | | * all kinds of bad things happen. |
451 | | */ |
452 | 12.1k | if (!fr_cond_assert(slen <= (end - attr))) { |
453 | 0 | talloc_free(decode_ctx.tmp_ctx); |
454 | 0 | return -slen - (attr - data); |
455 | 0 | } |
456 | | |
457 | 12.1k | attr += slen; |
458 | 12.1k | talloc_free_children(decode_ctx.tmp_ctx); |
459 | 12.1k | } |
460 | | |
461 | 3.06k | talloc_free(decode_ctx.tmp_ctx); |
462 | 531 | return data_len; |
463 | 3.06k | } |
464 | | |
465 | | static int decode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict, |
466 | | UNUSED fr_dict_attr_t const *root_da) |
467 | 2 | { |
468 | 2 | fr_dhcpv6_decode_ctx_t *test_ctx; |
469 | | |
470 | 2 | test_ctx = talloc_zero(ctx, fr_dhcpv6_decode_ctx_t); |
471 | 2 | if (!test_ctx) return -1; |
472 | | |
473 | 2 | test_ctx->tmp_ctx = talloc(test_ctx, uint8_t); |
474 | | |
475 | 2 | *out = test_ctx; |
476 | | |
477 | 2 | return 0; |
478 | 2 | } |
479 | | |
480 | | static ssize_t fr_dhcpv6_decode_proto(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *data, size_t data_len, UNUSED void *proto_ctx) |
481 | 0 | { |
482 | 0 | size_t packet_len = data_len; |
483 | | // fr_dhcpv6_decode_ctx_t *test_ctx = talloc_get_type_abort(proto_ctx, fr_dhcpv6_decode_ctx_t); |
484 | |
|
485 | 0 | if (!fr_dhcpv6_ok(data, packet_len, 200)) return -1; |
486 | | |
487 | 0 | return fr_dhcpv6_decode(ctx, out, data, packet_len); |
488 | 0 | } |
489 | | |
490 | | |
491 | | static ssize_t decode_pair(TALLOC_CTX *ctx, fr_pair_list_t *out, NDEBUG_UNUSED fr_dict_attr_t const *parent, |
492 | | uint8_t const *data, size_t data_len, void *decode_ctx) |
493 | 0 | { |
494 | 0 | fr_assert(parent == fr_dict_root(dict_dhcpv6)); |
495 | |
|
496 | 0 | return decode_option(ctx, out, fr_dict_root(dict_dhcpv6), data, data_len, decode_ctx); |
497 | 0 | } |
498 | | |
499 | | /* |
500 | | * Test points |
501 | | */ |
502 | | extern fr_test_point_pair_decode_t dhcpv6_tp_decode_pair; |
503 | | fr_test_point_pair_decode_t dhcpv6_tp_decode_pair = { |
504 | | .test_ctx = decode_test_ctx, |
505 | | .func = decode_pair, |
506 | | }; |
507 | | |
508 | | extern fr_test_point_proto_decode_t dhcpv6_tp_decode_proto; |
509 | | fr_test_point_proto_decode_t dhcpv6_tp_decode_proto = { |
510 | | .test_ctx = decode_test_ctx, |
511 | | .func = fr_dhcpv6_decode_proto |
512 | | }; |