Coverage Report

Created: 2026-04-12 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/protocols/internal/encode.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: 2a61464a5cd138dabce07297168318e2f3bdfaa0 $
19
 *
20
 * Because what we need is yet *ANOTHER* serialisation scheme.
21
 *
22
 * @file protocols/internal/encode.c
23
 * @brief Functions to encode data in our internal structure.
24
 *
25
 * @copyright 2020 The FreeRADIUS server project
26
 * @copyright 2020 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
27
 */
28
#include <freeradius-devel/internal/internal.h>
29
#include <freeradius-devel/io/pair.h>
30
#include <freeradius-devel/io/test_point.h>
31
#include <freeradius-devel/util/net.h>
32
#include <freeradius-devel/util/proto.h>
33
34
35
static fr_internal_encode_ctx_t default_encode_ctx = { };
36
37
/** We use the same header for all types
38
 *
39
 */
40
41
/** Encode the value of the value pair the cursor currently points at.
42
 *
43
 * @param dbuff   data buffer to place the encoded data in
44
 * @param da_stack  da stack corresponding to the value pair
45
 * @param depth   in da_stack
46
 * @param cursor  cursor whose current value is the one to be encoded
47
 * @param encode_ctx  encoder context
48
 *
49
 * @return  either a negative number, indicating an error
50
 *    or the number of bytes used to encode the value
51
 */
52
static ssize_t internal_encode(fr_dbuff_t *dbuff,
53
             fr_da_stack_t *da_stack, unsigned int depth,
54
             fr_dcursor_t *cursor, void *encode_ctx)
55
0
{
56
0
  fr_dbuff_t      work_dbuff = FR_DBUFF(dbuff);
57
0
  fr_dbuff_marker_t   enc_field, len_field, value_field;
58
0
  fr_dbuff_t      value_dbuff;
59
0
  fr_dict_attr_t const    *da = da_stack->da[depth];
60
0
  fr_pair_t     *vp = fr_dcursor_current(cursor);
61
0
  bool        unknown = false, internal = false;
62
63
0
  ssize_t       slen;
64
0
  size_t        flen, vlen, mlen;
65
66
0
  uint8_t       buff[sizeof(uint64_t)];
67
0
  uint8_t       enc_byte = 0;
68
0
  fr_internal_encode_ctx_t  *our_encode_ctx = encode_ctx;
69
70
0
  if (!our_encode_ctx) our_encode_ctx = &default_encode_ctx;
71
72
  /*
73
   *  Silently skip name only attributes if we're writing
74
   *  to a database or cache.
75
   */
76
0
  if (!our_encode_ctx->allow_name_only && vp->da->flags.name_only) {
77
0
    fr_dcursor_next(cursor);
78
0
    return 0;
79
0
  }
80
81
0
  FR_PROTO_STACK_PRINT(da_stack, depth);
82
83
0
  fr_dbuff_marker(&enc_field, &work_dbuff);
84
85
  /*
86
   *  Advance past first encoding byte
87
   */
88
0
  FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, 0x00);
89
90
0
  switch (vp->vp_type) {
91
  /*
92
   *  Only leaf attributes can be tainted
93
   */
94
0
  case FR_TYPE_LEAF:
95
0
    if (vp->vp_tainted) enc_byte |= FR_INTERNAL_FLAG_TAINTED;
96
0
    break;
97
98
0
  default:
99
0
    break;
100
0
  }
101
102
  /*
103
   *  Need to use the second encoding byte
104
   *
105
   *  0                   1
106
   *  0 1 2 3 4 5 6 7 8 9 0
107
   *  +-+-+-+-+-+-+-+-+-+-+
108
   *  |u|i|-|-|-|-|-|e|
109
   *  +-+-+-+-+-+-+-+-+-+-+
110
   */
111
0
  if ((unknown = da->flags.is_unknown) ||
112
0
      (internal = (da->parent == fr_dict_root(fr_dict_internal())))) {
113
0
    enc_byte |= FR_INTERNAL_FLAG_EXTENDED;
114
0
    FR_DBUFF_IN_BYTES_RETURN(&work_dbuff,
115
0
           (unknown * FR_INTERNAL_FLAG_UNKNOWN) |
116
0
           (internal * FR_INTERNAL_FLAG_INTERNAL));
117
0
  }
118
119
  /*
120
   *  Encode the type and write the width of the
121
   *  integer to the encoding byte.
122
   */
123
0
  flen = fr_dbuff_in_uint64v(&work_dbuff, da->attr);
124
0
  if (flen <= 0) return flen;
125
0
  enc_byte |= ((flen - 1) << 5);
126
127
  /*
128
   *  Leave one byte in hopes that the length will fit
129
   *  so we needn't move the encoded data.
130
   */
131
0
  fr_dbuff_marker(&len_field, &work_dbuff);
132
0
  FR_DBUFF_ADVANCE_RETURN(&work_dbuff, 1);
133
134
  /*
135
   *  Create dbuff to hold encoded data--the fr_dbuff_move() done
136
   *  if the length field needs more than one byte will guard
137
   *  against insufficient space.
138
   */
139
0
  value_dbuff = FR_DBUFF_BIND_CURRENT(&work_dbuff);
140
0
  fr_dbuff_marker(&value_field, &value_dbuff);
141
142
0
  switch (da->type) {
143
0
  case FR_TYPE_LEAF:
144
0
    slen = fr_value_box_to_network(&value_dbuff, &vp->data);
145
0
    if (slen < 0) return PAIR_ENCODE_FATAL_ERROR;
146
0
    fr_dcursor_next(cursor);
147
0
    break;
148
149
  /*
150
   *  This is the vendor container.
151
   *  For RADIUS it'd be something like attr 26.
152
   *
153
   *  Inside the VSA you then have the vendor
154
   *  which is just encoded as another TLVish
155
   *  type attribute.
156
   *
157
   *  For small vendor PENs <= 255 this
158
   *  encoding is 6 bytes, the same as RADIUS.
159
   *
160
   *  For larger vendor PENs it's more bytes
161
   *  but we really don't care.
162
   */
163
0
  case FR_TYPE_VSA:
164
0
  case FR_TYPE_VENDOR:
165
166
  /*
167
   *  Children of TLVs are encoded in the context
168
   *  of the TLV.
169
   *
170
   *  STRUCTs are encoded as TLVs, because the struct
171
   *  packing only applies to the original protocol, and not
172
   *  to our internal encoding.
173
   */
174
0
  case FR_TYPE_TLV:
175
0
  case FR_TYPE_STRUCT:
176
    /*
177
     *  We've done the complete stack.
178
     *  Hopefully this TLV has some
179
     *  children to encode...
180
     */
181
0
    if (da == vp->da) {
182
0
      fr_dcursor_t  children;
183
0
      fr_pair_t *child;
184
185
0
      for (child = fr_pair_dcursor_init(&children, &vp->vp_group);
186
0
           child;
187
0
           child = fr_dcursor_current(&children)) {
188
189
0
        FR_PROTO_TRACE("encode ctx changed %s -> %s", da->name, child->da->name);
190
191
0
        fr_proto_da_stack_build_partial(da_stack, da_stack->da[depth], child->da);
192
0
        FR_PROTO_STACK_PRINT(da_stack, depth);
193
194
0
        slen = internal_encode(&value_dbuff, da_stack, depth + 1, &children, encode_ctx);
195
0
        if (slen < 0) return slen;
196
0
      }
197
0
      fr_dcursor_next(cursor);
198
0
      break;
199
0
    }
200
201
    /*
202
     *  Still encoding intermediary TLVs...
203
     */
204
0
    slen = internal_encode(&value_dbuff, da_stack, depth + 1, cursor, encode_ctx);
205
0
    if (slen < 0) return slen;
206
0
    break;
207
208
  /*
209
   *  Each child of a group encodes from the
210
   *  dictionary root to the leaf da.
211
   *
212
   *  Re-enter the encoder at the start.
213
   *  We do this, because the child may
214
   *      have a completely different da_stack.
215
   */
216
0
  case FR_TYPE_GROUP:
217
0
  {
218
0
    fr_dcursor_t  children;
219
0
    fr_pair_t *child;
220
221
0
    for (child = fr_pair_dcursor_init(&children, &vp->vp_group);
222
0
         child;
223
0
         child = fr_dcursor_current(&children)) {
224
0
          FR_PROTO_TRACE("encode ctx changed %s -> %s", da->name, child->da->name);
225
226
0
      slen = fr_internal_encode_pair(&value_dbuff, &children, encode_ctx);
227
0
      if (slen < 0) return slen;
228
0
    }
229
0
    fr_dcursor_next(cursor);
230
0
  }
231
0
    break;
232
233
0
  default:
234
0
    fr_strerror_printf("%s: Unexpected attribute type \"%s\"",
235
0
           __FUNCTION__, fr_type_to_str(da->type));
236
0
    return PAIR_ENCODE_FATAL_ERROR;
237
0
  }
238
239
  /*
240
   *  Encode the total length, and write the width
241
   *  of the integer to the encoding byte.
242
   *
243
   *  Already did length checks at the start of
244
   *  the function.
245
   */
246
0
  vlen = fr_dbuff_used(&value_dbuff);
247
0
  flen = (ssize_t) fr_nbo_from_uint64v(buff, vlen);
248
249
  /*
250
   *  Ugh, it's a long one, need to move the data.
251
   */
252
0
  if (flen > 1) {
253
0
    fr_dbuff_advance(&value_field, flen - 1);
254
0
    fr_dbuff_set_to_start(&value_dbuff);
255
0
    mlen = fr_dbuff_move(&value_field, &value_dbuff, vlen);
256
0
    if (mlen < vlen) return -(vlen - mlen);
257
0
  }
258
259
0
  FR_DBUFF_IN_MEMCPY_RETURN(&len_field, buff, flen);
260
0
  enc_byte |= ((flen - 1) << 2);
261
0
  FR_DBUFF_IN_RETURN(&enc_field, enc_byte);
262
263
0
  FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff) - vlen, "header");
264
265
0
  FR_PROTO_HEX_DUMP(fr_dbuff_start(&value_dbuff), vlen, "value %s",
266
0
        fr_type_to_str(vp->vp_type));
267
268
0
  return fr_dbuff_set(dbuff, &work_dbuff);
269
0
}
270
271
/** Encode a data structure into an internal attribute
272
 *
273
 * @param[in,out] dbuff   Where to write encoded data and how much one can write.
274
 * @param[in] cursor    Specifying attribute to encode.
275
 * @param[in] encode_ctx  Additional data such as the shared secret to use.
276
 * @return
277
 *  - >0 The number of bytes written to out.
278
 *  - 0 Nothing to encode (or attribute skipped).
279
 *  - <0 an error occurred.
280
 */
281
ssize_t fr_internal_encode_pair(fr_dbuff_t *dbuff, fr_dcursor_t *cursor, void *encode_ctx)
282
0
{
283
0
  fr_pair_t   *vp;
284
0
  fr_da_stack_t   da_stack;
285
286
0
  vp = fr_dcursor_current(cursor);
287
0
  if (!vp) return 0;
288
289
0
  fr_proto_da_stack_build(&da_stack, vp->da);
290
291
0
  return internal_encode(dbuff, &da_stack, 0, cursor, encode_ctx);
292
0
}
293
294
/** Encode a list of pairs using the internal encoder
295
 *
296
 * @param[out] dbuff    Where to write encoded data.
297
 * @param[in] list    List of attributes to encode.
298
 * @param[in] encode_ctx  Additional data to be used by the encoder.
299
 * @return
300
 *  - length of encoded data on success
301
 *  - < 0 on failure
302
 */
303
ssize_t fr_internal_encode_list(fr_dbuff_t *dbuff, fr_pair_list_t const *list, void *encode_ctx)
304
0
{
305
0
  fr_pair_t   *vp;
306
0
  fr_dcursor_t    dcursor;
307
0
  ssize_t     ret = 0, len = 0;
308
0
  fr_da_stack_t   da_stack;
309
310
0
  for (vp = fr_pair_dcursor_init(&dcursor, list);
311
0
       vp;
312
0
       vp = fr_dcursor_current(&dcursor)) {
313
0
    fr_proto_da_stack_build(&da_stack, vp->da);
314
0
    ret = internal_encode(dbuff, &da_stack, 0, &dcursor, encode_ctx);
315
0
    if (ret < 0) return ret;
316
0
    len += ret;
317
0
  }
318
319
0
  return len;
320
0
}
321
322
/*
323
 *  Test points
324
 */
325
extern fr_test_point_pair_encode_t internal_tp_encode_pair;
326
fr_test_point_pair_encode_t internal_tp_encode_pair = {
327
  .test_ctx = NULL,
328
  .func   = fr_internal_encode_pair
329
};