diff --git a/.evergreen/scripts/compile-libmongocrypt.sh b/.evergreen/scripts/compile-libmongocrypt.sh index a68a638502c..e719fafbbee 100755 --- a/.evergreen/scripts/compile-libmongocrypt.sh +++ b/.evergreen/scripts/compile-libmongocrypt.sh @@ -9,7 +9,7 @@ compile_libmongocrypt() { # `src/kms-message`. # # Run `.evergreen/scripts/kms-divergence-check.sh` to ensure that there is no divergence in the copied files. - declare -r version="1.13.0" + declare -r version="1.15.1" git clone -q --depth=1 https://github.com/mongodb/libmongocrypt --branch "${version:?}" || return diff --git a/src/libmongoc/CMakeLists.txt b/src/libmongoc/CMakeLists.txt index 38dc6f3fbe9..4e98acd6efe 100644 --- a/src/libmongoc/CMakeLists.txt +++ b/src/libmongoc/CMakeLists.txt @@ -489,10 +489,10 @@ elseif (NOT ENABLE_CLIENT_SIDE_ENCRYPTION STREQUAL OFF) find_package (mongocrypt QUIET) endif () - if (mongocrypt_FOUND AND "${mongocrypt_VERSION}" VERSION_LESS 1.13.0) + if (mongocrypt_FOUND AND "${mongocrypt_VERSION}" VERSION_LESS 1.15.1) message (STATUS " libmongocrypt found at ${mongocrypt_DIR}") message (STATUS " libmongocrypt version ${mongocrypt_VERSION} found") - message (STATUS " libmongocrypt version 1.13.0 is required to enable In-Use Encryption Support.") + message (STATUS " libmongocrypt version 1.15.1 is required to enable In-Use Encryption Support.") set (REQUIRED_MONGOCRYPT_VERSION_FOUND OFF) elseif (mongocrypt_FOUND) set (REQUIRED_MONGOCRYPT_VERSION_FOUND ON) diff --git a/src/libmongoc/src/mongoc/mongoc-client-side-encryption.c b/src/libmongoc/src/mongoc/mongoc-client-side-encryption.c index f81ef3db4e8..477b1312419 100644 --- a/src/libmongoc/src/mongoc/mongoc-client-side-encryption.c +++ b/src/libmongoc/src/mongoc/mongoc-client-side-encryption.c @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #ifndef _WIN32 #include @@ -462,6 +463,28 @@ struct _mongoc_client_encryption_encrypt_range_opts_t { } precision; }; +typedef struct { + bool set; + int32_t value; +} mc_optional_int32_t; + +struct _encrypt_text_per_index_opts_t { + bool set; + mc_optional_int32_t str_max_length; + mc_optional_int32_t str_max_query_length; + mc_optional_int32_t str_min_query_length; +}; + +struct _mongoc_encrypt_text_opts_t { + bool set; + bool case_sensitive; + bool diacritic_sensitive; + + mongoc_encrypt_text_substring_opts_t *substring; + mongoc_encrypt_text_prefix_opts_t *prefix; + mongoc_encrypt_text_suffix_opts_t *suffix; +}; + struct _mongoc_client_encryption_encrypt_opts_t { bson_value_t keyid; char *algorithm; @@ -472,6 +495,7 @@ struct _mongoc_client_encryption_encrypt_opts_t { } contention_factor; char *query_type; mongoc_client_encryption_encrypt_range_opts_t *range_opts; + mongoc_encrypt_text_opts_t *text_opts; }; mongoc_client_encryption_encrypt_opts_t * @@ -480,6 +504,143 @@ mongoc_client_encryption_encrypt_opts_new(void) return bson_malloc0(sizeof(mongoc_client_encryption_encrypt_opts_t)); } +mongoc_encrypt_text_prefix_opts_t * +mongoc_encrypt_text_prefix_opts_new(void) +{ + return bson_malloc0(sizeof(mongoc_encrypt_text_prefix_opts_t)); +} + +void +mongoc_encrypt_text_prefix_opts_destroy(mongoc_encrypt_text_prefix_opts_t *opts) +{ + bson_free(opts); +} + +void +mongoc_encrypt_text_prefix_opts_set_str_max_query_length(mongoc_encrypt_text_prefix_opts_t *opts, int32_t val) +{ + BSON_ASSERT_PARAM(opts); + opts->str_max_query_length.set = true; + opts->str_max_query_length.value = val; +} + +void +mongoc_encrypt_text_prefix_opts_set_str_min_query_length(mongoc_encrypt_text_prefix_opts_t *opts, int32_t val) +{ + BSON_ASSERT_PARAM(opts); + opts->str_min_query_length.set = true; + opts->str_min_query_length.value = val; +} + +// Suffix opts +mongoc_encrypt_text_suffix_opts_t * +mongoc_encrypt_text_suffix_opts_new(void) +{ + return bson_malloc0(sizeof(mongoc_encrypt_text_suffix_opts_t)); +} + +void +mongoc_encrypt_text_suffix_opts_destroy(mongoc_encrypt_text_suffix_opts_t *opts) +{ + bson_free(opts); +} + +void +mongoc_encrypt_text_suffix_opts_set_str_max_query_length(mongoc_encrypt_text_suffix_opts_t *opts, int32_t val) +{ + BSON_ASSERT_PARAM(opts); + opts->str_max_query_length.set = true; + opts->str_max_query_length.value = val; +} + +void +mongoc_encrypt_text_suffix_opts_set_str_min_query_length(mongoc_encrypt_text_suffix_opts_t *opts, int32_t val) +{ + BSON_ASSERT_PARAM(opts); + opts->str_min_query_length.set = true; + opts->str_min_query_length.value = val; +} + +// Substring opts +mongoc_encrypt_text_substring_opts_t * +mongoc_encrypt_text_substring_opts_new(void) +{ + return bson_malloc0(sizeof(mongoc_encrypt_text_substring_opts_t)); +} + +void +mongoc_encrypt_text_substring_opts_destroy(mongoc_encrypt_text_substring_opts_t *opts) +{ + bson_free(opts); +} + +void +mongoc_encrypt_text_substring_opts_set_str_max_length(mongoc_encrypt_text_substring_opts_t *opts, int32_t val) +{ + BSON_ASSERT_PARAM(opts); + opts->str_max_length.set = true; + opts->str_max_length.value = val; +} + +void +mongoc_encrypt_text_substring_opts_set_str_max_query_length(mongoc_encrypt_text_substring_opts_t *opts, int32_t val) +{ + BSON_ASSERT_PARAM(opts); + opts->str_max_query_length.set = true; + opts->str_max_query_length.value = val; +} + +void +mongoc_encrypt_text_substring_opts_set_str_min_query_length(mongoc_encrypt_text_substring_opts_t *opts, int32_t val) +{ + BSON_ASSERT_PARAM(opts); + opts->str_min_query_length.set = true; + opts->str_min_query_length.value = val; +} + +// Setters for text opts +void +mongoc_encrypt_text_opts_set_prefix(mongoc_encrypt_text_opts_t *opts, mongoc_encrypt_text_prefix_opts_t *popts) +{ + BSON_ASSERT_PARAM(opts); + BSON_ASSERT_PARAM(popts); + opts->prefix = mongoc_encrypt_text_prefix_opts_new(); + *opts->prefix = *popts; + opts->prefix->set = true; +} + +void +mongoc_encrypt_text_opts_set_suffix(mongoc_encrypt_text_opts_t *opts, mongoc_encrypt_text_suffix_opts_t *sopts) +{ + BSON_ASSERT_PARAM(opts); + BSON_ASSERT_PARAM(sopts); + opts->suffix = mongoc_encrypt_text_suffix_opts_new(); + *opts->suffix = *sopts; + opts->suffix->set = true; +} + +void +mongoc_encrypt_text_opts_set_substring(mongoc_encrypt_text_opts_t *opts, mongoc_encrypt_text_substring_opts_t *ssopts) +{ + BSON_ASSERT_PARAM(opts); + BSON_ASSERT_PARAM(ssopts); + opts->substring = mongoc_encrypt_text_substring_opts_new(); + *opts->substring = *ssopts; + opts->substring->set = true; +} + +mongoc_encrypt_text_opts_t * +mongoc_encrypt_text_opts_new(void) +{ + return bson_malloc0(sizeof(mongoc_encrypt_text_opts_t)); +} + +void +mongoc_encrypt_text_opts_destroy(mongoc_encrypt_text_opts_t *topts) +{ + bson_free(topts); +} + void mongoc_client_encryption_encrypt_range_opts_destroy(mongoc_client_encryption_encrypt_range_opts_t *range_opts) { @@ -503,6 +664,7 @@ mongoc_client_encryption_encrypt_opts_destroy(mongoc_client_encryption_encrypt_o return; } mongoc_client_encryption_encrypt_range_opts_destroy(opts->range_opts); + mongoc_encrypt_text_opts_destroy(opts->text_opts); bson_value_destroy(&opts->keyid); bson_free(opts->algorithm); bson_free(opts->keyaltname); @@ -671,6 +833,35 @@ mongoc_client_encryption_encrypt_opts_set_range_opts(mongoc_client_encryption_en opts->range_opts = copy_range_opts(range_opts); } +/*-------------------------------------------------------------------------- + * Explicit Encryption TextPreview Options + *-------------------------------------------------------------------------- + */ +void +mongoc_client_encryption_encrypt_opts_set_text_opts(mongoc_client_encryption_encrypt_opts_t *opts, + const mongoc_encrypt_text_opts_t *text_opts) +{ + BSON_ASSERT_PARAM(opts); + opts->text_opts = mongoc_encrypt_text_opts_new(); + *opts->text_opts = *text_opts; + opts->text_opts->set = true; +} + +void +mongoc_client_encryption_encrypt_text_opts_set_case_sensitive(mongoc_encrypt_text_opts_t *opts, bool case_sensitive) +{ + BSON_ASSERT_PARAM(opts); + opts->case_sensitive = case_sensitive; +} + +void +mongoc_client_encryption_encrypt_text_opts_set_diacritic_sensitive(mongoc_encrypt_text_opts_t *opts, + bool diacritic_sensitive) +{ + BSON_ASSERT_PARAM(opts); + opts->diacritic_sensitive = diacritic_sensitive; +} + /*-------------------------------------------------------------------------- * RewrapManyDataKeyResult. *-------------------------------------------------------------------------- @@ -1038,6 +1229,50 @@ append_bson_range_opts(bson_t *bson_range_opts, const mongoc_client_encryption_e } } +static void +append_bson_text_per_index_opts(bson_t *out, const struct _encrypt_text_per_index_opts_t *opts) +{ + BSON_ASSERT_PARAM(out); + if (opts->str_max_length.set) { + BSON_ASSERT(bson_append_int32(out, "strMaxLength", -1, opts->str_max_length.value)); + } + if (opts->str_max_query_length.set) { + BSON_ASSERT(bson_append_int32(out, "strMaxQueryLength", -1, opts->str_max_query_length.value)); + } + if (opts->str_min_query_length.set) { + BSON_ASSERT(bson_append_int32(out, "strMinQueryLength", -1, opts->str_min_query_length.value)); + } +} + +static void +append_bson_text_opts(bson_t *bson_text_opts, const mongoc_encrypt_text_opts_t *opts) +{ + BSON_ASSERT_PARAM(bson_text_opts); + BSON_ASSERT_PARAM(opts); + + BSON_ASSERT(BSON_APPEND_BOOL(bson_text_opts, "caseSensitive", opts->case_sensitive)); + BSON_ASSERT(BSON_APPEND_BOOL(bson_text_opts, "diacriticSensitive", opts->diacritic_sensitive)); + + if (opts->prefix->set) { + bson_t per_index_spec; + BSON_ASSERT(BSON_APPEND_DOCUMENT_BEGIN(bson_text_opts, "prefix", &per_index_spec)); + append_bson_text_per_index_opts(&per_index_spec, opts->prefix); + BSON_ASSERT(bson_append_document_end(bson_text_opts, &per_index_spec)); + } + if (opts->suffix->set) { + bson_t per_index_spec; + BSON_ASSERT(BSON_APPEND_DOCUMENT_BEGIN(bson_text_opts, "suffix", &per_index_spec)); + append_bson_text_per_index_opts(&per_index_spec, opts->suffix); + BSON_ASSERT(bson_append_document_end(bson_text_opts, &per_index_spec)); + } + if (opts->substring->set) { + bson_t per_index_spec; + BSON_ASSERT(BSON_APPEND_DOCUMENT_BEGIN(bson_text_opts, "substring", &per_index_spec)); + append_bson_text_per_index_opts(&per_index_spec, opts->substring); + BSON_ASSERT(bson_append_document_end(bson_text_opts, &per_index_spec)); + } +} + /*-------------------------------------------------------------------------- * * _prep_for_auto_encryption -- @@ -2641,7 +2876,7 @@ mongoc_client_encryption_encrypt(mongoc_client_encryption_t *client_encryption, bson_error_t *error) { bool ret = false; - bson_t *range_opts = NULL; + bson_t *range_opts = NULL, *text_opts = NULL; ENTRY; @@ -2667,6 +2902,11 @@ mongoc_client_encryption_encrypt(mongoc_client_encryption_t *client_encryption, append_bson_range_opts(range_opts, opts); } + if (opts->text_opts->set) { + text_opts = bson_new(); + append_bson_text_opts(text_opts, opts->text_opts); + } + if (!_mongoc_crypt_explicit_encrypt(client_encryption->crypt, client_encryption->keyvault_coll, opts->algorithm, @@ -2675,6 +2915,7 @@ mongoc_client_encryption_encrypt(mongoc_client_encryption_t *client_encryption, opts->query_type, opts->contention_factor.set ? &opts->contention_factor.value : NULL, range_opts, + text_opts, value, ciphertext, error)) { @@ -2683,6 +2924,7 @@ mongoc_client_encryption_encrypt(mongoc_client_encryption_t *client_encryption, ret = true; fail: + bson_destroy(text_opts); bson_destroy(range_opts); RETURN(ret); } @@ -2711,6 +2953,12 @@ mongoc_client_encryption_encrypt_expression(mongoc_client_encryption_t *client_e append_bson_range_opts(range_opts, opts); } + bson_t *text_opts = NULL; + if (opts->text_opts->set) { + text_opts = bson_new(); + append_bson_text_opts(text_opts, opts->text_opts); + } + if (!_mongoc_crypt_explicit_encrypt_expression(client_encryption->crypt, client_encryption->keyvault_coll, opts->algorithm, @@ -2719,6 +2967,7 @@ mongoc_client_encryption_encrypt_expression(mongoc_client_encryption_t *client_e opts->query_type, opts->contention_factor.set ? &opts->contention_factor.value : NULL, range_opts, + text_opts, expr, expr_out, error)) { diff --git a/src/libmongoc/src/mongoc/mongoc-client-side-encryption.h b/src/libmongoc/src/mongoc/mongoc-client-side-encryption.h index b0ace91023f..10f4e1cafca 100644 --- a/src/libmongoc/src/mongoc/mongoc-client-side-encryption.h +++ b/src/libmongoc/src/mongoc/mongoc-client-side-encryption.h @@ -37,10 +37,14 @@ struct _mongoc_database_t; #define MONGOC_ENCRYPT_ALGORITHM_UNINDEXED "Unindexed" #define MONGOC_ENCRYPT_ALGORITHM_RANGE "Range" #define MONGOC_ENCRYPT_ALGORITHM_RANGEPREVIEW "RangePreview" +#define MONGOC_ENCRYPT_ALGORITHM_TEXTPREVIEW "TextPreview" #define MONGOC_ENCRYPT_QUERY_TYPE_EQUALITY "equality" #define MONGOC_ENCRYPT_QUERY_TYPE_RANGE "range" #define MONGOC_ENCRYPT_QUERY_TYPE_RANGEPREVIEW "rangePreview" +#define MONGOC_ENCRYPT_QUERY_TYPE_SUBSTRINGPREVIEW "substringPreview" +#define MONGOC_ENCRYPT_QUERY_TYPE_PREFIXPREVIEW "prefixPreview" +#define MONGOC_ENCRYPT_QUERY_TYPE_SUFFIXPREVIEW "suffixPreview" BSON_BEGIN_DECLS @@ -104,6 +108,10 @@ mongoc_auto_encryption_opts_set_kms_credential_provider_callback(mongoc_auto_enc typedef struct _mongoc_client_encryption_opts_t mongoc_client_encryption_opts_t; typedef struct _mongoc_client_encryption_t mongoc_client_encryption_t; typedef struct _mongoc_client_encryption_encrypt_range_opts_t mongoc_client_encryption_encrypt_range_opts_t; +typedef struct _encrypt_text_per_index_opts_t mongoc_encrypt_text_prefix_opts_t; +typedef struct _encrypt_text_per_index_opts_t mongoc_encrypt_text_suffix_opts_t; +typedef struct _encrypt_text_per_index_opts_t mongoc_encrypt_text_substring_opts_t; +typedef struct _mongoc_encrypt_text_opts_t mongoc_encrypt_text_opts_t; typedef struct _mongoc_client_encryption_encrypt_opts_t mongoc_client_encryption_encrypt_opts_t; typedef struct _mongoc_client_encryption_datakey_opts_t mongoc_client_encryption_datakey_opts_t; typedef struct _mongoc_client_encryption_rewrap_many_datakey_result_t @@ -182,6 +190,69 @@ mongoc_client_encryption_get_key(mongoc_client_encryption_t *client_encryption, bson_t *key_doc, bson_error_t *error); +MONGOC_EXPORT(mongoc_encrypt_text_prefix_opts_t *) +mongoc_encrypt_text_prefix_opts_new(void); + +MONGOC_EXPORT(mongoc_encrypt_text_suffix_opts_t *) +mongoc_encrypt_text_suffix_opts_new(void); + +MONGOC_EXPORT(mongoc_encrypt_text_substring_opts_t *) +mongoc_encrypt_text_substring_opts_new(void); + +MONGOC_EXPORT(void) +mongoc_encrypt_text_prefix_opts_destroy(mongoc_encrypt_text_prefix_opts_t *); + +MONGOC_EXPORT(void) +mongoc_encrypt_text_suffix_opts_destroy(mongoc_encrypt_text_suffix_opts_t *); + +MONGOC_EXPORT(void) +mongoc_encrypt_text_substring_opts_destroy(mongoc_encrypt_text_substring_opts_t *); + +MONGOC_EXPORT(void) +mongoc_encrypt_text_prefix_opts_set_str_max_query_length(mongoc_encrypt_text_prefix_opts_t *opts, + int32_t str_max_query_length); + +MONGOC_EXPORT(void) +mongoc_encrypt_text_prefix_opts_set_str_min_query_length(mongoc_encrypt_text_prefix_opts_t *opts, + int32_t str_min_query_length); + +MONGOC_EXPORT(void) +mongoc_encrypt_text_suffix_opts_set_str_max_query_length(mongoc_encrypt_text_suffix_opts_t *opts, + int32_t str_max_query_length); + +MONGOC_EXPORT(void) +mongoc_encrypt_text_suffix_opts_set_str_min_query_length(mongoc_encrypt_text_suffix_opts_t *opts, + int32_t str_min_query_length); + +MONGOC_EXPORT(void) +mongoc_encrypt_text_substring_opts_set_str_max_length(mongoc_encrypt_text_substring_opts_t *opts, + int32_t str_max_length); + +MONGOC_EXPORT(void) +mongoc_encrypt_text_substring_opts_set_str_max_query_length(mongoc_encrypt_text_substring_opts_t *opts, + int32_t str_max_query_length); + +MONGOC_EXPORT(void) +mongoc_encrypt_text_substring_opts_set_str_min_query_length(mongoc_encrypt_text_substring_opts_t *opts, + int32_t str_min_query_length); + +MONGOC_EXPORT(void) +mongoc_encrypt_text_opts_set_prefix(mongoc_encrypt_text_opts_t *opts, mongoc_encrypt_text_prefix_opts_t *popts); + +MONGOC_EXPORT(void) +mongoc_encrypt_text_opts_set_suffix(mongoc_encrypt_text_opts_t *opts, mongoc_encrypt_text_suffix_opts_t *sopts); + +MONGOC_EXPORT(void) +mongoc_encrypt_text_opts_set_substring(mongoc_encrypt_text_opts_t *opts, mongoc_encrypt_text_substring_opts_t *ssopts); + +MONGOC_EXPORT(mongoc_encrypt_text_opts_t *) +mongoc_encrypt_text_opts_new(void); + +MONGOC_EXPORT(void) +mongoc_encrypt_text_opts_destroy(mongoc_encrypt_text_opts_t *topts); + +MONGOC_EXPORT(void) +mongoc_client_encryption_encrypt_opts_destroy(mongoc_client_encryption_encrypt_opts_t *opts); MONGOC_EXPORT(struct _mongoc_cursor_t *) mongoc_client_encryption_get_keys(mongoc_client_encryption_t *client_encryption, bson_error_t *error); @@ -247,6 +318,20 @@ MONGOC_EXPORT(void) mongoc_client_encryption_encrypt_opts_set_contention_factor(mongoc_client_encryption_encrypt_opts_t *opts, int64_t contention_factor); +MONGOC_EXPORT(void) +mongoc_client_encryption_encrypt_opts_set_range_opts(mongoc_client_encryption_encrypt_opts_t *opts, + const mongoc_client_encryption_encrypt_range_opts_t *range_opts); +MONGOC_EXPORT(void) +mongoc_client_encryption_encrypt_opts_set_text_opts(mongoc_client_encryption_encrypt_opts_t *opts, + const mongoc_encrypt_text_opts_t *text_opts); + +MONGOC_EXPORT(void) +mongoc_client_encryption_encrypt_text_opts_set_case_sensitive(mongoc_encrypt_text_opts_t *opts, bool case_sensitive); + +MONGOC_EXPORT(void) +mongoc_client_encryption_encrypt_text_opts_set_diacritic_sensitive(mongoc_encrypt_text_opts_t *opts, + bool diacritic_sensitive); + MONGOC_EXPORT(void) mongoc_client_encryption_encrypt_opts_set_query_type(mongoc_client_encryption_encrypt_opts_t *opts, const char *query_type); diff --git a/src/libmongoc/src/mongoc/mongoc-crypt-private.h b/src/libmongoc/src/mongoc/mongoc-crypt-private.h index 958f67ac99d..94486d40abb 100644 --- a/src/libmongoc/src/mongoc/mongoc-crypt-private.h +++ b/src/libmongoc/src/mongoc/mongoc-crypt-private.h @@ -95,6 +95,7 @@ _mongoc_crypt_explicit_encrypt(_mongoc_crypt_t *crypt, const char *query_type /* may be NULL */, const int64_t *contention_factor /* may be NULL */, const bson_t *range_opts /* may be NULL */, + const bson_t *text_opts /* may be NULL */, const bson_value_t *value_in, bson_value_t *value_out, bson_error_t *error); @@ -112,6 +113,7 @@ _mongoc_crypt_explicit_encrypt_expression(_mongoc_crypt_t *crypt, const char *query_type /* may be NULL */, const int64_t *contention_factor /* may be NULL */, const bson_t *range_opts /* may be NULL */, + const bson_t *text_opts /* may be NULL */, const bson_t *expr_in, bson_t *expr_out, bson_error_t *error); diff --git a/src/libmongoc/src/mongoc/mongoc-crypt.c b/src/libmongoc/src/mongoc/mongoc-crypt.c index b53a7159f8f..204f1bab930 100644 --- a/src/libmongoc/src/mongoc/mongoc-crypt.c +++ b/src/libmongoc/src/mongoc/mongoc-crypt.c @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #define MONGOC_LOG_DOMAIN "client-side-encryption" #include @@ -1628,6 +1629,7 @@ _create_explicit_state_machine(_mongoc_crypt_t *crypt, const char *query_type, const int64_t *contention_factor, const bson_t *range_opts, + const bson_t *text_opts, bson_error_t *error) { BSON_ASSERT_PARAM(crypt); @@ -1637,6 +1639,7 @@ _create_explicit_state_machine(_mongoc_crypt_t *crypt, BSON_OPTIONAL_PARAM(keyaltname); BSON_OPTIONAL_PARAM(query_type); BSON_OPTIONAL_PARAM(range_opts); + BSON_OPTIONAL_PARAM(text_opts); BSON_OPTIONAL_PARAM(error); _state_machine_t *state_machine = NULL; @@ -1668,6 +1671,18 @@ _create_explicit_state_machine(_mongoc_crypt_t *crypt, mongocrypt_binary_destroy(binary_range_opts); } + if (text_opts != NULL) { + /* mongocrypt error checks and parses range options */ + mongocrypt_binary_t *binary_text_opts = + mongocrypt_binary_new_from_data((uint8_t *)bson_get_data(text_opts), text_opts->len); + if (!mongocrypt_ctx_setopt_algorithm_text(state_machine->ctx, binary_text_opts)) { + mongocrypt_binary_destroy(binary_text_opts); + _ctx_check_error(state_machine->ctx, error, true); + goto fail; + } + mongocrypt_binary_destroy(binary_text_opts); + } + if (query_type != NULL) { if (!mongocrypt_ctx_setopt_query_type(state_machine->ctx, query_type, -1)) { goto fail; @@ -1734,6 +1749,7 @@ _mongoc_crypt_explicit_encrypt(_mongoc_crypt_t *crypt, const char *query_type, const int64_t *contention_factor, const bson_t *range_opts, + const bson_t *text_opts, const bson_value_t *value_in, bson_value_t *value_out, bson_error_t *error) @@ -1759,7 +1775,7 @@ _mongoc_crypt_explicit_encrypt(_mongoc_crypt_t *crypt, value_out->value_type = BSON_TYPE_EOD; state_machine = _create_explicit_state_machine( - crypt, keyvault_coll, algorithm, keyid, keyaltname, query_type, contention_factor, range_opts, error); + crypt, keyvault_coll, algorithm, keyid, keyaltname, query_type, contention_factor, range_opts, text_opts, error); if (!state_machine) { goto fail; } @@ -1809,6 +1825,7 @@ _mongoc_crypt_explicit_encrypt_expression(_mongoc_crypt_t *crypt, const char *query_type, const int64_t *contention_factor, const bson_t *range_opts, + const bson_t *text_opts, const bson_t *expr_in, bson_t *expr_out, bson_error_t *error) @@ -1820,6 +1837,7 @@ _mongoc_crypt_explicit_encrypt_expression(_mongoc_crypt_t *crypt, BSON_OPTIONAL_PARAM(keyaltname); BSON_OPTIONAL_PARAM(query_type); BSON_OPTIONAL_PARAM(range_opts); + BSON_OPTIONAL_PARAM(text_opts); BSON_ASSERT_PARAM(expr_in); BSON_ASSERT_PARAM(expr_out); BSON_OPTIONAL_PARAM(error); @@ -1834,7 +1852,7 @@ _mongoc_crypt_explicit_encrypt_expression(_mongoc_crypt_t *crypt, bson_init(expr_out); state_machine = _create_explicit_state_machine( - crypt, keyvault_coll, algorithm, keyid, keyaltname, query_type, contention_factor, range_opts, error); + crypt, keyvault_coll, algorithm, keyid, keyaltname, query_type, contention_factor, range_opts, text_opts, error); if (!state_machine) { goto fail; } diff --git a/src/libmongoc/tests/client_side_encryption_prose/explicit_encryption/encryptedFields-prefix-suffix.json b/src/libmongoc/tests/client_side_encryption_prose/explicit_encryption/encryptedFields-prefix-suffix.json new file mode 100644 index 00000000000..8c52b6dd0ad --- /dev/null +++ b/src/libmongoc/tests/client_side_encryption_prose/explicit_encryption/encryptedFields-prefix-suffix.json @@ -0,0 +1,38 @@ +{ + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encrypted-textPreview", + "bsonType": "string", + "queries": [ + { + "queryType": "prefixPreview", + "strMinQueryLength": { + "$numberInt": "2" + }, + "strMaxQueryLength": { + "$numberInt": "10" + }, + "caseSensitive": true, + "diacriticSensitive": true + }, + { + "queryType": "suffixPreview", + "strMinQueryLength": { + "$numberInt": "2" + }, + "strMaxQueryLength": { + "$numberInt": "10" + }, + "caseSensitive": true, + "diacriticSensitive": true + } + ] + } + ] +} diff --git a/src/libmongoc/tests/client_side_encryption_prose/explicit_encryption/encryptedFields-substring.json b/src/libmongoc/tests/client_side_encryption_prose/explicit_encryption/encryptedFields-substring.json new file mode 100644 index 00000000000..cb97a167175 --- /dev/null +++ b/src/libmongoc/tests/client_side_encryption_prose/explicit_encryption/encryptedFields-substring.json @@ -0,0 +1,30 @@ +{ + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encrypted-textPreview", + "bsonType": "string", + "queries": [ + { + "queryType": "substringPreview", + "strMaxLength": { + "$numberInt": "10" + }, + "strMinQueryLength": { + "$numberInt": "2" + }, + "strMaxQueryLength": { + "$numberInt": "10" + }, + "caseSensitive": true, + "diacriticSensitive": true + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json-test.c b/src/libmongoc/tests/json-test.c index 0f28644fccd..3e80860f3ad 100644 --- a/src/libmongoc/tests/json-test.c +++ b/src/libmongoc/tests/json-test.c @@ -654,7 +654,7 @@ collect_tests_from_dir(char (*paths)[MAX_TEST_NAME_LENGTH] /* OUT */, *----------------------------------------------------------------------- */ bson_t * -get_bson_from_json_file(char *filename) +get_bson_from_json_file(const char *filename) { FILE *const file = fopen(filename, "rb"); if (!file) { diff --git a/src/libmongoc/tests/json-test.h b/src/libmongoc/tests/json-test.h index ac9b2fa7b33..0c4424915e9 100644 --- a/src/libmongoc/tests/json-test.h +++ b/src/libmongoc/tests/json-test.h @@ -54,7 +54,7 @@ typedef struct _json_test_config_t { #define JSON_TEST_CONFIG_INIT {NULL, NULL, NULL, NULL, NULL, NULL, false, false, NULL, NULL} bson_t * -get_bson_from_json_file(char *filename); +get_bson_from_json_file(const char *filename); int collect_tests_from_dir(char (*paths)[MAX_TEST_NAME_LENGTH] /* OUT */, diff --git a/src/libmongoc/tests/test-libmongoc.c b/src/libmongoc/tests/test-libmongoc.c index 4673a64a7e4..a1344d1d3ce 100644 --- a/src/libmongoc/tests/test-libmongoc.c +++ b/src/libmongoc/tests/test-libmongoc.c @@ -2283,6 +2283,8 @@ WIRE_VERSION_CHECKS(24) WIRE_VERSION_CHECKS(25) /* wire version 26 begins with the 8.1 release. */ WIRE_VERSION_CHECKS(26) +/* wire version 27 begins with the 8.2 release. */ +WIRE_VERSION_CHECKS(27) int test_framework_skip_if_no_dual_ip_hostname(void) diff --git a/src/libmongoc/tests/test-libmongoc.h b/src/libmongoc/tests/test-libmongoc.h index cecbaca4377..81fa1301ba4 100644 --- a/src/libmongoc/tests/test-libmongoc.h +++ b/src/libmongoc/tests/test-libmongoc.h @@ -221,6 +221,8 @@ WIRE_VERSION_CHECK_DECLS(24) WIRE_VERSION_CHECK_DECLS(25) /* wire version 26 begins with the 8.1 release. */ WIRE_VERSION_CHECK_DECLS(26) +/* wire version 27 begins with the 8.2 release. */ +WIRE_VERSION_CHECK_DECLS(27) #undef WIRE_VERSION_CHECK_DECLS diff --git a/src/libmongoc/tests/test-mongoc-client-side-encryption.c b/src/libmongoc/tests/test-mongoc-client-side-encryption.c index 72a42291741..cbb4596ee20 100644 --- a/src/libmongoc/tests/test-mongoc-client-side-encryption.c +++ b/src/libmongoc/tests/test-mongoc-client-side-encryption.c @@ -19,6 +19,9 @@ #include +#include +#include + #include #include @@ -3281,15 +3284,11 @@ typedef struct { } ee_fixture; static ee_fixture * -explicit_encryption_setup(void) +explicit_encryption_setup_full(const char *encrypted_fields_path, const char *key_path) { ee_fixture *eef = (ee_fixture *)bson_malloc0(sizeof(ee_fixture)); - bson_t *encryptedFields = - get_bson_from_json_file("./src/libmongoc/tests/client_side_encryption_prose/explicit_encryption/" - "encryptedFields.json"); - bson_t *key1Document = - get_bson_from_json_file("./src/libmongoc/tests/client_side_encryption_prose/explicit_encryption/" - "key1-document.json"); + bson_t *encryptedFields = get_bson_from_json_file(encrypted_fields_path); + bson_t *key1Document = get_bson_from_json_file(key_path); mongoc_client_t *setupClient = test_framework_new_default_client(); @@ -3400,6 +3399,15 @@ explicit_encryption_setup(void) return eef; } +static ee_fixture * +explicit_encryption_setup(void) +{ + return explicit_encryption_setup_full("./src/libmongoc/tests/client_side_encryption_prose/explicit_encryption/" + "encryptedFields.json", + "./src/libmongoc/tests/client_side_encryption_prose/explicit_encryption/" + "key1-document.json"); +} + static void explicit_encryption_destroy(ee_fixture *eef) { @@ -4406,6 +4414,331 @@ test_explicit_encryption_case5(void *unused) explicit_encryption_destroy(eef); } +static void +test_explicit_encryption_text(void *unused) +{ + bson_error_t error; + bool ok; + bson_value_t plaintext = {0}; + ee_fixture *eef = + explicit_encryption_setup_full("./src/libmongoc/tests/client_side_encryption_prose/explicit_encryption/" + "encryptedFields-prefix-suffix.json", + "./src/libmongoc/tests/client_side_encryption_prose/explicit_encryption/" + "key1-document.json"); + + BSON_UNUSED(unused); + + plaintext.value_type = BSON_TYPE_UTF8; + plaintext.value.v_utf8.str = "foobarbaz"; + plaintext.value.v_utf8.len = (uint32_t)strlen(plaintext.value.v_utf8.str); + + mongoc_encrypt_text_prefix_opts_t *popts = mongoc_encrypt_text_prefix_opts_new(); + mongoc_encrypt_text_prefix_opts_set_str_max_query_length(popts, 10); + mongoc_encrypt_text_prefix_opts_set_str_max_query_length(popts, 2); + mongoc_encrypt_text_suffix_opts_t *sopts = mongoc_encrypt_text_suffix_opts_new(); + mongoc_encrypt_text_suffix_opts_set_str_max_query_length(popts, 10); + mongoc_encrypt_text_suffix_opts_set_str_max_query_length(popts, 2); + mongoc_encrypt_text_substring_opts_t *ssopts = mongoc_encrypt_text_substring_opts_new(); + mongoc_encrypt_text_substring_opts_set_str_max_length(ssopts, 10); + mongoc_encrypt_text_substring_opts_set_str_max_query_length(popts, 10); + mongoc_encrypt_text_substring_opts_set_str_max_query_length(popts, 2); + + /* Prefix and suffix tests */ + /* Insert 'foobarbaz' with both prefix and suffix indexing */ + { + bson_value_t insertPayload; + bson_t to_insert = BSON_INITIALIZER; + + mongoc_client_encryption_encrypt_opts_t *eo = mongoc_client_encryption_encrypt_opts_new(); + mongoc_client_encryption_encrypt_opts_set_keyid(eo, &eef->key1ID); + mongoc_client_encryption_encrypt_opts_set_algorithm(eo, MONGOC_ENCRYPT_ALGORITHM_TEXTPREVIEW); + + mongoc_encrypt_text_opts_t *topts = mongoc_encrypt_text_opts_new(); + mongoc_encrypt_text_opts_set_prefix(topts, popts); + mongoc_encrypt_text_opts_set_suffix(topts, sopts); + mongoc_client_encryption_encrypt_opts_set_text_opts(eo, topts); + + ok = mongoc_client_encryption_encrypt(eef->clientEncryption, &plaintext, eo, &insertPayload, &error); + ASSERT_OR_PRINT(ok, error); + + ASSERT(BSON_APPEND_VALUE(&to_insert, "encrypted-textPreview", &insertPayload)); + + ok = mongoc_collection_insert_one(eef->encryptedColl, &to_insert, NULL /* opts */, NULL /* reply */, &error); + ASSERT_OR_PRINT(ok, error); + + bson_value_destroy(&insertPayload); + bson_destroy(&to_insert); + mongoc_encrypt_text_opts_destroy(topts); + mongoc_client_encryption_encrypt_opts_destroy(eo); + } + + /* Find the document using the 'foo' prefix */ + { + bson_value_t findPayload; + mongoc_client_encryption_encrypt_opts_t *eo = mongoc_client_encryption_encrypt_opts_new(); + mongoc_client_encryption_encrypt_opts_set_keyid(eo, &eef->key1ID); + mongoc_client_encryption_encrypt_opts_set_algorithm(eo, MONGOC_ENCRYPT_ALGORITHM_TEXTPREVIEW); + mongoc_client_encryption_encrypt_opts_set_query_type(eo, MONGOC_ENCRYPT_QUERY_TYPE_PREFIXPREVIEW); + mongoc_client_encryption_encrypt_opts_set_contention_factor(eo, 0); + + mongoc_encrypt_text_opts_t *topts = mongoc_encrypt_text_opts_new(); + mongoc_encrypt_text_opts_set_prefix(topts, popts); + mongoc_client_encryption_encrypt_opts_set_text_opts(eo, topts); + + plaintext.value.v_utf8.str = "foo"; + plaintext.value.v_utf8.len = 3; + ok = mongoc_client_encryption_encrypt(eef->clientEncryption, &plaintext, eo, &findPayload, &error); + + bsonBuildDecl(expr, + kv("$expr", + doc(kv("$encStrStartsWith", + doc(kv("input", cstr("$encrypted-textPreview")), kv("prefix", value(findPayload))))))); + ASSERT_OR_PRINT(ok, error); + + mongoc_cursor_t *cursor; + const bson_t *got; + + cursor = mongoc_collection_find_with_opts(eef->encryptedColl, &expr, NULL /* opts */, NULL /* read_prefs */); + ASSERT(mongoc_cursor_next(cursor, &got)); + ASSERT_OR_PRINT(!mongoc_cursor_error(cursor, &error), error); + ASSERT_MATCH(got, "{ 'encrypted-textPreview': 'foobarbaz' }"); + ASSERT(!mongoc_cursor_next(cursor, &got) && "expected one document to be returned, got more than one"); + + bson_value_destroy(&findPayload); + mongoc_cursor_destroy(cursor); + bson_destroy(&expr); + mongoc_encrypt_text_opts_destroy(topts); + mongoc_client_encryption_encrypt_opts_destroy(eo); + } + + /* Find the document using the 'baz' suffix */ + { + bson_value_t findPayload; + mongoc_client_encryption_encrypt_opts_t *eo = mongoc_client_encryption_encrypt_opts_new(); + mongoc_client_encryption_encrypt_opts_set_keyid(eo, &eef->key1ID); + mongoc_client_encryption_encrypt_opts_set_algorithm(eo, MONGOC_ENCRYPT_ALGORITHM_TEXTPREVIEW); + mongoc_client_encryption_encrypt_opts_set_query_type(eo, MONGOC_ENCRYPT_QUERY_TYPE_SUFFIXPREVIEW); + mongoc_client_encryption_encrypt_opts_set_contention_factor(eo, 0); + + mongoc_encrypt_text_opts_t *topts = mongoc_encrypt_text_opts_new(); + mongoc_encrypt_text_opts_set_suffix(topts, sopts); + mongoc_client_encryption_encrypt_opts_set_text_opts(eo, topts); + + plaintext.value.v_utf8.str = "baz"; + plaintext.value.v_utf8.len = 3; + ok = mongoc_client_encryption_encrypt(eef->clientEncryption, &plaintext, eo, &findPayload, &error); + + bsonBuildDecl(expr, + kv("$expr", + doc(kv("$encStrEndsWith", + doc(kv("input", cstr("$encrypted-textPreview")), kv("suffix", value(findPayload))))))); + ASSERT_OR_PRINT(ok, error); + + mongoc_cursor_t *cursor; + const bson_t *got; + + cursor = mongoc_collection_find_with_opts(eef->encryptedColl, &expr, NULL /* opts */, NULL /* read_prefs */); + ASSERT(mongoc_cursor_next(cursor, &got)); + ASSERT_OR_PRINT(!mongoc_cursor_error(cursor, &error), error); + ASSERT_MATCH(got, "{ 'encrypted-textPreview': 'foobarbaz' }"); + ASSERT(!mongoc_cursor_next(cursor, &got) && "expected one document to be returned, got more than one"); + + bson_value_destroy(&findPayload); + mongoc_cursor_destroy(cursor); + bson_destroy(&expr); + mongoc_encrypt_text_opts_destroy(topts); + mongoc_client_encryption_encrypt_opts_destroy(eo); + } + + /* Ensure querying for a 'foo' suffix returns no documents */ + { + bson_value_t findPayload; + mongoc_client_encryption_encrypt_opts_t *eo = mongoc_client_encryption_encrypt_opts_new(); + mongoc_client_encryption_encrypt_opts_set_keyid(eo, &eef->key1ID); + mongoc_client_encryption_encrypt_opts_set_algorithm(eo, MONGOC_ENCRYPT_ALGORITHM_TEXTPREVIEW); + mongoc_client_encryption_encrypt_opts_set_query_type(eo, MONGOC_ENCRYPT_QUERY_TYPE_SUFFIXPREVIEW); + mongoc_client_encryption_encrypt_opts_set_contention_factor(eo, 0); + + mongoc_encrypt_text_opts_t *topts = mongoc_encrypt_text_opts_new(); + mongoc_encrypt_text_opts_set_suffix(topts, sopts); + mongoc_client_encryption_encrypt_opts_set_text_opts(eo, topts); + + plaintext.value.v_utf8.str = "foo"; + plaintext.value.v_utf8.len = 3; + ok = mongoc_client_encryption_encrypt(eef->clientEncryption, &plaintext, eo, &findPayload, &error); + + bsonBuildDecl(expr, + kv("$expr", + doc(kv("$encStrEndsWith", + doc(kv("input", cstr("$encrypted-textPreview")), kv("suffix", value(findPayload))))))); + ASSERT_OR_PRINT(ok, error); + + mongoc_cursor_t *cursor; + const bson_t *got; + + cursor = mongoc_collection_find_with_opts(eef->encryptedColl, &expr, NULL /* opts */, NULL /* read_prefs */); + ASSERT(!mongoc_cursor_next(cursor, &got) && "expected no documents to be returned, got some"); + mongoc_cursor_next(cursor, &got); + + bson_value_destroy(&findPayload); + mongoc_cursor_destroy(cursor); + bson_destroy(&expr); + mongoc_encrypt_text_opts_destroy(topts); + mongoc_client_encryption_encrypt_opts_destroy(eo); + } + + /* Ensure querying for a 'baz' prefix returns no documents */ + { + bson_value_t findPayload; + mongoc_client_encryption_encrypt_opts_t *eo = mongoc_client_encryption_encrypt_opts_new(); + mongoc_client_encryption_encrypt_opts_set_keyid(eo, &eef->key1ID); + mongoc_client_encryption_encrypt_opts_set_algorithm(eo, MONGOC_ENCRYPT_ALGORITHM_TEXTPREVIEW); + mongoc_client_encryption_encrypt_opts_set_query_type(eo, MONGOC_ENCRYPT_QUERY_TYPE_PREFIXPREVIEW); + mongoc_client_encryption_encrypt_opts_set_contention_factor(eo, 0); + + mongoc_encrypt_text_opts_t *topts = mongoc_encrypt_text_opts_new(); + mongoc_encrypt_text_opts_set_prefix(topts, popts); + mongoc_client_encryption_encrypt_opts_set_text_opts(eo, topts); + + plaintext.value.v_utf8.str = "baz"; + plaintext.value.v_utf8.len = 3; + ok = mongoc_client_encryption_encrypt(eef->clientEncryption, &plaintext, eo, &findPayload, &error); + + bsonBuildDecl(expr, + kv("$expr", + doc(kv("$encStrStartsWith", + doc(kv("input", cstr("$encrypted-textPreview")), kv("prefix", value(findPayload))))))); + ASSERT_OR_PRINT(ok, error); + + mongoc_cursor_t *cursor; + const bson_t *got; + + cursor = mongoc_collection_find_with_opts(eef->encryptedColl, &expr, NULL /* opts */, NULL /* read_prefs */); + ASSERT(!mongoc_cursor_next(cursor, &got) && "expected no documents to be returned, got some"); + + bson_value_destroy(&findPayload); + mongoc_cursor_destroy(cursor); + bson_destroy(&expr); + mongoc_encrypt_text_opts_destroy(topts); + mongoc_client_encryption_encrypt_opts_destroy(eo); + } + + + /* Substring tests */ + explicit_encryption_destroy(eef); + eef = explicit_encryption_setup_full("./src/libmongoc/tests/client_side_encryption_prose/explicit_encryption/" + "encryptedFields-substring.json", + "./src/libmongoc/tests/client_side_encryption_prose/explicit_encryption/" + "key1-document.json"); + /* Insert 'foobarbaz' with substring indexing */ + { + bson_value_t insertPayload; + bson_t to_insert = BSON_INITIALIZER; + + mongoc_client_encryption_encrypt_opts_t *eo = mongoc_client_encryption_encrypt_opts_new(); + mongoc_client_encryption_encrypt_opts_set_keyid(eo, &eef->key1ID); + mongoc_client_encryption_encrypt_opts_set_algorithm(eo, MONGOC_ENCRYPT_ALGORITHM_TEXTPREVIEW); + + mongoc_encrypt_text_opts_t *topts = mongoc_encrypt_text_opts_new(); + mongoc_encrypt_text_opts_set_substring(topts, ssopts); + mongoc_client_encryption_encrypt_opts_set_text_opts(eo, topts); + + plaintext.value.v_utf8.str = "foobarbaz"; + plaintext.value.v_utf8.len = 9; + ok = mongoc_client_encryption_encrypt(eef->clientEncryption, &plaintext, eo, &insertPayload, &error); + ASSERT_OR_PRINT(ok, error); + + ASSERT(BSON_APPEND_VALUE(&to_insert, "encrypted-textPreview", &insertPayload)); + + ok = mongoc_collection_insert_one(eef->encryptedColl, &to_insert, NULL /* opts */, NULL /* reply */, &error); + ASSERT_OR_PRINT(ok, error); + + bson_value_destroy(&insertPayload); + bson_destroy(&to_insert); + mongoc_encrypt_text_opts_destroy(topts); + mongoc_client_encryption_encrypt_opts_destroy(eo); + } + + /* Find the document using the 'bar' substring */ + { + bson_value_t findPayload; + mongoc_client_encryption_encrypt_opts_t *eo = mongoc_client_encryption_encrypt_opts_new(); + mongoc_client_encryption_encrypt_opts_set_keyid(eo, &eef->key1ID); + mongoc_client_encryption_encrypt_opts_set_algorithm(eo, MONGOC_ENCRYPT_ALGORITHM_TEXTPREVIEW); + mongoc_client_encryption_encrypt_opts_set_query_type(eo, MONGOC_ENCRYPT_QUERY_TYPE_SUBSTRINGPREVIEW); + mongoc_client_encryption_encrypt_opts_set_contention_factor(eo, 0); + + mongoc_encrypt_text_opts_t *topts = mongoc_encrypt_text_opts_new(); + mongoc_encrypt_text_opts_set_substring(topts, ssopts); + mongoc_client_encryption_encrypt_opts_set_text_opts(eo, topts); + + plaintext.value.v_utf8.str = "bar"; + plaintext.value.v_utf8.len = 3; + ok = mongoc_client_encryption_encrypt(eef->clientEncryption, &plaintext, eo, &findPayload, &error); + + bsonBuildDecl(expr, + kv("$expr", + doc(kv("$encStrContains", + doc(kv("input", cstr("$encrypted-textPreview")), kv("substring", value(findPayload))))))); + ASSERT_OR_PRINT(ok, error); + + mongoc_cursor_t *cursor; + const bson_t *got; + + cursor = mongoc_collection_find_with_opts(eef->encryptedColl, &expr, NULL /* opts */, NULL /* read_prefs */); + ASSERT(mongoc_cursor_next(cursor, &got)); + ASSERT_OR_PRINT(!mongoc_cursor_error(cursor, &error), error); + ASSERT_MATCH(got, "{ 'encrypted-textPreview': 'foobarbaz' }"); + ASSERT(!mongoc_cursor_next(cursor, &got) && "expected one document to be returned, got more than one"); + + bson_value_destroy(&findPayload); + mongoc_cursor_destroy(cursor); + bson_destroy(&expr); + mongoc_encrypt_text_opts_destroy(topts); + mongoc_client_encryption_encrypt_opts_destroy(eo); + } + + /* Ensure querying for a 'qux' substring returns no documents */ + { + bson_value_t findPayload; + mongoc_client_encryption_encrypt_opts_t *eo = mongoc_client_encryption_encrypt_opts_new(); + mongoc_client_encryption_encrypt_opts_set_keyid(eo, &eef->key1ID); + mongoc_client_encryption_encrypt_opts_set_algorithm(eo, MONGOC_ENCRYPT_ALGORITHM_TEXTPREVIEW); + mongoc_client_encryption_encrypt_opts_set_query_type(eo, MONGOC_ENCRYPT_QUERY_TYPE_PREFIXPREVIEW); + mongoc_client_encryption_encrypt_opts_set_contention_factor(eo, 0); + + mongoc_encrypt_text_opts_t *topts = mongoc_encrypt_text_opts_new(); + mongoc_encrypt_text_opts_set_substring(topts, ssopts); + mongoc_client_encryption_encrypt_opts_set_text_opts(eo, topts); + + plaintext.value.v_utf8.str = "qux"; + plaintext.value.v_utf8.len = 3; + ok = mongoc_client_encryption_encrypt(eef->clientEncryption, &plaintext, eo, &findPayload, &error); + + bsonBuildDecl(expr, + kv("$expr", + doc(kv("$encStrContains", + doc(kv("input", cstr("$encrypted-textPreview")), kv("substring", value(findPayload))))))); + ASSERT_OR_PRINT(ok, error); + + mongoc_cursor_t *cursor; + const bson_t *got; + + cursor = mongoc_collection_find_with_opts(eef->encryptedColl, &expr, NULL /* opts */, NULL /* read_prefs */); + ASSERT(!mongoc_cursor_next(cursor, &got) && "expected no documents to be returned, got some"); + + bson_value_destroy(&findPayload); + mongoc_cursor_destroy(cursor); + bson_destroy(&expr); + mongoc_encrypt_text_opts_destroy(topts); + mongoc_client_encryption_encrypt_opts_destroy(eo); + } + mongoc_encrypt_text_prefix_opts_destroy(popts); + mongoc_encrypt_text_suffix_opts_destroy(sopts); + mongoc_encrypt_text_substring_opts_destroy(ssopts); + explicit_encryption_destroy(eef); +} + static void _test_unique_index_on_keyaltnames_setup(void (*test_case)(mongoc_client_encryption_t *, const bson_value_t *keyid)) { @@ -7321,5 +7654,13 @@ test_client_side_encryption_install(TestSuite *suite) test_framework_skip_if_max_wire_version_less_than_21 /* require server > 7.0 for QE support */, test_framework_skip_if_single, /* QE not supported on standalone */ test_framework_skip_if_no_client_side_encryption); + TestSuite_AddFull(suite, + "/client_side_encryption/explicit_encryption/text", + test_explicit_encryption_text, + NULL, + NULL, + test_framework_skip_if_max_wire_version_less_than_27 /* require server > 8.2 for QE support */, + test_framework_skip_if_single, /* QE not supported on standalone */ + test_framework_skip_if_no_client_side_encryption); } }