Skip to content

Commit a92a6f1

Browse files
authored
MONGOCRYPT-756 Convert FLE2EncryptionPlaceholder to FLE2InsertUpdatePayloadV2 for text search indexed fields (#939)
MONGOCRYPT-756 Convert FLE2EncryptionPlaceholder to FLE2InsertUpdatePayloadV2 for text search indexed fields
1 parent ee3a5d9 commit a92a6f1

17 files changed

+1865
-396
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ set (TEST_MONGOCRYPT_SOURCES
462462
test/test-gcp-auth.c
463463
test/test-mc-cmp.c
464464
test/test-mc-efc.c
465+
test/test-mc-fle2-encryption-placeholder.c
465466
test/test-mc-fle2-find-equality-payload-v2.c
466467
test/test-mc-fle2-find-range-payload-v2.c
467468
test/test-mc-fle2-payload-iev.c

src/mc-efc-private.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ typedef enum _supported_query_type_flags {
2828
SUPPORTS_RANGE_QUERIES = 1 << 1,
2929
// Range preview query supported
3030
SUPPORTS_RANGE_PREVIEW_DEPRECATED_QUERIES = 1 << 2,
31+
// Text search preview query supported
32+
SUPPORTS_SUBSTRING_PREVIEW_QUERIES = 1 << 3,
33+
SUPPORTS_SUFFIX_PREVIEW_QUERIES = 1 << 4,
34+
SUPPORTS_PREFIX_PREVIEW_QUERIES = 1 << 5,
3135
} supported_query_type_flags;
3236

3337
typedef struct _mc_EncryptedField_t {

src/mc-efc.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ static bool _parse_query_type_string(const char *queryType, supported_query_type
3232
*out = SUPPORTS_RANGE_QUERIES;
3333
} else if (mstr_eq_ignore_case(mstrv_lit(MONGOCRYPT_QUERY_TYPE_RANGEPREVIEW_DEPRECATED_STR), qtv)) {
3434
*out = SUPPORTS_RANGE_PREVIEW_DEPRECATED_QUERIES;
35+
} else if (mstr_eq_ignore_case(mstrv_lit(MONGOCRYPT_QUERY_TYPE_SUBSTRINGPREVIEW_STR), qtv)) {
36+
*out = SUPPORTS_SUBSTRING_PREVIEW_QUERIES;
37+
} else if (mstr_eq_ignore_case(mstrv_lit(MONGOCRYPT_QUERY_TYPE_SUFFIXPREVIEW_STR), qtv)) {
38+
*out = SUPPORTS_SUFFIX_PREVIEW_QUERIES;
39+
} else if (mstr_eq_ignore_case(mstrv_lit(MONGOCRYPT_QUERY_TYPE_PREFIXPREVIEW_STR), qtv)) {
40+
*out = SUPPORTS_PREFIX_PREVIEW_QUERIES;
3541
} else {
3642
return false;
3743
}

src/mc-fle2-encryption-placeholder-private.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ bool mc_FLE2RangeInsertSpec_parse(mc_FLE2RangeInsertSpec_t *out,
121121

122122
// Note: For the substring/suffix/prefix insert specs, all lengths are in terms of number of UTF-8 codepoints, not
123123
// number of bytes.
124+
/* mc_FLE2SubstringInsertSpec_t holds the parameters used to encode for substring search. */
124125
typedef struct {
125126
// mlen is the max string length that can be indexed.
126127
uint32_t mlen;
@@ -130,22 +131,31 @@ typedef struct {
130131
uint32_t ub;
131132
} mc_FLE2SubstringInsertSpec_t;
132133

134+
/* mc_FLE2SuffixInsertSpec_t holds the parameters used to encode for suffix search. */
133135
typedef struct {
134136
// lb is the lower bound on the length of suffixes to be indexed.
135137
uint32_t lb;
136138
// ub is the upper bound on the length of suffixes to be indexed.
137139
uint32_t ub;
138140
} mc_FLE2SuffixInsertSpec_t;
139141

142+
/* mc_FLE2PrefixInsertSpec_t holds the parameters used to encode for prefix search. */
140143
typedef struct {
141144
// lb is the lower bound on the length of prefixes to be indexed.
142145
uint32_t lb;
143146
// ub is the upper bound on the length of prefixes to be indexed.
144147
uint32_t ub;
145148
} mc_FLE2PrefixInsertSpec_t;
146149

150+
/** mc_FLE2TextSearchInsertSpec_t represents the text search insert specification that is
151+
* encoded inside of a FLE2EncryptionPlaceholder. See
152+
* https://github.com/mongodb/mongo/blob/master/src/mongo/crypto/fle_field_schema.idl
153+
* for the representation in the MongoDB server. */
147154
typedef struct {
148-
// v is the value to encrypt.
155+
// v_iter points to the value to encrypt.
156+
bson_iter_t v_iter;
157+
158+
// v is the value to encrypt, pointing to the value at v_iter.
149159
const char *v;
150160
// len is the byte length of v.
151161
uint32_t len;
@@ -174,6 +184,20 @@ typedef struct {
174184
bool diacf;
175185
} mc_FLE2TextSearchInsertSpec_t;
176186

187+
// `mc_FLE2TextSearchInsertSpec_t` inherits extended alignment from libbson. To dynamically allocate, use
188+
// aligned allocation (e.g. BSON_ALIGNED_ALLOC)
189+
BSON_STATIC_ASSERT2(alignof_mc_FLE2TextSearchInsertSpec_t,
190+
BSON_ALIGNOF(mc_FLE2TextSearchInsertSpec_t) >= BSON_ALIGNOF(bson_iter_t));
191+
192+
/** mc_FLE2TextSearchInsertSpec_parse parses a BSON document into a mc_FLE2TextSearchInsertSpec_t.
193+
* @in must point to a BSON document.
194+
* @out must outlive the BSON object @in is iterating on.
195+
* - Returns false on error.
196+
* - No cleanup needed for @out. */
197+
bool mc_FLE2TextSearchInsertSpec_parse(mc_FLE2TextSearchInsertSpec_t *out,
198+
const bson_iter_t *in,
199+
mongocrypt_status_t *status);
200+
177201
/** FLE2EncryptionPlaceholder implements Encryption BinData (subtype 6)
178202
* sub-subtype 0, the intent-to-encrypt mapping. Contains a value to encrypt and
179203
* a description of how it should be encrypted.

src/mc-fle2-encryption-placeholder.c

Lines changed: 226 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
#include "mc-fle2-encryption-placeholder-private.h"
2222
#include "mongocrypt-buffer-private.h"
23+
#include "mongocrypt-private.h"
24+
#include "mongocrypt-util-private.h" // mc_bson_type_to_string
2325
#include "mongocrypt.h"
2426

2527
#define CLIENT_ERR_PREFIXED_HELPER(Prefix, ErrorString, ...) CLIENT_ERR(Prefix ": " ErrorString, ##__VA_ARGS__)
@@ -44,6 +46,22 @@
4446
goto fail; \
4547
}
4648

49+
// Common logic for parsing int32 greater than zero
50+
#define IF_FIELD_INT32_GT0_PARSE(Name, Dest, Iter) \
51+
IF_FIELD(Name) { \
52+
if (!BSON_ITER_HOLDS_INT32(&Iter)) { \
53+
CLIENT_ERR_PREFIXED("'" #Name "' must be an int32"); \
54+
goto fail; \
55+
} \
56+
int32_t val = bson_iter_int32(&Iter); \
57+
if (val <= 0) { \
58+
CLIENT_ERR_PREFIXED("'" #Name "' must be greater than zero"); \
59+
goto fail; \
60+
} \
61+
Dest = (uint32_t)val; \
62+
} \
63+
END_IF_FIELD
64+
4765
void mc_FLE2EncryptionPlaceholder_init(mc_FLE2EncryptionPlaceholder_t *placeholder) {
4866
memset(placeholder, 0, sizeof(mc_FLE2EncryptionPlaceholder_t));
4967
}
@@ -94,7 +112,7 @@ bool mc_FLE2EncryptionPlaceholder_parse(mc_FLE2EncryptionPlaceholder_t *out,
94112
}
95113
algorithm = bson_iter_int32(&iter);
96114
if (algorithm != MONGOCRYPT_FLE2_ALGORITHM_UNINDEXED && algorithm != MONGOCRYPT_FLE2_ALGORITHM_EQUALITY
97-
&& algorithm != MONGOCRYPT_FLE2_ALGORITHM_RANGE) {
115+
&& algorithm != MONGOCRYPT_FLE2_ALGORITHM_RANGE && algorithm != MONGOCRYPT_FLE2_ALGORITHM_TEXT_SEARCH) {
98116
CLIENT_ERR_PREFIXED("invalid algorithm value: %d", algorithm);
99117
goto fail;
100118
}
@@ -491,3 +509,210 @@ bool mc_FLE2RangeInsertSpec_parse(mc_FLE2RangeInsertSpec_t *out,
491509
}
492510

493511
#undef ERROR_PREFIX
512+
513+
#define ERROR_PREFIX "Error parsing FLE2SubstringInsertSpec"
514+
515+
static bool mc_FLE2SubstringInsertSpec_parse(mc_FLE2SubstringInsertSpec_t *out,
516+
const bson_iter_t *in,
517+
mongocrypt_status_t *status) {
518+
bson_iter_t iter;
519+
bool has_mlen = false, has_ub = false, has_lb = false;
520+
BSON_ASSERT_PARAM(out);
521+
BSON_ASSERT_PARAM(in);
522+
523+
iter = *in;
524+
525+
if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) {
526+
CLIENT_ERR_PREFIXED("must be an iterator to a document");
527+
return false;
528+
}
529+
bson_iter_recurse(&iter, &iter);
530+
while (bson_iter_next(&iter)) {
531+
const char *field = bson_iter_key(&iter);
532+
BSON_ASSERT(field);
533+
IF_FIELD_INT32_GT0_PARSE(mlen, out->mlen, iter);
534+
IF_FIELD_INT32_GT0_PARSE(ub, out->ub, iter);
535+
IF_FIELD_INT32_GT0_PARSE(lb, out->lb, iter);
536+
}
537+
CHECK_HAS(mlen)
538+
CHECK_HAS(ub)
539+
CHECK_HAS(lb)
540+
if (out->ub < out->lb) {
541+
CLIENT_ERR_PREFIXED("upper bound cannot be less than the lower bound");
542+
goto fail;
543+
}
544+
if (out->mlen < out->ub) {
545+
CLIENT_ERR_PREFIXED("maximum indexed length cannot be less than the upper bound");
546+
goto fail;
547+
}
548+
return true;
549+
fail:
550+
return false;
551+
}
552+
553+
#undef ERROR_PREFIX
554+
555+
#define ERROR_PREFIX "Error parsing FLE2SuffixInsertSpec"
556+
557+
static bool
558+
mc_FLE2SuffixInsertSpec_parse(mc_FLE2SuffixInsertSpec_t *out, const bson_iter_t *in, mongocrypt_status_t *status) {
559+
bson_iter_t iter;
560+
bool has_ub = false, has_lb = false;
561+
562+
BSON_ASSERT_PARAM(out);
563+
BSON_ASSERT_PARAM(in);
564+
565+
iter = *in;
566+
567+
if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) {
568+
CLIENT_ERR_PREFIXED("must be an iterator to a document");
569+
return false;
570+
}
571+
bson_iter_recurse(&iter, &iter);
572+
while (bson_iter_next(&iter)) {
573+
const char *field = bson_iter_key(&iter);
574+
BSON_ASSERT(field);
575+
IF_FIELD_INT32_GT0_PARSE(ub, out->ub, iter);
576+
IF_FIELD_INT32_GT0_PARSE(lb, out->lb, iter);
577+
}
578+
CHECK_HAS(ub)
579+
CHECK_HAS(lb)
580+
if (out->ub < out->lb) {
581+
CLIENT_ERR_PREFIXED("upper bound cannot be less than the lower bound");
582+
goto fail;
583+
}
584+
return true;
585+
fail:
586+
return false;
587+
}
588+
589+
#undef ERROR_PREFIX
590+
591+
#define ERROR_PREFIX "Error parsing FLE2PrefixInsertSpec"
592+
593+
static bool
594+
mc_FLE2PrefixInsertSpec_parse(mc_FLE2PrefixInsertSpec_t *out, const bson_iter_t *in, mongocrypt_status_t *status) {
595+
bson_iter_t iter;
596+
bool has_ub = false, has_lb = false;
597+
BSON_ASSERT_PARAM(out);
598+
BSON_ASSERT_PARAM(in);
599+
600+
iter = *in;
601+
602+
if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) {
603+
CLIENT_ERR_PREFIXED("must be an iterator to a document");
604+
return false;
605+
}
606+
bson_iter_recurse(&iter, &iter);
607+
while (bson_iter_next(&iter)) {
608+
const char *field = bson_iter_key(&iter);
609+
BSON_ASSERT(field);
610+
IF_FIELD_INT32_GT0_PARSE(ub, out->ub, iter);
611+
IF_FIELD_INT32_GT0_PARSE(lb, out->lb, iter);
612+
}
613+
CHECK_HAS(ub)
614+
CHECK_HAS(lb)
615+
if (out->ub < out->lb) {
616+
CLIENT_ERR_PREFIXED("upper bound cannot be less than the lower bound");
617+
goto fail;
618+
}
619+
return true;
620+
fail:
621+
return false;
622+
}
623+
624+
#undef ERROR_PREFIX
625+
626+
#define ERROR_PREFIX "Error parsing FLE2TextSearchInsertSpec"
627+
628+
bool mc_FLE2TextSearchInsertSpec_parse(mc_FLE2TextSearchInsertSpec_t *out,
629+
const bson_iter_t *in,
630+
mongocrypt_status_t *status) {
631+
BSON_ASSERT_PARAM(out);
632+
BSON_ASSERT_PARAM(in);
633+
634+
*out = (mc_FLE2TextSearchInsertSpec_t){{0}};
635+
636+
bson_iter_t iter = *in;
637+
bool has_v = false, has_casef = false, has_diacf = false;
638+
bool has_substr = false, has_suffix = false, has_prefix = false;
639+
640+
if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) {
641+
CLIENT_ERR_PREFIXED("must be an iterator to a document");
642+
return false;
643+
}
644+
bson_iter_recurse(&iter, &iter);
645+
646+
while (bson_iter_next(&iter)) {
647+
const char *field = bson_iter_key(&iter);
648+
BSON_ASSERT(field);
649+
650+
IF_FIELD(v) {
651+
out->v = bson_iter_utf8(&iter, &out->len);
652+
if (!out->v) {
653+
CLIENT_ERR_PREFIXED("unsupported BSON type: %s for text search",
654+
mc_bson_type_to_string(bson_iter_type(&iter)));
655+
goto fail;
656+
}
657+
out->v_iter = iter;
658+
}
659+
END_IF_FIELD
660+
661+
IF_FIELD(casef) {
662+
if (!BSON_ITER_HOLDS_BOOL(&iter)) {
663+
CLIENT_ERR_PREFIXED("'casef' must be a bool");
664+
goto fail;
665+
}
666+
out->casef = bson_iter_bool(&iter);
667+
}
668+
END_IF_FIELD
669+
670+
IF_FIELD(diacf) {
671+
if (!BSON_ITER_HOLDS_BOOL(&iter)) {
672+
CLIENT_ERR_PREFIXED("'diacf' must be a bool");
673+
goto fail;
674+
}
675+
out->diacf = bson_iter_bool(&iter);
676+
}
677+
END_IF_FIELD
678+
679+
IF_FIELD(substr) {
680+
if (!mc_FLE2SubstringInsertSpec_parse(&out->substr.value, &iter, status)) {
681+
goto fail;
682+
}
683+
out->substr.set = true;
684+
}
685+
END_IF_FIELD
686+
687+
IF_FIELD(suffix) {
688+
if (!mc_FLE2SuffixInsertSpec_parse(&out->suffix.value, &iter, status)) {
689+
goto fail;
690+
}
691+
out->suffix.set = true;
692+
}
693+
END_IF_FIELD
694+
695+
IF_FIELD(prefix) {
696+
if (!mc_FLE2PrefixInsertSpec_parse(&out->prefix.value, &iter, status)) {
697+
goto fail;
698+
}
699+
out->prefix.set = true;
700+
}
701+
END_IF_FIELD
702+
}
703+
704+
CHECK_HAS(v)
705+
CHECK_HAS(casef)
706+
CHECK_HAS(diacf)
707+
// one of substr/suffix/prefix must be set
708+
if (!(has_substr || has_suffix || has_prefix)) {
709+
CLIENT_ERR_PREFIXED("Must have a substring, suffix, or prefix index specification");
710+
goto fail;
711+
}
712+
return true;
713+
714+
fail:
715+
return false;
716+
}
717+
718+
#undef ERROR_PREFIX

0 commit comments

Comments
 (0)