]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
create and use generic decode_value() function
authorAlan T. DeKok <aland@freeradius.org>
Sat, 23 Aug 2025 11:49:37 +0000 (07:49 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Sat, 23 Aug 2025 18:24:40 +0000 (14:24 -0400)
which makes the struct decoder a little simpler.

While we're at it, rework the struct decoder to be clearer.
And decode raw values more often, instead of hoisting the raw
pair to the enclosing struct.

src/lib/util/decode.c
src/lib/util/struct.c
src/tests/unit/protocols/dhcpv4/dhcp-auth.txt
src/tests/unit/protocols/dhcpv6/rfc6355.txt

index 42d5db02ac69bfc74c5eac25f02487985ba4dbe9..032a5dd3ca1263f8f1404818769d6867704cb545 100644 (file)
@@ -88,6 +88,7 @@ ssize_t fr_pair_raw_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_a
        if (!da->parent) return -1; /* stupid static analyzers */
 #endif
 
+       FR_PROTO_TRACE("Creating Raw attribute %s", da->name);
        FR_PROTO_HEX_DUMP(data, data_len, "fr_pair_raw_from_network");
 
        /*
index bc2d21d27ec1fb06558d27a0524629e81153b489..1fe85b1d71be33c6cf44047ab42abf1ca081d57f 100644 (file)
@@ -26,6 +26,47 @@ RCSID("$Id$")
 #include <freeradius-devel/util/struct.h>
 #include <freeradius-devel/io/pair.h>
 
+/** Generic decode value.
+ *
+ */
+static ssize_t generic_decode_value(TALLOC_CTX *ctx, fr_pair_list_t *out,
+                                   fr_dict_attr_t const *parent,
+                                   uint8_t const *data, size_t const data_len, UNUSED void *decode_ctx)
+{
+       fr_pair_t *vp;
+       ssize_t slen;
+
+       if (!fr_type_is_leaf(parent->type)) {
+               FR_PROTO_TRACE("Cannot use generic decoder for data type %s", fr_type_to_str(parent->type));
+               fr_strerror_printf("Cannot decode data type %s", fr_type_to_str(parent->type));
+               return -1;
+       }
+
+       vp = fr_pair_afrom_da(ctx, parent);
+       if (!vp) {
+               FR_PROTO_TRACE("fr_struct_from_network - failed allocating child VP");
+               return PAIR_DECODE_OOM;
+       }
+
+       /*
+        *      No protocol-specific data types here (yet).
+        *
+        *      If we can't decode this field, then the entire
+        *      structure is treated as a raw blob.
+        */
+       slen = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
+                                         &FR_DBUFF_TMP(data, data_len), data_len, true);
+       if (slen < 0) {
+               FR_PROTO_TRACE("fr_struct_from_network - failed decoding child VP %s", vp->da->name);
+               talloc_free(vp);
+               return slen;
+       }
+
+       fr_pair_append(out, vp);
+
+       return slen;
+}
+
 /** Convert a STRUCT to one or more VPs
  *
  */
@@ -49,6 +90,7 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out,
                return -1; /* at least one byte of data */
        }
 
+       FR_PROTO_TRACE("Decoding struct %s", parent->name);
        FR_PROTO_HEX_DUMP(data, data_len, "fr_struct_from_network");
 
        /*
@@ -67,6 +109,11 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out,
        child_num = 1;
        key_vp = NULL;
 
+       /*
+        *      Simplify the code by having a generic decode routine.
+        */
+       if (!decode_value) decode_value = generic_decode_value;
+
        /*
         *      Decode structs with length prefixes.
         */
@@ -84,7 +131,17 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out,
 
                if ((size_t) (end - p) < field_len) {
                        FR_PROTO_TRACE("Insufficient room for length field");
-                       goto unknown;
+
+               invalid_struct:
+                       /*
+                        *      Some field could not be decoded.  Nuke the entire struct, and just make the
+                        *      whole thing "raw".
+                        */
+                       TALLOC_FREE(struct_vp);
+
+                       slen = fr_pair_raw_from_network(ctx, out, parent, data, data_len);
+                       if (slen < 0) return slen;
+                       return data_len;
                }
 
                claimed_len = p[0];
@@ -97,7 +154,7 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out,
                if (claimed_len < da_length_offset(parent)) {
                        FR_PROTO_TRACE("Length header (%zu) is smaller than minimum value (%u)",
                                       claimed_len, parent->flags.type_size);
-                       goto unknown;
+                       goto invalid_struct;
                }
 
                /*
@@ -108,7 +165,7 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out,
                if (calc_len > (size_t) (end - p)) {
                        FR_PROTO_TRACE("Length header (%zu) is larger than remaining data (%zu)",
                                       claimed_len + field_len, (size_t) (end - p));
-                       goto unknown;
+                       goto invalid_struct;
                }
 
                /*
@@ -131,7 +188,8 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out,
                child = fr_dict_attr_child_by_num(parent, child_num);
                if (!child) break;
 
-               FR_PROTO_HEX_DUMP(p, (end - p), "fr_struct_from_network - child %s (%d)", child->name, child->attr);
+               FR_PROTO_TRACE("Decoding struct %s child %s (%d)", parent->name, child->name, child->attr);
+               FR_PROTO_HEX_DUMP(p, (end - p), "fr_struct_from_network - remaining %zu", (size_t) (end - p));
 
                /*
                 *      Check for bit fields.
@@ -144,7 +202,7 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out,
                        num_bits = offset + child->flags.length;
                        if ((size_t)(end - p) < fr_bytes_from_bits(num_bits)) {
                                FR_PROTO_TRACE("not enough data for bit decoder?");
-                               goto unknown;
+                               goto remainder_raw;
                        }
 
                        memset(array, 0, sizeof(array));
@@ -188,43 +246,39 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out,
 
                                default:
                                        FR_PROTO_TRACE("Can't decode unknown type?");
-                                       goto unknown;
+                                       goto remainder_raw;
                        }
 
                        vp->vp_tainted = true;
                        fr_pair_append(child_list, vp);
+
                        p += (num_bits >> 3); /* go to the LAST bit, not the byte AFTER the last bit */
                        offset = num_bits & 0x07;
                        child_num++;
                        continue;
                }
-               offset = 0;     /* reset for non-bit-field attributes */
-
-               child_length = child->flags.length;
 
-               /*
-                *      If this field overflows the input, then *all*
-                *      of the input is suspect.
-                */
-               if (child_length > (size_t) (end - p)) {        
-                       fprintf(stderr, "SHIT %s have %zd want %zd\n",
-                               child->name, (end - p), child_length);
-                       FR_PROTO_TRACE("fr_struct_from_network - child length %zu overflows buffer", child_length);
-                       goto unknown;
-               }
+               fr_assert(offset == 0);
+               offset = 0;     /* reset for non-bit-field attributes */
 
                /*
-                *      The child is variable sized, OR it's an array.
-                *      Eat up the rest of the data.
+                *      The child is either unknown width, OR known width with a length that is too large for
+                *      the "length" field, OR is known width via some kind of protocol-specific length header.
                 */
-               if (!child_length || (child->flags.array)) {
+               if (!child->flags.length || child->flags.array) {
                        child_length = end - p;
 
-               } else if ((size_t) (end - p) < child_length) {
-                       fprintf(stderr, "SHIT %s have %zd want %zd\n",
-                               child->name, (end - p), child_length);
-                       FR_PROTO_TRACE("fr_struct_from_network - child length %zu underflows buffer", child_length);
-                       goto unknown;
+               } else {
+                       child_length = child->flags.length;
+
+                       /*
+                        *      If this field overflows the input, then *all*
+                        *      of the input is suspect.
+                        */
+                       if (child_length > (size_t) (end - p)) {
+                               FR_PROTO_TRACE("fr_struct_from_network - child length %zu overflows buffer", child_length);
+                               goto remainder_raw;
+                       }
                }
 
                /*
@@ -232,16 +286,15 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out,
                 *      inside of a struct.
                 */
                switch (child->type) {
-               default:
-                       /*
-                        *      Other structural types can only be decoded by a callback.
-                        */
-                       if (!decode_value) {
-                               FR_PROTO_TRACE("fr_struct_from_network - unknown child type");
-                               goto unknown;
-                       }
-                       break;
-
+               case FR_TYPE_INTERNAL:
+               case FR_TYPE_NULL:
+                       FR_PROTO_TRACE("fr_struct_from_network - unknown child type");
+                       goto remainder_raw;
+
+               case FR_TYPE_STRUCT:
+               case FR_TYPE_VSA:
+               case FR_TYPE_VENDOR:
+               case FR_TYPE_GROUP:
                case FR_TYPE_LEAF:
                        break;
 
@@ -266,7 +319,7 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out,
                                slen = decode_tlv(child_ctx, child_list, child, p, end - p, decode_ctx);
                                if (slen < 0) {
                                        FR_PROTO_TRACE("failed decoding TLV?");
-                                       goto unknown;
+                                       goto remainder_raw;
                                }
                                p += slen;
                        }
@@ -280,7 +333,11 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out,
                 */
                case FR_TYPE_UNION:
                        fr_assert(!fr_dict_attr_child_by_num(parent, child_num + 1));
-                       if (!fr_cond_assert(key_vp)) goto unknown;
+                       if (!key_vp) {
+                       remainder_raw:
+                               child_length = end - p;
+                               goto raw;
+                       }
 
                        /*
                         *      Create the union wrapper, and reset the child_ctx and child_list to it.
@@ -303,64 +360,29 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out,
                 *      block, and then decode each field
                 *      individually.
                 */
-               if (decode_value) {
-                       if (child->flags.array) {
-                               slen = fr_pair_array_from_network(child_ctx, child_list, child, p, child_length, decode_ctx, decode_value);
-                       } else {
-                               slen = decode_value(child_ctx, child_list, child, p, child_length, decode_ctx);
-                       }
-                       if (slen < 0) {
-                               FR_PROTO_TRACE("Failed decoding value");
-                               goto unknown;
-                       }
-
-                       p += slen;      /* not always the same as child->flags.length */
-                       child_num++;    /* go to the next child */
-                       if (fr_dict_attr_is_key_field(child)) key_vp = fr_pair_list_tail(child_list);
-                       continue;
+               if (child->flags.array) {
+                       slen = fr_pair_array_from_network(child_ctx, child_list, child, p, child_length, decode_ctx, decode_value);
+               } else {
+                       slen = decode_value(child_ctx, child_list, child, p, child_length, decode_ctx);
                }
+               if (slen < 0) {
+                       FR_PROTO_TRACE("Failed decoding value");
 
-               /*
-                *      We don't handle this yet here.
-                */
-               fr_assert(!child->flags.array);
-
-               vp = fr_pair_afrom_da(child_ctx, child);
-               if (!vp) {
-                       FR_PROTO_TRACE("fr_struct_from_network - failed allocating child VP");
-                       goto unknown;
+               raw:
+                       slen = fr_pair_raw_from_network(child_ctx, child_list, child, p, child_length);
+                       if (slen < 0) {
+                               talloc_free(struct_vp);
+                               return slen;
+                       }
                }
 
-               /*
-                *      No protocol-specific data types here (yet).
-                *
-                *      If we can't decode this field, then the entire
-                *      structure is treated as a raw blob.
-                */
-               if (fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
-                                             &FR_DBUFF_TMP(p, child_length), child_length, true) < 0) {
-                       FR_PROTO_TRACE("fr_struct_from_network - failed decoding child VP %s", vp->da->name);
-                       talloc_free(vp);
-               unknown:
-                       TALLOC_FREE(struct_vp);
+               p += slen;      /* not always the same as child->flags.length */
+               child_num++;    /* go to the next child */
 
-                       slen = fr_pair_raw_from_network(ctx, out, parent, data, data_len);
-                       if (slen < 0) return slen;
-                       return data_len;
+               if (fr_dict_attr_is_key_field(child)) {
+                       fr_assert(!key_vp);
+                       key_vp = fr_pair_list_tail(child_list);
                }
-
-               vp->vp_tainted = true;
-               fr_pair_append(child_list, vp);
-
-               if (fr_dict_attr_is_key_field(vp->da)) key_vp = vp;
-
-               /*
-                *      Note that we're decoding fixed fields here.
-                *      So we skip the input based on the *known*
-                *      length, and not on the *decoded* length.
-                */
-               p += child_length;
-               child_num++;    /* go to the next child */
        }
 
        /*
@@ -375,7 +397,8 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out,
        substruct:
                child = NULL;
 
-               FR_PROTO_HEX_DUMP(p, (end - p), "fr_struct_from_network - substruct");
+               FR_PROTO_TRACE("Key %s", key_vp->da->name);
+               FR_PROTO_HEX_DUMP(p, (end - p), "fr_struct_from_network - child structure");
 
                /*
                 *      Nothing more to decode, don't decode it.
@@ -389,6 +412,7 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out,
                if (enumv) child = enumv->key_child_ref[0];
 
                if (!child) {
+                       FR_PROTO_TRACE("No matching child structure found");
                unknown_child:
                        /*
                         *      Always encode the unknown child as
@@ -402,7 +426,7 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out,
                        if (!child) {
                                FR_PROTO_TRACE("failed allocating unknown child for key VP %s - %s",
                                               key_vp->da->name, fr_strerror());
-                               goto unknown;
+                               goto oom;
                        }
 
                        slen = fr_pair_raw_from_network(child_ctx, child_list, child, p, end - p);
@@ -414,6 +438,8 @@ ssize_t fr_struct_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out,
 
                        p = end;
                } else {
+                       FR_PROTO_TRACE("Decoding child structure %s", child->name);
+
                        fr_assert(child->type == FR_TYPE_STRUCT);
 
                        slen = fr_struct_from_network(child_ctx, child_list, child, p, end - p,
@@ -843,7 +869,7 @@ ssize_t fr_struct_to_network(fr_dbuff_t *dbuff,
 
 done:
        vp = fr_dcursor_current(cursor);
-       if (tlv && vp && (vp->da == tlv) && encode_pair) {
+       if (tlv && vp && (vp->da == tlv)) {
                ssize_t slen;
                fr_dcursor_t tlv_cursor;
 
@@ -853,7 +879,7 @@ done:
                        return PAIR_ENCODE_FATAL_ERROR;
                }
 
-               fr_assert(fr_type_is_structural(vp->vp_type));
+               fr_assert(vp->vp_type == FR_TYPE_TLV);
 
                vp = fr_pair_dcursor_init(&tlv_cursor, &vp->vp_group);
                if (vp) {
index 73bf9ff9d99dfebbdaa70a8a11082d352c96b707..2a00f5b007cbb1bf893993be47a097740f00c4cc 100644 (file)
@@ -32,7 +32,7 @@ proto-dictionary dhcpv4
 #    options   = [message-type=offer subnet_mask=255.255.255.0 server_id=172.22.178.234 lease_time=43200 router=10.10.8.254 name_server=143.209.4.1,143.209.5.1 tftp_server_name=b'172.22.178.234' 120=b'\x01\xac\x16\xb2\xea' client_id='\x00nathan1clientid' 90=b'\x01\x01\x00\xc8x\xc4RV@ \x811234\x8f\xe0\xcc\xe2\xee\x85\x96\xab\xb2X\x17\xc4\x80\xb2\xfd0' relay_agent_information=b'\x01\x14 PON 1/1/07/01:1.0.1' end]
 #
 decode-proto 02 01 06 01 77 71 cf 85 00 0a 00 00 00 00 00 00 0a 0a 08 eb ac 16 b2 ea 0a 0a 08 f0 00 0e 86 11 c0 75 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 63 82 53 63 35 01 02 01 04 ff ff ff 00 36 04 ac 16 b2 ea 33 04 00 00 a8 c0 03 04 0a 0a 08 fe 06 08 8f d1 04 01 8f d1 05 01 42 0e 31 37 32 2e 32 32 2e 31 37 38 2e 32 33 34 78 05 01 ac 16 b2 ea 3d 10 00 6e 61 74 68 61 6e 31 63 6c 69 65 6e 74 69 64 5a 1f 01 01 00 c8 78 c4 52 56 40 20 81 31 32 33 34 8f e0 cc e2 ee 85 96 ab b2 58 17 c4 80 b2 fd 30 52 16 01 14 20 50 4f 4e 20 31 2f 31 2f 30 37 2f 30 31 3a 31 2e 30 2e 31 ff
-match Opcode = ::Server-Message, Hardware-Type = ::Ethernet, Hardware-Address-Length = 6, Hop-Count = 1, Transaction-Id = 2003947397, Number-of-Seconds = 10, Flags = 0, Client-IP-Address = 0.0.0.0, Your-IP-Address = 10.10.8.235, Server-IP-Address = 172.22.178.234, Gateway-IP-Address = 10.10.8.240, Client-Hardware-Address = 00:0e:86:11:c0:75, Message-Type = ::Offer, Subnet-Mask = 255.255.255.0, Server-Identifier = 172.22.178.234, IP-Address-Lease-Time = 43200, Router-Address = 10.10.8.254, Domain-Name-Server = 143.209.4.1, Domain-Name-Server = 143.209.5.1, TFTP-Server-Name = "172.22.178.234", raw.SIP-Servers-Option = 0x01ac16b2ea, Client-Identifier = 0x006e617468616e31636c69656e746964, Authentication = { protocol = ::delayed-authentication, algorithm = ::HMAC-SHA1-keyed-hash, RDM = 0, replay-detection = 14445511662704271489, raw.algorithm.HMAC-SHA1-keyed-hash = 0x313233348fe0cce2ee8596abb25817c480b2fd30 }, Relay-Agent-Information = { Circuit-Id = 0x20504f4e20312f312f30372f30313a312e302e31 }, Network-Subnet = 10.10.8.240/32
+match Opcode = ::Server-Message, Hardware-Type = ::Ethernet, Hardware-Address-Length = 6, Hop-Count = 1, Transaction-Id = 2003947397, Number-of-Seconds = 10, Flags = 0, Client-IP-Address = 0.0.0.0, Your-IP-Address = 10.10.8.235, Server-IP-Address = 172.22.178.234, Gateway-IP-Address = 10.10.8.240, Client-Hardware-Address = 00:0e:86:11:c0:75, Message-Type = ::Offer, Subnet-Mask = 255.255.255.0, Server-Identifier = 172.22.178.234, IP-Address-Lease-Time = 43200, Router-Address = 10.10.8.254, Domain-Name-Server = 143.209.4.1, Domain-Name-Server = 143.209.5.1, TFTP-Server-Name = "172.22.178.234", raw.SIP-Servers-Option = 0x01ac16b2ea, Client-Identifier = 0x006e617468616e31636c69656e746964, Authentication = { protocol = ::delayed-authentication, algorithm = ::HMAC-SHA1-keyed-hash, RDM = 0, replay-detection = 14445511662704271489, algorithm.HMAC-SHA1-keyed-hash = { key-id = 825373492, raw.hash = 0x8fe0cce2ee8596abb25817c480b2fd30 } }, Relay-Agent-Information = { Circuit-Id = 0x20504f4e20312f312f30372f30313a312e302e31 }, Network-Subnet = 10.10.8.240/32
 
 count
 match 4
index 101b30bb3d1228b5509b2bb93403118a1b40b414..3cf6579e9cf7e6da8a4e437e72d194dea051af64 100644 (file)
@@ -74,7 +74,7 @@ match 00 01 00 12 00 04 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 00 00
 
 #  And if we see something that's too short, we get a bad attribute.
 decode-pair 00 01 00 10 00 04 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d
-match Client-ID = { DUID = ::UUID, raw.DUID.UUID = 0x000102030405060708090a0b0c0d }
+match Client-ID = { DUID = ::UUID, DUID.UUID = { raw.Value = 0x000102030405060708090a0b0c0d } }
 
 #
 #  Server Identifier
@@ -111,7 +111,7 @@ match Server-ID = { DUID = ::UUID, DUID.UUID = { Value = 0x000102030405060708090
 
 #  And if we see something that's too short, we get a bad attribute.
 decode-pair 00 02 00 10 00 04 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d
-match Server-ID = { DUID = ::UUID, raw.DUID.UUID = 0x000102030405060708090a0b0c0d }
+match Server-ID = { DUID = ::UUID, DUID.UUID = { raw.Value = 0x000102030405060708090a0b0c0d } }
 
 count
 match 37