Skip to content

Commit 44ff6dd

Browse files
committed
MONGOCRYPT-756 Convert FLE2EncryptionPlaceholder to FLE2InsertUpdatePayloadV2 for text search indexed fields
1 parent b193dba commit 44ff6dd

9 files changed

+953
-5
lines changed

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: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,72 @@ bool mc_FLE2RangeInsertSpec_parse(mc_FLE2RangeInsertSpec_t *out,
119119
bool use_range_v2,
120120
mongocrypt_status_t *status);
121121

122+
/* mc_FLE2SubstringInsertSpec_t holds the parameters used to encode for substring search. */
123+
typedef struct {
124+
// max substring code point length to be indexed
125+
int32_t mlen;
126+
// upper bound code point length of valid substring queries
127+
int32_t ub;
128+
// lower bound code point length of valid substring queries
129+
int32_t lb;
130+
} mc_FLE2SubstringInsertSpec_t;
131+
132+
/* mc_FLE2SuffixInsertSpec_t holds the parameters used to encode for suffix search. */
133+
typedef struct {
134+
// upper bound code point length of valid suffix queries
135+
int32_t ub;
136+
// lower bound code point length of valid suffix queries
137+
int32_t lb;
138+
} mc_FLE2SuffixInsertSpec_t;
139+
140+
/* mc_FLE2PrefixInsertSpec_t holds the parameters used to encode for prefix search. */
141+
typedef struct {
142+
// upper bound code point length of valid prefix queries
143+
int32_t ub;
144+
// lower bound code point length of valid prefix queries
145+
int32_t lb;
146+
} mc_FLE2PrefixInsertSpec_t;
147+
148+
/** mc_FLE2TextSearchInsertSpec_t represents the text search insert specification that is
149+
* encoded inside of a FLE2EncryptionPlaceholder. See
150+
* https://github.com/mongodb/mongo/blob/master/src/mongo/crypto/fle_field_schema.idl
151+
* for the representation in the MongoDB server. */
152+
typedef struct {
153+
// v is the value to encrypt.
154+
bson_iter_t v;
155+
// casef is whether or not to case fold
156+
bool casef;
157+
// diacf is whether or not to diacritic fold
158+
bool diacf;
159+
160+
// optional parameters for substring search
161+
struct {
162+
mc_FLE2SubstringInsertSpec_t value;
163+
bool set;
164+
} substringSpec;
165+
166+
// optional parameters for suffix search
167+
struct {
168+
mc_FLE2SuffixInsertSpec_t value;
169+
bool set;
170+
} suffixSpec;
171+
172+
// optional parameters for prefix search
173+
struct {
174+
mc_FLE2PrefixInsertSpec_t value;
175+
bool set;
176+
} prefixSpec;
177+
} mc_FLE2TextSearchInsertSpec_t;
178+
179+
// `mc_FLE2TextSearchInsertSpec_t` inherits extended alignment from libbson. To dynamically allocate, use
180+
// aligned allocation (e.g. BSON_ALIGNED_ALLOC)
181+
BSON_STATIC_ASSERT2(alignof_mc_FLE2TextSearchInsertSpec_t,
182+
BSON_ALIGNOF(mc_FLE2TextSearchInsertSpec_t) >= BSON_ALIGNOF(bson_iter_t));
183+
184+
bool mc_FLE2TextSearchInsertSpec_parse(mc_FLE2TextSearchInsertSpec_t *out,
185+
const bson_iter_t *in,
186+
mongocrypt_status_t *status);
187+
122188
/** FLE2EncryptionPlaceholder implements Encryption BinData (subtype 6)
123189
* sub-subtype 0, the intent-to-encrypt mapping. Contains a value to encrypt and
124190
* a description of how it should be encrypted.

src/mc-fle2-encryption-placeholder.c

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

2121
#include "mc-fle2-encryption-placeholder-private.h"
2222
#include "mongocrypt-buffer-private.h"
23+
#include "mongocrypt-private.h"
2324
#include "mongocrypt.h"
2425

2526
#define CLIENT_ERR_PREFIXED_HELPER(Prefix, ErrorString, ...) CLIENT_ERR(Prefix ": " ErrorString, ##__VA_ARGS__)
@@ -44,6 +45,22 @@
4445
goto fail; \
4546
}
4647

48+
// Common logic for parsing int32 greater than zero
49+
#define IF_FIELD_INT32_GT0_PARSE(Name, Dest, Iter) \
50+
IF_FIELD(Name) { \
51+
if (!BSON_ITER_HOLDS_INT32(&Iter)) { \
52+
CLIENT_ERR_PREFIXED("'" #Name "' must be an int32"); \
53+
goto fail; \
54+
} \
55+
int32_t val = bson_iter_int32(&Iter); \
56+
if (val <= 0) { \
57+
CLIENT_ERR_PREFIXED("'" #Name "' must be greater than zero"); \
58+
goto fail; \
59+
} \
60+
Dest = val; \
61+
} \
62+
END_IF_FIELD
63+
4764
void mc_FLE2EncryptionPlaceholder_init(mc_FLE2EncryptionPlaceholder_t *placeholder) {
4865
memset(placeholder, 0, sizeof(mc_FLE2EncryptionPlaceholder_t));
4966
}
@@ -94,7 +111,7 @@ bool mc_FLE2EncryptionPlaceholder_parse(mc_FLE2EncryptionPlaceholder_t *out,
94111
}
95112
algorithm = bson_iter_int32(&iter);
96113
if (algorithm != MONGOCRYPT_FLE2_ALGORITHM_UNINDEXED && algorithm != MONGOCRYPT_FLE2_ALGORITHM_EQUALITY
97-
&& algorithm != MONGOCRYPT_FLE2_ALGORITHM_RANGE) {
114+
&& algorithm != MONGOCRYPT_FLE2_ALGORITHM_RANGE && algorithm != MONGOCRYPT_FLE2_ALGORITHM_TEXT_SEARCH) {
98115
CLIENT_ERR_PREFIXED("invalid algorithm value: %d", algorithm);
99116
goto fail;
100117
}
@@ -491,3 +508,203 @@ bool mc_FLE2RangeInsertSpec_parse(mc_FLE2RangeInsertSpec_t *out,
491508
}
492509

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

src/mc-fle2-insert-update-payload-private-v2.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,31 @@
2525
#include "mongocrypt-private.h"
2626
#include "mongocrypt.h"
2727

28+
#define DEF_TEXT_SEARCH_TOKEN_SET(Type) \
29+
typedef struct { \
30+
_mongocrypt_buffer_t edcDerivedToken; \
31+
_mongocrypt_buffer_t escDerivedToken; \
32+
_mongocrypt_buffer_t serverDerivedFromDataToken; \
33+
_mongocrypt_buffer_t encryptedTokens; \
34+
} mc_Text##Type##TokenSet_t; \
35+
void mc_Text##Type##TokenSet_init(mc_Text##Type##TokenSet_t *); \
36+
void mc_Text##Type##TokenSet_cleanup(mc_Text##Type##TokenSet_t *)
37+
38+
DEF_TEXT_SEARCH_TOKEN_SET(Exact);
39+
DEF_TEXT_SEARCH_TOKEN_SET(Substring);
40+
DEF_TEXT_SEARCH_TOKEN_SET(Suffix);
41+
DEF_TEXT_SEARCH_TOKEN_SET(Prefix);
42+
43+
typedef struct {
44+
mc_TextExactTokenSet_t exact; // e
45+
mc_array_t substringArray; // s
46+
mc_array_t suffixArray; // u
47+
mc_array_t prefixArray; // p
48+
} mc_TextSearchTokenSets_t;
49+
50+
void mc_TextSearchTokenSets_init(mc_TextSearchTokenSets_t *);
51+
void mc_TextSearchTokenSets_cleanup(mc_TextSearchTokenSets_t *);
52+
2853
/**
2954
* FLE2InsertUpdatePayloadV2 represents an FLE2 payload of an indexed field to
3055
* insert or update. It is created client side.
@@ -83,6 +108,12 @@ typedef struct {
83108
mc_optional_int32_t trimFactor; // tf
84109
bson_value_t indexMin; // mn
85110
bson_value_t indexMax; // mx
111+
112+
struct {
113+
mc_TextSearchTokenSets_t tsts;
114+
bool set;
115+
} textSearchTokenSets; // b
116+
86117
_mongocrypt_buffer_t plaintext;
87118
_mongocrypt_buffer_t userKeyId;
88119
} mc_FLE2InsertUpdatePayloadV2_t;
@@ -130,6 +161,8 @@ bool mc_FLE2InsertUpdatePayloadV2_serializeForRange(const mc_FLE2InsertUpdatePay
130161
bson_t *out,
131162
bool use_range_v2);
132163

164+
bool mc_FLE2InsertUpdatePayloadV2_serializeForTextSearch(const mc_FLE2InsertUpdatePayloadV2_t *payload, bson_t *out);
165+
133166
void mc_FLE2InsertUpdatePayloadV2_cleanup(mc_FLE2InsertUpdatePayloadV2_t *payload);
134167

135168
#endif /* MC_FLE2_INSERT_UPDATE_PAYLOAD_PRIVATE_V2_H */

0 commit comments

Comments
 (0)