]> git.kaiwu.me - haproxy.git/commitdiff
MINOR: sample: add the "be_sort" converter
authorWilliam Lallemand <wlallemand@haproxy.com>
Thu, 17 Sep 2026 12:56:44 +0000 (12:56 +0000)
committerWilliam Lallemand <wlallemand@haproxy.com>
Thu, 17 Sep 2026 12:56:44 +0000 (12:56 +0000)
This converter sorts in ascending numerical order the big-endian unsigned
integers of <elem_size> bytes stored in a binary sample. <elem_size> must
be 1, 2, 4 or 8, and the input length must be a multiple of <elem_size>,
otherwise the conversion fails.

Since the elements are unsigned and stored in network byte order, comparing
their bytes starting from the most significant one gives the numerical
order, so a simple memcmp() over the element width is enough and no byte
swapping is needed.

It is mainly intended to normalize a list of binary values whose order may
vary from one client to another before hashing it, for instance the cipher
list or the extension list of a TLS client hello:

    http-request set-var(txn.ciphers) \
        ssl_fc_cipherlist_bin(1),be_sort(2),xxh64

Documentation was updated.

doc/configuration.txt
src/sample.c

index 844a2afc4ed93fd99b146e0663b7fa037036c93b..fbcb62963d3ec96cec0b36d62ab052cf07f853e9 100644 (file)
@@ -21389,6 +21389,7 @@ base64                                             binary       string
 be2dec(separator,chunk_size[,truncate])            binary       string
 le2dec(separator,chunk_size[,truncate])            binary       string
 be2hex([separator[,chunk_size[,truncate]]])        binary       string
+be_sort(elem_size)                                 binary       binary
 bool                                               integer      boolean
 bytes(offset[,length])                             binary       binary
 capture-req(id)                                    string       string
@@ -21725,6 +21726,24 @@ be2hex([<separator>[,<chunk_size>[,<truncate>]]])
       bin(01020304050607),be2hex(--,2,1) # 0102--0304--0506
       bin(0102030405060708),be2hex(,3,1) # 010203040506
 
+be_sort(<elem_size>)
+  Sorts in ascending numerical order the big-endian unsigned integers of
+  <elem_size> bytes stored in the binary input sample, and returns the
+  resulting binary sample. <elem_size> must be 1, 2, 4 or 8, and the length of
+  the input sample must be a multiple of <elem_size>, otherwise the conversion
+  fails. This is typically used to normalize a list of binary values whose
+  order may vary from one client to another before hashing it, such as the
+  cipher list or the extension list of a TLS client hello.
+
+  Example:
+      bin(0302000104),be_sort(1)       # 0001020304
+      bin(1302130113031304),be_sort(2) # 1301130213031304
+      bin(010203),be_sort(2)           # fails, not a multiple of 2
+
+      # hash of the client's cipher list, independent of the cipher order
+      http-request set-var(txn.ciphers) \
+          ssl_fc_cipherlist_bin(1),be_sort(2),xxh64
+
 bool
   Returns a boolean TRUE if the input value of type signed integer is
   non-null, otherwise returns FALSE. Used in conjunction with and(), it can be
index a39b5ce2e9ed43991a53ad2d39abdeee4fe84273..0064252ae41975b67b4ebb7b0458857374ecd10c 100644 (file)
@@ -2189,6 +2189,94 @@ static int sample_conv_be2hex(const struct arg *args, struct sample *smp, void *
        return 1;
 }
 
+/* Comparison functions used by qsort() in the be_sort() converter below. They
+ * compare two big-endian unsigned integers of a fixed width. Since the values
+ * are unsigned and stored in network byte order, comparing their bytes from
+ * the most significant one gives the numerical order, which is exactly what
+ * memcmp() does.
+ */
+static int sample_conv_be_sort_cmp1(const void *a, const void *b)
+{
+       return memcmp(a, b, 1);
+}
+
+static int sample_conv_be_sort_cmp2(const void *a, const void *b)
+{
+       return memcmp(a, b, 2);
+}
+
+static int sample_conv_be_sort_cmp4(const void *a, const void *b)
+{
+       return memcmp(a, b, 4);
+}
+
+static int sample_conv_be_sort_cmp8(const void *a, const void *b)
+{
+       return memcmp(a, b, 8);
+}
+
+static int sample_conv_be_sort_check(struct arg *args, struct sample_conv *conv,
+                                     const char *file, int line, char **err)
+{
+       switch (args[0].data.sint) {
+       case 1:
+       case 2:
+       case 4:
+       case 8:
+               break;
+       default:
+               memprintf(err, "element size must be 1, 2, 4 or 8 (got %lld)", args[0].data.sint);
+               return 0;
+       }
+
+       return 1;
+}
+
+/* Sorts in ascending order the big-endian unsigned integers of <elem_size>
+ * bytes found in the binary input sample. The input length must be a multiple
+ * of <elem_size>, otherwise the conversion fails. This may be used to
+ * normalize a list of binary values before hashing it, e.g. the cipher list of
+ * a TLS client hello.
+ * Arguments: elem_size (1, 2, 4 or 8)
+ */
+static int sample_conv_be_sort(const struct arg *args, struct sample *smp, void *private)
+{
+       struct buffer *trash = get_best_trash_chunk(&smp->data.u.str, smp->data.u.str.data);
+       size_t elem_size = args[0].data.sint;
+       int (*cmp)(const void *, const void *);
+
+       if (!trash)
+               return 0;
+
+       if (smp->data.u.str.data % elem_size)
+               return 0;
+
+       switch (elem_size) {
+       case 1:
+               cmp = sample_conv_be_sort_cmp1;
+               break;
+       case 2:
+               cmp = sample_conv_be_sort_cmp2;
+               break;
+       case 4:
+               cmp = sample_conv_be_sort_cmp4;
+               break;
+       default:
+               cmp = sample_conv_be_sort_cmp8;
+               break;
+       }
+
+       memcpy(trash->area, smp->data.u.str.area, smp->data.u.str.data);
+       trash->data = smp->data.u.str.data;
+
+       qsort(trash->area, trash->data / elem_size, elem_size, cmp);
+
+       smp->data.u.str = *trash;
+       smp->data.type = SMP_T_BIN;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
 /* check and/or preset the optional argument of has_ctl() */
 static int sample_conv_hasctl_check(struct arg *args, struct sample_conv *conv,
                                     const char *file, int line, char **err)
@@ -5915,6 +6003,7 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, {
        { "be2dec",  sample_conv_be2dec,       ARG3(1,STR,SINT,SINT), sample_conv_2dec_check,   SMP_T_BIN,  SMP_T_STR  },
        { "le2dec",  sample_conv_le2dec,       ARG3(1,STR,SINT,SINT), sample_conv_2dec_check,   SMP_T_BIN,  SMP_T_STR  },
        { "be2hex",  sample_conv_be2hex,       ARG3(1,STR,SINT,SINT), sample_conv_be2hex_check, SMP_T_BIN,  SMP_T_STR  },
+       { "be_sort", sample_conv_be_sort,      ARG1(1,SINT),          sample_conv_be_sort_check, SMP_T_BIN, SMP_T_BIN  },
        { "has_ctl", sample_conv_hasctl,       ARG1(0,STR),           sample_conv_hasctl_check, SMP_T_BIN,  SMP_T_BOOL },
        { "hex",     sample_conv_bin2hex,      0,                     NULL,                     SMP_T_BIN,  SMP_T_STR  },
        { "hex2i",   sample_conv_hex2int,      0,                     NULL,                     SMP_T_STR,  SMP_T_SINT },